najm-auth 2.0.5 → 2.0.9
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 +7 -0
- package/dist/{NajmAuthClient-B9dGk9MH.d.ts → NajmAuthClient-0yzFb9CR.d.ts} +1 -1
- package/dist/client/edge.d.ts +26 -1
- package/dist/client/edge.js +129 -24
- package/dist/client/index.d.ts +2 -2
- package/dist/client/react/index.d.ts +1 -1
- package/dist/client/react/index.js +1 -1
- package/dist/client/server/index.d.ts +13 -4
- package/dist/client/server/index.js +173 -32
- package/dist/index.js +13 -13
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -681,6 +681,13 @@ throw new HttpError(403, 'Insufficient permissions for this action');
|
|
|
681
681
|
their session version.
|
|
682
682
|
- Expired signed sessions recover through authoritative, non-rotating refresh
|
|
683
683
|
validation; middleware verifies the reissued HMAC before using its claims.
|
|
684
|
+
- Server-side recovery sends only the configured refresh cookie and accepts
|
|
685
|
+
relative or exact same-origin endpoints. URL credentials and any
|
|
686
|
+
scheme/hostname/port change are rejected before the network request.
|
|
687
|
+
- Self-hosted apps may explicitly use a loopback-only `internalRecoveryURL`
|
|
688
|
+
when their public reverse-proxy origin is not reachable from the app process.
|
|
689
|
+
- `onRecoveryFailure` exposes structured, secret-free recovery diagnostics
|
|
690
|
+
without logging anything by default.
|
|
684
691
|
- `verifyAlways` forces that authoritative check on every protected request;
|
|
685
692
|
the default bounds cached role/status staleness to `session.maxAge`.
|
|
686
693
|
|
|
@@ -247,4 +247,4 @@ declare class NajmAuthClient {
|
|
|
247
247
|
*/
|
|
248
248
|
declare function createAuthClient(config: AuthClientConfig): NajmAuthClient;
|
|
249
249
|
|
|
250
|
-
export {
|
|
250
|
+
export { type AuthClientConfig as A, type DecodedToken as D, FetchClient as F, type HydrateSession as H, NajmAuthClient as N, type OAuthLoginOptions as O, type RequestOptions as R, type SyncPayload as S, type TabSyncMessage as T, AuthError as a, type AuthEvent as b, type AuthEventHandler as c, type AuthEventMap as d, type AuthState as e, type AuthUser as f, type OAuthProvider as g, type RetryConfig as h, type ServerResponse as i, type TokenPair as j, createAuthClient as k };
|
package/dist/client/edge.d.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import * as next_server from 'next/server';
|
|
2
2
|
|
|
3
|
+
type SessionRecoveryFailureReason = 'invalid-cookie-name' | 'invalid-refresh-cookie' | 'invalid-endpoint' | 'fetch-error' | 'http-status' | 'missing-set-cookie' | 'session-cookie-parse' | 'session-cookie-hmac' | 'session-cookie-payload';
|
|
4
|
+
interface SessionRecoveryErrorDetails {
|
|
5
|
+
name: string;
|
|
6
|
+
message: string;
|
|
7
|
+
code?: string;
|
|
8
|
+
cause?: {
|
|
9
|
+
name: string;
|
|
10
|
+
message: string;
|
|
11
|
+
code?: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
interface SessionRecoveryFailure {
|
|
15
|
+
reason: SessionRecoveryFailureReason;
|
|
16
|
+
httpStatus?: number;
|
|
17
|
+
error?: SessionRecoveryErrorDetails;
|
|
18
|
+
}
|
|
19
|
+
|
|
3
20
|
interface AuthMiddlewareConfig {
|
|
4
21
|
/** Routes that require authentication (glob patterns) */
|
|
5
22
|
protectedRoutes?: string[];
|
|
@@ -34,6 +51,14 @@ interface AuthMiddlewareConfig {
|
|
|
34
51
|
* Set to false to disable automatic recovery.
|
|
35
52
|
*/
|
|
36
53
|
recoveryURL?: string | false;
|
|
54
|
+
/**
|
|
55
|
+
* Optional loopback-only endpoint for self-hosted apps whose public origin
|
|
56
|
+
* cannot be reached from the app container. Takes precedence over
|
|
57
|
+
* `recoveryURL` for the server-side recovery request.
|
|
58
|
+
*/
|
|
59
|
+
internalRecoveryURL?: string;
|
|
60
|
+
/** Secret-free diagnostic hook for failed authoritative recovery attempts. */
|
|
61
|
+
onRecoveryFailure?: (failure: SessionRecoveryFailure) => void;
|
|
37
62
|
}
|
|
38
63
|
/**
|
|
39
64
|
* Create a Next.js middleware function that protects routes based on auth state.
|
|
@@ -57,4 +82,4 @@ interface AuthMiddlewareConfig {
|
|
|
57
82
|
*/
|
|
58
83
|
declare function withAuthMiddleware(config: AuthMiddlewareConfig): (request: Request) => Promise<next_server.NextResponse<unknown>>;
|
|
59
84
|
|
|
60
|
-
export { type AuthMiddlewareConfig, withAuthMiddleware };
|
|
85
|
+
export { type AuthMiddlewareConfig, type SessionRecoveryErrorDetails, type SessionRecoveryFailure, type SessionRecoveryFailureReason, withAuthMiddleware };
|
package/dist/client/edge.js
CHANGED
|
@@ -36,22 +36,36 @@ function parseSessionCookiePayload(payload, maxAgeSeconds = DEFAULT_SESSION_MAX_
|
|
|
36
36
|
}
|
|
37
37
|
__name(parseSessionCookiePayload, "parseSessionCookiePayload");
|
|
38
38
|
async function verifySessionCookie(rawCookieValue, options) {
|
|
39
|
-
|
|
39
|
+
const result = await verifySessionCookieDetailed(rawCookieValue, options);
|
|
40
|
+
return result.status === "valid" ? result.claims : null;
|
|
41
|
+
}
|
|
42
|
+
__name(verifySessionCookie, "verifySessionCookie");
|
|
43
|
+
async function verifySessionCookieDetailed(rawCookieValue, options) {
|
|
44
|
+
if (!rawCookieValue || !options.secret || !globalThis.crypto?.subtle) {
|
|
45
|
+
return { status: "invalid", reason: "format" };
|
|
46
|
+
}
|
|
47
|
+
let sawSignedFormat = false;
|
|
48
|
+
let sawValidHmac = false;
|
|
40
49
|
for (const signedValue of cookieValueCandidates(rawCookieValue)) {
|
|
41
50
|
const lastDot = signedValue.lastIndexOf(".");
|
|
42
51
|
if (lastDot <= 0 || lastDot === signedValue.length - 1) continue;
|
|
52
|
+
sawSignedFormat = true;
|
|
43
53
|
const payload = signedValue.slice(0, lastDot);
|
|
44
54
|
const signature = signedValue.slice(lastDot + 1);
|
|
45
55
|
if (!await verifyHmac(payload, signature, options.secret)) continue;
|
|
46
|
-
|
|
56
|
+
sawValidHmac = true;
|
|
57
|
+
const claims = parseSessionCookiePayload(
|
|
47
58
|
payload,
|
|
48
59
|
options.maxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
49
60
|
options.now
|
|
50
61
|
);
|
|
62
|
+
if (claims) return { status: "valid", claims };
|
|
51
63
|
}
|
|
52
|
-
return
|
|
64
|
+
if (sawValidHmac) return { status: "invalid", reason: "payload" };
|
|
65
|
+
if (sawSignedFormat) return { status: "invalid", reason: "hmac" };
|
|
66
|
+
return { status: "invalid", reason: "format" };
|
|
53
67
|
}
|
|
54
|
-
__name(
|
|
68
|
+
__name(verifySessionCookieDetailed, "verifySessionCookieDetailed");
|
|
55
69
|
function readCookieValue(cookieHeader, name) {
|
|
56
70
|
for (const part of cookieHeader.split(";")) {
|
|
57
71
|
const separator = part.indexOf("=");
|
|
@@ -132,19 +146,33 @@ function isStringArray(value) {
|
|
|
132
146
|
__name(isStringArray, "isStringArray");
|
|
133
147
|
|
|
134
148
|
// src/client/sessionRecovery.ts
|
|
149
|
+
function resolveInternalRecoveryURL(explicit) {
|
|
150
|
+
if (explicit !== void 0) return explicit || void 0;
|
|
151
|
+
if (typeof process === "undefined") return void 0;
|
|
152
|
+
return process.env.NAJM_AUTH_INTERNAL_URL || void 0;
|
|
153
|
+
}
|
|
154
|
+
__name(resolveInternalRecoveryURL, "resolveInternalRecoveryURL");
|
|
135
155
|
async function requestSessionRecovery(options) {
|
|
136
156
|
if (!isCookieName(options.refreshCookieName) || !isCookieName(options.sessionCookieName)) {
|
|
157
|
+
reportFailure(options, { reason: "invalid-cookie-name" });
|
|
137
158
|
return { status: "unavailable" };
|
|
138
159
|
}
|
|
139
160
|
if (!isCookieValue(options.refreshCookieValue)) {
|
|
161
|
+
reportFailure(options, { reason: "invalid-refresh-cookie" });
|
|
140
162
|
return { status: "invalid" };
|
|
141
163
|
}
|
|
142
|
-
|
|
164
|
+
const endpoint = sameOriginRecoveryEndpoint(
|
|
165
|
+
options.endpoint,
|
|
166
|
+
options.requestOrigin,
|
|
167
|
+
options.allowLoopbackEndpoint
|
|
168
|
+
);
|
|
169
|
+
if (!endpoint) {
|
|
170
|
+
reportFailure(options, { reason: "invalid-endpoint" });
|
|
143
171
|
return { status: "unavailable" };
|
|
144
172
|
}
|
|
145
173
|
let response;
|
|
146
174
|
try {
|
|
147
|
-
response = await fetch(
|
|
175
|
+
response = await fetch(endpoint, {
|
|
148
176
|
method: "POST",
|
|
149
177
|
headers: {
|
|
150
178
|
Accept: "application/json",
|
|
@@ -154,30 +182,51 @@ async function requestSessionRecovery(options) {
|
|
|
154
182
|
cache: "no-store",
|
|
155
183
|
redirect: "manual"
|
|
156
184
|
});
|
|
157
|
-
} catch {
|
|
185
|
+
} catch (error) {
|
|
186
|
+
reportFailure(options, {
|
|
187
|
+
reason: "fetch-error",
|
|
188
|
+
error: safeErrorDetails(error)
|
|
189
|
+
});
|
|
158
190
|
return { status: "unavailable" };
|
|
159
191
|
}
|
|
160
192
|
if (!response.ok) {
|
|
161
193
|
const status = response.status;
|
|
194
|
+
reportFailure(options, { reason: "http-status", httpStatus: status });
|
|
162
195
|
return {
|
|
163
196
|
status: status === 400 || status === 401 || status === 403 ? "invalid" : "unavailable",
|
|
164
197
|
httpStatus: status
|
|
165
198
|
};
|
|
166
199
|
}
|
|
167
200
|
const setCookie = response.headers.get("set-cookie");
|
|
168
|
-
if (!setCookie)
|
|
201
|
+
if (!setCookie) {
|
|
202
|
+
reportFailure(options, {
|
|
203
|
+
reason: "missing-set-cookie",
|
|
204
|
+
httpStatus: response.status
|
|
205
|
+
});
|
|
206
|
+
return { status: "unavailable", httpStatus: response.status };
|
|
207
|
+
}
|
|
169
208
|
const sessionCookieValue = readSetCookieValue(setCookie, options.sessionCookieName);
|
|
170
209
|
if (!sessionCookieValue) {
|
|
210
|
+
reportFailure(options, {
|
|
211
|
+
reason: "session-cookie-parse",
|
|
212
|
+
httpStatus: response.status
|
|
213
|
+
});
|
|
171
214
|
return { status: "unavailable", httpStatus: response.status };
|
|
172
215
|
}
|
|
173
|
-
const
|
|
216
|
+
const verification = await verifySessionCookieDetailed(sessionCookieValue, {
|
|
174
217
|
secret: options.sessionSecret,
|
|
175
218
|
maxAgeSeconds: options.sessionMaxAge
|
|
176
219
|
});
|
|
177
|
-
if (
|
|
220
|
+
if (verification.status === "invalid") {
|
|
221
|
+
reportFailure(options, {
|
|
222
|
+
reason: verification.reason === "hmac" ? "session-cookie-hmac" : verification.reason === "payload" ? "session-cookie-payload" : "session-cookie-parse",
|
|
223
|
+
httpStatus: response.status
|
|
224
|
+
});
|
|
225
|
+
return { status: "unavailable", httpStatus: response.status };
|
|
226
|
+
}
|
|
178
227
|
return {
|
|
179
228
|
status: "recovered",
|
|
180
|
-
claims,
|
|
229
|
+
claims: verification.claims,
|
|
181
230
|
setCookie,
|
|
182
231
|
sessionCookieValue
|
|
183
232
|
};
|
|
@@ -208,21 +257,71 @@ function isCookieName(value) {
|
|
|
208
257
|
}
|
|
209
258
|
__name(isCookieName, "isCookieName");
|
|
210
259
|
function isCookieValue(value) {
|
|
211
|
-
return value.length > 0 &&
|
|
260
|
+
return value.length > 0 && /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/.test(value);
|
|
212
261
|
}
|
|
213
262
|
__name(isCookieValue, "isCookieValue");
|
|
214
|
-
function
|
|
263
|
+
function sameOriginRecoveryEndpoint(endpoint, requestOrigin, allowLoopback = false) {
|
|
215
264
|
try {
|
|
216
|
-
const
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
265
|
+
const trusted = new URL(requestOrigin);
|
|
266
|
+
if (trusted.protocol !== "https:" && trusted.protocol !== "http:" || trusted.username || trusted.password) {
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
const resolved = new URL(endpoint, trusted.origin);
|
|
270
|
+
if (resolved.username || resolved.password) return void 0;
|
|
271
|
+
if (resolved.origin !== trusted.origin && (!allowLoopback || !isLoopbackURL(resolved))) {
|
|
272
|
+
return void 0;
|
|
273
|
+
}
|
|
274
|
+
return resolved.toString();
|
|
221
275
|
} catch {
|
|
222
|
-
return
|
|
276
|
+
return void 0;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
__name(sameOriginRecoveryEndpoint, "sameOriginRecoveryEndpoint");
|
|
280
|
+
function isLoopbackURL(url) {
|
|
281
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return false;
|
|
282
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]" || url.hostname === "::1";
|
|
283
|
+
}
|
|
284
|
+
__name(isLoopbackURL, "isLoopbackURL");
|
|
285
|
+
function reportFailure(options, failure) {
|
|
286
|
+
try {
|
|
287
|
+
options.onFailure?.(failure);
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
__name(reportFailure, "reportFailure");
|
|
292
|
+
function safeErrorDetails(value) {
|
|
293
|
+
const error = isRecord2(value) ? value : {};
|
|
294
|
+
const details = {
|
|
295
|
+
name: safeText(error.name, "Error"),
|
|
296
|
+
message: safeText(error.message, "Session recovery fetch failed")
|
|
297
|
+
};
|
|
298
|
+
const code = safeOptionalText(error.code);
|
|
299
|
+
if (code) details.code = code;
|
|
300
|
+
if (isRecord2(error.cause)) {
|
|
301
|
+
const causeCode = safeOptionalText(error.cause.code);
|
|
302
|
+
details.cause = {
|
|
303
|
+
name: safeText(error.cause.name, "Error"),
|
|
304
|
+
message: safeText(error.cause.message, "Session recovery fetch failed"),
|
|
305
|
+
...causeCode ? { code: causeCode } : {}
|
|
306
|
+
};
|
|
223
307
|
}
|
|
308
|
+
return details;
|
|
309
|
+
}
|
|
310
|
+
__name(safeErrorDetails, "safeErrorDetails");
|
|
311
|
+
function safeText(value, fallback) {
|
|
312
|
+
if (typeof value !== "string" || !value) return fallback;
|
|
313
|
+
return value.replace(/[\u0000-\u001F\u007F]/g, " ").slice(0, 300);
|
|
314
|
+
}
|
|
315
|
+
__name(safeText, "safeText");
|
|
316
|
+
function safeOptionalText(value) {
|
|
317
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
318
|
+
return safeText(String(value), "");
|
|
319
|
+
}
|
|
320
|
+
__name(safeOptionalText, "safeOptionalText");
|
|
321
|
+
function isRecord2(value) {
|
|
322
|
+
return typeof value === "object" && value !== null;
|
|
224
323
|
}
|
|
225
|
-
__name(
|
|
324
|
+
__name(isRecord2, "isRecord");
|
|
226
325
|
|
|
227
326
|
// src/client/server/withAuthMiddleware.ts
|
|
228
327
|
function withAuthMiddleware(config) {
|
|
@@ -238,8 +337,11 @@ function withAuthMiddleware(config) {
|
|
|
238
337
|
sessionSecret,
|
|
239
338
|
sessionMaxAge,
|
|
240
339
|
verifyAlways = false,
|
|
241
|
-
recoveryURL
|
|
340
|
+
recoveryURL,
|
|
341
|
+
internalRecoveryURL,
|
|
342
|
+
onRecoveryFailure
|
|
242
343
|
} = config;
|
|
344
|
+
const resolvedInternalRecoveryURL = resolveInternalRecoveryURL(internalRecoveryURL);
|
|
243
345
|
return /* @__PURE__ */ __name(async function middleware(request) {
|
|
244
346
|
const { NextResponse } = await import("next/server");
|
|
245
347
|
const redirectToLogin = /* @__PURE__ */ __name((returnPath2, clearCookies) => {
|
|
@@ -275,17 +377,20 @@ function withAuthMiddleware(config) {
|
|
|
275
377
|
let recovery = null;
|
|
276
378
|
if (!session || verifyAlways) {
|
|
277
379
|
const refreshCookie = readCookieValue(cookie, cookieName);
|
|
278
|
-
if (!refreshCookie || recoveryURL === false) {
|
|
380
|
+
if (!refreshCookie || recoveryURL === false && !resolvedInternalRecoveryURL) {
|
|
279
381
|
return redirectToLogin(returnPath, ["refresh", "session"]);
|
|
280
382
|
}
|
|
281
|
-
const endpoint = recoveryURL ? new URL(recoveryURL, request.url).toString() : authEndpoint(apiBaseURL, authPrefix, "/session/recover", request.url);
|
|
383
|
+
const endpoint = resolvedInternalRecoveryURL ?? (recoveryURL ? new URL(recoveryURL, request.url).toString() : authEndpoint(apiBaseURL, authPrefix, "/session/recover", request.url));
|
|
282
384
|
recovery = await requestSessionRecovery({
|
|
283
385
|
endpoint,
|
|
386
|
+
requestOrigin: url.origin,
|
|
387
|
+
allowLoopbackEndpoint: resolvedInternalRecoveryURL !== void 0,
|
|
284
388
|
refreshCookieName: cookieName,
|
|
285
389
|
refreshCookieValue: refreshCookie,
|
|
286
390
|
sessionCookieName,
|
|
287
391
|
sessionSecret: secret,
|
|
288
|
-
sessionMaxAge
|
|
392
|
+
sessionMaxAge,
|
|
393
|
+
onFailure: onRecoveryFailure
|
|
289
394
|
});
|
|
290
395
|
if (recovery.status !== "recovered") {
|
|
291
396
|
return redirectToLogin(
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DecodedToken, T as TabSyncMessage, S as SyncPayload } from '../NajmAuthClient-
|
|
2
|
-
export {
|
|
1
|
+
import { D as DecodedToken, T as TabSyncMessage, S as SyncPayload } from '../NajmAuthClient-0yzFb9CR.js';
|
|
2
|
+
export { A as AuthClientConfig, a as AuthError, b as AuthEvent, c as AuthEventHandler, d as AuthEventMap, e as AuthState, f as AuthUser, F as FetchClient, H as HydrateSession, N as NajmAuthClient, O as OAuthLoginOptions, g as OAuthProvider, R as RequestOptions, h as RetryConfig, i as ServerResponse, j as TokenPair, k as createAuthClient } from '../NajmAuthClient-0yzFb9CR.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Decode a JWT token payload without verification.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode, CSSProperties, ReactElement } from 'react';
|
|
4
|
-
import { N as NajmAuthClient, H as HydrateSession,
|
|
4
|
+
import { N as NajmAuthClient, H as HydrateSession, e as AuthState, f as AuthUser, a as AuthError, O as OAuthLoginOptions, b as AuthEvent, d as AuthEventMap } from '../../NajmAuthClient-0yzFb9CR.js';
|
|
5
5
|
|
|
6
6
|
interface AuthProviderProps {
|
|
7
7
|
client: NajmAuthClient;
|
|
@@ -7,7 +7,7 @@ import { useEffect, useRef } from "react";
|
|
|
7
7
|
|
|
8
8
|
// src/client/react/context.ts
|
|
9
9
|
import { createContext, useContext } from "react";
|
|
10
|
-
var KEY =
|
|
10
|
+
var KEY = Symbol.for("najm:auth:client:context");
|
|
11
11
|
var contextStore = globalThis;
|
|
12
12
|
function getAuthClientContext() {
|
|
13
13
|
const existing = contextStore[KEY];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { f as AuthUser, F as FetchClient, N as NajmAuthClient, h as RetryConfig } from '../../NajmAuthClient-0yzFb9CR.js';
|
|
2
|
+
import { SessionRecoveryFailure } from '../edge.js';
|
|
3
|
+
export { SessionRecoveryErrorDetails, SessionRecoveryFailureReason, withAuthMiddleware } from '../edge.js';
|
|
3
4
|
import 'next/server';
|
|
4
5
|
|
|
5
6
|
interface GetServerSessionOptions {
|
|
@@ -72,7 +73,7 @@ interface ServerSession {
|
|
|
72
73
|
interface GetSessionConfig {
|
|
73
74
|
/**
|
|
74
75
|
* Base URL for auth endpoints.
|
|
75
|
-
* Defaults to
|
|
76
|
+
* Defaults to `NEXT_PUBLIC_API_URL` or the same-origin `/api` path.
|
|
76
77
|
*/
|
|
77
78
|
baseURL?: string;
|
|
78
79
|
/** Auth route prefix appended to baseURL (default: '/auth'). */
|
|
@@ -96,12 +97,16 @@ interface GetSessionConfig {
|
|
|
96
97
|
* `${baseURL}${authPrefix}/session/recover`. Set to false to disable fallback.
|
|
97
98
|
*/
|
|
98
99
|
recoveryURL?: string | false;
|
|
100
|
+
/** Loopback-only recovery endpoint for self-hosted reverse-proxy setups. */
|
|
101
|
+
internalRecoveryURL?: string;
|
|
99
102
|
/**
|
|
100
103
|
* Error handling mode:
|
|
101
104
|
* - 'nullable' (default): returns null on any failure
|
|
102
105
|
* - 'strict': throws typed errors for debugging
|
|
103
106
|
*/
|
|
104
107
|
mode?: 'nullable' | 'strict';
|
|
108
|
+
/** Secret-free diagnostic hook for failed recovery attempts. */
|
|
109
|
+
onRecoveryFailure?: (failure: SessionRecoveryFailure) => void;
|
|
105
110
|
}
|
|
106
111
|
declare class NoSessionError extends Error {
|
|
107
112
|
readonly code = "NO_SESSION";
|
|
@@ -181,6 +186,8 @@ interface DefineAuthConfig {
|
|
|
181
186
|
* `${apiBaseURL}${authPrefix}/session/recover`; false disables recovery.
|
|
182
187
|
*/
|
|
183
188
|
recoveryURL?: string | false;
|
|
189
|
+
/** Loopback-only recovery endpoint for self-hosted reverse-proxy setups. */
|
|
190
|
+
internalRecoveryURL?: string;
|
|
184
191
|
/** Next.js middleware matcher (default: exclude _next, favicon, api) */
|
|
185
192
|
matcher?: string[];
|
|
186
193
|
/**
|
|
@@ -188,6 +195,8 @@ interface DefineAuthConfig {
|
|
|
188
195
|
* Recovery reissues the signed cookie without rotating refresh tokens.
|
|
189
196
|
*/
|
|
190
197
|
verifyAlways?: boolean;
|
|
198
|
+
/** Secret-free diagnostic hook for failed server or proxy recovery. */
|
|
199
|
+
onRecoveryFailure?: (failure: SessionRecoveryFailure) => void;
|
|
191
200
|
}
|
|
192
201
|
interface AuthKit {
|
|
193
202
|
/**
|
|
@@ -223,4 +232,4 @@ interface AuthKit {
|
|
|
223
232
|
}
|
|
224
233
|
declare function defineAuth(authConfig?: DefineAuthConfig): AuthKit;
|
|
225
234
|
|
|
226
|
-
export { AuthConfigError, type AuthKit, AuthTransportError, type DefineAuthConfig, type GetSessionConfig, NoSessionError, type ServerSession, type WithAuthOptions, type WithAuthProps, createServerClient, defineAuth, getServerSession, getSession, withAuth };
|
|
235
|
+
export { AuthConfigError, type AuthKit, AuthTransportError, type DefineAuthConfig, type GetSessionConfig, NoSessionError, type ServerSession, SessionRecoveryFailure, type WithAuthOptions, type WithAuthProps, createServerClient, defineAuth, getServerSession, getSession, withAuth };
|
|
@@ -38,20 +38,33 @@ function parseSessionCookiePayload(payload, maxAgeSeconds = DEFAULT_SESSION_MAX_
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
async function verifySessionCookie(rawCookieValue, options) {
|
|
41
|
-
|
|
41
|
+
const result = await verifySessionCookieDetailed(rawCookieValue, options);
|
|
42
|
+
return result.status === "valid" ? result.claims : null;
|
|
43
|
+
}
|
|
44
|
+
async function verifySessionCookieDetailed(rawCookieValue, options) {
|
|
45
|
+
if (!rawCookieValue || !options.secret || !globalThis.crypto?.subtle) {
|
|
46
|
+
return { status: "invalid", reason: "format" };
|
|
47
|
+
}
|
|
48
|
+
let sawSignedFormat = false;
|
|
49
|
+
let sawValidHmac = false;
|
|
42
50
|
for (const signedValue of cookieValueCandidates(rawCookieValue)) {
|
|
43
51
|
const lastDot = signedValue.lastIndexOf(".");
|
|
44
52
|
if (lastDot <= 0 || lastDot === signedValue.length - 1) continue;
|
|
53
|
+
sawSignedFormat = true;
|
|
45
54
|
const payload = signedValue.slice(0, lastDot);
|
|
46
55
|
const signature = signedValue.slice(lastDot + 1);
|
|
47
56
|
if (!await verifyHmac(payload, signature, options.secret)) continue;
|
|
48
|
-
|
|
57
|
+
sawValidHmac = true;
|
|
58
|
+
const claims = parseSessionCookiePayload(
|
|
49
59
|
payload,
|
|
50
60
|
options.maxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
51
61
|
options.now
|
|
52
62
|
);
|
|
63
|
+
if (claims) return { status: "valid", claims };
|
|
53
64
|
}
|
|
54
|
-
return
|
|
65
|
+
if (sawValidHmac) return { status: "invalid", reason: "payload" };
|
|
66
|
+
if (sawSignedFormat) return { status: "invalid", reason: "hmac" };
|
|
67
|
+
return { status: "invalid", reason: "format" };
|
|
55
68
|
}
|
|
56
69
|
function readCookieValue(cookieHeader, name) {
|
|
57
70
|
for (const part of cookieHeader.split(";")) {
|
|
@@ -134,6 +147,7 @@ var init_sessionCookie = __esm({
|
|
|
134
147
|
__name(resolveSessionSecret, "resolveSessionSecret");
|
|
135
148
|
__name(parseSessionCookiePayload, "parseSessionCookiePayload");
|
|
136
149
|
__name(verifySessionCookie, "verifySessionCookie");
|
|
150
|
+
__name(verifySessionCookieDetailed, "verifySessionCookieDetailed");
|
|
137
151
|
__name(readCookieValue, "readCookieValue");
|
|
138
152
|
__name(verifyHmac, "verifyHmac");
|
|
139
153
|
__name(cookieValueCandidates, "cookieValueCandidates");
|
|
@@ -145,19 +159,32 @@ var init_sessionCookie = __esm({
|
|
|
145
159
|
});
|
|
146
160
|
|
|
147
161
|
// src/client/sessionRecovery.ts
|
|
162
|
+
function resolveInternalRecoveryURL(explicit) {
|
|
163
|
+
if (explicit !== void 0) return explicit || void 0;
|
|
164
|
+
if (typeof process === "undefined") return void 0;
|
|
165
|
+
return process.env.NAJM_AUTH_INTERNAL_URL || void 0;
|
|
166
|
+
}
|
|
148
167
|
async function requestSessionRecovery(options) {
|
|
149
168
|
if (!isCookieName(options.refreshCookieName) || !isCookieName(options.sessionCookieName)) {
|
|
169
|
+
reportFailure(options, { reason: "invalid-cookie-name" });
|
|
150
170
|
return { status: "unavailable" };
|
|
151
171
|
}
|
|
152
172
|
if (!isCookieValue(options.refreshCookieValue)) {
|
|
173
|
+
reportFailure(options, { reason: "invalid-refresh-cookie" });
|
|
153
174
|
return { status: "invalid" };
|
|
154
175
|
}
|
|
155
|
-
|
|
176
|
+
const endpoint = sameOriginRecoveryEndpoint(
|
|
177
|
+
options.endpoint,
|
|
178
|
+
options.requestOrigin,
|
|
179
|
+
options.allowLoopbackEndpoint
|
|
180
|
+
);
|
|
181
|
+
if (!endpoint) {
|
|
182
|
+
reportFailure(options, { reason: "invalid-endpoint" });
|
|
156
183
|
return { status: "unavailable" };
|
|
157
184
|
}
|
|
158
185
|
let response;
|
|
159
186
|
try {
|
|
160
|
-
response = await fetch(
|
|
187
|
+
response = await fetch(endpoint, {
|
|
161
188
|
method: "POST",
|
|
162
189
|
headers: {
|
|
163
190
|
Accept: "application/json",
|
|
@@ -167,30 +194,51 @@ async function requestSessionRecovery(options) {
|
|
|
167
194
|
cache: "no-store",
|
|
168
195
|
redirect: "manual"
|
|
169
196
|
});
|
|
170
|
-
} catch {
|
|
197
|
+
} catch (error) {
|
|
198
|
+
reportFailure(options, {
|
|
199
|
+
reason: "fetch-error",
|
|
200
|
+
error: safeErrorDetails(error)
|
|
201
|
+
});
|
|
171
202
|
return { status: "unavailable" };
|
|
172
203
|
}
|
|
173
204
|
if (!response.ok) {
|
|
174
205
|
const status = response.status;
|
|
206
|
+
reportFailure(options, { reason: "http-status", httpStatus: status });
|
|
175
207
|
return {
|
|
176
208
|
status: status === 400 || status === 401 || status === 403 ? "invalid" : "unavailable",
|
|
177
209
|
httpStatus: status
|
|
178
210
|
};
|
|
179
211
|
}
|
|
180
212
|
const setCookie = response.headers.get("set-cookie");
|
|
181
|
-
if (!setCookie)
|
|
213
|
+
if (!setCookie) {
|
|
214
|
+
reportFailure(options, {
|
|
215
|
+
reason: "missing-set-cookie",
|
|
216
|
+
httpStatus: response.status
|
|
217
|
+
});
|
|
218
|
+
return { status: "unavailable", httpStatus: response.status };
|
|
219
|
+
}
|
|
182
220
|
const sessionCookieValue = readSetCookieValue(setCookie, options.sessionCookieName);
|
|
183
221
|
if (!sessionCookieValue) {
|
|
222
|
+
reportFailure(options, {
|
|
223
|
+
reason: "session-cookie-parse",
|
|
224
|
+
httpStatus: response.status
|
|
225
|
+
});
|
|
184
226
|
return { status: "unavailable", httpStatus: response.status };
|
|
185
227
|
}
|
|
186
|
-
const
|
|
228
|
+
const verification = await verifySessionCookieDetailed(sessionCookieValue, {
|
|
187
229
|
secret: options.sessionSecret,
|
|
188
230
|
maxAgeSeconds: options.sessionMaxAge
|
|
189
231
|
});
|
|
190
|
-
if (
|
|
232
|
+
if (verification.status === "invalid") {
|
|
233
|
+
reportFailure(options, {
|
|
234
|
+
reason: verification.reason === "hmac" ? "session-cookie-hmac" : verification.reason === "payload" ? "session-cookie-payload" : "session-cookie-parse",
|
|
235
|
+
httpStatus: response.status
|
|
236
|
+
});
|
|
237
|
+
return { status: "unavailable", httpStatus: response.status };
|
|
238
|
+
}
|
|
191
239
|
return {
|
|
192
240
|
status: "recovered",
|
|
193
|
-
claims,
|
|
241
|
+
claims: verification.claims,
|
|
194
242
|
setCookie,
|
|
195
243
|
sessionCookieValue
|
|
196
244
|
};
|
|
@@ -216,29 +264,80 @@ function isCookieName(value) {
|
|
|
216
264
|
return /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(value);
|
|
217
265
|
}
|
|
218
266
|
function isCookieValue(value) {
|
|
219
|
-
return value.length > 0 &&
|
|
267
|
+
return value.length > 0 && /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/.test(value);
|
|
220
268
|
}
|
|
221
|
-
function
|
|
269
|
+
function sameOriginRecoveryEndpoint(endpoint, requestOrigin, allowLoopback = false) {
|
|
222
270
|
try {
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
271
|
+
const trusted = new URL(requestOrigin);
|
|
272
|
+
if (trusted.protocol !== "https:" && trusted.protocol !== "http:" || trusted.username || trusted.password) {
|
|
273
|
+
return void 0;
|
|
274
|
+
}
|
|
275
|
+
const resolved = new URL(endpoint, trusted.origin);
|
|
276
|
+
if (resolved.username || resolved.password) return void 0;
|
|
277
|
+
if (resolved.origin !== trusted.origin && (!allowLoopback || !isLoopbackURL(resolved))) {
|
|
278
|
+
return void 0;
|
|
279
|
+
}
|
|
280
|
+
return resolved.toString();
|
|
228
281
|
} catch {
|
|
229
|
-
return
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function isLoopbackURL(url) {
|
|
286
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return false;
|
|
287
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]" || url.hostname === "::1";
|
|
288
|
+
}
|
|
289
|
+
function reportFailure(options, failure) {
|
|
290
|
+
try {
|
|
291
|
+
options.onFailure?.(failure);
|
|
292
|
+
} catch {
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function safeErrorDetails(value) {
|
|
296
|
+
const error = isRecord2(value) ? value : {};
|
|
297
|
+
const details = {
|
|
298
|
+
name: safeText(error.name, "Error"),
|
|
299
|
+
message: safeText(error.message, "Session recovery fetch failed")
|
|
300
|
+
};
|
|
301
|
+
const code = safeOptionalText(error.code);
|
|
302
|
+
if (code) details.code = code;
|
|
303
|
+
if (isRecord2(error.cause)) {
|
|
304
|
+
const causeCode = safeOptionalText(error.cause.code);
|
|
305
|
+
details.cause = {
|
|
306
|
+
name: safeText(error.cause.name, "Error"),
|
|
307
|
+
message: safeText(error.cause.message, "Session recovery fetch failed"),
|
|
308
|
+
...causeCode ? { code: causeCode } : {}
|
|
309
|
+
};
|
|
230
310
|
}
|
|
311
|
+
return details;
|
|
312
|
+
}
|
|
313
|
+
function safeText(value, fallback) {
|
|
314
|
+
if (typeof value !== "string" || !value) return fallback;
|
|
315
|
+
return value.replace(/[\u0000-\u001F\u007F]/g, " ").slice(0, 300);
|
|
316
|
+
}
|
|
317
|
+
function safeOptionalText(value) {
|
|
318
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
319
|
+
return safeText(String(value), "");
|
|
320
|
+
}
|
|
321
|
+
function isRecord2(value) {
|
|
322
|
+
return typeof value === "object" && value !== null;
|
|
231
323
|
}
|
|
232
324
|
var init_sessionRecovery = __esm({
|
|
233
325
|
"src/client/sessionRecovery.ts"() {
|
|
234
326
|
init_sessionCookie();
|
|
327
|
+
__name(resolveInternalRecoveryURL, "resolveInternalRecoveryURL");
|
|
235
328
|
__name(requestSessionRecovery, "requestSessionRecovery");
|
|
236
329
|
__name(authEndpoint, "authEndpoint");
|
|
237
330
|
__name(replaceCookieValue, "replaceCookieValue");
|
|
238
331
|
__name(readSetCookieValue, "readSetCookieValue");
|
|
239
332
|
__name(isCookieName, "isCookieName");
|
|
240
333
|
__name(isCookieValue, "isCookieValue");
|
|
241
|
-
__name(
|
|
334
|
+
__name(sameOriginRecoveryEndpoint, "sameOriginRecoveryEndpoint");
|
|
335
|
+
__name(isLoopbackURL, "isLoopbackURL");
|
|
336
|
+
__name(reportFailure, "reportFailure");
|
|
337
|
+
__name(safeErrorDetails, "safeErrorDetails");
|
|
338
|
+
__name(safeText, "safeText");
|
|
339
|
+
__name(safeOptionalText, "safeOptionalText");
|
|
340
|
+
__name(isRecord2, "isRecord");
|
|
242
341
|
}
|
|
243
342
|
});
|
|
244
343
|
|
|
@@ -253,8 +352,25 @@ __export(getSession_exports, {
|
|
|
253
352
|
function defaultBaseURL() {
|
|
254
353
|
const explicit = typeof process !== "undefined" ? process.env.NAJM_AUTH_BASE_URL : void 0;
|
|
255
354
|
if (explicit) return explicit;
|
|
256
|
-
const
|
|
257
|
-
return
|
|
355
|
+
const publicUrl = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_API_URL : void 0;
|
|
356
|
+
return publicUrl || "/api";
|
|
357
|
+
}
|
|
358
|
+
function firstForwardedValue(value) {
|
|
359
|
+
const first = value?.split(",")[0]?.trim();
|
|
360
|
+
return first || void 0;
|
|
361
|
+
}
|
|
362
|
+
function requestOriginFromHeaders(headers) {
|
|
363
|
+
const host = firstForwardedValue(headers.get("x-forwarded-host")) ?? firstForwardedValue(headers.get("host"));
|
|
364
|
+
if (!host) return void 0;
|
|
365
|
+
const protocol = firstForwardedValue(headers.get("x-forwarded-proto")) ?? "https";
|
|
366
|
+
if (protocol !== "https" && protocol !== "http") return void 0;
|
|
367
|
+
try {
|
|
368
|
+
const url = new URL(`${protocol}://${host}`);
|
|
369
|
+
if (url.username || url.password) return void 0;
|
|
370
|
+
return url.origin;
|
|
371
|
+
} catch {
|
|
372
|
+
return void 0;
|
|
373
|
+
}
|
|
258
374
|
}
|
|
259
375
|
async function getSession(config = {}) {
|
|
260
376
|
const cookieName = config.cookieName ?? "refreshToken";
|
|
@@ -262,13 +378,18 @@ async function getSession(config = {}) {
|
|
|
262
378
|
const baseURL = config.baseURL ?? defaultBaseURL();
|
|
263
379
|
const prefix = config.authPrefix ?? "/auth";
|
|
264
380
|
const strict = config.mode === "strict";
|
|
381
|
+
const internalRecoveryURL = resolveInternalRecoveryURL(config.internalRecoveryURL);
|
|
265
382
|
let sessionCookieValue;
|
|
266
383
|
let refreshCookieValue;
|
|
384
|
+
let requestOrigin;
|
|
267
385
|
try {
|
|
268
386
|
const mod = await import("next/headers");
|
|
269
387
|
const cookieStore = await mod.cookies();
|
|
270
388
|
sessionCookieValue = cookieStore.get(sessionCookieName)?.value;
|
|
271
389
|
refreshCookieValue = cookieStore.get(cookieName)?.value;
|
|
390
|
+
if (typeof mod.headers === "function") {
|
|
391
|
+
requestOrigin = requestOriginFromHeaders(await mod.headers());
|
|
392
|
+
}
|
|
272
393
|
} catch {
|
|
273
394
|
if (strict) throw new AuthConfigError("Failed to read cookies from Next.js headers()");
|
|
274
395
|
return null;
|
|
@@ -291,18 +412,25 @@ async function getSession(config = {}) {
|
|
|
291
412
|
if (strict) throw new AuthConfigError("Session cookie secret is not configured");
|
|
292
413
|
return null;
|
|
293
414
|
}
|
|
294
|
-
if (!refreshCookieValue || config.recoveryURL === false) {
|
|
415
|
+
if (!refreshCookieValue || config.recoveryURL === false && !internalRecoveryURL) {
|
|
295
416
|
if (strict) throw new NoSessionError("No recoverable refresh session");
|
|
296
417
|
return null;
|
|
297
418
|
}
|
|
298
|
-
|
|
419
|
+
if (!requestOrigin) {
|
|
420
|
+
if (strict) throw new AuthConfigError("Incoming request origin is unavailable");
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
const endpoint = internalRecoveryURL ?? (config.recoveryURL ? new URL(config.recoveryURL, requestOrigin).toString() : authEndpoint(baseURL, prefix, "/session/recover", requestOrigin));
|
|
299
424
|
const recovery = await requestSessionRecovery({
|
|
300
425
|
endpoint,
|
|
426
|
+
requestOrigin,
|
|
427
|
+
allowLoopbackEndpoint: internalRecoveryURL !== void 0,
|
|
301
428
|
refreshCookieName: cookieName,
|
|
302
429
|
refreshCookieValue,
|
|
303
430
|
sessionCookieName,
|
|
304
431
|
sessionSecret: secret,
|
|
305
|
-
sessionMaxAge: config.sessionMaxAge
|
|
432
|
+
sessionMaxAge: config.sessionMaxAge,
|
|
433
|
+
onFailure: config.onRecoveryFailure
|
|
306
434
|
});
|
|
307
435
|
if (recovery.status === "recovered") {
|
|
308
436
|
return {
|
|
@@ -359,6 +487,8 @@ var init_getSession = __esm({
|
|
|
359
487
|
code = "AUTH_TRANSPORT_ERROR";
|
|
360
488
|
};
|
|
361
489
|
__name(defaultBaseURL, "defaultBaseURL");
|
|
490
|
+
__name(firstForwardedValue, "firstForwardedValue");
|
|
491
|
+
__name(requestOriginFromHeaders, "requestOriginFromHeaders");
|
|
362
492
|
__name(getSession, "getSession");
|
|
363
493
|
}
|
|
364
494
|
});
|
|
@@ -568,8 +698,11 @@ function withAuthMiddleware(config) {
|
|
|
568
698
|
sessionSecret,
|
|
569
699
|
sessionMaxAge,
|
|
570
700
|
verifyAlways = false,
|
|
571
|
-
recoveryURL
|
|
701
|
+
recoveryURL,
|
|
702
|
+
internalRecoveryURL,
|
|
703
|
+
onRecoveryFailure
|
|
572
704
|
} = config;
|
|
705
|
+
const resolvedInternalRecoveryURL = resolveInternalRecoveryURL(internalRecoveryURL);
|
|
573
706
|
return /* @__PURE__ */ __name(async function middleware(request) {
|
|
574
707
|
const { NextResponse } = await import("next/server");
|
|
575
708
|
const redirectToLogin = /* @__PURE__ */ __name((returnPath2, clearCookies) => {
|
|
@@ -605,17 +738,20 @@ function withAuthMiddleware(config) {
|
|
|
605
738
|
let recovery = null;
|
|
606
739
|
if (!session || verifyAlways) {
|
|
607
740
|
const refreshCookie = readCookieValue(cookie, cookieName);
|
|
608
|
-
if (!refreshCookie || recoveryURL === false) {
|
|
741
|
+
if (!refreshCookie || recoveryURL === false && !resolvedInternalRecoveryURL) {
|
|
609
742
|
return redirectToLogin(returnPath, ["refresh", "session"]);
|
|
610
743
|
}
|
|
611
|
-
const endpoint = recoveryURL ? new URL(recoveryURL, request.url).toString() : authEndpoint(apiBaseURL, authPrefix, "/session/recover", request.url);
|
|
744
|
+
const endpoint = resolvedInternalRecoveryURL ?? (recoveryURL ? new URL(recoveryURL, request.url).toString() : authEndpoint(apiBaseURL, authPrefix, "/session/recover", request.url));
|
|
612
745
|
recovery = await requestSessionRecovery({
|
|
613
746
|
endpoint,
|
|
747
|
+
requestOrigin: url.origin,
|
|
748
|
+
allowLoopbackEndpoint: resolvedInternalRecoveryURL !== void 0,
|
|
614
749
|
refreshCookieName: cookieName,
|
|
615
750
|
refreshCookieValue: refreshCookie,
|
|
616
751
|
sessionCookieName,
|
|
617
752
|
sessionSecret: secret,
|
|
618
|
-
sessionMaxAge
|
|
753
|
+
sessionMaxAge,
|
|
754
|
+
onFailure: onRecoveryFailure
|
|
619
755
|
});
|
|
620
756
|
if (recovery.status !== "recovered") {
|
|
621
757
|
return redirectToLogin(
|
|
@@ -1176,8 +1312,10 @@ function defineAuth(authConfig = {}) {
|
|
|
1176
1312
|
sessionSecret,
|
|
1177
1313
|
sessionMaxAge,
|
|
1178
1314
|
recoveryURL,
|
|
1315
|
+
internalRecoveryURL,
|
|
1179
1316
|
matcher = ["/((?!_next/static|_next/image|favicon.ico|api).*)"],
|
|
1180
1317
|
verifyAlways = false,
|
|
1318
|
+
onRecoveryFailure,
|
|
1181
1319
|
refreshThreshold,
|
|
1182
1320
|
tabSync,
|
|
1183
1321
|
channelName,
|
|
@@ -1185,14 +1323,15 @@ function defineAuth(authConfig = {}) {
|
|
|
1185
1323
|
retry
|
|
1186
1324
|
} = authConfig;
|
|
1187
1325
|
const sessionConfig = {
|
|
1188
|
-
baseURL:
|
|
1189
|
-
// resolved at call time from env/defaults
|
|
1326
|
+
baseURL: apiBaseURL,
|
|
1190
1327
|
authPrefix,
|
|
1191
1328
|
cookieName,
|
|
1192
1329
|
sessionCookieName,
|
|
1193
1330
|
sessionSecret,
|
|
1194
1331
|
sessionMaxAge,
|
|
1195
|
-
recoveryURL
|
|
1332
|
+
recoveryURL,
|
|
1333
|
+
internalRecoveryURL,
|
|
1334
|
+
onRecoveryFailure
|
|
1196
1335
|
};
|
|
1197
1336
|
let _client = null;
|
|
1198
1337
|
const getClient = /* @__PURE__ */ __name(() => {
|
|
@@ -1246,7 +1385,9 @@ function defineAuth(authConfig = {}) {
|
|
|
1246
1385
|
sessionSecret,
|
|
1247
1386
|
sessionMaxAge,
|
|
1248
1387
|
recoveryURL,
|
|
1249
|
-
|
|
1388
|
+
internalRecoveryURL,
|
|
1389
|
+
verifyAlways,
|
|
1390
|
+
onRecoveryFailure
|
|
1250
1391
|
});
|
|
1251
1392
|
const protect = /* @__PURE__ */ __name((Page, options) => {
|
|
1252
1393
|
return /* @__PURE__ */ __name(async function ProtectedPage(props) {
|
package/dist/index.js
CHANGED
|
@@ -10,12 +10,12 @@ import { Err as Err11, plugin } from "najm-core";
|
|
|
10
10
|
import { cache } from "najm-cache";
|
|
11
11
|
|
|
12
12
|
// src/auth.tokens.ts
|
|
13
|
-
var AUTH_CONFIG =
|
|
14
|
-
var AUTH_SCHEMA =
|
|
15
|
-
var AUTH_USER =
|
|
16
|
-
var AUTH_ROLE =
|
|
17
|
-
var AUTH_PERMISSIONS =
|
|
18
|
-
var AUTH_ENCRYPTION_KEY =
|
|
13
|
+
var AUTH_CONFIG = Symbol.for("najm:auth:config");
|
|
14
|
+
var AUTH_SCHEMA = Symbol.for("najm:auth:schema");
|
|
15
|
+
var AUTH_USER = Symbol.for("najm:auth:user");
|
|
16
|
+
var AUTH_ROLE = Symbol.for("najm:auth:role");
|
|
17
|
+
var AUTH_PERMISSIONS = Symbol.for("najm:auth:permissions");
|
|
18
|
+
var AUTH_ENCRYPTION_KEY = Symbol.for("najm:auth:encryption-key");
|
|
19
19
|
|
|
20
20
|
// src/schema/pg.ts
|
|
21
21
|
import { pgTable, text, boolean, timestamp, pgEnum, primaryKey, integer, index, uniqueIndex } from "drizzle-orm/pg-core";
|
|
@@ -3980,9 +3980,9 @@ var revokeTokenDto = z4.object({
|
|
|
3980
3980
|
// src/ownership/scopedOwnership.ts
|
|
3981
3981
|
import { aliasedTable, eq as eq6, getTableColumns, sql as sql4 } from "drizzle-orm";
|
|
3982
3982
|
var DEFAULT_ADMIN_ROLES = ["admin"];
|
|
3983
|
-
var DRIZZLE_NAME =
|
|
3984
|
-
var DRIZZLE_BASE_NAME =
|
|
3985
|
-
var DRIZZLE_IS_ALIAS =
|
|
3983
|
+
var DRIZZLE_NAME = Symbol.for("drizzle:Name");
|
|
3984
|
+
var DRIZZLE_BASE_NAME = Symbol.for("drizzle:BaseName");
|
|
3985
|
+
var DRIZZLE_IS_ALIAS = Symbol.for("drizzle:IsAlias");
|
|
3986
3986
|
function join2(left, right) {
|
|
3987
3987
|
const table = right.table;
|
|
3988
3988
|
if (!table)
|
|
@@ -4430,9 +4430,9 @@ __name(configureOwnership, "configureOwnership");
|
|
|
4430
4430
|
// src/ownership/ScopeGuard.ts
|
|
4431
4431
|
import "reflect-metadata";
|
|
4432
4432
|
import { composeGuards as composeGuards5 } from "najm-guard";
|
|
4433
|
-
var ACTION_KEY =
|
|
4434
|
-
var TOKEN_KEY =
|
|
4435
|
-
var POLICY_KEY =
|
|
4433
|
+
var ACTION_KEY = Symbol.for("najm:guard:action");
|
|
4434
|
+
var TOKEN_KEY = Symbol.for("najm:guard:token");
|
|
4435
|
+
var POLICY_KEY = Symbol.for("najm:policy:token");
|
|
4436
4436
|
var PERM = {
|
|
4437
4437
|
list: /* @__PURE__ */ __name((n) => `read:${n}`, "list"),
|
|
4438
4438
|
read: /* @__PURE__ */ __name((n) => `read:${n}`, "read"),
|
|
@@ -4502,7 +4502,7 @@ var __metadata25 = function(k, v) {
|
|
|
4502
4502
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
4503
4503
|
};
|
|
4504
4504
|
var _a16;
|
|
4505
|
-
var OWNED_META =
|
|
4505
|
+
var OWNED_META = Symbol.for("najm:owned");
|
|
4506
4506
|
var ScopeContext = class ScopeContext2 {
|
|
4507
4507
|
static {
|
|
4508
4508
|
__name(this, "ScopeContext");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "najm-auth",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
4
4
|
"description": "Authentication and authorization library for najm framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsup",
|
|
50
50
|
"test": "bun test",
|
|
51
|
+
"test:next16": "bun run build && bun integration/next16-proxy/run.ts",
|
|
51
52
|
"test:watch": "bun test --watch",
|
|
52
53
|
"start": "bun --watch src/main.ts",
|
|
53
54
|
"clean": "rimraf dist tsconfig.tsbuildinfo"
|
|
@@ -67,9 +68,10 @@
|
|
|
67
68
|
"@testing-library/react": "^16.3.2",
|
|
68
69
|
"@types/jsonwebtoken": "^9.0.10",
|
|
69
70
|
"@types/node": "^25.0.2",
|
|
70
|
-
"drizzle-kit": "^0.31.
|
|
71
|
-
"drizzle-orm": "^0.45.
|
|
72
|
-
"happy-dom": "^
|
|
71
|
+
"drizzle-kit": "^0.31.10",
|
|
72
|
+
"drizzle-orm": "^0.45.2",
|
|
73
|
+
"happy-dom": "^20.11.1",
|
|
74
|
+
"next": "16.2.11",
|
|
73
75
|
"tsup": "^8.5.1",
|
|
74
76
|
"typescript": "^5.9.3"
|
|
75
77
|
},
|
|
@@ -93,8 +95,8 @@
|
|
|
93
95
|
"zod": "^4.2.1"
|
|
94
96
|
},
|
|
95
97
|
"peerDependencies": {
|
|
96
|
-
"drizzle-orm": "^0.45.
|
|
97
|
-
"hono": "^4.
|
|
98
|
+
"drizzle-orm": "^0.45.2",
|
|
99
|
+
"hono": "^4.12.18",
|
|
98
100
|
"react": ">=18",
|
|
99
101
|
"next": ">=14"
|
|
100
102
|
},
|