feat(07-03): SettingsModule — SMTP config backend + connection test (DKV-05)
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 37s
Tessera CI/CD / Tests (push) Waiting to run

- SmtpConfigDto: host/port/encryption/username/password/fromAddress with class-validator
- SettingsService: getSmtpConfig (SMTP_SAFE_SELECT, no password), saveSmtpConfig (AES-256-GCM
  encryption via CalendarCryptoService, preserve existing password on empty), getDecryptedSmtpConfig
  (internal, used by DkvMailService), testSmtpConfig (nodemailer.verify(), returns boolean, T-07-16),
  getStartupSmtpConfig (tenant-agnostic, used by MailModule factory, D-06)
- SettingsController: GET/PUT /settings/smtp + POST /settings/smtp/test, all @Roles(ADMIN, SUPER_ADMIN)
- SettingsModule: imports CalendarModule, exports SettingsService
- AppModule: imports SettingsModule
This commit is contained in:
2026-06-27 00:09:47 +02:00
parent c7b0103a10
commit 1bec0e76ee
5 changed files with 344 additions and 0 deletions
@@ -0,0 +1,47 @@
import {
IsEmail,
IsIn,
IsInt,
IsNotEmpty,
IsOptional,
IsString,
Max,
Min,
} from 'class-validator';
/**
* DTO for saving or testing an SMTP configuration.
*
* Security: T-07-08 — host/port/encryption validated to prevent open-relay abuse.
* Password field is optional on PUT (empty string = preserve existing stored password).
*/
export class SmtpConfigDto {
@IsString()
@IsNotEmpty()
host!: string;
@IsInt()
@Min(1)
@Max(65535)
port!: number;
/** 'none' | 'starttls' | 'ssl-tls' */
@IsIn(['none', 'starttls', 'ssl-tls'])
encryption!: string;
@IsOptional()
@IsString()
username?: string;
/**
* Plaintext password submitted by the client.
* Empty string means "keep existing stored password" on PUT.
* Never returned in GET responses.
*/
@IsOptional()
@IsString()
password?: string;
@IsEmail()
fromAddress!: string;
}