@ankhorage/auth-supabase 0.0.0-stage → 2.0.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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -2
  3. package/dist/authConfiguration.d.ts +15 -0
  4. package/dist/authConfiguration.d.ts.map +1 -0
  5. package/dist/authConfiguration.js +69 -0
  6. package/dist/authConfiguration.js.map +1 -0
  7. package/dist/authPasswordOperations.d.ts +10 -0
  8. package/dist/authPasswordOperations.d.ts.map +1 -0
  9. package/dist/authPasswordOperations.js +163 -0
  10. package/dist/authPasswordOperations.js.map +1 -0
  11. package/dist/authSessionContext.d.ts +19 -0
  12. package/dist/authSessionContext.d.ts.map +1 -0
  13. package/dist/authSessionContext.js +96 -0
  14. package/dist/authSessionContext.js.map +1 -0
  15. package/dist/authSessionOperations.d.ts +7 -0
  16. package/dist/authSessionOperations.d.ts.map +1 -0
  17. package/dist/authSessionOperations.js +79 -0
  18. package/dist/authSessionOperations.js.map +1 -0
  19. package/dist/createSupabaseAuthAdapter.d.ts +5 -0
  20. package/dist/createSupabaseAuthAdapter.d.ts.map +1 -0
  21. package/dist/createSupabaseAuthAdapter.js +76 -0
  22. package/dist/createSupabaseAuthAdapter.js.map +1 -0
  23. package/dist/errors.d.ts +7 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +89 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/index.d.ts +8 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +6 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/oauth.d.ts +5 -0
  32. package/dist/oauth.d.ts.map +1 -0
  33. package/dist/oauth.js +34 -0
  34. package/dist/oauth.js.map +1 -0
  35. package/dist/oauthAttemptId.d.ts +9 -0
  36. package/dist/oauthAttemptId.d.ts.map +1 -0
  37. package/dist/oauthAttemptId.js +51 -0
  38. package/dist/oauthAttemptId.js.map +1 -0
  39. package/dist/oauthCallback.d.ts +23 -0
  40. package/dist/oauthCallback.d.ts.map +1 -0
  41. package/dist/oauthCallback.js +81 -0
  42. package/dist/oauthCallback.js.map +1 -0
  43. package/dist/oauthComplete.d.ts +5 -0
  44. package/dist/oauthComplete.d.ts.map +1 -0
  45. package/dist/oauthComplete.js +133 -0
  46. package/dist/oauthComplete.js.map +1 -0
  47. package/dist/oauthCompletionState.d.ts +13 -0
  48. package/dist/oauthCompletionState.d.ts.map +1 -0
  49. package/dist/oauthCompletionState.js +25 -0
  50. package/dist/oauthCompletionState.js.map +1 -0
  51. package/dist/oauthErrors.d.ts +9 -0
  52. package/dist/oauthErrors.d.ts.map +1 -0
  53. package/dist/oauthErrors.js +87 -0
  54. package/dist/oauthErrors.js.map +1 -0
  55. package/dist/oauthExchange.d.ts +14 -0
  56. package/dist/oauthExchange.d.ts.map +1 -0
  57. package/dist/oauthExchange.js +100 -0
  58. package/dist/oauthExchange.js.map +1 -0
  59. package/dist/oauthLifecycle.d.ts +8 -0
  60. package/dist/oauthLifecycle.d.ts.map +1 -0
  61. package/dist/oauthLifecycle.js +24 -0
  62. package/dist/oauthLifecycle.js.map +1 -0
  63. package/dist/oauthProviderDefinitions.d.ts +77 -0
  64. package/dist/oauthProviderDefinitions.d.ts.map +1 -0
  65. package/dist/oauthProviderDefinitions.js +114 -0
  66. package/dist/oauthProviderDefinitions.js.map +1 -0
  67. package/dist/oauthRuntime.d.ts +23 -0
  68. package/dist/oauthRuntime.d.ts.map +1 -0
  69. package/dist/oauthRuntime.js +22 -0
  70. package/dist/oauthRuntime.js.map +1 -0
  71. package/dist/oauthSetupRequirements.d.ts +12 -0
  72. package/dist/oauthSetupRequirements.d.ts.map +1 -0
  73. package/dist/oauthSetupRequirements.js +39 -0
  74. package/dist/oauthSetupRequirements.js.map +1 -0
  75. package/dist/oauthStart.d.ts +4 -0
  76. package/dist/oauthStart.d.ts.map +1 -0
  77. package/dist/oauthStart.js +108 -0
  78. package/dist/oauthStart.js.map +1 -0
  79. package/dist/oauthStartSupport.d.ts +52 -0
  80. package/dist/oauthStartSupport.d.ts.map +1 -0
  81. package/dist/oauthStartSupport.js +137 -0
  82. package/dist/oauthStartSupport.js.map +1 -0
  83. package/dist/oauthStorage.d.ts +25 -0
  84. package/dist/oauthStorage.d.ts.map +1 -0
  85. package/dist/oauthStorage.js +146 -0
  86. package/dist/oauthStorage.js.map +1 -0
  87. package/dist/oauthTypes.d.ts +49 -0
  88. package/dist/oauthTypes.d.ts.map +1 -0
  89. package/dist/oauthTypes.js +6 -0
  90. package/dist/oauthTypes.js.map +1 -0
  91. package/dist/profileVerification.d.ts +24 -0
  92. package/dist/profileVerification.d.ts.map +1 -0
  93. package/dist/profileVerification.js +178 -0
  94. package/dist/profileVerification.js.map +1 -0
  95. package/dist/session.d.ts +6 -0
  96. package/dist/session.d.ts.map +1 -0
  97. package/dist/session.js +124 -0
  98. package/dist/session.js.map +1 -0
  99. package/dist/types.d.ts +63 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +8 -0
  102. package/dist/types.js.map +1 -0
  103. package/package.json +70 -4
