najm-auth 4.0.2 → 4.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/README.md CHANGED
@@ -138,9 +138,15 @@ auth({
138
138
  publicRegistration?: boolean // Default: true; mounts POST /auth/register
139
139
  bcryptRounds?: number // Default: 10 (valid: 4-31)
140
140
 
141
- // Frontend
141
+ // Frontend
142
142
  frontendUrl?: string // Password reset link base URL
143
143
  appName?: string // Security email brand (default: 'Your app')
144
+ accountInviteLogo?: { // Optional CID-backed inline mark
145
+ alt?: string
146
+ contentBase64: string
147
+ contentType: string
148
+ filename: string
149
+ }
144
150
 
145
151
  // Login identifier normalization (see "Identity presets")
146
152
  identity?: {
@@ -875,6 +881,13 @@ the successful Next.js render receives. Attempts to override `cookie` or
875
881
  `authorization` fail closed; only Najm's validated recovery path may replace
876
882
  the cookie after it authorizes the recovered session.
877
883
 
884
+ Speculative/prefetch requests (`next-router-prefetch`, `purpose: prefetch`,
885
+ `sec-purpose` containing `prefetch`, `next-router-state-tree` metadata-only)
886
+ receive the same treatment as direct navigation: a valid signed snapshot or
887
+ non-rotating `/session/recover` validation is still required. Refresh-cookie
888
+ presence is never authorization, so do not bypass `auth.proxy` when a refresh
889
+ cookie is present on a prefetch. Recovery never rotates refresh tokens.
890
+
878
891
  ```typescript
879
892
  // src/app/api/[...route]/route.ts
880
893
  import { handle } from 'najm-core';
@@ -1131,16 +1144,21 @@ consumption. Once consumed, a token stays consumed even if the later user
1131
1144
  mutation fails; restoring it would make the link replayable, so the user must
1132
1145
  request a new one.
1133
1146
 
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
1147
+ Accepting an account invitation also marks the destination email verified and
1148
+ activates the account when its status is `pending`. An ordinary password reset
1149
+ changes neither verification nor lifecycle status, and an explicitly inactive
1150
+ invited account remains inactive.
1151
+
1152
+ Set `appName` on `auth()` to brand the invitation subject and email card. The
1153
+ provisioned role is presented as the account type, so a sponsor invitation can
1154
+ say “Activate your sponsor account” without application-owned HTML. The shared
1142
1155
  template uses inline critical styles for Gmail and keeps the raw token URL out
1143
1156
  of visible fallback copy.
1157
+
1158
+ For a branded mark that works in email clients without a public asset URL, set
1159
+ `accountInviteLogo` to base64 content plus its MIME type and filename. Najm
1160
+ attaches it inline and points the shared template at a stable CID; when omitted,
1161
+ the template renders `appName` as text.
1144
1162
 
1145
1163
  The built-in memory and Redis drivers implement the required atomic primitive.
1146
1164
  A custom cache driver may omit `compareAndDelete()` for compatibility with
package/dist/index.d.ts CHANGED
@@ -200,6 +200,13 @@ interface AuthConfig {
200
200
  frontendUrl: string;
201
201
  /** Product name used in security email subjects and templates. */
202
202
  appName: string;
203
+ /** Optional inline logo embedded in account invitation emails. */
204
+ accountInviteLogo?: {
205
+ alt?: string;
206
+ contentBase64: string;
207
+ contentType: string;
208
+ filename: string;
209
+ };
203
210
  /** Registration mode: 'active' auto-activates, 'pending' requires admin approval (default: 'active') */
204
211
  registrationMode: 'active' | 'pending';
205
212
  /** Whether the unauthenticated POST /auth/register route is mounted. */
@@ -269,6 +276,13 @@ type AuthPluginConfig = {
269
276
  frontendUrl?: string;
270
277
  /** Product name used in account invitation emails (default: 'Your app'). */
271
278
  appName?: string;
279
+ /** Optional inline logo embedded in account invitation emails. */
280
+ accountInviteLogo?: {
281
+ alt?: string;
282
+ contentBase64: string;
283
+ contentType: string;
284
+ filename: string;
285
+ };
272
286
  /** Registration mode: 'active' auto-activates new users, 'pending' requires admin approval (default: 'active') */
273
287
  registrationMode?: 'active' | 'pending';
274
288
  /**
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") {
@@ -3492,16 +3492,32 @@ var AuthService = class AuthService2 {
3492
3492
  const accountLabel = accountType ? `${accountType} account` : "account";
3493
3493
  let emailSent = false;
3494
3494
  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,
3501
- inviteLink,
3502
- userName: user.name || body.email
3503
- }));
3504
- emailSent = true;
3495
+ const logo = this.config.accountInviteLogo;
3496
+ const logoCid = logo ? "najm-account-invite-logo" : void 0;
3497
+ const result = await this.emailService.send({
3498
+ to: body.email,
3499
+ subject: this.t("emails.accountInvite.subject", {
3500
+ accountLabel,
3501
+ appName: this.config.appName
3502
+ }),
3503
+ html: accountInviteTemplate({
3504
+ accountType,
3505
+ appName: this.config.appName,
3506
+ inviteLink,
3507
+ logoAlt: logo?.alt,
3508
+ logoSrc: logoCid ? `cid:${logoCid}` : void 0,
3509
+ userName: user.name || body.email
3510
+ }),
3511
+ attachments: logo ? [{
3512
+ filename: logo.filename,
3513
+ content: logo.contentBase64,
3514
+ contentType: logo.contentType,
3515
+ cid: logoCid,
3516
+ disposition: "inline",
3517
+ encoding: "base64"
3518
+ }] : void 0
3519
+ });
3520
+ emailSent = result.success;
3505
3521
  } catch (error) {
3506
3522
  this.logger.warn("Account invite email failed", { email: body.email, error });
3507
3523
  }
@@ -7569,6 +7585,7 @@ var resolveAuthConfig = /* @__PURE__ */ __name((config) => {
7569
7585
  defaultRole: config?.defaultRole ?? null,
7570
7586
  frontendUrl: config?.frontendUrl ?? process.env.FRONTEND_URL ?? "http://localhost:3000",
7571
7587
  appName: resolveAppName(config?.appName),
7588
+ accountInviteLogo: config?.accountInviteLogo,
7572
7589
  registrationMode: config?.registrationMode ?? "active",
7573
7590
  publicRegistration: config?.publicRegistration ?? true,
7574
7591
  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.4",
4
4
  "description": "Authentication and authorization library for najm framework",
5
5
  "type": "module",
6
6
  "files": [