# Migration Guide: 11.9.x → 11.10.x

## Overview

| Category | Details |
|----------|---------|
| **Breaking Changes** | None (trustedOrigins now auto-detected from baseUrl) |
| **New Features** | **Zero-Config Passkey**, URL Auto-Detection, Database Challenge Storage (JWT mode), Logging helpers, BetterAuthTokenService, Secure-by-Default RolesGuard, RolesGuardRegistry |
| **Bugfixes** | Session token forwarding, RolesGuard enforcement in IAM-only mode |
| **Migration Effort** | Very Low (~5 minutes) - Most projects need no changes |

---

## Quick Migration

### All Projects

```bash
# Update package
npm install @lenne.tech/nest-server@11.10.x

# Verify build
npm run build

# Run tests
npm test
```

**That's it!** Most projects need no code changes due to Zero-Config improvements.

### New in 11.10.3: Zero-Config Passkey

Passkey now works without explicit configuration! URL auto-detection makes setup much simpler:

```typescript
// config.env.ts - BEFORE (11.10.0-11.10.2)
betterAuth: {
  passkey: true,
  trustedOrigins: ['http://localhost:3001'],  // Was required
}

// config.env.ts - AFTER (11.10.3) - Zero-Config!
// Option 1: Use env: 'local' for development (auto: API=:3000, App=:3001)
const config = {
  env: 'local',  // Passkey auto-activated with localhost defaults
};

// Option 2: Set root-level baseUrl for production
const config = {
  baseUrl: 'https://api.example.com',  // Auto-detects appUrl, rpId, trustedOrigins
  env: 'production',
};
```

### IAM-only Projects (No Changes Required)

If you use the 1-parameter `CoreModule.forRoot(environment)` signature (IAM-only, no Legacy Auth), **no changes are required**. Since **11.10.3**, `registerRolesGuardGlobally` defaults to `true`, ensuring `@Roles()` decorators work automatically.

```typescript
// server.module.ts - RolesGuard is registered automatically!
CoreBetterAuthModule.forRoot({
  config: environment.betterAuth,
  // registerRolesGuardGlobally: true  // Now the default! No need to specify.
})
```

**Note:** Projects upgrading from 11.10.0-11.10.2 that already have `registerRolesGuardGlobally: true` can keep or remove it - the behavior is unchanged.

---

## What's New in 11.10.x

### 1. Zero-Config Passkey with URL Auto-Detection (11.10.3)

**All three plugins (JWT, 2FA, Passkey) are now enabled by default!**

Passkey configuration is now automatically derived from your server configuration:

| Config Source | `baseUrl` (API) | `appUrl` (Frontend) | Passkey |
|---------------|-----------------|---------------------|---------|
| `env: 'local'/'ci'/'e2e'` | `http://localhost:3000` | `http://localhost:3001` | ✅ auto |
| Root-level `baseUrl` set | as configured | derived (removes `api.` prefix) | ✅ auto |
| Neither set | - | - | ⚠️ disabled (warning) |

**URL Auto-Detection Logic:**
1. `appUrl` is derived from `baseUrl` by removing `api.` prefix (e.g., `api.example.com` → `example.com`)
2. `rpId` is extracted from `appUrl` hostname (e.g., `example.com`)
3. `origin` = `appUrl` (e.g., `https://example.com`)
4. `trustedOrigins` = `[appUrl]`

```typescript
// RECOMMENDED: Set root-level baseUrl
const config = {
  baseUrl: 'https://api.example.com',  // Passkey values auto-detected
  env: 'production',
};
// Auto-detected: rpId='example.com', origin='https://example.com', trustedOrigins=['https://example.com']

// For development: Use env: 'local'
const config = {
  env: 'local',  // Uses localhost:3000 (API), localhost:3001 (App)
};
```

**Graceful Degradation:** If URLs cannot be resolved, Passkey is disabled with a warning - other auth methods continue to work.

### 2. Database Challenge Storage for JWT Mode (11.10.3)

