mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
✅ 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:
parent
e3774ca08b
commit
fe33f4b355
14 changed files with 1282 additions and 25 deletions
|
|
@ -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)** |
|
||||
|
||||
|
|
|
|||
1099
docker/grafana/dashboards/auth-service.json
Normal file
1099
docker/grafana/dashboards/auth-service.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* Common utilities for writing tests
|
||||
*/
|
||||
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { type ConfigService } from '@nestjs/config';
|
||||
|
||||
/**
|
||||
* Create mock ConfigService
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue