najm-auth 1.1.29 → 1.1.30
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/server/index.d.ts +14 -0
- package/dist/client/server/index.js +46 -28
- package/package.json +1 -1
|
@@ -74,8 +74,16 @@ interface AuthMiddlewareConfig {
|
|
|
74
74
|
roleRoutes?: Record<string, string[]>;
|
|
75
75
|
/** Refresh token cookie name (default: 'refreshToken') */
|
|
76
76
|
cookieName?: string;
|
|
77
|
+
/** Session cookie name to clear on redirect (default: 'najm.session') */
|
|
78
|
+
sessionCookieName?: string;
|
|
77
79
|
/** URL of the verify endpoint (default: derived from request) */
|
|
78
80
|
verifyURL?: string;
|
|
81
|
+
/**
|
|
82
|
+
* When true, call the verify endpoint on EVERY protected route (not just
|
|
83
|
+
* roleRoutes). Redirects to loginRoute if the session is invalid. Adds one
|
|
84
|
+
* fetch per navigation — trade latency for stronger guarantees.
|
|
85
|
+
*/
|
|
86
|
+
verifyAlways?: boolean;
|
|
79
87
|
}
|
|
80
88
|
/**
|
|
81
89
|
* Create a Next.js middleware function that protects routes based on auth state.
|
|
@@ -230,6 +238,12 @@ interface DefineAuthConfig {
|
|
|
230
238
|
sessionSecret?: string;
|
|
231
239
|
/** Next.js middleware matcher (default: exclude _next, favicon, api) */
|
|
232
240
|
matcher?: string[];
|
|
241
|
+
/**
|
|
242
|
+
* When true, call the verify endpoint on every protected route (not just
|
|
243
|
+
* roleRoutes). Redirects to loginRoute if the session is invalid. One extra
|
|
244
|
+
* fetch per navigation in exchange for guaranteed fresh auth state.
|
|
245
|
+
*/
|
|
246
|
+
verifyAlways?: boolean;
|
|
233
247
|
}
|
|
234
248
|
interface AuthKit {
|
|
235
249
|
/**
|
|
@@ -361,10 +361,22 @@ function withAuthMiddleware(config) {
|
|
|
361
361
|
publicRoutes = [],
|
|
362
362
|
loginRoute = "/login",
|
|
363
363
|
roleRoutes = {},
|
|
364
|
-
cookieName = "refreshToken"
|
|
364
|
+
cookieName = "refreshToken",
|
|
365
|
+
sessionCookieName = "najm.session",
|
|
366
|
+
verifyAlways = false
|
|
365
367
|
} = config;
|
|
366
368
|
return /* @__PURE__ */ __name(async function middleware(request) {
|
|
367
369
|
const { NextResponse } = await import("next/server");
|
|
370
|
+
const redirectToLogin = /* @__PURE__ */ __name((pathname2, clearCookies) => {
|
|
371
|
+
const loginUrl = new URL(loginRoute, request.url);
|
|
372
|
+
loginUrl.searchParams.set("from", pathname2);
|
|
373
|
+
const res = NextResponse.redirect(loginUrl);
|
|
374
|
+
if (clearCookies) {
|
|
375
|
+
res.cookies.delete(cookieName);
|
|
376
|
+
res.cookies.delete(sessionCookieName);
|
|
377
|
+
}
|
|
378
|
+
return res;
|
|
379
|
+
}, "redirectToLogin");
|
|
368
380
|
const url = new URL(request.url);
|
|
369
381
|
const pathname = url.pathname;
|
|
370
382
|
if (matchesAny(pathname, publicRoutes)) {
|
|
@@ -375,30 +387,28 @@ function withAuthMiddleware(config) {
|
|
|
375
387
|
const cookie = request.headers.get("cookie") ?? "";
|
|
376
388
|
const hasToken = cookieRegex(cookieName).test(cookie);
|
|
377
389
|
if (!hasToken) {
|
|
378
|
-
|
|
379
|
-
loginUrl.searchParams.set("from", pathname);
|
|
380
|
-
return NextResponse.redirect(loginUrl);
|
|
390
|
+
return redirectToLogin(pathname, true);
|
|
381
391
|
}
|
|
382
392
|
const requiredRoles = findMatchingRoles(pathname, roleRoutes);
|
|
383
|
-
|
|
393
|
+
const needsVerify = verifyAlways || !!requiredRoles;
|
|
394
|
+
if (needsVerify) {
|
|
384
395
|
const verifyURL = config.verifyURL ?? `${url.origin}/api/auth/me`;
|
|
385
396
|
try {
|
|
386
397
|
const res = await fetch(verifyURL, {
|
|
387
398
|
headers: { "Cookie": cookie, "Accept": "application/json" }
|
|
388
399
|
});
|
|
389
400
|
if (!res.ok) {
|
|
390
|
-
|
|
391
|
-
loginUrl.searchParams.set("from", pathname);
|
|
392
|
-
return NextResponse.redirect(loginUrl);
|
|
401
|
+
return redirectToLogin(pathname, true);
|
|
393
402
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
+
}
|
|
398
409
|
}
|
|
399
410
|
} catch {
|
|
400
|
-
|
|
401
|
-
return NextResponse.redirect(loginUrl);
|
|
411
|
+
return redirectToLogin(pathname, true);
|
|
402
412
|
}
|
|
403
413
|
}
|
|
404
414
|
return NextResponse.next();
|
|
@@ -909,6 +919,7 @@ function defineAuth(authConfig = {}) {
|
|
|
909
919
|
sessionCookieName = "najm.session",
|
|
910
920
|
sessionSecret,
|
|
911
921
|
matcher = ["/((?!_next/static|_next/image|favicon.ico|api).*)"],
|
|
922
|
+
verifyAlways = false,
|
|
912
923
|
refreshThreshold,
|
|
913
924
|
tabSync,
|
|
914
925
|
channelName,
|
|
@@ -967,6 +978,16 @@ function defineAuth(authConfig = {}) {
|
|
|
967
978
|
const { NextResponse } = await import("next/server");
|
|
968
979
|
const url = new URL(request.url);
|
|
969
980
|
const pathname = url.pathname;
|
|
981
|
+
const redirectToLogin = /* @__PURE__ */ __name((clearCookies) => {
|
|
982
|
+
const loginUrl = new URL(loginRoute, request.url);
|
|
983
|
+
loginUrl.searchParams.set("from", pathname);
|
|
984
|
+
const res = NextResponse.redirect(loginUrl);
|
|
985
|
+
if (clearCookies) {
|
|
986
|
+
res.cookies.delete(cookieName);
|
|
987
|
+
res.cookies.delete(sessionCookieName);
|
|
988
|
+
}
|
|
989
|
+
return res;
|
|
990
|
+
}, "redirectToLogin");
|
|
970
991
|
if (matchesAny2(pathname, publicRoutes)) {
|
|
971
992
|
return NextResponse.next();
|
|
972
993
|
}
|
|
@@ -975,31 +996,28 @@ function defineAuth(authConfig = {}) {
|
|
|
975
996
|
const cookie = request.headers.get("cookie") ?? "";
|
|
976
997
|
const hasToken = cookieRegex2(cookieName).test(cookie);
|
|
977
998
|
if (!hasToken) {
|
|
978
|
-
|
|
979
|
-
loginUrl.searchParams.set("from", pathname);
|
|
980
|
-
return NextResponse.redirect(loginUrl);
|
|
999
|
+
return redirectToLogin(true);
|
|
981
1000
|
}
|
|
982
1001
|
const requiredRoles = findMatchingRoles2(pathname, roleRoutes);
|
|
983
|
-
|
|
1002
|
+
const needsVerify = verifyAlways || !!requiredRoles;
|
|
1003
|
+
if (needsVerify) {
|
|
984
1004
|
const verifyURL = `${url.origin}${apiBaseURL}${authPrefix}/me`;
|
|
985
1005
|
try {
|
|
986
1006
|
const res = await fetch(verifyURL, {
|
|
987
1007
|
headers: { Cookie: cookie, Accept: "application/json" }
|
|
988
1008
|
});
|
|
989
1009
|
if (!res.ok) {
|
|
990
|
-
|
|
991
|
-
loginUrl.searchParams.set("from", pathname);
|
|
992
|
-
return NextResponse.redirect(loginUrl);
|
|
1010
|
+
return redirectToLogin(true);
|
|
993
1011
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1012
|
+
if (requiredRoles) {
|
|
1013
|
+
const body = await res.json();
|
|
1014
|
+
const userRole = body?.data?.role;
|
|
1015
|
+
if (!userRole || !requiredRoles.includes(userRole)) {
|
|
1016
|
+
return new NextResponse(null, { status: 403 });
|
|
1017
|
+
}
|
|
998
1018
|
}
|
|
999
1019
|
} catch {
|
|
1000
|
-
|
|
1001
|
-
loginUrl.searchParams.set("from", pathname);
|
|
1002
|
-
return NextResponse.redirect(loginUrl);
|
|
1020
|
+
return redirectToLogin(true);
|
|
1003
1021
|
}
|
|
1004
1022
|
}
|
|
1005
1023
|
return NextResponse.next();
|