Security Implementation Guide
Overviewβ
This guide provides detailed implementation steps for securing both toto-app and toto-bo applications to production standards.
1. Authentication & Authorizationβ
Multi-Factor Authentication (MFA)β
toto-app Implementationβ
// src/lib/auth/mfa.ts
import { getAuth, multiFactor, PhoneAuthProvider } from 'firebase/auth';
export class MFAHandler {
private auth = getAuth();
async enrollMFA(phoneNumber: string): Promise<void> {
const user = this.auth.currentUser;
if (!user) throw new Error('User not authenticated');
const multiFactorSession = await multiFactor(user).getSession();
const phoneAuthCredential = PhoneAuthProvider.credential(
verificationId,
verificationCode
);
const multiFactorAssertion = PhoneAuthProvider.credential(
phoneAuthCredential
);
await multiFactor(user).enroll(multiFactorAssertion, 'Phone Number');
}
async verifyMFA(verificationId: string, verificationCode: string): Promise<boolean> {
try {
const user = this.auth.currentUser;
if (!user) return false;
const multiFactorAssertion = PhoneAuthProvider.credential(
verificationId,
verificationCode
);
await multiFactor(user).assertEnrolled(multiFactorAssertion);
return true;
} catch (error) {
console.error('MFA verification failed:', error);
return false;
}
}
}
toto-bo Implementationβ
// src/lib/auth/mfa.ts
import { NextAuthOptions } from 'next-auth';
import { authenticator } from 'otplib';
export const mfaOptions: NextAuthOptions = {
callbacks: {
async jwt({ token, user }) {
if (user) {
token.mfaEnabled = user.mfaEnabled;
token.mfaVerified = false;
}
return token;
},
async session({ session, token }) {
session.user.mfaEnabled = token.mfaEnabled;
session.user.mfaVerified = token.mfaVerified;
return session;
}
}
};
export class MFAHandler {
static generateSecret(): string {
return authenticator.generateSecret();
}
static generateQRCode(secret: string, email: string): string {
return authenticator.keyuri(email, 'Toto Backoffice', secret);
}
static verifyToken(token: string, secret: string): boolean {
return authenticator.verify({ token, secret });
}
}
Role-Based Access Control (RBAC)β
toto-app RBAC Implementationβ
// src/lib/auth/rbac.ts
export enum UserRole {
ADMIN = 'admin',
STAFF = 'staff',
GUARDIAN = 'guardian',
USER = 'user'
}
export enum Permission {
CREATE_CASE = 'create:case',
READ_CASE = 'read:case',
UPDATE_CASE = 'update:case',
DELETE_CASE = 'delete:case',
MANAGE_USERS = 'manage:users',
VIEW_ANALYTICS = 'view:analytics'
}
export const rolePermissions: Record<UserRole, Permission[]> = {
[UserRole.ADMIN]: Object.values(Permission),
[UserRole.STAFF]: [
Permission.CREATE_CASE,
Permission.READ_CASE,
Permission.UPDATE_CASE,
Permission.VIEW_ANALYTICS
],
[UserRole.GUARDIAN]: [
Permission.CREATE_CASE,
Permission.READ_CASE,
Permission.UPDATE_CASE
],
[UserRole.USER]: [
Permission.READ_CASE
]
};
export function hasPermission(userRole: UserRole, permission: Permission): boolean {
return rolePermissions[userRole]?.includes(permission) || false;
}
export function requirePermission(permission: Permission) {
return (req: NextRequest, user: any) => {
if (!hasPermission(user.role, permission)) {
throw new Error('Insufficient permissions');
}
};
}
toto-bo RBAC Implementationβ
// src/lib/auth/rbac.ts
export enum BackofficeRole {
SUPER_ADMIN = 'super_admin',
ADMIN = 'admin',
MANAGER = 'manager',
STAFF = 'staff',
VIEWER = 'viewer'
}
export enum BackofficePermission {
MANAGE_USERS = 'manage:users',
MANAGE_CASES = 'manage:cases',
VIEW_ANALYTICS = 'view:analytics',
MANAGE_SETTINGS = 'manage:settings',
VIEW_LOGS = 'view:logs',
MANAGE_ALERTS = 'manage:alerts'
}
export const backofficeRolePermissions: Record<BackofficeRole, BackofficePermission[]> = {
[BackofficeRole.SUPER_ADMIN]: Object.values(BackofficePermission),
[BackofficeRole.ADMIN]: [
BackofficePermission.MANAGE_USERS,
BackofficePermission.MANAGE_CASES,
BackofficePermission.VIEW_ANALYTICS,
BackofficePermission.VIEW_LOGS
],
[BackofficeRole.MANAGER]: [
BackofficePermission.MANAGE_CASES,
BackofficePermission.VIEW_ANALYTICS
],
[BackofficeRole.STAFF]: [
BackofficePermission.MANAGE_CASES
],
[BackofficeRole.VIEWER]: [
BackofficePermission.VIEW_ANALYTICS
]
};
2. API Securityβ
Rate Limiting Implementationβ
toto-app Rate Limitingβ
// src/lib/security/rateLimiter.ts
import { NextRequest, NextResponse } from 'next/server';
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
export class RateLimiter {
private windowMs: number;
private maxRequests: number;
constructor(windowMs: number = 15 * 60 * 1000, maxRequests: number = 100) {
this.windowMs = windowMs;
this.maxRequests = maxRequests;
}
async checkLimit(identifier: string): Promise<{ allowed: boolean; remaining: number; resetTime: number }> {
const key = `rate_limit:${identifier}`;
const now = Date.now();
const windowStart = now - this.windowMs;
// Remove expired entries
await redis.zremrangebyscore(key, 0, windowStart);
// Count current requests
const currentCount = await redis.zcard(key);
if (currentCount >= this.maxRequests) {
const oldestRequest = await redis.zrange(key, 0, 0, 'WITHSCORES');
const resetTime = oldestRequest.length > 0 ?
parseInt(oldestRequest[1]) + this.windowMs :
now + this.windowMs;
return {
allowed: false,
remaining: 0,
resetTime
};
}
// Add current request
await redis.zadd(key, now, now);
await redis.expire(key, Math.ceil(this.windowMs / 1000));
return {
allowed: true,
remaining: this.maxRequests - currentCount - 1,
resetTime: now + this.windowMs
};
}
}
export const rateLimiter = new RateLimiter();
export async function withRateLimit(
req: NextRequest,
identifier: string = req.ip || 'unknown'
) {
const limit = await rateLimiter.checkLimit(identifier);
if (!limit.allowed) {
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{
status: 429,
headers: {
'X-RateLimit-Limit': '100',
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': limit.resetTime.toString(),
'Retry-After': Math.ceil((limit.resetTime - Date.now()) / 1000).toString()
}
}
);
}
return null;
}
Input Validation & Sanitizationβ
toto-app Input Validationβ
// src/lib/security/validation.ts
import { z } from 'zod';
import DOMPurify from 'isomorphic-dompurify';
export const caseSchema = z.object({
title: z.string()
.min(1, 'Title is required')
.max(100, 'Title too long')
.transform(val => DOMPurify.sanitize(val)),
description: z.string()
.min(10, 'Description too short')
.max(1000, 'Description too long')
.transform(val => DOMPurify.sanitize(val)),
location: z.string()
.min(1, 'Location is required')
.max(100, 'Location too long'),
animalType: z.enum(['dog', 'cat', 'bird', 'other']),
urgency: z.enum(['low', 'medium', 'high', 'critical']),
images: z.array(z.string().url()).max(5, 'Too many images')
});
export const donationSchema = z.object({
amount: z.number()
.min(1, 'Amount must be positive')
.max(10000, 'Amount too large'),
currency: z.enum(['USD', 'EUR', 'GBP']),
donorEmail: z.string().email('Invalid email'),
caseId: z.string().uuid('Invalid case ID'),
paymentMethod: z.enum(['stripe', 'stellar'])
});
export function validateInput<T>(schema: z.ZodSchema<T>, data: unknown): T {
try {
return schema.parse(data);
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`Validation error: ${error.errors.map(e => e.message).join(', ')}`);
}
throw error;
}
}
3. Security Headersβ
Next.js Security Headers Configurationβ
toto-app Security Headersβ
// next.config.js
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-DNS-Prefetch-Control',
value: 'on'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()'
},
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"font-src 'self' https://fonts.gstatic.com",
"img-src 'self' data: https: blob:",
"connect-src 'self' https://api.stripe.com https://horizon.stellar.org",
"frame-src 'self' https://js.stripe.com",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'"
].join('; ')
}
]
}
];
}
};
4. Data Protectionβ
Encryption Implementationβ
Sensitive Data Encryptionβ
// src/lib/security/encryption.ts
import crypto from 'crypto';
const algorithm = 'aes-256-gcm';
const keyLength = 32;
const ivLength = 16;
const tagLength = 16;
export class EncryptionService {
private key: Buffer;
constructor() {
this.key = crypto.scryptSync(process.env.ENCRYPTION_KEY!, 'salt', keyLength);
}
encrypt(text: string): { encrypted: string; iv: string; tag: string } {
const iv = crypto.randomBytes(ivLength);
const cipher = crypto.createCipher(algorithm, this.key);
cipher.setAAD(Buffer.from('toto-app', 'utf8'));
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
const tag = cipher.getAuthTag();
return {
encrypted,
iv: iv.toString('hex'),
tag: tag.toString('hex')
};
}
decrypt(encrypted: string, iv: string, tag: string): string {
const decipher = crypto.createDecipher(algorithm, this.key);
decipher.setAAD(Buffer.from('toto-app', 'utf8'));
decipher.setAuthTag(Buffer.from(tag, 'hex'));
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
export const encryptionService = new EncryptionService();
PII Data Handlingβ
Data Anonymizationβ
// src/lib/security/pii.ts
export class PIIHandler {
static anonymizeEmail(email: string): string {
const [local, domain] = email.split('@');
const anonymizedLocal = local.length > 2
? local[0] + '*'.repeat(local.length - 2) + local[local.length - 1]
: local;
return `${anonymizedLocal}@${domain}`;
}
static anonymizePhone(phone: string): string {
return phone.replace(/(\d{3})\d{4}(\d{3})/, '$1****$2');
}
static hashIdentifier(identifier: string): string {
return crypto.createHash('sha256')
.update(identifier + process.env.HASH_SALT!)
.digest('hex');
}
static shouldRetainData(dataType: string, retentionPeriod: number): boolean {
// Implement GDPR data retention logic
return true; // Simplified for example
}
}
5. Audit Loggingβ
Security Event Loggingβ
Audit Logger Implementationβ
// src/lib/security/audit.ts
import { db } from '@/lib/firebase';
export enum SecurityEvent {
LOGIN_SUCCESS = 'login_success',
LOGIN_FAILURE = 'login_failure',
LOGOUT = 'logout',
PERMISSION_DENIED = 'permission_denied',
DATA_ACCESS = 'data_access',
DATA_MODIFICATION = 'data_modification',
SUSPICIOUS_ACTIVITY = 'suspicious_activity'
}
export interface AuditLog {
id: string;
event: SecurityEvent;
userId?: string;
ipAddress: string;
userAgent: string;
resource?: string;
resourceId?: string;
details: Record<string, any>;
timestamp: Date;
severity: 'low' | 'medium' | 'high' | 'critical';
}
export class AuditLogger {
static async log(event: SecurityEvent, details: AuditLog): Promise<void> {
try {
const auditLog: AuditLog = {
id: crypto.randomUUID(),
event,
...details,
timestamp: new Date()
};
await db.collection('audit_logs').add(auditLog);
} catch (error) {
console.error('Failed to log audit event:', error);
}
}
static async getAuditLogs(
userId?: string,
event?: SecurityEvent,
startDate?: Date,
endDate?: Date
): Promise<AuditLog[]> {
let query = db.collection('audit_logs');
if (userId) query = query.where('userId', '==', userId);
if (event) query = query.where('event', '==', event);
if (startDate) query = query.where('timestamp', '>=', startDate);
if (endDate) query = query.where('timestamp', '<=', endDate);
const snapshot = await query.orderBy('timestamp', 'desc').limit(1000).get();
return snapshot.docs.map(doc => doc.data() as AuditLog);
}
}
6. Security Monitoringβ
Intrusion Detectionβ
Security Monitoring Serviceβ
// src/lib/security/monitoring.ts
export class SecurityMonitor {
private static suspiciousPatterns = [
/admin/i,
/script/i,
/<script/i,
/javascript:/i,
/onload=/i,
/onerror=/i
];
static detectSuspiciousActivity(
input: string,
context: { endpoint: string; userId?: string; ipAddress: string }
): boolean {
// Check for suspicious patterns
const hasSuspiciousPattern = this.suspiciousPatterns.some(pattern =>
pattern.test(input)
);
if (hasSuspiciousPattern) {
this.alertSecurityTeam({
type: 'suspicious_input',
input,
context,
severity: 'high'
});
return true;
}
return false;
}
static async alertSecurityTeam(alert: {
type: string;
input?: string;
context: any;
severity: 'low' | 'medium' | 'high' | 'critical';
}): Promise<void> {
// Send alert to security team
console.error('SECURITY ALERT:', alert);
// Log to audit system
await AuditLogger.log(SecurityEvent.SUSPICIOUS_ACTIVITY, {
event: SecurityEvent.SUSPICIOUS_ACTIVITY,
ipAddress: alert.context.ipAddress,
userAgent: alert.context.userAgent || 'unknown',
details: alert,
severity: alert.severity
});
}
}
7. Security Testingβ
Automated Security Testsβ
Security Test Suiteβ
// tests/security/security.test.ts
import { describe, it, expect } from '@jest/globals';
import { validateInput, caseSchema } from '@/lib/security/validation';
import { hasPermission, UserRole, Permission } from '@/lib/auth/rbac';
describe('Security Tests', () => {
describe('Input Validation', () => {
it('should reject XSS attempts', () => {
const maliciousInput = {
title: '<script>alert("xss")</script>',
description: 'Normal description',
location: 'Test Location',
animalType: 'dog',
urgency: 'medium',
images: []
};
expect(() => validateInput(caseSchema, maliciousInput)).toThrow();
});
it('should sanitize HTML content', () => {
const input = {
title: 'Test <b>Bold</b> Title',
description: 'Description with <i>italic</i> text',
location: 'Test Location',
animalType: 'dog',
urgency: 'medium',
images: []
};
const result = validateInput(caseSchema, input);
expect(result.title).not.toContain('<b>');
expect(result.description).not.toContain('<i>');
});
});
describe('RBAC', () => {
it('should enforce role-based permissions', () => {
expect(hasPermission(UserRole.USER, Permission.CREATE_CASE)).toBe(false);
expect(hasPermission(UserRole.GUARDIAN, Permission.CREATE_CASE)).toBe(true);
expect(hasPermission(UserRole.ADMIN, Permission.MANAGE_USERS)).toBe(true);
});
});
});
8. Security Checklistβ
Pre-Production Security Checklistβ
-
Authentication
- MFA implemented for admin users
- Strong password policies enforced
- Session timeout configured
- Account lockout after failed attempts
-
Authorization
- RBAC implemented and tested
- API endpoints protected
- Resource-level permissions enforced
- Privilege escalation prevented
-
Input Validation
- All inputs validated and sanitized
- XSS protection implemented
- SQL injection prevention
- File upload restrictions
-
Data Protection
- Sensitive data encrypted
- PII handling compliant
- Data retention policies
- Secure data transmission
-
Infrastructure Security
- Security headers configured
- CORS properly configured
- Rate limiting implemented
- Monitoring and alerting active
-
Compliance
- GDPR compliance verified
- PCI DSS compliance (if applicable)
- Audit logging implemented
- Security documentation complete
This security implementation guide ensures both toto-app and toto-bo meet enterprise-grade security standards for production deployment.