Comprehensive security documentation for the Jink social media API.
- Security Overview
- Authentication & Authorization
- Data Protection
- Input Validation
- API Security
- Database Security
- Infrastructure Security
- Security Best Practices
- Vulnerability Management
- Incident Response
- Security Checklist
Jink implements multiple layers of security to protect user data and prevent unauthorized access:
- Authentication: JWT-based with refresh token rotation
- Authorization: Role-based access control
- Data Encryption: Passwords hashed with Argon2
- Input Validation: Comprehensive request validation
- Rate Limiting: Protection against abuse
- HTTPS: Encrypted communication
- Security Headers: Protection against common attacks
- Lifetime: 15 minutes (short-lived for security)
- Algorithm: HS256 with strong secret key
- Storage: HTTP-only cookies (prevents XSS)
- Rotation: Automatic refresh token rotation
- Lifetime: 7 days
- Storage: HTTP-only cookies
- Rotation: New refresh token issued on each use
- Revocation: Invalidated on logout
// JWT Strategy Configuration
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(config: ConfigService, private prisma: PrismaService) {
super({
jwtFromRequest: ExtractJwt.fromExtractors([
(req) => req.cookies?.['access_token'],
ExtractJwt.fromAuthHeaderAsBearerToken(),
]),
secretOrKey: config.get<string>('JWT_SECRET'),
ignoreExpiration: false,
});
}
}- Algorithm: Argon2id (resistant to side-channel attacks)
- Memory: 64MB
- Iterations: 3
- Parallelism: 1
- Salt: Random 32-byte salt per password
// Password hashing implementation
import * as argon from 'argon2';
export class AuthService {
async hashPassword(password: string): Promise<string> {
return argon.hash(password, {
type: argon.argon2id,
memoryCost: 2 ** 16, // 64MB
timeCost: 3,
parallelism: 1,
});
}
async verifyPassword(hash: string, password: string): Promise<boolean> {
return argon.verify(hash, password);
}
}- Minimum Length: 8 characters
- Complexity: At least one uppercase, lowercase, number
- Common Passwords: Blocked using common password lists
- Breach Check: Optional integration with HaveIBeenPwned API
// Secure cookie configuration
export const ACCESS_COOKIE_OPTIONS = (secure: boolean) => ({
httpOnly: true,
secure,
sameSite: 'strict' as const,
maxAge: 15 * 60 * 1000, // 15 minutes
path: '/',
});
export const REFRESH_COOKIE_OPTIONS = (secure: boolean) => ({
httpOnly: true,
secure,
sameSite: 'strict' as const,
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
path: '/',
});- Public: Username, display name, public posts
- Private: Email, phone, private posts
- Sensitive: Password hashes, authentication tokens
- Only collect necessary data
- Regular data cleanup
- User data export/deletion capabilities
- Database encryption enabled
- Backup encryption
- File storage encryption (Cloudinary)
- Right to Access: Users can view their data
- Right to Rectification: Users can update their data
- Right to Erasure: Users can delete their account
- Right to Portability: Users can export their data
// User data export
async exportUserData(userId: string): Promise<UserDataExport> {
const user = await this.prisma.user.findUnique({
where: { id: userId },
include: {
posts: true,
likes: true,
bookmarks: true,
// ... other relations
},
});
return {
personalData: {
email: user.email,
userName: user.userName,
displayName: user.displayName,
// ... other fields
},
posts: user.posts,
interactions: {
likes: user.likes,
bookmarks: user.bookmarks,
// ... other interactions
},
exportedAt: new Date(),
};
}
// User data deletion
async deleteUserData(userId: string): Promise<void> {
await this.prisma.$transaction(async (tx) => {
// Delete user data in correct order (respecting foreign keys)
await tx.like.deleteMany({ where: { userId } });
await tx.bookmark.deleteMany({ where: { userId } });
await tx.repost.deleteMany({ where: { userId } });
await tx.quote.deleteMany({ where: { userId } });
await tx.follow.deleteMany({
where: { OR: [{ followerId: userId }, { followingId: userId }] }
});
await tx.post.deleteMany({ where: { userId } });
await tx.user.delete({ where: { id: userId } });
});
}// User signup validation
export class SignupDto {
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@Length(3, 20)
@Matches(/^[a-zA-Z0-9_]+$/, {
message: 'Username can only contain letters, numbers, and underscores',
})
userName: string;
@IsString()
@MinLength(8)
@Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, {
message: 'Password must contain at least one uppercase letter, one lowercase letter, and one number',
})
password: string;
@IsOptional()
@IsString()
@MaxLength(50)
firstName?: string;
@IsOptional()
@IsString()
@MaxLength(50)
lastName?: string;
}// File upload validation
export class FileUploadValidator {
static validateImage(file: Express.Multer.File): void {
const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
const maxSize = 5 * 1024 * 1024; // 5MB
if (!allowedMimeTypes.includes(file.mimetype)) {
throw new BadRequestException('Invalid file type. Only JPEG, PNG, GIF, and WebP are allowed.');
}
if (file.size > maxSize) {
throw new BadRequestException('File size too large. Maximum size is 5MB.');
}
}
}- Prisma uses parameterized queries
- No raw SQL queries without proper sanitization
- Input validation before database operations
// Safe database query
async findUserByEmail(email: string): Promise<User | null> {
// Prisma automatically sanitizes the input
return this.prisma.user.findUnique({
where: { email }, // Safe parameterized query
});
}
// Unsafe - avoid this pattern
async unsafeQuery(email: string): Promise<any> {
// DON'T DO THIS - vulnerable to SQL injection
return this.prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`;
}// Rate limiting configuration
@Throttle({ auth: {} })
@Post('signin')
async signin(@Body() dto: SigninDto) {
// Limited to 5 requests per minute
}// Global throttler configuration
ThrottlerModule.forRoot([
{
name: 'short',
ttl: 1000,
limit: 3,
},
{
name: 'medium',
ttl: 10000,
limit: 20,
},
{
name: 'long',
ttl: 60000,
limit: 100,
},
])// Secure CORS configuration
app.enableCors({
origin: [process.env.FRONTEND_URL], // Specific allowed origins
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400, // 24 hours
});// 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,
},
}));# Secure database connection
DATABASE_URL="postgresql://username:password@host:port/database?sslmode=require"// Prisma connection configuration
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
log: ['error', 'warn'],
});-- Create application user with minimal privileges
CREATE USER jink_app WITH PASSWORD 'secure_password';
-- Grant only necessary permissions
GRANT CONNECT ON DATABASE jink TO jink_app;
GRANT USAGE ON SCHEMA public TO jink_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO jink_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO jink_app;
-- Revoke unnecessary permissions
REVOKE CREATE ON SCHEMA public FROM jink_app;
REVOKE DROP ON SCHEMA public FROM jink_app;- Enable PostgreSQL SSL/TLS
- Use encrypted connections
- Encrypt sensitive columns if needed
-- Example of column-level encryption (if needed)
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Encrypt sensitive data
INSERT INTO users (email, encrypted_phone)
VALUES ('user@example.com', pgp_sym_encrypt('+1234567890', 'encryption_key'));# Update system packages
sudo apt update && sudo apt upgrade -y
# Configure firewall
sudo ufw enable
sudo ufw allow ssh
sudo ufw allow 80
sudo ufw allow 443
sudo ufw deny 9000 # Block direct access to app port
# Disable root login
sudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart ssh
# Install fail2ban
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban# Use non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nestjs -u 1001
USER nestjs
# Use specific image tags
FROM node:18.15.0-alpine3.17
# Remove unnecessary packages
RUN apk del --no-cache package-name
# Use multi-stage build
FROM node:18-alpine AS builder
# ... build steps
FROM node:18-alpine AS production
# ... production steps# Nginx security configuration
server {
listen 443 ssl http2;
server_name api.jink.dev;
# SSL configuration
ssl_certificate /etc/ssl/certs/jink.crt;
ssl_certificate_key /etc/ssl/private/jink.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header Referrer-Policy "strict-origin-when-cross-origin";
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}- Input Validation: Validate all inputs
- Output Encoding: Encode outputs to prevent XSS
- Error Handling: Don't expose sensitive information in errors
- Logging: Log security events without sensitive data
// Secure error handling
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = 'Internal server error';
if (exception instanceof HttpException) {
status = exception.getStatus();
message = exception.message;
}
// Log error without sensitive data
this.logger.error(`Error ${status}: ${message}`, {
url: request.url,
method: request.method,
userAgent: request.get('User-Agent'),
ip: request.ip,
});
// Don't expose internal errors to client
const errorResponse = {
statusCode: status,
message: status >= 500 ? 'Internal server error' : message,
timestamp: new Date().toISOString(),
path: request.url,
};
response.status(status).json(errorResponse);
}
}# Regular security audits
npm audit
yarn audit
# Fix vulnerabilities
npm audit fix
yarn audit fix
# Use security-focused packages
npm install --save-dev @typescript-eslint/eslint-plugin-security- Log Analysis: Monitor logs for suspicious activity
- Metrics: Track authentication failures, rate limit hits
- Alerts: Set up alerts for security events
// Security event logging
@Injectable()
export class SecurityLogger {
private readonly logger = new Logger(SecurityLogger.name);
logFailedLogin(email: string, ip: string) {
this.logger.warn('Failed login attempt', {
email,
ip,
timestamp: new Date(),
event: 'failed_login',
});
}
logSuspiciousActivity(userId: string, activity: string, details: any) {
this.logger.warn('Suspicious activity detected', {
userId,
activity,
details,
timestamp: new Date(),
event: 'suspicious_activity',
});
}
}- Encrypted Backups: Encrypt database backups
- Offsite Storage: Store backups in secure, offsite locations
- Access Control: Limit backup access to authorized personnel
- Testing: Regularly test backup restoration
# Install security scanning tools
npm install -g snyk
npm install -g audit-ci
# Run security scans
snyk test
audit-ci --config audit-ci.json- Penetration Testing: Regular security assessments
- Code Reviews: Security-focused code reviews
- Dependency Audits: Regular dependency vulnerability checks
- Detection: Identify vulnerability
- Assessment: Evaluate severity and impact
- Containment: Implement temporary fixes
- Eradication: Apply permanent fixes
- Recovery: Restore normal operations
- Lessons Learned: Update security practices
- Responsible Disclosure: 90-day disclosure timeline
- Security Contact: security@jink.dev
- Bug Bounty: Consider implementing bug bounty program
- Critical: Data breach, system compromise
- High: Service disruption, authentication bypass
- Medium: Information disclosure, DoS
- Low: Minor security issues
- Incident Commander: Overall incident coordination
- Technical Lead: Technical investigation and remediation
- Communications Lead: Internal and external communications
- Legal Counsel: Legal and compliance guidance
// Security incident detection
@Injectable()
export class SecurityIncidentService {
async detectIncident(event: SecurityEvent): Promise<void> {
const severity = this.assessSeverity(event);
if (severity >= SeverityLevel.HIGH) {
await this.triggerIncidentResponse(event);
}
await this.logIncident(event);
}
private async triggerIncidentResponse(event: SecurityEvent): Promise<void> {
// Notify security team
await this.notifySecurityTeam(event);
// Implement containment measures
await this.implementContainment(event);
// Begin investigation
await this.startInvestigation(event);
}
}- Isolation: Isolate affected systems
- Evidence Preservation: Preserve evidence for investigation
- Communication: Notify stakeholders
- Recovery: Restore services securely
- All inputs validated and sanitized
- Authentication and authorization implemented
- Sensitive data encrypted
- Security headers configured
- Rate limiting enabled
- Error handling secure
- Logging configured (no sensitive data)
- Dependencies audited and updated
- HTTPS enabled with valid certificates
- Firewall configured
- Database access restricted
- Backup encryption enabled
- Monitoring and alerting configured
- Access controls implemented
- Security updates applied
- Security policies documented
- Incident response plan ready
- Security training completed
- Regular security reviews scheduled
- Vulnerability management process established
- Monitor security logs
- Check for failed authentication attempts
- Review system alerts
- Review access logs
- Check for unusual activity
- Update security signatures
- Security dependency audit
- Review user access permissions
- Test backup restoration
- Security training updates
- Penetration testing
- Security policy review
- Incident response plan testing
- Security architecture review
For security-related questions or to report vulnerabilities:
- Email: security@jink.dev
- PGP Key: [Available on request]
- Response Time: 24 hours for initial response
- OWASP Top 10
- NIST Cybersecurity Framework
- OWASP Application Security Verification Standard
- CIS Controls
Remember: Security is an ongoing process, not a one-time implementation. Regular reviews, updates, and testing are essential for maintaining a secure application.