@atproto/oauth-provider 0.18.4 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/account/account-manager.d.ts +11 -7
- package/dist/account/account-manager.d.ts.map +1 -1
- package/dist/account/account-manager.js +82 -19
- package/dist/account/account-manager.js.map +1 -1
- package/dist/account/account-store.d.ts +47 -13
- package/dist/account/account-store.d.ts.map +1 -1
- package/dist/account/account-store.js +4 -1
- package/dist/account/account-store.js.map +1 -1
- package/dist/account/sign-up-input.d.ts +2 -2
- package/dist/errors/invalid-credentials-error.d.ts +9 -9
- package/dist/errors/invalid-credentials-error.d.ts.map +1 -1
- package/dist/errors/invalid-credentials-error.js +8 -8
- package/dist/errors/invalid-credentials-error.js.map +1 -1
- package/dist/lib/util/function.js +1 -1
- package/dist/lib/util/function.js.map +1 -1
- package/dist/oauth-hooks.d.ts +77 -4
- package/dist/oauth-hooks.d.ts.map +1 -1
- package/dist/oauth-hooks.js.map +1 -1
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +15 -9
- package/dist/oauth-provider.js.map +1 -1
- package/dist/request/request-data.d.ts +4 -4
- package/dist/request/request-data.d.ts.map +1 -1
- package/dist/request/request-data.js +1 -1
- package/dist/request/request-data.js.map +1 -1
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +7 -4
- package/dist/request/request-manager.js.map +1 -1
- package/dist/request/request-store.d.ts +1 -1
- package/dist/request/request-store.js.map +1 -1
- package/dist/result/authorization-result-authorize-page.d.ts +1 -1
- package/dist/result/authorization-result-authorize-page.js.map +1 -1
- package/dist/router/assets/send-authorization-page.js +1 -1
- package/dist/router/assets/send-authorization-page.js.map +1 -1
- package/dist/router/create-api-middleware.d.ts.map +1 -1
- package/dist/router/create-api-middleware.js +87 -51
- package/dist/router/create-api-middleware.js.map +1 -1
- package/dist/signer/access-token-payload.d.ts +15 -15
- package/dist/signer/access-token-payload.js +2 -2
- package/dist/signer/access-token-payload.js.map +1 -1
- package/dist/signer/api-token-payload.d.ts +15 -15
- package/dist/signer/api-token-payload.js +2 -2
- package/dist/signer/api-token-payload.js.map +1 -1
- package/dist/signer/signer.d.ts +6 -187
- package/dist/signer/signer.d.ts.map +1 -1
- package/dist/signer/signer.js.map +1 -1
- package/dist/token/token-claims.d.ts +2 -1
- package/dist/token/token-claims.d.ts.map +1 -1
- package/dist/token/token-claims.js.map +1 -1
- package/dist/token/token-data.d.ts +3 -3
- package/dist/token/token-data.d.ts.map +1 -1
- package/dist/token/token-data.js.map +1 -1
- package/dist/token/token-manager.d.ts +3 -3
- package/dist/token/token-manager.d.ts.map +1 -1
- package/dist/token/token-manager.js +14 -14
- package/dist/token/token-manager.js.map +1 -1
- package/dist/token/token-store.d.ts +3 -3
- package/dist/token/token-store.d.ts.map +1 -1
- package/dist/token/token-store.js.map +1 -1
- package/dist/types/handle.d.ts +5 -1
- package/dist/types/handle.d.ts.map +1 -1
- package/dist/types/handle.js +9 -8
- package/dist/types/handle.js.map +1 -1
- package/package.json +8 -8
- package/src/account/account-manager.ts +123 -20
- package/src/account/account-store.ts +59 -11
- package/src/errors/invalid-credentials-error.ts +8 -8
- package/src/lib/util/function.ts +2 -2
- package/src/oauth-hooks.ts +85 -3
- package/src/oauth-provider.ts +17 -9
- package/src/request/request-data.ts +5 -5
- package/src/request/request-manager.ts +11 -4
- package/src/request/request-store.ts +1 -1
- package/src/result/authorization-result-authorize-page.ts +1 -1
- package/src/router/assets/send-authorization-page.ts +1 -1
- package/src/router/create-api-middleware.ts +128 -67
- package/src/signer/access-token-payload.ts +2 -2
- package/src/signer/api-token-payload.ts +2 -2
- package/src/signer/signer.ts +13 -4
- package/src/token/token-claims.ts +2 -1
- package/src/token/token-data.ts +3 -3
- package/src/token/token-manager.ts +15 -15
- package/src/token/token-store.ts +3 -3
- package/src/types/handle.ts +21 -16
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/oidc/sub.d.ts +0 -4
- package/dist/oidc/sub.d.ts.map +0 -1
- package/dist/oidc/sub.js +0 -3
- package/dist/oidc/sub.js.map +0 -1
- package/src/oidc/sub.ts +0 -4
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Did } from '@atproto/did';
|
|
1
2
|
import { SignedJwt } from '@atproto/jwk';
|
|
2
3
|
import type { Account } from '@atproto/oauth-provider-api';
|
|
3
4
|
import { OAuthAccessToken, OAuthAuthorizationRequestParameters, OAuthScope, OAuthTokenResponse, OAuthTokenType } from '@atproto/oauth-types';
|
|
@@ -8,7 +9,6 @@ import { DeviceId } from '../device/device-id.js';
|
|
|
8
9
|
import { LexiconManager } from '../lexicon/lexicon-manager.js';
|
|
9
10
|
import { RequestMetadata } from '../lib/http/request.js';
|
|
10
11
|
import { OAuthHooks } from '../oauth-hooks.js';
|
|
11
|
-
import { Sub } from '../oidc/sub.js';
|
|
12
12
|
import { Code } from '../request/code.js';
|
|
13
13
|
import { AccessTokenPayload } from '../signer/access-token-payload.js';
|
|
14
14
|
import { Signer } from '../signer/signer.js';
|
|
@@ -30,7 +30,7 @@ export declare class TokenManager {
|
|
|
30
30
|
protected createAccessToken(tokenId: TokenId, client: Client, account: Account, parameters: OAuthAuthorizationRequestParameters, issuedAt: Date, expiresAt: Date, scope: OAuthScope): Promise<OAuthAccessToken>;
|
|
31
31
|
createToken(client: Client, clientAuth: ClientAuth, clientMetadata: RequestMetadata, account: Account, deviceId: null | DeviceId, parameters: OAuthAuthorizationRequestParameters, code: Code): Promise<OAuthTokenResponse>;
|
|
32
32
|
protected validateTokenParams(client: Client, clientAuth: ClientAuth, parameters: OAuthAuthorizationRequestParameters): Promise<void>;
|
|
33
|
-
protected buildTokenResponse(tokenType: OAuthTokenType, accessToken: OAuthAccessToken, refreshToken: string | undefined, expiresAt: Date,
|
|
33
|
+
protected buildTokenResponse(tokenType: OAuthTokenType, accessToken: OAuthAccessToken, refreshToken: string | undefined, expiresAt: Date, did: Did, scope: string): OAuthTokenResponse;
|
|
34
34
|
rotateToken(client: Client, clientAuth: ClientAuth, clientMetadata: RequestMetadata, tokenInfo: TokenInfo): Promise<OAuthTokenResponse>;
|
|
35
35
|
/**
|
|
36
36
|
* @note The token validity is not guaranteed. The caller must ensure that the
|
|
@@ -49,6 +49,6 @@ export declare class TokenManager {
|
|
|
49
49
|
* data that was omitted in the token itself.
|
|
50
50
|
*/
|
|
51
51
|
loadTokenClaims(tokenType: OAuthTokenType, tokenPayload: AccessTokenPayload): Promise<TokenClaims>;
|
|
52
|
-
listAccountTokens(
|
|
52
|
+
listAccountTokens(did: Did): Promise<TokenInfo[]>;
|
|
53
53
|
}
|
|
54
54
|
//# sourceMappingURL=token-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/token/token-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAe,MAAM,cAAc,CAAA;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EACL,gBAAgB,EAChB,mCAAmC,EACnC,UAAU,EACV,kBAAkB,EAClB,cAAc,EACf,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAIjD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"token-manager.d.ts","sourceRoot":"","sources":["../../src/token/token-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,10 +26,10 @@ export class TokenManager {
|
|
|
26
26
|
async createAccessToken(tokenId, client, account, parameters, issuedAt, expiresAt, scope) {
|
|
27
27
|
const claims = {
|
|
28
28
|
jti: tokenId,
|
|
29
|
-
sub: account.
|
|
29
|
+
sub: account.did,
|
|
30
30
|
iat: dateToEpoch(issuedAt),
|
|
31
31
|
exp: dateToEpoch(expiresAt),
|
|
32
|
-
aud: account.
|
|
32
|
+
aud: account.pds,
|
|
33
33
|
...(parameters.dpop_jkt && {
|
|
34
34
|
cnf: { jkt: parameters.dpop_jkt },
|
|
35
35
|
}),
|
|
@@ -68,7 +68,7 @@ export class TokenManager {
|
|
|
68
68
|
throw err;
|
|
69
69
|
});
|
|
70
70
|
const accessToken = await this.createAccessToken(tokenId, client, account, parameters, now, expiresAt, scope);
|
|
71
|
-
const response = this.buildTokenResponse(inferTokenType(parameters), accessToken, refreshToken, expiresAt, account.
|
|
71
|
+
const response = this.buildTokenResponse(inferTokenType(parameters), accessToken, refreshToken, expiresAt, account.did, scope);
|
|
72
72
|
const tokenData = {
|
|
73
73
|
createdAt: now,
|
|
74
74
|
updatedAt: now,
|
|
@@ -76,7 +76,7 @@ export class TokenManager {
|
|
|
76
76
|
clientId: client.id,
|
|
77
77
|
clientAuth,
|
|
78
78
|
deviceId,
|
|
79
|
-
|
|
79
|
+
did: account.did,
|
|
80
80
|
parameters,
|
|
81
81
|
details: null,
|
|
82
82
|
scope,
|
|
@@ -105,7 +105,7 @@ export class TokenManager {
|
|
|
105
105
|
throw new InvalidGrantError(`DPoP JKT is required for DPoP bound access tokens`);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
-
buildTokenResponse(tokenType, accessToken, refreshToken, expiresAt,
|
|
108
|
+
buildTokenResponse(tokenType, accessToken, refreshToken, expiresAt, did, scope) {
|
|
109
109
|
return {
|
|
110
110
|
access_token: accessToken,
|
|
111
111
|
token_type: tokenType,
|
|
@@ -119,7 +119,7 @@ export class TokenManager {
|
|
|
119
119
|
// ATPROTO extension: add the sub claim to the token response to allow
|
|
120
120
|
// clients to resolve the PDS url (audience) using the did resolution
|
|
121
121
|
// mechanism.
|
|
122
|
-
sub,
|
|
122
|
+
sub: did,
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
125
|
async rotateToken(client, clientAuth, clientMetadata, tokenInfo) {
|
|
@@ -147,7 +147,7 @@ export class TokenManager {
|
|
|
147
147
|
scope,
|
|
148
148
|
});
|
|
149
149
|
const accessToken = await this.createAccessToken(nextTokenId, client, account, parameters, now, expiresAt, scope);
|
|
150
|
-
const response = this.buildTokenResponse(inferTokenType(parameters), accessToken, nextRefreshToken, expiresAt, account.
|
|
150
|
+
const response = this.buildTokenResponse(inferTokenType(parameters), accessToken, nextRefreshToken, expiresAt, account.did, scope);
|
|
151
151
|
await this.hooks.onTokenRefreshed?.call(null, {
|
|
152
152
|
client,
|
|
153
153
|
clientAuth,
|
|
@@ -186,9 +186,9 @@ export class TokenManager {
|
|
|
186
186
|
if (!tokenInfo)
|
|
187
187
|
return null;
|
|
188
188
|
// Fool-proof: Invalid store implementation ?
|
|
189
|
-
if (payload.sub !== tokenInfo.account.
|
|
189
|
+
if (payload.sub !== tokenInfo.account.did) {
|
|
190
190
|
await this.deleteToken(tokenInfo.id);
|
|
191
|
-
throw new Error(`Account sub (${tokenInfo.account.
|
|
191
|
+
throw new Error(`Account sub (${tokenInfo.account.did}) does not match token sub (${payload.sub})`);
|
|
192
192
|
}
|
|
193
193
|
return tokenInfo;
|
|
194
194
|
}
|
|
@@ -253,19 +253,19 @@ export class TokenManager {
|
|
|
253
253
|
}
|
|
254
254
|
return {
|
|
255
255
|
jti: tokenId,
|
|
256
|
-
sub: account.
|
|
256
|
+
sub: account.did,
|
|
257
257
|
iat: dateToEpoch(data.updatedAt),
|
|
258
258
|
exp: dateToEpoch(data.expiresAt),
|
|
259
|
-
aud: account.
|
|
259
|
+
aud: account.pds,
|
|
260
260
|
scope: data.scope ?? data.parameters.scope,
|
|
261
261
|
// https://datatracker.ietf.org/doc/html/rfc8693#section-4.3
|
|
262
262
|
client_id: data.clientId,
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
|
-
async listAccountTokens(
|
|
266
|
-
const results = await this.store.listAccountTokens(
|
|
265
|
+
async listAccountTokens(did) {
|
|
266
|
+
const results = await this.store.listAccountTokens(did);
|
|
267
267
|
return results
|
|
268
|
-
.filter((tokenInfo) => tokenInfo.account.
|
|
268
|
+
.filter((tokenInfo) => tokenInfo.account.did === did) // Fool proof
|
|
269
269
|
.filter((tokenInfo) => !isCurrentTokenExpired(tokenInfo));
|
|
270
270
|
}
|
|
271
271
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/token/token-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { SignedJwt, isSignedJwt } from '@atproto/jwk'\nimport { LexResolverError } from '@atproto/lex-resolver'\nimport type { Account } from '@atproto/oauth-provider-api'\nimport {\n OAuthAccessToken,\n OAuthAuthorizationRequestParameters,\n OAuthScope,\n OAuthTokenResponse,\n OAuthTokenType,\n} from '@atproto/oauth-types'\nimport { AccessTokenMode } from '../access-token/access-token-mode.js'\nimport { ClientAuth } from '../client/client-auth.js'\nimport { Client } from '../client/client.js'\nimport { TOKEN_MAX_AGE } from '../constants.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { InvalidGrantError } from '../errors/invalid-grant-error.js'\nimport { InvalidRequestError } from '../errors/invalid-request-error.js'\nimport { InvalidTokenError } from '../errors/invalid-token-error.js'\nimport { LexiconManager } from '../lexicon/lexicon-manager.js'\nimport { RequestMetadata } from '../lib/http/request.js'\nimport { dateToEpoch, dateToRelativeSeconds } from '../lib/util/date.js'\nimport { OAuthHooks } from '../oauth-hooks.js'\nimport { Sub } from '../oidc/sub.js'\nimport { Code, isCode } from '../request/code.js'\nimport { AccessTokenPayload } from '../signer/access-token-payload.js'\nimport { Signer } from '../signer/signer.js'\nimport {\n RefreshToken,\n generateRefreshToken,\n isRefreshToken,\n} from './refresh-token.js'\nimport { TokenClaims } from './token-claims.js'\nimport { TokenId, generateTokenId, isTokenId } from './token-id.js'\nimport { CreateTokenData, TokenInfo, TokenStore } from './token-store.js'\n\nexport { AccessTokenMode, Signer }\nexport type { OAuthHooks, TokenStore }\n\nexport class TokenManager {\n constructor(\n protected readonly store: TokenStore,\n protected readonly lexiconManager: LexiconManager,\n protected readonly signer: Signer,\n protected readonly hooks: OAuthHooks,\n protected readonly accessTokenMode: AccessTokenMode,\n protected readonly tokenMaxAge = TOKEN_MAX_AGE,\n ) {}\n\n protected createTokenExpiry(now = new Date()) {\n return new Date(now.getTime() + this.tokenMaxAge)\n }\n\n protected async createAccessToken(\n tokenId: TokenId,\n client: Client,\n account: Account,\n parameters: OAuthAuthorizationRequestParameters,\n issuedAt: Date,\n expiresAt: Date,\n scope: OAuthScope,\n ): Promise<OAuthAccessToken> {\n const claims: TokenClaims = {\n jti: tokenId,\n sub: account.sub,\n iat: dateToEpoch(issuedAt),\n exp: dateToEpoch(expiresAt),\n aud: account.aud,\n\n ...(parameters.dpop_jkt && {\n cnf: { jkt: parameters.dpop_jkt },\n }),\n\n // Because tokens can end-up being quite big, we only include the scope in\n // stateless mode.\n ...(this.accessTokenMode === AccessTokenMode.stateless && {\n scope,\n }),\n\n // https://datatracker.ietf.org/doc/html/rfc8693#section-4.3\n client_id: client.id,\n }\n\n const claimsOverride = await this.hooks.onCreateToken?.call(null, {\n client,\n account,\n parameters,\n claims,\n })\n\n return this.signer.createAccessToken(claimsOverride ?? claims)\n }\n\n async createToken(\n client: Client,\n clientAuth: ClientAuth,\n clientMetadata: RequestMetadata,\n account: Account,\n deviceId: null | DeviceId,\n parameters: OAuthAuthorizationRequestParameters,\n code: Code,\n ): Promise<OAuthTokenResponse> {\n await this.validateTokenParams(client, clientAuth, parameters)\n\n const tokenId = await generateTokenId()\n const refreshToken = client.metadata.grant_types.includes('refresh_token')\n ? await generateRefreshToken()\n : undefined\n\n const now = new Date()\n const expiresAt = this.createTokenExpiry(now)\n\n const scope = await this.lexiconManager\n .buildTokenScope(parameters.scope!)\n .catch((err) => {\n // Parse expected errors\n if (err instanceof LexResolverError) {\n throw new InvalidRequestError(err.message, err)\n }\n\n // Unexpected error\n throw err\n })\n\n const accessToken = await this.createAccessToken(\n tokenId,\n client,\n account,\n parameters,\n now,\n expiresAt,\n scope,\n )\n\n const response = this.buildTokenResponse(\n inferTokenType(parameters),\n accessToken,\n refreshToken,\n expiresAt,\n account.sub,\n scope,\n )\n\n const tokenData: CreateTokenData = {\n createdAt: now,\n updatedAt: now,\n expiresAt,\n clientId: client.id,\n clientAuth,\n deviceId,\n sub: account.sub,\n parameters,\n details: null,\n scope,\n code,\n }\n\n await this.store.createToken(tokenId, tokenData, refreshToken)\n\n try {\n await this.hooks.onTokenCreated?.call(null, {\n client,\n clientAuth,\n clientMetadata,\n account,\n parameters,\n })\n\n return response\n } catch (err) {\n // If the hook fails, we delete the token to avoid leaving a dangling\n // token in the store.\n await this.deleteToken(tokenId)\n throw err\n }\n }\n\n protected async validateTokenParams(\n client: Client,\n clientAuth: ClientAuth,\n parameters: OAuthAuthorizationRequestParameters,\n ): Promise<void> {\n if (client.metadata.dpop_bound_access_tokens && !parameters.dpop_jkt) {\n throw new InvalidGrantError(\n `DPoP JKT is required for DPoP bound access tokens`,\n )\n }\n }\n\n protected buildTokenResponse(\n tokenType: OAuthTokenType,\n accessToken: OAuthAccessToken,\n refreshToken: string | undefined,\n expiresAt: Date,\n sub: Sub,\n scope: string,\n ): OAuthTokenResponse {\n return {\n access_token: accessToken,\n token_type: tokenType,\n refresh_token: refreshToken,\n scope,\n\n // @NOTE using a getter so that the value gets computed when the JSON\n // response is generated, allowing to value to be as accurate as possible.\n get expires_in() {\n return dateToRelativeSeconds(expiresAt)\n },\n\n // ATPROTO extension: add the sub claim to the token response to allow\n // clients to resolve the PDS url (audience) using the did resolution\n // mechanism.\n sub,\n }\n }\n\n async rotateToken(\n client: Client,\n clientAuth: ClientAuth,\n clientMetadata: RequestMetadata,\n tokenInfo: TokenInfo,\n ): Promise<OAuthTokenResponse> {\n const { account, data } = tokenInfo\n const { parameters } = data\n\n await this.validateTokenParams(client, clientAuth, parameters)\n\n const nextTokenId = await generateTokenId()\n const nextRefreshToken = await generateRefreshToken()\n\n const now = new Date()\n const expiresAt = this.createTokenExpiry(now)\n\n // @NOTE since the permission sets are stored in a persistent store,\n // it's fine to propagate a 500 (server_error) here as the values should\n // be retrievable from the store.\n const scope = await this.lexiconManager.buildTokenScope(parameters.scope!)\n\n await this.store.rotateToken(tokenInfo.id, nextTokenId, nextRefreshToken, {\n updatedAt: now,\n expiresAt,\n // @NOTE Normally, the clientAuth not change over time. There are two\n // exceptions:\n // - Upgrade from a legacy representation of client authentication to\n // a modern one.\n // - Allow clients to become \"confidential\" if they were previously\n // \"public\"\n clientAuth,\n scope,\n })\n\n const accessToken = await this.createAccessToken(\n nextTokenId,\n client,\n account,\n parameters,\n now,\n expiresAt,\n scope,\n )\n\n const response = this.buildTokenResponse(\n inferTokenType(parameters),\n accessToken,\n nextRefreshToken,\n expiresAt,\n account.sub,\n scope,\n )\n\n await this.hooks.onTokenRefreshed?.call(null, {\n client,\n clientAuth,\n clientMetadata,\n account,\n parameters,\n })\n\n return response\n }\n\n /**\n * @note The token validity is not guaranteed. The caller must ensure that the\n * token is valid before using the returned token info.\n */\n public async findToken(token: string): Promise<null | TokenInfo> {\n if (isTokenId(token)) {\n return this.getTokenInfo(token)\n } else if (isCode(token)) {\n return this.findByCode(token)\n } else if (isRefreshToken(token)) {\n return this.findByRefreshToken(token)\n } else if (isSignedJwt(token)) {\n return this.findByAccessToken(token)\n } else {\n throw new InvalidRequestError(`Invalid token`)\n }\n }\n\n public async findByAccessToken(token: SignedJwt): Promise<null | TokenInfo> {\n const { payload } = await this.signer.verifyAccessToken(token, {\n clockTolerance: Infinity,\n })\n\n const tokenInfo = await this.getTokenInfo(payload.jti)\n if (!tokenInfo) return null\n\n // Fool-proof: Invalid store implementation ?\n if (payload.sub !== tokenInfo.account.sub) {\n await this.deleteToken(tokenInfo.id)\n throw new Error(\n `Account sub (${tokenInfo.account.sub}) does not match token sub (${payload.sub})`,\n )\n }\n\n return tokenInfo\n }\n\n protected async findByRefreshToken(\n token: RefreshToken,\n ): Promise<null | TokenInfo> {\n return this.store.findTokenByRefreshToken(token)\n }\n\n public async consumeRefreshToken(token: RefreshToken): Promise<TokenInfo> {\n // @NOTE concurrent refreshes of the same refresh token could theoretically\n // lead to two new tokens (access & refresh) being created. This is deemed\n // acceptable for now (as the mechanism can only be used once since only one\n // of the two refresh token created will be valid, and any future refresh\n // attempts from outdated tokens will cause the entire session to be\n // invalidated). Ideally, the store should be able to handle this case by\n // atomically consuming the refresh token and returning the token info.\n\n // @TODO Add another store method that atomically consumes the refresh token\n // with a lock.\n const tokenInfo = await this.findByRefreshToken(token).catch((err) => {\n throw InvalidGrantError.from(err, `Invalid refresh token`)\n })\n\n if (!tokenInfo) {\n throw new InvalidGrantError(`Invalid refresh token`)\n }\n\n if (tokenInfo.currentRefreshToken !== token) {\n await this.deleteToken(tokenInfo.id)\n throw new InvalidGrantError(`Refresh token replayed`)\n }\n\n return tokenInfo\n }\n\n public async findByCode(code: Code): Promise<null | TokenInfo> {\n return this.store.findTokenByCode(code)\n }\n\n public async deleteToken(tokenId: TokenId): Promise<void> {\n return this.store.deleteToken(tokenId)\n }\n\n async getTokenInfo(tokenId: TokenId): Promise<null | TokenInfo> {\n return this.store.readToken(tokenId)\n }\n\n /**\n * This method is called to when decoding a token that was encoded in\n * {@link AccessTokenMode.light} mode, using data from the store to fill the\n * data that was omitted in the token itself.\n */\n async loadTokenClaims(\n tokenType: OAuthTokenType,\n tokenPayload: AccessTokenPayload,\n ): Promise<TokenClaims> {\n const tokenId = tokenPayload.jti\n const tokenInfo = await this.getTokenInfo(tokenId).catch((err) => {\n throw InvalidTokenError.from(err, tokenType)\n })\n\n if (!tokenInfo) {\n throw new InvalidTokenError(tokenType, `Invalid token`)\n }\n\n const { account, data } = tokenInfo\n\n // Fool proof, make sure that the database & token payload are consistent.\n // These should both be either undefined or a string so it's safe to compare\n // the values directly.\n if (tokenPayload.cnf?.jkt !== data.parameters.dpop_jkt) {\n await this.deleteToken(tokenId)\n throw new InvalidTokenError(tokenType, `Invalid token`)\n }\n\n if (isCurrentTokenExpired(tokenInfo)) {\n await this.deleteToken(tokenId)\n throw new InvalidTokenError(tokenType, `Token expired`)\n }\n\n return {\n jti: tokenId,\n sub: account.sub,\n iat: dateToEpoch(data.updatedAt),\n exp: dateToEpoch(data.expiresAt),\n aud: account.aud,\n scope: data.scope ?? data.parameters.scope,\n // https://datatracker.ietf.org/doc/html/rfc8693#section-4.3\n client_id: data.clientId,\n }\n }\n\n async listAccountTokens(sub: Sub): Promise<TokenInfo[]> {\n const results = await this.store.listAccountTokens(sub)\n return results\n .filter((tokenInfo) => tokenInfo.account.sub === sub) // Fool proof\n .filter((tokenInfo) => !isCurrentTokenExpired(tokenInfo))\n }\n}\n\nfunction isCurrentTokenExpired(tokenInfo: TokenInfo): boolean {\n return tokenInfo.data.expiresAt.getTime() < Date.now()\n}\n\nfunction inferTokenType(\n parameters: OAuthAuthorizationRequestParameters,\n): OAuthTokenType {\n if (parameters.dpop_jkt) {\n return 'DPoP'\n }\n return 'Bearer'\n}\n"]}
|
|
1
|
+
{"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/token/token-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Did } from '@atproto/did'\nimport { SignedJwt, isSignedJwt } from '@atproto/jwk'\nimport { LexResolverError } from '@atproto/lex-resolver'\nimport type { Account } from '@atproto/oauth-provider-api'\nimport {\n OAuthAccessToken,\n OAuthAuthorizationRequestParameters,\n OAuthScope,\n OAuthTokenResponse,\n OAuthTokenType,\n} from '@atproto/oauth-types'\nimport { AccessTokenMode } from '../access-token/access-token-mode.js'\nimport { ClientAuth } from '../client/client-auth.js'\nimport { Client } from '../client/client.js'\nimport { TOKEN_MAX_AGE } from '../constants.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { InvalidGrantError } from '../errors/invalid-grant-error.js'\nimport { InvalidRequestError } from '../errors/invalid-request-error.js'\nimport { InvalidTokenError } from '../errors/invalid-token-error.js'\nimport { LexiconManager } from '../lexicon/lexicon-manager.js'\nimport { RequestMetadata } from '../lib/http/request.js'\nimport { dateToEpoch, dateToRelativeSeconds } from '../lib/util/date.js'\nimport { OAuthHooks } from '../oauth-hooks.js'\nimport { Code, isCode } from '../request/code.js'\nimport { AccessTokenPayload } from '../signer/access-token-payload.js'\nimport { Signer } from '../signer/signer.js'\nimport {\n RefreshToken,\n generateRefreshToken,\n isRefreshToken,\n} from './refresh-token.js'\nimport { TokenClaims } from './token-claims.js'\nimport { TokenId, generateTokenId, isTokenId } from './token-id.js'\nimport { CreateTokenData, TokenInfo, TokenStore } from './token-store.js'\n\nexport { AccessTokenMode, Signer }\nexport type { OAuthHooks, TokenStore }\n\nexport class TokenManager {\n constructor(\n protected readonly store: TokenStore,\n protected readonly lexiconManager: LexiconManager,\n protected readonly signer: Signer,\n protected readonly hooks: OAuthHooks,\n protected readonly accessTokenMode: AccessTokenMode,\n protected readonly tokenMaxAge = TOKEN_MAX_AGE,\n ) {}\n\n protected createTokenExpiry(now = new Date()) {\n return new Date(now.getTime() + this.tokenMaxAge)\n }\n\n protected async createAccessToken(\n tokenId: TokenId,\n client: Client,\n account: Account,\n parameters: OAuthAuthorizationRequestParameters,\n issuedAt: Date,\n expiresAt: Date,\n scope: OAuthScope,\n ): Promise<OAuthAccessToken> {\n const claims: TokenClaims = {\n jti: tokenId,\n sub: account.did,\n iat: dateToEpoch(issuedAt),\n exp: dateToEpoch(expiresAt),\n aud: account.pds,\n\n ...(parameters.dpop_jkt && {\n cnf: { jkt: parameters.dpop_jkt },\n }),\n\n // Because tokens can end-up being quite big, we only include the scope in\n // stateless mode.\n ...(this.accessTokenMode === AccessTokenMode.stateless && {\n scope,\n }),\n\n // https://datatracker.ietf.org/doc/html/rfc8693#section-4.3\n client_id: client.id,\n }\n\n const claimsOverride = await this.hooks.onCreateToken?.call(null, {\n client,\n account,\n parameters,\n claims,\n })\n\n return this.signer.createAccessToken(claimsOverride ?? claims)\n }\n\n async createToken(\n client: Client,\n clientAuth: ClientAuth,\n clientMetadata: RequestMetadata,\n account: Account,\n deviceId: null | DeviceId,\n parameters: OAuthAuthorizationRequestParameters,\n code: Code,\n ): Promise<OAuthTokenResponse> {\n await this.validateTokenParams(client, clientAuth, parameters)\n\n const tokenId = await generateTokenId()\n const refreshToken = client.metadata.grant_types.includes('refresh_token')\n ? await generateRefreshToken()\n : undefined\n\n const now = new Date()\n const expiresAt = this.createTokenExpiry(now)\n\n const scope = await this.lexiconManager\n .buildTokenScope(parameters.scope!)\n .catch((err) => {\n // Parse expected errors\n if (err instanceof LexResolverError) {\n throw new InvalidRequestError(err.message, err)\n }\n\n // Unexpected error\n throw err\n })\n\n const accessToken = await this.createAccessToken(\n tokenId,\n client,\n account,\n parameters,\n now,\n expiresAt,\n scope,\n )\n\n const response = this.buildTokenResponse(\n inferTokenType(parameters),\n accessToken,\n refreshToken,\n expiresAt,\n account.did,\n scope,\n )\n\n const tokenData: CreateTokenData = {\n createdAt: now,\n updatedAt: now,\n expiresAt,\n clientId: client.id,\n clientAuth,\n deviceId,\n did: account.did,\n parameters,\n details: null,\n scope,\n code,\n }\n\n await this.store.createToken(tokenId, tokenData, refreshToken)\n\n try {\n await this.hooks.onTokenCreated?.call(null, {\n client,\n clientAuth,\n clientMetadata,\n account,\n parameters,\n })\n\n return response\n } catch (err) {\n // If the hook fails, we delete the token to avoid leaving a dangling\n // token in the store.\n await this.deleteToken(tokenId)\n throw err\n }\n }\n\n protected async validateTokenParams(\n client: Client,\n clientAuth: ClientAuth,\n parameters: OAuthAuthorizationRequestParameters,\n ): Promise<void> {\n if (client.metadata.dpop_bound_access_tokens && !parameters.dpop_jkt) {\n throw new InvalidGrantError(\n `DPoP JKT is required for DPoP bound access tokens`,\n )\n }\n }\n\n protected buildTokenResponse(\n tokenType: OAuthTokenType,\n accessToken: OAuthAccessToken,\n refreshToken: string | undefined,\n expiresAt: Date,\n did: Did,\n scope: string,\n ): OAuthTokenResponse {\n return {\n access_token: accessToken,\n token_type: tokenType,\n refresh_token: refreshToken,\n scope,\n\n // @NOTE using a getter so that the value gets computed when the JSON\n // response is generated, allowing to value to be as accurate as possible.\n get expires_in() {\n return dateToRelativeSeconds(expiresAt)\n },\n\n // ATPROTO extension: add the sub claim to the token response to allow\n // clients to resolve the PDS url (audience) using the did resolution\n // mechanism.\n sub: did,\n }\n }\n\n async rotateToken(\n client: Client,\n clientAuth: ClientAuth,\n clientMetadata: RequestMetadata,\n tokenInfo: TokenInfo,\n ): Promise<OAuthTokenResponse> {\n const { account, data } = tokenInfo\n const { parameters } = data\n\n await this.validateTokenParams(client, clientAuth, parameters)\n\n const nextTokenId = await generateTokenId()\n const nextRefreshToken = await generateRefreshToken()\n\n const now = new Date()\n const expiresAt = this.createTokenExpiry(now)\n\n // @NOTE since the permission sets are stored in a persistent store,\n // it's fine to propagate a 500 (server_error) here as the values should\n // be retrievable from the store.\n const scope = await this.lexiconManager.buildTokenScope(parameters.scope!)\n\n await this.store.rotateToken(tokenInfo.id, nextTokenId, nextRefreshToken, {\n updatedAt: now,\n expiresAt,\n // @NOTE Normally, the clientAuth not change over time. There are two\n // exceptions:\n // - Upgrade from a legacy representation of client authentication to\n // a modern one.\n // - Allow clients to become \"confidential\" if they were previously\n // \"public\"\n clientAuth,\n scope,\n })\n\n const accessToken = await this.createAccessToken(\n nextTokenId,\n client,\n account,\n parameters,\n now,\n expiresAt,\n scope,\n )\n\n const response = this.buildTokenResponse(\n inferTokenType(parameters),\n accessToken,\n nextRefreshToken,\n expiresAt,\n account.did,\n scope,\n )\n\n await this.hooks.onTokenRefreshed?.call(null, {\n client,\n clientAuth,\n clientMetadata,\n account,\n parameters,\n })\n\n return response\n }\n\n /**\n * @note The token validity is not guaranteed. The caller must ensure that the\n * token is valid before using the returned token info.\n */\n public async findToken(token: string): Promise<null | TokenInfo> {\n if (isTokenId(token)) {\n return this.getTokenInfo(token)\n } else if (isCode(token)) {\n return this.findByCode(token)\n } else if (isRefreshToken(token)) {\n return this.findByRefreshToken(token)\n } else if (isSignedJwt(token)) {\n return this.findByAccessToken(token)\n } else {\n throw new InvalidRequestError(`Invalid token`)\n }\n }\n\n public async findByAccessToken(token: SignedJwt): Promise<null | TokenInfo> {\n const { payload } = await this.signer.verifyAccessToken(token, {\n clockTolerance: Infinity,\n })\n\n const tokenInfo = await this.getTokenInfo(payload.jti)\n if (!tokenInfo) return null\n\n // Fool-proof: Invalid store implementation ?\n if (payload.sub !== tokenInfo.account.did) {\n await this.deleteToken(tokenInfo.id)\n throw new Error(\n `Account sub (${tokenInfo.account.did}) does not match token sub (${payload.sub})`,\n )\n }\n\n return tokenInfo\n }\n\n protected async findByRefreshToken(\n token: RefreshToken,\n ): Promise<null | TokenInfo> {\n return this.store.findTokenByRefreshToken(token)\n }\n\n public async consumeRefreshToken(token: RefreshToken): Promise<TokenInfo> {\n // @NOTE concurrent refreshes of the same refresh token could theoretically\n // lead to two new tokens (access & refresh) being created. This is deemed\n // acceptable for now (as the mechanism can only be used once since only one\n // of the two refresh token created will be valid, and any future refresh\n // attempts from outdated tokens will cause the entire session to be\n // invalidated). Ideally, the store should be able to handle this case by\n // atomically consuming the refresh token and returning the token info.\n\n // @TODO Add another store method that atomically consumes the refresh token\n // with a lock.\n const tokenInfo = await this.findByRefreshToken(token).catch((err) => {\n throw InvalidGrantError.from(err, `Invalid refresh token`)\n })\n\n if (!tokenInfo) {\n throw new InvalidGrantError(`Invalid refresh token`)\n }\n\n if (tokenInfo.currentRefreshToken !== token) {\n await this.deleteToken(tokenInfo.id)\n throw new InvalidGrantError(`Refresh token replayed`)\n }\n\n return tokenInfo\n }\n\n public async findByCode(code: Code): Promise<null | TokenInfo> {\n return this.store.findTokenByCode(code)\n }\n\n public async deleteToken(tokenId: TokenId): Promise<void> {\n return this.store.deleteToken(tokenId)\n }\n\n async getTokenInfo(tokenId: TokenId): Promise<null | TokenInfo> {\n return this.store.readToken(tokenId)\n }\n\n /**\n * This method is called to when decoding a token that was encoded in\n * {@link AccessTokenMode.light} mode, using data from the store to fill the\n * data that was omitted in the token itself.\n */\n async loadTokenClaims(\n tokenType: OAuthTokenType,\n tokenPayload: AccessTokenPayload,\n ): Promise<TokenClaims> {\n const tokenId = tokenPayload.jti\n const tokenInfo = await this.getTokenInfo(tokenId).catch((err) => {\n throw InvalidTokenError.from(err, tokenType)\n })\n\n if (!tokenInfo) {\n throw new InvalidTokenError(tokenType, `Invalid token`)\n }\n\n const { account, data } = tokenInfo\n\n // Fool proof, make sure that the database & token payload are consistent.\n // These should both be either undefined or a string so it's safe to compare\n // the values directly.\n if (tokenPayload.cnf?.jkt !== data.parameters.dpop_jkt) {\n await this.deleteToken(tokenId)\n throw new InvalidTokenError(tokenType, `Invalid token`)\n }\n\n if (isCurrentTokenExpired(tokenInfo)) {\n await this.deleteToken(tokenId)\n throw new InvalidTokenError(tokenType, `Token expired`)\n }\n\n return {\n jti: tokenId,\n sub: account.did,\n iat: dateToEpoch(data.updatedAt),\n exp: dateToEpoch(data.expiresAt),\n aud: account.pds,\n scope: data.scope ?? data.parameters.scope,\n // https://datatracker.ietf.org/doc/html/rfc8693#section-4.3\n client_id: data.clientId,\n }\n }\n\n async listAccountTokens(did: Did): Promise<TokenInfo[]> {\n const results = await this.store.listAccountTokens(did)\n return results\n .filter((tokenInfo) => tokenInfo.account.did === did) // Fool proof\n .filter((tokenInfo) => !isCurrentTokenExpired(tokenInfo))\n }\n}\n\nfunction isCurrentTokenExpired(tokenInfo: TokenInfo): boolean {\n return tokenInfo.data.expiresAt.getTime() < Date.now()\n}\n\nfunction inferTokenType(\n parameters: OAuthAuthorizationRequestParameters,\n): OAuthTokenType {\n if (parameters.dpop_jkt) {\n return 'DPoP'\n }\n return 'Bearer'\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { Did } from '@atproto/did';
|
|
1
2
|
import type { Account } from '@atproto/oauth-provider-api';
|
|
2
3
|
import { Awaitable } from '../lib/util/type.js';
|
|
3
|
-
import { Sub } from '../oidc/sub.js';
|
|
4
4
|
import { Code } from '../request/code.js';
|
|
5
5
|
import { RefreshToken } from './refresh-token.js';
|
|
6
6
|
import { TokenData } from './token-data.js';
|
|
@@ -8,7 +8,7 @@ import { TokenId } from './token-id.js';
|
|
|
8
8
|
export * from './refresh-token.js';
|
|
9
9
|
export * from './token-data.js';
|
|
10
10
|
export * from './token-id.js';
|
|
11
|
-
export type { Account, Awaitable,
|
|
11
|
+
export type { Account, Awaitable, Did };
|
|
12
12
|
export type TokenInfo = {
|
|
13
13
|
id: TokenId;
|
|
14
14
|
data: TokenData;
|
|
@@ -42,7 +42,7 @@ export interface TokenStore {
|
|
|
42
42
|
*/
|
|
43
43
|
findTokenByRefreshToken(refreshToken: RefreshToken): Awaitable<null | TokenInfo>;
|
|
44
44
|
findTokenByCode(code: Code): Awaitable<null | TokenInfo>;
|
|
45
|
-
listAccountTokens(
|
|
45
|
+
listAccountTokens(did: Did): Awaitable<TokenInfo[]>;
|
|
46
46
|
}
|
|
47
47
|
export declare const isTokenStore: <V extends Partial<TokenStore>>(value: V) => value is V & import("../lib/util/type.js").RequiredDefined<TokenStore>;
|
|
48
48
|
export declare function asTokenStore<V extends Partial<TokenStore>>(implementation?: V): V & TokenStore;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/token/token-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/token/token-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,SAAS,EAAyB,MAAM,qBAAqB,CAAA;AACtE,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAGvC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,CAAA;AAEvC,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,OAAO,CAAA;IACX,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,mBAAmB,EAAE,IAAI,GAAG,YAAY,CAAA;CACzC,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;IACnC,SAAS,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;IACjC,SAAS,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;IACjC,KAAK,EAAE,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG;IACxC,KAAK,EAAE,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;CACvC,CAAA;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,CACT,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,eAAe,EACrB,YAAY,CAAC,EAAE,YAAY,GAC1B,SAAS,CAAC,IAAI,CAAC,CAAA;IAElB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC,IAAI,GAAG,SAAS,CAAC,CAAA;IAExD,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAE9C,WAAW,CACT,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,OAAO,EACnB,eAAe,EAAE,YAAY,EAC7B,OAAO,EAAE,YAAY,GACpB,SAAS,CAAC,IAAI,CAAC,CAAA;IAElB;;;;OAIG;IACH,uBAAuB,CACrB,YAAY,EAAE,YAAY,GACzB,SAAS,CAAC,IAAI,GAAG,SAAS,CAAC,CAAA;IAE9B,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CAAC,IAAI,GAAG,SAAS,CAAC,CAAA;IAExD,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,SAAS,CAAC,SAAS,EAAE,CAAC,CAAA;CACpD;AAED,eAAO,MAAM,YAAY,qHAQvB,CAAA;AAEF,wBAAgB,YAAY,CAAC,CAAC,SAAS,OAAO,CAAC,UAAU,CAAC,EACxD,cAAc,CAAC,EAAE,CAAC,GACjB,CAAC,GAAG,UAAU,CAKhB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/token/token-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/token/token-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAMtE,gEAAgE;AAChE,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AA2D7B,MAAM,CAAC,MAAM,YAAY,GAAG,qBAAqB,CAAa;IAC5D,aAAa;IACb,aAAa;IACb,iBAAiB;IACjB,yBAAyB;IACzB,mBAAmB;IACnB,WAAW;IACX,aAAa;CACd,CAAC,CAAA;AAEF,MAAM,UAAU,YAAY,CAC1B,cAAkB;IAElB,IAAI,CAAC,cAAc,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;IACtD,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC","sourcesContent":["import type { Did } from '@atproto/did'\nimport type { Account } from '@atproto/oauth-provider-api'\nimport { Awaitable, buildInterfaceChecker } from '../lib/util/type.js'\nimport { Code } from '../request/code.js'\nimport { RefreshToken } from './refresh-token.js'\nimport { TokenData } from './token-data.js'\nimport { TokenId } from './token-id.js'\n\n// Export all types needed to implement the TokenStore interface\nexport * from './refresh-token.js'\nexport * from './token-data.js'\nexport * from './token-id.js'\nexport type { Account, Awaitable, Did }\n\nexport type TokenInfo = {\n id: TokenId\n data: TokenData\n account: Account\n currentRefreshToken: null | RefreshToken\n}\n\nexport type NewTokenData = {\n clientAuth: TokenData['clientAuth']\n expiresAt: TokenData['expiresAt']\n updatedAt: TokenData['updatedAt']\n scope: NonNullable<TokenData['scope']>\n}\n\nexport type CreateTokenData = TokenData & {\n scope: NonNullable<TokenData['scope']>\n}\n\n/**\n * @param data historically, {@link TokenData.scope} was not present in\n * {@link TokenData}, causing it to be \"nullable\" when returned from\n * {@link TokenStore.readToken}. We use {@link CreateTokenData} here to allow\n * the store implementation to expect its presence.\n */\nexport interface TokenStore {\n createToken(\n tokenId: TokenId,\n data: CreateTokenData,\n refreshToken?: RefreshToken,\n ): Awaitable<void>\n\n readToken(tokenId: TokenId): Awaitable<null | TokenInfo>\n\n deleteToken(tokenId: TokenId): Awaitable<void>\n\n rotateToken(\n tokenId: TokenId,\n newTokenId: TokenId,\n newRefreshToken: RefreshToken,\n newData: NewTokenData,\n ): Awaitable<void>\n\n /**\n * Find a token by its refresh token. Note that previous refresh tokens\n * should also return the token. The data model is responsible for storing\n * old refresh tokens when a new one is issued.\n */\n findTokenByRefreshToken(\n refreshToken: RefreshToken,\n ): Awaitable<null | TokenInfo>\n\n findTokenByCode(code: Code): Awaitable<null | TokenInfo>\n\n listAccountTokens(did: Did): Awaitable<TokenInfo[]>\n}\n\nexport const isTokenStore = buildInterfaceChecker<TokenStore>([\n 'createToken',\n 'deleteToken',\n 'findTokenByCode',\n 'findTokenByRefreshToken',\n 'listAccountTokens',\n 'readToken',\n 'rotateToken',\n])\n\nexport function asTokenStore<V extends Partial<TokenStore>>(\n implementation?: V,\n): V & TokenStore {\n if (!implementation || !isTokenStore(implementation)) {\n throw new Error('Invalid TokenStore implementation')\n }\n return implementation\n}\n"]}
|
package/dist/types/handle.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* @note Only validates again AT Protocol's syntax. Additional rules (specific
|
|
4
|
+
* domains, slurs, etc.) may be imposed through the store implementation.
|
|
5
|
+
*/
|
|
6
|
+
export declare const handleSchema: z.ZodEffects<z.ZodString, `${string}.${string}`, string>;
|
|
3
7
|
//# sourceMappingURL=handle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../../src/types/handle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../../src/types/handle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAOvB;;;GAGG;AACH,eAAO,MAAM,YAAY,0DAWvB,CAAA"}
|
package/dist/types/handle.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { ensureValidHandle, normalizeHandle } from '@atproto/syntax';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import { ensureValidHandle, normalizeHandle, } from '@atproto/syntax';
|
|
3
|
+
/**
|
|
4
|
+
* @note Only validates again AT Protocol's syntax. Additional rules (specific
|
|
5
|
+
* domains, slurs, etc.) may be imposed through the store implementation.
|
|
6
|
+
*/
|
|
7
|
+
export const handleSchema = z.string().transform((value, ctx) => {
|
|
8
8
|
try {
|
|
9
9
|
ensureValidHandle(value);
|
|
10
|
+
return normalizeHandle(value);
|
|
10
11
|
}
|
|
11
12
|
catch (err) {
|
|
12
13
|
ctx.addIssue({
|
|
13
14
|
code: z.ZodIssueCode.custom,
|
|
14
15
|
message: err instanceof Error ? err.message : 'Invalid handle',
|
|
15
16
|
});
|
|
17
|
+
return z.NEVER;
|
|
16
18
|
}
|
|
17
|
-
})
|
|
18
|
-
.transform(normalizeHandle);
|
|
19
|
+
});
|
|
19
20
|
//# sourceMappingURL=handle.js.map
|
package/dist/types/handle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle.js","sourceRoot":"","sources":["../../src/types/handle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,
|
|
1
|
+
{"version":3,"file":"handle.js","sourceRoot":"","sources":["../../src/types/handle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAEL,iBAAiB,EACjB,eAAe,GAChB,MAAM,iBAAiB,CAAA;AAExB;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,GAAG,EAAgB,EAAE;IAC5E,IAAI,CAAC;QACH,iBAAiB,CAAC,KAAK,CAAC,CAAA;QACxB,OAAO,eAAe,CAAC,KAAK,CAAC,CAAA;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM;YAC3B,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB;SAC/D,CAAC,CAAA;QACF,OAAO,CAAC,CAAC,KAAK,CAAA;IAChB,CAAC;AACH,CAAC,CAAC,CAAA","sourcesContent":["import { z } from 'zod'\nimport {\n HandleString,\n ensureValidHandle,\n normalizeHandle,\n} from '@atproto/syntax'\n\n/**\n * @note Only validates again AT Protocol's syntax. Additional rules (specific\n * domains, slurs, etc.) may be imposed through the store implementation.\n */\nexport const handleSchema = z.string().transform((value, ctx): HandleString => {\n try {\n ensureValidHandle(value)\n return normalizeHandle(value)\n } catch (err) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: err instanceof Error ? err.message : 'Invalid handle',\n })\n return z.NEVER\n }\n})\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/oauth-provider",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Generic OAuth2 and OpenID Connect provider for Node.js. Currently only supports features needed for Atproto.",
|
|
6
6
|
"keywords": [
|
|
@@ -40,20 +40,20 @@
|
|
|
40
40
|
"ioredis": "^5.3.2",
|
|
41
41
|
"jose": "^5.2.0",
|
|
42
42
|
"zod": "^3.23.8",
|
|
43
|
-
"@atproto-labs/fetch-node": "^0.3.1",
|
|
44
43
|
"@atproto-labs/fetch": "^0.3.1",
|
|
44
|
+
"@atproto-labs/pipe": "^0.2.1",
|
|
45
|
+
"@atproto-labs/fetch-node": "^0.3.2",
|
|
45
46
|
"@atproto-labs/simple-store": "^0.4.1",
|
|
46
47
|
"@atproto-labs/simple-store-memory": "^0.2.1",
|
|
47
|
-
"@atproto
|
|
48
|
+
"@atproto/did": "^0.5.1",
|
|
49
|
+
"@atproto/jwk": "^0.7.1",
|
|
48
50
|
"@atproto/common": "^0.6.3",
|
|
49
51
|
"@atproto/jwk-jose": "^0.2.1",
|
|
50
|
-
"@atproto/did": "^0.5.1",
|
|
51
|
-
"@atproto/oauth-types": "^0.7.2",
|
|
52
52
|
"@atproto/lex-document": "^0.1.1",
|
|
53
|
-
"@atproto/jwk": "^0.7.1",
|
|
54
53
|
"@atproto/lex-resolver": "^0.1.1",
|
|
55
|
-
"@atproto/oauth-
|
|
56
|
-
"@atproto/oauth-provider-api": "0.
|
|
54
|
+
"@atproto/oauth-types": "^0.7.2",
|
|
55
|
+
"@atproto/oauth-provider-api": "0.7.0",
|
|
56
|
+
"@atproto/oauth-provider-ui": "0.8.1",
|
|
57
57
|
"@atproto/oauth-scopes": "^0.5.1",
|
|
58
58
|
"@atproto/syntax": "^0.6.2"
|
|
59
59
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Did } from '@atproto/did'
|
|
1
2
|
import {
|
|
2
3
|
OAuthIssuerIdentifier,
|
|
3
4
|
isOAuthClientIdLoopback,
|
|
@@ -12,11 +13,12 @@ import { callAsync } from '../lib/util/function.js'
|
|
|
12
13
|
import { constantTime } from '../lib/util/time.js'
|
|
13
14
|
import { OAuthHooks, RequestMetadata } from '../oauth-hooks.js'
|
|
14
15
|
import { Customization } from '../oauth-provider.js'
|
|
15
|
-
import { Sub } from '../oidc/sub.js'
|
|
16
16
|
import {
|
|
17
17
|
Account,
|
|
18
18
|
AccountStore,
|
|
19
19
|
AuthorizedClientData,
|
|
20
|
+
DeleteAccountConfirmInput,
|
|
21
|
+
DeleteAccountRequestInput,
|
|
20
22
|
DeviceAccount,
|
|
21
23
|
ResetPasswordConfirmInput,
|
|
22
24
|
ResetPasswordRequestInput,
|
|
@@ -152,7 +154,7 @@ export class AccountManager {
|
|
|
152
154
|
|
|
153
155
|
return account
|
|
154
156
|
} catch (err) {
|
|
155
|
-
await this.removeDeviceAccount(deviceId, account.
|
|
157
|
+
await this.removeDeviceAccount(deviceId, account.did)
|
|
156
158
|
|
|
157
159
|
throw InvalidRequestError.from(
|
|
158
160
|
err,
|
|
@@ -190,7 +192,7 @@ export class AccountManager {
|
|
|
190
192
|
// This information is only exposed to the hook and is never
|
|
191
193
|
// surfaced to the client.
|
|
192
194
|
const isCredentialsError = err instanceof InvalidCredentialsError
|
|
193
|
-
const
|
|
195
|
+
const did = isCredentialsError ? err.did ?? null : null
|
|
194
196
|
|
|
195
197
|
// Swallow any error from the hook itself so that it does not mask
|
|
196
198
|
// the underlying authentication failure being reported.
|
|
@@ -198,7 +200,7 @@ export class AccountManager {
|
|
|
198
200
|
await this.hooks.onSignInFailed?.call(null, {
|
|
199
201
|
data,
|
|
200
202
|
error: err,
|
|
201
|
-
|
|
203
|
+
did,
|
|
202
204
|
deviceId,
|
|
203
205
|
deviceMetadata,
|
|
204
206
|
clientId,
|
|
@@ -235,16 +237,16 @@ export class AccountManager {
|
|
|
235
237
|
|
|
236
238
|
public async upsertDeviceAccount(
|
|
237
239
|
deviceId: DeviceId,
|
|
238
|
-
|
|
240
|
+
did: Did,
|
|
239
241
|
): Promise<void> {
|
|
240
|
-
await this.store.upsertDeviceAccount(deviceId,
|
|
242
|
+
await this.store.upsertDeviceAccount(deviceId, did)
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
public async getDeviceAccount(
|
|
244
246
|
deviceId: DeviceId,
|
|
245
|
-
|
|
247
|
+
did: Did,
|
|
246
248
|
): Promise<DeviceAccount> {
|
|
247
|
-
const deviceAccount = await this.store.getDeviceAccount(deviceId,
|
|
249
|
+
const deviceAccount = await this.store.getDeviceAccount(deviceId, did)
|
|
248
250
|
if (!deviceAccount) throw new InvalidRequestError(`Account not found`)
|
|
249
251
|
|
|
250
252
|
return deviceAccount
|
|
@@ -258,15 +260,15 @@ export class AccountManager {
|
|
|
258
260
|
// "Loopback" clients are not distinguishable from one another.
|
|
259
261
|
if (isOAuthClientIdLoopback(client.id)) return
|
|
260
262
|
|
|
261
|
-
await this.store.setAuthorizedClient(account.
|
|
263
|
+
await this.store.setAuthorizedClient(account.did, client.id, data)
|
|
262
264
|
}
|
|
263
265
|
|
|
264
|
-
public async getAccount(
|
|
265
|
-
return this.store.getAccount(
|
|
266
|
+
public async getAccount(did: Did) {
|
|
267
|
+
return this.store.getAccount(did)
|
|
266
268
|
}
|
|
267
269
|
|
|
268
|
-
public async removeDeviceAccount(deviceId: DeviceId,
|
|
269
|
-
return this.store.removeDeviceAccount(deviceId,
|
|
270
|
+
public async removeDeviceAccount(deviceId: DeviceId, did: Did) {
|
|
271
|
+
return this.store.removeDeviceAccount(deviceId, did)
|
|
270
272
|
}
|
|
271
273
|
|
|
272
274
|
public async listDeviceAccounts(
|
|
@@ -280,13 +282,13 @@ export class AccountManager {
|
|
|
280
282
|
.filter((deviceAccount) => deviceAccount.deviceId === deviceId)
|
|
281
283
|
}
|
|
282
284
|
|
|
283
|
-
public async listAccountDevices(
|
|
285
|
+
public async listAccountDevices(did: Did): Promise<DeviceAccount[]> {
|
|
284
286
|
const deviceAccounts = await this.store.listDeviceAccounts({
|
|
285
|
-
|
|
287
|
+
did,
|
|
286
288
|
})
|
|
287
289
|
|
|
288
290
|
return deviceAccounts // Fool proof
|
|
289
|
-
.filter((deviceAccount) => deviceAccount.account.
|
|
291
|
+
.filter((deviceAccount) => deviceAccount.account.did === did)
|
|
290
292
|
}
|
|
291
293
|
|
|
292
294
|
public async resetPasswordRequest(
|
|
@@ -412,8 +414,8 @@ export class AccountManager {
|
|
|
412
414
|
await this.hooks.onVerifyEmailRequest?.call(null, {
|
|
413
415
|
deviceId,
|
|
414
416
|
deviceMetadata,
|
|
415
|
-
input,
|
|
416
417
|
account,
|
|
418
|
+
input,
|
|
417
419
|
})
|
|
418
420
|
|
|
419
421
|
await this.store.verifyEmailRequest(input)
|
|
@@ -421,8 +423,8 @@ export class AccountManager {
|
|
|
421
423
|
await this.hooks.onVerifyEmailRequested?.call(null, {
|
|
422
424
|
deviceId,
|
|
423
425
|
deviceMetadata,
|
|
424
|
-
input,
|
|
425
426
|
account,
|
|
427
|
+
input,
|
|
426
428
|
})
|
|
427
429
|
}
|
|
428
430
|
|
|
@@ -435,8 +437,8 @@ export class AccountManager {
|
|
|
435
437
|
await this.hooks.onVerifyEmailConfirm?.call(null, {
|
|
436
438
|
deviceId,
|
|
437
439
|
deviceMetadata,
|
|
438
|
-
input,
|
|
439
440
|
account,
|
|
441
|
+
input,
|
|
440
442
|
})
|
|
441
443
|
|
|
442
444
|
const updatedAccount = await this.store.verifyEmailConfirm(input)
|
|
@@ -448,8 +450,8 @@ export class AccountManager {
|
|
|
448
450
|
await this.hooks.onVerifyEmailConfirmed?.call(null, {
|
|
449
451
|
deviceId,
|
|
450
452
|
deviceMetadata,
|
|
451
|
-
input,
|
|
452
453
|
account: updatedAccount,
|
|
454
|
+
input,
|
|
453
455
|
})
|
|
454
456
|
|
|
455
457
|
return updatedAccount
|
|
@@ -479,4 +481,105 @@ export class AccountManager {
|
|
|
479
481
|
|
|
480
482
|
return updatedAccount
|
|
481
483
|
}
|
|
484
|
+
|
|
485
|
+
public async deactivateAccount(
|
|
486
|
+
deviceId: DeviceId,
|
|
487
|
+
deviceMetadata: RequestMetadata,
|
|
488
|
+
account: Account,
|
|
489
|
+
): Promise<Account> {
|
|
490
|
+
await this.hooks.onDeactivateAccount?.call(null, {
|
|
491
|
+
deviceId,
|
|
492
|
+
deviceMetadata,
|
|
493
|
+
account,
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
const updatedAccount = await callAsync(() =>
|
|
497
|
+
this.store.deactivateAccount({ did: account.did }),
|
|
498
|
+
).catch((err) => {
|
|
499
|
+
throw InvalidRequestError.from(err, 'Account deactivation failed')
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
await this.hooks.onDeactivatedAccount?.call(null, {
|
|
503
|
+
deviceId,
|
|
504
|
+
deviceMetadata,
|
|
505
|
+
account: updatedAccount,
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
return updatedAccount
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
public async reactivateAccount(
|
|
512
|
+
deviceId: DeviceId,
|
|
513
|
+
deviceMetadata: RequestMetadata,
|
|
514
|
+
account: Account,
|
|
515
|
+
): Promise<Account> {
|
|
516
|
+
await this.hooks.onReactivateAccount?.call(null, {
|
|
517
|
+
deviceId,
|
|
518
|
+
deviceMetadata,
|
|
519
|
+
account,
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
const updatedAccount = await callAsync(() =>
|
|
523
|
+
this.store.reactivateAccount({ did: account.did }),
|
|
524
|
+
).catch((err) => {
|
|
525
|
+
throw InvalidRequestError.from(err, 'Account reactivation failed')
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
await this.hooks.onReactivatedAccount?.call(null, {
|
|
529
|
+
deviceId,
|
|
530
|
+
deviceMetadata,
|
|
531
|
+
account: updatedAccount,
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
return updatedAccount
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
public async deleteAccountRequest(
|
|
538
|
+
deviceId: DeviceId,
|
|
539
|
+
deviceMetadata: RequestMetadata,
|
|
540
|
+
input: DeleteAccountRequestInput,
|
|
541
|
+
account: Account,
|
|
542
|
+
): Promise<void> {
|
|
543
|
+
await this.hooks.onDeleteAccountRequest?.call(null, {
|
|
544
|
+
deviceId,
|
|
545
|
+
deviceMetadata,
|
|
546
|
+
account,
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
await this.store.deleteAccountRequest(input)
|
|
550
|
+
|
|
551
|
+
await this.hooks.onDeleteAccountRequested?.call(null, {
|
|
552
|
+
deviceId,
|
|
553
|
+
deviceMetadata,
|
|
554
|
+
account,
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
public async deleteAccountConfirm(
|
|
559
|
+
deviceId: DeviceId,
|
|
560
|
+
deviceMetadata: RequestMetadata,
|
|
561
|
+
input: DeleteAccountConfirmInput,
|
|
562
|
+
account: Account,
|
|
563
|
+
): Promise<void> {
|
|
564
|
+
return constantTime(BRUTE_FORCE_MITIGATION_DELAY, async () => {
|
|
565
|
+
await this.hooks.onDeleteAccountConfirm?.call(null, {
|
|
566
|
+
deviceId,
|
|
567
|
+
deviceMetadata,
|
|
568
|
+
account,
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
await callAsync(() => this.store.deleteAccountConfirm(input)).catch(
|
|
572
|
+
(err) => {
|
|
573
|
+
throw InvalidRequestError.from(err, 'Account deletion failed')
|
|
574
|
+
},
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
await this.hooks.onDeleteAccountConfirmed?.call(null, {
|
|
578
|
+
deviceId,
|
|
579
|
+
deviceMetadata,
|
|
580
|
+
account,
|
|
581
|
+
input,
|
|
582
|
+
})
|
|
583
|
+
})
|
|
584
|
+
}
|
|
482
585
|
}
|