@rebasepro/auth 0.0.1-canary.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.
@@ -0,0 +1,692 @@
1
+ import { useCallback, useEffect, useState, useRef } from "react";
2
+ import { User } from "@rebasepro/core";
3
+ import * as authApi from "../api";
4
+ import { AuthConfigResponse } from "../api";
5
+ import {
6
+ RebaseAuthController,
7
+ RebaseAuthControllerProps,
8
+ AuthTokens,
9
+ UserInfo
10
+ } from "../types";
11
+
12
+ const STORAGE_KEY = "rebase_auth";
13
+
14
+ // Buffer time before expiry to trigger refresh (2 minutes)
15
+ const TOKEN_REFRESH_BUFFER_MS = 2 * 60 * 1000;
16
+
17
+ /**
18
+ * Convert UserInfo from API to Rebase User type
19
+ */
20
+ function convertToUser(userInfo: UserInfo): User {
21
+ return {
22
+ uid: userInfo.uid,
23
+ email: userInfo.email,
24
+ displayName: userInfo.displayName || null,
25
+ photoURL: userInfo.photoURL || null,
26
+ providerId: "custom",
27
+ isAnonymous: false,
28
+ roles: userInfo.roles || []
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Storage data structure
34
+ */
35
+ interface StoredAuthData {
36
+ tokens: AuthTokens;
37
+ user: UserInfo;
38
+ }
39
+
40
+ /**
41
+ * Save auth data to localStorage
42
+ */
43
+ function saveAuthToStorage(tokens: AuthTokens, user: UserInfo): void {
44
+ try {
45
+ const data: StoredAuthData = { tokens, user };
46
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
47
+ const expiryDate = new Date(tokens.accessTokenExpiresAt);
48
+ const expiryStr = Number.isFinite(tokens.accessTokenExpiresAt) ? expiryDate.toISOString() : "invalid";
49
+ } catch (e) {
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Load auth data from localStorage
55
+ */
56
+ function loadAuthFromStorage(): StoredAuthData | null {
57
+ try {
58
+ const data = localStorage.getItem(STORAGE_KEY);
59
+ if (data) {
60
+ const parsed = JSON.parse(data);
61
+ return parsed;
62
+ }
63
+ } catch (e) {
64
+ console.warn("Failed to load auth from storage:", e);
65
+ }
66
+ return null;
67
+ }
68
+
69
+ /**
70
+ * Clear auth data from localStorage
71
+ */
72
+ function clearAuthFromStorage(): void {
73
+ try {
74
+ localStorage.removeItem(STORAGE_KEY);
75
+ } catch (e) {
76
+ console.warn("Failed to clear auth from storage:", e);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Check if token is expired or about to expire
82
+ */
83
+ function isTokenExpiredOrNearExpiry(expiresAt: number, bufferMs: number = TOKEN_REFRESH_BUFFER_MS): boolean {
84
+ return Date.now() + bufferMs >= expiresAt;
85
+ }
86
+
87
+ /**
88
+ * Auth controller hook for JWT-based authentication
89
+ * with @rebasepro/backend
90
+ *
91
+ * @param props Configuration options
92
+ * @returns RebaseAuthController instance
93
+ */
94
+ export function useRebaseAuthController(
95
+ props: RebaseAuthControllerProps = {}
96
+ ): RebaseAuthController {
97
+ const { apiUrl, onSignOut, defineRolesFor } = props;
98
+
99
+ const [user, setUser] = useState<User | null>(null);
100
+ const [authLoading, setAuthLoading] = useState(false);
101
+ const [initialLoading, setInitialLoading] = useState(true);
102
+ const [authError, setAuthError] = useState<Error | null>(null);
103
+ const [authProviderError, setAuthProviderError] = useState<Error | null>(null);
104
+ const [loginSkipped, setLoginSkipped] = useState(false);
105
+ const [extra, setExtra] = useState<unknown>(null);
106
+ const [authConfig, setAuthConfig] = useState<AuthConfigResponse | null>(null);
107
+
108
+ // Store tokens in ref for quick access, but also persist to localStorage
109
+ const tokensRef = useRef<AuthTokens | null>(null);
110
+ const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
111
+ // Track if a refresh is currently in progress to avoid concurrent refreshes
112
+ const refreshPromiseRef = useRef<Promise<AuthTokens | null> | null>(null);
113
+ // Track if component is mounted
114
+ const isMountedRef = useRef(true);
115
+
116
+ // Configure API URL on mount
117
+ useEffect(() => {
118
+ if (apiUrl) {
119
+ authApi.setApiUrl(apiUrl);
120
+ }
121
+ }, [apiUrl]);
122
+
123
+ // Clear session and sign out
124
+ const clearSessionAndSignOut = useCallback(() => {
125
+ tokensRef.current = null;
126
+ clearAuthFromStorage();
127
+ if (refreshTimeoutRef.current) {
128
+ clearTimeout(refreshTimeoutRef.current);
129
+ refreshTimeoutRef.current = null;
130
+ }
131
+ setUser(null);
132
+ setLoginSkipped(false);
133
+ onSignOut?.();
134
+ }, [onSignOut]);
135
+
136
+ /**
137
+ * Refresh the access token using the stored refresh token.
138
+ * Returns the new tokens or null if refresh failed.
139
+ */
140
+ const refreshAccessToken = useCallback(async (): Promise<AuthTokens | null> => {
141
+ // Prevent concurrent refreshes
142
+ if (refreshPromiseRef.current) {
143
+ // Wait for the current refresh to complete
144
+ return refreshPromiseRef.current;
145
+ }
146
+
147
+ const executeRefresh = async (): Promise<AuthTokens | null> => {
148
+ // Check if another tab has already refreshed the token
149
+ const storedData = loadAuthFromStorage();
150
+ if (storedData?.tokens?.accessTokenExpiresAt) {
151
+ const storedTokens = storedData.tokens;
152
+ // If stored token is newer and not expired
153
+ if (!isTokenExpiredOrNearExpiry(storedTokens.accessTokenExpiresAt) && storedTokens.accessToken !== tokensRef.current?.accessToken) {
154
+ tokensRef.current = storedTokens;
155
+ return storedTokens;
156
+ }
157
+ }
158
+
159
+ const currentTokens = tokensRef.current;
160
+ if (!currentTokens?.refreshToken) {
161
+ return null;
162
+ }
163
+
164
+
165
+ try {
166
+ const response = await authApi.refreshAccessToken(currentTokens.refreshToken);
167
+ const newTokens = response.tokens;
168
+
169
+ // Update tokens immediately
170
+ tokensRef.current = newTokens;
171
+
172
+ // Persist to storage
173
+ const latestStoredData = loadAuthFromStorage();
174
+ if (latestStoredData) {
175
+ saveAuthToStorage(newTokens, latestStoredData.user);
176
+ }
177
+
178
+ const newExpiryStr = Number.isFinite(newTokens.accessTokenExpiresAt) ? new Date(newTokens.accessTokenExpiresAt).toISOString() : "invalid";
179
+ return newTokens;
180
+ } catch (error: unknown) {
181
+
182
+ // If it's a network error (e.g., backend restarting), we throw so callers can retry
183
+ // instead of immediately assuming the refresh token is invalid and signing out.
184
+ if (error instanceof Error && (error as { code?: string }).code === "NETWORK_ERROR") {
185
+ throw error;
186
+ }
187
+ return null;
188
+ } finally {
189
+ refreshPromiseRef.current = null;
190
+ }
191
+ };
192
+
193
+ refreshPromiseRef.current = executeRefresh();
194
+ return refreshPromiseRef.current;
195
+ }, []);
196
+
197
+ // Schedule token refresh before expiry
198
+ const scheduleTokenRefresh = useCallback((tokens: AuthTokens) => {
199
+ if (refreshTimeoutRef.current) {
200
+ clearTimeout(refreshTimeoutRef.current);
201
+ }
202
+
203
+ // Calculate when to refresh (2 minutes before expiry)
204
+ const expiresAt = tokens.accessTokenExpiresAt;
205
+ const refreshAt = expiresAt - TOKEN_REFRESH_BUFFER_MS;
206
+ const timeUntilRefresh = refreshAt - Date.now();
207
+
208
+ if (timeUntilRefresh <= 0) {
209
+ // Token already expired or about to expire - refresh now
210
+ refreshAccessToken().then(newTokens => {
211
+ if (newTokens && isMountedRef.current) {
212
+ scheduleTokenRefresh(newTokens);
213
+ } else if (!newTokens && isMountedRef.current) {
214
+ clearSessionAndSignOut();
215
+ }
216
+ });
217
+ return;
218
+ }
219
+
220
+
221
+ refreshTimeoutRef.current = setTimeout(async () => {
222
+ if (!isMountedRef.current) return;
223
+
224
+ try {
225
+ const newTokens = await refreshAccessToken();
226
+
227
+ if (newTokens && isMountedRef.current) {
228
+ scheduleTokenRefresh(newTokens);
229
+ } else if (!newTokens && isMountedRef.current) {
230
+ clearSessionAndSignOut();
231
+ }
232
+ } catch (error) {
233
+ // Network error - try again shortly instead of logging out
234
+ if (isMountedRef.current) {
235
+ refreshTimeoutRef.current = setTimeout(() => {
236
+ scheduleTokenRefresh(tokens);
237
+ }, 10000);
238
+ }
239
+ }
240
+ }, timeUntilRefresh);
241
+ }, [refreshAccessToken, clearSessionAndSignOut]);
242
+
243
+ // Get auth token for API requests (with automatic refresh if needed)
244
+ const getAuthToken = useCallback(async (): Promise<string> => {
245
+ // If still loading, throw - the UI should show a spinner
246
+ if (initialLoading) {
247
+ throw new Error("Auth is still loading");
248
+ }
249
+
250
+ const currentTokens = tokensRef.current;
251
+ if (!currentTokens) {
252
+ throw new Error("User is not logged in");
253
+ }
254
+
255
+ // Check if token is expired or about to expire
256
+ if (isTokenExpiredOrNearExpiry(currentTokens.accessTokenExpiresAt)) {
257
+ try {
258
+ const newTokens = await refreshAccessToken();
259
+ if (!newTokens) {
260
+ clearSessionAndSignOut();
261
+ throw new Error("Session expired. Please login again.");
262
+ }
263
+ return newTokens.accessToken;
264
+ } catch (error: unknown) {
265
+ // If the error was a network error during refresh, just re-throw it
266
+ // so the user isn't logged out locally and the network request fails naturally.
267
+ if (error instanceof Error && (error as { code?: string }).code === "NETWORK_ERROR") {
268
+ throw error;
269
+ }
270
+ clearSessionAndSignOut();
271
+ throw error;
272
+ }
273
+ }
274
+
275
+ return currentTokens.accessToken;
276
+ }, [initialLoading, refreshAccessToken, clearSessionAndSignOut]);
277
+
278
+ // Handle successful authentication
279
+ const handleAuthSuccess = useCallback(async (userInfo: UserInfo, tokens: AuthTokens) => {
280
+ tokensRef.current = tokens;
281
+ let convertedUser = convertToUser(userInfo);
282
+
283
+ // Apply custom roles if defineRolesFor provided
284
+ if (defineRolesFor) {
285
+ const customRoles = await defineRolesFor(convertedUser);
286
+ if (customRoles) {
287
+ convertedUser = { ...convertedUser, roles: customRoles.map(r => r.id) };
288
+ }
289
+ }
290
+
291
+ // Save to localStorage for persistence
292
+ saveAuthToStorage(tokens, userInfo);
293
+
294
+ setUser(convertedUser);
295
+ setAuthError(null);
296
+ setAuthProviderError(null);
297
+ setLoginSkipped(false);
298
+ scheduleTokenRefresh(tokens);
299
+ }, [scheduleTokenRefresh, defineRolesFor]);
300
+
301
+ // Email/password login
302
+ const emailPasswordLogin = useCallback(async (email: string, password: string) => {
303
+ setAuthLoading(true);
304
+ setAuthProviderError(null);
305
+
306
+ try {
307
+ const response = await authApi.login(email, password);
308
+ await handleAuthSuccess(response.user, response.tokens);
309
+ } catch (error: unknown) {
310
+ setAuthProviderError(error as Error);
311
+ throw error;
312
+ } finally {
313
+ setAuthLoading(false);
314
+ }
315
+ }, [handleAuthSuccess]);
316
+
317
+ // Register new user
318
+ const register = useCallback(async (email: string, password: string, displayName?: string) => {
319
+ setAuthLoading(true);
320
+ setAuthProviderError(null);
321
+
322
+ try {
323
+ const response = await authApi.register(email, password, displayName);
324
+ await handleAuthSuccess(response.user, response.tokens);
325
+ } catch (error: unknown) {
326
+ setAuthProviderError(error as Error);
327
+ throw error;
328
+ } finally {
329
+ setAuthLoading(false);
330
+ }
331
+ }, [handleAuthSuccess]);
332
+
333
+ // Google login with ID token
334
+ const googleLogin = useCallback(async (idToken: string) => {
335
+ setAuthLoading(true);
336
+ setAuthProviderError(null);
337
+
338
+ try {
339
+ const response = await authApi.googleLogin(idToken);
340
+ await handleAuthSuccess(response.user, response.tokens);
341
+ } catch (error: unknown) {
342
+ setAuthProviderError(error as Error);
343
+ throw error;
344
+ } finally {
345
+ setAuthLoading(false);
346
+ }
347
+ }, [handleAuthSuccess]);
348
+
349
+ // Sign out
350
+ const signOut = useCallback(async () => {
351
+ try {
352
+ if (tokensRef.current) {
353
+ await authApi.logout(tokensRef.current.refreshToken);
354
+ }
355
+ } catch (error) {
356
+ console.error("Logout error:", error);
357
+ } finally {
358
+ clearSessionAndSignOut();
359
+ }
360
+ }, [clearSessionAndSignOut]);
361
+
362
+ // Skip login
363
+ const skipLogin = useCallback(() => {
364
+ setLoginSkipped(true);
365
+ setUser(null);
366
+ }, []);
367
+
368
+ // Forgot password - request reset email
369
+ const forgotPassword = useCallback(async (email: string) => {
370
+ setAuthLoading(true);
371
+ setAuthProviderError(null);
372
+
373
+ try {
374
+ await authApi.forgotPassword(email);
375
+ } catch (error: unknown) {
376
+ setAuthProviderError(error as Error);
377
+ throw error;
378
+ } finally {
379
+ setAuthLoading(false);
380
+ }
381
+ }, []);
382
+
383
+ // Reset password using token
384
+ const resetPassword = useCallback(async (token: string, password: string) => {
385
+ setAuthLoading(true);
386
+ setAuthProviderError(null);
387
+
388
+ try {
389
+ await authApi.resetPassword(token, password);
390
+ } catch (error: unknown) {
391
+ setAuthProviderError(error as Error);
392
+ throw error;
393
+ } finally {
394
+ setAuthLoading(false);
395
+ }
396
+ }, []);
397
+
398
+ // Change password for authenticated user
399
+ const changePassword = useCallback(async (oldPassword: string, newPassword: string) => {
400
+ setAuthLoading(true);
401
+ setAuthProviderError(null);
402
+
403
+ try {
404
+ if (!tokensRef.current) {
405
+ throw new Error("User is not logged in");
406
+ }
407
+ await authApi.changePassword(tokensRef.current.accessToken, oldPassword, newPassword);
408
+ // After password change, user needs to log in again (all sessions invalidated)
409
+ clearSessionAndSignOut();
410
+ } catch (error: unknown) {
411
+ setAuthProviderError(error as Error);
412
+ throw error;
413
+ } finally {
414
+ setAuthLoading(false);
415
+ }
416
+ }, [clearSessionAndSignOut]);
417
+
418
+ // Update user profile
419
+ const updateProfile = useCallback(async (displayName?: string, photoURL?: string) => {
420
+ setAuthLoading(true);
421
+ setAuthProviderError(null);
422
+
423
+ try {
424
+ if (!tokensRef.current) {
425
+ throw new Error("User is not logged in");
426
+ }
427
+ const response = await authApi.updateProfile(tokensRef.current.accessToken, displayName, photoURL);
428
+
429
+ // Update local user state
430
+ let convertedUser = convertToUser(response.user);
431
+ if (defineRolesFor) {
432
+ const customRoles = await defineRolesFor(convertedUser);
433
+ if (customRoles) {
434
+ convertedUser = { ...convertedUser, roles: customRoles.map(r => r.id) };
435
+ }
436
+ }
437
+
438
+ // Update storage
439
+ const storedData = loadAuthFromStorage();
440
+ if (storedData) {
441
+ saveAuthToStorage(storedData.tokens, response.user);
442
+ }
443
+
444
+ setUser(convertedUser);
445
+ return convertedUser;
446
+ } catch (error: unknown) {
447
+ setAuthProviderError(error as Error);
448
+ throw error;
449
+ } finally {
450
+ setAuthLoading(false);
451
+ }
452
+ }, [defineRolesFor]);
453
+
454
+ // Fetch active sessions
455
+ const fetchSessions = useCallback(async () => {
456
+ try {
457
+ if (!tokensRef.current) {
458
+ throw new Error("User is not logged in");
459
+ }
460
+ const response = await authApi.fetchSessions(tokensRef.current.accessToken, tokensRef.current.refreshToken);
461
+ return response.sessions;
462
+ } catch (error: unknown) {
463
+ setAuthProviderError(error as Error);
464
+ throw error;
465
+ }
466
+ }, []);
467
+
468
+ // Revoke a session
469
+ const revokeSession = useCallback(async (sessionId: string) => {
470
+ try {
471
+ if (!tokensRef.current) {
472
+ throw new Error("User is not logged in");
473
+ }
474
+ await authApi.revokeSession(tokensRef.current.accessToken, sessionId);
475
+ // If the revoked session is the current one, the next API request will fail with 401
476
+ // and trigger an auto-logout. Otherwise, it just removes it from the DB.
477
+ } catch (error: unknown) {
478
+ setAuthProviderError(error as Error);
479
+ throw error;
480
+ }
481
+ }, []);
482
+
483
+ // Restore auth state from localStorage on mount
484
+ useEffect(() => {
485
+ isMountedRef.current = true;
486
+
487
+ const restoreAuth = async () => {
488
+
489
+ // Fetch auth config (needsSetup, registrationEnabled, etc.)
490
+ try {
491
+ const config = await authApi.fetchAuthConfig();
492
+ if (isMountedRef.current) {
493
+ setAuthConfig(config);
494
+ }
495
+ } catch (e) {
496
+ }
497
+
498
+ const stored = loadAuthFromStorage();
499
+
500
+ if (!stored) {
501
+ setInitialLoading(false);
502
+ return;
503
+ }
504
+
505
+ if (!stored.tokens?.refreshToken) {
506
+ clearAuthFromStorage();
507
+ setInitialLoading(false);
508
+ return;
509
+ }
510
+
511
+
512
+ // Validate accessTokenExpiresAt is a valid number
513
+ const expiresAt = stored.tokens.accessTokenExpiresAt;
514
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) {
515
+ clearAuthFromStorage();
516
+ setInitialLoading(false);
517
+ return;
518
+ }
519
+
520
+
521
+ // Check if access token is still valid
522
+ if (!isTokenExpiredOrNearExpiry(stored.tokens.accessTokenExpiresAt)) {
523
+ // Token is still valid - use it directly
524
+ tokensRef.current = stored.tokens;
525
+
526
+ let userToSet = convertToUser(stored.user);
527
+ if (defineRolesFor) {
528
+ const customRoles = await defineRolesFor(userToSet);
529
+ if (customRoles) {
530
+ userToSet = { ...userToSet, roles: customRoles.map(r => r.id) };
531
+ }
532
+ }
533
+
534
+ setUser(userToSet);
535
+ scheduleTokenRefresh(stored.tokens);
536
+ setInitialLoading(false);
537
+ return;
538
+ }
539
+
540
+ // Token is expired or near expiry - refresh it
541
+ tokensRef.current = stored.tokens; // Set so refreshAccessToken can use it
542
+
543
+ try {
544
+ const newTokens = await refreshAccessToken();
545
+
546
+ if (!newTokens) {
547
+ clearAuthFromStorage();
548
+ tokensRef.current = null;
549
+ setInitialLoading(false);
550
+ return;
551
+ }
552
+
553
+ if (!isMountedRef.current) return;
554
+
555
+ // Fetch fresh user data from the server
556
+ let userToSet: User;
557
+ try {
558
+ const meResponse = await authApi.getCurrentUser(newTokens.accessToken);
559
+
560
+ if (!isMountedRef.current) return;
561
+
562
+ const freshUserInfo = meResponse.user;
563
+
564
+ // Update stored data with fresh user info
565
+ saveAuthToStorage(newTokens, freshUserInfo);
566
+
567
+ userToSet = convertToUser(freshUserInfo);
568
+
569
+ if (defineRolesFor) {
570
+ const customRoles = await defineRolesFor(userToSet);
571
+ if (!isMountedRef.current) return;
572
+ if (customRoles) {
573
+ userToSet = { ...userToSet, roles: customRoles.map(r => r.id) };
574
+ }
575
+ }
576
+ } catch (meError: unknown) {
577
+ if (!isMountedRef.current) return;
578
+ userToSet = convertToUser(stored.user);
579
+ }
580
+
581
+ if (!isMountedRef.current) return;
582
+
583
+ setUser(userToSet);
584
+ scheduleTokenRefresh(newTokens);
585
+ } catch (error: unknown) {
586
+ if (!isMountedRef.current) return;
587
+
588
+ // Do not clear the session entirely if it's just a temporary network outage
589
+ if (!(error instanceof Error && (error as { code?: string }).code === "NETWORK_ERROR")) {
590
+ clearAuthFromStorage();
591
+ tokensRef.current = null;
592
+ }
593
+ } finally {
594
+ if (isMountedRef.current) {
595
+ setInitialLoading(false);
596
+ }
597
+ }
598
+ };
599
+
600
+ restoreAuth();
601
+
602
+ return () => {
603
+ isMountedRef.current = false;
604
+ };
605
+ }, [scheduleTokenRefresh, defineRolesFor, refreshAccessToken]);
606
+
607
+ // Handle visibility change - refresh token when user returns to tab
608
+ useEffect(() => {
609
+ const handleVisibilityChange = async () => {
610
+ if (initialLoading) return;
611
+
612
+ if (document.visibilityState === "visible" && tokensRef.current) {
613
+ // Check if token needs refreshing
614
+ if (isTokenExpiredOrNearExpiry(tokensRef.current.accessTokenExpiresAt)) {
615
+ try {
616
+ const newTokens = await refreshAccessToken();
617
+
618
+ if (newTokens && isMountedRef.current) {
619
+ scheduleTokenRefresh(newTokens);
620
+ } else if (!newTokens && isMountedRef.current) {
621
+ clearSessionAndSignOut();
622
+ }
623
+ } catch (error) {
624
+ }
625
+ }
626
+ }
627
+ };
628
+
629
+ document.addEventListener("visibilitychange", handleVisibilityChange);
630
+
631
+ return () => {
632
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
633
+ };
634
+ }, [initialLoading, refreshAccessToken, scheduleTokenRefresh, clearSessionAndSignOut]);
635
+
636
+
637
+ // Get currently configured API URL
638
+ const getApiUrl = useCallback(() => {
639
+ return authApi.getApiUrl();
640
+ }, []);
641
+
642
+ // Cleanup on unmount
643
+ useEffect(() => {
644
+ return () => {
645
+ isMountedRef.current = false;
646
+ if (refreshTimeoutRef.current) {
647
+ clearTimeout(refreshTimeoutRef.current);
648
+ }
649
+ };
650
+ }, []);
651
+
652
+ // Revoke all sessions
653
+ const revokeAllSessions = useCallback(async () => {
654
+ try {
655
+ if (!tokensRef.current) {
656
+ throw new Error("User is not logged in");
657
+ }
658
+ await authApi.revokeAllSessions(tokensRef.current.accessToken);
659
+ clearSessionAndSignOut();
660
+ } catch (error: unknown) {
661
+ setAuthProviderError(error as Error);
662
+ throw error;
663
+ }
664
+ }, [clearSessionAndSignOut]);
665
+
666
+ return {
667
+ user,
668
+ authLoading,
669
+ initialLoading,
670
+ authError,
671
+ authProviderError,
672
+ loginSkipped,
673
+ needsSetup: authConfig?.needsSetup ?? false,
674
+ registrationEnabled: authConfig?.registrationEnabled ?? false,
675
+ getAuthToken,
676
+ getApiUrl,
677
+ signOut,
678
+ emailPasswordLogin,
679
+ register,
680
+ googleLogin,
681
+ skipLogin,
682
+ forgotPassword,
683
+ resetPassword,
684
+ changePassword,
685
+ updateProfile,
686
+ fetchSessions,
687
+ revokeSession,
688
+ revokeAllSessions,
689
+ extra,
690
+ setExtra
691
+ };
692
+ }