actor-gate 0.1.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.
Files changed (155) hide show
  1. package/package.json +25 -0
  2. package/src/config/base-config.d.ts +17 -0
  3. package/src/config/base-config.js +33 -0
  4. package/src/config/index.d.ts +5 -0
  5. package/src/config/index.js +5 -0
  6. package/src/config/nextjs-public-config.d.ts +46 -0
  7. package/src/config/nextjs-public-config.js +89 -0
  8. package/src/config/nextjs-server-config.d.ts +32 -0
  9. package/src/config/nextjs-server-config.js +10 -0
  10. package/src/config/react-client.d.ts +23 -0
  11. package/src/config/react-client.js +69 -0
  12. package/src/config/react-config.d.ts +18 -0
  13. package/src/config/react-config.js +38 -0
  14. package/src/core/adapters/access-token-revocation-adapter.d.ts +8 -0
  15. package/src/core/adapters/access-token-revocation-adapter.js +1 -0
  16. package/src/core/adapters/access-token-transport-adapter.d.ts +15 -0
  17. package/src/core/adapters/access-token-transport-adapter.js +1 -0
  18. package/src/core/adapters/authorization-code-adapter.d.ts +21 -0
  19. package/src/core/adapters/authorization-code-adapter.js +1 -0
  20. package/src/core/adapters/authorization-hooks.d.ts +13 -0
  21. package/src/core/adapters/authorization-hooks.js +1 -0
  22. package/src/core/adapters/index.d.ts +14 -0
  23. package/src/core/adapters/index.js +1 -0
  24. package/src/core/adapters/login-method-adapter.d.ts +7 -0
  25. package/src/core/adapters/login-method-adapter.js +1 -0
  26. package/src/core/adapters/oauth-client-adapter.d.ts +13 -0
  27. package/src/core/adapters/oauth-client-adapter.js +1 -0
  28. package/src/core/adapters/oauth-client-management-adapter.d.ts +23 -0
  29. package/src/core/adapters/oauth-client-management-adapter.js +1 -0
  30. package/src/core/adapters/oauth-grant-type.d.ts +1 -0
  31. package/src/core/adapters/oauth-grant-type.js +1 -0
  32. package/src/core/adapters/oauth-policy.d.ts +9 -0
  33. package/src/core/adapters/oauth-policy.js +1 -0
  34. package/src/core/adapters/observability-hooks.d.ts +31 -0
  35. package/src/core/adapters/observability-hooks.js +1 -0
  36. package/src/core/adapters/pending-auth-request-adapter.d.ts +18 -0
  37. package/src/core/adapters/pending-auth-request-adapter.js +1 -0
  38. package/src/core/adapters/refresh-token-adapter.d.ts +24 -0
  39. package/src/core/adapters/refresh-token-adapter.js +1 -0
  40. package/src/core/adapters/session-adapter.d.ts +14 -0
  41. package/src/core/adapters/session-adapter.js +1 -0
  42. package/src/core/adapters/token-adapter.d.ts +15 -0
  43. package/src/core/adapters/token-adapter.js +1 -0
  44. package/src/core/http/bearer-challenge.d.ts +6 -0
  45. package/src/core/http/bearer-challenge.js +16 -0
  46. package/src/core/ids/id-codec.d.ts +6 -0
  47. package/src/core/ids/id-codec.js +30 -0
  48. package/src/core/index.d.ts +9 -0
  49. package/src/core/index.js +7 -0
  50. package/src/core/oauth/pkce.d.ts +9 -0
  51. package/src/core/oauth/pkce.js +30 -0
  52. package/src/core/services/access-token-service.d.ts +42 -0
  53. package/src/core/services/access-token-service.js +304 -0
  54. package/src/core/services/auth-error.d.ts +14 -0
  55. package/src/core/services/auth-error.js +47 -0
  56. package/src/core/services/contracts.d.ts +23 -0
  57. package/src/core/services/contracts.js +1 -0
  58. package/src/core/services/direct-auth-service.d.ts +50 -0
  59. package/src/core/services/direct-auth-service.js +267 -0
  60. package/src/core/services/index.d.ts +7 -0
  61. package/src/core/services/index.js +5 -0
  62. package/src/core/services/mcp-auth-service.d.ts +39 -0
  63. package/src/core/services/mcp-auth-service.js +170 -0
  64. package/src/core/services/oauth-service.d.ts +91 -0
  65. package/src/core/services/oauth-service.js +571 -0
  66. package/src/core/services/observability.d.ts +22 -0
  67. package/src/core/services/observability.js +71 -0
  68. package/src/core/services/revocation-policy.d.ts +21 -0
  69. package/src/core/services/revocation-policy.js +51 -0
  70. package/src/core/sessions/client-session.d.ts +7 -0
  71. package/src/core/sessions/client-session.js +18 -0
  72. package/src/core/tokens/access-claims.d.ts +21 -0
  73. package/src/core/tokens/access-claims.js +128 -0
  74. package/src/core/tokens/id-claims.d.ts +20 -0
  75. package/src/core/tokens/id-claims.js +25 -0
  76. package/src/core/types/auth-contract.d.ts +33 -0
  77. package/src/core/types/auth-contract.js +1 -0
  78. package/src/express/index.d.ts +1 -0
  79. package/src/express/index.js +1 -0
  80. package/src/express/protected-route.d.ts +44 -0
  81. package/src/express/protected-route.js +119 -0
  82. package/src/index.d.ts +8 -0
  83. package/src/index.js +8 -0
  84. package/src/mcp/index.d.ts +1 -0
  85. package/src/mcp/index.js +1 -0
  86. package/src/mcp/json-rpc-auth.d.ts +5 -0
  87. package/src/mcp/json-rpc-auth.js +41 -0
  88. package/src/next/app/catch-all.d.ts +32 -0
  89. package/src/next/app/catch-all.js +82 -0
  90. package/src/next/app/cookies.d.ts +22 -0
  91. package/src/next/app/cookies.js +36 -0
  92. package/src/next/app/direct-auth-handlers.d.ts +55 -0
  93. package/src/next/app/direct-auth-handlers.js +419 -0
  94. package/src/next/app/index.d.ts +8 -0
  95. package/src/next/app/index.js +8 -0
  96. package/src/next/app/mcp-oauth-handlers.d.ts +74 -0
  97. package/src/next/app/mcp-oauth-handlers.js +365 -0
  98. package/src/next/app/protected-route.d.ts +27 -0
  99. package/src/next/app/protected-route.js +59 -0
  100. package/src/next/app/request.d.ts +12 -0
  101. package/src/next/app/request.js +30 -0
  102. package/src/next/app/response.d.ts +16 -0
  103. package/src/next/app/response.js +48 -0
  104. package/src/next/app/wrapper.d.ts +28 -0
  105. package/src/next/app/wrapper.js +78 -0
  106. package/src/next/index.d.ts +6 -0
  107. package/src/next/index.js +5 -0
  108. package/src/next/pages/catch-all.d.ts +19 -0
  109. package/src/next/pages/catch-all.js +60 -0
  110. package/src/next/pages/cookies.d.ts +41 -0
  111. package/src/next/pages/cookies.js +87 -0
  112. package/src/next/pages/direct-auth-handlers.d.ts +58 -0
  113. package/src/next/pages/direct-auth-handlers.js +425 -0
  114. package/src/next/pages/index.d.ts +8 -0
  115. package/src/next/pages/index.js +8 -0
  116. package/src/next/pages/mcp-oauth-handlers.d.ts +77 -0
  117. package/src/next/pages/mcp-oauth-handlers.js +341 -0
  118. package/src/next/pages/protected-route.d.ts +28 -0
  119. package/src/next/pages/protected-route.js +59 -0
  120. package/src/next/pages/request.d.ts +14 -0
  121. package/src/next/pages/request.js +66 -0
  122. package/src/next/pages/response.d.ts +28 -0
  123. package/src/next/pages/response.js +29 -0
  124. package/src/next/pages/wrapper.d.ts +29 -0
  125. package/src/next/pages/wrapper.js +74 -0
  126. package/src/next/rewrites.d.ts +12 -0
  127. package/src/next/rewrites.js +74 -0
  128. package/src/next/shared/auth-http.d.ts +24 -0
  129. package/src/next/shared/auth-http.js +42 -0
  130. package/src/next/shared/auth-routes.d.ts +17 -0
  131. package/src/next/shared/auth-routes.js +153 -0
  132. package/src/next/shared/direct-auth-utils.d.ts +71 -0
  133. package/src/next/shared/direct-auth-utils.js +275 -0
  134. package/src/next/shared/oauth-utils.d.ts +45 -0
  135. package/src/next/shared/oauth-utils.js +308 -0
  136. package/src/next/shared/well-known-utils.d.ts +46 -0
  137. package/src/next/shared/well-known-utils.js +108 -0
  138. package/src/testing/in-memory/in-memory-access-token-revocation-adapter.d.ts +2 -0
  139. package/src/testing/in-memory/in-memory-access-token-revocation-adapter.js +14 -0
  140. package/src/testing/in-memory/in-memory-authorization-code-adapter.d.ts +2 -0
  141. package/src/testing/in-memory/in-memory-authorization-code-adapter.js +36 -0
  142. package/src/testing/in-memory/in-memory-oauth-client-adapter.d.ts +14 -0
  143. package/src/testing/in-memory/in-memory-oauth-client-adapter.js +26 -0
  144. package/src/testing/in-memory/in-memory-pending-auth-request-adapter.d.ts +2 -0
  145. package/src/testing/in-memory/in-memory-pending-auth-request-adapter.js +43 -0
  146. package/src/testing/in-memory/in-memory-refresh-token-adapter.d.ts +2 -0
  147. package/src/testing/in-memory/in-memory-refresh-token-adapter.js +67 -0
  148. package/src/testing/in-memory/in-memory-session-adapter.d.ts +6 -0
  149. package/src/testing/in-memory/in-memory-session-adapter.js +43 -0
  150. package/src/testing/in-memory/index.d.ts +7 -0
  151. package/src/testing/in-memory/index.js +7 -0
  152. package/src/testing/in-memory/test-fixtures.d.ts +5 -0
  153. package/src/testing/in-memory/test-fixtures.js +18 -0
  154. package/src/testing/index.d.ts +2 -0
  155. package/src/testing/index.js +4 -0
