@boxxy/auth-oauth 0.2.5 → 0.2.7

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 (144) hide show
  1. package/README.md +12 -13
  2. package/dist/auth/create-auth-admin.d.ts +25 -12
  3. package/dist/auth/create-security-event-log.d.ts +1 -1
  4. package/dist/auth/format-security-event.d.ts +10 -2
  5. package/dist/auth/open-auth-database.d.ts +2 -2
  6. package/dist/http/create-public-door.d.ts +5 -1
  7. package/dist/http/{answer-mcp-request.d.ts → types.d.ts} +0 -2
  8. package/dist/index.d.ts +2 -5
  9. package/dist/migrations/grant-rows-migration.d.ts +2 -0
  10. package/dist/migrations/run-auth-migrations.d.ts +2 -0
  11. package/dist/test-utils/authorize-test-client.d.ts +1 -13
  12. package/dist/test-utils/pre-package-schema.d.ts +1 -0
  13. package/dist/test-utils/seed-pre-package-auth.d.ts +21 -0
  14. package/package.json +7 -13
  15. package/src/auth/create-auth-admin.ts +109 -41
  16. package/src/auth/create-security-event-log.ts +1 -3
  17. package/src/auth/format-security-event.ts +12 -11
  18. package/src/auth/normalize-client-name.ts +0 -2
  19. package/src/auth/open-auth-database.ts +8 -13
  20. package/src/http/create-public-door.ts +113 -29
  21. package/src/http/is-loopback-host.ts +0 -2
  22. package/src/http/normalize-public-url.ts +0 -2
  23. package/src/http/types.ts +11 -0
  24. package/src/index.ts +9 -5
  25. package/src/migrations/grant-rows-migration.ts +108 -0
  26. package/src/migrations/run-auth-migrations.ts +53 -0
  27. package/src/test-utils/authorize-test-client.ts +2 -194
  28. package/src/test-utils/build-stub-clock.ts +0 -2
  29. package/src/test-utils/pre-package-schema.ts +36 -0
  30. package/src/test-utils/register-test-cleanup.ts +0 -2
  31. package/src/test-utils/seed-pre-package-auth.ts +162 -0
  32. package/src/test-utils/setup-temp-dir.ts +0 -2
  33. package/src/utils/types.ts +0 -2
  34. package/dist/auth/answer-authorize-request.d.ts +0 -2
  35. package/dist/auth/answer-consent-request.d.ts +0 -10
  36. package/dist/auth/answer-enroll-request.d.ts +0 -9
  37. package/dist/auth/answer-login-request.d.ts +0 -9
  38. package/dist/auth/approval-window-seconds.d.ts +0 -1
  39. package/dist/auth/build-consent-binding.d.ts +0 -1
  40. package/dist/auth/create-approval-state.d.ts +0 -41
  41. package/dist/auth/create-enrollment-state.d.ts +0 -6
  42. package/dist/auth/create-enrollment-token.d.ts +0 -6
  43. package/dist/auth/create-owner-plugin.d.ts +0 -38
  44. package/dist/auth/create-passkey.d.ts +0 -11
  45. package/dist/auth/derive-token-hash.d.ts +0 -1
  46. package/dist/auth/is-allowed-redirect-uri.d.ts +0 -1
  47. package/dist/auth/load-web-authn.d.ts +0 -6
  48. package/dist/auth/mcp-auth-database.d.ts +0 -5
  49. package/dist/auth/mcp-auth-schema.d.ts +0 -54
  50. package/dist/auth/open-mcp-auth.d.ts +0 -80
  51. package/dist/auth/passkey-script.d.ts +0 -4
  52. package/dist/auth/read-client-name.d.ts +0 -3
  53. package/dist/auth/read-client-views.d.ts +0 -9
  54. package/dist/auth/read-enrollment-token.d.ts +0 -3
  55. package/dist/auth/read-grant-views.d.ts +0 -12
  56. package/dist/auth/read-owner-session-id.d.ts +0 -2
  57. package/dist/auth/record-refused-exchange.d.ts +0 -10
  58. package/dist/auth/remove-client.d.ts +0 -3
  59. package/dist/auth/remove-owner-session.d.ts +0 -3
  60. package/dist/auth/render-consent-page.d.ts +0 -9
  61. package/dist/auth/render-enroll-page.d.ts +0 -10
  62. package/dist/auth/render-login-page.d.ts +0 -9
  63. package/dist/auth/revoke-grant.d.ts +0 -3
  64. package/dist/auth/to-html-text.d.ts +0 -1
  65. package/dist/auth/to-script-json.d.ts +0 -1
  66. package/dist/auth/types.d.ts +0 -79
  67. package/dist/auth/update-passkey-use.d.ts +0 -10
  68. package/dist/auth/verify-oauth-query.d.ts +0 -1
  69. package/dist/auth/verify-passkey-assertion.d.ts +0 -11
  70. package/dist/auth/verify-passkey-registration.d.ts +0 -14
  71. package/dist/auth/verify-session-passkey.d.ts +0 -8
  72. package/dist/auth/write-grant-use.d.ts +0 -3
  73. package/dist/http/build-page-response.d.ts +0 -5
  74. package/dist/http/create-mcp-http-app.d.ts +0 -92
  75. package/dist/http/pick-error-message.d.ts +0 -1
  76. package/dist/http/public-door-paths.d.ts +0 -17
  77. package/dist/http/read-token-access.d.ts +0 -8
  78. package/dist/http/verify-access-token.d.ts +0 -2
  79. package/dist/test-utils/build-mock-new-passkey.d.ts +0 -2
  80. package/dist/test-utils/build-passkey-seeder.d.ts +0 -5
  81. package/dist/test-utils/build-seeder.d.ts +0 -5
  82. package/dist/test-utils/build-stub-authenticator.d.ts +0 -48
  83. package/dist/test-utils/load-web-authn-early.d.ts +0 -1
  84. package/dist/test-utils/seed-passkey.d.ts +0 -4
  85. package/dist/test-utils/start-stub-telemetry-collector.d.ts +0 -5
  86. package/dist/test-utils/types.d.ts +0 -7
  87. package/dist/utils/is-record.d.ts +0 -1
  88. package/dist/utils/parse-json-strings.d.ts +0 -1
  89. package/src/auth/answer-authorize-request.ts +0 -137
  90. package/src/auth/answer-consent-request.ts +0 -189
  91. package/src/auth/answer-enroll-request.ts +0 -190
  92. package/src/auth/answer-login-request.ts +0 -256
  93. package/src/auth/approval-window-seconds.ts +0 -5
  94. package/src/auth/build-consent-binding.ts +0 -13
  95. package/src/auth/create-approval-state.ts +0 -221
  96. package/src/auth/create-enrollment-state.ts +0 -56
  97. package/src/auth/create-enrollment-token.ts +0 -28
  98. package/src/auth/create-owner-plugin.ts +0 -113
  99. package/src/auth/create-passkey.ts +0 -48
  100. package/src/auth/derive-token-hash.ts +0 -7
  101. package/src/auth/is-allowed-redirect-uri.ts +0 -19
  102. package/src/auth/load-web-authn.ts +0 -19
  103. package/src/auth/mcp-auth-database.ts +0 -10
  104. package/src/auth/mcp-auth-schema.ts +0 -56
  105. package/src/auth/open-mcp-auth.ts +0 -138
  106. package/src/auth/passkey-script.ts +0 -50
  107. package/src/auth/read-client-name.ts +0 -17
  108. package/src/auth/read-client-views.ts +0 -27
  109. package/src/auth/read-enrollment-token.ts +0 -20
  110. package/src/auth/read-grant-views.ts +0 -53
  111. package/src/auth/read-owner-session-id.ts +0 -16
  112. package/src/auth/record-refused-exchange.ts +0 -74
  113. package/src/auth/remove-client.ts +0 -33
  114. package/src/auth/remove-owner-session.ts +0 -11
  115. package/src/auth/render-consent-page.ts +0 -61
  116. package/src/auth/render-enroll-page.ts +0 -59
  117. package/src/auth/render-login-page.ts +0 -58
  118. package/src/auth/revoke-grant.ts +0 -25
  119. package/src/auth/to-html-text.ts +0 -10
  120. package/src/auth/to-script-json.ts +0 -8
  121. package/src/auth/types.ts +0 -115
  122. package/src/auth/update-passkey-use.ts +0 -31
  123. package/src/auth/verify-oauth-query.ts +0 -41
  124. package/src/auth/verify-passkey-assertion.ts +0 -124
  125. package/src/auth/verify-passkey-registration.ts +0 -118
  126. package/src/auth/verify-session-passkey.ts +0 -47
  127. package/src/auth/write-grant-use.ts +0 -18
  128. package/src/http/answer-mcp-request.ts +0 -77
  129. package/src/http/build-page-response.ts +0 -26
  130. package/src/http/create-mcp-http-app.ts +0 -340
  131. package/src/http/pick-error-message.ts +0 -21
  132. package/src/http/public-door-paths.ts +0 -21
  133. package/src/http/read-token-access.ts +0 -44
  134. package/src/http/verify-access-token.ts +0 -46
  135. package/src/test-utils/build-mock-new-passkey.ts +0 -15
  136. package/src/test-utils/build-passkey-seeder.ts +0 -11
  137. package/src/test-utils/build-seeder.ts +0 -10
  138. package/src/test-utils/build-stub-authenticator.ts +0 -244
  139. package/src/test-utils/load-web-authn-early.ts +0 -8
  140. package/src/test-utils/seed-passkey.ts +0 -32
  141. package/src/test-utils/start-stub-telemetry-collector.ts +0 -27
  142. package/src/test-utils/types.ts +0 -11
  143. package/src/utils/is-record.ts +0 -5
  144. package/src/utils/parse-json-strings.ts +0 -15
