Skip to content

Security: mrrmartin01/jink

Security

SECURITY.md

Security Guidelines

Comprehensive security documentation for the Jink social media API.

Table of Contents

Security Overview

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

Authentication & Authorization

JWT Token Security

Access Tokens

  • Lifetime: 15 minutes (short-lived for security)
  • Algorithm: HS256 with strong secret key
  • Storage: HTTP-only cookies (prevents XSS)
  • Rotation: Automatic refresh token rotation

Refresh Tokens

  • Lifetime: 7 days
  • Storage: HTTP-only cookies
  • Rotation: New refresh token issued on each use
  • Revocation: Invalidated on logout

Implementation

// 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,
    });
  }
}

Password Security

Argon2 Hashing

  • 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);
  }
}

Password Requirements

  • 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

Session Management

Cookie Security

// 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: '/',
});

Data Protection

Personal Data Handling

Data Classification

  • Public: Username, display name, public posts
  • Private: Email, phone, private posts
  • Sensitive: Password hashes, authentication tokens

Data Minimization

  • Only collect necessary data
  • Regular data cleanup
  • User data export/deletion capabilities

Encryption at Rest

  • Database encryption enabled
  • Backup encryption
  • File storage encryption (Cloudinary)

GDPR Compliance

User Rights

  • 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

Implementation

// 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 } });
  });
}

Input Validation

Request Validation

DTO Validation

// 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

// 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.');
    }
  }
}

SQL Injection Prevention

Prisma ORM Protection

  • 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}`;
}

API Security

Rate Limiting

Authentication Endpoints

// Rate limiting configuration
@Throttle({ auth: {} })
@Post('signin')
async signin(@Body() dto: SigninDto) {
  // Limited to 5 requests per minute
}

Global Rate Limiting

// Global throttler configuration
ThrottlerModule.forRoot([
  {
    name: 'short',
    ttl: 1000,
    limit: 3,
  },
  {
    name: 'medium',
    ttl: 10000,
    limit: 20,
  },
  {
    name: 'long',
    ttl: 60000,
    limit: 100,
  },
])

CORS Configuration

// 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

// 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,
  },
}));

Database Security

Connection Security

Environment Variables

# Secure database connection
DATABASE_URL="postgresql://username:password@host:port/database?sslmode=require"

Connection Pooling

// Prisma connection configuration
const prisma = new PrismaClient({
  datasources: {
    db: {
      url: process.env.DATABASE_URL,
    },
  },
  log: ['error', 'warn'],
});

Database Access Control

User Permissions

-- 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;

Data Encryption

Database-Level Encryption

  • 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'));

Infrastructure Security

Server Security

System Hardening

# 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

Docker Security

# 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

Network Security

Load Balancer Configuration

# 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;
    }
}

Security Best Practices

Development Security

Code Security

  • 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);
  }
}

Dependency Security

# 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

Production Security

Monitoring

  • 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',
    });
  }
}

Backup Security

  • 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

Vulnerability Management

Security Scanning

Automated Scanning

# Install security scanning tools
npm install -g snyk
npm install -g audit-ci

# Run security scans
snyk test
audit-ci --config audit-ci.json

Manual Security Testing

  • Penetration Testing: Regular security assessments
  • Code Reviews: Security-focused code reviews
  • Dependency Audits: Regular dependency vulnerability checks

Vulnerability Response

Response Process

  1. Detection: Identify vulnerability
  2. Assessment: Evaluate severity and impact
  3. Containment: Implement temporary fixes
  4. Eradication: Apply permanent fixes
  5. Recovery: Restore normal operations
  6. Lessons Learned: Update security practices

Disclosure Policy

  • Responsible Disclosure: 90-day disclosure timeline
  • Security Contact: security@jink.dev
  • Bug Bounty: Consider implementing bug bounty program

Incident Response

Incident Classification

Severity Levels

  • Critical: Data breach, system compromise
  • High: Service disruption, authentication bypass
  • Medium: Information disclosure, DoS
  • Low: Minor security issues

Response Team

Roles and Responsibilities

  • Incident Commander: Overall incident coordination
  • Technical Lead: Technical investigation and remediation
  • Communications Lead: Internal and external communications
  • Legal Counsel: Legal and compliance guidance

Response Procedures

Detection and Analysis

// 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);
  }
}

Containment and Recovery

  • Isolation: Isolate affected systems
  • Evidence Preservation: Preserve evidence for investigation
  • Communication: Notify stakeholders
  • Recovery: Restore services securely

Security Checklist

Pre-Deployment Checklist

Application Security

  • 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

Infrastructure Security

  • HTTPS enabled with valid certificates
  • Firewall configured
  • Database access restricted
  • Backup encryption enabled
  • Monitoring and alerting configured
  • Access controls implemented
  • Security updates applied

Operational Security

  • Security policies documented
  • Incident response plan ready
  • Security training completed
  • Regular security reviews scheduled
  • Vulnerability management process established

Regular Security Tasks

Daily

  • Monitor security logs
  • Check for failed authentication attempts
  • Review system alerts

Weekly

  • Review access logs
  • Check for unusual activity
  • Update security signatures

Monthly

  • Security dependency audit
  • Review user access permissions
  • Test backup restoration
  • Security training updates

Quarterly

  • Penetration testing
  • Security policy review
  • Incident response plan testing
  • Security architecture review

Security Contact

For security-related questions or to report vulnerabilities:

  • Email: security@jink.dev
  • PGP Key: [Available on request]
  • Response Time: 24 hours for initial response

Security Resources


Remember: Security is an ongoing process, not a one-time implementation. Regular reviews, updates, and testing are essential for maintaining a secure application.

There aren’t any published security advisories