New `CoreBetterAuthChallengeService` enables Passkey authentication in **cookieless/JWT mode**.

**Problem:** Better-Auth's Passkey plugin uses cookies to store WebAuthn challenges. This doesn't work with JWT-based APIs.

**Solution:** An adapter pattern bridges the gap:
1. On `generate-options`: Extract Better-Auth's `verificationToken` from Set-Cookie, store mapping in MongoDB
2. Return `challengeId` to client (not the verificationToken for security)
3. On `verify`: Client sends `challengeId`, we inject `verificationToken` as cookie for Better-Auth

**Security measures:**
- 256-bit entropy challenge IDs (`crypto.randomBytes(32)`)
- TTL-based MongoDB index for automatic cleanup
- One-time use (deleted after verification)
- User binding (challenges tied to specific user)

**Client-side usage (JWT mode):**
```typescript
// Use composable methods which handle challengeId automatically
const { authenticateWithPasskey, registerPasskey } = useBetterAuth();

// Authentication
const result = await authenticateWithPasskey();

// Registration
const result = await registerPasskey('My Device');
```

### 3. TypeScript Compile-Time Validation for Passkey CORS (11.10.0)

The `IBetterAuth` type enforces `trustedOrigins` when Passkey is enabled. **Note:** With URL auto-detection in 11.10.3, this is automatically handled.

### 2. Logging Helper for Secure Logging

New helper functions for safely logging sensitive data (GDPR compliant):

```typescript
import { maskToken, maskEmail, maskCookieHeader, isProduction } from '@lenne.tech/nest-server';

// Mask tokens: 'abc123xyz789' → 'abc1...9789'
this.logger.debug(`Token: ${maskToken(token)}`);

// Mask emails: 'john.doe@example.com' → 'jo***@example.com'
this.logger.debug(`User: ${maskEmail(user.email)}`);

// Mask cookie headers: 'session=abc123; token=xyz' → 'session=***; token=***'
this.logger.debug(`Cookies: ${maskCookieHeader(req.headers.cookie)}`);

// Conditional logging for non-production only
if (!isProduction()) {
  this.logger.debug('Debug info...');
}
```

### 3. Improved Session Lookup Performance

The `getSessionByToken()` method now uses a MongoDB aggregation pipeline for single-query session+user lookup, improving performance and handling both ObjectId and string userId formats automatically.

### 4. Enhanced Cookie Handling for Plugin Compatibility

Session tokens are now set in multiple cookies to ensure compatibility with Better Auth plugins (especially Passkey):

| Cookie | Purpose |
|--------|---------|
| `token` | nest-server compatibility |
| `{basePath}.session_token` | Better Auth native (e.g., `iam.session_token`) |
| `better-auth.session_token` | Legacy Better Auth |

### 5. Native Better Auth Plugin Endpoints

2FA and Passkey endpoints now use Better Auth's native plugin handlers, providing more features:

**Two-Factor Authentication (2FA):**

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/iam/two-factor/enable` | POST | Enable 2FA, get TOTP URI |
| `/iam/two-factor/disable` | POST | Disable 2FA |
| `/iam/two-factor/verify-totp` | POST | Verify TOTP code |
| `/iam/two-factor/generate-backup-codes` | POST | Generate backup codes |
| `/iam/two-factor/verify-backup-code` | POST | Verify backup code |

**Passkey (WebAuthn):**

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/iam/passkey/generate-register-options` | POST | Get WebAuthn registration options |
| `/iam/passkey/verify-registration` | POST | Verify and store passkey |
| `/iam/passkey/generate-authenticate-options` | POST | Get WebAuthn authentication options |
| `/iam/passkey/verify-authentication` | POST | Verify passkey authentication |
| `/iam/passkey/list-user-passkeys` | POST | List user's passkeys |
| `/iam/passkey/delete-passkey` | POST | Delete a passkey |
| `/iam/passkey/update-passkey` | POST | Update passkey name |

### 6. BetterAuthTokenService

