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.
- package/package.json +25 -0
- package/src/config/base-config.d.ts +17 -0
- package/src/config/base-config.js +33 -0
- package/src/config/index.d.ts +5 -0
- package/src/config/index.js +5 -0
- package/src/config/nextjs-public-config.d.ts +46 -0
- package/src/config/nextjs-public-config.js +89 -0
- package/src/config/nextjs-server-config.d.ts +32 -0
- package/src/config/nextjs-server-config.js +10 -0
- package/src/config/react-client.d.ts +23 -0
- package/src/config/react-client.js +69 -0
- package/src/config/react-config.d.ts +18 -0
- package/src/config/react-config.js +38 -0
- package/src/core/adapters/access-token-revocation-adapter.d.ts +8 -0
- package/src/core/adapters/access-token-revocation-adapter.js +1 -0
- package/src/core/adapters/access-token-transport-adapter.d.ts +15 -0
- package/src/core/adapters/access-token-transport-adapter.js +1 -0
- package/src/core/adapters/authorization-code-adapter.d.ts +21 -0
- package/src/core/adapters/authorization-code-adapter.js +1 -0
- package/src/core/adapters/authorization-hooks.d.ts +13 -0
- package/src/core/adapters/authorization-hooks.js +1 -0
- package/src/core/adapters/index.d.ts +14 -0
- package/src/core/adapters/index.js +1 -0
- package/src/core/adapters/login-method-adapter.d.ts +7 -0
- package/src/core/adapters/login-method-adapter.js +1 -0
- package/src/core/adapters/oauth-client-adapter.d.ts +13 -0
- package/src/core/adapters/oauth-client-adapter.js +1 -0
- package/src/core/adapters/oauth-client-management-adapter.d.ts +23 -0
- package/src/core/adapters/oauth-client-management-adapter.js +1 -0
- package/src/core/adapters/oauth-grant-type.d.ts +1 -0
- package/src/core/adapters/oauth-grant-type.js +1 -0
- package/src/core/adapters/oauth-policy.d.ts +9 -0
- package/src/core/adapters/oauth-policy.js +1 -0
- package/src/core/adapters/observability-hooks.d.ts +31 -0
- package/src/core/adapters/observability-hooks.js +1 -0
- package/src/core/adapters/pending-auth-request-adapter.d.ts +18 -0
- package/src/core/adapters/pending-auth-request-adapter.js +1 -0
- package/src/core/adapters/refresh-token-adapter.d.ts +24 -0
- package/src/core/adapters/refresh-token-adapter.js +1 -0
- package/src/core/adapters/session-adapter.d.ts +14 -0
- package/src/core/adapters/session-adapter.js +1 -0
- package/src/core/adapters/token-adapter.d.ts +15 -0
- package/src/core/adapters/token-adapter.js +1 -0
- package/src/core/http/bearer-challenge.d.ts +6 -0
- package/src/core/http/bearer-challenge.js +16 -0
- package/src/core/ids/id-codec.d.ts +6 -0
- package/src/core/ids/id-codec.js +30 -0
- package/src/core/index.d.ts +9 -0
- package/src/core/index.js +7 -0
- package/src/core/oauth/pkce.d.ts +9 -0
- package/src/core/oauth/pkce.js +30 -0
- package/src/core/services/access-token-service.d.ts +42 -0
- package/src/core/services/access-token-service.js +304 -0
- package/src/core/services/auth-error.d.ts +14 -0
- package/src/core/services/auth-error.js +47 -0
- package/src/core/services/contracts.d.ts +23 -0
- package/src/core/services/contracts.js +1 -0
- package/src/core/services/direct-auth-service.d.ts +50 -0
- package/src/core/services/direct-auth-service.js +267 -0
- package/src/core/services/index.d.ts +7 -0
- package/src/core/services/index.js +5 -0
- package/src/core/services/mcp-auth-service.d.ts +39 -0
- package/src/core/services/mcp-auth-service.js +170 -0
- package/src/core/services/oauth-service.d.ts +91 -0
- package/src/core/services/oauth-service.js +571 -0
- package/src/core/services/observability.d.ts +22 -0
- package/src/core/services/observability.js +71 -0
- package/src/core/services/revocation-policy.d.ts +21 -0
- package/src/core/services/revocation-policy.js +51 -0
- package/src/core/sessions/client-session.d.ts +7 -0
- package/src/core/sessions/client-session.js +18 -0
- package/src/core/tokens/access-claims.d.ts +21 -0
- package/src/core/tokens/access-claims.js +128 -0
- package/src/core/tokens/id-claims.d.ts +20 -0
- package/src/core/tokens/id-claims.js +25 -0
- package/src/core/types/auth-contract.d.ts +33 -0
- package/src/core/types/auth-contract.js +1 -0
- package/src/express/index.d.ts +1 -0
- package/src/express/index.js +1 -0
- package/src/express/protected-route.d.ts +44 -0
- package/src/express/protected-route.js +119 -0
- package/src/index.d.ts +8 -0
- package/src/index.js +8 -0
- package/src/mcp/index.d.ts +1 -0
- package/src/mcp/index.js +1 -0
- package/src/mcp/json-rpc-auth.d.ts +5 -0
- package/src/mcp/json-rpc-auth.js +41 -0
- package/src/next/app/catch-all.d.ts +32 -0
- package/src/next/app/catch-all.js +82 -0
- package/src/next/app/cookies.d.ts +22 -0
- package/src/next/app/cookies.js +36 -0
- package/src/next/app/direct-auth-handlers.d.ts +55 -0
- package/src/next/app/direct-auth-handlers.js +419 -0
- package/src/next/app/index.d.ts +8 -0
- package/src/next/app/index.js +8 -0
- package/src/next/app/mcp-oauth-handlers.d.ts +74 -0
- package/src/next/app/mcp-oauth-handlers.js +365 -0
- package/src/next/app/protected-route.d.ts +27 -0
- package/src/next/app/protected-route.js +59 -0
- package/src/next/app/request.d.ts +12 -0
- package/src/next/app/request.js +30 -0
- package/src/next/app/response.d.ts +16 -0
- package/src/next/app/response.js +48 -0
- package/src/next/app/wrapper.d.ts +28 -0
- package/src/next/app/wrapper.js +78 -0
- package/src/next/index.d.ts +6 -0
- package/src/next/index.js +5 -0
- package/src/next/pages/catch-all.d.ts +19 -0
- package/src/next/pages/catch-all.js +60 -0
- package/src/next/pages/cookies.d.ts +41 -0
- package/src/next/pages/cookies.js +87 -0
- package/src/next/pages/direct-auth-handlers.d.ts +58 -0
- package/src/next/pages/direct-auth-handlers.js +425 -0
- package/src/next/pages/index.d.ts +8 -0
- package/src/next/pages/index.js +8 -0
- package/src/next/pages/mcp-oauth-handlers.d.ts +77 -0
- package/src/next/pages/mcp-oauth-handlers.js +341 -0
- package/src/next/pages/protected-route.d.ts +28 -0
- package/src/next/pages/protected-route.js +59 -0
- package/src/next/pages/request.d.ts +14 -0
- package/src/next/pages/request.js +66 -0
- package/src/next/pages/response.d.ts +28 -0
- package/src/next/pages/response.js +29 -0
- package/src/next/pages/wrapper.d.ts +29 -0
- package/src/next/pages/wrapper.js +74 -0
- package/src/next/rewrites.d.ts +12 -0
- package/src/next/rewrites.js +74 -0
- package/src/next/shared/auth-http.d.ts +24 -0
- package/src/next/shared/auth-http.js +42 -0
- package/src/next/shared/auth-routes.d.ts +17 -0
- package/src/next/shared/auth-routes.js +153 -0
- package/src/next/shared/direct-auth-utils.d.ts +71 -0
- package/src/next/shared/direct-auth-utils.js +275 -0
- package/src/next/shared/oauth-utils.d.ts +45 -0
- package/src/next/shared/oauth-utils.js +308 -0
- package/src/next/shared/well-known-utils.d.ts +46 -0
- package/src/next/shared/well-known-utils.js +108 -0
- package/src/testing/in-memory/in-memory-access-token-revocation-adapter.d.ts +2 -0
- package/src/testing/in-memory/in-memory-access-token-revocation-adapter.js +14 -0
- package/src/testing/in-memory/in-memory-authorization-code-adapter.d.ts +2 -0
- package/src/testing/in-memory/in-memory-authorization-code-adapter.js +36 -0
- package/src/testing/in-memory/in-memory-oauth-client-adapter.d.ts +14 -0
- package/src/testing/in-memory/in-memory-oauth-client-adapter.js +26 -0
- package/src/testing/in-memory/in-memory-pending-auth-request-adapter.d.ts +2 -0
- package/src/testing/in-memory/in-memory-pending-auth-request-adapter.js +43 -0
- package/src/testing/in-memory/in-memory-refresh-token-adapter.d.ts +2 -0
- package/src/testing/in-memory/in-memory-refresh-token-adapter.js +67 -0
- package/src/testing/in-memory/in-memory-session-adapter.d.ts +6 -0
- package/src/testing/in-memory/in-memory-session-adapter.js +43 -0
- package/src/testing/in-memory/index.d.ts +7 -0
- package/src/testing/in-memory/index.js +7 -0
- package/src/testing/in-memory/test-fixtures.d.ts +5 -0
- package/src/testing/in-memory/test-fixtures.js +18 -0
- package/src/testing/index.d.ts +2 -0
- 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
|
+
}
|