From a1b2acc19d0ebfc403a8b8cd1e0b7c1b3b886aa5 Mon Sep 17 00:00:00 2001 From: Kai Haase Date: Wed, 4 Feb 2026 16:48:17 +0100 Subject: [PATCH] 11.13.0: Add configurable email verification and sign-up checks with termsAndPrivacyAccepted to BetterAuth (#489) * Add configurable email verification and sign-up checks with termsAndPrivacyAccepted to BetterAuth * Fix perfectionist sort order in interface properties, imports, and provider objects * Add cookie/JWT startup validation, robust token extraction fallback, and comprehensive BetterAuth security tests * Add session-token-to-JWT resolution for cookie-less mode and prioritize the Authorization header over cookies in middleware * Enforce email verification on all auth paths, enrich passkey responses with user data, and resolve JWT sessions for plugin endpoints --- .claude/rules/testing.md | 5 + migration-guides/11.11.x-to-11.12.x.md | 6 +- migration-guides/11.12.x-to-11.13.0.md | 543 +++++++++++++ package-lock.json | 10 +- package.json | 2 +- spectaql.yml | 2 +- src/config.env.ts | 2 + .../interfaces/server-options.interface.ts | 240 ++++++ .../better-auth/INTEGRATION-CHECKLIST.md | 113 +++ src/core/modules/better-auth/README.md | 79 +- .../modules/better-auth/better-auth.config.ts | 210 ++++- .../better-auth/better-auth.resolver.ts | 21 +- .../core-better-auth-api.middleware.ts | 73 +- .../core-better-auth-auth.model.ts | 10 + ...-better-auth-email-verification.service.ts | 433 +++++++++++ .../better-auth/core-better-auth-models.ts | 9 +- ...re-better-auth-signup-validator.service.ts | 178 +++++ .../core-better-auth-user.mapper.ts | 32 +- .../core-better-auth-web.helper.ts | 93 ++- .../core-better-auth.controller.ts | 171 ++++- .../core-better-auth.middleware.ts | 120 ++- .../better-auth/core-better-auth.module.ts | 253 +++++- .../better-auth/core-better-auth.resolver.ts | 160 +++- .../better-auth/core-better-auth.service.ts | 206 ++++- src/core/modules/better-auth/index.ts | 2 + src/core/modules/error-code/error-codes.ts | 45 ++ src/core/modules/user/core-user.model.ts | 15 + .../better-auth/better-auth.controller.ts | 8 +- .../better-auth/better-auth.resolver.ts | 21 +- src/templates/email-verification-de.ejs | 78 ++ src/templates/email-verification-en.ejs | 78 ++ src/test/README.md | 190 +++++ src/test/test.helper.ts | 83 +- tests/stories/auth-scenarios.e2e-spec.ts | 14 +- tests/stories/better-auth-api.story.test.ts | 11 +- ...tter-auth-email-verification.story.test.ts | 724 ++++++++++++++++++ .../better-auth-integration.story.test.ts | 139 ++++ .../better-auth-jwt-middleware.story.test.ts | 606 +++++++++++++++ .../stories/better-auth-plugins.story.test.ts | 286 ++++++- .../better-auth-rest-security.e2e-spec.ts | 396 +++++++++- .../stories/better-auth-security.e2e-spec.ts | 493 ++++++++++++ .../bidirectional-auth-sync.e2e-spec.ts | 8 +- .../scenario-1-legacy-only.e2e-spec.ts | 2 +- tests/stories/scenario-3-http410.e2e-spec.ts | 4 +- tests/stories/scenario-3-iam-only.e2e-spec.ts | 20 +- tests/stories/subscription-auth.e2e-spec.ts | 6 +- tests/stories/three-scenarios.e2e-spec.ts | 8 +- 47 files changed, 5889 insertions(+), 319 deletions(-) create mode 100644 migration-guides/11.12.x-to-11.13.0.md create mode 100644 src/core/modules/better-auth/core-better-auth-email-verification.service.ts create mode 100644 src/core/modules/better-auth/core-better-auth-signup-validator.service.ts create mode 100644 src/templates/email-verification-de.ejs create mode 100644 src/templates/email-verification-en.ejs create mode 100644 src/test/README.md create mode 100644 tests/stories/better-auth-email-verification.story.test.ts create mode 100644 tests/stories/better-auth-jwt-middleware.story.test.ts diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md index 6407e9f0..97da4391 100644 --- a/.claude/rules/testing.md +++ b/.claude/rules/testing.md @@ -104,6 +104,11 @@ await app.listen(3030); - Dynamic port (`0`) avoids port conflicts between parallel tests - Explicit `httpServer.close()` prevents open handle warnings +## TestHelper Reference + +Full documentation for TestHelper (REST, GraphQL, Cookie support): +`src/test/README.md` (also available in `node_modules/@lenne.tech/nest-server/src/test/README.md`) + ## Common Test Issues - **Tests timeout**: Ensure MongoDB is running diff --git a/migration-guides/11.11.x-to-11.12.x.md b/migration-guides/11.11.x-to-11.12.x.md index ed7f6993..700312bf 100644 --- a/migration-guides/11.11.x-to-11.12.x.md +++ b/migration-guides/11.11.x-to-11.12.x.md @@ -7,7 +7,7 @@ | **Breaking Changes** | Cookie structure simplified, `better-auth.session_token` removed | | **New Features** | Auto AppName detection, Token analysis helpers, Cookie-Mode/JWT-Mode dual support | | **Bugfixes** | Passkey/2FA now works correctly in both Cookie and JWT modes | -| **Migration Effort** | Low (~10 minutes) - Only if accessing cookies directly | +| **Migration Effort** | Low (~10 minutes) - Cookie access changes | --- @@ -277,7 +277,7 @@ betterAuth: { The following are now exported from `@lenne.tech/nest-server`: ```typescript -// Token analysis helpers (11.12.0) +// Token analysis helpers export { analyzeToken, getUserIdFromToken, @@ -289,7 +289,7 @@ export { TokenType, } from './core/modules/better-auth/core-better-auth-token.helper'; -// Cookie helper (11.12.0) +// Cookie helper export { AUTH_COOKIE_NAMES, AuthCookieOptions, diff --git a/migration-guides/11.12.x-to-11.13.0.md b/migration-guides/11.12.x-to-11.13.0.md new file mode 100644 index 00000000..1895f101 --- /dev/null +++ b/migration-guides/11.12.x-to-11.13.0.md @@ -0,0 +1,543 @@ +# Migration Guide: 11.12.x → 11.13.0 + +## Overview + +| Category | Details | +|----------|---------| +| **Breaking Changes** | Email verification enabled by default, `termsAndPrivacyAccepted` required by default on sign-up | +| **New Features** | Email Verification service, Sign-Up Checks with `termsAndPrivacyAccepted`, `termsAndPrivacyAcceptedAt` user field, TestHelper cookie support | +| **Bugfixes** | Passkey login response enrichment, Express Response import fix, JWT mode session fallback | +| **Migration Effort** | Low (~10 minutes) - Sign-up flow updates + optional email verification configuration | + +--- + +## Quick Migration + +```bash +# Update package +npm install @lenne.tech/nest-server@11.13.0 + +# Verify build +npm run build + +# Run tests +npm test +``` + +**Note:** Email verification is now **enabled by default** (zero-config). Sign-up requires `termsAndPrivacyAccepted: true`. See Breaking Changes if you need to restore the previous behavior. + +--- + +## What's New in 11.13.0 + +### 1. Email Verification (enabled by default) + +Email verification via Better-Auth's `emailVerification` plugin is now **enabled by default** without any configuration needed: + +- Users receive a verification email after sign-up +- `verifiedAt` is automatically set when email is verified +- Templates provided in German (`de`) and English (`en`) +- Falls back to nest-server's built-in templates if no project templates exist + +```typescript +// Zero-config: email verification is active by default + +// To customize: +betterAuth: { + emailVerification: { + expiresIn: 86400, // Token expiration in seconds (default: 24h) + template: 'custom-verify', // Custom template name + locale: 'en', // Template locale (default: 'en') + brevoTemplateId: 42, // Send via Brevo transactional API (optional) + }, +} + +// To disable email verification: +betterAuth: { + emailVerification: false, +} +``` + +**Configuration behavior:** +- `undefined` / `null` (no config): **enabled** with defaults +- `true`: **enabled** with defaults +- `false`: **disabled** +- `{}`: **enabled** with defaults +- `{ locale: 'de' }`: **enabled** with custom settings +- `{ enabled: false }`: **disabled** (allows pre-configuration) + +**Brevo Integration:** When `brevoTemplateId` is set and Brevo is configured (`config.brevo`), verification emails are sent via Brevo's transactional API instead of SMTP/EJS templates. Template variables: `name`, `link`, `appName`, `expiresIn`. + +### 2. Sign-Up Checks (enabled by default) + +Sign-up validation is now **enabled by default** requiring `termsAndPrivacyAccepted: true`: + +```graphql +# Sign-up now requires termsAndPrivacyAccepted +mutation { + betterAuthSignUp( + email: "user@example.com" + password: "hashedPassword" + name: "User" + termsAndPrivacyAccepted: true # Required by default + ) { + success + user { id email } + } +} +``` + +When accepted, `termsAndPrivacyAcceptedAt` is stored as a `Date` in the user record. + +```typescript +// To disable sign-up checks: +betterAuth: { + signUpChecks: false, +} + +// To customize required fields: +betterAuth: { + signUpChecks: { + requiredFields: ['termsAndPrivacyAccepted', 'ageConfirmed'], + }, +} +``` + +### 3. TestHelper Cookie Support + +The `TestHelper` now supports cookie-based authentication for REST API testing, making it easy to test BetterAuth session-based endpoints: + +```typescript +// Auto-detection: plain session token -> automatically sets iam.session_token + token cookies +const result = await testHelper.rest('/protected-endpoint', { + cookies: sessionToken, +}); + +// Explicit cookie pairs +const result = await testHelper.rest('/protected-endpoint', { + cookies: { 'iam.session_token': token, 'custom': 'value' }, +}); + +// Raw cookie string +const result = await testHelper.rest('/protected-endpoint', { + cookies: 'iam.session_token=abc; token=xyz', +}); +``` + +**New static helper methods:** + +```typescript +// Build BetterAuth cookie record from session token +const cookies = TestHelper.buildBetterAuthCookies(sessionToken); + +// Extract session token from Set-Cookie response headers +const token = TestHelper.extractSessionToken(response); + +// Extract all cookies from response +const allCookies = TestHelper.extractCookies(response); +``` + +`token` (Authorization header) and `cookies` (Cookie header) can be used simultaneously without conflict. + +Full documentation: `src/test/README.md` + +### 4. `termsAndPrivacyAcceptedAt` User Field + +A new `termsAndPrivacyAcceptedAt` field is added to `CoreUserModel`: + +```typescript +// Available on user objects +user.termsAndPrivacyAcceptedAt // Date | undefined +``` + +This field is automatically set when a user signs up with `termsAndPrivacyAccepted: true`. + +--- + +## Breaking Changes + +### 1. Email Verification Now Enabled by Default + +**Before (11.12.x):** Email verification was disabled when no `emailVerification` config was present. + +**After (11.13.0):** Email verification is enabled by default. Users must verify their email after sign-up. + +**Impact:** New sign-ups will trigger a verification email. Unverified users may be restricted depending on your role configuration (e.g., `S_VERIFIED`). + +**To restore old behavior:** +```typescript +// config.env.ts +betterAuth: { + emailVerification: false, +} +``` + +### 2. Sign-Up Now Requires `termsAndPrivacyAccepted` + +**Impact:** Sign-up via BetterAuth will fail if `termsAndPrivacyAccepted: true` is not provided. + +**Before (11.12.x):** +```graphql +mutation { + betterAuthSignUp(email: "user@example.com", password: "pass", name: "User") { + success + } +} +``` + +**After (11.13.0):** +```graphql +mutation { + betterAuthSignUp( + email: "user@example.com" + password: "pass" + name: "User" + termsAndPrivacyAccepted: true # Now required! + ) { + success + } +} +``` + +**To restore old behavior:** +```typescript +// config.env.ts +betterAuth: { + signUpChecks: false, +} +``` + +**For custom resolvers:** Update your `BetterAuthResolver` to include the new parameter and inject `CoreBetterAuthSignUpValidatorService`: + +```typescript +import { CoreBetterAuthSignUpValidatorService } from '@lenne.tech/nest-server'; + +constructor( + betterAuthService: CoreBetterAuthService, + userMapper: CoreBetterAuthUserMapper, + @Optional() signUpValidator?: CoreBetterAuthSignUpValidatorService, +) { + super(betterAuthService, userMapper, signUpValidator); +} + +@Mutation(() => CoreBetterAuthAuthModel) +@Roles(RoleEnum.S_EVERYONE) +override async betterAuthSignUp( + @Args('email') email: string, + @Args('password') password: string, + @Args('name', { nullable: true }) name?: string, + @Args('termsAndPrivacyAccepted', { nullable: true }) termsAndPrivacyAccepted?: boolean, +): Promise { + return super.betterAuthSignUp(email, password, name, termsAndPrivacyAccepted); +} +``` + +--- + +## Detailed Migration Steps + +### Step 1: Update Package + +```bash +npm install @lenne.tech/nest-server@11.13.0 +``` + +### Step 2: Update Sign-Up Calls + +If you have sign-up forms or API calls, add `termsAndPrivacyAccepted: true`: + +**GraphQL:** +```graphql +mutation { + betterAuthSignUp( + email: "user@example.com" + password: "hashedPassword" + name: "User" + termsAndPrivacyAccepted: true # Add this! + ) { + success + } +} +``` + +**REST API:** +```json +POST /iam/sign-up/email +{ + "email": "user@example.com", + "password": "hashedPassword", + "name": "User", + "termsAndPrivacyAccepted": true +} +``` + +**Or disable the requirement in config.env.ts:** +```typescript +betterAuth: { + signUpChecks: false, +} +``` + +### Step 3: Update Custom Resolvers and Controllers (if applicable) + +**Custom BetterAuthResolver:** Update to include: +1. Import `CoreBetterAuthSignUpValidatorService` +2. Add `@Optional() signUpValidator?` to constructor +3. Add `termsAndPrivacyAccepted` parameter to `betterAuthSignUp` method + +See the Breaking Changes section for the code example. + +**Custom BetterAuthController:** Update to include: +1. Import `CoreBetterAuthSignUpValidatorService` +2. Add `@Optional() signUpValidator?` to constructor +3. Pass it to `super()`: + +```typescript +import { CoreBetterAuthSignUpValidatorService } from '@lenne.tech/nest-server'; + +constructor( + betterAuthService: CoreBetterAuthService, + userMapper: CoreBetterAuthUserMapper, + configService: ConfigService, + @Optional() signUpValidator?: CoreBetterAuthSignUpValidatorService, +) { + super(betterAuthService, userMapper, configService, signUpValidator); +} +``` + +### Step 4: Configure Email Verification (optional) + +If you want to customize email verification behavior: + +```typescript +// config.env.ts +betterAuth: { + emailVerification: { + locale: 'de', // German email templates + expiresIn: 172800, // 48h instead of default 24h + template: 'my-verify', // Custom EJS template name + }, +} +``` + +Or create custom templates in your project's templates directory: +- `templates/email-verification-de.ejs` (German) +- `templates/email-verification-en.ejs` (English) +- `templates/email-verification.ejs` (Fallback) + +Available template variables: `name`, `link`, `expiresIn`, `appName`. + +### Step 5: Test Authentication Flows + +Test the following flows in your application: +- [ ] Sign-Up with `termsAndPrivacyAccepted: true` succeeds +- [ ] Sign-Up without `termsAndPrivacyAccepted` returns error `LTNS_0021` +- [ ] Email verification link appears in console (local/development mode) +- [ ] `verifiedAt` is set after email verification +- [ ] `termsAndPrivacyAcceptedAt` is stored in user record +- [ ] Email/Password Sign-In works for verified users +- [ ] Existing users (already verified) are not affected + +--- + +## Compatibility Notes + +### Existing Users + +Existing users in your database are **not affected** by the email verification change. Only new sign-ups trigger verification. + +### Projects Without BetterAuth + +If your project does not use BetterAuth (`betterAuth` not configured), these changes have **no impact**. + +### Frontend Integration + +Update your sign-up forms to include the `termsAndPrivacyAccepted` checkbox: + +```typescript +// Example: Nuxt/Vue +const signUp = async () => { + await authClient.signUp.email({ + email: form.email, + password: form.password, + name: form.name, + termsAndPrivacyAccepted: form.termsAccepted, // Add this + }); +}; +``` + +--- + +## Troubleshooting + +### Sign-Up Fails with Error LTNS_0021 + +**Cause:** `termsAndPrivacyAccepted` is not provided or is `false`. + +**Solution:** Ensure your sign-up form sends `termsAndPrivacyAccepted: true`, or disable the check: +```typescript +betterAuth: { + signUpChecks: false, +} +``` + +### Sign-In Fails with Error LTNS_0023 + +**Cause:** Email verification is enabled (default) and the user has not verified their email yet. The server rejects sign-in attempts with `#LTNS_0023: Email verification required`. + +**Solution:** +1. Ensure the user clicks the verification link sent via email (check console in local mode) +2. Or disable email verification: +```typescript +betterAuth: { + emailVerification: false, +} +``` + +**Note:** This check also applies to 2FA verification (`verifyTotp`) and REST sign-in (`POST /iam/sign-in/email`). Unverified users are blocked from all authentication paths. + +### Users Cannot Sign In After Sign-Up + +**Cause:** Email verification is enabled, and the user has not verified their email yet. + +**Solution:** Check your email configuration (SMTP or Brevo). In local/development mode, the verification URL is printed to the console. Users need to click the verification link before signing in with `S_VERIFIED`-protected endpoints. + +### Email Verification Emails Not Sent + +**Cause:** No email service configured (no SMTP and no Brevo). + +**Solution:** Configure either SMTP or Brevo: +```typescript +// SMTP +email: { + smtp: { host: 'smtp.example.com', port: 587 }, + defaultSender: { email: 'no-reply@example.com', name: 'My App' }, +} + +// Or Brevo +brevo: { + apiKey: process.env.BREVO_API_KEY, +} +betterAuth: { + emailVerification: { + brevoTemplateId: 42, + }, +} +``` + +**Note:** The verification URL is always logged to the console regardless of email configuration. + +### 401 Errors After Switching from Cookie to JWT Mode + +**Cause:** Stale httpOnly session cookies (`iam.session_token`) from a previous cookie-based configuration persist in the browser. These cookies are sent automatically with requests and can interfere with Bearer token authentication, as the server receives conflicting authentication sources. + +**Solution:** httpOnly cookies cannot be cleared via JavaScript. Use server-side sign-out to clear them: +```typescript +// Call sign-out endpoint with credentials to clear httpOnly cookies +await fetch('/api/iam/sign-out', { method: 'POST', credentials: 'include' }); + +// Then sign in fresh with the new configuration +``` + +**Prevention:** When switching from `cookies: true` to `cookies: false`, ensure all users are signed out first. + +### BetterAuth Authentication Fails with `cookies: false` + +**Cause:** When `cookies: false` is set without `betterAuth.jwt: true`, BetterAuth cannot establish sessions. The server will log a startup warning about this misconfiguration. + +**Solution:** When using `cookies: false`, always enable the JWT plugin: +```typescript +// config.env.ts +{ + cookies: false, + betterAuth: { + jwt: true, // Required when cookies: false + }, +} +``` + +**Note:** Even with `jwt: true`, the BetterAuth programmatic API returns session tokens (not JWTs). The JWT plugin enriches only native HTTP handler responses. Session tokens work for authentication via `Authorization: Bearer ` header. + +--- + +## Bugfixes + +### 1. Passkey Login Response Enrichment + +Better Auth's `@better-auth/passkey` plugin returns only `{ session }` in the verify-authentication response, despite its OpenAPI spec declaring both `{ session, user }`. This caused the frontend to not receive user data after passkey login, preventing proper auth state setup and dashboard redirect. + +**Fix:** The API middleware now enriches passkey verify-authentication responses by fetching user data from the database when the response only contains a session. No action needed from consumers. + +### 2. Express Response Import Fix + +The API middleware imported `Response` from Express, which shadowed the global Web API `Response` constructor. This caused `new Response(...)` calls to fail silently in certain environments (e.g., Vitest SSR). The Express import is now aliased as `ExpressResponse`. + +### 3. JWT Mode Session Fallback + +In JWT mode, the session middleware now resolves database sessions for JWT-authenticated users. This ensures that BetterAuth plugin endpoints (2FA, Passkey) can authenticate via session token, even when the client only sends a JWT. + +The middleware also supports JWT tokens stored in cookies (`lt-jwt-token`), bridging the gap between cookie-based frontend storage and JWT verification. + +### 4. Cookie/JWT Startup Validation + +The server now validates authentication configuration at startup and logs warnings for common misconfigurations: +- `cookies: false` without `betterAuth.jwt: true` +- Missing BetterAuth secret + +### 5. Cryptographically Secure ID Generation + +The `generateId()` method in `CoreBetterAuthUserMapper` now uses `crypto.randomBytes(21)` instead of `Math.random()`. This ensures cryptographically secure random IDs for user mapping and internal token generation. + +--- + +## New Exports + +The following are newly exported from `@lenne.tech/nest-server`: + +```typescript +// Email Verification Service +export { CoreBetterAuthEmailVerificationService } from './core/modules/better-auth/core-better-auth-email-verification.service'; + +// Sign-Up Validator Service +export { CoreBetterAuthSignUpValidatorService } from './core/modules/better-auth/core-better-auth-signup-validator.service'; + +// Configuration Interfaces +export { IBetterAuthEmailVerificationConfig, IBetterAuthSignUpChecksConfig } from './core/common/interfaces/server-options.interface'; +``` + +--- + +## New Test Files + +| Test File | Coverage | +|-----------|----------| +| `tests/stories/better-auth-email-verification.story.test.ts` | Email verification flow, LTNS_0023 enforcement, sign-up with verification | +| `tests/stories/better-auth-jwt-middleware.story.test.ts` | JWT-to-session resolution, Authorization header priority over cookies, cookie-less mode session handling | +| `tests/stories/better-auth-security.e2e-spec.ts` | Cookie/JWT startup validation, security configuration checks | + +## TestHelper Documentation + +Full reference for REST, GraphQL, and cookie-based testing: +- **Documentation:** [src/test/README.md](../src/test/README.md) + +--- + +## Module Documentation + +### Better-Auth Module + +- **README:** [src/core/modules/better-auth/README.md](../src/core/modules/better-auth/README.md) +- **Integration Checklist:** [src/core/modules/better-auth/INTEGRATION-CHECKLIST.md](../src/core/modules/better-auth/INTEGRATION-CHECKLIST.md) +- **Reference Implementation:** `src/server/modules/iam/` +- **Key Files:** + - `core-better-auth-email-verification.service.ts` - Email verification logic and template resolution + - `core-better-auth-signup-validator.service.ts` - Sign-up validation with configurable required fields + +--- + +## References + +- [Better-Auth Module](../src/core/modules/better-auth/) - Core module implementation +- [nest-server-starter](https://github.com/lenneTech/nest-server-starter) - Reference implementation +- [Better-Auth Documentation](https://www.better-auth.com/) - Official Better-Auth docs +- [Configurable Features Pattern](../.claude/rules/configurable-features.md) - Configuration patterns used diff --git a/package-lock.json b/package-lock.json index 2b83bd40..3a138b38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lenne.tech/nest-server", - "version": "11.12.0", + "version": "11.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@lenne.tech/nest-server", - "version": "11.12.0", + "version": "11.13.0", "license": "MIT", "dependencies": { "@apollo/server": "5.3.0", @@ -4188,9 +4188,9 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "devOptional": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index fb01b93a..cfc3558a 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lenne.tech/nest-server", - "version": "11.12.0", + "version": "11.13.0", "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).", "keywords": [ "node", diff --git a/spectaql.yml b/spectaql.yml index 35334ece..70c99978 100644 --- a/spectaql.yml +++ b/spectaql.yml @@ -11,7 +11,7 @@ servers: info: title: lT Nest Server description: Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases). - version: 11.12.0 + version: 11.13.0 contact: name: lenne.Tech GmbH url: https://lenne.tech diff --git a/src/config.env.ts b/src/config.env.ts index 97f6143a..da15d6ff 100644 --- a/src/config.env.ts +++ b/src/config.env.ts @@ -125,6 +125,8 @@ const config: { [env: string]: IServerOptions } = { }, automaticObjectIdFiltering: true, betterAuth: { + // Email verification disabled for test environment (no real mailbox available) + emailVerification: false, // JWT enabled by default (zero-config) jwt: { enabled: true, expiresIn: '15m' }, // Passkey auto-activated when URLs can be resolved (env: 'local' → localhost defaults) diff --git a/src/core/common/interfaces/server-options.interface.ts b/src/core/common/interfaces/server-options.interface.ts index eabea3b6..7f4f26e3 100644 --- a/src/core/common/interfaces/server-options.interface.ts +++ b/src/core/common/interfaces/server-options.interface.ts @@ -231,6 +231,128 @@ export interface IAuthRateLimit { */ export type IBetterAuth = IBetterAuthWithoutPasskey | IBetterAuthWithPasskey; +/** + * Email verification configuration for Better-Auth + * + * Controls email verification behavior after sign-up. + * When enabled, users receive a verification email and must verify + * their email address before certain actions are allowed. + * + * **Enabled by Default:** Email verification is enabled by default. + * Set `emailVerification: false` or `emailVerification: { enabled: false }` to disable. + * + * Accepts: + * - `undefined`: Enabled with defaults (zero-config) + * - `true` or `{}`: Enable with defaults (same as undefined) + * - `{ locale: 'de', ... }`: Enable with custom settings + * - `false` or `{ enabled: false }`: Explicitly disable + * + * @since 11.13.0 + * + * @example + * ```typescript + * // Default: Email verification enabled + * betterAuth: {} + * + * // Custom configuration + * betterAuth: { + * emailVerification: { + * locale: 'de', + * autoSignInAfterVerification: true, + * expiresIn: 86400, // 24 hours in seconds + * } + * } + * + * // Disable email verification + * betterAuth: { + * emailVerification: false, + * } + * ``` + */ +export interface IBetterAuthEmailVerificationConfig { + /** + * Whether to automatically sign in the user after email verification. + * @default true + */ + autoSignInAfterVerification?: boolean; + + /** + * Brevo template ID for verification emails. + * When set and Brevo is configured (config.brevo), verification emails + * are sent via Brevo's transactional API instead of SMTP/EJS templates. + * + * Template variables passed to Brevo: + * - `name`: User display name + * - `link`: Verification URL + * - `appName`: Application name + * - `expiresIn`: Formatted expiration time + * + * @default undefined (uses SMTP/EJS templates) + */ + brevoTemplateId?: number; + + /** + * Frontend callback URL for email verification. + * + * When set, the verification link in the email will point to this URL + * with the token as a query parameter (e.g., `{callbackURL}?token=xxx`). + * The frontend page should then call the backend verify-email endpoint + * to complete verification. + * + * Supports both absolute URLs and relative paths: + * - Absolute: `https://example.com/auth/verify-email` + * - Relative: `/auth/verify-email` (resolved against `appUrl`) + * + * When not set, the verification link points directly to the backend + * endpoint which handles verification and redirects. + * + * @default undefined (backend-handled verification) + * @since 11.13.0 + */ + callbackURL?: string; + + /** + * Whether email verification is enabled. + * @default true (enabled by default when BetterAuth is active) + */ + enabled?: boolean; + + /** + * Time in seconds until the verification link expires. + * @default 86400 (24 hours) + */ + expiresIn?: number; + + /** + * Locale for the verification email template. + * Used to select the correct language template. + * @default 'en' + */ + locale?: string; + + /** + * Cooldown in seconds between resend requests for the same email address. + * Prevents abuse by limiting how often verification emails can be resent. + * Applied per email address in-memory. + * + * @default 60 + * @since 11.13.0 + */ + resendCooldownSeconds?: number; + + /** + * Custom template name for the verification email. + * The system looks for templates in this order: + * 1. `