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,45 @@
1
+ export declare function parseBodyToRecord(body: unknown): Record<string, unknown>;
2
+ export declare function parseCodeMethod(value: unknown, defaultMethod?: 'S256' | 'plain'): 'S256' | 'plain';
3
+ export declare function parseScopes(value: unknown, fallback: readonly string[]): string[];
4
+ export declare function parseBoolean(value: unknown, defaultValue: boolean): boolean;
5
+ export declare function parsePositiveSafeInteger(value: unknown, fieldName: string): number;
6
+ export type ParsedAuthorizeRequest = {
7
+ responseType: 'code';
8
+ clientId: string;
9
+ redirectUri: string;
10
+ codeChallenge: string;
11
+ codeMethod: 'S256' | 'plain';
12
+ scopes: string[];
13
+ state?: string;
14
+ };
15
+ export declare function parseAuthorizeRequest(input: {
16
+ query: Record<string, unknown>;
17
+ requiredScope: string;
18
+ defaultCodeMethod?: 'S256' | 'plain';
19
+ requireState?: boolean;
20
+ }): ParsedAuthorizeRequest;
21
+ export type ParsedOAuthTokenRequest = {
22
+ grantType: 'authorization_code';
23
+ clientId: string;
24
+ clientSecret: string | null;
25
+ authorizationCode: string;
26
+ redirectUri: string;
27
+ codeVerifier: string;
28
+ } | {
29
+ grantType: 'refresh_token';
30
+ clientId: string;
31
+ clientSecret: string | null;
32
+ refreshToken: string;
33
+ } | {
34
+ grantType: 'client_credentials';
35
+ clientId: string;
36
+ clientSecret: string | null;
37
+ };
38
+ export declare function parseOAuthTokenRequest(input: {
39
+ body: unknown;
40
+ authorizationHeader?: string;
41
+ allowClientCredentials?: boolean;
42
+ allowGrantTypeInference?: boolean;
43
+ }): ParsedOAuthTokenRequest;
44
+ export declare function appendUrlParams(baseUrl: string, params: Record<string, string | undefined>): string;
45
+ export declare function toSingleString(value: unknown): string | undefined;
@@ -0,0 +1,308 @@
1
+ import { AuthServiceError } from '../../core/services/auth-error';
2
+ function toNonEmptyString(value) {
3
+ if (Array.isArray(value)) {
4
+ for (const item of value) {
5
+ const normalized = toNonEmptyString(item);
6
+ if (normalized !== undefined) {
7
+ return normalized;
8
+ }
9
+ }
10
+ return undefined;
11
+ }
12
+ if (typeof value !== 'string') {
13
+ return undefined;
14
+ }
15
+ const trimmed = value.trim();
16
+ return trimmed.length > 0 ? trimmed : undefined;
17
+ }
18
+ function assertObjectRecord(value) {
19
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
20
+ throw new AuthServiceError({
21
+ code: 'invalid_request',
22
+ message: 'Request body must be an object.',
23
+ });
24
+ }
25
+ return value;
26
+ }
27
+ function parseStringBody(body) {
28
+ const trimmed = body.trim();
29
+ if (trimmed.length === 0) {
30
+ return {};
31
+ }
32
+ let parsedJson;
33
+ try {
34
+ parsedJson = JSON.parse(trimmed);
35
+ }
36
+ catch (jsonError) {
37
+ const params = new URLSearchParams(trimmed);
38
+ if (params.size === 0) {
39
+ throw new AuthServiceError({
40
+ code: 'invalid_request',
41
+ message: 'Request body must be valid JSON or URL-encoded form data.',
42
+ cause: jsonError,
43
+ });
44
+ }
45
+ const output = {};
46
+ params.forEach((paramValue, paramName) => {
47
+ output[paramName] = paramValue;
48
+ });
49
+ return output;
50
+ }
51
+ return assertObjectRecord(parsedJson);
52
+ }
53
+ export function parseBodyToRecord(body) {
54
+ if (body === null || body === undefined) {
55
+ return {};
56
+ }
57
+ if (typeof body === 'string') {
58
+ return parseStringBody(body);
59
+ }
60
+ if (body instanceof URLSearchParams) {
61
+ const output = {};
62
+ body.forEach((paramValue, paramName) => {
63
+ output[paramName] = paramValue;
64
+ });
65
+ return output;
66
+ }
67
+ return assertObjectRecord(body);
68
+ }
69
+ export function parseCodeMethod(value, defaultMethod = 'S256') {
70
+ const normalized = toNonEmptyString(value);
71
+ if (normalized === undefined) {
72
+ return defaultMethod;
73
+ }
74
+ if (normalized !== 'S256' && normalized !== 'plain') {
75
+ throw new AuthServiceError({
76
+ code: 'invalid_request',
77
+ message: 'Unsupported PKCE code_challenge_method.',
78
+ });
79
+ }
80
+ return normalized;
81
+ }
82
+ export function parseScopes(value, fallback) {
83
+ const normalized = toNonEmptyString(value);
84
+ if (normalized === undefined) {
85
+ return [...fallback];
86
+ }
87
+ return normalized
88
+ .split(/\s+/)
89
+ .map(scope => scope.trim())
90
+ .filter(scope => scope.length > 0);
91
+ }
92
+ export function parseBoolean(value, defaultValue) {
93
+ if (value === undefined || value === null) {
94
+ return defaultValue;
95
+ }
96
+ if (typeof value === 'boolean') {
97
+ return value;
98
+ }
99
+ if (typeof value === 'number') {
100
+ if (value === 1) {
101
+ return true;
102
+ }
103
+ if (value === 0) {
104
+ return false;
105
+ }
106
+ }
107
+ if (typeof value === 'string') {
108
+ const normalized = value.trim().toLowerCase();
109
+ if (normalized === 'true' || normalized === '1' || normalized === 'yes') {
110
+ return true;
111
+ }
112
+ if (normalized === 'false' || normalized === '0' || normalized === 'no') {
113
+ return false;
114
+ }
115
+ }
116
+ throw new AuthServiceError({
117
+ code: 'invalid_request',
118
+ message: 'Boolean field is invalid.',
119
+ });
120
+ }
121
+ export function parsePositiveSafeInteger(value, fieldName) {
122
+ const normalized = typeof value === 'number'
123
+ ? value
124
+ : Number.parseInt(toNonEmptyString(value) ?? '', 10);
125
+ if (!Number.isSafeInteger(normalized) || normalized <= 0) {
126
+ throw new AuthServiceError({
127
+ code: 'invalid_request',
128
+ message: `${fieldName} must be a positive safe integer.`,
129
+ });
130
+ }
131
+ return normalized;
132
+ }
133
+ function parseBasicCredentials(authorizationHeader) {
134
+ if (!authorizationHeader) {
135
+ return null;
136
+ }
137
+ const match = authorizationHeader.match(/^Basic\s+(.+)$/i);
138
+ if (!match) {
139
+ return null;
140
+ }
141
+ let decoded;
142
+ try {
143
+ decoded = Buffer.from(match[1], 'base64').toString('utf8');
144
+ }
145
+ catch (error) {
146
+ throw new AuthServiceError({
147
+ code: 'invalid_request',
148
+ message: 'Invalid Basic authorization header.',
149
+ cause: error,
150
+ });
151
+ }
152
+ const separatorIndex = decoded.indexOf(':');
153
+ if (separatorIndex <= 0) {
154
+ throw new AuthServiceError({
155
+ code: 'invalid_request',
156
+ message: 'Invalid Basic authorization header.',
157
+ });
158
+ }
159
+ const clientId = decoded.slice(0, separatorIndex);
160
+ const clientSecretRaw = decoded.slice(separatorIndex + 1);
161
+ return {
162
+ clientId,
163
+ clientSecret: clientSecretRaw.length > 0 ? clientSecretRaw : null,
164
+ };
165
+ }
166
+ function requireField(body, fieldName) {
167
+ const value = toNonEmptyString(body[fieldName]);
168
+ if (value === undefined) {
169
+ throw new AuthServiceError({
170
+ code: 'invalid_request',
171
+ message: `Missing required field "${fieldName}".`,
172
+ });
173
+ }
174
+ return value;
175
+ }
176
+ export function parseAuthorizeRequest(input) {
177
+ const responseTypeRaw = toNonEmptyString(input.query.response_type) ?? 'code';
178
+ if (responseTypeRaw !== 'code') {
179
+ throw new AuthServiceError({
180
+ code: 'invalid_request',
181
+ message: 'Unsupported response_type.',
182
+ });
183
+ }
184
+ const clientId = toNonEmptyString(input.query.client_id);
185
+ const redirectUri = toNonEmptyString(input.query.redirect_uri);
186
+ const codeChallenge = toNonEmptyString(input.query.code_challenge);
187
+ const state = toNonEmptyString(input.query.state);
188
+ if (clientId === undefined || redirectUri === undefined) {
189
+ throw new AuthServiceError({
190
+ code: 'invalid_request',
191
+ message: 'Missing required authorize query parameters.',
192
+ });
193
+ }
194
+ if (codeChallenge === undefined) {
195
+ throw new AuthServiceError({
196
+ code: 'invalid_request',
197
+ message: 'Missing required query parameter "code_challenge".',
198
+ });
199
+ }
200
+ const requireState = input.requireState ?? true;
201
+ if (requireState && state === undefined) {
202
+ throw new AuthServiceError({
203
+ code: 'invalid_request',
204
+ message: 'Missing required query parameter "state".',
205
+ });
206
+ }
207
+ const codeMethod = parseCodeMethod(input.query.code_challenge_method, input.defaultCodeMethod ?? 'S256');
208
+ const scopes = parseScopes(input.query.scope, [input.requiredScope]);
209
+ if (!scopes.includes(input.requiredScope)) {
210
+ throw new AuthServiceError({
211
+ code: 'invalid_request',
212
+ message: `Missing required scope "${input.requiredScope}".`,
213
+ });
214
+ }
215
+ return {
216
+ responseType: 'code',
217
+ clientId,
218
+ redirectUri,
219
+ codeChallenge,
220
+ codeMethod,
221
+ scopes,
222
+ ...(state === undefined ? {} : { state }),
223
+ };
224
+ }
225
+ export function parseOAuthTokenRequest(input) {
226
+ const body = parseBodyToRecord(input.body);
227
+ const allowGrantTypeInference = input.allowGrantTypeInference ?? true;
228
+ const basicClient = parseBasicCredentials(input.authorizationHeader);
229
+ const bodyClientId = toNonEmptyString(body.client_id);
230
+ if (basicClient &&
231
+ bodyClientId !== undefined &&
232
+ basicClient.clientId !== bodyClientId) {
233
+ throw new AuthServiceError({
234
+ code: 'invalid_request',
235
+ message: 'client_id must match between Authorization header and request body.',
236
+ });
237
+ }
238
+ const clientId = basicClient?.clientId ?? bodyClientId;
239
+ const clientSecret = basicClient?.clientSecret ?? toNonEmptyString(body.client_secret) ?? null;
240
+ if (clientId === undefined) {
241
+ throw new AuthServiceError({
242
+ code: 'invalid_client',
243
+ message: 'OAuth client credentials are required.',
244
+ });
245
+ }
246
+ const grantTypeRaw = toNonEmptyString(body.grant_type);
247
+ const inferredGrantType = allowGrantTypeInference && grantTypeRaw === undefined
248
+ ? toNonEmptyString(body.code)
249
+ ? 'authorization_code'
250
+ : toNonEmptyString(body.refresh_token)
251
+ ? 'refresh_token'
252
+ : null
253
+ : null;
254
+ const grantType = (grantTypeRaw ?? inferredGrantType);
255
+ if (!grantType) {
256
+ throw new AuthServiceError({
257
+ code: 'invalid_request',
258
+ message: 'Missing required field "grant_type".',
259
+ });
260
+ }
261
+ if (grantType === 'authorization_code') {
262
+ return {
263
+ grantType,
264
+ clientId,
265
+ clientSecret,
266
+ authorizationCode: requireField(body, 'code'),
267
+ redirectUri: requireField(body, 'redirect_uri'),
268
+ codeVerifier: requireField(body, 'code_verifier'),
269
+ };
270
+ }
271
+ if (grantType === 'refresh_token') {
272
+ return {
273
+ grantType,
274
+ clientId,
275
+ clientSecret,
276
+ refreshToken: requireField(body, 'refresh_token'),
277
+ };
278
+ }
279
+ if (grantType === 'client_credentials') {
280
+ if (!input.allowClientCredentials) {
281
+ throw new AuthServiceError({
282
+ code: 'invalid_grant',
283
+ message: 'Grant type "client_credentials" is not enabled.',
284
+ });
285
+ }
286
+ return {
287
+ grantType,
288
+ clientId,
289
+ clientSecret,
290
+ };
291
+ }
292
+ throw new AuthServiceError({
293
+ code: 'invalid_request',
294
+ message: `Unsupported grant_type "${grantType}".`,
295
+ });
296
+ }
297
+ export function appendUrlParams(baseUrl, params) {
298
+ const url = new URL(baseUrl);
299
+ Object.entries(params).forEach(([name, value]) => {
300
+ if (value !== undefined) {
301
+ url.searchParams.set(name, value);
302
+ }
303
+ });
304
+ return url.toString();
305
+ }
306
+ export function toSingleString(value) {
307
+ return toNonEmptyString(value);
308
+ }
@@ -0,0 +1,46 @@
1
+ import type { OAuthGrantType } from '../../core/adapters/oauth-grant-type';
2
+ export declare function resolveRequestOrigin(input: {
3
+ requestUrl?: string;
4
+ hostHeader?: string;
5
+ forwardedHostHeader?: string;
6
+ forwardedProtoHeader?: string;
7
+ fallbackOrigin?: string;
8
+ }): string;
9
+ export type WellKnownMetadata = {
10
+ oauthProtectedResource: {
11
+ resource: string;
12
+ authorization_servers: string[];
13
+ };
14
+ oauthAuthorizationServer: {
15
+ issuer: string;
16
+ authorization_endpoint: string;
17
+ token_endpoint: string;
18
+ response_types_supported: string[];
19
+ grant_types_supported: OAuthGrantType[];
20
+ code_challenge_methods_supported: Array<'S256' | 'plain'>;
21
+ token_endpoint_auth_methods_supported: string[];
22
+ scopes_supported: string[];
23
+ };
24
+ openidConfiguration: {
25
+ issuer: string;
26
+ authorization_endpoint: string;
27
+ token_endpoint: string;
28
+ response_types_supported: string[];
29
+ grant_types_supported: OAuthGrantType[];
30
+ code_challenge_methods_supported: Array<'S256' | 'plain'>;
31
+ scopes_supported: string[];
32
+ token_endpoint_auth_methods_supported: string[];
33
+ };
34
+ };
35
+ export declare function buildWellKnownMetadata(input: {
36
+ issuer: string;
37
+ authorizationEndpoint?: string;
38
+ tokenEndpoint?: string;
39
+ protectedResource?: string;
40
+ authorizationServers?: readonly string[];
41
+ responseTypesSupported?: readonly string[];
42
+ grantTypesSupported?: readonly OAuthGrantType[];
43
+ codeChallengeMethodsSupported?: ReadonlyArray<'S256' | 'plain'>;
44
+ tokenEndpointAuthMethodsSupported?: readonly string[];
45
+ scopesSupported?: readonly string[];
46
+ }): WellKnownMetadata;
@@ -0,0 +1,108 @@
1
+ function firstHeaderToken(value) {
2
+ if (!value) {
3
+ return undefined;
4
+ }
5
+ const first = value.split(',')[0]?.trim();
6
+ return first && first.length > 0 ? first : undefined;
7
+ }
8
+ function normalizeOrigin(value) {
9
+ const trimmed = value.trim();
10
+ if (trimmed.length === 0) {
11
+ throw new Error('Origin must be a non-empty string.');
12
+ }
13
+ if (/^https?:\/\//i.test(trimmed)) {
14
+ return new URL(trimmed).origin;
15
+ }
16
+ return new URL(`https://${trimmed}`).origin;
17
+ }
18
+ function normalizePathOrUrl(value) {
19
+ const trimmed = value.trim();
20
+ if (trimmed.length === 0) {
21
+ throw new Error('Path must be a non-empty string.');
22
+ }
23
+ return trimmed;
24
+ }
25
+ function resolveAbsoluteUrl(origin, pathOrUrl) {
26
+ const normalized = normalizePathOrUrl(pathOrUrl);
27
+ if (/^https?:\/\//i.test(normalized)) {
28
+ return new URL(normalized).toString();
29
+ }
30
+ const withLeadingSlash = normalized.startsWith('/')
31
+ ? normalized
32
+ : `/${normalized}`;
33
+ return new URL(withLeadingSlash, origin).toString();
34
+ }
35
+ export function resolveRequestOrigin(input) {
36
+ if (input.requestUrl) {
37
+ try {
38
+ return new URL(input.requestUrl).origin;
39
+ }
40
+ catch {
41
+ // Falls through to header-based resolution.
42
+ }
43
+ }
44
+ const forwardedHost = firstHeaderToken(input.forwardedHostHeader);
45
+ const forwardedProto = firstHeaderToken(input.forwardedProtoHeader);
46
+ if (forwardedHost) {
47
+ const protocol = forwardedProto ?? 'https';
48
+ return normalizeOrigin(`${protocol}://${forwardedHost}`);
49
+ }
50
+ const host = firstHeaderToken(input.hostHeader);
51
+ if (host) {
52
+ const protocol = forwardedProto ??
53
+ (host.includes('localhost') || host.startsWith('127.')
54
+ ? 'http'
55
+ : 'https');
56
+ return normalizeOrigin(`${protocol}://${host}`);
57
+ }
58
+ return normalizeOrigin(input.fallbackOrigin ?? 'http://localhost');
59
+ }
60
+ export function buildWellKnownMetadata(input) {
61
+ const issuer = normalizeOrigin(input.issuer);
62
+ const authorizationEndpoint = resolveAbsoluteUrl(issuer, input.authorizationEndpoint ?? '/authorize');
63
+ const tokenEndpoint = resolveAbsoluteUrl(issuer, input.tokenEndpoint ?? '/token');
64
+ const protectedResource = resolveAbsoluteUrl(issuer, input.protectedResource ?? '/');
65
+ const authorizationServers = (input.authorizationServers ?? [issuer]).map(server => normalizeOrigin(server));
66
+ const responseTypesSupported = [
67
+ ...(input.responseTypesSupported ?? ['code']),
68
+ ];
69
+ const grantTypesSupported = [
70
+ ...(input.grantTypesSupported ?? ['authorization_code', 'refresh_token']),
71
+ ];
72
+ const codeChallengeMethodsSupported = [
73
+ ...(input.codeChallengeMethodsSupported ?? ['S256', 'plain']),
74
+ ];
75
+ const tokenEndpointAuthMethodsSupported = [
76
+ ...(input.tokenEndpointAuthMethodsSupported ?? [
77
+ 'client_secret_basic',
78
+ 'client_secret_post',
79
+ ]),
80
+ ];
81
+ const scopesSupported = [...(input.scopesSupported ?? ['mcp:access'])];
82
+ return {
83
+ oauthProtectedResource: {
84
+ resource: protectedResource,
85
+ authorization_servers: authorizationServers,
86
+ },
87
+ oauthAuthorizationServer: {
88
+ issuer,
89
+ authorization_endpoint: authorizationEndpoint,
90
+ token_endpoint: tokenEndpoint,
91
+ response_types_supported: responseTypesSupported,
92
+ grant_types_supported: grantTypesSupported,
93
+ code_challenge_methods_supported: codeChallengeMethodsSupported,
94
+ token_endpoint_auth_methods_supported: tokenEndpointAuthMethodsSupported,
95
+ scopes_supported: scopesSupported,
96
+ },
97
+ openidConfiguration: {
98
+ issuer,
99
+ authorization_endpoint: authorizationEndpoint,
100
+ token_endpoint: tokenEndpoint,
101
+ response_types_supported: responseTypesSupported,
102
+ grant_types_supported: grantTypesSupported,
103
+ code_challenge_methods_supported: codeChallengeMethodsSupported,
104
+ token_endpoint_auth_methods_supported: tokenEndpointAuthMethodsSupported,
105
+ scopes_supported: scopesSupported,
106
+ },
107
+ };
108
+ }
@@ -0,0 +1,2 @@
1
+ import type { AccessTokenRevocationAdapter } from '../../core/adapters/access-token-revocation-adapter';
2
+ export declare function createInMemoryAccessTokenRevocationAdapter(): AccessTokenRevocationAdapter;
@@ -0,0 +1,14 @@
1
+ export function createInMemoryAccessTokenRevocationAdapter() {
2
+ const revokedJtis = new Map();
3
+ return {
4
+ async revokeJti(input) {
5
+ revokedJtis.set(input.jti, {
6
+ expiresAtUnix: input.expiresAtUnix,
7
+ ...(input.reason === undefined ? {} : { reason: input.reason }),
8
+ });
9
+ },
10
+ async isRevoked(jti) {
11
+ return revokedJtis.has(jti);
12
+ },
13
+ };
14
+ }
@@ -0,0 +1,2 @@
1
+ import type { AuthorizationCodeAdapter } from '../../core/adapters/authorization-code-adapter';
2
+ export declare function createInMemoryAuthorizationCodeAdapter<TUserId>(): AuthorizationCodeAdapter<TUserId>;
@@ -0,0 +1,36 @@
1
+ export function createInMemoryAuthorizationCodeAdapter() {
2
+ const records = new Map();
3
+ return {
4
+ async create(input) {
5
+ records.set(input.codeHash, {
6
+ userId: input.userId,
7
+ codeHash: input.codeHash,
8
+ clientId: input.clientId,
9
+ redirectUri: input.redirectUri,
10
+ codeChallenge: input.codeChallenge,
11
+ codeMethod: input.codeMethod,
12
+ expiresAtUnix: input.expiresAtUnix,
13
+ });
14
+ },
15
+ async consume(codeHash, nowUnix) {
16
+ const record = records.get(codeHash);
17
+ if (!record) {
18
+ return null;
19
+ }
20
+ if (record.consumedAtUnix === undefined ||
21
+ record.consumedAtUnix === null) {
22
+ record.consumedAtUnix = nowUnix;
23
+ records.set(codeHash, record);
24
+ }
25
+ return {
26
+ userId: record.userId,
27
+ clientId: record.clientId,
28
+ redirectUri: record.redirectUri,
29
+ codeChallenge: record.codeChallenge,
30
+ codeMethod: record.codeMethod,
31
+ expiresAtUnix: record.expiresAtUnix,
32
+ consumedAtUnix: record.consumedAtUnix,
33
+ };
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,14 @@
1
+ import type { OAuthClientAdapter } from '../../core/adapters/oauth-client-adapter';
2
+ import type { OAuthGrantType } from '../../core/adapters/oauth-grant-type';
3
+ import type { AuthActor } from '../../core/types/auth-contract';
4
+ export type InMemoryOAuthClient<TActor extends AuthActor, TUserId> = {
5
+ clientId: string;
6
+ clientSecret: string | null;
7
+ actor: TActor;
8
+ userId: TUserId;
9
+ allowedGrantTypes?: readonly OAuthGrantType[];
10
+ disabled?: boolean;
11
+ };
12
+ export declare function createInMemoryOAuthClientAdapter<TActor extends AuthActor, TUserId>(input: {
13
+ clients: readonly InMemoryOAuthClient<TActor, TUserId>[];
14
+ }): OAuthClientAdapter<TActor, TUserId>;
@@ -0,0 +1,26 @@
1
+ export function createInMemoryOAuthClientAdapter(input) {
2
+ const clientsById = new Map();
3
+ for (const client of input.clients) {
4
+ clientsById.set(client.clientId, client);
5
+ }
6
+ return {
7
+ async validateClient(request) {
8
+ const client = clientsById.get(request.clientId);
9
+ if (!client || client.disabled) {
10
+ return null;
11
+ }
12
+ if (client.clientSecret !== request.clientSecret) {
13
+ return null;
14
+ }
15
+ if (client.allowedGrantTypes !== undefined &&
16
+ !client.allowedGrantTypes.includes(request.grantType)) {
17
+ return null;
18
+ }
19
+ return {
20
+ clientId: client.clientId,
21
+ actor: client.actor,
22
+ userId: client.userId,
23
+ };
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,2 @@
1
+ import type { PendingAuthRequestAdapter } from '../../core/adapters/pending-auth-request-adapter';
2
+ export declare function createInMemoryPendingAuthRequestAdapter<TPendingAuthRequest>(): PendingAuthRequestAdapter<TPendingAuthRequest>;
@@ -0,0 +1,43 @@
1
+ export function createInMemoryPendingAuthRequestAdapter() {
2
+ const records = new Map();
3
+ return {
4
+ async create(input) {
5
+ records.set(input.requestId, {
6
+ payload: structuredClone(input.payload),
7
+ expiresAtUnix: input.expiresAtUnix,
8
+ });
9
+ },
10
+ async find(requestId) {
11
+ const record = records.get(requestId);
12
+ if (!record) {
13
+ return null;
14
+ }
15
+ return {
16
+ payload: structuredClone(record.payload),
17
+ expiresAtUnix: record.expiresAtUnix,
18
+ ...(record.consumedAtUnix === undefined
19
+ ? {}
20
+ : { consumedAtUnix: record.consumedAtUnix }),
21
+ };
22
+ },
23
+ async consume(requestId, nowUnix) {
24
+ const record = records.get(requestId);
25
+ if (!record) {
26
+ return null;
27
+ }
28
+ if (record.consumedAtUnix === undefined ||
29
+ record.consumedAtUnix === null) {
30
+ record.consumedAtUnix = nowUnix;
31
+ records.set(requestId, record);
32
+ }
33
+ return {
34
+ payload: structuredClone(record.payload),
35
+ expiresAtUnix: record.expiresAtUnix,
36
+ consumedAtUnix: record.consumedAtUnix,
37
+ };
38
+ },
39
+ async delete(requestId) {
40
+ records.delete(requestId);
41
+ },
42
+ };
43
+ }
@@ -0,0 +1,2 @@
1
+ import type { RefreshTokenAdapter } from '../../core/adapters/refresh-token-adapter';
2
+ export declare function createInMemoryRefreshTokenAdapter<TSessionId>(): RefreshTokenAdapter<TSessionId>;