@atproto/oauth-provider 0.19.6 → 0.19.8

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.
Files changed (165) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/errors/error-parser.js +5 -5
  3. package/dist/errors/error-parser.js.map +1 -1
  4. package/package.json +22 -18
  5. package/src/access-token/access-token-mode.ts +0 -4
  6. package/src/account/account-manager.ts +0 -591
  7. package/src/account/account-store.ts +0 -305
  8. package/src/account/sign-in-data.ts +0 -15
  9. package/src/account/sign-up-input.ts +0 -20
  10. package/src/client/client-auth.ts +0 -64
  11. package/src/client/client-data.ts +0 -9
  12. package/src/client/client-id.ts +0 -4
  13. package/src/client/client-info.ts +0 -13
  14. package/src/client/client-manager.ts +0 -772
  15. package/src/client/client-store.ts +0 -35
  16. package/src/client/client-utils.ts +0 -37
  17. package/src/client/client.ts +0 -394
  18. package/src/constants.ts +0 -80
  19. package/src/customization/branding.ts +0 -12
  20. package/src/customization/build-customization-css.ts +0 -55
  21. package/src/customization/build-customization-data.ts +0 -24
  22. package/src/customization/colors.ts +0 -48
  23. package/src/customization/customization.ts +0 -29
  24. package/src/customization/links.ts +0 -10
  25. package/src/device/device-data.ts +0 -11
  26. package/src/device/device-id.ts +0 -27
  27. package/src/device/device-manager.ts +0 -292
  28. package/src/device/device-store.ts +0 -31
  29. package/src/device/session-id.ts +0 -21
  30. package/src/dpop/dpop-manager.ts +0 -214
  31. package/src/dpop/dpop-nonce.ts +0 -121
  32. package/src/dpop/dpop-proof.ts +0 -6
  33. package/src/errors/access-denied-error.ts +0 -12
  34. package/src/errors/account-selection-required-error.ts +0 -12
  35. package/src/errors/authorization-error.ts +0 -45
  36. package/src/errors/consent-required-error.ts +0 -12
  37. package/src/errors/error-parser.ts +0 -147
  38. package/src/errors/handle-unavailable-error.ts +0 -23
  39. package/src/errors/invalid-authorization-details-error.ts +0 -27
  40. package/src/errors/invalid-client-error.ts +0 -20
  41. package/src/errors/invalid-client-id-error.ts +0 -31
  42. package/src/errors/invalid-client-metadata-error.ts +0 -68
  43. package/src/errors/invalid-credentials-error.ts +0 -29
  44. package/src/errors/invalid-dpop-key-binding-error.ts +0 -21
  45. package/src/errors/invalid-dpop-proof-error.ts +0 -13
  46. package/src/errors/invalid-grant-error.ts +0 -21
  47. package/src/errors/invalid-invite-code-error.ts +0 -10
  48. package/src/errors/invalid-redirect-uri-error.ts +0 -17
  49. package/src/errors/invalid-request-error.ts +0 -32
  50. package/src/errors/invalid-scope-error.ts +0 -15
  51. package/src/errors/invalid-token-error.ts +0 -60
  52. package/src/errors/login-required-error.ts +0 -12
  53. package/src/errors/oauth-error.ts +0 -28
  54. package/src/errors/second-authentication-factor-required-error.ts +0 -25
  55. package/src/errors/unauthorized-client-error.ts +0 -20
  56. package/src/errors/use-dpop-nonce-error.ts +0 -32
  57. package/src/errors/www-authenticate-error.ts +0 -64
  58. package/src/index.ts +0 -16
  59. package/src/lexicon/lexicon-data.ts +0 -11
  60. package/src/lexicon/lexicon-getter.ts +0 -55
  61. package/src/lexicon/lexicon-manager.ts +0 -116
  62. package/src/lexicon/lexicon-store.ts +0 -35
  63. package/src/lib/csp/index.ts +0 -101
  64. package/src/lib/hcaptcha.ts +0 -228
  65. package/src/lib/html/README.md +0 -9
  66. package/src/lib/html/build-document.ts +0 -151
  67. package/src/lib/html/escapers.ts +0 -66
  68. package/src/lib/html/html.ts +0 -56
  69. package/src/lib/html/hydration-data.ts +0 -19
  70. package/src/lib/html/index.ts +0 -5
  71. package/src/lib/html/tags.ts +0 -67
  72. package/src/lib/html/util.ts +0 -17
  73. package/src/lib/http/README.md +0 -11
  74. package/src/lib/http/accept.ts +0 -90
  75. package/src/lib/http/context.ts +0 -42
  76. package/src/lib/http/headers.ts +0 -15
  77. package/src/lib/http/index.ts +0 -10
  78. package/src/lib/http/method.ts +0 -18
  79. package/src/lib/http/middleware.ts +0 -169
  80. package/src/lib/http/parser.ts +0 -101
  81. package/src/lib/http/path.ts +0 -82
  82. package/src/lib/http/request.ts +0 -256
  83. package/src/lib/http/response.ts +0 -122
  84. package/src/lib/http/route.ts +0 -60
  85. package/src/lib/http/router.ts +0 -117
  86. package/src/lib/http/security-headers.ts +0 -100
  87. package/src/lib/http/stream.ts +0 -57
  88. package/src/lib/http/types.ts +0 -16
  89. package/src/lib/http/url.ts +0 -23
  90. package/src/lib/nsid.ts +0 -10
  91. package/src/lib/redis.ts +0 -25
  92. package/src/lib/util/authorization-header.ts +0 -28
  93. package/src/lib/util/cast.ts +0 -18
  94. package/src/lib/util/color.ts +0 -168
  95. package/src/lib/util/crypto.ts +0 -32
  96. package/src/lib/util/date.ts +0 -7
  97. package/src/lib/util/error.ts +0 -7
  98. package/src/lib/util/function.ts +0 -39
  99. package/src/lib/util/locale.ts +0 -12
  100. package/src/lib/util/object.ts +0 -23
  101. package/src/lib/util/redirect-uri.ts +0 -46
  102. package/src/lib/util/time.ts +0 -49
  103. package/src/lib/util/type.ts +0 -182
  104. package/src/lib/util/ui8.ts +0 -14
  105. package/src/lib/util/well-known.ts +0 -8
  106. package/src/lib/util/zod-error.ts +0 -26
  107. package/src/lib/write-form-redirect.ts +0 -58
  108. package/src/lib/write-html.ts +0 -70
  109. package/src/metadata/build-metadata.ts +0 -141
  110. package/src/oauth-client.ts +0 -3
  111. package/src/oauth-dpop.ts +0 -2
  112. package/src/oauth-errors.ts +0 -26
  113. package/src/oauth-hooks.ts +0 -519
  114. package/src/oauth-middleware.ts +0 -53
  115. package/src/oauth-provider.ts +0 -1110
  116. package/src/oauth-store.ts +0 -12
  117. package/src/oauth-verifier.ts +0 -260
  118. package/src/replay/replay-manager.ts +0 -51
  119. package/src/replay/replay-store-memory.ts +0 -36
  120. package/src/replay/replay-store-redis.ts +0 -30
  121. package/src/replay/replay-store.ts +0 -44
  122. package/src/request/code.ts +0 -23
  123. package/src/request/request-data.ts +0 -36
  124. package/src/request/request-id.ts +0 -22
  125. package/src/request/request-manager.ts +0 -521
  126. package/src/request/request-store.ts +0 -60
  127. package/src/request/request-uri.ts +0 -42
  128. package/src/result/authorization-redirect-parameters.ts +0 -24
  129. package/src/result/authorization-result-authorize-page.ts +0 -16
  130. package/src/result/authorization-result-redirect.ts +0 -8
  131. package/src/router/assets/assets-manifest.ts +0 -117
  132. package/src/router/assets/assets.ts +0 -124
  133. package/src/router/assets/csrf.ts +0 -79
  134. package/src/router/assets/send-account-page.ts +0 -23
  135. package/src/router/assets/send-authorization-page.ts +0 -42
  136. package/src/router/assets/send-cookie-error-page.ts +0 -21
  137. package/src/router/assets/send-error-page.ts +0 -25
  138. package/src/router/assets/send-redirect.ts +0 -140
  139. package/src/router/create-account-page-middleware.ts +0 -88
  140. package/src/router/create-api-middleware.ts +0 -1051
  141. package/src/router/create-authorization-page-middleware.ts +0 -281
  142. package/src/router/create-oauth-middleware.ts +0 -257
  143. package/src/router/error-handler.ts +0 -6
  144. package/src/router/middleware-options.ts +0 -9
  145. package/src/signer/access-token-payload.ts +0 -25
  146. package/src/signer/api-token-payload.ts +0 -18
  147. package/src/signer/signer.ts +0 -121
  148. package/src/token/refresh-token.ts +0 -30
  149. package/src/token/token-claims.ts +0 -22
  150. package/src/token/token-data.ts +0 -40
  151. package/src/token/token-id.ts +0 -25
  152. package/src/token/token-manager.ts +0 -427
  153. package/src/token/token-store.ts +0 -88
  154. package/src/types/authorization-response-error.ts +0 -27
  155. package/src/types/color-hue.ts +0 -3
  156. package/src/types/email-otp.ts +0 -3
  157. package/src/types/email.ts +0 -26
  158. package/src/types/handle.ts +0 -23
  159. package/src/types/invite-code.ts +0 -4
  160. package/src/types/par-response-error.ts +0 -25
  161. package/src/types/password.ts +0 -4
  162. package/src/types/rgb-color.ts +0 -21
  163. package/tsconfig.build.json +0 -8
  164. package/tsconfig.build.tsbuildinfo +0 -1
  165. package/tsconfig.json +0 -4
