@rationalbloks/frontblok-auth 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +0 -156
- package/dist/api/createApiUrl.d.ts +0 -34
- package/dist/api/types.d.ts +0 -29
- package/dist/auth/AppProvider.d.ts +0 -5
- package/dist/auth/AuthContext.d.ts +0 -17
- package/dist/auth/ProtectedRoute.d.ts +0 -11
- package/dist/index.cjs +66 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +66 -107
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/api/client.d.ts
CHANGED
|
@@ -1,49 +1,7 @@
|
|
|
1
1
|
import type { User, ApiKey, ApiKeyCreateResponse, AuthResponse, GoogleOAuthResponse, PasswordResetRequestResponse, PasswordResetResponse, EmailVerificationResponse, SetPasswordResponse } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Generate a cryptographically secure nonce for OAuth CSRF protection.
|
|
4
|
-
*
|
|
5
|
-
* USAGE:
|
|
6
|
-
* 1. Call generateOAuthNonce() in component useState (runs once on mount)
|
|
7
|
-
* 2. Pass the nonce to GoogleLogin component via the 'nonce' prop
|
|
8
|
-
* 3. On success, call authApi.googleLogin(credential) - it handles the rest
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```typescript
|
|
12
|
-
* const [nonce] = useState(() => generateOAuthNonce());
|
|
13
|
-
* // Pass to GoogleLogin: <GoogleLogin nonce={nonce} onSuccess={...} />
|
|
14
|
-
* // On success: await authApi.googleLogin(credential);
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* @returns The generated nonce string
|
|
18
|
-
*/
|
|
19
2
|
export declare const generateOAuthNonce: () => string;
|
|
20
|
-
/**
|
|
21
|
-
* Retrieve the stored OAuth nonce.
|
|
22
|
-
* @returns The stored nonce or null if not found
|
|
23
|
-
*/
|
|
24
3
|
export declare const getOAuthNonce: () => string | null;
|
|
25
|
-
/**
|
|
26
|
-
* Clear the stored OAuth nonce (call after successful login).
|
|
27
|
-
*/
|
|
28
4
|
export declare const clearOAuthNonce: () => void;
|
|
29
|
-
/**
|
|
30
|
-
* BaseApi - Universal HTTP client with authentication.
|
|
31
|
-
*
|
|
32
|
-
* Extend this class to add your application-specific API methods.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```typescript
|
|
36
|
-
* class MyAppApi extends BaseApi {
|
|
37
|
-
* constructor() {
|
|
38
|
-
* super(import.meta.env.VITE_API_URL || 'https://api.myapp.com');
|
|
39
|
-
* }
|
|
40
|
-
*
|
|
41
|
-
* async getProducts() {
|
|
42
|
-
* return this.request<Product[]>('/api/products/');
|
|
43
|
-
* }
|
|
44
|
-
* }
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
5
|
export declare class BaseApi {
|
|
48
6
|
protected token: string | null;
|
|
49
7
|
protected refreshToken: string | null;
|
|
@@ -54,55 +12,15 @@ export declare class BaseApi {
|
|
|
54
12
|
protected tokenRefreshPromise: Promise<void> | null;
|
|
55
13
|
protected readonly apiBaseUrl: string;
|
|
56
14
|
constructor(apiBaseUrl: string);
|
|
57
|
-
/**
|
|
58
|
-
* Sync tokens across browser tabs when localStorage changes.
|
|
59
|
-
* Prevents "stale refresh token" issue where Tab A rotates the token
|
|
60
|
-
* but Tab B still has the old (now invalid) refresh token in memory.
|
|
61
|
-
*/
|
|
62
15
|
private setupStorageListener;
|
|
63
|
-
/**
|
|
64
|
-
* Start interval-based token check (runs every 60 seconds).
|
|
65
|
-
* More reliable than setTimeout which browsers throttle in background tabs.
|
|
66
|
-
*/
|
|
67
16
|
private startRefreshCheckInterval;
|
|
68
|
-
/**
|
|
69
|
-
* Handle tab visibility changes - check token when user returns to tab.
|
|
70
|
-
*/
|
|
71
17
|
private setupVisibilityHandler;
|
|
72
|
-
/**
|
|
73
|
-
* Check token expiry and refresh if needed.
|
|
74
|
-
*/
|
|
75
18
|
protected checkAndRefreshToken(): Promise<void>;
|
|
76
|
-
/**
|
|
77
|
-
* Parse JWT to get expiration time.
|
|
78
|
-
*/
|
|
79
19
|
protected getTokenExpiry(token: string): number | null;
|
|
80
|
-
/**
|
|
81
|
-
* Schedule proactive refresh 5 minutes before token expires.
|
|
82
|
-
*/
|
|
83
20
|
protected scheduleProactiveRefresh(): void;
|
|
84
|
-
/**
|
|
85
|
-
* Load tokens from localStorage on initialization.
|
|
86
|
-
*/
|
|
87
21
|
protected loadTokens(): void;
|
|
88
|
-
/**
|
|
89
|
-
* Wait for any pending token refresh to complete before making API calls.
|
|
90
|
-
*/
|
|
91
22
|
ensureTokenReady(): Promise<void>;
|
|
92
|
-
/**
|
|
93
|
-
* Refresh the access token using the refresh token.
|
|
94
|
-
*/
|
|
95
23
|
refreshAccessToken(): Promise<boolean>;
|
|
96
|
-
/**
|
|
97
|
-
* Make an authenticated HTTP request.
|
|
98
|
-
* Handles token refresh, 401 retry, and rate limiting automatically.
|
|
99
|
-
*
|
|
100
|
-
* This method is public so apps can make custom API calls without extending BaseApi.
|
|
101
|
-
*
|
|
102
|
-
* @example
|
|
103
|
-
* const authApi = createAuthApi(API_URL);
|
|
104
|
-
* const data = await authApi.request<MyType>('/api/my-endpoint/', { method: 'POST', body: JSON.stringify(payload) });
|
|
105
|
-
*/
|
|
106
24
|
request<T>(endpoint: string, options?: RequestInit, isRetry?: boolean, retryCount?: number): Promise<T>;
|
|
107
25
|
login(email: string, password: string): Promise<AuthResponse>;
|
|
108
26
|
register(email: string, password: string, firstName: string, lastName: string): Promise<AuthResponse>;
|
|
@@ -110,79 +28,12 @@ export declare class BaseApi {
|
|
|
110
28
|
user: User;
|
|
111
29
|
}>;
|
|
112
30
|
getCurrentUser(): Promise<User>;
|
|
113
|
-
/**
|
|
114
|
-
* Internal: Authenticate with Google OAuth.
|
|
115
|
-
* Use googleLogin() instead.
|
|
116
|
-
*/
|
|
117
31
|
private _googleOAuthLogin;
|
|
118
|
-
/**
|
|
119
|
-
* Simplified Google OAuth login.
|
|
120
|
-
*
|
|
121
|
-
* This is the default method for Google Sign-In. It handles the complete
|
|
122
|
-
* OAuth flow in a single call:
|
|
123
|
-
*
|
|
124
|
-
* 1. Retrieve nonce from sessionStorage (must exist from generateOAuthNonce())
|
|
125
|
-
* 2. Send credential + nonce to backend for verification
|
|
126
|
-
* 3. Store JWT tokens on success
|
|
127
|
-
* 4. Clear nonce to prevent replay attacks
|
|
128
|
-
*
|
|
129
|
-
* If any step fails, the entire operation fails cleanly. No partial states.
|
|
130
|
-
*
|
|
131
|
-
* @param credential - The Google ID token (JWT from GoogleLogin onSuccess)
|
|
132
|
-
* @returns GoogleOAuthResponse with tokens, user data, and is_new_user flag
|
|
133
|
-
* @throws Error if nonce is missing or authentication fails
|
|
134
|
-
*
|
|
135
|
-
* @example
|
|
136
|
-
* ```typescript
|
|
137
|
-
* // In component: generate nonce ONCE on mount
|
|
138
|
-
* const [nonce] = useState(() => generateOAuthNonce());
|
|
139
|
-
*
|
|
140
|
-
* // Pass nonce to GoogleLogin, then on success:
|
|
141
|
-
* const handleSuccess = async (response: CredentialResponse) => {
|
|
142
|
-
* const result = await authApi.googleLogin(response.credential!);
|
|
143
|
-
* // Done! Tokens stored, nonce cleared, user authenticated.
|
|
144
|
-
* };
|
|
145
|
-
* ```
|
|
146
|
-
*/
|
|
147
32
|
googleLogin(credential: string): Promise<GoogleOAuthResponse>;
|
|
148
|
-
/**
|
|
149
|
-
* Request a password reset email (forgot password flow).
|
|
150
|
-
*
|
|
151
|
-
* @param email - The email address to send reset link to
|
|
152
|
-
* @returns Message confirming email was sent (or would be sent)
|
|
153
|
-
*/
|
|
154
33
|
requestPasswordReset(email: string): Promise<PasswordResetRequestResponse>;
|
|
155
|
-
/**
|
|
156
|
-
* Reset password using token from email.
|
|
157
|
-
*
|
|
158
|
-
* @param token - The password reset token from the email link
|
|
159
|
-
* @param newPassword - The new password (min 8 characters)
|
|
160
|
-
* @returns Success message
|
|
161
|
-
*/
|
|
162
34
|
resetPassword(token: string, newPassword: string): Promise<PasswordResetResponse>;
|
|
163
|
-
/**
|
|
164
|
-
* Verify email address using token from verification email.
|
|
165
|
-
*
|
|
166
|
-
* @param token - The verification token from the email link
|
|
167
|
-
* @returns Success message
|
|
168
|
-
*/
|
|
169
35
|
verifyEmail(token: string): Promise<EmailVerificationResponse>;
|
|
170
|
-
/**
|
|
171
|
-
* Request a new verification email.
|
|
172
|
-
*
|
|
173
|
-
* @param email - The email address to send verification to
|
|
174
|
-
* @returns Message confirming email was sent
|
|
175
|
-
*/
|
|
176
36
|
requestVerificationEmail(email: string): Promise<EmailVerificationResponse>;
|
|
177
|
-
/**
|
|
178
|
-
* Set password for OAuth-only accounts.
|
|
179
|
-
*
|
|
180
|
-
* Allows users who registered via Google OAuth to set a password
|
|
181
|
-
* so they can also log in with email/password.
|
|
182
|
-
*
|
|
183
|
-
* @param newPassword - The password to set (min 8 characters)
|
|
184
|
-
* @returns Success message
|
|
185
|
-
*/
|
|
186
37
|
setPassword(newPassword: string): Promise<SetPasswordResponse>;
|
|
187
38
|
deleteAccount(password: string, confirmText: string): Promise<{
|
|
188
39
|
message: string;
|
|
@@ -207,13 +58,6 @@ export declare class BaseApi {
|
|
|
207
58
|
revoked_at: string;
|
|
208
59
|
}>;
|
|
209
60
|
}
|
|
210
|
-
/**
|
|
211
|
-
* Creates an auth API client instance.
|
|
212
|
-
* Preferred over class inheritance - simpler and cleaner.
|
|
213
|
-
*
|
|
214
|
-
* @param apiBaseUrl - The backend API base URL
|
|
215
|
-
* @returns A BaseApi instance with all auth methods
|
|
216
|
-
*/
|
|
217
61
|
export declare function createAuthApi(apiBaseUrl: string): BaseApi;
|
|
218
62
|
export declare const getStoredUser: () => User | null;
|
|
219
63
|
export declare const getStoredToken: () => string | null;
|
|
@@ -1,39 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Options for createApiUrl.
|
|
3
|
-
*/
|
|
4
1
|
export interface CreateApiUrlOptions {
|
|
5
|
-
/**
|
|
6
|
-
* Override the env variable name. Default: 'VITE_DATABASE_API_URL'.
|
|
7
|
-
* This is read from import.meta.env at call time.
|
|
8
|
-
*/
|
|
9
2
|
envVar?: string;
|
|
10
|
-
/**
|
|
11
|
-
* Fallback URL for local development (only used when import.meta.env.DEV is true).
|
|
12
|
-
* Default: 'http://localhost:8000'
|
|
13
|
-
*/
|
|
14
3
|
devFallback?: string;
|
|
15
4
|
}
|
|
16
|
-
/**
|
|
17
|
-
* Resolves the backend API URL for the current environment.
|
|
18
|
-
*
|
|
19
|
-
* Resolution order:
|
|
20
|
-
* 1. `import.meta.env.VITE_DATABASE_API_URL` (injected at build time)
|
|
21
|
-
* 2. `import.meta.env.VITE_API_URL` (alternative env var name)
|
|
22
|
-
* 3. In development mode only: devFallback (default: 'http://localhost:8000')
|
|
23
|
-
* 4. In production: empty string (will cause API calls to fail loudly)
|
|
24
|
-
*
|
|
25
|
-
* @param options - Configuration options
|
|
26
|
-
* @returns The resolved API base URL
|
|
27
|
-
*
|
|
28
|
-
* @example
|
|
29
|
-
* ```typescript
|
|
30
|
-
* import { createApiUrl, createAuthApi } from '@rationalbloks/frontblok-auth';
|
|
31
|
-
*
|
|
32
|
-
* // Simple usage
|
|
33
|
-
* const authApi = createAuthApi(createApiUrl());
|
|
34
|
-
*
|
|
35
|
-
* // Custom dev fallback
|
|
36
|
-
* const authApi = createAuthApi(createApiUrl({ devFallback: 'http://localhost:3000' }));
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
5
|
export declare function createApiUrl(options?: CreateApiUrlOptions): string;
|
package/dist/api/types.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authenticated user interface.
|
|
3
|
-
* Represents the user data returned from authentication endpoints.
|
|
4
|
-
*/
|
|
5
1
|
export interface User {
|
|
6
2
|
id: string;
|
|
7
3
|
email: string;
|
|
@@ -16,9 +12,6 @@ export interface User {
|
|
|
16
12
|
oauth_provider?: string | null;
|
|
17
13
|
has_google_linked?: boolean;
|
|
18
14
|
}
|
|
19
|
-
/**
|
|
20
|
-
* API Key interface for MCP servers and external integrations.
|
|
21
|
-
*/
|
|
22
15
|
export interface ApiKey {
|
|
23
16
|
id: string;
|
|
24
17
|
name: string;
|
|
@@ -29,10 +22,6 @@ export interface ApiKey {
|
|
|
29
22
|
last_used_at: string | null;
|
|
30
23
|
created_at: string;
|
|
31
24
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Response from creating a new API key.
|
|
34
|
-
* Includes the full key (only shown once).
|
|
35
|
-
*/
|
|
36
25
|
export interface ApiKeyCreateResponse {
|
|
37
26
|
api_key: string;
|
|
38
27
|
id: string;
|
|
@@ -44,17 +33,11 @@ export interface ApiKeyCreateResponse {
|
|
|
44
33
|
created_at: string;
|
|
45
34
|
message: string;
|
|
46
35
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Login/Register response with tokens and user data.
|
|
49
|
-
*/
|
|
50
36
|
export interface AuthResponse {
|
|
51
37
|
access_token: string;
|
|
52
38
|
refresh_token?: string;
|
|
53
39
|
user: User;
|
|
54
40
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Google OAuth login response.
|
|
57
|
-
*/
|
|
58
41
|
export interface GoogleOAuthResponse {
|
|
59
42
|
message: string;
|
|
60
43
|
access_token: string;
|
|
@@ -65,29 +48,17 @@ export interface GoogleOAuthResponse {
|
|
|
65
48
|
is_new_user: boolean;
|
|
66
49
|
is_account_link?: boolean;
|
|
67
50
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Password reset request response.
|
|
70
|
-
*/
|
|
71
51
|
export interface PasswordResetRequestResponse {
|
|
72
52
|
message: string;
|
|
73
53
|
dev_token?: string;
|
|
74
54
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Password reset response.
|
|
77
|
-
*/
|
|
78
55
|
export interface PasswordResetResponse {
|
|
79
56
|
message: string;
|
|
80
57
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Email verification response.
|
|
83
|
-
*/
|
|
84
58
|
export interface EmailVerificationResponse {
|
|
85
59
|
message: string;
|
|
86
60
|
dev_token?: string;
|
|
87
61
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Set password response (for OAuth-only accounts).
|
|
90
|
-
*/
|
|
91
62
|
export interface SetPasswordResponse {
|
|
92
63
|
message: string;
|
|
93
64
|
}
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export interface AuthConfig {
|
|
3
|
-
/** Google OAuth Client ID (optional - only needed if using Google Sign-In) */
|
|
4
3
|
googleClientId?: string;
|
|
5
4
|
}
|
|
6
|
-
/**
|
|
7
|
-
* Creates and renders the app root with authentication providers.
|
|
8
|
-
* This is the universal way to bootstrap a React app with auth.
|
|
9
|
-
*/
|
|
10
5
|
export declare function createAppRoot(App: React.ComponentType, config?: AuthConfig): void;
|
|
@@ -15,24 +15,7 @@ export interface AuthActions {
|
|
|
15
15
|
refreshUser: () => Promise<void>;
|
|
16
16
|
}
|
|
17
17
|
export type AuthContextType = AuthState & AuthActions;
|
|
18
|
-
/**
|
|
19
|
-
* Hook to access authentication state and actions.
|
|
20
|
-
* Must be used within an AuthProvider.
|
|
21
|
-
*/
|
|
22
18
|
export declare const useAuth: () => AuthContextType;
|
|
23
|
-
/**
|
|
24
|
-
* Creates an AuthProvider component that uses the specified API instance.
|
|
25
|
-
* This allows the universal auth context to work with any API that extends BaseApi.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```typescript
|
|
29
|
-
* // In your app's auth setup:
|
|
30
|
-
* import { createAuthProvider } from '@/core/auth';
|
|
31
|
-
* import { myAppApi } from '@/services/myAppApi';
|
|
32
|
-
*
|
|
33
|
-
* export const MyAppAuthProvider = createAuthProvider(myAppApi);
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
19
|
export declare function createAuthProvider(api: BaseApi): React.FC<{
|
|
37
20
|
children: React.ReactNode;
|
|
38
21
|
}>;
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
export interface ProtectedRouteProps {
|
|
3
3
|
children: React.ReactNode;
|
|
4
|
-
/**
|
|
5
|
-
* The useAuth hook from your app's datablokApi.ts (useClientAuth).
|
|
6
|
-
* We accept this as a prop instead of importing it directly to avoid
|
|
7
|
-
* circular dependencies and keep the package decoupled.
|
|
8
|
-
*/
|
|
9
4
|
useAuth: () => {
|
|
10
5
|
isAuthenticated: boolean;
|
|
11
6
|
};
|
|
12
|
-
/**
|
|
13
|
-
* Where to redirect unauthenticated users. Default: '/auth'
|
|
14
|
-
*/
|
|
15
7
|
redirectTo?: string;
|
|
16
8
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Route wrapper that redirects to the auth page if the user is not authenticated.
|
|
19
|
-
*/
|
|
20
9
|
export declare const ProtectedRoute: React.FC<ProtectedRouteProps>;
|
package/dist/index.cjs
CHANGED
|
@@ -40,11 +40,9 @@ class BaseApi {
|
|
|
40
40
|
// ========================================================================
|
|
41
41
|
// TOKEN MANAGEMENT
|
|
42
42
|
// ========================================================================
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* but Tab B still has the old (now invalid) refresh token in memory.
|
|
47
|
-
*/
|
|
43
|
+
// Sync tokens across browser tabs when localStorage changes.
|
|
44
|
+
// Prevents "stale refresh token" issue where Tab A rotates the token
|
|
45
|
+
// but Tab B still has the old (now invalid) refresh token in memory.
|
|
48
46
|
setupStorageListener() {
|
|
49
47
|
if (typeof window !== "undefined") {
|
|
50
48
|
window.addEventListener("storage", (event) => {
|
|
@@ -63,10 +61,8 @@ class BaseApi {
|
|
|
63
61
|
});
|
|
64
62
|
}
|
|
65
63
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* More reliable than setTimeout which browsers throttle in background tabs.
|
|
69
|
-
*/
|
|
64
|
+
// Start interval-based token check (runs every 60 seconds).
|
|
65
|
+
// More reliable than setTimeout which browsers throttle in background tabs.
|
|
70
66
|
startRefreshCheckInterval() {
|
|
71
67
|
if (this.refreshCheckInterval) {
|
|
72
68
|
clearInterval(this.refreshCheckInterval);
|
|
@@ -77,9 +73,7 @@ class BaseApi {
|
|
|
77
73
|
}
|
|
78
74
|
}, 60 * 1e3);
|
|
79
75
|
}
|
|
80
|
-
|
|
81
|
-
* Handle tab visibility changes - check token when user returns to tab.
|
|
82
|
-
*/
|
|
76
|
+
// Handle tab visibility changes - check token when user returns to tab.
|
|
83
77
|
setupVisibilityHandler() {
|
|
84
78
|
if (typeof document !== "undefined") {
|
|
85
79
|
document.addEventListener("visibilitychange", () => {
|
|
@@ -96,9 +90,7 @@ class BaseApi {
|
|
|
96
90
|
});
|
|
97
91
|
}
|
|
98
92
|
}
|
|
99
|
-
|
|
100
|
-
* Check token expiry and refresh if needed.
|
|
101
|
-
*/
|
|
93
|
+
// Check token expiry and refresh if needed.
|
|
102
94
|
async checkAndRefreshToken() {
|
|
103
95
|
if (!this.token) return;
|
|
104
96
|
const expiry = this.getTokenExpiry(this.token);
|
|
@@ -114,9 +106,7 @@ class BaseApi {
|
|
|
114
106
|
await this.refreshAccessToken();
|
|
115
107
|
}
|
|
116
108
|
}
|
|
117
|
-
|
|
118
|
-
* Parse JWT to get expiration time.
|
|
119
|
-
*/
|
|
109
|
+
// Parse JWT to get expiration time.
|
|
120
110
|
getTokenExpiry(token) {
|
|
121
111
|
try {
|
|
122
112
|
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
@@ -125,9 +115,7 @@ class BaseApi {
|
|
|
125
115
|
return null;
|
|
126
116
|
}
|
|
127
117
|
}
|
|
128
|
-
|
|
129
|
-
* Schedule proactive refresh 5 minutes before token expires.
|
|
130
|
-
*/
|
|
118
|
+
// Schedule proactive refresh 5 minutes before token expires.
|
|
131
119
|
scheduleProactiveRefresh() {
|
|
132
120
|
if (this.proactiveRefreshTimer) {
|
|
133
121
|
clearTimeout(this.proactiveRefreshTimer);
|
|
@@ -156,9 +144,7 @@ class BaseApi {
|
|
|
156
144
|
await this.refreshAccessToken();
|
|
157
145
|
}, refreshIn);
|
|
158
146
|
}
|
|
159
|
-
|
|
160
|
-
* Load tokens from localStorage on initialization.
|
|
161
|
-
*/
|
|
147
|
+
// Load tokens from localStorage on initialization.
|
|
162
148
|
loadTokens() {
|
|
163
149
|
const storedToken = localStorage.getItem("auth_token");
|
|
164
150
|
const storedRefreshToken = localStorage.getItem("refresh_token");
|
|
@@ -175,18 +161,14 @@ class BaseApi {
|
|
|
175
161
|
this.scheduleProactiveRefresh();
|
|
176
162
|
}
|
|
177
163
|
}
|
|
178
|
-
|
|
179
|
-
* Wait for any pending token refresh to complete before making API calls.
|
|
180
|
-
*/
|
|
164
|
+
// Wait for any pending token refresh to complete before making API calls.
|
|
181
165
|
async ensureTokenReady() {
|
|
182
166
|
if (this.tokenRefreshPromise) {
|
|
183
167
|
await this.tokenRefreshPromise;
|
|
184
168
|
this.tokenRefreshPromise = null;
|
|
185
169
|
}
|
|
186
170
|
}
|
|
187
|
-
|
|
188
|
-
* Refresh the access token using the refresh token.
|
|
189
|
-
*/
|
|
171
|
+
// Refresh the access token using the refresh token.
|
|
190
172
|
async refreshAccessToken() {
|
|
191
173
|
if (this.isRefreshing) {
|
|
192
174
|
return this.refreshPromise || Promise.resolve(false);
|
|
@@ -244,16 +226,14 @@ class BaseApi {
|
|
|
244
226
|
// ========================================================================
|
|
245
227
|
// HTTP REQUEST METHOD
|
|
246
228
|
// ========================================================================
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
* const data = await authApi.request<MyType>('/api/my-endpoint/', { method: 'POST', body: JSON.stringify(payload) });
|
|
256
|
-
*/
|
|
229
|
+
// Make an authenticated HTTP request.
|
|
230
|
+
// Handles token refresh, 401 retry, and rate limiting automatically.
|
|
231
|
+
//
|
|
232
|
+
// This method is public so apps can make custom API calls without extending BaseApi.
|
|
233
|
+
//
|
|
234
|
+
// Example:
|
|
235
|
+
// const authApi = createAuthApi(API_URL);
|
|
236
|
+
// const data = await authApi.request<MyType>('/api/my-endpoint/', { method: 'POST', body: JSON.stringify(payload) });
|
|
257
237
|
async request(endpoint, options = {}, isRetry = false, retryCount = 0) {
|
|
258
238
|
await this.ensureTokenReady();
|
|
259
239
|
if (this.token && !isRetry && !endpoint.includes("/auth/")) {
|
|
@@ -364,10 +344,8 @@ class BaseApi {
|
|
|
364
344
|
// ========================================================================
|
|
365
345
|
// GOOGLE OAUTH AUTHENTICATION
|
|
366
346
|
// ========================================================================
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
* Use googleLogin() instead.
|
|
370
|
-
*/
|
|
347
|
+
// Internal: Authenticate with Google OAuth.
|
|
348
|
+
// Use googleLogin() instead.
|
|
371
349
|
async _googleOAuthLogin(credential, nonce) {
|
|
372
350
|
console.log("[API] Google OAuth login attempt");
|
|
373
351
|
const data = await this.request("/api/auth/google/login", {
|
|
@@ -385,35 +363,31 @@ class BaseApi {
|
|
|
385
363
|
console.log(`[API] Google OAuth successful (new_user: ${data.is_new_user})`);
|
|
386
364
|
return data;
|
|
387
365
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
* // Done! Tokens stored, nonce cleared, user authenticated.
|
|
414
|
-
* };
|
|
415
|
-
* ```
|
|
416
|
-
*/
|
|
366
|
+
// Simplified Google OAuth login.
|
|
367
|
+
//
|
|
368
|
+
// This is the default method for Google Sign-In. It handles the complete
|
|
369
|
+
// OAuth flow in a single call:
|
|
370
|
+
//
|
|
371
|
+
// 1. Retrieve nonce from sessionStorage (must exist from generateOAuthNonce())
|
|
372
|
+
// 2. Send credential + nonce to backend for verification
|
|
373
|
+
// 3. Store JWT tokens on success
|
|
374
|
+
// 4. Clear nonce to prevent replay attacks
|
|
375
|
+
//
|
|
376
|
+
// If any step fails, the entire operation fails cleanly. No partial states.
|
|
377
|
+
//
|
|
378
|
+
// credential - The Google ID token (JWT from GoogleLogin onSuccess)
|
|
379
|
+
// Returns GoogleOAuthResponse with tokens, user data, and is_new_user flag
|
|
380
|
+
// Throws Error if nonce is missing or authentication fails
|
|
381
|
+
//
|
|
382
|
+
// Example:
|
|
383
|
+
// // In component: generate nonce ONCE on mount
|
|
384
|
+
// const [nonce] = useState(() => generateOAuthNonce());
|
|
385
|
+
//
|
|
386
|
+
// // Pass nonce to GoogleLogin, then on success:
|
|
387
|
+
// const handleSuccess = async (response: CredentialResponse) => {
|
|
388
|
+
// const result = await authApi.googleLogin(response.credential!);
|
|
389
|
+
// // Done! Tokens stored, nonce cleared, user authenticated.
|
|
390
|
+
// };
|
|
417
391
|
async googleLogin(credential) {
|
|
418
392
|
console.log("[API] Google OAuth login");
|
|
419
393
|
const nonce = getOAuthNonce();
|
|
@@ -428,12 +402,9 @@ class BaseApi {
|
|
|
428
402
|
// ========================================================================
|
|
429
403
|
// PASSWORD RESET FLOW
|
|
430
404
|
// ========================================================================
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
* @param email - The email address to send reset link to
|
|
435
|
-
* @returns Message confirming email was sent (or would be sent)
|
|
436
|
-
*/
|
|
405
|
+
// Request a password reset email (forgot password flow).
|
|
406
|
+
// email - The email address to send reset link to.
|
|
407
|
+
// Returns message confirming email was sent (or would be sent).
|
|
437
408
|
async requestPasswordReset(email) {
|
|
438
409
|
console.log("[API] Requesting password reset for:", email);
|
|
439
410
|
return this.request("/api/auth/request-password-reset", {
|
|
@@ -441,13 +412,10 @@ class BaseApi {
|
|
|
441
412
|
body: JSON.stringify({ email })
|
|
442
413
|
});
|
|
443
414
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
* @param newPassword - The new password (min 8 characters)
|
|
449
|
-
* @returns Success message
|
|
450
|
-
*/
|
|
415
|
+
// Reset password using token from email.
|
|
416
|
+
// token - The password reset token from the email link.
|
|
417
|
+
// newPassword - The new password (min 8 characters).
|
|
418
|
+
// Returns success message.
|
|
451
419
|
async resetPassword(token, newPassword) {
|
|
452
420
|
console.log("[API] Resetting password with token");
|
|
453
421
|
return this.request("/api/auth/reset-password", {
|
|
@@ -458,12 +426,9 @@ class BaseApi {
|
|
|
458
426
|
// ========================================================================
|
|
459
427
|
// EMAIL VERIFICATION FLOW
|
|
460
428
|
// ========================================================================
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
* @param token - The verification token from the email link
|
|
465
|
-
* @returns Success message
|
|
466
|
-
*/
|
|
429
|
+
// Verify email address using token from verification email.
|
|
430
|
+
// token - The verification token from the email link.
|
|
431
|
+
// Returns success message.
|
|
467
432
|
async verifyEmail(token) {
|
|
468
433
|
console.log("[API] Verifying email with token");
|
|
469
434
|
return this.request("/api/auth/verify-email", {
|
|
@@ -471,12 +436,9 @@ class BaseApi {
|
|
|
471
436
|
body: JSON.stringify({ token })
|
|
472
437
|
});
|
|
473
438
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
* @param email - The email address to send verification to
|
|
478
|
-
* @returns Message confirming email was sent
|
|
479
|
-
*/
|
|
439
|
+
// Request a new verification email.
|
|
440
|
+
// email - The email address to send verification to.
|
|
441
|
+
// Returns message confirming email was sent.
|
|
480
442
|
async requestVerificationEmail(email) {
|
|
481
443
|
console.log("[API] Requesting verification email for:", email);
|
|
482
444
|
return this.request("/api/auth/request-verification-email", {
|
|
@@ -487,15 +449,12 @@ class BaseApi {
|
|
|
487
449
|
// ========================================================================
|
|
488
450
|
// SET PASSWORD (FOR OAUTH-ONLY ACCOUNTS)
|
|
489
451
|
// ========================================================================
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
* @param newPassword - The password to set (min 8 characters)
|
|
497
|
-
* @returns Success message
|
|
498
|
-
*/
|
|
452
|
+
// Set password for OAuth-only accounts.
|
|
453
|
+
//
|
|
454
|
+
// Allows users who registered via Google OAuth to set a password
|
|
455
|
+
// so they can also log in with email/password.
|
|
456
|
+
// newPassword - The password to set (min 8 characters).
|
|
457
|
+
// Returns success message.
|
|
499
458
|
async setPassword(newPassword) {
|
|
500
459
|
console.log("[API] Setting password for OAuth account");
|
|
501
460
|
return this.request("/api/auth/set-password", {
|