next-token-auth 1.0.13 → 1.0.15
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 +165 -65
- package/dist/{index-CejL5heu.d.mts → index-Csz5lfEv.d.mts} +30 -2
- package/dist/{index-CejL5heu.d.ts → index-Csz5lfEv.d.ts} +30 -2
- package/dist/index.d.mts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +84 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +85 -46
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +4 -4
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +53 -526
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +54 -527
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.d.mts +45 -7
- package/dist/server/index.d.ts +45 -7
- package/dist/server/index.js +178 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +178 -1
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext,
|
|
1
|
+
import { createContext, useState, useEffect, useCallback, useContext } from 'react';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
// src/utils/expiry.ts
|
|
@@ -218,6 +218,11 @@ function getTextDecoder() {
|
|
|
218
218
|
return new TextDecoder();
|
|
219
219
|
}
|
|
220
220
|
async function deriveKey(secret) {
|
|
221
|
+
if (!secret) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
"[next-token-auth] `secret` is undefined. If using cookie storage, ensure AUTH_SECRET is set in your environment."
|
|
224
|
+
);
|
|
225
|
+
}
|
|
221
226
|
const raw = getTextEncoder().encode(secret.padEnd(32, "0").slice(0, 32));
|
|
222
227
|
return crypto.subtle.importKey("raw", raw, { name: ALGO }, false, [
|
|
223
228
|
"encrypt",
|
|
@@ -266,6 +271,8 @@ function base64ToBuffer(b64) {
|
|
|
266
271
|
var TokenManager = class {
|
|
267
272
|
constructor(config) {
|
|
268
273
|
this.memoryStore = null;
|
|
274
|
+
/** In-memory cache of the last successfully decrypted cookie value. */
|
|
275
|
+
this.decryptedCache = null;
|
|
269
276
|
this.config = config;
|
|
270
277
|
}
|
|
271
278
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
@@ -273,7 +280,23 @@ var TokenManager = class {
|
|
|
273
280
|
if (this.config.token.storage === "memory") {
|
|
274
281
|
return this.memoryStore;
|
|
275
282
|
}
|
|
276
|
-
return this.
|
|
283
|
+
return this.decryptedCache;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Must be called once on startup (before getTokens) when storage is "cookie".
|
|
287
|
+
* Reads and decrypts the cookie, populating the in-memory cache.
|
|
288
|
+
*/
|
|
289
|
+
async initFromCookie() {
|
|
290
|
+
if (typeof document === "undefined") return;
|
|
291
|
+
if (this.config.token.storage !== "cookie") return;
|
|
292
|
+
const raw = getCookieValue(this.cookieName());
|
|
293
|
+
if (!raw) return;
|
|
294
|
+
try {
|
|
295
|
+
const json = await decrypt(decodeURIComponent(raw), this.config.secret);
|
|
296
|
+
this.decryptedCache = JSON.parse(json);
|
|
297
|
+
} catch {
|
|
298
|
+
this.decryptedCache = null;
|
|
299
|
+
}
|
|
277
300
|
}
|
|
278
301
|
async setTokens(tokens) {
|
|
279
302
|
if (this.config.token.storage === "memory") {
|
|
@@ -284,6 +307,7 @@ var TokenManager = class {
|
|
|
284
307
|
}
|
|
285
308
|
clearTokens() {
|
|
286
309
|
this.memoryStore = null;
|
|
310
|
+
this.decryptedCache = null;
|
|
287
311
|
if (this.config.token.storage === "cookie") {
|
|
288
312
|
this.deleteCookie();
|
|
289
313
|
}
|
|
@@ -300,29 +324,20 @@ var TokenManager = class {
|
|
|
300
324
|
cookieName() {
|
|
301
325
|
return this.config.token.cookieName ?? "next-token-auth.session";
|
|
302
326
|
}
|
|
303
|
-
readFromCookie() {
|
|
304
|
-
if (typeof document === "undefined") return null;
|
|
305
|
-
const raw = getCookieValue(this.cookieName());
|
|
306
|
-
if (!raw) return null;
|
|
307
|
-
try {
|
|
308
|
-
return JSON.parse(decodeURIComponent(raw));
|
|
309
|
-
} catch {
|
|
310
|
-
return null;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
327
|
async writeToCookie(tokens) {
|
|
314
328
|
if (typeof document === "undefined") return;
|
|
315
|
-
const value =
|
|
329
|
+
const value = await encrypt(JSON.stringify(tokens), this.config.secret);
|
|
316
330
|
const secure = this.config.token.secure !== false ? "; Secure" : "";
|
|
317
331
|
const sameSite = this.config.token.sameSite ?? "lax";
|
|
318
332
|
const maxAge = tokens.refreshTokenExpiresAt ? Math.floor((tokens.refreshTokenExpiresAt - Date.now()) / 1e3) : 604800;
|
|
319
333
|
document.cookie = [
|
|
320
|
-
`${this.cookieName()}=${value}`,
|
|
334
|
+
`${this.cookieName()}=${encodeURIComponent(value)}`,
|
|
321
335
|
`Max-Age=${maxAge}`,
|
|
322
336
|
`Path=/`,
|
|
323
337
|
`SameSite=${sameSite}`,
|
|
324
338
|
secure
|
|
325
339
|
].filter(Boolean).join("; ");
|
|
340
|
+
this.decryptedCache = tokens;
|
|
326
341
|
}
|
|
327
342
|
deleteCookie() {
|
|
328
343
|
if (typeof document === "undefined") return;
|
|
@@ -330,7 +345,8 @@ var TokenManager = class {
|
|
|
330
345
|
}
|
|
331
346
|
// ─── Server-side helpers ─────────────────────────────────────────────────────
|
|
332
347
|
/**
|
|
333
|
-
* Encrypts tokens
|
|
348
|
+
* Encrypts tokens — used internally by writeToCookie and available for
|
|
349
|
+
* advanced server-side use cases.
|
|
334
350
|
*/
|
|
335
351
|
async encryptTokens(tokens) {
|
|
336
352
|
return encrypt(JSON.stringify(tokens), this.config.secret);
|
|
@@ -455,6 +471,7 @@ var AuthClient = class {
|
|
|
455
471
|
* Loads the session from stored tokens (call on app mount).
|
|
456
472
|
*/
|
|
457
473
|
async initialize() {
|
|
474
|
+
await this.tokenManager.initFromCookie();
|
|
458
475
|
const session = await this.sessionManager.loadSession();
|
|
459
476
|
if (session.isAuthenticated && session.tokens && this.tokenManager.isAccessExpired(session.tokens) && !this.tokenManager.isRefreshExpired(session.tokens)) {
|
|
460
477
|
await this.refresh();
|
|
@@ -496,15 +513,7 @@ var AuthClient = class {
|
|
|
496
513
|
}
|
|
497
514
|
};
|
|
498
515
|
var AuthContext = createContext(null);
|
|
499
|
-
function AuthProvider({
|
|
500
|
-
config,
|
|
501
|
-
children
|
|
502
|
-
}) {
|
|
503
|
-
const clientRef = useRef(null);
|
|
504
|
-
if (!clientRef.current) {
|
|
505
|
-
clientRef.current = new AuthClient(config);
|
|
506
|
-
}
|
|
507
|
-
const client = clientRef.current;
|
|
516
|
+
function AuthProvider({ config, children }) {
|
|
508
517
|
const [session, setSession] = useState({
|
|
509
518
|
user: null,
|
|
510
519
|
tokens: null,
|
|
@@ -513,57 +522,87 @@ function AuthProvider({
|
|
|
513
522
|
const [isLoading, setIsLoading] = useState(true);
|
|
514
523
|
useEffect(() => {
|
|
515
524
|
let cancelled = false;
|
|
516
|
-
|
|
525
|
+
fetch("/api/auth/session").then((res) => res.json()).then((data) => {
|
|
517
526
|
if (!cancelled) {
|
|
518
|
-
setSession(
|
|
527
|
+
setSession({
|
|
528
|
+
user: data.user ?? null,
|
|
529
|
+
tokens: null,
|
|
530
|
+
// tokens are HttpOnly, never exposed to client
|
|
531
|
+
isAuthenticated: data.isAuthenticated ?? false
|
|
532
|
+
});
|
|
533
|
+
setIsLoading(false);
|
|
534
|
+
}
|
|
535
|
+
}).catch(() => {
|
|
536
|
+
if (!cancelled) {
|
|
537
|
+
setSession({ user: null, tokens: null, isAuthenticated: false });
|
|
519
538
|
setIsLoading(false);
|
|
520
539
|
}
|
|
521
|
-
});
|
|
522
|
-
const unsubscribe = client.subscribe((s) => {
|
|
523
|
-
if (!cancelled) setSession(s);
|
|
524
540
|
});
|
|
525
541
|
return () => {
|
|
526
542
|
cancelled = true;
|
|
527
|
-
unsubscribe();
|
|
528
543
|
};
|
|
529
|
-
}, [
|
|
544
|
+
}, []);
|
|
530
545
|
useEffect(() => {
|
|
531
546
|
if (!config.autoRefresh) return;
|
|
532
547
|
const interval = setInterval(async () => {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
548
|
+
if (session.isAuthenticated) {
|
|
549
|
+
try {
|
|
550
|
+
await fetch("/api/auth/refresh", { method: "POST" });
|
|
551
|
+
const updated = await fetch("/api/auth/session").then((r) => r.json());
|
|
552
|
+
setSession({
|
|
553
|
+
user: updated.user ?? null,
|
|
554
|
+
tokens: null,
|
|
555
|
+
isAuthenticated: updated.isAuthenticated ?? false
|
|
556
|
+
});
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
536
559
|
}
|
|
537
|
-
},
|
|
560
|
+
}, (config.refreshThreshold ?? 60) * 1e3);
|
|
538
561
|
return () => clearInterval(interval);
|
|
539
|
-
}, [
|
|
562
|
+
}, [config.autoRefresh, config.refreshThreshold, session.isAuthenticated]);
|
|
540
563
|
const login = useCallback(
|
|
541
564
|
async (input) => {
|
|
542
565
|
setIsLoading(true);
|
|
543
566
|
try {
|
|
544
|
-
const
|
|
545
|
-
|
|
567
|
+
const res = await fetch("/api/auth/login", {
|
|
568
|
+
method: "POST",
|
|
569
|
+
headers: { "Content-Type": "application/json" },
|
|
570
|
+
body: JSON.stringify(input)
|
|
571
|
+
});
|
|
572
|
+
if (!res.ok) {
|
|
573
|
+
const { error } = await res.json();
|
|
574
|
+
throw new Error(error ?? "Login failed");
|
|
575
|
+
}
|
|
576
|
+
const { user } = await res.json();
|
|
577
|
+
const newSession = { user, tokens: null, isAuthenticated: true };
|
|
578
|
+
setSession(newSession);
|
|
579
|
+
config.onLogin?.(newSession);
|
|
546
580
|
} finally {
|
|
547
581
|
setIsLoading(false);
|
|
548
582
|
}
|
|
549
583
|
},
|
|
550
|
-
[
|
|
584
|
+
[config]
|
|
551
585
|
);
|
|
552
586
|
const logout = useCallback(async () => {
|
|
553
|
-
await
|
|
587
|
+
await fetch("/api/auth/logout", { method: "POST" });
|
|
554
588
|
setSession({ user: null, tokens: null, isAuthenticated: false });
|
|
555
|
-
|
|
589
|
+
config.onLogout?.();
|
|
590
|
+
}, [config]);
|
|
556
591
|
const refresh = useCallback(async () => {
|
|
557
|
-
await
|
|
558
|
-
|
|
559
|
-
|
|
592
|
+
await fetch("/api/auth/refresh", { method: "POST" });
|
|
593
|
+
const updated = await fetch("/api/auth/session").then((r) => r.json());
|
|
594
|
+
setSession({
|
|
595
|
+
user: updated.user ?? null,
|
|
596
|
+
tokens: null,
|
|
597
|
+
isAuthenticated: updated.isAuthenticated ?? false
|
|
598
|
+
});
|
|
599
|
+
}, []);
|
|
560
600
|
const value = {
|
|
561
601
|
session,
|
|
562
602
|
isLoading,
|
|
563
603
|
login,
|
|
564
604
|
logout,
|
|
565
|
-
refresh
|
|
566
|
-
client
|
|
605
|
+
refresh
|
|
567
606
|
};
|
|
568
607
|
return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
|
|
569
608
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/expiry.ts","../src/core/HttpClient.ts","../src/core/SessionManager.ts","../src/utils/crypto.ts","../src/core/TokenManager.ts","../src/core/AuthClient.ts","../src/react/AuthProvider.tsx","../src/react/hooks/useAuth.ts","../src/react/hooks/useSession.ts","../src/react/hooks/useRequireAuth.ts"],"names":["useEffect"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ExpiryInput, ExpiryStrategy, LoginResponse } from \"../types\";\n\nconst UNIT_MAP: Record<string, number> = {\n s: 1,\n m: 60,\n h: 3600,\n d: 86400,\n w: 604800,\n};\n\n/**\n * Parses an expiry value into seconds.\n * Accepts:\n * - number → treated as seconds\n * - string → e.g. \"15m\", \"2h\", \"2d\", \"7d\", \"1w\"\n *\n * @throws if the format is unrecognised\n */\nexport function parseExpiry(input?: ExpiryInput): number {\n if (input === undefined || input === null) {\n throw new Error(\"parseExpiry: no expiry value provided\");\n }\n\n if (typeof input === \"number\") {\n if (input <= 0) throw new Error(\"parseExpiry: value must be positive\");\n return input;\n }\n\n const trimmed = input.trim();\n\n // Pure numeric string\n if (/^\\d+$/.test(trimmed)) {\n return parseInt(trimmed, 10);\n }\n\n const match = trimmed.match(/^(\\d+(?:\\.\\d+)?)\\s*([smhdw])$/i);\n if (!match) {\n throw new Error(\n `parseExpiry: unrecognised format \"${input}\". ` +\n `Expected a number or a string like \"15m\", \"2h\", \"2d\", \"7d\", \"1w\".`\n );\n }\n\n const value = parseFloat(match[1]);\n const unit = match[2].toLowerCase();\n return Math.floor(value * UNIT_MAP[unit]);\n}\n\n/**\n * Safely parses an expiry value, returning a fallback on failure.\n */\nexport function safeParseExpiry(\n input?: ExpiryInput,\n fallbackSeconds = 900\n): number {\n try {\n return parseExpiry(input);\n } catch {\n return fallbackSeconds;\n }\n}\n\n/**\n * Resolves the access token expiry timestamp (ms) from a login response\n * using the configured strategy.\n */\nexport function resolveAccessTokenExpiry(\n response: LoginResponse,\n configExpiry?: ExpiryInput,\n strategy: ExpiryStrategy = \"hybrid\"\n): number {\n const now = Date.now();\n\n const fromBackend =\n response.accessTokenExpiresIn ?? response.expiresIn ?? undefined;\n\n if (strategy === \"backend\") {\n if (fromBackend === undefined) {\n throw new Error(\n 'resolveAccessTokenExpiry: strategy is \"backend\" but API returned no expiry'\n );\n }\n return now + parseExpiry(fromBackend) * 1000;\n }\n\n if (strategy === \"config\") {\n if (configExpiry === undefined) {\n throw new Error(\n 'resolveAccessTokenExpiry: strategy is \"config\" but no expiry configured'\n );\n }\n return now + parseExpiry(configExpiry) * 1000;\n }\n\n // hybrid: backend first, fallback to config\n if (fromBackend !== undefined) {\n return now + safeParseExpiry(fromBackend) * 1000;\n }\n if (configExpiry !== undefined) {\n return now + safeParseExpiry(configExpiry) * 1000;\n }\n\n // Last resort: 15 minutes\n return now + 900 * 1000;\n}\n\n/**\n * Resolves the refresh token expiry timestamp (ms).\n */\nexport function resolveRefreshTokenExpiry(\n response: LoginResponse,\n configExpiry?: ExpiryInput,\n strategy: ExpiryStrategy = \"hybrid\"\n): number | undefined {\n const now = Date.now();\n const fromBackend = response.refreshTokenExpiresIn;\n\n if (strategy === \"backend\") {\n return fromBackend !== undefined\n ? now + parseExpiry(fromBackend) * 1000\n : undefined;\n }\n\n if (strategy === \"config\") {\n return configExpiry !== undefined\n ? now + parseExpiry(configExpiry) * 1000\n : undefined;\n }\n\n // hybrid\n if (fromBackend !== undefined) {\n return now + safeParseExpiry(fromBackend) * 1000;\n }\n if (configExpiry !== undefined) {\n return now + safeParseExpiry(configExpiry) * 1000;\n }\n\n return undefined;\n}\n","import type { AuthConfig } from \"../types\";\nimport type { TokenManager } from \"./TokenManager\";\n\ntype RefreshFn = () => Promise<boolean>;\n\n/**\n * Authenticated HTTP client that:\n * - Injects Authorization: Bearer <accessToken>\n * - Auto-refreshes on 401 and retries the original request once\n */\nexport class HttpClient {\n private readonly config: AuthConfig;\n private readonly tokenManager: TokenManager;\n private refreshFn: RefreshFn | null = null;\n private refreshPromise: Promise<boolean> | null = null;\n\n constructor(config: AuthConfig, tokenManager: TokenManager) {\n this.config = config;\n this.tokenManager = tokenManager;\n }\n\n /** Register the refresh callback (set by AuthClient to avoid circular deps) */\n setRefreshFn(fn: RefreshFn): void {\n this.refreshFn = fn;\n }\n\n /**\n * Authenticated fetch wrapper.\n * Automatically injects the Bearer token and handles 401 → refresh → retry.\n */\n async fetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n const tokens = this.tokenManager.getTokens();\n\n const headers = new Headers(init.headers);\n if (tokens?.accessToken) {\n headers.set(\"Authorization\", `Bearer ${tokens.accessToken}`);\n }\n\n const response = await this.doFetch(input, { ...init, headers });\n\n if (response.status === 401 && this.refreshFn) {\n const refreshed = await this.deduplicatedRefresh();\n if (refreshed) {\n // Retry with new token\n const newTokens = this.tokenManager.getTokens();\n if (newTokens?.accessToken) {\n headers.set(\"Authorization\", `Bearer ${newTokens.accessToken}`);\n }\n return this.doFetch(input, { ...init, headers });\n }\n }\n\n return response;\n }\n\n /**\n * Raw fetch using the configured fetchFn or global fetch.\n */\n async doFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const fetchFn = this.config.fetchFn ?? fetch;\n return fetchFn(input as RequestInfo, init);\n }\n\n /**\n * Builds a full URL from a path relative to baseUrl.\n */\n url(path: string): string {\n return `${this.config.baseUrl.replace(/\\/$/, \"\")}/${path.replace(/^\\//, \"\")}`;\n }\n\n // ─── Private ────────────────────────────────────────────────────────────────\n\n /**\n * Ensures only one refresh request is in-flight at a time.\n */\n private async deduplicatedRefresh(): Promise<boolean> {\n if (this.refreshPromise) return this.refreshPromise;\n\n this.refreshPromise = this.refreshFn!().finally(() => {\n this.refreshPromise = null;\n });\n\n return this.refreshPromise;\n }\n}\n","import type { AuthConfig, AuthSession, AuthTokens } from \"../types\";\nimport type { HttpClient } from \"./HttpClient\";\nimport type { TokenManager } from \"./TokenManager\";\n\n/**\n * Derives and caches the current AuthSession from stored tokens.\n * Fetches the user profile from the /me endpoint when available.\n */\nexport class SessionManager<User = unknown> {\n private session: AuthSession<User> = {\n user: null,\n tokens: null,\n isAuthenticated: false,\n };\n\n private readonly config: AuthConfig<User>;\n private readonly tokenManager: TokenManager;\n private readonly httpClient: HttpClient;\n\n constructor(\n config: AuthConfig<User>,\n tokenManager: TokenManager,\n httpClient: HttpClient\n ) {\n this.config = config;\n this.tokenManager = tokenManager;\n this.httpClient = httpClient;\n }\n\n getSession(): AuthSession<User> {\n return this.session;\n }\n\n setSession(session: AuthSession<User>): void {\n this.session = session;\n }\n\n /**\n * Builds a session from stored tokens, optionally fetching the user profile.\n */\n async loadSession(): Promise<AuthSession<User>> {\n const tokens = this.tokenManager.getTokens();\n\n if (!tokens) {\n this.session = { user: null, tokens: null, isAuthenticated: false };\n return this.session;\n }\n\n // If access token is expired and refresh is also expired, clear everything\n if (\n this.tokenManager.isAccessExpired(tokens) &&\n this.tokenManager.isRefreshExpired(tokens)\n ) {\n this.tokenManager.clearTokens();\n this.session = { user: null, tokens: null, isAuthenticated: false };\n return this.session;\n }\n\n const user = await this.fetchUser(tokens);\n this.session = {\n user,\n tokens,\n isAuthenticated: true,\n };\n\n return this.session;\n }\n\n /**\n * Updates the session after a successful token refresh.\n */\n async refreshSession(tokens: AuthTokens): Promise<void> {\n const user = this.session.user ?? (await this.fetchUser(tokens));\n this.session = { user, tokens, isAuthenticated: true };\n }\n\n clearSession(): void {\n this.session = { user: null, tokens: null, isAuthenticated: false };\n }\n\n // ─── Private ────────────────────────────────────────────────────────────────\n\n private async fetchUser(tokens: AuthTokens): Promise<User | null> {\n const meEndpoint = this.config.endpoints.me;\n if (!meEndpoint) return null;\n\n try {\n const res = await this.httpClient.fetch(this.httpClient.url(meEndpoint));\n if (!res.ok) return null;\n return (await res.json()) as User;\n } catch {\n return null;\n }\n }\n}\n","/**\n * Lightweight symmetric encryption using AES-GCM via the Web Crypto API.\n * Works in both browser and Node.js (>=18) / Edge runtimes.\n */\n\nconst ALGO = \"AES-GCM\";\nconst IV_LENGTH = 12; // bytes\n\nfunction getTextEncoder() {\n return new TextEncoder();\n}\n\nfunction getTextDecoder() {\n return new TextDecoder();\n}\n\nasync function deriveKey(secret: string): Promise<CryptoKey> {\n const raw = getTextEncoder().encode(secret.padEnd(32, \"0\").slice(0, 32));\n return crypto.subtle.importKey(\"raw\", raw, { name: ALGO }, false, [\n \"encrypt\",\n \"decrypt\",\n ]);\n}\n\n/**\n * Encrypts a plaintext string using AES-GCM.\n * Returns a base64url-encoded string: `<iv>.<ciphertext>`\n */\nexport async function encrypt(data: string, secret: string): Promise<string> {\n const key = await deriveKey(secret);\n const ivArray = crypto.getRandomValues(new Uint8Array(IV_LENGTH));\n // Ensure we have a plain ArrayBuffer for SubtleCrypto\n const iv = ivArray.buffer.slice(0, IV_LENGTH) as ArrayBuffer;\n const encoded = getTextEncoder().encode(data);\n\n const cipherBuffer = await crypto.subtle.encrypt({ name: ALGO, iv }, key, encoded);\n\n const ivB64 = bufferToBase64(new Uint8Array(iv));\n const cipherB64 = bufferToBase64(new Uint8Array(cipherBuffer));\n return `${ivB64}.${cipherB64}`;\n}\n\n/**\n * Decrypts a string produced by `encrypt`.\n */\nexport async function decrypt(data: string, secret: string): Promise<string> {\n const [ivB64, cipherB64] = data.split(\".\");\n if (!ivB64 || !cipherB64) {\n throw new Error(\"decrypt: invalid ciphertext format\");\n }\n\n const key = await deriveKey(secret);\n const ivBytes = base64ToBuffer(ivB64);\n const iv = ivBytes.buffer.slice(\n ivBytes.byteOffset,\n ivBytes.byteOffset + ivBytes.byteLength\n ) as ArrayBuffer;\n\n const cipherBytes = base64ToBuffer(cipherB64);\n const cipherBuffer = cipherBytes.buffer.slice(\n cipherBytes.byteOffset,\n cipherBytes.byteOffset + cipherBytes.byteLength\n ) as ArrayBuffer;\n\n const plainBuffer = await crypto.subtle.decrypt({ name: ALGO, iv }, key, cipherBuffer);\n\n return getTextDecoder().decode(plainBuffer);\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction bufferToBase64(buffer: Uint8Array): string {\n return btoa(String.fromCharCode(...buffer))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nfunction base64ToBuffer(b64: string): Uint8Array {\n const padded = b64.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const binary = atob(padded);\n return Uint8Array.from(binary, (c) => c.charCodeAt(0));\n}\n","import type { AuthConfig, AuthTokens } from \"../types\";\nimport { encrypt, decrypt } from \"../utils/crypto\";\n\n/**\n * Manages storage, retrieval, and expiry checks for auth tokens.\n * Supports \"cookie\" and \"memory\" storage strategies.\n */\nexport class TokenManager {\n private memoryStore: AuthTokens | null = null;\n private readonly config: AuthConfig;\n\n constructor(config: AuthConfig) {\n this.config = config;\n }\n\n // ─── Public API ─────────────────────────────────────────────────────────────\n\n getTokens(): AuthTokens | null {\n if (this.config.token.storage === \"memory\") {\n return this.memoryStore;\n }\n return this.readFromCookie();\n }\n\n async setTokens(tokens: AuthTokens): Promise<void> {\n if (this.config.token.storage === \"memory\") {\n this.memoryStore = tokens;\n return;\n }\n await this.writeToCookie(tokens);\n }\n\n clearTokens(): void {\n this.memoryStore = null;\n if (this.config.token.storage === \"cookie\") {\n this.deleteCookie();\n }\n }\n\n isAccessExpired(tokens: AuthTokens): boolean {\n const threshold = (this.config.refreshThreshold ?? 60) * 1000;\n return Date.now() >= tokens.accessTokenExpiresAt - threshold;\n }\n\n isRefreshExpired(tokens: AuthTokens): boolean {\n if (!tokens.refreshTokenExpiresAt) return false;\n return Date.now() >= tokens.refreshTokenExpiresAt;\n }\n\n // ─── Cookie helpers (client-side only) ──────────────────────────────────────\n\n private cookieName(): string {\n return this.config.token.cookieName ?? \"next-token-auth.session\";\n }\n\n private readFromCookie(): AuthTokens | null {\n if (typeof document === \"undefined\") return null;\n\n const raw = getCookieValue(this.cookieName());\n if (!raw) return null;\n\n try {\n // Tokens are stored as JSON; encryption is applied server-side via\n // getServerSession. Client reads the plaintext access token from cookie.\n return JSON.parse(decodeURIComponent(raw)) as AuthTokens;\n } catch {\n return null;\n }\n }\n\n private async writeToCookie(tokens: AuthTokens): Promise<void> {\n if (typeof document === \"undefined\") return;\n\n const value = encodeURIComponent(JSON.stringify(tokens));\n const secure = this.config.token.secure !== false ? \"; Secure\" : \"\";\n const sameSite = this.config.token.sameSite ?? \"lax\";\n const maxAge = tokens.refreshTokenExpiresAt\n ? Math.floor((tokens.refreshTokenExpiresAt - Date.now()) / 1000)\n : 604800; // 7 days default\n\n document.cookie = [\n `${this.cookieName()}=${value}`,\n `Max-Age=${maxAge}`,\n `Path=/`,\n `SameSite=${sameSite}`,\n secure,\n ]\n .filter(Boolean)\n .join(\"; \");\n }\n\n private deleteCookie(): void {\n if (typeof document === \"undefined\") return;\n document.cookie = `${this.cookieName()}=; Max-Age=0; Path=/`;\n }\n\n // ─── Server-side helpers ─────────────────────────────────────────────────────\n\n /**\n * Encrypts tokens for secure server-side cookie storage.\n */\n async encryptTokens(tokens: AuthTokens): Promise<string> {\n return encrypt(JSON.stringify(tokens), this.config.secret);\n }\n\n /**\n * Decrypts tokens from a server-side cookie value.\n */\n async decryptTokens(ciphertext: string): Promise<AuthTokens | null> {\n try {\n const json = await decrypt(ciphertext, this.config.secret);\n return JSON.parse(json) as AuthTokens;\n } catch {\n return null;\n }\n }\n}\n\n// ─── Utility ──────────────────────────────────────────────────────────────────\n\nfunction getCookieValue(name: string): string | null {\n if (typeof document === \"undefined\") return null;\n const match = document.cookie.match(\n new RegExp(`(?:^|;\\\\s*)${escapeRegex(name)}=([^;]*)`)\n );\n return match ? match[1] : null;\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","import type {\n AuthConfig,\n AuthSession,\n AuthTokens,\n LoginInput,\n LoginResponse,\n} from \"../types\";\nimport { resolveAccessTokenExpiry, resolveRefreshTokenExpiry } from \"../utils/expiry\";\nimport { HttpClient } from \"./HttpClient\";\nimport { SessionManager } from \"./SessionManager\";\nimport { TokenManager } from \"./TokenManager\";\n\n/**\n * Central orchestrator for authentication operations.\n * Coordinates TokenManager, SessionManager, and HttpClient.\n */\nexport class AuthClient<User = unknown> {\n readonly tokenManager: TokenManager;\n readonly sessionManager: SessionManager<User>;\n readonly httpClient: HttpClient;\n\n private readonly config: AuthConfig<User>;\n private sessionListeners: Array<(session: AuthSession<User>) => void> = [];\n\n constructor(config: AuthConfig<User>) {\n this.config = config;\n this.tokenManager = new TokenManager(config as AuthConfig<unknown>);\n this.httpClient = new HttpClient(config as AuthConfig<unknown>, this.tokenManager);\n this.sessionManager = new SessionManager(\n config,\n this.tokenManager,\n this.httpClient\n );\n\n // Wire up the refresh callback\n this.httpClient.setRefreshFn(() => this.refresh());\n }\n\n // ─── Auth Operations ────────────────────────────────────────────────────────\n\n /**\n * Authenticates the user and stores tokens.\n */\n async login(input: LoginInput): Promise<AuthSession<User>> {\n const res = await this.httpClient.doFetch(\n this.httpClient.url(this.config.endpoints.login),\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(input),\n }\n );\n\n if (!res.ok) {\n const error = await res.text();\n throw new Error(`Login failed (${res.status}): ${error}`);\n }\n\n const data = (await res.json()) as LoginResponse<User>;\n const tokens = this.buildTokens(data);\n\n await this.tokenManager.setTokens(tokens);\n await this.sessionManager.loadSession();\n\n const session = this.sessionManager.getSession();\n this.config.onLogin?.(session);\n this.notifyListeners(session);\n\n return session;\n }\n\n /**\n * Logs out the user, clears tokens, and optionally calls the backend.\n */\n async logout(): Promise<void> {\n const logoutEndpoint = this.config.endpoints.logout;\n\n if (logoutEndpoint) {\n try {\n await this.httpClient.fetch(this.httpClient.url(logoutEndpoint), {\n method: \"POST\",\n });\n } catch {\n // Best-effort logout\n }\n }\n\n this.tokenManager.clearTokens();\n this.sessionManager.clearSession();\n this.config.onLogout?.();\n this.notifyListeners(this.sessionManager.getSession());\n }\n\n /**\n * Refreshes the access token using the stored refresh token.\n * Returns true on success, false on failure.\n */\n async refresh(): Promise<boolean> {\n const tokens = this.tokenManager.getTokens();\n\n if (!tokens) return false;\n if (this.tokenManager.isRefreshExpired(tokens)) {\n await this.logout();\n return false;\n }\n\n try {\n const res = await this.httpClient.doFetch(\n this.httpClient.url(this.config.endpoints.refresh),\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refreshToken: tokens.refreshToken }),\n }\n );\n\n if (!res.ok) {\n await this.logout();\n return false;\n }\n\n const data = (await res.json()) as LoginResponse<User>;\n const newTokens = this.buildTokens(data);\n\n await this.tokenManager.setTokens(newTokens);\n await this.sessionManager.refreshSession(newTokens);\n this.notifyListeners(this.sessionManager.getSession());\n\n return true;\n } catch (err) {\n this.config.onRefreshError?.(err);\n return false;\n }\n }\n\n /**\n * Loads the session from stored tokens (call on app mount).\n */\n async initialize(): Promise<AuthSession<User>> {\n const session = await this.sessionManager.loadSession();\n\n // Proactively refresh if access token is near expiry\n if (\n session.isAuthenticated &&\n session.tokens &&\n this.tokenManager.isAccessExpired(session.tokens) &&\n !this.tokenManager.isRefreshExpired(session.tokens)\n ) {\n await this.refresh();\n }\n\n return this.sessionManager.getSession();\n }\n\n getSession(): AuthSession<User> {\n return this.sessionManager.getSession();\n }\n\n /**\n * Returns the authenticated fetch wrapper.\n */\n get fetch(): HttpClient[\"fetch\"] {\n return this.httpClient.fetch.bind(this.httpClient);\n }\n\n // ─── Subscription ────────────────────────────────────────────────────────────\n\n subscribe(listener: (session: AuthSession<User>) => void): () => void {\n this.sessionListeners.push(listener);\n return () => {\n this.sessionListeners = this.sessionListeners.filter((l) => l !== listener);\n };\n }\n\n // ─── Private ────────────────────────────────────────────────────────────────\n\n private buildTokens(data: LoginResponse<User>): AuthTokens {\n const strategy = this.config.expiry?.strategy ?? \"hybrid\";\n const configAccess = this.config.expiry?.accessTokenExpiresIn;\n const configRefresh = this.config.expiry?.refreshTokenExpiresIn;\n\n return {\n accessToken: data.accessToken,\n refreshToken: data.refreshToken,\n accessTokenExpiresAt: resolveAccessTokenExpiry(data, configAccess, strategy),\n refreshTokenExpiresAt: resolveRefreshTokenExpiry(data, configRefresh, strategy),\n };\n }\n\n private notifyListeners(session: AuthSession<User>): void {\n for (const listener of this.sessionListeners) {\n listener(session);\n }\n }\n}\n","\"use client\";\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { AuthClient } from \"../core/AuthClient\";\nimport type { AuthConfig, AuthSession, LoginInput } from \"../types\";\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\ninterface AuthContextValue<User = unknown> {\n session: AuthSession<User>;\n isLoading: boolean;\n login: (input: LoginInput) => Promise<void>;\n logout: () => Promise<void>;\n refresh: () => Promise<void>;\n /** The underlying AuthClient for advanced use cases */\n client: AuthClient<User>;\n}\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\n// ─── Provider ─────────────────────────────────────────────────────────────────\n\ninterface AuthProviderProps<User = unknown> {\n config: AuthConfig<User>;\n children: React.ReactNode;\n}\n\nexport function AuthProvider<User = unknown>({\n config,\n children,\n}: AuthProviderProps<User>) {\n const clientRef = useRef<AuthClient<User> | null>(null);\n\n if (!clientRef.current) {\n clientRef.current = new AuthClient<User>(config);\n }\n\n const client = clientRef.current;\n\n const [session, setSession] = useState<AuthSession<User>>({\n user: null,\n tokens: null,\n isAuthenticated: false,\n });\n const [isLoading, setIsLoading] = useState(true);\n\n // Initialize session on mount\n useEffect(() => {\n let cancelled = false;\n\n client.initialize().then((s) => {\n if (!cancelled) {\n setSession(s);\n setIsLoading(false);\n }\n });\n\n // Subscribe to session changes\n const unsubscribe = client.subscribe((s) => {\n if (!cancelled) setSession(s);\n });\n\n return () => {\n cancelled = true;\n unsubscribe();\n };\n }, [client]);\n\n // Auto-refresh timer\n useEffect(() => {\n if (!config.autoRefresh) return;\n\n const interval = setInterval(async () => {\n const tokens = client.tokenManager.getTokens();\n if (\n tokens &&\n client.tokenManager.isAccessExpired(tokens) &&\n !client.tokenManager.isRefreshExpired(tokens)\n ) {\n await client.refresh();\n }\n }, 30_000); // check every 30s\n\n return () => clearInterval(interval);\n }, [client, config.autoRefresh]);\n\n const login = useCallback(\n async (input: LoginInput) => {\n setIsLoading(true);\n try {\n const s = await client.login(input);\n setSession(s);\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n const logout = useCallback(async () => {\n await client.logout();\n setSession({ user: null, tokens: null, isAuthenticated: false });\n }, [client]);\n\n const refresh = useCallback(async () => {\n await client.refresh();\n setSession(client.getSession());\n }, [client]);\n\n const value: AuthContextValue<User> = {\n session,\n isLoading,\n login,\n logout,\n refresh,\n client,\n };\n\n return (\n <AuthContext.Provider value={value as AuthContextValue}>\n {children}\n </AuthContext.Provider>\n );\n}\n\n// ─── Internal hook ────────────────────────────────────────────────────────────\n\nexport function useAuthContext<User = unknown>(): AuthContextValue<User> {\n const ctx = useContext(AuthContext);\n if (!ctx) {\n throw new Error(\"useAuthContext must be used within <AuthProvider>\");\n }\n return ctx as AuthContextValue<User>;\n}\n","\"use client\";\n\nimport type { AuthSession, LoginInput } from \"../../types\";\nimport { useAuthContext } from \"../AuthProvider\";\n\nexport interface UseAuthReturn<User = unknown> {\n session: AuthSession<User>;\n login: (input: LoginInput) => Promise<void>;\n logout: () => Promise<void>;\n refresh: () => Promise<void>;\n isLoading: boolean;\n}\n\n/**\n * Primary hook for authentication operations.\n *\n * @example\n * const { session, login, logout, isLoading } = useAuth();\n */\nexport function useAuth<User = unknown>(): UseAuthReturn<User> {\n const { session, login, logout, refresh, isLoading } =\n useAuthContext<User>();\n\n return { session, login, logout, refresh, isLoading };\n}\n","\"use client\";\n\nimport type { AuthSession } from \"../../types\";\nimport { useAuthContext } from \"../AuthProvider\";\n\n/**\n * Returns the current auth session without exposing login/logout actions.\n *\n * @example\n * const { user, isAuthenticated } = useSession();\n */\nexport function useSession<User = unknown>(): AuthSession<User> {\n return useAuthContext<User>().session;\n}\n","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { useAuthContext } from \"../AuthProvider\";\n\nexport interface UseRequireAuthOptions {\n /** Path to redirect unauthenticated users to. @default \"/login\" */\n redirectTo?: string;\n /** Called when the user is not authenticated (use for custom redirect logic) */\n onUnauthenticated?: () => void;\n}\n\n/**\n * Redirects unauthenticated users to the login page.\n * Works with both Next.js App Router and Pages Router.\n *\n * @example\n * // App Router (client component)\n * useRequireAuth({ redirectTo: \"/login\" });\n *\n * @example\n * // Custom handler\n * useRequireAuth({ onUnauthenticated: () => router.push(\"/login\") });\n */\nexport function useRequireAuth(options: UseRequireAuthOptions = {}): void {\n const { redirectTo = \"/login\", onUnauthenticated } = options;\n const { session, isLoading } = useAuthContext();\n\n useEffect(() => {\n if (isLoading) return;\n if (session.isAuthenticated) return;\n\n if (onUnauthenticated) {\n onUnauthenticated();\n return;\n }\n\n // Works in both App Router and Pages Router environments\n if (typeof window !== \"undefined\") {\n window.location.href = redirectTo;\n }\n }, [session.isAuthenticated, isLoading, redirectTo, onUnauthenticated]);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/expiry.ts","../src/core/HttpClient.ts","../src/core/SessionManager.ts","../src/utils/crypto.ts","../src/core/TokenManager.ts","../src/core/AuthClient.ts","../src/react/AuthProvider.tsx","../src/react/hooks/useAuth.ts","../src/react/hooks/useSession.ts","../src/react/hooks/useRequireAuth.ts"],"names":["useEffect"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ExpiryInput, ExpiryStrategy, LoginResponse } from \"../types\";\n\nconst UNIT_MAP: Record<string, number> = {\n s: 1,\n m: 60,\n h: 3600,\n d: 86400,\n w: 604800,\n};\n\n/**\n * Parses an expiry value into seconds.\n * Accepts:\n * - number → treated as seconds\n * - string → e.g. \"15m\", \"2h\", \"2d\", \"7d\", \"1w\"\n *\n * @throws if the format is unrecognised\n */\nexport function parseExpiry(input?: ExpiryInput): number {\n if (input === undefined || input === null) {\n throw new Error(\"parseExpiry: no expiry value provided\");\n }\n\n if (typeof input === \"number\") {\n if (input <= 0) throw new Error(\"parseExpiry: value must be positive\");\n return input;\n }\n\n const trimmed = input.trim();\n\n // Pure numeric string\n if (/^\\d+$/.test(trimmed)) {\n return parseInt(trimmed, 10);\n }\n\n const match = trimmed.match(/^(\\d+(?:\\.\\d+)?)\\s*([smhdw])$/i);\n if (!match) {\n throw new Error(\n `parseExpiry: unrecognised format \"${input}\". ` +\n `Expected a number or a string like \"15m\", \"2h\", \"2d\", \"7d\", \"1w\".`\n );\n }\n\n const value = parseFloat(match[1]);\n const unit = match[2].toLowerCase();\n return Math.floor(value * UNIT_MAP[unit]);\n}\n\n/**\n * Safely parses an expiry value, returning a fallback on failure.\n */\nexport function safeParseExpiry(\n input?: ExpiryInput,\n fallbackSeconds = 900\n): number {\n try {\n return parseExpiry(input);\n } catch {\n return fallbackSeconds;\n }\n}\n\n/**\n * Resolves the access token expiry timestamp (ms) from a login response\n * using the configured strategy.\n */\nexport function resolveAccessTokenExpiry(\n response: LoginResponse,\n configExpiry?: ExpiryInput,\n strategy: ExpiryStrategy = \"hybrid\"\n): number {\n const now = Date.now();\n\n const fromBackend =\n response.accessTokenExpiresIn ?? response.expiresIn ?? undefined;\n\n if (strategy === \"backend\") {\n if (fromBackend === undefined) {\n throw new Error(\n 'resolveAccessTokenExpiry: strategy is \"backend\" but API returned no expiry'\n );\n }\n return now + parseExpiry(fromBackend) * 1000;\n }\n\n if (strategy === \"config\") {\n if (configExpiry === undefined) {\n throw new Error(\n 'resolveAccessTokenExpiry: strategy is \"config\" but no expiry configured'\n );\n }\n return now + parseExpiry(configExpiry) * 1000;\n }\n\n // hybrid: backend first, fallback to config\n if (fromBackend !== undefined) {\n return now + safeParseExpiry(fromBackend) * 1000;\n }\n if (configExpiry !== undefined) {\n return now + safeParseExpiry(configExpiry) * 1000;\n }\n\n // Last resort: 15 minutes\n return now + 900 * 1000;\n}\n\n/**\n * Resolves the refresh token expiry timestamp (ms).\n */\nexport function resolveRefreshTokenExpiry(\n response: LoginResponse,\n configExpiry?: ExpiryInput,\n strategy: ExpiryStrategy = \"hybrid\"\n): number | undefined {\n const now = Date.now();\n const fromBackend = response.refreshTokenExpiresIn;\n\n if (strategy === \"backend\") {\n return fromBackend !== undefined\n ? now + parseExpiry(fromBackend) * 1000\n : undefined;\n }\n\n if (strategy === \"config\") {\n return configExpiry !== undefined\n ? now + parseExpiry(configExpiry) * 1000\n : undefined;\n }\n\n // hybrid\n if (fromBackend !== undefined) {\n return now + safeParseExpiry(fromBackend) * 1000;\n }\n if (configExpiry !== undefined) {\n return now + safeParseExpiry(configExpiry) * 1000;\n }\n\n return undefined;\n}\n","import type { AuthConfig } from \"../types\";\nimport type { TokenManager } from \"./TokenManager\";\n\ntype RefreshFn = () => Promise<boolean>;\n\n/**\n * Authenticated HTTP client that:\n * - Injects Authorization: Bearer <accessToken>\n * - Auto-refreshes on 401 and retries the original request once\n */\nexport class HttpClient {\n private readonly config: AuthConfig;\n private readonly tokenManager: TokenManager;\n private refreshFn: RefreshFn | null = null;\n private refreshPromise: Promise<boolean> | null = null;\n\n constructor(config: AuthConfig, tokenManager: TokenManager) {\n this.config = config;\n this.tokenManager = tokenManager;\n }\n\n /** Register the refresh callback (set by AuthClient to avoid circular deps) */\n setRefreshFn(fn: RefreshFn): void {\n this.refreshFn = fn;\n }\n\n /**\n * Authenticated fetch wrapper.\n * Automatically injects the Bearer token and handles 401 → refresh → retry.\n */\n async fetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {\n const tokens = this.tokenManager.getTokens();\n\n const headers = new Headers(init.headers);\n if (tokens?.accessToken) {\n headers.set(\"Authorization\", `Bearer ${tokens.accessToken}`);\n }\n\n const response = await this.doFetch(input, { ...init, headers });\n\n if (response.status === 401 && this.refreshFn) {\n const refreshed = await this.deduplicatedRefresh();\n if (refreshed) {\n // Retry with new token\n const newTokens = this.tokenManager.getTokens();\n if (newTokens?.accessToken) {\n headers.set(\"Authorization\", `Bearer ${newTokens.accessToken}`);\n }\n return this.doFetch(input, { ...init, headers });\n }\n }\n\n return response;\n }\n\n /**\n * Raw fetch using the configured fetchFn or global fetch.\n */\n async doFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const fetchFn = this.config.fetchFn ?? fetch;\n return fetchFn(input as RequestInfo, init);\n }\n\n /**\n * Builds a full URL from a path relative to baseUrl.\n */\n url(path: string): string {\n return `${this.config.baseUrl.replace(/\\/$/, \"\")}/${path.replace(/^\\//, \"\")}`;\n }\n\n // ─── Private ────────────────────────────────────────────────────────────────\n\n /**\n * Ensures only one refresh request is in-flight at a time.\n */\n private async deduplicatedRefresh(): Promise<boolean> {\n if (this.refreshPromise) return this.refreshPromise;\n\n this.refreshPromise = this.refreshFn!().finally(() => {\n this.refreshPromise = null;\n });\n\n return this.refreshPromise;\n }\n}\n","import type { AuthConfig, AuthSession, AuthTokens } from \"../types\";\nimport type { HttpClient } from \"./HttpClient\";\nimport type { TokenManager } from \"./TokenManager\";\n\n/**\n * Derives and caches the current AuthSession from stored tokens.\n * Fetches the user profile from the /me endpoint when available.\n */\nexport class SessionManager<User = unknown> {\n private session: AuthSession<User> = {\n user: null,\n tokens: null,\n isAuthenticated: false,\n };\n\n private readonly config: AuthConfig<User>;\n private readonly tokenManager: TokenManager;\n private readonly httpClient: HttpClient;\n\n constructor(\n config: AuthConfig<User>,\n tokenManager: TokenManager,\n httpClient: HttpClient\n ) {\n this.config = config;\n this.tokenManager = tokenManager;\n this.httpClient = httpClient;\n }\n\n getSession(): AuthSession<User> {\n return this.session;\n }\n\n setSession(session: AuthSession<User>): void {\n this.session = session;\n }\n\n /**\n * Builds a session from stored tokens, optionally fetching the user profile.\n */\n async loadSession(): Promise<AuthSession<User>> {\n const tokens = this.tokenManager.getTokens();\n\n if (!tokens) {\n this.session = { user: null, tokens: null, isAuthenticated: false };\n return this.session;\n }\n\n // If access token is expired and refresh is also expired, clear everything\n if (\n this.tokenManager.isAccessExpired(tokens) &&\n this.tokenManager.isRefreshExpired(tokens)\n ) {\n this.tokenManager.clearTokens();\n this.session = { user: null, tokens: null, isAuthenticated: false };\n return this.session;\n }\n\n const user = await this.fetchUser(tokens);\n this.session = {\n user,\n tokens,\n isAuthenticated: true,\n };\n\n return this.session;\n }\n\n /**\n * Updates the session after a successful token refresh.\n */\n async refreshSession(tokens: AuthTokens): Promise<void> {\n const user = this.session.user ?? (await this.fetchUser(tokens));\n this.session = { user, tokens, isAuthenticated: true };\n }\n\n clearSession(): void {\n this.session = { user: null, tokens: null, isAuthenticated: false };\n }\n\n // ─── Private ────────────────────────────────────────────────────────────────\n\n private async fetchUser(tokens: AuthTokens): Promise<User | null> {\n const meEndpoint = this.config.endpoints.me;\n if (!meEndpoint) return null;\n\n try {\n const res = await this.httpClient.fetch(this.httpClient.url(meEndpoint));\n if (!res.ok) return null;\n return (await res.json()) as User;\n } catch {\n return null;\n }\n }\n}\n","/**\n * Lightweight symmetric encryption using AES-GCM via the Web Crypto API.\n * Works in both browser and Node.js (>=18) / Edge runtimes.\n */\n\nconst ALGO = \"AES-GCM\";\nconst IV_LENGTH = 12; // bytes\n\nfunction getTextEncoder() {\n return new TextEncoder();\n}\n\nfunction getTextDecoder() {\n return new TextDecoder();\n}\n\nasync function deriveKey(secret: string): Promise<CryptoKey> {\n if (!secret) {\n throw new Error(\n \"[next-token-auth] `secret` is undefined. \" +\n \"If using cookie storage, ensure AUTH_SECRET is set in your environment.\"\n );\n }\n const raw = getTextEncoder().encode(secret.padEnd(32, \"0\").slice(0, 32));\n return crypto.subtle.importKey(\"raw\", raw, { name: ALGO }, false, [\n \"encrypt\",\n \"decrypt\",\n ]);\n}\n\n/**\n * Encrypts a plaintext string using AES-GCM.\n * Returns a base64url-encoded string: `<iv>.<ciphertext>`\n */\nexport async function encrypt(data: string, secret: string): Promise<string> {\n const key = await deriveKey(secret);\n const ivArray = crypto.getRandomValues(new Uint8Array(IV_LENGTH));\n // Ensure we have a plain ArrayBuffer for SubtleCrypto\n const iv = ivArray.buffer.slice(0, IV_LENGTH) as ArrayBuffer;\n const encoded = getTextEncoder().encode(data);\n\n const cipherBuffer = await crypto.subtle.encrypt({ name: ALGO, iv }, key, encoded);\n\n const ivB64 = bufferToBase64(new Uint8Array(iv));\n const cipherB64 = bufferToBase64(new Uint8Array(cipherBuffer));\n return `${ivB64}.${cipherB64}`;\n}\n\n/**\n * Decrypts a string produced by `encrypt`.\n */\nexport async function decrypt(data: string, secret: string): Promise<string> {\n const [ivB64, cipherB64] = data.split(\".\");\n if (!ivB64 || !cipherB64) {\n throw new Error(\"decrypt: invalid ciphertext format\");\n }\n\n const key = await deriveKey(secret);\n const ivBytes = base64ToBuffer(ivB64);\n const iv = ivBytes.buffer.slice(\n ivBytes.byteOffset,\n ivBytes.byteOffset + ivBytes.byteLength\n ) as ArrayBuffer;\n\n const cipherBytes = base64ToBuffer(cipherB64);\n const cipherBuffer = cipherBytes.buffer.slice(\n cipherBytes.byteOffset,\n cipherBytes.byteOffset + cipherBytes.byteLength\n ) as ArrayBuffer;\n\n const plainBuffer = await crypto.subtle.decrypt({ name: ALGO, iv }, key, cipherBuffer);\n\n return getTextDecoder().decode(plainBuffer);\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction bufferToBase64(buffer: Uint8Array): string {\n return btoa(String.fromCharCode(...buffer))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nfunction base64ToBuffer(b64: string): Uint8Array {\n const padded = b64.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const binary = atob(padded);\n return Uint8Array.from(binary, (c) => c.charCodeAt(0));\n}\n","import type { AuthConfig, AuthTokens } from \"../types\";\nimport { encrypt, decrypt } from \"../utils/crypto\";\n\n/**\n * Manages storage, retrieval, and expiry checks for auth tokens.\n * Supports \"cookie\" and \"memory\" storage strategies.\n */\nexport class TokenManager {\n private memoryStore: AuthTokens | null = null;\n private readonly config: AuthConfig;\n\n constructor(config: AuthConfig) {\n this.config = config;\n }\n\n // ─── Public API ─────────────────────────────────────────────────────────────\n\n getTokens(): AuthTokens | null {\n if (this.config.token.storage === \"memory\") {\n return this.memoryStore;\n }\n // Return the in-memory cache (populated by setTokens or initFromCookie)\n return this.decryptedCache;\n }\n\n /**\n * Must be called once on startup (before getTokens) when storage is \"cookie\".\n * Reads and decrypts the cookie, populating the in-memory cache.\n */\n async initFromCookie(): Promise<void> {\n if (typeof document === \"undefined\") return;\n if (this.config.token.storage !== \"cookie\") return;\n\n const raw = getCookieValue(this.cookieName());\n if (!raw) return;\n\n try {\n const json = await decrypt(decodeURIComponent(raw), this.config.secret);\n this.decryptedCache = JSON.parse(json) as AuthTokens;\n } catch {\n this.decryptedCache = null;\n }\n }\n\n async setTokens(tokens: AuthTokens): Promise<void> {\n if (this.config.token.storage === \"memory\") {\n this.memoryStore = tokens;\n return;\n }\n await this.writeToCookie(tokens);\n }\n\n clearTokens(): void {\n this.memoryStore = null;\n this.decryptedCache = null;\n if (this.config.token.storage === \"cookie\") {\n this.deleteCookie();\n }\n }\n\n isAccessExpired(tokens: AuthTokens): boolean {\n const threshold = (this.config.refreshThreshold ?? 60) * 1000;\n return Date.now() >= tokens.accessTokenExpiresAt - threshold;\n }\n\n isRefreshExpired(tokens: AuthTokens): boolean {\n if (!tokens.refreshTokenExpiresAt) return false;\n return Date.now() >= tokens.refreshTokenExpiresAt;\n }\n\n // ─── Cookie helpers (client-side only) ──────────────────────────────────────\n\n private cookieName(): string {\n return this.config.token.cookieName ?? \"next-token-auth.session\";\n }\n\n /** In-memory cache of the last successfully decrypted cookie value. */\n private decryptedCache: AuthTokens | null = null;\n\n private async writeToCookie(tokens: AuthTokens): Promise<void> {\n if (typeof document === \"undefined\") return;\n\n // Encrypt before writing — must match what the server reads via decrypt()\n const value = await encrypt(JSON.stringify(tokens), this.config.secret);\n const secure = this.config.token.secure !== false ? \"; Secure\" : \"\";\n const sameSite = this.config.token.sameSite ?? \"lax\";\n const maxAge = tokens.refreshTokenExpiresAt\n ? Math.floor((tokens.refreshTokenExpiresAt - Date.now()) / 1000)\n : 604800;\n\n document.cookie = [\n `${this.cookieName()}=${encodeURIComponent(value)}`,\n `Max-Age=${maxAge}`,\n `Path=/`,\n `SameSite=${sameSite}`,\n secure,\n ]\n .filter(Boolean)\n .join(\"; \");\n\n // Keep the in-memory cache in sync\n this.decryptedCache = tokens;\n }\n\n private deleteCookie(): void {\n if (typeof document === \"undefined\") return;\n document.cookie = `${this.cookieName()}=; Max-Age=0; Path=/`;\n }\n\n // ─── Server-side helpers ─────────────────────────────────────────────────────\n\n /**\n * Encrypts tokens — used internally by writeToCookie and available for\n * advanced server-side use cases.\n */\n async encryptTokens(tokens: AuthTokens): Promise<string> {\n return encrypt(JSON.stringify(tokens), this.config.secret);\n }\n\n /**\n * Decrypts tokens from a server-side cookie value.\n */\n async decryptTokens(ciphertext: string): Promise<AuthTokens | null> {\n try {\n const json = await decrypt(ciphertext, this.config.secret);\n return JSON.parse(json) as AuthTokens;\n } catch {\n return null;\n }\n }\n}\n\n// ─── Utility ──────────────────────────────────────────────────────────────────\n\nfunction getCookieValue(name: string): string | null {\n if (typeof document === \"undefined\") return null;\n const match = document.cookie.match(\n new RegExp(`(?:^|;\\\\s*)${escapeRegex(name)}=([^;]*)`)\n );\n return match ? match[1] : null;\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","import type {\n AuthConfig,\n AuthSession,\n AuthTokens,\n LoginInput,\n LoginResponse,\n} from \"../types\";\nimport { resolveAccessTokenExpiry, resolveRefreshTokenExpiry } from \"../utils/expiry\";\nimport { HttpClient } from \"./HttpClient\";\nimport { SessionManager } from \"./SessionManager\";\nimport { TokenManager } from \"./TokenManager\";\n\n/**\n * Central orchestrator for authentication operations.\n * Coordinates TokenManager, SessionManager, and HttpClient.\n */\nexport class AuthClient<User = unknown> {\n readonly tokenManager: TokenManager;\n readonly sessionManager: SessionManager<User>;\n readonly httpClient: HttpClient;\n\n private readonly config: AuthConfig<User>;\n private sessionListeners: Array<(session: AuthSession<User>) => void> = [];\n\n constructor(config: AuthConfig<User>) {\n this.config = config;\n this.tokenManager = new TokenManager(config as AuthConfig<unknown>);\n this.httpClient = new HttpClient(config as AuthConfig<unknown>, this.tokenManager);\n this.sessionManager = new SessionManager(\n config,\n this.tokenManager,\n this.httpClient\n );\n\n // Wire up the refresh callback\n this.httpClient.setRefreshFn(() => this.refresh());\n }\n\n // ─── Auth Operations ────────────────────────────────────────────────────────\n\n /**\n * Authenticates the user and stores tokens.\n */\n async login(input: LoginInput): Promise<AuthSession<User>> {\n const res = await this.httpClient.doFetch(\n this.httpClient.url(this.config.endpoints.login),\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(input),\n }\n );\n\n if (!res.ok) {\n const error = await res.text();\n throw new Error(`Login failed (${res.status}): ${error}`);\n }\n\n const data = (await res.json()) as LoginResponse<User>;\n const tokens = this.buildTokens(data);\n\n await this.tokenManager.setTokens(tokens);\n await this.sessionManager.loadSession();\n\n const session = this.sessionManager.getSession();\n this.config.onLogin?.(session);\n this.notifyListeners(session);\n\n return session;\n }\n\n /**\n * Logs out the user, clears tokens, and optionally calls the backend.\n */\n async logout(): Promise<void> {\n const logoutEndpoint = this.config.endpoints.logout;\n\n if (logoutEndpoint) {\n try {\n await this.httpClient.fetch(this.httpClient.url(logoutEndpoint), {\n method: \"POST\",\n });\n } catch {\n // Best-effort logout\n }\n }\n\n this.tokenManager.clearTokens();\n this.sessionManager.clearSession();\n this.config.onLogout?.();\n this.notifyListeners(this.sessionManager.getSession());\n }\n\n /**\n * Refreshes the access token using the stored refresh token.\n * Returns true on success, false on failure.\n */\n async refresh(): Promise<boolean> {\n const tokens = this.tokenManager.getTokens();\n\n if (!tokens) return false;\n if (this.tokenManager.isRefreshExpired(tokens)) {\n await this.logout();\n return false;\n }\n\n try {\n const res = await this.httpClient.doFetch(\n this.httpClient.url(this.config.endpoints.refresh),\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ refreshToken: tokens.refreshToken }),\n }\n );\n\n if (!res.ok) {\n await this.logout();\n return false;\n }\n\n const data = (await res.json()) as LoginResponse<User>;\n const newTokens = this.buildTokens(data);\n\n await this.tokenManager.setTokens(newTokens);\n await this.sessionManager.refreshSession(newTokens);\n this.notifyListeners(this.sessionManager.getSession());\n\n return true;\n } catch (err) {\n this.config.onRefreshError?.(err);\n return false;\n }\n }\n\n /**\n * Loads the session from stored tokens (call on app mount).\n */\n async initialize(): Promise<AuthSession<User>> {\n // Decrypt the cookie into the in-memory cache before any session reads\n await this.tokenManager.initFromCookie();\n\n const session = await this.sessionManager.loadSession();\n\n // Proactively refresh if access token is near expiry\n if (\n session.isAuthenticated &&\n session.tokens &&\n this.tokenManager.isAccessExpired(session.tokens) &&\n !this.tokenManager.isRefreshExpired(session.tokens)\n ) {\n await this.refresh();\n }\n\n return this.sessionManager.getSession();\n }\n\n getSession(): AuthSession<User> {\n return this.sessionManager.getSession();\n }\n\n /**\n * Returns the authenticated fetch wrapper.\n */\n get fetch(): HttpClient[\"fetch\"] {\n return this.httpClient.fetch.bind(this.httpClient);\n }\n\n // ─── Subscription ────────────────────────────────────────────────────────────\n\n subscribe(listener: (session: AuthSession<User>) => void): () => void {\n this.sessionListeners.push(listener);\n return () => {\n this.sessionListeners = this.sessionListeners.filter((l) => l !== listener);\n };\n }\n\n // ─── Private ────────────────────────────────────────────────────────────────\n\n private buildTokens(data: LoginResponse<User>): AuthTokens {\n const strategy = this.config.expiry?.strategy ?? \"hybrid\";\n const configAccess = this.config.expiry?.accessTokenExpiresIn;\n const configRefresh = this.config.expiry?.refreshTokenExpiresIn;\n\n return {\n accessToken: data.accessToken,\n refreshToken: data.refreshToken,\n accessTokenExpiresAt: resolveAccessTokenExpiry(data, configAccess, strategy),\n refreshTokenExpiresAt: resolveRefreshTokenExpiry(data, configRefresh, strategy),\n };\n }\n\n private notifyListeners(session: AuthSession<User>): void {\n for (const listener of this.sessionListeners) {\n listener(session);\n }\n }\n}\n","\"use client\";\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport type { AuthSession, ClientAuthConfig, LoginInput } from \"../types\";\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\ninterface AuthContextValue<User = unknown> {\n session: AuthSession<User>;\n isLoading: boolean;\n login: (input: LoginInput) => Promise<void>;\n logout: () => Promise<void>;\n refresh: () => Promise<void>;\n}\n\nconst AuthContext = createContext<AuthContextValue | null>(null);\n\n// ─── Provider ─────────────────────────────────────────────────────────────────\n\ninterface AuthProviderProps {\n config: ClientAuthConfig;\n children: React.ReactNode;\n}\n\nexport function AuthProvider({ config, children }: AuthProviderProps) {\n const [session, setSession] = useState<AuthSession>({\n user: null,\n tokens: null,\n isAuthenticated: false,\n });\n const [isLoading, setIsLoading] = useState(true);\n\n // Initialize session on mount\n useEffect(() => {\n let cancelled = false;\n\n fetch(\"/api/auth/session\")\n .then((res) => res.json())\n .then((data) => {\n if (!cancelled) {\n setSession({\n user: data.user ?? null,\n tokens: null, // tokens are HttpOnly, never exposed to client\n isAuthenticated: data.isAuthenticated ?? false,\n });\n setIsLoading(false);\n }\n })\n .catch(() => {\n if (!cancelled) {\n setSession({ user: null, tokens: null, isAuthenticated: false });\n setIsLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, []);\n\n // Auto-refresh timer\n useEffect(() => {\n if (!config.autoRefresh) return;\n\n const interval = setInterval(async () => {\n if (session.isAuthenticated) {\n try {\n await fetch(\"/api/auth/refresh\", { method: \"POST\" });\n const updated = await fetch(\"/api/auth/session\").then((r) => r.json());\n setSession({\n user: updated.user ?? null,\n tokens: null,\n isAuthenticated: updated.isAuthenticated ?? false,\n });\n } catch {\n // Refresh failed — session will be cleared on next page load\n }\n }\n }, (config.refreshThreshold ?? 60) * 1000);\n\n return () => clearInterval(interval);\n }, [config.autoRefresh, config.refreshThreshold, session.isAuthenticated]);\n\n const login = useCallback(\n async (input: LoginInput) => {\n setIsLoading(true);\n try {\n const res = await fetch(\"/api/auth/login\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(input),\n });\n\n if (!res.ok) {\n const { error } = await res.json();\n throw new Error(error ?? \"Login failed\");\n }\n\n const { user } = await res.json();\n const newSession = { user, tokens: null, isAuthenticated: true };\n setSession(newSession);\n config.onLogin?.(newSession);\n } finally {\n setIsLoading(false);\n }\n },\n [config]\n );\n\n const logout = useCallback(async () => {\n await fetch(\"/api/auth/logout\", { method: \"POST\" });\n setSession({ user: null, tokens: null, isAuthenticated: false });\n config.onLogout?.();\n }, [config]);\n\n const refresh = useCallback(async () => {\n await fetch(\"/api/auth/refresh\", { method: \"POST\" });\n const updated = await fetch(\"/api/auth/session\").then((r) => r.json());\n setSession({\n user: updated.user ?? null,\n tokens: null,\n isAuthenticated: updated.isAuthenticated ?? false,\n });\n }, []);\n\n const value: AuthContextValue = {\n session,\n isLoading,\n login,\n logout,\n refresh,\n };\n\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\n}\n\n// ─── Internal hook ────────────────────────────────────────────────────────────\n\nexport function useAuthContext<User = unknown>(): AuthContextValue<User> {\n const ctx = useContext(AuthContext);\n if (!ctx) {\n throw new Error(\"useAuthContext must be used within <AuthProvider>\");\n }\n return ctx as AuthContextValue<User>;\n}\n","\"use client\";\n\nimport type { AuthSession, LoginInput } from \"../../types\";\nimport { useAuthContext } from \"../AuthProvider\";\n\nexport interface UseAuthReturn<User = unknown> {\n session: AuthSession<User>;\n login: (input: LoginInput) => Promise<void>;\n logout: () => Promise<void>;\n refresh: () => Promise<void>;\n isLoading: boolean;\n}\n\n/**\n * Primary hook for authentication operations.\n *\n * @example\n * const { session, login, logout, isLoading } = useAuth();\n */\nexport function useAuth<User = unknown>(): UseAuthReturn<User> {\n const { session, login, logout, refresh, isLoading } =\n useAuthContext<User>();\n\n return { session, login, logout, refresh, isLoading };\n}\n","\"use client\";\n\nimport type { AuthSession } from \"../../types\";\nimport { useAuthContext } from \"../AuthProvider\";\n\n/**\n * Returns the current auth session without exposing login/logout actions.\n *\n * @example\n * const { user, isAuthenticated } = useSession();\n */\nexport function useSession<User = unknown>(): AuthSession<User> {\n return useAuthContext<User>().session;\n}\n","\"use client\";\n\nimport { useEffect } from \"react\";\nimport { useAuthContext } from \"../AuthProvider\";\n\nexport interface UseRequireAuthOptions {\n /** Path to redirect unauthenticated users to. @default \"/login\" */\n redirectTo?: string;\n /** Called when the user is not authenticated (use for custom redirect logic) */\n onUnauthenticated?: () => void;\n}\n\n/**\n * Redirects unauthenticated users to the login page.\n * Works with both Next.js App Router and Pages Router.\n *\n * @example\n * // App Router (client component)\n * useRequireAuth({ redirectTo: \"/login\" });\n *\n * @example\n * // Custom handler\n * useRequireAuth({ onUnauthenticated: () => router.push(\"/login\") });\n */\nexport function useRequireAuth(options: UseRequireAuthOptions = {}): void {\n const { redirectTo = \"/login\", onUnauthenticated } = options;\n const { session, isLoading } = useAuthContext();\n\n useEffect(() => {\n if (isLoading) return;\n if (session.isAuthenticated) return;\n\n if (onUnauthenticated) {\n onUnauthenticated();\n return;\n }\n\n // Works in both App Router and Pages Router environments\n if (typeof window !== \"undefined\") {\n window.location.href = redirectTo;\n }\n }, [session.isAuthenticated, isLoading, redirectTo, onUnauthenticated]);\n}\n"]}
|
package/dist/react/index.d.mts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { C as ClientAuthConfig, a as AuthSession, L as LoginInput } from '../index-Csz5lfEv.mjs';
|
|
4
4
|
|
|
5
|
-
interface AuthProviderProps
|
|
6
|
-
config:
|
|
5
|
+
interface AuthProviderProps {
|
|
6
|
+
config: ClientAuthConfig;
|
|
7
7
|
children: React.ReactNode;
|
|
8
8
|
}
|
|
9
|
-
declare function AuthProvider
|
|
9
|
+
declare function AuthProvider({ config, children }: AuthProviderProps): react_jsx_runtime.JSX.Element;
|
|
10
10
|
|
|
11
11
|
interface UseAuthReturn<User = unknown> {
|
|
12
12
|
session: AuthSession<User>;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { C as ClientAuthConfig, a as AuthSession, L as LoginInput } from '../index-Csz5lfEv.js';
|
|
4
4
|
|
|
5
|
-
interface AuthProviderProps
|
|
6
|
-
config:
|
|
5
|
+
interface AuthProviderProps {
|
|
6
|
+
config: ClientAuthConfig;
|
|
7
7
|
children: React.ReactNode;
|
|
8
8
|
}
|
|
9
|
-
declare function AuthProvider
|
|
9
|
+
declare function AuthProvider({ config, children }: AuthProviderProps): react_jsx_runtime.JSX.Element;
|
|
10
10
|
|
|
11
11
|
interface UseAuthReturn<User = unknown> {
|
|
12
12
|
session: AuthSession<User>;
|