@lenne.tech/nest-server 11.43.5 → 11.43.6
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/.claude/rules/configurable-features.md +1 -0
- package/FRAMEWORK-API.md +2 -2
- package/dist/core/common/interfaces/server-options.interface.d.ts +1 -0
- package/dist/core/common/services/email.service.d.ts +3 -0
- package/dist/core/common/services/email.service.js +20 -0
- package/dist/core/common/services/email.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/migration-guides/11.43.5-to-11.43.6.md +53 -0
- package/package.json +1 -1
- package/src/core/common/interfaces/server-options.interface.ts +11 -0
- package/src/core/common/services/email.service.ts +33 -1
|
@@ -300,6 +300,7 @@ This pattern is currently applied to:
|
|
|
300
300
|
| Password-Reset Token Lifetime | `auth.passwordReset.tokenExpiresInMinutes` | **Numeric Sentinel — Family C** (see above) | **60 minutes since 11.38.0. Before that the legacy reset token NEVER expired** — `resetPassword()` looked it up by value and compared no time, while the exception it threw read "Invalid or expired password reset token", claiming a check that did not exist. A reset link takes over an EXISTING account, so an unbounded one means a mail in an archive or a restored backup opens that account years later. The IAM half already expired after an hour (Better-Auth's `resetPasswordTokenExpiresIn`); only the legacy half had nothing. `0` opts out; an invalid value falls back to 60, never to unbounded. **A token minted before 11.38.0 carries no timestamp and is treated as EXPIRED** — reading "no timestamp" as "valid forever" would preserve the defect permanently rather than for a migration window. An expired token is answered exactly like an unknown one (404, same message) and deleted on sight. Model field `passwordResetTokenExpiresAt`, `@Restricted(S_NO_ONE)`. Implementation: `core-user.service.ts` → `passwordResetTokenExpiryMinutes()` / `isPasswordResetTokenExpired()` |
|
|
301
301
|
| Legacy Password-Reset Link | `email.passwordResetLink` | Explicit Value with derived default | **`<appUrl>/auth/reset-password?token={token}` since 11.38.0.** Two conventions live here and which applies depends on whether the option is set AT ALL: unset yields `?token=`, the same string written out yields a PATH segment. That is deliberate — a project that configures such a value has a page built for a path parameter, and moving it silently would break the flow this release repairs — and because it is not guessable, `CoreUserService` **warns once at boot** when a configured value carries no `{token}`. The warning names a precondition: `{token}` is substituted by `buildPasswordResetLink()` and nothing else, so a caller still concatenating by hand must switch FIRST or it mails the placeholder verbatim. Returns `null` rather than a string containing `undefined`; resolves `appUrl` through `resolveServerUrls` (localhost defaults, host-split `baseUrl`) and honours `cors.deriveAppUrl` |
|
|
302
302
|
| SMTP TLS Mode | `email.smtp.secure` + `SMTP_REQUIRE_TLS` | Derived from the port | **`secure` follows `SMTP_PORT` unless set to `'true'`/`'false'`.** It is not an independent setting: 465 negotiates TLS immediately, everything else upgrades via STARTTLS. The production profile shipped 587 + `secure: true` — a pair that cannot connect — so a deployment configuring only host and credentials sent NO mail, invisibly, because authentication mail is not awaited. Only the two canonical values override; anything else defers to the port, which is the only rule under which no input produces an unconnectable pair. `requireTLS` defaults to **true**: `secure: false` alone means opportunistic STARTTLS, and an on-path attacker stripping the capability line gets credentials and a live reset link in plaintext. `EmailService` warns once when the resolved pair cannot connect — reported, not overruled. Implementation: `config.helper.ts` → `resolveSmtpSecure()` / `isImpossibleSmtpTlsCombination()` |
|
|
303
|
+
| Mail Reply-To | `email.defaultReplyTo` | Explicit Value — absent = no header | **Absent → no `Reply-To` header, exactly as before (11.43.6+).** A string (an address, or `"Name" <address>`) becomes the Reply-To of every mail `EmailService.sendMail()` sends; a call overrides it with `replyTo`, and `replyTo: false` (or `''`) sends that one mail without it. Env: `NSC__EMAIL__DEFAULT_REPLY_TO`. A value that is not a string is ignored with one warning — a misconfigured default must not stop password-reset or verification mail. SMTP path only (`email.smtp`, nodemailer, Hub capture unaffected): Brevo and Mailjet templates carry their own reply-to. Pinned by `tests/unit/email-smtp-roundtrip.spec.ts` (Reply-To block, real SMTP). Implementation: `common/services/email.service.ts` → `resolveReplyTo()` |
|
|
303
304
|
| Legacy Auth Rate Limiting | `auth.rateLimit` | Presence Implies Enabled | `max: 10`, `windowSeconds: 60`. Counters live in a `RateLimitStore`: `RedisRateLimitStore` when `redis` is configured (limit enforced EXACTLY across replicas instead of `max × replicas`), else `InMemoryRateLimitStore` (previous behavior). On a Redis outage it degrades to the in-memory counter and logs once per transition — never a 500, never "allowed". `check()` / `reset()` / `clear()` are ASYNC since 11.33.0; `getStats().activeEntries` is `-1` on the Redis store |
|
|
304
305
|
| BetterAuth Rate Limiting | `betterAuth.rateLimit` | Presence Implies Enabled | `max: 10`, `windowSeconds: 60`. Same `RateLimitStore` selection, degradation and async signatures as the Legacy Auth row (namespace `better-auth`) |
|
|
305
306
|
| BetterAuth JWT Plugin | `betterAuth.jwt` | Boolean Shorthand | `expiresIn: '15m'` |
|
package/FRAMEWORK-API.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @lenne.tech/nest-server — Framework API Reference
|
|
2
2
|
|
|
3
|
-
> Auto-generated from source code as of 2026-
|
|
3
|
+
> Auto-generated from source code as of 2026-11-03 (v11.43.6)
|
|
4
4
|
> File: `FRAMEWORK-API.md` — compact, machine-readable API surface for Claude Code
|
|
5
5
|
|
|
6
6
|
## CoreModule.forRoot()
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
- `cors?`: `boolean | ICorsConfig | undefined` (default: `undefined (enabled with auto-derived origins)`) — CORS (Cross-Origin Resource Sharing) configuration.
|
|
27
27
|
- `cronJobs?`: `Record<string, string | false | 0 | CronJobConfigWithTimeZone<null, null> | C...` — Cron jobs configuration object with the name of the cron job function as key
|
|
28
28
|
- `debugProcessInput?`: `boolean | undefined` (default: `false`) — When true, logs a debug message when prepareInput() changes the input type during process().
|
|
29
|
-
- `email?`: `{ defaultSender?: { email?: string; name?: string; }
|
|
29
|
+
- `email?`: `{ defaultReplyTo?: string; defaultSender?: { email?: string; name?: string; }...` — SMTP and template configuration for sending emails
|
|
30
30
|
- `env?`: `string | undefined` — Environment
|
|
31
31
|
- `version?`: `string | undefined` — Semantic version of the running build (e.g. from package.json / meta.json).
|
|
32
32
|
- `errorCode?`: `IErrorCode | undefined` — Configuration for the error code module
|
|
@@ -293,6 +293,7 @@ export interface IServerOptions {
|
|
|
293
293
|
cronJobs?: Record<string, CronExpression | CronJobConfigWithTimeZone | CronJobConfigWithUtcOffset | Date | Falsy | string>;
|
|
294
294
|
debugProcessInput?: boolean;
|
|
295
295
|
email?: {
|
|
296
|
+
defaultReplyTo?: string;
|
|
296
297
|
defaultSender?: {
|
|
297
298
|
email?: string;
|
|
298
299
|
name?: string;
|
|
@@ -12,13 +12,16 @@ export declare class EmailService implements OnModuleDestroy {
|
|
|
12
12
|
private cachedSmtpConfig;
|
|
13
13
|
protected readonly emailServiceLogger: Logger;
|
|
14
14
|
private smtpTlsWarningEmitted;
|
|
15
|
+
private replyToWarningEmitted;
|
|
15
16
|
constructor(configService: ConfigService, templateService: TemplateService, emailCapture?: IHubEmailCapture);
|
|
16
17
|
protected warnOnImpossibleSmtpTlsCombination(smtp: unknown): void;
|
|
18
|
+
protected resolveReplyTo(replyTo: false | string | undefined): string | undefined;
|
|
17
19
|
onModuleDestroy(): void;
|
|
18
20
|
sendMail(recipients: string | string[], subject: string, config: {
|
|
19
21
|
attachments?: Attachment[];
|
|
20
22
|
html?: string;
|
|
21
23
|
htmlTemplate?: string;
|
|
24
|
+
replyTo?: false | string;
|
|
22
25
|
senderEmail?: string;
|
|
23
26
|
senderName?: string;
|
|
24
27
|
smtp?: MailTransportOptions;
|
|
@@ -30,6 +30,7 @@ let EmailService = EmailService_1 = class EmailService {
|
|
|
30
30
|
cachedSmtpConfig = null;
|
|
31
31
|
emailServiceLogger = new common_1.Logger(EmailService_1.name);
|
|
32
32
|
smtpTlsWarningEmitted = false;
|
|
33
|
+
replyToWarningEmitted = false;
|
|
33
34
|
constructor(configService, templateService, emailCapture) {
|
|
34
35
|
this.configService = configService;
|
|
35
36
|
this.templateService = templateService;
|
|
@@ -53,6 +54,23 @@ let EmailService = EmailService_1 = class EmailService {
|
|
|
53
54
|
'"wrong version number" from OpenSSL and NO outgoing mail — silently, because authentication mail ' +
|
|
54
55
|
'is not awaited. Set SMTP_SECURE=false for port 587, or SMTP_PORT=465 to keep implicit TLS.');
|
|
55
56
|
}
|
|
57
|
+
resolveReplyTo(replyTo) {
|
|
58
|
+
if (replyTo === false) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const value = replyTo ?? this.configService.getFastButReadOnly('email.defaultReplyTo');
|
|
62
|
+
if (value === undefined || value === null) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
if (typeof value === 'string') {
|
|
66
|
+
return value.trim() || undefined;
|
|
67
|
+
}
|
|
68
|
+
if (!this.replyToWarningEmitted) {
|
|
69
|
+
this.replyToWarningEmitted = true;
|
|
70
|
+
this.emailServiceLogger.warn(`Reply-To ignored: expected an address string (email.defaultReplyTo / replyTo), got ${typeof value}.`);
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
56
74
|
onModuleDestroy() {
|
|
57
75
|
if (this.cachedTransporter) {
|
|
58
76
|
this.cachedTransporter.close();
|
|
@@ -121,10 +139,12 @@ let EmailService = EmailService_1 = class EmailService {
|
|
|
121
139
|
this.cachedTransporter = nodemailer.createTransport(smtp);
|
|
122
140
|
this.cachedSmtpConfig = smtpKey;
|
|
123
141
|
}
|
|
142
|
+
const replyTo = this.resolveReplyTo(config.replyTo);
|
|
124
143
|
return this.cachedTransporter.sendMail({
|
|
125
144
|
attachments,
|
|
126
145
|
from: `"${senderName}" <${senderEmail}>`,
|
|
127
146
|
html,
|
|
147
|
+
...(replyTo ? { replyTo } : {}),
|
|
128
148
|
subject,
|
|
129
149
|
text,
|
|
130
150
|
to: recipients,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email.service.js","sourceRoot":"","sources":["../../../../src/core/common/services/email.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mCAAoC;AACpC,2CAAuF;AACvF,yCAA0C;AAG1C,4DAA0E;AAC1E,0DAAgF;AAEhF,mEAAoE;AACpE,qDAAiD;AACjD,yDAAqD;AAO9C,IAAM,YAAY,oBAAlB,MAAM,YAAY;
|
|
1
|
+
{"version":3,"file":"email.service.js","sourceRoot":"","sources":["../../../../src/core/common/services/email.service.ts"],"names":[],"mappings":"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"}
|