@rebasepro/auth 0.2.3 → 0.2.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.
- package/dist/api.d.ts +5 -1
- package/dist/hooks/useBackendUserManagement.d.ts +5 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +94 -73
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +94 -73
- package/dist/index.umd.js.map +1 -1
- package/dist/types.d.ts +9 -1
- package/package.json +30 -4
- package/src/api.ts +5 -1
- package/src/hooks/useBackendUserManagement.ts +121 -86
- package/src/hooks/useRebaseAuthController.ts +12 -9
- package/src/index.ts +1 -1
- package/src/types.ts +7 -1
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../src/api.ts","../src/hooks/useRebaseAuthController.ts","../src/hooks/useBackendUserManagement.ts"],"sourcesContent":["import { AuthResponse, RefreshResponse, Session, UserInfo } from \"./types\";\n\n/**\n * Default API URL - can be overridden in hook props\n */\nlet baseApiUrl = \"\";\n\n/**\n * Configure the API base URL\n */\nexport function setApiUrl(url: string): void {\n baseApiUrl = url;\n}\n\n/**\n * Get the current API URL\n */\nexport function getApiUrl(): string {\n return baseApiUrl;\n}\n\nclass AuthApiError extends Error {\n code: string;\n\n constructor(message: string, code: string) {\n super(message);\n this.code = code;\n this.name = \"AuthApiError\";\n }\n}\n\nasync function handleResponse<T>(response: Response): Promise<T> {\n let data: Record<string, unknown>;\n try {\n data = await response.json();\n } catch (parseError) {\n // Response wasn't JSON - could be network error or server issue\n throw new AuthApiError(\n `Server returned non-JSON response (status: ${response.status})`,\n \"PARSE_ERROR\"\n );\n }\n\n if (!response.ok) {\n throw new AuthApiError(\n (data as Record<string, Record<string, string>>).error?.message || \"Request failed\",\n (data as Record<string, Record<string, string>>).error?.code || \"UNKNOWN_ERROR\"\n );\n }\n\n return data as T;\n}\n\n/**\n * Wrapper for fetch that catches generic network failures (like server down)\n * and translates them to an AuthApiError.\n */\nasync function fetchWithHandling(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n try {\n return await fetch(input, init);\n } catch (error: unknown) {\n if (error instanceof TypeError && error.message.includes(\"Failed to fetch\")) {\n throw new AuthApiError(\n \"Failed to connect to the backend server. The backend might be down or failed to initialize (e.g., database connection timeout).\",\n \"NETWORK_ERROR\"\n );\n }\n throw new AuthApiError(\"Network error: \" + (error instanceof Error ? error.message : String(error)), \"NETWORK_ERROR\");\n }\n}\n\n/**\n * Register a new user with email/password\n */\nexport async function register(\n email: string,\n password: string,\n displayName?: string\n): Promise<AuthResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/register`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email,\npassword,\ndisplayName })\n });\n\n return handleResponse<AuthResponse>(response);\n}\n\n/**\n * Login with email/password\n */\nexport async function login(email: string, password: string): Promise<AuthResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/login`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email,\npassword })\n });\n\n return handleResponse<AuthResponse>(response);\n}\n\n/**\n * Google login payload — one of the three supported flows.\n */\nexport type GoogleLoginPayload =\n | { idToken: string }\n | { accessToken: string }\n | { code: string; redirectUri: string };\n\n/**\n * Login with Google.\n *\n * Accepts one of:\n * - `{ idToken }` — ID-token flow (One Tap / Sign In button)\n * - `{ accessToken }` — Access-token flow (popup)\n * - `{ code, redirectUri }` — Authorization code flow (most secure)\n */\nexport async function googleLogin(payload: GoogleLoginPayload): Promise<AuthResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/google`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload)\n });\n\n return handleResponse<AuthResponse>(response);\n}\n\n/**\n * Login with LinkedIn OAuth code\n */\nexport async function linkedinLogin(code: string, redirectUri: string): Promise<AuthResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/linkedin`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ code,\nredirectUri })\n });\n\n return handleResponse<AuthResponse>(response);\n}\n\n/**\n * Generic OAuth login — works with any provider registered on the backend.\n * The `providerId` is used to build the endpoint: `/api/auth/{providerId}`.\n */\nexport async function oauthLogin(providerId: string, payload: Record<string, unknown>): Promise<AuthResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/${providerId}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload)\n });\n\n return handleResponse<AuthResponse>(response);\n}\n\n/**\n * Refresh access token using refresh token\n */\nexport async function refreshAccessToken(refreshToken: string): Promise<RefreshResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/refresh`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refreshToken })\n });\n\n return handleResponse<RefreshResponse>(response);\n}\n\n/**\n * Logout and invalidate refresh token\n */\nexport async function logout(refreshToken?: string): Promise<void> {\n await fetchWithHandling(`${baseApiUrl}/api/auth/logout`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refreshToken })\n });\n}\n\n/**\n * Get current user info\n */\nexport async function getCurrentUser(accessToken: string): Promise<{ user: UserInfo }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/me`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n }\n });\n\n return handleResponse<{ user: UserInfo }>(response);\n}\n\n/**\n * Request password reset email\n */\nexport async function forgotPassword(email: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/forgot-password`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email })\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Reset password using token from email\n */\nexport async function resetPassword(token: string, password: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/reset-password`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ token,\npassword })\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Change password for authenticated user\n */\nexport async function changePassword(\n accessToken: string,\n oldPassword: string,\n newPassword: string\n): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/change-password`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n },\n body: JSON.stringify({ oldPassword,\nnewPassword })\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Send email verification link\n */\nexport async function sendVerificationEmail(accessToken: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/send-verification`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n }\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Verify email address using token\n */\nexport async function verifyEmail(token: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/verify-email?token=${encodeURIComponent(token)}`, {\n method: \"GET\",\n headers: { \"Content-Type\": \"application/json\" }\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Update current user profile\n */\nexport async function updateProfile(\n accessToken: string,\n displayName?: string,\n photoURL?: string\n): Promise<{ user: UserInfo }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/me`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n },\n body: JSON.stringify({ displayName,\nphotoURL })\n });\n\n return handleResponse<{ user: UserInfo }>(response);\n}\n\n/**\n * Fetch active sessions for current user\n */\nexport async function fetchSessions(accessToken: string, currentRefreshToken?: string): Promise<{ sessions: Session[] }> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n };\n if (currentRefreshToken) {\n headers[\"X-Refresh-Token\"] = currentRefreshToken;\n }\n\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/sessions`, {\n method: \"GET\",\n headers\n });\n\n return handleResponse<{ sessions: Session[] }>(response);\n}\n\n/**\n * Revoke a specific session\n */\nexport async function revokeSession(accessToken: string, sessionId: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/sessions/${sessionId}`, {\n method: \"DELETE\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n }\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Revoke all sessions for current user\n */\nexport async function revokeAllSessions(accessToken: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/sessions`, {\n method: \"DELETE\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n }\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Auth config response from the backend\n */\nexport interface AuthConfigResponse {\n /** True when there are no users in the system and first user setup is needed */\n needsSetup: boolean;\n /** Whether new user registration is enabled */\n registrationEnabled: boolean;\n /** Whether email service is configured */\n emailServiceEnabled: boolean;\n /** Complete list of enabled OAuth provider IDs (e.g. [\"google\", \"github\", \"discord\"]) */\n enabledProviders: string[];\n}\n\n/**\n * Inflight promise for `fetchAuthConfig` — ensures concurrent callers\n * (e.g. React StrictMode double-mount) reuse the same network request.\n */\nlet authConfigInflight: Promise<AuthConfigResponse> | null = null;\n\n/**\n * Cached result of the last successful `fetchAuthConfig` call.\n * Auth config is static for the lifetime of the app session, so\n * repeat calls (e.g. from effect re-runs) return instantly.\n */\nlet authConfigCached: AuthConfigResponse | null = null;\n\n/**\n * Fetch auth configuration / status from the backend\n * This is an unauthenticated endpoint used to detect bootstrap mode.\n *\n * Results are cached for the session lifetime.\n * Concurrent calls are deduplicated: only one network request is made\n * and all callers share the same promise.\n */\nexport async function fetchAuthConfig(): Promise<AuthConfigResponse> {\n if (authConfigCached) {\n return authConfigCached;\n }\n\n if (authConfigInflight) {\n return authConfigInflight;\n }\n\n authConfigInflight = (async () => {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/config`, {\n method: \"GET\",\n headers: { \"Content-Type\": \"application/json\" }\n });\n return handleResponse<AuthConfigResponse>(response);\n })();\n\n try {\n const result = await authConfigInflight;\n authConfigCached = result;\n return result;\n } finally {\n authConfigInflight = null;\n }\n}\n\n/**\n * Clear the cached auth config (e.g. on logout or for testing).\n */\nexport function clearAuthConfigCache(): void {\n authConfigCached = null;\n authConfigInflight = null;\n}\n\nexport { AuthApiError };\n\n\n","import { useCallback, useEffect, useState, useRef } from \"react\";\nimport { User } from \"@rebasepro/types\";\nimport * as authApi from \"../api\";\nimport { AuthConfigResponse } from \"../api\";\nimport {\n RebaseAuthController,\n RebaseAuthControllerProps,\n AuthTokens,\n UserInfo\n} from \"../types\";\n\nconst STORAGE_KEY = \"rebase_auth\";\n\n// Buffer time before expiry to trigger refresh (2 minutes)\nconst TOKEN_REFRESH_BUFFER_MS = 2 * 60 * 1000;\n\n/**\n * Convert UserInfo from API to Rebase User type\n */\nfunction convertToUser(userInfo: UserInfo): User {\n return {\n uid: userInfo.uid,\n email: userInfo.email,\n displayName: userInfo.displayName || null,\n photoURL: userInfo.photoURL || null,\n providerId: \"custom\",\n isAnonymous: false,\n roles: userInfo.roles || [],\n metadata: userInfo.metadata\n };\n}\n\n/**\n * Storage data structure\n */\ninterface StoredAuthData {\n tokens: AuthTokens;\n user: UserInfo;\n}\n\n/**\n * Save auth data to localStorage\n */\nfunction saveAuthToStorage(tokens: AuthTokens, user: UserInfo): void {\n try {\n const data: StoredAuthData = { tokens, user };\n localStorage.setItem(STORAGE_KEY, JSON.stringify(data));\n } catch (e) { /* ignore */ }\n}\n\n/**\n * Load auth data from localStorage\n */\nfunction loadAuthFromStorage(): StoredAuthData | null {\n try {\n const data = localStorage.getItem(STORAGE_KEY);\n if (data) {\n const parsed = JSON.parse(data);\n return parsed;\n }\n } catch (e) {\n console.warn(\"Failed to load auth from storage:\", e);\n }\n return null;\n}\n\n/**\n * Clear auth data from localStorage\n */\nfunction clearAuthFromStorage(): void {\n try {\n localStorage.removeItem(STORAGE_KEY);\n } catch (e) {\n console.warn(\"Failed to clear auth from storage:\", e);\n }\n}\n\n/**\n * Check if token is expired or about to expire\n */\nfunction isTokenExpiredOrNearExpiry(expiresAt: number, bufferMs: number = TOKEN_REFRESH_BUFFER_MS): boolean {\n return Date.now() + bufferMs >= expiresAt;\n}\n\n/**\n * Auth controller hook for JWT-based authentication\n * with @rebasepro/server-core\n *\n * @param props Configuration options\n * @returns RebaseAuthController instance\n */\nexport function useRebaseAuthController(\n props: RebaseAuthControllerProps = {}\n): RebaseAuthController {\n const { client, apiUrl, onSignOut, defineRolesFor } = props;\n\n const [user, setUser] = useState<User | null>(null);\n const [authLoading, setAuthLoading] = useState(false);\n const [initialLoading, setInitialLoading] = useState(true);\n const [authError, setAuthError] = useState<Error | null>(null);\n const [authProviderError, setAuthProviderError] = useState<Error | null>(null);\n const [loginSkipped, setLoginSkipped] = useState(false);\n const [extra, setExtra] = useState<unknown>(null);\n const [authConfig, setAuthConfig] = useState<AuthConfigResponse | null>(null);\n\n // Store tokens in ref for quick access, but also persist to localStorage\n const tokensRef = useRef<AuthTokens | null>(null);\n const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // Track if a refresh is currently in progress to avoid concurrent refreshes\n const refreshPromiseRef = useRef<Promise<AuthTokens | null> | null>(null);\n // Track if component is mounted\n const isMountedRef = useRef(true);\n\n // Configure API URL on mount\n useEffect(() => {\n if (client) {\n authApi.setApiUrl(client.baseUrl);\n } else if (apiUrl) {\n authApi.setApiUrl(apiUrl);\n }\n }, [client, apiUrl]);\n\n const clearError = useCallback(() => {\n setAuthProviderError(null);\n }, []);\n\n // Clear session and sign out\n const clearSessionAndSignOut = useCallback(() => {\n tokensRef.current = null;\n clearAuthFromStorage();\n if (refreshTimeoutRef.current) {\n clearTimeout(refreshTimeoutRef.current);\n refreshTimeoutRef.current = null;\n }\n setUser(null);\n setLoginSkipped(false);\n onSignOut?.();\n }, [onSignOut]);\n\n /**\n * Refresh the access token using the stored refresh token.\n * Returns the new tokens or null if refresh failed.\n */\n const refreshAccessToken = useCallback(async (): Promise<AuthTokens | null> => {\n // Prevent concurrent refreshes\n if (refreshPromiseRef.current) {\n // Wait for the current refresh to complete\n return refreshPromiseRef.current;\n }\n\n const executeRefresh = async (): Promise<AuthTokens | null> => {\n // Check if another tab has already refreshed the token\n const storedData = loadAuthFromStorage();\n if (storedData?.tokens?.accessTokenExpiresAt) {\n const storedTokens = storedData.tokens;\n // If stored token is newer and not expired\n if (!isTokenExpiredOrNearExpiry(storedTokens.accessTokenExpiresAt) && storedTokens.accessToken !== tokensRef.current?.accessToken) {\n tokensRef.current = storedTokens;\n return storedTokens;\n }\n }\n\n const currentTokens = tokensRef.current;\n if (!currentTokens?.refreshToken) {\n return null;\n }\n\n\n try {\n const response = await authApi.refreshAccessToken(currentTokens.refreshToken);\n const newTokens = response.tokens;\n\n // Update tokens immediately\n tokensRef.current = newTokens;\n\n // Persist to storage\n const latestStoredData = loadAuthFromStorage();\n if (latestStoredData) {\n saveAuthToStorage(newTokens, latestStoredData.user);\n }\n\n return newTokens;\n } catch (error: unknown) {\n\n // If it's a network error (e.g., backend restarting), we throw so callers can retry\n // instead of immediately assuming the refresh token is invalid and signing out.\n if (error instanceof Error && (error as { code?: string }).code === \"NETWORK_ERROR\") {\n throw error;\n }\n return null;\n } finally {\n refreshPromiseRef.current = null;\n }\n };\n\n refreshPromiseRef.current = executeRefresh();\n return refreshPromiseRef.current;\n }, []);\n\n // Schedule token refresh before expiry\n const scheduleTokenRefresh = useCallback((tokens: AuthTokens) => {\n if (refreshTimeoutRef.current) {\n clearTimeout(refreshTimeoutRef.current);\n }\n\n // Calculate when to refresh (2 minutes before expiry)\n const expiresAt = tokens.accessTokenExpiresAt;\n const refreshAt = expiresAt - TOKEN_REFRESH_BUFFER_MS;\n const timeUntilRefresh = refreshAt - Date.now();\n\n if (timeUntilRefresh <= 0) {\n // Token already expired or about to expire - refresh now\n refreshAccessToken().then(newTokens => {\n if (newTokens && isMountedRef.current) {\n scheduleTokenRefresh(newTokens);\n } else if (!newTokens && isMountedRef.current) {\n clearSessionAndSignOut();\n }\n });\n return;\n }\n\n\n refreshTimeoutRef.current = setTimeout(async () => {\n if (!isMountedRef.current) return;\n\n try {\n const newTokens = await refreshAccessToken();\n\n if (newTokens && isMountedRef.current) {\n scheduleTokenRefresh(newTokens);\n } else if (!newTokens && isMountedRef.current) {\n clearSessionAndSignOut();\n }\n } catch (error) {\n // Network error - try again shortly instead of logging out\n if (isMountedRef.current) {\n refreshTimeoutRef.current = setTimeout(() => {\n scheduleTokenRefresh(tokens);\n }, 10000);\n }\n }\n }, timeUntilRefresh);\n }, [refreshAccessToken, clearSessionAndSignOut]);\n\n // Get auth token for API requests (with automatic refresh if needed)\n const getAuthToken = useCallback(async (): Promise<string> => {\n // If still loading, throw - the UI should show a spinner\n if (initialLoading) {\n throw new Error(\"Auth is still loading\");\n }\n\n const currentTokens = tokensRef.current;\n if (!currentTokens) {\n throw new Error(\"User is not logged in\");\n }\n\n // Check if token is expired or about to expire\n if (isTokenExpiredOrNearExpiry(currentTokens.accessTokenExpiresAt)) {\n try {\n const newTokens = await refreshAccessToken();\n if (!newTokens) {\n clearSessionAndSignOut();\n throw new Error(\"Session expired. Please login again.\");\n }\n return newTokens.accessToken;\n } catch (error: unknown) {\n // If the error was a network error during refresh, just re-throw it\n // so the user isn't logged out locally and the network request fails naturally.\n if (error instanceof Error && (error as { code?: string }).code === \"NETWORK_ERROR\") {\n throw error;\n }\n clearSessionAndSignOut();\n throw error;\n }\n }\n\n return currentTokens.accessToken;\n }, [initialLoading, refreshAccessToken, clearSessionAndSignOut]);\n\n // Install token getter onto client\n useEffect(() => {\n if (client) {\n client.setAuthTokenGetter(async () => {\n try { return await getAuthToken(); } catch { return null; }\n });\n if (client.setOnUnauthorized) {\n client.setOnUnauthorized(async () => {\n try {\n const newTokens = await refreshAccessToken();\n if (newTokens) return true;\n clearSessionAndSignOut();\n return false;\n } catch (e) {\n clearSessionAndSignOut();\n return false;\n }\n });\n }\n if (client.ws) {\n client.ws.setAuthTokenGetter(async () => {\n return await getAuthToken();\n });\n }\n }\n }, [client, getAuthToken, refreshAccessToken, clearSessionAndSignOut]);\n\n // Handle successful authentication\n const handleAuthSuccess = useCallback(async (userInfo: UserInfo, tokens: AuthTokens) => {\n tokensRef.current = tokens;\n let convertedUser = convertToUser(userInfo);\n\n // Apply custom roles if defineRolesFor provided\n if (defineRolesFor) {\n const customRoles = await defineRolesFor(convertedUser);\n if (customRoles) {\n convertedUser = { ...convertedUser, roles: customRoles.map(r => r.id) };\n }\n }\n\n // Save to localStorage for persistence\n saveAuthToStorage(tokens, userInfo);\n\n setUser(convertedUser);\n setAuthError(null);\n setAuthProviderError(null);\n setLoginSkipped(false);\n scheduleTokenRefresh(tokens);\n }, [scheduleTokenRefresh, defineRolesFor]);\n\n // Email/password login\n const emailPasswordLogin = useCallback(async (email: string, password: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n const response = await authApi.login(email, password);\n await handleAuthSuccess(response.user, response.tokens);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [handleAuthSuccess]);\n\n // Register new user\n const register = useCallback(async (email: string, password: string, displayName?: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n const response = await authApi.register(email, password, displayName);\n await handleAuthSuccess(response.user, response.tokens);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [handleAuthSuccess]);\n\n // Google login — accepts payload object with code flow or token\n const googleLogin = useCallback(async (\n payload: { code: string; redirectUri: string } | { idToken: string } | { accessToken: string }\n ) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n const response = await authApi.googleLogin(payload);\n await handleAuthSuccess(response.user, response.tokens);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [handleAuthSuccess]);\n\n // Generic OAuth login — works with any provider registered on the backend\n const oauthLogin = useCallback(async (providerId: string, payload: Record<string, unknown>) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n const response = await authApi.oauthLogin(providerId, payload);\n await handleAuthSuccess(response.user, response.tokens);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [handleAuthSuccess]);\n\n // Sign out\n const signOut = useCallback(async () => {\n try {\n if (tokensRef.current) {\n await authApi.logout(tokensRef.current.refreshToken);\n }\n } catch (error) {\n console.error(\"Logout error:\", error);\n } finally {\n clearSessionAndSignOut();\n }\n }, [clearSessionAndSignOut]);\n\n // Skip login\n const skipLogin = useCallback(() => {\n setLoginSkipped(true);\n setUser(null);\n }, []);\n\n // Forgot password - request reset email\n const forgotPassword = useCallback(async (email: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n await authApi.forgotPassword(email);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, []);\n\n // Reset password using token\n const resetPassword = useCallback(async (token: string, password: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n await authApi.resetPassword(token, password);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, []);\n\n // Change password for authenticated user\n const changePassword = useCallback(async (oldPassword: string, newPassword: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n if (!tokensRef.current) {\n throw new Error(\"User is not logged in\");\n }\n await authApi.changePassword(tokensRef.current.accessToken, oldPassword, newPassword);\n // After password change, user needs to log in again (all sessions invalidated)\n clearSessionAndSignOut();\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [clearSessionAndSignOut]);\n\n // Update user profile\n const updateProfile = useCallback(async (displayName?: string, photoURL?: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n if (!tokensRef.current) {\n throw new Error(\"User is not logged in\");\n }\n const response = await authApi.updateProfile(tokensRef.current.accessToken, displayName, photoURL);\n\n // Update local user state\n let convertedUser = convertToUser(response.user);\n if (defineRolesFor) {\n const customRoles = await defineRolesFor(convertedUser);\n if (customRoles) {\n convertedUser = { ...convertedUser,\nroles: customRoles.map(r => r.id) };\n }\n }\n\n // Update storage\n const storedData = loadAuthFromStorage();\n if (storedData) {\n saveAuthToStorage(storedData.tokens, response.user);\n }\n\n setUser(convertedUser);\n return convertedUser;\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [defineRolesFor]);\n\n // Fetch active sessions\n const fetchSessions = useCallback(async () => {\n try {\n if (!tokensRef.current) {\n throw new Error(\"User is not logged in\");\n }\n const response = await authApi.fetchSessions(tokensRef.current.accessToken, tokensRef.current.refreshToken);\n return response.sessions;\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n }\n }, []);\n\n // Revoke a session\n const revokeSession = useCallback(async (sessionId: string) => {\n try {\n if (!tokensRef.current) {\n throw new Error(\"User is not logged in\");\n }\n await authApi.revokeSession(tokensRef.current.accessToken, sessionId);\n // If the revoked session is the current one, the next API request will fail with 401\n // and trigger an auto-logout. Otherwise, it just removes it from the DB.\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n }\n }, []);\n\n // Restore auth state from localStorage on mount\n useEffect(() => {\n isMountedRef.current = true;\n\n const restoreAuth = async () => {\n\n // Fetch auth config (needsSetup, registrationEnabled, etc.)\n try {\n const config = await authApi.fetchAuthConfig();\n if (isMountedRef.current) {\n setAuthConfig(config);\n }\n } catch (e) { /* ignore */ }\n\n const stored = loadAuthFromStorage();\n\n if (!stored) {\n setInitialLoading(false);\n return;\n }\n\n if (!stored.tokens?.refreshToken) {\n clearAuthFromStorage();\n setInitialLoading(false);\n return;\n }\n\n\n // Validate accessTokenExpiresAt is a valid number\n const expiresAt = stored.tokens.accessTokenExpiresAt;\n if (typeof expiresAt !== \"number\" || !Number.isFinite(expiresAt)) {\n clearAuthFromStorage();\n setInitialLoading(false);\n return;\n }\n\n\n // Check if access token is still valid\n if (!isTokenExpiredOrNearExpiry(stored.tokens.accessTokenExpiresAt)) {\n // Token is still valid - use it directly\n tokensRef.current = stored.tokens;\n\n let userToSet = convertToUser(stored.user);\n if (defineRolesFor) {\n const customRoles = await defineRolesFor(userToSet);\n if (customRoles) {\n userToSet = { ...userToSet,\nroles: customRoles.map(r => r.id) };\n }\n }\n\n setUser(userToSet);\n scheduleTokenRefresh(stored.tokens);\n setInitialLoading(false);\n return;\n }\n\n // Token is expired or near expiry - refresh it\n tokensRef.current = stored.tokens; // Set so refreshAccessToken can use it\n\n try {\n const newTokens = await refreshAccessToken();\n\n if (!newTokens) {\n clearAuthFromStorage();\n tokensRef.current = null;\n setInitialLoading(false);\n return;\n }\n\n if (!isMountedRef.current) return;\n\n // Fetch fresh user data from the server\n let userToSet: User;\n try {\n const meResponse = await authApi.getCurrentUser(newTokens.accessToken);\n\n if (!isMountedRef.current) return;\n\n const freshUserInfo = meResponse.user;\n\n // Update stored data with fresh user info\n saveAuthToStorage(newTokens, freshUserInfo);\n\n userToSet = convertToUser(freshUserInfo);\n\n if (defineRolesFor) {\n const customRoles = await defineRolesFor(userToSet);\n if (!isMountedRef.current) return;\n if (customRoles) {\n userToSet = { ...userToSet,\nroles: customRoles.map(r => r.id) };\n }\n }\n } catch (meError: unknown) {\n if (!isMountedRef.current) return;\n userToSet = convertToUser(stored.user);\n }\n\n if (!isMountedRef.current) return;\n\n setUser(userToSet);\n scheduleTokenRefresh(newTokens);\n } catch (error: unknown) {\n if (!isMountedRef.current) return;\n\n // Do not clear the session entirely if it's just a temporary network outage\n if (!(error instanceof Error && (error as { code?: string }).code === \"NETWORK_ERROR\")) {\n clearAuthFromStorage();\n tokensRef.current = null;\n }\n } finally {\n if (isMountedRef.current) {\n setInitialLoading(false);\n }\n }\n };\n\n restoreAuth();\n\n return () => {\n isMountedRef.current = false;\n };\n }, [scheduleTokenRefresh, defineRolesFor, refreshAccessToken]);\n\n // Handle visibility change - refresh token when user returns to tab\n useEffect(() => {\n const handleVisibilityChange = async () => {\n if (initialLoading) return;\n\n if (document.visibilityState === \"visible\" && tokensRef.current) {\n // Check if token needs refreshing\n if (isTokenExpiredOrNearExpiry(tokensRef.current.accessTokenExpiresAt)) {\n try {\n const newTokens = await refreshAccessToken();\n\n if (newTokens && isMountedRef.current) {\n scheduleTokenRefresh(newTokens);\n } else if (!newTokens && isMountedRef.current) {\n clearSessionAndSignOut();\n }\n } catch (e) { /* ignore */ }\n }\n }\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n return () => {\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [initialLoading, refreshAccessToken, scheduleTokenRefresh, clearSessionAndSignOut]);\n\n\n // Get currently configured API URL\n const getApiUrl = useCallback(() => {\n return authApi.getApiUrl();\n }, []);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n isMountedRef.current = false;\n if (refreshTimeoutRef.current) {\n clearTimeout(refreshTimeoutRef.current);\n }\n };\n }, []);\n\n // Revoke all sessions\n const revokeAllSessions = useCallback(async () => {\n try {\n if (!tokensRef.current) {\n throw new Error(\"User is not logged in\");\n }\n await authApi.revokeAllSessions(tokensRef.current.accessToken);\n clearSessionAndSignOut();\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n }\n }, [clearSessionAndSignOut]);\n\n return {\n user,\n authLoading,\n initialLoading,\n authError,\n authProviderError,\n loginSkipped,\n needsSetup: authConfig?.needsSetup ?? false,\n registrationEnabled: authConfig?.registrationEnabled ?? false,\n getAuthToken,\n getApiUrl,\n signOut,\n emailPasswordLogin,\n register,\n googleLogin,\n oauthLogin,\n skipLogin,\n forgotPassword,\n resetPassword,\n changePassword,\n updateProfile,\n fetchSessions,\n revokeSession,\n revokeAllSessions,\n clearError,\n setAuthProviderError,\n extra,\n setExtra,\n capabilities: {\n emailPasswordLogin: true,\n googleLogin: !!(props.googleClientId),\n registration: authConfig?.registrationEnabled ?? false,\n passwordReset: true,\n sessionManagement: true,\n profileUpdate: true,\n emailVerification: false,\n enabledProviders: authConfig?.enabledProviders ?? []\n }\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Role, User } from \"@rebasepro/types\";\n\n/**\n * UserManagement interface - compatible with @rebasepro/user_management\n * Defined inline to avoid dependency on that package\n */\nexport interface UserManagement<USER extends User = User> {\n loading: boolean;\n\n users: USER[];\n saveUser: (user: USER) => Promise<USER>;\n createUser?: (user: USER) => Promise<{\n user: USER;\n invitationSent: boolean;\n temporaryPassword?: string;\n }>;\n resetPassword?: (user: USER) => Promise<{\n user: USER;\n invitationSent: boolean;\n temporaryPassword?: string;\n }>;\n deleteUser: (user: USER) => Promise<void>;\n\n roles: Role[];\n saveRole: (role: Role) => Promise<void>;\n deleteRole: (role: Role) => Promise<void>;\n\n isAdmin?: boolean;\n allowDefaultRolesCreation?: boolean;\n includeCollectionConfigPermissions?: boolean;\n defineRolesFor: (user: User) => Promise<Role[] | undefined> | Role[] | undefined;\n getUser: (uid: string) => User | null;\n\n /**\n * Search users with server-side pagination.\n * When provided, the CMS will use this for the users table\n * instead of loading all users into memory.\n */\n searchUsers?: (options: {\n search?: string;\n limit?: number;\n offset?: number;\n orderBy?: string;\n orderDir?: \"asc\" | \"desc\";\n roleId?: string;\n }) => Promise<{ users: USER[]; total: number }>;\n\n usersError?: Error;\n rolesError?: Error;\n bootstrapAdmin?: () => Promise<void>;\n}\n\nexport interface BackendUserManagementConfig {\n /**\n * The Rebase Client instance\n */\n client?: any;\n\n /**\n * Base API URL for the backend (optional, extracted from client if not provided)\n */\n apiUrl?: string;\n\n /**\n * Function to get the current auth token (optional, extracted from client if not provided)\n */\n getAuthToken?: () => Promise<string>;\n\n /**\n * Current logged-in user\n */\n currentUser?: User | null;\n}\n\ninterface ApiUser {\n uid: string;\n email: string;\n displayName?: string | null;\n photoURL?: string | null;\n roles: string[];\n createdAt?: string;\n updatedAt?: string;\n}\n\ninterface ApiRole {\n id: string;\n name: string;\n isAdmin?: boolean;\n config?: Record<string, any>;\n}\n\n/**\n * Convert API user to Rebase User\n * @param apiUser - The API user object\n * @param availableRoles - Optional array of available roles to look up names\n */\nfunction convertUser(apiUser: ApiUser): User {\n return {\n uid: apiUser.uid,\n email: apiUser.email,\n displayName: apiUser.displayName || null,\n photoURL: apiUser.photoURL || null,\n providerId: \"custom\",\n isAnonymous: false,\n roles: apiUser.roles,\n createdAt: apiUser.createdAt ? new Date(apiUser.createdAt) : null\n } as User;\n}\n\n/**\n * Convert API role to Rebase Role\n */\nfunction convertRole(apiRole: ApiRole): Role {\n return {\n id: apiRole.id,\n name: apiRole.name,\n isAdmin: apiRole.isAdmin ?? false,\n config: apiRole.config ?? undefined\n };\n}\n\n/**\n * Hook to manage users and roles via backend API\n * Compatible with Rebase UserManagement interface\n */\nexport function useBackendUserManagement(config: BackendUserManagementConfig): UserManagement {\n const { client, apiUrl, getAuthToken, currentUser } = config;\n\n // We no longer load ALL users into memory.\n // `users` now only holds admin/role-bearing users for getUser/defineRolesFor lookups.\n const [users, setUsers] = useState<User[]>([]);\n const [roles, setRoles] = useState<Role[]>([]);\n const [loading, setLoading] = useState(true);\n const [usersError, setUsersError] = useState<Error | undefined>();\n const [rolesError, setRolesError] = useState<Error | undefined>();\n\n // Tracks the UID for which roles+users were last successfully loaded.\n // Prevents redundant refetches on React StrictMode double-mounts.\n const lastLoadedUidRef = useRef<string | null>(null);\n\n // Ref to hold the latest apiRequest so the initial-load effect doesn't\n // re-trigger every time the callback identity changes.\n const apiRequestRef = useRef<typeof apiRequest | null>(null);\n\n /**\n * Make authenticated API request\n */\n const apiRequest = useCallback(async (\n endpoint: string,\n method = \"GET\",\n body?: Record<string, unknown>,\n retryCount = 6,\n signal?: AbortSignal\n ): Promise<any> => {\n let lastError: Error | null = null;\n for (let attempt = 0; attempt < retryCount; attempt++) {\n if (signal?.aborted) {\n const error = new Error(\"Request aborted\");\n error.name = \"AbortError\";\n throw error;\n }\n\n try {\n // Determine token provider\n const token = getAuthToken ? await getAuthToken() : (client ? await client.resolveToken() : null);\n const baseUrl = apiUrl || (client?.baseUrl ? client.baseUrl : \"\");\n\n // Use /api/admin prefix for admin endpoints\n const response = await fetch(`${baseUrl}/api/admin${endpoint}`, {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(token ? { \"Authorization\": `Bearer ${token}` } : {})\n },\n body: body ? JSON.stringify(body) : undefined,\n signal\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage = \"API request failed\";\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error?.message || errorMessage;\n } catch (e) {\n errorMessage = errorText || `HTTP error ${response.status}`;\n }\n\n const error = Object.assign(new Error(errorMessage), { status: response.status });\n throw error;\n }\n\n return await response.json();\n } catch (error: unknown) {\n if (error instanceof Error && error.name === \"AbortError\" || signal?.aborted) {\n throw error;\n }\n\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Retry conditions: Network errors (TypeError) OR 5xx Server Errors (Backend rebooting)\n const isNetworkError = error instanceof TypeError;\n const isServerError = typeof (error as { status?: number }).status === \"number\" && (error as { status: number }).status >= 500 && (error as { status: number }).status < 600;\n\n if (attempt < retryCount - 1 && (isNetworkError || isServerError)) {\n const delay = Math.min(1000 * Math.pow(2, attempt), 5000); // 1s, 2s, 4s...\n console.warn(`Admin API request to ${endpoint} failed, retrying in ${delay}ms...`);\n\n // Wait for delay or abort\n await new Promise<void>((resolve, reject) => {\n if (signal?.aborted) return reject(new Error(\"AbortError\"));\n const timer = setTimeout(resolve, delay);\n if (signal) {\n signal.addEventListener(\"abort\", () => {\n clearTimeout(timer);\n reject(new Error(\"AbortError\"));\n }, { once: true });\n }\n }).catch(() => {}); // Catch AbortError from wait\n\n if (signal?.aborted) {\n const abortError = new Error(\"Request aborted\");\n abortError.name = \"AbortError\";\n throw abortError;\n }\n continue;\n }\n\n console.error(\"Admin API error after retries:\", error);\n throw error;\n }\n }\n throw lastError;\n }, [apiUrl, getAuthToken]);\n\n // Keep the ref in sync after every render.\n apiRequestRef.current = apiRequest;\n\n /**\n * Load roles from API\n */\n const loadRoles = useCallback(async (signal?: AbortSignal) => {\n try {\n const data = await apiRequest(\"/roles\", \"GET\", undefined, 6, signal);\n setRoles(data.roles.map(convertRole));\n setRolesError(undefined);\n } catch (error: unknown) {\n if (error instanceof Error && error.name === \"AbortError\") return;\n console.error(\"Failed to load roles:\", error);\n setRolesError(error instanceof Error ? error : new Error(String(error)));\n }\n }, [apiRequest]);\n\n /**\n * Load users for getUser/defineRolesFor lookups and for UserSelect dropdowns.\n */\n const loadUsers = useCallback(async (signal?: AbortSignal) => {\n try {\n // Load all users to satisfy Rebase CMS UserSelect field bindings\n const data = await apiRequest(\"/users\", \"GET\", undefined, 6, signal);\n const allUsers: User[] = data.users.map((u: ApiUser) => convertUser(u));\n setUsers(allUsers);\n setUsersError(undefined);\n } catch (error: unknown) {\n if (error instanceof Error && error.name === \"AbortError\") return;\n console.error(\"Failed to load users:\", error);\n setUsersError(error instanceof Error ? error : new Error(String(error)));\n }\n }, [apiRequest]);\n\n /**\n * Initial data load - only when user is logged in\n * Load roles first, then admin users.\n *\n * Dependencies are intentionally limited to `currentUser?.uid` so the\n * effect does NOT re-run when callback identities change. The latest\n * `apiRequest` is read via `apiRequestRef`.\n */\n useEffect(() => {\n // Don't load if no user is logged in\n if (!currentUser) {\n setLoading(false);\n return;\n }\n\n // Skip admin API calls for non-admin users — they'd get 403 anyway.\n // This avoids a spurious warning in backend logs on every non-admin login.\n const userRoles = currentUser.roles ?? [];\n const isUserAdmin = userRoles.some(r => r === \"admin\" || r === \"schema-admin\");\n if (!isUserAdmin) {\n setLoading(false);\n return;\n }\n\n // Skip refetch if we already loaded data for this same UID\n // (e.g. React StrictMode unmounts and re-mounts with the same user).\n if (lastLoadedUidRef.current === currentUser.uid) {\n setLoading(false);\n return;\n }\n\n const abortController = new AbortController();\n\n const load = async () => {\n setLoading(true);\n const request = apiRequestRef.current!;\n\n // Load roles first\n try {\n const data = await request(\"/roles\", \"GET\", undefined, 6, abortController.signal);\n setRoles(data.roles.map(convertRole));\n setRolesError(undefined);\n } catch (error: unknown) {\n if (error instanceof Error && error.name === \"AbortError\") return;\n console.error(\"Failed to load roles:\", error);\n setRolesError(error instanceof Error ? error : new Error(String(error)));\n\n // If the error is a permission issue (e.g. 403), skip loading\n // users — they will fail with the same error and we'd show a\n // duplicate snackbar / error message.\n const status = (error as { status?: number }).status;\n if (status === 403 || status === 401) {\n setUsersError(error instanceof Error ? error : new Error(String(error)));\n setLoading(false);\n return;\n }\n }\n\n // Then load all users if not aborted\n if (!abortController.signal.aborted) {\n try {\n const data = await request(\"/users\", \"GET\", undefined, 6, abortController.signal);\n const allUsers: User[] = data.users.map((u: ApiUser) => convertUser(u));\n setUsers(allUsers);\n setUsersError(undefined);\n } catch (error: unknown) {\n if (error instanceof Error && error.name === \"AbortError\") return;\n console.error(\"Failed to load users:\", error);\n setUsersError(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n if (!abortController.signal.aborted) {\n lastLoadedUidRef.current = currentUser.uid;\n setLoading(false);\n }\n };\n load();\n\n return () => {\n abortController.abort();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [currentUser?.uid]);\n\n /**\n * Search users with server-side pagination.\n * This is the primary method used by the UsersView table.\n */\n const searchUsers = useCallback(async (options: {\n search?: string;\n limit?: number;\n offset?: number;\n orderBy?: string;\n orderDir?: \"asc\" | \"desc\";\n roleId?: string;\n }): Promise<{ users: User[]; total: number }> => {\n const params = new URLSearchParams();\n if (options.limit !== undefined) params.set(\"limit\", String(options.limit));\n if (options.offset !== undefined) params.set(\"offset\", String(options.offset));\n if (options.search) params.set(\"search\", options.search);\n if (options.orderBy) params.set(\"orderBy\", options.orderBy);\n if (options.orderDir) params.set(\"orderDir\", options.orderDir);\n if (options.roleId) params.set(\"role\", options.roleId);\n const qs = params.toString();\n\n const data = await apiRequest(\"/users\" + (qs ? \"?\" + qs : \"\"), \"GET\");\n return {\n users: data.users.map((u: ApiUser) => convertUser(u)),\n total: data.total\n };\n }, [apiRequest]);\n\n /**\n * Save user (create or update)\n */\n const saveUser = useCallback(async (user: User): Promise<User> => {\n const roleIds = user.roles ?? [];\n\n // Check if user exists\n const existingUser = users.find(u => u.uid === user.uid);\n\n if (existingUser) {\n // Update\n const data = await apiRequest(`/users/${user.uid}`, \"PUT\", {\n email: user.email,\n displayName: user.displayName,\n roles: roleIds\n });\n const updated = convertUser(data.user);\n setUsers(prev => prev.map(u => u.uid === updated.uid ? updated : u));\n return updated;\n } else {\n // Create\n const data = await apiRequest(\"/users\", \"POST\", {\n email: user.email,\n displayName: user.displayName,\n roles: roleIds\n });\n const created = convertUser(data.user);\n setUsers(prev => [...prev, created]);\n return created;\n }\n }, [apiRequest, users, roles]);\n\n /**\n * Create a new user with invitation/password generation support.\n * Returns additional info about how credentials were delivered.\n */\n const createUser = useCallback(async (user: User): Promise<{\n user: User;\n invitationSent: boolean;\n temporaryPassword?: string;\n }> => {\n const roleIds = user.roles ?? [];\n\n const data = await apiRequest(\"/users\", \"POST\", {\n email: user.email,\n displayName: user.displayName,\n roles: roleIds\n });\n const created = convertUser(data.user);\n // Add to users cache\n setUsers(prev => [...prev, created]);\n return {\n user: created,\n invitationSent: data.invitationSent ?? false,\n temporaryPassword: data.temporaryPassword\n };\n }, [apiRequest, roles]);\n\n /**\n * Reset the password for an existing user\n */\n const resetPassword = useCallback(async (user: User): Promise<{\n user: User;\n invitationSent: boolean;\n temporaryPassword?: string;\n }> => {\n const data = await apiRequest(`/users/${user.uid}/reset-password`, \"POST\");\n const updatedUser = convertUser(data.user);\n setUsers(prev => prev.map(u => u.uid === updatedUser.uid ? updatedUser : u));\n return {\n user: updatedUser,\n invitationSent: data.invitationSent ?? false,\n temporaryPassword: data.temporaryPassword\n };\n }, [apiRequest]);\n\n /**\n * Delete user\n */\n const deleteUser = useCallback(async (user: User): Promise<void> => {\n await apiRequest(`/users/${user.uid}`, \"DELETE\");\n setUsers(prev => prev.filter(u => u.uid !== user.uid));\n }, [apiRequest]);\n\n /**\n * Save role (create or update)\n */\n const saveRole = useCallback(async (role: Role): Promise<void> => {\n // Check if role exists\n const existingRole = roles.find(r => r.id === role.id);\n\n if (existingRole) {\n // Update\n const data = await apiRequest(`/roles/${role.id}`, \"PUT\", {\n name: role.name,\n isAdmin: role.isAdmin,\n config: role.config\n });\n const updated = convertRole(data.role);\n setRoles(prev => prev.map(r => r.id === updated.id ? updated : r));\n } else {\n // Create\n const data = await apiRequest(\"/roles\", \"POST\", {\n id: role.id,\n name: role.name,\n isAdmin: role.isAdmin ?? false,\n config: role.config\n });\n const created = convertRole(data.role);\n setRoles(prev => [...prev, created]);\n }\n }, [apiRequest, roles]);\n\n /**\n * Delete role\n */\n const deleteRole = useCallback(async (role: Role): Promise<void> => {\n await apiRequest(`/roles/${role.id}`, \"DELETE\");\n setRoles(prev => prev.filter(r => r.id !== role.id));\n }, [apiRequest]);\n\n /**\n * Get user by uid\n */\n const getUser = useCallback((uid: string): User | null => {\n return users.find(u => u.uid === uid) ?? null;\n }, [users]);\n\n /**\n * Define roles for a given user (for authController)\n */\n const defineRolesFor = useCallback(async (user: User): Promise<Role[] | undefined> => {\n // Find the user in our list\n const existingUser = users.find(u => u.uid === user.uid || u.email === user.email);\n if (!existingUser) return undefined;\n\n // Return roles from our cached role data (string IDs → full Role objects)\n const userRoleIds = existingUser.roles ?? [];\n return roles.filter(r => userRoleIds.includes(r.id));\n }, [users, roles]);\n\n /**\n * Check if current user is admin\n */\n const isAdmin = currentUser?.roles?.includes(\"admin\") ?? false;\n\n\n /**\n * Bootstrap default admin\n */\n const bootstrapAdmin = useCallback(async (): Promise<void> => {\n try {\n await apiRequest(\"/bootstrap\", \"POST\");\n // Reload users and roles after successful bootstrap\n const data = await apiRequest(\"/roles\");\n const loadedRoles = data.roles.map(convertRole);\n setRoles(loadedRoles);\n await loadUsers();\n } catch (error) {\n console.error(\"Failed to bootstrap admin:\", error);\n throw error;\n }\n }, [apiRequest, loadUsers]);\n\n return {\n loading,\n users,\n saveUser,\n createUser,\n resetPassword,\n deleteUser,\n roles,\n saveRole,\n deleteRole,\n isAdmin,\n allowDefaultRolesCreation: isAdmin,\n includeCollectionConfigPermissions: true,\n defineRolesFor,\n getUser,\n searchUsers,\n usersError,\n rolesError,\n bootstrapAdmin\n };\n}\n"],"names":["authApi.setApiUrl","refreshAccessToken","authApi.refreshAccessToken","authApi.login","register","authApi.register","googleLogin","authApi.googleLogin","oauthLogin","authApi.oauthLogin","authApi.logout","forgotPassword","authApi.forgotPassword","resetPassword","authApi.resetPassword","changePassword","authApi.changePassword","updateProfile","authApi.updateProfile","fetchSessions","authApi.fetchSessions","revokeSession","authApi.revokeSession","authApi.fetchAuthConfig","authApi.getCurrentUser","getApiUrl","authApi.getApiUrl","revokeAllSessions","authApi.revokeAllSessions"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/api.ts","../src/hooks/useRebaseAuthController.ts","../src/hooks/useBackendUserManagement.ts"],"sourcesContent":["import { AuthResponse, RefreshResponse, Session, UserInfo } from \"./types\";\n\n/**\n * Default API URL - can be overridden in hook props\n */\nlet baseApiUrl = \"\";\n\n/**\n * Configure the API base URL\n */\nexport function setApiUrl(url: string): void {\n baseApiUrl = url;\n}\n\n/**\n * Get the current API URL\n */\nexport function getApiUrl(): string {\n return baseApiUrl;\n}\n\nclass AuthApiError extends Error {\n code: string;\n\n constructor(message: string, code: string) {\n super(message);\n this.code = code;\n this.name = \"AuthApiError\";\n }\n}\n\nasync function handleResponse<T>(response: Response): Promise<T> {\n let data: Record<string, unknown>;\n try {\n data = await response.json();\n } catch (parseError) {\n // Response wasn't JSON - could be network error or server issue\n throw new AuthApiError(\n `Server returned non-JSON response (status: ${response.status})`,\n \"PARSE_ERROR\"\n );\n }\n\n if (!response.ok) {\n throw new AuthApiError(\n (data as Record<string, Record<string, string>>).error?.message || \"Request failed\",\n (data as Record<string, Record<string, string>>).error?.code || \"UNKNOWN_ERROR\"\n );\n }\n\n return data as T;\n}\n\n/**\n * Wrapper for fetch that catches generic network failures (like server down)\n * and translates them to an AuthApiError.\n */\nasync function fetchWithHandling(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n try {\n return await fetch(input, init);\n } catch (error: unknown) {\n if (error instanceof TypeError && error.message.includes(\"Failed to fetch\")) {\n throw new AuthApiError(\n \"Failed to connect to the backend server. The backend might be down or failed to initialize (e.g., database connection timeout).\",\n \"NETWORK_ERROR\"\n );\n }\n throw new AuthApiError(\"Network error: \" + (error instanceof Error ? error.message : String(error)), \"NETWORK_ERROR\");\n }\n}\n\n/**\n * Register a new user with email/password\n */\nexport async function register(\n email: string,\n password: string,\n displayName?: string\n): Promise<AuthResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/register`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email,\npassword,\ndisplayName })\n });\n\n return handleResponse<AuthResponse>(response);\n}\n\n/**\n * Login with email/password\n */\nexport async function login(email: string, password: string): Promise<AuthResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/login`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email,\npassword })\n });\n\n return handleResponse<AuthResponse>(response);\n}\n\n/**\n * Google login payload — one of the three supported flows.\n */\nexport type GoogleLoginPayload =\n | { idToken: string }\n | { accessToken: string }\n | { code: string; redirectUri: string };\n\n/**\n * Login with Google.\n *\n * Accepts one of:\n * - `{ idToken }` — ID-token flow (One Tap / Sign In button)\n * - `{ accessToken }` — Access-token flow (popup)\n * - `{ code, redirectUri }` — Authorization code flow (most secure)\n */\nexport async function googleLogin(payload: GoogleLoginPayload): Promise<AuthResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/google`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload)\n });\n\n return handleResponse<AuthResponse>(response);\n}\n\n/**\n * Login with LinkedIn OAuth code\n */\nexport async function linkedinLogin(code: string, redirectUri: string): Promise<AuthResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/linkedin`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ code,\nredirectUri })\n });\n\n return handleResponse<AuthResponse>(response);\n}\n\n/**\n * Generic OAuth login — works with any provider registered on the backend.\n * The `providerId` is used to build the endpoint: `/api/auth/{providerId}`.\n */\nexport async function oauthLogin(providerId: string, payload: Record<string, unknown>): Promise<AuthResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/${providerId}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload)\n });\n\n return handleResponse<AuthResponse>(response);\n}\n\n/**\n * Refresh access token using refresh token\n */\nexport async function refreshAccessToken(refreshToken: string): Promise<RefreshResponse> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/refresh`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refreshToken })\n });\n\n return handleResponse<RefreshResponse>(response);\n}\n\n/**\n * Logout and invalidate refresh token\n */\nexport async function logout(refreshToken?: string): Promise<void> {\n await fetchWithHandling(`${baseApiUrl}/api/auth/logout`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refreshToken })\n });\n}\n\n/**\n * Get current user info\n */\nexport async function getCurrentUser(accessToken: string): Promise<{ user: UserInfo }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/me`, {\n method: \"GET\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n }\n });\n\n return handleResponse<{ user: UserInfo }>(response);\n}\n\n/**\n * Request password reset email\n */\nexport async function forgotPassword(email: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/forgot-password`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email })\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Reset password using token from email\n */\nexport async function resetPassword(token: string, password: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/reset-password`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ token,\npassword })\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Change password for authenticated user\n */\nexport async function changePassword(\n accessToken: string,\n oldPassword: string,\n newPassword: string\n): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/change-password`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n },\n body: JSON.stringify({ oldPassword,\nnewPassword })\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Send email verification link\n */\nexport async function sendVerificationEmail(accessToken: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/send-verification`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n }\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Verify email address using token\n */\nexport async function verifyEmail(token: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/verify-email?token=${encodeURIComponent(token)}`, {\n method: \"GET\",\n headers: { \"Content-Type\": \"application/json\" }\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Update current user profile\n */\nexport async function updateProfile(\n accessToken: string,\n displayName?: string,\n photoURL?: string\n): Promise<{ user: UserInfo }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/me`, {\n method: \"PATCH\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n },\n body: JSON.stringify({ displayName,\nphotoURL })\n });\n\n return handleResponse<{ user: UserInfo }>(response);\n}\n\n/**\n * Fetch active sessions for current user\n */\nexport async function fetchSessions(accessToken: string, currentRefreshToken?: string): Promise<{ sessions: Session[] }> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n };\n if (currentRefreshToken) {\n headers[\"X-Refresh-Token\"] = currentRefreshToken;\n }\n\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/sessions`, {\n method: \"GET\",\n headers\n });\n\n return handleResponse<{ sessions: Session[] }>(response);\n}\n\n/**\n * Revoke a specific session\n */\nexport async function revokeSession(accessToken: string, sessionId: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/sessions/${sessionId}`, {\n method: \"DELETE\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n }\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Revoke all sessions for current user\n */\nexport async function revokeAllSessions(accessToken: string): Promise<{ success: boolean; message: string }> {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/sessions`, {\n method: \"DELETE\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${accessToken}`\n }\n });\n\n return handleResponse<{ success: boolean; message: string }>(response);\n}\n\n/**\n * Auth config response from the backend\n */\nexport interface AuthConfigResponse {\n /** True when there are no users in the system and first user setup is needed */\n needsSetup: boolean;\n /** Whether new user registration is enabled */\n registrationEnabled: boolean;\n /** Whether email service is configured */\n emailServiceEnabled?: boolean;\n /** Whether password reset is supported */\n passwordReset?: boolean;\n /** Whether email verification is supported */\n emailVerification?: boolean;\n /** Complete list of enabled OAuth provider IDs (e.g. [\"google\", \"github\", \"discord\"]) */\n enabledProviders: string[];\n}\n\n/**\n * Inflight promise for `fetchAuthConfig` — ensures concurrent callers\n * (e.g. React StrictMode double-mount) reuse the same network request.\n */\nlet authConfigInflight: Promise<AuthConfigResponse> | null = null;\n\n/**\n * Cached result of the last successful `fetchAuthConfig` call.\n * Auth config is static for the lifetime of the app session, so\n * repeat calls (e.g. from effect re-runs) return instantly.\n */\nlet authConfigCached: AuthConfigResponse | null = null;\n\n/**\n * Fetch auth configuration / status from the backend\n * This is an unauthenticated endpoint used to detect bootstrap mode.\n *\n * Results are cached for the session lifetime.\n * Concurrent calls are deduplicated: only one network request is made\n * and all callers share the same promise.\n */\nexport async function fetchAuthConfig(): Promise<AuthConfigResponse> {\n if (authConfigCached) {\n return authConfigCached;\n }\n\n if (authConfigInflight) {\n return authConfigInflight;\n }\n\n authConfigInflight = (async () => {\n const response = await fetchWithHandling(`${baseApiUrl}/api/auth/config`, {\n method: \"GET\",\n headers: { \"Content-Type\": \"application/json\" }\n });\n return handleResponse<AuthConfigResponse>(response);\n })();\n\n try {\n const result = await authConfigInflight;\n authConfigCached = result;\n return result;\n } finally {\n authConfigInflight = null;\n }\n}\n\n/**\n * Clear the cached auth config (e.g. on logout or for testing).\n */\nexport function clearAuthConfigCache(): void {\n authConfigCached = null;\n authConfigInflight = null;\n}\n\nexport { AuthApiError };\n\n\n","import { useCallback, useEffect, useState, useRef } from \"react\";\nimport { User } from \"@rebasepro/types\";\nimport * as authApi from \"../api\";\nimport { AuthConfigResponse } from \"../api\";\nimport {\n RebaseAuthController,\n RebaseAuthControllerProps,\n AuthTokens,\n UserInfo\n} from \"../types\";\n\nconst STORAGE_KEY = \"rebase_react_auth\";\n\n// Buffer time before expiry to trigger refresh (2 minutes)\nconst TOKEN_REFRESH_BUFFER_MS = 2 * 60 * 1000;\n\n/**\n * Convert UserInfo from API to Rebase User type\n */\nfunction convertToUser(userInfo: UserInfo): User {\n return {\n uid: userInfo.uid,\n email: userInfo.email,\n displayName: userInfo.displayName || null,\n photoURL: userInfo.photoURL || null,\n providerId: \"custom\",\n isAnonymous: false,\n roles: userInfo.roles || [],\n metadata: userInfo.metadata\n };\n}\n\n/**\n * Storage data structure\n */\ninterface StoredAuthData {\n tokens: AuthTokens;\n user: UserInfo;\n}\n\n/**\n * Save auth data to localStorage\n */\nfunction saveAuthToStorage(tokens: AuthTokens, user: UserInfo): void {\n try {\n const data: StoredAuthData = { tokens, user };\n localStorage.setItem(STORAGE_KEY, JSON.stringify(data));\n } catch (e) { /* ignore */ }\n}\n\n/**\n * Load auth data from localStorage\n */\nfunction loadAuthFromStorage(): StoredAuthData | null {\n try {\n const data = localStorage.getItem(STORAGE_KEY);\n if (data) {\n const parsed = JSON.parse(data);\n return parsed;\n }\n } catch (e) {\n console.warn(\"Failed to load auth from storage:\", e);\n }\n return null;\n}\n\n/**\n * Clear auth data from localStorage\n */\nfunction clearAuthFromStorage(): void {\n try {\n localStorage.removeItem(STORAGE_KEY);\n } catch (e) {\n console.warn(\"Failed to clear auth from storage:\", e);\n }\n}\n\n/**\n * Check if token is expired or about to expire\n */\nfunction isTokenExpiredOrNearExpiry(expiresAt: number, bufferMs: number = TOKEN_REFRESH_BUFFER_MS): boolean {\n return Date.now() + bufferMs >= expiresAt;\n}\n\n/**\n * Auth controller hook for JWT-based authentication\n * with @rebasepro/server-core\n *\n * @param props Configuration options\n * @returns RebaseAuthController instance\n */\nexport function useRebaseAuthController(\n props: RebaseAuthControllerProps = {}\n): RebaseAuthController {\n const { client, apiUrl, onSignOut, defineRolesFor } = props;\n\n const [user, setUser] = useState<User | null>(null);\n const [authLoading, setAuthLoading] = useState(false);\n const [initialLoading, setInitialLoading] = useState(true);\n const [authError, setAuthError] = useState<Error | null>(null);\n const [authProviderError, setAuthProviderError] = useState<Error | null>(null);\n const [loginSkipped, setLoginSkipped] = useState(false);\n const [extra, setExtra] = useState<unknown>(null);\n const [authConfig, setAuthConfig] = useState<AuthConfigResponse | null>(null);\n\n // Store tokens in ref for quick access, but also persist to localStorage\n const tokensRef = useRef<AuthTokens | null>(null);\n const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // Track if a refresh is currently in progress to avoid concurrent refreshes\n const refreshPromiseRef = useRef<Promise<AuthTokens | null> | null>(null);\n // Track if component is mounted\n const isMountedRef = useRef(true);\n\n // Configure API URL on mount\n useEffect(() => {\n const url = client?.baseUrl || apiUrl;\n if (url) {\n authApi.setApiUrl(url);\n }\n }, [client, client?.baseUrl, apiUrl]);\n\n const clearError = useCallback(() => {\n setAuthProviderError(null);\n }, []);\n\n // Clear session and sign out\n const clearSessionAndSignOut = useCallback(() => {\n tokensRef.current = null;\n clearAuthFromStorage();\n if (refreshTimeoutRef.current) {\n clearTimeout(refreshTimeoutRef.current);\n refreshTimeoutRef.current = null;\n }\n setUser(null);\n setLoginSkipped(false);\n onSignOut?.();\n }, [onSignOut]);\n\n /**\n * Refresh the access token using the stored refresh token.\n * Returns the new tokens or null if refresh failed.\n */\n const refreshAccessToken = useCallback(async (): Promise<AuthTokens | null> => {\n // Prevent concurrent refreshes\n if (refreshPromiseRef.current) {\n // Wait for the current refresh to complete\n return refreshPromiseRef.current;\n }\n\n const executeRefresh = async (): Promise<AuthTokens | null> => {\n // Check if another tab has already refreshed the token\n const storedData = loadAuthFromStorage();\n if (storedData?.tokens?.accessTokenExpiresAt) {\n const storedTokens = storedData.tokens;\n // If stored token is newer and not expired\n if (!isTokenExpiredOrNearExpiry(storedTokens.accessTokenExpiresAt) && storedTokens.accessToken !== tokensRef.current?.accessToken) {\n tokensRef.current = storedTokens;\n return storedTokens;\n }\n }\n\n const currentTokens = tokensRef.current;\n if (!currentTokens?.refreshToken) {\n return null;\n }\n\n\n try {\n const response = await authApi.refreshAccessToken(currentTokens.refreshToken);\n const newTokens = response.tokens;\n\n // Update tokens immediately\n tokensRef.current = newTokens;\n\n // Persist to storage\n const latestStoredData = loadAuthFromStorage();\n if (latestStoredData) {\n saveAuthToStorage(newTokens, latestStoredData.user);\n }\n\n return newTokens;\n } catch (error: unknown) {\n\n // If it's a network error (e.g., backend restarting), we throw so callers can retry\n // instead of immediately assuming the refresh token is invalid and signing out.\n if (error instanceof Error && (error as { code?: string }).code === \"NETWORK_ERROR\") {\n throw error;\n }\n return null;\n } finally {\n refreshPromiseRef.current = null;\n }\n };\n\n refreshPromiseRef.current = executeRefresh();\n return refreshPromiseRef.current;\n }, []);\n\n // Schedule token refresh before expiry\n const scheduleTokenRefresh = useCallback((tokens: AuthTokens) => {\n if (refreshTimeoutRef.current) {\n clearTimeout(refreshTimeoutRef.current);\n }\n\n // Calculate when to refresh (2 minutes before expiry)\n const expiresAt = tokens.accessTokenExpiresAt;\n const refreshAt = expiresAt - TOKEN_REFRESH_BUFFER_MS;\n const timeUntilRefresh = refreshAt - Date.now();\n\n if (timeUntilRefresh <= 0) {\n // Token already expired or about to expire - refresh now\n refreshAccessToken().then(newTokens => {\n if (newTokens && isMountedRef.current) {\n scheduleTokenRefresh(newTokens);\n } else if (!newTokens && isMountedRef.current) {\n clearSessionAndSignOut();\n }\n });\n return;\n }\n\n\n refreshTimeoutRef.current = setTimeout(async () => {\n if (!isMountedRef.current) return;\n\n try {\n const newTokens = await refreshAccessToken();\n\n if (newTokens && isMountedRef.current) {\n scheduleTokenRefresh(newTokens);\n } else if (!newTokens && isMountedRef.current) {\n clearSessionAndSignOut();\n }\n } catch (error) {\n // Network error - try again shortly instead of logging out\n if (isMountedRef.current) {\n refreshTimeoutRef.current = setTimeout(() => {\n scheduleTokenRefresh(tokens);\n }, 10000);\n }\n }\n }, timeUntilRefresh);\n }, [refreshAccessToken, clearSessionAndSignOut]);\n\n // Get auth token for API requests (with automatic refresh if needed)\n const getAuthToken = useCallback(async (): Promise<string> => {\n // If still loading, throw - the UI should show a spinner\n if (initialLoading) {\n throw new Error(\"Auth is still loading\");\n }\n\n const currentTokens = tokensRef.current;\n if (!currentTokens) {\n throw new Error(\"User is not logged in\");\n }\n\n // Check if token is expired or about to expire\n if (isTokenExpiredOrNearExpiry(currentTokens.accessTokenExpiresAt)) {\n try {\n const newTokens = await refreshAccessToken();\n if (!newTokens) {\n clearSessionAndSignOut();\n throw new Error(\"Session expired. Please login again.\");\n }\n return newTokens.accessToken;\n } catch (error: unknown) {\n // If the error was a network error during refresh, just re-throw it\n // so the user isn't logged out locally and the network request fails naturally.\n if (error instanceof Error && (error as { code?: string }).code === \"NETWORK_ERROR\") {\n throw error;\n }\n clearSessionAndSignOut();\n throw error;\n }\n }\n\n return currentTokens.accessToken;\n }, [initialLoading, refreshAccessToken, clearSessionAndSignOut]);\n\n // Install token getter onto client\n useEffect(() => {\n if (client) {\n client.setAuthTokenGetter?.(async () => {\n try { return await getAuthToken(); } catch { return null; }\n });\n if (client.setOnUnauthorized) {\n client.setOnUnauthorized(async () => {\n try {\n const newTokens = await refreshAccessToken();\n if (newTokens) return true;\n clearSessionAndSignOut();\n return false;\n } catch (e) {\n clearSessionAndSignOut();\n return false;\n }\n });\n }\n if (client.ws) {\n client.ws.setAuthTokenGetter(async () => {\n return await getAuthToken();\n });\n }\n }\n }, [client, getAuthToken, refreshAccessToken, clearSessionAndSignOut]);\n\n // Handle successful authentication\n const handleAuthSuccess = useCallback(async (userInfo: UserInfo, tokens: AuthTokens) => {\n tokensRef.current = tokens;\n let convertedUser = convertToUser(userInfo);\n\n // Apply custom roles if defineRolesFor provided\n if (defineRolesFor) {\n const customRoles = await defineRolesFor(convertedUser);\n if (customRoles) {\n convertedUser = { ...convertedUser, roles: customRoles.map(r => r.id) };\n }\n }\n\n // Save to localStorage for persistence\n saveAuthToStorage(tokens, userInfo);\n\n setUser(convertedUser);\n setAuthError(null);\n setAuthProviderError(null);\n setLoginSkipped(false);\n scheduleTokenRefresh(tokens);\n }, [scheduleTokenRefresh, defineRolesFor]);\n\n // Email/password login\n const emailPasswordLogin = useCallback(async (email: string, password: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n const response = await authApi.login(email, password);\n await handleAuthSuccess(response.user, response.tokens);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [handleAuthSuccess]);\n\n // Register new user\n const register = useCallback(async (email: string, password: string, displayName?: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n const response = await authApi.register(email, password, displayName);\n await handleAuthSuccess(response.user, response.tokens);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [handleAuthSuccess]);\n\n // Google login — accepts payload object with code flow or token\n const googleLogin = useCallback(async (\n payload: { code: string; redirectUri: string } | { idToken: string } | { accessToken: string }\n ) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n const response = await authApi.googleLogin(payload);\n await handleAuthSuccess(response.user, response.tokens);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [handleAuthSuccess]);\n\n // Generic OAuth login — works with any provider registered on the backend\n const oauthLogin = useCallback(async (providerId: string, payload: Record<string, unknown>) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n const response = await authApi.oauthLogin(providerId, payload);\n await handleAuthSuccess(response.user, response.tokens);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [handleAuthSuccess]);\n\n // Sign out\n const signOut = useCallback(async () => {\n try {\n if (tokensRef.current) {\n await authApi.logout(tokensRef.current.refreshToken);\n }\n } catch (error) {\n console.error(\"Logout error:\", error);\n } finally {\n clearSessionAndSignOut();\n }\n }, [clearSessionAndSignOut]);\n\n // Skip login\n const skipLogin = useCallback(() => {\n setLoginSkipped(true);\n setUser(null);\n }, []);\n\n // Forgot password - request reset email\n const forgotPassword = useCallback(async (email: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n await authApi.forgotPassword(email);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, []);\n\n // Reset password using token\n const resetPassword = useCallback(async (token: string, password: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n await authApi.resetPassword(token, password);\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, []);\n\n // Change password for authenticated user\n const changePassword = useCallback(async (oldPassword: string, newPassword: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n if (!tokensRef.current) {\n throw new Error(\"User is not logged in\");\n }\n await authApi.changePassword(tokensRef.current.accessToken, oldPassword, newPassword);\n // After password change, user needs to log in again (all sessions invalidated)\n clearSessionAndSignOut();\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [clearSessionAndSignOut]);\n\n // Update user profile\n const updateProfile = useCallback(async (displayName?: string, photoURL?: string) => {\n setAuthLoading(true);\n setAuthProviderError(null);\n\n try {\n if (!tokensRef.current) {\n throw new Error(\"User is not logged in\");\n }\n const response = await authApi.updateProfile(tokensRef.current.accessToken, displayName, photoURL);\n\n // Update local user state\n let convertedUser = convertToUser(response.user);\n if (defineRolesFor) {\n const customRoles = await defineRolesFor(convertedUser);\n if (customRoles) {\n convertedUser = { ...convertedUser,\nroles: customRoles.map(r => r.id) };\n }\n }\n\n // Update storage\n const storedData = loadAuthFromStorage();\n if (storedData) {\n saveAuthToStorage(storedData.tokens, response.user);\n }\n\n setUser(convertedUser);\n return convertedUser;\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n } finally {\n setAuthLoading(false);\n }\n }, [defineRolesFor]);\n\n // Fetch active sessions\n const fetchSessions = useCallback(async () => {\n try {\n if (!tokensRef.current) {\n throw new Error(\"User is not logged in\");\n }\n const response = await authApi.fetchSessions(tokensRef.current.accessToken, tokensRef.current.refreshToken);\n return response.sessions;\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n }\n }, []);\n\n // Revoke a session\n const revokeSession = useCallback(async (sessionId: string) => {\n try {\n if (!tokensRef.current) {\n throw new Error(\"User is not logged in\");\n }\n await authApi.revokeSession(tokensRef.current.accessToken, sessionId);\n // If the revoked session is the current one, the next API request will fail with 401\n // and trigger an auto-logout. Otherwise, it just removes it from the DB.\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n }\n }, []);\n\n // Restore auth state from localStorage on mount\n useEffect(() => {\n isMountedRef.current = true;\n\n const restoreAuth = async () => {\n\n // Fetch auth config (needsSetup, registrationEnabled, etc.)\n try {\n const config = await authApi.fetchAuthConfig();\n if (isMountedRef.current) {\n setAuthConfig(config);\n }\n } catch (e) { /* ignore */ }\n\n const stored = loadAuthFromStorage();\n\n if (!stored) {\n setInitialLoading(false);\n return;\n }\n\n if (!stored.tokens?.refreshToken) {\n clearAuthFromStorage();\n setInitialLoading(false);\n return;\n }\n\n\n // Validate accessTokenExpiresAt is a valid number\n const expiresAt = stored.tokens.accessTokenExpiresAt;\n if (typeof expiresAt !== \"number\" || !Number.isFinite(expiresAt)) {\n clearAuthFromStorage();\n setInitialLoading(false);\n return;\n }\n\n\n // Check if access token is still valid\n if (!isTokenExpiredOrNearExpiry(stored.tokens.accessTokenExpiresAt)) {\n // Token is still valid - use it directly\n tokensRef.current = stored.tokens;\n\n let userToSet = convertToUser(stored.user);\n if (defineRolesFor) {\n const customRoles = await defineRolesFor(userToSet);\n if (customRoles) {\n userToSet = { ...userToSet,\nroles: customRoles.map(r => r.id) };\n }\n }\n\n setUser(userToSet);\n scheduleTokenRefresh(stored.tokens);\n setInitialLoading(false);\n return;\n }\n\n // Token is expired or near expiry - refresh it\n tokensRef.current = stored.tokens; // Set so refreshAccessToken can use it\n\n try {\n const newTokens = await refreshAccessToken();\n\n if (!newTokens) {\n clearAuthFromStorage();\n tokensRef.current = null;\n setInitialLoading(false);\n return;\n }\n\n if (!isMountedRef.current) return;\n\n // Fetch fresh user data from the server\n let userToSet: User;\n try {\n const meResponse = await authApi.getCurrentUser(newTokens.accessToken);\n\n if (!isMountedRef.current) return;\n\n const freshUserInfo = meResponse.user;\n\n // Update stored data with fresh user info\n saveAuthToStorage(newTokens, freshUserInfo);\n\n userToSet = convertToUser(freshUserInfo);\n\n if (defineRolesFor) {\n const customRoles = await defineRolesFor(userToSet);\n if (!isMountedRef.current) return;\n if (customRoles) {\n userToSet = { ...userToSet,\nroles: customRoles.map(r => r.id) };\n }\n }\n } catch (meError: unknown) {\n if (!isMountedRef.current) return;\n if (meError instanceof authApi.AuthApiError && (meError.code === \"NOT_FOUND\" || meError.code === \"UNAUTHORIZED\")) {\n clearSessionAndSignOut();\n return;\n }\n userToSet = convertToUser(stored.user);\n }\n\n if (!isMountedRef.current) return;\n\n setUser(userToSet);\n scheduleTokenRefresh(newTokens);\n } catch (error: unknown) {\n if (!isMountedRef.current) return;\n\n // Do not clear the session entirely if it's just a temporary network outage\n if (!(error instanceof Error && (error as { code?: string }).code === \"NETWORK_ERROR\")) {\n clearAuthFromStorage();\n tokensRef.current = null;\n }\n } finally {\n if (isMountedRef.current) {\n setInitialLoading(false);\n }\n }\n };\n\n restoreAuth();\n\n return () => {\n isMountedRef.current = false;\n };\n }, [scheduleTokenRefresh, defineRolesFor, refreshAccessToken]);\n\n // Handle visibility change - refresh token when user returns to tab\n useEffect(() => {\n const handleVisibilityChange = async () => {\n if (initialLoading) return;\n\n if (document.visibilityState === \"visible\" && tokensRef.current) {\n // Check if token needs refreshing\n if (isTokenExpiredOrNearExpiry(tokensRef.current.accessTokenExpiresAt)) {\n try {\n const newTokens = await refreshAccessToken();\n\n if (newTokens && isMountedRef.current) {\n scheduleTokenRefresh(newTokens);\n } else if (!newTokens && isMountedRef.current) {\n clearSessionAndSignOut();\n }\n } catch (e) { /* ignore */ }\n }\n }\n };\n\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\n\n return () => {\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\n };\n }, [initialLoading, refreshAccessToken, scheduleTokenRefresh, clearSessionAndSignOut]);\n\n\n // Get currently configured API URL\n const getApiUrl = useCallback(() => {\n return authApi.getApiUrl();\n }, []);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n isMountedRef.current = false;\n if (refreshTimeoutRef.current) {\n clearTimeout(refreshTimeoutRef.current);\n }\n };\n }, []);\n\n // Revoke all sessions\n const revokeAllSessions = useCallback(async () => {\n try {\n if (!tokensRef.current) {\n throw new Error(\"User is not logged in\");\n }\n await authApi.revokeAllSessions(tokensRef.current.accessToken);\n clearSessionAndSignOut();\n } catch (error: unknown) {\n setAuthProviderError(error as Error);\n throw error;\n }\n }, [clearSessionAndSignOut]);\n\n return {\n user,\n authLoading,\n initialLoading,\n authError,\n authProviderError,\n loginSkipped,\n needsSetup: authConfig?.needsSetup ?? false,\n registrationEnabled: authConfig?.registrationEnabled ?? false,\n getAuthToken,\n getApiUrl,\n signOut,\n emailPasswordLogin,\n register,\n googleLogin,\n oauthLogin,\n skipLogin,\n forgotPassword,\n resetPassword,\n changePassword,\n updateProfile,\n fetchSessions,\n revokeSession,\n revokeAllSessions,\n clearError,\n setAuthProviderError,\n extra,\n setExtra,\n capabilities: {\n emailPasswordLogin: true,\n googleLogin: !!(props.googleClientId),\n registration: authConfig?.registrationEnabled ?? false,\n passwordReset: authConfig?.passwordReset ?? false,\n sessionManagement: true,\n profileUpdate: true,\n emailVerification: authConfig?.emailVerification ?? false,\n enabledProviders: authConfig?.enabledProviders ?? []\n }\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { Role, User } from \"@rebasepro/types\";\n\n/**\n * UserManagement interface - compatible with @rebasepro/user_management\n * Defined inline to avoid dependency on that package\n */\nexport interface UserManagement<USER extends User = User> {\n loading: boolean;\n hasAdminUsers?: boolean;\n\n users: USER[];\n saveUser: (user: USER) => Promise<USER>;\n createUser?: (user: USER) => Promise<{\n user: USER;\n invitationSent: boolean;\n temporaryPassword?: string;\n }>;\n resetPassword?: (user: USER) => Promise<{\n user: USER;\n invitationSent: boolean;\n temporaryPassword?: string;\n }>;\n deleteUser: (user: USER) => Promise<void>;\n\n roles: Role[];\n saveRole: (role: Role) => Promise<void>;\n deleteRole: (role: Role) => Promise<void>;\n\n isAdmin?: boolean;\n allowDefaultRolesCreation?: boolean;\n defineRolesFor: (user: User) => Promise<Role[] | undefined> | Role[] | undefined;\n getUser: (uid: string) => User | null;\n\n /**\n * Search users with server-side pagination.\n * When provided, the CMS will use this for the users table\n * instead of loading all users into memory.\n */\n searchUsers?: (options: {\n search?: string;\n limit?: number;\n offset?: number;\n orderBy?: string;\n orderDir?: \"asc\" | \"desc\";\n roleId?: string;\n }) => Promise<{ users: USER[]; total: number }>;\n\n usersError?: Error;\n rolesError?: Error;\n bootstrapAdmin?: () => Promise<void>;\n}\n\nexport interface BackendUserManagementConfig {\n /**\n * The Rebase Client instance\n */\n client?: { baseUrl?: string; resolveToken?: () => Promise<string | null> };\n\n /**\n * Base API URL for the backend (optional, extracted from client if not provided)\n */\n apiUrl?: string;\n\n /**\n * Function to get the current auth token (optional, extracted from client if not provided)\n */\n getAuthToken?: () => Promise<string>;\n\n /**\n * Current logged-in user\n */\n currentUser?: User | null;\n}\n\ninterface ApiUser {\n uid: string;\n email: string;\n displayName?: string | null;\n photoURL?: string | null;\n roles: string[];\n createdAt?: string;\n updatedAt?: string;\n}\n\ninterface ApiRole {\n id: string;\n name: string;\n isAdmin?: boolean;\n}\n\n/** Response shapes from the admin API */\ninterface ApiRolesResponse { roles: ApiRole[] }\ninterface ApiUsersResponse { users: ApiUser[]; total: number }\ninterface ApiUserResponse { user: ApiUser; invitationSent?: boolean; temporaryPassword?: string }\ninterface ApiRoleResponse { role: ApiRole }\n\n/**\n * Convert API user to Rebase User\n * @param apiUser - The API user object\n */\nfunction convertUser(apiUser: ApiUser): User {\n return {\n uid: apiUser.uid,\n email: apiUser.email,\n displayName: apiUser.displayName || null,\n photoURL: apiUser.photoURL || null,\n providerId: \"custom\",\n isAnonymous: false,\n roles: apiUser.roles,\n createdAt: apiUser.createdAt ? new Date(apiUser.createdAt) : null\n } as User;\n}\n\n/**\n * Convert API role to Rebase Role\n */\nfunction convertRole(apiRole: ApiRole): Role {\n return {\n id: apiRole.id,\n name: apiRole.name,\n isAdmin: apiRole.isAdmin ?? false\n };\n}\n\n/**\n * Hook to manage users and roles via backend API\n * Compatible with Rebase UserManagement interface\n */\nexport function useBackendUserManagement(config: BackendUserManagementConfig): UserManagement {\n const { client, apiUrl, getAuthToken, currentUser } = config;\n\n // Lazy user cache — populated on demand from search results, saves, and\n // individual API lookups. We never load ALL users into memory.\n const [userCache, setUserCache] = useState<Map<string, User>>(new Map());\n const [hasAdminUsers, setHasAdminUsers] = useState(false);\n const [roles, setRoles] = useState<Role[]>([]);\n const userRoles = currentUser?.roles ?? [];\n const isUserAdmin = userRoles.some(r => r === \"admin\" || r === \"schema-admin\");\n\n const [loading, setLoading] = useState(() => {\n if (!currentUser) return false;\n if (!isUserAdmin) return false;\n return true;\n });\n const [usersError, setUsersError] = useState<Error | undefined>();\n const [rolesError, setRolesError] = useState<Error | undefined>();\n\n // Tracks the UID for which roles+users were last successfully loaded.\n // Prevents redundant refetches on React StrictMode double-mounts.\n const lastLoadedUidRef = useRef<string | null>(null);\n\n const effectiveLoading = loading || !!(currentUser && isUserAdmin && lastLoadedUidRef.current !== currentUser.uid);\n\n /** Merge one or more users into the cache without replacing the whole Map. */\n const mergeIntoCache = useCallback((incoming: User[]) => {\n setUserCache(prev => {\n const next = new Map(prev);\n for (const u of incoming) {\n next.set(u.uid, u);\n }\n return next;\n });\n }, []);\n\n // Ref to hold the latest apiRequest so the initial-load effect doesn't\n // re-trigger every time the callback identity changes.\n const apiRequestRef = useRef<typeof apiRequest | null>(null);\n\n /**\n * Make authenticated API request\n */\n const apiRequest = useCallback(async <T = Record<string, unknown>>(\n endpoint: string,\n method = \"GET\",\n body?: Record<string, unknown>,\n retryCount = 6,\n signal?: AbortSignal\n ): Promise<T> => {\n let lastError: Error | null = null;\n for (let attempt = 0; attempt < retryCount; attempt++) {\n if (signal?.aborted) {\n const error = new Error(\"Request aborted\");\n error.name = \"AbortError\";\n throw error;\n }\n\n try {\n // Determine token provider\n const token = getAuthToken ? await getAuthToken() : (client?.resolveToken ? await client.resolveToken() : null);\n const baseUrl = apiUrl || (client?.baseUrl ? client.baseUrl : \"\");\n\n // Use /api/admin prefix for admin endpoints\n const response = await fetch(`${baseUrl}/api/admin${endpoint}`, {\n method,\n headers: {\n \"Content-Type\": \"application/json\",\n ...(token ? { \"Authorization\": `Bearer ${token}` } : {})\n },\n body: body ? JSON.stringify(body) : undefined,\n signal\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n let errorMessage = \"API request failed\";\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error?.message || errorMessage;\n } catch (e) {\n errorMessage = errorText || `HTTP error ${response.status}`;\n }\n\n const error = Object.assign(new Error(errorMessage), { status: response.status });\n throw error;\n }\n\n return await response.json();\n } catch (error: unknown) {\n if (error instanceof Error && error.name === \"AbortError\" || signal?.aborted) {\n throw error;\n }\n\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Retry conditions: Network errors (TypeError) OR 5xx Server Errors (Backend rebooting)\n const isNetworkError = error instanceof TypeError;\n const isServerError = typeof (error as { status?: number }).status === \"number\" && (error as { status: number }).status >= 500 && (error as { status: number }).status < 600;\n\n if (attempt < retryCount - 1 && (isNetworkError || isServerError)) {\n const delay = Math.min(1000 * Math.pow(2, attempt), 5000); // 1s, 2s, 4s...\n console.warn(`Admin API request to ${endpoint} failed, retrying in ${delay}ms...`);\n\n // Wait for delay or abort\n await new Promise<void>((resolve, reject) => {\n if (signal?.aborted) return reject(new Error(\"AbortError\"));\n const timer = setTimeout(resolve, delay);\n if (signal) {\n signal.addEventListener(\"abort\", () => {\n clearTimeout(timer);\n reject(new Error(\"AbortError\"));\n }, { once: true });\n }\n }).catch(() => {}); // Catch AbortError from wait\n\n if (signal?.aborted) {\n const abortError = new Error(\"Request aborted\");\n abortError.name = \"AbortError\";\n throw abortError;\n }\n continue;\n }\n\n console.error(\"Admin API error after retries:\", error);\n throw error;\n }\n }\n throw lastError;\n }, [apiUrl, getAuthToken]);\n\n // Keep the ref in sync after every render.\n apiRequestRef.current = apiRequest;\n\n /**\n * Load roles from API\n */\n const loadRoles = useCallback(async (signal?: AbortSignal) => {\n try {\n const data = await apiRequest<ApiRolesResponse>(\"/roles\", \"GET\", undefined, 6, signal);\n setRoles(data.roles.map(convertRole));\n setRolesError(undefined);\n } catch (error: unknown) {\n if (error instanceof Error && error.name === \"AbortError\") return;\n console.error(\"Failed to load roles:\", error);\n setRolesError(error instanceof Error ? error : new Error(String(error)));\n }\n }, [apiRequest]);\n\n /**\n * Lightweight admin-existence check: fetch a single admin user.\n * Used by the BootstrapAdminBanner to decide whether to show.\n */\n const checkAdminExists = useCallback(async (signal?: AbortSignal) => {\n try {\n const data = await apiRequest<ApiUsersResponse>(\"/users?role=admin&limit=1\", \"GET\", undefined, 6, signal);\n const adminUsers: User[] = data.users.map((u: ApiUser) => convertUser(u));\n setHasAdminUsers(adminUsers.length > 0);\n // Also cache these admin users for getUser lookups\n if (adminUsers.length > 0) {\n mergeIntoCache(adminUsers);\n }\n setUsersError(undefined);\n } catch (error: unknown) {\n if (error instanceof Error && error.name === \"AbortError\") return;\n console.error(\"Failed to check admin users:\", error);\n setUsersError(error instanceof Error ? error : new Error(String(error)));\n }\n }, [apiRequest, mergeIntoCache]);\n\n /**\n * Initial data load - only when user is logged in\n * Load roles first, then admin users.\n *\n * Dependencies are intentionally limited to `currentUser?.uid` so the\n * effect does NOT re-run when callback identities change. The latest\n * `apiRequest` is read via `apiRequestRef`.\n */\n useEffect(() => {\n // Don't load if no user is logged in\n if (!currentUser) {\n setLoading(false);\n return;\n }\n\n // Skip admin API calls for non-admin users — they'd get 403 anyway.\n // This avoids a spurious warning in backend logs on every non-admin login.\n const userRoles = currentUser.roles ?? [];\n const isUserAdmin = userRoles.some(r => r === \"admin\" || r === \"schema-admin\");\n if (!isUserAdmin) {\n setLoading(false);\n return;\n }\n\n // Skip refetch if we already loaded data for this same UID\n // (e.g. React StrictMode unmounts and re-mounts with the same user).\n if (lastLoadedUidRef.current === currentUser.uid) {\n setLoading(false);\n return;\n }\n\n const abortController = new AbortController();\n\n const load = async () => {\n setLoading(true);\n const request = apiRequestRef.current!;\n\n // Load roles first\n try {\n const data = await request<ApiRolesResponse>(\"/roles\", \"GET\", undefined, 6, abortController.signal);\n setRoles(data.roles.map(convertRole));\n setRolesError(undefined);\n } catch (error: unknown) {\n if (error instanceof Error && error.name === \"AbortError\") return;\n console.error(\"Failed to load roles:\", error);\n setRolesError(error instanceof Error ? error : new Error(String(error)));\n\n // If the error is a permission issue (e.g. 403), skip the\n // admin check — it will fail with the same error and we'd\n // show a duplicate snackbar / error message.\n const status = (error as { status?: number }).status;\n if (status === 403 || status === 401) {\n setUsersError(error instanceof Error ? error : new Error(String(error)));\n setLoading(false);\n return;\n }\n }\n\n // Lightweight admin-existence check (NOT loading all users)\n if (!abortController.signal.aborted) {\n try {\n const data = await request<ApiUsersResponse>(\"/users?role=admin&limit=1\", \"GET\", undefined, 6, abortController.signal);\n const adminUsers: User[] = data.users.map((u: ApiUser) => convertUser(u));\n setHasAdminUsers(adminUsers.length > 0);\n if (adminUsers.length > 0) {\n mergeIntoCache(adminUsers);\n }\n setUsersError(undefined);\n } catch (error: unknown) {\n if (error instanceof Error && error.name === \"AbortError\") return;\n console.error(\"Failed to check admin users:\", error);\n setUsersError(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n if (!abortController.signal.aborted) {\n lastLoadedUidRef.current = currentUser.uid;\n setLoading(false);\n }\n };\n load();\n\n return () => {\n abortController.abort();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [currentUser?.uid]);\n\n /**\n * Search users with server-side pagination.\n * This is the primary method used by the UsersView table.\n * Results are also merged into the cache for getUser lookups.\n */\n const searchUsers = useCallback(async (options: {\n search?: string;\n limit?: number;\n offset?: number;\n orderBy?: string;\n orderDir?: \"asc\" | \"desc\";\n roleId?: string;\n }): Promise<{ users: User[]; total: number }> => {\n const params = new URLSearchParams();\n if (options.limit !== undefined) params.set(\"limit\", String(options.limit));\n if (options.offset !== undefined) params.set(\"offset\", String(options.offset));\n if (options.search) params.set(\"search\", options.search);\n if (options.orderBy) params.set(\"orderBy\", options.orderBy);\n if (options.orderDir) params.set(\"orderDir\", options.orderDir);\n if (options.roleId) params.set(\"role\", options.roleId);\n const qs = params.toString();\n\n const data = await apiRequest<ApiUsersResponse>(\"/users\" + (qs ? \"?\" + qs : \"\"), \"GET\");\n const converted = data.users.map((u: ApiUser) => convertUser(u));\n // Feed search results into cache for getUser/defineRolesFor\n mergeIntoCache(converted);\n return {\n users: converted,\n total: data.total\n };\n }, [apiRequest, mergeIntoCache]);\n\n /**\n * Save user (update existing user)\n */\n const saveUser = useCallback(async (user: User): Promise<User> => {\n const roleIds = user.roles ?? [];\n\n const data = await apiRequest<ApiUserResponse>(`/users/${user.uid}`, \"PUT\", {\n email: user.email,\n displayName: user.displayName,\n roles: roleIds\n });\n const updated = convertUser(data.user);\n mergeIntoCache([updated]);\n return updated;\n }, [apiRequest, mergeIntoCache]);\n\n /**\n * Create a new user with invitation/password generation support.\n * Returns additional info about how credentials were delivered.\n */\n const createUser = useCallback(async (user: User): Promise<{\n user: User;\n invitationSent: boolean;\n temporaryPassword?: string;\n }> => {\n const roleIds = user.roles ?? [];\n\n const data = await apiRequest<ApiUserResponse>(\"/users\", \"POST\", {\n email: user.email,\n displayName: user.displayName,\n roles: roleIds\n });\n const created = convertUser(data.user);\n mergeIntoCache([created]);\n return {\n user: created,\n invitationSent: data.invitationSent ?? false,\n temporaryPassword: data.temporaryPassword\n };\n }, [apiRequest, mergeIntoCache]);\n\n /**\n * Reset the password for an existing user\n */\n const resetPassword = useCallback(async (user: User): Promise<{\n user: User;\n invitationSent: boolean;\n temporaryPassword?: string;\n }> => {\n const data = await apiRequest<ApiUserResponse>(`/users/${user.uid}/reset-password`, \"POST\");\n const updatedUser = convertUser(data.user);\n mergeIntoCache([updatedUser]);\n return {\n user: updatedUser,\n invitationSent: data.invitationSent ?? false,\n temporaryPassword: data.temporaryPassword\n };\n }, [apiRequest, mergeIntoCache]);\n\n /**\n * Delete user\n */\n const deleteUser = useCallback(async (user: User): Promise<void> => {\n await apiRequest(`/users/${user.uid}`, \"DELETE\");\n setUserCache(prev => {\n const next = new Map(prev);\n next.delete(user.uid);\n return next;\n });\n }, [apiRequest]);\n\n /**\n * Save role (create or update)\n */\n const saveRole = useCallback(async (role: Role): Promise<void> => {\n // Check if role exists\n const existingRole = roles.find(r => r.id === role.id);\n\n if (existingRole) {\n // Update\n const data = await apiRequest<ApiRoleResponse>(`/roles/${role.id}`, \"PUT\", {\n name: role.name,\n isAdmin: role.isAdmin\n });\n const updated = convertRole(data.role);\n setRoles(prev => prev.map(r => r.id === updated.id ? updated : r));\n } else {\n // Create\n const data = await apiRequest<ApiRoleResponse>(\"/roles\", \"POST\", {\n id: role.id,\n name: role.name,\n isAdmin: role.isAdmin ?? false\n });\n const created = convertRole(data.role);\n setRoles(prev => [...prev, created]);\n }\n }, [apiRequest, roles]);\n\n /**\n * Delete role\n */\n const deleteRole = useCallback(async (role: Role): Promise<void> => {\n await apiRequest(`/roles/${role.id}`, \"DELETE\");\n setRoles(prev => prev.filter(r => r.id !== role.id));\n }, [apiRequest]);\n\n /**\n * Get user by uid\n */\n const getUser = useCallback((uid: string): User | null => {\n return userCache.get(uid) ?? null;\n }, [userCache]);\n\n /**\n * Define roles for a given user (for authController)\n */\n const defineRolesFor = useCallback(async (user: User): Promise<Role[] | undefined> => {\n // Check cache first\n let existingUser = userCache.get(user.uid)\n ?? Array.from(userCache.values()).find(u => u.email === user.email);\n\n // If not cached, fetch from API\n if (!existingUser) {\n try {\n const data = await apiRequest<ApiUserResponse>(`/users/${user.uid}`, \"GET\");\n existingUser = convertUser(data.user);\n mergeIntoCache([existingUser]);\n } catch {\n return undefined;\n }\n }\n\n // Return roles from our cached role data (string IDs → full Role objects)\n const userRoleIds = existingUser.roles ?? [];\n return roles.filter(r => userRoleIds.includes(r.id));\n }, [userCache, roles, apiRequest, mergeIntoCache]);\n\n /**\n * Check if current user is admin\n */\n const isAdmin = currentUser?.roles?.includes(\"admin\") ?? false;\n\n\n /**\n * Bootstrap default admin\n */\n const bootstrapAdmin = useCallback(async (): Promise<void> => {\n try {\n await apiRequest(\"/bootstrap\", \"POST\");\n // Reload roles and re-check admin existence after successful bootstrap\n const data = await apiRequest<ApiRolesResponse>(\"/roles\");\n const loadedRoles = data.roles.map(convertRole);\n setRoles(loadedRoles);\n await checkAdminExists();\n } catch (error) {\n console.error(\"Failed to bootstrap admin:\", error);\n throw error;\n }\n }, [apiRequest, checkAdminExists]);\n\n // Expose cached users as an array for backward compat (BootstrapAdminBanner,\n // UsersView fallback). This is NOT the full user list — just the cache.\n const users = Array.from(userCache.values());\n\n return {\n loading: effectiveLoading,\n users,\n hasAdminUsers,\n saveUser,\n createUser,\n resetPassword,\n deleteUser,\n roles,\n saveRole,\n deleteRole,\n isAdmin,\n allowDefaultRolesCreation: isAdmin,\n defineRolesFor,\n getUser,\n searchUsers,\n usersError,\n rolesError,\n bootstrapAdmin\n };\n}\n"],"names":["authApi.setApiUrl","refreshAccessToken","authApi.refreshAccessToken","authApi.login","register","authApi.register","googleLogin","authApi.googleLogin","oauthLogin","authApi.oauthLogin","authApi.logout","forgotPassword","authApi.forgotPassword","resetPassword","authApi.resetPassword","changePassword","authApi.changePassword","updateProfile","authApi.updateProfile","fetchSessions","authApi.fetchSessions","revokeSession","authApi.revokeSession","authApi.fetchAuthConfig","authApi.getCurrentUser","authApi.AuthApiError","getApiUrl","authApi.getApiUrl","revokeAllSessions","authApi.revokeAllSessions","userRoles","isUserAdmin"],"mappings":"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"}
|