Refactor auth-service to iam-service and update related documentation
- Renamed auth-service to iam-service across various files for consistency. - Updated Dockerfiles, deployment configurations, and documentation to reflect the service name change. - Enhanced testing commands in documentation to point to the new iam-service. - Removed outdated auth-service files and configurations to streamline the project structure. - Improved bilingual documentation for clarity on the new service structure and usage.
This commit is contained in:
@@ -1,771 +0,0 @@
|
||||
---
|
||||
name: Enterprise Auth Service
|
||||
overview: Refactor toàn bộ auth-service thành hệ thống enterprise-grade phục vụ 100+ triệu users với kiến trúc phù hợp GoodGo Platform, sử dụng template và cấu trúc monorepo hiện tại.
|
||||
todos:
|
||||
- id: backup-current
|
||||
content: Backup auth-service hiện tại trước khi xóa
|
||||
status: pending
|
||||
- id: delete-auth-service
|
||||
content: Xóa toàn bộ auth-service hiện tại
|
||||
status: pending
|
||||
- id: copy-template
|
||||
content: Copy template service và rename thành auth-service
|
||||
status: pending
|
||||
- id: setup-base-structure
|
||||
content: Setup cấu trúc base với các modules cơ bản
|
||||
status: pending
|
||||
- id: implement-advanced-schema
|
||||
content: Tạo Prisma schema cho RBAC, Social Login, Sessions
|
||||
status: pending
|
||||
- id: implement-multi-layer-cache
|
||||
content: Implement multi-layer cache với Redis và in-memory
|
||||
status: pending
|
||||
- id: implement-rbac-module
|
||||
content: Tạo module RBAC với permissions, roles, policies
|
||||
status: pending
|
||||
- id: implement-social-auth
|
||||
content: Tạo module social auth (Google, Facebook, GitHub)
|
||||
status: pending
|
||||
- id: implement-oidc-module
|
||||
content: Implement OIDC provider và client
|
||||
status: pending
|
||||
- id: implement-jwt-service
|
||||
content: Tạo JWT service với access/refresh/id tokens
|
||||
status: pending
|
||||
- id: implement-cookie-service
|
||||
content: Implement secure cookie management
|
||||
status: pending
|
||||
- id: implement-mfa-module
|
||||
content: Tạo module MFA với TOTP và WebAuthn
|
||||
status: pending
|
||||
- id: implement-zero-trust
|
||||
content: Implement zero-trust security middleware
|
||||
status: pending
|
||||
- id: setup-event-sourcing
|
||||
content: Setup event sourcing cho audit logs
|
||||
status: pending
|
||||
- id: implement-rate-limiting
|
||||
content: Dynamic rate limiting theo role
|
||||
status: pending
|
||||
- id: setup-monitoring
|
||||
content: Setup monitoring với Prometheus và Grafana
|
||||
status: pending
|
||||
- id: write-tests
|
||||
content: Viết unit và integration tests
|
||||
status: pending
|
||||
- id: update-docker-compose
|
||||
content: Update docker-compose.yml với services mới
|
||||
status: pending
|
||||
- id: write-documentation
|
||||
content: Viết documentation và API specs
|
||||
status: pending
|
||||
---
|
||||
|
||||
# Enterprise Auth Service - Implementation Plan
|
||||
|
||||
## 1. Phân tích thách thức và giải pháp cho scale lớn
|
||||
|
||||
### Performance Challenges
|
||||
|
||||
- **100M+ users** = ~10-50K requests/second peak time
|
||||
- **Token validation** phải < 10ms (in-memory cache)
|
||||
- **Login/Register** phải < 100ms (với DB write)
|
||||
- **Social login** phải handle timeout từ providers
|
||||
|
||||
### Security Challenges
|
||||
|
||||
- **Distributed attacks** (DDoS, brute force từ nhiều regions)
|
||||
- **Token hijacking** và replay attacks
|
||||
- **Data breaches** với 100M+ records
|
||||
- **Compliance** (GDPR, SOC2, ISO 27001)
|
||||
|
||||
### Availability Challenges
|
||||
|
||||
- **99.999% uptime** = max 5.26 phút downtime/năm
|
||||
- **Multi-region failover** < 30 seconds
|
||||
- **Zero-downtime deployments**
|
||||
- **Database replication lag** < 100ms
|
||||
|
||||
## 2. Kiến trúc tổng quan
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph clients [Client Layer]
|
||||
web[Web Apps]
|
||||
mobile[Mobile Apps]
|
||||
api[API Clients]
|
||||
end
|
||||
|
||||
subgraph cdn [CDN Layer]
|
||||
cloudflare[Cloudflare]
|
||||
fastly[Fastly Cache]
|
||||
end
|
||||
|
||||
subgraph gateway [API Gateway Layer]
|
||||
kong1[Kong Gateway Region 1]
|
||||
kong2[Kong Gateway Region 2]
|
||||
kong3[Kong Gateway Region 3]
|
||||
end
|
||||
|
||||
subgraph auth [Auth Service Cluster]
|
||||
subgraph region1 [US Region]
|
||||
auth1[Auth Service Pods]
|
||||
cache1[Redis Cluster]
|
||||
db1[PostgreSQL Primary]
|
||||
end
|
||||
|
||||
subgraph region2 [EU Region]
|
||||
auth2[Auth Service Pods]
|
||||
cache2[Redis Cluster]
|
||||
db2[PostgreSQL Replica]
|
||||
end
|
||||
|
||||
subgraph region3 [APAC Region]
|
||||
auth3[Auth Service Pods]
|
||||
cache3[Redis Cluster]
|
||||
db3[PostgreSQL Replica]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph external [External Services]
|
||||
oidc[OIDC Providers]
|
||||
social[Social Providers]
|
||||
sms[SMS Gateway]
|
||||
email[Email Service]
|
||||
end
|
||||
|
||||
subgraph monitoring [Observability]
|
||||
datadog[Datadog APM]
|
||||
elastic[ELK Stack]
|
||||
pagerduty[PagerDuty]
|
||||
end
|
||||
|
||||
clients --> cdn
|
||||
cdn --> gateway
|
||||
gateway --> auth
|
||||
auth --> external
|
||||
auth --> monitoring
|
||||
```
|
||||
|
||||
## 3. Microservices Architecture
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph authCore [Auth Core Services]
|
||||
authAPI[Auth API Service]
|
||||
tokenService[Token Service]
|
||||
sessionService[Session Service]
|
||||
mfaService[MFA Service]
|
||||
end
|
||||
|
||||
subgraph rbacServices [RBAC Services]
|
||||
permissionService[Permission Service]
|
||||
roleService[Role Service]
|
||||
policyService[Policy Service]
|
||||
end
|
||||
|
||||
subgraph socialServices [Social Services]
|
||||
socialAuth[Social Auth Service]
|
||||
oidcProvider[OIDC Provider]
|
||||
oidcClient[OIDC Client]
|
||||
end
|
||||
|
||||
subgraph supportServices [Support Services]
|
||||
auditService[Audit Service]
|
||||
notificationService[Notification Service]
|
||||
analyticsService[Analytics Service]
|
||||
end
|
||||
|
||||
subgraph dataLayer [Data Layer]
|
||||
postgresCluster[PostgreSQL Cluster]
|
||||
redisCluster[Redis Cluster]
|
||||
elasticSearch[ElasticSearch]
|
||||
kafka[Kafka Event Bus]
|
||||
end
|
||||
|
||||
authAPI --> tokenService
|
||||
authAPI --> sessionService
|
||||
authAPI --> mfaService
|
||||
authAPI --> rbacServices
|
||||
authAPI --> socialServices
|
||||
|
||||
authCore --> kafka
|
||||
rbacServices --> kafka
|
||||
socialServices --> kafka
|
||||
kafka --> supportServices
|
||||
|
||||
authCore --> dataLayer
|
||||
rbacServices --> dataLayer
|
||||
socialServices --> dataLayer
|
||||
supportServices --> dataLayer
|
||||
```
|
||||
|
||||
## 4. Cấu trúc dự án với GoodGo monorepo
|
||||
|
||||
```
|
||||
services/
|
||||
└── auth-service/ # Single service với modules
|
||||
├── Dockerfile
|
||||
├── package.json
|
||||
├── prisma/
|
||||
│ ├── schema.prisma # Advanced schema với RBAC
|
||||
│ └── migrations/
|
||||
├── src/
|
||||
│ ├── config/
|
||||
│ │ ├── app.config.ts
|
||||
│ │ ├── database.config.ts
|
||||
│ │ ├── redis.config.ts
|
||||
│ │ ├── jwt.config.ts
|
||||
│ │ └── social.config.ts
|
||||
│ ├── core/ # Core utilities
|
||||
│ │ ├── cache/
|
||||
│ │ │ ├── multi-layer-cache.ts
|
||||
│ │ │ └── cache.service.ts
|
||||
│ │ ├── security/
|
||||
│ │ │ ├── zero-trust.validator.ts
|
||||
│ │ │ └── encryption.service.ts
|
||||
│ │ └── events/
|
||||
│ │ ├── event-bus.ts
|
||||
│ │ └── audit.service.ts
|
||||
│ ├── modules/
|
||||
│ │ ├── auth/ # Core authentication
|
||||
│ │ │ ├── auth.controller.ts
|
||||
│ │ │ ├── auth.service.ts
|
||||
│ │ │ ├── auth.dto.ts
|
||||
│ │ │ └── strategies/
|
||||
│ │ ├── rbac/ # RBAC system
|
||||
│ │ │ ├── rbac.controller.ts
|
||||
│ │ │ ├── rbac.service.ts
|
||||
│ │ │ ├── permission.service.ts
|
||||
│ │ │ ├── role.service.ts
|
||||
│ │ │ ├── policy.engine.ts
|
||||
│ │ │ └── rbac.dto.ts
|
||||
│ │ ├── social/ # Social authentication
|
||||
│ │ │ ├── social.controller.ts
|
||||
│ │ │ ├── social.service.ts
|
||||
│ │ │ ├── providers/
|
||||
│ │ │ │ ├── google.provider.ts
|
||||
│ │ │ │ ├── facebook.provider.ts
|
||||
│ │ │ │ └── github.provider.ts
|
||||
│ │ │ └── circuit-breaker.ts
|
||||
│ │ ├── oidc/ # OIDC implementation
|
||||
│ │ │ ├── oidc.controller.ts
|
||||
│ │ │ ├── oidc-provider.service.ts
|
||||
│ │ │ ├── oidc-client.service.ts
|
||||
│ │ │ └── multi-tenant.service.ts
|
||||
│ │ ├── token/ # Token management
|
||||
│ │ │ ├── jwt.service.ts
|
||||
│ │ │ ├── cookie.service.ts
|
||||
│ │ │ └── token-rotation.service.ts
|
||||
│ │ ├── session/ # Session management
|
||||
│ │ │ ├── session.service.ts
|
||||
│ │ │ └── distributed-session.ts
|
||||
│ │ ├── mfa/ # Multi-factor auth
|
||||
│ │ │ ├── mfa.controller.ts
|
||||
│ │ │ ├── mfa.service.ts
|
||||
│ │ │ ├── totp.service.ts
|
||||
│ │ │ └── webauthn.service.ts
|
||||
│ │ └── health/
|
||||
│ │ └── health.controller.ts
|
||||
│ ├── middlewares/
|
||||
│ │ ├── auth.middleware.ts
|
||||
│ │ ├── rbac.middleware.ts
|
||||
│ │ ├── rate-limit.middleware.ts
|
||||
│ │ ├── zero-trust.middleware.ts
|
||||
│ │ └── error.middleware.ts
|
||||
│ ├── repositories/
|
||||
│ │ ├── base.repository.ts
|
||||
│ │ ├── user.repository.ts
|
||||
│ │ ├── role.repository.ts
|
||||
│ │ └── session.repository.ts
|
||||
│ ├── routes/
|
||||
│ │ └── index.ts
|
||||
│ └── main.ts
|
||||
└── tests/
|
||||
├── unit/
|
||||
├── integration/
|
||||
└── load/
|
||||
```
|
||||
|
||||
## 5. Database Architecture
|
||||
|
||||
### 5.1 Sharding Strategy
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph shardRouter [Shard Router]
|
||||
router[Vitess/Citus Router]
|
||||
end
|
||||
|
||||
subgraph shard1 [Shard 1: Users 0-33M]
|
||||
master1[PostgreSQL Primary]
|
||||
replica1a[Replica 1A]
|
||||
replica1b[Replica 1B]
|
||||
end
|
||||
|
||||
subgraph shard2 [Shard 2: Users 33M-66M]
|
||||
master2[PostgreSQL Primary]
|
||||
replica2a[Replica 2A]
|
||||
replica2b[Replica 2B]
|
||||
end
|
||||
|
||||
subgraph shard3 [Shard 3: Users 66M-100M+]
|
||||
master3[PostgreSQL Primary]
|
||||
replica3a[Replica 3A]
|
||||
replica3b[Replica 3B]
|
||||
end
|
||||
|
||||
router --> shard1
|
||||
router --> shard2
|
||||
router --> shard3
|
||||
```
|
||||
|
||||
### 5.2 Schema Design
|
||||
|
||||
```prisma
|
||||
// Optimized for sharding and performance
|
||||
model User {
|
||||
id String @id @default(cuid()) // Use CUID for better distribution
|
||||
shardKey Int // For sharding (hash of userId % shard_count)
|
||||
email String
|
||||
username String?
|
||||
passwordHash String?
|
||||
|
||||
// Denormalized for performance
|
||||
primaryRole String? // Cache primary role
|
||||
permissionCache Json? // Cache computed permissions
|
||||
lastLoginAt DateTime?
|
||||
loginCount Int @default(0)
|
||||
|
||||
@@unique([shardKey, email])
|
||||
@@index([shardKey, id])
|
||||
@@index([email])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
// Separate table for hot data
|
||||
model UserSession {
|
||||
id String @id
|
||||
userId String
|
||||
shardKey Int
|
||||
deviceId String
|
||||
ipAddress String
|
||||
userAgent String
|
||||
expiresAt DateTime
|
||||
|
||||
@@index([userId, deviceId])
|
||||
@@index([expiresAt])
|
||||
@@map("user_sessions")
|
||||
}
|
||||
|
||||
// Event sourcing for audit
|
||||
model AuthEvent {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
eventType String // LOGIN, LOGOUT, MFA_ENABLED, etc.
|
||||
eventData Json
|
||||
ipAddress String
|
||||
userAgent String
|
||||
timestamp DateTime @default(now())
|
||||
|
||||
@@index([userId, timestamp])
|
||||
@@index([eventType, timestamp])
|
||||
@@map("auth_events")
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Caching Strategy
|
||||
|
||||
### 6.1 Multi-layer Cache
|
||||
|
||||
```typescript
|
||||
// [services/auth-core/token-service/src/cache/multi-layer-cache.ts]
|
||||
|
||||
export class MultiLayerCache {
|
||||
private l1Cache: NodeCache; // In-memory (10MB)
|
||||
private l2Cache: RedisCluster; // Redis (100GB)
|
||||
private l3Cache: CDNCache; // CDN Edge (unlimited)
|
||||
|
||||
async get(key: string): Promise<any> {
|
||||
// L1: Local memory (< 1ms)
|
||||
let value = this.l1Cache.get(key);
|
||||
if (value) return value;
|
||||
|
||||
// L2: Redis cluster (< 5ms)
|
||||
value = await this.l2Cache.get(key);
|
||||
if (value) {
|
||||
this.l1Cache.set(key, value, 60); // Cache 1 min
|
||||
return value;
|
||||
}
|
||||
|
||||
// L3: CDN edge cache (< 20ms)
|
||||
value = await this.l3Cache.get(key);
|
||||
if (value) {
|
||||
await this.warmUpCache(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 Cache Invalidation
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant AuthAPI
|
||||
participant Cache
|
||||
participant DB
|
||||
participant EventBus
|
||||
|
||||
User->>AuthAPI: Update Permission
|
||||
AuthAPI->>DB: Write to DB
|
||||
AuthAPI->>EventBus: Publish PERMISSION_CHANGED
|
||||
EventBus->>Cache: Invalidate user cache
|
||||
EventBus->>Cache: Invalidate permission cache
|
||||
Cache-->>AuthAPI: Cache cleared
|
||||
AuthAPI-->>User: Success
|
||||
```
|
||||
|
||||
## 7. Security Implementation
|
||||
|
||||
### 7.1 Zero-Trust Architecture
|
||||
|
||||
```typescript
|
||||
// [services/auth-core/auth-api/src/security/zero-trust.ts]
|
||||
|
||||
export class ZeroTrustValidator {
|
||||
async validateRequest(req: Request): Promise<ValidationResult> {
|
||||
const checks = await Promise.all([
|
||||
this.validateDevice(req), // Device fingerprinting
|
||||
this.validateLocation(req), // Geo-location check
|
||||
this.validateBehavior(req), // ML-based behavior analysis
|
||||
this.validateToken(req), // Token validation
|
||||
this.validateRateLimit(req), // Rate limiting
|
||||
]);
|
||||
|
||||
const riskScore = this.calculateRiskScore(checks);
|
||||
|
||||
if (riskScore > 0.8) {
|
||||
// High risk - require MFA
|
||||
return { requireMFA: true };
|
||||
} else if (riskScore > 0.5) {
|
||||
// Medium risk - additional logging
|
||||
await this.auditLog(req, 'MEDIUM_RISK');
|
||||
}
|
||||
|
||||
return { allowed: true };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Advanced RBAC with ABAC
|
||||
|
||||
```typescript
|
||||
// [services/auth-rbac/policy-service/src/models/policy.ts]
|
||||
|
||||
export class PolicyEngine {
|
||||
async evaluate(context: PolicyContext): Promise<boolean> {
|
||||
// Attribute-based access control
|
||||
const policies = await this.loadPolicies(context.resource);
|
||||
|
||||
for (const policy of policies) {
|
||||
if (!this.evaluateCondition(policy.condition, context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Role-based check
|
||||
const hasRole = await this.checkRole(
|
||||
context.userId,
|
||||
policy.requiredRole
|
||||
);
|
||||
|
||||
// Permission check with scope
|
||||
const hasPermission = await this.checkPermission(
|
||||
context.userId,
|
||||
context.resource,
|
||||
context.action,
|
||||
context.scope
|
||||
);
|
||||
|
||||
// Time-based access
|
||||
const inTimeWindow = this.checkTimeWindow(
|
||||
policy.timeRestriction
|
||||
);
|
||||
|
||||
return hasRole && hasPermission && inTimeWindow;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Social Login với Circuit Breaker
|
||||
|
||||
```typescript
|
||||
// [services/auth-social/social-auth-service/src/providers/social-provider.ts]
|
||||
|
||||
export class SocialAuthProvider {
|
||||
private circuitBreaker: CircuitBreaker;
|
||||
|
||||
constructor(provider: string) {
|
||||
this.circuitBreaker = new CircuitBreaker({
|
||||
timeout: 3000, // 3s timeout
|
||||
errorThreshold: 50, // 50% error rate
|
||||
resetTimeout: 30000, // Reset after 30s
|
||||
});
|
||||
}
|
||||
|
||||
async authenticate(code: string): Promise<SocialUser> {
|
||||
return this.circuitBreaker.execute(async () => {
|
||||
// Fallback to cached profile if provider is down
|
||||
try {
|
||||
return await this.provider.getProfile(code);
|
||||
} catch (error) {
|
||||
const cached = await this.getCachedProfile(code);
|
||||
if (cached) {
|
||||
this.logger.warn('Using cached profile', { provider });
|
||||
return cached;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 9. OIDC Provider với Multi-tenancy
|
||||
|
||||
```typescript
|
||||
// [services/auth-social/oidc-provider/src/provider/multi-tenant.ts]
|
||||
|
||||
export class MultiTenantOIDCProvider {
|
||||
async getConfiguration(tenantId: string): Promise<Configuration> {
|
||||
const tenant = await this.tenantService.get(tenantId);
|
||||
|
||||
return {
|
||||
issuer: `https://auth.goodgo.com/${tenantId}`,
|
||||
clients: tenant.clients,
|
||||
claims: tenant.customClaims,
|
||||
features: {
|
||||
introspection: { enabled: true },
|
||||
revocation: { enabled: true },
|
||||
deviceFlow: { enabled: tenant.features.deviceFlow },
|
||||
mTLS: {
|
||||
enabled: tenant.security.mtls,
|
||||
certificateAuth: true
|
||||
}
|
||||
},
|
||||
jwks: await this.keyRotation.getCurrentKeys(tenantId),
|
||||
ttl: this.getTTLConfig(tenant.security.level)
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 10. Implementation Phases
|
||||
|
||||
### Phase 1: Core Refactoring (2 tuần)
|
||||
|
||||
- Tách auth-service thành microservices
|
||||
- Implement sharding cho database
|
||||
- Setup Redis Cluster
|
||||
- Basic monitoring với Datadog
|
||||
|
||||
### Phase 2: Performance Optimization (2 tuần)
|
||||
|
||||
- Implement multi-layer caching
|
||||
- Optimize database queries
|
||||
- Add connection pooling
|
||||
- Load testing với K6
|
||||
|
||||
### Phase 3: Security Enhancement (2 tuần)
|
||||
|
||||
- Zero-trust architecture
|
||||
- Advanced RBAC với ABAC
|
||||
- MFA với TOTP/WebAuthn
|
||||
- Audit logging với ElasticSearch
|
||||
|
||||
### Phase 4: High Availability (2 tuần)
|
||||
|
||||
- Multi-region deployment
|
||||
- Database replication
|
||||
- Disaster recovery plan
|
||||
- Chaos engineering tests
|
||||
|
||||
### Phase 5: Social & OIDC (1 tuần)
|
||||
|
||||
- Social login với circuit breaker
|
||||
- OIDC provider multi-tenant
|
||||
- Federation với enterprise IdPs
|
||||
- SSO implementation
|
||||
|
||||
### Phase 6: Monitoring & Optimization (1 tuần)
|
||||
|
||||
- Complete observability stack
|
||||
- Performance tuning
|
||||
- Security hardening
|
||||
- Documentation & training
|
||||
|
||||
## 11. Implementation Steps Chi Tiết
|
||||
|
||||
### Step 1: Backup và Setup Base (30 phút)
|
||||
|
||||
```bash
|
||||
# Backup current auth-service
|
||||
cp -r services/auth-service services/auth-service.backup
|
||||
|
||||
# Delete current service
|
||||
rm -rf services/auth-service
|
||||
|
||||
# Copy template
|
||||
cp -r services/_template services/auth-service
|
||||
|
||||
# Update package.json name
|
||||
```
|
||||
|
||||
### Step 2: Prisma Schema Setup (1 giờ)
|
||||
|
||||
- Tạo schema với User, Role, Permission, Session, SocialAccount
|
||||
- Setup indexes cho performance
|
||||
- Add sharding support fields
|
||||
|
||||
### Step 3: Core Modules Implementation (2 ngày)
|
||||
|
||||
- **Auth Module**: Login, Register, Logout, RefreshToken
|
||||
- **RBAC Module**: Roles, Permissions, Policies
|
||||
- **Token Module**: JWT service với rotation
|
||||
- **Session Module**: Distributed session management
|
||||
|
||||
### Step 4: Advanced Features (3 ngày)
|
||||
|
||||
- **Social Auth**: Google, Facebook, GitHub với Passport.js
|
||||
- **OIDC**: Provider và Client implementation
|
||||
- **MFA**: TOTP và WebAuthn
|
||||
- **Zero-Trust**: Device fingerprinting, geo-location
|
||||
|
||||
### Step 5: Performance Optimization (2 ngày)
|
||||
|
||||
- **Multi-layer Cache**: Memory → Redis → CDN
|
||||
- **Database Optimization**: Connection pooling, indexes
|
||||
- **Rate Limiting**: Dynamic theo role
|
||||
- **Load Testing**: K6 tests cho 10K req/s
|
||||
|
||||
### Step 6: Security & Monitoring (1 ngày)
|
||||
|
||||
- **Audit Logging**: Event sourcing pattern
|
||||
- **Monitoring**: Prometheus metrics
|
||||
- **Security Headers**: Helmet.js
|
||||
- **Testing**: Unit & Integration tests
|
||||
|
||||
### Step 7: Deployment (1 ngày)
|
||||
|
||||
- Update docker-compose.yml
|
||||
- Configure Traefik routing
|
||||
- Setup environment variables
|
||||
- Documentation
|
||||
|
||||
## 12. Key Technologies
|
||||
|
||||
### Core Stack (Phù hợp với GoodGo)
|
||||
|
||||
- **Express.js**: Web framework (giữ nguyên theo template)
|
||||
- **Prisma**: ORM với PostgreSQL/Neon
|
||||
- **Redis**: Caching layer
|
||||
- **TypeScript**: Type safety
|
||||
- **Zod**: Validation
|
||||
|
||||
### Authentication Libraries
|
||||
|
||||
- **jsonwebtoken**: JWT handling
|
||||
- **passport**: Social auth strategies
|
||||
- **bcryptjs**: Password hashing
|
||||
- **speakeasy**: TOTP for MFA
|
||||
- **@simplewebauthn/server**: WebAuthn
|
||||
|
||||
### Security Libraries
|
||||
|
||||
- **helmet**: Security headers
|
||||
- **express-rate-limit**: Rate limiting
|
||||
- **ioredis**: Redis client
|
||||
- **node-cache**: In-memory cache
|
||||
- **fingerprint.js**: Device fingerprinting
|
||||
|
||||
### Monitoring (Existing trong GoodGo)
|
||||
|
||||
- **Prometheus**: Metrics (existing)
|
||||
- **Grafana**: Dashboards (existing)
|
||||
- **Loki**: Logging (existing)
|
||||
- **@goodgo/logger**: Custom logger
|
||||
- **@goodgo/tracing**: OpenTelemetry
|
||||
|
||||
## 13. Performance Targets (Realistic cho Start)
|
||||
|
||||
### Phase 1: MVP (Current Infrastructure)
|
||||
|
||||
- **Authentication**: < 200ms p99
|
||||
- **Token Validation**: < 50ms p99
|
||||
- **Permission Check**: < 100ms p99
|
||||
- **Throughput**: 1,000 req/s
|
||||
- **Availability**: 99.9% uptime
|
||||
|
||||
### Phase 2: Scale Up (3-6 months)
|
||||
|
||||
- **Authentication**: < 100ms p99
|
||||
- **Token Validation**: < 20ms p99
|
||||
- **Throughput**: 10,000 req/s
|
||||
- **Availability**: 99.99% uptime
|
||||
|
||||
### Phase 3: Enterprise (1+ year)
|
||||
|
||||
- **Authentication**: < 50ms p99
|
||||
- **Token Validation**: < 10ms p99
|
||||
- **Throughput**: 50,000 req/s
|
||||
- **Availability**: 99.999% uptime
|
||||
|
||||
## 14. File Structure to Create
|
||||
|
||||
```bash
|
||||
services/auth-service/
|
||||
├── package.json # Dependencies
|
||||
├── tsconfig.json # TypeScript config
|
||||
├── .env.example # Environment template
|
||||
├── Dockerfile # Docker build
|
||||
├── jest.config.ts # Test configuration
|
||||
├── prisma/
|
||||
│ └── schema.prisma # Database schema
|
||||
├── src/
|
||||
│ ├── main.ts # Entry point
|
||||
│ ├── config/*.ts # Configurations
|
||||
│ ├── core/* # Core utilities
|
||||
│ ├── modules/* # Feature modules
|
||||
│ ├── middlewares/*.ts # Express middlewares
|
||||
│ ├── repositories/*.ts # Data access
|
||||
│ └── routes/index.ts # Route definitions
|
||||
└── tests/
|
||||
├── unit/* # Unit tests
|
||||
└── integration/* # Integration tests
|
||||
```
|
||||
|
||||
## 15. Timeline Thực Tế
|
||||
|
||||
### Week 1: Foundation
|
||||
|
||||
- **Day 1-2**: Setup base structure, Prisma schema
|
||||
- **Day 3-4**: Core auth module (login, register, JWT)
|
||||
- **Day 5**: Basic RBAC (roles, permissions)
|
||||
|
||||
### Week 2: Advanced Features
|
||||
|
||||
- **Day 1-2**: Social authentication
|
||||
- **Day 3-4**: OIDC implementation
|
||||
- **Day 5**: MFA và security features
|
||||
|
||||
### Week 3: Optimization & Deployment
|
||||
|
||||
- **Day 1-2**: Performance optimization, caching
|
||||
- **Day 3**: Testing và bug fixes
|
||||
- **Day 4-5**: Documentation và deployment
|
||||
|
||||
**Total: 3 tuần cho MVP production-ready**
|
||||
1390
.cursor/plans/iam_service_migration_plan_fc0d64b3.plan.md
Normal file
1390
.cursor/plans/iam_service_migration_plan_fc0d64b3.plan.md
Normal file
File diff suppressed because it is too large
Load Diff
796
.cursor/skills/security/SKILL.md
Normal file
796
.cursor/skills/security/SKILL.md
Normal file
@@ -0,0 +1,796 @@
|
||||
---
|
||||
name: security
|
||||
description: Security best practices and patterns for GoodGo microservices platform. Use when implementing authentication, authorization, data protection, input validation, rate limiting, secrets management, or security testing across all services.
|
||||
---
|
||||
|
||||
# Security Patterns for GoodGo Microservices
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when:
|
||||
- Implementing authentication and authorization in any service
|
||||
- Protecting sensitive data (PII, credentials, tokens)
|
||||
- Validating user inputs and file uploads
|
||||
- Implementing rate limiting and DDoS protection
|
||||
- Setting up audit logging and security monitoring
|
||||
- Encrypting data at rest and in transit
|
||||
- Managing secrets and credentials
|
||||
- Implementing security testing
|
||||
- Handling security incidents
|
||||
- Designing secure API endpoints
|
||||
|
||||
## Core Security Principles
|
||||
|
||||
1. **Defense in Depth**: Multiple layers of security controls
|
||||
2. **Least Privilege**: Grant minimum required permissions
|
||||
3. **Fail Secure**: Default to deny access
|
||||
4. **Separation of Duties**: Critical operations require multiple approvals
|
||||
5. **Audit Everything**: Log all security-relevant events
|
||||
6. **Encrypt Sensitive Data**: PII, tokens, credentials must be encrypted
|
||||
7. **Validate All Inputs**: Never trust user input
|
||||
8. **Principle of Least Exposure**: Minimize attack surface
|
||||
9. **Secure by Default**: Security built-in, not bolted on
|
||||
10. **Assume Breach**: Design for detection and response
|
||||
|
||||
## Authentication & Authorization
|
||||
|
||||
### JWT Token Validation
|
||||
|
||||
```typescript
|
||||
// src/middlewares/auth.middleware.ts
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { jwtService } from '@goodgo/auth-sdk';
|
||||
import { logger } from '@goodgo/logger';
|
||||
|
||||
export const authenticate = () => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// Extract token from Authorization header or cookie
|
||||
let token: string | null = null;
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader?.startsWith('Bearer ')) {
|
||||
token = authHeader.substring(7);
|
||||
} else if (req.cookies?.access_token) {
|
||||
token = req.cookies.access_token;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
|
||||
});
|
||||
}
|
||||
|
||||
// Verify token
|
||||
const payload = await jwtService.verifyAccessToken(token);
|
||||
|
||||
// Attach user to request
|
||||
req.user = {
|
||||
id: payload.sub,
|
||||
userId: payload.sub,
|
||||
email: payload.email,
|
||||
roles: payload.roles || [],
|
||||
permissions: payload.permissions || []
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
logger.warn('Authentication failed', { error: error.message });
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'INVALID_TOKEN', message: 'Invalid or expired token' }
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Role-Based Authorization
|
||||
|
||||
```typescript
|
||||
// src/middlewares/rbac.middleware.ts
|
||||
export const requireRole = (...allowedRoles: string[]) => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
|
||||
});
|
||||
}
|
||||
|
||||
const userRoles = req.user.roles || [];
|
||||
const hasRole = userRoles.some(role => allowedRoles.includes(role));
|
||||
|
||||
if (!hasRole) {
|
||||
logger.warn('Access denied - insufficient role', {
|
||||
userId: req.user.id,
|
||||
userRoles,
|
||||
requiredRoles: allowedRoles
|
||||
});
|
||||
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: { code: 'FORBIDDEN', message: 'Insufficient permissions' }
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// Permission-based authorization
|
||||
export const requirePermission = (resource: string, action: string) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
|
||||
});
|
||||
}
|
||||
|
||||
const permission = `${resource}:${action}`;
|
||||
const hasPermission = req.user.permissions?.includes(permission);
|
||||
|
||||
if (!hasPermission) {
|
||||
logger.warn('Access denied - insufficient permission', {
|
||||
userId: req.user.id,
|
||||
required: permission
|
||||
});
|
||||
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: { code: 'FORBIDDEN', message: 'Insufficient permissions' }
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// Usage in routes
|
||||
router.post(
|
||||
'/api/v1/users',
|
||||
authenticate(),
|
||||
requirePermission('users', 'create'),
|
||||
userController.create
|
||||
);
|
||||
```
|
||||
|
||||
### Resource Ownership Validation
|
||||
|
||||
```typescript
|
||||
// Ensure users can only access their own resources
|
||||
export const requireOwnership = (resourceIdParam: string = 'id') => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
const resourceId = req.params[resourceIdParam];
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (resourceId !== userId) {
|
||||
logger.warn('Access denied - resource ownership mismatch', {
|
||||
userId,
|
||||
resourceId
|
||||
});
|
||||
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: { code: 'FORBIDDEN', message: 'Access denied' }
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Data Protection
|
||||
|
||||
### Encryption Service
|
||||
|
||||
```typescript
|
||||
// src/core/security/encryption.service.ts
|
||||
import crypto from 'crypto';
|
||||
|
||||
const ALGORITHM = 'aes-256-gcm';
|
||||
const IV_LENGTH = 16;
|
||||
const TAG_LENGTH = 16;
|
||||
|
||||
export class EncryptionService {
|
||||
private getKey(): Buffer {
|
||||
const secret = process.env.ENCRYPTION_KEY;
|
||||
if (!secret || secret.length < 32) {
|
||||
throw new Error('ENCRYPTION_KEY must be at least 32 characters');
|
||||
}
|
||||
return crypto.scryptSync(secret, 'salt', 32);
|
||||
}
|
||||
|
||||
encrypt(text: string): string {
|
||||
const key = this.getKey();
|
||||
const iv = crypto.randomBytes(IV_LENGTH);
|
||||
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
||||
|
||||
let encrypted = cipher.update(text, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
const tag = cipher.getAuthTag();
|
||||
|
||||
return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted}`;
|
||||
}
|
||||
|
||||
decrypt(encryptedText: string): string {
|
||||
const [ivHex, tagHex, encrypted] = encryptedText.split(':');
|
||||
const iv = Buffer.from(ivHex, 'hex');
|
||||
const tag = Buffer.from(tagHex, 'hex');
|
||||
|
||||
const key = this.getKey();
|
||||
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
||||
decipher.setAuthTag(tag);
|
||||
|
||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
return decrypted;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage: Encrypt PII before storing
|
||||
const encryption = new EncryptionService();
|
||||
const encryptedPhone = encryption.encrypt(user.phone);
|
||||
```
|
||||
|
||||
### Password Hashing
|
||||
|
||||
```typescript
|
||||
// Always use bcrypt with appropriate cost factor
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
const SALT_ROUNDS = 12; // Production: 12, Development: 10
|
||||
|
||||
export class PasswordService {
|
||||
async hash(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, SALT_ROUNDS);
|
||||
}
|
||||
|
||||
async verify(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
// Never log passwords
|
||||
sanitizeForLogging(data: any): any {
|
||||
const sanitized = { ...data };
|
||||
if (sanitized.password) sanitized.password = '[REDACTED]';
|
||||
if (sanitized.passwordHash) sanitized.passwordHash = '[REDACTED]';
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Token Hashing
|
||||
|
||||
```typescript
|
||||
// Hash tokens before storing in database
|
||||
import crypto from 'crypto';
|
||||
|
||||
export class TokenService {
|
||||
hashToken(token: string): string {
|
||||
const salt = process.env.TOKEN_SALT || 'default-salt-change-in-production';
|
||||
return crypto
|
||||
.createHash('sha256')
|
||||
.update(token + salt)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
generateSecureToken(length: number = 32): string {
|
||||
return crypto.randomBytes(length).toString('hex');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Input Validation
|
||||
|
||||
### Zod Schema Validation
|
||||
|
||||
```typescript
|
||||
// Always validate inputs with Zod
|
||||
import { z } from 'zod';
|
||||
|
||||
// DTO with validation
|
||||
export const CreateUserDto = z.object({
|
||||
email: z.string().email('Invalid email format'),
|
||||
password: z.string()
|
||||
.min(8, 'Password must be at least 8 characters')
|
||||
.regex(/[A-Z]/, 'Password must contain uppercase letter')
|
||||
.regex(/[a-z]/, 'Password must contain lowercase letter')
|
||||
.regex(/[0-9]/, 'Password must contain number')
|
||||
.regex(/[^A-Za-z0-9]/, 'Password must contain special character'),
|
||||
phone: z.string()
|
||||
.regex(/^\+[1-9]\d{1,14}$/, 'Invalid phone format (E.164)')
|
||||
.optional(),
|
||||
name: z.string().min(1).max(255)
|
||||
});
|
||||
|
||||
// In controller
|
||||
export class UserController {
|
||||
async create(req: Request, res: Response) {
|
||||
try {
|
||||
const dto = CreateUserDto.parse(req.body);
|
||||
const user = await this.service.create(dto);
|
||||
res.status(201).json({ success: true, data: user });
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Invalid input data',
|
||||
details: error.errors
|
||||
}
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### File Upload Validation
|
||||
|
||||
```typescript
|
||||
// Validate file uploads
|
||||
import fileType from 'file-type';
|
||||
|
||||
export class FileValidationService {
|
||||
private readonly MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
private readonly ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
|
||||
|
||||
async validateFile(file: Express.Multer.File): Promise<void> {
|
||||
// Size check
|
||||
if (file.size > this.MAX_FILE_SIZE) {
|
||||
throw new HttpError(400, 'FILE_TOO_LARGE', 'File exceeds maximum size');
|
||||
}
|
||||
|
||||
// Type check
|
||||
if (!this.ALLOWED_TYPES.includes(file.mimetype)) {
|
||||
throw new HttpError(400, 'INVALID_FILE_TYPE', 'File type not allowed');
|
||||
}
|
||||
|
||||
// Content validation (prevent MIME type spoofing)
|
||||
const type = await fileType.fromBuffer(file.buffer);
|
||||
if (!type || !this.ALLOWED_TYPES.includes(type.mime)) {
|
||||
throw new HttpError(400, 'INVALID_FILE_CONTENT', 'File content mismatch');
|
||||
}
|
||||
|
||||
// TODO: Add virus scanning for production
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SQL Injection Prevention
|
||||
|
||||
```typescript
|
||||
// Always use Prisma parameterized queries (automatic)
|
||||
// Never use string concatenation for queries
|
||||
|
||||
// ❌ BAD - Never do this
|
||||
const query = `SELECT * FROM users WHERE email = '${email}'`;
|
||||
|
||||
// ✅ GOOD - Use Prisma
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email }
|
||||
});
|
||||
|
||||
// ✅ GOOD - For dynamic queries
|
||||
const where: any = {};
|
||||
if (email) where.email = email;
|
||||
if (status) where.status = status;
|
||||
|
||||
const users = await prisma.user.findMany({ where });
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
```typescript
|
||||
// Implement rate limiting for all endpoints
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import RedisStore from 'rate-limit-redis';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
const redis = new Redis(process.env.REDIS_URL);
|
||||
|
||||
// Standard rate limit
|
||||
export const standardLimiter = rateLimit({
|
||||
store: new RedisStore({
|
||||
client: redis,
|
||||
prefix: 'rl:standard:'
|
||||
}),
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100, // 100 requests per window
|
||||
message: 'Too many requests, please try again later',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false
|
||||
});
|
||||
|
||||
// Strict rate limit for sensitive operations
|
||||
export const strictLimiter = rateLimit({
|
||||
store: new RedisStore({
|
||||
client: redis,
|
||||
prefix: 'rl:strict:'
|
||||
}),
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 10,
|
||||
message: 'Rate limit exceeded for this operation'
|
||||
});
|
||||
|
||||
// Login-specific rate limit
|
||||
export const loginLimiter = rateLimit({
|
||||
store: new RedisStore({
|
||||
client: redis,
|
||||
prefix: 'rl:login:'
|
||||
}),
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 5, // 5 login attempts per 15 minutes
|
||||
skipSuccessfulRequests: true,
|
||||
message: 'Too many login attempts, please try again later'
|
||||
});
|
||||
|
||||
// Usage
|
||||
router.post('/api/v1/auth/login', loginLimiter, authController.login);
|
||||
router.post('/api/v1/users', authenticate(), strictLimiter, userController.create);
|
||||
```
|
||||
|
||||
## Error Handling Security
|
||||
|
||||
```typescript
|
||||
// Sanitize error messages to prevent information disclosure
|
||||
export class SecureErrorHandler {
|
||||
handleError(error: Error, req: Request, res: Response) {
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
// Log full error internally
|
||||
logger.error('Request error', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
userId: req.user?.id
|
||||
});
|
||||
|
||||
// Don't expose user existence
|
||||
if (error.message.includes('user not found') ||
|
||||
error.message.includes('invalid credentials')) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_CREDENTIALS',
|
||||
message: 'Invalid email or password'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Validation errors - safe to expose
|
||||
if (error instanceof z.ZodError) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'VALIDATION_ERROR',
|
||||
message: 'Invalid input data',
|
||||
details: error.errors
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Generic errors for production
|
||||
if (isProd) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: 'An error occurred. Please try again later.'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Detailed errors only in development
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: error.message,
|
||||
stack: isDev ? error.stack : undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Secrets Management
|
||||
|
||||
```typescript
|
||||
// Never hardcode secrets
|
||||
// Always use environment variables with validation
|
||||
import { z } from 'zod';
|
||||
|
||||
const secretsSchema = z.object({
|
||||
JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 characters'),
|
||||
JWT_REFRESH_SECRET: z.string().min(32),
|
||||
DATABASE_URL: z.string().url(),
|
||||
REDIS_URL: z.string().url().optional(),
|
||||
ENCRYPTION_KEY: z.string().min(32).optional()
|
||||
});
|
||||
|
||||
export const secrets = secretsSchema.parse(process.env);
|
||||
|
||||
// For production, use secret management:
|
||||
// - AWS Secrets Manager
|
||||
// - HashiCorp Vault
|
||||
// - Kubernetes Secrets
|
||||
// - Azure Key Vault
|
||||
|
||||
// Rotate secrets regularly (quarterly recommended)
|
||||
```
|
||||
|
||||
## Audit Logging
|
||||
|
||||
```typescript
|
||||
// Log all security-relevant events
|
||||
export class AuditService {
|
||||
async logSecurityEvent(
|
||||
event: string,
|
||||
userId: string | null,
|
||||
details: Record<string, any>,
|
||||
req?: Request
|
||||
) {
|
||||
await this.prisma.auditLog.create({
|
||||
data: {
|
||||
event,
|
||||
userId,
|
||||
type: 'SECURITY',
|
||||
details: this.sanitizeDetails(details),
|
||||
ipAddress: req?.ip || details.ipAddress,
|
||||
userAgent: req?.get('user-agent'),
|
||||
timestamp: new Date()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sanitize PII from logs
|
||||
private sanitizeDetails(details: Record<string, any>): Record<string, any> {
|
||||
const sensitive = ['password', 'token', 'secret', 'ssn', 'creditCard'];
|
||||
const sanitized = { ...details };
|
||||
|
||||
for (const key of sensitive) {
|
||||
if (sanitized[key]) {
|
||||
sanitized[key] = '[REDACTED]';
|
||||
}
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
await auditService.logSecurityEvent('LOGIN_SUCCESS', user.id, {
|
||||
email: user.email,
|
||||
ipAddress: req.ip
|
||||
}, req);
|
||||
|
||||
await auditService.logSecurityEvent('PERMISSION_DENIED', user.id, {
|
||||
resource: 'users',
|
||||
action: 'delete',
|
||||
targetId: targetUserId
|
||||
}, req);
|
||||
```
|
||||
|
||||
## Security Headers
|
||||
|
||||
```typescript
|
||||
// Add security headers middleware
|
||||
import helmet from 'helmet';
|
||||
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", "data:", "https:"]
|
||||
}
|
||||
},
|
||||
hsts: {
|
||||
maxAge: 31536000,
|
||||
includeSubDomains: true,
|
||||
preload: true
|
||||
}
|
||||
}));
|
||||
|
||||
// Additional headers
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
||||
## CORS Configuration
|
||||
|
||||
```typescript
|
||||
// Configure CORS securely
|
||||
import cors from 'cors';
|
||||
|
||||
const allowedOrigins = process.env.CORS_ORIGIN?.split(',') || [];
|
||||
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
if (!origin || allowedOrigins.includes(origin)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
exposedHeaders: ['X-Request-ID'],
|
||||
maxAge: 86400 // 24 hours
|
||||
}));
|
||||
```
|
||||
|
||||
## Security Testing
|
||||
|
||||
```typescript
|
||||
// Security test patterns
|
||||
describe('Security Tests', () => {
|
||||
it('should prevent SQL injection', async () => {
|
||||
const maliciousInput = "'; DROP TABLE users; --";
|
||||
const response = await request(app)
|
||||
.get(`/api/v1/users?search=${encodeURIComponent(maliciousInput)}`)
|
||||
.set('Authorization', `Bearer ${token}`);
|
||||
|
||||
expect(response.status).not.toBe(500);
|
||||
// Should return 400 or empty results, not crash
|
||||
});
|
||||
|
||||
it('should prevent XSS attacks', async () => {
|
||||
const xssPayload = '<script>alert("XSS")</script>';
|
||||
const response = await request(app)
|
||||
.post('/api/v1/users')
|
||||
.send({ email: xssPayload, password: 'test123' });
|
||||
|
||||
// Response should sanitize or reject
|
||||
expect(response.body.data?.email).not.toContain('<script>');
|
||||
});
|
||||
|
||||
it('should enforce authentication', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/v1/users');
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
|
||||
it('should enforce authorization', async () => {
|
||||
const userToken = await createUserToken({ roles: ['user'] });
|
||||
const response = await request(app)
|
||||
.delete('/api/v1/users/123')
|
||||
.set('Authorization', `Bearer ${userToken}`);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
});
|
||||
|
||||
it('should rate limit excessive requests', async () => {
|
||||
const requests = Array(20).fill(null).map(() =>
|
||||
request(app).get('/api/v1/users')
|
||||
);
|
||||
|
||||
const responses = await Promise.all(requests);
|
||||
const rateLimited = responses.filter(r => r.status === 429);
|
||||
|
||||
expect(rateLimited.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
Before deploying any service:
|
||||
|
||||
- [ ] All endpoints require authentication (except public)
|
||||
- [ ] Authorization checks implemented (RBAC/ABAC)
|
||||
- [ ] Input validation with Zod schemas
|
||||
- [ ] Rate limiting configured
|
||||
- [ ] Error messages sanitized (no info disclosure)
|
||||
- [ ] PII encrypted at rest
|
||||
- [ ] Passwords hashed with bcrypt (cost 12+)
|
||||
- [ ] Tokens hashed before storing
|
||||
- [ ] Secrets in environment variables (never hardcoded)
|
||||
- [ ] HTTPS enforced (TLS 1.2+)
|
||||
- [ ] CORS configured correctly
|
||||
- [ ] Security headers set (helmet)
|
||||
- [ ] Audit logging enabled
|
||||
- [ ] SQL injection prevented (use Prisma)
|
||||
- [ ] XSS prevention (input sanitization)
|
||||
- [ ] File upload validation
|
||||
- [ ] Security tests passing
|
||||
- [ ] Dependencies scanned for vulnerabilities
|
||||
- [ ] Secrets rotation plan in place
|
||||
|
||||
## Common Security Anti-Patterns
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Hardcoded secrets
|
||||
const SECRET = 'my-secret-key';
|
||||
|
||||
// ✅ GOOD: Environment variables
|
||||
const SECRET = process.env.JWT_SECRET;
|
||||
|
||||
// ❌ BAD: Plain text passwords
|
||||
await prisma.user.create({ data: { password: password } });
|
||||
|
||||
// ✅ GOOD: Hashed passwords
|
||||
await prisma.user.create({
|
||||
data: { passwordHash: await bcrypt.hash(password, 12) }
|
||||
});
|
||||
|
||||
// ❌ BAD: Exposing user existence
|
||||
if (!user) {
|
||||
throw new Error('User not found'); // Reveals user doesn't exist
|
||||
}
|
||||
|
||||
// ✅ GOOD: Generic error
|
||||
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
|
||||
throw new Error('Invalid credentials');
|
||||
}
|
||||
|
||||
// ❌ BAD: No input validation
|
||||
const email = req.body.email;
|
||||
|
||||
// ✅ GOOD: Validate with Zod
|
||||
const { email } = CreateUserDto.parse(req.body);
|
||||
|
||||
// ❌ BAD: Stack traces in production
|
||||
res.status(500).json({ error: error.stack });
|
||||
|
||||
// ✅ GOOD: Sanitized errors
|
||||
res.status(500).json({
|
||||
error: { code: 'INTERNAL_ERROR', message: 'An error occurred' }
|
||||
});
|
||||
```
|
||||
|
||||
## Incident Response
|
||||
|
||||
```typescript
|
||||
// Security incident detection and response
|
||||
export class SecurityIncidentService {
|
||||
async detectAnomaly(userId: string, event: string, context: any) {
|
||||
// Check for suspicious patterns
|
||||
const recentEvents = await this.getRecentEvents(userId, '1h');
|
||||
|
||||
if (recentEvents.length > 10) {
|
||||
await this.triggerAlert('SUSPICIOUS_ACTIVITY', {
|
||||
userId,
|
||||
eventCount: recentEvents.length,
|
||||
timeWindow: '1h'
|
||||
});
|
||||
}
|
||||
|
||||
// Check for privilege escalation attempts
|
||||
if (event === 'PERMISSION_DENIED' && context.requiredPermission) {
|
||||
await this.logSecurityEvent('PRIVILEGE_ESCALATION_ATTEMPT', userId, context);
|
||||
}
|
||||
}
|
||||
|
||||
async triggerAlert(type: string, details: any) {
|
||||
// Send to monitoring system
|
||||
logger.error('Security alert', { type, details });
|
||||
|
||||
// TODO: Integrate with PagerDuty, Slack, etc.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [OWASP API Security Top 10](https://owasp.org/www-project-api-security/)
|
||||
- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)
|
||||
- [Express Security Best Practices](https://expressjs.com/en/advanced/best-practice-security.html)
|
||||
Reference in New Issue
Block a user