@atproto/oauth-provider 0.16.5 → 0.17.0
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 +32 -0
- package/dist/access-token/access-token-mode.js +2 -5
- package/dist/access-token/access-token-mode.js.map +1 -1
- package/dist/account/account-manager.js +25 -33
- package/dist/account/account-manager.js.map +1 -1
- package/dist/account/account-store.js +11 -32
- package/dist/account/account-store.js.map +1 -1
- package/dist/account/sign-in-data.js +9 -12
- package/dist/account/sign-in-data.js.map +1 -1
- package/dist/account/sign-up-input.js +14 -17
- package/dist/account/sign-up-input.js.map +1 -1
- package/dist/client/client-auth.js +1 -2
- package/dist/client/client-data.js +1 -2
- package/dist/client/client-id.js +2 -5
- package/dist/client/client-id.js.map +1 -1
- package/dist/client/client-info.js +1 -2
- package/dist/client/client-manager.js +86 -97
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client-store.js +7 -26
- package/dist/client/client-store.js.map +1 -1
- package/dist/client/client-utils.js +10 -14
- package/dist/client/client-utils.js.map +1 -1
- package/dist/client/client.js +43 -53
- package/dist/client/client.js.map +1 -1
- package/dist/constants.js +28 -31
- package/dist/constants.js.map +1 -1
- package/dist/customization/branding.js +8 -11
- package/dist/customization/branding.js.map +1 -1
- package/dist/customization/build-customization-css.js +8 -11
- package/dist/customization/build-customization-css.js.map +1 -1
- package/dist/customization/build-customization-data.js +1 -4
- package/dist/customization/build-customization-data.js.map +1 -1
- package/dist/customization/colors.js +11 -14
- package/dist/customization/colors.js.map +1 -1
- package/dist/customization/customization.js +8 -11
- package/dist/customization/customization.js.map +1 -1
- package/dist/customization/links.js +7 -10
- package/dist/customization/links.js.map +1 -1
- package/dist/device/device-data.js +7 -10
- package/dist/device/device-data.js.map +1 -1
- package/dist/device/device-id.js +11 -16
- package/dist/device/device-id.js.map +1 -1
- package/dist/device/device-manager.js +32 -38
- package/dist/device/device-manager.js.map +1 -1
- package/dist/device/device-store.js +7 -25
- package/dist/device/device-store.js.map +1 -1
- package/dist/device/session-id.js +9 -13
- package/dist/device/session-id.js.map +1 -1
- package/dist/dpop/dpop-manager.d.ts +3 -3
- package/dist/dpop/dpop-manager.js +38 -43
- package/dist/dpop/dpop-manager.js.map +1 -1
- package/dist/dpop/dpop-nonce.d.ts +2 -2
- package/dist/dpop/dpop-nonce.d.ts.map +1 -1
- package/dist/dpop/dpop-nonce.js +14 -18
- package/dist/dpop/dpop-nonce.js.map +1 -1
- package/dist/dpop/dpop-proof.js +1 -2
- package/dist/errors/access-denied-error.js +2 -6
- package/dist/errors/access-denied-error.js.map +1 -1
- package/dist/errors/account-selection-required-error.js +2 -6
- package/dist/errors/account-selection-required-error.js.map +1 -1
- package/dist/errors/authorization-error.js +7 -12
- package/dist/errors/authorization-error.js.map +1 -1
- package/dist/errors/consent-required-error.js +2 -6
- package/dist/errors/consent-required-error.js.map +1 -1
- package/dist/errors/error-parser.js +14 -18
- package/dist/errors/error-parser.js.map +1 -1
- package/dist/errors/handle-unavailable-error.js +2 -7
- package/dist/errors/handle-unavailable-error.js.map +1 -1
- package/dist/errors/invalid-authorization-details-error.js +2 -6
- package/dist/errors/invalid-authorization-details-error.js.map +1 -1
- package/dist/errors/invalid-client-error.js +2 -6
- package/dist/errors/invalid-client-error.js.map +1 -1
- package/dist/errors/invalid-client-id-error.js +2 -6
- package/dist/errors/invalid-client-id-error.js.map +1 -1
- package/dist/errors/invalid-client-metadata-error.js +7 -11
- package/dist/errors/invalid-client-metadata-error.js.map +1 -1
- package/dist/errors/invalid-credentials-error.js +2 -7
- package/dist/errors/invalid-credentials-error.js.map +1 -1
- package/dist/errors/invalid-dpop-key-binding-error.js +2 -6
- package/dist/errors/invalid-dpop-key-binding-error.js.map +1 -1
- package/dist/errors/invalid-dpop-proof-error.js +2 -6
- package/dist/errors/invalid-dpop-proof-error.js.map +1 -1
- package/dist/errors/invalid-grant-error.js +2 -6
- package/dist/errors/invalid-grant-error.js.map +1 -1
- package/dist/errors/invalid-invite-code-error.d.ts +1 -1
- package/dist/errors/invalid-invite-code-error.d.ts.map +1 -1
- package/dist/errors/invalid-invite-code-error.js +2 -6
- package/dist/errors/invalid-invite-code-error.js.map +1 -1
- package/dist/errors/invalid-redirect-uri-error.js +2 -6
- package/dist/errors/invalid-redirect-uri-error.js.map +1 -1
- package/dist/errors/invalid-request-error.js +3 -7
- package/dist/errors/invalid-request-error.js.map +1 -1
- package/dist/errors/invalid-scope-error.js +2 -6
- package/dist/errors/invalid-scope-error.js.map +1 -1
- package/dist/errors/invalid-token-error.js +10 -15
- package/dist/errors/invalid-token-error.js.map +1 -1
- package/dist/errors/login-required-error.js +2 -6
- package/dist/errors/login-required-error.js.map +1 -1
- package/dist/errors/oauth-error.js +1 -9
- package/dist/errors/oauth-error.js.map +1 -1
- package/dist/errors/second-authentication-factor-required-error.js +2 -8
- package/dist/errors/second-authentication-factor-required-error.js.map +1 -1
- package/dist/errors/unauthorized-client-error.js +2 -6
- package/dist/errors/unauthorized-client-error.js.map +1 -1
- package/dist/errors/use-dpop-nonce-error.js +4 -8
- package/dist/errors/use-dpop-nonce-error.js.map +1 -1
- package/dist/errors/www-authenticate-error.js +4 -9
- package/dist/errors/www-authenticate-error.js.map +1 -1
- package/dist/index.js +14 -30
- package/dist/index.js.map +1 -1
- package/dist/lexicon/lexicon-data.js +1 -2
- package/dist/lexicon/lexicon-getter.js +6 -10
- package/dist/lexicon/lexicon-getter.js.map +1 -1
- package/dist/lexicon/lexicon-manager.js +10 -30
- package/dist/lexicon/lexicon-manager.js.map +1 -1
- package/dist/lexicon/lexicon-store.js +5 -10
- package/dist/lexicon/lexicon-store.js.map +1 -1
- package/dist/lib/csp/index.js +3 -8
- package/dist/lib/csp/index.js.map +1 -1
- package/dist/lib/hcaptcha.js +33 -43
- package/dist/lib/hcaptcha.js.map +1 -1
- package/dist/lib/html/build-document.js +19 -24
- package/dist/lib/html/build-document.js.map +1 -1
- package/dist/lib/html/escapers.js +10 -16
- package/dist/lib/html/escapers.js.map +1 -1
- package/dist/lib/html/html.js +1 -5
- package/dist/lib/html/html.js.map +1 -1
- package/dist/lib/html/hydration-data.js +6 -10
- package/dist/lib/html/hydration-data.js.map +1 -1
- package/dist/lib/html/index.js +3 -19
- package/dist/lib/html/index.js.map +1 -1
- package/dist/lib/html/tags.js +14 -23
- package/dist/lib/html/tags.js.map +1 -1
- package/dist/lib/html/util.js +1 -4
- package/dist/lib/html/util.js.map +1 -1
- package/dist/lib/http/accept.d.ts.map +1 -1
- package/dist/lib/http/accept.js +8 -8
- package/dist/lib/http/accept.js.map +1 -1
- package/dist/lib/http/context.js +1 -4
- package/dist/lib/http/context.js.map +1 -1
- package/dist/lib/http/headers.js +1 -4
- package/dist/lib/http/headers.js.map +1 -1
- package/dist/lib/http/index.js +10 -26
- package/dist/lib/http/index.js.map +1 -1
- package/dist/lib/http/method.js +1 -4
- package/dist/lib/http/method.js.map +1 -1
- package/dist/lib/http/middleware.js +11 -17
- package/dist/lib/http/middleware.js.map +1 -1
- package/dist/lib/http/parser.js +13 -20
- package/dist/lib/http/parser.js.map +1 -1
- package/dist/lib/http/path.js +1 -4
- package/dist/lib/http/path.js.map +1 -1
- package/dist/lib/http/request.d.ts.map +1 -1
- package/dist/lib/http/request.js +32 -47
- package/dist/lib/http/request.js.map +1 -1
- package/dist/lib/http/response.js +14 -27
- package/dist/lib/http/response.js.map +1 -1
- package/dist/lib/http/route.js +9 -12
- package/dist/lib/http/route.js.map +1 -1
- package/dist/lib/http/router.js +8 -13
- package/dist/lib/http/router.js.map +1 -1
- package/dist/lib/http/security-headers.js +10 -15
- package/dist/lib/http/security-headers.js.map +1 -1
- package/dist/lib/http/stream.js +12 -20
- package/dist/lib/http/stream.js.map +1 -1
- package/dist/lib/http/types.js +1 -2
- package/dist/lib/http/url.js +1 -4
- package/dist/lib/http/url.js.map +1 -1
- package/dist/lib/nsid.js +4 -8
- package/dist/lib/nsid.js.map +1 -1
- package/dist/lib/redis.js +4 -7
- package/dist/lib/redis.js.map +1 -1
- package/dist/lib/util/authorization-header.js +11 -15
- package/dist/lib/util/authorization-header.js.map +1 -1
- package/dist/lib/util/cast.js +3 -8
- package/dist/lib/util/cast.js.map +1 -1
- package/dist/lib/util/color.js +23 -32
- package/dist/lib/util/color.js.map +1 -1
- package/dist/lib/util/crypto.js +5 -10
- package/dist/lib/util/crypto.js.map +1 -1
- package/dist/lib/util/date.js +2 -6
- package/dist/lib/util/date.js.map +1 -1
- package/dist/lib/util/error.js +5 -8
- package/dist/lib/util/error.js.map +1 -1
- package/dist/lib/util/function.js +3 -8
- package/dist/lib/util/function.js.map +1 -1
- package/dist/lib/util/locale.js +3 -6
- package/dist/lib/util/locale.js.map +1 -1
- package/dist/lib/util/object.js +1 -4
- package/dist/lib/util/object.js.map +1 -1
- package/dist/lib/util/redirect-uri.js +3 -6
- package/dist/lib/util/redirect-uri.js.map +1 -1
- package/dist/lib/util/time.js +5 -9
- package/dist/lib/util/time.js.map +1 -1
- package/dist/lib/util/type.d.ts.map +1 -1
- package/dist/lib/util/type.js +1 -5
- package/dist/lib/util/type.js.map +1 -1
- package/dist/lib/util/ui8.js +3 -8
- package/dist/lib/util/ui8.js.map +1 -1
- package/dist/lib/util/well-known.js +1 -4
- package/dist/lib/util/well-known.js.map +1 -1
- package/dist/lib/util/zod-error.js +4 -8
- package/dist/lib/util/zod-error.js.map +1 -1
- package/dist/lib/write-form-redirect.js +9 -12
- package/dist/lib/write-form-redirect.js.map +1 -1
- package/dist/lib/write-html.js +12 -15
- package/dist/lib/write-html.js.map +1 -1
- package/dist/metadata/build-metadata.js +9 -12
- package/dist/metadata/build-metadata.js.map +1 -1
- package/dist/oauth-client.js +2 -18
- package/dist/oauth-client.js.map +1 -1
- package/dist/oauth-dpop.js +2 -18
- package/dist/oauth-dpop.js.map +1 -1
- package/dist/oauth-errors.js +24 -42
- package/dist/oauth-errors.js.map +1 -1
- package/dist/oauth-hooks.js +8 -15
- package/dist/oauth-hooks.js.map +1 -1
- package/dist/oauth-middleware.js +13 -16
- package/dist/oauth-middleware.js.map +1 -1
- package/dist/oauth-provider.js +108 -125
- package/dist/oauth-provider.js.map +1 -1
- package/dist/oauth-store.js +7 -23
- package/dist/oauth-store.js.map +1 -1
- package/dist/oauth-verifier.js +41 -53
- package/dist/oauth-verifier.js.map +1 -1
- package/dist/oidc/sub.js +2 -5
- package/dist/oidc/sub.js.map +1 -1
- package/dist/replay/replay-manager.js +6 -11
- package/dist/replay/replay-manager.js.map +1 -1
- package/dist/replay/replay-store-memory.js +5 -7
- package/dist/replay/replay-store-memory.js.map +1 -1
- package/dist/replay/replay-store-redis.js +3 -8
- package/dist/replay/replay-store-redis.js.map +1 -1
- package/dist/replay/replay-store.js +3 -8
- package/dist/replay/replay-store.js.map +1 -1
- package/dist/request/code.js +10 -15
- package/dist/request/code.js.map +1 -1
- package/dist/request/request-data.js +1 -5
- package/dist/request/request-data.js.map +1 -1
- package/dist/request/request-id.js +9 -13
- package/dist/request/request-id.js.map +1 -1
- package/dist/request/request-manager.js +61 -71
- package/dist/request/request-manager.js.map +1 -1
- package/dist/request/request-store.js +9 -27
- package/dist/request/request-store.js.map +1 -1
- package/dist/request/request-uri.js +17 -23
- package/dist/request/request-uri.js.map +1 -1
- package/dist/result/authorization-redirect-parameters.js +1 -2
- package/dist/result/authorization-result-authorize-page.js +1 -2
- package/dist/result/authorization-result-redirect.js +1 -2
- package/dist/router/assets/assets-manifest.d.ts.map +1 -1
- package/dist/router/assets/assets-manifest.js +14 -15
- package/dist/router/assets/assets-manifest.js.map +1 -1
- package/dist/router/assets/assets.d.ts.map +1 -1
- package/dist/router/assets/assets.js +25 -27
- package/dist/router/assets/assets.js.map +1 -1
- package/dist/router/assets/csrf.js +16 -25
- package/dist/router/assets/csrf.js.map +1 -1
- package/dist/router/assets/send-account-page.js +3 -6
- package/dist/router/assets/send-account-page.js.map +1 -1
- package/dist/router/assets/send-authorization-page.js +3 -6
- package/dist/router/assets/send-authorization-page.js.map +1 -1
- package/dist/router/assets/send-cookie-error-page.js +3 -6
- package/dist/router/assets/send-cookie-error-page.js.map +1 -1
- package/dist/router/assets/send-error-page.js +6 -9
- package/dist/router/assets/send-error-page.js.map +1 -1
- package/dist/router/assets/send-redirect.js +12 -20
- package/dist/router/assets/send-redirect.js.map +1 -1
- package/dist/router/create-account-page-middleware.js +11 -14
- package/dist/router/create-account-page-middleware.js.map +1 -1
- package/dist/router/create-api-middleware.js +83 -90
- package/dist/router/create-api-middleware.js.map +1 -1
- package/dist/router/create-authorization-page-middleware.js +43 -46
- package/dist/router/create-authorization-page-middleware.js.map +1 -1
- package/dist/router/create-oauth-middleware.js +31 -34
- package/dist/router/create-oauth-middleware.js.map +1 -1
- package/dist/router/error-handler.js +1 -2
- package/dist/router/middleware-options.js +1 -2
- package/dist/signer/access-token-payload.js +12 -15
- package/dist/signer/access-token-payload.js.map +1 -1
- package/dist/signer/api-token-payload.js +8 -11
- package/dist/signer/api-token-payload.js.map +1 -1
- package/dist/signer/signer.js +11 -17
- package/dist/signer/signer.js.map +1 -1
- package/dist/token/refresh-token.js +10 -15
- package/dist/token/refresh-token.js.map +1 -1
- package/dist/token/token-claims.js +1 -2
- package/dist/token/token-data.js +1 -2
- package/dist/token/token-id.js +10 -15
- package/dist/token/token-id.js.map +1 -1
- package/dist/token/token-manager.js +40 -51
- package/dist/token/token-manager.js.map +1 -1
- package/dist/token/token-store.js +7 -25
- package/dist/token/token-store.js.map +1 -1
- package/dist/types/authorization-response-error.js +8 -12
- package/dist/types/authorization-response-error.js.map +1 -1
- package/dist/types/color-hue.js +2 -5
- package/dist/types/color-hue.js.map +1 -1
- package/dist/types/email-otp.js +2 -5
- package/dist/types/email-otp.js.map +1 -1
- package/dist/types/email.js +6 -9
- package/dist/types/email.js.map +1 -1
- package/dist/types/handle.js +6 -9
- package/dist/types/handle.js.map +1 -1
- package/dist/types/invite-code.js +2 -5
- package/dist/types/invite-code.js.map +1 -1
- package/dist/types/par-response-error.js +5 -9
- package/dist/types/par-response-error.js.map +1 -1
- package/dist/types/password.js +3 -6
- package/dist/types/password.js.map +1 -1
- package/dist/types/rgb-color.js +7 -10
- package/dist/types/rgb-color.js.map +1 -1
- package/package.json +20 -22
- package/src/dpop/dpop-nonce.ts +1 -1
- package/src/errors/invalid-invite-code-error.ts +1 -1
- package/src/lib/http/accept.ts +4 -1
- package/src/lib/http/request.ts +4 -1
- package/src/lib/util/type.ts +0 -1
- package/src/router/assets/assets-manifest.ts +3 -1
- package/src/router/assets/assets.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,29 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ReplayManager = void 0;
|
|
4
|
-
const constants_js_1 = require("../constants.js");
|
|
1
|
+
import { CLIENT_ASSERTION_MAX_AGE, CODE_CHALLENGE_REPLAY_TIMEFRAME, DPOP_NONCE_MAX_AGE, JAR_MAX_AGE, } from '../constants.js';
|
|
5
2
|
const SECURITY_RATIO = 1.1; // 10% extra time for security
|
|
6
3
|
const asTimeFrame = (timeFrame) => Math.ceil(timeFrame * SECURITY_RATIO);
|
|
7
|
-
class ReplayManager {
|
|
8
|
-
replayStore;
|
|
4
|
+
export class ReplayManager {
|
|
9
5
|
constructor(replayStore) {
|
|
10
6
|
this.replayStore = replayStore;
|
|
11
7
|
}
|
|
12
8
|
async uniqueAuth(jti, clientId, exp) {
|
|
13
9
|
const timeFrame = exp == null
|
|
14
|
-
? asTimeFrame(
|
|
10
|
+
? asTimeFrame(CLIENT_ASSERTION_MAX_AGE)
|
|
15
11
|
: exp * 1000 - Date.now();
|
|
16
12
|
return this.replayStore.unique(`Auth@${clientId}`, jti, timeFrame);
|
|
17
13
|
}
|
|
18
14
|
async uniqueJar(jti, clientId) {
|
|
19
|
-
return this.replayStore.unique(`JAR@${clientId}`, jti, asTimeFrame(
|
|
15
|
+
return this.replayStore.unique(`JAR@${clientId}`, jti, asTimeFrame(JAR_MAX_AGE));
|
|
20
16
|
}
|
|
21
17
|
async uniqueDpop(jti, clientId) {
|
|
22
|
-
return this.replayStore.unique(clientId ? `DPoP@${clientId}` : `DPoP`, jti, asTimeFrame(
|
|
18
|
+
return this.replayStore.unique(clientId ? `DPoP@${clientId}` : `DPoP`, jti, asTimeFrame(DPOP_NONCE_MAX_AGE));
|
|
23
19
|
}
|
|
24
20
|
async uniqueCodeChallenge(challenge) {
|
|
25
|
-
return this.replayStore.unique('CodeChallenge', challenge, asTimeFrame(
|
|
21
|
+
return this.replayStore.unique('CodeChallenge', challenge, asTimeFrame(CODE_CHALLENGE_REPLAY_TIMEFRAME));
|
|
26
22
|
}
|
|
27
23
|
}
|
|
28
|
-
exports.ReplayManager = ReplayManager;
|
|
29
24
|
//# sourceMappingURL=replay-manager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay-manager.js","sourceRoot":"","sources":["../../src/replay/replay-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replay-manager.js","sourceRoot":"","sources":["../../src/replay/replay-manager.ts"],"names":[],"mappings":"AACA,OAAO,EACL,wBAAwB,EACxB,+BAA+B,EAC/B,kBAAkB,EAClB,WAAW,GACZ,MAAM,iBAAiB,CAAA;AAGxB,MAAM,cAAc,GAAG,GAAG,CAAA,CAAC,8BAA8B;AACzD,MAAM,WAAW,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,cAAc,CAAC,CAAA;AAEhF,MAAM,OAAO,aAAa;IACxB,YAA+B,WAAwB;QAAxB,gBAAW,GAAX,WAAW,CAAa;IAAG,CAAC;IAE3D,KAAK,CAAC,UAAU,CACd,GAAW,EACX,QAAkB,EAClB,GAAY;QAEZ,MAAM,SAAS,GACb,GAAG,IAAI,IAAI;YACT,CAAC,CAAC,WAAW,CAAC,wBAAwB,CAAC;YACvC,CAAC,CAAC,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAC7B,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,QAAQ,QAAQ,EAAE,EAAE,GAAG,EAAE,SAAS,CAAC,CAAA;IACpE,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAW,EAAE,QAAkB;QAC7C,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAC5B,OAAO,QAAQ,EAAE,EACjB,GAAG,EACH,WAAW,CAAC,WAAW,CAAC,CACzB,CAAA;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,GAAW,EAAE,QAAmB;QAC/C,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAC5B,QAAQ,CAAC,CAAC,CAAC,QAAQ,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,EACtC,GAAG,EACH,WAAW,CAAC,kBAAkB,CAAC,CAChC,CAAA;IACH,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,SAAiB;QACzC,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAC5B,eAAe,EACf,SAAS,EACT,WAAW,CAAC,+BAA+B,CAAC,CAC7C,CAAA;IACH,CAAC;CACF","sourcesContent":["import { ClientId } from '../client/client-id.js'\nimport {\n CLIENT_ASSERTION_MAX_AGE,\n CODE_CHALLENGE_REPLAY_TIMEFRAME,\n DPOP_NONCE_MAX_AGE,\n JAR_MAX_AGE,\n} from '../constants.js'\nimport { ReplayStore } from './replay-store.js'\n\nconst SECURITY_RATIO = 1.1 // 10% extra time for security\nconst asTimeFrame = (timeFrame: number) => Math.ceil(timeFrame * SECURITY_RATIO)\n\nexport class ReplayManager {\n constructor(protected readonly replayStore: ReplayStore) {}\n\n async uniqueAuth(\n jti: string,\n clientId: ClientId,\n exp?: number,\n ): Promise<boolean> {\n const timeFrame =\n exp == null\n ? asTimeFrame(CLIENT_ASSERTION_MAX_AGE)\n : exp * 1000 - Date.now()\n return this.replayStore.unique(`Auth@${clientId}`, jti, timeFrame)\n }\n\n async uniqueJar(jti: string, clientId: ClientId): Promise<boolean> {\n return this.replayStore.unique(\n `JAR@${clientId}`,\n jti,\n asTimeFrame(JAR_MAX_AGE),\n )\n }\n\n async uniqueDpop(jti: string, clientId?: ClientId): Promise<boolean> {\n return this.replayStore.unique(\n clientId ? `DPoP@${clientId}` : `DPoP`,\n jti,\n asTimeFrame(DPOP_NONCE_MAX_AGE),\n )\n }\n\n async uniqueCodeChallenge(challenge: string): Promise<boolean> {\n return this.replayStore.unique(\n 'CodeChallenge',\n challenge,\n asTimeFrame(CODE_CHALLENGE_REPLAY_TIMEFRAME),\n )\n }\n}\n"]}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
nonces = new Map();
|
|
1
|
+
export class ReplayStoreMemory {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.lastCleanup = Date.now();
|
|
4
|
+
this.nonces = new Map();
|
|
5
|
+
}
|
|
7
6
|
/**
|
|
8
7
|
* Returns true if the nonce is unique within the given time frame.
|
|
9
8
|
*/
|
|
@@ -26,5 +25,4 @@ class ReplayStoreMemory {
|
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
}
|
|
29
|
-
exports.ReplayStoreMemory = ReplayStoreMemory;
|
|
30
28
|
//# sourceMappingURL=replay-store-memory.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay-store-memory.js","sourceRoot":"","sources":["../../src/replay/replay-store-memory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replay-store-memory.js","sourceRoot":"","sources":["../../src/replay/replay-store-memory.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,iBAAiB;IAA9B;QACU,gBAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACxB,WAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IA+B5C,CAAC;IA7BC;;OAEG;IACH,KAAK,CAAC,MAAM,CACV,SAAiB,EACjB,KAAa,EACb,SAAiB;QAEjB,IAAI,CAAC,OAAO,EAAE,CAAA;QACd,MAAM,GAAG,GAAG,GAAG,SAAS,IAAI,KAAK,EAAE,CAAA;QAEnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAEtB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,SAAS,CAAC,CAAA;QAErC,OAAO,GAAG,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,CAAA;IACjC,CAAC;IAEO,OAAO;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAEtB,IAAI,IAAI,CAAC,WAAW,GAAG,GAAG,GAAG,MAAM,EAAE,CAAC;YACpC,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBACzC,IAAI,OAAO,GAAG,GAAG;oBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAC5C,CAAC;YACD,IAAI,CAAC,WAAW,GAAG,GAAG,CAAA;QACxB,CAAC;IACH,CAAC;CACF","sourcesContent":["import type { ReplayStore } from './replay-store.js'\n\nexport class ReplayStoreMemory implements ReplayStore {\n private lastCleanup = Date.now()\n private nonces = new Map<string, number>()\n\n /**\n * Returns true if the nonce is unique within the given time frame.\n */\n async unique(\n namespace: string,\n nonce: string,\n timeFrame: number,\n ): Promise<boolean> {\n this.cleanup()\n const key = `${namespace}:${nonce}`\n\n const now = Date.now()\n\n const exp = this.nonces.get(key)\n this.nonces.set(key, now + timeFrame)\n\n return exp == null || exp < now\n }\n\n private cleanup() {\n const now = Date.now()\n\n if (this.lastCleanup < now - 60_000) {\n for (const [key, expires] of this.nonces) {\n if (expires < now) this.nonces.delete(key)\n }\n this.lastCleanup = now\n }\n }\n}\n"]}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.ReplayStoreRedis = void 0;
|
|
4
|
-
const redis_js_1 = require("../lib/redis.js");
|
|
5
|
-
class ReplayStoreRedis {
|
|
6
|
-
redis;
|
|
1
|
+
import { createRedis } from '../lib/redis.js';
|
|
2
|
+
export class ReplayStoreRedis {
|
|
7
3
|
constructor(options) {
|
|
8
|
-
this.redis =
|
|
4
|
+
this.redis = createRedis(options.redis);
|
|
9
5
|
}
|
|
10
6
|
/**
|
|
11
7
|
* Returns true if the nonce is unique within the given time frame.
|
|
@@ -16,5 +12,4 @@ class ReplayStoreRedis {
|
|
|
16
12
|
return prev == null;
|
|
17
13
|
}
|
|
18
14
|
}
|
|
19
|
-
exports.ReplayStoreRedis = ReplayStoreRedis;
|
|
20
15
|
//# sourceMappingURL=replay-store-redis.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay-store-redis.js","sourceRoot":"","sources":["../../src/replay/replay-store-redis.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replay-store-redis.js","sourceRoot":"","sources":["../../src/replay/replay-store-redis.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,WAAW,EAAE,MAAM,iBAAiB,CAAA;AASjE,MAAM,OAAO,gBAAgB;IAG3B,YAAY,OAAgC;QAC1C,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACzC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM,CACV,SAAiB,EACjB,KAAa,EACb,SAAiB;QAEjB,MAAM,GAAG,GAAG,UAAU,SAAS,IAAI,KAAK,EAAE,CAAA;QAC1C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,CAAA;QACnE,OAAO,IAAI,IAAI,IAAI,CAAA;IACrB,CAAC;CACF","sourcesContent":["import type { Redis } from 'ioredis'\nimport { CreateRedisOptions, createRedis } from '../lib/redis.js'\nimport type { ReplayStore } from './replay-store.js'\n\nexport type { CreateRedisOptions, Redis }\n\nexport type ReplayStoreRedisOptions = {\n redis: CreateRedisOptions\n}\n\nexport class ReplayStoreRedis implements ReplayStore {\n private readonly redis: Redis\n\n constructor(options: ReplayStoreRedisOptions) {\n this.redis = createRedis(options.redis)\n }\n\n /**\n * Returns true if the nonce is unique within the given time frame.\n */\n async unique(\n namespace: string,\n nonce: string,\n timeFrame: number,\n ): Promise<boolean> {\n const key = `nonces:${namespace}:${nonce}`\n const prev = await this.redis.set(key, '1', 'PX', timeFrame, 'GET')\n return prev == null\n }\n}\n"]}
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isReplayStore = isReplayStore;
|
|
4
|
-
exports.ifReplayStore = ifReplayStore;
|
|
5
|
-
exports.asReplayStore = asReplayStore;
|
|
6
|
-
function isReplayStore(implementation) {
|
|
1
|
+
export function isReplayStore(implementation) {
|
|
7
2
|
return typeof implementation.unique === 'function';
|
|
8
3
|
}
|
|
9
|
-
function ifReplayStore(implementation) {
|
|
4
|
+
export function ifReplayStore(implementation) {
|
|
10
5
|
if (implementation && isReplayStore(implementation)) {
|
|
11
6
|
return implementation;
|
|
12
7
|
}
|
|
13
8
|
return undefined;
|
|
14
9
|
}
|
|
15
|
-
function asReplayStore(implementation) {
|
|
10
|
+
export function asReplayStore(implementation) {
|
|
16
11
|
const store = ifReplayStore(implementation);
|
|
17
12
|
if (store)
|
|
18
13
|
return store;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay-store.js","sourceRoot":"","sources":["../../src/replay/replay-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replay-store.js","sourceRoot":"","sources":["../../src/replay/replay-store.ts"],"names":[],"mappings":"AAoBA,MAAM,UAAU,aAAa,CAC3B,cAA8D;IAE9D,OAAO,OAAO,cAAc,CAAC,MAAM,KAAK,UAAU,CAAA;AACpD,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,cAA+D;IAE/D,IAAI,cAAc,IAAI,aAAa,CAAC,cAAc,CAAC,EAAE,CAAC;QACpD,OAAO,cAAc,CAAA;IACvB,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,cAA+D;IAE/D,MAAM,KAAK,GAAG,aAAa,CAAC,cAAc,CAAC,CAAA;IAC3C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAA;IAEvB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;AACvD,CAAC","sourcesContent":["import { Awaitable } from '../lib/util/type.js'\n\n// Export all types needed to implement the ReplayStore interface\nexport type { Awaitable }\n\nexport interface ReplayStore {\n /**\n * Returns true if the nonce is unique within the given time frame. While not\n * strictly necessary for security purposes, the namespace should be used to\n * mitigate denial of service attacks from one client to the other.\n *\n * @param timeFrame expressed in milliseconds.\n */\n unique(\n namespace: string,\n nonce: string,\n timeFrame: number,\n ): Awaitable<boolean>\n}\n\nexport function isReplayStore(\n implementation: Record<string, unknown> & Partial<ReplayStore>,\n): implementation is Record<string, unknown> & ReplayStore {\n return typeof implementation.unique === 'function'\n}\n\nexport function ifReplayStore(\n implementation?: Record<string, unknown> & Partial<ReplayStore>,\n): ReplayStore | undefined {\n if (implementation && isReplayStore(implementation)) {\n return implementation\n }\n\n return undefined\n}\n\nexport function asReplayStore(\n implementation?: Record<string, unknown> & Partial<ReplayStore>,\n): ReplayStore {\n const store = ifReplayStore(implementation)\n if (store) return store\n\n throw new Error('Invalid ReplayStore implementation')\n}\n"]}
|
package/dist/request/code.js
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const crypto_js_1 = require("../lib/util/crypto.js");
|
|
7
|
-
exports.CODE_LENGTH = constants_js_1.CODE_PREFIX.length + constants_js_1.CODE_BYTES_LENGTH * 2; // hex encoding
|
|
8
|
-
exports.codeSchema = zod_1.z
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { CODE_BYTES_LENGTH, CODE_PREFIX } from '../constants.js';
|
|
3
|
+
import { randomHexId } from '../lib/util/crypto.js';
|
|
4
|
+
export const CODE_LENGTH = CODE_PREFIX.length + CODE_BYTES_LENGTH * 2; // hex encoding
|
|
5
|
+
export const codeSchema = z
|
|
9
6
|
.string()
|
|
10
|
-
.length(
|
|
11
|
-
.refine((v) => v.startsWith(
|
|
7
|
+
.length(CODE_LENGTH) // hex encoding
|
|
8
|
+
.refine((v) => v.startsWith(CODE_PREFIX), {
|
|
12
9
|
message: `Invalid code format`,
|
|
13
10
|
});
|
|
14
|
-
const isCode = (data) =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return `${constants_js_1.CODE_PREFIX}${await (0, crypto_js_1.randomHexId)(constants_js_1.CODE_BYTES_LENGTH)}`;
|
|
11
|
+
export const isCode = (data) => codeSchema.safeParse(data).success;
|
|
12
|
+
export const generateCode = async () => {
|
|
13
|
+
return `${CODE_PREFIX}${await randomHexId(CODE_BYTES_LENGTH)}`;
|
|
18
14
|
};
|
|
19
|
-
exports.generateCode = generateCode;
|
|
20
15
|
//# sourceMappingURL=code.js.map
|
package/dist/request/code.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code.js","sourceRoot":"","sources":["../../src/request/code.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"code.js","sourceRoot":"","sources":["../../src/request/code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAEnD,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC,MAAM,GAAG,iBAAiB,GAAG,CAAC,CAAA,CAAC,eAAe;AAErF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC;KACxB,MAAM,EAAE;KACR,MAAM,CAAC,WAAW,CAAC,CAAC,eAAe;KACnC,MAAM,CACL,CAAC,CAAC,EAAyC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,EACvE;IACE,OAAO,EAAE,qBAAqB;CAC/B,CACF,CAAA;AAEH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,IAAa,EAAgB,EAAE,CACpD,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,CAAA;AAGpC,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,IAAmB,EAAE;IACpD,OAAO,GAAG,WAAW,GAAG,MAAM,WAAW,CAAC,iBAAiB,CAAC,EAAE,CAAA;AAChE,CAAC,CAAA","sourcesContent":["import { z } from 'zod'\nimport { CODE_BYTES_LENGTH, CODE_PREFIX } from '../constants.js'\nimport { randomHexId } from '../lib/util/crypto.js'\n\nexport const CODE_LENGTH = CODE_PREFIX.length + CODE_BYTES_LENGTH * 2 // hex encoding\n\nexport const codeSchema = z\n .string()\n .length(CODE_LENGTH) // hex encoding\n .refine(\n (v): v is `${typeof CODE_PREFIX}${string}` => v.startsWith(CODE_PREFIX),\n {\n message: `Invalid code format`,\n },\n )\n\nexport const isCode = (data: unknown): data is Code =>\n codeSchema.safeParse(data).success\n\nexport type Code = z.infer<typeof codeSchema>\nexport const generateCode = async (): Promise<Code> => {\n return `${CODE_PREFIX}${await randomHexId(CODE_BYTES_LENGTH)}`\n}\n"]}
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isRequestDataAuthorized = void 0;
|
|
4
|
-
const isRequestDataAuthorized = (data) => data.sub !== null && data.deviceId !== null;
|
|
5
|
-
exports.isRequestDataAuthorized = isRequestDataAuthorized;
|
|
1
|
+
export const isRequestDataAuthorized = (data) => data.sub !== null && data.deviceId !== null;
|
|
6
2
|
//# sourceMappingURL=request-data.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-data.js","sourceRoot":"","sources":["../../src/request/request-data.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"request-data.js","sourceRoot":"","sources":["../../src/request/request-data.ts"],"names":[],"mappings":"AAiCA,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,IAAiB,EACc,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAA","sourcesContent":["import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'\nimport { ClientAuth, ClientAuthLegacy } from '../client/client-auth.js'\nimport { ClientId } from '../client/client-id.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { NonNullableKeys } from '../lib/util/type.js'\nimport { Sub } from '../oidc/sub.js'\nimport { Code } from './code.js'\n\nexport type {\n ClientAuth,\n ClientAuthLegacy,\n ClientId,\n Code,\n DeviceId,\n OAuthAuthorizationRequestParameters,\n Sub,\n}\n\nexport type RequestData = {\n clientId: ClientId\n clientAuth: null | ClientAuth | ClientAuthLegacy\n parameters: Readonly<OAuthAuthorizationRequestParameters>\n expiresAt: Date\n deviceId: DeviceId | null\n sub: Sub | null\n code: Code | null\n}\n\nexport type RequestDataAuthorized = NonNullableKeys<\n RequestData,\n 'sub' | 'deviceId'\n>\n\nexport const isRequestDataAuthorized = (\n data: RequestData,\n): data is RequestDataAuthorized => data.sub !== null && data.deviceId !== null\n"]}
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const crypto_js_1 = require("../lib/util/crypto.js");
|
|
7
|
-
exports.REQUEST_ID_LENGTH = constants_js_1.REQUEST_ID_PREFIX.length + constants_js_1.REQUEST_ID_BYTES_LENGTH * 2; // hex encoding
|
|
8
|
-
exports.requestIdSchema = zod_1.z
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { REQUEST_ID_BYTES_LENGTH, REQUEST_ID_PREFIX } from '../constants.js';
|
|
3
|
+
import { randomHexId } from '../lib/util/crypto.js';
|
|
4
|
+
export const REQUEST_ID_LENGTH = REQUEST_ID_PREFIX.length + REQUEST_ID_BYTES_LENGTH * 2; // hex encoding
|
|
5
|
+
export const requestIdSchema = z
|
|
9
6
|
.string()
|
|
10
|
-
.length(
|
|
11
|
-
.refine((v) => v.startsWith(
|
|
7
|
+
.length(REQUEST_ID_LENGTH)
|
|
8
|
+
.refine((v) => v.startsWith(REQUEST_ID_PREFIX), {
|
|
12
9
|
message: `Invalid request ID format`,
|
|
13
10
|
});
|
|
14
|
-
const generateRequestId = async () => {
|
|
15
|
-
return `${
|
|
11
|
+
export const generateRequestId = async () => {
|
|
12
|
+
return `${REQUEST_ID_PREFIX}${await randomHexId(REQUEST_ID_BYTES_LENGTH)}`;
|
|
16
13
|
};
|
|
17
|
-
exports.generateRequestId = generateRequestId;
|
|
18
14
|
//# sourceMappingURL=request-id.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-id.js","sourceRoot":"","sources":["../../src/request/request-id.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"request-id.js","sourceRoot":"","sources":["../../src/request/request-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAEnD,MAAM,CAAC,MAAM,iBAAiB,GAC5B,iBAAiB,CAAC,MAAM,GAAG,uBAAuB,GAAG,CAAC,CAAA,CAAC,eAAe;AAExE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC;KAC7B,MAAM,EAAE;KACR,MAAM,CAAC,iBAAiB,CAAC;KACzB,MAAM,CACL,CAAC,CAAC,EAA+C,EAAE,CACjD,CAAC,CAAC,UAAU,CAAC,iBAAiB,CAAC,EACjC;IACE,OAAO,EAAE,2BAA2B;CACrC,CACF,CAAA;AAGH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,IAAwB,EAAE;IAC9D,OAAO,GAAG,iBAAiB,GAAG,MAAM,WAAW,CAAC,uBAAuB,CAAC,EAAE,CAAA;AAC5E,CAAC,CAAA","sourcesContent":["import { z } from 'zod'\nimport { REQUEST_ID_BYTES_LENGTH, REQUEST_ID_PREFIX } from '../constants.js'\nimport { randomHexId } from '../lib/util/crypto.js'\n\nexport const REQUEST_ID_LENGTH =\n REQUEST_ID_PREFIX.length + REQUEST_ID_BYTES_LENGTH * 2 // hex encoding\n\nexport const requestIdSchema = z\n .string()\n .length(REQUEST_ID_LENGTH)\n .refine(\n (v): v is `${typeof REQUEST_ID_PREFIX}${string}` =>\n v.startsWith(REQUEST_ID_PREFIX),\n {\n message: `Invalid request ID format`,\n },\n )\n\nexport type RequestId = z.infer<typeof requestIdSchema>\nexport const generateRequestId = async (): Promise<RequestId> => {\n return `${REQUEST_ID_PREFIX}${await randomHexId(REQUEST_ID_BYTES_LENGTH)}`\n}\n"]}
|
|
@@ -1,30 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const request_uri_js_1 = require("./request-uri.js");
|
|
20
|
-
class RequestManager {
|
|
21
|
-
store;
|
|
22
|
-
lexiconManager;
|
|
23
|
-
signer;
|
|
24
|
-
metadata;
|
|
25
|
-
hooks;
|
|
26
|
-
tokenMaxAge;
|
|
27
|
-
constructor(store, lexiconManager, signer, metadata, hooks, tokenMaxAge = constants_js_1.TOKEN_MAX_AGE) {
|
|
1
|
+
import { isAtprotoDid } from '@atproto/did';
|
|
2
|
+
import { LexResolverError } from '@atproto/lex-resolver';
|
|
3
|
+
import { isAtprotoOauthScope } from '@atproto/oauth-scopes';
|
|
4
|
+
import { isValidHandle } from '@atproto/syntax';
|
|
5
|
+
import { AUTHORIZATION_INACTIVITY_TIMEOUT, NODE_ENV, PAR_EXPIRES_IN, TOKEN_MAX_AGE, } from '../constants.js';
|
|
6
|
+
import { AccessDeniedError } from '../errors/access-denied-error.js';
|
|
7
|
+
import { AuthorizationError } from '../errors/authorization-error.js';
|
|
8
|
+
import { ConsentRequiredError } from '../errors/consent-required-error.js';
|
|
9
|
+
import { InvalidAuthorizationDetailsError } from '../errors/invalid-authorization-details-error.js';
|
|
10
|
+
import { InvalidGrantError } from '../errors/invalid-grant-error.js';
|
|
11
|
+
import { InvalidRequestError } from '../errors/invalid-request-error.js';
|
|
12
|
+
import { InvalidScopeError } from '../errors/invalid-scope-error.js';
|
|
13
|
+
import { generateCode } from './code.js';
|
|
14
|
+
import { isRequestDataAuthorized, } from './request-data.js';
|
|
15
|
+
import { generateRequestId } from './request-id.js';
|
|
16
|
+
import { decodeRequestUri, encodeRequestUri, } from './request-uri.js';
|
|
17
|
+
export class RequestManager {
|
|
18
|
+
constructor(store, lexiconManager, signer, metadata, hooks, tokenMaxAge = TOKEN_MAX_AGE) {
|
|
28
19
|
this.store = store;
|
|
29
20
|
this.lexiconManager = lexiconManager;
|
|
30
21
|
this.signer = signer;
|
|
@@ -42,8 +33,8 @@ class RequestManager {
|
|
|
42
33
|
clientAuth,
|
|
43
34
|
parameters,
|
|
44
35
|
});
|
|
45
|
-
const expiresAt = new Date(Date.now() +
|
|
46
|
-
const requestId = await
|
|
36
|
+
const expiresAt = new Date(Date.now() + PAR_EXPIRES_IN);
|
|
37
|
+
const requestId = await generateRequestId();
|
|
47
38
|
await this.store.createRequest(requestId, {
|
|
48
39
|
clientId: client.id,
|
|
49
40
|
clientAuth,
|
|
@@ -53,7 +44,7 @@ class RequestManager {
|
|
|
53
44
|
sub: null,
|
|
54
45
|
code: null,
|
|
55
46
|
});
|
|
56
|
-
const requestUri =
|
|
47
|
+
const requestUri = encodeRequestUri(requestId);
|
|
57
48
|
return { requestUri, expiresAt, parameters };
|
|
58
49
|
}
|
|
59
50
|
async validate(client, clientAuth, parameters) {
|
|
@@ -67,23 +58,23 @@ class RequestManager {
|
|
|
67
58
|
'nonce', // note that OIDC "nonce" is redundant with PKCE
|
|
68
59
|
]) {
|
|
69
60
|
if (parameters[k] !== undefined) {
|
|
70
|
-
throw new
|
|
61
|
+
throw new AuthorizationError(parameters, `Unsupported "${k}" parameter`);
|
|
71
62
|
}
|
|
72
63
|
}
|
|
73
64
|
// -----------------------
|
|
74
65
|
// Validate against server
|
|
75
66
|
// -----------------------
|
|
76
67
|
if (!this.metadata.response_types_supported?.includes(parameters.response_type)) {
|
|
77
|
-
throw new
|
|
68
|
+
throw new AuthorizationError(parameters, `Unsupported response_type "${parameters.response_type}"`, 'unsupported_response_type');
|
|
78
69
|
}
|
|
79
70
|
if (parameters.response_type === 'code' &&
|
|
80
71
|
!this.metadata.grant_types_supported?.includes('authorization_code')) {
|
|
81
|
-
throw new
|
|
72
|
+
throw new AuthorizationError(parameters, `Unsupported grant_type "authorization_code"`, 'invalid_request');
|
|
82
73
|
}
|
|
83
74
|
if (parameters.authorization_details) {
|
|
84
75
|
for (const detail of parameters.authorization_details) {
|
|
85
76
|
if (!this.metadata.authorization_details_types_supported?.includes(detail.type)) {
|
|
86
|
-
throw new
|
|
77
|
+
throw new InvalidAuthorizationDetailsError(parameters, `Unsupported "authorization_details" type "${detail.type}"`);
|
|
87
78
|
}
|
|
88
79
|
}
|
|
89
80
|
}
|
|
@@ -97,7 +88,7 @@ class RequestManager {
|
|
|
97
88
|
if (!parameters.redirect_uri) {
|
|
98
89
|
// Should already be ensured by client.validateRequest(). Adding here for
|
|
99
90
|
// clarity & extra safety.
|
|
100
|
-
throw new
|
|
91
|
+
throw new AuthorizationError(parameters, 'Missing "redirect_uri"');
|
|
101
92
|
}
|
|
102
93
|
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-10#section-1.4.1
|
|
103
94
|
// > The authorization server MAY fully or partially ignore the scope
|
|
@@ -113,7 +104,7 @@ class RequestManager {
|
|
|
113
104
|
// re-authenticate the user once the scopes are supported. This is due to
|
|
114
105
|
// the fact that the AS does not know how to properly display those scopes
|
|
115
106
|
// to the user, so it cannot properly ask for consent.
|
|
116
|
-
const scope = Array.from(scopes).filter(
|
|
107
|
+
const scope = Array.from(scopes).filter(isAtprotoOauthScope).join(' ') || undefined;
|
|
117
108
|
parameters = { ...parameters, scope };
|
|
118
109
|
if (parameters.code_challenge) {
|
|
119
110
|
switch (parameters.code_challenge_method) {
|
|
@@ -125,14 +116,14 @@ class RequestManager {
|
|
|
125
116
|
case 'S256':
|
|
126
117
|
break;
|
|
127
118
|
default: {
|
|
128
|
-
throw new
|
|
119
|
+
throw new AuthorizationError(parameters, `Unsupported code_challenge_method "${parameters.code_challenge_method}"`);
|
|
129
120
|
}
|
|
130
121
|
}
|
|
131
122
|
}
|
|
132
123
|
else {
|
|
133
124
|
if (parameters.code_challenge_method) {
|
|
134
125
|
// https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1
|
|
135
|
-
throw new
|
|
126
|
+
throw new AuthorizationError(parameters, 'code_challenge is required when code_challenge_method is provided');
|
|
136
127
|
}
|
|
137
128
|
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.2.1
|
|
138
129
|
//
|
|
@@ -148,22 +139,22 @@ class RequestManager {
|
|
|
148
139
|
//
|
|
149
140
|
// atproto does not implement the OpenID Connect nonce mechanism, so we
|
|
150
141
|
// require the use of PKCE for all clients.
|
|
151
|
-
throw new
|
|
142
|
+
throw new AuthorizationError(parameters, 'Use of PKCE is required');
|
|
152
143
|
}
|
|
153
144
|
// -----------------
|
|
154
145
|
// atproto extension
|
|
155
146
|
// -----------------
|
|
156
147
|
if (parameters.response_type !== 'code') {
|
|
157
|
-
throw new
|
|
148
|
+
throw new AuthorizationError(parameters, 'atproto only supports the "code" response_type');
|
|
158
149
|
}
|
|
159
150
|
if (!scopes.has('atproto')) {
|
|
160
|
-
throw new
|
|
151
|
+
throw new InvalidScopeError(parameters, 'The "atproto" scope is required');
|
|
161
152
|
}
|
|
162
153
|
else if (scopes.has('openid')) {
|
|
163
|
-
throw new
|
|
154
|
+
throw new InvalidScopeError(parameters, 'OpenID Connect is not compatible with atproto');
|
|
164
155
|
}
|
|
165
156
|
if (parameters.code_challenge_method !== 'S256') {
|
|
166
|
-
throw new
|
|
157
|
+
throw new AuthorizationError(parameters, 'atproto requires use of "S256" code_challenge_method');
|
|
167
158
|
}
|
|
168
159
|
// atproto extension: if the client is not trusted, and not authenticated,
|
|
169
160
|
// force users to consent to authorization requests. We do this to avoid
|
|
@@ -173,7 +164,7 @@ class RequestManager {
|
|
|
173
164
|
!client.info.isFirstParty &&
|
|
174
165
|
client.metadata.token_endpoint_auth_method === 'none') {
|
|
175
166
|
if (parameters.prompt === 'none') {
|
|
176
|
-
throw new
|
|
167
|
+
throw new ConsentRequiredError(parameters, 'Public clients are not allowed to use silent-sign-on');
|
|
177
168
|
}
|
|
178
169
|
// force "consent" for unauthenticated third party clients, unless they
|
|
179
170
|
// are trying to create accounts:
|
|
@@ -185,8 +176,8 @@ class RequestManager {
|
|
|
185
176
|
// @NOTE we to allow invalid case here, which is not spec'd anywhere.
|
|
186
177
|
const hint = parameters.login_hint?.toLowerCase();
|
|
187
178
|
if (hint) {
|
|
188
|
-
if (!
|
|
189
|
-
throw new
|
|
179
|
+
if (!isAtprotoDid(hint) && !isValidHandle(hint)) {
|
|
180
|
+
throw new AuthorizationError(parameters, `Invalid login_hint "${hint}"`);
|
|
190
181
|
}
|
|
191
182
|
// @TODO: ensure that the account actually exists on this server (there is
|
|
192
183
|
// no point in showing the UI to the user if the account does not exist).
|
|
@@ -201,8 +192,8 @@ class RequestManager {
|
|
|
201
192
|
}
|
|
202
193
|
catch (err) {
|
|
203
194
|
// Parse expected errors
|
|
204
|
-
if (err instanceof
|
|
205
|
-
throw new
|
|
195
|
+
if (err instanceof LexResolverError) {
|
|
196
|
+
throw new AuthorizationError(parameters, err.message, 'invalid_scope', err);
|
|
206
197
|
}
|
|
207
198
|
// Unexpected error
|
|
208
199
|
throw err;
|
|
@@ -217,37 +208,37 @@ class RequestManager {
|
|
|
217
208
|
* Returns `undefined` when no such request exists.
|
|
218
209
|
*/
|
|
219
210
|
async peekClientId(requestUri) {
|
|
220
|
-
const requestId =
|
|
211
|
+
const requestId = decodeRequestUri(requestUri);
|
|
221
212
|
const data = await this.store.readRequest(requestId);
|
|
222
213
|
return data?.clientId;
|
|
223
214
|
}
|
|
224
215
|
async get(requestUri, deviceId, clientId) {
|
|
225
|
-
const requestId =
|
|
216
|
+
const requestId = decodeRequestUri(requestUri);
|
|
226
217
|
const data = await this.store.readRequest(requestId);
|
|
227
218
|
if (!data)
|
|
228
|
-
throw new
|
|
219
|
+
throw new InvalidRequestError('Unknown request_uri');
|
|
229
220
|
const updates = {};
|
|
230
221
|
try {
|
|
231
222
|
if (data.sub || data.code) {
|
|
232
223
|
// If an account was linked to the request, the next step is to exchange
|
|
233
224
|
// the code for a token.
|
|
234
|
-
throw new
|
|
225
|
+
throw new AccessDeniedError(data.parameters, 'This request was already authorized');
|
|
235
226
|
}
|
|
236
227
|
if (data.expiresAt < new Date()) {
|
|
237
|
-
throw new
|
|
228
|
+
throw new AccessDeniedError(data.parameters, 'This request has expired');
|
|
238
229
|
}
|
|
239
230
|
else {
|
|
240
|
-
updates.expiresAt = new Date(Date.now() +
|
|
231
|
+
updates.expiresAt = new Date(Date.now() + AUTHORIZATION_INACTIVITY_TIMEOUT);
|
|
241
232
|
}
|
|
242
233
|
if (clientId != null && data.clientId !== clientId) {
|
|
243
|
-
throw new
|
|
234
|
+
throw new AccessDeniedError(data.parameters, 'This request was initiated for another client');
|
|
244
235
|
}
|
|
245
236
|
if (deviceId != null) {
|
|
246
237
|
if (!data.deviceId) {
|
|
247
238
|
updates.deviceId = deviceId;
|
|
248
239
|
}
|
|
249
240
|
else if (data.deviceId !== deviceId) {
|
|
250
|
-
throw new
|
|
241
|
+
throw new AccessDeniedError(data.parameters, 'This request was initiated from another device');
|
|
251
242
|
}
|
|
252
243
|
}
|
|
253
244
|
}
|
|
@@ -266,23 +257,23 @@ class RequestManager {
|
|
|
266
257
|
};
|
|
267
258
|
}
|
|
268
259
|
async setAuthorized(requestUri, client, account, deviceId, deviceMetadata, scopeOverride) {
|
|
269
|
-
const requestId =
|
|
260
|
+
const requestId = decodeRequestUri(requestUri);
|
|
270
261
|
const data = await this.store.readRequest(requestId);
|
|
271
262
|
if (!data)
|
|
272
|
-
throw new
|
|
263
|
+
throw new InvalidRequestError('Unknown request_uri');
|
|
273
264
|
let { parameters } = data;
|
|
274
265
|
try {
|
|
275
266
|
if (data.expiresAt < new Date()) {
|
|
276
|
-
throw new
|
|
267
|
+
throw new AccessDeniedError(parameters, 'This request has expired');
|
|
277
268
|
}
|
|
278
269
|
if (!data.deviceId) {
|
|
279
|
-
throw new
|
|
270
|
+
throw new AccessDeniedError(parameters, 'This request was not initiated');
|
|
280
271
|
}
|
|
281
272
|
if (data.deviceId !== deviceId) {
|
|
282
|
-
throw new
|
|
273
|
+
throw new AccessDeniedError(parameters, 'This request was initiated from another device');
|
|
283
274
|
}
|
|
284
275
|
if (data.sub || data.code) {
|
|
285
|
-
throw new
|
|
276
|
+
throw new AccessDeniedError(parameters, 'This request was already authorized');
|
|
286
277
|
}
|
|
287
278
|
// If a new scope value is provided, update the parameters by ensuring
|
|
288
279
|
// that every existing scope in the parameters is also present in the
|
|
@@ -295,18 +286,18 @@ class RequestManager {
|
|
|
295
286
|
const newScopes = existingScopes?.filter((s) => allowedScopes.has(s));
|
|
296
287
|
// Validate: make sure the new scopes are valid
|
|
297
288
|
if (!newScopes?.includes('atproto')) {
|
|
298
|
-
throw new
|
|
289
|
+
throw new AccessDeniedError(parameters, 'The "atproto" scope is required');
|
|
299
290
|
}
|
|
300
291
|
parameters = { ...parameters, scope: newScopes.join(' ') };
|
|
301
292
|
}
|
|
302
293
|
// Only response_type=code is supported
|
|
303
|
-
const code = await
|
|
294
|
+
const code = await generateCode();
|
|
304
295
|
// Bind the request to the account, preventing it from being used again.
|
|
305
296
|
await this.store.updateRequest(requestId, {
|
|
306
297
|
sub: account.sub,
|
|
307
298
|
code,
|
|
308
299
|
// Allow the client to exchange the code for a token within the next 60 seconds.
|
|
309
|
-
expiresAt: new Date(Date.now() +
|
|
300
|
+
expiresAt: new Date(Date.now() + AUTHORIZATION_INACTIVITY_TIMEOUT),
|
|
310
301
|
parameters,
|
|
311
302
|
});
|
|
312
303
|
await this.hooks.onAuthorized?.call(null, {
|
|
@@ -331,29 +322,28 @@ class RequestManager {
|
|
|
331
322
|
async consumeCode(code) {
|
|
332
323
|
const result = await this.store.consumeRequestCode(code);
|
|
333
324
|
if (!result)
|
|
334
|
-
throw new
|
|
325
|
+
throw new InvalidGrantError('Invalid code');
|
|
335
326
|
const { requestId, data } = result;
|
|
336
327
|
// Fool-proofing the store implementation against code replay attacks (in
|
|
337
328
|
// case consumeRequestCode() does not delete the request).
|
|
338
|
-
if (
|
|
329
|
+
if (NODE_ENV !== 'production') {
|
|
339
330
|
const result = await this.store.readRequest(requestId);
|
|
340
331
|
if (result) {
|
|
341
332
|
throw new Error('Invalid store implementation: request not deleted');
|
|
342
333
|
}
|
|
343
334
|
}
|
|
344
|
-
if (!
|
|
335
|
+
if (!isRequestDataAuthorized(data) || data.code !== code) {
|
|
345
336
|
// Should never happen: maybe the store implementation is faulty ?
|
|
346
337
|
throw new Error('Unexpected request state');
|
|
347
338
|
}
|
|
348
339
|
if (data.expiresAt < new Date()) {
|
|
349
|
-
throw new
|
|
340
|
+
throw new InvalidGrantError('This code has expired');
|
|
350
341
|
}
|
|
351
342
|
return data;
|
|
352
343
|
}
|
|
353
344
|
async delete(requestUri) {
|
|
354
|
-
const requestId =
|
|
345
|
+
const requestId = decodeRequestUri(requestUri);
|
|
355
346
|
await this.store.deleteRequest(requestId);
|
|
356
347
|
}
|
|
357
348
|
}
|
|
358
|
-
exports.RequestManager = RequestManager;
|
|
359
349
|
//# sourceMappingURL=request-manager.js.map
|