@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.
- 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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fabio Gartenmann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- markdownlint-disable MD013 MD033 -->
|
|
2
|
+
<!-- This file is generated by Paradox. Do not edit manually. -->
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
# SUPABASE AUTH
|
|
5
|
+
|
|
6
|
+
        
|
|
7
|
+
|
|
8
|
+
Supabase adapter implementing the provider-neutral Ankhorage auth port.
|
|
9
|
+
|
|
10
|
+
## Generated documentation
|
|
11
|
+
|
|
12
|
+
- [Interactive documentation app](./paradox/index.html)
|
|
13
|
+
- [Public API reference](./paradox/exports.md)
|
|
14
|
+
- [Component registry](./paradox/components.md)
|
|
15
|
+
- [Architecture overview](./paradox/diagrams/architecture-overview.mmd)
|
|
16
|
+
- [Module relationships](./paradox/diagrams/module-relationships.mmd)
|
|
17
|
+
- [Export graph](./paradox/diagrams/export-graph.mmd)
|
|
18
|
+
- [createSupabaseOAuthProfileVerifier sequence](./paradox/diagrams/sequences/create-supabase-oauth-profile-verifier.mmd)
|
|
19
|
+
- [getSupabaseOAuthProviderDefinition sequence](./paradox/diagrams/sequences/get-supabase-oauth-provider-definition.mmd)
|
|
20
|
+
- [materializeSupabaseOAuthEnvironment sequence](./paradox/diagrams/sequences/materialize-supabase-oauth-environment.mmd)
|
|
21
|
+
- [normalizeSupabaseAuthProfileVerificationConfig sequence](./paradox/diagrams/sequences/normalize-supabase-auth-profile-verification-config.mmd)
|
|
22
|
+
- [resolveSupabaseOAuthSetupPlan sequence](./paradox/diagrams/sequences/resolve-supabase-oauth-setup-plan.mmd)
|
|
23
|
+
- [validateSupabaseOAuthSecretPayload sequence](./paradox/diagrams/sequences/validate-supabase-oauth-secret-payload.mmd)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type SupabaseOAuthProviderId } from './oauthProviderDefinitions.js';
|
|
2
|
+
import type { SupabaseAuthConfig, SupabaseAuthFetch, SupabaseAuthProfileVerificationConfig, SupabaseAuthRandomBytes, SupabaseAuthStorage, SupabaseOAuthLifecycleObserver } from './types.js';
|
|
3
|
+
export interface RequiredSupabaseAuthConfig {
|
|
4
|
+
readonly url: string;
|
|
5
|
+
readonly anonKey: string;
|
|
6
|
+
readonly fetch: SupabaseAuthFetch;
|
|
7
|
+
readonly storage?: SupabaseAuthStorage;
|
|
8
|
+
readonly storageKey: string;
|
|
9
|
+
readonly oauthProviders: SupabaseOAuthProviderId[];
|
|
10
|
+
readonly oauthRandomBytes?: SupabaseAuthRandomBytes;
|
|
11
|
+
readonly profileVerification?: SupabaseAuthProfileVerificationConfig;
|
|
12
|
+
readonly onOAuthLifecycleEvent?: SupabaseOAuthLifecycleObserver;
|
|
13
|
+
}
|
|
14
|
+
export declare function validateSupabaseAuthConfig(config: SupabaseAuthConfig): RequiredSupabaseAuthConfig;
|
|
15
|
+
//# sourceMappingURL=authConfiguration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authConfiguration.d.ts","sourceRoot":"","sources":["../src/authConfiguration.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,qCAAqC,EACrC,uBAAuB,EACvB,mBAAmB,EACnB,8BAA8B,EAC/B,MAAM,YAAY,CAAC;AAIpB,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,uBAAuB,EAAE,CAAC;IACnD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IACpD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,qCAAqC,CAAC;IACrE,QAAQ,CAAC,qBAAqB,CAAC,EAAE,8BAA8B,CAAC;CACjE;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,kBAAkB,GAAG,0BAA0B,CAwBjG"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { isSupabaseOAuthProviderId, } from './oauthProviderDefinitions.js';
|
|
2
|
+
import { normalizeSupabaseAuthProfileVerificationConfig } from './profileVerification.js';
|
|
3
|
+
const DEFAULT_STORAGE_KEY = 'ankhorage.supabase-auth.session';
|
|
4
|
+
export function validateSupabaseAuthConfig(config) {
|
|
5
|
+
const url = normalizeUrl(config.url);
|
|
6
|
+
const anonKey = config.anonKey.trim();
|
|
7
|
+
if (anonKey.length === 0)
|
|
8
|
+
throw new TypeError('Supabase anon key is required.');
|
|
9
|
+
const oauthProviders = normalizeOAuthProviders(config);
|
|
10
|
+
validateOAuthConfiguration(config, oauthProviders);
|
|
11
|
+
const configuredStorageKey = config.storageKey?.trim();
|
|
12
|
+
return {
|
|
13
|
+
url,
|
|
14
|
+
anonKey,
|
|
15
|
+
fetch: requireFetch(config.fetch),
|
|
16
|
+
storage: config.storage,
|
|
17
|
+
storageKey: configuredStorageKey === undefined || configuredStorageKey.length === 0
|
|
18
|
+
? DEFAULT_STORAGE_KEY
|
|
19
|
+
: configuredStorageKey,
|
|
20
|
+
oauthProviders,
|
|
21
|
+
oauthRandomBytes: config.oauthRandomBytes,
|
|
22
|
+
profileVerification: config.profileVerification === undefined
|
|
23
|
+
? undefined
|
|
24
|
+
: normalizeSupabaseAuthProfileVerificationConfig(config.profileVerification),
|
|
25
|
+
onOAuthLifecycleEvent: config.onOAuthLifecycleEvent,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function normalizeUrl(rawUrl) {
|
|
29
|
+
const url = rawUrl.trim();
|
|
30
|
+
if (url.length === 0)
|
|
31
|
+
throw new TypeError('Supabase Auth URL is required.');
|
|
32
|
+
try {
|
|
33
|
+
new URL(url);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new TypeError('Supabase Auth URL must be a valid URL.');
|
|
37
|
+
}
|
|
38
|
+
return url.replace(/\/+$/u, '');
|
|
39
|
+
}
|
|
40
|
+
function normalizeOAuthProviders(config) {
|
|
41
|
+
const providers = [];
|
|
42
|
+
for (const rawProvider of config.oauthProviders ?? []) {
|
|
43
|
+
const provider = rawProvider.trim();
|
|
44
|
+
if (!isSupabaseOAuthProviderId(provider)) {
|
|
45
|
+
throw new TypeError(`Supabase OAuth provider "${provider}" is not supported.`);
|
|
46
|
+
}
|
|
47
|
+
if (!providers.includes(provider))
|
|
48
|
+
providers.push(provider);
|
|
49
|
+
}
|
|
50
|
+
return providers;
|
|
51
|
+
}
|
|
52
|
+
function validateOAuthConfiguration(config, providers) {
|
|
53
|
+
if (providers.length > 0 && config.storage === undefined) {
|
|
54
|
+
throw new TypeError('Supabase OAuth PKCE requires persistent auth storage.');
|
|
55
|
+
}
|
|
56
|
+
if (config.profileVerification !== undefined && providers.length === 0) {
|
|
57
|
+
throw new TypeError('Supabase OAuth profile verification requires an OAuth provider.');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function requireFetch(fetchImplementation = createDefaultFetch()) {
|
|
61
|
+
if (typeof fetchImplementation !== 'function') {
|
|
62
|
+
throw new TypeError('A fetch implementation is required to use Supabase Auth.');
|
|
63
|
+
}
|
|
64
|
+
return fetchImplementation;
|
|
65
|
+
}
|
|
66
|
+
function createDefaultFetch() {
|
|
67
|
+
return (input, init) => globalThis.fetch(input, init);
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=authConfiguration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authConfiguration.js","sourceRoot":"","sources":["../src/authConfiguration.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthOAuthProviderId } from '@ankhorage/contracts/auth';\n\nimport {\n isSupabaseOAuthProviderId,\n type SupabaseOAuthProviderId,\n} from './oauthProviderDefinitions.js';\nimport { normalizeSupabaseAuthProfileVerificationConfig } from './profileVerification.js';\nimport type {\n SupabaseAuthConfig,\n SupabaseAuthFetch,\n SupabaseAuthProfileVerificationConfig,\n SupabaseAuthRandomBytes,\n SupabaseAuthStorage,\n SupabaseOAuthLifecycleObserver,\n} from './types.js';\n\nconst DEFAULT_STORAGE_KEY = 'ankhorage.supabase-auth.session';\n\nexport interface RequiredSupabaseAuthConfig {\n readonly url: string;\n readonly anonKey: string;\n readonly fetch: SupabaseAuthFetch;\n readonly storage?: SupabaseAuthStorage;\n readonly storageKey: string;\n readonly oauthProviders: SupabaseOAuthProviderId[];\n readonly oauthRandomBytes?: SupabaseAuthRandomBytes;\n readonly profileVerification?: SupabaseAuthProfileVerificationConfig;\n readonly onOAuthLifecycleEvent?: SupabaseOAuthLifecycleObserver;\n}\n\nexport function validateSupabaseAuthConfig(config: SupabaseAuthConfig): RequiredSupabaseAuthConfig {\n const url = normalizeUrl(config.url);\n const anonKey = config.anonKey.trim();\n if (anonKey.length === 0) throw new TypeError('Supabase anon key is required.');\n const oauthProviders = normalizeOAuthProviders(config);\n validateOAuthConfiguration(config, oauthProviders);\n const configuredStorageKey = config.storageKey?.trim();\n return {\n url,\n anonKey,\n fetch: requireFetch(config.fetch),\n storage: config.storage,\n storageKey:\n configuredStorageKey === undefined || configuredStorageKey.length === 0\n ? DEFAULT_STORAGE_KEY\n : configuredStorageKey,\n oauthProviders,\n oauthRandomBytes: config.oauthRandomBytes,\n profileVerification:\n config.profileVerification === undefined\n ? undefined\n : normalizeSupabaseAuthProfileVerificationConfig(config.profileVerification),\n onOAuthLifecycleEvent: config.onOAuthLifecycleEvent,\n };\n}\n\nfunction normalizeUrl(rawUrl: string): string {\n const url = rawUrl.trim();\n if (url.length === 0) throw new TypeError('Supabase Auth URL is required.');\n try {\n new URL(url);\n } catch {\n throw new TypeError('Supabase Auth URL must be a valid URL.');\n }\n return url.replace(/\\/+$/u, '');\n}\n\nfunction normalizeOAuthProviders(config: SupabaseAuthConfig): SupabaseOAuthProviderId[] {\n const providers: SupabaseOAuthProviderId[] = [];\n for (const rawProvider of config.oauthProviders ?? []) {\n const provider: AuthOAuthProviderId = rawProvider.trim();\n if (!isSupabaseOAuthProviderId(provider)) {\n throw new TypeError(`Supabase OAuth provider \"${provider}\" is not supported.`);\n }\n if (!providers.includes(provider)) providers.push(provider);\n }\n return providers;\n}\n\nfunction validateOAuthConfiguration(\n config: SupabaseAuthConfig,\n providers: readonly SupabaseOAuthProviderId[],\n): void {\n if (providers.length > 0 && config.storage === undefined) {\n throw new TypeError('Supabase OAuth PKCE requires persistent auth storage.');\n }\n if (config.profileVerification !== undefined && providers.length === 0) {\n throw new TypeError('Supabase OAuth profile verification requires an OAuth provider.');\n }\n}\n\nfunction requireFetch(fetchImplementation = createDefaultFetch()): SupabaseAuthFetch {\n if (typeof fetchImplementation !== 'function') {\n throw new TypeError('A fetch implementation is required to use Supabase Auth.');\n }\n return fetchImplementation;\n}\n\nfunction createDefaultFetch(): SupabaseAuthFetch {\n return (input, init) => globalThis.fetch(input, init);\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AuthPort } from '@ankhorage/auth';
|
|
2
|
+
import type { AuthOperationContext } from './authSessionContext.js';
|
|
3
|
+
/*** Create the Supabase sign-in implementation for the neutral authentication port. */
|
|
4
|
+
export declare function createSignInOperation(context: AuthOperationContext): AuthPort['signIn'];
|
|
5
|
+
/*** Create the Supabase sign-up implementation for the neutral authentication port. */
|
|
6
|
+
export declare function createSignUpOperation(context: AuthOperationContext): AuthPort['signUp'];
|
|
7
|
+
export declare function createPasswordResetOperation(context: AuthOperationContext): AuthPort['requestPasswordReset'];
|
|
8
|
+
/*** Create the Supabase OTP verification implementation for the neutral authentication port. */
|
|
9
|
+
export declare function createVerifyOtpOperation(context: AuthOperationContext): AuthPort['verifyOtp'];
|
|
10
|
+
//# sourceMappingURL=authPasswordOperations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authPasswordOperations.d.ts","sourceRoot":"","sources":["../src/authPasswordOperations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAWhD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAIpE,uFAAuF;AACvF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAavF;AAED,uFAAuF;AACvF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,oBAAoB,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAcvF;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,oBAAoB,GAC5B,QAAQ,CAAC,sBAAsB,CAAC,CAelC;AAED,gGAAgG;AAChG,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,oBAAoB,GAAG,QAAQ,CAAC,WAAW,CAAC,CAc7F"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createAuthError, mapNetworkError, mapSupabaseError, readResponseBody } from './errors.js';
|
|
2
|
+
import { normalizeSupabaseSession, normalizeSupabaseUser } from './session.js';
|
|
3
|
+
/*** Create the Supabase sign-in implementation for the neutral authentication port. */
|
|
4
|
+
export function createSignInOperation(context) {
|
|
5
|
+
return async (input) => {
|
|
6
|
+
const credentials = passwordCredentials(input.identifier, input.password);
|
|
7
|
+
if (!credentials.ok)
|
|
8
|
+
return credentials;
|
|
9
|
+
try {
|
|
10
|
+
const response = await context.request('token?grant_type=password', {
|
|
11
|
+
body: credentials.data,
|
|
12
|
+
});
|
|
13
|
+
return await context.handleSessionResponse(response);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return { ok: false, error: mapNetworkError(error) };
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/*** Create the Supabase sign-up implementation for the neutral authentication port. */
|
|
21
|
+
export function createSignUpOperation(context) {
|
|
22
|
+
return async (input) => {
|
|
23
|
+
const credentials = passwordCredentials(input.identifier, input.password);
|
|
24
|
+
if (!credentials.ok)
|
|
25
|
+
return credentials;
|
|
26
|
+
try {
|
|
27
|
+
const response = await context.request('signup', {
|
|
28
|
+
body: { ...credentials.data, ...metadataBody(input.profile, input.metadata) },
|
|
29
|
+
redirectTo: input.redirectTo,
|
|
30
|
+
});
|
|
31
|
+
return await handleSignUpResponse(context, response);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return { ok: false, error: mapNetworkError(error) };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function createPasswordResetOperation(context) {
|
|
39
|
+
return async (input) => {
|
|
40
|
+
const email = passwordResetEmail(input);
|
|
41
|
+
if (!email.ok)
|
|
42
|
+
return email;
|
|
43
|
+
try {
|
|
44
|
+
const response = await context.request('recover', {
|
|
45
|
+
body: { email: email.data },
|
|
46
|
+
redirectTo: input.redirectTo,
|
|
47
|
+
});
|
|
48
|
+
if (response.ok)
|
|
49
|
+
return { ok: true };
|
|
50
|
+
return { ok: false, error: mapSupabaseError(response, await readResponseBody(response)) };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return { ok: false, error: mapNetworkError(error) };
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/*** Create the Supabase OTP verification implementation for the neutral authentication port. */
|
|
58
|
+
export function createVerifyOtpOperation(context) {
|
|
59
|
+
return async (input) => {
|
|
60
|
+
const body = otpBody(input);
|
|
61
|
+
if (!body.ok)
|
|
62
|
+
return body;
|
|
63
|
+
try {
|
|
64
|
+
const response = await context.request('verify', {
|
|
65
|
+
body: body.data,
|
|
66
|
+
redirectTo: input.redirectTo,
|
|
67
|
+
});
|
|
68
|
+
return await context.handleSessionResponse(response);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return { ok: false, error: mapNetworkError(error) };
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async function handleSignUpResponse(context, response) {
|
|
76
|
+
const body = await readResponseBody(response);
|
|
77
|
+
if (!response.ok)
|
|
78
|
+
return { ok: false, error: mapSupabaseError(response, body) };
|
|
79
|
+
const session = normalizeSupabaseSession(body);
|
|
80
|
+
if (session !== null) {
|
|
81
|
+
const error = await context.persistSessionSafely(session);
|
|
82
|
+
return error === null ? { ok: true, data: session } : { ok: false, error };
|
|
83
|
+
}
|
|
84
|
+
const user = normalizeSupabaseUser(isRecord(body) && 'user' in body ? body.user : body);
|
|
85
|
+
return user === null
|
|
86
|
+
? {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: createAuthError('provider_error', 'Supabase returned an invalid sign-up.', body),
|
|
89
|
+
}
|
|
90
|
+
: { ok: true, data: user };
|
|
91
|
+
}
|
|
92
|
+
function passwordCredentials(identifier, password) {
|
|
93
|
+
const passwordError = validatePassword(password);
|
|
94
|
+
if (passwordError !== null)
|
|
95
|
+
return { ok: false, error: passwordError };
|
|
96
|
+
const body = identifierBody(identifier);
|
|
97
|
+
return body.ok ? { ok: true, data: { ...body.data, password } } : body;
|
|
98
|
+
}
|
|
99
|
+
function passwordResetEmail(input) {
|
|
100
|
+
if (input.identifier.kind !== 'email') {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
error: createAuthError('unsupported_identifier', 'Password reset supports email only.'),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const value = input.identifier.value.trim();
|
|
107
|
+
return value.length === 0
|
|
108
|
+
? {
|
|
109
|
+
ok: false,
|
|
110
|
+
error: createAuthError('missing_identifier', 'An auth identifier is required.'),
|
|
111
|
+
}
|
|
112
|
+
: { ok: true, data: value };
|
|
113
|
+
}
|
|
114
|
+
function otpBody(input) {
|
|
115
|
+
const token = input.token.trim();
|
|
116
|
+
if (token.length === 0) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
error: createAuthError('validation_error', 'An OTP token is required.'),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const identifier = identifierBody(input.identifier);
|
|
123
|
+
return identifier.ok
|
|
124
|
+
? {
|
|
125
|
+
ok: true,
|
|
126
|
+
data: {
|
|
127
|
+
...identifier.data,
|
|
128
|
+
token,
|
|
129
|
+
type: input.identifier.kind === 'phone' ? 'sms' : 'email',
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
: identifier;
|
|
133
|
+
}
|
|
134
|
+
function validatePassword(password) {
|
|
135
|
+
return password === undefined || password.length === 0
|
|
136
|
+
? createAuthError('missing_password', 'A password is required.')
|
|
137
|
+
: null;
|
|
138
|
+
}
|
|
139
|
+
function identifierBody(identifier) {
|
|
140
|
+
const value = identifier.value.trim();
|
|
141
|
+
if (value.length === 0) {
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
error: createAuthError('missing_identifier', 'An auth identifier is required.'),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (identifier.kind === 'email')
|
|
148
|
+
return { ok: true, data: { email: value } };
|
|
149
|
+
if (identifier.kind === 'phone')
|
|
150
|
+
return { ok: true, data: { phone: value } };
|
|
151
|
+
return {
|
|
152
|
+
ok: false,
|
|
153
|
+
error: createAuthError('unsupported_identifier', 'Supabase supports email and phone.'),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function metadataBody(profile, metadata) {
|
|
157
|
+
const data = { ...(profile ?? {}), ...(metadata ?? {}) };
|
|
158
|
+
return Object.keys(data).length > 0 ? { data } : {};
|
|
159
|
+
}
|
|
160
|
+
function isRecord(value) {
|
|
161
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=authPasswordOperations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authPasswordOperations.js","sourceRoot":"","sources":["../src/authPasswordOperations.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnG,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE/E,uFAAuF;AACvF,MAAM,UAAU,qBAAqB,CAAC,OAA6B;IACjE,OAAO,KAAK,EAAE,KAAkB,EAAoC,EAAE;QACpE,MAAM,WAAW,GAAG,mBAAmB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC1E,IAAI,CAAC,WAAW,CAAC,EAAE;YAAE,OAAO,WAAW,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,2BAA2B,EAAE;gBAClE,IAAI,EAAE,WAAW,CAAC,IAAI;aACvB,CAAC,CAAC;YACH,OAAO,MAAM,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,qBAAqB,CAAC,OAA6B;IACjE,OAAO,KAAK,EAAE,KAAkB,EAA+C,EAAE;QAC/E,MAAM,WAAW,GAAG,mBAAmB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC1E,IAAI,CAAC,WAAW,CAAC,EAAE;YAAE,OAAO,WAAW,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE;gBAC/C,IAAI,EAAE,EAAE,GAAG,WAAW,CAAC,IAAI,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE;gBAC7E,UAAU,EAAE,KAAK,CAAC,UAAU;aAC7B,CAAC,CAAC;YACH,OAAO,MAAM,oBAAoB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,OAA6B;IAE7B,OAAO,KAAK,EAAE,KAAyB,EAAuB,EAAE;QAC9D,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE;gBAChD,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE;gBAC3B,UAAU,EAAE,KAAK,CAAC,UAAU;aAC7B,CAAC,CAAC;YACH,IAAI,QAAQ,CAAC,EAAE;gBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;YACrC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,CAAC,QAAQ,EAAE,MAAM,gBAAgB,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC5F,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,wBAAwB,CAAC,OAA6B;IACpE,OAAO,KAAK,EAAE,KAAqB,EAAoC,EAAE;QACvE,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,UAAU,EAAE,KAAK,CAAC,UAAU;aAC7B,CAAC,CAAC;YACH,OAAO,MAAM,OAAO,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,oBAAoB,CACjC,OAA6B,EAC7B,QAAkB;IAElB,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;IAChF,MAAM,OAAO,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAC1D,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC7E,CAAC;IACD,MAAM,IAAI,GAAG,qBAAqB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACxF,OAAO,IAAI,KAAK,IAAI;QAClB,CAAC,CAAC;YACE,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,eAAe,CAAC,gBAAgB,EAAE,uCAAuC,EAAE,IAAI,CAAC;SACxF;QACH,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC/B,CAAC;AAED,SAAS,mBAAmB,CAAC,UAAqC,EAAE,QAA4B;IAC9F,MAAM,aAAa,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,aAAa,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAc,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;IAChF,MAAM,IAAI,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IACxC,OAAO,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAa,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAClF,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAyB;IACnD,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACtC,OAAO;YACL,EAAE,EAAE,KAAc;YAClB,KAAK,EAAE,eAAe,CAAC,wBAAwB,EAAE,qCAAqC,CAAC;SACxF,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC5C,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;QACvB,CAAC,CAAC;YACE,EAAE,EAAE,KAAc;YAClB,KAAK,EAAE,eAAe,CAAC,oBAAoB,EAAE,iCAAiC,CAAC;SAChF;QACH,CAAC,CAAC,EAAE,EAAE,EAAE,IAAa,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzC,CAAC;AAED,SAAS,OAAO,CAAC,KAAqB;IACpC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,EAAE,EAAE,KAAc;YAClB,KAAK,EAAE,eAAe,CAAC,kBAAkB,EAAE,2BAA2B,CAAC;SACxE,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACpD,OAAO,UAAU,CAAC,EAAE;QAClB,CAAC,CAAC;YACE,EAAE,EAAE,IAAa;YACjB,IAAI,EAAE;gBACJ,GAAG,UAAU,CAAC,IAAI;gBAClB,KAAK;gBACL,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO;aAC1D;SACF;QACH,CAAC,CAAC,UAAU,CAAC;AACjB,CAAC;AAED,SAAS,gBAAgB,CAAC,QAA4B;IACpD,OAAO,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QACpD,CAAC,CAAC,eAAe,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;QAChE,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,SAAS,cAAc,CAAC,UAAqC;IAC3D,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,EAAE,EAAE,KAAc;YAClB,KAAK,EAAE,eAAe,CAAC,oBAAoB,EAAE,iCAAiC,CAAC;SAChF,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,IAAa,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;IACtF,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,IAAa,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;IACtF,OAAO;QACL,EAAE,EAAE,KAAc;QAClB,KAAK,EAAE,eAAe,CAAC,wBAAwB,EAAE,oCAAoC,CAAC;KACvF,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CACnB,OAA4C,EAC5C,QAA6C;IAE7C,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;IACzD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACtD,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import type { AuthPort } from '@ankhorage/auth';\nimport type {\n AuthResult,\n AuthSession,\n AuthUser,\n PasswordResetInput,\n SignInInput,\n SignUpInput,\n VerifyOtpInput,\n} from '@ankhorage/contracts/auth';\n\nimport type { AuthOperationContext } from './authSessionContext.js';\nimport { createAuthError, mapNetworkError, mapSupabaseError, readResponseBody } from './errors.js';\nimport { normalizeSupabaseSession, normalizeSupabaseUser } from './session.js';\n\n/*** Create the Supabase sign-in implementation for the neutral authentication port. */\nexport function createSignInOperation(context: AuthOperationContext): AuthPort['signIn'] {\n return async (input: SignInInput): Promise<AuthResult<AuthSession>> => {\n const credentials = passwordCredentials(input.identifier, input.password);\n if (!credentials.ok) return credentials;\n try {\n const response = await context.request('token?grant_type=password', {\n body: credentials.data,\n });\n return await context.handleSessionResponse(response);\n } catch (error) {\n return { ok: false, error: mapNetworkError(error) };\n }\n };\n}\n\n/*** Create the Supabase sign-up implementation for the neutral authentication port. */\nexport function createSignUpOperation(context: AuthOperationContext): AuthPort['signUp'] {\n return async (input: SignUpInput): Promise<AuthResult<AuthSession | AuthUser>> => {\n const credentials = passwordCredentials(input.identifier, input.password);\n if (!credentials.ok) return credentials;\n try {\n const response = await context.request('signup', {\n body: { ...credentials.data, ...metadataBody(input.profile, input.metadata) },\n redirectTo: input.redirectTo,\n });\n return await handleSignUpResponse(context, response);\n } catch (error) {\n return { ok: false, error: mapNetworkError(error) };\n }\n };\n}\n\nexport function createPasswordResetOperation(\n context: AuthOperationContext,\n): AuthPort['requestPasswordReset'] {\n return async (input: PasswordResetInput): Promise<AuthResult> => {\n const email = passwordResetEmail(input);\n if (!email.ok) return email;\n try {\n const response = await context.request('recover', {\n body: { email: email.data },\n redirectTo: input.redirectTo,\n });\n if (response.ok) return { ok: true };\n return { ok: false, error: mapSupabaseError(response, await readResponseBody(response)) };\n } catch (error) {\n return { ok: false, error: mapNetworkError(error) };\n }\n };\n}\n\n/*** Create the Supabase OTP verification implementation for the neutral authentication port. */\nexport function createVerifyOtpOperation(context: AuthOperationContext): AuthPort['verifyOtp'] {\n return async (input: VerifyOtpInput): Promise<AuthResult<AuthSession>> => {\n const body = otpBody(input);\n if (!body.ok) return body;\n try {\n const response = await context.request('verify', {\n body: body.data,\n redirectTo: input.redirectTo,\n });\n return await context.handleSessionResponse(response);\n } catch (error) {\n return { ok: false, error: mapNetworkError(error) };\n }\n };\n}\n\nasync function handleSignUpResponse(\n context: AuthOperationContext,\n response: Response,\n): Promise<AuthResult<AuthSession | AuthUser>> {\n const body = await readResponseBody(response);\n if (!response.ok) return { ok: false, error: mapSupabaseError(response, body) };\n const session = normalizeSupabaseSession(body);\n if (session !== null) {\n const error = await context.persistSessionSafely(session);\n return error === null ? { ok: true, data: session } : { ok: false, error };\n }\n const user = normalizeSupabaseUser(isRecord(body) && 'user' in body ? body.user : body);\n return user === null\n ? {\n ok: false,\n error: createAuthError('provider_error', 'Supabase returned an invalid sign-up.', body),\n }\n : { ok: true, data: user };\n}\n\nfunction passwordCredentials(identifier: SignInInput['identifier'], password: string | undefined) {\n const passwordError = validatePassword(password);\n if (passwordError !== null) return { ok: false as const, error: passwordError };\n const body = identifierBody(identifier);\n return body.ok ? { ok: true as const, data: { ...body.data, password } } : body;\n}\n\nfunction passwordResetEmail(input: PasswordResetInput) {\n if (input.identifier.kind !== 'email') {\n return {\n ok: false as const,\n error: createAuthError('unsupported_identifier', 'Password reset supports email only.'),\n };\n }\n const value = input.identifier.value.trim();\n return value.length === 0\n ? {\n ok: false as const,\n error: createAuthError('missing_identifier', 'An auth identifier is required.'),\n }\n : { ok: true as const, data: value };\n}\n\nfunction otpBody(input: VerifyOtpInput) {\n const token = input.token.trim();\n if (token.length === 0) {\n return {\n ok: false as const,\n error: createAuthError('validation_error', 'An OTP token is required.'),\n };\n }\n const identifier = identifierBody(input.identifier);\n return identifier.ok\n ? {\n ok: true as const,\n data: {\n ...identifier.data,\n token,\n type: input.identifier.kind === 'phone' ? 'sms' : 'email',\n },\n }\n : identifier;\n}\n\nfunction validatePassword(password: string | undefined) {\n return password === undefined || password.length === 0\n ? createAuthError('missing_password', 'A password is required.')\n : null;\n}\n\nfunction identifierBody(identifier: SignInInput['identifier']) {\n const value = identifier.value.trim();\n if (value.length === 0) {\n return {\n ok: false as const,\n error: createAuthError('missing_identifier', 'An auth identifier is required.'),\n };\n }\n if (identifier.kind === 'email') return { ok: true as const, data: { email: value } };\n if (identifier.kind === 'phone') return { ok: true as const, data: { phone: value } };\n return {\n ok: false as const,\n error: createAuthError('unsupported_identifier', 'Supabase supports email and phone.'),\n };\n}\n\nfunction metadataBody(\n profile: Record<string, unknown> | undefined,\n metadata: Record<string, unknown> | undefined,\n): { readonly data?: Record<string, unknown> } {\n const data = { ...(profile ?? {}), ...(metadata ?? {}) };\n return Object.keys(data).length > 0 ? { data } : {};\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AuthAdapterError, AuthResult, AuthSession } from '@ankhorage/contracts/auth';
|
|
2
|
+
import type { RequiredSupabaseAuthConfig } from './authConfiguration.js';
|
|
3
|
+
export interface AuthOperationContext {
|
|
4
|
+
readonly config: RequiredSupabaseAuthConfig;
|
|
5
|
+
readonly handleSessionResponse: (response: Response, options?: {
|
|
6
|
+
readonly clearExpiredSession?: boolean;
|
|
7
|
+
}) => Promise<AuthResult<AuthSession>>;
|
|
8
|
+
readonly persistSessionSafely: (session: AuthSession | null) => Promise<AuthAdapterError | null>;
|
|
9
|
+
readonly readStoredSession: () => Promise<AuthSession | null>;
|
|
10
|
+
readonly request: (path: string, options?: AuthRequestOptions) => Promise<Response>;
|
|
11
|
+
}
|
|
12
|
+
interface AuthRequestOptions {
|
|
13
|
+
readonly body?: Record<string, unknown>;
|
|
14
|
+
readonly accessToken?: string;
|
|
15
|
+
readonly redirectTo?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function createAuthOperationContext(config: RequiredSupabaseAuthConfig): AuthOperationContext;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=authSessionContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authSessionContext.d.ts","sourceRoot":"","sources":["../src/authSessionContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE3F,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAIzE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,CAC9B,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAA;KAAE,KACjD,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IACtC,QAAQ,CAAC,oBAAoB,EAAE,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACjG,QAAQ,CAAC,iBAAiB,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC9D,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CACrF;AAED,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,0BAA0B,GACjC,oBAAoB,CA8BtB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createAuthError, mapSupabaseError, readResponseBody } from './errors.js';
|
|
2
|
+
import { isAuthSessionExpired, normalizeSupabaseSession, parseStoredSession } from './session.js';
|
|
3
|
+
export function createAuthOperationContext(config) {
|
|
4
|
+
let currentSession = null;
|
|
5
|
+
let sessionLoaded = false;
|
|
6
|
+
const persistSession = async (session) => {
|
|
7
|
+
if (config.storage !== undefined) {
|
|
8
|
+
if (session === null)
|
|
9
|
+
await config.storage.removeItem(config.storageKey);
|
|
10
|
+
else
|
|
11
|
+
await config.storage.setItem(config.storageKey, JSON.stringify(session));
|
|
12
|
+
}
|
|
13
|
+
currentSession = session;
|
|
14
|
+
sessionLoaded = true;
|
|
15
|
+
};
|
|
16
|
+
const persistSessionSafely = createSafeSessionPersistence(persistSession);
|
|
17
|
+
const readStoredSession = async () => {
|
|
18
|
+
if (sessionLoaded)
|
|
19
|
+
return currentSession;
|
|
20
|
+
sessionLoaded = true;
|
|
21
|
+
if (config.storage === undefined)
|
|
22
|
+
return null;
|
|
23
|
+
currentSession = parseStoredSession(await config.storage.getItem(config.storageKey));
|
|
24
|
+
if (!isAuthSessionExpired(currentSession))
|
|
25
|
+
return currentSession;
|
|
26
|
+
currentSession = null;
|
|
27
|
+
await config.storage.removeItem(config.storageKey);
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
const request = createAuthRequest(config);
|
|
31
|
+
return {
|
|
32
|
+
config,
|
|
33
|
+
request,
|
|
34
|
+
persistSessionSafely,
|
|
35
|
+
readStoredSession,
|
|
36
|
+
handleSessionResponse: createSessionResponseHandler(persistSessionSafely),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function createAuthRequest(config) {
|
|
40
|
+
return async (path, options = {}) => {
|
|
41
|
+
const url = new URL(`${config.url}/auth/v1/${path}`);
|
|
42
|
+
if (options.redirectTo !== undefined)
|
|
43
|
+
url.searchParams.set('redirect_to', options.redirectTo);
|
|
44
|
+
return config.fetch(url.toString(), {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: createHeaders(config.anonKey, options.accessToken),
|
|
47
|
+
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function createSafeSessionPersistence(persistSession) {
|
|
52
|
+
return async (session) => {
|
|
53
|
+
try {
|
|
54
|
+
await persistSession(session);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return createAuthError('session_persistence_failed', session === null
|
|
59
|
+
? 'The persisted auth session could not be cleared.'
|
|
60
|
+
: 'The auth session could not be persisted.');
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function createSessionResponseHandler(persistSessionSafely) {
|
|
65
|
+
return async (response, options = {}) => {
|
|
66
|
+
const body = await readResponseBody(response);
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const error = mapSupabaseError(response, body);
|
|
69
|
+
if (options.clearExpiredSession === true && error.code === 'session_expired') {
|
|
70
|
+
const persistenceError = await persistSessionSafely(null);
|
|
71
|
+
if (persistenceError !== null)
|
|
72
|
+
return { ok: false, error: persistenceError };
|
|
73
|
+
}
|
|
74
|
+
return { ok: false, error };
|
|
75
|
+
}
|
|
76
|
+
const session = normalizeSupabaseSession(body);
|
|
77
|
+
if (session === null) {
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
error: createAuthError('provider_error', 'Supabase returned an invalid session.', body),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const persistenceError = await persistSessionSafely(session);
|
|
84
|
+
return persistenceError === null
|
|
85
|
+
? { ok: true, data: session }
|
|
86
|
+
: { ok: false, error: persistenceError };
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function createHeaders(anonKey, accessToken) {
|
|
90
|
+
return {
|
|
91
|
+
apikey: anonKey,
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
...(accessToken === undefined ? {} : { Authorization: `Bearer ${accessToken}` }),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=authSessionContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authSessionContext.js","sourceRoot":"","sources":["../src/authSessionContext.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthAdapterError, AuthResult, AuthSession } from '@ankhorage/contracts/auth';\n\nimport type { RequiredSupabaseAuthConfig } from './authConfiguration.js';\nimport { createAuthError, mapSupabaseError, readResponseBody } from './errors.js';\nimport { isAuthSessionExpired, normalizeSupabaseSession, parseStoredSession } from './session.js';\n\nexport interface AuthOperationContext {\n readonly config: RequiredSupabaseAuthConfig;\n readonly handleSessionResponse: (\n response: Response,\n options?: { readonly clearExpiredSession?: boolean },\n ) => Promise<AuthResult<AuthSession>>;\n readonly persistSessionSafely: (session: AuthSession | null) => Promise<AuthAdapterError | null>;\n readonly readStoredSession: () => Promise<AuthSession | null>;\n readonly request: (path: string, options?: AuthRequestOptions) => Promise<Response>;\n}\n\ninterface AuthRequestOptions {\n readonly body?: Record<string, unknown>;\n readonly accessToken?: string;\n readonly redirectTo?: string;\n}\n\nexport function createAuthOperationContext(\n config: RequiredSupabaseAuthConfig,\n): AuthOperationContext {\n let currentSession: AuthSession | null = null;\n let sessionLoaded = false;\n const persistSession = async (session: AuthSession | null): Promise<void> => {\n if (config.storage !== undefined) {\n if (session === null) await config.storage.removeItem(config.storageKey);\n else await config.storage.setItem(config.storageKey, JSON.stringify(session));\n }\n currentSession = session;\n sessionLoaded = true;\n };\n const persistSessionSafely = createSafeSessionPersistence(persistSession);\n const readStoredSession = async (): Promise<AuthSession | null> => {\n if (sessionLoaded) return currentSession;\n sessionLoaded = true;\n if (config.storage === undefined) return null;\n currentSession = parseStoredSession(await config.storage.getItem(config.storageKey));\n if (!isAuthSessionExpired(currentSession)) return currentSession;\n currentSession = null;\n await config.storage.removeItem(config.storageKey);\n return null;\n };\n const request = createAuthRequest(config);\n return {\n config,\n request,\n persistSessionSafely,\n readStoredSession,\n handleSessionResponse: createSessionResponseHandler(persistSessionSafely),\n };\n}\n\nfunction createAuthRequest(config: RequiredSupabaseAuthConfig) {\n return async (path: string, options: AuthRequestOptions = {}): Promise<Response> => {\n const url = new URL(`${config.url}/auth/v1/${path}`);\n if (options.redirectTo !== undefined) url.searchParams.set('redirect_to', options.redirectTo);\n return config.fetch(url.toString(), {\n method: 'POST',\n headers: createHeaders(config.anonKey, options.accessToken),\n body: options.body === undefined ? undefined : JSON.stringify(options.body),\n });\n };\n}\n\nfunction createSafeSessionPersistence(\n persistSession: (session: AuthSession | null) => Promise<void>,\n) {\n return async (session: AuthSession | null): Promise<AuthAdapterError | null> => {\n try {\n await persistSession(session);\n return null;\n } catch {\n return createAuthError(\n 'session_persistence_failed',\n session === null\n ? 'The persisted auth session could not be cleared.'\n : 'The auth session could not be persisted.',\n );\n }\n };\n}\n\nfunction createSessionResponseHandler(\n persistSessionSafely: AuthOperationContext['persistSessionSafely'],\n): AuthOperationContext['handleSessionResponse'] {\n return async (response, options = {}) => {\n const body = await readResponseBody(response);\n if (!response.ok) {\n const error = mapSupabaseError(response, body);\n if (options.clearExpiredSession === true && error.code === 'session_expired') {\n const persistenceError = await persistSessionSafely(null);\n if (persistenceError !== null) return { ok: false, error: persistenceError };\n }\n return { ok: false, error };\n }\n const session = normalizeSupabaseSession(body);\n if (session === null) {\n return {\n ok: false,\n error: createAuthError('provider_error', 'Supabase returned an invalid session.', body),\n };\n }\n const persistenceError = await persistSessionSafely(session);\n return persistenceError === null\n ? { ok: true, data: session }\n : { ok: false, error: persistenceError };\n };\n}\n\nfunction createHeaders(anonKey: string, accessToken?: string): Record<string, string> {\n return {\n apikey: anonKey,\n 'Content-Type': 'application/json',\n ...(accessToken === undefined ? {} : { Authorization: `Bearer ${accessToken}` }),\n };\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AuthPort } from '@ankhorage/auth';
|
|
2
|
+
import type { AuthOperationContext } from './authSessionContext.js';
|
|
3
|
+
/*** Create the Supabase sign-out implementation for the neutral authentication port. */
|
|
4
|
+
export declare function createSignOutOperation(context: AuthOperationContext): AuthPort['signOut'];
|
|
5
|
+
export declare function createGetSessionOperation(context: AuthOperationContext): AuthPort['getSession'];
|
|
6
|
+
export declare function createRefreshSessionOperation(context: AuthOperationContext): AuthPort['refreshSession'];
|
|
7
|
+
//# sourceMappingURL=authSessionOperations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authSessionOperations.d.ts","sourceRoot":"","sources":["../src/authSessionOperations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAGpE,wFAAwF;AACxF,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,oBAAoB,GAAG,QAAQ,CAAC,SAAS,CAAC,CAOzF;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,oBAAoB,GAAG,QAAQ,CAAC,YAAY,CAAC,CAc/F;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oBAAoB,GAC5B,QAAQ,CAAC,gBAAgB,CAAC,CAmB5B"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createAuthError, mapNetworkError, mapSupabaseError, readResponseBody } from './errors.js';
|
|
2
|
+
/*** Create the Supabase sign-out implementation for the neutral authentication port. */
|
|
3
|
+
export function createSignOutOperation(context) {
|
|
4
|
+
return async (input) => {
|
|
5
|
+
const providerError = await signOutProvider(context, input?.allDevices === true);
|
|
6
|
+
const persistenceError = await context.persistSessionSafely(null);
|
|
7
|
+
if (persistenceError !== null)
|
|
8
|
+
return { ok: false, error: persistenceError };
|
|
9
|
+
return providerError === null ? { ok: true } : { ok: false, error: providerError };
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function createGetSessionOperation(context) {
|
|
13
|
+
return async () => {
|
|
14
|
+
try {
|
|
15
|
+
return { ok: true, data: await context.readStoredSession() };
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
error: createAuthError('session_persistence_failed', 'The persisted auth session could not be read.'),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function createRefreshSessionOperation(context) {
|
|
26
|
+
return async () => {
|
|
27
|
+
const session = await readRefreshSession(context);
|
|
28
|
+
if (!session.ok)
|
|
29
|
+
return session;
|
|
30
|
+
if (session.data?.refreshToken === undefined) {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
error: createAuthError('missing_refresh_token', 'No refresh token is available.'),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const response = await context.request('token?grant_type=refresh_token', {
|
|
38
|
+
body: { refresh_token: session.data.refreshToken },
|
|
39
|
+
});
|
|
40
|
+
return await context.handleSessionResponse(response, { clearExpiredSession: true });
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return { ok: false, error: mapNetworkError(error) };
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function signOutProvider(context, allDevices) {
|
|
48
|
+
let session;
|
|
49
|
+
try {
|
|
50
|
+
session = await context.readStoredSession();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return createAuthError('session_persistence_failed', 'The persisted auth session could not be read before sign-out.');
|
|
54
|
+
}
|
|
55
|
+
if (session?.accessToken === undefined)
|
|
56
|
+
return null;
|
|
57
|
+
try {
|
|
58
|
+
const response = await context.request('logout', {
|
|
59
|
+
accessToken: session.accessToken,
|
|
60
|
+
body: allDevices ? { scope: 'global' } : undefined,
|
|
61
|
+
});
|
|
62
|
+
return response.ok ? null : mapSupabaseError(response, await readResponseBody(response));
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return mapNetworkError(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function readRefreshSession(context) {
|
|
69
|
+
try {
|
|
70
|
+
return { ok: true, data: await context.readStoredSession() };
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
error: createAuthError('session_persistence_failed', 'The persisted auth session could not be read for refresh.'),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=authSessionOperations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authSessionOperations.js","sourceRoot":"","sources":["../src/authSessionOperations.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthPort } from '@ankhorage/auth';\nimport type { AuthAdapterError, AuthResult, AuthSession } from '@ankhorage/contracts/auth';\n\nimport type { AuthOperationContext } from './authSessionContext.js';\nimport { createAuthError, mapNetworkError, mapSupabaseError, readResponseBody } from './errors.js';\n\n/*** Create the Supabase sign-out implementation for the neutral authentication port. */\nexport function createSignOutOperation(context: AuthOperationContext): AuthPort['signOut'] {\n return async (input): Promise<AuthResult> => {\n const providerError = await signOutProvider(context, input?.allDevices === true);\n const persistenceError = await context.persistSessionSafely(null);\n if (persistenceError !== null) return { ok: false, error: persistenceError };\n return providerError === null ? { ok: true } : { ok: false, error: providerError };\n };\n}\n\nexport function createGetSessionOperation(context: AuthOperationContext): AuthPort['getSession'] {\n return async (): Promise<AuthResult<AuthSession | null>> => {\n try {\n return { ok: true, data: await context.readStoredSession() };\n } catch {\n return {\n ok: false,\n error: createAuthError(\n 'session_persistence_failed',\n 'The persisted auth session could not be read.',\n ),\n };\n }\n };\n}\n\nexport function createRefreshSessionOperation(\n context: AuthOperationContext,\n): AuthPort['refreshSession'] {\n return async (): Promise<AuthResult<AuthSession | null>> => {\n const session = await readRefreshSession(context);\n if (!session.ok) return session;\n if (session.data?.refreshToken === undefined) {\n return {\n ok: false,\n error: createAuthError('missing_refresh_token', 'No refresh token is available.'),\n };\n }\n try {\n const response = await context.request('token?grant_type=refresh_token', {\n body: { refresh_token: session.data.refreshToken },\n });\n return await context.handleSessionResponse(response, { clearExpiredSession: true });\n } catch (error) {\n return { ok: false, error: mapNetworkError(error) };\n }\n };\n}\n\nasync function signOutProvider(\n context: AuthOperationContext,\n allDevices: boolean,\n): Promise<AuthAdapterError | null> {\n let session: AuthSession | null;\n try {\n session = await context.readStoredSession();\n } catch {\n return createAuthError(\n 'session_persistence_failed',\n 'The persisted auth session could not be read before sign-out.',\n );\n }\n if (session?.accessToken === undefined) return null;\n try {\n const response = await context.request('logout', {\n accessToken: session.accessToken,\n body: allDevices ? { scope: 'global' } : undefined,\n });\n return response.ok ? null : mapSupabaseError(response, await readResponseBody(response));\n } catch (error) {\n return mapNetworkError(error);\n }\n}\n\nasync function readRefreshSession(\n context: AuthOperationContext,\n): Promise<AuthResult<AuthSession | null>> {\n try {\n return { ok: true, data: await context.readStoredSession() };\n } catch {\n return {\n ok: false,\n error: createAuthError(\n 'session_persistence_failed',\n 'The persisted auth session could not be read for refresh.',\n ),\n };\n }\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AuthPort } from '@ankhorage/auth';
|
|
2
|
+
import type { SupabaseAuthConfig } from './types.js';
|
|
3
|
+
/*** Create a Supabase implementation of the provider-neutral authentication port. */
|
|
4
|
+
export declare function createSupabaseAuthAdapter(config: SupabaseAuthConfig): AuthPort;
|
|
5
|
+
//# sourceMappingURL=createSupabaseAuthAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createSupabaseAuthAdapter.d.ts","sourceRoot":"","sources":["../src/createSupabaseAuthAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAsBhD,OAAO,KAAK,EAAE,kBAAkB,EAAgC,MAAM,YAAY,CAAC;AAEnF,qFAAqF;AACrF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,kBAAkB,GAAG,QAAQ,CAe9E"}
|