File size: 4,358 Bytes
4554903 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | # Security Patterns for Multiverse Campus
## Environment Variable Safety
```typescript
// NEVER do this:
const secret = process.env.SECRET || ''; // Empty string bypass!
// ALWAYS do this:
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) {
console.error(`FATAL: ${name} is not set. Refusing to start.`);
process.exit(1);
}
return value;
}
const JWT_SECRET = requireEnv('JWT_SECRET');
const WEBHOOK_SECRET = requireEnv('SCHOOL_WEBHOOK_SECRET');
```
## Webhook Signature Verification
```typescript
import crypto from 'crypto';
function verifyWebhookSignature(payload: Buffer, signature: string, secret: string): boolean {
// NEVER skip verification — reject if secret is missing
if (!secret) {
throw new Error('Webhook secret not configured — rejecting all requests');
}
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
// Route handler
router.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-signature'] as string;
if (!verifyWebhookSignature(req.body, sig, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook...
});
```
## JWT Best Practices
```typescript
// Token creation — minimal claims, short expiry
function createAccessToken(student: { id: string; email: string }): string {
return jwt.sign(
{ sub: student.id, email: student.email },
JWT_SECRET,
{ expiresIn: '15m', algorithm: 'HS256' }
);
}
// Token verification — explicit algorithm, clock tolerance
function verifyToken(token: string): JwtPayload {
return jwt.verify(token, JWT_SECRET, {
algorithms: ['HS256'],
clockTolerance: 30, // 30 second clock skew tolerance
}) as JwtPayload;
}
// NEVER put mutable data (roles, permissions) in the access token
// unless you accept the 15-min staleness window.
// For real-time permission checks, hit the database.
```
## Socket.IO Auth Pattern
```typescript
io.use(async (socket, next) => {
const token = socket.handshake.auth?.token;
if (!token) {
return next(new Error('Authentication required'));
}
try {
const payload = verifyToken(token);
socket.userId = payload.sub;
socket.email = payload.email;
next();
} catch (err) {
next(new Error('Invalid or expired token'));
}
});
// Reconnection: exponential backoff on client
const socket = io(SERVER_URL, {
auth: { token: getAccessToken() },
reconnectionDelay: 1000, // Start at 1s
reconnectionDelayMax: 30000, // Cap at 30s
reconnectionAttempts: 10, // Give up after 10
});
socket.on('connect_error', async (err) => {
if (err.message === 'Invalid or expired token') {
// Only refresh on auth errors, not all errors
const newToken = await refreshAccessToken();
socket.auth = { token: newToken };
socket.connect();
}
// For other errors, let the built-in backoff handle it
});
```
## Input Validation
```typescript
import { z } from 'zod';
// Define schema
const CreateResourceSchema = z.object({
name: z.string().min(1).max(255),
type: z.enum(['quest', 'achievement', 'item']),
value: z.number().int().min(0).max(10000),
});
// Validate in route
router.post('/resource', async (req, res) => {
const parsed = CreateResourceSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({
success: false,
errors: parsed.error.flatten().fieldErrors,
});
}
// parsed.data is fully typed and validated
const result = await service.create(parsed.data);
res.json({ success: true, data: result });
});
```
## Rate Limiting
```typescript
import rateLimit from 'express-rate-limit';
// Global rate limit
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
standardHeaders: true,
legacyHeaders: false,
});
// Strict limit for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 login attempts per 15 min
message: { error: 'Too many attempts, please try again later' },
});
app.use('/api/', globalLimiter);
app.use('/api/auth/login', authLimiter);
```
|