@ankhorage/auth-supabase 0.0.0-stage → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +22 -2
- package/dist/authConfiguration.d.ts +15 -0
- package/dist/authConfiguration.d.ts.map +1 -0
- package/dist/authConfiguration.js +69 -0
- package/dist/authConfiguration.js.map +1 -0
- package/dist/authPasswordOperations.d.ts +10 -0
- package/dist/authPasswordOperations.d.ts.map +1 -0
- package/dist/authPasswordOperations.js +163 -0
- package/dist/authPasswordOperations.js.map +1 -0
- package/dist/authSessionContext.d.ts +19 -0
- package/dist/authSessionContext.d.ts.map +1 -0
- package/dist/authSessionContext.js +96 -0
- package/dist/authSessionContext.js.map +1 -0
- package/dist/authSessionOperations.d.ts +7 -0
- package/dist/authSessionOperations.d.ts.map +1 -0
- package/dist/authSessionOperations.js +79 -0
- package/dist/authSessionOperations.js.map +1 -0
- package/dist/createSupabaseAuthAdapter.d.ts +5 -0
- package/dist/createSupabaseAuthAdapter.d.ts.map +1 -0
- package/dist/createSupabaseAuthAdapter.js +76 -0
- package/dist/createSupabaseAuthAdapter.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +89 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +5 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +34 -0
- package/dist/oauth.js.map +1 -0
- package/dist/oauthAttemptId.d.ts +9 -0
- package/dist/oauthAttemptId.d.ts.map +1 -0
- package/dist/oauthAttemptId.js +51 -0
- package/dist/oauthAttemptId.js.map +1 -0
- package/dist/oauthCallback.d.ts +23 -0
- package/dist/oauthCallback.d.ts.map +1 -0
- package/dist/oauthCallback.js +81 -0
- package/dist/oauthCallback.js.map +1 -0
- package/dist/oauthComplete.d.ts +5 -0
- package/dist/oauthComplete.d.ts.map +1 -0
- package/dist/oauthComplete.js +133 -0
- package/dist/oauthComplete.js.map +1 -0
- package/dist/oauthCompletionState.d.ts +13 -0
- package/dist/oauthCompletionState.d.ts.map +1 -0
- package/dist/oauthCompletionState.js +25 -0
- package/dist/oauthCompletionState.js.map +1 -0
- package/dist/oauthErrors.d.ts +9 -0
- package/dist/oauthErrors.d.ts.map +1 -0
- package/dist/oauthErrors.js +87 -0
- package/dist/oauthErrors.js.map +1 -0
- package/dist/oauthExchange.d.ts +14 -0
- package/dist/oauthExchange.d.ts.map +1 -0
- package/dist/oauthExchange.js +100 -0
- package/dist/oauthExchange.js.map +1 -0
- package/dist/oauthLifecycle.d.ts +8 -0
- package/dist/oauthLifecycle.d.ts.map +1 -0
- package/dist/oauthLifecycle.js +24 -0
- package/dist/oauthLifecycle.js.map +1 -0
- package/dist/oauthProviderDefinitions.d.ts +77 -0
- package/dist/oauthProviderDefinitions.d.ts.map +1 -0
- package/dist/oauthProviderDefinitions.js +114 -0
- package/dist/oauthProviderDefinitions.js.map +1 -0
- package/dist/oauthRuntime.d.ts +23 -0
- package/dist/oauthRuntime.d.ts.map +1 -0
- package/dist/oauthRuntime.js +22 -0
- package/dist/oauthRuntime.js.map +1 -0
- package/dist/oauthSetupRequirements.d.ts +12 -0
- package/dist/oauthSetupRequirements.d.ts.map +1 -0
- package/dist/oauthSetupRequirements.js +39 -0
- package/dist/oauthSetupRequirements.js.map +1 -0
- package/dist/oauthStart.d.ts +4 -0
- package/dist/oauthStart.d.ts.map +1 -0
- package/dist/oauthStart.js +108 -0
- package/dist/oauthStart.js.map +1 -0
- package/dist/oauthStartSupport.d.ts +52 -0
- package/dist/oauthStartSupport.d.ts.map +1 -0
- package/dist/oauthStartSupport.js +137 -0
- package/dist/oauthStartSupport.js.map +1 -0
- package/dist/oauthStorage.d.ts +25 -0
- package/dist/oauthStorage.d.ts.map +1 -0
- package/dist/oauthStorage.js +146 -0
- package/dist/oauthStorage.js.map +1 -0
- package/dist/oauthTypes.d.ts +49 -0
- package/dist/oauthTypes.d.ts.map +1 -0
- package/dist/oauthTypes.js +6 -0
- package/dist/oauthTypes.js.map +1 -0
- package/dist/profileVerification.d.ts +24 -0
- package/dist/profileVerification.d.ts.map +1 -0
- package/dist/profileVerification.js +178 -0
- package/dist/profileVerification.js.map +1 -0
- package/dist/session.d.ts +6 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +124 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +70 -4
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export const SUPABASE_OAUTH_PROVIDER_IDS = ['google', 'apple'];
|
|
2
|
+
const SHARED_SECRET_FIELDS = [
|
|
3
|
+
{ name: 'clientId', label: 'Client ID', secret: false },
|
|
4
|
+
{ name: 'clientSecret', label: 'Client secret', secret: true },
|
|
5
|
+
];
|
|
6
|
+
export const SUPABASE_OAUTH_PROVIDER_DEFINITIONS = {
|
|
7
|
+
google: {
|
|
8
|
+
id: 'google',
|
|
9
|
+
label: 'Google',
|
|
10
|
+
defaultScopes: ['openid', 'email', 'profile'],
|
|
11
|
+
secretFields: SHARED_SECRET_FIELDS,
|
|
12
|
+
runtimeEnvironment: {
|
|
13
|
+
enabled: 'GOTRUE_EXTERNAL_GOOGLE_ENABLED',
|
|
14
|
+
clientId: 'GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID',
|
|
15
|
+
clientSecret: 'GOTRUE_EXTERNAL_GOOGLE_SECRET',
|
|
16
|
+
redirectUri: 'GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
apple: {
|
|
20
|
+
id: 'apple',
|
|
21
|
+
label: 'Apple',
|
|
22
|
+
defaultScopes: ['name', 'email'],
|
|
23
|
+
secretFields: SHARED_SECRET_FIELDS,
|
|
24
|
+
runtimeEnvironment: {
|
|
25
|
+
enabled: 'GOTRUE_EXTERNAL_APPLE_ENABLED',
|
|
26
|
+
clientId: 'GOTRUE_EXTERNAL_APPLE_CLIENT_ID',
|
|
27
|
+
clientSecret: 'GOTRUE_EXTERNAL_APPLE_SECRET',
|
|
28
|
+
redirectUri: 'GOTRUE_EXTERNAL_APPLE_REDIRECT_URI',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const SUPABASE_OAUTH_PROVIDER_DEFINITION_MAP = new Map(Object.entries(SUPABASE_OAUTH_PROVIDER_DEFINITIONS));
|
|
33
|
+
export function isSupabaseOAuthProviderId(provider) {
|
|
34
|
+
return SUPABASE_OAUTH_PROVIDER_IDS.includes(provider);
|
|
35
|
+
}
|
|
36
|
+
export function getSupabaseOAuthProviderDefinition(provider) {
|
|
37
|
+
return isSupabaseOAuthProviderId(provider)
|
|
38
|
+
? (SUPABASE_OAUTH_PROVIDER_DEFINITION_MAP.get(provider) ?? null)
|
|
39
|
+
: null;
|
|
40
|
+
}
|
|
41
|
+
export function validateSupabaseOAuthSecretPayload(provider, payload) {
|
|
42
|
+
const definition = getSupabaseOAuthProviderDefinition(provider);
|
|
43
|
+
if (definition === null) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
error: {
|
|
47
|
+
code: 'invalid_config',
|
|
48
|
+
message: `Supabase OAuth provider "${provider}" is not supported by the current provider registry.`,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const { clientId } = payload;
|
|
53
|
+
const { clientSecret } = payload;
|
|
54
|
+
const missingFields = definition.secretFields
|
|
55
|
+
.map((field) => field.name)
|
|
56
|
+
.filter((field) => {
|
|
57
|
+
const value = Reflect.get(payload, field);
|
|
58
|
+
return typeof value !== 'string' || value.trim().length === 0;
|
|
59
|
+
});
|
|
60
|
+
if (missingFields.length > 0 ||
|
|
61
|
+
typeof clientId !== 'string' ||
|
|
62
|
+
typeof clientSecret !== 'string') {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
error: {
|
|
66
|
+
code: 'invalid_payload',
|
|
67
|
+
message: `OAuth credentials for "${provider}" are missing required fields: ${missingFields.join(', ')}.`,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
ok: true,
|
|
73
|
+
data: {
|
|
74
|
+
...payload,
|
|
75
|
+
clientId,
|
|
76
|
+
clientSecret,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function materializeSupabaseOAuthEnvironment(input) {
|
|
81
|
+
const definition = getSupabaseOAuthProviderDefinition(input.provider);
|
|
82
|
+
if (definition === null) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
error: {
|
|
86
|
+
code: 'invalid_config',
|
|
87
|
+
message: `Supabase OAuth provider "${input.provider}" is not supported by the current provider registry.`,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const payloadResult = validateSupabaseOAuthSecretPayload(input.provider, input.payload);
|
|
92
|
+
if (!payloadResult.ok)
|
|
93
|
+
return payloadResult;
|
|
94
|
+
const redirectUri = input.redirectUri.trim();
|
|
95
|
+
if (redirectUri.length === 0) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
error: {
|
|
99
|
+
code: 'invalid_config',
|
|
100
|
+
message: `OAuth provider "${input.provider}" requires a redirect URI.`,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
ok: true,
|
|
106
|
+
data: {
|
|
107
|
+
[definition.runtimeEnvironment.enabled]: 'true',
|
|
108
|
+
[definition.runtimeEnvironment.clientId]: payloadResult.data.clientId,
|
|
109
|
+
[definition.runtimeEnvironment.clientSecret]: payloadResult.data.clientSecret,
|
|
110
|
+
[definition.runtimeEnvironment.redirectUri]: redirectUri,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=oauthProviderDefinitions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthProviderDefinitions.js","sourceRoot":"","sources":["../src/oauthProviderDefinitions.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthOAuthProviderId } from '@ankhorage/contracts/auth';\nimport type { SecretPayload, SecretStoreResult } from '@ankhorage/contracts/secrets';\n\nexport const SUPABASE_OAUTH_PROVIDER_IDS = ['google', 'apple'] as const;\nexport type SupabaseOAuthProviderId = (typeof SUPABASE_OAUTH_PROVIDER_IDS)[number];\n\nexport type SupabaseOAuthSecretPayload = SecretPayload & {\n readonly clientId: string;\n readonly clientSecret: string;\n};\n\nexport interface SupabaseOAuthSecretFieldDefinition {\n name: 'clientId' | 'clientSecret';\n label: string;\n secret: boolean;\n}\n\nexport interface SupabaseOAuthRuntimeEnvironmentDefinition {\n enabled: string;\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n}\n\nexport interface SupabaseOAuthProviderDefinition {\n id: SupabaseOAuthProviderId;\n label: string;\n defaultScopes: readonly string[];\n secretFields: readonly SupabaseOAuthSecretFieldDefinition[];\n runtimeEnvironment: SupabaseOAuthRuntimeEnvironmentDefinition;\n}\n\nconst SHARED_SECRET_FIELDS = [\n { name: 'clientId', label: 'Client ID', secret: false },\n { name: 'clientSecret', label: 'Client secret', secret: true },\n] as const satisfies readonly SupabaseOAuthSecretFieldDefinition[];\n\nexport const SUPABASE_OAUTH_PROVIDER_DEFINITIONS = {\n google: {\n id: 'google',\n label: 'Google',\n defaultScopes: ['openid', 'email', 'profile'],\n secretFields: SHARED_SECRET_FIELDS,\n runtimeEnvironment: {\n enabled: 'GOTRUE_EXTERNAL_GOOGLE_ENABLED',\n clientId: 'GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID',\n clientSecret: 'GOTRUE_EXTERNAL_GOOGLE_SECRET',\n redirectUri: 'GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI',\n },\n },\n apple: {\n id: 'apple',\n label: 'Apple',\n defaultScopes: ['name', 'email'],\n secretFields: SHARED_SECRET_FIELDS,\n runtimeEnvironment: {\n enabled: 'GOTRUE_EXTERNAL_APPLE_ENABLED',\n clientId: 'GOTRUE_EXTERNAL_APPLE_CLIENT_ID',\n clientSecret: 'GOTRUE_EXTERNAL_APPLE_SECRET',\n redirectUri: 'GOTRUE_EXTERNAL_APPLE_REDIRECT_URI',\n },\n },\n} as const satisfies Record<SupabaseOAuthProviderId, SupabaseOAuthProviderDefinition>;\n\nconst SUPABASE_OAUTH_PROVIDER_DEFINITION_MAP = new Map<\n SupabaseOAuthProviderId,\n SupabaseOAuthProviderDefinition\n>(\n Object.entries(SUPABASE_OAUTH_PROVIDER_DEFINITIONS) as [\n SupabaseOAuthProviderId,\n SupabaseOAuthProviderDefinition,\n ][],\n);\n\nexport function isSupabaseOAuthProviderId(\n provider: AuthOAuthProviderId,\n): provider is SupabaseOAuthProviderId {\n return (SUPABASE_OAUTH_PROVIDER_IDS as readonly string[]).includes(provider);\n}\n\nexport function getSupabaseOAuthProviderDefinition(\n provider: AuthOAuthProviderId,\n): SupabaseOAuthProviderDefinition | null {\n return isSupabaseOAuthProviderId(provider)\n ? (SUPABASE_OAUTH_PROVIDER_DEFINITION_MAP.get(provider) ?? null)\n : null;\n}\n\nexport function validateSupabaseOAuthSecretPayload(\n provider: AuthOAuthProviderId,\n payload: SecretPayload,\n): SecretStoreResult<SupabaseOAuthSecretPayload> {\n const definition = getSupabaseOAuthProviderDefinition(provider);\n if (definition === null) {\n return {\n ok: false,\n error: {\n code: 'invalid_config',\n message: `Supabase OAuth provider \"${provider}\" is not supported by the current provider registry.`,\n },\n };\n }\n\n const { clientId } = payload;\n const { clientSecret } = payload;\n const missingFields = definition.secretFields\n .map((field) => field.name)\n .filter((field) => {\n const value: unknown = Reflect.get(payload, field);\n return typeof value !== 'string' || value.trim().length === 0;\n });\n\n if (\n missingFields.length > 0 ||\n typeof clientId !== 'string' ||\n typeof clientSecret !== 'string'\n ) {\n return {\n ok: false,\n error: {\n code: 'invalid_payload',\n message: `OAuth credentials for \"${provider}\" are missing required fields: ${missingFields.join(', ')}.`,\n },\n };\n }\n\n return {\n ok: true,\n data: {\n ...payload,\n clientId,\n clientSecret,\n },\n };\n}\n\nexport function materializeSupabaseOAuthEnvironment(input: {\n provider: AuthOAuthProviderId;\n payload: SecretPayload;\n redirectUri: string;\n}): SecretStoreResult<Readonly<Record<string, string>>> {\n const definition = getSupabaseOAuthProviderDefinition(input.provider);\n if (definition === null) {\n return {\n ok: false,\n error: {\n code: 'invalid_config',\n message: `Supabase OAuth provider \"${input.provider}\" is not supported by the current provider registry.`,\n },\n };\n }\n\n const payloadResult = validateSupabaseOAuthSecretPayload(input.provider, input.payload);\n if (!payloadResult.ok) return payloadResult;\n\n const redirectUri = input.redirectUri.trim();\n if (redirectUri.length === 0) {\n return {\n ok: false,\n error: {\n code: 'invalid_config',\n message: `OAuth provider \"${input.provider}\" requires a redirect URI.`,\n },\n };\n }\n\n return {\n ok: true,\n data: {\n [definition.runtimeEnvironment.enabled]: 'true',\n [definition.runtimeEnvironment.clientId]: payloadResult.data.clientId,\n [definition.runtimeEnvironment.clientSecret]: payloadResult.data.clientSecret,\n [definition.runtimeEnvironment.redirectUri]: redirectUri,\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AuthOAuthStartResult } from '@ankhorage/contracts/auth';
|
|
2
|
+
import type { SupabaseOAuthProviderId } from './oauthProviderDefinitions.js';
|
|
3
|
+
import type { CreateSupabaseOAuthAdapterInput } from './oauthTypes.js';
|
|
4
|
+
export interface OAuthRuntimeContext extends CreateSupabaseOAuthAdapterInput {
|
|
5
|
+
readonly providers: [SupabaseOAuthProviderId, ...SupabaseOAuthProviderId[]];
|
|
6
|
+
readonly providerSet: ReadonlySet<SupabaseOAuthProviderId>;
|
|
7
|
+
readonly attemptStorageKey: string;
|
|
8
|
+
readonly consumedCallbacksStorageKey: string;
|
|
9
|
+
readonly codeVerifierStorageKey: string;
|
|
10
|
+
readonly now: () => number;
|
|
11
|
+
readonly attemptLifetimeMs: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function createOAuthRuntimeContext(input: CreateSupabaseOAuthAdapterInput): OAuthRuntimeContext;
|
|
14
|
+
export type OAuthStartPreparation = {
|
|
15
|
+
readonly ok: true;
|
|
16
|
+
readonly provider: SupabaseOAuthProviderId;
|
|
17
|
+
readonly redirectUri: string;
|
|
18
|
+
readonly queryParams: Readonly<Record<string, string>>;
|
|
19
|
+
} | {
|
|
20
|
+
readonly ok: false;
|
|
21
|
+
readonly result: AuthOAuthStartResult;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=oauthRuntime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthRuntime.d.ts","sourceRoot":"","sources":["../src/oauthRuntime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAE7E,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAC;AAEvE,MAAM,WAAW,mBAAoB,SAAQ,+BAA+B;IAC1E,QAAQ,CAAC,SAAS,EAAE,CAAC,uBAAuB,EAAE,GAAG,uBAAuB,EAAE,CAAC,CAAC;IAC5E,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,uBAAuB,CAAC,CAAC;IAC3D,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,2BAA2B,EAAE,MAAM,CAAC;IAC7C,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,+BAA+B,GACrC,mBAAmB,CAkBrB;AAED,MAAM,MAAM,qBAAqB,GAC7B;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;IAC3C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACxD,GACD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { normalizeAttemptLifetime } from './oauthStorage.js';
|
|
2
|
+
export function createOAuthRuntimeContext(input) {
|
|
3
|
+
const providers = [...new Set(input.providers)];
|
|
4
|
+
if (providers.length === 0) {
|
|
5
|
+
throw new TypeError('At least one enabled Supabase OAuth provider is required.');
|
|
6
|
+
}
|
|
7
|
+
const [firstProvider, ...remainingProviders] = providers;
|
|
8
|
+
if (firstProvider === undefined)
|
|
9
|
+
throw new TypeError('An OAuth provider is required.');
|
|
10
|
+
const oauthStorageKey = `${input.storageKey}.oauth`;
|
|
11
|
+
return {
|
|
12
|
+
...input,
|
|
13
|
+
providers: [firstProvider, ...remainingProviders],
|
|
14
|
+
providerSet: new Set(providers),
|
|
15
|
+
attemptStorageKey: `${oauthStorageKey}.attempt`,
|
|
16
|
+
consumedCallbacksStorageKey: `${oauthStorageKey}.consumed-callbacks`,
|
|
17
|
+
codeVerifierStorageKey: `${oauthStorageKey}.pkce-verifier`,
|
|
18
|
+
now: input.now ?? Date.now,
|
|
19
|
+
attemptLifetimeMs: normalizeAttemptLifetime(input.attemptLifetimeMs),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=oauthRuntime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthRuntime.js","sourceRoot":"","sources":["../src/oauthRuntime.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAa7D,MAAM,UAAU,yBAAyB,CACvC,KAAsC;IAEtC,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAChD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,SAAS,CAAC,2DAA2D,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,CAAC,aAAa,EAAE,GAAG,kBAAkB,CAAC,GAAG,SAAS,CAAC;IACzD,IAAI,aAAa,KAAK,SAAS;QAAE,MAAM,IAAI,SAAS,CAAC,gCAAgC,CAAC,CAAC;IACvF,MAAM,eAAe,GAAG,GAAG,KAAK,CAAC,UAAU,QAAQ,CAAC;IACpD,OAAO;QACL,GAAG,KAAK;QACR,SAAS,EAAE,CAAC,aAAa,EAAE,GAAG,kBAAkB,CAAC;QACjD,WAAW,EAAE,IAAI,GAAG,CAAC,SAAS,CAAC;QAC/B,iBAAiB,EAAE,GAAG,eAAe,UAAU;QAC/C,2BAA2B,EAAE,GAAG,eAAe,qBAAqB;QACpE,sBAAsB,EAAE,GAAG,eAAe,gBAAgB;QAC1D,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG;QAC1B,iBAAiB,EAAE,wBAAwB,CAAC,KAAK,CAAC,iBAAiB,CAAC;KACrE,CAAC;AACJ,CAAC","sourcesContent":["import type { AuthOAuthStartResult } from '@ankhorage/contracts/auth';\n\nimport type { SupabaseOAuthProviderId } from './oauthProviderDefinitions.js';\nimport { normalizeAttemptLifetime } from './oauthStorage.js';\nimport type { CreateSupabaseOAuthAdapterInput } from './oauthTypes.js';\n\nexport interface OAuthRuntimeContext extends CreateSupabaseOAuthAdapterInput {\n readonly providers: [SupabaseOAuthProviderId, ...SupabaseOAuthProviderId[]];\n readonly providerSet: ReadonlySet<SupabaseOAuthProviderId>;\n readonly attemptStorageKey: string;\n readonly consumedCallbacksStorageKey: string;\n readonly codeVerifierStorageKey: string;\n readonly now: () => number;\n readonly attemptLifetimeMs: number;\n}\n\nexport function createOAuthRuntimeContext(\n input: CreateSupabaseOAuthAdapterInput,\n): OAuthRuntimeContext {\n const providers = [...new Set(input.providers)];\n if (providers.length === 0) {\n throw new TypeError('At least one enabled Supabase OAuth provider is required.');\n }\n const [firstProvider, ...remainingProviders] = providers;\n if (firstProvider === undefined) throw new TypeError('An OAuth provider is required.');\n const oauthStorageKey = `${input.storageKey}.oauth`;\n return {\n ...input,\n providers: [firstProvider, ...remainingProviders],\n providerSet: new Set(providers),\n attemptStorageKey: `${oauthStorageKey}.attempt`,\n consumedCallbacksStorageKey: `${oauthStorageKey}.consumed-callbacks`,\n codeVerifierStorageKey: `${oauthStorageKey}.pkce-verifier`,\n now: input.now ?? Date.now,\n attemptLifetimeMs: normalizeAttemptLifetime(input.attemptLifetimeMs),\n };\n}\n\nexport type OAuthStartPreparation =\n | {\n readonly ok: true;\n readonly provider: SupabaseOAuthProviderId;\n readonly redirectUri: string;\n readonly queryParams: Readonly<Record<string, string>>;\n }\n | { readonly ok: false; readonly result: AuthOAuthStartResult };\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AuthOAuthProviderId, type AuthOAuthSetupPlan, type AuthOAuthTransportId } from '@ankhorage/contracts/auth';
|
|
2
|
+
export declare const SUPABASE_OAUTH_SETUP_CAPABILITIES: {
|
|
3
|
+
readonly providers: readonly ["google", "apple"];
|
|
4
|
+
readonly transports: readonly ["brokeredRedirect"];
|
|
5
|
+
};
|
|
6
|
+
export declare function resolveSupabaseOAuthSetupPlan(input: {
|
|
7
|
+
readonly provider: AuthOAuthProviderId;
|
|
8
|
+
readonly transport: AuthOAuthTransportId;
|
|
9
|
+
readonly environment: AuthOAuthSetupPlan['environment'];
|
|
10
|
+
readonly targets: AuthOAuthSetupPlan['targets'];
|
|
11
|
+
}): AuthOAuthSetupPlan | null;
|
|
12
|
+
//# sourceMappingURL=oauthSetupRequirements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthSetupRequirements.d.ts","sourceRoot":"","sources":["../src/oauthSetupRequirements.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,mBAAmB,EAExB,KAAK,kBAAkB,EAEvB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAOnC,eAAO,MAAM,iCAAiC;;;CAGC,CAAC;AAEhD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE;IACnD,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACxD,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;CACjD,GAAG,kBAAkB,GAAG,IAAI,CAkC5B"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AUTH_OAUTH_SETUP_FIELD_PERSISTENCE_KINDS, AUTH_OAUTH_SETUP_FIELD_SENSITIVITIES, } from '@ankhorage/contracts/auth';
|
|
2
|
+
import { getSupabaseOAuthProviderDefinition, SUPABASE_OAUTH_PROVIDER_IDS, } from './oauthProviderDefinitions.js';
|
|
3
|
+
export const SUPABASE_OAUTH_SETUP_CAPABILITIES = {
|
|
4
|
+
providers: SUPABASE_OAUTH_PROVIDER_IDS,
|
|
5
|
+
transports: ['brokeredRedirect'],
|
|
6
|
+
};
|
|
7
|
+
export function resolveSupabaseOAuthSetupPlan(input) {
|
|
8
|
+
if (input.transport !== 'brokeredRedirect')
|
|
9
|
+
return null;
|
|
10
|
+
const definition = getSupabaseOAuthProviderDefinition(input.provider);
|
|
11
|
+
if (definition === null)
|
|
12
|
+
return null;
|
|
13
|
+
const targets = [...new Set(input.targets)];
|
|
14
|
+
const requirements = definition.secretFields.map((field) => ({
|
|
15
|
+
kind: 'field',
|
|
16
|
+
key: field.name,
|
|
17
|
+
label: field.label,
|
|
18
|
+
required: true,
|
|
19
|
+
sensitivity: AUTH_OAUTH_SETUP_FIELD_SENSITIVITIES[field.secret ? 1 : 0],
|
|
20
|
+
persistence: AUTH_OAUTH_SETUP_FIELD_PERSISTENCE_KINDS[0],
|
|
21
|
+
}));
|
|
22
|
+
requirements.push({
|
|
23
|
+
kind: 'callback',
|
|
24
|
+
role: 'provider',
|
|
25
|
+
label: 'Provider callback URI',
|
|
26
|
+
required: true,
|
|
27
|
+
});
|
|
28
|
+
for (const target of targets) {
|
|
29
|
+
requirements.push({
|
|
30
|
+
kind: 'callback',
|
|
31
|
+
role: 'app',
|
|
32
|
+
target,
|
|
33
|
+
label: `${target} app callback`,
|
|
34
|
+
required: true,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return { ...input, targets, requirements };
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=oauthSetupRequirements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthSetupRequirements.js","sourceRoot":"","sources":["../src/oauthSetupRequirements.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wCAAwC,EACxC,oCAAoC,GAMrC,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,kCAAkC,EAClC,2BAA2B,GAC5B,MAAM,+BAA+B,CAAC;AAEvC,MAAM,CAAC,MAAM,iCAAiC,GAAG;IAC/C,SAAS,EAAE,2BAA2B;IACtC,UAAU,EAAE,CAAC,kBAAkB,CAAC;CACa,CAAC;AAEhD,MAAM,UAAU,6BAA6B,CAAC,KAK7C;IACC,IAAI,KAAK,CAAC,SAAS,KAAK,kBAAkB;QAAE,OAAO,IAAI,CAAC;IAExD,MAAM,UAAU,GAAG,kCAAkC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACtE,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAgC,UAAU,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACxF,IAAI,EAAE,OAAO;QACb,GAAG,EAAE,KAAK,CAAC,IAAI;QACf,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,oCAAoC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,WAAW,EAAE,wCAAwC,CAAC,CAAC,CAAC;KACzD,CAAC,CAAC,CAAC;IAEJ,YAAY,CAAC,IAAI,CAAC;QAChB,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,uBAAuB;QAC9B,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,YAAY,CAAC,IAAI,CAAC;YAChB,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,KAAK;YACX,MAAM;YACN,KAAK,EAAE,GAAG,MAAM,eAAe;YAC/B,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;AAC7C,CAAC","sourcesContent":["import {\n AUTH_OAUTH_SETUP_FIELD_PERSISTENCE_KINDS,\n AUTH_OAUTH_SETUP_FIELD_SENSITIVITIES,\n type AuthOAuthProviderId,\n type AuthOAuthSetupCapabilities,\n type AuthOAuthSetupPlan,\n type AuthOAuthSetupRequirement,\n type AuthOAuthTransportId,\n} from '@ankhorage/contracts/auth';\n\nimport {\n getSupabaseOAuthProviderDefinition,\n SUPABASE_OAUTH_PROVIDER_IDS,\n} from './oauthProviderDefinitions.js';\n\nexport const SUPABASE_OAUTH_SETUP_CAPABILITIES = {\n providers: SUPABASE_OAUTH_PROVIDER_IDS,\n transports: ['brokeredRedirect'],\n} as const satisfies AuthOAuthSetupCapabilities;\n\nexport function resolveSupabaseOAuthSetupPlan(input: {\n readonly provider: AuthOAuthProviderId;\n readonly transport: AuthOAuthTransportId;\n readonly environment: AuthOAuthSetupPlan['environment'];\n readonly targets: AuthOAuthSetupPlan['targets'];\n}): AuthOAuthSetupPlan | null {\n if (input.transport !== 'brokeredRedirect') return null;\n\n const definition = getSupabaseOAuthProviderDefinition(input.provider);\n if (definition === null) return null;\n\n const targets = [...new Set(input.targets)];\n const requirements: AuthOAuthSetupRequirement[] = definition.secretFields.map((field) => ({\n kind: 'field',\n key: field.name,\n label: field.label,\n required: true,\n sensitivity: AUTH_OAUTH_SETUP_FIELD_SENSITIVITIES[field.secret ? 1 : 0],\n persistence: AUTH_OAUTH_SETUP_FIELD_PERSISTENCE_KINDS[0],\n }));\n\n requirements.push({\n kind: 'callback',\n role: 'provider',\n label: 'Provider callback URI',\n required: true,\n });\n\n for (const target of targets) {\n requirements.push({\n kind: 'callback',\n role: 'app',\n target,\n label: `${target} app callback`,\n required: true,\n });\n }\n\n return { ...input, targets, requirements };\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AuthOAuthStartResult, StartOAuthAuthorizationInput } from '@ankhorage/contracts/auth';
|
|
2
|
+
import type { OAuthRuntimeContext } from './oauthRuntime.js';
|
|
3
|
+
export declare function startSupabaseOAuthAuthorization(context: OAuthRuntimeContext, input: StartOAuthAuthorizationInput): Promise<AuthOAuthStartResult>;
|
|
4
|
+
//# sourceMappingURL=oauthStart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthStart.d.ts","sourceRoot":"","sources":["../src/oauthStart.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAUpG,OAAO,KAAK,EAAE,mBAAmB,EAAyB,MAAM,mBAAmB,CAAC;AAiBpF,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,mBAAmB,EAC5B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,oBAAoB,CAAC,CAM/B"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { randomBytes } from '@noble/hashes/utils.js';
|
|
2
|
+
import { createOAuthAttemptId } from './oauthAttemptId.js';
|
|
3
|
+
import { oauthStartError } from './oauthErrors.js';
|
|
4
|
+
import { emitLifecycleEvent } from './oauthLifecycle.js';
|
|
5
|
+
import { getSupabaseOAuthProviderDefinition, } from './oauthProviderDefinitions.js';
|
|
6
|
+
import { createAuthorizationUrl, createPkcePair, normalizeQueryParams, normalizeRedirectUri, normalizeScopes, resolveEnabledProvider, } from './oauthStartSupport.js';
|
|
7
|
+
import { clearOAuthAttemptState, isAttemptExpired, readAttempt, writeAttempt, } from './oauthStorage.js';
|
|
8
|
+
import { ATTEMPT_VERSION } from './oauthTypes.js';
|
|
9
|
+
export async function startSupabaseOAuthAuthorization(context, input) {
|
|
10
|
+
const prepared = prepareStartInput(context, input);
|
|
11
|
+
if (!prepared.ok)
|
|
12
|
+
return prepared.result;
|
|
13
|
+
const previousError = await clearPreviousAttempt(context, prepared.provider);
|
|
14
|
+
if (previousError !== null)
|
|
15
|
+
return previousError;
|
|
16
|
+
return createAuthorizationAttempt(context, input, prepared);
|
|
17
|
+
}
|
|
18
|
+
function prepareStartInput(context, input) {
|
|
19
|
+
const provider = resolveEnabledProvider(input.provider, context.providerSet);
|
|
20
|
+
if (!provider.ok)
|
|
21
|
+
return provider;
|
|
22
|
+
const redirectUri = normalizeRedirectUri(input.redirectUri, provider.provider);
|
|
23
|
+
if (!redirectUri.ok)
|
|
24
|
+
return redirectUri;
|
|
25
|
+
const queryParams = normalizeQueryParams(input.queryParams, provider.provider);
|
|
26
|
+
return queryParams.ok
|
|
27
|
+
? {
|
|
28
|
+
ok: true,
|
|
29
|
+
provider: provider.provider,
|
|
30
|
+
redirectUri: redirectUri.value,
|
|
31
|
+
queryParams: queryParams.value,
|
|
32
|
+
}
|
|
33
|
+
: queryParams;
|
|
34
|
+
}
|
|
35
|
+
async function clearPreviousAttempt(context, provider) {
|
|
36
|
+
let previous;
|
|
37
|
+
try {
|
|
38
|
+
previous = await readAttempt(context.storage, context.attemptStorageKey);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return oauthStartError('session_persistence_failed', 'Unable to read the persisted OAuth authorization state.', provider, true);
|
|
42
|
+
}
|
|
43
|
+
if (previous.type === 'invalid')
|
|
44
|
+
await clearAttempt(context);
|
|
45
|
+
if (previous.type !== 'valid')
|
|
46
|
+
return null;
|
|
47
|
+
if (previous.attempt.status === 'completed' ||
|
|
48
|
+
isAttemptExpired(previous.attempt, context.now())) {
|
|
49
|
+
await clearAttempt(context);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return oauthStartError('authorization_failed', 'An OAuth authorization attempt is already active.', provider, true);
|
|
53
|
+
}
|
|
54
|
+
async function createAuthorizationAttempt(context, input, prepared) {
|
|
55
|
+
try {
|
|
56
|
+
const { challenge, verifier } = createPkcePair(context.randomBytes ?? randomBytes);
|
|
57
|
+
const attempt = createAttempt(context, prepared);
|
|
58
|
+
await context.storage.setItem(context.codeVerifierStorageKey, verifier);
|
|
59
|
+
await writeAttempt(context.storage, context.attemptStorageKey, attempt);
|
|
60
|
+
await emitLifecycleEvent(context.onLifecycleEvent, {
|
|
61
|
+
correlationId: attempt.id,
|
|
62
|
+
provider: attempt.provider,
|
|
63
|
+
stage: 'start',
|
|
64
|
+
status: 'started',
|
|
65
|
+
});
|
|
66
|
+
return createStartResult(context, input, prepared, attempt, challenge);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
await clearAttempt(context);
|
|
70
|
+
return oauthStartError('session_persistence_failed', 'Unable to initialize the OAuth PKCE authorization attempt.', prepared.provider, true);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function createAttempt(context, prepared) {
|
|
74
|
+
const createdAt = context.now();
|
|
75
|
+
return {
|
|
76
|
+
version: ATTEMPT_VERSION,
|
|
77
|
+
id: createOAuthAttemptId(),
|
|
78
|
+
provider: prepared.provider,
|
|
79
|
+
redirectUri: prepared.redirectUri,
|
|
80
|
+
status: 'pending',
|
|
81
|
+
createdAt,
|
|
82
|
+
expiresAt: createdAt + context.attemptLifetimeMs,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function createStartResult(context, input, prepared, attempt, challenge) {
|
|
86
|
+
const definition = getSupabaseOAuthProviderDefinition(prepared.provider);
|
|
87
|
+
const scopes = normalizeScopes(input.scopes, definition?.defaultScopes ?? []);
|
|
88
|
+
return {
|
|
89
|
+
ok: true,
|
|
90
|
+
data: {
|
|
91
|
+
attemptId: attempt.id,
|
|
92
|
+
provider: prepared.provider,
|
|
93
|
+
authorizationUrl: createAuthorizationUrl({
|
|
94
|
+
baseUrl: context.url,
|
|
95
|
+
challenge,
|
|
96
|
+
provider: prepared.provider,
|
|
97
|
+
queryParams: prepared.queryParams,
|
|
98
|
+
redirectUri: prepared.redirectUri,
|
|
99
|
+
scopes,
|
|
100
|
+
}),
|
|
101
|
+
redirectUri: prepared.redirectUri,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function clearAttempt(context) {
|
|
106
|
+
await clearOAuthAttemptState(context.storage, context.attemptStorageKey, context.codeVerifierStorageKey);
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=oauthStart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthStart.js","sourceRoot":"","sources":["../src/oauthStart.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthOAuthStartResult, StartOAuthAuthorizationInput } from '@ankhorage/contracts/auth';\nimport { randomBytes } from '@noble/hashes/utils.js';\n\nimport { createOAuthAttemptId } from './oauthAttemptId.js';\nimport { oauthStartError } from './oauthErrors.js';\nimport { emitLifecycleEvent } from './oauthLifecycle.js';\nimport {\n getSupabaseOAuthProviderDefinition,\n type SupabaseOAuthProviderId,\n} from './oauthProviderDefinitions.js';\nimport type { OAuthRuntimeContext, OAuthStartPreparation } from './oauthRuntime.js';\nimport {\n createAuthorizationUrl,\n createPkcePair,\n normalizeQueryParams,\n normalizeRedirectUri,\n normalizeScopes,\n resolveEnabledProvider,\n} from './oauthStartSupport.js';\nimport {\n clearOAuthAttemptState,\n isAttemptExpired,\n readAttempt,\n writeAttempt,\n} from './oauthStorage.js';\nimport { ATTEMPT_VERSION, type StoredOAuthAttempt } from './oauthTypes.js';\n\nexport async function startSupabaseOAuthAuthorization(\n context: OAuthRuntimeContext,\n input: StartOAuthAuthorizationInput,\n): Promise<AuthOAuthStartResult> {\n const prepared = prepareStartInput(context, input);\n if (!prepared.ok) return prepared.result;\n const previousError = await clearPreviousAttempt(context, prepared.provider);\n if (previousError !== null) return previousError;\n return createAuthorizationAttempt(context, input, prepared);\n}\n\nfunction prepareStartInput(\n context: OAuthRuntimeContext,\n input: StartOAuthAuthorizationInput,\n): OAuthStartPreparation {\n const provider = resolveEnabledProvider(input.provider, context.providerSet);\n if (!provider.ok) return provider;\n const redirectUri = normalizeRedirectUri(input.redirectUri, provider.provider);\n if (!redirectUri.ok) return redirectUri;\n const queryParams = normalizeQueryParams(input.queryParams, provider.provider);\n return queryParams.ok\n ? {\n ok: true,\n provider: provider.provider,\n redirectUri: redirectUri.value,\n queryParams: queryParams.value,\n }\n : queryParams;\n}\n\nasync function clearPreviousAttempt(\n context: OAuthRuntimeContext,\n provider: SupabaseOAuthProviderId,\n): Promise<AuthOAuthStartResult | null> {\n let previous;\n try {\n previous = await readAttempt(context.storage, context.attemptStorageKey);\n } catch {\n return oauthStartError(\n 'session_persistence_failed',\n 'Unable to read the persisted OAuth authorization state.',\n provider,\n true,\n );\n }\n if (previous.type === 'invalid') await clearAttempt(context);\n if (previous.type !== 'valid') return null;\n if (\n previous.attempt.status === 'completed' ||\n isAttemptExpired(previous.attempt, context.now())\n ) {\n await clearAttempt(context);\n return null;\n }\n return oauthStartError(\n 'authorization_failed',\n 'An OAuth authorization attempt is already active.',\n provider,\n true,\n );\n}\n\nasync function createAuthorizationAttempt(\n context: OAuthRuntimeContext,\n input: StartOAuthAuthorizationInput,\n prepared: Extract<OAuthStartPreparation, { ok: true }>,\n): Promise<AuthOAuthStartResult> {\n try {\n const { challenge, verifier } = createPkcePair(context.randomBytes ?? randomBytes);\n const attempt = createAttempt(context, prepared);\n await context.storage.setItem(context.codeVerifierStorageKey, verifier);\n await writeAttempt(context.storage, context.attemptStorageKey, attempt);\n await emitLifecycleEvent(context.onLifecycleEvent, {\n correlationId: attempt.id,\n provider: attempt.provider,\n stage: 'start',\n status: 'started',\n });\n return createStartResult(context, input, prepared, attempt, challenge);\n } catch {\n await clearAttempt(context);\n return oauthStartError(\n 'session_persistence_failed',\n 'Unable to initialize the OAuth PKCE authorization attempt.',\n prepared.provider,\n true,\n );\n }\n}\n\nfunction createAttempt(\n context: OAuthRuntimeContext,\n prepared: Extract<OAuthStartPreparation, { ok: true }>,\n): StoredOAuthAttempt {\n const createdAt = context.now();\n return {\n version: ATTEMPT_VERSION,\n id: createOAuthAttemptId(),\n provider: prepared.provider,\n redirectUri: prepared.redirectUri,\n status: 'pending',\n createdAt,\n expiresAt: createdAt + context.attemptLifetimeMs,\n };\n}\n\nfunction createStartResult(\n context: OAuthRuntimeContext,\n input: StartOAuthAuthorizationInput,\n prepared: Extract<OAuthStartPreparation, { ok: true }>,\n attempt: StoredOAuthAttempt,\n challenge: string,\n): AuthOAuthStartResult {\n const definition = getSupabaseOAuthProviderDefinition(prepared.provider);\n const scopes = normalizeScopes(input.scopes, definition?.defaultScopes ?? []);\n return {\n ok: true,\n data: {\n attemptId: attempt.id,\n provider: prepared.provider,\n authorizationUrl: createAuthorizationUrl({\n baseUrl: context.url,\n challenge,\n provider: prepared.provider,\n queryParams: prepared.queryParams,\n redirectUri: prepared.redirectUri,\n scopes,\n }),\n redirectUri: prepared.redirectUri,\n },\n };\n}\n\nasync function clearAttempt(context: OAuthRuntimeContext): Promise<void> {\n await clearOAuthAttemptState(\n context.storage,\n context.attemptStorageKey,\n context.codeVerifierStorageKey,\n );\n}\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AuthOAuthProviderId, AuthOAuthStartResult } from '@ankhorage/contracts/auth';
|
|
2
|
+
import { type SupabaseOAuthProviderId } from './oauthProviderDefinitions.js';
|
|
3
|
+
import type { SupabaseAuthFetch, SupabaseAuthRandomBytes } from './types.js';
|
|
4
|
+
/*** Resolves an OAuth provider against the adapter's enabled provider set. */
|
|
5
|
+
export declare function resolveEnabledProvider(requestedProvider: AuthOAuthProviderId, providers: ReadonlySet<SupabaseOAuthProviderId>): {
|
|
6
|
+
ok: true;
|
|
7
|
+
provider: SupabaseOAuthProviderId;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
result: AuthOAuthStartResult;
|
|
11
|
+
};
|
|
12
|
+
/*** Normalizes and validates a canonical OAuth callback URI. */
|
|
13
|
+
export declare function normalizeRedirectUri(rawRedirectUri: string, provider: SupabaseOAuthProviderId): {
|
|
14
|
+
ok: true;
|
|
15
|
+
value: string;
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
result: AuthOAuthStartResult;
|
|
19
|
+
};
|
|
20
|
+
/*** Normalizes provider query parameters while rejecting reserved OAuth fields. */
|
|
21
|
+
export declare function normalizeQueryParams(queryParams: Readonly<Record<string, string>> | undefined, provider: SupabaseOAuthProviderId): {
|
|
22
|
+
ok: true;
|
|
23
|
+
value: Record<string, string>;
|
|
24
|
+
} | {
|
|
25
|
+
ok: false;
|
|
26
|
+
result: AuthOAuthStartResult;
|
|
27
|
+
};
|
|
28
|
+
/*** Returns unique non-empty OAuth scopes in request order. */
|
|
29
|
+
export declare function normalizeScopes(scopes: readonly string[] | undefined, defaults: readonly string[]): string[];
|
|
30
|
+
/*** Creates a PKCE verifier and its S256 challenge from an injected secure byte source. */
|
|
31
|
+
export declare function createPkcePair(generateRandomBytes: SupabaseAuthRandomBytes): {
|
|
32
|
+
challenge: string;
|
|
33
|
+
verifier: string;
|
|
34
|
+
};
|
|
35
|
+
/*** Creates the public Supabase PKCE authorization URL. */
|
|
36
|
+
export declare function createAuthorizationUrl(input: {
|
|
37
|
+
baseUrl: string;
|
|
38
|
+
challenge: string;
|
|
39
|
+
provider: SupabaseOAuthProviderId;
|
|
40
|
+
queryParams: Readonly<Record<string, string>>;
|
|
41
|
+
redirectUri: string;
|
|
42
|
+
scopes: readonly string[];
|
|
43
|
+
}): string;
|
|
44
|
+
/*** Exchanges a Supabase PKCE authorization code through the public HTTP contract. */
|
|
45
|
+
export declare function exchangeAuthorizationCode(input: {
|
|
46
|
+
anonKey: string;
|
|
47
|
+
baseUrl: string;
|
|
48
|
+
code: string;
|
|
49
|
+
fetch: SupabaseAuthFetch;
|
|
50
|
+
verifier: string;
|
|
51
|
+
}): Promise<Response>;
|
|
52
|
+
//# sourceMappingURL=oauthStartSupport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthStartSupport.d.ts","sourceRoot":"","sources":["../src/oauthStartSupport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAK3F,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAc7E,8EAA8E;AAC9E,wBAAgB,sBAAsB,CACpC,iBAAiB,EAAE,mBAAmB,EACtC,SAAS,EAAE,WAAW,CAAC,uBAAuB,CAAC,GAC9C;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,uBAAuB,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAc/F;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,uBAAuB,GAChC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAe3E;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,EACzD,QAAQ,EAAE,uBAAuB,GAChC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAiB3F;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACrC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,MAAM,EAAE,CAGV;AAED,2FAA2F;AAC3F,wBAAgB,cAAc,CAAC,mBAAmB,EAAE,uBAAuB,GAAG;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAOA;AAED,2DAA2D;AAC3D,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,uBAAuB,CAAC;IAClC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,GAAG,MAAM,CAYT;AAED,sFAAsF;AACtF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAUpB"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
2
|
+
import { isBytes, utf8ToBytes } from '@noble/hashes/utils.js';
|
|
3
|
+
import { oauthStartError } from './oauthErrors.js';
|
|
4
|
+
import { isSupabaseOAuthProviderId, } from './oauthProviderDefinitions.js';
|
|
5
|
+
const PKCE_RANDOM_BYTE_COUNT = 32;
|
|
6
|
+
const BASE64_URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
7
|
+
const FORBIDDEN_QUERY_PARAMS = new Set([
|
|
8
|
+
'code_challenge',
|
|
9
|
+
'code_challenge_method',
|
|
10
|
+
'provider',
|
|
11
|
+
'redirect_to',
|
|
12
|
+
'scopes',
|
|
13
|
+
'skip_http_redirect',
|
|
14
|
+
]);
|
|
15
|
+
const DANGEROUS_PROTOCOLS = new Set(['data:', 'file:', 'javascript:']);
|
|
16
|
+
/*** Resolves an OAuth provider against the adapter's enabled provider set. */
|
|
17
|
+
export function resolveEnabledProvider(requestedProvider, providers) {
|
|
18
|
+
const provider = requestedProvider.trim();
|
|
19
|
+
if (!isSupabaseOAuthProviderId(provider) || !providers.has(provider)) {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
result: oauthStartError('provider_disabled', `OAuth provider "${provider}" is not enabled for this adapter.`, requestedProvider, true),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return { ok: true, provider };
|
|
26
|
+
}
|
|
27
|
+
/*** Normalizes and validates a canonical OAuth callback URI. */
|
|
28
|
+
export function normalizeRedirectUri(rawRedirectUri, provider) {
|
|
29
|
+
const url = parseAbsoluteUrl(rawRedirectUri.trim());
|
|
30
|
+
if (url === null) {
|
|
31
|
+
return invalidRedirect(provider, 'OAuth redirect URI must be an absolute URL or application deep link.');
|
|
32
|
+
}
|
|
33
|
+
if (!isPermittedRedirect(url)) {
|
|
34
|
+
return invalidRedirect(provider, 'OAuth redirect URI is not a permitted canonical callback URL.');
|
|
35
|
+
}
|
|
36
|
+
return { ok: true, value: url.toString() };
|
|
37
|
+
}
|
|
38
|
+
/*** Normalizes provider query parameters while rejecting reserved OAuth fields. */
|
|
39
|
+
export function normalizeQueryParams(queryParams, provider) {
|
|
40
|
+
const entries = Object.entries(queryParams ?? {}).map(([key, value]) => [key.trim(), value.trim()]);
|
|
41
|
+
const invalid = entries.find(([key]) => key.length === 0 || FORBIDDEN_QUERY_PARAMS.has(key));
|
|
42
|
+
if (invalid !== undefined) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
result: oauthStartError('provider_misconfigured', `OAuth query parameter "${invalid[0]}" is reserved or invalid.`, provider, true),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { ok: true, value: Object.fromEntries(entries) };
|
|
49
|
+
}
|
|
50
|
+
/*** Returns unique non-empty OAuth scopes in request order. */
|
|
51
|
+
export function normalizeScopes(scopes, defaults) {
|
|
52
|
+
const requested = scopes === undefined || scopes.length === 0 ? defaults : scopes;
|
|
53
|
+
return [...new Set(requested.map((scope) => scope.trim()).filter(Boolean))];
|
|
54
|
+
}
|
|
55
|
+
/*** Creates a PKCE verifier and its S256 challenge from an injected secure byte source. */
|
|
56
|
+
export function createPkcePair(generateRandomBytes) {
|
|
57
|
+
const randomValue = generateRandomBytes(PKCE_RANDOM_BYTE_COUNT);
|
|
58
|
+
if (!isBytes(randomValue) || randomValue.length !== PKCE_RANDOM_BYTE_COUNT) {
|
|
59
|
+
throw new TypeError('OAuth random byte source returned an invalid value.');
|
|
60
|
+
}
|
|
61
|
+
const verifier = bytesToBase64Url(randomValue);
|
|
62
|
+
return { challenge: bytesToBase64Url(sha256(utf8ToBytes(verifier))), verifier };
|
|
63
|
+
}
|
|
64
|
+
/*** Creates the public Supabase PKCE authorization URL. */
|
|
65
|
+
export function createAuthorizationUrl(input) {
|
|
66
|
+
const url = new URL(`${input.baseUrl.replace(/\/+$/u, '')}/auth/v1/authorize`);
|
|
67
|
+
const parameters = [
|
|
68
|
+
['provider', input.provider],
|
|
69
|
+
['redirect_to', input.redirectUri],
|
|
70
|
+
['code_challenge', input.challenge],
|
|
71
|
+
['code_challenge_method', 's256'],
|
|
72
|
+
...Object.entries(input.queryParams),
|
|
73
|
+
];
|
|
74
|
+
if (input.scopes.length > 0)
|
|
75
|
+
url.searchParams.set('scopes', input.scopes.join(' '));
|
|
76
|
+
for (const [key, value] of parameters)
|
|
77
|
+
url.searchParams.set(key, value);
|
|
78
|
+
return url.toString();
|
|
79
|
+
}
|
|
80
|
+
/*** Exchanges a Supabase PKCE authorization code through the public HTTP contract. */
|
|
81
|
+
export function exchangeAuthorizationCode(input) {
|
|
82
|
+
return input.fetch(`${input.baseUrl.replace(/\/+$/u, '')}/auth/v1/token?grant_type=pkce`, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
apikey: input.anonKey,
|
|
86
|
+
Authorization: `Bearer ${input.anonKey}`,
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify({ auth_code: input.code, code_verifier: input.verifier }),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function bytesToBase64Url(bytes) {
|
|
93
|
+
let encoded = '';
|
|
94
|
+
for (let index = 0; index < bytes.length; index += 3)
|
|
95
|
+
encoded += encodeBase64Triplet(bytes, index);
|
|
96
|
+
return encoded;
|
|
97
|
+
}
|
|
98
|
+
function encodeBase64Triplet(bytes, index) {
|
|
99
|
+
const first = bytes.at(index) ?? 0;
|
|
100
|
+
const second = bytes.at(index + 1);
|
|
101
|
+
const third = bytes.at(index + 2);
|
|
102
|
+
const encoded = [
|
|
103
|
+
BASE64_URL_ALPHABET.at(first >> 2) ?? '',
|
|
104
|
+
BASE64_URL_ALPHABET.at(((first & 0b11) << 4) | ((second ?? 0) >> 4)) ?? '',
|
|
105
|
+
second === undefined
|
|
106
|
+
? ''
|
|
107
|
+
: (BASE64_URL_ALPHABET.at(((second & 0b1111) << 2) | ((third ?? 0) >> 6)) ?? ''),
|
|
108
|
+
third === undefined ? '' : (BASE64_URL_ALPHABET.at(third & 0b11_1111) ?? ''),
|
|
109
|
+
];
|
|
110
|
+
return encoded.join('');
|
|
111
|
+
}
|
|
112
|
+
function parseAbsoluteUrl(value) {
|
|
113
|
+
try {
|
|
114
|
+
return new URL(value);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function isPermittedRedirect(url) {
|
|
121
|
+
return (!DANGEROUS_PROTOCOLS.has(url.protocol) &&
|
|
122
|
+
url.username.length === 0 &&
|
|
123
|
+
url.password.length === 0 &&
|
|
124
|
+
url.search.length === 0 &&
|
|
125
|
+
url.hash.length === 0 &&
|
|
126
|
+
(url.protocol !== 'http:' || isLocalhost(url.hostname)));
|
|
127
|
+
}
|
|
128
|
+
function invalidRedirect(provider, message) {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
result: oauthStartError('invalid_redirect_uri', message, provider, true),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function isLocalhost(hostname) {
|
|
135
|
+
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=oauthStartSupport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthStartSupport.js","sourceRoot":"","sources":["../src/oauthStartSupport.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthOAuthProviderId, AuthOAuthStartResult } from '@ankhorage/contracts/auth';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { isBytes, utf8ToBytes } from '@noble/hashes/utils.js';\n\nimport { oauthStartError } from './oauthErrors.js';\nimport {\n isSupabaseOAuthProviderId,\n type SupabaseOAuthProviderId,\n} from './oauthProviderDefinitions.js';\nimport type { SupabaseAuthFetch, SupabaseAuthRandomBytes } from './types.js';\n\nconst PKCE_RANDOM_BYTE_COUNT = 32;\nconst BASE64_URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';\nconst FORBIDDEN_QUERY_PARAMS = new Set([\n 'code_challenge',\n 'code_challenge_method',\n 'provider',\n 'redirect_to',\n 'scopes',\n 'skip_http_redirect',\n]);\nconst DANGEROUS_PROTOCOLS = new Set(['data:', 'file:', 'javascript:']);\n\n/*** Resolves an OAuth provider against the adapter's enabled provider set. */\nexport function resolveEnabledProvider(\n requestedProvider: AuthOAuthProviderId,\n providers: ReadonlySet<SupabaseOAuthProviderId>,\n): { ok: true; provider: SupabaseOAuthProviderId } | { ok: false; result: AuthOAuthStartResult } {\n const provider = requestedProvider.trim();\n if (!isSupabaseOAuthProviderId(provider) || !providers.has(provider)) {\n return {\n ok: false,\n result: oauthStartError(\n 'provider_disabled',\n `OAuth provider \"${provider}\" is not enabled for this adapter.`,\n requestedProvider,\n true,\n ),\n };\n }\n return { ok: true, provider };\n}\n\n/*** Normalizes and validates a canonical OAuth callback URI. */\nexport function normalizeRedirectUri(\n rawRedirectUri: string,\n provider: SupabaseOAuthProviderId,\n): { ok: true; value: string } | { ok: false; result: AuthOAuthStartResult } {\n const url = parseAbsoluteUrl(rawRedirectUri.trim());\n if (url === null) {\n return invalidRedirect(\n provider,\n 'OAuth redirect URI must be an absolute URL or application deep link.',\n );\n }\n if (!isPermittedRedirect(url)) {\n return invalidRedirect(\n provider,\n 'OAuth redirect URI is not a permitted canonical callback URL.',\n );\n }\n return { ok: true, value: url.toString() };\n}\n\n/*** Normalizes provider query parameters while rejecting reserved OAuth fields. */\nexport function normalizeQueryParams(\n queryParams: Readonly<Record<string, string>> | undefined,\n provider: SupabaseOAuthProviderId,\n): { ok: true; value: Record<string, string> } | { ok: false; result: AuthOAuthStartResult } {\n const entries = Object.entries(queryParams ?? {}).map(\n ([key, value]) => [key.trim(), value.trim()] as const,\n );\n const invalid = entries.find(([key]) => key.length === 0 || FORBIDDEN_QUERY_PARAMS.has(key));\n if (invalid !== undefined) {\n return {\n ok: false,\n result: oauthStartError(\n 'provider_misconfigured',\n `OAuth query parameter \"${invalid[0]}\" is reserved or invalid.`,\n provider,\n true,\n ),\n };\n }\n return { ok: true, value: Object.fromEntries(entries) };\n}\n\n/*** Returns unique non-empty OAuth scopes in request order. */\nexport function normalizeScopes(\n scopes: readonly string[] | undefined,\n defaults: readonly string[],\n): string[] {\n const requested = scopes === undefined || scopes.length === 0 ? defaults : scopes;\n return [...new Set(requested.map((scope) => scope.trim()).filter(Boolean))];\n}\n\n/*** Creates a PKCE verifier and its S256 challenge from an injected secure byte source. */\nexport function createPkcePair(generateRandomBytes: SupabaseAuthRandomBytes): {\n challenge: string;\n verifier: string;\n} {\n const randomValue = generateRandomBytes(PKCE_RANDOM_BYTE_COUNT);\n if (!isBytes(randomValue) || randomValue.length !== PKCE_RANDOM_BYTE_COUNT) {\n throw new TypeError('OAuth random byte source returned an invalid value.');\n }\n const verifier = bytesToBase64Url(randomValue);\n return { challenge: bytesToBase64Url(sha256(utf8ToBytes(verifier))), verifier };\n}\n\n/*** Creates the public Supabase PKCE authorization URL. */\nexport function createAuthorizationUrl(input: {\n baseUrl: string;\n challenge: string;\n provider: SupabaseOAuthProviderId;\n queryParams: Readonly<Record<string, string>>;\n redirectUri: string;\n scopes: readonly string[];\n}): string {\n const url = new URL(`${input.baseUrl.replace(/\\/+$/u, '')}/auth/v1/authorize`);\n const parameters = [\n ['provider', input.provider],\n ['redirect_to', input.redirectUri],\n ['code_challenge', input.challenge],\n ['code_challenge_method', 's256'],\n ...Object.entries(input.queryParams),\n ] as const;\n if (input.scopes.length > 0) url.searchParams.set('scopes', input.scopes.join(' '));\n for (const [key, value] of parameters) url.searchParams.set(key, value);\n return url.toString();\n}\n\n/*** Exchanges a Supabase PKCE authorization code through the public HTTP contract. */\nexport function exchangeAuthorizationCode(input: {\n anonKey: string;\n baseUrl: string;\n code: string;\n fetch: SupabaseAuthFetch;\n verifier: string;\n}): Promise<Response> {\n return input.fetch(`${input.baseUrl.replace(/\\/+$/u, '')}/auth/v1/token?grant_type=pkce`, {\n method: 'POST',\n headers: {\n apikey: input.anonKey,\n Authorization: `Bearer ${input.anonKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ auth_code: input.code, code_verifier: input.verifier }),\n });\n}\n\nfunction bytesToBase64Url(bytes: Uint8Array): string {\n let encoded = '';\n for (let index = 0; index < bytes.length; index += 3)\n encoded += encodeBase64Triplet(bytes, index);\n return encoded;\n}\n\nfunction encodeBase64Triplet(bytes: Uint8Array, index: number): string {\n const first = bytes.at(index) ?? 0;\n const second = bytes.at(index + 1);\n const third = bytes.at(index + 2);\n const encoded = [\n BASE64_URL_ALPHABET.at(first >> 2) ?? '',\n BASE64_URL_ALPHABET.at(((first & 0b11) << 4) | ((second ?? 0) >> 4)) ?? '',\n second === undefined\n ? ''\n : (BASE64_URL_ALPHABET.at(((second & 0b1111) << 2) | ((third ?? 0) >> 6)) ?? ''),\n third === undefined ? '' : (BASE64_URL_ALPHABET.at(third & 0b11_1111) ?? ''),\n ];\n return encoded.join('');\n}\n\nfunction parseAbsoluteUrl(value: string): URL | null {\n try {\n return new URL(value);\n } catch {\n return null;\n }\n}\n\nfunction isPermittedRedirect(url: URL): boolean {\n return (\n !DANGEROUS_PROTOCOLS.has(url.protocol) &&\n url.username.length === 0 &&\n url.password.length === 0 &&\n url.search.length === 0 &&\n url.hash.length === 0 &&\n (url.protocol !== 'http:' || isLocalhost(url.hostname))\n );\n}\n\nfunction invalidRedirect(provider: SupabaseOAuthProviderId, message: string) {\n return {\n ok: false as const,\n result: oauthStartError('invalid_redirect_uri', message, provider, true),\n };\n}\n\nfunction isLocalhost(hostname: string): boolean {\n return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';\n}\n"]}
|