New centralized service for token extraction and user loading in BetterAuth authentication.

**What the service provides:**
- Token extraction from Authorization header or cookies
- JWT token verification via BetterAuth
- Session token verification via database lookup
- User loading from MongoDB with `hasRole()` capability

**Benefits:**
- Consolidates token verification logic that was previously duplicated in AuthGuard and RolesGuard
- Lazy resolution: Guards use `ModuleRef.get()` with `{ strict: false }` for optional dependencies
- Improved testability and maintainability

```typescript
import { BetterAuthTokenService } from '@lenne.tech/nest-server';

// In a guard or service
const { token, source } = this.tokenService.extractTokenFromRequest(request);
if (token) {
  const user = await this.tokenService.verifyAndLoadUser(token);
  if (user) {
    request.user = user;
  }
}
```

### 7. Secure-by-Default RolesGuard (11.10.3)

The `registerRolesGuardGlobally` option now defaults to `true` for security-by-default behavior.

**Problem (11.10.0-11.10.2):**
In IAM-only setups, `@Roles()` decorators were not enforced unless `registerRolesGuardGlobally: true` was explicitly set.

**Solution (11.10.3+):**
```typescript
// server.module.ts - IAM-only Setup
@Module({
  imports: [
    CoreModule.forRoot(environment),  // 1-parameter signature = IAM-only
    CoreBetterAuthModule.forRoot({
      config: environment.betterAuth,
      // registerRolesGuardGlobally defaults to true - @Roles() decorators work automatically!
    }),
  ],
})
export class ServerModule {}
```

**Behavior:**
- `registerRolesGuardGlobally: true` (default) - RolesGuard registered globally, `@Roles()` enforced
- `registerRolesGuardGlobally: false` - Explicit opt-out (shows security warning if no other guard registered)

**RolesGuardRegistry (Advanced):**

A shared `RolesGuardRegistry` prevents duplicate registrations when both CoreAuthModule (Legacy) and CoreBetterAuthModule (IAM) are used together:

```typescript
import { RolesGuardRegistry } from '@lenne.tech/nest-server';

// Check if RolesGuard is already registered
if (RolesGuardRegistry.isRegistered()) {
  console.log(`Registered by: ${RolesGuardRegistry.getRegisteredBy()}`);
}
```

**Security Warning:**

If `registerRolesGuardGlobally: false` is explicitly set and no other guard is registered, a warning is logged:

```
⚠️ SECURITY WARNING: registerRolesGuardGlobally is explicitly set to false,
but no RolesGuard is registered globally. @Roles() decorators will NOT enforce access control!
```

---

## Breaking Changes

### No Breaking Changes in 11.10.3

With URL auto-detection, the previous requirement for explicit `trustedOrigins` is now resolved automatically.

**For 11.10.0-11.10.2 users:** The TypeScript requirement for `trustedOrigins` with Passkey is now handled by auto-detection. You can simplify your config:

**Before (11.10.0-11.10.2):**
```typescript
betterAuth: {
  passkey: true,
  trustedOrigins: ['http://localhost:3001'],  // Was required
}
```

**After (11.10.3) - Zero-Config:**
```typescript
// Just set env: 'local' or root-level baseUrl - trustedOrigins auto-detected!
const config = {
  env: 'local',
  // Passkey works automatically!
};
```

### Historical: `trustedOrigins` Requirement (11.10.0-11.10.2)

**Note:** This was resolved in 11.10.3 with URL auto-detection.

**Before (11.9.x):**
```typescript
betterAuth: {
  passkey: true,
  // trustedOrigins was optional (caused runtime CORS errors)
}
```

**In 11.10.0-11.10.2:**
```typescript
betterAuth: {
  passkey: true,
  trustedOrigins: ['http://localhost:3001', 'https://app.example.com'],
}
```

### 2. 2FA Endpoint Changes

**Impact:** Projects calling 2FA endpoints directly

The endpoint for verifying TOTP codes changed:

