@rationalbloks/frontblok-auth 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +5 -5
- package/dist/api/createApiUrl.d.ts +39 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/auth/ProtectedRoute.d.ts +20 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/index.cjs +29 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +30 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/api/client.d.ts
CHANGED
|
@@ -112,21 +112,21 @@ export declare class BaseApi {
|
|
|
112
112
|
getCurrentUser(): Promise<User>;
|
|
113
113
|
/**
|
|
114
114
|
* Internal: Authenticate with Google OAuth.
|
|
115
|
-
* Use googleLogin() instead
|
|
115
|
+
* Use googleLogin() instead.
|
|
116
116
|
*/
|
|
117
117
|
private _googleOAuthLogin;
|
|
118
118
|
/**
|
|
119
|
-
*
|
|
119
|
+
* Simplified Google OAuth login.
|
|
120
120
|
*
|
|
121
|
-
* This is the
|
|
122
|
-
* OAuth flow in
|
|
121
|
+
* This is the default method for Google Sign-In. It handles the complete
|
|
122
|
+
* OAuth flow in a single call:
|
|
123
123
|
*
|
|
124
124
|
* 1. Retrieve nonce from sessionStorage (must exist from generateOAuthNonce())
|
|
125
125
|
* 2. Send credential + nonce to backend for verification
|
|
126
126
|
* 3. Store JWT tokens on success
|
|
127
127
|
* 4. Clear nonce to prevent replay attacks
|
|
128
128
|
*
|
|
129
|
-
* If
|
|
129
|
+
* If any step fails, the entire operation fails cleanly. No partial states.
|
|
130
130
|
*
|
|
131
131
|
* @param credential - The Google ID token (JWT from GoogleLogin onSuccess)
|
|
132
132
|
* @returns GoogleOAuthResponse with tokens, user data, and is_new_user flag
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for createApiUrl.
|
|
3
|
+
*/
|
|
4
|
+
export interface CreateApiUrlOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Override the env variable name. Default: 'VITE_DATABASE_API_URL'.
|
|
7
|
+
* This is read from import.meta.env at call time.
|
|
8
|
+
*/
|
|
9
|
+
envVar?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Fallback URL for local development (only used when import.meta.env.DEV is true).
|
|
12
|
+
* Default: 'http://localhost:8000'
|
|
13
|
+
*/
|
|
14
|
+
devFallback?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the backend API URL for the current environment.
|
|
18
|
+
*
|
|
19
|
+
* Resolution order:
|
|
20
|
+
* 1. `import.meta.env.VITE_DATABASE_API_URL` (injected at build time)
|
|
21
|
+
* 2. `import.meta.env.VITE_API_URL` (alternative env var name)
|
|
22
|
+
* 3. In development mode only: devFallback (default: 'http://localhost:8000')
|
|
23
|
+
* 4. In production: empty string (will cause API calls to fail loudly)
|
|
24
|
+
*
|
|
25
|
+
* @param options - Configuration options
|
|
26
|
+
* @returns The resolved API base URL
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { createApiUrl, createAuthApi } from '@rationalbloks/frontblok-auth';
|
|
31
|
+
*
|
|
32
|
+
* // Simple usage
|
|
33
|
+
* const authApi = createAuthApi(createApiUrl());
|
|
34
|
+
*
|
|
35
|
+
* // Custom dev fallback
|
|
36
|
+
* const authApi = createAuthApi(createApiUrl({ devFallback: 'http://localhost:3000' }));
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function createApiUrl(options?: CreateApiUrlOptions): string;
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { BaseApi, createAuthApi, getStoredUser, getStoredToken, isAuthenticated, generateOAuthNonce } from './client';
|
|
2
|
+
export { createApiUrl } from './createApiUrl';
|
|
3
|
+
export type { CreateApiUrlOptions } from './createApiUrl';
|
|
2
4
|
export type { User, ApiKey, ApiKeyCreateResponse, AuthResponse, GoogleOAuthResponse, PasswordResetRequestResponse, PasswordResetResponse, EmailVerificationResponse, SetPasswordResponse } from './types';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface ProtectedRouteProps {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
/**
|
|
5
|
+
* The useAuth hook from your app's datablokApi.ts (useClientAuth).
|
|
6
|
+
* We accept this as a prop instead of importing it directly to avoid
|
|
7
|
+
* circular dependencies and keep the package decoupled.
|
|
8
|
+
*/
|
|
9
|
+
useAuth: () => {
|
|
10
|
+
isAuthenticated: boolean;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Where to redirect unauthenticated users. Default: '/auth'
|
|
14
|
+
*/
|
|
15
|
+
redirectTo?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Route wrapper that redirects to the auth page if the user is not authenticated.
|
|
19
|
+
*/
|
|
20
|
+
export declare const ProtectedRoute: React.FC<ProtectedRouteProps>;
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -2,3 +2,5 @@ export { useAuth, createAuthProvider } from './AuthContext';
|
|
|
2
2
|
export type { AuthState, AuthActions, AuthContextType } from './AuthContext';
|
|
3
3
|
export { createAppRoot } from './AppProvider';
|
|
4
4
|
export type { AuthConfig } from './AppProvider';
|
|
5
|
+
export { ProtectedRoute } from './ProtectedRoute';
|
|
6
|
+
export type { ProtectedRouteProps } from './ProtectedRoute';
|
package/dist/index.cjs
CHANGED
|
@@ -4,6 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
4
4
|
const react = require("react");
|
|
5
5
|
const client = require("react-dom/client");
|
|
6
6
|
const google = require("@react-oauth/google");
|
|
7
|
+
const reactRouterDom = require("react-router-dom");
|
|
7
8
|
const generateOAuthNonce = () => {
|
|
8
9
|
const array = new Uint8Array(32);
|
|
9
10
|
crypto.getRandomValues(array);
|
|
@@ -365,7 +366,7 @@ class BaseApi {
|
|
|
365
366
|
// ========================================================================
|
|
366
367
|
/**
|
|
367
368
|
* Internal: Authenticate with Google OAuth.
|
|
368
|
-
* Use googleLogin() instead
|
|
369
|
+
* Use googleLogin() instead.
|
|
369
370
|
*/
|
|
370
371
|
async _googleOAuthLogin(credential, nonce) {
|
|
371
372
|
console.log("[API] Google OAuth login attempt");
|
|
@@ -385,17 +386,17 @@ class BaseApi {
|
|
|
385
386
|
return data;
|
|
386
387
|
}
|
|
387
388
|
/**
|
|
388
|
-
*
|
|
389
|
+
* Simplified Google OAuth login.
|
|
389
390
|
*
|
|
390
|
-
* This is the
|
|
391
|
-
* OAuth flow in
|
|
391
|
+
* This is the default method for Google Sign-In. It handles the complete
|
|
392
|
+
* OAuth flow in a single call:
|
|
392
393
|
*
|
|
393
394
|
* 1. Retrieve nonce from sessionStorage (must exist from generateOAuthNonce())
|
|
394
395
|
* 2. Send credential + nonce to backend for verification
|
|
395
396
|
* 3. Store JWT tokens on success
|
|
396
397
|
* 4. Clear nonce to prevent replay attacks
|
|
397
398
|
*
|
|
398
|
-
* If
|
|
399
|
+
* If any step fails, the entire operation fails cleanly. No partial states.
|
|
399
400
|
*
|
|
400
401
|
* @param credential - The Google ID token (JWT from GoogleLogin onSuccess)
|
|
401
402
|
* @returns GoogleOAuthResponse with tokens, user data, and is_new_user flag
|
|
@@ -414,7 +415,7 @@ class BaseApi {
|
|
|
414
415
|
* ```
|
|
415
416
|
*/
|
|
416
417
|
async googleLogin(credential) {
|
|
417
|
-
console.log("[API] Google OAuth login
|
|
418
|
+
console.log("[API] Google OAuth login");
|
|
418
419
|
const nonce = getOAuthNonce();
|
|
419
420
|
if (!nonce) {
|
|
420
421
|
throw new Error("Security validation failed: OAuth nonce not found. Call generateOAuthNonce() before Google Sign-In.");
|
|
@@ -600,6 +601,13 @@ const getStoredToken = () => {
|
|
|
600
601
|
const isAuthenticated = () => {
|
|
601
602
|
return !!getStoredToken();
|
|
602
603
|
};
|
|
604
|
+
function createApiUrl(options = {}) {
|
|
605
|
+
const { devFallback = "http://localhost:8000" } = options;
|
|
606
|
+
console.error(
|
|
607
|
+
"[RationalBloks] VITE_DATABASE_API_URL is not set in production. API calls will fail. Ensure the Docker build passes --build-arg VITE_DATABASE_API_URL."
|
|
608
|
+
);
|
|
609
|
+
return "";
|
|
610
|
+
}
|
|
603
611
|
const AuthContext = react.createContext(void 0);
|
|
604
612
|
const useAuth = () => {
|
|
605
613
|
const context = react.useContext(AuthContext);
|
|
@@ -742,17 +750,27 @@ function createAuthProvider(api) {
|
|
|
742
750
|
return AuthProvider;
|
|
743
751
|
}
|
|
744
752
|
function createAppRoot(App, config = {}) {
|
|
745
|
-
const { googleClientId } = config;
|
|
753
|
+
const { googleClientId = "" } = config;
|
|
746
754
|
const root = client.createRoot(document.getElementById("root"));
|
|
747
|
-
|
|
748
|
-
if (googleClientId) {
|
|
749
|
-
appElement = /* @__PURE__ */ jsxRuntime.jsx(google.GoogleOAuthProvider, { clientId: googleClientId, children: appElement });
|
|
750
|
-
}
|
|
755
|
+
const appElement = /* @__PURE__ */ jsxRuntime.jsx(google.GoogleOAuthProvider, { clientId: googleClientId, children: /* @__PURE__ */ jsxRuntime.jsx(App, {}) });
|
|
751
756
|
root.render(
|
|
752
757
|
/* @__PURE__ */ jsxRuntime.jsx(react.StrictMode, { children: appElement })
|
|
753
758
|
);
|
|
754
759
|
}
|
|
760
|
+
const ProtectedRoute = ({
|
|
761
|
+
children,
|
|
762
|
+
useAuth: useAuth2,
|
|
763
|
+
redirectTo = "/auth"
|
|
764
|
+
}) => {
|
|
765
|
+
const { isAuthenticated: isAuthenticated2 } = useAuth2();
|
|
766
|
+
if (!isAuthenticated2) {
|
|
767
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: redirectTo, replace: true });
|
|
768
|
+
}
|
|
769
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
770
|
+
};
|
|
755
771
|
exports.BaseApi = BaseApi;
|
|
772
|
+
exports.ProtectedRoute = ProtectedRoute;
|
|
773
|
+
exports.createApiUrl = createApiUrl;
|
|
756
774
|
exports.createAppRoot = createAppRoot;
|
|
757
775
|
exports.createAuthApi = createAuthApi;
|
|
758
776
|
exports.createAuthProvider = createAuthProvider;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/api/client.ts","../src/auth/AuthContext.tsx","../src/auth/AppProvider.tsx"],"sourcesContent":["// ========================================================================\r\n// BASE API CLIENT\r\n// Universal HTTP client with JWT token management\r\n// ========================================================================\r\n// This file is part of the UNIVERSAL BOILERPLATE.\r\n// Copy as-is when creating new applications.\r\n//\r\n// Features:\r\n// - JWT token storage and automatic refresh\r\n// - Cross-tab synchronization via localStorage events\r\n// - Proactive token refresh before expiration\r\n// - Automatic 401 retry with token refresh\r\n// - Rate limiting (429) retry with exponential backoff\r\n// - Authentication methods (login, register, logout)\r\n// - API key management for external integrations\r\n//\r\n// Usage:\r\n// 1. Extend this class for your application's API\r\n// 2. Add your domain-specific CRUD methods in the subclass\r\n// 3. Call super() in constructor with your API base URL\r\n//\r\n// ========================================================================\r\n\r\nimport type { \r\n User, \r\n ApiKey, \r\n ApiKeyCreateResponse, \r\n AuthResponse,\r\n GoogleOAuthResponse,\r\n PasswordResetRequestResponse,\r\n PasswordResetResponse,\r\n EmailVerificationResponse,\r\n SetPasswordResponse\r\n} from './types';\r\n\r\n// ========================================================================\r\n// OAUTH UTILITIES (defined before class so they can be used within it)\r\n// ========================================================================\r\n\r\n/**\r\n * Generate a cryptographically secure nonce for OAuth CSRF protection.\r\n * \r\n * USAGE:\r\n * 1. Call generateOAuthNonce() in component useState (runs once on mount)\r\n * 2. Pass the nonce to GoogleLogin component via the 'nonce' prop\r\n * 3. On success, call authApi.googleLogin(credential) - it handles the rest\r\n * \r\n * @example\r\n * ```typescript\r\n * const [nonce] = useState(() => generateOAuthNonce());\r\n * // Pass to GoogleLogin: <GoogleLogin nonce={nonce} onSuccess={...} />\r\n * // On success: await authApi.googleLogin(credential);\r\n * ```\r\n * \r\n * @returns The generated nonce string\r\n */\r\nexport const generateOAuthNonce = (): string => {\r\n const array = new Uint8Array(32);\r\n crypto.getRandomValues(array);\r\n const nonce = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\r\n sessionStorage.setItem('google_oauth_nonce', nonce);\r\n console.log('[OAuth] Generated nonce for CSRF protection');\r\n return nonce;\r\n};\r\n\r\n/**\r\n * Retrieve the stored OAuth nonce.\r\n * @returns The stored nonce or null if not found\r\n */\r\nexport const getOAuthNonce = (): string | null => {\r\n return sessionStorage.getItem('google_oauth_nonce');\r\n};\r\n\r\n/**\r\n * Clear the stored OAuth nonce (call after successful login).\r\n */\r\nexport const clearOAuthNonce = (): void => {\r\n sessionStorage.removeItem('google_oauth_nonce');\r\n console.log('[OAuth] Cleared nonce');\r\n};\r\n\r\n// ========================================================================\r\n// BASE API CLASS\r\n// ========================================================================\r\n\r\n/**\r\n * BaseApi - Universal HTTP client with authentication.\r\n * \r\n * Extend this class to add your application-specific API methods.\r\n * \r\n * @example\r\n * ```typescript\r\n * class MyAppApi extends BaseApi {\r\n * constructor() {\r\n * super(import.meta.env.VITE_API_URL || 'https://api.myapp.com');\r\n * }\r\n * \r\n * async getProducts() {\r\n * return this.request<Product[]>('/api/products/');\r\n * }\r\n * }\r\n * ```\r\n */\r\nexport class BaseApi {\r\n protected token: string | null = null;\r\n protected refreshToken: string | null = null;\r\n protected isRefreshing: boolean = false;\r\n protected refreshPromise: Promise<boolean> | null = null;\r\n protected proactiveRefreshTimer: ReturnType<typeof setTimeout> | null = null;\r\n protected refreshCheckInterval: ReturnType<typeof setInterval> | null = null;\r\n protected tokenRefreshPromise: Promise<void> | null = null;\r\n protected readonly apiBaseUrl: string;\r\n\r\n constructor(apiBaseUrl: string) {\r\n this.apiBaseUrl = apiBaseUrl;\r\n this.loadTokens();\r\n this.setupVisibilityHandler();\r\n this.setupStorageListener();\r\n this.startRefreshCheckInterval();\r\n \r\n console.log(`[API] Base URL: ${this.apiBaseUrl}`);\r\n console.log(`[API] Environment: ${import.meta.env.DEV ? 'Development' : 'Production'}`);\r\n }\r\n\r\n // ========================================================================\r\n // TOKEN MANAGEMENT\r\n // ========================================================================\r\n\r\n /**\r\n * Sync tokens across browser tabs when localStorage changes.\r\n * Prevents \"stale refresh token\" issue where Tab A rotates the token\r\n * but Tab B still has the old (now invalid) refresh token in memory.\r\n */\r\n private setupStorageListener(): void {\r\n if (typeof window !== 'undefined') {\r\n window.addEventListener('storage', (event) => {\r\n if (event.key === 'auth_token') {\r\n console.log('[API] Token updated in another tab, syncing...');\r\n this.token = event.newValue;\r\n } else if (event.key === 'refresh_token') {\r\n console.log('[API] Refresh token updated in another tab, syncing...');\r\n this.refreshToken = event.newValue;\r\n } else if (event.key === null) {\r\n // Storage was cleared (logout in another tab)\r\n console.log('[API] Storage cleared in another tab, syncing logout...');\r\n this.token = null;\r\n this.refreshToken = null;\r\n window.dispatchEvent(new CustomEvent('auth:cleared'));\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Start interval-based token check (runs every 60 seconds).\r\n * More reliable than setTimeout which browsers throttle in background tabs.\r\n */\r\n private startRefreshCheckInterval(): void {\r\n if (this.refreshCheckInterval) {\r\n clearInterval(this.refreshCheckInterval);\r\n }\r\n \r\n this.refreshCheckInterval = setInterval(() => {\r\n if (this.token) {\r\n this.checkAndRefreshToken();\r\n }\r\n }, 60 * 1000);\r\n }\r\n\r\n /**\r\n * Handle tab visibility changes - check token when user returns to tab.\r\n */\r\n private setupVisibilityHandler(): void {\r\n if (typeof document !== 'undefined') {\r\n document.addEventListener('visibilitychange', () => {\r\n if (document.visibilityState === 'visible' && this.token) {\r\n console.log('[API] Tab became visible, checking token validity...');\r\n this.checkAndRefreshToken();\r\n }\r\n });\r\n \r\n window.addEventListener('focus', () => {\r\n if (this.token) {\r\n console.log('[API] Window focused, checking token validity...');\r\n this.checkAndRefreshToken();\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Check token expiry and refresh if needed.\r\n */\r\n protected async checkAndRefreshToken(): Promise<void> {\r\n if (!this.token) return;\r\n\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (!expiry) return;\r\n\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n const refreshBuffer = 3 * 60 * 1000; // 3 minutes buffer\r\n\r\n if (timeUntilExpiry <= 0) {\r\n console.log('[API] Token expired, refreshing immediately...');\r\n await this.refreshAccessToken();\r\n } else if (timeUntilExpiry <= refreshBuffer) {\r\n console.log(`[API] Token expires in ${Math.round(timeUntilExpiry / 1000)}s, refreshing proactively...`);\r\n await this.refreshAccessToken();\r\n }\r\n }\r\n\r\n /**\r\n * Parse JWT to get expiration time.\r\n */\r\n protected getTokenExpiry(token: string): number | null {\r\n try {\r\n const payload = JSON.parse(atob(token.split('.')[1]));\r\n return payload.exp ? payload.exp * 1000 : null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Schedule proactive refresh 5 minutes before token expires.\r\n */\r\n protected scheduleProactiveRefresh(): void {\r\n if (this.proactiveRefreshTimer) {\r\n clearTimeout(this.proactiveRefreshTimer);\r\n this.proactiveRefreshTimer = null;\r\n }\r\n\r\n if (!this.token) return;\r\n\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (!expiry) return;\r\n\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n const refreshBuffer = 5 * 60 * 1000;\r\n\r\n if (timeUntilExpiry <= 0) {\r\n console.log('[API] Token already expired, refreshing immediately...');\r\n this.refreshAccessToken();\r\n return;\r\n }\r\n\r\n if (timeUntilExpiry <= refreshBuffer) {\r\n console.log('[API] Token expiring soon, refreshing immediately...');\r\n this.refreshAccessToken();\r\n return;\r\n }\r\n\r\n const refreshIn = timeUntilExpiry - refreshBuffer;\r\n console.log(`[API] Scheduling proactive token refresh in ${Math.round(refreshIn / 60000)} minutes`);\r\n \r\n this.proactiveRefreshTimer = setTimeout(async () => {\r\n console.log('[API] Proactive token refresh triggered');\r\n await this.refreshAccessToken();\r\n }, refreshIn);\r\n }\r\n\r\n /**\r\n * Load tokens from localStorage on initialization.\r\n */\r\n protected loadTokens(): void {\r\n const storedToken = localStorage.getItem('auth_token');\r\n const storedRefreshToken = localStorage.getItem('refresh_token');\r\n \r\n if (storedToken) {\r\n this.token = storedToken;\r\n console.log('[API] Access token loaded from localStorage');\r\n }\r\n if (storedRefreshToken) {\r\n this.refreshToken = storedRefreshToken;\r\n console.log('[API] Refresh token loaded from localStorage');\r\n }\r\n \r\n if (this.token) {\r\n this.tokenRefreshPromise = this.checkAndRefreshToken();\r\n this.scheduleProactiveRefresh();\r\n }\r\n }\r\n\r\n /**\r\n * Wait for any pending token refresh to complete before making API calls.\r\n */\r\n async ensureTokenReady(): Promise<void> {\r\n if (this.tokenRefreshPromise) {\r\n await this.tokenRefreshPromise;\r\n this.tokenRefreshPromise = null;\r\n }\r\n }\r\n\r\n /**\r\n * Refresh the access token using the refresh token.\r\n */\r\n async refreshAccessToken(): Promise<boolean> {\r\n if (this.isRefreshing) {\r\n return this.refreshPromise || Promise.resolve(false);\r\n }\r\n\r\n // Sync from localStorage before refresh (another tab may have rotated)\r\n const latestRefreshToken = localStorage.getItem('refresh_token');\r\n if (latestRefreshToken && latestRefreshToken !== this.refreshToken) {\r\n console.log('[API] Syncing refresh token from localStorage');\r\n this.refreshToken = latestRefreshToken;\r\n }\r\n\r\n if (!this.refreshToken) {\r\n console.warn('[API] No refresh token available');\r\n return false;\r\n }\r\n\r\n this.isRefreshing = true;\r\n this.refreshPromise = (async () => {\r\n try {\r\n console.log('[API] Refreshing access token...');\r\n const response = await fetch(`${this.apiBaseUrl}/api/auth/refresh`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ refresh_token: this.refreshToken }),\r\n });\r\n\r\n if (!response.ok) {\r\n console.error('[API] Token refresh failed:', response.status);\r\n if (response.status === 401 || response.status === 403) {\r\n console.log('[API] Refresh token is invalid, logging out...');\r\n this.clearAuth();\r\n } else {\r\n console.warn(`[API] Refresh failed with ${response.status}, will retry on next interval`);\r\n }\r\n return false;\r\n }\r\n\r\n const data = await response.json();\r\n \r\n this.token = data.access_token;\r\n localStorage.setItem('auth_token', data.access_token);\r\n \r\n if (data.refresh_token) {\r\n this.refreshToken = data.refresh_token;\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n console.log('[API] Refresh token rotated');\r\n }\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Access token refreshed successfully');\r\n return true;\r\n } catch (error) {\r\n console.error('[API] Token refresh network error:', error);\r\n console.warn('[API] Will retry refresh on next interval');\r\n return false;\r\n } finally {\r\n this.isRefreshing = false;\r\n this.refreshPromise = null;\r\n }\r\n })();\r\n\r\n return this.refreshPromise;\r\n }\r\n\r\n // ========================================================================\r\n // HTTP REQUEST METHOD\r\n // ========================================================================\r\n\r\n /**\r\n * Make an authenticated HTTP request.\r\n * Handles token refresh, 401 retry, and rate limiting automatically.\r\n * \r\n * This method is public so apps can make custom API calls without extending BaseApi.\r\n * \r\n * @example\r\n * const authApi = createAuthApi(API_URL);\r\n * const data = await authApi.request<MyType>('/api/my-endpoint/', { method: 'POST', body: JSON.stringify(payload) });\r\n */\r\n async request<T>(endpoint: string, options: RequestInit = {}, isRetry = false, retryCount = 0): Promise<T> {\r\n await this.ensureTokenReady();\r\n \r\n // Proactive token refresh before request\r\n if (this.token && !isRetry && !endpoint.includes('/auth/')) {\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (expiry) {\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n if (timeUntilExpiry < 2 * 60 * 1000) {\r\n console.log(`[API] Token expires in ${Math.round(timeUntilExpiry/1000)}s, refreshing before request...`);\r\n await this.refreshAccessToken();\r\n }\r\n }\r\n }\r\n \r\n const url = `${this.apiBaseUrl}${endpoint}`;\r\n \r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n ...(options.headers as Record<string, string>),\r\n };\r\n\r\n if (this.token) {\r\n headers.Authorization = `Bearer ${this.token}`;\r\n }\r\n\r\n console.log(`[API Request] ${options.method || 'GET'} ${url}`);\r\n \r\n try {\r\n const response = await fetch(url, { ...options, headers });\r\n \r\n console.log(`[API Response] ${response.status} ${response.statusText}`);\r\n \r\n if (!response.ok) {\r\n const error = await response.text();\r\n console.error(`[API Error] ${response.status}: ${error}`);\r\n \r\n // Rate limiting retry\r\n if (response.status === 429 && retryCount < 3) {\r\n const backoffMs = Math.pow(2, retryCount) * 1000;\r\n console.log(`[API] Rate limited (429), retrying in ${backoffMs}ms (attempt ${retryCount + 1}/3)...`);\r\n await new Promise(resolve => setTimeout(resolve, backoffMs));\r\n return this.request<T>(endpoint, options, isRetry, retryCount + 1);\r\n }\r\n \r\n // 401 retry with token refresh\r\n if (response.status === 401 && !isRetry && !endpoint.includes('/auth/login') && !endpoint.includes('/auth/register') && !endpoint.includes('/auth/refresh')) {\r\n console.log('[API] Attempting token refresh...');\r\n const refreshed = await this.refreshAccessToken();\r\n if (refreshed) {\r\n return this.request<T>(endpoint, options, true, retryCount);\r\n }\r\n console.warn('[API] Token refresh failed, returning error to caller');\r\n }\r\n \r\n const apiError = new Error(`API Error: ${response.status} - ${error}`) as Error & { status: number };\r\n apiError.status = response.status;\r\n throw apiError;\r\n }\r\n\r\n return response.json();\r\n } catch (error) {\r\n console.error('[API Connection Error]:', error);\r\n if (error instanceof TypeError && error.message.includes('Failed to fetch')) {\r\n throw new Error(`Cannot connect to API: ${this.apiBaseUrl}`);\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n // ========================================================================\r\n // AUTHENTICATION METHODS\r\n // ========================================================================\r\n\r\n async login(email: string, password: string): Promise<AuthResponse> {\r\n console.log('[API] Login attempt:', email);\r\n \r\n const data = await this.request<AuthResponse>('/api/auth/login', {\r\n method: 'POST',\r\n body: JSON.stringify({ email, password }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Login successful');\r\n return data;\r\n }\r\n\r\n async register(email: string, password: string, firstName: string, lastName: string): Promise<AuthResponse> {\r\n console.log('[API] Registration attempt:', email);\r\n \r\n const data = await this.request<AuthResponse>('/api/auth/register', {\r\n method: 'POST',\r\n body: JSON.stringify({ \r\n email, \r\n password, \r\n first_name: firstName, \r\n last_name: lastName \r\n }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Registration successful');\r\n return data;\r\n }\r\n\r\n async getMe(): Promise<{ user: User }> {\r\n console.log('[API] Fetching current user');\r\n return this.request<{ user: User }>('/api/auth/me');\r\n }\r\n\r\n async getCurrentUser(): Promise<User> {\r\n console.log('[API] Fetching current user data');\r\n const response = await this.request<{ user: User }>('/api/auth/me');\r\n localStorage.setItem('user', JSON.stringify(response.user));\r\n return response.user;\r\n }\r\n\r\n // ========================================================================\r\n // GOOGLE OAUTH AUTHENTICATION\r\n // ========================================================================\r\n\r\n /**\r\n * Internal: Authenticate with Google OAuth.\r\n * Use googleLogin() instead - THE ONE WAY.\r\n */\r\n private async _googleOAuthLogin(credential: string, nonce: string): Promise<GoogleOAuthResponse> {\r\n console.log('[API] Google OAuth login attempt');\r\n \r\n const data = await this.request<GoogleOAuthResponse>('/api/auth/google/login', {\r\n method: 'POST',\r\n body: JSON.stringify({ credential, nonce }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log(`[API] Google OAuth successful (new_user: ${data.is_new_user})`);\r\n return data;\r\n }\r\n\r\n /**\r\n * THE ONE WAY: Simplified Google OAuth login.\r\n * \r\n * This is the DEFAULT method for Google Sign-In. It handles the complete\r\n * OAuth flow in one call following the Chain of Events mantra:\r\n * \r\n * 1. Retrieve nonce from sessionStorage (must exist from generateOAuthNonce())\r\n * 2. Send credential + nonce to backend for verification\r\n * 3. Store JWT tokens on success\r\n * 4. Clear nonce to prevent replay attacks\r\n * \r\n * If ANY step fails, the entire chain fails. No partial states.\r\n * \r\n * @param credential - The Google ID token (JWT from GoogleLogin onSuccess)\r\n * @returns GoogleOAuthResponse with tokens, user data, and is_new_user flag\r\n * @throws Error if nonce is missing or authentication fails\r\n * \r\n * @example\r\n * ```typescript\r\n * // In component: generate nonce ONCE on mount\r\n * const [nonce] = useState(() => generateOAuthNonce());\r\n * \r\n * // Pass nonce to GoogleLogin, then on success:\r\n * const handleSuccess = async (response: CredentialResponse) => {\r\n * const result = await authApi.googleLogin(response.credential!);\r\n * // Done! Tokens stored, nonce cleared, user authenticated.\r\n * };\r\n * ```\r\n */\r\n async googleLogin(credential: string): Promise<GoogleOAuthResponse> {\r\n console.log('[API] Google OAuth login (THE ONE WAY)');\r\n \r\n // Step 1: Retrieve nonce - MUST exist or we fail immediately\r\n const nonce = getOAuthNonce();\r\n if (!nonce) {\r\n throw new Error('Security validation failed: OAuth nonce not found. Call generateOAuthNonce() before Google Sign-In.');\r\n }\r\n \r\n // Step 2: Authenticate with backend\r\n const result = await this._googleOAuthLogin(credential, nonce);\r\n \r\n // Step 3: Clear nonce AFTER success to prevent replay attacks\r\n clearOAuthNonce();\r\n console.log('[API] OAuth nonce cleared (replay protection)');\r\n \r\n return result;\r\n }\r\n\r\n // ========================================================================\r\n // PASSWORD RESET FLOW\r\n // ========================================================================\r\n\r\n /**\r\n * Request a password reset email (forgot password flow).\r\n * \r\n * @param email - The email address to send reset link to\r\n * @returns Message confirming email was sent (or would be sent)\r\n */\r\n async requestPasswordReset(email: string): Promise<PasswordResetRequestResponse> {\r\n console.log('[API] Requesting password reset for:', email);\r\n \r\n return this.request<PasswordResetRequestResponse>('/api/auth/request-password-reset', {\r\n method: 'POST',\r\n body: JSON.stringify({ email }),\r\n });\r\n }\r\n\r\n /**\r\n * Reset password using token from email.\r\n * \r\n * @param token - The password reset token from the email link\r\n * @param newPassword - The new password (min 8 characters)\r\n * @returns Success message\r\n */\r\n async resetPassword(token: string, newPassword: string): Promise<PasswordResetResponse> {\r\n console.log('[API] Resetting password with token');\r\n \r\n return this.request<PasswordResetResponse>('/api/auth/reset-password', {\r\n method: 'POST',\r\n body: JSON.stringify({ token, new_password: newPassword }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // EMAIL VERIFICATION FLOW\r\n // ========================================================================\r\n\r\n /**\r\n * Verify email address using token from verification email.\r\n * \r\n * @param token - The verification token from the email link\r\n * @returns Success message\r\n */\r\n async verifyEmail(token: string): Promise<EmailVerificationResponse> {\r\n console.log('[API] Verifying email with token');\r\n \r\n return this.request<EmailVerificationResponse>('/api/auth/verify-email', {\r\n method: 'POST',\r\n body: JSON.stringify({ token }),\r\n });\r\n }\r\n\r\n /**\r\n * Request a new verification email.\r\n * \r\n * @param email - The email address to send verification to\r\n * @returns Message confirming email was sent\r\n */\r\n async requestVerificationEmail(email: string): Promise<EmailVerificationResponse> {\r\n console.log('[API] Requesting verification email for:', email);\r\n \r\n return this.request<EmailVerificationResponse>('/api/auth/request-verification-email', {\r\n method: 'POST',\r\n body: JSON.stringify({ email }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // SET PASSWORD (FOR OAUTH-ONLY ACCOUNTS)\r\n // ========================================================================\r\n\r\n /**\r\n * Set password for OAuth-only accounts.\r\n * \r\n * Allows users who registered via Google OAuth to set a password\r\n * so they can also log in with email/password.\r\n * \r\n * @param newPassword - The password to set (min 8 characters)\r\n * @returns Success message\r\n */\r\n async setPassword(newPassword: string): Promise<SetPasswordResponse> {\r\n console.log('[API] Setting password for OAuth account');\r\n \r\n return this.request<SetPasswordResponse>('/api/auth/set-password', {\r\n method: 'POST',\r\n body: JSON.stringify({ new_password: newPassword }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // ACCOUNT MANAGEMENT\r\n // ========================================================================\r\n\r\n async deleteAccount(password: string, confirmText: string): Promise<{ message: string; note: string }> {\r\n console.log('[API] Deleting account (DESTRUCTIVE)');\r\n \r\n const response = await this.request<{ message: string; note: string }>('/api/auth/me', {\r\n method: 'DELETE',\r\n body: JSON.stringify({ \r\n password, \r\n confirm_text: confirmText \r\n }),\r\n });\r\n \r\n this.clearAuth();\r\n console.log('[API] Account deleted successfully');\r\n return response;\r\n }\r\n\r\n async logout(): Promise<void> {\r\n console.log('[API] Logging out');\r\n \r\n if (this.refreshToken) {\r\n try {\r\n await this.request('/api/auth/logout', {\r\n method: 'POST',\r\n body: JSON.stringify({ refresh_token: this.refreshToken }),\r\n });\r\n console.log('[API] Refresh token revoked on server');\r\n } catch (error) {\r\n console.warn('[API] Failed to revoke refresh token on server:', error);\r\n }\r\n }\r\n \r\n this.clearAuth();\r\n }\r\n\r\n async logoutAllDevices(): Promise<void> {\r\n console.log('[API] Logging out from all devices');\r\n \r\n try {\r\n await this.request('/api/auth/logout-all', { method: 'POST' });\r\n console.log('[API] All sessions revoked on server');\r\n } catch (error) {\r\n console.warn('[API] Failed to revoke all sessions:', error);\r\n }\r\n \r\n this.clearAuth();\r\n }\r\n\r\n protected clearAuth(): void {\r\n if (this.proactiveRefreshTimer) {\r\n clearTimeout(this.proactiveRefreshTimer);\r\n this.proactiveRefreshTimer = null;\r\n }\r\n \r\n this.token = null;\r\n this.refreshToken = null;\r\n localStorage.removeItem('auth_token');\r\n localStorage.removeItem('refresh_token');\r\n localStorage.removeItem('user_data');\r\n console.log('[API] Auth cache cleared');\r\n \r\n window.dispatchEvent(new CustomEvent('auth:cleared'));\r\n }\r\n\r\n // ========================================================================\r\n // API KEY MANAGEMENT\r\n // ========================================================================\r\n\r\n async listApiKeys(): Promise<{ api_keys: ApiKey[]; total: number }> {\r\n console.log('[API] Listing user API keys');\r\n return this.request('/api/auth/api-keys', { method: 'GET' });\r\n }\r\n\r\n async createApiKey(data: {\r\n name: string;\r\n scopes?: string;\r\n expires_in_days?: number;\r\n rate_limit_per_minute?: number;\r\n }): Promise<ApiKeyCreateResponse> {\r\n console.log(`[API] Creating API key: ${data.name}`);\r\n return this.request('/api/auth/api-keys', {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n name: data.name,\r\n scopes: data.scopes || 'read,write',\r\n expires_in_days: data.expires_in_days,\r\n rate_limit_per_minute: data.rate_limit_per_minute || 60\r\n }),\r\n });\r\n }\r\n\r\n async revokeApiKey(keyId: string): Promise<{ message: string; key_prefix: string; revoked_at: string }> {\r\n console.log(`[API] Revoking API key: ${keyId}`);\r\n return this.request(`/api/auth/api-keys/${keyId}`, { method: 'DELETE' });\r\n }\r\n}\r\n\r\n// ========================================================================\r\n// FACTORY FUNCTION (RECOMMENDED)\r\n// ========================================================================\r\n// Use this instead of extending BaseApi. Cleaner, no inheritance.\r\n//\r\n// Usage:\r\n// import { createAuthApi } from '@rationalbloks/frontblok-auth';\r\n// export const authApi = createAuthApi(import.meta.env.VITE_API_URL);\r\n//\r\n// // Then use directly:\r\n// authApi.login(email, password);\r\n// authApi.logout();\r\n// authApi.listApiKeys();\r\n\r\n/**\r\n * Creates an auth API client instance.\r\n * Preferred over class inheritance - simpler and cleaner.\r\n * \r\n * @param apiBaseUrl - The backend API base URL\r\n * @returns A BaseApi instance with all auth methods\r\n */\r\nexport function createAuthApi(apiBaseUrl: string): BaseApi {\r\n return new BaseApi(apiBaseUrl);\r\n}\r\n\r\n// ========================================================================\r\n// STORAGE UTILITIES\r\n// ========================================================================\r\n\r\nexport const getStoredUser = (): User | null => {\r\n const userData = localStorage.getItem('user_data');\r\n if (!userData) return null;\r\n \r\n try {\r\n return JSON.parse(userData);\r\n } catch (error) {\r\n console.error('[API] Invalid user_data in localStorage:', error);\r\n localStorage.removeItem('user_data');\r\n return null;\r\n }\r\n};\r\n\r\nexport const getStoredToken = (): string | null => {\r\n return localStorage.getItem('auth_token');\r\n};\r\n\r\nexport const isAuthenticated = (): boolean => {\r\n return !!getStoredToken();\r\n};\r\n","// ========================================================================\r\n// AUTH CONTEXT\r\n// Universal authentication state management\r\n// ========================================================================\r\n// This file is part of the UNIVERSAL BOILERPLATE.\r\n// It provides:\r\n// - Authentication state (user, isLoading, isAuthenticated, error)\r\n// - Authentication actions (login, register, logout, clearError, refreshUser)\r\n// - React Context pattern for app-wide auth state\r\n// - Token expiration handling via 'auth:cleared' event\r\n// ========================================================================\r\n\r\nimport React, { createContext, useContext, useState, useEffect } from 'react';\r\nimport { getStoredUser, getStoredToken } from '../api';\r\nimport type { User } from '../api';\r\nimport type { BaseApi } from '../api/client';\r\n\r\n// ========================================================================\r\n// TYPES\r\n// ========================================================================\r\n\r\nexport interface AuthState {\r\n user: User | null;\r\n isLoading: boolean;\r\n isAuthenticated: boolean;\r\n error: string | null;\r\n}\r\n\r\nexport interface AuthActions {\r\n login: (email: string, password: string) => Promise<boolean>;\r\n register: (email: string, password: string, firstName: string, lastName: string) => Promise<boolean>;\r\n logout: () => void;\r\n clearError: () => void;\r\n refreshUser: () => Promise<void>;\r\n}\r\n\r\nexport type AuthContextType = AuthState & AuthActions;\r\n\r\n// ========================================================================\r\n// CONTEXT\r\n// ========================================================================\r\n\r\nconst AuthContext = createContext<AuthContextType | undefined>(undefined);\r\n\r\n/**\r\n * Hook to access authentication state and actions.\r\n * Must be used within an AuthProvider.\r\n */\r\nexport const useAuth = () => {\r\n const context = useContext(AuthContext);\r\n if (!context) {\r\n throw new Error('useAuth must be used within AuthProvider');\r\n }\r\n return context;\r\n};\r\n\r\n// ========================================================================\r\n// PROVIDER FACTORY\r\n// ========================================================================\r\n\r\n/**\r\n * Creates an AuthProvider component that uses the specified API instance.\r\n * This allows the universal auth context to work with any API that extends BaseApi.\r\n * \r\n * @example\r\n * ```typescript\r\n * // In your app's auth setup:\r\n * import { createAuthProvider } from '@/core/auth';\r\n * import { myAppApi } from '@/services/myAppApi';\r\n * \r\n * export const MyAppAuthProvider = createAuthProvider(myAppApi);\r\n * ```\r\n */\r\nexport function createAuthProvider(api: BaseApi) {\r\n const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {\r\n const [state, setState] = useState<AuthState>({\r\n user: getStoredUser(),\r\n isLoading: false,\r\n isAuthenticated: !!getStoredToken(),\r\n error: null,\r\n });\r\n\r\n // Listen for auth:cleared event (token expiration, session invalidation)\r\n useEffect(() => {\r\n const handleAuthCleared = () => {\r\n console.log('[Auth] Session expired or invalidated - clearing auth state');\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n };\r\n\r\n window.addEventListener('auth:cleared', handleAuthCleared);\r\n return () => window.removeEventListener('auth:cleared', handleAuthCleared);\r\n }, []);\r\n\r\n // Refresh user data from backend on mount if authenticated\r\n useEffect(() => {\r\n const refreshUserData = async () => {\r\n const token = getStoredToken();\r\n if (token) {\r\n try {\r\n const currentUser = await api.getCurrentUser();\r\n setState(prev => ({\r\n ...prev,\r\n user: currentUser,\r\n isAuthenticated: true,\r\n }));\r\n } catch (error: unknown) {\r\n console.error('[Auth] Failed to refresh user data:', error);\r\n const httpError = error as { status?: number };\r\n if (httpError?.status === 401 || httpError?.status === 403) {\r\n console.warn('[Auth] Auth failed - clearing local state only');\r\n localStorage.removeItem('auth_token');\r\n localStorage.removeItem('user_data');\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n } else {\r\n console.warn('[Auth] Keeping user logged in with cached data');\r\n }\r\n }\r\n }\r\n };\r\n\r\n refreshUserData();\r\n }, []);\r\n\r\n const login = async (email: string, password: string): Promise<boolean> => {\r\n setState(prev => ({ ...prev, isLoading: true, error: null }));\r\n \r\n try {\r\n const result = await api.login(email, password);\r\n setState(prev => ({\r\n ...prev,\r\n user: result.user,\r\n isAuthenticated: true,\r\n isLoading: false,\r\n }));\r\n return true;\r\n } catch (error) {\r\n setState(prev => ({\r\n ...prev,\r\n error: error instanceof Error ? error.message : 'Login failed',\r\n isLoading: false,\r\n }));\r\n return false;\r\n }\r\n };\r\n\r\n const register = async (email: string, password: string, firstName: string, lastName: string): Promise<boolean> => {\r\n setState(prev => ({ ...prev, isLoading: true, error: null }));\r\n \r\n try {\r\n const result = await api.register(email, password, firstName, lastName);\r\n setState(prev => ({\r\n ...prev,\r\n user: result.user,\r\n isAuthenticated: true,\r\n isLoading: false,\r\n }));\r\n return true;\r\n } catch (error) {\r\n setState(prev => ({\r\n ...prev,\r\n error: error instanceof Error ? error.message : 'Registration failed',\r\n isLoading: false,\r\n }));\r\n return false;\r\n }\r\n };\r\n\r\n const logout = () => {\r\n api.logout();\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n };\r\n\r\n const clearError = () => {\r\n setState(prev => ({ ...prev, error: null }));\r\n };\r\n\r\n const refreshUser = async () => {\r\n try {\r\n const currentUser = await api.getCurrentUser();\r\n setState(prev => ({\r\n ...prev,\r\n user: currentUser,\r\n }));\r\n } catch (error) {\r\n console.error('[Auth] Failed to refresh user:', error);\r\n }\r\n };\r\n\r\n return (\r\n <AuthContext.Provider\r\n value={{\r\n ...state,\r\n login,\r\n register,\r\n logout,\r\n clearError,\r\n refreshUser,\r\n }}\r\n >\r\n {children}\r\n </AuthContext.Provider>\r\n );\r\n };\r\n\r\n return AuthProvider;\r\n}\r\n","// ========================================================================\r\n// CORE AUTH - App Provider Factory\r\n// ========================================================================\r\n// Creates a wrapped app component with OAuth providers configured.\r\n// This is the universal entry point for React apps with authentication.\r\n//\r\n// Usage:\r\n// import { createAppRoot } from './core/auth';\r\n// import App from './App';\r\n// createAppRoot(App, { googleClientId: '...' });\r\n// ========================================================================\r\n\r\nimport React, { StrictMode } from 'react';\r\nimport { createRoot } from 'react-dom/client';\r\nimport { GoogleOAuthProvider } from '@react-oauth/google';\r\n\r\nexport interface AuthConfig {\r\n /** Google OAuth Client ID (optional - only needed if using Google Sign-In) */\r\n googleClientId?: string;\r\n}\r\n\r\n/**\r\n * Creates and renders the app root with authentication providers.\r\n * This is the universal way to bootstrap a React app with auth.\r\n */\r\nexport function createAppRoot(\r\n App: React.ComponentType,\r\n config: AuthConfig = {}\r\n): void {\r\n const { googleClientId } = config;\r\n\r\n const root = createRoot(document.getElementById('root')!);\r\n\r\n // Build the component tree with optional providers\r\n let appElement: React.ReactElement = <App />;\r\n\r\n // Wrap with Google OAuth if client ID is provided\r\n if (googleClientId) {\r\n appElement = (\r\n <GoogleOAuthProvider clientId={googleClientId}>\r\n {appElement}\r\n </GoogleOAuthProvider>\r\n );\r\n }\r\n\r\n // Render with StrictMode\r\n root.render(\r\n <StrictMode>\r\n {appElement}\r\n </StrictMode>\r\n );\r\n}\r\n"],"names":["createContext","useContext","useState","useEffect","jsx","createRoot","GoogleOAuthProvider","StrictMode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/api/client.ts","../src/api/createApiUrl.ts","../src/auth/AuthContext.tsx","../src/auth/AppProvider.tsx","../src/auth/ProtectedRoute.tsx"],"sourcesContent":["// ========================================================================\r\n// BASE API CLIENT\r\n// Universal HTTP client with JWT token management\r\n// ========================================================================\r\n//\r\n// Features:\r\n// - JWT token storage and automatic refresh\r\n// - Cross-tab synchronization via localStorage events\r\n// - Proactive token refresh before expiration\r\n// - Automatic 401 retry with token refresh\r\n// - Rate limiting (429) retry with exponential backoff\r\n// - Authentication methods (login, register, logout)\r\n// - API key management for external integrations\r\n//\r\n// Usage:\r\n// 1. Extend this class for your application's API\r\n// 2. Add your domain-specific CRUD methods in the subclass\r\n// 3. Call super() in constructor with your API base URL\r\n//\r\n// ========================================================================\r\n\r\nimport type { \r\n User, \r\n ApiKey, \r\n ApiKeyCreateResponse, \r\n AuthResponse,\r\n GoogleOAuthResponse,\r\n PasswordResetRequestResponse,\r\n PasswordResetResponse,\r\n EmailVerificationResponse,\r\n SetPasswordResponse\r\n} from './types';\r\n\r\n// ========================================================================\r\n// OAUTH UTILITIES (defined before class so they can be used within it)\r\n// ========================================================================\r\n\r\n/**\r\n * Generate a cryptographically secure nonce for OAuth CSRF protection.\r\n * \r\n * USAGE:\r\n * 1. Call generateOAuthNonce() in component useState (runs once on mount)\r\n * 2. Pass the nonce to GoogleLogin component via the 'nonce' prop\r\n * 3. On success, call authApi.googleLogin(credential) - it handles the rest\r\n * \r\n * @example\r\n * ```typescript\r\n * const [nonce] = useState(() => generateOAuthNonce());\r\n * // Pass to GoogleLogin: <GoogleLogin nonce={nonce} onSuccess={...} />\r\n * // On success: await authApi.googleLogin(credential);\r\n * ```\r\n * \r\n * @returns The generated nonce string\r\n */\r\nexport const generateOAuthNonce = (): string => {\r\n const array = new Uint8Array(32);\r\n crypto.getRandomValues(array);\r\n const nonce = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\r\n sessionStorage.setItem('google_oauth_nonce', nonce);\r\n console.log('[OAuth] Generated nonce for CSRF protection');\r\n return nonce;\r\n};\r\n\r\n/**\r\n * Retrieve the stored OAuth nonce.\r\n * @returns The stored nonce or null if not found\r\n */\r\nexport const getOAuthNonce = (): string | null => {\r\n return sessionStorage.getItem('google_oauth_nonce');\r\n};\r\n\r\n/**\r\n * Clear the stored OAuth nonce (call after successful login).\r\n */\r\nexport const clearOAuthNonce = (): void => {\r\n sessionStorage.removeItem('google_oauth_nonce');\r\n console.log('[OAuth] Cleared nonce');\r\n};\r\n\r\n// ========================================================================\r\n// BASE API CLASS\r\n// ========================================================================\r\n\r\n/**\r\n * BaseApi - Universal HTTP client with authentication.\r\n * \r\n * Extend this class to add your application-specific API methods.\r\n * \r\n * @example\r\n * ```typescript\r\n * class MyAppApi extends BaseApi {\r\n * constructor() {\r\n * super(import.meta.env.VITE_API_URL || 'https://api.myapp.com');\r\n * }\r\n * \r\n * async getProducts() {\r\n * return this.request<Product[]>('/api/products/');\r\n * }\r\n * }\r\n * ```\r\n */\r\nexport class BaseApi {\r\n protected token: string | null = null;\r\n protected refreshToken: string | null = null;\r\n protected isRefreshing: boolean = false;\r\n protected refreshPromise: Promise<boolean> | null = null;\r\n protected proactiveRefreshTimer: ReturnType<typeof setTimeout> | null = null;\r\n protected refreshCheckInterval: ReturnType<typeof setInterval> | null = null;\r\n protected tokenRefreshPromise: Promise<void> | null = null;\r\n protected readonly apiBaseUrl: string;\r\n\r\n constructor(apiBaseUrl: string) {\r\n this.apiBaseUrl = apiBaseUrl;\r\n this.loadTokens();\r\n this.setupVisibilityHandler();\r\n this.setupStorageListener();\r\n this.startRefreshCheckInterval();\r\n \r\n console.log(`[API] Base URL: ${this.apiBaseUrl}`);\r\n console.log(`[API] Environment: ${import.meta.env.DEV ? 'Development' : 'Production'}`);\r\n }\r\n\r\n // ========================================================================\r\n // TOKEN MANAGEMENT\r\n // ========================================================================\r\n\r\n /**\r\n * Sync tokens across browser tabs when localStorage changes.\r\n * Prevents \"stale refresh token\" issue where Tab A rotates the token\r\n * but Tab B still has the old (now invalid) refresh token in memory.\r\n */\r\n private setupStorageListener(): void {\r\n if (typeof window !== 'undefined') {\r\n window.addEventListener('storage', (event) => {\r\n if (event.key === 'auth_token') {\r\n console.log('[API] Token updated in another tab, syncing...');\r\n this.token = event.newValue;\r\n } else if (event.key === 'refresh_token') {\r\n console.log('[API] Refresh token updated in another tab, syncing...');\r\n this.refreshToken = event.newValue;\r\n } else if (event.key === null) {\r\n // Storage was cleared (logout in another tab)\r\n console.log('[API] Storage cleared in another tab, syncing logout...');\r\n this.token = null;\r\n this.refreshToken = null;\r\n window.dispatchEvent(new CustomEvent('auth:cleared'));\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Start interval-based token check (runs every 60 seconds).\r\n * More reliable than setTimeout which browsers throttle in background tabs.\r\n */\r\n private startRefreshCheckInterval(): void {\r\n if (this.refreshCheckInterval) {\r\n clearInterval(this.refreshCheckInterval);\r\n }\r\n \r\n this.refreshCheckInterval = setInterval(() => {\r\n if (this.token) {\r\n this.checkAndRefreshToken();\r\n }\r\n }, 60 * 1000);\r\n }\r\n\r\n /**\r\n * Handle tab visibility changes - check token when user returns to tab.\r\n */\r\n private setupVisibilityHandler(): void {\r\n if (typeof document !== 'undefined') {\r\n document.addEventListener('visibilitychange', () => {\r\n if (document.visibilityState === 'visible' && this.token) {\r\n console.log('[API] Tab became visible, checking token validity...');\r\n this.checkAndRefreshToken();\r\n }\r\n });\r\n \r\n window.addEventListener('focus', () => {\r\n if (this.token) {\r\n console.log('[API] Window focused, checking token validity...');\r\n this.checkAndRefreshToken();\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Check token expiry and refresh if needed.\r\n */\r\n protected async checkAndRefreshToken(): Promise<void> {\r\n if (!this.token) return;\r\n\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (!expiry) return;\r\n\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n const refreshBuffer = 3 * 60 * 1000; // 3 minutes buffer\r\n\r\n if (timeUntilExpiry <= 0) {\r\n console.log('[API] Token expired, refreshing immediately...');\r\n await this.refreshAccessToken();\r\n } else if (timeUntilExpiry <= refreshBuffer) {\r\n console.log(`[API] Token expires in ${Math.round(timeUntilExpiry / 1000)}s, refreshing proactively...`);\r\n await this.refreshAccessToken();\r\n }\r\n }\r\n\r\n /**\r\n * Parse JWT to get expiration time.\r\n */\r\n protected getTokenExpiry(token: string): number | null {\r\n try {\r\n const payload = JSON.parse(atob(token.split('.')[1]));\r\n return payload.exp ? payload.exp * 1000 : null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Schedule proactive refresh 5 minutes before token expires.\r\n */\r\n protected scheduleProactiveRefresh(): void {\r\n if (this.proactiveRefreshTimer) {\r\n clearTimeout(this.proactiveRefreshTimer);\r\n this.proactiveRefreshTimer = null;\r\n }\r\n\r\n if (!this.token) return;\r\n\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (!expiry) return;\r\n\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n const refreshBuffer = 5 * 60 * 1000;\r\n\r\n if (timeUntilExpiry <= 0) {\r\n console.log('[API] Token already expired, refreshing immediately...');\r\n this.refreshAccessToken();\r\n return;\r\n }\r\n\r\n if (timeUntilExpiry <= refreshBuffer) {\r\n console.log('[API] Token expiring soon, refreshing immediately...');\r\n this.refreshAccessToken();\r\n return;\r\n }\r\n\r\n const refreshIn = timeUntilExpiry - refreshBuffer;\r\n console.log(`[API] Scheduling proactive token refresh in ${Math.round(refreshIn / 60000)} minutes`);\r\n \r\n this.proactiveRefreshTimer = setTimeout(async () => {\r\n console.log('[API] Proactive token refresh triggered');\r\n await this.refreshAccessToken();\r\n }, refreshIn);\r\n }\r\n\r\n /**\r\n * Load tokens from localStorage on initialization.\r\n */\r\n protected loadTokens(): void {\r\n const storedToken = localStorage.getItem('auth_token');\r\n const storedRefreshToken = localStorage.getItem('refresh_token');\r\n \r\n if (storedToken) {\r\n this.token = storedToken;\r\n console.log('[API] Access token loaded from localStorage');\r\n }\r\n if (storedRefreshToken) {\r\n this.refreshToken = storedRefreshToken;\r\n console.log('[API] Refresh token loaded from localStorage');\r\n }\r\n \r\n if (this.token) {\r\n this.tokenRefreshPromise = this.checkAndRefreshToken();\r\n this.scheduleProactiveRefresh();\r\n }\r\n }\r\n\r\n /**\r\n * Wait for any pending token refresh to complete before making API calls.\r\n */\r\n async ensureTokenReady(): Promise<void> {\r\n if (this.tokenRefreshPromise) {\r\n await this.tokenRefreshPromise;\r\n this.tokenRefreshPromise = null;\r\n }\r\n }\r\n\r\n /**\r\n * Refresh the access token using the refresh token.\r\n */\r\n async refreshAccessToken(): Promise<boolean> {\r\n if (this.isRefreshing) {\r\n return this.refreshPromise || Promise.resolve(false);\r\n }\r\n\r\n // Sync from localStorage before refresh (another tab may have rotated)\r\n const latestRefreshToken = localStorage.getItem('refresh_token');\r\n if (latestRefreshToken && latestRefreshToken !== this.refreshToken) {\r\n console.log('[API] Syncing refresh token from localStorage');\r\n this.refreshToken = latestRefreshToken;\r\n }\r\n\r\n if (!this.refreshToken) {\r\n console.warn('[API] No refresh token available');\r\n return false;\r\n }\r\n\r\n this.isRefreshing = true;\r\n this.refreshPromise = (async () => {\r\n try {\r\n console.log('[API] Refreshing access token...');\r\n const response = await fetch(`${this.apiBaseUrl}/api/auth/refresh`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ refresh_token: this.refreshToken }),\r\n });\r\n\r\n if (!response.ok) {\r\n console.error('[API] Token refresh failed:', response.status);\r\n if (response.status === 401 || response.status === 403) {\r\n console.log('[API] Refresh token is invalid, logging out...');\r\n this.clearAuth();\r\n } else {\r\n console.warn(`[API] Refresh failed with ${response.status}, will retry on next interval`);\r\n }\r\n return false;\r\n }\r\n\r\n const data = await response.json();\r\n \r\n this.token = data.access_token;\r\n localStorage.setItem('auth_token', data.access_token);\r\n \r\n if (data.refresh_token) {\r\n this.refreshToken = data.refresh_token;\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n console.log('[API] Refresh token rotated');\r\n }\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Access token refreshed successfully');\r\n return true;\r\n } catch (error) {\r\n console.error('[API] Token refresh network error:', error);\r\n console.warn('[API] Will retry refresh on next interval');\r\n return false;\r\n } finally {\r\n this.isRefreshing = false;\r\n this.refreshPromise = null;\r\n }\r\n })();\r\n\r\n return this.refreshPromise;\r\n }\r\n\r\n // ========================================================================\r\n // HTTP REQUEST METHOD\r\n // ========================================================================\r\n\r\n /**\r\n * Make an authenticated HTTP request.\r\n * Handles token refresh, 401 retry, and rate limiting automatically.\r\n * \r\n * This method is public so apps can make custom API calls without extending BaseApi.\r\n * \r\n * @example\r\n * const authApi = createAuthApi(API_URL);\r\n * const data = await authApi.request<MyType>('/api/my-endpoint/', { method: 'POST', body: JSON.stringify(payload) });\r\n */\r\n async request<T>(endpoint: string, options: RequestInit = {}, isRetry = false, retryCount = 0): Promise<T> {\r\n await this.ensureTokenReady();\r\n \r\n // Proactive token refresh before request\r\n if (this.token && !isRetry && !endpoint.includes('/auth/')) {\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (expiry) {\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n if (timeUntilExpiry < 2 * 60 * 1000) {\r\n console.log(`[API] Token expires in ${Math.round(timeUntilExpiry/1000)}s, refreshing before request...`);\r\n await this.refreshAccessToken();\r\n }\r\n }\r\n }\r\n \r\n const url = `${this.apiBaseUrl}${endpoint}`;\r\n \r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n ...(options.headers as Record<string, string>),\r\n };\r\n\r\n if (this.token) {\r\n headers.Authorization = `Bearer ${this.token}`;\r\n }\r\n\r\n console.log(`[API Request] ${options.method || 'GET'} ${url}`);\r\n \r\n try {\r\n const response = await fetch(url, { ...options, headers });\r\n \r\n console.log(`[API Response] ${response.status} ${response.statusText}`);\r\n \r\n if (!response.ok) {\r\n const error = await response.text();\r\n console.error(`[API Error] ${response.status}: ${error}`);\r\n \r\n // Rate limiting retry\r\n if (response.status === 429 && retryCount < 3) {\r\n const backoffMs = Math.pow(2, retryCount) * 1000;\r\n console.log(`[API] Rate limited (429), retrying in ${backoffMs}ms (attempt ${retryCount + 1}/3)...`);\r\n await new Promise(resolve => setTimeout(resolve, backoffMs));\r\n return this.request<T>(endpoint, options, isRetry, retryCount + 1);\r\n }\r\n \r\n // 401 retry with token refresh\r\n if (response.status === 401 && !isRetry && !endpoint.includes('/auth/login') && !endpoint.includes('/auth/register') && !endpoint.includes('/auth/refresh')) {\r\n console.log('[API] Attempting token refresh...');\r\n const refreshed = await this.refreshAccessToken();\r\n if (refreshed) {\r\n return this.request<T>(endpoint, options, true, retryCount);\r\n }\r\n console.warn('[API] Token refresh failed, returning error to caller');\r\n }\r\n \r\n const apiError = new Error(`API Error: ${response.status} - ${error}`) as Error & { status: number };\r\n apiError.status = response.status;\r\n throw apiError;\r\n }\r\n\r\n return response.json();\r\n } catch (error) {\r\n console.error('[API Connection Error]:', error);\r\n if (error instanceof TypeError && error.message.includes('Failed to fetch')) {\r\n throw new Error(`Cannot connect to API: ${this.apiBaseUrl}`);\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n // ========================================================================\r\n // AUTHENTICATION METHODS\r\n // ========================================================================\r\n\r\n async login(email: string, password: string): Promise<AuthResponse> {\r\n console.log('[API] Login attempt:', email);\r\n \r\n const data = await this.request<AuthResponse>('/api/auth/login', {\r\n method: 'POST',\r\n body: JSON.stringify({ email, password }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Login successful');\r\n return data;\r\n }\r\n\r\n async register(email: string, password: string, firstName: string, lastName: string): Promise<AuthResponse> {\r\n console.log('[API] Registration attempt:', email);\r\n \r\n const data = await this.request<AuthResponse>('/api/auth/register', {\r\n method: 'POST',\r\n body: JSON.stringify({ \r\n email, \r\n password, \r\n first_name: firstName, \r\n last_name: lastName \r\n }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Registration successful');\r\n return data;\r\n }\r\n\r\n async getMe(): Promise<{ user: User }> {\r\n console.log('[API] Fetching current user');\r\n return this.request<{ user: User }>('/api/auth/me');\r\n }\r\n\r\n async getCurrentUser(): Promise<User> {\r\n console.log('[API] Fetching current user data');\r\n const response = await this.request<{ user: User }>('/api/auth/me');\r\n localStorage.setItem('user', JSON.stringify(response.user));\r\n return response.user;\r\n }\r\n\r\n // ========================================================================\r\n // GOOGLE OAUTH AUTHENTICATION\r\n // ========================================================================\r\n\r\n /**\r\n * Internal: Authenticate with Google OAuth.\r\n * Use googleLogin() instead.\r\n */\r\n private async _googleOAuthLogin(credential: string, nonce: string): Promise<GoogleOAuthResponse> {\r\n console.log('[API] Google OAuth login attempt');\r\n \r\n const data = await this.request<GoogleOAuthResponse>('/api/auth/google/login', {\r\n method: 'POST',\r\n body: JSON.stringify({ credential, nonce }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log(`[API] Google OAuth successful (new_user: ${data.is_new_user})`);\r\n return data;\r\n }\r\n\r\n /**\r\n * Simplified Google OAuth login.\r\n * \r\n * This is the default method for Google Sign-In. It handles the complete\r\n * OAuth flow in a single call:\r\n * \r\n * 1. Retrieve nonce from sessionStorage (must exist from generateOAuthNonce())\r\n * 2. Send credential + nonce to backend for verification\r\n * 3. Store JWT tokens on success\r\n * 4. Clear nonce to prevent replay attacks\r\n * \r\n * If any step fails, the entire operation fails cleanly. No partial states.\r\n * \r\n * @param credential - The Google ID token (JWT from GoogleLogin onSuccess)\r\n * @returns GoogleOAuthResponse with tokens, user data, and is_new_user flag\r\n * @throws Error if nonce is missing or authentication fails\r\n * \r\n * @example\r\n * ```typescript\r\n * // In component: generate nonce ONCE on mount\r\n * const [nonce] = useState(() => generateOAuthNonce());\r\n * \r\n * // Pass nonce to GoogleLogin, then on success:\r\n * const handleSuccess = async (response: CredentialResponse) => {\r\n * const result = await authApi.googleLogin(response.credential!);\r\n * // Done! Tokens stored, nonce cleared, user authenticated.\r\n * };\r\n * ```\r\n */\r\n async googleLogin(credential: string): Promise<GoogleOAuthResponse> {\r\n console.log('[API] Google OAuth login');\r\n \r\n // Step 1: Retrieve nonce - MUST exist or we fail immediately\r\n const nonce = getOAuthNonce();\r\n if (!nonce) {\r\n throw new Error('Security validation failed: OAuth nonce not found. Call generateOAuthNonce() before Google Sign-In.');\r\n }\r\n \r\n // Step 2: Authenticate with backend\r\n const result = await this._googleOAuthLogin(credential, nonce);\r\n \r\n // Step 3: Clear nonce AFTER success to prevent replay attacks\r\n clearOAuthNonce();\r\n console.log('[API] OAuth nonce cleared (replay protection)');\r\n \r\n return result;\r\n }\r\n\r\n // ========================================================================\r\n // PASSWORD RESET FLOW\r\n // ========================================================================\r\n\r\n /**\r\n * Request a password reset email (forgot password flow).\r\n * \r\n * @param email - The email address to send reset link to\r\n * @returns Message confirming email was sent (or would be sent)\r\n */\r\n async requestPasswordReset(email: string): Promise<PasswordResetRequestResponse> {\r\n console.log('[API] Requesting password reset for:', email);\r\n \r\n return this.request<PasswordResetRequestResponse>('/api/auth/request-password-reset', {\r\n method: 'POST',\r\n body: JSON.stringify({ email }),\r\n });\r\n }\r\n\r\n /**\r\n * Reset password using token from email.\r\n * \r\n * @param token - The password reset token from the email link\r\n * @param newPassword - The new password (min 8 characters)\r\n * @returns Success message\r\n */\r\n async resetPassword(token: string, newPassword: string): Promise<PasswordResetResponse> {\r\n console.log('[API] Resetting password with token');\r\n \r\n return this.request<PasswordResetResponse>('/api/auth/reset-password', {\r\n method: 'POST',\r\n body: JSON.stringify({ token, new_password: newPassword }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // EMAIL VERIFICATION FLOW\r\n // ========================================================================\r\n\r\n /**\r\n * Verify email address using token from verification email.\r\n * \r\n * @param token - The verification token from the email link\r\n * @returns Success message\r\n */\r\n async verifyEmail(token: string): Promise<EmailVerificationResponse> {\r\n console.log('[API] Verifying email with token');\r\n \r\n return this.request<EmailVerificationResponse>('/api/auth/verify-email', {\r\n method: 'POST',\r\n body: JSON.stringify({ token }),\r\n });\r\n }\r\n\r\n /**\r\n * Request a new verification email.\r\n * \r\n * @param email - The email address to send verification to\r\n * @returns Message confirming email was sent\r\n */\r\n async requestVerificationEmail(email: string): Promise<EmailVerificationResponse> {\r\n console.log('[API] Requesting verification email for:', email);\r\n \r\n return this.request<EmailVerificationResponse>('/api/auth/request-verification-email', {\r\n method: 'POST',\r\n body: JSON.stringify({ email }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // SET PASSWORD (FOR OAUTH-ONLY ACCOUNTS)\r\n // ========================================================================\r\n\r\n /**\r\n * Set password for OAuth-only accounts.\r\n * \r\n * Allows users who registered via Google OAuth to set a password\r\n * so they can also log in with email/password.\r\n * \r\n * @param newPassword - The password to set (min 8 characters)\r\n * @returns Success message\r\n */\r\n async setPassword(newPassword: string): Promise<SetPasswordResponse> {\r\n console.log('[API] Setting password for OAuth account');\r\n \r\n return this.request<SetPasswordResponse>('/api/auth/set-password', {\r\n method: 'POST',\r\n body: JSON.stringify({ new_password: newPassword }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // ACCOUNT MANAGEMENT\r\n // ========================================================================\r\n\r\n async deleteAccount(password: string, confirmText: string): Promise<{ message: string; note: string }> {\r\n console.log('[API] Deleting account (DESTRUCTIVE)');\r\n \r\n const response = await this.request<{ message: string; note: string }>('/api/auth/me', {\r\n method: 'DELETE',\r\n body: JSON.stringify({ \r\n password, \r\n confirm_text: confirmText \r\n }),\r\n });\r\n \r\n this.clearAuth();\r\n console.log('[API] Account deleted successfully');\r\n return response;\r\n }\r\n\r\n async logout(): Promise<void> {\r\n console.log('[API] Logging out');\r\n \r\n if (this.refreshToken) {\r\n try {\r\n await this.request('/api/auth/logout', {\r\n method: 'POST',\r\n body: JSON.stringify({ refresh_token: this.refreshToken }),\r\n });\r\n console.log('[API] Refresh token revoked on server');\r\n } catch (error) {\r\n console.warn('[API] Failed to revoke refresh token on server:', error);\r\n }\r\n }\r\n \r\n this.clearAuth();\r\n }\r\n\r\n async logoutAllDevices(): Promise<void> {\r\n console.log('[API] Logging out from all devices');\r\n \r\n try {\r\n await this.request('/api/auth/logout-all', { method: 'POST' });\r\n console.log('[API] All sessions revoked on server');\r\n } catch (error) {\r\n console.warn('[API] Failed to revoke all sessions:', error);\r\n }\r\n \r\n this.clearAuth();\r\n }\r\n\r\n protected clearAuth(): void {\r\n if (this.proactiveRefreshTimer) {\r\n clearTimeout(this.proactiveRefreshTimer);\r\n this.proactiveRefreshTimer = null;\r\n }\r\n \r\n this.token = null;\r\n this.refreshToken = null;\r\n localStorage.removeItem('auth_token');\r\n localStorage.removeItem('refresh_token');\r\n localStorage.removeItem('user_data');\r\n console.log('[API] Auth cache cleared');\r\n \r\n window.dispatchEvent(new CustomEvent('auth:cleared'));\r\n }\r\n\r\n // ========================================================================\r\n // API KEY MANAGEMENT\r\n // ========================================================================\r\n\r\n async listApiKeys(): Promise<{ api_keys: ApiKey[]; total: number }> {\r\n console.log('[API] Listing user API keys');\r\n return this.request('/api/auth/api-keys', { method: 'GET' });\r\n }\r\n\r\n async createApiKey(data: {\r\n name: string;\r\n scopes?: string;\r\n expires_in_days?: number;\r\n rate_limit_per_minute?: number;\r\n }): Promise<ApiKeyCreateResponse> {\r\n console.log(`[API] Creating API key: ${data.name}`);\r\n return this.request('/api/auth/api-keys', {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n name: data.name,\r\n scopes: data.scopes || 'read,write',\r\n expires_in_days: data.expires_in_days,\r\n rate_limit_per_minute: data.rate_limit_per_minute || 60\r\n }),\r\n });\r\n }\r\n\r\n async revokeApiKey(keyId: string): Promise<{ message: string; key_prefix: string; revoked_at: string }> {\r\n console.log(`[API] Revoking API key: ${keyId}`);\r\n return this.request(`/api/auth/api-keys/${keyId}`, { method: 'DELETE' });\r\n }\r\n}\r\n\r\n// ========================================================================\r\n// FACTORY FUNCTION (RECOMMENDED)\r\n// ========================================================================\r\n// Use this instead of extending BaseApi. Cleaner, no inheritance.\r\n//\r\n// Usage:\r\n// import { createAuthApi } from '@rationalbloks/frontblok-auth';\r\n// export const authApi = createAuthApi(import.meta.env.VITE_API_URL);\r\n//\r\n// // Then use directly:\r\n// authApi.login(email, password);\r\n// authApi.logout();\r\n// authApi.listApiKeys();\r\n\r\n/**\r\n * Creates an auth API client instance.\r\n * Preferred over class inheritance - simpler and cleaner.\r\n * \r\n * @param apiBaseUrl - The backend API base URL\r\n * @returns A BaseApi instance with all auth methods\r\n */\r\nexport function createAuthApi(apiBaseUrl: string): BaseApi {\r\n return new BaseApi(apiBaseUrl);\r\n}\r\n\r\n// ========================================================================\r\n// STORAGE UTILITIES\r\n// ========================================================================\r\n\r\nexport const getStoredUser = (): User | null => {\r\n const userData = localStorage.getItem('user_data');\r\n if (!userData) return null;\r\n \r\n try {\r\n return JSON.parse(userData);\r\n } catch (error) {\r\n console.error('[API] Invalid user_data in localStorage:', error);\r\n localStorage.removeItem('user_data');\r\n return null;\r\n }\r\n};\r\n\r\nexport const getStoredToken = (): string | null => {\r\n return localStorage.getItem('auth_token');\r\n};\r\n\r\nexport const isAuthenticated = (): boolean => {\r\n return !!getStoredToken();\r\n};\r\n","// ========================================================================\r\n// API URL FACTORY\r\n// ========================================================================\r\n// Universal API URL resolution for RationalBloks apps.\r\n//\r\n// In production, the URL is injected via Docker build args (VITE_DATABASE_API_URL).\r\n// In development, falls back to localhost:8000.\r\n//\r\n// This function lives in the package so URL resolution logic is consistent\r\n// across ALL customer apps and can be updated via npm — eliminating the\r\n// entire class of \"localhost baked into production\" bugs.\r\n//\r\n// Usage:\r\n// import { createApiUrl } from '@rationalbloks/frontblok-auth';\r\n// const authApi = createAuthApi(createApiUrl());\r\n// ========================================================================\r\n\r\n/**\r\n * Options for createApiUrl.\r\n */\r\nexport interface CreateApiUrlOptions {\r\n /** \r\n * Override the env variable name. Default: 'VITE_DATABASE_API_URL'.\r\n * This is read from import.meta.env at call time.\r\n */\r\n envVar?: string;\r\n \r\n /**\r\n * Fallback URL for local development (only used when import.meta.env.DEV is true).\r\n * Default: 'http://localhost:8000'\r\n */\r\n devFallback?: string;\r\n}\r\n\r\n/**\r\n * Resolves the backend API URL for the current environment.\r\n * \r\n * Resolution order:\r\n * 1. `import.meta.env.VITE_DATABASE_API_URL` (injected at build time)\r\n * 2. `import.meta.env.VITE_API_URL` (alternative env var name)\r\n * 3. In development mode only: devFallback (default: 'http://localhost:8000')\r\n * 4. In production: empty string (will cause API calls to fail loudly)\r\n * \r\n * @param options - Configuration options\r\n * @returns The resolved API base URL\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createApiUrl, createAuthApi } from '@rationalbloks/frontblok-auth';\r\n * \r\n * // Simple usage\r\n * const authApi = createAuthApi(createApiUrl());\r\n * \r\n * // Custom dev fallback\r\n * const authApi = createAuthApi(createApiUrl({ devFallback: 'http://localhost:3000' }));\r\n * ```\r\n */\r\nexport function createApiUrl(options: CreateApiUrlOptions = {}): string {\r\n const { devFallback = 'http://localhost:8000' } = options;\r\n \r\n // Try the standard env var first\r\n const fromEnv = import.meta.env.VITE_DATABASE_API_URL || import.meta.env.VITE_API_URL;\r\n \r\n if (fromEnv) {\r\n return fromEnv;\r\n }\r\n \r\n // In development, use the fallback\r\n if (import.meta.env.DEV) {\r\n console.log(`[RationalBloks] Using dev fallback API URL: ${devFallback}`);\r\n return devFallback;\r\n }\r\n \r\n // In production without env var — this is a build config error\r\n console.error(\r\n '[RationalBloks] VITE_DATABASE_API_URL is not set in production. ' +\r\n 'API calls will fail. Ensure the Docker build passes --build-arg VITE_DATABASE_API_URL.'\r\n );\r\n return '';\r\n}\r\n","// ========================================================================\r\n// AUTH CONTEXT\r\n// Universal authentication state management\r\n// ========================================================================\r\n// It provides:\r\n// - Authentication state (user, isLoading, isAuthenticated, error)\r\n// - Authentication actions (login, register, logout, clearError, refreshUser)\r\n// - React Context pattern for app-wide auth state\r\n// - Token expiration handling via 'auth:cleared' event\r\n// ========================================================================\r\n\r\nimport React, { createContext, useContext, useState, useEffect } from 'react';\r\nimport { getStoredUser, getStoredToken } from '../api';\r\nimport type { User } from '../api';\r\nimport type { BaseApi } from '../api/client';\r\n\r\n// ========================================================================\r\n// TYPES\r\n// ========================================================================\r\n\r\nexport interface AuthState {\r\n user: User | null;\r\n isLoading: boolean;\r\n isAuthenticated: boolean;\r\n error: string | null;\r\n}\r\n\r\nexport interface AuthActions {\r\n login: (email: string, password: string) => Promise<boolean>;\r\n register: (email: string, password: string, firstName: string, lastName: string) => Promise<boolean>;\r\n logout: () => void;\r\n clearError: () => void;\r\n refreshUser: () => Promise<void>;\r\n}\r\n\r\nexport type AuthContextType = AuthState & AuthActions;\r\n\r\n// ========================================================================\r\n// CONTEXT\r\n// ========================================================================\r\n\r\nconst AuthContext = createContext<AuthContextType | undefined>(undefined);\r\n\r\n/**\r\n * Hook to access authentication state and actions.\r\n * Must be used within an AuthProvider.\r\n */\r\nexport const useAuth = () => {\r\n const context = useContext(AuthContext);\r\n if (!context) {\r\n throw new Error('useAuth must be used within AuthProvider');\r\n }\r\n return context;\r\n};\r\n\r\n// ========================================================================\r\n// PROVIDER FACTORY\r\n// ========================================================================\r\n\r\n/**\r\n * Creates an AuthProvider component that uses the specified API instance.\r\n * This allows the universal auth context to work with any API that extends BaseApi.\r\n * \r\n * @example\r\n * ```typescript\r\n * // In your app's auth setup:\r\n * import { createAuthProvider } from '@/core/auth';\r\n * import { myAppApi } from '@/services/myAppApi';\r\n * \r\n * export const MyAppAuthProvider = createAuthProvider(myAppApi);\r\n * ```\r\n */\r\nexport function createAuthProvider(api: BaseApi) {\r\n const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {\r\n const [state, setState] = useState<AuthState>({\r\n user: getStoredUser(),\r\n isLoading: false,\r\n isAuthenticated: !!getStoredToken(),\r\n error: null,\r\n });\r\n\r\n // Listen for auth:cleared event (token expiration, session invalidation)\r\n useEffect(() => {\r\n const handleAuthCleared = () => {\r\n console.log('[Auth] Session expired or invalidated - clearing auth state');\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n };\r\n\r\n window.addEventListener('auth:cleared', handleAuthCleared);\r\n return () => window.removeEventListener('auth:cleared', handleAuthCleared);\r\n }, []);\r\n\r\n // Refresh user data from backend on mount if authenticated\r\n useEffect(() => {\r\n const refreshUserData = async () => {\r\n const token = getStoredToken();\r\n if (token) {\r\n try {\r\n const currentUser = await api.getCurrentUser();\r\n setState(prev => ({\r\n ...prev,\r\n user: currentUser,\r\n isAuthenticated: true,\r\n }));\r\n } catch (error: unknown) {\r\n console.error('[Auth] Failed to refresh user data:', error);\r\n const httpError = error as { status?: number };\r\n if (httpError?.status === 401 || httpError?.status === 403) {\r\n console.warn('[Auth] Auth failed - clearing local state only');\r\n localStorage.removeItem('auth_token');\r\n localStorage.removeItem('user_data');\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n } else {\r\n console.warn('[Auth] Keeping user logged in with cached data');\r\n }\r\n }\r\n }\r\n };\r\n\r\n refreshUserData();\r\n }, []);\r\n\r\n const login = async (email: string, password: string): Promise<boolean> => {\r\n setState(prev => ({ ...prev, isLoading: true, error: null }));\r\n \r\n try {\r\n const result = await api.login(email, password);\r\n setState(prev => ({\r\n ...prev,\r\n user: result.user,\r\n isAuthenticated: true,\r\n isLoading: false,\r\n }));\r\n return true;\r\n } catch (error) {\r\n setState(prev => ({\r\n ...prev,\r\n error: error instanceof Error ? error.message : 'Login failed',\r\n isLoading: false,\r\n }));\r\n return false;\r\n }\r\n };\r\n\r\n const register = async (email: string, password: string, firstName: string, lastName: string): Promise<boolean> => {\r\n setState(prev => ({ ...prev, isLoading: true, error: null }));\r\n \r\n try {\r\n const result = await api.register(email, password, firstName, lastName);\r\n setState(prev => ({\r\n ...prev,\r\n user: result.user,\r\n isAuthenticated: true,\r\n isLoading: false,\r\n }));\r\n return true;\r\n } catch (error) {\r\n setState(prev => ({\r\n ...prev,\r\n error: error instanceof Error ? error.message : 'Registration failed',\r\n isLoading: false,\r\n }));\r\n return false;\r\n }\r\n };\r\n\r\n const logout = () => {\r\n api.logout();\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n };\r\n\r\n const clearError = () => {\r\n setState(prev => ({ ...prev, error: null }));\r\n };\r\n\r\n const refreshUser = async () => {\r\n try {\r\n const currentUser = await api.getCurrentUser();\r\n setState(prev => ({\r\n ...prev,\r\n user: currentUser,\r\n }));\r\n } catch (error) {\r\n console.error('[Auth] Failed to refresh user:', error);\r\n }\r\n };\r\n\r\n return (\r\n <AuthContext.Provider\r\n value={{\r\n ...state,\r\n login,\r\n register,\r\n logout,\r\n clearError,\r\n refreshUser,\r\n }}\r\n >\r\n {children}\r\n </AuthContext.Provider>\r\n );\r\n };\r\n\r\n return AuthProvider;\r\n}\r\n","// ========================================================================\r\n// CORE AUTH - App Provider Factory\r\n// ========================================================================\r\n// Creates a wrapped app component with OAuth providers configured.\r\n// This is the universal entry point for React apps with authentication.\r\n//\r\n// Usage:\r\n// import { createAppRoot } from './core/auth';\r\n// import App from './App';\r\n// createAppRoot(App, { googleClientId: '...' });\r\n// ========================================================================\r\n\r\nimport React, { StrictMode } from 'react';\r\nimport { createRoot } from 'react-dom/client';\r\nimport { GoogleOAuthProvider } from '@react-oauth/google';\r\n\r\nexport interface AuthConfig {\r\n /** Google OAuth Client ID (optional - only needed if using Google Sign-In) */\r\n googleClientId?: string;\r\n}\r\n\r\n/**\r\n * Creates and renders the app root with authentication providers.\r\n * This is the universal way to bootstrap a React app with auth.\r\n */\r\nexport function createAppRoot(\r\n App: React.ComponentType,\r\n config: AuthConfig = {}\r\n): void {\r\n const { googleClientId = '' } = config;\r\n\r\n const root = createRoot(document.getElementById('root')!);\r\n\r\n // Build the component tree with OAuth provider\r\n // Always wrap with GoogleOAuthProvider to prevent crashes in AuthView\r\n // When clientId is empty, Google Sign-In just won't work (graceful degradation)\r\n const appElement = (\r\n <GoogleOAuthProvider clientId={googleClientId}>\r\n <App />\r\n </GoogleOAuthProvider>\r\n );\r\n\r\n // Render with StrictMode\r\n root.render(\r\n <StrictMode>\r\n {appElement}\r\n </StrictMode>\r\n );\r\n}\r\n","// ========================================================================\r\n// PROTECTED ROUTE\r\n// ========================================================================\r\n// Universal auth-guarded route wrapper for React Router.\r\n//\r\n// This component is identical in every RationalBloks app, so it lives\r\n// in the auth package to be updated via npm.\r\n//\r\n// Usage:\r\n// import { ProtectedRoute } from '@rationalbloks/frontblok-auth';\r\n// \r\n// <Route path=\"/dashboard\" element={\r\n// <ProtectedRoute useAuth={useClientAuth}>\r\n// <DashboardView />\r\n// </ProtectedRoute>\r\n// } />\r\n//\r\n// // Or with a custom redirect:\r\n// <ProtectedRoute useAuth={useClientAuth} redirectTo=\"/login\">\r\n// ========================================================================\r\n\r\nimport React from 'react';\r\nimport { Navigate } from 'react-router-dom';\r\n\r\nexport interface ProtectedRouteProps {\r\n children: React.ReactNode;\r\n /** \r\n * The useAuth hook from your app's datablokApi.ts (useClientAuth).\r\n * We accept this as a prop instead of importing it directly to avoid\r\n * circular dependencies and keep the package decoupled.\r\n */\r\n useAuth: () => { isAuthenticated: boolean };\r\n /**\r\n * Where to redirect unauthenticated users. Default: '/auth'\r\n */\r\n redirectTo?: string;\r\n}\r\n\r\n/**\r\n * Route wrapper that redirects to the auth page if the user is not authenticated.\r\n */\r\nexport const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ \r\n children, \r\n useAuth,\r\n redirectTo = '/auth' \r\n}) => {\r\n const { isAuthenticated } = useAuth();\r\n \r\n if (!isAuthenticated) {\r\n return <Navigate to={redirectTo} replace />;\r\n }\r\n \r\n return <>{children}</>;\r\n};\r\n"],"names":["createContext","useContext","useState","useEffect","jsx","createRoot","GoogleOAuthProvider","StrictMode","useAuth","isAuthenticated","Navigate"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { BaseApi, createAuthApi, getStoredUser, getStoredToken, isAuthenticated, generateOAuthNonce } from './api';
|
|
1
|
+
export { BaseApi, createAuthApi, createApiUrl, getStoredUser, getStoredToken, isAuthenticated, generateOAuthNonce } from './api';
|
|
2
|
+
export type { CreateApiUrlOptions } from './api';
|
|
2
3
|
export type { User, ApiKey, ApiKeyCreateResponse, AuthResponse, GoogleOAuthResponse, PasswordResetRequestResponse, PasswordResetResponse, EmailVerificationResponse, SetPasswordResponse } from './api';
|
|
3
|
-
export { useAuth, createAuthProvider, createAppRoot } from './auth';
|
|
4
|
-
export type { AuthState, AuthActions, AuthContextType, AuthConfig } from './auth';
|
|
4
|
+
export { useAuth, createAuthProvider, createAppRoot, ProtectedRoute } from './auth';
|
|
5
|
+
export type { AuthState, AuthActions, AuthContextType, AuthConfig, ProtectedRouteProps } from './auth';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext, useState, useEffect, StrictMode } from "react";
|
|
3
3
|
import { createRoot } from "react-dom/client";
|
|
4
4
|
import { GoogleOAuthProvider } from "@react-oauth/google";
|
|
5
|
+
import { Navigate } from "react-router-dom";
|
|
5
6
|
const generateOAuthNonce = () => {
|
|
6
7
|
const array = new Uint8Array(32);
|
|
7
8
|
crypto.getRandomValues(array);
|
|
@@ -363,7 +364,7 @@ class BaseApi {
|
|
|
363
364
|
// ========================================================================
|
|
364
365
|
/**
|
|
365
366
|
* Internal: Authenticate with Google OAuth.
|
|
366
|
-
* Use googleLogin() instead
|
|
367
|
+
* Use googleLogin() instead.
|
|
367
368
|
*/
|
|
368
369
|
async _googleOAuthLogin(credential, nonce) {
|
|
369
370
|
console.log("[API] Google OAuth login attempt");
|
|
@@ -383,17 +384,17 @@ class BaseApi {
|
|
|
383
384
|
return data;
|
|
384
385
|
}
|
|
385
386
|
/**
|
|
386
|
-
*
|
|
387
|
+
* Simplified Google OAuth login.
|
|
387
388
|
*
|
|
388
|
-
* This is the
|
|
389
|
-
* OAuth flow in
|
|
389
|
+
* This is the default method for Google Sign-In. It handles the complete
|
|
390
|
+
* OAuth flow in a single call:
|
|
390
391
|
*
|
|
391
392
|
* 1. Retrieve nonce from sessionStorage (must exist from generateOAuthNonce())
|
|
392
393
|
* 2. Send credential + nonce to backend for verification
|
|
393
394
|
* 3. Store JWT tokens on success
|
|
394
395
|
* 4. Clear nonce to prevent replay attacks
|
|
395
396
|
*
|
|
396
|
-
* If
|
|
397
|
+
* If any step fails, the entire operation fails cleanly. No partial states.
|
|
397
398
|
*
|
|
398
399
|
* @param credential - The Google ID token (JWT from GoogleLogin onSuccess)
|
|
399
400
|
* @returns GoogleOAuthResponse with tokens, user data, and is_new_user flag
|
|
@@ -412,7 +413,7 @@ class BaseApi {
|
|
|
412
413
|
* ```
|
|
413
414
|
*/
|
|
414
415
|
async googleLogin(credential) {
|
|
415
|
-
console.log("[API] Google OAuth login
|
|
416
|
+
console.log("[API] Google OAuth login");
|
|
416
417
|
const nonce = getOAuthNonce();
|
|
417
418
|
if (!nonce) {
|
|
418
419
|
throw new Error("Security validation failed: OAuth nonce not found. Call generateOAuthNonce() before Google Sign-In.");
|
|
@@ -598,6 +599,13 @@ const getStoredToken = () => {
|
|
|
598
599
|
const isAuthenticated = () => {
|
|
599
600
|
return !!getStoredToken();
|
|
600
601
|
};
|
|
602
|
+
function createApiUrl(options = {}) {
|
|
603
|
+
const { devFallback = "http://localhost:8000" } = options;
|
|
604
|
+
console.error(
|
|
605
|
+
"[RationalBloks] VITE_DATABASE_API_URL is not set in production. API calls will fail. Ensure the Docker build passes --build-arg VITE_DATABASE_API_URL."
|
|
606
|
+
);
|
|
607
|
+
return "";
|
|
608
|
+
}
|
|
601
609
|
const AuthContext = createContext(void 0);
|
|
602
610
|
const useAuth = () => {
|
|
603
611
|
const context = useContext(AuthContext);
|
|
@@ -740,18 +748,28 @@ function createAuthProvider(api) {
|
|
|
740
748
|
return AuthProvider;
|
|
741
749
|
}
|
|
742
750
|
function createAppRoot(App, config = {}) {
|
|
743
|
-
const { googleClientId } = config;
|
|
751
|
+
const { googleClientId = "" } = config;
|
|
744
752
|
const root = createRoot(document.getElementById("root"));
|
|
745
|
-
|
|
746
|
-
if (googleClientId) {
|
|
747
|
-
appElement = /* @__PURE__ */ jsx(GoogleOAuthProvider, { clientId: googleClientId, children: appElement });
|
|
748
|
-
}
|
|
753
|
+
const appElement = /* @__PURE__ */ jsx(GoogleOAuthProvider, { clientId: googleClientId, children: /* @__PURE__ */ jsx(App, {}) });
|
|
749
754
|
root.render(
|
|
750
755
|
/* @__PURE__ */ jsx(StrictMode, { children: appElement })
|
|
751
756
|
);
|
|
752
757
|
}
|
|
758
|
+
const ProtectedRoute = ({
|
|
759
|
+
children,
|
|
760
|
+
useAuth: useAuth2,
|
|
761
|
+
redirectTo = "/auth"
|
|
762
|
+
}) => {
|
|
763
|
+
const { isAuthenticated: isAuthenticated2 } = useAuth2();
|
|
764
|
+
if (!isAuthenticated2) {
|
|
765
|
+
return /* @__PURE__ */ jsx(Navigate, { to: redirectTo, replace: true });
|
|
766
|
+
}
|
|
767
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
768
|
+
};
|
|
753
769
|
export {
|
|
754
770
|
BaseApi,
|
|
771
|
+
ProtectedRoute,
|
|
772
|
+
createApiUrl,
|
|
755
773
|
createAppRoot,
|
|
756
774
|
createAuthApi,
|
|
757
775
|
createAuthProvider,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/api/client.ts","../src/auth/AuthContext.tsx","../src/auth/AppProvider.tsx"],"sourcesContent":["// ========================================================================\r\n// BASE API CLIENT\r\n// Universal HTTP client with JWT token management\r\n// ========================================================================\r\n// This file is part of the UNIVERSAL BOILERPLATE.\r\n// Copy as-is when creating new applications.\r\n//\r\n// Features:\r\n// - JWT token storage and automatic refresh\r\n// - Cross-tab synchronization via localStorage events\r\n// - Proactive token refresh before expiration\r\n// - Automatic 401 retry with token refresh\r\n// - Rate limiting (429) retry with exponential backoff\r\n// - Authentication methods (login, register, logout)\r\n// - API key management for external integrations\r\n//\r\n// Usage:\r\n// 1. Extend this class for your application's API\r\n// 2. Add your domain-specific CRUD methods in the subclass\r\n// 3. Call super() in constructor with your API base URL\r\n//\r\n// ========================================================================\r\n\r\nimport type { \r\n User, \r\n ApiKey, \r\n ApiKeyCreateResponse, \r\n AuthResponse,\r\n GoogleOAuthResponse,\r\n PasswordResetRequestResponse,\r\n PasswordResetResponse,\r\n EmailVerificationResponse,\r\n SetPasswordResponse\r\n} from './types';\r\n\r\n// ========================================================================\r\n// OAUTH UTILITIES (defined before class so they can be used within it)\r\n// ========================================================================\r\n\r\n/**\r\n * Generate a cryptographically secure nonce for OAuth CSRF protection.\r\n * \r\n * USAGE:\r\n * 1. Call generateOAuthNonce() in component useState (runs once on mount)\r\n * 2. Pass the nonce to GoogleLogin component via the 'nonce' prop\r\n * 3. On success, call authApi.googleLogin(credential) - it handles the rest\r\n * \r\n * @example\r\n * ```typescript\r\n * const [nonce] = useState(() => generateOAuthNonce());\r\n * // Pass to GoogleLogin: <GoogleLogin nonce={nonce} onSuccess={...} />\r\n * // On success: await authApi.googleLogin(credential);\r\n * ```\r\n * \r\n * @returns The generated nonce string\r\n */\r\nexport const generateOAuthNonce = (): string => {\r\n const array = new Uint8Array(32);\r\n crypto.getRandomValues(array);\r\n const nonce = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\r\n sessionStorage.setItem('google_oauth_nonce', nonce);\r\n console.log('[OAuth] Generated nonce for CSRF protection');\r\n return nonce;\r\n};\r\n\r\n/**\r\n * Retrieve the stored OAuth nonce.\r\n * @returns The stored nonce or null if not found\r\n */\r\nexport const getOAuthNonce = (): string | null => {\r\n return sessionStorage.getItem('google_oauth_nonce');\r\n};\r\n\r\n/**\r\n * Clear the stored OAuth nonce (call after successful login).\r\n */\r\nexport const clearOAuthNonce = (): void => {\r\n sessionStorage.removeItem('google_oauth_nonce');\r\n console.log('[OAuth] Cleared nonce');\r\n};\r\n\r\n// ========================================================================\r\n// BASE API CLASS\r\n// ========================================================================\r\n\r\n/**\r\n * BaseApi - Universal HTTP client with authentication.\r\n * \r\n * Extend this class to add your application-specific API methods.\r\n * \r\n * @example\r\n * ```typescript\r\n * class MyAppApi extends BaseApi {\r\n * constructor() {\r\n * super(import.meta.env.VITE_API_URL || 'https://api.myapp.com');\r\n * }\r\n * \r\n * async getProducts() {\r\n * return this.request<Product[]>('/api/products/');\r\n * }\r\n * }\r\n * ```\r\n */\r\nexport class BaseApi {\r\n protected token: string | null = null;\r\n protected refreshToken: string | null = null;\r\n protected isRefreshing: boolean = false;\r\n protected refreshPromise: Promise<boolean> | null = null;\r\n protected proactiveRefreshTimer: ReturnType<typeof setTimeout> | null = null;\r\n protected refreshCheckInterval: ReturnType<typeof setInterval> | null = null;\r\n protected tokenRefreshPromise: Promise<void> | null = null;\r\n protected readonly apiBaseUrl: string;\r\n\r\n constructor(apiBaseUrl: string) {\r\n this.apiBaseUrl = apiBaseUrl;\r\n this.loadTokens();\r\n this.setupVisibilityHandler();\r\n this.setupStorageListener();\r\n this.startRefreshCheckInterval();\r\n \r\n console.log(`[API] Base URL: ${this.apiBaseUrl}`);\r\n console.log(`[API] Environment: ${import.meta.env.DEV ? 'Development' : 'Production'}`);\r\n }\r\n\r\n // ========================================================================\r\n // TOKEN MANAGEMENT\r\n // ========================================================================\r\n\r\n /**\r\n * Sync tokens across browser tabs when localStorage changes.\r\n * Prevents \"stale refresh token\" issue where Tab A rotates the token\r\n * but Tab B still has the old (now invalid) refresh token in memory.\r\n */\r\n private setupStorageListener(): void {\r\n if (typeof window !== 'undefined') {\r\n window.addEventListener('storage', (event) => {\r\n if (event.key === 'auth_token') {\r\n console.log('[API] Token updated in another tab, syncing...');\r\n this.token = event.newValue;\r\n } else if (event.key === 'refresh_token') {\r\n console.log('[API] Refresh token updated in another tab, syncing...');\r\n this.refreshToken = event.newValue;\r\n } else if (event.key === null) {\r\n // Storage was cleared (logout in another tab)\r\n console.log('[API] Storage cleared in another tab, syncing logout...');\r\n this.token = null;\r\n this.refreshToken = null;\r\n window.dispatchEvent(new CustomEvent('auth:cleared'));\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Start interval-based token check (runs every 60 seconds).\r\n * More reliable than setTimeout which browsers throttle in background tabs.\r\n */\r\n private startRefreshCheckInterval(): void {\r\n if (this.refreshCheckInterval) {\r\n clearInterval(this.refreshCheckInterval);\r\n }\r\n \r\n this.refreshCheckInterval = setInterval(() => {\r\n if (this.token) {\r\n this.checkAndRefreshToken();\r\n }\r\n }, 60 * 1000);\r\n }\r\n\r\n /**\r\n * Handle tab visibility changes - check token when user returns to tab.\r\n */\r\n private setupVisibilityHandler(): void {\r\n if (typeof document !== 'undefined') {\r\n document.addEventListener('visibilitychange', () => {\r\n if (document.visibilityState === 'visible' && this.token) {\r\n console.log('[API] Tab became visible, checking token validity...');\r\n this.checkAndRefreshToken();\r\n }\r\n });\r\n \r\n window.addEventListener('focus', () => {\r\n if (this.token) {\r\n console.log('[API] Window focused, checking token validity...');\r\n this.checkAndRefreshToken();\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Check token expiry and refresh if needed.\r\n */\r\n protected async checkAndRefreshToken(): Promise<void> {\r\n if (!this.token) return;\r\n\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (!expiry) return;\r\n\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n const refreshBuffer = 3 * 60 * 1000; // 3 minutes buffer\r\n\r\n if (timeUntilExpiry <= 0) {\r\n console.log('[API] Token expired, refreshing immediately...');\r\n await this.refreshAccessToken();\r\n } else if (timeUntilExpiry <= refreshBuffer) {\r\n console.log(`[API] Token expires in ${Math.round(timeUntilExpiry / 1000)}s, refreshing proactively...`);\r\n await this.refreshAccessToken();\r\n }\r\n }\r\n\r\n /**\r\n * Parse JWT to get expiration time.\r\n */\r\n protected getTokenExpiry(token: string): number | null {\r\n try {\r\n const payload = JSON.parse(atob(token.split('.')[1]));\r\n return payload.exp ? payload.exp * 1000 : null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Schedule proactive refresh 5 minutes before token expires.\r\n */\r\n protected scheduleProactiveRefresh(): void {\r\n if (this.proactiveRefreshTimer) {\r\n clearTimeout(this.proactiveRefreshTimer);\r\n this.proactiveRefreshTimer = null;\r\n }\r\n\r\n if (!this.token) return;\r\n\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (!expiry) return;\r\n\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n const refreshBuffer = 5 * 60 * 1000;\r\n\r\n if (timeUntilExpiry <= 0) {\r\n console.log('[API] Token already expired, refreshing immediately...');\r\n this.refreshAccessToken();\r\n return;\r\n }\r\n\r\n if (timeUntilExpiry <= refreshBuffer) {\r\n console.log('[API] Token expiring soon, refreshing immediately...');\r\n this.refreshAccessToken();\r\n return;\r\n }\r\n\r\n const refreshIn = timeUntilExpiry - refreshBuffer;\r\n console.log(`[API] Scheduling proactive token refresh in ${Math.round(refreshIn / 60000)} minutes`);\r\n \r\n this.proactiveRefreshTimer = setTimeout(async () => {\r\n console.log('[API] Proactive token refresh triggered');\r\n await this.refreshAccessToken();\r\n }, refreshIn);\r\n }\r\n\r\n /**\r\n * Load tokens from localStorage on initialization.\r\n */\r\n protected loadTokens(): void {\r\n const storedToken = localStorage.getItem('auth_token');\r\n const storedRefreshToken = localStorage.getItem('refresh_token');\r\n \r\n if (storedToken) {\r\n this.token = storedToken;\r\n console.log('[API] Access token loaded from localStorage');\r\n }\r\n if (storedRefreshToken) {\r\n this.refreshToken = storedRefreshToken;\r\n console.log('[API] Refresh token loaded from localStorage');\r\n }\r\n \r\n if (this.token) {\r\n this.tokenRefreshPromise = this.checkAndRefreshToken();\r\n this.scheduleProactiveRefresh();\r\n }\r\n }\r\n\r\n /**\r\n * Wait for any pending token refresh to complete before making API calls.\r\n */\r\n async ensureTokenReady(): Promise<void> {\r\n if (this.tokenRefreshPromise) {\r\n await this.tokenRefreshPromise;\r\n this.tokenRefreshPromise = null;\r\n }\r\n }\r\n\r\n /**\r\n * Refresh the access token using the refresh token.\r\n */\r\n async refreshAccessToken(): Promise<boolean> {\r\n if (this.isRefreshing) {\r\n return this.refreshPromise || Promise.resolve(false);\r\n }\r\n\r\n // Sync from localStorage before refresh (another tab may have rotated)\r\n const latestRefreshToken = localStorage.getItem('refresh_token');\r\n if (latestRefreshToken && latestRefreshToken !== this.refreshToken) {\r\n console.log('[API] Syncing refresh token from localStorage');\r\n this.refreshToken = latestRefreshToken;\r\n }\r\n\r\n if (!this.refreshToken) {\r\n console.warn('[API] No refresh token available');\r\n return false;\r\n }\r\n\r\n this.isRefreshing = true;\r\n this.refreshPromise = (async () => {\r\n try {\r\n console.log('[API] Refreshing access token...');\r\n const response = await fetch(`${this.apiBaseUrl}/api/auth/refresh`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ refresh_token: this.refreshToken }),\r\n });\r\n\r\n if (!response.ok) {\r\n console.error('[API] Token refresh failed:', response.status);\r\n if (response.status === 401 || response.status === 403) {\r\n console.log('[API] Refresh token is invalid, logging out...');\r\n this.clearAuth();\r\n } else {\r\n console.warn(`[API] Refresh failed with ${response.status}, will retry on next interval`);\r\n }\r\n return false;\r\n }\r\n\r\n const data = await response.json();\r\n \r\n this.token = data.access_token;\r\n localStorage.setItem('auth_token', data.access_token);\r\n \r\n if (data.refresh_token) {\r\n this.refreshToken = data.refresh_token;\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n console.log('[API] Refresh token rotated');\r\n }\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Access token refreshed successfully');\r\n return true;\r\n } catch (error) {\r\n console.error('[API] Token refresh network error:', error);\r\n console.warn('[API] Will retry refresh on next interval');\r\n return false;\r\n } finally {\r\n this.isRefreshing = false;\r\n this.refreshPromise = null;\r\n }\r\n })();\r\n\r\n return this.refreshPromise;\r\n }\r\n\r\n // ========================================================================\r\n // HTTP REQUEST METHOD\r\n // ========================================================================\r\n\r\n /**\r\n * Make an authenticated HTTP request.\r\n * Handles token refresh, 401 retry, and rate limiting automatically.\r\n * \r\n * This method is public so apps can make custom API calls without extending BaseApi.\r\n * \r\n * @example\r\n * const authApi = createAuthApi(API_URL);\r\n * const data = await authApi.request<MyType>('/api/my-endpoint/', { method: 'POST', body: JSON.stringify(payload) });\r\n */\r\n async request<T>(endpoint: string, options: RequestInit = {}, isRetry = false, retryCount = 0): Promise<T> {\r\n await this.ensureTokenReady();\r\n \r\n // Proactive token refresh before request\r\n if (this.token && !isRetry && !endpoint.includes('/auth/')) {\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (expiry) {\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n if (timeUntilExpiry < 2 * 60 * 1000) {\r\n console.log(`[API] Token expires in ${Math.round(timeUntilExpiry/1000)}s, refreshing before request...`);\r\n await this.refreshAccessToken();\r\n }\r\n }\r\n }\r\n \r\n const url = `${this.apiBaseUrl}${endpoint}`;\r\n \r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n ...(options.headers as Record<string, string>),\r\n };\r\n\r\n if (this.token) {\r\n headers.Authorization = `Bearer ${this.token}`;\r\n }\r\n\r\n console.log(`[API Request] ${options.method || 'GET'} ${url}`);\r\n \r\n try {\r\n const response = await fetch(url, { ...options, headers });\r\n \r\n console.log(`[API Response] ${response.status} ${response.statusText}`);\r\n \r\n if (!response.ok) {\r\n const error = await response.text();\r\n console.error(`[API Error] ${response.status}: ${error}`);\r\n \r\n // Rate limiting retry\r\n if (response.status === 429 && retryCount < 3) {\r\n const backoffMs = Math.pow(2, retryCount) * 1000;\r\n console.log(`[API] Rate limited (429), retrying in ${backoffMs}ms (attempt ${retryCount + 1}/3)...`);\r\n await new Promise(resolve => setTimeout(resolve, backoffMs));\r\n return this.request<T>(endpoint, options, isRetry, retryCount + 1);\r\n }\r\n \r\n // 401 retry with token refresh\r\n if (response.status === 401 && !isRetry && !endpoint.includes('/auth/login') && !endpoint.includes('/auth/register') && !endpoint.includes('/auth/refresh')) {\r\n console.log('[API] Attempting token refresh...');\r\n const refreshed = await this.refreshAccessToken();\r\n if (refreshed) {\r\n return this.request<T>(endpoint, options, true, retryCount);\r\n }\r\n console.warn('[API] Token refresh failed, returning error to caller');\r\n }\r\n \r\n const apiError = new Error(`API Error: ${response.status} - ${error}`) as Error & { status: number };\r\n apiError.status = response.status;\r\n throw apiError;\r\n }\r\n\r\n return response.json();\r\n } catch (error) {\r\n console.error('[API Connection Error]:', error);\r\n if (error instanceof TypeError && error.message.includes('Failed to fetch')) {\r\n throw new Error(`Cannot connect to API: ${this.apiBaseUrl}`);\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n // ========================================================================\r\n // AUTHENTICATION METHODS\r\n // ========================================================================\r\n\r\n async login(email: string, password: string): Promise<AuthResponse> {\r\n console.log('[API] Login attempt:', email);\r\n \r\n const data = await this.request<AuthResponse>('/api/auth/login', {\r\n method: 'POST',\r\n body: JSON.stringify({ email, password }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Login successful');\r\n return data;\r\n }\r\n\r\n async register(email: string, password: string, firstName: string, lastName: string): Promise<AuthResponse> {\r\n console.log('[API] Registration attempt:', email);\r\n \r\n const data = await this.request<AuthResponse>('/api/auth/register', {\r\n method: 'POST',\r\n body: JSON.stringify({ \r\n email, \r\n password, \r\n first_name: firstName, \r\n last_name: lastName \r\n }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Registration successful');\r\n return data;\r\n }\r\n\r\n async getMe(): Promise<{ user: User }> {\r\n console.log('[API] Fetching current user');\r\n return this.request<{ user: User }>('/api/auth/me');\r\n }\r\n\r\n async getCurrentUser(): Promise<User> {\r\n console.log('[API] Fetching current user data');\r\n const response = await this.request<{ user: User }>('/api/auth/me');\r\n localStorage.setItem('user', JSON.stringify(response.user));\r\n return response.user;\r\n }\r\n\r\n // ========================================================================\r\n // GOOGLE OAUTH AUTHENTICATION\r\n // ========================================================================\r\n\r\n /**\r\n * Internal: Authenticate with Google OAuth.\r\n * Use googleLogin() instead - THE ONE WAY.\r\n */\r\n private async _googleOAuthLogin(credential: string, nonce: string): Promise<GoogleOAuthResponse> {\r\n console.log('[API] Google OAuth login attempt');\r\n \r\n const data = await this.request<GoogleOAuthResponse>('/api/auth/google/login', {\r\n method: 'POST',\r\n body: JSON.stringify({ credential, nonce }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log(`[API] Google OAuth successful (new_user: ${data.is_new_user})`);\r\n return data;\r\n }\r\n\r\n /**\r\n * THE ONE WAY: Simplified Google OAuth login.\r\n * \r\n * This is the DEFAULT method for Google Sign-In. It handles the complete\r\n * OAuth flow in one call following the Chain of Events mantra:\r\n * \r\n * 1. Retrieve nonce from sessionStorage (must exist from generateOAuthNonce())\r\n * 2. Send credential + nonce to backend for verification\r\n * 3. Store JWT tokens on success\r\n * 4. Clear nonce to prevent replay attacks\r\n * \r\n * If ANY step fails, the entire chain fails. No partial states.\r\n * \r\n * @param credential - The Google ID token (JWT from GoogleLogin onSuccess)\r\n * @returns GoogleOAuthResponse with tokens, user data, and is_new_user flag\r\n * @throws Error if nonce is missing or authentication fails\r\n * \r\n * @example\r\n * ```typescript\r\n * // In component: generate nonce ONCE on mount\r\n * const [nonce] = useState(() => generateOAuthNonce());\r\n * \r\n * // Pass nonce to GoogleLogin, then on success:\r\n * const handleSuccess = async (response: CredentialResponse) => {\r\n * const result = await authApi.googleLogin(response.credential!);\r\n * // Done! Tokens stored, nonce cleared, user authenticated.\r\n * };\r\n * ```\r\n */\r\n async googleLogin(credential: string): Promise<GoogleOAuthResponse> {\r\n console.log('[API] Google OAuth login (THE ONE WAY)');\r\n \r\n // Step 1: Retrieve nonce - MUST exist or we fail immediately\r\n const nonce = getOAuthNonce();\r\n if (!nonce) {\r\n throw new Error('Security validation failed: OAuth nonce not found. Call generateOAuthNonce() before Google Sign-In.');\r\n }\r\n \r\n // Step 2: Authenticate with backend\r\n const result = await this._googleOAuthLogin(credential, nonce);\r\n \r\n // Step 3: Clear nonce AFTER success to prevent replay attacks\r\n clearOAuthNonce();\r\n console.log('[API] OAuth nonce cleared (replay protection)');\r\n \r\n return result;\r\n }\r\n\r\n // ========================================================================\r\n // PASSWORD RESET FLOW\r\n // ========================================================================\r\n\r\n /**\r\n * Request a password reset email (forgot password flow).\r\n * \r\n * @param email - The email address to send reset link to\r\n * @returns Message confirming email was sent (or would be sent)\r\n */\r\n async requestPasswordReset(email: string): Promise<PasswordResetRequestResponse> {\r\n console.log('[API] Requesting password reset for:', email);\r\n \r\n return this.request<PasswordResetRequestResponse>('/api/auth/request-password-reset', {\r\n method: 'POST',\r\n body: JSON.stringify({ email }),\r\n });\r\n }\r\n\r\n /**\r\n * Reset password using token from email.\r\n * \r\n * @param token - The password reset token from the email link\r\n * @param newPassword - The new password (min 8 characters)\r\n * @returns Success message\r\n */\r\n async resetPassword(token: string, newPassword: string): Promise<PasswordResetResponse> {\r\n console.log('[API] Resetting password with token');\r\n \r\n return this.request<PasswordResetResponse>('/api/auth/reset-password', {\r\n method: 'POST',\r\n body: JSON.stringify({ token, new_password: newPassword }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // EMAIL VERIFICATION FLOW\r\n // ========================================================================\r\n\r\n /**\r\n * Verify email address using token from verification email.\r\n * \r\n * @param token - The verification token from the email link\r\n * @returns Success message\r\n */\r\n async verifyEmail(token: string): Promise<EmailVerificationResponse> {\r\n console.log('[API] Verifying email with token');\r\n \r\n return this.request<EmailVerificationResponse>('/api/auth/verify-email', {\r\n method: 'POST',\r\n body: JSON.stringify({ token }),\r\n });\r\n }\r\n\r\n /**\r\n * Request a new verification email.\r\n * \r\n * @param email - The email address to send verification to\r\n * @returns Message confirming email was sent\r\n */\r\n async requestVerificationEmail(email: string): Promise<EmailVerificationResponse> {\r\n console.log('[API] Requesting verification email for:', email);\r\n \r\n return this.request<EmailVerificationResponse>('/api/auth/request-verification-email', {\r\n method: 'POST',\r\n body: JSON.stringify({ email }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // SET PASSWORD (FOR OAUTH-ONLY ACCOUNTS)\r\n // ========================================================================\r\n\r\n /**\r\n * Set password for OAuth-only accounts.\r\n * \r\n * Allows users who registered via Google OAuth to set a password\r\n * so they can also log in with email/password.\r\n * \r\n * @param newPassword - The password to set (min 8 characters)\r\n * @returns Success message\r\n */\r\n async setPassword(newPassword: string): Promise<SetPasswordResponse> {\r\n console.log('[API] Setting password for OAuth account');\r\n \r\n return this.request<SetPasswordResponse>('/api/auth/set-password', {\r\n method: 'POST',\r\n body: JSON.stringify({ new_password: newPassword }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // ACCOUNT MANAGEMENT\r\n // ========================================================================\r\n\r\n async deleteAccount(password: string, confirmText: string): Promise<{ message: string; note: string }> {\r\n console.log('[API] Deleting account (DESTRUCTIVE)');\r\n \r\n const response = await this.request<{ message: string; note: string }>('/api/auth/me', {\r\n method: 'DELETE',\r\n body: JSON.stringify({ \r\n password, \r\n confirm_text: confirmText \r\n }),\r\n });\r\n \r\n this.clearAuth();\r\n console.log('[API] Account deleted successfully');\r\n return response;\r\n }\r\n\r\n async logout(): Promise<void> {\r\n console.log('[API] Logging out');\r\n \r\n if (this.refreshToken) {\r\n try {\r\n await this.request('/api/auth/logout', {\r\n method: 'POST',\r\n body: JSON.stringify({ refresh_token: this.refreshToken }),\r\n });\r\n console.log('[API] Refresh token revoked on server');\r\n } catch (error) {\r\n console.warn('[API] Failed to revoke refresh token on server:', error);\r\n }\r\n }\r\n \r\n this.clearAuth();\r\n }\r\n\r\n async logoutAllDevices(): Promise<void> {\r\n console.log('[API] Logging out from all devices');\r\n \r\n try {\r\n await this.request('/api/auth/logout-all', { method: 'POST' });\r\n console.log('[API] All sessions revoked on server');\r\n } catch (error) {\r\n console.warn('[API] Failed to revoke all sessions:', error);\r\n }\r\n \r\n this.clearAuth();\r\n }\r\n\r\n protected clearAuth(): void {\r\n if (this.proactiveRefreshTimer) {\r\n clearTimeout(this.proactiveRefreshTimer);\r\n this.proactiveRefreshTimer = null;\r\n }\r\n \r\n this.token = null;\r\n this.refreshToken = null;\r\n localStorage.removeItem('auth_token');\r\n localStorage.removeItem('refresh_token');\r\n localStorage.removeItem('user_data');\r\n console.log('[API] Auth cache cleared');\r\n \r\n window.dispatchEvent(new CustomEvent('auth:cleared'));\r\n }\r\n\r\n // ========================================================================\r\n // API KEY MANAGEMENT\r\n // ========================================================================\r\n\r\n async listApiKeys(): Promise<{ api_keys: ApiKey[]; total: number }> {\r\n console.log('[API] Listing user API keys');\r\n return this.request('/api/auth/api-keys', { method: 'GET' });\r\n }\r\n\r\n async createApiKey(data: {\r\n name: string;\r\n scopes?: string;\r\n expires_in_days?: number;\r\n rate_limit_per_minute?: number;\r\n }): Promise<ApiKeyCreateResponse> {\r\n console.log(`[API] Creating API key: ${data.name}`);\r\n return this.request('/api/auth/api-keys', {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n name: data.name,\r\n scopes: data.scopes || 'read,write',\r\n expires_in_days: data.expires_in_days,\r\n rate_limit_per_minute: data.rate_limit_per_minute || 60\r\n }),\r\n });\r\n }\r\n\r\n async revokeApiKey(keyId: string): Promise<{ message: string; key_prefix: string; revoked_at: string }> {\r\n console.log(`[API] Revoking API key: ${keyId}`);\r\n return this.request(`/api/auth/api-keys/${keyId}`, { method: 'DELETE' });\r\n }\r\n}\r\n\r\n// ========================================================================\r\n// FACTORY FUNCTION (RECOMMENDED)\r\n// ========================================================================\r\n// Use this instead of extending BaseApi. Cleaner, no inheritance.\r\n//\r\n// Usage:\r\n// import { createAuthApi } from '@rationalbloks/frontblok-auth';\r\n// export const authApi = createAuthApi(import.meta.env.VITE_API_URL);\r\n//\r\n// // Then use directly:\r\n// authApi.login(email, password);\r\n// authApi.logout();\r\n// authApi.listApiKeys();\r\n\r\n/**\r\n * Creates an auth API client instance.\r\n * Preferred over class inheritance - simpler and cleaner.\r\n * \r\n * @param apiBaseUrl - The backend API base URL\r\n * @returns A BaseApi instance with all auth methods\r\n */\r\nexport function createAuthApi(apiBaseUrl: string): BaseApi {\r\n return new BaseApi(apiBaseUrl);\r\n}\r\n\r\n// ========================================================================\r\n// STORAGE UTILITIES\r\n// ========================================================================\r\n\r\nexport const getStoredUser = (): User | null => {\r\n const userData = localStorage.getItem('user_data');\r\n if (!userData) return null;\r\n \r\n try {\r\n return JSON.parse(userData);\r\n } catch (error) {\r\n console.error('[API] Invalid user_data in localStorage:', error);\r\n localStorage.removeItem('user_data');\r\n return null;\r\n }\r\n};\r\n\r\nexport const getStoredToken = (): string | null => {\r\n return localStorage.getItem('auth_token');\r\n};\r\n\r\nexport const isAuthenticated = (): boolean => {\r\n return !!getStoredToken();\r\n};\r\n","// ========================================================================\r\n// AUTH CONTEXT\r\n// Universal authentication state management\r\n// ========================================================================\r\n// This file is part of the UNIVERSAL BOILERPLATE.\r\n// It provides:\r\n// - Authentication state (user, isLoading, isAuthenticated, error)\r\n// - Authentication actions (login, register, logout, clearError, refreshUser)\r\n// - React Context pattern for app-wide auth state\r\n// - Token expiration handling via 'auth:cleared' event\r\n// ========================================================================\r\n\r\nimport React, { createContext, useContext, useState, useEffect } from 'react';\r\nimport { getStoredUser, getStoredToken } from '../api';\r\nimport type { User } from '../api';\r\nimport type { BaseApi } from '../api/client';\r\n\r\n// ========================================================================\r\n// TYPES\r\n// ========================================================================\r\n\r\nexport interface AuthState {\r\n user: User | null;\r\n isLoading: boolean;\r\n isAuthenticated: boolean;\r\n error: string | null;\r\n}\r\n\r\nexport interface AuthActions {\r\n login: (email: string, password: string) => Promise<boolean>;\r\n register: (email: string, password: string, firstName: string, lastName: string) => Promise<boolean>;\r\n logout: () => void;\r\n clearError: () => void;\r\n refreshUser: () => Promise<void>;\r\n}\r\n\r\nexport type AuthContextType = AuthState & AuthActions;\r\n\r\n// ========================================================================\r\n// CONTEXT\r\n// ========================================================================\r\n\r\nconst AuthContext = createContext<AuthContextType | undefined>(undefined);\r\n\r\n/**\r\n * Hook to access authentication state and actions.\r\n * Must be used within an AuthProvider.\r\n */\r\nexport const useAuth = () => {\r\n const context = useContext(AuthContext);\r\n if (!context) {\r\n throw new Error('useAuth must be used within AuthProvider');\r\n }\r\n return context;\r\n};\r\n\r\n// ========================================================================\r\n// PROVIDER FACTORY\r\n// ========================================================================\r\n\r\n/**\r\n * Creates an AuthProvider component that uses the specified API instance.\r\n * This allows the universal auth context to work with any API that extends BaseApi.\r\n * \r\n * @example\r\n * ```typescript\r\n * // In your app's auth setup:\r\n * import { createAuthProvider } from '@/core/auth';\r\n * import { myAppApi } from '@/services/myAppApi';\r\n * \r\n * export const MyAppAuthProvider = createAuthProvider(myAppApi);\r\n * ```\r\n */\r\nexport function createAuthProvider(api: BaseApi) {\r\n const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {\r\n const [state, setState] = useState<AuthState>({\r\n user: getStoredUser(),\r\n isLoading: false,\r\n isAuthenticated: !!getStoredToken(),\r\n error: null,\r\n });\r\n\r\n // Listen for auth:cleared event (token expiration, session invalidation)\r\n useEffect(() => {\r\n const handleAuthCleared = () => {\r\n console.log('[Auth] Session expired or invalidated - clearing auth state');\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n };\r\n\r\n window.addEventListener('auth:cleared', handleAuthCleared);\r\n return () => window.removeEventListener('auth:cleared', handleAuthCleared);\r\n }, []);\r\n\r\n // Refresh user data from backend on mount if authenticated\r\n useEffect(() => {\r\n const refreshUserData = async () => {\r\n const token = getStoredToken();\r\n if (token) {\r\n try {\r\n const currentUser = await api.getCurrentUser();\r\n setState(prev => ({\r\n ...prev,\r\n user: currentUser,\r\n isAuthenticated: true,\r\n }));\r\n } catch (error: unknown) {\r\n console.error('[Auth] Failed to refresh user data:', error);\r\n const httpError = error as { status?: number };\r\n if (httpError?.status === 401 || httpError?.status === 403) {\r\n console.warn('[Auth] Auth failed - clearing local state only');\r\n localStorage.removeItem('auth_token');\r\n localStorage.removeItem('user_data');\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n } else {\r\n console.warn('[Auth] Keeping user logged in with cached data');\r\n }\r\n }\r\n }\r\n };\r\n\r\n refreshUserData();\r\n }, []);\r\n\r\n const login = async (email: string, password: string): Promise<boolean> => {\r\n setState(prev => ({ ...prev, isLoading: true, error: null }));\r\n \r\n try {\r\n const result = await api.login(email, password);\r\n setState(prev => ({\r\n ...prev,\r\n user: result.user,\r\n isAuthenticated: true,\r\n isLoading: false,\r\n }));\r\n return true;\r\n } catch (error) {\r\n setState(prev => ({\r\n ...prev,\r\n error: error instanceof Error ? error.message : 'Login failed',\r\n isLoading: false,\r\n }));\r\n return false;\r\n }\r\n };\r\n\r\n const register = async (email: string, password: string, firstName: string, lastName: string): Promise<boolean> => {\r\n setState(prev => ({ ...prev, isLoading: true, error: null }));\r\n \r\n try {\r\n const result = await api.register(email, password, firstName, lastName);\r\n setState(prev => ({\r\n ...prev,\r\n user: result.user,\r\n isAuthenticated: true,\r\n isLoading: false,\r\n }));\r\n return true;\r\n } catch (error) {\r\n setState(prev => ({\r\n ...prev,\r\n error: error instanceof Error ? error.message : 'Registration failed',\r\n isLoading: false,\r\n }));\r\n return false;\r\n }\r\n };\r\n\r\n const logout = () => {\r\n api.logout();\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n };\r\n\r\n const clearError = () => {\r\n setState(prev => ({ ...prev, error: null }));\r\n };\r\n\r\n const refreshUser = async () => {\r\n try {\r\n const currentUser = await api.getCurrentUser();\r\n setState(prev => ({\r\n ...prev,\r\n user: currentUser,\r\n }));\r\n } catch (error) {\r\n console.error('[Auth] Failed to refresh user:', error);\r\n }\r\n };\r\n\r\n return (\r\n <AuthContext.Provider\r\n value={{\r\n ...state,\r\n login,\r\n register,\r\n logout,\r\n clearError,\r\n refreshUser,\r\n }}\r\n >\r\n {children}\r\n </AuthContext.Provider>\r\n );\r\n };\r\n\r\n return AuthProvider;\r\n}\r\n","// ========================================================================\r\n// CORE AUTH - App Provider Factory\r\n// ========================================================================\r\n// Creates a wrapped app component with OAuth providers configured.\r\n// This is the universal entry point for React apps with authentication.\r\n//\r\n// Usage:\r\n// import { createAppRoot } from './core/auth';\r\n// import App from './App';\r\n// createAppRoot(App, { googleClientId: '...' });\r\n// ========================================================================\r\n\r\nimport React, { StrictMode } from 'react';\r\nimport { createRoot } from 'react-dom/client';\r\nimport { GoogleOAuthProvider } from '@react-oauth/google';\r\n\r\nexport interface AuthConfig {\r\n /** Google OAuth Client ID (optional - only needed if using Google Sign-In) */\r\n googleClientId?: string;\r\n}\r\n\r\n/**\r\n * Creates and renders the app root with authentication providers.\r\n * This is the universal way to bootstrap a React app with auth.\r\n */\r\nexport function createAppRoot(\r\n App: React.ComponentType,\r\n config: AuthConfig = {}\r\n): void {\r\n const { googleClientId } = config;\r\n\r\n const root = createRoot(document.getElementById('root')!);\r\n\r\n // Build the component tree with optional providers\r\n let appElement: React.ReactElement = <App />;\r\n\r\n // Wrap with Google OAuth if client ID is provided\r\n if (googleClientId) {\r\n appElement = (\r\n <GoogleOAuthProvider clientId={googleClientId}>\r\n {appElement}\r\n </GoogleOAuthProvider>\r\n );\r\n }\r\n\r\n // Render with StrictMode\r\n root.render(\r\n <StrictMode>\r\n {appElement}\r\n </StrictMode>\r\n );\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/api/client.ts","../src/api/createApiUrl.ts","../src/auth/AuthContext.tsx","../src/auth/AppProvider.tsx","../src/auth/ProtectedRoute.tsx"],"sourcesContent":["// ========================================================================\r\n// BASE API CLIENT\r\n// Universal HTTP client with JWT token management\r\n// ========================================================================\r\n//\r\n// Features:\r\n// - JWT token storage and automatic refresh\r\n// - Cross-tab synchronization via localStorage events\r\n// - Proactive token refresh before expiration\r\n// - Automatic 401 retry with token refresh\r\n// - Rate limiting (429) retry with exponential backoff\r\n// - Authentication methods (login, register, logout)\r\n// - API key management for external integrations\r\n//\r\n// Usage:\r\n// 1. Extend this class for your application's API\r\n// 2. Add your domain-specific CRUD methods in the subclass\r\n// 3. Call super() in constructor with your API base URL\r\n//\r\n// ========================================================================\r\n\r\nimport type { \r\n User, \r\n ApiKey, \r\n ApiKeyCreateResponse, \r\n AuthResponse,\r\n GoogleOAuthResponse,\r\n PasswordResetRequestResponse,\r\n PasswordResetResponse,\r\n EmailVerificationResponse,\r\n SetPasswordResponse\r\n} from './types';\r\n\r\n// ========================================================================\r\n// OAUTH UTILITIES (defined before class so they can be used within it)\r\n// ========================================================================\r\n\r\n/**\r\n * Generate a cryptographically secure nonce for OAuth CSRF protection.\r\n * \r\n * USAGE:\r\n * 1. Call generateOAuthNonce() in component useState (runs once on mount)\r\n * 2. Pass the nonce to GoogleLogin component via the 'nonce' prop\r\n * 3. On success, call authApi.googleLogin(credential) - it handles the rest\r\n * \r\n * @example\r\n * ```typescript\r\n * const [nonce] = useState(() => generateOAuthNonce());\r\n * // Pass to GoogleLogin: <GoogleLogin nonce={nonce} onSuccess={...} />\r\n * // On success: await authApi.googleLogin(credential);\r\n * ```\r\n * \r\n * @returns The generated nonce string\r\n */\r\nexport const generateOAuthNonce = (): string => {\r\n const array = new Uint8Array(32);\r\n crypto.getRandomValues(array);\r\n const nonce = Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\r\n sessionStorage.setItem('google_oauth_nonce', nonce);\r\n console.log('[OAuth] Generated nonce for CSRF protection');\r\n return nonce;\r\n};\r\n\r\n/**\r\n * Retrieve the stored OAuth nonce.\r\n * @returns The stored nonce or null if not found\r\n */\r\nexport const getOAuthNonce = (): string | null => {\r\n return sessionStorage.getItem('google_oauth_nonce');\r\n};\r\n\r\n/**\r\n * Clear the stored OAuth nonce (call after successful login).\r\n */\r\nexport const clearOAuthNonce = (): void => {\r\n sessionStorage.removeItem('google_oauth_nonce');\r\n console.log('[OAuth] Cleared nonce');\r\n};\r\n\r\n// ========================================================================\r\n// BASE API CLASS\r\n// ========================================================================\r\n\r\n/**\r\n * BaseApi - Universal HTTP client with authentication.\r\n * \r\n * Extend this class to add your application-specific API methods.\r\n * \r\n * @example\r\n * ```typescript\r\n * class MyAppApi extends BaseApi {\r\n * constructor() {\r\n * super(import.meta.env.VITE_API_URL || 'https://api.myapp.com');\r\n * }\r\n * \r\n * async getProducts() {\r\n * return this.request<Product[]>('/api/products/');\r\n * }\r\n * }\r\n * ```\r\n */\r\nexport class BaseApi {\r\n protected token: string | null = null;\r\n protected refreshToken: string | null = null;\r\n protected isRefreshing: boolean = false;\r\n protected refreshPromise: Promise<boolean> | null = null;\r\n protected proactiveRefreshTimer: ReturnType<typeof setTimeout> | null = null;\r\n protected refreshCheckInterval: ReturnType<typeof setInterval> | null = null;\r\n protected tokenRefreshPromise: Promise<void> | null = null;\r\n protected readonly apiBaseUrl: string;\r\n\r\n constructor(apiBaseUrl: string) {\r\n this.apiBaseUrl = apiBaseUrl;\r\n this.loadTokens();\r\n this.setupVisibilityHandler();\r\n this.setupStorageListener();\r\n this.startRefreshCheckInterval();\r\n \r\n console.log(`[API] Base URL: ${this.apiBaseUrl}`);\r\n console.log(`[API] Environment: ${import.meta.env.DEV ? 'Development' : 'Production'}`);\r\n }\r\n\r\n // ========================================================================\r\n // TOKEN MANAGEMENT\r\n // ========================================================================\r\n\r\n /**\r\n * Sync tokens across browser tabs when localStorage changes.\r\n * Prevents \"stale refresh token\" issue where Tab A rotates the token\r\n * but Tab B still has the old (now invalid) refresh token in memory.\r\n */\r\n private setupStorageListener(): void {\r\n if (typeof window !== 'undefined') {\r\n window.addEventListener('storage', (event) => {\r\n if (event.key === 'auth_token') {\r\n console.log('[API] Token updated in another tab, syncing...');\r\n this.token = event.newValue;\r\n } else if (event.key === 'refresh_token') {\r\n console.log('[API] Refresh token updated in another tab, syncing...');\r\n this.refreshToken = event.newValue;\r\n } else if (event.key === null) {\r\n // Storage was cleared (logout in another tab)\r\n console.log('[API] Storage cleared in another tab, syncing logout...');\r\n this.token = null;\r\n this.refreshToken = null;\r\n window.dispatchEvent(new CustomEvent('auth:cleared'));\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Start interval-based token check (runs every 60 seconds).\r\n * More reliable than setTimeout which browsers throttle in background tabs.\r\n */\r\n private startRefreshCheckInterval(): void {\r\n if (this.refreshCheckInterval) {\r\n clearInterval(this.refreshCheckInterval);\r\n }\r\n \r\n this.refreshCheckInterval = setInterval(() => {\r\n if (this.token) {\r\n this.checkAndRefreshToken();\r\n }\r\n }, 60 * 1000);\r\n }\r\n\r\n /**\r\n * Handle tab visibility changes - check token when user returns to tab.\r\n */\r\n private setupVisibilityHandler(): void {\r\n if (typeof document !== 'undefined') {\r\n document.addEventListener('visibilitychange', () => {\r\n if (document.visibilityState === 'visible' && this.token) {\r\n console.log('[API] Tab became visible, checking token validity...');\r\n this.checkAndRefreshToken();\r\n }\r\n });\r\n \r\n window.addEventListener('focus', () => {\r\n if (this.token) {\r\n console.log('[API] Window focused, checking token validity...');\r\n this.checkAndRefreshToken();\r\n }\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Check token expiry and refresh if needed.\r\n */\r\n protected async checkAndRefreshToken(): Promise<void> {\r\n if (!this.token) return;\r\n\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (!expiry) return;\r\n\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n const refreshBuffer = 3 * 60 * 1000; // 3 minutes buffer\r\n\r\n if (timeUntilExpiry <= 0) {\r\n console.log('[API] Token expired, refreshing immediately...');\r\n await this.refreshAccessToken();\r\n } else if (timeUntilExpiry <= refreshBuffer) {\r\n console.log(`[API] Token expires in ${Math.round(timeUntilExpiry / 1000)}s, refreshing proactively...`);\r\n await this.refreshAccessToken();\r\n }\r\n }\r\n\r\n /**\r\n * Parse JWT to get expiration time.\r\n */\r\n protected getTokenExpiry(token: string): number | null {\r\n try {\r\n const payload = JSON.parse(atob(token.split('.')[1]));\r\n return payload.exp ? payload.exp * 1000 : null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Schedule proactive refresh 5 minutes before token expires.\r\n */\r\n protected scheduleProactiveRefresh(): void {\r\n if (this.proactiveRefreshTimer) {\r\n clearTimeout(this.proactiveRefreshTimer);\r\n this.proactiveRefreshTimer = null;\r\n }\r\n\r\n if (!this.token) return;\r\n\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (!expiry) return;\r\n\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n const refreshBuffer = 5 * 60 * 1000;\r\n\r\n if (timeUntilExpiry <= 0) {\r\n console.log('[API] Token already expired, refreshing immediately...');\r\n this.refreshAccessToken();\r\n return;\r\n }\r\n\r\n if (timeUntilExpiry <= refreshBuffer) {\r\n console.log('[API] Token expiring soon, refreshing immediately...');\r\n this.refreshAccessToken();\r\n return;\r\n }\r\n\r\n const refreshIn = timeUntilExpiry - refreshBuffer;\r\n console.log(`[API] Scheduling proactive token refresh in ${Math.round(refreshIn / 60000)} minutes`);\r\n \r\n this.proactiveRefreshTimer = setTimeout(async () => {\r\n console.log('[API] Proactive token refresh triggered');\r\n await this.refreshAccessToken();\r\n }, refreshIn);\r\n }\r\n\r\n /**\r\n * Load tokens from localStorage on initialization.\r\n */\r\n protected loadTokens(): void {\r\n const storedToken = localStorage.getItem('auth_token');\r\n const storedRefreshToken = localStorage.getItem('refresh_token');\r\n \r\n if (storedToken) {\r\n this.token = storedToken;\r\n console.log('[API] Access token loaded from localStorage');\r\n }\r\n if (storedRefreshToken) {\r\n this.refreshToken = storedRefreshToken;\r\n console.log('[API] Refresh token loaded from localStorage');\r\n }\r\n \r\n if (this.token) {\r\n this.tokenRefreshPromise = this.checkAndRefreshToken();\r\n this.scheduleProactiveRefresh();\r\n }\r\n }\r\n\r\n /**\r\n * Wait for any pending token refresh to complete before making API calls.\r\n */\r\n async ensureTokenReady(): Promise<void> {\r\n if (this.tokenRefreshPromise) {\r\n await this.tokenRefreshPromise;\r\n this.tokenRefreshPromise = null;\r\n }\r\n }\r\n\r\n /**\r\n * Refresh the access token using the refresh token.\r\n */\r\n async refreshAccessToken(): Promise<boolean> {\r\n if (this.isRefreshing) {\r\n return this.refreshPromise || Promise.resolve(false);\r\n }\r\n\r\n // Sync from localStorage before refresh (another tab may have rotated)\r\n const latestRefreshToken = localStorage.getItem('refresh_token');\r\n if (latestRefreshToken && latestRefreshToken !== this.refreshToken) {\r\n console.log('[API] Syncing refresh token from localStorage');\r\n this.refreshToken = latestRefreshToken;\r\n }\r\n\r\n if (!this.refreshToken) {\r\n console.warn('[API] No refresh token available');\r\n return false;\r\n }\r\n\r\n this.isRefreshing = true;\r\n this.refreshPromise = (async () => {\r\n try {\r\n console.log('[API] Refreshing access token...');\r\n const response = await fetch(`${this.apiBaseUrl}/api/auth/refresh`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ refresh_token: this.refreshToken }),\r\n });\r\n\r\n if (!response.ok) {\r\n console.error('[API] Token refresh failed:', response.status);\r\n if (response.status === 401 || response.status === 403) {\r\n console.log('[API] Refresh token is invalid, logging out...');\r\n this.clearAuth();\r\n } else {\r\n console.warn(`[API] Refresh failed with ${response.status}, will retry on next interval`);\r\n }\r\n return false;\r\n }\r\n\r\n const data = await response.json();\r\n \r\n this.token = data.access_token;\r\n localStorage.setItem('auth_token', data.access_token);\r\n \r\n if (data.refresh_token) {\r\n this.refreshToken = data.refresh_token;\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n console.log('[API] Refresh token rotated');\r\n }\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Access token refreshed successfully');\r\n return true;\r\n } catch (error) {\r\n console.error('[API] Token refresh network error:', error);\r\n console.warn('[API] Will retry refresh on next interval');\r\n return false;\r\n } finally {\r\n this.isRefreshing = false;\r\n this.refreshPromise = null;\r\n }\r\n })();\r\n\r\n return this.refreshPromise;\r\n }\r\n\r\n // ========================================================================\r\n // HTTP REQUEST METHOD\r\n // ========================================================================\r\n\r\n /**\r\n * Make an authenticated HTTP request.\r\n * Handles token refresh, 401 retry, and rate limiting automatically.\r\n * \r\n * This method is public so apps can make custom API calls without extending BaseApi.\r\n * \r\n * @example\r\n * const authApi = createAuthApi(API_URL);\r\n * const data = await authApi.request<MyType>('/api/my-endpoint/', { method: 'POST', body: JSON.stringify(payload) });\r\n */\r\n async request<T>(endpoint: string, options: RequestInit = {}, isRetry = false, retryCount = 0): Promise<T> {\r\n await this.ensureTokenReady();\r\n \r\n // Proactive token refresh before request\r\n if (this.token && !isRetry && !endpoint.includes('/auth/')) {\r\n const expiry = this.getTokenExpiry(this.token);\r\n if (expiry) {\r\n const now = Date.now();\r\n const timeUntilExpiry = expiry - now;\r\n if (timeUntilExpiry < 2 * 60 * 1000) {\r\n console.log(`[API] Token expires in ${Math.round(timeUntilExpiry/1000)}s, refreshing before request...`);\r\n await this.refreshAccessToken();\r\n }\r\n }\r\n }\r\n \r\n const url = `${this.apiBaseUrl}${endpoint}`;\r\n \r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n ...(options.headers as Record<string, string>),\r\n };\r\n\r\n if (this.token) {\r\n headers.Authorization = `Bearer ${this.token}`;\r\n }\r\n\r\n console.log(`[API Request] ${options.method || 'GET'} ${url}`);\r\n \r\n try {\r\n const response = await fetch(url, { ...options, headers });\r\n \r\n console.log(`[API Response] ${response.status} ${response.statusText}`);\r\n \r\n if (!response.ok) {\r\n const error = await response.text();\r\n console.error(`[API Error] ${response.status}: ${error}`);\r\n \r\n // Rate limiting retry\r\n if (response.status === 429 && retryCount < 3) {\r\n const backoffMs = Math.pow(2, retryCount) * 1000;\r\n console.log(`[API] Rate limited (429), retrying in ${backoffMs}ms (attempt ${retryCount + 1}/3)...`);\r\n await new Promise(resolve => setTimeout(resolve, backoffMs));\r\n return this.request<T>(endpoint, options, isRetry, retryCount + 1);\r\n }\r\n \r\n // 401 retry with token refresh\r\n if (response.status === 401 && !isRetry && !endpoint.includes('/auth/login') && !endpoint.includes('/auth/register') && !endpoint.includes('/auth/refresh')) {\r\n console.log('[API] Attempting token refresh...');\r\n const refreshed = await this.refreshAccessToken();\r\n if (refreshed) {\r\n return this.request<T>(endpoint, options, true, retryCount);\r\n }\r\n console.warn('[API] Token refresh failed, returning error to caller');\r\n }\r\n \r\n const apiError = new Error(`API Error: ${response.status} - ${error}`) as Error & { status: number };\r\n apiError.status = response.status;\r\n throw apiError;\r\n }\r\n\r\n return response.json();\r\n } catch (error) {\r\n console.error('[API Connection Error]:', error);\r\n if (error instanceof TypeError && error.message.includes('Failed to fetch')) {\r\n throw new Error(`Cannot connect to API: ${this.apiBaseUrl}`);\r\n }\r\n throw error;\r\n }\r\n }\r\n\r\n // ========================================================================\r\n // AUTHENTICATION METHODS\r\n // ========================================================================\r\n\r\n async login(email: string, password: string): Promise<AuthResponse> {\r\n console.log('[API] Login attempt:', email);\r\n \r\n const data = await this.request<AuthResponse>('/api/auth/login', {\r\n method: 'POST',\r\n body: JSON.stringify({ email, password }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Login successful');\r\n return data;\r\n }\r\n\r\n async register(email: string, password: string, firstName: string, lastName: string): Promise<AuthResponse> {\r\n console.log('[API] Registration attempt:', email);\r\n \r\n const data = await this.request<AuthResponse>('/api/auth/register', {\r\n method: 'POST',\r\n body: JSON.stringify({ \r\n email, \r\n password, \r\n first_name: firstName, \r\n last_name: lastName \r\n }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log('[API] Registration successful');\r\n return data;\r\n }\r\n\r\n async getMe(): Promise<{ user: User }> {\r\n console.log('[API] Fetching current user');\r\n return this.request<{ user: User }>('/api/auth/me');\r\n }\r\n\r\n async getCurrentUser(): Promise<User> {\r\n console.log('[API] Fetching current user data');\r\n const response = await this.request<{ user: User }>('/api/auth/me');\r\n localStorage.setItem('user', JSON.stringify(response.user));\r\n return response.user;\r\n }\r\n\r\n // ========================================================================\r\n // GOOGLE OAUTH AUTHENTICATION\r\n // ========================================================================\r\n\r\n /**\r\n * Internal: Authenticate with Google OAuth.\r\n * Use googleLogin() instead.\r\n */\r\n private async _googleOAuthLogin(credential: string, nonce: string): Promise<GoogleOAuthResponse> {\r\n console.log('[API] Google OAuth login attempt');\r\n \r\n const data = await this.request<GoogleOAuthResponse>('/api/auth/google/login', {\r\n method: 'POST',\r\n body: JSON.stringify({ credential, nonce }),\r\n });\r\n\r\n this.token = data.access_token;\r\n this.refreshToken = data.refresh_token || null;\r\n localStorage.setItem('auth_token', data.access_token);\r\n if (data.refresh_token) {\r\n localStorage.setItem('refresh_token', data.refresh_token);\r\n }\r\n localStorage.setItem('user_data', JSON.stringify(data.user));\r\n \r\n this.scheduleProactiveRefresh();\r\n console.log(`[API] Google OAuth successful (new_user: ${data.is_new_user})`);\r\n return data;\r\n }\r\n\r\n /**\r\n * Simplified Google OAuth login.\r\n * \r\n * This is the default method for Google Sign-In. It handles the complete\r\n * OAuth flow in a single call:\r\n * \r\n * 1. Retrieve nonce from sessionStorage (must exist from generateOAuthNonce())\r\n * 2. Send credential + nonce to backend for verification\r\n * 3. Store JWT tokens on success\r\n * 4. Clear nonce to prevent replay attacks\r\n * \r\n * If any step fails, the entire operation fails cleanly. No partial states.\r\n * \r\n * @param credential - The Google ID token (JWT from GoogleLogin onSuccess)\r\n * @returns GoogleOAuthResponse with tokens, user data, and is_new_user flag\r\n * @throws Error if nonce is missing or authentication fails\r\n * \r\n * @example\r\n * ```typescript\r\n * // In component: generate nonce ONCE on mount\r\n * const [nonce] = useState(() => generateOAuthNonce());\r\n * \r\n * // Pass nonce to GoogleLogin, then on success:\r\n * const handleSuccess = async (response: CredentialResponse) => {\r\n * const result = await authApi.googleLogin(response.credential!);\r\n * // Done! Tokens stored, nonce cleared, user authenticated.\r\n * };\r\n * ```\r\n */\r\n async googleLogin(credential: string): Promise<GoogleOAuthResponse> {\r\n console.log('[API] Google OAuth login');\r\n \r\n // Step 1: Retrieve nonce - MUST exist or we fail immediately\r\n const nonce = getOAuthNonce();\r\n if (!nonce) {\r\n throw new Error('Security validation failed: OAuth nonce not found. Call generateOAuthNonce() before Google Sign-In.');\r\n }\r\n \r\n // Step 2: Authenticate with backend\r\n const result = await this._googleOAuthLogin(credential, nonce);\r\n \r\n // Step 3: Clear nonce AFTER success to prevent replay attacks\r\n clearOAuthNonce();\r\n console.log('[API] OAuth nonce cleared (replay protection)');\r\n \r\n return result;\r\n }\r\n\r\n // ========================================================================\r\n // PASSWORD RESET FLOW\r\n // ========================================================================\r\n\r\n /**\r\n * Request a password reset email (forgot password flow).\r\n * \r\n * @param email - The email address to send reset link to\r\n * @returns Message confirming email was sent (or would be sent)\r\n */\r\n async requestPasswordReset(email: string): Promise<PasswordResetRequestResponse> {\r\n console.log('[API] Requesting password reset for:', email);\r\n \r\n return this.request<PasswordResetRequestResponse>('/api/auth/request-password-reset', {\r\n method: 'POST',\r\n body: JSON.stringify({ email }),\r\n });\r\n }\r\n\r\n /**\r\n * Reset password using token from email.\r\n * \r\n * @param token - The password reset token from the email link\r\n * @param newPassword - The new password (min 8 characters)\r\n * @returns Success message\r\n */\r\n async resetPassword(token: string, newPassword: string): Promise<PasswordResetResponse> {\r\n console.log('[API] Resetting password with token');\r\n \r\n return this.request<PasswordResetResponse>('/api/auth/reset-password', {\r\n method: 'POST',\r\n body: JSON.stringify({ token, new_password: newPassword }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // EMAIL VERIFICATION FLOW\r\n // ========================================================================\r\n\r\n /**\r\n * Verify email address using token from verification email.\r\n * \r\n * @param token - The verification token from the email link\r\n * @returns Success message\r\n */\r\n async verifyEmail(token: string): Promise<EmailVerificationResponse> {\r\n console.log('[API] Verifying email with token');\r\n \r\n return this.request<EmailVerificationResponse>('/api/auth/verify-email', {\r\n method: 'POST',\r\n body: JSON.stringify({ token }),\r\n });\r\n }\r\n\r\n /**\r\n * Request a new verification email.\r\n * \r\n * @param email - The email address to send verification to\r\n * @returns Message confirming email was sent\r\n */\r\n async requestVerificationEmail(email: string): Promise<EmailVerificationResponse> {\r\n console.log('[API] Requesting verification email for:', email);\r\n \r\n return this.request<EmailVerificationResponse>('/api/auth/request-verification-email', {\r\n method: 'POST',\r\n body: JSON.stringify({ email }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // SET PASSWORD (FOR OAUTH-ONLY ACCOUNTS)\r\n // ========================================================================\r\n\r\n /**\r\n * Set password for OAuth-only accounts.\r\n * \r\n * Allows users who registered via Google OAuth to set a password\r\n * so they can also log in with email/password.\r\n * \r\n * @param newPassword - The password to set (min 8 characters)\r\n * @returns Success message\r\n */\r\n async setPassword(newPassword: string): Promise<SetPasswordResponse> {\r\n console.log('[API] Setting password for OAuth account');\r\n \r\n return this.request<SetPasswordResponse>('/api/auth/set-password', {\r\n method: 'POST',\r\n body: JSON.stringify({ new_password: newPassword }),\r\n });\r\n }\r\n\r\n // ========================================================================\r\n // ACCOUNT MANAGEMENT\r\n // ========================================================================\r\n\r\n async deleteAccount(password: string, confirmText: string): Promise<{ message: string; note: string }> {\r\n console.log('[API] Deleting account (DESTRUCTIVE)');\r\n \r\n const response = await this.request<{ message: string; note: string }>('/api/auth/me', {\r\n method: 'DELETE',\r\n body: JSON.stringify({ \r\n password, \r\n confirm_text: confirmText \r\n }),\r\n });\r\n \r\n this.clearAuth();\r\n console.log('[API] Account deleted successfully');\r\n return response;\r\n }\r\n\r\n async logout(): Promise<void> {\r\n console.log('[API] Logging out');\r\n \r\n if (this.refreshToken) {\r\n try {\r\n await this.request('/api/auth/logout', {\r\n method: 'POST',\r\n body: JSON.stringify({ refresh_token: this.refreshToken }),\r\n });\r\n console.log('[API] Refresh token revoked on server');\r\n } catch (error) {\r\n console.warn('[API] Failed to revoke refresh token on server:', error);\r\n }\r\n }\r\n \r\n this.clearAuth();\r\n }\r\n\r\n async logoutAllDevices(): Promise<void> {\r\n console.log('[API] Logging out from all devices');\r\n \r\n try {\r\n await this.request('/api/auth/logout-all', { method: 'POST' });\r\n console.log('[API] All sessions revoked on server');\r\n } catch (error) {\r\n console.warn('[API] Failed to revoke all sessions:', error);\r\n }\r\n \r\n this.clearAuth();\r\n }\r\n\r\n protected clearAuth(): void {\r\n if (this.proactiveRefreshTimer) {\r\n clearTimeout(this.proactiveRefreshTimer);\r\n this.proactiveRefreshTimer = null;\r\n }\r\n \r\n this.token = null;\r\n this.refreshToken = null;\r\n localStorage.removeItem('auth_token');\r\n localStorage.removeItem('refresh_token');\r\n localStorage.removeItem('user_data');\r\n console.log('[API] Auth cache cleared');\r\n \r\n window.dispatchEvent(new CustomEvent('auth:cleared'));\r\n }\r\n\r\n // ========================================================================\r\n // API KEY MANAGEMENT\r\n // ========================================================================\r\n\r\n async listApiKeys(): Promise<{ api_keys: ApiKey[]; total: number }> {\r\n console.log('[API] Listing user API keys');\r\n return this.request('/api/auth/api-keys', { method: 'GET' });\r\n }\r\n\r\n async createApiKey(data: {\r\n name: string;\r\n scopes?: string;\r\n expires_in_days?: number;\r\n rate_limit_per_minute?: number;\r\n }): Promise<ApiKeyCreateResponse> {\r\n console.log(`[API] Creating API key: ${data.name}`);\r\n return this.request('/api/auth/api-keys', {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n name: data.name,\r\n scopes: data.scopes || 'read,write',\r\n expires_in_days: data.expires_in_days,\r\n rate_limit_per_minute: data.rate_limit_per_minute || 60\r\n }),\r\n });\r\n }\r\n\r\n async revokeApiKey(keyId: string): Promise<{ message: string; key_prefix: string; revoked_at: string }> {\r\n console.log(`[API] Revoking API key: ${keyId}`);\r\n return this.request(`/api/auth/api-keys/${keyId}`, { method: 'DELETE' });\r\n }\r\n}\r\n\r\n// ========================================================================\r\n// FACTORY FUNCTION (RECOMMENDED)\r\n// ========================================================================\r\n// Use this instead of extending BaseApi. Cleaner, no inheritance.\r\n//\r\n// Usage:\r\n// import { createAuthApi } from '@rationalbloks/frontblok-auth';\r\n// export const authApi = createAuthApi(import.meta.env.VITE_API_URL);\r\n//\r\n// // Then use directly:\r\n// authApi.login(email, password);\r\n// authApi.logout();\r\n// authApi.listApiKeys();\r\n\r\n/**\r\n * Creates an auth API client instance.\r\n * Preferred over class inheritance - simpler and cleaner.\r\n * \r\n * @param apiBaseUrl - The backend API base URL\r\n * @returns A BaseApi instance with all auth methods\r\n */\r\nexport function createAuthApi(apiBaseUrl: string): BaseApi {\r\n return new BaseApi(apiBaseUrl);\r\n}\r\n\r\n// ========================================================================\r\n// STORAGE UTILITIES\r\n// ========================================================================\r\n\r\nexport const getStoredUser = (): User | null => {\r\n const userData = localStorage.getItem('user_data');\r\n if (!userData) return null;\r\n \r\n try {\r\n return JSON.parse(userData);\r\n } catch (error) {\r\n console.error('[API] Invalid user_data in localStorage:', error);\r\n localStorage.removeItem('user_data');\r\n return null;\r\n }\r\n};\r\n\r\nexport const getStoredToken = (): string | null => {\r\n return localStorage.getItem('auth_token');\r\n};\r\n\r\nexport const isAuthenticated = (): boolean => {\r\n return !!getStoredToken();\r\n};\r\n","// ========================================================================\r\n// API URL FACTORY\r\n// ========================================================================\r\n// Universal API URL resolution for RationalBloks apps.\r\n//\r\n// In production, the URL is injected via Docker build args (VITE_DATABASE_API_URL).\r\n// In development, falls back to localhost:8000.\r\n//\r\n// This function lives in the package so URL resolution logic is consistent\r\n// across ALL customer apps and can be updated via npm — eliminating the\r\n// entire class of \"localhost baked into production\" bugs.\r\n//\r\n// Usage:\r\n// import { createApiUrl } from '@rationalbloks/frontblok-auth';\r\n// const authApi = createAuthApi(createApiUrl());\r\n// ========================================================================\r\n\r\n/**\r\n * Options for createApiUrl.\r\n */\r\nexport interface CreateApiUrlOptions {\r\n /** \r\n * Override the env variable name. Default: 'VITE_DATABASE_API_URL'.\r\n * This is read from import.meta.env at call time.\r\n */\r\n envVar?: string;\r\n \r\n /**\r\n * Fallback URL for local development (only used when import.meta.env.DEV is true).\r\n * Default: 'http://localhost:8000'\r\n */\r\n devFallback?: string;\r\n}\r\n\r\n/**\r\n * Resolves the backend API URL for the current environment.\r\n * \r\n * Resolution order:\r\n * 1. `import.meta.env.VITE_DATABASE_API_URL` (injected at build time)\r\n * 2. `import.meta.env.VITE_API_URL` (alternative env var name)\r\n * 3. In development mode only: devFallback (default: 'http://localhost:8000')\r\n * 4. In production: empty string (will cause API calls to fail loudly)\r\n * \r\n * @param options - Configuration options\r\n * @returns The resolved API base URL\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createApiUrl, createAuthApi } from '@rationalbloks/frontblok-auth';\r\n * \r\n * // Simple usage\r\n * const authApi = createAuthApi(createApiUrl());\r\n * \r\n * // Custom dev fallback\r\n * const authApi = createAuthApi(createApiUrl({ devFallback: 'http://localhost:3000' }));\r\n * ```\r\n */\r\nexport function createApiUrl(options: CreateApiUrlOptions = {}): string {\r\n const { devFallback = 'http://localhost:8000' } = options;\r\n \r\n // Try the standard env var first\r\n const fromEnv = import.meta.env.VITE_DATABASE_API_URL || import.meta.env.VITE_API_URL;\r\n \r\n if (fromEnv) {\r\n return fromEnv;\r\n }\r\n \r\n // In development, use the fallback\r\n if (import.meta.env.DEV) {\r\n console.log(`[RationalBloks] Using dev fallback API URL: ${devFallback}`);\r\n return devFallback;\r\n }\r\n \r\n // In production without env var — this is a build config error\r\n console.error(\r\n '[RationalBloks] VITE_DATABASE_API_URL is not set in production. ' +\r\n 'API calls will fail. Ensure the Docker build passes --build-arg VITE_DATABASE_API_URL.'\r\n );\r\n return '';\r\n}\r\n","// ========================================================================\r\n// AUTH CONTEXT\r\n// Universal authentication state management\r\n// ========================================================================\r\n// It provides:\r\n// - Authentication state (user, isLoading, isAuthenticated, error)\r\n// - Authentication actions (login, register, logout, clearError, refreshUser)\r\n// - React Context pattern for app-wide auth state\r\n// - Token expiration handling via 'auth:cleared' event\r\n// ========================================================================\r\n\r\nimport React, { createContext, useContext, useState, useEffect } from 'react';\r\nimport { getStoredUser, getStoredToken } from '../api';\r\nimport type { User } from '../api';\r\nimport type { BaseApi } from '../api/client';\r\n\r\n// ========================================================================\r\n// TYPES\r\n// ========================================================================\r\n\r\nexport interface AuthState {\r\n user: User | null;\r\n isLoading: boolean;\r\n isAuthenticated: boolean;\r\n error: string | null;\r\n}\r\n\r\nexport interface AuthActions {\r\n login: (email: string, password: string) => Promise<boolean>;\r\n register: (email: string, password: string, firstName: string, lastName: string) => Promise<boolean>;\r\n logout: () => void;\r\n clearError: () => void;\r\n refreshUser: () => Promise<void>;\r\n}\r\n\r\nexport type AuthContextType = AuthState & AuthActions;\r\n\r\n// ========================================================================\r\n// CONTEXT\r\n// ========================================================================\r\n\r\nconst AuthContext = createContext<AuthContextType | undefined>(undefined);\r\n\r\n/**\r\n * Hook to access authentication state and actions.\r\n * Must be used within an AuthProvider.\r\n */\r\nexport const useAuth = () => {\r\n const context = useContext(AuthContext);\r\n if (!context) {\r\n throw new Error('useAuth must be used within AuthProvider');\r\n }\r\n return context;\r\n};\r\n\r\n// ========================================================================\r\n// PROVIDER FACTORY\r\n// ========================================================================\r\n\r\n/**\r\n * Creates an AuthProvider component that uses the specified API instance.\r\n * This allows the universal auth context to work with any API that extends BaseApi.\r\n * \r\n * @example\r\n * ```typescript\r\n * // In your app's auth setup:\r\n * import { createAuthProvider } from '@/core/auth';\r\n * import { myAppApi } from '@/services/myAppApi';\r\n * \r\n * export const MyAppAuthProvider = createAuthProvider(myAppApi);\r\n * ```\r\n */\r\nexport function createAuthProvider(api: BaseApi) {\r\n const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {\r\n const [state, setState] = useState<AuthState>({\r\n user: getStoredUser(),\r\n isLoading: false,\r\n isAuthenticated: !!getStoredToken(),\r\n error: null,\r\n });\r\n\r\n // Listen for auth:cleared event (token expiration, session invalidation)\r\n useEffect(() => {\r\n const handleAuthCleared = () => {\r\n console.log('[Auth] Session expired or invalidated - clearing auth state');\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n };\r\n\r\n window.addEventListener('auth:cleared', handleAuthCleared);\r\n return () => window.removeEventListener('auth:cleared', handleAuthCleared);\r\n }, []);\r\n\r\n // Refresh user data from backend on mount if authenticated\r\n useEffect(() => {\r\n const refreshUserData = async () => {\r\n const token = getStoredToken();\r\n if (token) {\r\n try {\r\n const currentUser = await api.getCurrentUser();\r\n setState(prev => ({\r\n ...prev,\r\n user: currentUser,\r\n isAuthenticated: true,\r\n }));\r\n } catch (error: unknown) {\r\n console.error('[Auth] Failed to refresh user data:', error);\r\n const httpError = error as { status?: number };\r\n if (httpError?.status === 401 || httpError?.status === 403) {\r\n console.warn('[Auth] Auth failed - clearing local state only');\r\n localStorage.removeItem('auth_token');\r\n localStorage.removeItem('user_data');\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n } else {\r\n console.warn('[Auth] Keeping user logged in with cached data');\r\n }\r\n }\r\n }\r\n };\r\n\r\n refreshUserData();\r\n }, []);\r\n\r\n const login = async (email: string, password: string): Promise<boolean> => {\r\n setState(prev => ({ ...prev, isLoading: true, error: null }));\r\n \r\n try {\r\n const result = await api.login(email, password);\r\n setState(prev => ({\r\n ...prev,\r\n user: result.user,\r\n isAuthenticated: true,\r\n isLoading: false,\r\n }));\r\n return true;\r\n } catch (error) {\r\n setState(prev => ({\r\n ...prev,\r\n error: error instanceof Error ? error.message : 'Login failed',\r\n isLoading: false,\r\n }));\r\n return false;\r\n }\r\n };\r\n\r\n const register = async (email: string, password: string, firstName: string, lastName: string): Promise<boolean> => {\r\n setState(prev => ({ ...prev, isLoading: true, error: null }));\r\n \r\n try {\r\n const result = await api.register(email, password, firstName, lastName);\r\n setState(prev => ({\r\n ...prev,\r\n user: result.user,\r\n isAuthenticated: true,\r\n isLoading: false,\r\n }));\r\n return true;\r\n } catch (error) {\r\n setState(prev => ({\r\n ...prev,\r\n error: error instanceof Error ? error.message : 'Registration failed',\r\n isLoading: false,\r\n }));\r\n return false;\r\n }\r\n };\r\n\r\n const logout = () => {\r\n api.logout();\r\n setState({\r\n user: null,\r\n isAuthenticated: false,\r\n isLoading: false,\r\n error: null,\r\n });\r\n };\r\n\r\n const clearError = () => {\r\n setState(prev => ({ ...prev, error: null }));\r\n };\r\n\r\n const refreshUser = async () => {\r\n try {\r\n const currentUser = await api.getCurrentUser();\r\n setState(prev => ({\r\n ...prev,\r\n user: currentUser,\r\n }));\r\n } catch (error) {\r\n console.error('[Auth] Failed to refresh user:', error);\r\n }\r\n };\r\n\r\n return (\r\n <AuthContext.Provider\r\n value={{\r\n ...state,\r\n login,\r\n register,\r\n logout,\r\n clearError,\r\n refreshUser,\r\n }}\r\n >\r\n {children}\r\n </AuthContext.Provider>\r\n );\r\n };\r\n\r\n return AuthProvider;\r\n}\r\n","// ========================================================================\r\n// CORE AUTH - App Provider Factory\r\n// ========================================================================\r\n// Creates a wrapped app component with OAuth providers configured.\r\n// This is the universal entry point for React apps with authentication.\r\n//\r\n// Usage:\r\n// import { createAppRoot } from './core/auth';\r\n// import App from './App';\r\n// createAppRoot(App, { googleClientId: '...' });\r\n// ========================================================================\r\n\r\nimport React, { StrictMode } from 'react';\r\nimport { createRoot } from 'react-dom/client';\r\nimport { GoogleOAuthProvider } from '@react-oauth/google';\r\n\r\nexport interface AuthConfig {\r\n /** Google OAuth Client ID (optional - only needed if using Google Sign-In) */\r\n googleClientId?: string;\r\n}\r\n\r\n/**\r\n * Creates and renders the app root with authentication providers.\r\n * This is the universal way to bootstrap a React app with auth.\r\n */\r\nexport function createAppRoot(\r\n App: React.ComponentType,\r\n config: AuthConfig = {}\r\n): void {\r\n const { googleClientId = '' } = config;\r\n\r\n const root = createRoot(document.getElementById('root')!);\r\n\r\n // Build the component tree with OAuth provider\r\n // Always wrap with GoogleOAuthProvider to prevent crashes in AuthView\r\n // When clientId is empty, Google Sign-In just won't work (graceful degradation)\r\n const appElement = (\r\n <GoogleOAuthProvider clientId={googleClientId}>\r\n <App />\r\n </GoogleOAuthProvider>\r\n );\r\n\r\n // Render with StrictMode\r\n root.render(\r\n <StrictMode>\r\n {appElement}\r\n </StrictMode>\r\n );\r\n}\r\n","// ========================================================================\r\n// PROTECTED ROUTE\r\n// ========================================================================\r\n// Universal auth-guarded route wrapper for React Router.\r\n//\r\n// This component is identical in every RationalBloks app, so it lives\r\n// in the auth package to be updated via npm.\r\n//\r\n// Usage:\r\n// import { ProtectedRoute } from '@rationalbloks/frontblok-auth';\r\n// \r\n// <Route path=\"/dashboard\" element={\r\n// <ProtectedRoute useAuth={useClientAuth}>\r\n// <DashboardView />\r\n// </ProtectedRoute>\r\n// } />\r\n//\r\n// // Or with a custom redirect:\r\n// <ProtectedRoute useAuth={useClientAuth} redirectTo=\"/login\">\r\n// ========================================================================\r\n\r\nimport React from 'react';\r\nimport { Navigate } from 'react-router-dom';\r\n\r\nexport interface ProtectedRouteProps {\r\n children: React.ReactNode;\r\n /** \r\n * The useAuth hook from your app's datablokApi.ts (useClientAuth).\r\n * We accept this as a prop instead of importing it directly to avoid\r\n * circular dependencies and keep the package decoupled.\r\n */\r\n useAuth: () => { isAuthenticated: boolean };\r\n /**\r\n * Where to redirect unauthenticated users. Default: '/auth'\r\n */\r\n redirectTo?: string;\r\n}\r\n\r\n/**\r\n * Route wrapper that redirects to the auth page if the user is not authenticated.\r\n */\r\nexport const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ \r\n children, \r\n useAuth,\r\n redirectTo = '/auth' \r\n}) => {\r\n const { isAuthenticated } = useAuth();\r\n \r\n if (!isAuthenticated) {\r\n return <Navigate to={redirectTo} replace />;\r\n }\r\n \r\n return <>{children}</>;\r\n};\r\n"],"names":["useAuth","isAuthenticated"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rationalbloks/frontblok-auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Authentication mechanics for RationalBloks frontends - JWT tokens, Google OAuth, password reset, email verification, cross-tab sync",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Authentication mechanics for RationalBloks frontends - JWT tokens, Google OAuth, password reset, email verification, cross-tab sync, API URL resolution, route protection",
|
|
5
5
|
"author": "RationalBloks Team",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|