Security Patterns for Multiverse Campus
Environment Variable Safety
const secret = process.env.SECRET || '';
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
import crypto from 'crypto';
function verifyWebhookSignature(payload: Buffer, signature: string, secret: string): boolean {
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}`)
);
}
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' });
}
});
JWT Best Practices
function createAccessToken(student: { id: string; email: string }): string {
return jwt.sign(
{ sub: student.id, email: student.email },
JWT_SECRET,
{ expiresIn: '15m', algorithm: 'HS256' }
);
}
function verifyToken(token: string): JwtPayload {
return jwt.verify(token, JWT_SECRET, {
algorithms: ['HS256'],
clockTolerance: 30,
}) as JwtPayload;
}
Socket.IO Auth Pattern
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'));
}
});
const socket = io(SERVER_URL, {
auth: { token: getAccessToken() },
reconnectionDelay: 1000,
reconnectionDelayMax: 30000,
reconnectionAttempts: 10,
});
socket.on('connect_error', async (err) => {
if (err.message === 'Invalid or expired token') {
const newToken = await refreshAccessToken();
socket.auth = { token: newToken };
socket.connect();
}
});
Input Validation
import { z } from 'zod';
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),
});
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,
});
}
const result = await service.create(parsed.data);
res.json({ success: true, data: result });
});
Rate Limiting
import rateLimit from 'express-rate-limit';
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
});
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: { error: 'Too many attempts, please try again later' },
});
app.use('/api/', globalLimiter);
app.use('/api/auth/login', authLimiter);