@@ -1,281 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http'
2
- import {
3
- OAuthAuthorizationRequestQuery,
4
- oauthAuthorizationRequestQuerySchema,
5
- } from '@atproto/oauth-types'
6
- import { AuthorizationError } from '../errors/authorization-error.js'
7
- import { InvalidRequestError } from '../errors/invalid-request-error.js'
8
- import {
9
- Middleware,
10
- Router,
11
- RouterCtx,
12
- getCookie,
13
- setCookie,
14
- validateFetchDest,
15
- validateFetchMode,
16
- validateFetchSite,
17
- validateOrigin,
18
- validateReferrer,
19
- } from '../lib/http/index.js'
20
- import { SecurityHeadersOptions } from '../lib/http/security-headers.js'
21
- import { formatError } from '../lib/util/error.js'
22
- import type { Awaitable } from '../lib/util/type.js'
23
- import { writeFormRedirect } from '../lib/write-form-redirect.js'
24
- import type { OAuthProvider } from '../oauth-provider.js'
25
- import { parseRequestUri, requestUriSchema } from '../request/request-uri.js'
26
- import { sendAuthorizePageFactory } from './assets/send-authorization-page.js'
27
- import { sendCookieErrorPageFactory } from './assets/send-cookie-error-page.js'
28
- import { sendErrorPageFactory } from './assets/send-error-page.js'
29
- import {
30
- sendAuthorizationResultRedirect,
31
- sendRedirect,
32
- } from './assets/send-redirect.js'
33
- import { parseRedirectUrl } from './create-api-middleware.js'
34
- import type { MiddlewareOptions } from './middleware-options.js'
35
-
36
- export function createAuthorizationPageMiddleware<
37
- Ctx extends object | void = void,
38
- Req extends IncomingMessage = IncomingMessage,
39
- Res extends ServerResponse = ServerResponse,
40
- >(
41
- server: OAuthProvider,
42
- { onError }: MiddlewareOptions<Req, Res>,
43
- ): Middleware<Ctx, Req, Res> {
44
- const issuerUrl = new URL(server.issuer)
45
- const issuerOrigin = issuerUrl.origin
46
-
47
- const securityOptions: SecurityHeadersOptions = {
48
- hsts: issuerUrl.protocol === 'http:' ? false : undefined,
49
- }
50
-
51
- const sendAuthorizePage = sendAuthorizePageFactory(
52
- server.customization,
53
- securityOptions,
54
- )
55
- const sendErrorPage = sendErrorPageFactory(
56
- server.customization,
57
- securityOptions,
58
- )
59
- const sendCookieErrorPage = sendCookieErrorPageFactory(
60
- server.customization,
61
- securityOptions,
62
- )
63
-
64
- const router = new Router<Ctx, Req, Res>(issuerUrl)
65
-
66
- router.get(
67
- '/oauth/authorize',
68
- withErrorHandler(async function (req, res) {
69
- res.setHeader('Cache-Control', 'no-store')
70
- res.setHeader('Pragma', 'no-cache')
71
-
72
- // "same-origin" is required to support the redirect test logic below (as
73
- // well as refreshing the authorization page).
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'])
77
- validateFetchMode(req, ['navigate'])
78
- validateFetchDest(req, ['document'])
79
- validateOrigin(req, issuerOrigin)
80
-
81
- // Do not perform any of the following logic if the request is invalid
82
- const query = parseOAuthAuthorizationRequestQuery(this.url)
83
-
84
- // @NOTE For some reason, even when loaded through a
85
- // ASWebAuthenticationSession, iOS will sometimes fail to properly save
86
- // cookies set during the rendering of the page. When this happens, the
87
- // authorization page logic, which relies on cookies to maintain the session,
88
- // will fail. To work around this, we perform an initial redirect to ourselves
89
- // using a form GET submit, in an attempt to verify if the browser saves
90
- // cookies on redirect or not. If it does, we proceed as normal. If it
91
- // doesn't, we redirect the user back to the client with an error message.
92
- if (
93
- // Only for iOS users
94
- req.headers['user-agent']?.includes('iPhone OS') &&
95
- // Disabled if the user already passed the test, which means their browser preserves cookies on redirect
96
- !(getCookie(req, 'cookie-test') === 'succeeded') &&
97
- // Disabled if the user already has a session
98
- !(await server.deviceManager.hasSession(req))
99
- ) {
100
- // @TODO Another possible solution would be to avoid relying on cookies if we
101
- // detect that they are not being preserved. This would mean that preserving
102
- // sessions (SSO) would not be possible for browsers that don't preserve
103
- // cookies on redirect, but at least the authorization request could still be
104
- // completed. This was not implemented yet due to the extra complexity
105
- // involved in supporting this.
106
-
107
- // 1) When the user first comes here, we will test if their browser
108
- // preserves cookies by redirecting back to ourselves
109
- if (!this.url.searchParams.has('redirect-test')) {
110
- // 2) Set a testing cookie
111
- setCookie(res, 'cookie-test', 'testing', {
112
- sameSite: 'lax',
113
- httpOnly: true,
114
- })
115
-
116
- // 3) And send an auto-submit form redirecting back to ourselves
117
- return writeFormRedirect(
118
- res,
119
- 'get',
120
- this.url.href,
121
- // 4) We add an extra query parameter to trigger the test logic after
122
- // the redirect occurred.
123
- [...this.url.searchParams, ['redirect-test', '1']],
124
- securityOptions,
125
- )
126
- } else {
127
- // 5) We just got redirected back to ourselves. Verify that the
128
- // browser preserved cookies during the redirect
129
- if (getCookie(req, 'cookie-test')) {
130
- // 6) Success! The browser preserved cookies. Proceed with the
131
- // normal authorization flow.
132
-
133
- // 7) Set a long lasting cookie to skip the test next time
134
- setCookie(res, 'cookie-test', 'succeeded', {
135
- sameSite: 'lax',
136
- maxAge: 31 * 24 * 60 * 60,
137
- httpOnly: true,
138
- })
139
- } else {
140
- // The browser did NOT preserve cookies. We have to abort the
141
- // authorization request.
142
-
143
- if (this.url.searchParams.get('redirect-test') === '1') {
144
- // 8) Show an error page to the user explaining the situation
145
-
146
- // Give the browser another chance to save cookies after the use
147
- // pressed "Continue"
148
- setCookie(res, 'cookie-test', 'testing', {
149
- sameSite: 'lax',
150
- httpOnly: true,
151
- })
152
-
153
- // Make sure next time we reach the other branch and redirect back
154
- // to the client
155
- const continueUrl = new URL(this.url.href)
156
- continueUrl.searchParams.set('redirect-test', '2')
157
- return sendCookieErrorPage(req, res, { continueUrl })
158
- } else {
159
- // 9) Once the use acknowledges the error, redirect them back to
160
- // the client with an error message.
161
-
162
- // Allow the client to understand what happened (the `error`
163
- // response parameter value is constrained by the OAuth2 spec)
164
- const message = 'ERR_COOKIES_UNSUPPORTED'
165
-
166
- // @NOTE AuthorizationError thrown here will be caught by the
167
- // error handler middleware defined below, and cause a redirect
168
- // back to the client with the error parameters.
169
- if ('request_uri' in query) {
170
- // Load and delete the authorization request
171
- const requestUri = parseRequestUri(query.request_uri, {
172
- path: ['query', 'request_uri'],
173
- })
174
- const data = await server.requestManager.get(
175
- requestUri,
176
- undefined,
177
- query.client_id,
178
- )
179
- await server.requestManager.delete(requestUri)
180
- throw new AuthorizationError(data.parameters, message)
181
- } else if ('request' in query) {
182
- const client = await server.clientManager.getClient(
183
- query.client_id,
184
- )
185
- const parameters = await server.decodeJAR(client, query)
186
- throw new AuthorizationError(parameters, message)
187
- } else {
188
- throw new AuthorizationError(query, message)
189
- }
190
- }
191
- }
192
- }
193
- }
194
-
195
- // Normal authorization flow
196
- const device = await server.deviceManager.load(req, res)
197
-
198
- const result = await server.authorize(query, device)
199
-
200
- if ('redirect' in result) {
201
- return sendAuthorizationResultRedirect(res, result, securityOptions)
202
- } else {
203
- return sendAuthorizePage(req, res, result)
204
- }
205
- }),
206
- )
207
-
208
- // This is a private endpoint that will be called by the user after the
209
- // authorization request was either approved or denied. The logic performed
210
- // here **could** be performed directly in the frontend. We decided to
211
- // implement it here to avoid duplicating the logic.
212
- router.get(
213
- '/oauth/authorize/redirect',
214
- withErrorHandler(async function (req, res) {
215
- // Ensure we come from the authorization page
216
- validateFetchSite(req, ['same-origin'])
217
- validateFetchMode(req, ['navigate'])
218
- validateFetchDest(req, ['document'])
219
- validateOrigin(req, issuerOrigin)
220
-
221
- const referrer = validateReferrer(req, {
222
- origin: issuerOrigin,
223
- pathname: '/oauth/authorize',
224
- })
225
-
226
- // Ensure we are coming from the authorization page
227
- requestUriSchema.parse(referrer.searchParams.get('request_uri'))
228
-
229
- return sendRedirect(res, parseRedirectUrl(this.url), securityOptions)
230
- }),
231
- )
232
-
233
- return router.buildMiddleware()
234
-
235
- function withErrorHandler<T extends RouterCtx>(
236
- handler: (this: T, req: Req, res: Res) => Awaitable<void>,
237
- ): Middleware<T, Req, Res> {
238
- return async function (req, res) {
239
- try {
240
- await handler.call(this, req, res)
241
- } catch (err) {
242
- onError?.(req, res, err, `Authorization Request Error`)
243
-
244
- if (!res.headersSent) {
245
- if (err instanceof AuthorizationError) {
246
- return sendAuthorizationResultRedirect(
247
- res,
248
- {
249
- issuer: server.issuer,
250
- parameters: err.parameters,
251
- redirect: err.toJSON(),
252
- },
253
- securityOptions,
254
- )
255
- } else {
256
- return sendErrorPage(req, res, err)
257
- }
258
- } else if (!res.destroyed) {
259
- res.end()
260
- }
261
- }
262
- }
263
- }
264
- }
265
-
266
- function parseOAuthAuthorizationRequestQuery(
267
- url: URL,
268
- ): OAuthAuthorizationRequestQuery {
269
- const query = Object.fromEntries(url.searchParams)
270
- const result = oauthAuthorizationRequestQuerySchema.safeParse(query, {
271
- path: ['query'],
272
- })
273
-
274
- if (!result.success) {
275
- const message = 'Invalid request parameters'
276
- const err = result.error
277
- throw new InvalidRequestError(formatError(err, message), err)
278
- }
279
-
280
- return result.data
281
- }
@@ -1,257 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http'
2
- import {
3
- oauthAuthorizationRequestParSchema,
4
- oauthClientCredentialsSchema,
5
- oauthTokenIdentificationSchema,
6
- oauthTokenRequestSchema,
7
- } from '@atproto/oauth-types'
8
- import { buildErrorPayload, buildErrorStatus } from '../errors/error-parser.js'
9
- import { InvalidClientError } from '../errors/invalid-client-error.js'
10
- import { InvalidGrantError } from '../errors/invalid-grant-error.js'
11
- import { InvalidRequestError } from '../errors/invalid-request-error.js'
12
- import { WWWAuthenticateError } from '../errors/www-authenticate-error.js'
13
- import {
14
- Middleware,
15
- Router,
16
- cacheControlMiddleware,
17
- combineMiddlewares,
18
- jsonHandler,
19
- parseHttpRequest,
20
- staticJsonMiddleware,
21
- } from '../lib/http/index.js'
22
- import { formatError } from '../lib/util/error.js'
23
- import { OAuthError } from '../oauth-errors.js'
24
- import type { OAuthProvider } from '../oauth-provider.js'
25
- import type { MiddlewareOptions } from './middleware-options.js'
26
-
27
- // CORS preflight
28
- const corsHeaders: Middleware = function (req, res, next) {
29
- res.setHeader('Access-Control-Max-Age', '86400') // 1 day
30
-
31
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
32
- //
33
- // > For requests without credentials, the literal value "*" can be
34
- // > specified as a wildcard; the value tells browsers to allow
35
- // > requesting code from any origin to access the resource.
36
- // > Attempting to use the wildcard with credentials results in an
37
- // > error.
38
- //
39
- // A "*" is safer to use than reflecting the request origin.
40
- res.setHeader('Access-Control-Allow-Origin', '*')
41
-
42
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
43
- // > The value "*" only counts as a special wildcard value for
44
- // > requests without credentials (requests without HTTP cookies or
45
- // > HTTP authentication information). In requests with credentials,
46
- // > it is treated as the literal method name "*" without special
47
- // > semantics.
48
- res.setHeader('Access-Control-Allow-Methods', '*')
49
-
50
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type,DPoP')
51
-
52
- next()
53
- }
54
-
55
- const corsPreflight: Middleware = combineMiddlewares([
56
- corsHeaders,
57
- (req, res) => {
58
- res.writeHead(200).end()
59
- },
60
- ])
61
-
62
- export function createOAuthMiddleware<
63
- Ctx extends object | void = void,
64
- Req extends IncomingMessage = IncomingMessage,
65
- Res extends ServerResponse = ServerResponse,
66
- >(
67
- server: OAuthProvider,
68
- { onError }: MiddlewareOptions<Req, Res>,
69
- ): Middleware<Ctx, Req, Res> {
70
- const router = new Router<Ctx, Req, Res>(new URL(server.issuer))
71
-
72
- //- Public OAuth endpoints
73
-
74
- router.options('/.well-known/oauth-authorization-server', corsPreflight)
75
- router.get(
76
- '/.well-known/oauth-authorization-server',
77
- corsHeaders,
78
- cacheControlMiddleware(300),
79
- staticJsonMiddleware(server.metadata),
80
- )
81
-
82
- router.options('/oauth/jwks', corsPreflight)
83
- router.get(
84
- '/oauth/jwks',
85
- corsHeaders,
86
- cacheControlMiddleware(300),
87
- staticJsonMiddleware(server.jwks),
88
- )
89
-
90
- router.options('/oauth/par', corsPreflight)
91
- router.post(
92
- '/oauth/par',
93
- corsHeaders,
94
- oauthHandler(async function (req) {
95
- const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
96
-
97
- // https://datatracker.ietf.org/doc/html/rfc9126#name-error-response
98
- // https://datatracker.ietf.org/doc/html/rfc6749#autoid-56
99
-
100
- const credentials = await oauthClientCredentialsSchema
101
- .parseAsync(payload, { path: ['body'] })
102
- .catch((err) => throwInvalidClient(err, 'Client credentials missing'))
103
-
104
- const authorizationRequest = await oauthAuthorizationRequestParSchema
105
- .parseAsync(payload, { path: ['body'] })
106
- .catch((err) =>
107
- throwInvalidRequest(err, 'Invalid authorization request'),
108
- )
109
-
110
- const dpopProof = await server.checkDpopProof(
111
- req.method!,
112
- this.url,
113
- req.headers,
114
- )
115
-
116
- return server.pushedAuthorizationRequest(
117
- credentials,
118
- authorizationRequest,
119
- dpopProof,
120
- )
121
- }, 201),
122
- )
123
- // https://datatracker.ietf.org/doc/html/rfc9126#section-2.3
124
- // > If the request did not use the POST method, the authorization server
125
- // > responds with an HTTP 405 (Method Not Allowed) status code.
126
- router.all('/oauth/par', (req, res) => {
127
- res.writeHead(405).end()
128
- })
129
-
130
- router.options('/oauth/token', corsPreflight)
131
- router.post(
132
- '/oauth/token',
133
- corsHeaders,
134
- oauthHandler(async function (req) {
135
- const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
136
-
137
- const clientMetadata = await server.deviceManager.getRequestMetadata(req)
138
-
139
- const clientCredentials = await oauthClientCredentialsSchema
140
- .parseAsync(payload, { path: ['body'] })
141
- .catch((err) => throwInvalidGrant(err, 'Client credentials missing'))
142
-
143
- const tokenRequest = await oauthTokenRequestSchema
144
- .parseAsync(payload, { path: ['body'] })
145
- .catch((err) => throwInvalidGrant(err, 'Invalid request payload'))
146
-
147
- const dpopProof = await server.checkDpopProof(
148
- req.method!,
149
- this.url,
150
- req.headers,
151
- )
152
-
153
- return server.token(
154
- clientCredentials,
155
- clientMetadata,
156
- tokenRequest,
157
- dpopProof,
158
- )
159
- }),
160
- )
161
-
162
- router.options('/oauth/revoke', corsPreflight)
163
- router.post(
164
- '/oauth/revoke',
165
- corsHeaders,
166
- oauthHandler(async function (req, res) {
167
- const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
168
-
169
- const credentials = await oauthClientCredentialsSchema
170
- .parseAsync(payload, { path: ['body'] })
171
- .catch((err) => throwInvalidRequest(err, 'Client credentials missing'))
172
-
173
- const tokenIdentification = await oauthTokenIdentificationSchema
174
- .parseAsync(payload, { path: ['body'] })
175
- .catch((err) => throwInvalidRequest(err, 'Invalid request payload'))
176
-
177
- const dpopProof = await server.checkDpopProof(
178
- req.method!,
179
- this.url,
180
- req.headers,
181
- )
182
-
183
- try {
184
- await server.revoke(credentials, tokenIdentification, dpopProof)
185
- } catch (err) {
186
- // > Note: invalid tokens do not cause an error response since the
187
- // > client cannot handle such an error in a reasonable way. Moreover,
188
- // > the purpose of the revocation request, invalidating the particular
189
- // > token, is already achieved.
190
- //
191
- // https://datatracker.ietf.org/doc/html/rfc7009#section-2.2
192
-
193
- onError?.(req, res, err, 'Failed to revoke token')
194
- }
195
-
196
- return {}
197
- }),
198
- )
199
-
200
- return router.buildMiddleware()
201
-
202
- function oauthHandler<T>(
203
- buildOAuthResponse: (this: T, req: Req, res: Res) => unknown,
204
- status?: number,
205
- ): Middleware<T, Req, Res> {
206
- return jsonHandler<T, Req, Res>(async function (req, res) {
207
- try {
208
- // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
209
- res.setHeader('Cache-Control', 'no-store')
210
- res.setHeader('Pragma', 'no-cache')
211
-
212
- // https://datatracker.ietf.org/doc/html/rfc9449#section-8.2
213
- const dpopNonce = server.nextDpopNonce()
214
- if (dpopNonce) {
215
- const name = 'DPoP-Nonce'
216
- res.setHeader(name, dpopNonce)
217
- res.appendHeader('Access-Control-Expose-Headers', name)
218
- }
219
-
220
- const json = await buildOAuthResponse.call(this, req, res)
221
- return { json, status }
222
- } catch (err) {
223
- onError?.(
224
- req,
225
- res,
226
- err,
227
- err instanceof OAuthError
228
- ? `OAuth "${err.error}" error`
229
- : 'Unexpected error',
230
- )
231
-
232
- if (!res.headersSent && err instanceof WWWAuthenticateError) {
233
- const name = 'WWW-Authenticate'
234
- res.setHeader(name, err.wwwAuthenticateHeader)
235
- res.appendHeader('Access-Control-Expose-Headers', name)
236
- }
237
-
238
- const status = buildErrorStatus(err)
239
- const json = buildErrorPayload(err)
240
-
241
- return { json, status }
242
- }
243
- })
244
- }
245
- }
246
-
247
- function throwInvalidGrant(err: unknown, prefix: string): never {
248
- throw new InvalidGrantError(formatError(err, prefix), err)
249
- }
250
-
251
- function throwInvalidClient(err: unknown, prefix: string): never {
252
- throw new InvalidClientError(formatError(err, prefix), err)
253
- }
254
-
255
- function throwInvalidRequest(err: unknown, prefix: string): never {
256
- throw new InvalidRequestError(formatError(err, prefix), err)
257
- }
@@ -1,6 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http'
2
-
3
- export type ErrorHandler<
4
- Req extends IncomingMessage = IncomingMessage,
5
- Res extends ServerResponse = ServerResponse,
6
- > = (req: Req, res: Res, err: unknown, message: string) => void
@@ -1,9 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from 'node:http'
2
- import type { ErrorHandler } from './error-handler.js'
3
-
4
- export type MiddlewareOptions<
5
- Req extends IncomingMessage = IncomingMessage,
6
- Res extends ServerResponse = ServerResponse,
7
- > = {
8
- onError?: ErrorHandler<Req, Res>
9
- }
@@ -1,25 +0,0 @@
1
- import { z } from 'zod'
2
- import { didSchema } from '@atproto/did'
3
- import { jwtPayloadSchema } from '@atproto/jwk'
4
- import { clientIdSchema } from '../client/client-id.js'
5
- import { tokenIdSchema } from '../token/token-id.js'
6
-
7
- export const accessTokenPayloadSchema = jwtPayloadSchema
8
- .partial()
9
- .extend({
10
- // Following are required
11
- jti: tokenIdSchema,
12
- sub: didSchema,
13
- exp: z.number().int(),
14
- iat: z.number().int(),
15
- iss: z.string().min(1),
16
-
17
- // @NOTE "aud", "scope", "client_id" are not required, as are stored in the
18
- // DB in 'light' access token mode.
19
-
20
- // Restrict type of following
21
- client_id: clientIdSchema.optional(),
22
- })
23
- .passthrough()
24
-
25
- export type AccessTokenPayload = z.infer<typeof accessTokenPayloadSchema>
@@ -1,18 +0,0 @@
1
- import { z } from 'zod'
2
- import { didSchema } from '@atproto/did'
3
- import { jwtPayloadSchema } from '@atproto/jwk'
4
- import { deviceIdSchema } from '../oauth-store.js'
5
- import { requestUriSchema } from '../request/request-uri.js'
6
-
7
- export const apiTokenPayloadSchema = jwtPayloadSchema
8
- .extend({
9
- sub: didSchema,
10
-
11
- deviceId: deviceIdSchema,
12
- // If the token is bound to a particular authorization request, it can only
13
- // be used in the context of that request.
14
- requestUri: requestUriSchema.optional(),
15
- })
16
- .passthrough()
17
-
18
- export type ApiTokenPayload = z.infer<typeof apiTokenPayloadSchema>