next-token-auth 1.0.17 → 1.0.19
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 +96 -6
- package/dist/{index-Csz5lfEv.d.mts → index-cC0Csz3x.d.mts} +1 -1
- package/dist/{index-Csz5lfEv.d.ts → index-cC0Csz3x.d.ts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -7
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +4 -2
- package/dist/react/index.d.ts +4 -2
- package/dist/react/index.js +6 -7
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +6 -7
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.d.mts +22 -2
- package/dist/server/index.d.ts +22 -2
- package/dist/server/index.js +52 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +52 -2
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -163,13 +163,18 @@ Your `AuthProvider` calls these automatically. You never call them directly.
|
|
|
163
163
|
```tsx
|
|
164
164
|
// app/layout.tsx
|
|
165
165
|
import { AuthProvider } from "next-token-auth/react";
|
|
166
|
+
import { getLayoutSession } from "next-token-auth/server";
|
|
166
167
|
import { clientAuthConfig } from "@/lib/auth.client";
|
|
168
|
+
import { authConfig } from "@/lib/auth";
|
|
169
|
+
|
|
170
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
171
|
+
// Pre-fetch session server-side to avoid auth flash on page load
|
|
172
|
+
const session = await getLayoutSession(authConfig);
|
|
167
173
|
|
|
168
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
169
174
|
return (
|
|
170
175
|
<html lang="en">
|
|
171
176
|
<body>
|
|
172
|
-
<AuthProvider config={clientAuthConfig}>
|
|
177
|
+
<AuthProvider config={clientAuthConfig} initialSession={session}>
|
|
173
178
|
{children}
|
|
174
179
|
</AuthProvider>
|
|
175
180
|
</body>
|
|
@@ -178,6 +183,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
178
183
|
}
|
|
179
184
|
```
|
|
180
185
|
|
|
186
|
+
**Why `initialSession`?** Without it, there's a flash of the wrong auth state on every page load while the client fetches the session. Pre-fetching server-side eliminates the flash — the correct auth state is rendered from the first paint.
|
|
187
|
+
|
|
181
188
|
---
|
|
182
189
|
|
|
183
190
|
### Step 5: Build your login page
|
|
@@ -312,6 +319,39 @@ If `secret` is in the client config, it gets bundled into your JavaScript and ex
|
|
|
312
319
|
|
|
313
320
|
---
|
|
314
321
|
|
|
322
|
+
## Avoiding the Auth Flash
|
|
323
|
+
|
|
324
|
+
Without server-side session pre-fetching, there's a visible flash on every page load:
|
|
325
|
+
|
|
326
|
+
1. Server sends HTML with `isLoading = true`
|
|
327
|
+
2. Browser renders a skeleton or empty state
|
|
328
|
+
3. Client fetches `/api/auth/session`
|
|
329
|
+
4. UI flips to the correct auth state (flash visible)
|
|
330
|
+
|
|
331
|
+
To fix this, use `getLayoutSession` in your root layout:
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
// app/layout.tsx
|
|
335
|
+
import { getLayoutSession } from "next-token-auth/server";
|
|
336
|
+
import { authConfig } from "@/lib/auth";
|
|
337
|
+
|
|
338
|
+
export default async function RootLayout({ children }) {
|
|
339
|
+
const session = await getLayoutSession(authConfig);
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<AuthProvider config={clientAuthConfig} initialSession={session}>
|
|
343
|
+
{children}
|
|
344
|
+
</AuthProvider>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Now the correct auth state is rendered from the first paint — no flash, no skeleton, no layout shift.
|
|
350
|
+
|
|
351
|
+
`getLayoutSession` reads and decrypts the session cookie server-side using `next/headers`, which is available in layouts and server components (unlike `getServerSession`, which requires a `NextRequest` and only works in middleware and route handlers).
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
315
355
|
## API Reference
|
|
316
356
|
|
|
317
357
|
### `useAuth()`
|
|
@@ -438,6 +478,37 @@ This keeps tokens secure — they never leave the server.
|
|
|
438
478
|
|
|
439
479
|
---
|
|
440
480
|
|
|
481
|
+
### `getLayoutSession(config)`
|
|
482
|
+
|
|
483
|
+
Reads and validates the session in layouts and server components using `next/headers`.
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
// app/layout.tsx
|
|
487
|
+
import { getLayoutSession } from "next-token-auth/server";
|
|
488
|
+
import { authConfig } from "@/lib/auth";
|
|
489
|
+
|
|
490
|
+
export default async function RootLayout({ children }) {
|
|
491
|
+
const session = await getLayoutSession(authConfig);
|
|
492
|
+
|
|
493
|
+
return (
|
|
494
|
+
<AuthProvider config={clientAuthConfig} initialSession={session}>
|
|
495
|
+
{children}
|
|
496
|
+
</AuthProvider>
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
What it does:
|
|
502
|
+
1. Reads the encrypted session cookie via `next/headers`
|
|
503
|
+
2. Decrypts it using your `secret`
|
|
504
|
+
3. Validates token expiry
|
|
505
|
+
4. Optionally fetches the user profile from your backend
|
|
506
|
+
5. Returns `{ user, isAuthenticated }` (tokens are omitted for client safety)
|
|
507
|
+
|
|
508
|
+
Use this instead of `getServerSession` when you need the session in a layout or server component where `NextRequest` is not available.
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
441
512
|
### `getServerSession(req, config)`
|
|
442
513
|
|
|
443
514
|
Reads and validates the session in server components and API routes.
|
|
@@ -631,7 +702,7 @@ Your backend needs to implement these endpoints:
|
|
|
631
702
|
|
|
632
703
|
Request body: whatever fields you pass to `login()` (e.g. `{ email, password }`)
|
|
633
704
|
|
|
634
|
-
Response
|
|
705
|
+
**Response option 1 — user included:**
|
|
635
706
|
|
|
636
707
|
```json
|
|
637
708
|
{
|
|
@@ -645,10 +716,29 @@ Response:
|
|
|
645
716
|
|
|
646
717
|
// Optional — used by "backend" and "hybrid" expiry strategies
|
|
647
718
|
"accessTokenExpiresIn": "2d",
|
|
648
|
-
"refreshTokenExpiresIn": "7d"
|
|
719
|
+
"refreshTokenExpiresIn": "7d"
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
**Response option 2 — user fetched separately:**
|
|
724
|
+
|
|
725
|
+
If your login endpoint only returns tokens, omit the `user` field and configure the `me` endpoint. The library will automatically call it after login.
|
|
726
|
+
|
|
727
|
+
```json
|
|
728
|
+
{
|
|
729
|
+
"accessToken": "eyJhbGc...",
|
|
730
|
+
"refreshToken": "eyJhbGc...",
|
|
731
|
+
"accessTokenExpiresIn": "2d",
|
|
732
|
+
"refreshTokenExpiresIn": "7d"
|
|
733
|
+
}
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
Then in your config:
|
|
649
737
|
|
|
650
|
-
|
|
651
|
-
|
|
738
|
+
```ts
|
|
739
|
+
endpoints: {
|
|
740
|
+
login: "/auth/login",
|
|
741
|
+
me: "/auth/me", // ← library calls this to fetch user profile
|
|
652
742
|
}
|
|
653
743
|
```
|
|
654
744
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AuthConfig, b as AuthTokens, a as AuthSession, L as LoginInput, E as ExpiryInput } from './index-
|
|
2
|
-
export { C as ClientAuthConfig, c as ExpiryStrategy, d as LoginResponse } from './index-
|
|
1
|
+
import { A as AuthConfig, b as AuthTokens, a as AuthSession, L as LoginInput, E as ExpiryInput } from './index-cC0Csz3x.mjs';
|
|
2
|
+
export { C as ClientAuthConfig, c as ExpiryStrategy, d as LoginResponse } from './index-cC0Csz3x.mjs';
|
|
3
3
|
export { AuthProvider, UseAuthReturn, UseRequireAuthOptions, useAuth, useRequireAuth, useSession } from './react/index.mjs';
|
|
4
4
|
import 'react/jsx-runtime';
|
|
5
5
|
import 'react';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AuthConfig, b as AuthTokens, a as AuthSession, L as LoginInput, E as ExpiryInput } from './index-
|
|
2
|
-
export { C as ClientAuthConfig, c as ExpiryStrategy, d as LoginResponse } from './index-
|
|
1
|
+
import { A as AuthConfig, b as AuthTokens, a as AuthSession, L as LoginInput, E as ExpiryInput } from './index-cC0Csz3x.js';
|
|
2
|
+
export { C as ClientAuthConfig, c as ExpiryStrategy, d as LoginResponse } from './index-cC0Csz3x.js';
|
|
3
3
|
export { AuthProvider, UseAuthReturn, UseRequireAuthOptions, useAuth, useRequireAuth, useSession } from './react/index.js';
|
|
4
4
|
import 'react/jsx-runtime';
|
|
5
5
|
import 'react';
|
package/dist/index.js
CHANGED
|
@@ -515,14 +515,13 @@ var AuthClient = class {
|
|
|
515
515
|
}
|
|
516
516
|
};
|
|
517
517
|
var AuthContext = react.createContext(null);
|
|
518
|
-
function AuthProvider({ config, children }) {
|
|
519
|
-
const [session, setSession] = react.useState(
|
|
520
|
-
user: null,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
});
|
|
524
|
-
const [isLoading, setIsLoading] = react.useState(true);
|
|
518
|
+
function AuthProvider({ config, children, initialSession }) {
|
|
519
|
+
const [session, setSession] = react.useState(
|
|
520
|
+
initialSession ?? { user: null, tokens: null, isAuthenticated: false }
|
|
521
|
+
);
|
|
522
|
+
const [isLoading, setIsLoading] = react.useState(!initialSession);
|
|
525
523
|
react.useEffect(() => {
|
|
524
|
+
if (initialSession) return;
|
|
526
525
|
let cancelled = false;
|
|
527
526
|
fetch("/api/auth/session").then((res) => res.json()).then((data) => {
|
|
528
527
|
if (!cancelled) {
|
package/dist/index.js.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":["createContext","useState","useEffect","useCallback","jsx","useContext"],"mappings":"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","file":"index.js","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"]}
|
|
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":["createContext","useState","useEffect","useCallback","jsx","useContext"],"mappings":"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","file":"index.js","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 /** Pre-fetched session from server (use getLayoutSession in your layout) */\n initialSession?: AuthSession | null;\n}\n\nexport function AuthProvider({ config, children, initialSession }: AuthProviderProps) {\n const [session, setSession] = useState<AuthSession>(\n initialSession ?? { user: null, tokens: null, isAuthenticated: false }\n );\n // If the server pre-seeded the session, skip the loading phase entirely\n const [isLoading, setIsLoading] = useState(!initialSession);\n\n // Initialize session on mount\n useEffect(() => {\n // Skip if server already provided initial session\n if (initialSession) return;\n\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/index.mjs
CHANGED
|
@@ -513,14 +513,13 @@ var AuthClient = class {
|
|
|
513
513
|
}
|
|
514
514
|
};
|
|
515
515
|
var AuthContext = createContext(null);
|
|
516
|
-
function AuthProvider({ config, children }) {
|
|
517
|
-
const [session, setSession] = useState(
|
|
518
|
-
user: null,
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
});
|
|
522
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
516
|
+
function AuthProvider({ config, children, initialSession }) {
|
|
517
|
+
const [session, setSession] = useState(
|
|
518
|
+
initialSession ?? { user: null, tokens: null, isAuthenticated: false }
|
|
519
|
+
);
|
|
520
|
+
const [isLoading, setIsLoading] = useState(!initialSession);
|
|
523
521
|
useEffect(() => {
|
|
522
|
+
if (initialSession) return;
|
|
524
523
|
let cancelled = false;
|
|
525
524
|
fetch("/api/auth/session").then((res) => res.json()).then((data) => {
|
|
526
525
|
if (!cancelled) {
|