@rationalbloks/frontblok-auth 0.1.0 → 0.2.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/README.md CHANGED
@@ -136,6 +136,80 @@ export const myApi = new MyAppApi({
136
136
  | `getStoredToken()` | Get token from localStorage |
137
137
  | `isAuthenticated()` | Check if user is logged in |
138
138
 
139
+ ### OAuth Utilities
140
+
141
+ | Export | Description |
142
+ |--------|-------------|
143
+ | `generateOAuthNonce()` | Generate CSRF nonce for Google OAuth |
144
+ | `getOAuthNonce()` | Retrieve stored OAuth nonce |
145
+ | `clearOAuthNonce()` | Clear stored nonce after login |
146
+
147
+ ### BaseApi Methods
148
+
149
+ #### Core Authentication
150
+ | Method | Description |
151
+ |--------|-------------|
152
+ | `login(email, password)` | Email/password login |
153
+ | `register(email, password, firstName, lastName)` | Register new account |
154
+ | `logout()` | Logout current session |
155
+ | `logoutAllDevices()` | Revoke all sessions |
156
+ | `getCurrentUser()` | Get authenticated user info |
157
+ | `deleteAccount(password, confirmText)` | Delete account permanently |
158
+
159
+ #### Google OAuth (v0.2.0+)
160
+ | Method | Description |
161
+ |--------|-------------|
162
+ | `googleOAuthLogin(credential, nonce)` | Login with Google OAuth |
163
+
164
+ #### Password Reset (v0.2.0+)
165
+ | Method | Description |
166
+ |--------|-------------|
167
+ | `requestPasswordReset(email)` | Send password reset email |
168
+ | `resetPassword(token, newPassword)` | Reset password with token |
169
+
170
+ #### Email Verification (v0.2.0+)
171
+ | Method | Description |
172
+ |--------|-------------|
173
+ | `verifyEmail(token)` | Verify email with token |
174
+ | `requestVerificationEmail(email)` | Resend verification email |
175
+ | `setPassword(newPassword)` | Set password for OAuth accounts |
176
+
177
+ #### API Key Management
178
+ | Method | Description |
179
+ |--------|-------------|
180
+ | `listApiKeys()` | List user's API keys |
181
+ | `createApiKey(data)` | Create new API key |
182
+ | `revokeApiKey(keyId)` | Revoke an API key |
183
+
184
+ ---
185
+
186
+ ## 🔐 Google OAuth Example
187
+
188
+ ```tsx
189
+ import { GoogleLogin } from '@react-oauth/google';
190
+ import { generateOAuthNonce, getOAuthNonce, createAuthApi } from '@rationalbloks/frontblok-auth';
191
+
192
+ const authApi = createAuthApi(import.meta.env.VITE_API_URL);
193
+
194
+ function LoginPage() {
195
+ // Generate nonce before rendering GoogleLogin
196
+ const nonce = generateOAuthNonce();
197
+
198
+ return (
199
+ <GoogleLogin
200
+ nonce={nonce}
201
+ onSuccess={async (response) => {
202
+ const result = await authApi.googleOAuthLogin(
203
+ response.credential!,
204
+ getOAuthNonce()!
205
+ );
206
+ console.log('Logged in:', result.user);
207
+ }}
208
+ />
209
+ );
210
+ }
211
+ ```
212
+
139
213
  ---
140
214
 
141
215
  ## 🔧 Development
@@ -1,4 +1,4 @@
1
- import type { User, ApiKey, ApiKeyCreateResponse, AuthResponse } from './types';
1
+ import type { User, ApiKey, ApiKeyCreateResponse, AuthResponse, GoogleOAuthResponse, PasswordResetRequestResponse, PasswordResetResponse, EmailVerificationResponse, SetPasswordResponse } from './types';
2
2
  /**
3
3
  * BaseApi - Universal HTTP client with authentication.
4
4
  *
@@ -83,6 +83,57 @@ export declare class BaseApi {
83
83
  user: User;
84
84
  }>;
85
85
  getCurrentUser(): Promise<User>;
86
+ /**
87
+ * Authenticate with Google OAuth.
88
+ *
89
+ * SECURITY: Requires a nonce for CSRF protection.
90
+ * Use generateOAuthNonce() before initiating Google Sign-In, then pass
91
+ * the same nonce here to prevent CSRF attacks.
92
+ *
93
+ * @param credential - The Google ID token (JWT from Google Sign-In)
94
+ * @param nonce - The CSRF protection nonce (must match what was set in Google Sign-In)
95
+ * @returns GoogleOAuthResponse with tokens and user data
96
+ */
97
+ googleOAuthLogin(credential: string, nonce: string): Promise<GoogleOAuthResponse>;
98
+ /**
99
+ * Request a password reset email (forgot password flow).
100
+ *
101
+ * @param email - The email address to send reset link to
102
+ * @returns Message confirming email was sent (or would be sent)
103
+ */
104
+ requestPasswordReset(email: string): Promise<PasswordResetRequestResponse>;
105
+ /**
106
+ * Reset password using token from email.
107
+ *
108
+ * @param token - The password reset token from the email link
109
+ * @param newPassword - The new password (min 8 characters)
110
+ * @returns Success message
111
+ */
112
+ resetPassword(token: string, newPassword: string): Promise<PasswordResetResponse>;
113
+ /**
114
+ * Verify email address using token from verification email.
115
+ *
116
+ * @param token - The verification token from the email link
117
+ * @returns Success message
118
+ */
119
+ verifyEmail(token: string): Promise<EmailVerificationResponse>;
120
+ /**
121
+ * Request a new verification email.
122
+ *
123
+ * @param email - The email address to send verification to
124
+ * @returns Message confirming email was sent
125
+ */
126
+ requestVerificationEmail(email: string): Promise<EmailVerificationResponse>;
127
+ /**
128
+ * Set password for OAuth-only accounts.
129
+ *
130
+ * Allows users who registered via Google OAuth to set a password
131
+ * so they can also log in with email/password.
132
+ *
133
+ * @param newPassword - The password to set (min 8 characters)
134
+ * @returns Success message
135
+ */
136
+ setPassword(newPassword: string): Promise<SetPasswordResponse>;
86
137
  deleteAccount(password: string, confirmText: string): Promise<{
87
138
  message: string;
88
139
  note: string;
@@ -117,3 +168,32 @@ export declare function createAuthApi(apiBaseUrl: string): BaseApi;
117
168
  export declare const getStoredUser: () => User | null;
118
169
  export declare const getStoredToken: () => string | null;
119
170
  export declare const isAuthenticated: () => boolean;
171
+ /**
172
+ * Generate a cryptographically secure nonce for OAuth CSRF protection.
173
+ *
174
+ * USAGE:
175
+ * 1. Call generateOAuthNonce() before initiating Google Sign-In
176
+ * 2. Pass the nonce to GoogleLogin component via the 'nonce' prop
177
+ * 3. The nonce is automatically stored in sessionStorage
178
+ * 4. When calling googleOAuthLogin(), retrieve it with getOAuthNonce()
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * const nonce = generateOAuthNonce();
183
+ * // Pass to GoogleLogin: <GoogleLogin nonce={nonce} ... />
184
+ * // On success: authApi.googleOAuthLogin(credential, getOAuthNonce()!)
185
+ * ```
186
+ *
187
+ * @returns The generated nonce string
188
+ */
189
+ export declare const generateOAuthNonce: () => string;
190
+ /**
191
+ * Retrieve the stored OAuth nonce.
192
+ *
193
+ * @returns The stored nonce or null if not found
194
+ */
195
+ export declare const getOAuthNonce: () => string | null;
196
+ /**
197
+ * Clear the stored OAuth nonce (call after successful login).
198
+ */
199
+ export declare const clearOAuthNonce: () => void;
@@ -1,2 +1,2 @@
1
- export { BaseApi, createAuthApi, getStoredUser, getStoredToken, isAuthenticated } from './client';
2
- export type { User, ApiKey, ApiKeyCreateResponse, AuthResponse } from './types';
1
+ export { BaseApi, createAuthApi, getStoredUser, getStoredToken, isAuthenticated, generateOAuthNonce, getOAuthNonce, clearOAuthNonce } from './client';
2
+ export type { User, ApiKey, ApiKeyCreateResponse, AuthResponse, GoogleOAuthResponse, PasswordResetRequestResponse, PasswordResetResponse, EmailVerificationResponse, SetPasswordResponse } from './types';
@@ -52,3 +52,42 @@ export interface AuthResponse {
52
52
  refresh_token?: string;
53
53
  user: User;
54
54
  }
55
+ /**
56
+ * Google OAuth login response.
57
+ */
58
+ export interface GoogleOAuthResponse {
59
+ message: string;
60
+ access_token: string;
61
+ refresh_token?: string;
62
+ token_type: string;
63
+ expires_in: number;
64
+ user: User;
65
+ is_new_user: boolean;
66
+ is_account_link?: boolean;
67
+ }
68
+ /**
69
+ * Password reset request response.
70
+ */
71
+ export interface PasswordResetRequestResponse {
72
+ message: string;
73
+ dev_token?: string;
74
+ }
75
+ /**
76
+ * Password reset response.
77
+ */
78
+ export interface PasswordResetResponse {
79
+ message: string;
80
+ }
81
+ /**
82
+ * Email verification response.
83
+ */
84
+ export interface EmailVerificationResponse {
85
+ message: string;
86
+ dev_token?: string;
87
+ }
88
+ /**
89
+ * Set password response (for OAuth-only accounts).
90
+ */
91
+ export interface SetPasswordResponse {
92
+ message: string;
93
+ }
package/dist/index.cjs CHANGED
@@ -345,6 +345,118 @@ class BaseApi {
345
345
  localStorage.setItem("user", JSON.stringify(response.user));
346
346
  return response.user;
347
347
  }
348
+ // ========================================================================
349
+ // GOOGLE OAUTH AUTHENTICATION
350
+ // ========================================================================
351
+ /**
352
+ * Authenticate with Google OAuth.
353
+ *
354
+ * SECURITY: Requires a nonce for CSRF protection.
355
+ * Use generateOAuthNonce() before initiating Google Sign-In, then pass
356
+ * the same nonce here to prevent CSRF attacks.
357
+ *
358
+ * @param credential - The Google ID token (JWT from Google Sign-In)
359
+ * @param nonce - The CSRF protection nonce (must match what was set in Google Sign-In)
360
+ * @returns GoogleOAuthResponse with tokens and user data
361
+ */
362
+ async googleOAuthLogin(credential, nonce) {
363
+ console.log("[API] Google OAuth login attempt");
364
+ const data = await this.request("/api/auth/google/login", {
365
+ method: "POST",
366
+ body: JSON.stringify({ credential, nonce })
367
+ });
368
+ this.token = data.access_token;
369
+ this.refreshToken = data.refresh_token || null;
370
+ localStorage.setItem("auth_token", data.access_token);
371
+ if (data.refresh_token) {
372
+ localStorage.setItem("refresh_token", data.refresh_token);
373
+ }
374
+ localStorage.setItem("user_data", JSON.stringify(data.user));
375
+ this.scheduleProactiveRefresh();
376
+ console.log(`[API] Google OAuth successful (new_user: ${data.is_new_user})`);
377
+ return data;
378
+ }
379
+ // ========================================================================
380
+ // PASSWORD RESET FLOW
381
+ // ========================================================================
382
+ /**
383
+ * Request a password reset email (forgot password flow).
384
+ *
385
+ * @param email - The email address to send reset link to
386
+ * @returns Message confirming email was sent (or would be sent)
387
+ */
388
+ async requestPasswordReset(email) {
389
+ console.log("[API] Requesting password reset for:", email);
390
+ return this.request("/api/auth/request-password-reset", {
391
+ method: "POST",
392
+ body: JSON.stringify({ email })
393
+ });
394
+ }
395
+ /**
396
+ * Reset password using token from email.
397
+ *
398
+ * @param token - The password reset token from the email link
399
+ * @param newPassword - The new password (min 8 characters)
400
+ * @returns Success message
401
+ */
402
+ async resetPassword(token, newPassword) {
403
+ console.log("[API] Resetting password with token");
404
+ return this.request("/api/auth/reset-password", {
405
+ method: "POST",
406
+ body: JSON.stringify({ token, new_password: newPassword })
407
+ });
408
+ }
409
+ // ========================================================================
410
+ // EMAIL VERIFICATION FLOW
411
+ // ========================================================================
412
+ /**
413
+ * Verify email address using token from verification email.
414
+ *
415
+ * @param token - The verification token from the email link
416
+ * @returns Success message
417
+ */
418
+ async verifyEmail(token) {
419
+ console.log("[API] Verifying email with token");
420
+ return this.request("/api/auth/verify-email", {
421
+ method: "POST",
422
+ body: JSON.stringify({ token })
423
+ });
424
+ }
425
+ /**
426
+ * Request a new verification email.
427
+ *
428
+ * @param email - The email address to send verification to
429
+ * @returns Message confirming email was sent
430
+ */
431
+ async requestVerificationEmail(email) {
432
+ console.log("[API] Requesting verification email for:", email);
433
+ return this.request("/api/auth/request-verification-email", {
434
+ method: "POST",
435
+ body: JSON.stringify({ email })
436
+ });
437
+ }
438
+ // ========================================================================
439
+ // SET PASSWORD (FOR OAUTH-ONLY ACCOUNTS)
440
+ // ========================================================================
441
+ /**
442
+ * Set password for OAuth-only accounts.
443
+ *
444
+ * Allows users who registered via Google OAuth to set a password
445
+ * so they can also log in with email/password.
446
+ *
447
+ * @param newPassword - The password to set (min 8 characters)
448
+ * @returns Success message
449
+ */
450
+ async setPassword(newPassword) {
451
+ console.log("[API] Setting password for OAuth account");
452
+ return this.request("/api/auth/set-password", {
453
+ method: "POST",
454
+ body: JSON.stringify({ new_password: newPassword })
455
+ });
456
+ }
457
+ // ========================================================================
458
+ // ACCOUNT MANAGEMENT
459
+ // ========================================================================
348
460
  async deleteAccount(password, confirmText) {
349
461
  console.log("[API] Deleting account (DESTRUCTIVE)");
350
462
  const response = await this.request("/api/auth/me", {
@@ -440,6 +552,21 @@ const getStoredToken = () => {
440
552
  const isAuthenticated = () => {
441
553
  return !!getStoredToken();
442
554
  };
555
+ const generateOAuthNonce = () => {
556
+ const array = new Uint8Array(32);
557
+ crypto.getRandomValues(array);
558
+ const nonce = Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
559
+ sessionStorage.setItem("google_oauth_nonce", nonce);
560
+ console.log("[OAuth] Generated nonce for CSRF protection");
561
+ return nonce;
562
+ };
563
+ const getOAuthNonce = () => {
564
+ return sessionStorage.getItem("google_oauth_nonce");
565
+ };
566
+ const clearOAuthNonce = () => {
567
+ sessionStorage.removeItem("google_oauth_nonce");
568
+ console.log("[OAuth] Cleared nonce");
569
+ };
443
570
  const AuthContext = react.createContext(void 0);
444
571
  const useAuth = () => {
445
572
  const context = react.useContext(AuthContext);
@@ -593,9 +720,12 @@ function createAppRoot(App, config = {}) {
593
720
  );
594
721
  }
595
722
  exports.BaseApi = BaseApi;
723
+ exports.clearOAuthNonce = clearOAuthNonce;
596
724
  exports.createAppRoot = createAppRoot;
597
725
  exports.createAuthApi = createAuthApi;
598
726
  exports.createAuthProvider = createAuthProvider;
727
+ exports.generateOAuthNonce = generateOAuthNonce;
728
+ exports.getOAuthNonce = getOAuthNonce;
599
729
  exports.getStoredToken = getStoredToken;
600
730
  exports.getStoredUser = getStoredUser;
601
731
  exports.isAuthenticated = isAuthenticated;
@@ -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 { User, ApiKey, ApiKeyCreateResponse, AuthResponse } from './types';\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 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/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 * 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 * Authenticate with Google OAuth.\r\n * \r\n * SECURITY: Requires a nonce for CSRF protection.\r\n * Use generateOAuthNonce() before initiating Google Sign-In, then pass\r\n * the same nonce here to prevent CSRF attacks.\r\n * \r\n * @param credential - The Google ID token (JWT from Google Sign-In)\r\n * @param nonce - The CSRF protection nonce (must match what was set in Google Sign-In)\r\n * @returns GoogleOAuthResponse with tokens and user data\r\n */\r\n 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 // 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// ========================================================================\r\n// OAUTH UTILITIES\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() before initiating Google Sign-In\r\n * 2. Pass the nonce to GoogleLogin component via the 'nonce' prop\r\n * 3. The nonce is automatically stored in sessionStorage\r\n * 4. When calling googleOAuthLogin(), retrieve it with getOAuthNonce()\r\n * \r\n * @example\r\n * ```typescript\r\n * const nonce = generateOAuthNonce();\r\n * // Pass to GoogleLogin: <GoogleLogin nonce={nonce} ... />\r\n * // On success: authApi.googleOAuthLogin(credential, getOAuthNonce()!)\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 * \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// 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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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { BaseApi, createAuthApi, getStoredUser, getStoredToken, isAuthenticated } from './api';
2
- export type { User, ApiKey, ApiKeyCreateResponse, AuthResponse } from './api';
1
+ export { BaseApi, createAuthApi, getStoredUser, getStoredToken, isAuthenticated, generateOAuthNonce, getOAuthNonce, clearOAuthNonce } from './api';
2
+ export type { User, ApiKey, ApiKeyCreateResponse, AuthResponse, GoogleOAuthResponse, PasswordResetRequestResponse, PasswordResetResponse, EmailVerificationResponse, SetPasswordResponse } from './api';
3
3
  export { useAuth, createAuthProvider, createAppRoot } from './auth';
4
4
  export type { AuthState, AuthActions, AuthContextType, AuthConfig } from './auth';
package/dist/index.js CHANGED
@@ -343,6 +343,118 @@ class BaseApi {
343
343
  localStorage.setItem("user", JSON.stringify(response.user));
344
344
  return response.user;
345
345
  }
346
+ // ========================================================================
347
+ // GOOGLE OAUTH AUTHENTICATION
348
+ // ========================================================================
349
+ /**
350
+ * Authenticate with Google OAuth.
351
+ *
352
+ * SECURITY: Requires a nonce for CSRF protection.
353
+ * Use generateOAuthNonce() before initiating Google Sign-In, then pass
354
+ * the same nonce here to prevent CSRF attacks.
355
+ *
356
+ * @param credential - The Google ID token (JWT from Google Sign-In)
357
+ * @param nonce - The CSRF protection nonce (must match what was set in Google Sign-In)
358
+ * @returns GoogleOAuthResponse with tokens and user data
359
+ */
360
+ async googleOAuthLogin(credential, nonce) {
361
+ console.log("[API] Google OAuth login attempt");
362
+ const data = await this.request("/api/auth/google/login", {
363
+ method: "POST",
364
+ body: JSON.stringify({ credential, nonce })
365
+ });
366
+ this.token = data.access_token;
367
+ this.refreshToken = data.refresh_token || null;
368
+ localStorage.setItem("auth_token", data.access_token);
369
+ if (data.refresh_token) {
370
+ localStorage.setItem("refresh_token", data.refresh_token);
371
+ }
372
+ localStorage.setItem("user_data", JSON.stringify(data.user));
373
+ this.scheduleProactiveRefresh();
374
+ console.log(`[API] Google OAuth successful (new_user: ${data.is_new_user})`);
375
+ return data;
376
+ }
377
+ // ========================================================================
378
+ // PASSWORD RESET FLOW
379
+ // ========================================================================
380
+ /**
381
+ * Request a password reset email (forgot password flow).
382
+ *
383
+ * @param email - The email address to send reset link to
384
+ * @returns Message confirming email was sent (or would be sent)
385
+ */
386
+ async requestPasswordReset(email) {
387
+ console.log("[API] Requesting password reset for:", email);
388
+ return this.request("/api/auth/request-password-reset", {
389
+ method: "POST",
390
+ body: JSON.stringify({ email })
391
+ });
392
+ }
393
+ /**
394
+ * Reset password using token from email.
395
+ *
396
+ * @param token - The password reset token from the email link
397
+ * @param newPassword - The new password (min 8 characters)
398
+ * @returns Success message
399
+ */
400
+ async resetPassword(token, newPassword) {
401
+ console.log("[API] Resetting password with token");
402
+ return this.request("/api/auth/reset-password", {
403
+ method: "POST",
404
+ body: JSON.stringify({ token, new_password: newPassword })
405
+ });
406
+ }
407
+ // ========================================================================
408
+ // EMAIL VERIFICATION FLOW
409
+ // ========================================================================
410
+ /**
411
+ * Verify email address using token from verification email.
412
+ *
413
+ * @param token - The verification token from the email link
414
+ * @returns Success message
415
+ */
416
+ async verifyEmail(token) {
417
+ console.log("[API] Verifying email with token");
418
+ return this.request("/api/auth/verify-email", {
419
+ method: "POST",
420
+ body: JSON.stringify({ token })
421
+ });
422
+ }
423
+ /**
424
+ * Request a new verification email.
425
+ *
426
+ * @param email - The email address to send verification to
427
+ * @returns Message confirming email was sent
428
+ */
429
+ async requestVerificationEmail(email) {
430
+ console.log("[API] Requesting verification email for:", email);
431
+ return this.request("/api/auth/request-verification-email", {
432
+ method: "POST",
433
+ body: JSON.stringify({ email })
434
+ });
435
+ }
436
+ // ========================================================================
437
+ // SET PASSWORD (FOR OAUTH-ONLY ACCOUNTS)
438
+ // ========================================================================
439
+ /**
440
+ * Set password for OAuth-only accounts.
441
+ *
442
+ * Allows users who registered via Google OAuth to set a password
443
+ * so they can also log in with email/password.
444
+ *
445
+ * @param newPassword - The password to set (min 8 characters)
446
+ * @returns Success message
447
+ */
448
+ async setPassword(newPassword) {
449
+ console.log("[API] Setting password for OAuth account");
450
+ return this.request("/api/auth/set-password", {
451
+ method: "POST",
452
+ body: JSON.stringify({ new_password: newPassword })
453
+ });
454
+ }
455
+ // ========================================================================
456
+ // ACCOUNT MANAGEMENT
457
+ // ========================================================================
346
458
  async deleteAccount(password, confirmText) {
347
459
  console.log("[API] Deleting account (DESTRUCTIVE)");
348
460
  const response = await this.request("/api/auth/me", {
@@ -438,6 +550,21 @@ const getStoredToken = () => {
438
550
  const isAuthenticated = () => {
439
551
  return !!getStoredToken();
440
552
  };
553
+ const generateOAuthNonce = () => {
554
+ const array = new Uint8Array(32);
555
+ crypto.getRandomValues(array);
556
+ const nonce = Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
557
+ sessionStorage.setItem("google_oauth_nonce", nonce);
558
+ console.log("[OAuth] Generated nonce for CSRF protection");
559
+ return nonce;
560
+ };
561
+ const getOAuthNonce = () => {
562
+ return sessionStorage.getItem("google_oauth_nonce");
563
+ };
564
+ const clearOAuthNonce = () => {
565
+ sessionStorage.removeItem("google_oauth_nonce");
566
+ console.log("[OAuth] Cleared nonce");
567
+ };
441
568
  const AuthContext = createContext(void 0);
442
569
  const useAuth = () => {
443
570
  const context = useContext(AuthContext);
@@ -592,9 +719,12 @@ function createAppRoot(App, config = {}) {
592
719
  }
593
720
  export {
594
721
  BaseApi,
722
+ clearOAuthNonce,
595
723
  createAppRoot,
596
724
  createAuthApi,
597
725
  createAuthProvider,
726
+ generateOAuthNonce,
727
+ getOAuthNonce,
598
728
  getStoredToken,
599
729
  getStoredUser,
600
730
  isAuthenticated,
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 { User, ApiKey, ApiKeyCreateResponse, AuthResponse } from './types';\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 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":";;;;AA2CO,MAAM,QAAQ;AAAA,EAUnB,YAAY,YAAoB;AAThC,SAAU,QAAuB;AACjC,SAAU,eAA8B;AACxC,SAAU,eAAwB;AAClC,SAAU,iBAA0C;AACpD,SAAU,wBAA8D;AACxE,SAAU,uBAA8D;AACxE,SAAU,sBAA4C;AAIpD,SAAK,aAAa;AAClB,SAAK,WAAA;AACL,SAAK,uBAAA;AACL,SAAK,qBAAA;AACL,SAAK,0BAAA;AAEL,YAAQ,IAAI,mBAAmB,KAAK,UAAU,EAAE;AAChD,YAAQ,IAAI,sBAA4D,YAAY,EAAE;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,uBAA6B;AACnC,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,iBAAiB,WAAW,CAAC,UAAU;AAC5C,YAAI,MAAM,QAAQ,cAAc;AAC9B,kBAAQ,IAAI,gDAAgD;AAC5D,eAAK,QAAQ,MAAM;AAAA,QACrB,WAAW,MAAM,QAAQ,iBAAiB;AACxC,kBAAQ,IAAI,wDAAwD;AACpE,eAAK,eAAe,MAAM;AAAA,QAC5B,WAAW,MAAM,QAAQ,MAAM;AAE7B,kBAAQ,IAAI,yDAAyD;AACrE,eAAK,QAAQ;AACb,eAAK,eAAe;AACpB,iBAAO,cAAc,IAAI,YAAY,cAAc,CAAC;AAAA,QACtD;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,4BAAkC;AACxC,QAAI,KAAK,sBAAsB;AAC7B,oBAAc,KAAK,oBAAoB;AAAA,IACzC;AAEA,SAAK,uBAAuB,YAAY,MAAM;AAC5C,UAAI,KAAK,OAAO;AACd,aAAK,qBAAA;AAAA,MACP;AAAA,IACF,GAAG,KAAK,GAAI;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,yBAA+B;AACrC,QAAI,OAAO,aAAa,aAAa;AACnC,eAAS,iBAAiB,oBAAoB,MAAM;AAClD,YAAI,SAAS,oBAAoB,aAAa,KAAK,OAAO;AACxD,kBAAQ,IAAI,sDAAsD;AAClE,eAAK,qBAAA;AAAA,QACP;AAAA,MACF,CAAC;AAED,aAAO,iBAAiB,SAAS,MAAM;AACrC,YAAI,KAAK,OAAO;AACd,kBAAQ,IAAI,kDAAkD;AAC9D,eAAK,qBAAA;AAAA,QACP;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAgB,uBAAsC;AACpD,QAAI,CAAC,KAAK,MAAO;AAEjB,UAAM,SAAS,KAAK,eAAe,KAAK,KAAK;AAC7C,QAAI,CAAC,OAAQ;AAEb,UAAM,MAAM,KAAK,IAAA;AACjB,UAAM,kBAAkB,SAAS;AACjC,UAAM,gBAAgB,IAAI,KAAK;AAE/B,QAAI,mBAAmB,GAAG;AACxB,cAAQ,IAAI,gDAAgD;AAC5D,YAAM,KAAK,mBAAA;AAAA,IACb,WAAW,mBAAmB,eAAe;AAC3C,cAAQ,IAAI,0BAA0B,KAAK,MAAM,kBAAkB,GAAI,CAAC,8BAA8B;AACtG,YAAM,KAAK,mBAAA;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKU,eAAe,OAA8B;AACrD,QAAI;AACF,YAAM,UAAU,KAAK,MAAM,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC;AACpD,aAAO,QAAQ,MAAM,QAAQ,MAAM,MAAO;AAAA,IAC5C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKU,2BAAiC;AACzC,QAAI,KAAK,uBAAuB;AAC9B,mBAAa,KAAK,qBAAqB;AACvC,WAAK,wBAAwB;AAAA,IAC/B;AAEA,QAAI,CAAC,KAAK,MAAO;AAEjB,UAAM,SAAS,KAAK,eAAe,KAAK,KAAK;AAC7C,QAAI,CAAC,OAAQ;AAEb,UAAM,MAAM,KAAK,IAAA;AACjB,UAAM,kBAAkB,SAAS;AACjC,UAAM,gBAAgB,IAAI,KAAK;AAE/B,QAAI,mBAAmB,GAAG;AACxB,cAAQ,IAAI,wDAAwD;AACpE,WAAK,mBAAA;AACL;AAAA,IACF;AAEA,QAAI,mBAAmB,eAAe;AACpC,cAAQ,IAAI,sDAAsD;AAClE,WAAK,mBAAA;AACL;AAAA,IACF;AAEA,UAAM,YAAY,kBAAkB;AACpC,YAAQ,IAAI,+CAA+C,KAAK,MAAM,YAAY,GAAK,CAAC,UAAU;AAElG,SAAK,wBAAwB,WAAW,YAAY;AAClD,cAAQ,IAAI,yCAAyC;AACrD,YAAM,KAAK,mBAAA;AAAA,IACb,GAAG,SAAS;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKU,aAAmB;AAC3B,UAAM,cAAc,aAAa,QAAQ,YAAY;AACrD,UAAM,qBAAqB,aAAa,QAAQ,eAAe;AAE/D,QAAI,aAAa;AACf,WAAK,QAAQ;AACb,cAAQ,IAAI,6CAA6C;AAAA,IAC3D;AACA,QAAI,oBAAoB;AACtB,WAAK,eAAe;AACpB,cAAQ,IAAI,8CAA8C;AAAA,IAC5D;AAEA,QAAI,KAAK,OAAO;AACd,WAAK,sBAAsB,KAAK,qBAAA;AAChC,WAAK,yBAAA;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAkC;AACtC,QAAI,KAAK,qBAAqB;AAC5B,YAAM,KAAK;AACX,WAAK,sBAAsB;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAuC;AAC3C,QAAI,KAAK,cAAc;AACrB,aAAO,KAAK,kBAAkB,QAAQ,QAAQ,KAAK;AAAA,IACrD;AAGA,UAAM,qBAAqB,aAAa,QAAQ,eAAe;AAC/D,QAAI,sBAAsB,uBAAuB,KAAK,cAAc;AAClE,cAAQ,IAAI,+CAA+C;AAC3D,WAAK,eAAe;AAAA,IACtB;AAEA,QAAI,CAAC,KAAK,cAAc;AACtB,cAAQ,KAAK,kCAAkC;AAC/C,aAAO;AAAA,IACT;AAEA,SAAK,eAAe;AACpB,SAAK,kBAAkB,YAAY;AACjC,UAAI;AACF,gBAAQ,IAAI,kCAAkC;AAC9C,cAAM,WAAW,MAAM,MAAM,GAAG,KAAK,UAAU,qBAAqB;AAAA,UAClE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAA;AAAA,UAC3B,MAAM,KAAK,UAAU,EAAE,eAAe,KAAK,cAAc;AAAA,QAAA,CAC1D;AAED,YAAI,CAAC,SAAS,IAAI;AAChB,kBAAQ,MAAM,+BAA+B,SAAS,MAAM;AAC5D,cAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACtD,oBAAQ,IAAI,gDAAgD;AAC5D,iBAAK,UAAA;AAAA,UACP,OAAO;AACL,oBAAQ,KAAK,6BAA6B,SAAS,MAAM,+BAA+B;AAAA,UAC1F;AACA,iBAAO;AAAA,QACT;AAEA,cAAM,OAAO,MAAM,SAAS,KAAA;AAE5B,aAAK,QAAQ,KAAK;AAClB,qBAAa,QAAQ,cAAc,KAAK,YAAY;AAEpD,YAAI,KAAK,eAAe;AACtB,eAAK,eAAe,KAAK;AACzB,uBAAa,QAAQ,iBAAiB,KAAK,aAAa;AACxD,kBAAQ,IAAI,6BAA6B;AAAA,QAC3C;AAEA,aAAK,yBAAA;AACL,gBAAQ,IAAI,2CAA2C;AACvD,eAAO;AAAA,MACT,SAAS,OAAO;AACd,gBAAQ,MAAM,sCAAsC,KAAK;AACzD,gBAAQ,KAAK,2CAA2C;AACxD,eAAO;AAAA,MACT,UAAA;AACE,aAAK,eAAe;AACpB,aAAK,iBAAiB;AAAA,MACxB;AAAA,IACF,GAAA;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,QAAW,UAAkB,UAAuB,CAAA,GAAI,UAAU,OAAO,aAAa,GAAe;AACzG,UAAM,KAAK,iBAAA;AAGX,QAAI,KAAK,SAAS,CAAC,WAAW,CAAC,SAAS,SAAS,QAAQ,GAAG;AAC1D,YAAM,SAAS,KAAK,eAAe,KAAK,KAAK;AAC7C,UAAI,QAAQ;AACV,cAAM,MAAM,KAAK,IAAA;AACjB,cAAM,kBAAkB,SAAS;AACjC,YAAI,kBAAkB,IAAI,KAAK,KAAM;AACnC,kBAAQ,IAAI,0BAA0B,KAAK,MAAM,kBAAgB,GAAI,CAAC,iCAAiC;AACvG,gBAAM,KAAK,mBAAA;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM,GAAG,KAAK,UAAU,GAAG,QAAQ;AAEzC,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,GAAI,QAAQ;AAAA,IAAA;AAGd,QAAI,KAAK,OAAO;AACd,cAAQ,gBAAgB,UAAU,KAAK,KAAK;AAAA,IAC9C;AAEA,YAAQ,IAAI,iBAAiB,QAAQ,UAAU,KAAK,IAAI,GAAG,EAAE;AAE7D,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK,EAAE,GAAG,SAAS,SAAS;AAEzD,cAAQ,IAAI,kBAAkB,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAEtE,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,QAAQ,MAAM,SAAS,KAAA;AAC7B,gBAAQ,MAAM,eAAe,SAAS,MAAM,KAAK,KAAK,EAAE;AAGxD,YAAI,SAAS,WAAW,OAAO,aAAa,GAAG;AAC7C,gBAAM,YAAY,KAAK,IAAI,GAAG,UAAU,IAAI;AAC5C,kBAAQ,IAAI,yCAAyC,SAAS,eAAe,aAAa,CAAC,QAAQ;AACnG,gBAAM,IAAI,QAAQ,CAAA,YAAW,WAAW,SAAS,SAAS,CAAC;AAC3D,iBAAO,KAAK,QAAW,UAAU,SAAS,SAAS,aAAa,CAAC;AAAA,QACnE;AAGA,YAAI,SAAS,WAAW,OAAO,CAAC,WAAW,CAAC,SAAS,SAAS,aAAa,KAAK,CAAC,SAAS,SAAS,gBAAgB,KAAK,CAAC,SAAS,SAAS,eAAe,GAAG;AAC3J,kBAAQ,IAAI,mCAAmC;AAC/C,gBAAM,YAAY,MAAM,KAAK,mBAAA;AAC7B,cAAI,WAAW;AACb,mBAAO,KAAK,QAAW,UAAU,SAAS,MAAM,UAAU;AAAA,UAC5D;AACA,kBAAQ,KAAK,uDAAuD;AAAA,QACtE;AAEA,cAAM,WAAW,IAAI,MAAM,cAAc,SAAS,MAAM,MAAM,KAAK,EAAE;AACrE,iBAAS,SAAS,SAAS;AAC3B,cAAM;AAAA,MACR;AAEA,aAAO,SAAS,KAAA;AAAA,IAClB,SAAS,OAAO;AACd,cAAQ,MAAM,2BAA2B,KAAK;AAC9C,UAAI,iBAAiB,aAAa,MAAM,QAAQ,SAAS,iBAAiB,GAAG;AAC3E,cAAM,IAAI,MAAM,0BAA0B,KAAK,UAAU,EAAE;AAAA,MAC7D;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,OAAe,UAAyC;AAClE,YAAQ,IAAI,wBAAwB,KAAK;AAEzC,UAAM,OAAO,MAAM,KAAK,QAAsB,mBAAmB;AAAA,MAC/D,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,OAAO,UAAU;AAAA,IAAA,CACzC;AAED,SAAK,QAAQ,KAAK;AAClB,SAAK,eAAe,KAAK,iBAAiB;AAC1C,iBAAa,QAAQ,cAAc,KAAK,YAAY;AACpD,QAAI,KAAK,eAAe;AACtB,mBAAa,QAAQ,iBAAiB,KAAK,aAAa;AAAA,IAC1D;AACA,iBAAa,QAAQ,aAAa,KAAK,UAAU,KAAK,IAAI,CAAC;AAE3D,SAAK,yBAAA;AACL,YAAQ,IAAI,wBAAwB;AACpC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS,OAAe,UAAkB,WAAmB,UAAyC;AAC1G,YAAQ,IAAI,+BAA+B,KAAK;AAEhD,UAAM,OAAO,MAAM,KAAK,QAAsB,sBAAsB;AAAA,MAClE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA;AAAA,QACA,YAAY;AAAA,QACZ,WAAW;AAAA,MAAA,CACZ;AAAA,IAAA,CACF;AAED,SAAK,QAAQ,KAAK;AAClB,SAAK,eAAe,KAAK,iBAAiB;AAC1C,iBAAa,QAAQ,cAAc,KAAK,YAAY;AACpD,QAAI,KAAK,eAAe;AACtB,mBAAa,QAAQ,iBAAiB,KAAK,aAAa;AAAA,IAC1D;AACA,iBAAa,QAAQ,aAAa,KAAK,UAAU,KAAK,IAAI,CAAC;AAE3D,SAAK,yBAAA;AACL,YAAQ,IAAI,+BAA+B;AAC3C,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAiC;AACrC,YAAQ,IAAI,6BAA6B;AACzC,WAAO,KAAK,QAAwB,cAAc;AAAA,EACpD;AAAA,EAEA,MAAM,iBAAgC;AACpC,YAAQ,IAAI,kCAAkC;AAC9C,UAAM,WAAW,MAAM,KAAK,QAAwB,cAAc;AAClE,iBAAa,QAAQ,QAAQ,KAAK,UAAU,SAAS,IAAI,CAAC;AAC1D,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,MAAM,cAAc,UAAkB,aAAiE;AACrG,YAAQ,IAAI,sCAAsC;AAElD,UAAM,WAAW,MAAM,KAAK,QAA2C,gBAAgB;AAAA,MACrF,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA,cAAc;AAAA,MAAA,CACf;AAAA,IAAA,CACF;AAED,SAAK,UAAA;AACL,YAAQ,IAAI,oCAAoC;AAChD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAwB;AAC5B,YAAQ,IAAI,mBAAmB;AAE/B,QAAI,KAAK,cAAc;AACrB,UAAI;AACF,cAAM,KAAK,QAAQ,oBAAoB;AAAA,UACrC,QAAQ;AAAA,UACR,MAAM,KAAK,UAAU,EAAE,eAAe,KAAK,cAAc;AAAA,QAAA,CAC1D;AACD,gBAAQ,IAAI,uCAAuC;AAAA,MACrD,SAAS,OAAO;AACd,gBAAQ,KAAK,mDAAmD,KAAK;AAAA,MACvE;AAAA,IACF;AAEA,SAAK,UAAA;AAAA,EACP;AAAA,EAEA,MAAM,mBAAkC;AACtC,YAAQ,IAAI,oCAAoC;AAEhD,QAAI;AACF,YAAM,KAAK,QAAQ,wBAAwB,EAAE,QAAQ,QAAQ;AAC7D,cAAQ,IAAI,sCAAsC;AAAA,IACpD,SAAS,OAAO;AACd,cAAQ,KAAK,wCAAwC,KAAK;AAAA,IAC5D;AAEA,SAAK,UAAA;AAAA,EACP;AAAA,EAEU,YAAkB;AAC1B,QAAI,KAAK,uBAAuB;AAC9B,mBAAa,KAAK,qBAAqB;AACvC,WAAK,wBAAwB;AAAA,IAC/B;AAEA,SAAK,QAAQ;AACb,SAAK,eAAe;AACpB,iBAAa,WAAW,YAAY;AACpC,iBAAa,WAAW,eAAe;AACvC,iBAAa,WAAW,WAAW;AACnC,YAAQ,IAAI,0BAA0B;AAEtC,WAAO,cAAc,IAAI,YAAY,cAAc,CAAC;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAA8D;AAClE,YAAQ,IAAI,6BAA6B;AACzC,WAAO,KAAK,QAAQ,sBAAsB,EAAE,QAAQ,OAAO;AAAA,EAC7D;AAAA,EAEA,MAAM,aAAa,MAKe;AAChC,YAAQ,IAAI,2BAA2B,KAAK,IAAI,EAAE;AAClD,WAAO,KAAK,QAAQ,sBAAsB;AAAA,MACxC,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU;AAAA,QACnB,MAAM,KAAK;AAAA,QACX,QAAQ,KAAK,UAAU;AAAA,QACvB,iBAAiB,KAAK;AAAA,QACtB,uBAAuB,KAAK,yBAAyB;AAAA,MAAA,CACtD;AAAA,IAAA,CACF;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,OAAqF;AACtG,YAAQ,IAAI,2BAA2B,KAAK,EAAE;AAC9C,WAAO,KAAK,QAAQ,sBAAsB,KAAK,IAAI,EAAE,QAAQ,UAAU;AAAA,EACzE;AACF;AAuBO,SAAS,cAAc,YAA6B;AACzD,SAAO,IAAI,QAAQ,UAAU;AAC/B;AAMO,MAAM,gBAAgB,MAAmB;AAC9C,QAAM,WAAW,aAAa,QAAQ,WAAW;AACjD,MAAI,CAAC,SAAU,QAAO;AAEtB,MAAI;AACF,WAAO,KAAK,MAAM,QAAQ;AAAA,EAC5B,SAAS,OAAO;AACd,YAAQ,MAAM,4CAA4C,KAAK;AAC/D,iBAAa,WAAW,WAAW;AACnC,WAAO;AAAA,EACT;AACF;AAEO,MAAM,iBAAiB,MAAqB;AACjD,SAAO,aAAa,QAAQ,YAAY;AAC1C;AAEO,MAAM,kBAAkB,MAAe;AAC5C,SAAO,CAAC,CAAC,eAAA;AACX;ACziBA,MAAM,cAAc,cAA2C,MAAS;AAMjE,MAAM,UAAU,MAAM;AAC3B,QAAM,UAAU,WAAW,WAAW;AACtC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,SAAO;AACT;AAmBO,SAAS,mBAAmB,KAAc;AAC/C,QAAM,eAAwD,CAAC,EAAE,eAAe;AAC9E,UAAM,CAAC,OAAO,QAAQ,IAAI,SAAoB;AAAA,MAC5C,MAAM,cAAA;AAAA,MACN,WAAW;AAAA,MACX,iBAAiB,CAAC,CAAC,eAAA;AAAA,MACnB,OAAO;AAAA,IAAA,CACR;AAGD,cAAU,MAAM;AACd,YAAM,oBAAoB,MAAM;AAC9B,gBAAQ,IAAI,6DAA6D;AACzE,iBAAS;AAAA,UACP,MAAM;AAAA,UACN,iBAAiB;AAAA,UACjB,WAAW;AAAA,UACX,OAAO;AAAA,QAAA,CACR;AAAA,MACH;AAEA,aAAO,iBAAiB,gBAAgB,iBAAiB;AACzD,aAAO,MAAM,OAAO,oBAAoB,gBAAgB,iBAAiB;AAAA,IAC3E,GAAG,CAAA,CAAE;AAGL,cAAU,MAAM;AACd,YAAM,kBAAkB,YAAY;AAClC,cAAM,QAAQ,eAAA;AACd,YAAI,OAAO;AACT,cAAI;AACF,kBAAM,cAAc,MAAM,IAAI,eAAA;AAC9B,qBAAS,CAAA,UAAS;AAAA,cAChB,GAAG;AAAA,cACH,MAAM;AAAA,cACN,iBAAiB;AAAA,YAAA,EACjB;AAAA,UACJ,SAAS,OAAgB;AACvB,oBAAQ,MAAM,uCAAuC,KAAK;AAC1D,kBAAM,YAAY;AAClB,iBAAI,uCAAW,YAAW,QAAO,uCAAW,YAAW,KAAK;AAC1D,sBAAQ,KAAK,gDAAgD;AAC7D,2BAAa,WAAW,YAAY;AACpC,2BAAa,WAAW,WAAW;AACnC,uBAAS;AAAA,gBACP,MAAM;AAAA,gBACN,iBAAiB;AAAA,gBACjB,WAAW;AAAA,gBACX,OAAO;AAAA,cAAA,CACR;AAAA,YACH,OAAO;AACL,sBAAQ,KAAK,gDAAgD;AAAA,YAC/D;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,sBAAA;AAAA,IACF,GAAG,CAAA,CAAE;AAEL,UAAM,QAAQ,OAAO,OAAe,aAAuC;AACzE,eAAS,CAAA,UAAS,EAAE,GAAG,MAAM,WAAW,MAAM,OAAO,OAAO;AAE5D,UAAI;AACF,cAAM,SAAS,MAAM,IAAI,MAAM,OAAO,QAAQ;AAC9C,iBAAS,CAAA,UAAS;AAAA,UAChB,GAAG;AAAA,UACH,MAAM,OAAO;AAAA,UACb,iBAAiB;AAAA,UACjB,WAAW;AAAA,QAAA,EACX;AACF,eAAO;AAAA,MACT,SAAS,OAAO;AACd,iBAAS,CAAA,UAAS;AAAA,UAChB,GAAG;AAAA,UACH,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UAChD,WAAW;AAAA,QAAA,EACX;AACF,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,WAAW,OAAO,OAAe,UAAkB,WAAmB,aAAuC;AACjH,eAAS,CAAA,UAAS,EAAE,GAAG,MAAM,WAAW,MAAM,OAAO,OAAO;AAE5D,UAAI;AACF,cAAM,SAAS,MAAM,IAAI,SAAS,OAAO,UAAU,WAAW,QAAQ;AACtE,iBAAS,CAAA,UAAS;AAAA,UAChB,GAAG;AAAA,UACH,MAAM,OAAO;AAAA,UACb,iBAAiB;AAAA,UACjB,WAAW;AAAA,QAAA,EACX;AACF,eAAO;AAAA,MACT,SAAS,OAAO;AACd,iBAAS,CAAA,UAAS;AAAA,UAChB,GAAG;AAAA,UACH,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UAChD,WAAW;AAAA,QAAA,EACX;AACF,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,SAAS,MAAM;AACnB,UAAI,OAAA;AACJ,eAAS;AAAA,QACP,MAAM;AAAA,QACN,iBAAiB;AAAA,QACjB,WAAW;AAAA,QACX,OAAO;AAAA,MAAA,CACR;AAAA,IACH;AAEA,UAAM,aAAa,MAAM;AACvB,eAAS,WAAS,EAAE,GAAG,MAAM,OAAO,OAAO;AAAA,IAC7C;AAEA,UAAM,cAAc,YAAY;AAC9B,UAAI;AACF,cAAM,cAAc,MAAM,IAAI,eAAA;AAC9B,iBAAS,CAAA,UAAS;AAAA,UAChB,GAAG;AAAA,UACH,MAAM;AAAA,QAAA,EACN;AAAA,MACJ,SAAS,OAAO;AACd,gBAAQ,MAAM,kCAAkC,KAAK;AAAA,MACvD;AAAA,IACF;AAEA,WACE;AAAA,MAAC,YAAY;AAAA,MAAZ;AAAA,QACC,OAAO;AAAA,UACL,GAAG;AAAA,UACH;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAGD;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AAEA,SAAO;AACT;ACnMO,SAAS,cACd,KACA,SAAqB,IACf;AACN,QAAM,EAAE,mBAAmB;AAE3B,QAAM,OAAO,WAAW,SAAS,eAAe,MAAM,CAAE;AAGxD,MAAI,iCAAkC,KAAA,EAAI;AAG1C,MAAI,gBAAgB;AAClB,iBACE,oBAAC,qBAAA,EAAoB,UAAU,gBAC5B,UAAA,YACH;AAAA,EAEJ;AAGA,OAAK;AAAA,IACH,oBAAC,cACE,UAAA,WAAA,CACH;AAAA,EAAA;AAEJ;"}
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 * 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 * Authenticate with Google OAuth.\r\n * \r\n * SECURITY: Requires a nonce for CSRF protection.\r\n * Use generateOAuthNonce() before initiating Google Sign-In, then pass\r\n * the same nonce here to prevent CSRF attacks.\r\n * \r\n * @param credential - The Google ID token (JWT from Google Sign-In)\r\n * @param nonce - The CSRF protection nonce (must match what was set in Google Sign-In)\r\n * @returns GoogleOAuthResponse with tokens and user data\r\n */\r\n 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 // 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// ========================================================================\r\n// OAUTH UTILITIES\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() before initiating Google Sign-In\r\n * 2. Pass the nonce to GoogleLogin component via the 'nonce' prop\r\n * 3. The nonce is automatically stored in sessionStorage\r\n * 4. When calling googleOAuthLogin(), retrieve it with getOAuthNonce()\r\n * \r\n * @example\r\n * ```typescript\r\n * const nonce = generateOAuthNonce();\r\n * // Pass to GoogleLogin: <GoogleLogin nonce={nonce} ... />\r\n * // On success: authApi.googleOAuthLogin(credential, getOAuthNonce()!)\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 * \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// 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":";;;;AAqDO,MAAM,QAAQ;AAAA,EAUnB,YAAY,YAAoB;AAThC,SAAU,QAAuB;AACjC,SAAU,eAA8B;AACxC,SAAU,eAAwB;AAClC,SAAU,iBAA0C;AACpD,SAAU,wBAA8D;AACxE,SAAU,uBAA8D;AACxE,SAAU,sBAA4C;AAIpD,SAAK,aAAa;AAClB,SAAK,WAAA;AACL,SAAK,uBAAA;AACL,SAAK,qBAAA;AACL,SAAK,0BAAA;AAEL,YAAQ,IAAI,mBAAmB,KAAK,UAAU,EAAE;AAChD,YAAQ,IAAI,sBAA4D,YAAY,EAAE;AAAA,EACxF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,uBAA6B;AACnC,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,iBAAiB,WAAW,CAAC,UAAU;AAC5C,YAAI,MAAM,QAAQ,cAAc;AAC9B,kBAAQ,IAAI,gDAAgD;AAC5D,eAAK,QAAQ,MAAM;AAAA,QACrB,WAAW,MAAM,QAAQ,iBAAiB;AACxC,kBAAQ,IAAI,wDAAwD;AACpE,eAAK,eAAe,MAAM;AAAA,QAC5B,WAAW,MAAM,QAAQ,MAAM;AAE7B,kBAAQ,IAAI,yDAAyD;AACrE,eAAK,QAAQ;AACb,eAAK,eAAe;AACpB,iBAAO,cAAc,IAAI,YAAY,cAAc,CAAC;AAAA,QACtD;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,4BAAkC;AACxC,QAAI,KAAK,sBAAsB;AAC7B,oBAAc,KAAK,oBAAoB;AAAA,IACzC;AAEA,SAAK,uBAAuB,YAAY,MAAM;AAC5C,UAAI,KAAK,OAAO;AACd,aAAK,qBAAA;AAAA,MACP;AAAA,IACF,GAAG,KAAK,GAAI;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKQ,yBAA+B;AACrC,QAAI,OAAO,aAAa,aAAa;AACnC,eAAS,iBAAiB,oBAAoB,MAAM;AAClD,YAAI,SAAS,oBAAoB,aAAa,KAAK,OAAO;AACxD,kBAAQ,IAAI,sDAAsD;AAClE,eAAK,qBAAA;AAAA,QACP;AAAA,MACF,CAAC;AAED,aAAO,iBAAiB,SAAS,MAAM;AACrC,YAAI,KAAK,OAAO;AACd,kBAAQ,IAAI,kDAAkD;AAC9D,eAAK,qBAAA;AAAA,QACP;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAgB,uBAAsC;AACpD,QAAI,CAAC,KAAK,MAAO;AAEjB,UAAM,SAAS,KAAK,eAAe,KAAK,KAAK;AAC7C,QAAI,CAAC,OAAQ;AAEb,UAAM,MAAM,KAAK,IAAA;AACjB,UAAM,kBAAkB,SAAS;AACjC,UAAM,gBAAgB,IAAI,KAAK;AAE/B,QAAI,mBAAmB,GAAG;AACxB,cAAQ,IAAI,gDAAgD;AAC5D,YAAM,KAAK,mBAAA;AAAA,IACb,WAAW,mBAAmB,eAAe;AAC3C,cAAQ,IAAI,0BAA0B,KAAK,MAAM,kBAAkB,GAAI,CAAC,8BAA8B;AACtG,YAAM,KAAK,mBAAA;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKU,eAAe,OAA8B;AACrD,QAAI;AACF,YAAM,UAAU,KAAK,MAAM,KAAK,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC;AACpD,aAAO,QAAQ,MAAM,QAAQ,MAAM,MAAO;AAAA,IAC5C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKU,2BAAiC;AACzC,QAAI,KAAK,uBAAuB;AAC9B,mBAAa,KAAK,qBAAqB;AACvC,WAAK,wBAAwB;AAAA,IAC/B;AAEA,QAAI,CAAC,KAAK,MAAO;AAEjB,UAAM,SAAS,KAAK,eAAe,KAAK,KAAK;AAC7C,QAAI,CAAC,OAAQ;AAEb,UAAM,MAAM,KAAK,IAAA;AACjB,UAAM,kBAAkB,SAAS;AACjC,UAAM,gBAAgB,IAAI,KAAK;AAE/B,QAAI,mBAAmB,GAAG;AACxB,cAAQ,IAAI,wDAAwD;AACpE,WAAK,mBAAA;AACL;AAAA,IACF;AAEA,QAAI,mBAAmB,eAAe;AACpC,cAAQ,IAAI,sDAAsD;AAClE,WAAK,mBAAA;AACL;AAAA,IACF;AAEA,UAAM,YAAY,kBAAkB;AACpC,YAAQ,IAAI,+CAA+C,KAAK,MAAM,YAAY,GAAK,CAAC,UAAU;AAElG,SAAK,wBAAwB,WAAW,YAAY;AAClD,cAAQ,IAAI,yCAAyC;AACrD,YAAM,KAAK,mBAAA;AAAA,IACb,GAAG,SAAS;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKU,aAAmB;AAC3B,UAAM,cAAc,aAAa,QAAQ,YAAY;AACrD,UAAM,qBAAqB,aAAa,QAAQ,eAAe;AAE/D,QAAI,aAAa;AACf,WAAK,QAAQ;AACb,cAAQ,IAAI,6CAA6C;AAAA,IAC3D;AACA,QAAI,oBAAoB;AACtB,WAAK,eAAe;AACpB,cAAQ,IAAI,8CAA8C;AAAA,IAC5D;AAEA,QAAI,KAAK,OAAO;AACd,WAAK,sBAAsB,KAAK,qBAAA;AAChC,WAAK,yBAAA;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAkC;AACtC,QAAI,KAAK,qBAAqB;AAC5B,YAAM,KAAK;AACX,WAAK,sBAAsB;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAuC;AAC3C,QAAI,KAAK,cAAc;AACrB,aAAO,KAAK,kBAAkB,QAAQ,QAAQ,KAAK;AAAA,IACrD;AAGA,UAAM,qBAAqB,aAAa,QAAQ,eAAe;AAC/D,QAAI,sBAAsB,uBAAuB,KAAK,cAAc;AAClE,cAAQ,IAAI,+CAA+C;AAC3D,WAAK,eAAe;AAAA,IACtB;AAEA,QAAI,CAAC,KAAK,cAAc;AACtB,cAAQ,KAAK,kCAAkC;AAC/C,aAAO;AAAA,IACT;AAEA,SAAK,eAAe;AACpB,SAAK,kBAAkB,YAAY;AACjC,UAAI;AACF,gBAAQ,IAAI,kCAAkC;AAC9C,cAAM,WAAW,MAAM,MAAM,GAAG,KAAK,UAAU,qBAAqB;AAAA,UAClE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAA;AAAA,UAC3B,MAAM,KAAK,UAAU,EAAE,eAAe,KAAK,cAAc;AAAA,QAAA,CAC1D;AAED,YAAI,CAAC,SAAS,IAAI;AAChB,kBAAQ,MAAM,+BAA+B,SAAS,MAAM;AAC5D,cAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK;AACtD,oBAAQ,IAAI,gDAAgD;AAC5D,iBAAK,UAAA;AAAA,UACP,OAAO;AACL,oBAAQ,KAAK,6BAA6B,SAAS,MAAM,+BAA+B;AAAA,UAC1F;AACA,iBAAO;AAAA,QACT;AAEA,cAAM,OAAO,MAAM,SAAS,KAAA;AAE5B,aAAK,QAAQ,KAAK;AAClB,qBAAa,QAAQ,cAAc,KAAK,YAAY;AAEpD,YAAI,KAAK,eAAe;AACtB,eAAK,eAAe,KAAK;AACzB,uBAAa,QAAQ,iBAAiB,KAAK,aAAa;AACxD,kBAAQ,IAAI,6BAA6B;AAAA,QAC3C;AAEA,aAAK,yBAAA;AACL,gBAAQ,IAAI,2CAA2C;AACvD,eAAO;AAAA,MACT,SAAS,OAAO;AACd,gBAAQ,MAAM,sCAAsC,KAAK;AACzD,gBAAQ,KAAK,2CAA2C;AACxD,eAAO;AAAA,MACT,UAAA;AACE,aAAK,eAAe;AACpB,aAAK,iBAAiB;AAAA,MACxB;AAAA,IACF,GAAA;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,QAAW,UAAkB,UAAuB,CAAA,GAAI,UAAU,OAAO,aAAa,GAAe;AACzG,UAAM,KAAK,iBAAA;AAGX,QAAI,KAAK,SAAS,CAAC,WAAW,CAAC,SAAS,SAAS,QAAQ,GAAG;AAC1D,YAAM,SAAS,KAAK,eAAe,KAAK,KAAK;AAC7C,UAAI,QAAQ;AACV,cAAM,MAAM,KAAK,IAAA;AACjB,cAAM,kBAAkB,SAAS;AACjC,YAAI,kBAAkB,IAAI,KAAK,KAAM;AACnC,kBAAQ,IAAI,0BAA0B,KAAK,MAAM,kBAAgB,GAAI,CAAC,iCAAiC;AACvG,gBAAM,KAAK,mBAAA;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM,GAAG,KAAK,UAAU,GAAG,QAAQ;AAEzC,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,GAAI,QAAQ;AAAA,IAAA;AAGd,QAAI,KAAK,OAAO;AACd,cAAQ,gBAAgB,UAAU,KAAK,KAAK;AAAA,IAC9C;AAEA,YAAQ,IAAI,iBAAiB,QAAQ,UAAU,KAAK,IAAI,GAAG,EAAE;AAE7D,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,KAAK,EAAE,GAAG,SAAS,SAAS;AAEzD,cAAQ,IAAI,kBAAkB,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAEtE,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,QAAQ,MAAM,SAAS,KAAA;AAC7B,gBAAQ,MAAM,eAAe,SAAS,MAAM,KAAK,KAAK,EAAE;AAGxD,YAAI,SAAS,WAAW,OAAO,aAAa,GAAG;AAC7C,gBAAM,YAAY,KAAK,IAAI,GAAG,UAAU,IAAI;AAC5C,kBAAQ,IAAI,yCAAyC,SAAS,eAAe,aAAa,CAAC,QAAQ;AACnG,gBAAM,IAAI,QAAQ,CAAA,YAAW,WAAW,SAAS,SAAS,CAAC;AAC3D,iBAAO,KAAK,QAAW,UAAU,SAAS,SAAS,aAAa,CAAC;AAAA,QACnE;AAGA,YAAI,SAAS,WAAW,OAAO,CAAC,WAAW,CAAC,SAAS,SAAS,aAAa,KAAK,CAAC,SAAS,SAAS,gBAAgB,KAAK,CAAC,SAAS,SAAS,eAAe,GAAG;AAC3J,kBAAQ,IAAI,mCAAmC;AAC/C,gBAAM,YAAY,MAAM,KAAK,mBAAA;AAC7B,cAAI,WAAW;AACb,mBAAO,KAAK,QAAW,UAAU,SAAS,MAAM,UAAU;AAAA,UAC5D;AACA,kBAAQ,KAAK,uDAAuD;AAAA,QACtE;AAEA,cAAM,WAAW,IAAI,MAAM,cAAc,SAAS,MAAM,MAAM,KAAK,EAAE;AACrE,iBAAS,SAAS,SAAS;AAC3B,cAAM;AAAA,MACR;AAEA,aAAO,SAAS,KAAA;AAAA,IAClB,SAAS,OAAO;AACd,cAAQ,MAAM,2BAA2B,KAAK;AAC9C,UAAI,iBAAiB,aAAa,MAAM,QAAQ,SAAS,iBAAiB,GAAG;AAC3E,cAAM,IAAI,MAAM,0BAA0B,KAAK,UAAU,EAAE;AAAA,MAC7D;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,OAAe,UAAyC;AAClE,YAAQ,IAAI,wBAAwB,KAAK;AAEzC,UAAM,OAAO,MAAM,KAAK,QAAsB,mBAAmB;AAAA,MAC/D,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,OAAO,UAAU;AAAA,IAAA,CACzC;AAED,SAAK,QAAQ,KAAK;AAClB,SAAK,eAAe,KAAK,iBAAiB;AAC1C,iBAAa,QAAQ,cAAc,KAAK,YAAY;AACpD,QAAI,KAAK,eAAe;AACtB,mBAAa,QAAQ,iBAAiB,KAAK,aAAa;AAAA,IAC1D;AACA,iBAAa,QAAQ,aAAa,KAAK,UAAU,KAAK,IAAI,CAAC;AAE3D,SAAK,yBAAA;AACL,YAAQ,IAAI,wBAAwB;AACpC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAS,OAAe,UAAkB,WAAmB,UAAyC;AAC1G,YAAQ,IAAI,+BAA+B,KAAK;AAEhD,UAAM,OAAO,MAAM,KAAK,QAAsB,sBAAsB;AAAA,MAClE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA;AAAA,QACA,YAAY;AAAA,QACZ,WAAW;AAAA,MAAA,CACZ;AAAA,IAAA,CACF;AAED,SAAK,QAAQ,KAAK;AAClB,SAAK,eAAe,KAAK,iBAAiB;AAC1C,iBAAa,QAAQ,cAAc,KAAK,YAAY;AACpD,QAAI,KAAK,eAAe;AACtB,mBAAa,QAAQ,iBAAiB,KAAK,aAAa;AAAA,IAC1D;AACA,iBAAa,QAAQ,aAAa,KAAK,UAAU,KAAK,IAAI,CAAC;AAE3D,SAAK,yBAAA;AACL,YAAQ,IAAI,+BAA+B;AAC3C,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAiC;AACrC,YAAQ,IAAI,6BAA6B;AACzC,WAAO,KAAK,QAAwB,cAAc;AAAA,EACpD;AAAA,EAEA,MAAM,iBAAgC;AACpC,YAAQ,IAAI,kCAAkC;AAC9C,UAAM,WAAW,MAAM,KAAK,QAAwB,cAAc;AAClE,iBAAa,QAAQ,QAAQ,KAAK,UAAU,SAAS,IAAI,CAAC;AAC1D,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,iBAAiB,YAAoB,OAA6C;AACtF,YAAQ,IAAI,kCAAkC;AAE9C,UAAM,OAAO,MAAM,KAAK,QAA6B,0BAA0B;AAAA,MAC7E,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,YAAY,OAAO;AAAA,IAAA,CAC3C;AAED,SAAK,QAAQ,KAAK;AAClB,SAAK,eAAe,KAAK,iBAAiB;AAC1C,iBAAa,QAAQ,cAAc,KAAK,YAAY;AACpD,QAAI,KAAK,eAAe;AACtB,mBAAa,QAAQ,iBAAiB,KAAK,aAAa;AAAA,IAC1D;AACA,iBAAa,QAAQ,aAAa,KAAK,UAAU,KAAK,IAAI,CAAC;AAE3D,SAAK,yBAAA;AACL,YAAQ,IAAI,4CAA4C,KAAK,WAAW,GAAG;AAC3E,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,qBAAqB,OAAsD;AAC/E,YAAQ,IAAI,wCAAwC,KAAK;AAEzD,WAAO,KAAK,QAAsC,oCAAoC;AAAA,MACpF,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,OAAO;AAAA,IAAA,CAC/B;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,cAAc,OAAe,aAAqD;AACtF,YAAQ,IAAI,qCAAqC;AAEjD,WAAO,KAAK,QAA+B,4BAA4B;AAAA,MACrE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,OAAO,cAAc,aAAa;AAAA,IAAA,CAC1D;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,YAAY,OAAmD;AACnE,YAAQ,IAAI,kCAAkC;AAE9C,WAAO,KAAK,QAAmC,0BAA0B;AAAA,MACvE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,OAAO;AAAA,IAAA,CAC/B;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,yBAAyB,OAAmD;AAChF,YAAQ,IAAI,4CAA4C,KAAK;AAE7D,WAAO,KAAK,QAAmC,wCAAwC;AAAA,MACrF,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,OAAO;AAAA,IAAA,CAC/B;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,YAAY,aAAmD;AACnE,YAAQ,IAAI,0CAA0C;AAEtD,WAAO,KAAK,QAA6B,0BAA0B;AAAA,MACjE,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,cAAc,aAAa;AAAA,IAAA,CACnD;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,UAAkB,aAAiE;AACrG,YAAQ,IAAI,sCAAsC;AAElD,UAAM,WAAW,MAAM,KAAK,QAA2C,gBAAgB;AAAA,MACrF,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA,cAAc;AAAA,MAAA,CACf;AAAA,IAAA,CACF;AAED,SAAK,UAAA;AACL,YAAQ,IAAI,oCAAoC;AAChD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,SAAwB;AAC5B,YAAQ,IAAI,mBAAmB;AAE/B,QAAI,KAAK,cAAc;AACrB,UAAI;AACF,cAAM,KAAK,QAAQ,oBAAoB;AAAA,UACrC,QAAQ;AAAA,UACR,MAAM,KAAK,UAAU,EAAE,eAAe,KAAK,cAAc;AAAA,QAAA,CAC1D;AACD,gBAAQ,IAAI,uCAAuC;AAAA,MACrD,SAAS,OAAO;AACd,gBAAQ,KAAK,mDAAmD,KAAK;AAAA,MACvE;AAAA,IACF;AAEA,SAAK,UAAA;AAAA,EACP;AAAA,EAEA,MAAM,mBAAkC;AACtC,YAAQ,IAAI,oCAAoC;AAEhD,QAAI;AACF,YAAM,KAAK,QAAQ,wBAAwB,EAAE,QAAQ,QAAQ;AAC7D,cAAQ,IAAI,sCAAsC;AAAA,IACpD,SAAS,OAAO;AACd,cAAQ,KAAK,wCAAwC,KAAK;AAAA,IAC5D;AAEA,SAAK,UAAA;AAAA,EACP;AAAA,EAEU,YAAkB;AAC1B,QAAI,KAAK,uBAAuB;AAC9B,mBAAa,KAAK,qBAAqB;AACvC,WAAK,wBAAwB;AAAA,IAC/B;AAEA,SAAK,QAAQ;AACb,SAAK,eAAe;AACpB,iBAAa,WAAW,YAAY;AACpC,iBAAa,WAAW,eAAe;AACvC,iBAAa,WAAW,WAAW;AACnC,YAAQ,IAAI,0BAA0B;AAEtC,WAAO,cAAc,IAAI,YAAY,cAAc,CAAC;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAA8D;AAClE,YAAQ,IAAI,6BAA6B;AACzC,WAAO,KAAK,QAAQ,sBAAsB,EAAE,QAAQ,OAAO;AAAA,EAC7D;AAAA,EAEA,MAAM,aAAa,MAKe;AAChC,YAAQ,IAAI,2BAA2B,KAAK,IAAI,EAAE;AAClD,WAAO,KAAK,QAAQ,sBAAsB;AAAA,MACxC,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU;AAAA,QACnB,MAAM,KAAK;AAAA,QACX,QAAQ,KAAK,UAAU;AAAA,QACvB,iBAAiB,KAAK;AAAA,QACtB,uBAAuB,KAAK,yBAAyB;AAAA,MAAA,CACtD;AAAA,IAAA,CACF;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,OAAqF;AACtG,YAAQ,IAAI,2BAA2B,KAAK,EAAE;AAC9C,WAAO,KAAK,QAAQ,sBAAsB,KAAK,IAAI,EAAE,QAAQ,UAAU;AAAA,EACzE;AACF;AAuBO,SAAS,cAAc,YAA6B;AACzD,SAAO,IAAI,QAAQ,UAAU;AAC/B;AAMO,MAAM,gBAAgB,MAAmB;AAC9C,QAAM,WAAW,aAAa,QAAQ,WAAW;AACjD,MAAI,CAAC,SAAU,QAAO;AAEtB,MAAI;AACF,WAAO,KAAK,MAAM,QAAQ;AAAA,EAC5B,SAAS,OAAO;AACd,YAAQ,MAAM,4CAA4C,KAAK;AAC/D,iBAAa,WAAW,WAAW;AACnC,WAAO;AAAA,EACT;AACF;AAEO,MAAM,iBAAiB,MAAqB;AACjD,SAAO,aAAa,QAAQ,YAAY;AAC1C;AAEO,MAAM,kBAAkB,MAAe;AAC5C,SAAO,CAAC,CAAC,eAAA;AACX;AAwBO,MAAM,qBAAqB,MAAc;AAC9C,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,SAAO,gBAAgB,KAAK;AAC5B,QAAM,QAAQ,MAAM,KAAK,OAAO,UAAQ,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACnF,iBAAe,QAAQ,sBAAsB,KAAK;AAClD,UAAQ,IAAI,6CAA6C;AACzD,SAAO;AACT;AAOO,MAAM,gBAAgB,MAAqB;AAChD,SAAO,eAAe,QAAQ,oBAAoB;AACpD;AAKO,MAAM,kBAAkB,MAAY;AACzC,iBAAe,WAAW,oBAAoB;AAC9C,UAAQ,IAAI,uBAAuB;AACrC;ACtuBA,MAAM,cAAc,cAA2C,MAAS;AAMjE,MAAM,UAAU,MAAM;AAC3B,QAAM,UAAU,WAAW,WAAW;AACtC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,SAAO;AACT;AAmBO,SAAS,mBAAmB,KAAc;AAC/C,QAAM,eAAwD,CAAC,EAAE,eAAe;AAC9E,UAAM,CAAC,OAAO,QAAQ,IAAI,SAAoB;AAAA,MAC5C,MAAM,cAAA;AAAA,MACN,WAAW;AAAA,MACX,iBAAiB,CAAC,CAAC,eAAA;AAAA,MACnB,OAAO;AAAA,IAAA,CACR;AAGD,cAAU,MAAM;AACd,YAAM,oBAAoB,MAAM;AAC9B,gBAAQ,IAAI,6DAA6D;AACzE,iBAAS;AAAA,UACP,MAAM;AAAA,UACN,iBAAiB;AAAA,UACjB,WAAW;AAAA,UACX,OAAO;AAAA,QAAA,CACR;AAAA,MACH;AAEA,aAAO,iBAAiB,gBAAgB,iBAAiB;AACzD,aAAO,MAAM,OAAO,oBAAoB,gBAAgB,iBAAiB;AAAA,IAC3E,GAAG,CAAA,CAAE;AAGL,cAAU,MAAM;AACd,YAAM,kBAAkB,YAAY;AAClC,cAAM,QAAQ,eAAA;AACd,YAAI,OAAO;AACT,cAAI;AACF,kBAAM,cAAc,MAAM,IAAI,eAAA;AAC9B,qBAAS,CAAA,UAAS;AAAA,cAChB,GAAG;AAAA,cACH,MAAM;AAAA,cACN,iBAAiB;AAAA,YAAA,EACjB;AAAA,UACJ,SAAS,OAAgB;AACvB,oBAAQ,MAAM,uCAAuC,KAAK;AAC1D,kBAAM,YAAY;AAClB,iBAAI,uCAAW,YAAW,QAAO,uCAAW,YAAW,KAAK;AAC1D,sBAAQ,KAAK,gDAAgD;AAC7D,2BAAa,WAAW,YAAY;AACpC,2BAAa,WAAW,WAAW;AACnC,uBAAS;AAAA,gBACP,MAAM;AAAA,gBACN,iBAAiB;AAAA,gBACjB,WAAW;AAAA,gBACX,OAAO;AAAA,cAAA,CACR;AAAA,YACH,OAAO;AACL,sBAAQ,KAAK,gDAAgD;AAAA,YAC/D;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,sBAAA;AAAA,IACF,GAAG,CAAA,CAAE;AAEL,UAAM,QAAQ,OAAO,OAAe,aAAuC;AACzE,eAAS,CAAA,UAAS,EAAE,GAAG,MAAM,WAAW,MAAM,OAAO,OAAO;AAE5D,UAAI;AACF,cAAM,SAAS,MAAM,IAAI,MAAM,OAAO,QAAQ;AAC9C,iBAAS,CAAA,UAAS;AAAA,UAChB,GAAG;AAAA,UACH,MAAM,OAAO;AAAA,UACb,iBAAiB;AAAA,UACjB,WAAW;AAAA,QAAA,EACX;AACF,eAAO;AAAA,MACT,SAAS,OAAO;AACd,iBAAS,CAAA,UAAS;AAAA,UAChB,GAAG;AAAA,UACH,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UAChD,WAAW;AAAA,QAAA,EACX;AACF,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,WAAW,OAAO,OAAe,UAAkB,WAAmB,aAAuC;AACjH,eAAS,CAAA,UAAS,EAAE,GAAG,MAAM,WAAW,MAAM,OAAO,OAAO;AAE5D,UAAI;AACF,cAAM,SAAS,MAAM,IAAI,SAAS,OAAO,UAAU,WAAW,QAAQ;AACtE,iBAAS,CAAA,UAAS;AAAA,UAChB,GAAG;AAAA,UACH,MAAM,OAAO;AAAA,UACb,iBAAiB;AAAA,UACjB,WAAW;AAAA,QAAA,EACX;AACF,eAAO;AAAA,MACT,SAAS,OAAO;AACd,iBAAS,CAAA,UAAS;AAAA,UAChB,GAAG;AAAA,UACH,OAAO,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UAChD,WAAW;AAAA,QAAA,EACX;AACF,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,SAAS,MAAM;AACnB,UAAI,OAAA;AACJ,eAAS;AAAA,QACP,MAAM;AAAA,QACN,iBAAiB;AAAA,QACjB,WAAW;AAAA,QACX,OAAO;AAAA,MAAA,CACR;AAAA,IACH;AAEA,UAAM,aAAa,MAAM;AACvB,eAAS,WAAS,EAAE,GAAG,MAAM,OAAO,OAAO;AAAA,IAC7C;AAEA,UAAM,cAAc,YAAY;AAC9B,UAAI;AACF,cAAM,cAAc,MAAM,IAAI,eAAA;AAC9B,iBAAS,CAAA,UAAS;AAAA,UAChB,GAAG;AAAA,UACH,MAAM;AAAA,QAAA,EACN;AAAA,MACJ,SAAS,OAAO;AACd,gBAAQ,MAAM,kCAAkC,KAAK;AAAA,MACvD;AAAA,IACF;AAEA,WACE;AAAA,MAAC,YAAY;AAAA,MAAZ;AAAA,QACC,OAAO;AAAA,UACL,GAAG;AAAA,UACH;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAAA,QAGD;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AAEA,SAAO;AACT;ACnMO,SAAS,cACd,KACA,SAAqB,IACf;AACN,QAAM,EAAE,mBAAmB;AAE3B,QAAM,OAAO,WAAW,SAAS,eAAe,MAAM,CAAE;AAGxD,MAAI,iCAAkC,KAAA,EAAI;AAG1C,MAAI,gBAAgB;AAClB,iBACE,oBAAC,qBAAA,EAAoB,UAAU,gBAC5B,UAAA,YACH;AAAA,EAEJ;AAGA,OAAK;AAAA,IACH,oBAAC,cACE,UAAA,WAAA,CACH;AAAA,EAAA;AAEJ;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rationalbloks/frontblok-auth",
3
- "version": "0.1.0",
4
- "description": "Authentication mechanics for RationalBloks frontends - JWT tokens, refresh, cross-tab sync",
3
+ "version": "0.2.0",
4
+ "description": "Authentication mechanics for RationalBloks frontends - JWT tokens, Google OAuth, password reset, email verification, cross-tab sync",
5
5
  "author": "RationalBloks Team",
6
6
  "license": "MIT",
7
7
  "private": false,