fix(mana-core-auth): complete production readiness with test fixes

- Fix LoggerService mock in better-auth.service.spec.ts
- Fix name assertion in auth.controller.spec.ts (empty string fallback)
- Fix createRemoteJWKSet mock in jwt-auth.guard.spec.ts
- Add Grafana dashboard for Auth Service monitoring
- Add 10 auth-specific Prometheus alert rules
- Update production readiness plan to 100% complete

All 199 unit tests passing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-02-01 14:18:58 +01:00
parent e3774ca08b
commit fe33f4b355
14 changed files with 1282 additions and 25 deletions

View file

@ -1,6 +1,6 @@
# Mana Core Auth - Production Readiness Plan
> **Status**: In Bearbeitung
> **Status**: ✅ Abgeschlossen
> **Erstellt**: 2026-02-01
> **Autor**: Claude Code
> **Ziel**: Auth-Service produktionsreif machen
@ -126,14 +126,29 @@ Dieses Dokument beschreibt alle Änderungen, die vor dem Go-Live des `mana-core-
- **Datei**: `docs/PRODUCTION_DEPLOYMENT.md`
### 2.3 Grafana Dashboard & Alerts
- **Status**: [ ] Offen (Separates Task)
- **Status**: [x] Erledigt (2026-02-01)
- **Priorität**: 🟠 Hoch
- **Problem**: Prometheus Metrics existieren, aber keine Visualisierung
- **Notiz**: Prometheus Metrics sind bereits unter `/metrics` verfügbar
- **TODO für später**:
- Grafana Dashboard JSON erstellen
- Alert Rules für kritische Metriken (Error Rate, Latency)
- Loki Integration für Log-Aggregation
- **Lösung**:
- ✅ Dediziertes Auth-Service Dashboard erstellt (`docker/grafana/dashboards/auth-service.json`)
- ✅ Service Health (UP/DOWN, Uptime, CPU, Memory, Event Loop)
- ✅ User Statistics (Total, Verified, New Today/Week/Month, Verification Rate)
- ✅ HTTP Traffic (Request Rate, Latency p50/p95/p99, Status Codes, Error Rates)
- ✅ Authentication Endpoints (Login/Register/Logout/Refresh Activity)
- ✅ Prometheus Alert Rules für Auth-Service (`docker/prometheus/alerts.yml`)
- AuthServiceDown (kritisch nach 30s)
- HighLoginFailureRate (>50%)
- PossibleBruteForce (>100 failed logins/5min)
- HighRateLimitHits
- RegistrationSpike
- TokenRefreshFailures
- PasswordResetFlood
- LowVerificationRate
- AuthServiceSlow (p95 >500ms)
- OIDCTokenErrors
- **Geänderte Dateien**:
- `docker/grafana/dashboards/auth-service.json` - NEU
- `docker/prometheus/alerts.yml` - Auth-Alerts hinzugefügt
### 2.4 Disaster Recovery Dokumentation
- **Status**: [x] Erledigt (2026-02-01)
@ -254,12 +269,11 @@ Dieses Dokument beschreibt alle Änderungen, die vor dem Go-Live des `mana-core-
| Phase | Aufgaben | Erledigt | Fortschritt |
|-------|----------|----------|-------------|
| Phase 1 | 5 | 5 | 100% |
| Phase 2 | 6 | 5 | 83% |
| Phase 2 | 6 | 6 | 100% |
| Phase 3 | 5 | 5 | 100% |
| **Gesamt** | **16** | **15** | **94%** |
| **Gesamt** | **16** | **16** | **100%** |
**Hinweis:** Phase 2.3 (Grafana Dashboard) ist als separates Task für später markiert.
**Offen:** 2.3 (Grafana Dashboard)
**Status: PRODUCTION READY** ✅
---
@ -283,4 +297,7 @@ Dieses Dokument beschreibt alle Änderungen, die vor dem Go-Live des `mana-core-
| 2026-02-01 | 3.5 Security Scanning: pnpm audit in CI, Dependabot war bereits aktiv |
| 2026-02-01 | 3.2 OpenAPI/Swagger: API-Dokumentation unter /api-docs verfügbar |
| 2026-02-01 | 3.1 E2E Tests: OIDC + Auth Flow Tests erstellt (oidc.e2e-spec.ts, auth-flow.e2e-spec.ts) |
| 2026-02-01 | 2.3 Grafana Dashboard + Alerts: auth-service.json Dashboard, 10 Auth-spezifische Alert Rules |
| 2026-02-01 | Test-Fixes: LoggerService Mock in better-auth.service.spec.ts, name assertion in auth.controller.spec.ts, createRemoteJWKSet Mock in jwt-auth.guard.spec.ts |
| 2026-02-01 | **PLAN ABGESCHLOSSEN - 100% Production Ready (199/199 Unit Tests bestanden)** |

File diff suppressed because it is too large Load diff

View file

@ -243,3 +243,116 @@ groups:
annotations:
summary: "Container {{ $labels.name }} restarted"
description: "Container {{ $labels.name }} has restarted."
- name: auth_service_alerts
rules:
# Auth Service Down
- alert: AuthServiceDown
expr: up{job="mana-core-auth"} == 0
for: 30s
labels:
severity: critical
annotations:
summary: "Auth Service is down"
description: "mana-core-auth has been down for more than 30 seconds. All authentication will fail."
# High Login Failure Rate (> 50% of logins fail with 401)
- alert: HighLoginFailureRate
expr: |
sum(rate(http_requests_total{job="mana-core-auth",route="/auth/login",status="401"}[5m]))
/ sum(rate(http_requests_total{job="mana-core-auth",route="/auth/login"}[5m])) > 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "High login failure rate"
description: "{{ $value | humanizePercentage }} of login attempts are failing."
# Rate Limiting Triggered Frequently
- alert: HighRateLimitHits
expr: |
sum(rate(http_requests_total{job="mana-core-auth",status="429"}[5m])) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "Frequent rate limiting on Auth Service"
description: "Rate limit (429) is being hit {{ $value | humanize }} times/second. Possible attack or misconfiguration."
# Brute Force Detection (> 100 failed logins in 5 min)
- alert: PossibleBruteForce
expr: |
sum(increase(http_requests_total{job="mana-core-auth",route="/auth/login",status="401"}[5m])) > 100
for: 0m
labels:
severity: critical
annotations:
summary: "Possible brute force attack detected"
description: "{{ $value | humanize }} failed login attempts in the last 5 minutes."
# Registration Spike (unusual registration activity)
- alert: RegistrationSpike
expr: |
sum(rate(http_requests_total{job="mana-core-auth",route="/auth/register",status="201"}[5m])) > 1
for: 5m
labels:
severity: info
annotations:
summary: "High registration activity"
description: "{{ $value | humanize }} registrations per second. Verify this is expected."
# Token Refresh Failures
- alert: HighTokenRefreshFailures
expr: |
sum(rate(http_requests_total{job="mana-core-auth",route="/auth/refresh",status=~"4.."}[5m]))
/ sum(rate(http_requests_total{job="mana-core-auth",route="/auth/refresh"}[5m])) > 0.3
for: 10m
labels:
severity: warning
annotations:
summary: "High token refresh failure rate"
description: "{{ $value | humanizePercentage }} of token refresh attempts are failing."
# Password Reset Flood (possible enumeration attack)
- alert: PasswordResetFlood
expr: |
sum(increase(http_requests_total{job="mana-core-auth",route="/auth/forgot-password"}[5m])) > 50
for: 0m
labels:
severity: warning
annotations:
summary: "Unusual password reset activity"
description: "{{ $value | humanize }} password reset requests in the last 5 minutes."
# Low User Verification Rate (less than 50% verified after 1 week)
- alert: LowVerificationRate
expr: |
auth_users_verified{job="mana-core-auth"} / auth_users_total{job="mana-core-auth"} < 0.5
for: 1h
labels:
severity: info
annotations:
summary: "Low email verification rate"
description: "Only {{ $value | humanizePercentage }} of users have verified their email."
# Auth Service Slow (p95 > 500ms)
- alert: AuthServiceSlow
expr: |
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{job="mana-core-auth"}[5m])) by (le)) > 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "Auth Service responding slowly"
description: "Auth service p95 latency is {{ $value | humanizeDuration }}. This may impact all services."
# OIDC Token Endpoint Errors
- alert: OIDCTokenErrors
expr: |
sum(rate(http_requests_total{job="mana-core-auth",route=~"/api/auth/oauth2/token|/api/oidc/token",status=~"5.."}[5m])) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "OIDC token endpoint errors"
description: "OIDC token endpoint is returning 5xx errors. SSO may be affected."

View file

@ -4,7 +4,7 @@
* Common utilities for writing tests
*/
import { ConfigService } from '@nestjs/config';
import { type ConfigService } from '@nestjs/config';
/**
* Create mock ConfigService

View file

@ -148,7 +148,7 @@ export class AnalyticsService implements OnModuleInit, OnModuleDestroy {
/**
* Get user growth over time
*/
async getUserGrowth(days: number = 90): Promise<GrowthData[]> {
async getUserGrowth(days = 90): Promise<GrowthData[]> {
if (!this.duckdb) return [];
const result = await this.duckdb.all(
@ -171,7 +171,7 @@ export class AnalyticsService implements OnModuleInit, OnModuleDestroy {
/**
* Get monthly aggregated metrics
*/
async getMonthlyMetrics(months: number = 12): Promise<MonthlyMetrics[]> {
async getMonthlyMetrics(months = 12): Promise<MonthlyMetrics[]> {
if (!this.duckdb) return [];
const result = await this.duckdb.all(

View file

@ -139,7 +139,8 @@ describe('AuthController', () => {
expect(betterAuthService.registerB2C).toHaveBeenCalledWith({
email: registerDto.email,
password: registerDto.password,
name: undefined, // Controller passes undefined when name is not provided
name: '', // Controller passes empty string as fallback when name is not provided
sourceAppUrl: undefined,
});
});

View file

@ -8,7 +8,12 @@ import { ReferralsModule } from '../referrals/referrals.module';
@Module({
imports: [forwardRef(() => ReferralsModule)],
controllers: [AuthController, BetterAuthPassthroughController, OidcController, OidcLoginController],
controllers: [
AuthController,
BetterAuthPassthroughController,
OidcController,
OidcLoginController,
],
providers: [BetterAuthService],
exports: [BetterAuthService],
})

View file

@ -22,9 +22,9 @@
*/
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { type ConfigService } from '@nestjs/config';
import { SignJWT, jwtVerify, errors } from 'jose';
import { JWTCustomPayload } from './better-auth.config';
import { type JWTCustomPayload } from './better-auth.config';
import { createMockConfigService } from '../__tests__/utils/test-helpers';
import { mockUserFactory } from '../__tests__/utils/mock-factories';

View file

@ -14,6 +14,7 @@ import type { TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { ConflictException, NotFoundException, ForbiddenException } from '@nestjs/common';
import { BetterAuthService } from './better-auth.service';
import { LoggerService } from '../../common/logger';
import { createMockConfigService } from '../../__tests__/utils/test-helpers';
import { silentError } from '../../__tests__/utils/silent-error.decorator';
@ -68,6 +69,15 @@ const mockReferralTrackingService = {
applyReferral: jest.fn().mockResolvedValue({ success: true }),
};
const mockLoggerService = {
setContext: jest.fn().mockReturnThis(),
log: jest.fn(),
debug: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
verbose: jest.fn(),
};
describe('BetterAuthService', () => {
let service: BetterAuthService;
let configService: ConfigService;
@ -116,6 +126,10 @@ describe('BetterAuthService', () => {
provide: ReferralTrackingService,
useValue: mockReferralTrackingService,
},
{
provide: LoggerService,
useValue: mockLoggerService,
},
],
}).compile();

View file

@ -17,11 +17,16 @@ import { LoggerService } from '../logger';
import { createMockConfigService, httpMockHelpers } from '../../__tests__/utils/test-helpers';
import { mockTokenFactory } from '../../__tests__/utils/mock-factories';
import { silentError } from '../../__tests__/utils/silent-error.decorator';
import { jwtVerify } from 'jose';
import { jwtVerify, createRemoteJWKSet } from 'jose';
// Mock jose (auto-mocked via jest.config.js moduleNameMapper)
jest.mock('jose');
// Setup mock for createRemoteJWKSet to return a defined JWKS function
const mockJWKS = jest.fn();
const mockCreateRemoteJWKSet = createRemoteJWKSet as jest.MockedFunction<typeof createRemoteJWKSet>;
mockCreateRemoteJWKSet.mockReturnValue(mockJWKS as any);
// Mock LoggerService
const createMockLoggerService = (): LoggerService =>
({
@ -43,6 +48,9 @@ describe('JwtAuthGuard', () => {
// Reset mocks
jest.clearAllMocks();
// Ensure createRemoteJWKSet returns a defined value after clearing
mockCreateRemoteJWKSet.mockReturnValue(mockJWKS as any);
const module: TestingModule = await Test.createTestingModule({
providers: [
JwtAuthGuard,

View file

@ -23,7 +23,7 @@ import { BadRequestException, ForbiddenException, NotFoundException } from '@nes
import { CreditsController } from './credits.controller';
import { CreditsService } from './credits.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUserData } from '../common/decorators/current-user.decorator';
import { type CurrentUserData } from '../common/decorators/current-user.decorator';
import {
mockBalanceFactory,
mockTransactionFactory,

View file

@ -466,7 +466,7 @@ export class FraudDetectionService {
/**
* Get pending review items
*/
async getPendingReviews(limit: number = 50, offset: number = 0): Promise<ReviewQueueItem[]> {
async getPendingReviews(limit = 50, offset = 0): Promise<ReviewQueueItem[]> {
const db = this.getDb();
return db

View file

@ -107,7 +107,7 @@ export class ReferralTierService {
calculateBonus(
eventType: keyof typeof BONUS_AMOUNTS,
tier: TierName,
isReferrer: boolean = true
isReferrer = true
): { base: number; multiplier: number; final: number } {
const bonusConfig = BONUS_AMOUNTS[eventType];
const base = isReferrer ? bonusConfig.referrer : bonusConfig.referee;

View file

@ -501,8 +501,8 @@ export class ReferralTrackingService {
async getReferredUsers(
userId: string,
status?: string,
limit: number = 20,
offset: number = 0
limit = 20,
offset = 0
): Promise<PaginatedResponse<ReferredUser>> {
const db = this.getDb();
@ -734,7 +734,7 @@ export class ReferralTrackingService {
): Promise<number> {
// Basic fraud score calculation
// Full fraud detection will be implemented in Phase 3
let score = 0;
const score = 0;
// For now, just return 0 (no fraud detected)
// TODO: Implement full fraud detection in Phase 3