najm-auth 2.0.2 → 2.0.4
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/dist/client/edge.d.ts +9 -6
- package/dist/client/edge.js +145 -26
- package/dist/client/server/index.d.ts +9 -3
- package/dist/client/server/index.js +184 -138
- package/dist/index.d.ts +2 -2
- package/dist/index.js +59 -25
- package/package.json +3 -3
package/dist/client/edge.d.ts
CHANGED
|
@@ -11,14 +11,17 @@ interface AuthMiddlewareConfig {
|
|
|
11
11
|
roleRoutes?: Record<string, string[]>;
|
|
12
12
|
/** Refresh token cookie name (default: 'refreshToken') */
|
|
13
13
|
cookieName?: string;
|
|
14
|
-
/**
|
|
14
|
+
/** Signed session cookie name (default: 'najm.session') */
|
|
15
15
|
sessionCookieName?: string;
|
|
16
|
-
/**
|
|
16
|
+
/** @deprecated Session cookies are verified locally at the Edge. */
|
|
17
17
|
verifyURL?: string;
|
|
18
|
+
/** Secret for verifying the session cookie HMAC. Falls back to env vars. */
|
|
19
|
+
sessionSecret?: string;
|
|
20
|
+
/** Must match the auth plugin's session.maxAge. Default: 300 seconds. */
|
|
21
|
+
sessionMaxAge?: number;
|
|
18
22
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* fetch per navigation — trade latency for stronger guarantees.
|
|
23
|
+
* Retained for compatibility. Every protected route now verifies the signed
|
|
24
|
+
* session cookie locally, so enabling this never calls `/auth/me`.
|
|
22
25
|
*/
|
|
23
26
|
verifyAlways?: boolean;
|
|
24
27
|
}
|
|
@@ -28,7 +31,7 @@ interface AuthMiddlewareConfig {
|
|
|
28
31
|
* @example
|
|
29
32
|
* ```ts
|
|
30
33
|
* // middleware.ts
|
|
31
|
-
|
|
34
|
+
* import { withAuthMiddleware } from 'najm-auth/client/edge';
|
|
32
35
|
*
|
|
33
36
|
* export default withAuthMiddleware({
|
|
34
37
|
* protectedRoutes: ['/dashboard/:path*', '/admin/:path*'],
|
package/dist/client/edge.js
CHANGED
|
@@ -1,6 +1,136 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
|
+
// src/client/sessionCookie.ts
|
|
5
|
+
var DEFAULT_SESSION_MAX_AGE_SECONDS = 300;
|
|
6
|
+
var MAX_CLOCK_SKEW_MS = 3e4;
|
|
7
|
+
var HMAC_SHA256_BASE64URL_LENGTH = 43;
|
|
8
|
+
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
9
|
+
function resolveSessionSecret(explicit) {
|
|
10
|
+
if (explicit !== void 0) return explicit || void 0;
|
|
11
|
+
if (typeof process === "undefined") return void 0;
|
|
12
|
+
return process.env.NAJM_SESSION_SECRET || process.env.JWT_ACCESS_SECRET || void 0;
|
|
13
|
+
}
|
|
14
|
+
__name(resolveSessionSecret, "resolveSessionSecret");
|
|
15
|
+
function parseSessionCookiePayload(payload, maxAgeSeconds = DEFAULT_SESSION_MAX_AGE_SECONDS, now = Date.now()) {
|
|
16
|
+
if (!Number.isFinite(maxAgeSeconds) || maxAgeSeconds <= 0) return null;
|
|
17
|
+
try {
|
|
18
|
+
const data = JSON.parse(payload);
|
|
19
|
+
if (!isRecord(data) || !isValidUser(data.user)) return null;
|
|
20
|
+
if (!isStringArray(data.roles) || !isStringArray(data.permissions)) return null;
|
|
21
|
+
if (!Number.isInteger(data.sessionVersion) || data.sessionVersion < 0) return null;
|
|
22
|
+
if (!Number.isFinite(data.iat) || !Number.isInteger(data.iat) || data.iat <= 0) return null;
|
|
23
|
+
const issuedAt = data.iat;
|
|
24
|
+
if (issuedAt > now + MAX_CLOCK_SKEW_MS) return null;
|
|
25
|
+
if (now - issuedAt >= maxAgeSeconds * 1e3) return null;
|
|
26
|
+
return {
|
|
27
|
+
user: data.user,
|
|
28
|
+
roles: [...data.roles],
|
|
29
|
+
permissions: [...data.permissions],
|
|
30
|
+
sessionVersion: data.sessionVersion,
|
|
31
|
+
iat: issuedAt
|
|
32
|
+
};
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
__name(parseSessionCookiePayload, "parseSessionCookiePayload");
|
|
38
|
+
async function verifySessionCookie(rawCookieValue, options) {
|
|
39
|
+
if (!rawCookieValue || !options.secret || !globalThis.crypto?.subtle) return null;
|
|
40
|
+
for (const signedValue of cookieValueCandidates(rawCookieValue)) {
|
|
41
|
+
const lastDot = signedValue.lastIndexOf(".");
|
|
42
|
+
if (lastDot <= 0 || lastDot === signedValue.length - 1) continue;
|
|
43
|
+
const payload = signedValue.slice(0, lastDot);
|
|
44
|
+
const signature = signedValue.slice(lastDot + 1);
|
|
45
|
+
if (!await verifyHmac(payload, signature, options.secret)) continue;
|
|
46
|
+
return parseSessionCookiePayload(
|
|
47
|
+
payload,
|
|
48
|
+
options.maxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
49
|
+
options.now
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
__name(verifySessionCookie, "verifySessionCookie");
|
|
55
|
+
function readCookieValue(cookieHeader, name) {
|
|
56
|
+
for (const part of cookieHeader.split(";")) {
|
|
57
|
+
const separator = part.indexOf("=");
|
|
58
|
+
if (separator === -1) continue;
|
|
59
|
+
if (part.slice(0, separator).trim() === name) {
|
|
60
|
+
const value = part.slice(separator + 1).trim();
|
|
61
|
+
return value || void 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
__name(readCookieValue, "readCookieValue");
|
|
67
|
+
async function verifyHmac(payload, signature, secret) {
|
|
68
|
+
if (signature.length !== HMAC_SHA256_BASE64URL_LENGTH) return false;
|
|
69
|
+
const signatureBytes = decodeBase64Url(signature);
|
|
70
|
+
if (!signatureBytes || signatureBytes.length !== 32) return false;
|
|
71
|
+
try {
|
|
72
|
+
const encoder = new TextEncoder();
|
|
73
|
+
const key = await globalThis.crypto.subtle.importKey(
|
|
74
|
+
"raw",
|
|
75
|
+
encoder.encode(secret),
|
|
76
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
77
|
+
false,
|
|
78
|
+
["verify"]
|
|
79
|
+
);
|
|
80
|
+
return await globalThis.crypto.subtle.verify(
|
|
81
|
+
"HMAC",
|
|
82
|
+
key,
|
|
83
|
+
signatureBytes,
|
|
84
|
+
encoder.encode(payload)
|
|
85
|
+
);
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
__name(verifyHmac, "verifyHmac");
|
|
91
|
+
function cookieValueCandidates(raw) {
|
|
92
|
+
const candidates = [raw];
|
|
93
|
+
try {
|
|
94
|
+
const decoded = decodeURIComponent(raw);
|
|
95
|
+
if (decoded !== raw) candidates.push(decoded);
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
return candidates;
|
|
99
|
+
}
|
|
100
|
+
__name(cookieValueCandidates, "cookieValueCandidates");
|
|
101
|
+
function decodeBase64Url(value) {
|
|
102
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) return null;
|
|
103
|
+
const bytes = [];
|
|
104
|
+
let accumulator = 0;
|
|
105
|
+
let bits = 0;
|
|
106
|
+
for (const char of value) {
|
|
107
|
+
const digit = BASE64URL_ALPHABET.indexOf(char);
|
|
108
|
+
if (digit === -1) return null;
|
|
109
|
+
accumulator = accumulator << 6 | digit;
|
|
110
|
+
bits += 6;
|
|
111
|
+
if (bits >= 8) {
|
|
112
|
+
bits -= 8;
|
|
113
|
+
bytes.push(accumulator >> bits & 255);
|
|
114
|
+
accumulator &= (1 << bits) - 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (bits > 0 && accumulator !== 0) return null;
|
|
118
|
+
return new Uint8Array(bytes);
|
|
119
|
+
}
|
|
120
|
+
__name(decodeBase64Url, "decodeBase64Url");
|
|
121
|
+
function isRecord(value) {
|
|
122
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
123
|
+
}
|
|
124
|
+
__name(isRecord, "isRecord");
|
|
125
|
+
function isValidUser(value) {
|
|
126
|
+
return isRecord(value) && typeof value.id === "string" && value.id.length > 0 && typeof value.email === "string" && value.email.length > 0 && (value.role === void 0 || value.role === null || typeof value.role === "string");
|
|
127
|
+
}
|
|
128
|
+
__name(isValidUser, "isValidUser");
|
|
129
|
+
function isStringArray(value) {
|
|
130
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
131
|
+
}
|
|
132
|
+
__name(isStringArray, "isStringArray");
|
|
133
|
+
|
|
4
134
|
// src/client/server/withAuthMiddleware.ts
|
|
5
135
|
function withAuthMiddleware(config) {
|
|
6
136
|
const {
|
|
@@ -10,8 +140,11 @@ function withAuthMiddleware(config) {
|
|
|
10
140
|
roleRoutes = {},
|
|
11
141
|
cookieName = "refreshToken",
|
|
12
142
|
sessionCookieName = "najm.session",
|
|
143
|
+
sessionSecret,
|
|
144
|
+
sessionMaxAge,
|
|
13
145
|
verifyAlways = false
|
|
14
146
|
} = config;
|
|
147
|
+
void verifyAlways;
|
|
15
148
|
return /* @__PURE__ */ __name(async function middleware(request) {
|
|
16
149
|
const { NextResponse } = await import("next/server");
|
|
17
150
|
const redirectToLogin = /* @__PURE__ */ __name((pathname2, clearCookies) => {
|
|
@@ -32,31 +165,21 @@ function withAuthMiddleware(config) {
|
|
|
32
165
|
const isProtected = protectedRoutes.length === 0 || matchesAny(pathname, protectedRoutes);
|
|
33
166
|
if (!isProtected) return NextResponse.next();
|
|
34
167
|
const cookie = request.headers.get("cookie") ?? "";
|
|
35
|
-
const
|
|
36
|
-
|
|
168
|
+
const sessionCookie = readCookieValue(cookie, sessionCookieName);
|
|
169
|
+
const secret = resolveSessionSecret(sessionSecret);
|
|
170
|
+
if (!sessionCookie || !secret) {
|
|
171
|
+
return redirectToLogin(pathname, true);
|
|
172
|
+
}
|
|
173
|
+
const session = await verifySessionCookie(sessionCookie, {
|
|
174
|
+
secret,
|
|
175
|
+
maxAgeSeconds: sessionMaxAge
|
|
176
|
+
});
|
|
177
|
+
if (!session) {
|
|
37
178
|
return redirectToLogin(pathname, true);
|
|
38
179
|
}
|
|
39
180
|
const requiredRoles = findMatchingRoles(pathname, roleRoutes);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const verifyURL = config.verifyURL ?? `${url.origin}/api/auth/me`;
|
|
43
|
-
try {
|
|
44
|
-
const res = await fetch(verifyURL, {
|
|
45
|
-
headers: { "Cookie": cookie, "Accept": "application/json" }
|
|
46
|
-
});
|
|
47
|
-
if (!res.ok) {
|
|
48
|
-
return redirectToLogin(pathname, true);
|
|
49
|
-
}
|
|
50
|
-
if (requiredRoles) {
|
|
51
|
-
const body = await res.json();
|
|
52
|
-
const userRole = body?.data?.role;
|
|
53
|
-
if (!userRole || !requiredRoles.includes(userRole)) {
|
|
54
|
-
return new NextResponse(null, { status: 403 });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
} catch {
|
|
58
|
-
return redirectToLogin(pathname, true);
|
|
59
|
-
}
|
|
181
|
+
if (requiredRoles && !session.roles.some((role) => requiredRoles.includes(role))) {
|
|
182
|
+
return new NextResponse(null, { status: 403 });
|
|
60
183
|
}
|
|
61
184
|
return NextResponse.next();
|
|
62
185
|
}, "middleware");
|
|
@@ -72,10 +195,6 @@ function matchPattern(pathname, pattern) {
|
|
|
72
195
|
return new RegExp(`^${regex}$`).test(pathname);
|
|
73
196
|
}
|
|
74
197
|
__name(matchPattern, "matchPattern");
|
|
75
|
-
function cookieRegex(name) {
|
|
76
|
-
return new RegExp(`(?:^|;\\s*)${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=[^;]`);
|
|
77
|
-
}
|
|
78
|
-
__name(cookieRegex, "cookieRegex");
|
|
79
198
|
function findMatchingRoles(pathname, roleRoutes) {
|
|
80
199
|
for (const [pattern, roles] of Object.entries(roleRoutes)) {
|
|
81
200
|
if (matchPattern(pathname, pattern)) return roles;
|
|
@@ -96,6 +96,11 @@ interface GetSessionConfig {
|
|
|
96
96
|
* Falls back to NAJM_SESSION_SECRET or JWT_ACCESS_SECRET env vars.
|
|
97
97
|
*/
|
|
98
98
|
sessionSecret?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Maximum accepted session-cookie age in seconds.
|
|
101
|
+
* Must match the auth plugin's `session.maxAge`. Default: 300.
|
|
102
|
+
*/
|
|
103
|
+
sessionMaxAge?: number;
|
|
99
104
|
/**
|
|
100
105
|
* Error handling mode:
|
|
101
106
|
* - 'nullable' (default): returns null on any failure
|
|
@@ -193,12 +198,13 @@ interface DefineAuthConfig {
|
|
|
193
198
|
sessionCookieName?: string;
|
|
194
199
|
/** Secret for verifying session cookie HMAC. Falls back to env vars. */
|
|
195
200
|
sessionSecret?: string;
|
|
201
|
+
/** Must match the auth plugin's session.maxAge. Default: 300 seconds. */
|
|
202
|
+
sessionMaxAge?: number;
|
|
196
203
|
/** Next.js middleware matcher (default: exclude _next, favicon, api) */
|
|
197
204
|
matcher?: string[];
|
|
198
205
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* fetch per navigation in exchange for guaranteed fresh auth state.
|
|
206
|
+
* Retained for compatibility. Every protected route verifies the signed
|
|
207
|
+
* session cookie locally without an `/auth/me` request.
|
|
202
208
|
*/
|
|
203
209
|
verifyAlways?: boolean;
|
|
204
210
|
}
|
|
@@ -9,6 +9,141 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/client/sessionCookie.ts
|
|
13
|
+
function resolveSessionSecret(explicit) {
|
|
14
|
+
if (explicit !== void 0) return explicit || void 0;
|
|
15
|
+
if (typeof process === "undefined") return void 0;
|
|
16
|
+
return process.env.NAJM_SESSION_SECRET || process.env.JWT_ACCESS_SECRET || void 0;
|
|
17
|
+
}
|
|
18
|
+
function parseSessionCookiePayload(payload, maxAgeSeconds = DEFAULT_SESSION_MAX_AGE_SECONDS, now = Date.now()) {
|
|
19
|
+
if (!Number.isFinite(maxAgeSeconds) || maxAgeSeconds <= 0) return null;
|
|
20
|
+
try {
|
|
21
|
+
const data = JSON.parse(payload);
|
|
22
|
+
if (!isRecord(data) || !isValidUser(data.user)) return null;
|
|
23
|
+
if (!isStringArray(data.roles) || !isStringArray(data.permissions)) return null;
|
|
24
|
+
if (!Number.isInteger(data.sessionVersion) || data.sessionVersion < 0) return null;
|
|
25
|
+
if (!Number.isFinite(data.iat) || !Number.isInteger(data.iat) || data.iat <= 0) return null;
|
|
26
|
+
const issuedAt = data.iat;
|
|
27
|
+
if (issuedAt > now + MAX_CLOCK_SKEW_MS) return null;
|
|
28
|
+
if (now - issuedAt >= maxAgeSeconds * 1e3) return null;
|
|
29
|
+
return {
|
|
30
|
+
user: data.user,
|
|
31
|
+
roles: [...data.roles],
|
|
32
|
+
permissions: [...data.permissions],
|
|
33
|
+
sessionVersion: data.sessionVersion,
|
|
34
|
+
iat: issuedAt
|
|
35
|
+
};
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function verifySessionCookie(rawCookieValue, options) {
|
|
41
|
+
if (!rawCookieValue || !options.secret || !globalThis.crypto?.subtle) return null;
|
|
42
|
+
for (const signedValue of cookieValueCandidates(rawCookieValue)) {
|
|
43
|
+
const lastDot = signedValue.lastIndexOf(".");
|
|
44
|
+
if (lastDot <= 0 || lastDot === signedValue.length - 1) continue;
|
|
45
|
+
const payload = signedValue.slice(0, lastDot);
|
|
46
|
+
const signature = signedValue.slice(lastDot + 1);
|
|
47
|
+
if (!await verifyHmac(payload, signature, options.secret)) continue;
|
|
48
|
+
return parseSessionCookiePayload(
|
|
49
|
+
payload,
|
|
50
|
+
options.maxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
|
|
51
|
+
options.now
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function readCookieValue(cookieHeader, name) {
|
|
57
|
+
for (const part of cookieHeader.split(";")) {
|
|
58
|
+
const separator = part.indexOf("=");
|
|
59
|
+
if (separator === -1) continue;
|
|
60
|
+
if (part.slice(0, separator).trim() === name) {
|
|
61
|
+
const value = part.slice(separator + 1).trim();
|
|
62
|
+
return value || void 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
async function verifyHmac(payload, signature, secret) {
|
|
68
|
+
if (signature.length !== HMAC_SHA256_BASE64URL_LENGTH) return false;
|
|
69
|
+
const signatureBytes = decodeBase64Url(signature);
|
|
70
|
+
if (!signatureBytes || signatureBytes.length !== 32) return false;
|
|
71
|
+
try {
|
|
72
|
+
const encoder = new TextEncoder();
|
|
73
|
+
const key = await globalThis.crypto.subtle.importKey(
|
|
74
|
+
"raw",
|
|
75
|
+
encoder.encode(secret),
|
|
76
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
77
|
+
false,
|
|
78
|
+
["verify"]
|
|
79
|
+
);
|
|
80
|
+
return await globalThis.crypto.subtle.verify(
|
|
81
|
+
"HMAC",
|
|
82
|
+
key,
|
|
83
|
+
signatureBytes,
|
|
84
|
+
encoder.encode(payload)
|
|
85
|
+
);
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function cookieValueCandidates(raw) {
|
|
91
|
+
const candidates = [raw];
|
|
92
|
+
try {
|
|
93
|
+
const decoded = decodeURIComponent(raw);
|
|
94
|
+
if (decoded !== raw) candidates.push(decoded);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
return candidates;
|
|
98
|
+
}
|
|
99
|
+
function decodeBase64Url(value) {
|
|
100
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) return null;
|
|
101
|
+
const bytes = [];
|
|
102
|
+
let accumulator = 0;
|
|
103
|
+
let bits = 0;
|
|
104
|
+
for (const char of value) {
|
|
105
|
+
const digit = BASE64URL_ALPHABET.indexOf(char);
|
|
106
|
+
if (digit === -1) return null;
|
|
107
|
+
accumulator = accumulator << 6 | digit;
|
|
108
|
+
bits += 6;
|
|
109
|
+
if (bits >= 8) {
|
|
110
|
+
bits -= 8;
|
|
111
|
+
bytes.push(accumulator >> bits & 255);
|
|
112
|
+
accumulator &= (1 << bits) - 1;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (bits > 0 && accumulator !== 0) return null;
|
|
116
|
+
return new Uint8Array(bytes);
|
|
117
|
+
}
|
|
118
|
+
function isRecord(value) {
|
|
119
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
120
|
+
}
|
|
121
|
+
function isValidUser(value) {
|
|
122
|
+
return isRecord(value) && typeof value.id === "string" && value.id.length > 0 && typeof value.email === "string" && value.email.length > 0 && (value.role === void 0 || value.role === null || typeof value.role === "string");
|
|
123
|
+
}
|
|
124
|
+
function isStringArray(value) {
|
|
125
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
126
|
+
}
|
|
127
|
+
var DEFAULT_SESSION_MAX_AGE_SECONDS, MAX_CLOCK_SKEW_MS, HMAC_SHA256_BASE64URL_LENGTH, BASE64URL_ALPHABET;
|
|
128
|
+
var init_sessionCookie = __esm({
|
|
129
|
+
"src/client/sessionCookie.ts"() {
|
|
130
|
+
DEFAULT_SESSION_MAX_AGE_SECONDS = 300;
|
|
131
|
+
MAX_CLOCK_SKEW_MS = 3e4;
|
|
132
|
+
HMAC_SHA256_BASE64URL_LENGTH = 43;
|
|
133
|
+
BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
134
|
+
__name(resolveSessionSecret, "resolveSessionSecret");
|
|
135
|
+
__name(parseSessionCookiePayload, "parseSessionCookiePayload");
|
|
136
|
+
__name(verifySessionCookie, "verifySessionCookie");
|
|
137
|
+
__name(readCookieValue, "readCookieValue");
|
|
138
|
+
__name(verifyHmac, "verifyHmac");
|
|
139
|
+
__name(cookieValueCandidates, "cookieValueCandidates");
|
|
140
|
+
__name(decodeBase64Url, "decodeBase64Url");
|
|
141
|
+
__name(isRecord, "isRecord");
|
|
142
|
+
__name(isValidUser, "isValidUser");
|
|
143
|
+
__name(isStringArray, "isStringArray");
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
12
147
|
// src/client/server/getSession.ts
|
|
13
148
|
var getSession_exports = {};
|
|
14
149
|
__export(getSession_exports, {
|
|
@@ -17,45 +152,12 @@ __export(getSession_exports, {
|
|
|
17
152
|
NoSessionError: () => NoSessionError,
|
|
18
153
|
getSession: () => getSession
|
|
19
154
|
});
|
|
20
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
21
155
|
function defaultBaseURL() {
|
|
22
156
|
const explicit = typeof process !== "undefined" ? process.env.NAJM_AUTH_BASE_URL : void 0;
|
|
23
157
|
if (explicit) return explicit;
|
|
24
158
|
const origin = typeof process !== "undefined" ? process.env.NEXT_PUBLIC_API_URL ?? `http://localhost:${process.env.PORT ?? 3e3}` : "http://localhost:3000";
|
|
25
159
|
return `${origin.replace(/\/$/, "")}/api`;
|
|
26
160
|
}
|
|
27
|
-
function getSessionSecret(config) {
|
|
28
|
-
return config.sessionSecret ?? (typeof process !== "undefined" ? process.env.NAJM_SESSION_SECRET : void 0) ?? (typeof process !== "undefined" ? process.env.JWT_ACCESS_SECRET : void 0);
|
|
29
|
-
}
|
|
30
|
-
function verifyHmac(payload, signature, secret) {
|
|
31
|
-
const expected = createHmac("sha256", secret).update(payload).digest("base64url");
|
|
32
|
-
if (expected.length !== signature.length) return false;
|
|
33
|
-
try {
|
|
34
|
-
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
|
|
35
|
-
} catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function parseSessionCookie(raw, secret) {
|
|
40
|
-
const lastDot = raw.lastIndexOf(".");
|
|
41
|
-
if (lastDot === -1) return null;
|
|
42
|
-
const payload = raw.substring(0, lastDot);
|
|
43
|
-
const signature = raw.substring(lastDot + 1);
|
|
44
|
-
if (!verifyHmac(payload, signature, secret)) return null;
|
|
45
|
-
try {
|
|
46
|
-
const decoded = decodeURIComponent(payload);
|
|
47
|
-
const data = JSON.parse(decoded);
|
|
48
|
-
if (Date.now() - data.iat > SESSION_COOKIE_MAX_AGE_MS) return null;
|
|
49
|
-
const { user, roles, permissions } = data;
|
|
50
|
-
return {
|
|
51
|
-
user,
|
|
52
|
-
roles: roles ?? (user.role ? [user.role] : void 0),
|
|
53
|
-
permissions: permissions ?? void 0
|
|
54
|
-
};
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
161
|
function buildSession(body) {
|
|
60
162
|
if (!body?.data) return null;
|
|
61
163
|
const { roles, permissions, ...user } = body.data;
|
|
@@ -88,11 +190,24 @@ async function getSession(config = {}) {
|
|
|
88
190
|
return null;
|
|
89
191
|
}
|
|
90
192
|
if (sessionCookieValue) {
|
|
91
|
-
const secret =
|
|
92
|
-
if (secret) {
|
|
93
|
-
|
|
94
|
-
|
|
193
|
+
const secret = resolveSessionSecret(config.sessionSecret);
|
|
194
|
+
if (!secret) {
|
|
195
|
+
if (strict) throw new AuthConfigError("Session cookie secret is not configured");
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const claims = await verifySessionCookie(sessionCookieValue, {
|
|
199
|
+
secret,
|
|
200
|
+
maxAgeSeconds: config.sessionMaxAge
|
|
201
|
+
});
|
|
202
|
+
if (!claims) {
|
|
203
|
+
if (strict) throw new NoSessionError("Invalid or expired session cookie");
|
|
204
|
+
return null;
|
|
95
205
|
}
|
|
206
|
+
return {
|
|
207
|
+
user: claims.user,
|
|
208
|
+
roles: claims.roles,
|
|
209
|
+
permissions: claims.permissions
|
|
210
|
+
};
|
|
96
211
|
}
|
|
97
212
|
if (!hasRefreshCookie) {
|
|
98
213
|
if (strict) throw new NoSessionError("No refresh token cookie");
|
|
@@ -121,9 +236,10 @@ async function getSession(config = {}) {
|
|
|
121
236
|
return null;
|
|
122
237
|
}
|
|
123
238
|
}
|
|
124
|
-
var NoSessionError, AuthConfigError, AuthTransportError
|
|
239
|
+
var NoSessionError, AuthConfigError, AuthTransportError;
|
|
125
240
|
var init_getSession = __esm({
|
|
126
241
|
"src/client/server/getSession.ts"() {
|
|
242
|
+
init_sessionCookie();
|
|
127
243
|
NoSessionError = class extends Error {
|
|
128
244
|
static {
|
|
129
245
|
__name(this, "NoSessionError");
|
|
@@ -156,10 +272,6 @@ var init_getSession = __esm({
|
|
|
156
272
|
code = "AUTH_TRANSPORT_ERROR";
|
|
157
273
|
};
|
|
158
274
|
__name(defaultBaseURL, "defaultBaseURL");
|
|
159
|
-
__name(getSessionSecret, "getSessionSecret");
|
|
160
|
-
__name(verifyHmac, "verifyHmac");
|
|
161
|
-
SESSION_COOKIE_MAX_AGE_MS = 3e5;
|
|
162
|
-
__name(parseSessionCookie, "parseSessionCookie");
|
|
163
275
|
__name(buildSession, "buildSession");
|
|
164
276
|
__name(getSession, "getSession");
|
|
165
277
|
}
|
|
@@ -355,6 +467,7 @@ function createServerClient(config) {
|
|
|
355
467
|
__name(createServerClient, "createServerClient");
|
|
356
468
|
|
|
357
469
|
// src/client/server/withAuthMiddleware.ts
|
|
470
|
+
init_sessionCookie();
|
|
358
471
|
function withAuthMiddleware(config) {
|
|
359
472
|
const {
|
|
360
473
|
protectedRoutes = [],
|
|
@@ -363,8 +476,11 @@ function withAuthMiddleware(config) {
|
|
|
363
476
|
roleRoutes = {},
|
|
364
477
|
cookieName = "refreshToken",
|
|
365
478
|
sessionCookieName = "najm.session",
|
|
479
|
+
sessionSecret,
|
|
480
|
+
sessionMaxAge,
|
|
366
481
|
verifyAlways = false
|
|
367
482
|
} = config;
|
|
483
|
+
void verifyAlways;
|
|
368
484
|
return /* @__PURE__ */ __name(async function middleware(request) {
|
|
369
485
|
const { NextResponse } = await import("next/server");
|
|
370
486
|
const redirectToLogin = /* @__PURE__ */ __name((pathname2, clearCookies) => {
|
|
@@ -385,31 +501,21 @@ function withAuthMiddleware(config) {
|
|
|
385
501
|
const isProtected = protectedRoutes.length === 0 || matchesAny(pathname, protectedRoutes);
|
|
386
502
|
if (!isProtected) return NextResponse.next();
|
|
387
503
|
const cookie = request.headers.get("cookie") ?? "";
|
|
388
|
-
const
|
|
389
|
-
|
|
504
|
+
const sessionCookie = readCookieValue(cookie, sessionCookieName);
|
|
505
|
+
const secret = resolveSessionSecret(sessionSecret);
|
|
506
|
+
if (!sessionCookie || !secret) {
|
|
507
|
+
return redirectToLogin(pathname, true);
|
|
508
|
+
}
|
|
509
|
+
const session = await verifySessionCookie(sessionCookie, {
|
|
510
|
+
secret,
|
|
511
|
+
maxAgeSeconds: sessionMaxAge
|
|
512
|
+
});
|
|
513
|
+
if (!session) {
|
|
390
514
|
return redirectToLogin(pathname, true);
|
|
391
515
|
}
|
|
392
516
|
const requiredRoles = findMatchingRoles(pathname, roleRoutes);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const verifyURL = config.verifyURL ?? `${url.origin}/api/auth/me`;
|
|
396
|
-
try {
|
|
397
|
-
const res = await fetch(verifyURL, {
|
|
398
|
-
headers: { "Cookie": cookie, "Accept": "application/json" }
|
|
399
|
-
});
|
|
400
|
-
if (!res.ok) {
|
|
401
|
-
return redirectToLogin(pathname, true);
|
|
402
|
-
}
|
|
403
|
-
if (requiredRoles) {
|
|
404
|
-
const body = await res.json();
|
|
405
|
-
const userRole = body?.data?.role;
|
|
406
|
-
if (!userRole || !requiredRoles.includes(userRole)) {
|
|
407
|
-
return new NextResponse(null, { status: 403 });
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
} catch {
|
|
411
|
-
return redirectToLogin(pathname, true);
|
|
412
|
-
}
|
|
517
|
+
if (requiredRoles && !session.roles.some((role) => requiredRoles.includes(role))) {
|
|
518
|
+
return new NextResponse(null, { status: 403 });
|
|
413
519
|
}
|
|
414
520
|
return NextResponse.next();
|
|
415
521
|
}, "middleware");
|
|
@@ -425,10 +531,6 @@ function matchPattern(pathname, pattern) {
|
|
|
425
531
|
return new RegExp(`^${regex}$`).test(pathname);
|
|
426
532
|
}
|
|
427
533
|
__name(matchPattern, "matchPattern");
|
|
428
|
-
function cookieRegex(name) {
|
|
429
|
-
return new RegExp(`(?:^|;\\s*)${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=[^;]`);
|
|
430
|
-
}
|
|
431
|
-
__name(cookieRegex, "cookieRegex");
|
|
432
534
|
function findMatchingRoles(pathname, roleRoutes) {
|
|
433
535
|
for (const [pattern, roles] of Object.entries(roleRoutes)) {
|
|
434
536
|
if (matchPattern(pathname, pattern)) return roles;
|
|
@@ -935,27 +1037,6 @@ function createAuthClient(config) {
|
|
|
935
1037
|
__name(createAuthClient, "createAuthClient");
|
|
936
1038
|
|
|
937
1039
|
// src/client/server/defineAuth.ts
|
|
938
|
-
function matchesAny2(pathname, patterns) {
|
|
939
|
-
return patterns.some((p) => matchPattern2(pathname, p));
|
|
940
|
-
}
|
|
941
|
-
__name(matchesAny2, "matchesAny");
|
|
942
|
-
function matchPattern2(pathname, pattern) {
|
|
943
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
944
|
-
const regex = escaped.replace(/\/:[^/]+\*/g, "(?:/.*)?").replace(/\/\\\*$/g, "(?:/.*)?").replace(/\\\*/g, "(?:/.*)?").replace(/\//g, "\\/");
|
|
945
|
-
return new RegExp(`^${regex}$`).test(pathname);
|
|
946
|
-
}
|
|
947
|
-
__name(matchPattern2, "matchPattern");
|
|
948
|
-
function cookieRegex2(name) {
|
|
949
|
-
return new RegExp(`(?:^|;\\s*)${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}=[^;]`);
|
|
950
|
-
}
|
|
951
|
-
__name(cookieRegex2, "cookieRegex");
|
|
952
|
-
function findMatchingRoles2(pathname, roleRoutes) {
|
|
953
|
-
for (const [pattern, roles] of Object.entries(roleRoutes)) {
|
|
954
|
-
if (matchPattern2(pathname, pattern)) return roles;
|
|
955
|
-
}
|
|
956
|
-
return null;
|
|
957
|
-
}
|
|
958
|
-
__name(findMatchingRoles2, "findMatchingRoles");
|
|
959
1040
|
function defineAuth(authConfig = {}) {
|
|
960
1041
|
const {
|
|
961
1042
|
apiBaseURL = "/api",
|
|
@@ -967,6 +1048,7 @@ function defineAuth(authConfig = {}) {
|
|
|
967
1048
|
cookieName = "refreshToken",
|
|
968
1049
|
sessionCookieName = "najm.session",
|
|
969
1050
|
sessionSecret,
|
|
1051
|
+
sessionMaxAge,
|
|
970
1052
|
matcher = ["/((?!_next/static|_next/image|favicon.ico|api).*)"],
|
|
971
1053
|
verifyAlways = false,
|
|
972
1054
|
refreshThreshold,
|
|
@@ -981,7 +1063,8 @@ function defineAuth(authConfig = {}) {
|
|
|
981
1063
|
authPrefix,
|
|
982
1064
|
cookieName,
|
|
983
1065
|
sessionCookieName,
|
|
984
|
-
sessionSecret
|
|
1066
|
+
sessionSecret,
|
|
1067
|
+
sessionMaxAge
|
|
985
1068
|
};
|
|
986
1069
|
let _client = null;
|
|
987
1070
|
const getClient = /* @__PURE__ */ __name(() => {
|
|
@@ -1023,54 +1106,17 @@ function defineAuth(authConfig = {}) {
|
|
|
1023
1106
|
throw err;
|
|
1024
1107
|
}
|
|
1025
1108
|
}, "requireSession");
|
|
1026
|
-
const middleware =
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
}
|
|
1038
|
-
return res;
|
|
1039
|
-
}, "redirectToLogin");
|
|
1040
|
-
if (matchesAny2(pathname, publicRoutes)) {
|
|
1041
|
-
return NextResponse.next();
|
|
1042
|
-
}
|
|
1043
|
-
const isProtected = protectedRoutes.length === 0 || matchesAny2(pathname, protectedRoutes);
|
|
1044
|
-
if (!isProtected) return NextResponse.next();
|
|
1045
|
-
const cookie = request.headers.get("cookie") ?? "";
|
|
1046
|
-
const hasToken = cookieRegex2(cookieName).test(cookie);
|
|
1047
|
-
if (!hasToken) {
|
|
1048
|
-
return redirectToLogin(true);
|
|
1049
|
-
}
|
|
1050
|
-
const requiredRoles = findMatchingRoles2(pathname, roleRoutes);
|
|
1051
|
-
const needsVerify = verifyAlways || !!requiredRoles;
|
|
1052
|
-
if (needsVerify) {
|
|
1053
|
-
const verifyURL = `${url.origin}${apiBaseURL}${authPrefix}/me`;
|
|
1054
|
-
try {
|
|
1055
|
-
const res = await fetch(verifyURL, {
|
|
1056
|
-
headers: { Cookie: cookie, Accept: "application/json" }
|
|
1057
|
-
});
|
|
1058
|
-
if (!res.ok) {
|
|
1059
|
-
return redirectToLogin(true);
|
|
1060
|
-
}
|
|
1061
|
-
if (requiredRoles) {
|
|
1062
|
-
const body = await res.json();
|
|
1063
|
-
const userRole = body?.data?.role;
|
|
1064
|
-
if (!userRole || !requiredRoles.includes(userRole)) {
|
|
1065
|
-
return new NextResponse(null, { status: 403 });
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
} catch {
|
|
1069
|
-
return redirectToLogin(true);
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
return NextResponse.next();
|
|
1073
|
-
}, "middleware");
|
|
1109
|
+
const middleware = withAuthMiddleware({
|
|
1110
|
+
protectedRoutes,
|
|
1111
|
+
publicRoutes,
|
|
1112
|
+
loginRoute,
|
|
1113
|
+
roleRoutes,
|
|
1114
|
+
cookieName,
|
|
1115
|
+
sessionCookieName,
|
|
1116
|
+
sessionSecret,
|
|
1117
|
+
sessionMaxAge,
|
|
1118
|
+
verifyAlways
|
|
1119
|
+
});
|
|
1074
1120
|
const protect = /* @__PURE__ */ __name((Page, options) => {
|
|
1075
1121
|
return /* @__PURE__ */ __name(async function ProtectedPage(props) {
|
|
1076
1122
|
const session = await getSession2();
|
package/dist/index.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ interface SessionCookieConfig {
|
|
|
42
42
|
name: string;
|
|
43
43
|
/** Max age in seconds (default: 300 = 5 min) */
|
|
44
44
|
maxAge: number;
|
|
45
|
-
/**
|
|
45
|
+
/** HMAC secret. Falls back to NAJM_SESSION_SECRET, then jwt.accessSecret. */
|
|
46
46
|
secret?: string;
|
|
47
47
|
}
|
|
48
48
|
type OAuthProvider = 'google';
|
|
@@ -518,7 +518,7 @@ declare class CookieManager {
|
|
|
518
518
|
getCookieName(): string;
|
|
519
519
|
/**
|
|
520
520
|
* Write a signed session cookie containing user data, roles, and permissions.
|
|
521
|
-
* The cookie is HMAC-signed with the
|
|
521
|
+
* The cookie is HMAC-signed with the configured session secret so it is tamper-proof
|
|
522
522
|
* but readable without a database query. Short TTL (5 min) ensures freshness.
|
|
523
523
|
*/
|
|
524
524
|
setSessionCookie(data: Omit<SessionCookieData, 'iat'>): void;
|
package/dist/index.js
CHANGED
|
@@ -293,6 +293,47 @@ EncryptionService = __decorate([
|
|
|
293
293
|
import { Service, Inject as Inject2 } from "najm-core";
|
|
294
294
|
import { CookieService } from "najm-cookies";
|
|
295
295
|
import timestring from "timestring";
|
|
296
|
+
|
|
297
|
+
// src/client/sessionCookie.ts
|
|
298
|
+
var DEFAULT_SESSION_MAX_AGE_SECONDS = 300;
|
|
299
|
+
var MAX_CLOCK_SKEW_MS = 3e4;
|
|
300
|
+
function parseSessionCookiePayload(payload, maxAgeSeconds = DEFAULT_SESSION_MAX_AGE_SECONDS, now = Date.now()) {
|
|
301
|
+
if (!Number.isFinite(maxAgeSeconds) || maxAgeSeconds <= 0) return null;
|
|
302
|
+
try {
|
|
303
|
+
const data = JSON.parse(payload);
|
|
304
|
+
if (!isRecord(data) || !isValidUser(data.user)) return null;
|
|
305
|
+
if (!isStringArray(data.roles) || !isStringArray(data.permissions)) return null;
|
|
306
|
+
if (!Number.isInteger(data.sessionVersion) || data.sessionVersion < 0) return null;
|
|
307
|
+
if (!Number.isFinite(data.iat) || !Number.isInteger(data.iat) || data.iat <= 0) return null;
|
|
308
|
+
const issuedAt = data.iat;
|
|
309
|
+
if (issuedAt > now + MAX_CLOCK_SKEW_MS) return null;
|
|
310
|
+
if (now - issuedAt >= maxAgeSeconds * 1e3) return null;
|
|
311
|
+
return {
|
|
312
|
+
user: data.user,
|
|
313
|
+
roles: [...data.roles],
|
|
314
|
+
permissions: [...data.permissions],
|
|
315
|
+
sessionVersion: data.sessionVersion,
|
|
316
|
+
iat: issuedAt
|
|
317
|
+
};
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
__name(parseSessionCookiePayload, "parseSessionCookiePayload");
|
|
323
|
+
function isRecord(value) {
|
|
324
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
325
|
+
}
|
|
326
|
+
__name(isRecord, "isRecord");
|
|
327
|
+
function isValidUser(value) {
|
|
328
|
+
return isRecord(value) && typeof value.id === "string" && value.id.length > 0 && typeof value.email === "string" && value.email.length > 0 && (value.role === void 0 || value.role === null || typeof value.role === "string");
|
|
329
|
+
}
|
|
330
|
+
__name(isValidUser, "isValidUser");
|
|
331
|
+
function isStringArray(value) {
|
|
332
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
333
|
+
}
|
|
334
|
+
__name(isStringArray, "isStringArray");
|
|
335
|
+
|
|
336
|
+
// src/auth/CookieManager.ts
|
|
296
337
|
var __decorate2 = function(decorators, target, key, desc) {
|
|
297
338
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
298
339
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -348,7 +389,7 @@ var CookieManager = class CookieManager2 {
|
|
|
348
389
|
// =========================================================================
|
|
349
390
|
/**
|
|
350
391
|
* Write a signed session cookie containing user data, roles, and permissions.
|
|
351
|
-
* The cookie is HMAC-signed with the
|
|
392
|
+
* The cookie is HMAC-signed with the configured session secret so it is tamper-proof
|
|
352
393
|
* but readable without a database query. Short TTL (5 min) ensures freshness.
|
|
353
394
|
*/
|
|
354
395
|
setSessionCookie(data) {
|
|
@@ -368,15 +409,7 @@ var CookieManager = class CookieManager2 {
|
|
|
368
409
|
const raw = this.cookieService.getSigned(this.sessionCookieName, this.sessionSecret);
|
|
369
410
|
if (!raw)
|
|
370
411
|
return null;
|
|
371
|
-
|
|
372
|
-
const data = JSON.parse(raw);
|
|
373
|
-
const age = Date.now() - data.iat;
|
|
374
|
-
if (age > this.sessionMaxAge * 1e3)
|
|
375
|
-
return null;
|
|
376
|
-
return data;
|
|
377
|
-
} catch {
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
412
|
+
return parseSessionCookiePayload(raw, this.sessionMaxAge);
|
|
380
413
|
}
|
|
381
414
|
/**
|
|
382
415
|
* Clear the session cookie (on logout, password change, etc.)
|
|
@@ -400,7 +433,7 @@ CookieManager = __decorate2([
|
|
|
400
433
|
// src/auth/AuthController.ts
|
|
401
434
|
import { Controller } from "najm-core";
|
|
402
435
|
import { Get, Post, ResMsg } from "najm-core";
|
|
403
|
-
import { Body, User as
|
|
436
|
+
import { Body, User as User2, Headers } from "najm-core";
|
|
404
437
|
|
|
405
438
|
// src/auth/AuthService.ts
|
|
406
439
|
import { Injectable as Injectable8, Inject as Inject8 } from "najm-core";
|
|
@@ -2402,7 +2435,7 @@ var isAuth = createGuard(AuthGuard);
|
|
|
2402
2435
|
|
|
2403
2436
|
// src/roles/RoleGuards.ts
|
|
2404
2437
|
import { Service as Service3 } from "najm-core";
|
|
2405
|
-
import { GuardParams,
|
|
2438
|
+
import { GuardParams, Role as RequestRole } from "najm-core";
|
|
2406
2439
|
import { composeGuards, createGuard as createGuard2 } from "najm-guard";
|
|
2407
2440
|
var __decorate14 = function(decorators, target, key, desc) {
|
|
2408
2441
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2435,7 +2468,7 @@ var RoleGuard = class RoleGuard2 {
|
|
|
2435
2468
|
};
|
|
2436
2469
|
__decorate14([
|
|
2437
2470
|
__param4(0, GuardParams()),
|
|
2438
|
-
__param4(1,
|
|
2471
|
+
__param4(1, RequestRole()),
|
|
2439
2472
|
__metadata14("design:type", Function),
|
|
2440
2473
|
__metadata14("design:paramtypes", [Object, String]),
|
|
2441
2474
|
__metadata14("design:returntype", void 0)
|
|
@@ -2630,7 +2663,7 @@ __decorate15([
|
|
|
2630
2663
|
Post("/logout"),
|
|
2631
2664
|
isAuth(),
|
|
2632
2665
|
RateLimit({ limit: 10, window: "15m", key: "user" }),
|
|
2633
|
-
__param5(0,
|
|
2666
|
+
__param5(0, User2("id")),
|
|
2634
2667
|
__param5(1, Headers("authorization")),
|
|
2635
2668
|
__metadata15("design:type", Function),
|
|
2636
2669
|
__metadata15("design:paramtypes", [String, String]),
|
|
@@ -2641,7 +2674,7 @@ __decorate15([
|
|
|
2641
2674
|
isAuth(),
|
|
2642
2675
|
Validate(changePasswordDto),
|
|
2643
2676
|
ResMsg("auth.success.passwordChanged"),
|
|
2644
|
-
__param5(0,
|
|
2677
|
+
__param5(0, User2("id")),
|
|
2645
2678
|
__param5(1, Body()),
|
|
2646
2679
|
__metadata15("design:type", Function),
|
|
2647
2680
|
__metadata15("design:paramtypes", [String, Object]),
|
|
@@ -3370,7 +3403,7 @@ PermissionRepository = __decorate19([
|
|
|
3370
3403
|
|
|
3371
3404
|
// src/permissions/PermissionGuards.ts
|
|
3372
3405
|
import { Injectable as Injectable9 } from "najm-core";
|
|
3373
|
-
import { GuardParams as GuardParams2, User as
|
|
3406
|
+
import { GuardParams as GuardParams2, User as User3 } from "najm-core";
|
|
3374
3407
|
import { createGuard as createGuard4, composeGuards as composeGuards3 } from "najm-guard";
|
|
3375
3408
|
var __decorate20 = function(decorators, target, key, desc) {
|
|
3376
3409
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -3416,7 +3449,7 @@ var PermissionGuard = class PermissionGuard2 {
|
|
|
3416
3449
|
};
|
|
3417
3450
|
__decorate20([
|
|
3418
3451
|
__param8(0, GuardParams2()),
|
|
3419
|
-
__param8(1,
|
|
3452
|
+
__param8(1, User3("permissions")),
|
|
3420
3453
|
__metadata20("design:type", Function),
|
|
3421
3454
|
__metadata20("design:paramtypes", [String, Array]),
|
|
3422
3455
|
__metadata20("design:returntype", Object)
|
|
@@ -4038,7 +4071,7 @@ function own(table, opts) {
|
|
|
4038
4071
|
__name(own, "own");
|
|
4039
4072
|
|
|
4040
4073
|
// src/ownership/configureOwnership.ts
|
|
4041
|
-
import { Injectable as Injectable12, Inject as Inject11, User as
|
|
4074
|
+
import { Injectable as Injectable12, Inject as Inject11, User as User4, Body as Body5, Params as Params4 } from "najm-core";
|
|
4042
4075
|
import { createGuard as createGuard5, composeGuards as composeGuards4 } from "najm-guard";
|
|
4043
4076
|
var __decorate24 = function(decorators, target, key, desc) {
|
|
4044
4077
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -4083,7 +4116,7 @@ function createResourceGuards(ownershipClass, resourceType, resource, options) {
|
|
|
4083
4116
|
__metadata24("design:type", Object)
|
|
4084
4117
|
], AccessGuard.prototype, "ownership", void 0);
|
|
4085
4118
|
__decorate24([
|
|
4086
|
-
__param10(0,
|
|
4119
|
+
__param10(0, User4()),
|
|
4087
4120
|
__param10(1, Params4("id")),
|
|
4088
4121
|
__metadata24("design:type", Function),
|
|
4089
4122
|
__metadata24("design:paramtypes", [Object, String]),
|
|
@@ -4107,7 +4140,7 @@ function createResourceGuards(ownershipClass, resourceType, resource, options) {
|
|
|
4107
4140
|
__metadata24("design:type", Object)
|
|
4108
4141
|
], ListGuard.prototype, "ownership", void 0);
|
|
4109
4142
|
__decorate24([
|
|
4110
|
-
__param10(0,
|
|
4143
|
+
__param10(0, User4()),
|
|
4111
4144
|
__metadata24("design:type", Function),
|
|
4112
4145
|
__metadata24("design:paramtypes", [Object]),
|
|
4113
4146
|
__metadata24("design:returntype", typeof (_b12 = typeof Promise !== "undefined" && Promise) === "function" ? _b12 : Object)
|
|
@@ -4266,7 +4299,7 @@ function configureOwnership(config) {
|
|
|
4266
4299
|
__metadata24("design:type", GeneratedOwnershipService)
|
|
4267
4300
|
], BodyAccessGuard.prototype, "ownership", void 0);
|
|
4268
4301
|
__decorate24([
|
|
4269
|
-
__param10(0,
|
|
4302
|
+
__param10(0, User4()),
|
|
4270
4303
|
__param10(1, Body5()),
|
|
4271
4304
|
__metadata24("design:type", Function),
|
|
4272
4305
|
__metadata24("design:paramtypes", [Object, Object]),
|
|
@@ -4977,7 +5010,7 @@ OAuthAccountService = __decorate29([
|
|
|
4977
5010
|
|
|
4978
5011
|
// src/oauth/OAuthController.ts
|
|
4979
5012
|
import { createHash as createHash4 } from "crypto";
|
|
4980
|
-
import { Controller as Controller5, Ctx, Get as Get5, Post as Post5, Query as Query2, User as
|
|
5013
|
+
import { Controller as Controller5, Ctx, Get as Get5, Post as Post5, Query as Query2, User as User5 } from "najm-core";
|
|
4981
5014
|
import { RateLimit as RateLimit2 } from "najm-rate";
|
|
4982
5015
|
|
|
4983
5016
|
// src/oauth/OAuthService.ts
|
|
@@ -5284,7 +5317,7 @@ __decorate32([
|
|
|
5284
5317
|
Post5("/link"),
|
|
5285
5318
|
isAuth(),
|
|
5286
5319
|
RateLimit2({ limit: 10, window: "15m", key: "user" }),
|
|
5287
|
-
__param11(0,
|
|
5320
|
+
__param11(0, User5("id")),
|
|
5288
5321
|
__param11(1, Query2("returnTo")),
|
|
5289
5322
|
__metadata32("design:type", Function),
|
|
5290
5323
|
__metadata32("design:paramtypes", [String, String]),
|
|
@@ -5379,8 +5412,9 @@ var resolveAuthConfig = /* @__PURE__ */ __name((config) => {
|
|
|
5379
5412
|
session: {
|
|
5380
5413
|
name: config?.session?.name ?? "najm.session",
|
|
5381
5414
|
maxAge: config?.session?.maxAge ?? 300,
|
|
5382
|
-
secret
|
|
5383
|
-
//
|
|
5415
|
+
// Keep Edge/server readers aligned with the documented secret order.
|
|
5416
|
+
// CookieManager falls back to jwt.accessSecret when this is undefined.
|
|
5417
|
+
secret: config?.session?.secret ?? process.env.NAJM_SESSION_SECRET
|
|
5384
5418
|
},
|
|
5385
5419
|
oauth: {
|
|
5386
5420
|
google: resolveGoogleConfig(config)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "najm-auth",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Authentication and authorization library for najm framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"bcryptjs": "^3.0.2",
|
|
78
78
|
"najm-cookies": "^2.0.2",
|
|
79
|
-
"najm-core": "^2.0.
|
|
80
|
-
"najm-database": "^2.0.
|
|
79
|
+
"najm-core": "^2.0.4",
|
|
80
|
+
"najm-database": "^2.0.3",
|
|
81
81
|
"najm-guard": "^2.0.2",
|
|
82
82
|
"najm-i18n": "^2.0.2",
|
|
83
83
|
"najm-cache": "^2.0.2",
|