@jskit-ai/auth-provider-supabase-core 0.1.4

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.
@@ -0,0 +1,24 @@
1
+ function displayNameFromEmail(email) {
2
+ const local = String(email || "").split("@")[0] || "user";
3
+ return local.slice(0, 120);
4
+ }
5
+
6
+ function resolveDisplayName(supabaseUser, fallbackEmail) {
7
+ const metadataDisplayName = String(supabaseUser?.user_metadata?.display_name || "").trim();
8
+ if (metadataDisplayName) {
9
+ return metadataDisplayName.slice(0, 120);
10
+ }
11
+
12
+ return displayNameFromEmail(fallbackEmail);
13
+ }
14
+
15
+ function resolveDisplayNameFromClaims(claims, fallbackEmail) {
16
+ const metadataDisplayName = String(claims?.user_metadata?.display_name || "").trim();
17
+ if (metadataDisplayName) {
18
+ return metadataDisplayName.slice(0, 120);
19
+ }
20
+
21
+ return displayNameFromEmail(fallbackEmail);
22
+ }
23
+
24
+ export { displayNameFromEmail, resolveDisplayName, resolveDisplayNameFromClaims };
@@ -0,0 +1,135 @@
1
+ import {
2
+ OAUTH_QUERY_PARAM_INTENT,
3
+ OAUTH_QUERY_PARAM_PROVIDER,
4
+ OAUTH_QUERY_PARAM_RETURN_TO
5
+ } from "@jskit-ai/auth-core/shared/oauthCallbackParams";
6
+ import { normalizeOAuthProviderList } from "@jskit-ai/auth-core/shared/oauthProviders";
7
+ import { normalizeOAuthProviderFromCatalog } from "./oauthProviderCatalog.js";
8
+ import { normalizeOAuthIntent, normalizeReturnToPath } from "@jskit-ai/auth-core/server/utils";
9
+
10
+ const PASSWORD_RESET_PATH = "reset-password";
11
+ const OAUTH_LOGIN_PATH = "auth/login";
12
+ const OAUTH_LOGIN_INTENT = "login";
13
+ const OAUTH_LINK_INTENT = "link";
14
+
15
+ function parseHttpUrl(rawValue, variableName) {
16
+ let parsedUrl;
17
+ try {
18
+ parsedUrl = new URL(rawValue);
19
+ } catch {
20
+ throw new Error(`${variableName} must be a valid absolute URL.`);
21
+ }
22
+
23
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
24
+ throw new Error(`${variableName} must start with http:// or https://.`);
25
+ }
26
+
27
+ return parsedUrl;
28
+ }
29
+
30
+ function buildPasswordResetRedirectUrl(options) {
31
+ const appPublicUrl = String(options.appPublicUrl || "").trim();
32
+
33
+ if (!appPublicUrl) {
34
+ throw new Error("APP_PUBLIC_URL is required to build password reset links.");
35
+ }
36
+
37
+ const baseUrl = parseHttpUrl(appPublicUrl, "APP_PUBLIC_URL");
38
+ if (!baseUrl.pathname.endsWith("/")) {
39
+ baseUrl.pathname = `${baseUrl.pathname}/`;
40
+ }
41
+ baseUrl.search = "";
42
+ baseUrl.hash = "";
43
+ return new URL(PASSWORD_RESET_PATH, baseUrl).toString();
44
+ }
45
+
46
+ function buildOtpLoginRedirectUrl(options) {
47
+ const appPublicUrl = String(options.appPublicUrl || "").trim();
48
+ const returnTo = normalizeReturnToPath(options.returnTo, { fallback: "" });
49
+
50
+ if (!appPublicUrl) {
51
+ throw new Error("APP_PUBLIC_URL is required to build OTP login redirects.");
52
+ }
53
+
54
+ const baseUrl = parseHttpUrl(appPublicUrl, "APP_PUBLIC_URL");
55
+ if (!baseUrl.pathname.endsWith("/")) {
56
+ baseUrl.pathname = `${baseUrl.pathname}/`;
57
+ }
58
+ baseUrl.search = "";
59
+ baseUrl.hash = "";
60
+ const redirectUrl = new URL(OAUTH_LOGIN_PATH, baseUrl);
61
+ if (returnTo) {
62
+ redirectUrl.searchParams.set("returnTo", returnTo);
63
+ }
64
+ return redirectUrl.toString();
65
+ }
66
+
67
+ function buildOAuthRedirectUrl(options) {
68
+ const appPublicUrl = String(options.appPublicUrl || "").trim();
69
+ const providerIds = normalizeOAuthProviderList(options.providerIds, { fallback: [] });
70
+ const provider = normalizeOAuthProviderFromCatalog(options.provider, {
71
+ providerIds,
72
+ fallback: null
73
+ });
74
+ const intent = normalizeOAuthIntent(options.intent, { fallback: OAUTH_LOGIN_INTENT });
75
+ const callbackPath = String(options.callbackPath || OAUTH_LOGIN_PATH).trim();
76
+ const returnTo = normalizeReturnToPath(options.returnTo, { fallback: "/" });
77
+
78
+ if (!appPublicUrl) {
79
+ throw new Error("APP_PUBLIC_URL is required to build OAuth login redirects.");
80
+ }
81
+
82
+ if (providerIds.length < 1) {
83
+ throw new Error("OAuth providers are not configured.");
84
+ }
85
+
86
+ if (!provider) {
87
+ throw new Error(`OAuth provider must be one of: ${providerIds.join(", ")}.`);
88
+ }
89
+
90
+ if (!callbackPath) {
91
+ throw new Error("OAuth callback path is required.");
92
+ }
93
+
94
+ const baseUrl = parseHttpUrl(appPublicUrl, "APP_PUBLIC_URL");
95
+ if (!baseUrl.pathname.endsWith("/")) {
96
+ baseUrl.pathname = `${baseUrl.pathname}/`;
97
+ }
98
+ baseUrl.search = "";
99
+ baseUrl.hash = "";
100
+
101
+ const redirectUrl = new URL(callbackPath, baseUrl);
102
+ redirectUrl.searchParams.set(OAUTH_QUERY_PARAM_PROVIDER, provider);
103
+ redirectUrl.searchParams.set(OAUTH_QUERY_PARAM_INTENT, intent);
104
+ if (returnTo) {
105
+ redirectUrl.searchParams.set(OAUTH_QUERY_PARAM_RETURN_TO, returnTo);
106
+ }
107
+ return redirectUrl.toString();
108
+ }
109
+
110
+ function buildOAuthLoginRedirectUrl(options) {
111
+ return buildOAuthRedirectUrl({
112
+ ...options,
113
+ intent: OAUTH_LOGIN_INTENT,
114
+ callbackPath: OAUTH_LOGIN_PATH
115
+ });
116
+ }
117
+
118
+ function buildOAuthLinkRedirectUrl(options) {
119
+ return buildOAuthRedirectUrl({
120
+ ...options,
121
+ intent: OAUTH_LINK_INTENT,
122
+ callbackPath: OAUTH_LOGIN_PATH
123
+ });
124
+ }
125
+
126
+ export {
127
+ parseHttpUrl,
128
+ buildPasswordResetRedirectUrl,
129
+ buildOtpLoginRedirectUrl,
130
+ normalizeOAuthIntent,
131
+ normalizeReturnToPath,
132
+ buildOAuthRedirectUrl,
133
+ buildOAuthLoginRedirectUrl,
134
+ buildOAuthLinkRedirectUrl
135
+ };
@@ -0,0 +1,9 @@
1
+ import { randomBytes } from "node:crypto";
2
+
3
+ function buildDisabledPasswordSecret() {
4
+ const randomSegment = randomBytes(24).toString("base64url");
5
+ // Supabase password updates follow bcrypt's 72-byte input limit.
6
+ return `disabled-A1!-${randomSegment}`;
7
+ }
8
+
9
+ export { buildDisabledPasswordSecret };
@@ -0,0 +1,20 @@
1
+ import { normalizePositiveInteger } from "@jskit-ai/kernel/shared/support/normalize";
2
+
3
+ function createAuthSessionEventsService() {
4
+ async function notifySessionChanged(options = {}) {
5
+ const actorId = normalizePositiveInteger(options?.context?.actor?.id);
6
+ if (!actorId) {
7
+ return null;
8
+ }
9
+
10
+ return {
11
+ id: actorId
12
+ };
13
+ }
14
+
15
+ return Object.freeze({
16
+ notifySessionChanged
17
+ });
18
+ }
19
+
20
+ export { createAuthSessionEventsService };
@@ -0,0 +1,2 @@
1
+ export { createService, __testables } from "./service.js";
2
+ export { authActions } from "./actions/auth.contributor.js";
@@ -0,0 +1,201 @@
1
+ import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
2
+
3
+ function createOauthFlows(deps) {
4
+ const {
5
+ ensureConfigured,
6
+ normalizeOAuthProviderInput,
7
+ normalizeReturnToPath,
8
+ buildOAuthLoginRedirectUrl,
9
+ appPublicUrl,
10
+ authOAuthDefaultProvider,
11
+ resolveOAuthProviderQueryParams = () => null,
12
+ getSupabaseClient,
13
+ mapAuthError,
14
+ setSessionFromRequestCookies,
15
+ buildOAuthLinkRedirectUrl,
16
+ parseOAuthCompletePayload,
17
+ validationError,
18
+ mapOAuthCallbackError,
19
+ mapRecoveryError,
20
+ syncProfileFromSupabaseUser,
21
+ resolveCurrentAuthContext,
22
+ buildOAuthMethodId,
23
+ findAuthMethodById,
24
+ findLinkedIdentityByProvider,
25
+ buildSecurityStatusFromAuthMethodsStatus
26
+ } = deps;
27
+
28
+ async function requestOAuthRedirectUrl(supabase, provider, redirectTo, requestFlow) {
29
+ let response;
30
+ try {
31
+ const queryParams = resolveOAuthProviderQueryParams(provider);
32
+ response = await requestFlow({
33
+ provider,
34
+ options: {
35
+ redirectTo,
36
+ queryParams: queryParams || undefined
37
+ }
38
+ });
39
+ } catch (error) {
40
+ throw mapAuthError(error, 500);
41
+ }
42
+
43
+ if (response.error || !response.data?.url) {
44
+ throw mapAuthError(response.error, 400);
45
+ }
46
+
47
+ return String(response.data.url);
48
+ }
49
+
50
+ async function oauthStart(payload = {}) {
51
+ ensureConfigured();
52
+
53
+ const provider = normalizeOAuthProviderInput(payload.provider || authOAuthDefaultProvider);
54
+ const returnTo = normalizeReturnToPath(payload.returnTo, { fallback: "/" });
55
+ const redirectTo = buildOAuthLoginRedirectUrl({
56
+ appPublicUrl,
57
+ provider,
58
+ returnTo
59
+ });
60
+ const supabase = getSupabaseClient();
61
+ const url = await requestOAuthRedirectUrl(supabase, provider, redirectTo, (input) =>
62
+ supabase.auth.signInWithOAuth(input)
63
+ );
64
+
65
+ return {
66
+ provider,
67
+ returnTo,
68
+ url
69
+ };
70
+ }
71
+
72
+ async function startProviderLink(request, payload = {}) {
73
+ ensureConfigured();
74
+
75
+ const supabase = getSupabaseClient();
76
+ const provider = normalizeOAuthProviderInput(payload.provider || authOAuthDefaultProvider);
77
+ const returnTo = normalizeReturnToPath(payload.returnTo, { fallback: "/" });
78
+ await setSessionFromRequestCookies(request, {
79
+ supabaseClient: supabase
80
+ });
81
+
82
+ if (typeof supabase.auth.linkIdentity !== "function") {
83
+ throw new AppError(500, "Supabase client does not support identity linking in this environment.");
84
+ }
85
+
86
+ const redirectTo = buildOAuthLinkRedirectUrl({
87
+ appPublicUrl,
88
+ provider,
89
+ returnTo
90
+ });
91
+ const url = await requestOAuthRedirectUrl(supabase, provider, redirectTo, (input) =>
92
+ supabase.auth.linkIdentity(input)
93
+ );
94
+
95
+ return {
96
+ provider,
97
+ returnTo,
98
+ url
99
+ };
100
+ }
101
+
102
+ async function oauthComplete(payload = {}) {
103
+ ensureConfigured();
104
+
105
+ const parsed = parseOAuthCompletePayload(payload);
106
+ if (Object.keys(parsed.fieldErrors).length > 0) {
107
+ throw validationError(parsed.fieldErrors);
108
+ }
109
+
110
+ if (parsed.errorCode) {
111
+ throw mapOAuthCallbackError(parsed.errorCode, parsed.errorDescription);
112
+ }
113
+
114
+ const supabase = getSupabaseClient();
115
+ let response;
116
+ try {
117
+ if (parsed.hasSessionPair) {
118
+ response = await supabase.auth.setSession({
119
+ access_token: parsed.accessToken,
120
+ refresh_token: parsed.refreshToken
121
+ });
122
+ } else {
123
+ response = await supabase.auth.exchangeCodeForSession(parsed.code);
124
+ }
125
+ } catch (error) {
126
+ throw mapRecoveryError(error);
127
+ }
128
+
129
+ if (response.error || !response.data?.session || !response.data?.user) {
130
+ throw mapRecoveryError(response.error);
131
+ }
132
+
133
+ const profile = await syncProfileFromSupabaseUser(response.data.user, response.data.user.email);
134
+
135
+ return {
136
+ provider: parsed.provider,
137
+ profile,
138
+ session: response.data.session
139
+ };
140
+ }
141
+
142
+ async function unlinkProvider(request, payload = {}) {
143
+ ensureConfigured();
144
+
145
+ const provider = normalizeOAuthProviderInput(payload.provider);
146
+ const supabase = getSupabaseClient();
147
+ if (typeof supabase.auth.unlinkIdentity !== "function") {
148
+ throw new AppError(500, "Supabase client does not support identity unlinking in this environment.");
149
+ }
150
+
151
+ await setSessionFromRequestCookies(request, {
152
+ supabaseClient: supabase
153
+ });
154
+ const current = await resolveCurrentAuthContext(request, {
155
+ supabaseClient: supabase
156
+ });
157
+ const methodId = buildOAuthMethodId(provider);
158
+ const providerMethod = findAuthMethodById(current.authMethodsStatus, methodId);
159
+ if (!providerMethod || !providerMethod.configured) {
160
+ throw validationError({
161
+ provider: `${provider} is not linked to this account.`
162
+ });
163
+ }
164
+
165
+ if (!providerMethod.canDisable) {
166
+ throw new AppError(409, "At least one sign-in method must remain enabled.");
167
+ }
168
+
169
+ const identity = findLinkedIdentityByProvider(current.user, provider);
170
+ if (!identity) {
171
+ throw new AppError(409, "Linked identity details could not be resolved for this provider.");
172
+ }
173
+
174
+ let response;
175
+ try {
176
+ response = await supabase.auth.unlinkIdentity(identity);
177
+ } catch (error) {
178
+ throw mapAuthError(error, 500);
179
+ }
180
+
181
+ if (response.error) {
182
+ throw mapAuthError(response.error, Number(response.error?.status || 400));
183
+ }
184
+
185
+ const refreshed = await resolveCurrentAuthContext(request, {
186
+ supabaseClient: supabase
187
+ });
188
+ return {
189
+ securityStatus: buildSecurityStatusFromAuthMethodsStatus(refreshed.authMethodsStatus)
190
+ };
191
+ }
192
+
193
+ return {
194
+ oauthStart,
195
+ startProviderLink,
196
+ oauthComplete,
197
+ unlinkProvider
198
+ };
199
+ }
200
+
201
+ export { createOauthFlows };
@@ -0,0 +1,272 @@
1
+ import {
2
+ normalizeOAuthProviderId,
3
+ normalizeOAuthProviderList
4
+ } from "@jskit-ai/auth-core/shared/oauthProviders";
5
+
6
+ const SUPABASE_OAUTH_PROVIDER_METADATA = Object.freeze({
7
+ apple: Object.freeze({ id: "apple", label: "Apple" }),
8
+ azure: Object.freeze({ id: "azure", label: "Microsoft" }),
9
+ bitbucket: Object.freeze({ id: "bitbucket", label: "Bitbucket" }),
10
+ discord: Object.freeze({ id: "discord", label: "Discord" }),
11
+ facebook: Object.freeze({ id: "facebook", label: "Facebook" }),
12
+ figma: Object.freeze({ id: "figma", label: "Figma" }),
13
+ github: Object.freeze({ id: "github", label: "GitHub" }),
14
+ gitlab: Object.freeze({ id: "gitlab", label: "GitLab" }),
15
+ google: Object.freeze({ id: "google", label: "Google", queryParams: { prompt: "select_account" } }),
16
+ kakao: Object.freeze({ id: "kakao", label: "Kakao" }),
17
+ keycloak: Object.freeze({ id: "keycloak", label: "Keycloak" }),
18
+ linkedin_oidc: Object.freeze({ id: "linkedin_oidc", label: "LinkedIn" }),
19
+ notion: Object.freeze({ id: "notion", label: "Notion" }),
20
+ slack: Object.freeze({ id: "slack", label: "Slack" }),
21
+ spotify: Object.freeze({ id: "spotify", label: "Spotify" }),
22
+ twitch: Object.freeze({ id: "twitch", label: "Twitch" }),
23
+ twitter: Object.freeze({ id: "twitter", label: "X" }),
24
+ workos: Object.freeze({ id: "workos", label: "WorkOS" }),
25
+ zoom: Object.freeze({ id: "zoom", label: "Zoom" })
26
+ });
27
+
28
+ const DEFAULT_SUPABASE_OAUTH_PROVIDER_IDS = Object.freeze(["google"]);
29
+
30
+ function normalizeProviderLabel(value, fallback) {
31
+ const normalized = String(value || "").trim();
32
+ if (normalized.length > 0) {
33
+ return normalized;
34
+ }
35
+ return String(fallback || "OAuth provider");
36
+ }
37
+
38
+ function normalizeProviderQueryParams(value) {
39
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
40
+ return null;
41
+ }
42
+
43
+ const params = {};
44
+ for (const [key, entryValue] of Object.entries(value)) {
45
+ const normalizedKey = String(key || "").trim();
46
+ const normalizedValue = String(entryValue || "").trim();
47
+ if (!normalizedKey || !normalizedValue) {
48
+ continue;
49
+ }
50
+ params[normalizedKey] = normalizedValue;
51
+ }
52
+
53
+ if (Object.keys(params).length < 1) {
54
+ return null;
55
+ }
56
+
57
+ return Object.freeze(params);
58
+ }
59
+
60
+ function normalizeProviderLabelOverrides(value) {
61
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
62
+ return new Map();
63
+ }
64
+
65
+ const overrides = new Map();
66
+ for (const [providerId, label] of Object.entries(value)) {
67
+ const normalizedProviderId = normalizeOAuthProviderId(providerId, { fallback: null });
68
+ if (!normalizedProviderId) {
69
+ continue;
70
+ }
71
+
72
+ overrides.set(normalizedProviderId, normalizeProviderLabel(label, normalizedProviderId));
73
+ }
74
+
75
+ return overrides;
76
+ }
77
+
78
+ function normalizeProviderQueryParamOverrides(value) {
79
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
80
+ return new Map();
81
+ }
82
+
83
+ const overrides = new Map();
84
+ for (const [providerId, queryParams] of Object.entries(value)) {
85
+ const normalizedProviderId = normalizeOAuthProviderId(providerId, { fallback: null });
86
+ if (!normalizedProviderId) {
87
+ continue;
88
+ }
89
+
90
+ const normalizedQueryParams = normalizeProviderQueryParams(queryParams);
91
+ if (normalizedQueryParams) {
92
+ overrides.set(normalizedProviderId, normalizedQueryParams);
93
+ }
94
+ }
95
+
96
+ return overrides;
97
+ }
98
+
99
+ function normalizeExplicitProviderCatalogEntry(entry) {
100
+ if (typeof entry === "string") {
101
+ const providerId = normalizeOAuthProviderId(entry, { fallback: null });
102
+ if (!providerId) {
103
+ return null;
104
+ }
105
+
106
+ return {
107
+ id: providerId,
108
+ label: normalizeProviderLabel(SUPABASE_OAUTH_PROVIDER_METADATA[providerId]?.label, providerId),
109
+ queryParams: normalizeProviderQueryParams(SUPABASE_OAUTH_PROVIDER_METADATA[providerId]?.queryParams)
110
+ };
111
+ }
112
+
113
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
114
+ return null;
115
+ }
116
+
117
+ const providerId = normalizeOAuthProviderId(entry.id, { fallback: null });
118
+ if (!providerId) {
119
+ return null;
120
+ }
121
+
122
+ const fallbackMetadata = SUPABASE_OAUTH_PROVIDER_METADATA[providerId] || {};
123
+ return {
124
+ id: providerId,
125
+ label: normalizeProviderLabel(entry.label, fallbackMetadata.label || providerId),
126
+ queryParams: normalizeProviderQueryParams(entry.queryParams ?? fallbackMetadata.queryParams)
127
+ };
128
+ }
129
+
130
+ function normalizeExplicitProviderCatalog(value) {
131
+ if (!Array.isArray(value)) {
132
+ return [];
133
+ }
134
+
135
+ const entries = [];
136
+ for (const entry of value) {
137
+ const normalized = normalizeExplicitProviderCatalogEntry(entry);
138
+ if (!normalized || entries.some((existing) => existing.id === normalized.id)) {
139
+ continue;
140
+ }
141
+
142
+ entries.push(
143
+ Object.freeze({
144
+ id: normalized.id,
145
+ label: normalized.label,
146
+ queryParams: normalized.queryParams
147
+ })
148
+ );
149
+ }
150
+
151
+ return entries;
152
+ }
153
+
154
+ function resolveSupabaseOAuthProviderCatalog(options = {}) {
155
+ const explicitCatalog = normalizeExplicitProviderCatalog(options.oauthProviderCatalog);
156
+ const labelOverrides = normalizeProviderLabelOverrides(options.oauthProviderLabels);
157
+ const queryParamOverrides = normalizeProviderQueryParamOverrides(options.oauthProviderQueryParams);
158
+
159
+ let providers;
160
+ if (explicitCatalog.length > 0) {
161
+ providers = explicitCatalog.map((entry) => {
162
+ const label = labelOverrides.get(entry.id) || entry.label;
163
+ const queryParams = queryParamOverrides.get(entry.id) || entry.queryParams || null;
164
+ return Object.freeze({
165
+ id: entry.id,
166
+ label,
167
+ queryParams
168
+ });
169
+ });
170
+ } else {
171
+ const providerIds = normalizeOAuthProviderList(options.oauthProviders, {
172
+ fallback: DEFAULT_SUPABASE_OAUTH_PROVIDER_IDS
173
+ });
174
+
175
+ providers = providerIds.map((providerId) => {
176
+ const fallbackMetadata = SUPABASE_OAUTH_PROVIDER_METADATA[providerId] || {};
177
+ const label = labelOverrides.get(providerId) || normalizeProviderLabel(fallbackMetadata.label, providerId);
178
+ const queryParams =
179
+ queryParamOverrides.get(providerId) || normalizeProviderQueryParams(fallbackMetadata.queryParams) || null;
180
+
181
+ return Object.freeze({
182
+ id: providerId,
183
+ label,
184
+ queryParams
185
+ });
186
+ });
187
+ }
188
+
189
+ const providerIds = Object.freeze(providers.map((provider) => provider.id));
190
+ const providerQueryParamsById = Object.freeze(
191
+ providers.reduce((accumulator, provider) => {
192
+ if (provider.queryParams) {
193
+ accumulator[provider.id] = provider.queryParams;
194
+ }
195
+ return accumulator;
196
+ }, {})
197
+ );
198
+
199
+ const defaultProvider = normalizeOAuthProviderId(options.oauthDefaultProvider, {
200
+ fallback: providerIds[0] || null
201
+ });
202
+
203
+ return {
204
+ providers: Object.freeze(
205
+ providers.map((provider) =>
206
+ Object.freeze({
207
+ id: provider.id,
208
+ label: provider.label,
209
+ queryParams: provider.queryParams
210
+ })
211
+ )
212
+ ),
213
+ providerIds,
214
+ defaultProvider: providerIds.includes(defaultProvider) ? defaultProvider : providerIds[0] || null,
215
+ providerQueryParamsById
216
+ };
217
+ }
218
+
219
+ function normalizeOAuthProviderFromCatalog(value, { providerIds = [], fallback = null } = {}) {
220
+ const normalizedProviderId = normalizeOAuthProviderId(value, { fallback: null });
221
+ if (normalizedProviderId && providerIds.includes(normalizedProviderId)) {
222
+ return normalizedProviderId;
223
+ }
224
+
225
+ const normalizedFallbackProviderId = normalizeOAuthProviderId(fallback, { fallback: null });
226
+ if (normalizedFallbackProviderId && providerIds.includes(normalizedFallbackProviderId)) {
227
+ return normalizedFallbackProviderId;
228
+ }
229
+
230
+ return null;
231
+ }
232
+
233
+ function resolveOAuthProviderQueryParams(providerId, { providerQueryParamsById = {} } = {}) {
234
+ const normalizedProviderId = normalizeOAuthProviderId(providerId, { fallback: null });
235
+ if (!normalizedProviderId) {
236
+ return null;
237
+ }
238
+
239
+ const queryParams = providerQueryParamsById[normalizedProviderId];
240
+ if (!queryParams || typeof queryParams !== "object") {
241
+ return null;
242
+ }
243
+
244
+ return queryParams;
245
+ }
246
+
247
+ function buildOAuthProviderCatalogResponse(catalog) {
248
+ const providers = Array.isArray(catalog?.providers) ? catalog.providers : [];
249
+ const providerIds = providers.map((provider) => provider.id);
250
+
251
+ const defaultProvider = normalizeOAuthProviderFromCatalog(catalog?.defaultProvider, {
252
+ providerIds,
253
+ fallback: providerIds[0] || null
254
+ });
255
+
256
+ return {
257
+ providers: providers.map((provider) => ({
258
+ id: provider.id,
259
+ label: provider.label
260
+ })),
261
+ defaultProvider
262
+ };
263
+ }
264
+
265
+ export {
266
+ SUPABASE_OAUTH_PROVIDER_METADATA,
267
+ DEFAULT_SUPABASE_OAUTH_PROVIDER_IDS,
268
+ resolveSupabaseOAuthProviderCatalog,
269
+ normalizeOAuthProviderFromCatalog,
270
+ resolveOAuthProviderQueryParams,
271
+ buildOAuthProviderCatalogResponse
272
+ };