@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,28 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const send_redirect_js_1 = require("./assets/send-redirect.js");
|
|
15
|
-
const create_api_middleware_js_1 = require("./create-api-middleware.js");
|
|
16
|
-
function createAuthorizationPageMiddleware(server, { onError }) {
|
|
1
|
+
import { oauthAuthorizationRequestQuerySchema, } from '@atproto/oauth-types';
|
|
2
|
+
import { AuthorizationError } from '../errors/authorization-error.js';
|
|
3
|
+
import { InvalidRequestError } from '../errors/invalid-request-error.js';
|
|
4
|
+
import { Router, getCookie, setCookie, validateFetchDest, validateFetchMode, validateFetchSite, validateOrigin, validateReferrer, } from '../lib/http/index.js';
|
|
5
|
+
import { formatError } from '../lib/util/error.js';
|
|
6
|
+
import { writeFormRedirect } from '../lib/write-form-redirect.js';
|
|
7
|
+
import { parseRequestUri, requestUriSchema } from '../request/request-uri.js';
|
|
8
|
+
import { sendAuthorizePageFactory } from './assets/send-authorization-page.js';
|
|
9
|
+
import { sendCookieErrorPageFactory } from './assets/send-cookie-error-page.js';
|
|
10
|
+
import { sendErrorPageFactory } from './assets/send-error-page.js';
|
|
11
|
+
import { sendAuthorizationResultRedirect, sendRedirect, } from './assets/send-redirect.js';
|
|
12
|
+
import { parseRedirectUrl } from './create-api-middleware.js';
|
|
13
|
+
export function createAuthorizationPageMiddleware(server, { onError }) {
|
|
17
14
|
const issuerUrl = new URL(server.issuer);
|
|
18
15
|
const issuerOrigin = issuerUrl.origin;
|
|
19
16
|
const securityOptions = {
|
|
20
17
|
hsts: issuerUrl.protocol === 'http:' ? false : undefined,
|
|
21
18
|
};
|
|
22
|
-
const sendAuthorizePage =
|
|
23
|
-
const sendErrorPage =
|
|
24
|
-
const sendCookieErrorPage =
|
|
25
|
-
const router = new
|
|
19
|
+
const sendAuthorizePage = sendAuthorizePageFactory(server.customization, securityOptions);
|
|
20
|
+
const sendErrorPage = sendErrorPageFactory(server.customization, securityOptions);
|
|
21
|
+
const sendCookieErrorPage = sendCookieErrorPageFactory(server.customization, securityOptions);
|
|
22
|
+
const router = new Router(issuerUrl);
|
|
26
23
|
router.get('/oauth/authorize', withErrorHandler(async function (req, res) {
|
|
27
24
|
res.setHeader('Cache-Control', 'no-store');
|
|
28
25
|
res.setHeader('Pragma', 'no-cache');
|
|
@@ -30,10 +27,10 @@ function createAuthorizationPageMiddleware(server, { onError }) {
|
|
|
30
27
|
// well as refreshing the authorization page).
|
|
31
28
|
// @TODO Consider removing this altogether to allow hosting PDS and app on
|
|
32
29
|
// the same site but different origins (different subdomains).
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
validateFetchSite(req, ['same-origin', 'cross-site', 'none']);
|
|
31
|
+
validateFetchMode(req, ['navigate']);
|
|
32
|
+
validateFetchDest(req, ['document']);
|
|
33
|
+
validateOrigin(req, issuerOrigin);
|
|
37
34
|
// Do not perform any of the following logic if the request is invalid
|
|
38
35
|
const query = parseOAuthAuthorizationRequestQuery(this.url);
|
|
39
36
|
// @NOTE For some reason, even when loaded through a
|
|
@@ -48,7 +45,7 @@ function createAuthorizationPageMiddleware(server, { onError }) {
|
|
|
48
45
|
// Only for iOS users
|
|
49
46
|
req.headers['user-agent']?.includes('iPhone OS') &&
|
|
50
47
|
// Disabled if the user already passed the test, which means their browser preserves cookies on redirect
|
|
51
|
-
!(
|
|
48
|
+
!(getCookie(req, 'cookie-test') === 'succeeded') &&
|
|
52
49
|
// Disabled if the user already has a session
|
|
53
50
|
!(await server.deviceManager.hasSession(req))) {
|
|
54
51
|
// @TODO Another possible solution would be to avoid relying on cookies if we
|
|
@@ -61,12 +58,12 @@ function createAuthorizationPageMiddleware(server, { onError }) {
|
|
|
61
58
|
// preserves cookies by redirecting back to ourselves
|
|
62
59
|
if (!this.url.searchParams.has('redirect-test')) {
|
|
63
60
|
// 2) Set a testing cookie
|
|
64
|
-
|
|
61
|
+
setCookie(res, 'cookie-test', 'testing', {
|
|
65
62
|
sameSite: 'lax',
|
|
66
63
|
httpOnly: true,
|
|
67
64
|
});
|
|
68
65
|
// 3) And send an auto-submit form redirecting back to ourselves
|
|
69
|
-
return
|
|
66
|
+
return writeFormRedirect(res, 'get', this.url.href,
|
|
70
67
|
// 4) We add an extra query parameter to trigger the test logic after
|
|
71
68
|
// the redirect occurred.
|
|
72
69
|
[...this.url.searchParams, ['redirect-test', '1']], securityOptions);
|
|
@@ -74,11 +71,11 @@ function createAuthorizationPageMiddleware(server, { onError }) {
|
|
|
74
71
|
else {
|
|
75
72
|
// 5) We just got redirected back to ourselves. Verify that the
|
|
76
73
|
// browser preserved cookies during the redirect
|
|
77
|
-
if (
|
|
74
|
+
if (getCookie(req, 'cookie-test')) {
|
|
78
75
|
// 6) Success! The browser preserved cookies. Proceed with the
|
|
79
76
|
// normal authorization flow.
|
|
80
77
|
// 7) Set a long lasting cookie to skip the test next time
|
|
81
|
-
|
|
78
|
+
setCookie(res, 'cookie-test', 'succeeded', {
|
|
82
79
|
sameSite: 'lax',
|
|
83
80
|
maxAge: 31 * 24 * 60 * 60,
|
|
84
81
|
httpOnly: true,
|
|
@@ -91,7 +88,7 @@ function createAuthorizationPageMiddleware(server, { onError }) {
|
|
|
91
88
|
// 8) Show an error page to the user explaining the situation
|
|
92
89
|
// Give the browser another chance to save cookies after the use
|
|
93
90
|
// pressed "Continue"
|
|
94
|
-
|
|
91
|
+
setCookie(res, 'cookie-test', 'testing', {
|
|
95
92
|
sameSite: 'lax',
|
|
96
93
|
httpOnly: true,
|
|
97
94
|
});
|
|
@@ -112,20 +109,20 @@ function createAuthorizationPageMiddleware(server, { onError }) {
|
|
|
112
109
|
// back to the client with the error parameters.
|
|
113
110
|
if ('request_uri' in query) {
|
|
114
111
|
// Load and delete the authorization request
|
|
115
|
-
const requestUri =
|
|
112
|
+
const requestUri = parseRequestUri(query.request_uri, {
|
|
116
113
|
path: ['query', 'request_uri'],
|
|
117
114
|
});
|
|
118
115
|
const data = await server.requestManager.get(requestUri, undefined, query.client_id);
|
|
119
116
|
await server.requestManager.delete(requestUri);
|
|
120
|
-
throw new
|
|
117
|
+
throw new AuthorizationError(data.parameters, message);
|
|
121
118
|
}
|
|
122
119
|
else if ('request' in query) {
|
|
123
120
|
const client = await server.clientManager.getClient(query.client_id);
|
|
124
121
|
const parameters = await server.decodeJAR(client, query);
|
|
125
|
-
throw new
|
|
122
|
+
throw new AuthorizationError(parameters, message);
|
|
126
123
|
}
|
|
127
124
|
else {
|
|
128
|
-
throw new
|
|
125
|
+
throw new AuthorizationError(query, message);
|
|
129
126
|
}
|
|
130
127
|
}
|
|
131
128
|
}
|
|
@@ -135,7 +132,7 @@ function createAuthorizationPageMiddleware(server, { onError }) {
|
|
|
135
132
|
const device = await server.deviceManager.load(req, res);
|
|
136
133
|
const result = await server.authorize(query, device);
|
|
137
134
|
if ('redirect' in result) {
|
|
138
|
-
return
|
|
135
|
+
return sendAuthorizationResultRedirect(res, result, securityOptions);
|
|
139
136
|
}
|
|
140
137
|
else {
|
|
141
138
|
return sendAuthorizePage(req, res, result);
|
|
@@ -147,17 +144,17 @@ function createAuthorizationPageMiddleware(server, { onError }) {
|
|
|
147
144
|
// implement it here to avoid duplicating the logic.
|
|
148
145
|
router.get('/oauth/authorize/redirect', withErrorHandler(async function (req, res) {
|
|
149
146
|
// Ensure we come from the authorization page
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const referrer =
|
|
147
|
+
validateFetchSite(req, ['same-origin']);
|
|
148
|
+
validateFetchMode(req, ['navigate']);
|
|
149
|
+
validateFetchDest(req, ['document']);
|
|
150
|
+
validateOrigin(req, issuerOrigin);
|
|
151
|
+
const referrer = validateReferrer(req, {
|
|
155
152
|
origin: issuerOrigin,
|
|
156
153
|
pathname: '/oauth/authorize',
|
|
157
154
|
});
|
|
158
155
|
// Ensure we are coming from the authorization page
|
|
159
|
-
|
|
160
|
-
return
|
|
156
|
+
requestUriSchema.parse(referrer.searchParams.get('request_uri'));
|
|
157
|
+
return sendRedirect(res, parseRedirectUrl(this.url), securityOptions);
|
|
161
158
|
}));
|
|
162
159
|
return router.buildMiddleware();
|
|
163
160
|
function withErrorHandler(handler) {
|
|
@@ -168,8 +165,8 @@ function createAuthorizationPageMiddleware(server, { onError }) {
|
|
|
168
165
|
catch (err) {
|
|
169
166
|
onError?.(req, res, err, `Authorization Request Error`);
|
|
170
167
|
if (!res.headersSent) {
|
|
171
|
-
if (err instanceof
|
|
172
|
-
return
|
|
168
|
+
if (err instanceof AuthorizationError) {
|
|
169
|
+
return sendAuthorizationResultRedirect(res, {
|
|
173
170
|
issuer: server.issuer,
|
|
174
171
|
parameters: err.parameters,
|
|
175
172
|
redirect: err.toJSON(),
|
|
@@ -188,13 +185,13 @@ function createAuthorizationPageMiddleware(server, { onError }) {
|
|
|
188
185
|
}
|
|
189
186
|
function parseOAuthAuthorizationRequestQuery(url) {
|
|
190
187
|
const query = Object.fromEntries(url.searchParams);
|
|
191
|
-
const result =
|
|
188
|
+
const result = oauthAuthorizationRequestQuerySchema.safeParse(query, {
|
|
192
189
|
path: ['query'],
|
|
193
190
|
});
|
|
194
191
|
if (!result.success) {
|
|
195
192
|
const message = 'Invalid request parameters';
|
|
196
193
|
const err = result.error;
|
|
197
|
-
throw new
|
|
194
|
+
throw new InvalidRequestError(formatError(err, message), err);
|
|
198
195
|
}
|
|
199
196
|
return result.data;
|
|
200
197
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-authorization-page-middleware.js","sourceRoot":"","sources":["../../src/router/create-authorization-page-middleware.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport {\n OAuthAuthorizationRequestQuery,\n oauthAuthorizationRequestQuerySchema,\n} from '@atproto/oauth-types'\nimport { AuthorizationError } from '../errors/authorization-error.js'\nimport { InvalidRequestError } from '../errors/invalid-request-error.js'\nimport {\n Middleware,\n Router,\n RouterCtx,\n getCookie,\n setCookie,\n validateFetchDest,\n validateFetchMode,\n validateFetchSite,\n validateOrigin,\n validateReferrer,\n} from '../lib/http/index.js'\nimport { SecurityHeadersOptions } from '../lib/http/security-headers.js'\nimport { formatError } from '../lib/util/error.js'\nimport type { Awaitable } from '../lib/util/type.js'\nimport { writeFormRedirect } from '../lib/write-form-redirect.js'\nimport type { OAuthProvider } from '../oauth-provider.js'\nimport { parseRequestUri, requestUriSchema } from '../request/request-uri.js'\nimport { sendAuthorizePageFactory } from './assets/send-authorization-page.js'\nimport { sendCookieErrorPageFactory } from './assets/send-cookie-error-page.js'\nimport { sendErrorPageFactory } from './assets/send-error-page.js'\nimport {\n sendAuthorizationResultRedirect,\n sendRedirect,\n} from './assets/send-redirect.js'\nimport { parseRedirectUrl } from './create-api-middleware.js'\nimport type { MiddlewareOptions } from './middleware-options.js'\n\nexport function createAuthorizationPageMiddleware<\n Ctx extends object | void = void,\n Req extends IncomingMessage = IncomingMessage,\n Res extends ServerResponse = ServerResponse,\n>(\n server: OAuthProvider,\n { onError }: MiddlewareOptions<Req, Res>,\n): Middleware<Ctx, Req, Res> {\n const issuerUrl = new URL(server.issuer)\n const issuerOrigin = issuerUrl.origin\n\n const securityOptions: SecurityHeadersOptions = {\n hsts: issuerUrl.protocol === 'http:' ? false : undefined,\n }\n\n const sendAuthorizePage = sendAuthorizePageFactory(\n server.customization,\n securityOptions,\n )\n const sendErrorPage = sendErrorPageFactory(\n server.customization,\n securityOptions,\n )\n const sendCookieErrorPage = sendCookieErrorPageFactory(\n server.customization,\n securityOptions,\n )\n\n const router = new Router<Ctx, Req, Res>(issuerUrl)\n\n router.get(\n '/oauth/authorize',\n withErrorHandler(async function (req, res) {\n res.setHeader('Cache-Control', 'no-store')\n res.setHeader('Pragma', 'no-cache')\n\n // \"same-origin\" is required to support the redirect test logic below (as\n // well as refreshing the authorization page).\n\n // @TODO Consider removing this altogether to allow hosting PDS and app on\n // the same site but different origins (different subdomains).\n validateFetchSite(req, ['same-origin', 'cross-site', 'none'])\n validateFetchMode(req, ['navigate'])\n validateFetchDest(req, ['document'])\n validateOrigin(req, issuerOrigin)\n\n // Do not perform any of the following logic if the request is invalid\n const query = parseOAuthAuthorizationRequestQuery(this.url)\n\n // @NOTE For some reason, even when loaded through a\n // ASWebAuthenticationSession, iOS will sometimes fail to properly save\n // cookies set during the rendering of the page. When this happens, the\n // authorization page logic, which relies on cookies to maintain the session,\n // will fail. To work around this, we perform an initial redirect to ourselves\n // using a form GET submit, in an attempt to verify if the browser saves\n // cookies on redirect or not. If it does, we proceed as normal. If it\n // doesn't, we redirect the user back to the client with an error message.\n if (\n // Only for iOS users\n req.headers['user-agent']?.includes('iPhone OS') &&\n // Disabled if the user already passed the test, which means their browser preserves cookies on redirect\n !(getCookie(req, 'cookie-test') === 'succeeded') &&\n // Disabled if the user already has a session\n !(await server.deviceManager.hasSession(req))\n ) {\n // @TODO Another possible solution would be to avoid relying on cookies if we\n // detect that they are not being preserved. This would mean that preserving\n // sessions (SSO) would not be possible for browsers that don't preserve\n // cookies on redirect, but at least the authorization request could still be\n // completed. This was not implemented yet due to the extra complexity\n // involved in supporting this.\n\n // 1) When the user first comes here, we will test if their browser\n // preserves cookies by redirecting back to ourselves\n if (!this.url.searchParams.has('redirect-test')) {\n // 2) Set a testing cookie\n setCookie(res, 'cookie-test', 'testing', {\n sameSite: 'lax',\n httpOnly: true,\n })\n\n // 3) And send an auto-submit form redirecting back to ourselves\n return writeFormRedirect(\n res,\n 'get',\n this.url.href,\n // 4) We add an extra query parameter to trigger the test logic after\n // the redirect occurred.\n [...this.url.searchParams, ['redirect-test', '1']],\n securityOptions,\n )\n } else {\n // 5) We just got redirected back to ourselves. Verify that the\n // browser preserved cookies during the redirect\n if (getCookie(req, 'cookie-test')) {\n // 6) Success! The browser preserved cookies. Proceed with the\n // normal authorization flow.\n\n // 7) Set a long lasting cookie to skip the test next time\n setCookie(res, 'cookie-test', 'succeeded', {\n sameSite: 'lax',\n maxAge: 31 * 24 * 60 * 60,\n httpOnly: true,\n })\n } else {\n // The browser did NOT preserve cookies. We have to abort the\n // authorization request.\n\n if (this.url.searchParams.get('redirect-test') === '1') {\n // 8) Show an error page to the user explaining the situation\n\n // Give the browser another chance to save cookies after the use\n // pressed \"Continue\"\n setCookie(res, 'cookie-test', 'testing', {\n sameSite: 'lax',\n httpOnly: true,\n })\n\n // Make sure next time we reach the other branch and redirect back\n // to the client\n const continueUrl = new URL(this.url.href)\n continueUrl.searchParams.set('redirect-test', '2')\n return sendCookieErrorPage(req, res, { continueUrl })\n } else {\n // 9) Once the use acknowledges the error, redirect them back to\n // the client with an error message.\n\n // Allow the client to understand what happened (the `error`\n // response parameter value is constrained by the OAuth2 spec)\n const message = 'ERR_COOKIES_UNSUPPORTED'\n\n // @NOTE AuthorizationError thrown here will be caught by the\n // error handler middleware defined below, and cause a redirect\n // back to the client with the error parameters.\n if ('request_uri' in query) {\n // Load and delete the authorization request\n const requestUri = parseRequestUri(query.request_uri, {\n path: ['query', 'request_uri'],\n })\n const data = await server.requestManager.get(\n requestUri,\n undefined,\n query.client_id,\n )\n await server.requestManager.delete(requestUri)\n throw new AuthorizationError(data.parameters, message)\n } else if ('request' in query) {\n const client = await server.clientManager.getClient(\n query.client_id,\n )\n const parameters = await server.decodeJAR(client, query)\n throw new AuthorizationError(parameters, message)\n } else {\n throw new AuthorizationError(query, message)\n }\n }\n }\n }\n }\n\n // Normal authorization flow\n const device = await server.deviceManager.load(req, res)\n\n const result = await server.authorize(query, device)\n\n if ('redirect' in result) {\n return sendAuthorizationResultRedirect(res, result, securityOptions)\n } else {\n return sendAuthorizePage(req, res, result)\n }\n }),\n )\n\n // This is a private endpoint that will be called by the user after the\n // authorization request was either approved or denied. The logic performed\n // here **could** be performed directly in the frontend. We decided to\n // implement it here to avoid duplicating the logic.\n router.get(\n '/oauth/authorize/redirect',\n withErrorHandler(async function (req, res) {\n // Ensure we come from the authorization page\n validateFetchSite(req, ['same-origin'])\n validateFetchMode(req, ['navigate'])\n validateFetchDest(req, ['document'])\n validateOrigin(req, issuerOrigin)\n\n const referrer = validateReferrer(req, {\n origin: issuerOrigin,\n pathname: '/oauth/authorize',\n })\n\n // Ensure we are coming from the authorization page\n requestUriSchema.parse(referrer.searchParams.get('request_uri'))\n\n return sendRedirect(res, parseRedirectUrl(this.url), securityOptions)\n }),\n )\n\n return router.buildMiddleware()\n\n function withErrorHandler<T extends RouterCtx>(\n handler: (this: T, req: Req, res: Res) => Awaitable<void>,\n ): Middleware<T, Req, Res> {\n return async function (req, res) {\n try {\n await handler.call(this, req, res)\n } catch (err) {\n onError?.(req, res, err, `Authorization Request Error`)\n\n if (!res.headersSent) {\n if (err instanceof AuthorizationError) {\n return sendAuthorizationResultRedirect(\n res,\n {\n issuer: server.issuer,\n parameters: err.parameters,\n redirect: err.toJSON(),\n },\n securityOptions,\n )\n } else {\n return sendErrorPage(req, res, err)\n }\n } else if (!res.destroyed) {\n res.end()\n }\n }\n }\n }\n}\n\nfunction parseOAuthAuthorizationRequestQuery(\n url: URL,\n): OAuthAuthorizationRequestQuery {\n const query = Object.fromEntries(url.searchParams)\n const result = oauthAuthorizationRequestQuerySchema.safeParse(query, {\n path: ['query'],\n })\n\n if (!result.success) {\n const message = 'Invalid request parameters'\n const err = result.error\n throw new InvalidRequestError(formatError(err, message), err)\n }\n\n return result.data\n}\n"]}
|
|
1
|
+
{"version":3,"file":"create-authorization-page-middleware.js","sourceRoot":"","sources":["../../src/router/create-authorization-page-middleware.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport {\n OAuthAuthorizationRequestQuery,\n oauthAuthorizationRequestQuerySchema,\n} from '@atproto/oauth-types'\nimport { AuthorizationError } from '../errors/authorization-error.js'\nimport { InvalidRequestError } from '../errors/invalid-request-error.js'\nimport {\n Middleware,\n Router,\n RouterCtx,\n getCookie,\n setCookie,\n validateFetchDest,\n validateFetchMode,\n validateFetchSite,\n validateOrigin,\n validateReferrer,\n} from '../lib/http/index.js'\nimport { SecurityHeadersOptions } from '../lib/http/security-headers.js'\nimport { formatError } from '../lib/util/error.js'\nimport type { Awaitable } from '../lib/util/type.js'\nimport { writeFormRedirect } from '../lib/write-form-redirect.js'\nimport type { OAuthProvider } from '../oauth-provider.js'\nimport { parseRequestUri, requestUriSchema } from '../request/request-uri.js'\nimport { sendAuthorizePageFactory } from './assets/send-authorization-page.js'\nimport { sendCookieErrorPageFactory } from './assets/send-cookie-error-page.js'\nimport { sendErrorPageFactory } from './assets/send-error-page.js'\nimport {\n sendAuthorizationResultRedirect,\n sendRedirect,\n} from './assets/send-redirect.js'\nimport { parseRedirectUrl } from './create-api-middleware.js'\nimport type { MiddlewareOptions } from './middleware-options.js'\n\nexport function createAuthorizationPageMiddleware<\n Ctx extends object | void = void,\n Req extends IncomingMessage = IncomingMessage,\n Res extends ServerResponse = ServerResponse,\n>(\n server: OAuthProvider,\n { onError }: MiddlewareOptions<Req, Res>,\n): Middleware<Ctx, Req, Res> {\n const issuerUrl = new URL(server.issuer)\n const issuerOrigin = issuerUrl.origin\n\n const securityOptions: SecurityHeadersOptions = {\n hsts: issuerUrl.protocol === 'http:' ? false : undefined,\n }\n\n const sendAuthorizePage = sendAuthorizePageFactory(\n server.customization,\n securityOptions,\n )\n const sendErrorPage = sendErrorPageFactory(\n server.customization,\n securityOptions,\n )\n const sendCookieErrorPage = sendCookieErrorPageFactory(\n server.customization,\n securityOptions,\n )\n\n const router = new Router<Ctx, Req, Res>(issuerUrl)\n\n router.get(\n '/oauth/authorize',\n withErrorHandler(async function (req, res) {\n res.setHeader('Cache-Control', 'no-store')\n res.setHeader('Pragma', 'no-cache')\n\n // \"same-origin\" is required to support the redirect test logic below (as\n // well as refreshing the authorization page).\n\n // @TODO Consider removing this altogether to allow hosting PDS and app on\n // the same site but different origins (different subdomains).\n validateFetchSite(req, ['same-origin', 'cross-site', 'none'])\n validateFetchMode(req, ['navigate'])\n validateFetchDest(req, ['document'])\n validateOrigin(req, issuerOrigin)\n\n // Do not perform any of the following logic if the request is invalid\n const query = parseOAuthAuthorizationRequestQuery(this.url)\n\n // @NOTE For some reason, even when loaded through a\n // ASWebAuthenticationSession, iOS will sometimes fail to properly save\n // cookies set during the rendering of the page. When this happens, the\n // authorization page logic, which relies on cookies to maintain the session,\n // will fail. To work around this, we perform an initial redirect to ourselves\n // using a form GET submit, in an attempt to verify if the browser saves\n // cookies on redirect or not. If it does, we proceed as normal. If it\n // doesn't, we redirect the user back to the client with an error message.\n if (\n // Only for iOS users\n req.headers['user-agent']?.includes('iPhone OS') &&\n // Disabled if the user already passed the test, which means their browser preserves cookies on redirect\n !(getCookie(req, 'cookie-test') === 'succeeded') &&\n // Disabled if the user already has a session\n !(await server.deviceManager.hasSession(req))\n ) {\n // @TODO Another possible solution would be to avoid relying on cookies if we\n // detect that they are not being preserved. This would mean that preserving\n // sessions (SSO) would not be possible for browsers that don't preserve\n // cookies on redirect, but at least the authorization request could still be\n // completed. This was not implemented yet due to the extra complexity\n // involved in supporting this.\n\n // 1) When the user first comes here, we will test if their browser\n // preserves cookies by redirecting back to ourselves\n if (!this.url.searchParams.has('redirect-test')) {\n // 2) Set a testing cookie\n setCookie(res, 'cookie-test', 'testing', {\n sameSite: 'lax',\n httpOnly: true,\n })\n\n // 3) And send an auto-submit form redirecting back to ourselves\n return writeFormRedirect(\n res,\n 'get',\n this.url.href,\n // 4) We add an extra query parameter to trigger the test logic after\n // the redirect occurred.\n [...this.url.searchParams, ['redirect-test', '1']],\n securityOptions,\n )\n } else {\n // 5) We just got redirected back to ourselves. Verify that the\n // browser preserved cookies during the redirect\n if (getCookie(req, 'cookie-test')) {\n // 6) Success! The browser preserved cookies. Proceed with the\n // normal authorization flow.\n\n // 7) Set a long lasting cookie to skip the test next time\n setCookie(res, 'cookie-test', 'succeeded', {\n sameSite: 'lax',\n maxAge: 31 * 24 * 60 * 60,\n httpOnly: true,\n })\n } else {\n // The browser did NOT preserve cookies. We have to abort the\n // authorization request.\n\n if (this.url.searchParams.get('redirect-test') === '1') {\n // 8) Show an error page to the user explaining the situation\n\n // Give the browser another chance to save cookies after the use\n // pressed \"Continue\"\n setCookie(res, 'cookie-test', 'testing', {\n sameSite: 'lax',\n httpOnly: true,\n })\n\n // Make sure next time we reach the other branch and redirect back\n // to the client\n const continueUrl = new URL(this.url.href)\n continueUrl.searchParams.set('redirect-test', '2')\n return sendCookieErrorPage(req, res, { continueUrl })\n } else {\n // 9) Once the use acknowledges the error, redirect them back to\n // the client with an error message.\n\n // Allow the client to understand what happened (the `error`\n // response parameter value is constrained by the OAuth2 spec)\n const message = 'ERR_COOKIES_UNSUPPORTED'\n\n // @NOTE AuthorizationError thrown here will be caught by the\n // error handler middleware defined below, and cause a redirect\n // back to the client with the error parameters.\n if ('request_uri' in query) {\n // Load and delete the authorization request\n const requestUri = parseRequestUri(query.request_uri, {\n path: ['query', 'request_uri'],\n })\n const data = await server.requestManager.get(\n requestUri,\n undefined,\n query.client_id,\n )\n await server.requestManager.delete(requestUri)\n throw new AuthorizationError(data.parameters, message)\n } else if ('request' in query) {\n const client = await server.clientManager.getClient(\n query.client_id,\n )\n const parameters = await server.decodeJAR(client, query)\n throw new AuthorizationError(parameters, message)\n } else {\n throw new AuthorizationError(query, message)\n }\n }\n }\n }\n }\n\n // Normal authorization flow\n const device = await server.deviceManager.load(req, res)\n\n const result = await server.authorize(query, device)\n\n if ('redirect' in result) {\n return sendAuthorizationResultRedirect(res, result, securityOptions)\n } else {\n return sendAuthorizePage(req, res, result)\n }\n }),\n )\n\n // This is a private endpoint that will be called by the user after the\n // authorization request was either approved or denied. The logic performed\n // here **could** be performed directly in the frontend. We decided to\n // implement it here to avoid duplicating the logic.\n router.get(\n '/oauth/authorize/redirect',\n withErrorHandler(async function (req, res) {\n // Ensure we come from the authorization page\n validateFetchSite(req, ['same-origin'])\n validateFetchMode(req, ['navigate'])\n validateFetchDest(req, ['document'])\n validateOrigin(req, issuerOrigin)\n\n const referrer = validateReferrer(req, {\n origin: issuerOrigin,\n pathname: '/oauth/authorize',\n })\n\n // Ensure we are coming from the authorization page\n requestUriSchema.parse(referrer.searchParams.get('request_uri'))\n\n return sendRedirect(res, parseRedirectUrl(this.url), securityOptions)\n }),\n )\n\n return router.buildMiddleware()\n\n function withErrorHandler<T extends RouterCtx>(\n handler: (this: T, req: Req, res: Res) => Awaitable<void>,\n ): Middleware<T, Req, Res> {\n return async function (req, res) {\n try {\n await handler.call(this, req, res)\n } catch (err) {\n onError?.(req, res, err, `Authorization Request Error`)\n\n if (!res.headersSent) {\n if (err instanceof AuthorizationError) {\n return sendAuthorizationResultRedirect(\n res,\n {\n issuer: server.issuer,\n parameters: err.parameters,\n redirect: err.toJSON(),\n },\n securityOptions,\n )\n } else {\n return sendErrorPage(req, res, err)\n }\n } else if (!res.destroyed) {\n res.end()\n }\n }\n }\n }\n}\n\nfunction parseOAuthAuthorizationRequestQuery(\n url: URL,\n): OAuthAuthorizationRequestQuery {\n const query = Object.fromEntries(url.searchParams)\n const result = oauthAuthorizationRequestQuerySchema.safeParse(query, {\n path: ['query'],\n })\n\n if (!result.success) {\n const message = 'Invalid request parameters'\n const err = result.error\n throw new InvalidRequestError(formatError(err, message), err)\n }\n\n return result.data\n}\n"]}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const index_js_1 = require("../lib/http/index.js");
|
|
11
|
-
const error_js_1 = require("../lib/util/error.js");
|
|
12
|
-
const oauth_errors_js_1 = require("../oauth-errors.js");
|
|
1
|
+
import { oauthAuthorizationRequestParSchema, oauthClientCredentialsSchema, oauthTokenIdentificationSchema, oauthTokenRequestSchema, } from '@atproto/oauth-types';
|
|
2
|
+
import { buildErrorPayload, buildErrorStatus } from '../errors/error-parser.js';
|
|
3
|
+
import { InvalidClientError } from '../errors/invalid-client-error.js';
|
|
4
|
+
import { InvalidGrantError } from '../errors/invalid-grant-error.js';
|
|
5
|
+
import { InvalidRequestError } from '../errors/invalid-request-error.js';
|
|
6
|
+
import { WWWAuthenticateError } from '../errors/www-authenticate-error.js';
|
|
7
|
+
import { Router, cacheControlMiddleware, combineMiddlewares, jsonHandler, parseHttpRequest, staticJsonMiddleware, } from '../lib/http/index.js';
|
|
8
|
+
import { formatError } from '../lib/util/error.js';
|
|
9
|
+
import { OAuthError } from '../oauth-errors.js';
|
|
13
10
|
// CORS preflight
|
|
14
11
|
const corsHeaders = function (req, res, next) {
|
|
15
12
|
res.setHeader('Access-Control-Max-Age', '86400'); // 1 day
|
|
@@ -33,28 +30,28 @@ const corsHeaders = function (req, res, next) {
|
|
|
33
30
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,DPoP');
|
|
34
31
|
next();
|
|
35
32
|
};
|
|
36
|
-
const corsPreflight =
|
|
33
|
+
const corsPreflight = combineMiddlewares([
|
|
37
34
|
corsHeaders,
|
|
38
35
|
(req, res) => {
|
|
39
36
|
res.writeHead(200).end();
|
|
40
37
|
},
|
|
41
38
|
]);
|
|
42
|
-
function createOAuthMiddleware(server, { onError }) {
|
|
43
|
-
const router = new
|
|
39
|
+
export function createOAuthMiddleware(server, { onError }) {
|
|
40
|
+
const router = new Router(new URL(server.issuer));
|
|
44
41
|
//- Public OAuth endpoints
|
|
45
42
|
router.options('/.well-known/oauth-authorization-server', corsPreflight);
|
|
46
|
-
router.get('/.well-known/oauth-authorization-server', corsHeaders,
|
|
43
|
+
router.get('/.well-known/oauth-authorization-server', corsHeaders, cacheControlMiddleware(300), staticJsonMiddleware(server.metadata));
|
|
47
44
|
router.options('/oauth/jwks', corsPreflight);
|
|
48
|
-
router.get('/oauth/jwks', corsHeaders,
|
|
45
|
+
router.get('/oauth/jwks', corsHeaders, cacheControlMiddleware(300), staticJsonMiddleware(server.jwks));
|
|
49
46
|
router.options('/oauth/par', corsPreflight);
|
|
50
47
|
router.post('/oauth/par', corsHeaders, oauthHandler(async function (req) {
|
|
51
|
-
const payload = await
|
|
48
|
+
const payload = await parseHttpRequest(req, ['json', 'urlencoded']);
|
|
52
49
|
// https://datatracker.ietf.org/doc/html/rfc9126#name-error-response
|
|
53
50
|
// https://datatracker.ietf.org/doc/html/rfc6749#autoid-56
|
|
54
|
-
const credentials = await
|
|
51
|
+
const credentials = await oauthClientCredentialsSchema
|
|
55
52
|
.parseAsync(payload, { path: ['body'] })
|
|
56
53
|
.catch((err) => throwInvalidClient(err, 'Client credentials missing'));
|
|
57
|
-
const authorizationRequest = await
|
|
54
|
+
const authorizationRequest = await oauthAuthorizationRequestParSchema
|
|
58
55
|
.parseAsync(payload, { path: ['body'] })
|
|
59
56
|
.catch((err) => throwInvalidRequest(err, 'Invalid authorization request'));
|
|
60
57
|
const dpopProof = await server.checkDpopProof(req.method, this.url, req.headers);
|
|
@@ -68,12 +65,12 @@ function createOAuthMiddleware(server, { onError }) {
|
|
|
68
65
|
});
|
|
69
66
|
router.options('/oauth/token', corsPreflight);
|
|
70
67
|
router.post('/oauth/token', corsHeaders, oauthHandler(async function (req) {
|
|
71
|
-
const payload = await
|
|
68
|
+
const payload = await parseHttpRequest(req, ['json', 'urlencoded']);
|
|
72
69
|
const clientMetadata = await server.deviceManager.getRequestMetadata(req);
|
|
73
|
-
const clientCredentials = await
|
|
70
|
+
const clientCredentials = await oauthClientCredentialsSchema
|
|
74
71
|
.parseAsync(payload, { path: ['body'] })
|
|
75
72
|
.catch((err) => throwInvalidGrant(err, 'Client credentials missing'));
|
|
76
|
-
const tokenRequest = await
|
|
73
|
+
const tokenRequest = await oauthTokenRequestSchema
|
|
77
74
|
.parseAsync(payload, { path: ['body'] })
|
|
78
75
|
.catch((err) => throwInvalidGrant(err, 'Invalid request payload'));
|
|
79
76
|
const dpopProof = await server.checkDpopProof(req.method, this.url, req.headers);
|
|
@@ -81,11 +78,11 @@ function createOAuthMiddleware(server, { onError }) {
|
|
|
81
78
|
}));
|
|
82
79
|
router.options('/oauth/revoke', corsPreflight);
|
|
83
80
|
router.post('/oauth/revoke', corsHeaders, oauthHandler(async function (req, res) {
|
|
84
|
-
const payload = await
|
|
85
|
-
const credentials = await
|
|
81
|
+
const payload = await parseHttpRequest(req, ['json', 'urlencoded']);
|
|
82
|
+
const credentials = await oauthClientCredentialsSchema
|
|
86
83
|
.parseAsync(payload, { path: ['body'] })
|
|
87
84
|
.catch((err) => throwInvalidRequest(err, 'Client credentials missing'));
|
|
88
|
-
const tokenIdentification = await
|
|
85
|
+
const tokenIdentification = await oauthTokenIdentificationSchema
|
|
89
86
|
.parseAsync(payload, { path: ['body'] })
|
|
90
87
|
.catch((err) => throwInvalidRequest(err, 'Invalid request payload'));
|
|
91
88
|
const dpopProof = await server.checkDpopProof(req.method, this.url, req.headers);
|
|
@@ -105,7 +102,7 @@ function createOAuthMiddleware(server, { onError }) {
|
|
|
105
102
|
}));
|
|
106
103
|
return router.buildMiddleware();
|
|
107
104
|
function oauthHandler(buildOAuthResponse, status) {
|
|
108
|
-
return
|
|
105
|
+
return jsonHandler(async function (req, res) {
|
|
109
106
|
try {
|
|
110
107
|
// https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
|
|
111
108
|
res.setHeader('Cache-Control', 'no-store');
|
|
@@ -121,28 +118,28 @@ function createOAuthMiddleware(server, { onError }) {
|
|
|
121
118
|
return { json, status };
|
|
122
119
|
}
|
|
123
120
|
catch (err) {
|
|
124
|
-
onError?.(req, res, err, err instanceof
|
|
121
|
+
onError?.(req, res, err, err instanceof OAuthError
|
|
125
122
|
? `OAuth "${err.error}" error`
|
|
126
123
|
: 'Unexpected error');
|
|
127
|
-
if (!res.headersSent && err instanceof
|
|
124
|
+
if (!res.headersSent && err instanceof WWWAuthenticateError) {
|
|
128
125
|
const name = 'WWW-Authenticate';
|
|
129
126
|
res.setHeader(name, err.wwwAuthenticateHeader);
|
|
130
127
|
res.appendHeader('Access-Control-Expose-Headers', name);
|
|
131
128
|
}
|
|
132
|
-
const status =
|
|
133
|
-
const json =
|
|
129
|
+
const status = buildErrorStatus(err);
|
|
130
|
+
const json = buildErrorPayload(err);
|
|
134
131
|
return { json, status };
|
|
135
132
|
}
|
|
136
133
|
});
|
|
137
134
|
}
|
|
138
135
|
}
|
|
139
136
|
function throwInvalidGrant(err, prefix) {
|
|
140
|
-
throw new
|
|
137
|
+
throw new InvalidGrantError(formatError(err, prefix), err);
|
|
141
138
|
}
|
|
142
139
|
function throwInvalidClient(err, prefix) {
|
|
143
|
-
throw new
|
|
140
|
+
throw new InvalidClientError(formatError(err, prefix), err);
|
|
144
141
|
}
|
|
145
142
|
function throwInvalidRequest(err, prefix) {
|
|
146
|
-
throw new
|
|
143
|
+
throw new InvalidRequestError(formatError(err, prefix), err);
|
|
147
144
|
}
|
|
148
145
|
//# sourceMappingURL=create-oauth-middleware.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-oauth-middleware.js","sourceRoot":"","sources":["../../src/router/create-oauth-middleware.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport {\n oauthAuthorizationRequestParSchema,\n oauthClientCredentialsSchema,\n oauthTokenIdentificationSchema,\n oauthTokenRequestSchema,\n} from '@atproto/oauth-types'\nimport { buildErrorPayload, buildErrorStatus } from '../errors/error-parser.js'\nimport { InvalidClientError } from '../errors/invalid-client-error.js'\nimport { InvalidGrantError } from '../errors/invalid-grant-error.js'\nimport { InvalidRequestError } from '../errors/invalid-request-error.js'\nimport { WWWAuthenticateError } from '../errors/www-authenticate-error.js'\nimport {\n Middleware,\n Router,\n cacheControlMiddleware,\n combineMiddlewares,\n jsonHandler,\n parseHttpRequest,\n staticJsonMiddleware,\n} from '../lib/http/index.js'\nimport { formatError } from '../lib/util/error.js'\nimport { OAuthError } from '../oauth-errors.js'\nimport type { OAuthProvider } from '../oauth-provider.js'\nimport type { MiddlewareOptions } from './middleware-options.js'\n\n// CORS preflight\nconst corsHeaders: Middleware = function (req, res, next) {\n res.setHeader('Access-Control-Max-Age', '86400') // 1 day\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin\n //\n // > For requests without credentials, the literal value \"*\" can be\n // > specified as a wildcard; the value tells browsers to allow\n // > requesting code from any origin to access the resource.\n // > Attempting to use the wildcard with credentials results in an\n // > error.\n //\n // A \"*\" is safer to use than reflecting the request origin.\n res.setHeader('Access-Control-Allow-Origin', '*')\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods\n // > The value \"*\" only counts as a special wildcard value for\n // > requests without credentials (requests without HTTP cookies or\n // > HTTP authentication information). In requests with credentials,\n // > it is treated as the literal method name \"*\" without special\n // > semantics.\n res.setHeader('Access-Control-Allow-Methods', '*')\n\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type,DPoP')\n\n next()\n}\n\nconst corsPreflight: Middleware = combineMiddlewares([\n corsHeaders,\n (req, res) => {\n res.writeHead(200).end()\n },\n])\n\nexport function createOAuthMiddleware<\n Ctx extends object | void = void,\n Req extends IncomingMessage = IncomingMessage,\n Res extends ServerResponse = ServerResponse,\n>(\n server: OAuthProvider,\n { onError }: MiddlewareOptions<Req, Res>,\n): Middleware<Ctx, Req, Res> {\n const router = new Router<Ctx, Req, Res>(new URL(server.issuer))\n\n //- Public OAuth endpoints\n\n router.options('/.well-known/oauth-authorization-server', corsPreflight)\n router.get(\n '/.well-known/oauth-authorization-server',\n corsHeaders,\n cacheControlMiddleware(300),\n staticJsonMiddleware(server.metadata),\n )\n\n router.options('/oauth/jwks', corsPreflight)\n router.get(\n '/oauth/jwks',\n corsHeaders,\n cacheControlMiddleware(300),\n staticJsonMiddleware(server.jwks),\n )\n\n router.options('/oauth/par', corsPreflight)\n router.post(\n '/oauth/par',\n corsHeaders,\n oauthHandler(async function (req) {\n const payload = await parseHttpRequest(req, ['json', 'urlencoded'])\n\n // https://datatracker.ietf.org/doc/html/rfc9126#name-error-response\n // https://datatracker.ietf.org/doc/html/rfc6749#autoid-56\n\n const credentials = await oauthClientCredentialsSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) => throwInvalidClient(err, 'Client credentials missing'))\n\n const authorizationRequest = await oauthAuthorizationRequestParSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) =>\n throwInvalidRequest(err, 'Invalid authorization request'),\n )\n\n const dpopProof = await server.checkDpopProof(\n req.method!,\n this.url,\n req.headers,\n )\n\n return server.pushedAuthorizationRequest(\n credentials,\n authorizationRequest,\n dpopProof,\n )\n }, 201),\n )\n // https://datatracker.ietf.org/doc/html/rfc9126#section-2.3\n // > If the request did not use the POST method, the authorization server\n // > responds with an HTTP 405 (Method Not Allowed) status code.\n router.all('/oauth/par', (req, res) => {\n res.writeHead(405).end()\n })\n\n router.options('/oauth/token', corsPreflight)\n router.post(\n '/oauth/token',\n corsHeaders,\n oauthHandler(async function (req) {\n const payload = await parseHttpRequest(req, ['json', 'urlencoded'])\n\n const clientMetadata = await server.deviceManager.getRequestMetadata(req)\n\n const clientCredentials = await oauthClientCredentialsSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) => throwInvalidGrant(err, 'Client credentials missing'))\n\n const tokenRequest = await oauthTokenRequestSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) => throwInvalidGrant(err, 'Invalid request payload'))\n\n const dpopProof = await server.checkDpopProof(\n req.method!,\n this.url,\n req.headers,\n )\n\n return server.token(\n clientCredentials,\n clientMetadata,\n tokenRequest,\n dpopProof,\n )\n }),\n )\n\n router.options('/oauth/revoke', corsPreflight)\n router.post(\n '/oauth/revoke',\n corsHeaders,\n oauthHandler(async function (req, res) {\n const payload = await parseHttpRequest(req, ['json', 'urlencoded'])\n\n const credentials = await oauthClientCredentialsSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) => throwInvalidRequest(err, 'Client credentials missing'))\n\n const tokenIdentification = await oauthTokenIdentificationSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) => throwInvalidRequest(err, 'Invalid request payload'))\n\n const dpopProof = await server.checkDpopProof(\n req.method!,\n this.url,\n req.headers,\n )\n\n try {\n await server.revoke(credentials, tokenIdentification, dpopProof)\n } catch (err) {\n // > Note: invalid tokens do not cause an error response since the\n // > client cannot handle such an error in a reasonable way. Moreover,\n // > the purpose of the revocation request, invalidating the particular\n // > token, is already achieved.\n //\n // https://datatracker.ietf.org/doc/html/rfc7009#section-2.2\n\n onError?.(req, res, err, 'Failed to revoke token')\n }\n\n return {}\n }),\n )\n\n return router.buildMiddleware()\n\n function oauthHandler<T>(\n buildOAuthResponse: (this: T, req: Req, res: Res) => unknown,\n status?: number,\n ): Middleware<T, Req, Res> {\n return jsonHandler<T, Req, Res>(async function (req, res) {\n try {\n // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1\n res.setHeader('Cache-Control', 'no-store')\n res.setHeader('Pragma', 'no-cache')\n\n // https://datatracker.ietf.org/doc/html/rfc9449#section-8.2\n const dpopNonce = server.nextDpopNonce()\n if (dpopNonce) {\n const name = 'DPoP-Nonce'\n res.setHeader(name, dpopNonce)\n res.appendHeader('Access-Control-Expose-Headers', name)\n }\n\n const json = await buildOAuthResponse.call(this, req, res)\n return { json, status }\n } catch (err) {\n onError?.(\n req,\n res,\n err,\n err instanceof OAuthError\n ? `OAuth \"${err.error}\" error`\n : 'Unexpected error',\n )\n\n if (!res.headersSent && err instanceof WWWAuthenticateError) {\n const name = 'WWW-Authenticate'\n res.setHeader(name, err.wwwAuthenticateHeader)\n res.appendHeader('Access-Control-Expose-Headers', name)\n }\n\n const status = buildErrorStatus(err)\n const json = buildErrorPayload(err)\n\n return { json, status }\n }\n })\n }\n}\n\nfunction throwInvalidGrant(err: unknown, prefix: string): never {\n throw new InvalidGrantError(formatError(err, prefix), err)\n}\n\nfunction throwInvalidClient(err: unknown, prefix: string): never {\n throw new InvalidClientError(formatError(err, prefix), err)\n}\n\nfunction throwInvalidRequest(err: unknown, prefix: string): never {\n throw new InvalidRequestError(formatError(err, prefix), err)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"create-oauth-middleware.js","sourceRoot":"","sources":["../../src/router/create-oauth-middleware.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport {\n oauthAuthorizationRequestParSchema,\n oauthClientCredentialsSchema,\n oauthTokenIdentificationSchema,\n oauthTokenRequestSchema,\n} from '@atproto/oauth-types'\nimport { buildErrorPayload, buildErrorStatus } from '../errors/error-parser.js'\nimport { InvalidClientError } from '../errors/invalid-client-error.js'\nimport { InvalidGrantError } from '../errors/invalid-grant-error.js'\nimport { InvalidRequestError } from '../errors/invalid-request-error.js'\nimport { WWWAuthenticateError } from '../errors/www-authenticate-error.js'\nimport {\n Middleware,\n Router,\n cacheControlMiddleware,\n combineMiddlewares,\n jsonHandler,\n parseHttpRequest,\n staticJsonMiddleware,\n} from '../lib/http/index.js'\nimport { formatError } from '../lib/util/error.js'\nimport { OAuthError } from '../oauth-errors.js'\nimport type { OAuthProvider } from '../oauth-provider.js'\nimport type { MiddlewareOptions } from './middleware-options.js'\n\n// CORS preflight\nconst corsHeaders: Middleware = function (req, res, next) {\n res.setHeader('Access-Control-Max-Age', '86400') // 1 day\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin\n //\n // > For requests without credentials, the literal value \"*\" can be\n // > specified as a wildcard; the value tells browsers to allow\n // > requesting code from any origin to access the resource.\n // > Attempting to use the wildcard with credentials results in an\n // > error.\n //\n // A \"*\" is safer to use than reflecting the request origin.\n res.setHeader('Access-Control-Allow-Origin', '*')\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods\n // > The value \"*\" only counts as a special wildcard value for\n // > requests without credentials (requests without HTTP cookies or\n // > HTTP authentication information). In requests with credentials,\n // > it is treated as the literal method name \"*\" without special\n // > semantics.\n res.setHeader('Access-Control-Allow-Methods', '*')\n\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type,DPoP')\n\n next()\n}\n\nconst corsPreflight: Middleware = combineMiddlewares([\n corsHeaders,\n (req, res) => {\n res.writeHead(200).end()\n },\n])\n\nexport function createOAuthMiddleware<\n Ctx extends object | void = void,\n Req extends IncomingMessage = IncomingMessage,\n Res extends ServerResponse = ServerResponse,\n>(\n server: OAuthProvider,\n { onError }: MiddlewareOptions<Req, Res>,\n): Middleware<Ctx, Req, Res> {\n const router = new Router<Ctx, Req, Res>(new URL(server.issuer))\n\n //- Public OAuth endpoints\n\n router.options('/.well-known/oauth-authorization-server', corsPreflight)\n router.get(\n '/.well-known/oauth-authorization-server',\n corsHeaders,\n cacheControlMiddleware(300),\n staticJsonMiddleware(server.metadata),\n )\n\n router.options('/oauth/jwks', corsPreflight)\n router.get(\n '/oauth/jwks',\n corsHeaders,\n cacheControlMiddleware(300),\n staticJsonMiddleware(server.jwks),\n )\n\n router.options('/oauth/par', corsPreflight)\n router.post(\n '/oauth/par',\n corsHeaders,\n oauthHandler(async function (req) {\n const payload = await parseHttpRequest(req, ['json', 'urlencoded'])\n\n // https://datatracker.ietf.org/doc/html/rfc9126#name-error-response\n // https://datatracker.ietf.org/doc/html/rfc6749#autoid-56\n\n const credentials = await oauthClientCredentialsSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) => throwInvalidClient(err, 'Client credentials missing'))\n\n const authorizationRequest = await oauthAuthorizationRequestParSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) =>\n throwInvalidRequest(err, 'Invalid authorization request'),\n )\n\n const dpopProof = await server.checkDpopProof(\n req.method!,\n this.url,\n req.headers,\n )\n\n return server.pushedAuthorizationRequest(\n credentials,\n authorizationRequest,\n dpopProof,\n )\n }, 201),\n )\n // https://datatracker.ietf.org/doc/html/rfc9126#section-2.3\n // > If the request did not use the POST method, the authorization server\n // > responds with an HTTP 405 (Method Not Allowed) status code.\n router.all('/oauth/par', (req, res) => {\n res.writeHead(405).end()\n })\n\n router.options('/oauth/token', corsPreflight)\n router.post(\n '/oauth/token',\n corsHeaders,\n oauthHandler(async function (req) {\n const payload = await parseHttpRequest(req, ['json', 'urlencoded'])\n\n const clientMetadata = await server.deviceManager.getRequestMetadata(req)\n\n const clientCredentials = await oauthClientCredentialsSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) => throwInvalidGrant(err, 'Client credentials missing'))\n\n const tokenRequest = await oauthTokenRequestSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) => throwInvalidGrant(err, 'Invalid request payload'))\n\n const dpopProof = await server.checkDpopProof(\n req.method!,\n this.url,\n req.headers,\n )\n\n return server.token(\n clientCredentials,\n clientMetadata,\n tokenRequest,\n dpopProof,\n )\n }),\n )\n\n router.options('/oauth/revoke', corsPreflight)\n router.post(\n '/oauth/revoke',\n corsHeaders,\n oauthHandler(async function (req, res) {\n const payload = await parseHttpRequest(req, ['json', 'urlencoded'])\n\n const credentials = await oauthClientCredentialsSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) => throwInvalidRequest(err, 'Client credentials missing'))\n\n const tokenIdentification = await oauthTokenIdentificationSchema\n .parseAsync(payload, { path: ['body'] })\n .catch((err) => throwInvalidRequest(err, 'Invalid request payload'))\n\n const dpopProof = await server.checkDpopProof(\n req.method!,\n this.url,\n req.headers,\n )\n\n try {\n await server.revoke(credentials, tokenIdentification, dpopProof)\n } catch (err) {\n // > Note: invalid tokens do not cause an error response since the\n // > client cannot handle such an error in a reasonable way. Moreover,\n // > the purpose of the revocation request, invalidating the particular\n // > token, is already achieved.\n //\n // https://datatracker.ietf.org/doc/html/rfc7009#section-2.2\n\n onError?.(req, res, err, 'Failed to revoke token')\n }\n\n return {}\n }),\n )\n\n return router.buildMiddleware()\n\n function oauthHandler<T>(\n buildOAuthResponse: (this: T, req: Req, res: Res) => unknown,\n status?: number,\n ): Middleware<T, Req, Res> {\n return jsonHandler<T, Req, Res>(async function (req, res) {\n try {\n // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1\n res.setHeader('Cache-Control', 'no-store')\n res.setHeader('Pragma', 'no-cache')\n\n // https://datatracker.ietf.org/doc/html/rfc9449#section-8.2\n const dpopNonce = server.nextDpopNonce()\n if (dpopNonce) {\n const name = 'DPoP-Nonce'\n res.setHeader(name, dpopNonce)\n res.appendHeader('Access-Control-Expose-Headers', name)\n }\n\n const json = await buildOAuthResponse.call(this, req, res)\n return { json, status }\n } catch (err) {\n onError?.(\n req,\n res,\n err,\n err instanceof OAuthError\n ? `OAuth \"${err.error}\" error`\n : 'Unexpected error',\n )\n\n if (!res.headersSent && err instanceof WWWAuthenticateError) {\n const name = 'WWW-Authenticate'\n res.setHeader(name, err.wwwAuthenticateHeader)\n res.appendHeader('Access-Control-Expose-Headers', name)\n }\n\n const status = buildErrorStatus(err)\n const json = buildErrorPayload(err)\n\n return { json, status }\n }\n })\n }\n}\n\nfunction throwInvalidGrant(err: unknown, prefix: string): never {\n throw new InvalidGrantError(formatError(err, prefix), err)\n}\n\nfunction throwInvalidClient(err: unknown, prefix: string): never {\n throw new InvalidClientError(formatError(err, prefix), err)\n}\n\nfunction throwInvalidRequest(err: unknown, prefix: string): never {\n throw new InvalidRequestError(formatError(err, prefix), err)\n}\n"]}
|
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const sub_js_1 = require("../oidc/sub.js");
|
|
8
|
-
const token_id_js_1 = require("../token/token-id.js");
|
|
9
|
-
exports.accessTokenPayloadSchema = jwk_1.jwtPayloadSchema
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { jwtPayloadSchema } from '@atproto/jwk';
|
|
3
|
+
import { clientIdSchema } from '../client/client-id.js';
|
|
4
|
+
import { subSchema } from '../oidc/sub.js';
|
|
5
|
+
import { tokenIdSchema } from '../token/token-id.js';
|
|
6
|
+
export const accessTokenPayloadSchema = jwtPayloadSchema
|
|
10
7
|
.partial()
|
|
11
8
|
.extend({
|
|
12
9
|
// Following are required
|
|
13
|
-
jti:
|
|
14
|
-
sub:
|
|
15
|
-
exp:
|
|
16
|
-
iat:
|
|
17
|
-
iss:
|
|
10
|
+
jti: tokenIdSchema,
|
|
11
|
+
sub: subSchema,
|
|
12
|
+
exp: z.number().int(),
|
|
13
|
+
iat: z.number().int(),
|
|
14
|
+
iss: z.string().min(1),
|
|
18
15
|
// @NOTE "aud", "scope", "client_id" are not required, as are stored in the
|
|
19
16
|
// DB in 'light' access token mode.
|
|
20
17
|
// Restrict type of following
|
|
21
|
-
client_id:
|
|
18
|
+
client_id: clientIdSchema.optional(),
|
|
22
19
|
})
|
|
23
20
|
.passthrough();
|
|
24
21
|
//# sourceMappingURL=access-token-payload.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-token-payload.js","sourceRoot":"","sources":["../../src/signer/access-token-payload.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"access-token-payload.js","sourceRoot":"","sources":["../../src/signer/access-token-payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,MAAM,CAAC,MAAM,wBAAwB,GAAG,gBAAgB;KACrD,OAAO,EAAE;KACT,MAAM,CAAC;IACN,yBAAyB;IACzB,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACrB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACrB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAEtB,2EAA2E;IAC3E,mCAAmC;IAEnC,6BAA6B;IAC7B,SAAS,EAAE,cAAc,CAAC,QAAQ,EAAE;CACrC,CAAC;KACD,WAAW,EAAE,CAAA","sourcesContent":["import { z } from 'zod'\nimport { jwtPayloadSchema } from '@atproto/jwk'\nimport { clientIdSchema } from '../client/client-id.js'\nimport { subSchema } from '../oidc/sub.js'\nimport { tokenIdSchema } from '../token/token-id.js'\n\nexport const accessTokenPayloadSchema = jwtPayloadSchema\n .partial()\n .extend({\n // Following are required\n jti: tokenIdSchema,\n sub: subSchema,\n exp: z.number().int(),\n iat: z.number().int(),\n iss: z.string().min(1),\n\n // @NOTE \"aud\", \"scope\", \"client_id\" are not required, as are stored in the\n // DB in 'light' access token mode.\n\n // Restrict type of following\n client_id: clientIdSchema.optional(),\n })\n .passthrough()\n\nexport type AccessTokenPayload = z.infer<typeof accessTokenPayloadSchema>\n"]}
|
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const sub_js_1 = require("../oidc/sub.js");
|
|
7
|
-
const request_uri_js_1 = require("../request/request-uri.js");
|
|
8
|
-
exports.apiTokenPayloadSchema = jwk_1.jwtPayloadSchema
|
|
1
|
+
import { jwtPayloadSchema } from '@atproto/jwk';
|
|
2
|
+
import { deviceIdSchema } from '../oauth-store.js';
|
|
3
|
+
import { subSchema } from '../oidc/sub.js';
|
|
4
|
+
import { requestUriSchema } from '../request/request-uri.js';
|
|
5
|
+
export const apiTokenPayloadSchema = jwtPayloadSchema
|
|
9
6
|
.extend({
|
|
10
|
-
sub:
|
|
11
|
-
deviceId:
|
|
7
|
+
sub: subSchema,
|
|
8
|
+
deviceId: deviceIdSchema,
|
|
12
9
|
// If the token is bound to a particular authorization request, it can only
|
|
13
10
|
// be used in the context of that request.
|
|
14
|
-
requestUri:
|
|
11
|
+
requestUri: requestUriSchema.optional(),
|
|
15
12
|
})
|
|
16
13
|
.passthrough();
|
|
17
14
|
//# sourceMappingURL=api-token-payload.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-token-payload.js","sourceRoot":"","sources":["../../src/signer/api-token-payload.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api-token-payload.js","sourceRoot":"","sources":["../../src/signer/api-token-payload.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAE5D,MAAM,CAAC,MAAM,qBAAqB,GAAG,gBAAgB;KAClD,MAAM,CAAC;IACN,GAAG,EAAE,SAAS;IAEd,QAAQ,EAAE,cAAc;IACxB,2EAA2E;IAC3E,0CAA0C;IAC1C,UAAU,EAAE,gBAAgB,CAAC,QAAQ,EAAE;CACxC,CAAC;KACD,WAAW,EAAE,CAAA","sourcesContent":["import { z } from 'zod'\nimport { jwtPayloadSchema } from '@atproto/jwk'\nimport { deviceIdSchema } from '../oauth-store.js'\nimport { subSchema } from '../oidc/sub.js'\nimport { requestUriSchema } from '../request/request-uri.js'\n\nexport const apiTokenPayloadSchema = jwtPayloadSchema\n .extend({\n sub: subSchema,\n\n deviceId: deviceIdSchema,\n // If the token is bound to a particular authorization request, it can only\n // be used in the context of that request.\n requestUri: requestUriSchema.optional(),\n })\n .passthrough()\n\nexport type ApiTokenPayload = z.infer<typeof apiTokenPayloadSchema>\n"]}
|