| Before (11.9.x) | After (11.10.x) |
|-----------------|-----------------|
| `POST /iam/two-factor/verify` | `POST /iam/two-factor/verify-totp` |

Additionally, new endpoints are available for backup codes.

---

## Bugfixes

### 2FA Session Token Cookie Handling

**Fixed Issue:** When signing in with 2FA enabled, the temporary session token was not being set as a cookie. This caused the 2FA verification step (`/iam/two-factor/verify-totp`) to fail with 401 Unauthorized because the browser couldn't authenticate the request.

**What was happening:**
1. User signs in with email/password
2. Server returns `requiresTwoFactor: true` but no cookie was set
3. User enters TOTP code
4. Request fails because there's no session token to identify the 2FA flow

**What's fixed:**
1. User signs in with email/password
2. Server returns `requiresTwoFactor: true` AND sets the temporary session token as a cookie
3. User enters TOTP code
4. Browser automatically sends the session cookie → verification succeeds

**No action required** - this is a bugfix that improves 2FA functionality without requiring any code changes.

### RolesGuard Enforcement in IAM-only Mode

**Fixed Issue:** In IAM-only setups (CoreModule.forRoot with 1 parameter), `@Roles()` decorators were not automatically enforced because the RolesGuard was not globally registered.

**Symptom:** Endpoints with `@Roles(RoleEnum.ADMIN)` were accessible without authentication.

**Cause:** CoreAuthModule (which globally registers the RolesGuard) is not imported in IAM-only setups.

**Solution (11.10.3+):** `registerRolesGuardGlobally` now defaults to `true`. **No action required** - `@Roles()` decorators are automatically enforced.

```typescript
// Works automatically in 11.10.3+
CoreBetterAuthModule.forRoot({
  config: environment.betterAuth,
  // registerRolesGuardGlobally defaults to true
})
```

**Note:** Projects upgrading from 11.10.0-11.10.2 that already have `registerRolesGuardGlobally: true` can keep or remove it.

---

## Detailed Migration Steps

### Step 1: Update Package

```bash
npm install @lenne.tech/nest-server@11.10.x
```

### Step 2: Add trustedOrigins (If Using Passkey)

If you have `passkey: true` in your config, add `trustedOrigins`:

```typescript
// config.env.ts
betterAuth: {
  passkey: true,
  trustedOrigins: [
    'http://localhost:3001',      // Development frontend
    'https://app.example.com',    // Production frontend
  ],
}
```

### Step 3: Update 2FA API Calls (If Using 2FA)

If your frontend calls the 2FA verify endpoint, update the path:

```typescript
// Before
await fetch('/iam/two-factor/verify', { body: { code: '123456' } });

// After
await fetch('/iam/two-factor/verify-totp', { body: { code: '123456' } });
```

### Step 4: Verify RolesGuard (IAM-only Setup)

**No changes required for 11.10.3+.** The `registerRolesGuardGlobally` option now defaults to `true`.

If you upgraded from 11.10.0-11.10.2 and added `registerRolesGuardGlobally: true`, you can optionally remove it:

```typescript
// server.module.ts - 11.10.3+ (registerRolesGuardGlobally defaults to true)
CoreBetterAuthModule.forRoot({
  config: environment.betterAuth,
  // registerRolesGuardGlobally: true,  // Optional - this is now the default
})
```

**How to check if you're IAM-only:**
- 1-parameter: `CoreModule.forRoot(environment)` → IAM-only, RolesGuard registered automatically
- 3-parameter: `CoreModule.forRoot(CoreAuthService, AuthModule.forRoot(...), environment)` → Legacy + IAM, RolesGuard registered by CoreAuthModule

### Step 5: Verify Build and Tests

```bash
npm run build
npm test
```

---

## Compatibility Notes

### Existing Projects Without Passkey

No changes required. The migration is seamless.

### Existing Projects With 2FA Only

No breaking changes for basic 2FA usage. The new endpoints provide additional features (backup codes) but the core flow remains the same.

### Custom BetterAuthController Extensions

