@jskit-ai/auth-provider-supabase-core 0.1.99 → 0.1.100

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.
@@ -1,5 +1,7 @@
1
1
  import { createClient } from "@supabase/supabase-js";
2
2
  import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
3
+ import { normalizeAuthCapabilities } from "@jskit-ai/auth-core/shared/authCapabilities";
4
+ import { normalizeAuthActor, normalizeAuthResult } from "@jskit-ai/auth-core/server/authActor";
3
5
  import {
4
6
  AUTH_METHOD_PASSWORD_ID,
5
7
  AUTH_METHOD_PASSWORD_PROVIDER,
@@ -68,6 +70,8 @@ import {
68
70
 
69
71
  const ACCESS_TOKEN_COOKIE = "sb_access_token";
70
72
  const REFRESH_TOKEN_COOKIE = "sb_refresh_token";
73
+ const RECOVERY_ACCESS_TOKEN_COOKIE = "sb_recovery_access_token";
74
+ const RECOVERY_REFRESH_TOKEN_COOKIE = "sb_recovery_refresh_token";
71
75
  const DEFAULT_AUDIENCE = "authenticated";
72
76
  const DEFAULT_AUTH_PROVIDER_ID = "supabase";
73
77
  const AUTH_PROVIDER_ID_PATTERN = /^[a-z][a-z0-9_-]{1,63}$/;
@@ -108,9 +112,14 @@ function createService(options) {
108
112
 
109
113
  const supabaseUrl = String(authProvider.supabaseUrl || "").trim();
110
114
  const supabasePublishableKey = String(authProvider.supabasePublishableKey || "").trim();
115
+ const supabaseConfigured = Boolean(supabaseUrl && supabasePublishableKey);
111
116
  const userSettingsRepository = options.userSettingsRepository || null;
117
+ const passwordMethodToggleSupported =
118
+ typeof userSettingsRepository?.ensureForUserId === "function" &&
119
+ typeof userSettingsRepository?.updatePasswordSignInEnabled === "function";
112
120
  const userProfilesRepository = options.userProfilesRepository || null;
113
121
  const userProfileSyncService = options.userProfileSyncService;
122
+ const profileProjectionEnabled = options.profileProjectionEnabled === true;
114
123
  if (
115
124
  !userProfileSyncService ||
116
125
  typeof userProfileSyncService.syncIdentityProfile !== "function" ||
@@ -156,6 +165,56 @@ function createService(options) {
156
165
  const authOAuthProviderIds = authOAuthCatalog.providerIds;
157
166
  const authOAuthDefaultProvider = authOAuthCatalog.defaultProvider;
158
167
  const authOAuthCatalogResponse = Object.freeze(buildOAuthProviderCatalogResponse(authOAuthCatalog));
168
+ const capabilities = normalizeAuthCapabilities({
169
+ provider: {
170
+ id: authProviderId,
171
+ label: "Supabase"
172
+ },
173
+ features: {
174
+ password: {
175
+ login: supabaseConfigured,
176
+ register: supabaseConfigured,
177
+ change: supabaseConfigured,
178
+ methodToggle: supabaseConfigured && passwordMethodToggleSupported
179
+ },
180
+ passwordRecovery: {
181
+ request: supabaseConfigured,
182
+ complete: supabaseConfigured,
183
+ delivery: supabaseConfigured ? "smtp" : "disabled"
184
+ },
185
+ otp: {
186
+ login: supabaseConfigured
187
+ },
188
+ oauthLogin: {
189
+ enabled: supabaseConfigured && authOAuthProviders.length > 0,
190
+ providers: authOAuthCatalogResponse.providers,
191
+ defaultProvider: authOAuthCatalogResponse.defaultProvider
192
+ },
193
+ emailConfirmation: supabaseConfigured,
194
+ profileUpdate: supabaseConfigured,
195
+ providerLinking: {
196
+ start: supabaseConfigured && authOAuthProviders.length > 0,
197
+ unlink: supabaseConfigured && authOAuthProviders.length > 0
198
+ },
199
+ securityStatus: supabaseConfigured || devAuthConfig.enabled === true,
200
+ signOutOtherSessions: supabaseConfigured,
201
+ appProfileProjection: profileProjectionEnabled,
202
+ devLoginAs: devAuthConfig.enabled === true
203
+ }
204
+ });
205
+ const securityStatusActions = Object.freeze({
206
+ changePassword: true,
207
+ setPasswordEnabled: capabilities.features.password.methodToggle,
208
+ linkProvider: capabilities.features.providerLinking.start,
209
+ unlinkProvider: capabilities.features.providerLinking.unlink,
210
+ signOutOtherSessions: capabilities.features.signOutOtherSessions
211
+ });
212
+
213
+ function buildProviderSecurityStatus(authMethodsStatus) {
214
+ return buildSecurityStatusFromAuthMethodsStatus(authMethodsStatus, {
215
+ actions: securityStatusActions
216
+ });
217
+ }
159
218
 
160
219
  function normalizeOAuthProviderInput(value) {
161
220
  return normalizeOAuthProviderInputFromCatalog(value, {
@@ -283,7 +342,22 @@ function createService(options) {
283
342
  const accessToken = String(cookies[ACCESS_TOKEN_COOKIE] || "").trim();
284
343
  const refreshToken = String(cookies[REFRESH_TOKEN_COOKIE] || "").trim();
285
344
 
286
- if (!accessToken || !refreshToken) {
345
+ return setSupabaseSessionFromTokens({ accessToken, refreshToken }, options);
346
+ }
347
+
348
+ async function setRecoverySessionFromRequestCookies(request, options = {}) {
349
+ const cookies = safeRequestCookies(request);
350
+ const accessToken = String(cookies[RECOVERY_ACCESS_TOKEN_COOKIE] || "").trim();
351
+ const refreshToken = String(cookies[RECOVERY_REFRESH_TOKEN_COOKIE] || "").trim();
352
+
353
+ return setSupabaseSessionFromTokens({ accessToken, refreshToken }, options);
354
+ }
355
+
356
+ async function setSupabaseSessionFromTokens({ accessToken, refreshToken }, options = {}) {
357
+ const normalizedAccessToken = String(accessToken || "").trim();
358
+ const normalizedRefreshToken = String(refreshToken || "").trim();
359
+
360
+ if (!normalizedAccessToken || !normalizedRefreshToken) {
287
361
  throw new AppError(401, "Authentication required.");
288
362
  }
289
363
 
@@ -291,8 +365,8 @@ function createService(options) {
291
365
  let sessionResponse;
292
366
  try {
293
367
  sessionResponse = await supabase.auth.setSession({
294
- access_token: accessToken,
295
- refresh_token: refreshToken
368
+ access_token: normalizedAccessToken,
369
+ refresh_token: normalizedRefreshToken
296
370
  });
297
371
  } catch (error) {
298
372
  throw mapRecoveryError(error);
@@ -398,12 +472,12 @@ function createService(options) {
398
472
  return devAuthResult;
399
473
  }
400
474
 
401
- return {
475
+ return withActor({
402
476
  ...devAuthResult,
403
477
  profile: requireAuthenticatedProfile(devAuthResult.profile, {
404
478
  context: "dev auth profile"
405
479
  })
406
- };
480
+ });
407
481
  }
408
482
 
409
483
  function writeSessionCookies(reply, session) {
@@ -414,16 +488,23 @@ function createService(options) {
414
488
  }
415
489
 
416
490
  const sessionAccessMaxAge = Number.isFinite(Number(session?.expires_in)) ? Number(session.expires_in) : 3600;
417
- const persistentCookieMaxAge = Math.max(Math.floor(sessionAccessMaxAge), PERSISTENT_SESSION_COOKIE_MAX_AGE_SECONDS);
418
-
419
- reply.setCookie(ACCESS_TOKEN_COOKIE, accessToken, cookieOptions(isProduction, persistentCookieMaxAge));
420
- reply.setCookie(REFRESH_TOKEN_COOKIE, refreshToken, cookieOptions(isProduction, persistentCookieMaxAge));
491
+ const purpose = String(session?.purpose || "normal").trim();
492
+ const accessCookieName = purpose === "recovery" ? RECOVERY_ACCESS_TOKEN_COOKIE : ACCESS_TOKEN_COOKIE;
493
+ const refreshCookieName = purpose === "recovery" ? RECOVERY_REFRESH_TOKEN_COOKIE : REFRESH_TOKEN_COOKIE;
494
+ const cookieMaxAge = purpose === "recovery"
495
+ ? Math.floor(sessionAccessMaxAge)
496
+ : Math.max(Math.floor(sessionAccessMaxAge), PERSISTENT_SESSION_COOKIE_MAX_AGE_SECONDS);
497
+
498
+ reply.setCookie(accessCookieName, accessToken, cookieOptions(isProduction, cookieMaxAge));
499
+ reply.setCookie(refreshCookieName, refreshToken, cookieOptions(isProduction, cookieMaxAge));
421
500
  }
422
501
 
423
502
  function clearSessionCookies(reply) {
424
503
  for (const clearOptions of cookieClearOptions(isProduction)) {
425
504
  reply.clearCookie(ACCESS_TOKEN_COOKIE, clearOptions);
426
505
  reply.clearCookie(REFRESH_TOKEN_COOKIE, clearOptions);
506
+ reply.clearCookie(RECOVERY_ACCESS_TOKEN_COOKIE, clearOptions);
507
+ reply.clearCookie(RECOVERY_REFRESH_TOKEN_COOKIE, clearOptions);
427
508
  }
428
509
  }
429
510
 
@@ -565,6 +646,44 @@ function createService(options) {
565
646
  });
566
647
  }
567
648
 
649
+ function buildActorFromProfile(profile) {
650
+ return normalizeAuthActor(
651
+ {
652
+ provider: profile?.authProvider || authProviderId,
653
+ providerUserId: profile?.authProviderUserSid || profile?.id,
654
+ email: profile?.email,
655
+ displayName: profile?.displayName,
656
+ appUserId: profileProjectionEnabled ? profile?.id : null,
657
+ profileSource: profile?.profileSource || (profileProjectionEnabled ? "users" : "auth-provider")
658
+ },
659
+ {
660
+ provider: authProviderId,
661
+ profileSource: profileProjectionEnabled ? "users" : "auth-provider"
662
+ }
663
+ );
664
+ }
665
+
666
+ function withActor(result) {
667
+ if (!result || typeof result !== "object") {
668
+ return result;
669
+ }
670
+ if (result.authenticated !== true && !result.profile) {
671
+ return result;
672
+ }
673
+ const actor = buildActorFromProfile(result.profile);
674
+ if (!actor) {
675
+ return result;
676
+ }
677
+ return normalizeAuthResult({
678
+ ...result,
679
+ actor
680
+ });
681
+ }
682
+
683
+ function withActorResult(operation) {
684
+ return async (...args) => withActor(await operation(...args));
685
+ }
686
+
568
687
  async function syncProfileFromJwtClaims(claims) {
569
688
  const supabaseUserId = normalizeProviderUserId(claims?.sub);
570
689
  if (!supabaseUserId) {
@@ -661,7 +780,7 @@ function createService(options) {
661
780
  oauthProviders: authOAuthProviders
662
781
  });
663
782
 
664
- return buildSecurityStatusFromAuthMethodsStatus(authMethodsStatus);
783
+ return buildProviderSecurityStatus(authMethodsStatus);
665
784
  }
666
785
 
667
786
  const { register, resendRegisterConfirmation, login, requestOtpLogin, verifyOtpLogin, updateDisplayName } = createAccountFlows({
@@ -703,7 +822,7 @@ function createService(options) {
703
822
  buildOAuthMethodId,
704
823
  findAuthMethodById,
705
824
  findLinkedIdentityByProvider,
706
- buildSecurityStatusFromAuthMethodsStatus
825
+ buildSecurityStatusFromAuthMethodsStatus: buildProviderSecurityStatus
707
826
  });
708
827
 
709
828
  const {
@@ -723,6 +842,7 @@ function createService(options) {
723
842
  mapRecoveryError,
724
843
  syncProfileFromSupabaseUser,
725
844
  setSessionFromRequestCookies,
845
+ setRecoverySessionFromRequestCookies,
726
846
  resolvePasswordSignInPolicyForUserId,
727
847
  mapPasswordUpdateError,
728
848
  setPasswordSetupRequiredForUserId,
@@ -739,7 +859,7 @@ function createService(options) {
739
859
  ...statusOptions,
740
860
  oauthProviders: authOAuthProviders
741
861
  }),
742
- buildSecurityStatusFromAuthMethodsStatus,
862
+ buildSecurityStatusFromAuthMethodsStatus: buildProviderSecurityStatus,
743
863
  authMethodPasswordProvider: AUTH_METHOD_PASSWORD_PROVIDER,
744
864
  buildAuthMethodsStatusFromProviderIds: (providerIds, statusOptions = {}) =>
745
865
  buildAuthMethodsStatusFromProviderIds(providerIds, {
@@ -773,13 +893,13 @@ function createService(options) {
773
893
 
774
894
  if (verification.status === "valid") {
775
895
  const profile = await syncProfileFromJwtClaims(verification.payload);
776
- return {
896
+ return withActor({
777
897
  authenticated: true,
778
898
  profile,
779
899
  clearSession: false,
780
900
  session: null,
781
901
  transientFailure: false
782
- };
902
+ });
783
903
  }
784
904
 
785
905
  if (verification.status === "transient") {
@@ -794,13 +914,13 @@ function createService(options) {
794
914
  if (verification.status === "invalid") {
795
915
  const supabaseVerification = await verifyAccessTokenViaSupabase(accessToken);
796
916
  if (supabaseVerification.status === "valid") {
797
- return {
917
+ return withActor({
798
918
  authenticated: true,
799
919
  profile: supabaseVerification.profile,
800
920
  clearSession: false,
801
921
  session: null,
802
922
  transientFailure: false
803
- };
923
+ });
804
924
  }
805
925
 
806
926
  if (supabaseVerification.status === "transient") {
@@ -875,29 +995,38 @@ function createService(options) {
875
995
 
876
996
  const profile = await syncProfileFromSupabaseUser(refreshResponse.data.user, refreshResponse.data.user.email);
877
997
 
878
- return {
998
+ return withActor({
879
999
  authenticated: true,
880
1000
  profile,
881
1001
  clearSession: false,
882
1002
  session: refreshResponse.data.session,
883
1003
  transientFailure: false
884
- };
1004
+ });
885
1005
  }
886
1006
 
887
1007
  function hasAccessTokenCookie(request) {
888
1008
  const cookies = safeRequestCookies(request);
889
- return Boolean(cookies[ACCESS_TOKEN_COOKIE]);
1009
+ return Boolean(cookies[ACCESS_TOKEN_COOKIE] || cookies[RECOVERY_ACCESS_TOKEN_COOKIE]);
890
1010
  }
891
1011
 
892
1012
  function hasSessionCookie(request) {
893
1013
  const cookies = safeRequestCookies(request);
894
- return Boolean(cookies[ACCESS_TOKEN_COOKIE] || cookies[REFRESH_TOKEN_COOKIE]);
1014
+ return Boolean(
1015
+ cookies[ACCESS_TOKEN_COOKIE] ||
1016
+ cookies[REFRESH_TOKEN_COOKIE] ||
1017
+ cookies[RECOVERY_ACCESS_TOKEN_COOKIE] ||
1018
+ cookies[RECOVERY_REFRESH_TOKEN_COOKIE]
1019
+ );
895
1020
  }
896
1021
 
897
1022
  function getOAuthProviderCatalog() {
898
1023
  return authOAuthCatalogResponse;
899
1024
  }
900
1025
 
1026
+ function getCapabilities() {
1027
+ return capabilities;
1028
+ }
1029
+
901
1030
  function isDevAuthBootstrapEnabled() {
902
1031
  return devAuthConfig.enabled === true;
903
1032
  }
@@ -911,26 +1040,27 @@ function createService(options) {
911
1040
  const profile = requireAuthenticatedProfile(rawProfile, {
912
1041
  context: "dev auth profile"
913
1042
  });
914
- return {
1043
+ return withActor({
915
1044
  profile,
916
1045
  session: await createDevAuthSession(profile, devAuthConfig)
917
- };
1046
+ });
918
1047
  }
919
1048
 
920
1049
  return {
921
- register,
1050
+ getCapabilities,
1051
+ register: withActorResult(register),
922
1052
  resendRegisterConfirmation,
923
- login,
1053
+ login: withActorResult(login),
924
1054
  requestOtpLogin,
925
- verifyOtpLogin,
1055
+ verifyOtpLogin: withActorResult(verifyOtpLogin),
926
1056
  oauthStart,
927
- oauthComplete,
1057
+ oauthComplete: withActorResult(oauthComplete),
928
1058
  startProviderLink,
929
1059
  requestPasswordReset,
930
- completePasswordRecovery,
1060
+ completePasswordRecovery: withActorResult(completePasswordRecovery),
931
1061
  resetPassword,
932
- updateDisplayName,
933
- changePassword,
1062
+ updateDisplayName: withActorResult(updateDisplayName),
1063
+ changePassword: withActorResult(changePassword),
934
1064
  setPasswordSignInEnabled,
935
1065
  unlinkProvider,
936
1066
  signOutOtherSessions,
@@ -1,14 +1,13 @@
1
1
  import { resolveAllowedOriginsFromSurfaceDefinitions } from "@jskit-ai/kernel/shared/support/returnToPath";
2
2
  import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
3
- import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
4
3
  import { resolveAuthServiceDecorators } from "@jskit-ai/auth-core/server/authServiceDecoratorRegistry";
4
+ import { normalizeEmail } from "@jskit-ai/auth-core/server/utils";
5
5
  import { createService } from "../lib/service.js";
6
- import { createStandaloneProfileSyncService } from "../lib/standaloneProfileSyncService.js";
7
- import { createAuthSessionEventsService } from "../lib/authSessionEventsService.js";
8
- import { buildAuthActions } from "../lib/actions/auth.contributor.js";
6
+ import { devLoginAsAction } from "../lib/actions/auth.contributor.js";
7
+ const PROFILE_MODE_PROVIDER = "provider";
9
8
  const PROFILE_MODE_STANDALONE = "standalone";
10
9
  const PROFILE_MODE_USERS = "users";
11
- const SUPPORTED_PROFILE_MODES = Object.freeze([PROFILE_MODE_STANDALONE, PROFILE_MODE_USERS]);
10
+ const SUPPORTED_PROFILE_MODES = Object.freeze([PROFILE_MODE_PROVIDER, PROFILE_MODE_STANDALONE, PROFILE_MODE_USERS]);
12
11
  const INTERNAL_JSON_REST_API = "internal.json-rest-api";
13
12
 
14
13
  function splitCsv(value) {
@@ -93,11 +92,11 @@ function resolveAuthProviderConfig(env, appConfig = {}) {
93
92
 
94
93
  function resolveAuthProfileMode(appConfig = {}) {
95
94
  const auth = normalizeRecord(normalizeRecord(appConfig).auth);
96
- const mode = String(auth.profileMode || PROFILE_MODE_USERS)
95
+ const mode = String(auth.profileMode || PROFILE_MODE_PROVIDER)
97
96
  .trim()
98
97
  .toLowerCase();
99
98
  if (SUPPORTED_PROFILE_MODES.includes(mode)) {
100
- return mode;
99
+ return mode === PROFILE_MODE_STANDALONE ? PROFILE_MODE_PROVIDER : mode;
101
100
  }
102
101
  throw new Error(
103
102
  `Unsupported config.auth.profileMode "${mode}". Supported values: ${SUPPORTED_PROFILE_MODES.join(", ")}.`
@@ -116,44 +115,32 @@ function isDevAuthBypassRequested(env) {
116
115
  return parseBoolean(env?.AUTH_DEV_BYPASS_ENABLED, false);
117
116
  }
118
117
 
119
- function createInMemoryUserSettingsRepository() {
120
- const settingsByUserId = new Map();
121
-
122
- function ensure(userId) {
123
- const normalizedUserId = normalizeRecordId(userId, { fallback: null });
124
- if (!normalizedUserId) {
125
- throw new TypeError("User settings require a valid user id.");
126
- }
127
- if (!settingsByUserId.has(normalizedUserId)) {
128
- settingsByUserId.set(normalizedUserId, {
129
- userId: normalizedUserId,
130
- passwordSignInEnabled: true,
131
- passwordSetupRequired: false
132
- });
133
- }
134
- return settingsByUserId.get(normalizedUserId);
135
- }
136
-
118
+ function createProviderIdentityProfileSyncService({ authProviderId = "supabase" } = {}) {
137
119
  return Object.freeze({
138
- async ensureForUserId(userId) {
139
- return { ...ensure(userId) };
120
+ async findByIdentity() {
121
+ return null;
140
122
  },
141
- async updatePasswordSignInEnabled(userId, enabled) {
142
- const settings = ensure(userId);
143
- settings.passwordSignInEnabled = enabled !== false;
144
- return { ...settings };
145
- },
146
- async updatePasswordSetupRequired(userId, required) {
147
- const settings = ensure(userId);
148
- settings.passwordSetupRequired = required === true;
149
- return { ...settings };
123
+ async syncIdentityProfile(profileLike) {
124
+ const source = normalizeRecord(profileLike);
125
+ const authProvider = String(source.authProvider || authProviderId).trim() || authProviderId;
126
+ const authProviderUserSid = String(source.authProviderUserSid || "").trim();
127
+ const email = normalizeEmail(source.email || "");
128
+ const displayName = String(source.displayName || email.split("@")[0] || "User").trim();
129
+ if (!authProviderUserSid || !email) {
130
+ throw new TypeError("Provider identity profile requires authProviderUserSid and email.");
131
+ }
132
+ return {
133
+ id: authProviderUserSid,
134
+ authProvider,
135
+ authProviderUserSid,
136
+ email,
137
+ displayName,
138
+ profileSource: "auth-provider"
139
+ };
150
140
  }
151
141
  });
152
142
  }
153
143
 
154
- const fallbackUserSettingsRepository = createInMemoryUserSettingsRepository();
155
- const fallbackStandaloneProfileSyncService = createStandaloneProfileSyncService();
156
-
157
144
  function resolveCommonDependencies(scope) {
158
145
  const dependencies = {};
159
146
  if (scope.has("jskit.env")) {
@@ -176,6 +163,15 @@ function resolveRuntimeEnv(scope) {
176
163
  };
177
164
  }
178
165
 
166
+ function assertSelectedAuthProvider(env) {
167
+ const selectedProvider = String(env?.AUTH_PROVIDER || "").trim().toLowerCase();
168
+ if (selectedProvider && selectedProvider !== "supabase") {
169
+ throw new Error(
170
+ `AUTH_PROVIDER is "${selectedProvider}", but @jskit-ai/auth-provider-supabase-core is installed as the selected auth provider.`
171
+ );
172
+ }
173
+ }
174
+
179
175
  function resolveOptionalRepositories(scope) {
180
176
  const repositories = {};
181
177
  if (scope.has("internal.repository.user-settings")) {
@@ -231,95 +227,69 @@ class AuthSupabaseServiceProvider {
231
227
  throw new Error("AuthSupabaseServiceProvider requires application singleton()/has()/actions()/service().");
232
228
  }
233
229
 
234
- if (!app.has("authService")) {
235
- app.singleton("authService", (scope) => {
236
- const env = resolveRuntimeEnv(scope);
237
- const appConfig = scope.has("appConfig") ? scope.make("appConfig") : {};
238
- const authProvider = resolveAuthProviderConfig(env, appConfig);
239
- const repositories = resolveOptionalRepositories(scope);
240
- const userSettingsRepository = repositories.userSettingsRepository || fallbackUserSettingsRepository;
241
- const devAuthBypassEnabled = parseBoolean(env.AUTH_DEV_BYPASS_ENABLED, false);
242
- if (!authProvider.supabaseUrl || !authProvider.supabasePublishableKey) {
243
- if (!devAuthBypassEnabled) {
244
- return null;
245
- }
246
- }
247
- const authProfileMode = resolveAuthProfileMode(appConfig);
248
- let userProfileSyncService = fallbackStandaloneProfileSyncService;
249
- if (authProfileMode === PROFILE_MODE_USERS) {
250
- if (!scope.has("users.profile.sync.service")) {
251
- throw new Error(
252
- "AuthSupabaseServiceProvider requires users.profile.sync.service when config.auth.profileMode is \"users\"."
253
- );
254
- }
255
- userProfileSyncService = scope.make("users.profile.sync.service");
256
- }
230
+ assertSelectedAuthProvider(resolveRuntimeEnv(app));
257
231
 
258
- const authService = createService({
259
- authProvider,
260
- appPublicUrl: String(env.APP_PUBLIC_URL || "").trim(),
261
- authAllowedReturnToOrigins: resolveAllowedReturnToOrigins({
262
- appConfig,
263
- appPublicUrl: String(env.APP_PUBLIC_URL || "").trim()
264
- }),
265
- nodeEnv: String(env.NODE_ENV || "development").trim() || "development",
266
- userSettingsRepository,
267
- userProfileSyncService,
268
- userProfilesRepository: repositories.userProfilesRepository || null,
269
- devAuthBypassEnabled,
270
- devAuthBypassSecret: String(env.AUTH_DEV_BYPASS_SECRET || "").trim(),
271
- devAuthAccessTtlSeconds: env.AUTH_DEV_ACCESS_TTL_SECONDS,
272
- devAuthRefreshTtlSeconds: env.AUTH_DEV_REFRESH_TTL_SECONDS
273
- });
274
-
275
- return applyAuthServiceDecorators(scope, authService);
276
- });
232
+ if (app.has("authService")) {
233
+ throw new Error("AuthSupabaseServiceProvider cannot register authService because another auth provider already registered it.");
277
234
  }
278
235
 
279
- app.service(
280
- "auth.session.events.service",
281
- () => createAuthSessionEventsService(),
282
- {
283
- events: {
284
- notifySessionChanged: [
285
- {
286
- type: "entity.changed",
287
- source: "auth",
288
- entity: "session",
289
- operation: "updated",
290
- entityId: ({ result }) => result?.id,
291
- realtime: {
292
- event: "auth.session.changed",
293
- audience: "actor_user"
294
- }
295
- },
296
- {
297
- type: "entity.changed",
298
- source: "users",
299
- entity: "bootstrap",
300
- operation: "updated",
301
- entityId: ({ result }) => result?.id,
302
- realtime: {
303
- event: "users.bootstrap.changed",
304
- audience: "actor_user"
305
- }
306
- }
307
- ]
236
+ app.singleton("authService", (scope) => {
237
+ const env = resolveRuntimeEnv(scope);
238
+ assertSelectedAuthProvider(env);
239
+ const appConfig = scope.has("appConfig") ? scope.make("appConfig") : {};
240
+ const authProvider = resolveAuthProviderConfig(env, appConfig);
241
+ const repositories = resolveOptionalRepositories(scope);
242
+ const userSettingsRepository = repositories.userSettingsRepository || null;
243
+ const devAuthBypassEnabled = parseBoolean(env.AUTH_DEV_BYPASS_ENABLED, false);
244
+ const authProfileMode = resolveAuthProfileMode(appConfig);
245
+ let userProfileSyncService = createProviderIdentityProfileSyncService({
246
+ authProviderId: authProvider.id
247
+ });
248
+ let profileProjectionEnabled = false;
249
+ if (authProfileMode === PROFILE_MODE_USERS) {
250
+ if (!scope.has("users.profile.sync.service")) {
251
+ throw new Error(
252
+ "AuthSupabaseServiceProvider requires users.profile.sync.service when config.auth.profileMode is \"users\"."
253
+ );
308
254
  }
255
+ userProfileSyncService = scope.make("users.profile.sync.service");
256
+ profileProjectionEnabled = true;
257
+ } else if (scope.has("auth.profile.projector")) {
258
+ userProfileSyncService = scope.make("auth.profile.projector");
259
+ profileProjectionEnabled = true;
309
260
  }
310
- );
311
261
 
312
- app.actions(
313
- withActionDefaults(buildAuthActions({
314
- includeDevLoginAs: isDevAuthBypassEnabledForRegistration(resolveRuntimeEnv(app))
315
- }), {
316
- domain: "auth",
317
- dependencies: {
318
- authService: "authService",
319
- authSessionEventsService: "auth.session.events.service"
320
- }
321
- })
322
- );
262
+ const authService = createService({
263
+ authProvider,
264
+ appPublicUrl: String(env.APP_PUBLIC_URL || "").trim(),
265
+ authAllowedReturnToOrigins: resolveAllowedReturnToOrigins({
266
+ appConfig,
267
+ appPublicUrl: String(env.APP_PUBLIC_URL || "").trim()
268
+ }),
269
+ nodeEnv: String(env.NODE_ENV || "development").trim() || "development",
270
+ userSettingsRepository,
271
+ userProfileSyncService,
272
+ profileProjectionEnabled,
273
+ userProfilesRepository: repositories.userProfilesRepository || null,
274
+ devAuthBypassEnabled,
275
+ devAuthBypassSecret: String(env.AUTH_DEV_BYPASS_SECRET || "").trim(),
276
+ devAuthAccessTtlSeconds: env.AUTH_DEV_ACCESS_TTL_SECONDS,
277
+ devAuthRefreshTtlSeconds: env.AUTH_DEV_REFRESH_TTL_SECONDS
278
+ });
279
+
280
+ return applyAuthServiceDecorators(scope, authService);
281
+ });
282
+
283
+ if (isDevAuthBypassEnabledForRegistration(resolveRuntimeEnv(app))) {
284
+ app.actions(
285
+ withActionDefaults([devLoginAsAction], {
286
+ domain: "auth",
287
+ dependencies: {
288
+ authService: "authService"
289
+ }
290
+ })
291
+ );
292
+ }
323
293
  }
324
294
 
325
295
  boot(app) {
@@ -327,10 +297,6 @@ class AuthSupabaseServiceProvider {
327
297
  throw new Error("AuthSupabaseServiceProvider requires application make().");
328
298
  }
329
299
 
330
- if (!isDevAuthBypassRequested(resolveRuntimeEnv(app))) {
331
- return;
332
- }
333
-
334
300
  try {
335
301
  app.make("authService");
336
302
  } catch (error) {