say-auth 1.0.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/LICENSE.md +21 -0
- package/README.md +121 -0
- package/dist/index.d.mts +312 -0
- package/dist/index.d.ts +312 -0
- package/dist/index.js +1496 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1446 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +117 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Sanyii
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# 🔐 say-auth
|
|
2
|
+
|
|
3
|
+
> Enterprise-grade authentication library for Next.js with MFA, JWT, and security features
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/say-auth)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://nextjs.org/)
|
|
9
|
+
[](https://www.npmjs.com/package/say-auth)
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
- 🔐 **JWT Authentication** - Secure token-based authentication
|
|
14
|
+
- 🔄 **Auto Token Refresh** - Automatic token refresh with request queuing
|
|
15
|
+
- 🛡️ **MFA Support** - Two-factor authentication with TOTP
|
|
16
|
+
- 🚫 **Token Blacklisting** - Revoke tokens on logout
|
|
17
|
+
- 📝 **Audit Logging** - Track all authentication events
|
|
18
|
+
- 🖥️ **Device Fingerprinting** - Prevent session hijacking
|
|
19
|
+
- ⚡ **Rate Limiting** - Brute force protection
|
|
20
|
+
- 🔒 **AES-256 Encryption** - Secure token storage
|
|
21
|
+
- 📦 **Zero Config** - Works out of the box
|
|
22
|
+
- 🎯 **TypeScript** - Full type safety
|
|
23
|
+
|
|
24
|
+
## 📦 Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install say-auth
|
|
28
|
+
# or
|
|
29
|
+
pnpm add say-auth
|
|
30
|
+
# or
|
|
31
|
+
yarn add say-auth
|
|
32
|
+
🚀 Quick Start
|
|
33
|
+
1. Wrap your app with AuthProvider
|
|
34
|
+
tsx
|
|
35
|
+
// app/layout.tsx
|
|
36
|
+
import { AuthProvider } from 'say-auth';
|
|
37
|
+
|
|
38
|
+
export default function RootLayout({ children }) {
|
|
39
|
+
return (
|
|
40
|
+
<html>
|
|
41
|
+
<body>
|
|
42
|
+
<AuthProvider apiUrl={process.env.NEXT_PUBLIC_API_URL}>
|
|
43
|
+
{children}
|
|
44
|
+
</AuthProvider>
|
|
45
|
+
</body>
|
|
46
|
+
</html>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
2. Create login page
|
|
50
|
+
tsx
|
|
51
|
+
// app/login/page.tsx
|
|
52
|
+
'use client';
|
|
53
|
+
import { useAuth } from 'say-auth';
|
|
54
|
+
|
|
55
|
+
export default function LoginPage() {
|
|
56
|
+
const { login, isLoading } = useAuth();
|
|
57
|
+
|
|
58
|
+
const handleSubmit = async (e) => {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
const formData = new FormData(e.currentTarget);
|
|
61
|
+
await login({
|
|
62
|
+
email: formData.get('email'),
|
|
63
|
+
password: formData.get('password'),
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<form onSubmit={handleSubmit}>
|
|
69
|
+
<input name="email" type="email" required />
|
|
70
|
+
<input name="password" type="password" required />
|
|
71
|
+
<button type="submit" disabled={isLoading}>
|
|
72
|
+
{isLoading ? 'Loading...' : 'Login'}
|
|
73
|
+
</button>
|
|
74
|
+
</form>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
3. Protect routes
|
|
78
|
+
tsx
|
|
79
|
+
// app/dashboard/page.tsx
|
|
80
|
+
'use client';
|
|
81
|
+
import { ProtectedRoute, useAuth } from 'say-auth';
|
|
82
|
+
|
|
83
|
+
export default function DashboardPage() {
|
|
84
|
+
const { user } = useAuth();
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<ProtectedRoute>
|
|
88
|
+
<h1>Welcome, {user?.name}!</h1>
|
|
89
|
+
</ProtectedRoute>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
📖 API Reference
|
|
93
|
+
useAuth()
|
|
94
|
+
Main authentication hook that returns:
|
|
95
|
+
|
|
96
|
+
Property Type Description
|
|
97
|
+
user User | null Current user data
|
|
98
|
+
isAuthenticated boolean Authentication status
|
|
99
|
+
isLoading boolean Loading state
|
|
100
|
+
login() (credentials) => Promise Login function
|
|
101
|
+
logout() () => Promise Logout function
|
|
102
|
+
refreshSession() () => Promise Refresh session
|
|
103
|
+
ProtectedRoute
|
|
104
|
+
Component for protecting routes:
|
|
105
|
+
|
|
106
|
+
tsx
|
|
107
|
+
<ProtectedRoute requiredRole="admin">
|
|
108
|
+
<AdminPanel />
|
|
109
|
+
</ProtectedRoute>
|
|
110
|
+
🔧 Requirements
|
|
111
|
+
Next.js 13+
|
|
112
|
+
|
|
113
|
+
React 18+
|
|
114
|
+
|
|
115
|
+
Axios 1.6+
|
|
116
|
+
|
|
117
|
+
🤝 Contributing
|
|
118
|
+
Contributions are welcome! Please read our contributing guidelines.
|
|
119
|
+
|
|
120
|
+
📄 License
|
|
121
|
+
MIT © Sanyii
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
import React$1 from 'react';
|
|
4
|
+
|
|
5
|
+
interface User {
|
|
6
|
+
id: string;
|
|
7
|
+
email: string;
|
|
8
|
+
name: string;
|
|
9
|
+
role?: string;
|
|
10
|
+
avatar?: string;
|
|
11
|
+
mfaEnabled: boolean;
|
|
12
|
+
mfaSecret?: string;
|
|
13
|
+
backupCodes?: string[];
|
|
14
|
+
createdAt?: Date;
|
|
15
|
+
updatedAt?: Date;
|
|
16
|
+
}
|
|
17
|
+
interface AuthTokens {
|
|
18
|
+
accessToken: string;
|
|
19
|
+
refreshToken: string;
|
|
20
|
+
expiresIn: number;
|
|
21
|
+
}
|
|
22
|
+
interface LoginCredentials {
|
|
23
|
+
email: string;
|
|
24
|
+
password: string;
|
|
25
|
+
}
|
|
26
|
+
interface RegisterData {
|
|
27
|
+
email: string;
|
|
28
|
+
password: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}
|
|
31
|
+
interface AuthState {
|
|
32
|
+
user: User | null;
|
|
33
|
+
tokens: AuthTokens | null;
|
|
34
|
+
isAuthenticated: boolean;
|
|
35
|
+
isLoading: boolean;
|
|
36
|
+
error: string | null;
|
|
37
|
+
}
|
|
38
|
+
interface MFAVerify {
|
|
39
|
+
userId: string;
|
|
40
|
+
code: string;
|
|
41
|
+
trustDevice?: boolean;
|
|
42
|
+
}
|
|
43
|
+
interface TokenBlacklistEntry {
|
|
44
|
+
token: string;
|
|
45
|
+
userId: string;
|
|
46
|
+
expiresAt: Date;
|
|
47
|
+
reason: 'logout' | 'revoke' | 'security';
|
|
48
|
+
}
|
|
49
|
+
interface AuditEntry$1 {
|
|
50
|
+
action: string;
|
|
51
|
+
userId?: string;
|
|
52
|
+
email?: string;
|
|
53
|
+
ipAddress: string;
|
|
54
|
+
userAgent: string;
|
|
55
|
+
timestamp: Date;
|
|
56
|
+
details?: Record<string, any>;
|
|
57
|
+
success: boolean;
|
|
58
|
+
errorMessage?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
declare class AuthService {
|
|
62
|
+
private static instance;
|
|
63
|
+
private axiosInstance;
|
|
64
|
+
private state;
|
|
65
|
+
private listeners;
|
|
66
|
+
private baseURL;
|
|
67
|
+
private constructor();
|
|
68
|
+
static getInstance(apiUrl?: string): AuthService;
|
|
69
|
+
private makeRequest;
|
|
70
|
+
healthCheck(): Promise<boolean>;
|
|
71
|
+
healthCheckWithRetry(retries?: number): Promise<boolean>;
|
|
72
|
+
private loadInitialState;
|
|
73
|
+
private notifyListeners;
|
|
74
|
+
subscribe(listener: (state: AuthState) => void): () => void;
|
|
75
|
+
private updateState;
|
|
76
|
+
login(credentials: LoginCredentials): Promise<{
|
|
77
|
+
requiresMFA: boolean;
|
|
78
|
+
user?: User;
|
|
79
|
+
}>;
|
|
80
|
+
verifyMFA(code: string, trustDevice?: boolean): Promise<User>;
|
|
81
|
+
register(data: RegisterData): Promise<User>;
|
|
82
|
+
logout(reason?: 'logout' | 'revoke' | 'security'): Promise<void>;
|
|
83
|
+
refreshSession(): Promise<void>;
|
|
84
|
+
logoutAllDevices(): Promise<void>;
|
|
85
|
+
changePassword(currentPassword: string, newPassword: string): Promise<void>;
|
|
86
|
+
hasRole(role: string | string[]): boolean;
|
|
87
|
+
getAxiosInstance(): AxiosInstance;
|
|
88
|
+
getUser(): User | null;
|
|
89
|
+
getState(): AuthState;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
declare class SecureTokenStorage {
|
|
93
|
+
private static instance;
|
|
94
|
+
private encryptionKey;
|
|
95
|
+
private memoryCache;
|
|
96
|
+
private readonly TOKEN_VERSION;
|
|
97
|
+
private readonly SUPPORTED_VERSIONS;
|
|
98
|
+
private constructor();
|
|
99
|
+
static getInstance(): SecureTokenStorage;
|
|
100
|
+
private getOrCreateEncryptionKey;
|
|
101
|
+
private generateEncryptionKey;
|
|
102
|
+
private encrypt;
|
|
103
|
+
private decrypt;
|
|
104
|
+
setTokens(tokens: AuthTokens): void;
|
|
105
|
+
getTokens(): AuthTokens | null;
|
|
106
|
+
private migrateTokenFormat;
|
|
107
|
+
shouldRefreshToken(): boolean;
|
|
108
|
+
getTokenAge(): number | null;
|
|
109
|
+
setUser(user: User): void;
|
|
110
|
+
getUser(): User | null;
|
|
111
|
+
clear(): void;
|
|
112
|
+
hardClear(): void;
|
|
113
|
+
getStorageInfo(): {
|
|
114
|
+
hasTokens: boolean;
|
|
115
|
+
hasUser: boolean;
|
|
116
|
+
tokenVersion: string | null;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
declare class TokenManager {
|
|
121
|
+
private static instance;
|
|
122
|
+
private refreshPromise;
|
|
123
|
+
static getInstance(): TokenManager;
|
|
124
|
+
getAccessToken(): string | null;
|
|
125
|
+
isTokenExpired(): boolean;
|
|
126
|
+
refreshToken(refreshFn: () => Promise<AuthTokens>): Promise<string>;
|
|
127
|
+
private performRefresh;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
declare class TokenBlacklist {
|
|
131
|
+
private static instance;
|
|
132
|
+
private blacklist;
|
|
133
|
+
private cleanupInterval;
|
|
134
|
+
private constructor();
|
|
135
|
+
static getInstance(): TokenBlacklist;
|
|
136
|
+
addToBlacklist(token: string, userId: string, reason: TokenBlacklistEntry['reason']): Promise<void>;
|
|
137
|
+
isBlacklisted(token: string): Promise<boolean>;
|
|
138
|
+
revokeAllUserTokens(userId: string): Promise<void>;
|
|
139
|
+
private hashToken;
|
|
140
|
+
private syncToBackend;
|
|
141
|
+
private revokeAllOnBackend;
|
|
142
|
+
private startCleanup;
|
|
143
|
+
stopCleanup(): void;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
declare class MFAService {
|
|
147
|
+
private static instance;
|
|
148
|
+
static getInstance(): MFAService;
|
|
149
|
+
setupMFA(userId: string, email: string): Promise<{
|
|
150
|
+
secret: string;
|
|
151
|
+
qrCode: string;
|
|
152
|
+
backupCodes: string[];
|
|
153
|
+
}>;
|
|
154
|
+
verifyAndEnableMFA(userId: string, code: string): Promise<boolean>;
|
|
155
|
+
verifyMFA(userId: string, code: string): Promise<boolean>;
|
|
156
|
+
private generateBackupCodes;
|
|
157
|
+
private saveMFAConfig;
|
|
158
|
+
private getUserMFASecret;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
declare class RateLimiter {
|
|
162
|
+
private static instance;
|
|
163
|
+
private attempts;
|
|
164
|
+
private readonly MAX_ATTEMPTS;
|
|
165
|
+
private readonly WINDOW_MS;
|
|
166
|
+
private readonly BLOCK_DURATION_MS;
|
|
167
|
+
static getInstance(): RateLimiter;
|
|
168
|
+
checkRateLimit(identifier: string): {
|
|
169
|
+
allowed: boolean;
|
|
170
|
+
waitTime?: number;
|
|
171
|
+
};
|
|
172
|
+
reset(identifier: string): void;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
declare enum AuditAction {
|
|
176
|
+
LOGIN_SUCCESS = "LOGIN_SUCCESS",
|
|
177
|
+
LOGIN_FAILURE = "LOGIN_FAILURE",
|
|
178
|
+
LOGOUT = "LOGOUT",
|
|
179
|
+
REGISTER_SUCCESS = "REGISTER_SUCCESS",
|
|
180
|
+
PASSWORD_CHANGE = "PASSWORD_CHANGE",
|
|
181
|
+
TOKEN_REFRESH = "TOKEN_REFRESH",
|
|
182
|
+
SESSION_TERMINATED = "SESSION_TERMINATED",
|
|
183
|
+
MFA_ENABLED = "MFA_ENABLED",
|
|
184
|
+
MFA_DISABLED = "MFA_DISABLED",
|
|
185
|
+
MFA_VERIFIED = "MFA_VERIFIED"
|
|
186
|
+
}
|
|
187
|
+
interface AuditEntry {
|
|
188
|
+
action: AuditAction;
|
|
189
|
+
userId?: string;
|
|
190
|
+
email?: string;
|
|
191
|
+
ipAddress: string;
|
|
192
|
+
userAgent: string;
|
|
193
|
+
timestamp: Date;
|
|
194
|
+
details?: Record<string, any>;
|
|
195
|
+
success: boolean;
|
|
196
|
+
errorMessage?: string;
|
|
197
|
+
}
|
|
198
|
+
declare class AuditLogger {
|
|
199
|
+
private static instance;
|
|
200
|
+
private logQueue;
|
|
201
|
+
private isFlushing;
|
|
202
|
+
static getInstance(): AuditLogger;
|
|
203
|
+
log(entry: Omit<AuditEntry, 'timestamp' | 'ipAddress' | 'userAgent'>): Promise<void>;
|
|
204
|
+
private scheduleFlush;
|
|
205
|
+
private flush;
|
|
206
|
+
private getClientIP;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
declare class SessionManager {
|
|
210
|
+
private static instance;
|
|
211
|
+
private activeSessions;
|
|
212
|
+
static getInstance(): SessionManager;
|
|
213
|
+
registerSession(userId: string): Promise<string>;
|
|
214
|
+
validateSession(userId: string, deviceId: string): Promise<boolean>;
|
|
215
|
+
terminateSession(userId: string): Promise<void>;
|
|
216
|
+
terminateAllOtherSessions(userId: string, currentDeviceId: string): Promise<void>;
|
|
217
|
+
getActiveSessionsCount(userId: string): number;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
declare class DeviceFingerprint {
|
|
221
|
+
static generate(): Promise<string>;
|
|
222
|
+
private static hashObject;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
declare function useAuth(apiUrl?: string): {
|
|
226
|
+
login: (credentials: LoginCredentials) => Promise<{
|
|
227
|
+
requiresMFA: boolean;
|
|
228
|
+
user?: User;
|
|
229
|
+
}>;
|
|
230
|
+
verifyMFA: (code: string, trustDevice?: boolean) => Promise<User>;
|
|
231
|
+
register: (data: RegisterData) => Promise<User>;
|
|
232
|
+
logout: () => Promise<void>;
|
|
233
|
+
refreshSession: () => Promise<void>;
|
|
234
|
+
hasRole: (role: string | string[]) => boolean;
|
|
235
|
+
user: User | null;
|
|
236
|
+
tokens: AuthTokens | null;
|
|
237
|
+
isAuthenticated: boolean;
|
|
238
|
+
isLoading: boolean;
|
|
239
|
+
error: string | null;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
declare function useProtectedRoute(redirectTo?: string, requiredRole?: string | string[]): {
|
|
243
|
+
isAuthenticated: boolean;
|
|
244
|
+
isLoading: boolean;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
interface AuthProviderProps {
|
|
248
|
+
children: React$1.ReactNode;
|
|
249
|
+
apiUrl?: string;
|
|
250
|
+
}
|
|
251
|
+
declare function AuthProvider({ children, apiUrl }: AuthProviderProps): react_jsx_runtime.JSX.Element;
|
|
252
|
+
|
|
253
|
+
interface ProtectedRouteProps {
|
|
254
|
+
children: React.ReactNode;
|
|
255
|
+
requiredRole?: string | string[];
|
|
256
|
+
fallback?: React.ReactNode;
|
|
257
|
+
}
|
|
258
|
+
declare function ProtectedRoute({ children, requiredRole, fallback }: ProtectedRouteProps): react_jsx_runtime.JSX.Element | null;
|
|
259
|
+
|
|
260
|
+
interface MFASetupProps {
|
|
261
|
+
userId: string;
|
|
262
|
+
email: string;
|
|
263
|
+
onComplete: () => void;
|
|
264
|
+
onCancel: () => void;
|
|
265
|
+
}
|
|
266
|
+
declare function MFASetup({ userId, email, onComplete, onCancel }: MFASetupProps): react_jsx_runtime.JSX.Element;
|
|
267
|
+
|
|
268
|
+
interface MFAVerificationProps {
|
|
269
|
+
onSubmit: (code: string, trustDevice: boolean) => Promise<void>;
|
|
270
|
+
onBack?: () => void;
|
|
271
|
+
}
|
|
272
|
+
declare function MFAVerification({ onSubmit, onBack }: MFAVerificationProps): react_jsx_runtime.JSX.Element;
|
|
273
|
+
|
|
274
|
+
declare function SessionWarning({ warningMinutes }: {
|
|
275
|
+
warningMinutes?: number;
|
|
276
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
277
|
+
|
|
278
|
+
declare function setupAuthInterceptors(axiosInstance: AxiosInstance, baseURL: string): void;
|
|
279
|
+
|
|
280
|
+
declare function cn(...classes: (string | undefined | false)[]): string;
|
|
281
|
+
declare function getErrorMessage(error: unknown): string;
|
|
282
|
+
declare function isValidEmail(email: string): boolean;
|
|
283
|
+
|
|
284
|
+
declare const AUTH_CONFIG: {
|
|
285
|
+
tokenRefreshThreshold: number;
|
|
286
|
+
maxLoginAttempts: number;
|
|
287
|
+
lockoutDuration: number;
|
|
288
|
+
sessionTimeout: number;
|
|
289
|
+
mfaCodeLength: number;
|
|
290
|
+
backupCodesCount: number;
|
|
291
|
+
trustDeviceDuration: number;
|
|
292
|
+
};
|
|
293
|
+
declare const STORAGE_KEYS: {
|
|
294
|
+
tokens: string;
|
|
295
|
+
user: string;
|
|
296
|
+
encryptionKey: string;
|
|
297
|
+
deviceId: string;
|
|
298
|
+
trustedDevices: string;
|
|
299
|
+
};
|
|
300
|
+
declare const API_ENDPOINTS: {
|
|
301
|
+
login: string;
|
|
302
|
+
logout: string;
|
|
303
|
+
register: string;
|
|
304
|
+
refresh: string;
|
|
305
|
+
mfaSetup: string;
|
|
306
|
+
mfaVerify: string;
|
|
307
|
+
mfaDisable: string;
|
|
308
|
+
changePassword: string;
|
|
309
|
+
audit: string;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export { API_ENDPOINTS, AUTH_CONFIG, AuditAction, type AuditEntry$1 as AuditEntry, AuditLogger, AuthProvider, AuthService, type AuthState, type AuthTokens, DeviceFingerprint, type LoginCredentials, MFAService, MFASetup, MFAVerification, type MFAVerify, ProtectedRoute, RateLimiter, type RegisterData, STORAGE_KEYS, SessionManager, SessionWarning, TokenBlacklist, type TokenBlacklistEntry, TokenManager, SecureTokenStorage as TokenStorage, type User, cn, getErrorMessage, isValidEmail, setupAuthInterceptors, useAuth, useProtectedRoute };
|