najm-auth 4.0.2 → 4.0.3

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 CHANGED
@@ -138,9 +138,8 @@ auth({
138
138
  publicRegistration?: boolean // Default: true; mounts POST /auth/register
139
139
  bcryptRounds?: number // Default: 10 (valid: 4-31)
140
140
 
141
- // Frontend
142
- frontendUrl?: string // Password reset link base URL
143
- appName?: string // Security email brand (default: 'Your app')
141
+ // Frontend
142
+ frontendUrl?: string // Password reset link base URL
144
143
 
145
144
  // Login identifier normalization (see "Identity presets")
146
145
  identity?: {
@@ -875,6 +874,13 @@ the successful Next.js render receives. Attempts to override `cookie` or
875
874
  `authorization` fail closed; only Najm's validated recovery path may replace
876
875
  the cookie after it authorizes the recovered session.
877
876
 
877
+ Speculative/prefetch requests (`next-router-prefetch`, `purpose: prefetch`,
878
+ `sec-purpose` containing `prefetch`, `next-router-state-tree` metadata-only)
879
+ receive the same treatment as direct navigation: a valid signed snapshot or
880
+ non-rotating `/session/recover` validation is still required. Refresh-cookie
881
+ presence is never authorization, so do not bypass `auth.proxy` when a refresh
882
+ cookie is present on a prefetch. Recovery never rotates refresh tokens.
883
+
878
884
  ```typescript
879
885
  // src/app/api/[...route]/route.ts
880
886
  import { handle } from 'najm-core';
@@ -1131,16 +1137,10 @@ consumption. Once consumed, a token stays consumed even if the later user
1131
1137
  mutation fails; restoring it would make the link replayable, so the user must
1132
1138
  request a new one.
1133
1139
 
1134
- Accepting an account invitation also marks the destination email verified and
1135
- activates the account when its status is `pending`. An ordinary password reset
1136
- changes neither verification nor lifecycle status, and an explicitly inactive
1137
- invited account remains inactive.
1138
-
1139
- Set `appName` on `auth()` to brand the invitation subject and email card. The
1140
- provisioned role is presented as the account type, so a sponsor invitation can
1141
- say “Activate your sponsor account” without application-owned HTML. The shared
1142
- template uses inline critical styles for Gmail and keeps the raw token URL out
1143
- of visible fallback copy.
1140
+ Accepting an account invitation also marks the destination email verified and
1141
+ activates the account when its status is `pending`. An ordinary password reset
1142
+ changes neither verification nor lifecycle status, and an explicitly inactive
1143
+ invited account remains inactive.
1144
1144
 
1145
1145
  The built-in memory and Redis drivers implement the required atomic primitive.
1146
1146
  A custom cache driver may omit `compareAndDelete()` for compatibility with
package/dist/index.d.ts CHANGED
@@ -198,8 +198,6 @@ interface AuthConfig {
198
198
  defaultRole: string | null;
199
199
  /** Frontend URL for password reset links (default: 'http://localhost:3000') */
200
200
  frontendUrl: string;
201
- /** Product name used in security email subjects and templates. */
202
- appName: string;
203
201
  /** Registration mode: 'active' auto-activates, 'pending' requires admin approval (default: 'active') */
204
202
  registrationMode: 'active' | 'pending';
205
203
  /** Whether the unauthenticated POST /auth/register route is mounted. */
@@ -267,8 +265,6 @@ type AuthPluginConfig = {
267
265
  defaultRole?: string | null;
268
266
  /** Frontend URL for password reset links. Falls back to FRONTEND_URL env var, then 'http://localhost:3000' */
269
267
  frontendUrl?: string;
270
- /** Product name used in account invitation emails (default: 'Your app'). */
271
- appName?: string;
272
268
  /** Registration mode: 'active' auto-activates new users, 'pending' requires admin approval (default: 'active') */
273
269
  registrationMode?: 'active' | 'pending';
274
270
  /**
@@ -436,7 +432,7 @@ var auth = {
436
432
  subject: "Reset your password"
437
433
  },
438
434
  accountInvite: {
439
- subject: "{{appName}}: activate your {{accountLabel}}"
435
+ subject: "You've been invited — set up your account"
440
436
  }
441
437
  }
442
438
  };
package/dist/index.js CHANGED
@@ -2212,7 +2212,7 @@ var TokenService = class TokenService2 {
2212
2212
  }
2213
2213
  rejectRefreshSession(messageKey = "errors.refreshTokenInvalid") {
2214
2214
  this.clearRefreshSessionCookies();
2215
- Err7(this.t(messageKey), 401);
2215
+ return Err7(this.t(messageKey), 401);
2216
2216
  }
2217
2217
  async assertRefreshFamilyAllowed(tokenFamily, userId) {
2218
2218
  if (await this.invalidation.familyStatus(tokenFamily, userId) === "revoked") {
@@ -3488,16 +3488,9 @@ var AuthService = class AuthService2 {
3488
3488
  });
3489
3489
  const { token } = await this.tokenService.generateInviteToken(user.id);
3490
3490
  const inviteLink = `${this.config.frontendUrl}/reset-password?token=${token}`;
3491
- const accountType = body.role?.trim().toLowerCase();
3492
- const accountLabel = accountType ? `${accountType} account` : "account";
3493
3491
  let emailSent = false;
3494
3492
  try {
3495
- await this.emailService.sendHtml(body.email, this.t("emails.accountInvite.subject", {
3496
- accountLabel,
3497
- appName: this.config.appName
3498
- }), accountInviteTemplate({
3499
- accountType,
3500
- appName: this.config.appName,
3493
+ await this.emailService.sendHtml(body.email, this.t("emails.accountInvite.subject"), accountInviteTemplate({
3501
3494
  inviteLink,
3502
3495
  userName: user.name || body.email
3503
3496
  }));
@@ -6353,7 +6346,7 @@ var en_default = {
6353
6346
  subject: "Reset your password"
6354
6347
  },
6355
6348
  accountInvite: {
6356
- subject: "{{appName}}: activate your {{accountLabel}}"
6349
+ subject: "You've been invited \u2014 set up your account"
6357
6350
  }
6358
6351
  }
6359
6352
  },
@@ -7485,13 +7478,6 @@ var validateCallbackUrl = /* @__PURE__ */ __name((value, name) => {
7485
7478
  }
7486
7479
  return callback.toString();
7487
7480
  }, "validateCallbackUrl");
7488
- var resolveAppName = /* @__PURE__ */ __name((value) => {
7489
- const appName = value?.trim() || "Your app";
7490
- if (appName.length > 80 || /[\u0000-\u001f\u007f]/.test(appName)) {
7491
- throw new Error("auth.appName must be at most 80 characters without control characters");
7492
- }
7493
- return appName;
7494
- }, "resolveAppName");
7495
7481
  var resolveGoogleConfig = /* @__PURE__ */ __name((config) => {
7496
7482
  const configuredGoogle = config?.oauth?.google;
7497
7483
  if (!configuredGoogle)
@@ -7568,7 +7554,6 @@ var resolveAuthConfig = /* @__PURE__ */ __name((config) => {
7568
7554
  blacklistPrefix: config?.blacklistPrefix ?? "auth:blacklist:",
7569
7555
  defaultRole: config?.defaultRole ?? null,
7570
7556
  frontendUrl: config?.frontendUrl ?? process.env.FRONTEND_URL ?? "http://localhost:3000",
7571
- appName: resolveAppName(config?.appName),
7572
7557
  registrationMode: config?.registrationMode ?? "active",
7573
7558
  publicRegistration: config?.publicRegistration ?? true,
7574
7559
  requireVerifiedEmail: config?.requireVerifiedEmail ?? false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-auth",
3
- "version": "4.0.2",
3
+ "version": "4.0.3",
4
4
  "description": "Authentication and authorization library for najm framework",
5
5
  "type": "module",
6
6
  "files": [
@@ -97,7 +97,7 @@
97
97
  "najm-guard": "^2.0.2",
98
98
  "najm-i18n": "^2.1.2",
99
99
  "najm-cache": "^2.2.0",
100
- "najm-email": "^2.0.4",
100
+ "najm-email": "^2.0.3",
101
101
  "najm-rate": "^2.1.1",
102
102
  "najm-validation": "^2.0.2",
103
103
  "jsonwebtoken": "^9.0.3",