@@ -0,0 +1,76 @@
1
+ import { validateSupabaseAuthConfig, } from './authConfiguration.js';
2
+ import { createPasswordResetOperation, createSignInOperation, createSignUpOperation, createVerifyOtpOperation, } from './authPasswordOperations.js';
3
+ import { createAuthOperationContext } from './authSessionContext.js';
4
+ import { createGetSessionOperation, createRefreshSessionOperation, createSignOutOperation, } from './authSessionOperations.js';
5
+ import { createSupabaseOAuthAdapter } from './oauth.js';
6
+ import { oauthCompletionError, oauthStartError } from './oauthErrors.js';
7
+ import { createSupabaseOAuthProfileVerifier } from './profileVerification.js';
8
+ /*** Create a Supabase implementation of the provider-neutral authentication port. */
9
+ export function createSupabaseAuthAdapter(config) {
10
+ const normalizedConfig = validateSupabaseAuthConfig(config);
11
+ const context = createAuthOperationContext(normalizedConfig);
12
+ const oauth = createOAuthOperations(context);
13
+ return {
14
+ signIn: createSignInOperation(context),
15
+ signUp: createSignUpOperation(context),
16
+ signOut: createSignOutOperation(context),
17
+ getSession: createGetSessionOperation(context),
18
+ refreshSession: createRefreshSessionOperation(context),
19
+ requestPasswordReset: createPasswordResetOperation(context),
20
+ verifyOtp: createVerifyOtpOperation(context),
21
+ startAuthorization: oauth.startAuthorization,
22
+ completeAuthorization: oauth.completeAuthorization,
23
+ };
24
+ }
25
+ /*** Create OAuth port operations from the configured Supabase provider set. */
26
+ function createOAuthOperations(context) {
27
+ const { config } = context;
28
+ if (config.oauthProviders.length === 0)
29
+ return createDisabledOAuthOperations();
30
+ const profileVerifier = createProfileVerifier(config);
31
+ return createSupabaseOAuthAdapter({
32
+ url: config.url,
33
+ anonKey: config.anonKey,
34
+ fetch: config.fetch,
35
+ storage: requireOAuthStorage(config),
36
+ storageKey: config.storageKey,
37
+ providers: config.oauthProviders,
38
+ randomBytes: config.oauthRandomBytes,
39
+ persistSession: createOAuthSessionPersistence(context),
40
+ ...(profileVerifier === undefined ? {} : { verifyProfile: profileVerifier }),
41
+ ...(config.onOAuthLifecycleEvent === undefined
42
+ ? {}
43
+ : { onLifecycleEvent: config.onOAuthLifecycleEvent }),
44
+ });
45
+ }
46
+ /*** Return explicit unsupported OAuth results when no OAuth provider was configured. */
47
+ function createDisabledOAuthOperations() {
48
+ return {
49
+ startAuthorization: (input) => Promise.resolve(oauthStartError('provider_misconfigured', 'No OAuth providers are configured for this Supabase Auth adapter.', input.provider, false)),
50
+ completeAuthorization: () => Promise.resolve(oauthCompletionError('authorization_attempt_not_found', 'callback', 'No OAuth authorization attempt can exist without a configured OAuth provider.', undefined, false)),
51
+ };
52
+ }
53
+ function createOAuthSessionPersistence(context) {
54
+ return async (session) => {
55
+ const error = await context.persistSessionSafely(session);
56
+ if (error !== null)
57
+ throw new Error(error.message);
58
+ };
59
+ }
60
+ function createProfileVerifier(config) {
61
+ if (config.profileVerification === undefined)
62
+ return undefined;
63
+ return createSupabaseOAuthProfileVerifier({
64
+ url: config.url,
65
+ anonKey: config.anonKey,
66
+ fetch: config.fetch,
67
+ config: config.profileVerification,
68
+ });
69
+ }
70
+ function requireOAuthStorage(config) {
71
+ if (config.storage === undefined) {
72
+ throw new TypeError('Supabase OAuth PKCE requires persistent auth storage.');
73
+ }
74
+ return config.storage;
75
+ }
76
+ //# sourceMappingURL=createSupabaseAuthAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createSupabaseAuthAdapter.js","sourceRoot":"","sources":["../src/createSupabaseAuthAdapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthPort } from '@ankhorage/auth';\nimport type { AuthSession } from '@ankhorage/contracts/auth';\n\nimport {\n type RequiredSupabaseAuthConfig,\n validateSupabaseAuthConfig,\n} from './authConfiguration.js';\nimport {\n createPasswordResetOperation,\n createSignInOperation,\n createSignUpOperation,\n createVerifyOtpOperation,\n} from './authPasswordOperations.js';\nimport { type AuthOperationContext, createAuthOperationContext } from './authSessionContext.js';\nimport {\n createGetSessionOperation,\n createRefreshSessionOperation,\n createSignOutOperation,\n} from './authSessionOperations.js';\nimport { createSupabaseOAuthAdapter } from './oauth.js';\nimport { oauthCompletionError, oauthStartError } from './oauthErrors.js';\nimport { createSupabaseOAuthProfileVerifier } from './profileVerification.js';\nimport type { SupabaseAuthConfig, SupabaseOAuthProfileVerifier } from './types.js';\n\n/*** Create a Supabase implementation of the provider-neutral authentication port. */\nexport function createSupabaseAuthAdapter(config: SupabaseAuthConfig): AuthPort {\n const normalizedConfig = validateSupabaseAuthConfig(config);\n const context = createAuthOperationContext(normalizedConfig);\n const oauth = createOAuthOperations(context);\n return {\n signIn: createSignInOperation(context),\n signUp: createSignUpOperation(context),\n signOut: createSignOutOperation(context),\n getSession: createGetSessionOperation(context),\n refreshSession: createRefreshSessionOperation(context),\n requestPasswordReset: createPasswordResetOperation(context),\n verifyOtp: createVerifyOtpOperation(context),\n startAuthorization: oauth.startAuthorization,\n completeAuthorization: oauth.completeAuthorization,\n };\n}\n\n/*** Create OAuth port operations from the configured Supabase provider set. */\nfunction createOAuthOperations(context: AuthOperationContext) {\n const { config } = context;\n if (config.oauthProviders.length === 0) return createDisabledOAuthOperations();\n const profileVerifier = createProfileVerifier(config);\n return createSupabaseOAuthAdapter({\n url: config.url,\n anonKey: config.anonKey,\n fetch: config.fetch,\n storage: requireOAuthStorage(config),\n storageKey: config.storageKey,\n providers: config.oauthProviders,\n randomBytes: config.oauthRandomBytes,\n persistSession: createOAuthSessionPersistence(context),\n ...(profileVerifier === undefined ? {} : { verifyProfile: profileVerifier }),\n ...(config.onOAuthLifecycleEvent === undefined\n ? {}\n : { onLifecycleEvent: config.onOAuthLifecycleEvent }),\n });\n}\n\n/*** Return explicit unsupported OAuth results when no OAuth provider was configured. */\nfunction createDisabledOAuthOperations(): Pick<\n AuthPort,\n 'startAuthorization' | 'completeAuthorization'\n> {\n return {\n startAuthorization: (input) =>\n Promise.resolve(\n oauthStartError(\n 'provider_misconfigured',\n 'No OAuth providers are configured for this Supabase Auth adapter.',\n input.provider,\n false,\n ),\n ),\n completeAuthorization: () =>\n Promise.resolve(\n oauthCompletionError(\n 'authorization_attempt_not_found',\n 'callback',\n 'No OAuth authorization attempt can exist without a configured OAuth provider.',\n undefined,\n false,\n ),\n ),\n };\n}\n\nfunction createOAuthSessionPersistence(context: AuthOperationContext) {\n return async (session: AuthSession): Promise<void> => {\n const error = await context.persistSessionSafely(session);\n if (error !== null) throw new Error(error.message);\n };\n}\n\nfunction createProfileVerifier(\n config: RequiredSupabaseAuthConfig,\n): SupabaseOAuthProfileVerifier | undefined {\n if (config.profileVerification === undefined) return undefined;\n return createSupabaseOAuthProfileVerifier({\n url: config.url,\n anonKey: config.anonKey,\n fetch: config.fetch,\n config: config.profileVerification,\n });\n}\n\nfunction requireOAuthStorage(config: RequiredSupabaseAuthConfig) {\n if (config.storage === undefined) {\n throw new TypeError('Supabase OAuth PKCE requires persistent auth storage.');\n }\n return config.storage;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { AuthAdapterError } from '@ankhorage/contracts/auth';
2
+ import type { SupabaseAuthErrorCode } from './types.js';
3
+ export declare function createAuthError(code: SupabaseAuthErrorCode, message: string, cause?: unknown): AuthAdapterError;
4
+ export declare function mapNetworkError(cause: unknown): AuthAdapterError;
5
+ export declare function mapSupabaseError(response: Response, body: unknown): AuthAdapterError;
6
+ export declare function readResponseBody(response: Response): Promise<unknown>;
7
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAElE,OAAO,KAAK,EAAE,qBAAqB,EAA8B,MAAM,YAAY,CAAC;AAEpF,wBAAgB,eAAe,CAC7B,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO,GACd,gBAAgB,CAElB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAEhE;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,GAAG,gBAAgB,CA6BpF;AA2BD,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAsB3E"}
package/dist/errors.js ADDED
@@ -0,0 +1,89 @@
1
+ export function createAuthError(code, message, cause) {
2
+ return cause === undefined ? { code, message } : { code, message, cause };
3
+ }
4
+ export function mapNetworkError(cause) {
5
+ return createAuthError('network_error', 'Unable to reach Supabase Auth.', cause);
6
+ }
7
+ export function mapSupabaseError(response, body) {
8
+ const message = extractErrorMessage(body) ?? response.statusText;
9
+ const normalizedMessage = message.toLowerCase();
10
+ const cause = {
11
+ status: response.status,
12
+ body,
13
+ };
14
+ if (isExpiredAccessSession(response.status, normalizedMessage)) {
15
+ return createAuthError('session_expired', 'The auth session expired.', cause);
16
+ }
17
+ if (isInvalidCredentialMessage(normalizedMessage)) {
18
+ return createAuthError('invalid_credentials', 'Invalid credentials.', cause);
19
+ }
20
+ if (isExpiredRefreshSession(normalizedMessage)) {
21
+ return createAuthError('session_expired', 'The auth session expired.', cause);
22
+ }
23
+ if (response.status === 400 || response.status === 422) {
24
+ return createAuthError('validation_error', message || 'Supabase rejected the auth request.', cause);
25
+ }
26
+ return createAuthError('provider_error', message || 'Supabase Auth returned an error.', cause);
27
+ }
28
+ function isExpiredAccessSession(status, message) {
29
+ return status === 401 && includesAny(message, 'jwt', 'session', 'token');
30
+ }
31
+ function isInvalidCredentialMessage(message) {
32
+ return includesAny(message, 'invalid login credentials', 'invalid credentials', 'invalid_credentials', 'invalid_grant');
33
+ }
34
+ function isExpiredRefreshSession(message) {
35
+ return (includesAny(message, 'refresh token', 'refresh_token') &&
36
+ includesAny(message, 'invalid', 'expired', 'not found', 'not_found'));
37
+ }
38
+ function includesAny(value, ...needles) {
39
+ return needles.some((needle) => value.includes(needle));
40
+ }
41
+ export async function readResponseBody(response) {
42
+ const contentType = response.headers.get('content-type') ?? '';
43
+ if (contentType.includes('application/json')) {
44
+ try {
45
+ return await response.json();
46
+ }
47
+ catch (error) {
48
+ return {
49
+ message: 'Unable to parse Supabase Auth JSON response.',
50
+ cause: error,
51
+ };
52
+ }
53
+ }
54
+ try {
55
+ return await response.text();
56
+ }
57
+ catch (error) {
58
+ return {
59
+ message: 'Unable to read Supabase Auth response.',
60
+ cause: error,
61
+ };
62
+ }
63
+ }
64
+ function extractErrorMessage(body) {
65
+ if (typeof body === 'string') {
66
+ return body;
67
+ }
68
+ if (!isRecord(body)) {
69
+ return undefined;
70
+ }
71
+ const candidates = [
72
+ body.message,
73
+ body.msg,
74
+ body.error_description,
75
+ body.error,
76
+ body.error_code,
77
+ body.code,
78
+ ];
79
+ for (const candidate of candidates) {
80
+ if (typeof candidate === 'string' && candidate.length > 0) {
81
+ return candidate;
82
+ }
83
+ }
84
+ return undefined;
85
+ }
86
+ function isRecord(value) {
87
+ return typeof value === 'object' && value !== null;
88
+ }
89
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthAdapterError } from '@ankhorage/contracts/auth';\n\nimport type { SupabaseAuthErrorCode, SupabaseProviderErrorCause } from './types.js';\n\nexport function createAuthError(\n code: SupabaseAuthErrorCode,\n message: string,\n cause?: unknown,\n): AuthAdapterError {\n return cause === undefined ? { code, message } : { code, message, cause };\n}\n\nexport function mapNetworkError(cause: unknown): AuthAdapterError {\n return createAuthError('network_error', 'Unable to reach Supabase Auth.', cause);\n}\n\nexport function mapSupabaseError(response: Response, body: unknown): AuthAdapterError {\n const message = extractErrorMessage(body) ?? response.statusText;\n const normalizedMessage = message.toLowerCase();\n const cause: SupabaseProviderErrorCause = {\n status: response.status,\n body,\n };\n\n if (isExpiredAccessSession(response.status, normalizedMessage)) {\n return createAuthError('session_expired', 'The auth session expired.', cause);\n }\n\n if (isInvalidCredentialMessage(normalizedMessage)) {\n return createAuthError('invalid_credentials', 'Invalid credentials.', cause);\n }\n\n if (isExpiredRefreshSession(normalizedMessage)) {\n return createAuthError('session_expired', 'The auth session expired.', cause);\n }\n\n if (response.status === 400 || response.status === 422) {\n return createAuthError(\n 'validation_error',\n message || 'Supabase rejected the auth request.',\n cause,\n );\n }\n\n return createAuthError('provider_error', message || 'Supabase Auth returned an error.', cause);\n}\n\nfunction isExpiredAccessSession(status: number, message: string): boolean {\n return status === 401 && includesAny(message, 'jwt', 'session', 'token');\n}\n\nfunction isInvalidCredentialMessage(message: string): boolean {\n return includesAny(\n message,\n 'invalid login credentials',\n 'invalid credentials',\n 'invalid_credentials',\n 'invalid_grant',\n );\n}\n\nfunction isExpiredRefreshSession(message: string): boolean {\n return (\n includesAny(message, 'refresh token', 'refresh_token') &&\n includesAny(message, 'invalid', 'expired', 'not found', 'not_found')\n );\n}\n\nfunction includesAny(value: string, ...needles: readonly string[]): boolean {\n return needles.some((needle) => value.includes(needle));\n}\n\nexport async function readResponseBody(response: Response): Promise<unknown> {\n const contentType = response.headers.get('content-type') ?? '';\n\n if (contentType.includes('application/json')) {\n try {\n return await response.json();\n } catch (error) {\n return {\n message: 'Unable to parse Supabase Auth JSON response.',\n cause: error,\n };\n }\n }\n\n try {\n return await response.text();\n } catch (error) {\n return {\n message: 'Unable to read Supabase Auth response.',\n cause: error,\n };\n }\n}\n\nfunction extractErrorMessage(body: unknown): string | undefined {\n if (typeof body === 'string') {\n return body;\n }\n\n if (!isRecord(body)) {\n return undefined;\n }\n\n const candidates = [\n body.message,\n body.msg,\n body.error_description,\n body.error,\n body.error_code,\n body.code,\n ];\n\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.length > 0) {\n return candidate;\n }\n }\n\n return undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n"]}
@@ -0,0 +1,8 @@
1
+ export { createSupabaseAuthAdapter } from './createSupabaseAuthAdapter.js';
2
+ export type { SupabaseOAuthProviderDefinition, SupabaseOAuthProviderId, SupabaseOAuthRuntimeEnvironmentDefinition, SupabaseOAuthSecretFieldDefinition, SupabaseOAuthSecretPayload, } from './oauthProviderDefinitions.js';
3
+ export { getSupabaseOAuthProviderDefinition, isSupabaseOAuthProviderId, materializeSupabaseOAuthEnvironment, SUPABASE_OAUTH_PROVIDER_DEFINITIONS, SUPABASE_OAUTH_PROVIDER_IDS, validateSupabaseOAuthSecretPayload, } from './oauthProviderDefinitions.js';
4
+ export { resolveSupabaseOAuthSetupPlan, SUPABASE_OAUTH_SETUP_CAPABILITIES, } from './oauthSetupRequirements.js';
5
+ export { createSupabaseOAuthProfileVerifier, normalizeSupabaseAuthProfileVerificationConfig, verifySupabaseOAuthProfile, } from './profileVerification.js';
6
+ export type { SupabaseAuthConfig, SupabaseAuthErrorCode, SupabaseAuthProfileField, SupabaseAuthProfileVerificationConfig, SupabaseAuthRandomBytes, SupabaseAuthStorage, SupabaseOAuthLifecycleEvent, SupabaseOAuthLifecycleObserver, SupabaseOAuthLifecycleStatus, SupabaseOAuthProfileVerificationInput, SupabaseOAuthProfileVerificationResult, SupabaseOAuthProfileVerifier, SupabaseProviderErrorCause, } from './types.js';
7
+ export { SUPABASE_AUTH_PROFILE_FIELDS } from './types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,YAAY,EACV,+BAA+B,EAC/B,uBAAuB,EACvB,yCAAyC,EACzC,kCAAkC,EAClC,0BAA0B,GAC3B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,kCAAkC,EAClC,yBAAyB,EACzB,mCAAmC,EACnC,mCAAmC,EACnC,2BAA2B,EAC3B,kCAAkC,GACnC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,6BAA6B,EAC7B,iCAAiC,GAClC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,kCAAkC,EAClC,8CAA8C,EAC9C,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,wBAAwB,EACxB,qCAAqC,EACrC,uBAAuB,EACvB,mBAAmB,EACnB,2BAA2B,EAC3B,8BAA8B,EAC9B,4BAA4B,EAC5B,qCAAqC,EACrC,sCAAsC,EACtC,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { createSupabaseAuthAdapter } from './createSupabaseAuthAdapter.js';
2
+ export { getSupabaseOAuthProviderDefinition, isSupabaseOAuthProviderId, materializeSupabaseOAuthEnvironment, SUPABASE_OAUTH_PROVIDER_DEFINITIONS, SUPABASE_OAUTH_PROVIDER_IDS, validateSupabaseOAuthSecretPayload, } from './oauthProviderDefinitions.js';
3
+ export { resolveSupabaseOAuthSetupPlan, SUPABASE_OAUTH_SETUP_CAPABILITIES, } from './oauthSetupRequirements.js';
4
+ export { createSupabaseOAuthProfileVerifier, normalizeSupabaseAuthProfileVerificationConfig, verifySupabaseOAuthProfile, } from './profileVerification.js';
5
+ export { SUPABASE_AUTH_PROFILE_FIELDS } from './types.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAQ3E,OAAO,EACL,kCAAkC,EAClC,yBAAyB,EACzB,mCAAmC,EACnC,mCAAmC,EACnC,2BAA2B,EAC3B,kCAAkC,GACnC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,6BAA6B,EAC7B,iCAAiC,GAClC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,kCAAkC,EAClC,8CAA8C,EAC9C,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAgBlC,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { createSupabaseAuthAdapter } from './createSupabaseAuthAdapter.js';\nexport type {\n SupabaseOAuthProviderDefinition,\n SupabaseOAuthProviderId,\n SupabaseOAuthRuntimeEnvironmentDefinition,\n SupabaseOAuthSecretFieldDefinition,\n SupabaseOAuthSecretPayload,\n} from './oauthProviderDefinitions.js';\nexport {\n getSupabaseOAuthProviderDefinition,\n isSupabaseOAuthProviderId,\n materializeSupabaseOAuthEnvironment,\n SUPABASE_OAUTH_PROVIDER_DEFINITIONS,\n SUPABASE_OAUTH_PROVIDER_IDS,\n validateSupabaseOAuthSecretPayload,\n} from './oauthProviderDefinitions.js';\nexport {\n resolveSupabaseOAuthSetupPlan,\n SUPABASE_OAUTH_SETUP_CAPABILITIES,\n} from './oauthSetupRequirements.js';\nexport {\n createSupabaseOAuthProfileVerifier,\n normalizeSupabaseAuthProfileVerificationConfig,\n verifySupabaseOAuthProfile,\n} from './profileVerification.js';\nexport type {\n SupabaseAuthConfig,\n SupabaseAuthErrorCode,\n SupabaseAuthProfileField,\n SupabaseAuthProfileVerificationConfig,\n SupabaseAuthRandomBytes,\n SupabaseAuthStorage,\n SupabaseOAuthLifecycleEvent,\n SupabaseOAuthLifecycleObserver,\n SupabaseOAuthLifecycleStatus,\n SupabaseOAuthProfileVerificationInput,\n SupabaseOAuthProfileVerificationResult,\n SupabaseOAuthProfileVerifier,\n SupabaseProviderErrorCause,\n} from './types.js';\nexport { SUPABASE_AUTH_PROFILE_FIELDS } from './types.js';\n"]}
@@ -0,0 +1,5 @@
1
+ import type { AuthPort } from '@ankhorage/auth';
2
+ import type { CreateSupabaseOAuthAdapterInput } from './oauthTypes.js';
3
+ /*** Create the Supabase implementations of the neutral OAuth port operations. */
4
+ export declare function createSupabaseOAuthAdapter(input: CreateSupabaseOAuthAdapterInput): Pick<AuthPort, 'startAuthorization' | 'completeAuthorization'>;
5
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAShD,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAC;AAEvE,iFAAiF;AACjF,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,+BAA+B,GACrC,IAAI,CAAC,QAAQ,EAAE,oBAAoB,GAAG,uBAAuB,CAAC,CAQhE"}
package/dist/oauth.js ADDED
@@ -0,0 +1,34 @@
1
+ import { completeSupabaseOAuthAuthorization } from './oauthComplete.js';
2
+ import { createOAuthRuntimeContext } from './oauthRuntime.js';
3
+ import { startSupabaseOAuthAuthorization } from './oauthStart.js';
4
+ /*** Create the Supabase implementations of the neutral OAuth port operations. */
5
+ export function createSupabaseOAuthAdapter(input) {
6
+ const context = createOAuthRuntimeContext(input);
7
+ const completeAuthorization = createCompletionRunner(context);
8
+ return {
9
+ startAuthorization: (authorizationInput) => startSupabaseOAuthAuthorization(context, authorizationInput),
10
+ completeAuthorization,
11
+ };
12
+ }
13
+ /*** Serializes completion attempts so the same callback cannot be consumed concurrently. */
14
+ function createCompletionRunner(context) {
15
+ let activeCompletion = null;
16
+ return async (input) => {
17
+ while (activeCompletion !== null)
18
+ await activeCompletion;
19
+ let releaseCompletion;
20
+ const completionLock = new Promise((resolve) => {
21
+ releaseCompletion = resolve;
22
+ });
23
+ activeCompletion = completionLock;
24
+ try {
25
+ return await completeSupabaseOAuthAuthorization(context, input);
26
+ }
27
+ finally {
28
+ if (activeCompletion === completionLock)
29
+ activeCompletion = null;
30
+ releaseCompletion?.();
31
+ }
32
+ };
33
+ }
34
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,kCAAkC,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAC;AAGlE,iFAAiF;AACjF,MAAM,UAAU,0BAA0B,CACxC,KAAsC;IAEtC,MAAM,OAAO,GAAG,yBAAyB,CAAC,KAAK,CAAC,CAAC;IACjD,MAAM,qBAAqB,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAC9D,OAAO;QACL,kBAAkB,EAAE,CAAC,kBAAkB,EAAE,EAAE,CACzC,+BAA+B,CAAC,OAAO,EAAE,kBAAkB,CAAC;QAC9D,qBAAqB;KACtB,CAAC;AACJ,CAAC;AAED,4FAA4F;AAC5F,SAAS,sBAAsB,CAC7B,OAAqD;IAErD,IAAI,gBAAgB,GAAyB,IAAI,CAAC;IAClD,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;QACrB,OAAO,gBAAgB,KAAK,IAAI;YAAE,MAAM,gBAAgB,CAAC;QACzD,IAAI,iBAA2C,CAAC;QAChD,MAAM,cAAc,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnD,iBAAiB,GAAG,OAAO,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,gBAAgB,GAAG,cAAc,CAAC;QAClC,IAAI,CAAC;YACH,OAAO,MAAM,kCAAkC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAClE,CAAC;gBAAS,CAAC;YACT,IAAI,gBAAgB,KAAK,cAAc;gBAAE,gBAAgB,GAAG,IAAI,CAAC;YACjE,iBAAiB,EAAE,EAAE,CAAC;QACxB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { AuthPort } from '@ankhorage/auth';\nimport type {\n AuthOAuthCompletionResult,\n CompleteOAuthAuthorizationInput,\n} from '@ankhorage/contracts/auth';\n\nimport { completeSupabaseOAuthAuthorization } from './oauthComplete.js';\nimport { createOAuthRuntimeContext } from './oauthRuntime.js';\nimport { startSupabaseOAuthAuthorization } from './oauthStart.js';\nimport type { CreateSupabaseOAuthAdapterInput } from './oauthTypes.js';\n\n/*** Create the Supabase implementations of the neutral OAuth port operations. */\nexport function createSupabaseOAuthAdapter(\n input: CreateSupabaseOAuthAdapterInput,\n): Pick<AuthPort, 'startAuthorization' | 'completeAuthorization'> {\n const context = createOAuthRuntimeContext(input);\n const completeAuthorization = createCompletionRunner(context);\n return {\n startAuthorization: (authorizationInput) =>\n startSupabaseOAuthAuthorization(context, authorizationInput),\n completeAuthorization,\n };\n}\n\n/*** Serializes completion attempts so the same callback cannot be consumed concurrently. */\nfunction createCompletionRunner(\n context: ReturnType<typeof createOAuthRuntimeContext>,\n): (input: CompleteOAuthAuthorizationInput) => Promise<AuthOAuthCompletionResult> {\n let activeCompletion: Promise<void> | null = null;\n return async (input) => {\n while (activeCompletion !== null) await activeCompletion;\n let releaseCompletion: (() => void) | undefined;\n const completionLock = new Promise<void>((resolve) => {\n releaseCompletion = resolve;\n });\n activeCompletion = completionLock;\n try {\n return await completeSupabaseOAuthAuthorization(context, input);\n } finally {\n if (activeCompletion === completionLock) activeCompletion = null;\n releaseCompletion?.();\n }\n };\n}\n"]}
@@ -0,0 +1,9 @@
1
+ interface OAuthAttemptIdRuntime {
2
+ readonly randomUuid?: () => string;
3
+ readonly fillRandomBytes?: (bytes: Uint8Array) => void;
4
+ readonly now: () => number;
5
+ readonly random: () => number;
6
+ }
7
+ export declare function createOAuthAttemptId(runtime?: OAuthAttemptIdRuntime): string;
8
+ export {};
9
+ //# sourceMappingURL=oauthAttemptId.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthAttemptId.d.ts","sourceRoot":"","sources":["../src/oauthAttemptId.ts"],"names":[],"mappings":"AAMA,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC;IACnC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC/B;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,qBAAsD,GAC9D,MAAM,CAeR"}
@@ -0,0 +1,51 @@
1
+ const SECURE_RANDOM_BYTE_COUNT = 16;
2
+ const FALLBACK_RANDOM_CHUNK_COUNT = 4;
3
+ const RANDOM_CHUNK_RANGE = 0x1_0000_0000;
4
+ let fallbackSequence = 0;
5
+ export function createOAuthAttemptId(runtime = resolveOAuthAttemptIdRuntime()) {
6
+ if (runtime.randomUuid !== undefined)
7
+ return runtime.randomUuid();
8
+ if (runtime.fillRandomBytes !== undefined) {
9
+ const bytes = new Uint8Array(SECURE_RANDOM_BYTE_COUNT);
10
+ runtime.fillRandomBytes(bytes);
11
+ const value = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
12
+ return `oauth-${value}`;
13
+ }
14
+ fallbackSequence = (fallbackSequence + 1) % Number.MAX_SAFE_INTEGER;
15
+ const randomPart = Array.from({ length: FALLBACK_RANDOM_CHUNK_COUNT }, () => createFallbackRandomChunk(runtime.random)).join('');
16
+ return `oauth-${runtime.now().toString(36)}-${fallbackSequence.toString(36)}-${randomPart}`;
17
+ }
18
+ function resolveOAuthAttemptIdRuntime() {
19
+ const cryptoValue = Reflect.get(globalThis, 'crypto');
20
+ if (typeof cryptoValue !== 'object' || cryptoValue === null) {
21
+ return { now: Date.now, random: Math.random };
22
+ }
23
+ const randomUuidValue = Reflect.get(cryptoValue, 'randomUUID');
24
+ const randomValuesValue = Reflect.get(cryptoValue, 'getRandomValues');
25
+ const randomUuid = typeof randomUuidValue === 'function'
26
+ ? () => {
27
+ const value = Reflect.apply(randomUuidValue, cryptoValue, []);
28
+ if (typeof value !== 'string') {
29
+ throw new TypeError('crypto.randomUUID() must return a string.');
30
+ }
31
+ return value;
32
+ }
33
+ : undefined;
34
+ const fillRandomBytes = typeof randomValuesValue === 'function'
35
+ ? (bytes) => {
36
+ Reflect.apply(randomValuesValue, cryptoValue, [bytes]);
37
+ }
38
+ : undefined;
39
+ return {
40
+ ...(randomUuid === undefined ? {} : { randomUuid }),
41
+ ...(fillRandomBytes === undefined ? {} : { fillRandomBytes }),
42
+ now: Date.now,
43
+ random: Math.random,
44
+ };
45
+ }
46
+ function createFallbackRandomChunk(random) {
47
+ return Math.floor(random() * RANDOM_CHUNK_RANGE)
48
+ .toString(36)
49
+ .padStart(7, '0');
50
+ }
51
+ //# sourceMappingURL=oauthAttemptId.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthAttemptId.js","sourceRoot":"","sources":["../src/oauthAttemptId.ts"],"names":[],"mappings":"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","sourcesContent":["const SECURE_RANDOM_BYTE_COUNT = 16;\nconst FALLBACK_RANDOM_CHUNK_COUNT = 4;\nconst RANDOM_CHUNK_RANGE = 0x1_0000_0000;\n\nlet fallbackSequence = 0;\n\ninterface OAuthAttemptIdRuntime {\n readonly randomUuid?: () => string;\n readonly fillRandomBytes?: (bytes: Uint8Array) => void;\n readonly now: () => number;\n readonly random: () => number;\n}\n\nexport function createOAuthAttemptId(\n runtime: OAuthAttemptIdRuntime = resolveOAuthAttemptIdRuntime(),\n): string {\n if (runtime.randomUuid !== undefined) return runtime.randomUuid();\n\n if (runtime.fillRandomBytes !== undefined) {\n const bytes = new Uint8Array(SECURE_RANDOM_BYTE_COUNT);\n runtime.fillRandomBytes(bytes);\n const value = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');\n return `oauth-${value}`;\n }\n\n fallbackSequence = (fallbackSequence + 1) % Number.MAX_SAFE_INTEGER;\n const randomPart = Array.from({ length: FALLBACK_RANDOM_CHUNK_COUNT }, () =>\n createFallbackRandomChunk(runtime.random),\n ).join('');\n return `oauth-${runtime.now().toString(36)}-${fallbackSequence.toString(36)}-${randomPart}`;\n}\n\nfunction resolveOAuthAttemptIdRuntime(): OAuthAttemptIdRuntime {\n const cryptoValue: unknown = Reflect.get(globalThis, 'crypto');\n if (typeof cryptoValue !== 'object' || cryptoValue === null) {\n return { now: Date.now, random: Math.random };\n }\n\n const randomUuidValue: unknown = Reflect.get(cryptoValue, 'randomUUID');\n const randomValuesValue: unknown = Reflect.get(cryptoValue, 'getRandomValues');\n const randomUuid =\n typeof randomUuidValue === 'function'\n ? () => {\n const value: unknown = Reflect.apply(randomUuidValue, cryptoValue, []);\n if (typeof value !== 'string') {\n throw new TypeError('crypto.randomUUID() must return a string.');\n }\n return value;\n }\n : undefined;\n const fillRandomBytes =\n typeof randomValuesValue === 'function'\n ? (bytes: Uint8Array) => {\n Reflect.apply(randomValuesValue, cryptoValue, [bytes]);\n }\n : undefined;\n\n return {\n ...(randomUuid === undefined ? {} : { randomUuid }),\n ...(fillRandomBytes === undefined ? {} : { fillRandomBytes }),\n now: Date.now,\n random: Math.random,\n };\n}\n\nfunction createFallbackRandomChunk(random: () => number): string {\n return Math.floor(random() * RANDOM_CHUNK_RANGE)\n .toString(36)\n .padStart(7, '0');\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import type { AuthOAuthCompletionResult, CompleteOAuthAuthorizationInput } from '@ankhorage/contracts/auth';
2
+ import type { SupabaseOAuthProviderId } from './oauthProviderDefinitions.js';
3
+ import type { StoredOAuthAttempt } from './oauthTypes.js';
4
+ type ParsedCallback = {
5
+ type: 'code';
6
+ code: string;
7
+ } | {
8
+ type: 'cancelled';
9
+ } | {
10
+ type: 'error';
11
+ result: AuthOAuthCompletionResult;
12
+ };
13
+ /*** Validates an OAuth replay against the callback fingerprint stored for its attempt. */
14
+ export declare function completeOAuthReplay(input: CompleteOAuthAuthorizationInput, attempt: StoredOAuthAttempt): AuthOAuthCompletionResult;
15
+ /*** Parses and validates the provider callback against the stored redirect URI. */
16
+ export declare function parseCallback(rawCallbackUrl: string, attempt: StoredOAuthAttempt): ParsedCallback;
17
+ /*** Creates a safe invalid-callback completion result. */
18
+ export declare function invalidCallback(provider: SupabaseOAuthProviderId, message: string): {
19
+ type: 'error';
20
+ result: AuthOAuthCompletionResult;
21
+ };
22
+ export {};
23
+ //# sourceMappingURL=oauthCallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthCallback.d.ts","sourceRoot":"","sources":["../src/oauthCallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EACzB,+BAA+B,EAChC,MAAM,2BAA2B,CAAC;AAGnC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAE7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAI1D,KAAK,cAAc,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,CAAC;AAEzD,0FAA0F;AAC1F,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,+BAA+B,EACtC,OAAO,EAAE,kBAAkB,GAC1B,yBAAyB,CAkB3B;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,cAAc,CAmBjG;AAED,0DAA0D;AAC1D,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,uBAAuB,EACjC,OAAO,EAAE,MAAM,GACd;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,CAKtD"}
@@ -0,0 +1,81 @@
1
+ import { oauthCompletionError } from './oauthErrors.js';
2
+ import { constantTimeEqual, fingerprintOAuthCallback } from './oauthStorage.js';
3
+ const CALLBACK_PARAMS = new Set(['code', 'error', 'error_code', 'error_description']);
4
+ /*** Validates an OAuth replay against the callback fingerprint stored for its attempt. */
5
+ export function completeOAuthReplay(input, attempt) {
6
+ if (input.response.type !== 'callback')
7
+ return replayMismatch(attempt.provider, 'response');
8
+ const callback = parseCallback(input.response.url, attempt);
9
+ if (callback.type !== 'code')
10
+ return replayMismatch(attempt.provider, 'callback');
11
+ const fingerprint = fingerprintOAuthCallback(attempt.id, callback.code);
12
+ if (attempt.callbackFingerprint === undefined ||
13
+ !constantTimeEqual(fingerprint, attempt.callbackFingerprint)) {
14
+ return replayMismatch(attempt.provider, 'callback');
15
+ }
16
+ return oauthCompletionError('callback_already_completed', 'callback', 'The OAuth authorization callback was already handled.', attempt.provider, false);
17
+ }
18
+ /*** Parses and validates the provider callback against the stored redirect URI. */
19
+ export function parseCallback(rawCallbackUrl, attempt) {
20
+ const locations = parseCallbackLocations(rawCallbackUrl, attempt.redirectUri);
21
+ if (locations === null)
22
+ return invalidCallback(attempt.provider, 'The OAuth callback URL is invalid.');
23
+ if (!sameCallbackLocation(locations.callback, locations.redirect) ||
24
+ locations.callback.hash.length > 0) {
25
+ return invalidCallback(attempt.provider, 'The OAuth callback does not match the expected redirect URI.');
26
+ }
27
+ const unexpected = [...locations.callback.searchParams.keys()].find((key) => !CALLBACK_PARAMS.has(key));
28
+ if (unexpected !== undefined)
29
+ return invalidCallback(attempt.provider, 'The OAuth callback contains unexpected parameters.');
30
+ return classifyCallback(locations.callback.searchParams, attempt.provider);
31
+ }
32
+ /*** Creates a safe invalid-callback completion result. */
33
+ export function invalidCallback(provider, message) {
34
+ return {
35
+ type: 'error',
36
+ result: oauthCompletionError('invalid_callback', 'callback', message, provider, true),
37
+ };
38
+ }
39
+ function classifyCallback(parameters, provider) {
40
+ const codes = parameters.getAll('code');
41
+ const errors = [...parameters.getAll('error'), ...parameters.getAll('error_code')].filter((value) => value.length > 0);
42
+ if (parameters.getAll('error_description').length > 0 && errors.length === 0) {
43
+ return invalidCallback(provider, 'The OAuth callback contains an error description without an error.');
44
+ }
45
+ if (codes.length > 0 && errors.length > 0) {
46
+ return invalidCallback(provider, 'The OAuth callback contains both a code and an error.');
47
+ }
48
+ if (errors.length > 0)
49
+ return classifyProviderError(errors, provider);
50
+ const code = codes.at(0);
51
+ return codes.length === 1 && code !== undefined && code.trim().length > 0
52
+ ? { type: 'code', code }
53
+ : invalidCallback(provider, 'The OAuth callback does not contain one authorization code.');
54
+ }
55
+ function classifyProviderError(errors, provider) {
56
+ if (errors.length === 1 && errors.at(0) === 'access_denied')
57
+ return { type: 'cancelled' };
58
+ return {
59
+ type: 'error',
60
+ result: oauthCompletionError('authorization_failed', 'callback', 'The OAuth provider rejected the authorization request.', provider, true),
61
+ };
62
+ }
63
+ function parseCallbackLocations(rawCallbackUrl, rawRedirectUri) {
64
+ try {
65
+ return { callback: new URL(rawCallbackUrl), redirect: new URL(rawRedirectUri) };
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ function sameCallbackLocation(callback, redirect) {
72
+ return (callback.protocol === redirect.protocol &&
73
+ callback.username === redirect.username &&
74
+ callback.password === redirect.password &&
75
+ callback.host === redirect.host &&
76
+ callback.pathname === redirect.pathname);
77
+ }
78
+ function replayMismatch(provider, subject) {
79
+ return invalidCallback(provider, `The OAuth ${subject} does not match the completed authorization callback.`).result;
80
+ }
81
+ //# sourceMappingURL=oauthCallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthCallback.js","sourceRoot":"","sources":["../src/oauthCallback.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AuthOAuthCompletionResult,\n CompleteOAuthAuthorizationInput,\n} from '@ankhorage/contracts/auth';\n\nimport { oauthCompletionError } from './oauthErrors.js';\nimport type { SupabaseOAuthProviderId } from './oauthProviderDefinitions.js';\nimport { constantTimeEqual, fingerprintOAuthCallback } from './oauthStorage.js';\nimport type { StoredOAuthAttempt } from './oauthTypes.js';\n\nconst CALLBACK_PARAMS = new Set(['code', 'error', 'error_code', 'error_description']);\n\ntype ParsedCallback =\n | { type: 'code'; code: string }\n | { type: 'cancelled' }\n | { type: 'error'; result: AuthOAuthCompletionResult };\n\n/*** Validates an OAuth replay against the callback fingerprint stored for its attempt. */\nexport function completeOAuthReplay(\n input: CompleteOAuthAuthorizationInput,\n attempt: StoredOAuthAttempt,\n): AuthOAuthCompletionResult {\n if (input.response.type !== 'callback') return replayMismatch(attempt.provider, 'response');\n const callback = parseCallback(input.response.url, attempt);\n if (callback.type !== 'code') return replayMismatch(attempt.provider, 'callback');\n const fingerprint = fingerprintOAuthCallback(attempt.id, callback.code);\n if (\n attempt.callbackFingerprint === undefined ||\n !constantTimeEqual(fingerprint, attempt.callbackFingerprint)\n ) {\n return replayMismatch(attempt.provider, 'callback');\n }\n return oauthCompletionError(\n 'callback_already_completed',\n 'callback',\n 'The OAuth authorization callback was already handled.',\n attempt.provider,\n false,\n );\n}\n\n/*** Parses and validates the provider callback against the stored redirect URI. */\nexport function parseCallback(rawCallbackUrl: string, attempt: StoredOAuthAttempt): ParsedCallback {\n const locations = parseCallbackLocations(rawCallbackUrl, attempt.redirectUri);\n if (locations === null)\n return invalidCallback(attempt.provider, 'The OAuth callback URL is invalid.');\n if (\n !sameCallbackLocation(locations.callback, locations.redirect) ||\n locations.callback.hash.length > 0\n ) {\n return invalidCallback(\n attempt.provider,\n 'The OAuth callback does not match the expected redirect URI.',\n );\n }\n const unexpected = [...locations.callback.searchParams.keys()].find(\n (key) => !CALLBACK_PARAMS.has(key),\n );\n if (unexpected !== undefined)\n return invalidCallback(attempt.provider, 'The OAuth callback contains unexpected parameters.');\n return classifyCallback(locations.callback.searchParams, attempt.provider);\n}\n\n/*** Creates a safe invalid-callback completion result. */\nexport function invalidCallback(\n provider: SupabaseOAuthProviderId,\n message: string,\n): { type: 'error'; result: AuthOAuthCompletionResult } {\n return {\n type: 'error',\n result: oauthCompletionError('invalid_callback', 'callback', message, provider, true),\n };\n}\n\nfunction classifyCallback(\n parameters: URLSearchParams,\n provider: SupabaseOAuthProviderId,\n): ParsedCallback {\n const codes = parameters.getAll('code');\n const errors = [...parameters.getAll('error'), ...parameters.getAll('error_code')].filter(\n (value) => value.length > 0,\n );\n if (parameters.getAll('error_description').length > 0 && errors.length === 0) {\n return invalidCallback(\n provider,\n 'The OAuth callback contains an error description without an error.',\n );\n }\n if (codes.length > 0 && errors.length > 0) {\n return invalidCallback(provider, 'The OAuth callback contains both a code and an error.');\n }\n if (errors.length > 0) return classifyProviderError(errors, provider);\n const code = codes.at(0);\n return codes.length === 1 && code !== undefined && code.trim().length > 0\n ? { type: 'code', code }\n : invalidCallback(provider, 'The OAuth callback does not contain one authorization code.');\n}\n\nfunction classifyProviderError(\n errors: readonly string[],\n provider: SupabaseOAuthProviderId,\n): ParsedCallback {\n if (errors.length === 1 && errors.at(0) === 'access_denied') return { type: 'cancelled' };\n return {\n type: 'error',\n result: oauthCompletionError(\n 'authorization_failed',\n 'callback',\n 'The OAuth provider rejected the authorization request.',\n provider,\n true,\n ),\n };\n}\n\nfunction parseCallbackLocations(\n rawCallbackUrl: string,\n rawRedirectUri: string,\n): { callback: URL; redirect: URL } | null {\n try {\n return { callback: new URL(rawCallbackUrl), redirect: new URL(rawRedirectUri) };\n } catch {\n return null;\n }\n}\n\nfunction sameCallbackLocation(callback: URL, redirect: URL): boolean {\n return (\n callback.protocol === redirect.protocol &&\n callback.username === redirect.username &&\n callback.password === redirect.password &&\n callback.host === redirect.host &&\n callback.pathname === redirect.pathname\n );\n}\n\nfunction replayMismatch(\n provider: SupabaseOAuthProviderId,\n subject: 'response' | 'callback',\n): AuthOAuthCompletionResult {\n return invalidCallback(\n provider,\n `The OAuth ${subject} does not match the completed authorization callback.`,\n ).result;\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import type { AuthOAuthCompletionResult, CompleteOAuthAuthorizationInput } from '@ankhorage/contracts/auth';
2
+ import type { OAuthRuntimeContext } from './oauthRuntime.js';
3
+ /*** Completes one Supabase OAuth authorization attempt. */
4
+ export declare function completeSupabaseOAuthAuthorization(context: OAuthRuntimeContext, input: CompleteOAuthAuthorizationInput): Promise<AuthOAuthCompletionResult>;
5
+ //# sourceMappingURL=oauthComplete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthComplete.d.ts","sourceRoot":"","sources":["../src/oauthComplete.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,yBAAyB,EACzB,+BAA+B,EAChC,MAAM,2BAA2B,CAAC;AAWnC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AA2B7D,2DAA2D;AAC3D,wBAAsB,kCAAkC,CACtD,OAAO,EAAE,mBAAmB,EAC5B,KAAK,EAAE,+BAA+B,GACrC,OAAO,CAAC,yBAAyB,CAAC,CAapC"}