najm-auth 1.1.27 → 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.
@@ -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
- const loginUrl = new URL(loginRoute, request.url);
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
- if (requiredRoles) {
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
- const loginUrl = new URL(loginRoute, request.url);
391
- loginUrl.searchParams.set("from", pathname);
392
- return NextResponse.redirect(loginUrl);
401
+ return redirectToLogin(pathname, true);
393
402
  }
394
- const body = await res.json();
395
- const userRole = body?.data?.role;
396
- if (!userRole || !requiredRoles.includes(userRole)) {
397
- return new NextResponse(null, { status: 403 });
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
- const loginUrl = new URL(loginRoute, request.url);
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
- const loginUrl = new URL(loginRoute, request.url);
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
- if (requiredRoles) {
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
- const loginUrl = new URL(loginRoute, request.url);
991
- loginUrl.searchParams.set("from", pathname);
992
- return NextResponse.redirect(loginUrl);
1010
+ return redirectToLogin(true);
993
1011
  }
994
- const body = await res.json();
995
- const userRole = body?.data?.role;
996
- if (!userRole || !requiredRoles.includes(userRole)) {
997
- return new NextResponse(null, { status: 403 });
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
- const loginUrl = new URL(loginRoute, request.url);
1001
- loginUrl.searchParams.set("from", pathname);
1002
- return NextResponse.redirect(loginUrl);
1020
+ return redirectToLogin(true);
1003
1021
  }
1004
1022
  }
1005
1023
  return NextResponse.next();
package/dist/index.d.ts CHANGED
@@ -823,7 +823,7 @@ declare const createUserDto: z.ZodObject<{
823
823
  email: z.ZodString;
824
824
  password: z.ZodString;
825
825
  roleId: z.ZodOptional<z.ZodString>;
826
- image: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<File, File>]>>;
826
+ image: z.ZodOptional<z.ZodNullable<z.ZodString>>;
827
827
  emailVerified: z.ZodDefault<z.ZodBoolean>;
828
828
  status: z.ZodOptional<z.ZodEnum<{
829
829
  active: "active";
@@ -836,7 +836,7 @@ declare const updateUserDto: z.ZodObject<{
836
836
  email: z.ZodOptional<z.ZodString>;
837
837
  password: z.ZodOptional<z.ZodString>;
838
838
  roleId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
839
- image: z.ZodOptional<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<File, File>]>>>;
839
+ image: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
840
840
  emailVerified: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
841
841
  status: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
842
842
  active: "active";
package/dist/index.js CHANGED
@@ -2034,7 +2034,7 @@ var createUserDto = z.object({
2034
2034
  email: emailField,
2035
2035
  password: passwordField,
2036
2036
  roleId: z.string().min(1).optional(),
2037
- image: z.union([z.string(), z.instanceof(File)]).optional(),
2037
+ image: z.string().nullish(),
2038
2038
  emailVerified: z.boolean().default(false),
2039
2039
  status: z.enum(["active", "inactive", "pending"]).optional()
2040
2040
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-auth",
3
- "version": "1.1.27",
3
+ "version": "1.1.30",
4
4
  "description": "Authentication and authorization library for najm framework",
5
5
  "type": "module",
6
6
  "files": [