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,82 @@
1
+ import { AUTH_ROUTE_HTTP_METHODS, isAuthRouteMethodAllowed, normalizeAuthRouteSegments, resolveAuthRoute, } from '../shared/auth-routes';
2
+ function jsonResponse(statusCode, body, headers) {
3
+ const responseHeaders = new Headers(headers);
4
+ responseHeaders.set('Content-Type', 'application/json');
5
+ return new Response(JSON.stringify(body), {
6
+ status: statusCode,
7
+ headers: responseHeaders,
8
+ });
9
+ }
10
+ function sendUnsupportedRoute() {
11
+ return jsonResponse(404, {
12
+ error: 'not_found',
13
+ error_description: 'Unsupported auth route.',
14
+ });
15
+ }
16
+ function sendMethodNotAllowed(allowedMethods) {
17
+ return jsonResponse(405, {
18
+ error: 'method_not_allowed',
19
+ error_description: 'HTTP method not allowed for auth route.',
20
+ }, { Allow: allowedMethods.join(', ') });
21
+ }
22
+ async function resolveParams(context) {
23
+ if (context.params === undefined) {
24
+ return {};
25
+ }
26
+ return context.params instanceof Promise
27
+ ? await context.params
28
+ : context.params;
29
+ }
30
+ export function createAppAuthCatchAllHandlers(options) {
31
+ const dispatch = async function dispatch(req, context) {
32
+ const params = await resolveParams(context);
33
+ const segments = normalizeAuthRouteSegments(params.auth);
34
+ const route = resolveAuthRoute(segments);
35
+ if (!route) {
36
+ return options.onUnsupportedRoute
37
+ ? options.onUnsupportedRoute({
38
+ req,
39
+ params,
40
+ segments,
41
+ })
42
+ : sendUnsupportedRoute();
43
+ }
44
+ if (!isAuthRouteMethodAllowed(route, req.method)) {
45
+ return options.onMethodNotAllowed
46
+ ? options.onMethodNotAllowed({
47
+ req,
48
+ params,
49
+ action: route.action,
50
+ allowedMethods: route.methods,
51
+ })
52
+ : sendMethodNotAllowed(route.methods);
53
+ }
54
+ const handler = options.handlers[route.action];
55
+ if (!handler) {
56
+ return options.onUnsupportedRoute
57
+ ? options.onUnsupportedRoute({
58
+ req,
59
+ params,
60
+ segments,
61
+ action: route.action,
62
+ })
63
+ : sendUnsupportedRoute();
64
+ }
65
+ return handler({
66
+ req,
67
+ params,
68
+ action: route.action,
69
+ segments,
70
+ });
71
+ };
72
+ return {
73
+ GET: dispatch,
74
+ POST: dispatch,
75
+ PUT: dispatch,
76
+ PATCH: dispatch,
77
+ DELETE: dispatch,
78
+ OPTIONS: dispatch,
79
+ HEAD: dispatch,
80
+ };
81
+ }
82
+ export const APP_AUTH_CATCH_ALL_METHODS = AUTH_ROUTE_HTTP_METHODS;
@@ -0,0 +1,22 @@
1
+ import { type CookieSameSite, type PagesCookieOptions } from '../pages/cookies';
2
+ export type AppCookieOptions = PagesCookieOptions;
3
+ export declare function appendAppSetCookieHeader(headers: Headers, serializedCookie: string): void;
4
+ export declare function setAppCookie(headers: Headers, input: {
5
+ name: string;
6
+ value: string;
7
+ options?: AppCookieOptions;
8
+ }): string;
9
+ export declare function setAppAuthCookie(headers: Headers, input: {
10
+ name: string;
11
+ value: string;
12
+ maxAgeSeconds: number;
13
+ secure?: boolean;
14
+ sameSite?: CookieSameSite;
15
+ path?: string;
16
+ }): string;
17
+ export declare function clearAppAuthCookie(headers: Headers, input: {
18
+ name: string;
19
+ secure?: boolean;
20
+ sameSite?: CookieSameSite;
21
+ path?: string;
22
+ }): string;
@@ -0,0 +1,36 @@
1
+ import { serializeSetCookie, } from '../pages/cookies';
2
+ export function appendAppSetCookieHeader(headers, serializedCookie) {
3
+ headers.append('Set-Cookie', serializedCookie);
4
+ }
5
+ export function setAppCookie(headers, input) {
6
+ const serializedCookie = serializeSetCookie(input);
7
+ appendAppSetCookieHeader(headers, serializedCookie);
8
+ return serializedCookie;
9
+ }
10
+ export function setAppAuthCookie(headers, input) {
11
+ return setAppCookie(headers, {
12
+ name: input.name,
13
+ value: input.value,
14
+ options: {
15
+ path: input.path ?? '/',
16
+ maxAgeSeconds: input.maxAgeSeconds,
17
+ secure: input.secure ?? false,
18
+ httpOnly: true,
19
+ sameSite: input.sameSite ?? 'lax',
20
+ },
21
+ });
22
+ }
23
+ export function clearAppAuthCookie(headers, input) {
24
+ return setAppCookie(headers, {
25
+ name: input.name,
26
+ value: '',
27
+ options: {
28
+ path: input.path ?? '/',
29
+ maxAgeSeconds: 0,
30
+ expires: new Date(0),
31
+ secure: input.secure ?? false,
32
+ httpOnly: true,
33
+ sameSite: input.sameSite ?? 'lax',
34
+ },
35
+ });
36
+ }
@@ -0,0 +1,55 @@
1
+ import type { LoginMethodAdapter } from '../../core/adapters/login-method-adapter';
2
+ import type { DirectAuthService } from '../../core/services/direct-auth-service';
3
+ import type { AuthActor } from '../../core/types/auth-contract';
4
+ import { type DirectAccessTokenTransportConfig, type DirectCsrfConfig, type RefreshTokenSource } from '../shared/direct-auth-utils';
5
+ export type DirectCookieConfig = {
6
+ enabled?: boolean;
7
+ accessTokenCookieName?: string;
8
+ refreshTokenCookieName?: string;
9
+ path?: string;
10
+ sameSite?: 'lax' | 'strict' | 'none';
11
+ secure?: boolean;
12
+ secureInProduction?: boolean;
13
+ refreshTokenMaxAgeSeconds?: number;
14
+ };
15
+ export type AppDirectLoginIssueResult = {
16
+ accessToken?: string;
17
+ accessClaims?: {
18
+ exp: number;
19
+ iat: number;
20
+ };
21
+ refreshToken?: string;
22
+ clientSession?: unknown;
23
+ body?: Record<string, unknown>;
24
+ redirectTo?: string;
25
+ statusCode?: number;
26
+ };
27
+ export type CreateAppDirectAuthHandlersOptions<TSessionId, TUserId, TActor extends AuthActor = AuthActor> = {
28
+ directAuthService: DirectAuthService<TSessionId, TUserId, TActor>;
29
+ loginMethods?: Readonly<Record<string, LoginMethodAdapter<TUserId>>>;
30
+ issueLogin?: (input: {
31
+ req: Request;
32
+ requestId?: string;
33
+ method: string;
34
+ userId: TUserId;
35
+ }) => Promise<AppDirectLoginIssueResult | void> | AppDirectLoginIssueResult | void;
36
+ defaultLoginMethod?: string;
37
+ parseSessionId?: (value: unknown) => TSessionId | undefined;
38
+ accessTokenTransport?: DirectAccessTokenTransportConfig<TActor>;
39
+ refreshTokenFieldName?: string;
40
+ refreshTokenPriority?: readonly RefreshTokenSource[];
41
+ expectedAudience?: string;
42
+ allowedActors?: readonly TActor[] | ReadonlySet<TActor>;
43
+ csrf?: DirectCsrfConfig;
44
+ cookies?: DirectCookieConfig;
45
+ logoutAllowMissingCredentials?: boolean;
46
+ requestIdHeaderName?: string;
47
+ authorizationHeaderName?: string;
48
+ };
49
+ export type AppDirectAuthHandlers = {
50
+ refresh: (req: Request) => Promise<Response>;
51
+ logout: (req: Request) => Promise<Response>;
52
+ loginStart: (req: Request) => Promise<Response>;
53
+ loginFinish: (req: Request) => Promise<Response>;
54
+ };
55
+ export declare function createAppDirectAuthHandlers<TSessionId, TUserId, TActor extends AuthActor = AuthActor>(options: CreateAppDirectAuthHandlersOptions<TSessionId, TUserId, TActor>): AppDirectAuthHandlers;
@@ -0,0 +1,419 @@
1
+ import { AuthServiceError } from '../../core/services/auth-error';
2
+ import { clearAppAuthCookie, setAppAuthCookie, } from './cookies';
3
+ import { sendAppRedirect } from './response';
4
+ import { withAppAuthRoute } from './wrapper';
5
+ import { assertBearerOnlyActorPolicy, assertCsrfForCookieMutation, DEFAULT_ACCESS_TOKEN_COOKIE_NAME, DEFAULT_REFRESH_TOKEN_COOKIE_NAME, extractRefreshToken, parseNonNegativeSafeInteger, resolveAccessTokenTransportAdapter, resolveCookieSecureFlag, resolveLoginMethod, resolvePathnameFromUrl, } from '../shared/direct-auth-utils';
6
+ import { parseBodyToRecord, toSingleString } from '../shared/oauth-utils';
7
+ function jsonResponse(statusCode, body, headers) {
8
+ const responseHeaders = new Headers(headers);
9
+ responseHeaders.set('Content-Type', 'application/json');
10
+ return new Response(JSON.stringify(body), {
11
+ status: statusCode,
12
+ headers: responseHeaders,
13
+ });
14
+ }
15
+ function methodNotAllowedResponse(allowedMethods) {
16
+ return jsonResponse(405, {
17
+ error: 'method_not_allowed',
18
+ error_description: 'HTTP method not allowed for auth route.',
19
+ }, { Allow: allowedMethods.join(', ') });
20
+ }
21
+ function buildTokenOutput(input) {
22
+ const expiresIn = Math.max(0, input.accessClaims.exp - input.accessClaims.iat);
23
+ return {
24
+ access_token: input.accessToken,
25
+ token_type: 'Bearer',
26
+ expires_in: expiresIn,
27
+ ...(input.refreshToken === undefined
28
+ ? {}
29
+ : { refresh_token: input.refreshToken }),
30
+ ...(input.clientSession === undefined
31
+ ? {}
32
+ : { clientSession: input.clientSession }),
33
+ };
34
+ }
35
+ function resolveCookiesConfig(config) {
36
+ const path = toSingleString(config?.path) ?? '/';
37
+ const sameSite = config?.sameSite ?? 'lax';
38
+ const secure = resolveCookieSecureFlag({
39
+ ...(config?.secure === undefined ? {} : { secure: config.secure }),
40
+ ...(config?.secureInProduction === undefined
41
+ ? {}
42
+ : { secureInProduction: config.secureInProduction }),
43
+ });
44
+ return {
45
+ enabled: config?.enabled ?? true,
46
+ accessTokenCookieName: toSingleString(config?.accessTokenCookieName) ??
47
+ DEFAULT_ACCESS_TOKEN_COOKIE_NAME,
48
+ refreshTokenCookieName: toSingleString(config?.refreshTokenCookieName) ??
49
+ DEFAULT_REFRESH_TOKEN_COOKIE_NAME,
50
+ options: {
51
+ path,
52
+ sameSite,
53
+ secure,
54
+ },
55
+ refreshTokenMaxAgeSeconds: parseNonNegativeSafeInteger(config?.refreshTokenMaxAgeSeconds ?? 60 * 60 * 24 * 30, 'cookies.refreshTokenMaxAgeSeconds'),
56
+ };
57
+ }
58
+ function appendAuthCookies(input) {
59
+ if (!input.cookiesConfig.enabled) {
60
+ return;
61
+ }
62
+ setAppAuthCookie(input.headers, {
63
+ name: input.cookiesConfig.accessTokenCookieName,
64
+ value: input.accessToken,
65
+ maxAgeSeconds: input.accessTokenMaxAgeSeconds,
66
+ ...input.cookiesConfig.options,
67
+ });
68
+ if (input.refreshToken !== undefined) {
69
+ setAppAuthCookie(input.headers, {
70
+ name: input.cookiesConfig.refreshTokenCookieName,
71
+ value: input.refreshToken,
72
+ maxAgeSeconds: input.cookiesConfig.refreshTokenMaxAgeSeconds,
73
+ ...input.cookiesConfig.options,
74
+ });
75
+ }
76
+ }
77
+ function appendClearAuthCookies(headers, cookiesConfig) {
78
+ if (!cookiesConfig.enabled) {
79
+ return;
80
+ }
81
+ clearAppAuthCookie(headers, {
82
+ name: cookiesConfig.accessTokenCookieName,
83
+ ...cookiesConfig.options,
84
+ });
85
+ clearAppAuthCookie(headers, {
86
+ name: cookiesConfig.refreshTokenCookieName,
87
+ ...cookiesConfig.options,
88
+ });
89
+ }
90
+ async function readBody(req) {
91
+ return parseBodyToRecord(await req.text());
92
+ }
93
+ function getQueryMethod(req) {
94
+ return new URL(req.url).searchParams.get('method') ?? undefined;
95
+ }
96
+ function requireLoginMethods(loginMethods) {
97
+ if (!loginMethods || Object.keys(loginMethods).length === 0) {
98
+ throw new AuthServiceError({
99
+ code: 'invalid_request',
100
+ message: 'No login methods are configured.',
101
+ });
102
+ }
103
+ return loginMethods;
104
+ }
105
+ function requireLoginMethod(input) {
106
+ const adapter = input.loginMethods[input.method];
107
+ if (!adapter) {
108
+ throw new AuthServiceError({
109
+ code: 'invalid_request',
110
+ message: `Unsupported login method "${input.method}".`,
111
+ });
112
+ }
113
+ return adapter;
114
+ }
115
+ export function createAppDirectAuthHandlers(options) {
116
+ const cookiesConfig = resolveCookiesConfig(options.cookies);
117
+ const refreshTokenFieldName = toSingleString(options.refreshTokenFieldName) ?? 'refresh_token';
118
+ const accessTokenTransport = resolveAccessTokenTransportAdapter(options.accessTokenTransport);
119
+ const logoutAllowMissingCredentials = options.logoutAllowMissingCredentials ?? true;
120
+ const refresh = withAppAuthRoute({
121
+ ...(options.requestIdHeaderName === undefined
122
+ ? {}
123
+ : { requestIdHeaderName: options.requestIdHeaderName }),
124
+ ...(options.authorizationHeaderName === undefined
125
+ ? {}
126
+ : { authorizationHeaderName: options.authorizationHeaderName }),
127
+ handler: async ({ req, requestId, cookies }) => {
128
+ if (req.method !== 'POST') {
129
+ return methodNotAllowedResponse(['POST']);
130
+ }
131
+ const body = await readBody(req);
132
+ const pathname = resolvePathnameFromUrl(req.url);
133
+ const extractedRefreshToken = extractRefreshToken({
134
+ body,
135
+ cookies,
136
+ bodyFieldName: refreshTokenFieldName,
137
+ cookieName: cookiesConfig.refreshTokenCookieName,
138
+ ...(options.refreshTokenPriority === undefined
139
+ ? {}
140
+ : { priority: options.refreshTokenPriority }),
141
+ });
142
+ assertCsrfForCookieMutation({
143
+ method: req.method,
144
+ pathname,
145
+ cookies,
146
+ headers: req.headers,
147
+ body,
148
+ cookieAuthenticated: extractedRefreshToken.source === 'cookie',
149
+ ...(options.csrf === undefined ? {} : { config: options.csrf }),
150
+ });
151
+ if (extractedRefreshToken.token === undefined) {
152
+ throw new AuthServiceError({
153
+ code: 'unauthorized',
154
+ message: 'Refresh token is required.',
155
+ });
156
+ }
157
+ const refreshed = await options.directAuthService.refresh({
158
+ refreshToken: extractedRefreshToken.token,
159
+ ...(requestId === undefined ? {} : { requestId }),
160
+ });
161
+ const output = buildTokenOutput({
162
+ accessToken: refreshed.accessToken,
163
+ accessClaims: refreshed.accessClaims,
164
+ ...(refreshed.refreshToken === undefined
165
+ ? {}
166
+ : { refreshToken: refreshed.refreshToken }),
167
+ ...(refreshed.clientSession === undefined
168
+ ? {}
169
+ : { clientSession: refreshed.clientSession }),
170
+ });
171
+ if (extractedRefreshToken.source !== 'cookie') {
172
+ return output;
173
+ }
174
+ const headers = new Headers();
175
+ appendAuthCookies({
176
+ headers,
177
+ cookiesConfig,
178
+ accessToken: refreshed.accessToken,
179
+ accessTokenMaxAgeSeconds: output.expires_in,
180
+ refreshToken: refreshed.refreshToken ?? extractedRefreshToken.token,
181
+ });
182
+ return jsonResponse(200, output, headers);
183
+ },
184
+ });
185
+ const logout = withAppAuthRoute({
186
+ ...(options.requestIdHeaderName === undefined
187
+ ? {}
188
+ : { requestIdHeaderName: options.requestIdHeaderName }),
189
+ ...(options.authorizationHeaderName === undefined
190
+ ? {}
191
+ : { authorizationHeaderName: options.authorizationHeaderName }),
192
+ handler: async ({ req, requestId, transport, cookies }) => {
193
+ if (req.method !== 'POST') {
194
+ return methodNotAllowedResponse(['POST']);
195
+ }
196
+ const body = await readBody(req);
197
+ const pathname = resolvePathnameFromUrl(req.url);
198
+ const extractedRefreshToken = extractRefreshToken({
199
+ body,
200
+ cookies,
201
+ bodyFieldName: refreshTokenFieldName,
202
+ cookieName: cookiesConfig.refreshTokenCookieName,
203
+ ...(options.refreshTokenPriority === undefined
204
+ ? {}
205
+ : { priority: options.refreshTokenPriority }),
206
+ });
207
+ const extractedAccessToken = accessTokenTransport.extractAccessToken({
208
+ transport,
209
+ });
210
+ assertCsrfForCookieMutation({
211
+ method: req.method,
212
+ pathname,
213
+ cookies,
214
+ headers: req.headers,
215
+ body,
216
+ cookieAuthenticated: extractedRefreshToken.source === 'cookie' ||
217
+ extractedAccessToken.source === 'cookie',
218
+ ...(options.csrf === undefined ? {} : { config: options.csrf }),
219
+ });
220
+ let validatedAccess;
221
+ if (extractedAccessToken.token) {
222
+ validatedAccess = await options.directAuthService.validateAccessToken({
223
+ accessToken: extractedAccessToken.token,
224
+ ...(requestId === undefined ? {} : { requestId }),
225
+ ...(options.expectedAudience === undefined
226
+ ? {}
227
+ : { expectedAudience: options.expectedAudience }),
228
+ ...(options.allowedActors === undefined
229
+ ? {}
230
+ : { allowedActors: options.allowedActors }),
231
+ });
232
+ assertBearerOnlyActorPolicy({
233
+ actor: validatedAccess.claims.actor,
234
+ ...(extractedAccessToken.source === undefined
235
+ ? {}
236
+ : { source: extractedAccessToken.source }),
237
+ ...(options.accessTokenTransport?.requireBearerForActors === undefined
238
+ ? {}
239
+ : {
240
+ requireBearerForActors: options.accessTokenTransport.requireBearerForActors,
241
+ }),
242
+ });
243
+ }
244
+ const sessionId = options.parseSessionId?.(body.session_id);
245
+ const resolvedSessionId = sessionId ?? validatedAccess?.authContext.sessionId;
246
+ const headers = new Headers();
247
+ appendClearAuthCookies(headers, cookiesConfig);
248
+ if (resolvedSessionId === undefined &&
249
+ extractedRefreshToken.token === undefined &&
250
+ validatedAccess === undefined) {
251
+ if (logoutAllowMissingCredentials) {
252
+ return jsonResponse(200, {
253
+ ok: true,
254
+ session_revoked: false,
255
+ refresh_token_revoked: false,
256
+ access_token_revoked: false,
257
+ }, headers);
258
+ }
259
+ throw new AuthServiceError({
260
+ code: 'invalid_request',
261
+ message: 'At least one logout credential (session_id, refresh_token, or access token) is required.',
262
+ });
263
+ }
264
+ const result = await options.directAuthService.logout({
265
+ ...(requestId === undefined ? {} : { requestId }),
266
+ ...(resolvedSessionId === undefined
267
+ ? {}
268
+ : { sessionId: resolvedSessionId }),
269
+ ...(extractedRefreshToken.token === undefined
270
+ ? {}
271
+ : { refreshToken: extractedRefreshToken.token }),
272
+ ...(validatedAccess === undefined
273
+ ? {}
274
+ : {
275
+ accessTokenJti: validatedAccess.claims.jti,
276
+ accessTokenExpiresAtUnix: validatedAccess.claims.exp,
277
+ }),
278
+ });
279
+ return jsonResponse(200, {
280
+ ok: true,
281
+ session_revoked: result.sessionRevoked,
282
+ refresh_token_revoked: result.refreshTokenRevoked,
283
+ access_token_revoked: result.accessTokenRevoked,
284
+ }, headers);
285
+ },
286
+ });
287
+ const loginStart = withAppAuthRoute({
288
+ ...(options.requestIdHeaderName === undefined
289
+ ? {}
290
+ : { requestIdHeaderName: options.requestIdHeaderName }),
291
+ handler: async ({ req }) => {
292
+ if (req.method !== 'POST') {
293
+ return methodNotAllowedResponse(['POST']);
294
+ }
295
+ const loginMethods = requireLoginMethods(options.loginMethods);
296
+ const body = await readBody(req);
297
+ const method = resolveLoginMethod({
298
+ pathname: resolvePathnameFromUrl(req.url),
299
+ phase: 'start',
300
+ body,
301
+ queryMethod: getQueryMethod(req),
302
+ ...(options.defaultLoginMethod === undefined
303
+ ? {}
304
+ : { defaultMethod: options.defaultLoginMethod }),
305
+ });
306
+ const adapter = requireLoginMethod({
307
+ loginMethods,
308
+ method,
309
+ });
310
+ if (!adapter.start) {
311
+ throw new AuthServiceError({
312
+ code: 'invalid_request',
313
+ message: `Login method "${method}" does not implement start.`,
314
+ });
315
+ }
316
+ await adapter.start(body);
317
+ return { ok: true };
318
+ },
319
+ });
320
+ const loginFinish = withAppAuthRoute({
321
+ ...(options.requestIdHeaderName === undefined
322
+ ? {}
323
+ : { requestIdHeaderName: options.requestIdHeaderName }),
324
+ handler: async ({ req, requestId }) => {
325
+ if (req.method !== 'GET') {
326
+ return methodNotAllowedResponse(['GET']);
327
+ }
328
+ const loginMethods = requireLoginMethods(options.loginMethods);
329
+ const query = Object.fromEntries(new URL(req.url).searchParams.entries());
330
+ const method = resolveLoginMethod({
331
+ pathname: resolvePathnameFromUrl(req.url),
332
+ phase: 'finish',
333
+ queryMethod: query.method,
334
+ ...(options.defaultLoginMethod === undefined
335
+ ? {}
336
+ : { defaultMethod: options.defaultLoginMethod }),
337
+ });
338
+ const adapter = requireLoginMethod({
339
+ loginMethods,
340
+ method,
341
+ });
342
+ const finishResult = await adapter.finish(query);
343
+ if (finishResult === null) {
344
+ throw new AuthServiceError({
345
+ code: 'unauthorized',
346
+ message: 'Login method did not authenticate a user.',
347
+ });
348
+ }
349
+ if (!options.issueLogin) {
350
+ return { ok: true };
351
+ }
352
+ const issued = await options.issueLogin({
353
+ req,
354
+ method,
355
+ userId: finishResult.userId,
356
+ ...(requestId === undefined ? {} : { requestId }),
357
+ });
358
+ if (!issued) {
359
+ return { ok: true };
360
+ }
361
+ if (issued.accessToken !== undefined) {
362
+ if (issued.accessClaims === undefined) {
363
+ throw new AuthServiceError({
364
+ code: 'invalid_request',
365
+ message: 'issueLogin must include accessClaims when accessToken is provided.',
366
+ });
367
+ }
368
+ const tokenOutput = buildTokenOutput({
369
+ accessToken: issued.accessToken,
370
+ accessClaims: issued.accessClaims,
371
+ ...(issued.refreshToken === undefined
372
+ ? {}
373
+ : { refreshToken: issued.refreshToken }),
374
+ ...(issued.clientSession === undefined
375
+ ? {}
376
+ : { clientSession: issued.clientSession }),
377
+ });
378
+ const headers = new Headers();
379
+ appendAuthCookies({
380
+ headers,
381
+ cookiesConfig,
382
+ accessToken: issued.accessToken,
383
+ accessTokenMaxAgeSeconds: tokenOutput.expires_in,
384
+ ...(issued.refreshToken === undefined
385
+ ? {}
386
+ : { refreshToken: issued.refreshToken }),
387
+ });
388
+ if (issued.redirectTo) {
389
+ const redirectResponse = sendAppRedirect({
390
+ location: issued.redirectTo,
391
+ ...(issued.statusCode === undefined
392
+ ? {}
393
+ : { statusCode: issued.statusCode }),
394
+ });
395
+ headers.forEach((value, key) => {
396
+ redirectResponse.headers.append(key, value);
397
+ });
398
+ return redirectResponse;
399
+ }
400
+ return jsonResponse(200, issued.body ?? tokenOutput, headers);
401
+ }
402
+ if (issued.redirectTo) {
403
+ return sendAppRedirect({
404
+ location: issued.redirectTo,
405
+ ...(issued.statusCode === undefined
406
+ ? {}
407
+ : { statusCode: issued.statusCode }),
408
+ });
409
+ }
410
+ return issued.body ?? { ok: true };
411
+ },
412
+ });
413
+ return {
414
+ refresh,
415
+ logout,
416
+ loginStart,
417
+ loginFinish,
418
+ };
419
+ }
@@ -0,0 +1,8 @@
1
+ export { buildAppAccessTokenTransportInput, getAppAuthorizationHeader, getAppCookies, getAppRequestId, } from './request';
2
+ export { appendAppSetCookieHeader, clearAppAuthCookie, setAppAuthCookie, setAppCookie, type AppCookieOptions, } from './cookies';
3
+ export { sendAppAuthError, sendAppRedirect, sendAppSystemError, type AppAuthErrorResponseBody, } from './response';
4
+ export { withAppAuthRoute, type AppAuthRouteContext, type WithAppAuthRouteOptions, } from './wrapper';
5
+ export { withAppProtectedRoute, type AppProtectedAuth, type AppProtectedRouteContext, type WithAppProtectedRouteOptions, } from './protected-route';
6
+ export { APP_AUTH_CATCH_ALL_METHODS, createAppAuthCatchAllHandlers, type AppAuthCatchAllActionHandler, type AppAuthCatchAllContext, type AppAuthCatchAllHandlers, type AppAuthCatchAllMethodHandler, type AppAuthCatchAllParams, type CreateAppAuthCatchAllHandlersOptions, } from './catch-all';
7
+ export { createAppMcpHandler, createAppOAuthHandlers, createAppWellKnownHandlers, type AppOAuthHandlers, type AppWellKnownHandlers, type CreateAppMcpHandlerOptions, type CreateAppOAuthHandlersOptions, type CreateAppWellKnownHandlersOptions, } from './mcp-oauth-handlers';
8
+ export { createAppDirectAuthHandlers, type AppDirectAuthHandlers, type AppDirectLoginIssueResult, type CreateAppDirectAuthHandlersOptions, type DirectCookieConfig as AppDirectCookieConfig, } from './direct-auth-handlers';
@@ -0,0 +1,8 @@
1
+ export { buildAppAccessTokenTransportInput, getAppAuthorizationHeader, getAppCookies, getAppRequestId, } from './request';
2
+ export { appendAppSetCookieHeader, clearAppAuthCookie, setAppAuthCookie, setAppCookie, } from './cookies';
3
+ export { sendAppAuthError, sendAppRedirect, sendAppSystemError, } from './response';
4
+ export { withAppAuthRoute, } from './wrapper';
5
+ export { withAppProtectedRoute, } from './protected-route';
6
+ export { APP_AUTH_CATCH_ALL_METHODS, createAppAuthCatchAllHandlers, } from './catch-all';
7
+ export { createAppMcpHandler, createAppOAuthHandlers, createAppWellKnownHandlers, } from './mcp-oauth-handlers';
8
+ export { createAppDirectAuthHandlers, } from './direct-auth-handlers';