najm-auth 4.0.3 → 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
@@ -139,7 +139,14 @@ auth({
139
139
  bcryptRounds?: number // Default: 10 (valid: 4-31)
140
140
 
141
141
  // Frontend
142
- frontendUrl?: string // Password reset link base URL
142
+ frontendUrl?: string // Password reset link base URL
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
+ }
143
150
 
144
151
  // Login identifier normalization (see "Identity presets")
145
152
  identity?: {
@@ -1142,6 +1149,17 @@ activates the account when its status is `pending`. An ordinary password reset
1142
1149
  changes neither verification nor lifecycle status, and an explicitly inactive
1143
1150
  invited account remains inactive.
1144
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
1155
+ template uses inline critical styles for Gmail and keeps the raw token URL out
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.
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
1147
1165
  unrelated cache usage, but reset and invite consumption then fails closed. Do
package/dist/index.d.ts CHANGED
@@ -198,6 +198,15 @@ 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
+ /** Optional inline logo embedded in account invitation emails. */
204
+ accountInviteLogo?: {
205
+ alt?: string;
206
+ contentBase64: string;
207
+ contentType: string;
208
+ filename: string;
209
+ };
201
210
  /** Registration mode: 'active' auto-activates, 'pending' requires admin approval (default: 'active') */
202
211
  registrationMode: 'active' | 'pending';
203
212
  /** Whether the unauthenticated POST /auth/register route is mounted. */
@@ -265,6 +274,15 @@ type AuthPluginConfig = {
265
274
  defaultRole?: string | null;
266
275
  /** Frontend URL for password reset links. Falls back to FRONTEND_URL env var, then 'http://localhost:3000' */
267
276
  frontendUrl?: string;
277
+ /** Product name used in account invitation emails (default: 'Your app'). */
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
+ };
268
286
  /** Registration mode: 'active' auto-activates new users, 'pending' requires admin approval (default: 'active') */
269
287
  registrationMode?: 'active' | 'pending';
270
288
  /**
@@ -432,7 +450,7 @@ var auth = {
432
450
  subject: "Reset your password"
433
451
  },
434
452
  accountInvite: {
435
- subject: "You've been invited — set up your account"
453
+ subject: "{{appName}}: activate your {{accountLabel}}"
436
454
  }
437
455
  }
438
456
  };
package/dist/index.js CHANGED
@@ -3488,13 +3488,36 @@ 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";
3491
3493
  let emailSent = false;
3492
3494
  try {
3493
- await this.emailService.sendHtml(body.email, this.t("emails.accountInvite.subject"), accountInviteTemplate({
3494
- inviteLink,
3495
- userName: user.name || body.email
3496
- }));
3497
- 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;
3498
3521
  } catch (error) {
3499
3522
  this.logger.warn("Account invite email failed", { email: body.email, error });
3500
3523
  }
@@ -6346,7 +6369,7 @@ var en_default = {
6346
6369
  subject: "Reset your password"
6347
6370
  },
6348
6371
  accountInvite: {
6349
- subject: "You've been invited \u2014 set up your account"
6372
+ subject: "{{appName}}: activate your {{accountLabel}}"
6350
6373
  }
6351
6374
  }
6352
6375
  },
@@ -7478,6 +7501,13 @@ var validateCallbackUrl = /* @__PURE__ */ __name((value, name) => {
7478
7501
  }
7479
7502
  return callback.toString();
7480
7503
  }, "validateCallbackUrl");
7504
+ var resolveAppName = /* @__PURE__ */ __name((value) => {
7505
+ const appName = value?.trim() || "Your app";
7506
+ if (appName.length > 80 || /[\u0000-\u001f\u007f]/.test(appName)) {
7507
+ throw new Error("auth.appName must be at most 80 characters without control characters");
7508
+ }
7509
+ return appName;
7510
+ }, "resolveAppName");
7481
7511
  var resolveGoogleConfig = /* @__PURE__ */ __name((config) => {
7482
7512
  const configuredGoogle = config?.oauth?.google;
7483
7513
  if (!configuredGoogle)
@@ -7554,6 +7584,8 @@ var resolveAuthConfig = /* @__PURE__ */ __name((config) => {
7554
7584
  blacklistPrefix: config?.blacklistPrefix ?? "auth:blacklist:",
7555
7585
  defaultRole: config?.defaultRole ?? null,
7556
7586
  frontendUrl: config?.frontendUrl ?? process.env.FRONTEND_URL ?? "http://localhost:3000",
7587
+ appName: resolveAppName(config?.appName),
7588
+ accountInviteLogo: config?.accountInviteLogo,
7557
7589
  registrationMode: config?.registrationMode ?? "active",
7558
7590
  publicRegistration: config?.publicRegistration ?? true,
7559
7591
  requireVerifiedEmail: config?.requireVerifiedEmail ?? false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "najm-auth",
3
- "version": "4.0.3",
3
+ "version": "4.0.4",
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.3",
100
+ "najm-email": "^2.0.4",
101
101
  "najm-rate": "^2.1.1",
102
102
  "najm-validation": "^2.0.2",
103
103
  "jsonwebtoken": "^9.0.3",