Project-Rivet / context /packs /security_patterns.md
HumboldtJoker's picture
Upload folder using huggingface_hub
4554903 verified
|
Raw
History Blame Contribute Delete
4.36 kB
# 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);
```