najm-auth 2.0.5 → 2.0.6
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 +3 -0
- package/dist/{NajmAuthClient-B9dGk9MH.d.ts → NajmAuthClient-0yzFb9CR.d.ts} +1 -1
- package/dist/client/edge.js +19 -11
- 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 +2 -2
- package/dist/client/server/index.js +51 -16
- package/dist/index.js +13 -13
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -681,6 +681,9 @@ 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.
|
|
684
687
|
- `verifyAlways` forces that authoritative check on every protected request;
|
|
685
688
|
the default bounds cached role/status staleness to `session.maxAge`.
|
|
686
689
|
|
|
@@ -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.js
CHANGED
|
@@ -139,12 +139,16 @@ async function requestSessionRecovery(options) {
|
|
|
139
139
|
if (!isCookieValue(options.refreshCookieValue)) {
|
|
140
140
|
return { status: "invalid" };
|
|
141
141
|
}
|
|
142
|
-
|
|
142
|
+
const endpoint = sameOriginRecoveryEndpoint(
|
|
143
|
+
options.endpoint,
|
|
144
|
+
options.requestOrigin
|
|
145
|
+
);
|
|
146
|
+
if (!endpoint) {
|
|
143
147
|
return { status: "unavailable" };
|
|
144
148
|
}
|
|
145
149
|
let response;
|
|
146
150
|
try {
|
|
147
|
-
response = await fetch(
|
|
151
|
+
response = await fetch(endpoint, {
|
|
148
152
|
method: "POST",
|
|
149
153
|
headers: {
|
|
150
154
|
Accept: "application/json",
|
|
@@ -208,21 +212,24 @@ function isCookieName(value) {
|
|
|
208
212
|
}
|
|
209
213
|
__name(isCookieName, "isCookieName");
|
|
210
214
|
function isCookieValue(value) {
|
|
211
|
-
return value.length > 0 &&
|
|
215
|
+
return value.length > 0 && /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/.test(value);
|
|
212
216
|
}
|
|
213
217
|
__name(isCookieValue, "isCookieValue");
|
|
214
|
-
function
|
|
218
|
+
function sameOriginRecoveryEndpoint(endpoint, requestOrigin) {
|
|
215
219
|
try {
|
|
216
|
-
const
|
|
217
|
-
if (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
const trusted = new URL(requestOrigin);
|
|
221
|
+
if (trusted.protocol !== "https:" && trusted.protocol !== "http:" || trusted.username || trusted.password) {
|
|
222
|
+
return void 0;
|
|
223
|
+
}
|
|
224
|
+
const resolved = new URL(endpoint, trusted.origin);
|
|
225
|
+
if (resolved.username || resolved.password) return void 0;
|
|
226
|
+
if (resolved.origin !== trusted.origin) return void 0;
|
|
227
|
+
return resolved.toString();
|
|
221
228
|
} catch {
|
|
222
|
-
return
|
|
229
|
+
return void 0;
|
|
223
230
|
}
|
|
224
231
|
}
|
|
225
|
-
__name(
|
|
232
|
+
__name(sameOriginRecoveryEndpoint, "sameOriginRecoveryEndpoint");
|
|
226
233
|
|
|
227
234
|
// src/client/server/withAuthMiddleware.ts
|
|
228
235
|
function withAuthMiddleware(config) {
|
|
@@ -281,6 +288,7 @@ function withAuthMiddleware(config) {
|
|
|
281
288
|
const endpoint = recoveryURL ? new URL(recoveryURL, request.url).toString() : authEndpoint(apiBaseURL, authPrefix, "/session/recover", request.url);
|
|
282
289
|
recovery = await requestSessionRecovery({
|
|
283
290
|
endpoint,
|
|
291
|
+
requestOrigin: url.origin,
|
|
284
292
|
refreshCookieName: cookieName,
|
|
285
293
|
refreshCookieValue: refreshCookie,
|
|
286
294
|
sessionCookieName,
|
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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as AuthUser, F as FetchClient, N as NajmAuthClient, h as RetryConfig } from '../../NajmAuthClient-0yzFb9CR.js';
|
|
2
2
|
export { withAuthMiddleware } from '../edge.js';
|
|
3
3
|
import 'next/server';
|
|
4
4
|
|
|
@@ -72,7 +72,7 @@ interface ServerSession {
|
|
|
72
72
|
interface GetSessionConfig {
|
|
73
73
|
/**
|
|
74
74
|
* Base URL for auth endpoints.
|
|
75
|
-
* Defaults to
|
|
75
|
+
* Defaults to `NEXT_PUBLIC_API_URL` or the same-origin `/api` path.
|
|
76
76
|
*/
|
|
77
77
|
baseURL?: string;
|
|
78
78
|
/** Auth route prefix appended to baseURL (default: '/auth'). */
|
|
@@ -152,12 +152,16 @@ async function requestSessionRecovery(options) {
|
|
|
152
152
|
if (!isCookieValue(options.refreshCookieValue)) {
|
|
153
153
|
return { status: "invalid" };
|
|
154
154
|
}
|
|
155
|
-
|
|
155
|
+
const endpoint = sameOriginRecoveryEndpoint(
|
|
156
|
+
options.endpoint,
|
|
157
|
+
options.requestOrigin
|
|
158
|
+
);
|
|
159
|
+
if (!endpoint) {
|
|
156
160
|
return { status: "unavailable" };
|
|
157
161
|
}
|
|
158
162
|
let response;
|
|
159
163
|
try {
|
|
160
|
-
response = await fetch(
|
|
164
|
+
response = await fetch(endpoint, {
|
|
161
165
|
method: "POST",
|
|
162
166
|
headers: {
|
|
163
167
|
Accept: "application/json",
|
|
@@ -216,17 +220,20 @@ function isCookieName(value) {
|
|
|
216
220
|
return /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(value);
|
|
217
221
|
}
|
|
218
222
|
function isCookieValue(value) {
|
|
219
|
-
return value.length > 0 &&
|
|
223
|
+
return value.length > 0 && /^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/.test(value);
|
|
220
224
|
}
|
|
221
|
-
function
|
|
225
|
+
function sameOriginRecoveryEndpoint(endpoint, requestOrigin) {
|
|
222
226
|
try {
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
227
|
+
const trusted = new URL(requestOrigin);
|
|
228
|
+
if (trusted.protocol !== "https:" && trusted.protocol !== "http:" || trusted.username || trusted.password) {
|
|
229
|
+
return void 0;
|
|
230
|
+
}
|
|
231
|
+
const resolved = new URL(endpoint, trusted.origin);
|
|
232
|
+
if (resolved.username || resolved.password) return void 0;
|
|
233
|
+
if (resolved.origin !== trusted.origin) return void 0;
|
|
234
|
+
return resolved.toString();
|
|
228
235
|
} catch {
|
|
229
|
-
return
|
|
236
|
+
return void 0;
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
var init_sessionRecovery = __esm({
|
|
@@ -238,7 +245,7 @@ var init_sessionRecovery = __esm({
|
|
|
238
245
|
__name(readSetCookieValue, "readSetCookieValue");
|
|
239
246
|
__name(isCookieName, "isCookieName");
|
|
240
247
|
__name(isCookieValue, "isCookieValue");
|
|
241
|
-
__name(
|
|
248
|
+
__name(sameOriginRecoveryEndpoint, "sameOriginRecoveryEndpoint");
|
|
242
249
|
}
|
|
243
250
|
});
|
|
244
251
|
|
|
@@ -253,8 +260,25 @@ __export(getSession_exports, {
|
|
|
253
260
|
function defaultBaseURL() {
|
|
254
261
|
const explicit = typeof process !== "undefined" ? process.env.NAJM_AUTH_BASE_URL : void 0;
|
|
255
262
|
if (explicit) return explicit;
|
|
256
|
-
const
|
|
257
|
-
return
|
|
263
|
+
const publicUrl = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_API_URL : void 0;
|
|
264
|
+
return publicUrl || "/api";
|
|
265
|
+
}
|
|
266
|
+
function firstForwardedValue(value) {
|
|
267
|
+
const first = value?.split(",")[0]?.trim();
|
|
268
|
+
return first || void 0;
|
|
269
|
+
}
|
|
270
|
+
function requestOriginFromHeaders(headers) {
|
|
271
|
+
const host = firstForwardedValue(headers.get("x-forwarded-host")) ?? firstForwardedValue(headers.get("host"));
|
|
272
|
+
if (!host) return void 0;
|
|
273
|
+
const protocol = firstForwardedValue(headers.get("x-forwarded-proto")) ?? "https";
|
|
274
|
+
if (protocol !== "https" && protocol !== "http") return void 0;
|
|
275
|
+
try {
|
|
276
|
+
const url = new URL(`${protocol}://${host}`);
|
|
277
|
+
if (url.username || url.password) return void 0;
|
|
278
|
+
return url.origin;
|
|
279
|
+
} catch {
|
|
280
|
+
return void 0;
|
|
281
|
+
}
|
|
258
282
|
}
|
|
259
283
|
async function getSession(config = {}) {
|
|
260
284
|
const cookieName = config.cookieName ?? "refreshToken";
|
|
@@ -264,11 +288,15 @@ async function getSession(config = {}) {
|
|
|
264
288
|
const strict = config.mode === "strict";
|
|
265
289
|
let sessionCookieValue;
|
|
266
290
|
let refreshCookieValue;
|
|
291
|
+
let requestOrigin;
|
|
267
292
|
try {
|
|
268
293
|
const mod = await import("next/headers");
|
|
269
294
|
const cookieStore = await mod.cookies();
|
|
270
295
|
sessionCookieValue = cookieStore.get(sessionCookieName)?.value;
|
|
271
296
|
refreshCookieValue = cookieStore.get(cookieName)?.value;
|
|
297
|
+
if (typeof mod.headers === "function") {
|
|
298
|
+
requestOrigin = requestOriginFromHeaders(await mod.headers());
|
|
299
|
+
}
|
|
272
300
|
} catch {
|
|
273
301
|
if (strict) throw new AuthConfigError("Failed to read cookies from Next.js headers()");
|
|
274
302
|
return null;
|
|
@@ -295,9 +323,14 @@ async function getSession(config = {}) {
|
|
|
295
323
|
if (strict) throw new NoSessionError("No recoverable refresh session");
|
|
296
324
|
return null;
|
|
297
325
|
}
|
|
298
|
-
|
|
326
|
+
if (!requestOrigin) {
|
|
327
|
+
if (strict) throw new AuthConfigError("Incoming request origin is unavailable");
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
const endpoint = config.recoveryURL ? new URL(config.recoveryURL, requestOrigin).toString() : authEndpoint(baseURL, prefix, "/session/recover", requestOrigin);
|
|
299
331
|
const recovery = await requestSessionRecovery({
|
|
300
332
|
endpoint,
|
|
333
|
+
requestOrigin,
|
|
301
334
|
refreshCookieName: cookieName,
|
|
302
335
|
refreshCookieValue,
|
|
303
336
|
sessionCookieName,
|
|
@@ -359,6 +392,8 @@ var init_getSession = __esm({
|
|
|
359
392
|
code = "AUTH_TRANSPORT_ERROR";
|
|
360
393
|
};
|
|
361
394
|
__name(defaultBaseURL, "defaultBaseURL");
|
|
395
|
+
__name(firstForwardedValue, "firstForwardedValue");
|
|
396
|
+
__name(requestOriginFromHeaders, "requestOriginFromHeaders");
|
|
362
397
|
__name(getSession, "getSession");
|
|
363
398
|
}
|
|
364
399
|
});
|
|
@@ -611,6 +646,7 @@ function withAuthMiddleware(config) {
|
|
|
611
646
|
const endpoint = recoveryURL ? new URL(recoveryURL, request.url).toString() : authEndpoint(apiBaseURL, authPrefix, "/session/recover", request.url);
|
|
612
647
|
recovery = await requestSessionRecovery({
|
|
613
648
|
endpoint,
|
|
649
|
+
requestOrigin: url.origin,
|
|
614
650
|
refreshCookieName: cookieName,
|
|
615
651
|
refreshCookieValue: refreshCookie,
|
|
616
652
|
sessionCookieName,
|
|
@@ -1185,8 +1221,7 @@ function defineAuth(authConfig = {}) {
|
|
|
1185
1221
|
retry
|
|
1186
1222
|
} = authConfig;
|
|
1187
1223
|
const sessionConfig = {
|
|
1188
|
-
baseURL:
|
|
1189
|
-
// resolved at call time from env/defaults
|
|
1224
|
+
baseURL: apiBaseURL,
|
|
1190
1225
|
authPrefix,
|
|
1191
1226
|
cookieName,
|
|
1192
1227
|
sessionCookieName,
|
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.6",
|
|
4
4
|
"description": "Authentication and authorization library for najm framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"@testing-library/react": "^16.3.2",
|
|
68
68
|
"@types/jsonwebtoken": "^9.0.10",
|
|
69
69
|
"@types/node": "^25.0.2",
|
|
70
|
-
"drizzle-kit": "^0.31.
|
|
71
|
-
"drizzle-orm": "^0.45.
|
|
72
|
-
"happy-dom": "^
|
|
70
|
+
"drizzle-kit": "^0.31.10",
|
|
71
|
+
"drizzle-orm": "^0.45.2",
|
|
72
|
+
"happy-dom": "^20.11.1",
|
|
73
73
|
"tsup": "^8.5.1",
|
|
74
74
|
"typescript": "^5.9.3"
|
|
75
75
|
},
|
|
@@ -93,8 +93,8 @@
|
|
|
93
93
|
"zod": "^4.2.1"
|
|
94
94
|
},
|
|
95
95
|
"peerDependencies": {
|
|
96
|
-
"drizzle-orm": "^0.45.
|
|
97
|
-
"hono": "^4.
|
|
96
|
+
"drizzle-orm": "^0.45.2",
|
|
97
|
+
"hono": "^4.12.18",
|
|
98
98
|
"react": ">=18",
|
|
99
99
|
"next": ">=14"
|
|
100
100
|
},
|