If you extend `CoreBetterAuthController`, note that the `handleBetterAuthPlugins()` method is now used for plugin endpoints. Your existing overrides should continue to work.

---

## Troubleshooting

### Passkey Disabled Warning

**Symptom:** Console shows `⚠️ PASSKEY DISABLED: Cannot auto-detect required values`

**Solution:** Ensure URLs can be resolved via one of these methods:
```typescript
// Option 1: Set env for local development
const config = { env: 'local' };

// Option 2: Set root-level baseUrl for production
const config = { baseUrl: 'https://api.example.com', env: 'production' };

// Option 3: Explicit passkey config (not recommended)
betterAuth: {
  passkey: { rpId: 'example.com', origin: 'https://example.com' },
  trustedOrigins: ['https://example.com'],
}
```

### CORS Error with Passkey

**Symptom:** Browser console shows CORS error when using Passkey

**Solution:** Ensure `trustedOrigins` is correctly auto-detected. Check console for URL resolution warnings. If auto-detection fails, provide explicit `trustedOrigins`:
```typescript
betterAuth: {
  trustedOrigins: ['http://localhost:3001'],  // Include protocol and port!
}
```

### Passkey Challenge Mapping Not Found (JWT Mode)

**Symptom:** `Challenge mapping not found` error when verifying passkey

**Solution:** Ensure you're using the composable methods that handle `challengeId`:
```typescript
// CORRECT: Use composable methods
const { authenticateWithPasskey } = useBetterAuth();
await authenticateWithPasskey();

// WRONG: Direct client call (doesn't handle challengeId in JWT mode)
await authClient.signIn.passkey();
```

### 2FA Verify Returns 404

**Symptom:** `POST /iam/two-factor/verify` returns 404

**Solution:** Update to new endpoint: `POST /iam/two-factor/verify-totp`

### Debug Logging Not Appearing

**Symptom:** Debug logs missing in development

**Solution:** Debug logs are now suppressed in production. Ensure `NODE_ENV` is not set to `production` during development.

### Protected Endpoints Accessible Without Authentication (IAM-only)

**Symptom:** Endpoints with `@Roles(RoleEnum.ADMIN)` are accessible without authentication in IAM-only setups.

**Cause (11.10.0-11.10.2):** RolesGuard was not globally registered by default.

**Solution:** Upgrade to 11.10.3+. The `registerRolesGuardGlobally` option now defaults to `true`.

If you explicitly set `registerRolesGuardGlobally: false`, ensure you have another guard mechanism in place:
```typescript
// Check console for security warning:
// ⚠️ SECURITY WARNING: registerRolesGuardGlobally is explicitly set to false...
```

---

## Module Documentation

### BetterAuth 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/better-auth/`

---

## New Exports

The following are now exported from `@lenne.tech/nest-server`:

```typescript
// Logging helpers (11.10.0)
export { isProduction, maskCookieHeader, maskEmail, maskId, maskSensitive, maskToken } from './core/common/helpers/logging.helper';

// BetterAuth types (updated)
export { IBetterAuth, IBetterAuthWithPasskey, IBetterAuthWithoutPasskey } from './core/common/interfaces/server-options.interface';

// BetterAuthTokenService (11.10.2)
export { BetterAuthTokenService, TokenExtractionResult } from './core/modules/better-auth/better-auth-token.service';

// CoreBetterAuthChallengeService (11.10.3) - Database challenge storage for JWT mode
export { CoreBetterAuthChallengeService } from './core/modules/better-auth/core-better-auth-challenge.service';

// RolesGuardRegistry (11.10.3) - Prevents duplicate global RolesGuard registrations
export { RolesGuardRegistry } from './core/modules/auth/guards/roles-guard-registry';
```

---

## References

- [BetterAuth README](../src/core/modules/better-auth/README.md)
- [BetterAuth Integration Checklist](../src/core/modules/better-auth/INTEGRATION-CHECKLIST.md)
- [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
- [Better Auth Documentation](https://www.better-auth.com/docs)
