@atproto/oauth-provider 0.19.4 → 0.19.6

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 CHANGED
@@ -1,5 +1,35 @@
1
1
  # @atproto/oauth-provider
2
2
 
3
+ ## 0.19.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [#5147](https://github.com/bluesky-social/atproto/pull/5147) [`af02ea1`](https://github.com/bluesky-social/atproto/commit/af02ea14e710f5930d78b49836aa460cfe168941) Thanks [@joefitter](https://github.com/joefitter)! - Allow same-site OAuth authorization page requests.
8
+
9
+ ## 0.19.5
10
+
11
+ ### Patch Changes
12
+
13
+ - [#5151](https://github.com/bluesky-social/atproto/pull/5151) [`a51c45d`](https://github.com/bluesky-social/atproto/commit/a51c45d38f6bd7b8765f640e564cf921d52162e7) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update dependencies
14
+
15
+ - Updated dependencies [[`60e9b83`](https://github.com/bluesky-social/atproto/commit/60e9b8391f212c274b1f21991ee2a3a2d14f2f88), [`60e9b83`](https://github.com/bluesky-social/atproto/commit/60e9b8391f212c274b1f21991ee2a3a2d14f2f88), [`60e9b83`](https://github.com/bluesky-social/atproto/commit/60e9b8391f212c274b1f21991ee2a3a2d14f2f88), [`60e9b83`](https://github.com/bluesky-social/atproto/commit/60e9b8391f212c274b1f21991ee2a3a2d14f2f88), [`60e9b83`](https://github.com/bluesky-social/atproto/commit/60e9b8391f212c274b1f21991ee2a3a2d14f2f88), [`f2cf8f7`](https://github.com/bluesky-social/atproto/commit/f2cf8f7fc5f3a10847f2e6d785e5fa2244ee8cfb), [`60e9b83`](https://github.com/bluesky-social/atproto/commit/60e9b8391f212c274b1f21991ee2a3a2d14f2f88), [`a51c45d`](https://github.com/bluesky-social/atproto/commit/a51c45d38f6bd7b8765f640e564cf921d52162e7), [`60e9b83`](https://github.com/bluesky-social/atproto/commit/60e9b8391f212c274b1f21991ee2a3a2d14f2f88), [`f2cf8f7`](https://github.com/bluesky-social/atproto/commit/f2cf8f7fc5f3a10847f2e6d785e5fa2244ee8cfb)]:
16
+ - @atproto-labs/fetch@0.3.2
17
+ - @atproto-labs/fetch-node@0.3.3
18
+ - @atproto/common@0.6.4
19
+ - @atproto/did@0.5.2
20
+ - @atproto-labs/pipe@0.2.2
21
+ - @atproto-labs/simple-store@0.4.2
22
+ - @atproto-labs/simple-store-memory@0.2.2
23
+ - @atproto/lex-document@0.1.2
24
+ - @atproto/lex-resolver@0.1.2
25
+ - @atproto/jwk@0.7.2
26
+ - @atproto/jwk-jose@0.2.2
27
+ - @atproto/oauth-provider-api@0.7.2
28
+ - @atproto/oauth-provider-ui@0.8.3
29
+ - @atproto/oauth-scopes@0.5.2
30
+ - @atproto/oauth-types@0.7.3
31
+ - @atproto/syntax@0.6.3
32
+
3
33
  ## 0.19.4
4
34
 
5
35
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"create-authorization-page-middleware.d.ts","sourceRoot":"","sources":["../../src/router/create-authorization-page-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAOhE,OAAO,EACL,UAAU,EAUX,MAAM,sBAAsB,CAAA;AAK7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAUzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAEhE,wBAAgB,iCAAiC,CAC/C,GAAG,SAAS,MAAM,GAAG,IAAI,GAAG,IAAI,EAChC,GAAG,SAAS,eAAe,GAAG,eAAe,EAC7C,GAAG,SAAS,cAAc,GAAG,cAAc,EAE3C,MAAM,EAAE,aAAa,EACrB,EAAE,OAAO,EAAE,EAAE,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,GACvC,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CA8N3B"}
1
+ {"version":3,"file":"create-authorization-page-middleware.d.ts","sourceRoot":"","sources":["../../src/router/create-authorization-page-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAOhE,OAAO,EACL,UAAU,EAUX,MAAM,sBAAsB,CAAA;AAK7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAUzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAEhE,wBAAgB,iCAAiC,CAC/C,GAAG,SAAS,MAAM,GAAG,IAAI,GAAG,IAAI,EAChC,GAAG,SAAS,eAAe,GAAG,eAAe,EAC7C,GAAG,SAAS,cAAc,GAAG,cAAc,EAE3C,MAAM,EAAE,aAAa,EACrB,EAAE,OAAO,EAAE,EAAE,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,GACvC,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CA6N3B"}
@@ -25,9 +25,9 @@ export function createAuthorizationPageMiddleware(server, { onError }) {
25
25
  res.setHeader('Pragma', 'no-cache');
26
26
  // "same-origin" is required to support the redirect test logic below (as
27
27
  // well as refreshing the authorization page).
28
- // @TODO Consider removing this altogether to allow hosting PDS and app on
29
- // the same site but different origins (different subdomains).
30
- validateFetchSite(req, ['same-origin', 'cross-site', 'none']);
28
+ // "same-site" supports hosting PDS and app on the same site but different
29
+ // origins (different subdomains).
30
+ validateFetchSite(req, ['same-origin', 'same-site', 'cross-site', 'none']);
31
31
  validateFetchMode(req, ['navigate']);
32
32
  validateFetchDest(req, ['document']);
33
33
  validateOrigin(req, issuerOrigin);
@@ -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 // \"same-site\" supports hosting PDS and app on the same site but different\n // origins (different subdomains).\n validateFetchSite(req, ['same-origin', 'same-site', '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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/oauth-provider",
3
- "version": "0.19.4",
3
+ "version": "0.19.6",
4
4
  "license": "MIT",
5
5
  "description": "Generic OAuth2 and OpenID Connect provider for Node.js. Currently only supports features needed for Atproto.",
6
6
  "keywords": [
@@ -40,29 +40,29 @@
40
40
  "ioredis": "^5.3.2",
41
41
  "jose": "^5.2.0",
42
42
  "zod": "^3.23.8",
43
- "@atproto-labs/fetch": "^0.3.1",
44
- "@atproto-labs/pipe": "^0.2.1",
45
- "@atproto/common": "^0.6.3",
46
- "@atproto/did": "^0.5.1",
47
- "@atproto-labs/simple-store-memory": "^0.2.1",
48
- "@atproto/jwk": "^0.7.1",
49
- "@atproto/lex-document": "^0.1.1",
50
- "@atproto/jwk-jose": "^0.2.1",
51
- "@atproto/oauth-types": "^0.7.2",
52
- "@atproto/lex-resolver": "^0.1.1",
53
- "@atproto-labs/simple-store": "^0.4.1",
54
- "@atproto-labs/fetch-node": "^0.3.2",
55
- "@atproto/oauth-provider-api": "0.7.1",
56
- "@atproto/oauth-provider-ui": "0.8.2",
57
- "@atproto/oauth-scopes": "^0.5.1",
58
- "@atproto/syntax": "^0.6.2"
43
+ "@atproto-labs/fetch": "^0.3.2",
44
+ "@atproto-labs/pipe": "^0.2.2",
45
+ "@atproto-labs/fetch-node": "^0.3.3",
46
+ "@atproto-labs/simple-store": "^0.4.2",
47
+ "@atproto-labs/simple-store-memory": "^0.2.2",
48
+ "@atproto/common": "^0.6.4",
49
+ "@atproto/did": "^0.5.2",
50
+ "@atproto/jwk": "^0.7.2",
51
+ "@atproto/lex-document": "^0.1.2",
52
+ "@atproto/lex-resolver": "^0.1.2",
53
+ "@atproto/jwk-jose": "^0.2.2",
54
+ "@atproto/oauth-provider-api": "0.7.2",
55
+ "@atproto/oauth-types": "^0.7.3",
56
+ "@atproto/oauth-provider-ui": "0.8.3",
57
+ "@atproto/oauth-scopes": "^0.5.2",
58
+ "@atproto/syntax": "^0.6.3"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/cookie": "^0.6.0",
62
62
  "@types/forwarded": "0.1.3",
63
63
  "@types/http-errors": "^2.0.4",
64
64
  "@types/send": "^0.17.4",
65
- "@atproto-labs/rollup-plugin-bundle-manifest": "^0.3.1"
65
+ "@atproto-labs/rollup-plugin-bundle-manifest": "^0.3.2"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "tsgo --build tsconfig.build.json"
@@ -71,10 +71,9 @@ export function createAuthorizationPageMiddleware<
71
71
 
72
72
  // "same-origin" is required to support the redirect test logic below (as
73
73
  // well as refreshing the authorization page).
74
-
75
- // @TODO Consider removing this altogether to allow hosting PDS and app on
76
- // the same site but different origins (different subdomains).
77
- validateFetchSite(req, ['same-origin', 'cross-site', 'none'])
74
+ // "same-site" supports hosting PDS and app on the same site but different
75
+ // origins (different subdomains).
76
+ validateFetchSite(req, ['same-origin', 'same-site', 'cross-site', 'none'])
78
77
  validateFetchMode(req, ['navigate'])
79
78
  validateFetchDest(req, ['document'])
80
79
  validateOrigin(req, issuerOrigin)