@@ -0,0 +1,341 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { AuthServiceError } from '../../core/services/auth-error';
3
+ import { sendPagesRedirect } from './response';
4
+ import { withPagesAuthRoute } from './wrapper';
5
+ import { appendUrlParams, parseAuthorizeRequest, parseBodyToRecord, parseBoolean, parseOAuthTokenRequest, parsePositiveSafeInteger, toSingleString, } from '../shared/oauth-utils';
6
+ import { buildWellKnownMetadata, resolveRequestOrigin, } from '../shared/well-known-utils';
7
+ function sendMethodNotAllowed(res, allowedMethods) {
8
+ res.setHeader('Allow', allowedMethods.join(', '));
9
+ res.status(405).json({
10
+ error: 'method_not_allowed',
11
+ error_description: 'HTTP method not allowed for auth route.',
12
+ });
13
+ }
14
+ function getPagesOrigin(req, fallbackOrigin) {
15
+ const hostHeader = toSingleString(req.headers.host);
16
+ const forwardedHostHeader = toSingleString(req.headers['x-forwarded-host']);
17
+ const forwardedProtoHeader = toSingleString(req.headers['x-forwarded-proto']);
18
+ return resolveRequestOrigin({
19
+ ...(req.url === undefined ? {} : { requestUrl: req.url }),
20
+ ...(hostHeader === undefined ? {} : { hostHeader }),
21
+ ...(forwardedHostHeader === undefined ? {} : { forwardedHostHeader }),
22
+ ...(forwardedProtoHeader === undefined ? {} : { forwardedProtoHeader }),
23
+ ...(fallbackOrigin === undefined ? {} : { fallbackOrigin }),
24
+ });
25
+ }
26
+ function resolveMetadata(req, options) {
27
+ const issuerValue = typeof options.issuer === 'function' ? options.issuer(req) : options.issuer;
28
+ const issuer = issuerValue ?? getPagesOrigin(req, options.fallbackOrigin);
29
+ const protectedResourceValue = typeof options.protectedResource === 'function'
30
+ ? options.protectedResource(req)
31
+ : options.protectedResource;
32
+ const authorizationServersValue = typeof options.authorizationServers === 'function'
33
+ ? options.authorizationServers(req)
34
+ : options.authorizationServers;
35
+ return buildWellKnownMetadata({
36
+ issuer,
37
+ ...(options.authorizationEndpoint === undefined
38
+ ? {}
39
+ : { authorizationEndpoint: options.authorizationEndpoint }),
40
+ ...(options.tokenEndpoint === undefined
41
+ ? {}
42
+ : { tokenEndpoint: options.tokenEndpoint }),
43
+ ...(protectedResourceValue === undefined
44
+ ? {}
45
+ : { protectedResource: protectedResourceValue }),
46
+ ...(authorizationServersValue === undefined
47
+ ? {}
48
+ : { authorizationServers: authorizationServersValue }),
49
+ ...(options.responseTypesSupported === undefined
50
+ ? {}
51
+ : { responseTypesSupported: options.responseTypesSupported }),
52
+ ...(options.grantTypesSupported === undefined
53
+ ? {}
54
+ : { grantTypesSupported: options.grantTypesSupported }),
55
+ ...(options.codeChallengeMethodsSupported === undefined
56
+ ? {}
57
+ : {
58
+ codeChallengeMethodsSupported: options.codeChallengeMethodsSupported,
59
+ }),
60
+ ...(options.tokenEndpointAuthMethodsSupported === undefined
61
+ ? {}
62
+ : {
63
+ tokenEndpointAuthMethodsSupported: options.tokenEndpointAuthMethodsSupported,
64
+ }),
65
+ ...(options.scopesSupported === undefined
66
+ ? {}
67
+ : { scopesSupported: options.scopesSupported }),
68
+ });
69
+ }
70
+ function buildTokenOutput(input) {
71
+ const expiresIn = Math.max(0, input.accessClaims.exp - input.accessClaims.iat);
72
+ return {
73
+ access_token: input.accessToken,
74
+ token_type: 'Bearer',
75
+ expires_in: expiresIn,
76
+ ...(input.refreshToken === undefined
77
+ ? {}
78
+ : { refresh_token: input.refreshToken }),
79
+ };
80
+ }
81
+ export function createPagesMcpHandler(options) {
82
+ return withPagesAuthRoute({
83
+ ...(options.requestIdHeaderName === undefined
84
+ ? {}
85
+ : { requestIdHeaderName: options.requestIdHeaderName }),
86
+ ...(options.authorizationHeaderName === undefined
87
+ ? {}
88
+ : { authorizationHeaderName: options.authorizationHeaderName }),
89
+ challenge: {
90
+ ...(options.challengeScope === undefined
91
+ ? {}
92
+ : { scope: options.challengeScope }),
93
+ ...(options.resourceMetadataUrl === undefined
94
+ ? {}
95
+ : { resourceMetadataUrl: options.resourceMetadataUrl }),
96
+ },
97
+ handler: async ({ req, res, requestId, transport }) => {
98
+ const body = options.parseBody ? options.parseBody(req) : req.body;
99
+ const authentication = await options.mcpAuthService.authenticateRequest({
100
+ body,
101
+ transport,
102
+ ...(requestId === undefined ? {} : { requestId }),
103
+ ...(options.expectedAudience === undefined
104
+ ? {}
105
+ : { expectedAudience: options.expectedAudience }),
106
+ ...(options.allowedActors === undefined
107
+ ? {}
108
+ : { allowedActors: options.allowedActors }),
109
+ });
110
+ return options.handleRequest({
111
+ req,
112
+ res,
113
+ body,
114
+ authentication,
115
+ ...(requestId === undefined ? {} : { requestId }),
116
+ });
117
+ },
118
+ });
119
+ }
120
+ export function createPagesOAuthHandlers(options) {
121
+ const requiredScope = options.requiredScope ?? 'mcp:access';
122
+ const generateRequestId = options.generateRequestId ?? (() => randomUUID());
123
+ const authorizationCodeSessionTtlSeconds = parsePositiveSafeInteger(options.token?.authorizationCodeSessionTtlSeconds ?? 3600, 'authorizationCodeSessionTtlSeconds');
124
+ const clientCredentialsSessionTtlSeconds = parsePositiveSafeInteger(options.token?.clientCredentials?.sessionTtlSeconds ?? 3600, 'clientCredentials.sessionTtlSeconds');
125
+ const getAuthorizeLoginLocation = options.getAuthorizeLoginLocation ??
126
+ (({ requestId }) => `/login?request_id=${requestId}`);
127
+ const authorize = withPagesAuthRoute({
128
+ ...(options.requestIdHeaderName === undefined
129
+ ? {}
130
+ : { requestIdHeaderName: options.requestIdHeaderName }),
131
+ handler: async ({ req, res, requestId }) => {
132
+ if (req.method !== 'GET') {
133
+ sendMethodNotAllowed(res, ['GET']);
134
+ return;
135
+ }
136
+ const parsed = parseAuthorizeRequest({
137
+ query: req.query,
138
+ requiredScope,
139
+ ...(options.defaultCodeMethod === undefined
140
+ ? {}
141
+ : { defaultCodeMethod: options.defaultCodeMethod }),
142
+ ...(options.requireState === undefined
143
+ ? {}
144
+ : { requireState: options.requireState }),
145
+ });
146
+ const oauthRequestId = generateRequestId();
147
+ const started = await options.oauthService.startAuthorization({
148
+ requestId: oauthRequestId,
149
+ clientId: parsed.clientId,
150
+ redirectUri: parsed.redirectUri,
151
+ codeChallenge: parsed.codeChallenge,
152
+ codeMethod: parsed.codeMethod,
153
+ scopes: parsed.scopes,
154
+ ...(parsed.state === undefined ? {} : { state: parsed.state }),
155
+ ...(requestId === undefined ? {} : { auditRequestId: requestId }),
156
+ });
157
+ return {
158
+ location: getAuthorizeLoginLocation({
159
+ req,
160
+ requestId: oauthRequestId,
161
+ expiresAtUnix: started.expiresAtUnix,
162
+ }),
163
+ statusCode: 302,
164
+ };
165
+ },
166
+ send: ({ res, output }) => {
167
+ sendPagesRedirect(res, output);
168
+ },
169
+ });
170
+ const authorizeConfirm = withPagesAuthRoute({
171
+ ...(options.requestIdHeaderName === undefined
172
+ ? {}
173
+ : { requestIdHeaderName: options.requestIdHeaderName }),
174
+ handler: async ({ req, res, requestId }) => {
175
+ if (req.method !== 'POST') {
176
+ sendMethodNotAllowed(res, ['POST']);
177
+ return;
178
+ }
179
+ const body = parseBodyToRecord(req.body);
180
+ const pendingRequestId = toSingleString(body?.request_id) ??
181
+ toSingleString(req.query.request_id);
182
+ if (pendingRequestId === undefined) {
183
+ throw new AuthServiceError({
184
+ code: 'invalid_request',
185
+ message: 'Missing required field "request_id".',
186
+ });
187
+ }
188
+ const approved = parseBoolean(body?.approved, true);
189
+ const userId = await options.resolveUserId({
190
+ req,
191
+ res,
192
+ requestId: pendingRequestId,
193
+ approved,
194
+ });
195
+ if (userId === null) {
196
+ throw new AuthServiceError({
197
+ code: 'unauthorized',
198
+ message: 'Authenticated user is required to confirm authorization.',
199
+ });
200
+ }
201
+ const confirmed = await options.oauthService.confirmAuthorization({
202
+ requestId: pendingRequestId,
203
+ userId,
204
+ approved,
205
+ ...(requestId === undefined ? {} : { auditRequestId: requestId }),
206
+ });
207
+ const location = confirmed.approved
208
+ ? appendUrlParams(confirmed.redirectUri, {
209
+ code: confirmed.authorizationCode,
210
+ state: confirmed.state,
211
+ })
212
+ : appendUrlParams(confirmed.redirectUri, {
213
+ error: 'access_denied',
214
+ state: confirmed.state,
215
+ });
216
+ return {
217
+ location,
218
+ statusCode: 302,
219
+ };
220
+ },
221
+ send: ({ res, output }) => {
222
+ sendPagesRedirect(res, output);
223
+ },
224
+ });
225
+ const token = withPagesAuthRoute({
226
+ ...(options.requestIdHeaderName === undefined
227
+ ? {}
228
+ : { requestIdHeaderName: options.requestIdHeaderName }),
229
+ ...(options.authorizationHeaderName === undefined
230
+ ? {}
231
+ : { authorizationHeaderName: options.authorizationHeaderName }),
232
+ handler: async ({ req, res, requestId }) => {
233
+ if (req.method !== 'POST') {
234
+ sendMethodNotAllowed(res, ['POST']);
235
+ return;
236
+ }
237
+ const authorizationHeader = toSingleString(req.headers[options.authorizationHeaderName ?? 'authorization']);
238
+ const parsed = parseOAuthTokenRequest({
239
+ body: req.body,
240
+ ...(authorizationHeader === undefined ? {} : { authorizationHeader }),
241
+ ...(options.token?.clientCredentials?.enabled === undefined
242
+ ? {}
243
+ : {
244
+ allowClientCredentials: options.token.clientCredentials.enabled,
245
+ }),
246
+ ...(options.token?.allowGrantTypeInference === undefined
247
+ ? {}
248
+ : {
249
+ allowGrantTypeInference: options.token.allowGrantTypeInference,
250
+ }),
251
+ });
252
+ if (parsed.grantType === 'authorization_code') {
253
+ const exchanged = await options.oauthService.exchangeAuthorizationCode({
254
+ clientId: parsed.clientId,
255
+ clientSecret: parsed.clientSecret,
256
+ authorizationCode: parsed.authorizationCode,
257
+ redirectUri: parsed.redirectUri,
258
+ codeVerifier: parsed.codeVerifier,
259
+ sessionTtlSeconds: authorizationCodeSessionTtlSeconds,
260
+ ...(options.token?.issueRefreshToken === undefined
261
+ ? {}
262
+ : { issueRefreshToken: options.token.issueRefreshToken }),
263
+ ...(requestId === undefined ? {} : { auditRequestId: requestId }),
264
+ });
265
+ return buildTokenOutput({
266
+ accessToken: exchanged.accessToken,
267
+ accessClaims: exchanged.accessClaims,
268
+ ...(exchanged.refreshToken === undefined
269
+ ? {}
270
+ : { refreshToken: exchanged.refreshToken }),
271
+ });
272
+ }
273
+ if (parsed.grantType === 'refresh_token') {
274
+ const exchanged = await options.oauthService.exchangeRefreshToken({
275
+ clientId: parsed.clientId,
276
+ clientSecret: parsed.clientSecret,
277
+ refreshToken: parsed.refreshToken,
278
+ ...(requestId === undefined ? {} : { auditRequestId: requestId }),
279
+ });
280
+ return buildTokenOutput({
281
+ accessToken: exchanged.accessToken,
282
+ accessClaims: exchanged.accessClaims,
283
+ refreshToken: exchanged.refreshToken,
284
+ });
285
+ }
286
+ const exchanged = await options.oauthService.exchangeClientCredentials({
287
+ clientId: parsed.clientId,
288
+ clientSecret: parsed.clientSecret,
289
+ sessionTtlSeconds: clientCredentialsSessionTtlSeconds,
290
+ ...(requestId === undefined ? {} : { auditRequestId: requestId }),
291
+ });
292
+ return buildTokenOutput({
293
+ accessToken: exchanged.accessToken,
294
+ accessClaims: exchanged.accessClaims,
295
+ });
296
+ },
297
+ });
298
+ return {
299
+ authorize,
300
+ authorizeConfirm,
301
+ token,
302
+ };
303
+ }
304
+ export function createPagesWellKnownHandlers(options = {}) {
305
+ const cacheControlHeader = options.cacheControlHeader ?? 'no-store';
306
+ const oauthProtectedResource = (req, res) => {
307
+ if (req.method !== 'GET') {
308
+ sendMethodNotAllowed(res, ['GET']);
309
+ return;
310
+ }
311
+ const metadata = resolveMetadata(req, options);
312
+ res.setHeader('content-type', 'application/json');
313
+ res.setHeader('cache-control', cacheControlHeader);
314
+ res.status(200).json(metadata.oauthProtectedResource);
315
+ };
316
+ const oauthAuthorizationServer = (req, res) => {
317
+ if (req.method !== 'GET') {
318
+ sendMethodNotAllowed(res, ['GET']);
319
+ return;
320
+ }
321
+ const metadata = resolveMetadata(req, options);
322
+ res.setHeader('content-type', 'application/json');
323
+ res.setHeader('cache-control', cacheControlHeader);
324
+ res.status(200).json(metadata.oauthAuthorizationServer);
325
+ };
326
+ const openidConfiguration = (req, res) => {
327
+ if (req.method !== 'GET') {
328
+ sendMethodNotAllowed(res, ['GET']);
329
+ return;
330
+ }
331
+ const metadata = resolveMetadata(req, options);
332
+ res.setHeader('content-type', 'application/json');
333
+ res.setHeader('cache-control', cacheControlHeader);
334
+ res.status(200).json(metadata.openidConfiguration);
335
+ };
336
+ return {
337
+ oauthProtectedResource,
338
+ oauthAuthorizationServer,
339
+ openidConfiguration,
340
+ };
341
+ }
@@ -0,0 +1,28 @@
1
+ import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';
2
+ import type { AccessTokenSource } from '../../core/adapters/access-token-transport-adapter';
3
+ import type { ValidatedAccessTokenResult } from '../../core/services/contracts';
4
+ import type { DirectAuthService } from '../../core/services/direct-auth-service';
5
+ import type { AuthActor } from '../../core/types/auth-contract';
6
+ import { type DirectAccessTokenTransportConfig } from '../shared/direct-auth-utils';
7
+ import { type WithPagesAuthRouteOptions } from './wrapper';
8
+ export type PagesProtectedAuth<TSessionId, TUserId, TActor extends AuthActor = AuthActor, TServerSessionData extends Record<string, unknown> = Record<string, never>, TClientSessionData extends Record<string, unknown> = Record<string, never>, TExtClaims extends Record<string, unknown> = Record<string, never>> = ValidatedAccessTokenResult<TSessionId, TUserId, TActor, TServerSessionData, TClientSessionData, TExtClaims> & {
9
+ accessToken: string;
10
+ accessTokenSource: AccessTokenSource | undefined;
11
+ };
12
+ export type PagesProtectedRouteContext<TSessionId, TUserId, TActor extends AuthActor = AuthActor, TServerSessionData extends Record<string, unknown> = Record<string, never>, TClientSessionData extends Record<string, unknown> = Record<string, never>, TExtClaims extends Record<string, unknown> = Record<string, never>> = {
13
+ req: NextApiRequest;
14
+ res: NextApiResponse;
15
+ requestId: string | undefined;
16
+ auth: PagesProtectedAuth<TSessionId, TUserId, TActor, TServerSessionData, TClientSessionData, TExtClaims>;
17
+ };
18
+ export type WithPagesProtectedRouteOptions<TSessionId, TUserId, TActor extends AuthActor = AuthActor, TServerSessionData extends Record<string, unknown> = Record<string, never>, TClientSessionData extends Record<string, unknown> = Record<string, never>, TExtClaims extends Record<string, unknown> = Record<string, never>, TOutput = unknown> = {
19
+ directAuthService: DirectAuthService<TSessionId, TUserId, TActor, TServerSessionData, TClientSessionData, TExtClaims>;
20
+ handler: (input: PagesProtectedRouteContext<TSessionId, TUserId, TActor, TServerSessionData, TClientSessionData, TExtClaims>) => Promise<TOutput | void> | TOutput | void;
21
+ expectedAudience?: string;
22
+ allowedActors?: readonly TActor[] | ReadonlySet<TActor>;
23
+ accessTokenTransport?: DirectAccessTokenTransportConfig<TActor>;
24
+ requestIdHeaderName?: string;
25
+ authorizationHeaderName?: string;
26
+ challenge?: WithPagesAuthRouteOptions<TOutput>['challenge'];
27
+ };
28
+ export declare function withPagesProtectedRoute<TSessionId, TUserId, TActor extends AuthActor = AuthActor, TServerSessionData extends Record<string, unknown> = Record<string, never>, TClientSessionData extends Record<string, unknown> = Record<string, never>, TExtClaims extends Record<string, unknown> = Record<string, never>, TOutput = unknown>(options: WithPagesProtectedRouteOptions<TSessionId, TUserId, TActor, TServerSessionData, TClientSessionData, TExtClaims, TOutput>): NextApiHandler;
@@ -0,0 +1,59 @@
1
+ import { AuthServiceError } from '../../core/services/auth-error';
2
+ import { assertBearerOnlyActorPolicy, resolveAccessTokenTransportAdapter, } from '../shared/direct-auth-utils';
3
+ import { withPagesAuthRoute } from './wrapper';
4
+ export function withPagesProtectedRoute(options) {
5
+ const accessTokenTransportAdapter = resolveAccessTokenTransportAdapter(options.accessTokenTransport);
6
+ return withPagesAuthRoute({
7
+ ...(options.challenge === undefined
8
+ ? {}
9
+ : { challenge: options.challenge }),
10
+ ...(options.requestIdHeaderName === undefined
11
+ ? {}
12
+ : { requestIdHeaderName: options.requestIdHeaderName }),
13
+ ...(options.authorizationHeaderName === undefined
14
+ ? {}
15
+ : { authorizationHeaderName: options.authorizationHeaderName }),
16
+ handler: async ({ req, res, requestId, transport }) => {
17
+ const extractedAccessToken = accessTokenTransportAdapter.extractAccessToken({
18
+ transport,
19
+ });
20
+ if (!extractedAccessToken.token) {
21
+ throw new AuthServiceError({
22
+ code: 'invalid_token',
23
+ message: 'Access token is required.',
24
+ });
25
+ }
26
+ const validatedAccessToken = await options.directAuthService.validateAccessToken({
27
+ accessToken: extractedAccessToken.token,
28
+ ...(requestId === undefined ? {} : { requestId }),
29
+ ...(options.expectedAudience === undefined
30
+ ? {}
31
+ : { expectedAudience: options.expectedAudience }),
32
+ ...(options.allowedActors === undefined
33
+ ? {}
34
+ : { allowedActors: options.allowedActors }),
35
+ });
36
+ assertBearerOnlyActorPolicy({
37
+ actor: validatedAccessToken.claims.actor,
38
+ ...(extractedAccessToken.source === undefined
39
+ ? {}
40
+ : { source: extractedAccessToken.source }),
41
+ ...(options.accessTokenTransport?.requireBearerForActors === undefined
42
+ ? {}
43
+ : {
44
+ requireBearerForActors: options.accessTokenTransport.requireBearerForActors,
45
+ }),
46
+ });
47
+ return options.handler({
48
+ req,
49
+ res,
50
+ requestId,
51
+ auth: {
52
+ ...validatedAccessToken,
53
+ accessToken: extractedAccessToken.token,
54
+ accessTokenSource: extractedAccessToken.source,
55
+ },
56
+ });
57
+ },
58
+ });
59
+ }
@@ -0,0 +1,14 @@
1
+ import type { AccessTokenTransportInput } from '../../core/adapters/access-token-transport-adapter';
2
+ type RequestLike = {
3
+ headers: Record<string, string | string[] | undefined>;
4
+ cookies?: Record<string, string | undefined>;
5
+ };
6
+ export declare function parseCookieHeader(rawCookieHeader: string | undefined): Record<string, string | undefined>;
7
+ export declare function getPagesAuthorizationHeader(req: RequestLike, headerName?: string): string | undefined;
8
+ export declare function getPagesRequestId(req: RequestLike, headerName?: string): string | undefined;
9
+ export declare function getPagesCookies(req: RequestLike): Record<string, string | undefined>;
10
+ export declare function buildPagesAccessTokenTransportInput(input: {
11
+ req: RequestLike;
12
+ authorizationHeaderName?: string;
13
+ }): AccessTokenTransportInput;
14
+ export {};
@@ -0,0 +1,66 @@
1
+ function getFirstHeaderValue(value) {
2
+ if (Array.isArray(value)) {
3
+ for (const item of value) {
4
+ if (item.length > 0) {
5
+ return item;
6
+ }
7
+ }
8
+ return undefined;
9
+ }
10
+ if (typeof value === 'string' && value.length > 0) {
11
+ return value;
12
+ }
13
+ return undefined;
14
+ }
15
+ function decodeCookieComponent(value) {
16
+ try {
17
+ return decodeURIComponent(value);
18
+ }
19
+ catch {
20
+ return value;
21
+ }
22
+ }
23
+ export function parseCookieHeader(rawCookieHeader) {
24
+ if (!rawCookieHeader) {
25
+ return {};
26
+ }
27
+ const cookies = {};
28
+ for (const segment of rawCookieHeader.split(';')) {
29
+ const separatorIndex = segment.indexOf('=');
30
+ if (separatorIndex < 0) {
31
+ continue;
32
+ }
33
+ const rawName = segment.slice(0, separatorIndex).trim();
34
+ if (rawName.length === 0) {
35
+ continue;
36
+ }
37
+ const rawValue = segment.slice(separatorIndex + 1).trim();
38
+ cookies[decodeCookieComponent(rawName)] = decodeCookieComponent(rawValue);
39
+ }
40
+ return cookies;
41
+ }
42
+ export function getPagesAuthorizationHeader(req, headerName = 'authorization') {
43
+ return getFirstHeaderValue(req.headers[headerName]);
44
+ }
45
+ export function getPagesRequestId(req, headerName = 'x-request-id') {
46
+ const requestId = getFirstHeaderValue(req.headers[headerName]);
47
+ if (!requestId) {
48
+ return undefined;
49
+ }
50
+ const trimmed = requestId.trim();
51
+ return trimmed.length > 0 ? trimmed : undefined;
52
+ }
53
+ export function getPagesCookies(req) {
54
+ if (req.cookies && Object.keys(req.cookies).length > 0) {
55
+ return { ...req.cookies };
56
+ }
57
+ return parseCookieHeader(getFirstHeaderValue(req.headers.cookie));
58
+ }
59
+ export function buildPagesAccessTokenTransportInput(input) {
60
+ const authorizationHeader = getPagesAuthorizationHeader(input.req, input.authorizationHeaderName);
61
+ const cookies = getPagesCookies(input.req);
62
+ return {
63
+ ...(authorizationHeader === undefined ? {} : { authorizationHeader }),
64
+ ...(Object.keys(cookies).length === 0 ? {} : { cookies }),
65
+ };
66
+ }
@@ -0,0 +1,28 @@
1
+ import type { AuthServiceError } from '../../core/services/auth-error';
2
+ import { type AuthErrorResponseBody } from '../shared/auth-http';
3
+ export type PagesAuthErrorResponseBody = AuthErrorResponseBody;
4
+ type StatusJsonResponse = {
5
+ status(code: number): {
6
+ json(body: PagesAuthErrorResponseBody): void;
7
+ };
8
+ };
9
+ type AuthErrorResponse = StatusJsonResponse & {
10
+ setHeader(name: 'WWW-Authenticate', value: string): void;
11
+ };
12
+ type RedirectResponse = {
13
+ redirect(statusCode: number, location: string): void;
14
+ };
15
+ export declare function sendPagesAuthError(res: AuthErrorResponse, error: AuthServiceError, options?: {
16
+ requestId?: string;
17
+ includeChallengeError?: boolean;
18
+ challengeScope?: string;
19
+ resourceMetadataUrl?: string;
20
+ }): void;
21
+ export declare function sendPagesSystemError(res: StatusJsonResponse, options?: {
22
+ requestId?: string;
23
+ }): void;
24
+ export declare function sendPagesRedirect(res: RedirectResponse, input: {
25
+ location: string;
26
+ statusCode?: number;
27
+ }): void;
28
+ export {};
@@ -0,0 +1,29 @@
1
+ import { buildAuthErrorHttpResponse, buildSystemErrorHttpResponse, } from '../shared/auth-http';
2
+ export function sendPagesAuthError(res, error, options) {
3
+ const mapped = buildAuthErrorHttpResponse({
4
+ error,
5
+ ...(options?.requestId === undefined
6
+ ? {}
7
+ : { requestId: options.requestId }),
8
+ ...(options?.includeChallengeError === undefined
9
+ ? {}
10
+ : { includeChallengeError: options.includeChallengeError }),
11
+ ...(options?.challengeScope === undefined
12
+ ? {}
13
+ : { challengeScope: options.challengeScope }),
14
+ ...(options?.resourceMetadataUrl === undefined
15
+ ? {}
16
+ : { resourceMetadataUrl: options.resourceMetadataUrl }),
17
+ });
18
+ if (mapped.challengeHeader !== undefined) {
19
+ res.setHeader('WWW-Authenticate', mapped.challengeHeader);
20
+ }
21
+ res.status(mapped.statusCode).json(mapped.body);
22
+ }
23
+ export function sendPagesSystemError(res, options) {
24
+ const mapped = buildSystemErrorHttpResponse(options);
25
+ res.status(mapped.statusCode).json(mapped.body);
26
+ }
27
+ export function sendPagesRedirect(res, input) {
28
+ res.redirect(input.statusCode ?? 302, input.location);
29
+ }
@@ -0,0 +1,29 @@
1
+ import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';
2
+ import type { AccessTokenTransportInput } from '../../core/adapters/access-token-transport-adapter';
3
+ export type PagesAuthRouteContext = {
4
+ req: NextApiRequest;
5
+ res: NextApiResponse;
6
+ requestId?: string;
7
+ transport: AccessTokenTransportInput;
8
+ };
9
+ export type WithPagesAuthRouteOptions<TOutput> = {
10
+ handler: (input: PagesAuthRouteContext) => Promise<TOutput | void> | TOutput | void;
11
+ send?: (input: {
12
+ res: NextApiResponse;
13
+ output: TOutput;
14
+ }) => void;
15
+ requestIdHeaderName?: string;
16
+ authorizationHeaderName?: string;
17
+ challenge?: {
18
+ includeErrorInWwwAuthenticate?: boolean;
19
+ scope?: string;
20
+ resourceMetadataUrl?: string;
21
+ };
22
+ onSystemError?: (input: {
23
+ req: NextApiRequest;
24
+ res: NextApiResponse;
25
+ error: unknown;
26
+ requestId?: string;
27
+ }) => Promise<void> | void;
28
+ };
29
+ export declare function withPagesAuthRoute<TOutput>(options: WithPagesAuthRouteOptions<TOutput>): NextApiHandler;
@@ -0,0 +1,74 @@
1
+ import { isAuthServiceError } from '../../core/services/auth-error';
2
+ import { buildPagesAccessTokenTransportInput, getPagesRequestId, } from './request';
3
+ import { sendPagesAuthError, sendPagesSystemError } from './response';
4
+ export function withPagesAuthRoute(options) {
5
+ return async function pagesAuthRoute(req, res) {
6
+ const requestId = getPagesRequestId(req, options.requestIdHeaderName);
7
+ const transport = buildPagesAccessTokenTransportInput({
8
+ req,
9
+ ...(options.authorizationHeaderName === undefined
10
+ ? {}
11
+ : { authorizationHeaderName: options.authorizationHeaderName }),
12
+ });
13
+ try {
14
+ const output = await options.handler({
15
+ req,
16
+ res,
17
+ ...(requestId === undefined ? {} : { requestId }),
18
+ transport,
19
+ });
20
+ if (res.writableEnded) {
21
+ return;
22
+ }
23
+ if (output === undefined) {
24
+ res.status(204).end();
25
+ return;
26
+ }
27
+ if (options.send) {
28
+ options.send({ res, output });
29
+ return;
30
+ }
31
+ res.status(200).json(output);
32
+ }
33
+ catch (error) {
34
+ if (res.writableEnded) {
35
+ return;
36
+ }
37
+ if (isAuthServiceError(error)) {
38
+ sendPagesAuthError(res, error, {
39
+ ...(requestId === undefined ? {} : { requestId }),
40
+ ...(options.challenge?.scope === undefined
41
+ ? {}
42
+ : { challengeScope: options.challenge.scope }),
43
+ ...(options.challenge?.resourceMetadataUrl === undefined
44
+ ? {}
45
+ : { resourceMetadataUrl: options.challenge.resourceMetadataUrl }),
46
+ ...(options.challenge?.includeErrorInWwwAuthenticate === undefined
47
+ ? {}
48
+ : {
49
+ includeChallengeError: options.challenge.includeErrorInWwwAuthenticate,
50
+ }),
51
+ });
52
+ return;
53
+ }
54
+ if (options.onSystemError) {
55
+ try {
56
+ await options.onSystemError({
57
+ req,
58
+ res,
59
+ error,
60
+ ...(requestId === undefined ? {} : { requestId }),
61
+ });
62
+ }
63
+ catch {
64
+ // Wrapper never throws from system-error hooks.
65
+ }
66
+ }
67
+ if (!res.writableEnded) {
68
+ sendPagesSystemError(res, {
69
+ ...(requestId === undefined ? {} : { requestId }),
70
+ });
71
+ }
72
+ }
73
+ };
74
+ }