@@ -1,79 +0,0 @@
1
- import type { GrantScope } from '@boxxy/contract';
2
- import type { AuthClock } from '../utils/types.ts';
3
- import type { ApprovalState } from './create-approval-state.ts';
4
- import type { EnrollmentState } from './create-enrollment-state.ts';
5
- import type { openMCPAuth } from './open-mcp-auth.ts';
6
- export interface Requester {
7
- readonly socketAddress: string | null;
8
- readonly connectingIP: {
9
- readonly address: string;
10
- readonly provenance: 'trusted' | 'unverified';
11
- } | null;
12
- readonly userAgent: string | null;
13
- }
14
- export type SecurityOutcome = {
15
- readonly kind: 'authorize_started';
16
- readonly outcome: 'accepted';
17
- } | {
18
- readonly kind: 'authorize_started';
19
- readonly outcome: 'rejected';
20
- readonly reason: 'rate_limited';
21
- } | {
22
- readonly kind: 'authorize_invalid';
23
- readonly outcome: 'rejected';
24
- readonly reason: AuthorizeInvalidReason;
25
- } | {
26
- readonly kind: 'owner_auth';
27
- readonly outcome: 'succeeded';
28
- } | {
29
- readonly kind: 'owner_auth';
30
- readonly outcome: 'failed';
31
- readonly reason?: OwnerAuthFailure;
32
- } | {
33
- readonly kind: 'consent';
34
- readonly outcome: 'allowed';
35
- } | {
36
- readonly kind: 'consent';
37
- readonly outcome: 'denied';
38
- readonly reason?: 'none_ticked';
39
- } | {
40
- readonly kind: 'token_exchange';
41
- readonly outcome: 'rejected';
42
- readonly reason: TokenExchangeReason;
43
- } | {
44
- readonly kind: 'bearer';
45
- readonly outcome: 'rejected';
46
- readonly reason: BearerReason;
47
- };
48
- export type OwnerAuthFailure = 'assertion_failed' | 'locked' | 'expired' | 'not_enrolled' | 'invalid_token';
49
- export type AuthorizeInvalidReason = 'invalid_client' | 'client_disabled' | 'invalid_redirect' | 'unauthorized_client' | 'unsupported_response_type' | 'invalid_request' | 'invalid_scope' | 'invalid_target' | 'bad_host' | 'bad_origin' | 'other';
50
- export type TokenExchangeReason = 'invalid_request' | 'invalid_client' | 'invalid_grant' | 'unauthorized_client' | 'unsupported_grant_type' | 'invalid_scope' | 'invalid_target' | 'other';
51
- type BearerReason = 'malformed' | 'invalid_token';
52
- export type SecurityEvent = SecurityOutcome & {
53
- readonly path: string;
54
- readonly requester: Requester;
55
- readonly client?: {
56
- readonly id: string;
57
- readonly name: string;
58
- };
59
- readonly redirectHost?: string;
60
- readonly correlationID?: string;
61
- };
62
- export interface VerifiedAccess {
63
- readonly clientID: string;
64
- readonly clientName: string;
65
- readonly scopes: readonly GrantScope[];
66
- }
67
- type MCPAuth = Awaited<ReturnType<typeof openMCPAuth>>;
68
- export interface HTTPServerContext {
69
- readonly origin: string;
70
- readonly resource: string;
71
- readonly store: MCPAuth;
72
- readonly approvals: ApprovalState;
73
- readonly enrollments: EnrollmentState;
74
- readonly security: {
75
- readonly record: (event: SecurityEvent) => void;
76
- };
77
- readonly clock: AuthClock;
78
- }
79
- export {};
@@ -1,10 +0,0 @@
1
- import type { Kysely } from 'kysely';
2
- import type { MCPAuthSchema } from './mcp-auth-schema.ts';
3
- interface PasskeyUse {
4
- readonly id: string;
5
- readonly counter: number;
6
- readonly newCounter: number;
7
- readonly now: number;
8
- }
9
- export declare function updatePasskeyUse(db: Kysely<MCPAuthSchema>, use: PasskeyUse): Promise<boolean>;
10
- export {};
@@ -1 +0,0 @@
1
- export declare function verifyOAuthQuery(oauthQuery: string, secret: string, now: number): Promise<boolean>;
@@ -1,11 +0,0 @@
1
- import type { Kysely } from 'kysely';
2
- import type { MCPAuthSchema } from './mcp-auth-schema.ts';
3
- interface AssertionCheck {
4
- readonly credential: string;
5
- readonly challenge: string;
6
- readonly origin: string;
7
- readonly rpID: string;
8
- readonly now: number;
9
- }
10
- export declare function verifyPasskeyAssertion(db: Kysely<MCPAuthSchema>, check: AssertionCheck): Promise<string | null>;
11
- export {};
@@ -1,14 +0,0 @@
1
- interface RegistrationCheck {
2
- readonly credential: string;
3
- readonly challenge: string;
4
- readonly origin: string;
5
- readonly rpID: string;
6
- }
7
- export interface RegisteredPasskey {
8
- readonly id: string;
9
- readonly publicKey: Uint8Array;
10
- readonly counter: number;
11
- readonly transports: readonly string[];
12
- }
13
- export declare function verifyPasskeyRegistration(check: RegistrationCheck): Promise<RegisteredPasskey | null>;
14
- export {};
@@ -1,8 +0,0 @@
1
- import type { Kysely } from 'kysely';
2
- import type { MCPAuthSchema } from './mcp-auth-schema.ts';
3
- interface SessionPasskey {
4
- readonly sessionID: string;
5
- readonly passkeyID: string;
6
- }
7
- export declare function verifySessionPasskey(db: Kysely<MCPAuthSchema>, check: SessionPasskey): Promise<boolean>;
8
- export {};
@@ -1,3 +0,0 @@
1
- import type { Kysely } from 'kysely';
2
- import type { MCPAuthSchema } from './mcp-auth-schema.ts';
3
- export declare function writeGrantUse(db: Kysely<MCPAuthSchema>, grantID: string, now: number): Promise<void>;
@@ -1,5 +0,0 @@
1
- export declare function buildPageResponse(status: number, body: {
2
- readonly page: string;
3
- } | {
4
- readonly message: string;
5
- }, formTargets?: readonly string[], scriptSources?: readonly string[]): Response;
@@ -1,92 +0,0 @@
1
- import type { HTTPServerContext, Requester, SecurityEvent } from '../auth/types.ts';
2
- import type { McpServe } from './answer-mcp-request.ts';
3
- interface MCPHTTPAppOptions {
4
- readonly hosts: ReadonlySet<string>;
5
- readonly origins: ReadonlySet<string>;
6
- readonly readContext: () => HTTPServerContext | null;
7
- readonly readRequester: (request: Request) => Requester;
8
- readonly serveMCP: McpServe;
9
- readonly security: {
10
- readonly record: (event: SecurityEvent) => void;
11
- };
12
- }
13
- interface RouteContext {
14
- readonly request: Request;
15
- }
16
- export declare function createMCPHTTPApp(options: MCPHTTPAppOptions): import("elysia/types").AddRoute<"", "local", import("elysia").DefaultSingleton, {
17
- typebox: {};
18
- error: [];
19
- }, import("elysia").DefaultMetadata, {
20
- oauth2: {
21
- revoke: {
22
- post: {
23
- body: unknown;
24
- params: {};
25
- query: unknown;
26
- headers: unknown;
27
- response: {
28
- 200: Response;
29
- };
30
- error: never;
31
- };
32
- };
33
- };
34
- } & {
35
- ".well-known": {
36
- "oauth-authorization-server": {
37
- get: {
38
- body: unknown;
39
- params: {};
40
- query: unknown;
41
- headers: unknown;
42
- response: {
43
- 200: Response;
44
- };
45
- error: never;
46
- };
47
- };
48
- };
49
- } & {
50
- oauth2: {
51
- authorize: {
52
- get: {
53
- body: unknown;
54
- params: {};
55
- query: unknown;
56
- headers: unknown;
57
- response: {
58
- 200: Response;
59
- };
60
- error: never;
61
- };
62
- };
63
- };
64
- } & {
65
- oauth2: {
66
- token: {
67
- post: {
68
- body: unknown;
69
- params: {};
70
- query: unknown;
71
- headers: unknown;
72
- response: {
73
- 200: Response;
74
- };
75
- error: never;
76
- };
77
- };
78
- };
79
- }, import("elysia").DefaultEphemeral, {
80
- derive: {};
81
- schema: {};
82
- schemas: {};
83
- response: import("elysia/types").ExtractErrorFromHandle<Response>;
84
- error: [{
85
- error: Error;
86
- response: {};
87
- passes: false;
88
- '~return': Response;
89
- '~catchAll': true;
90
- }];
91
- }, "get", "/error", import("elysia/types").IntersectIfObjectSchema<import("elysia").UnwrapRoute<{}, {}, "/error">, import("elysia/types").MergeScopedSchemas<{}, {}, {}>>, {}, (context: RouteContext) => Response>;
92
- export {};
@@ -1 +0,0 @@
1
- export declare function pickErrorMessage(code: string | null): string;
@@ -1,17 +0,0 @@
1
- export declare const PUBLIC_DOOR_PATHS: {
2
- readonly browser: {
3
- readonly authorize: '/oauth2/authorize';
4
- readonly login: '/login';
5
- readonly enroll: '/enroll';
6
- readonly consent: '/consent';
7
- readonly error: '/error';
8
- };
9
- readonly machine: {
10
- readonly mcp: '/mcp';
11
- readonly token: '/oauth2/token';
12
- readonly revoke: '/oauth2/revoke';
13
- readonly authorizationServerMetadata: '/.well-known/oauth-authorization-server';
14
- readonly resourceMetadata: '/.well-known/oauth-protected-resource';
15
- readonly mcpResourceMetadata: '/.well-known/oauth-protected-resource/mcp';
16
- };
17
- };
@@ -1,8 +0,0 @@
1
- import type { GrantScope } from '@boxxy/contract';
2
- import type { HTTPServerContext } from '../auth/types.ts';
3
- interface TokenAccess {
4
- readonly clientID: string;
5
- readonly scopes: readonly GrantScope[];
6
- }
7
- export declare function readTokenAccess(ctx: HTTPServerContext, token: string): Promise<TokenAccess | null>;
8
- export {};
@@ -1,2 +0,0 @@
1
- import type { HTTPServerContext, VerifiedAccess } from '../auth/types.ts';
2
- export declare function verifyAccessToken(ctx: HTTPServerContext, token: string): Promise<VerifiedAccess | null>;
@@ -1,2 +0,0 @@
1
- import type { NewPasskey } from '../auth/create-passkey.ts';
2
- export declare function buildMockNewPasskey(overrides?: Partial<NewPasskey>): NewPasskey;
@@ -1,5 +0,0 @@
1
- import type { PasskeyInput } from './seed-passkey.ts';
2
- import type { MCPAuthSeedDeps } from './types.ts';
3
- export declare function buildPasskeySeeder(deps: MCPAuthSeedDeps): {
4
- passkey: (input?: PasskeyInput) => Promise<import("../auth/create-passkey.ts").NewPasskey>;
5
- };
@@ -1,5 +0,0 @@
1
- import type { MCPAuthSeedDeps } from './types.ts';
2
- export type TestDeps = MCPAuthSeedDeps;
3
- export declare function buildSeeder(deps: TestDeps): {
4
- passkey: (input?: import("./seed-passkey.ts").PasskeyInput) => Promise<import("../auth/create-passkey.ts").NewPasskey>;
5
- };
@@ -1,48 +0,0 @@
1
- interface CreationOptions {
2
- readonly challenge: string;
3
- readonly rp: {
4
- readonly id?: string;
5
- };
6
- readonly user: {
7
- readonly id: string;
8
- };
9
- }
10
- interface RequestOptions {
11
- readonly challenge: string;
12
- readonly rpId?: string;
13
- }
14
- interface Ceremony {
15
- readonly origin: string;
16
- readonly rpID?: string;
17
- readonly userVerified?: boolean;
18
- }
19
- export interface StubRegistration {
20
- readonly id: string;
21
- readonly rawId: string;
22
- readonly type: 'public-key';
23
- readonly response: {
24
- readonly clientDataJSON: string;
25
- readonly attestationObject: string;
26
- readonly transports: readonly string[];
27
- };
28
- readonly clientExtensionResults: Readonly<Record<string, never>>;
29
- readonly authenticatorAttachment: 'platform';
30
- }
31
- export interface StubAssertion {
32
- readonly id: string;
33
- readonly rawId: string;
34
- readonly type: 'public-key';
35
- readonly response: {
36
- readonly clientDataJSON: string;
37
- readonly authenticatorData: string;
38
- readonly signature: string;
39
- readonly userHandle: string;
40
- };
41
- readonly clientExtensionResults: Readonly<Record<string, never>>;
42
- }
43
- export declare function buildStubAuthenticator(): {
44
- credentialID: string;
45
- create(options: CreationOptions, ceremony: Ceremony): Promise<StubRegistration>;
46
- signAssertion(options: RequestOptions, ceremony: Ceremony): Promise<StubAssertion>;
47
- };
48
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,4 +0,0 @@
1
- import type { NewPasskey } from '../auth/create-passkey.ts';
2
- import type { MCPAuthSeedDeps } from './types.ts';
3
- export type PasskeyInput = Partial<Omit<NewPasskey, 'tokenHash'>>;
4
- export declare function seedPasskey(deps: MCPAuthSeedDeps, input?: PasskeyInput): Promise<NewPasskey>;
@@ -1,5 +0,0 @@
1
- export declare function startStubTelemetryCollector(): {
2
- url: string;
3
- received: string[];
4
- stop: () => Promise<void>;
5
- };
@@ -1,7 +0,0 @@
1
- import type { Kysely } from 'kysely';
2
- import type { MCPAuthSchema } from '../auth/mcp-auth-schema.ts';
3
- import type { AuthClock } from '../utils/types.ts';
4
- export interface MCPAuthSeedDeps {
5
- readonly db: Kysely<MCPAuthSchema>;
6
- readonly clock: Pick<AuthClock, 'now'>;
7
- }
@@ -1 +0,0 @@
1
- export declare function isRecord(value: unknown): value is Record<string, unknown>;
@@ -1 +0,0 @@
1
- export declare function parseJSONStrings(raw: string): string[] | null;
@@ -1,137 +0,0 @@
1
- // Copied from atc: packages/mcp/src/auth/answer-authorize-request.ts at commit 4fe647cf.
2
-
3
- import { buildPageResponse } from '../http/build-page-response.ts';
4
- import { PUBLIC_DOOR_PATHS } from '../http/public-door-paths.ts';
5
- import { normalizeClientName } from './normalize-client-name.ts';
6
- import { readClientName } from './read-client-name.ts';
7
- import type { AuthorizeInvalidReason, HTTPServerContext, Requester } from './types.ts';
8
-
9
- export async function answerAuthorizeRequest(
10
- ctx: HTTPServerContext,
11
- url: URL,
12
- requester: Requester,
13
- ): Promise<Response> {
14
- const params = new URLSearchParams(url.searchParams);
15
-
16
- // every authorization asks for a fresh passkey sign-in and the consent page,
17
- // whatever session or consent the browser kept.
18
- params.set('prompt', 'login consent');
19
-
20
- const scope = params.get('scope');
21
-
22
- if (scope !== null) {
23
- const scopes = scope.split(' ').filter((item) => item !== '');
24
-
25
- params.set('scope', [...new Set([...scopes, 'offline_access'])].join(' '));
26
- }
27
-
28
- if (!params.has('resource')) {
29
- params.set('resource', ctx.resource);
30
- }
31
-
32
- const response = await ctx.store.auth.handler(
33
- new Request(`${ctx.origin}${PUBLIC_DOOR_PATHS.browser.authorize}?${params.toString()}`, {
34
- headers: { accept: 'text/html' },
35
- }),
36
- );
37
-
38
- const location = response.headers.get('location');
39
- const target = location === null ? null : new URL(location, ctx.origin);
40
-
41
- if (
42
- target === null ||
43
- target.origin !== ctx.origin ||
44
- target.pathname !== PUBLIC_DOOR_PATHS.browser.login
45
- ) {
46
- await recordInvalidRequest(ctx, url, requester, target);
47
-
48
- return response;
49
- }
50
-
51
- const clientID = target.searchParams.get('client_id') ?? '';
52
- const redirectURI = target.searchParams.get('redirect_uri') ?? '';
53
-
54
- const storedName = await readClientName(ctx.store.db, clientID);
55
-
56
- const clientName = normalizeClientName(storedName);
57
-
58
- const accepted = {
59
- path: url.pathname,
60
- requester,
61
- client: { id: clientID, name: clientName },
62
- redirectHost: new URL(redirectURI).host,
63
- };
64
-
65
- const approval = ctx.approvals.createPending({
66
- key: target.search.slice(1),
67
- clientID,
68
- clientName,
69
- redirectURI,
70
- });
71
-
72
- if (approval === null) {
73
- ctx.security.record({
74
- kind: 'authorize_started',
75
- outcome: 'rejected',
76
- reason: 'rate_limited',
77
- ...accepted,
78
- });
79
-
80
- return buildPageResponse(429, {
81
- message: 'Too many approvals started in the last minute. Wait a minute, then start again.',
82
- });
83
- }
84
-
85
- ctx.security.record({
86
- kind: 'authorize_started',
87
- outcome: 'accepted',
88
- ...accepted,
89
- correlationID: approval.correlationID,
90
- });
91
-
92
- return response;
93
- }
94
-
95
- // A refusal that returns to the client went to a redirect URI better-auth
96
- // validated, so only then does the event hold its host. The client's id
97
- // and name go in only for a client boxxy holds.
98
- async function recordInvalidRequest(
99
- ctx: HTTPServerContext,
100
- url: URL,
101
- requester: Requester,
102
- target: URL | null,
103
- ): Promise<void> {
104
- const code = target?.searchParams.get('error') ?? null;
105
- const reason = isAuthorizeError(code) ? code : 'other';
106
- const clientID = url.searchParams.get('client_id');
107
- const storedName = clientID === null ? null : await readClientName(ctx.store.db, clientID);
108
-
109
- ctx.security.record({
110
- kind: 'authorize_invalid',
111
- outcome: 'rejected',
112
- reason,
113
- path: url.pathname,
114
- requester,
115
- ...(clientID === null || storedName === null
116
- ? {}
117
- : { client: { id: clientID, name: normalizeClientName(storedName) } }),
118
- ...(target === null || target.origin === ctx.origin ? {} : { redirectHost: target.host }),
119
- });
120
- }
121
-
122
- // The better-auth error codes an `authorize_invalid` event may hold; any
123
- // other code becomes `other`.
124
- const AUTHORIZE_ERRORS: ReadonlySet<string> = new Set([
125
- 'invalid_client',
126
- 'client_disabled',
127
- 'invalid_redirect',
128
- 'unauthorized_client',
129
- 'unsupported_response_type',
130
- 'invalid_request',
131
- 'invalid_scope',
132
- 'invalid_target',
133
- ]);
134
-
135
- function isAuthorizeError(code: string | null): code is AuthorizeInvalidReason {
136
- return code !== null && AUTHORIZE_ERRORS.has(code);
137
- }