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,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';
|