- Auth: register/login/me with bcrypt (12 rounds) and JWT bearer tokens - Workspaces: CRUD, role-based members (owner/admin/member/viewer), invite by email - Pages: nested hierarchy, TipTap JSON+HTML content, archive, search, comments - Realtime: Socket.IO rooms per page with JWT handshake, live edits, presence, typing - DB schema with triggers (owner membership, updated_at) and migrate script - Helmet, CORS allow-list, rate limiting, /api/health with DB check - Dockerfile (migrate-on-boot) for compose/production
36 lines
945 B
JavaScript
36 lines
945 B
JavaScript
import jwt from 'jsonwebtoken';
|
|
|
|
function getSecret() {
|
|
const s = process.env.JWT_SECRET;
|
|
if (!s) throw new Error('JWT_SECRET is not set');
|
|
return s;
|
|
}
|
|
|
|
export function signToken(user) {
|
|
return jwt.sign(
|
|
{ sub: user.id, email: user.email },
|
|
getSecret(),
|
|
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' },
|
|
);
|
|
}
|
|
|
|
export function verifyToken(token) {
|
|
return jwt.verify(token, getSecret());
|
|
}
|
|
|
|
/** Express middleware: requires Authorization: Bearer <jwt> */
|
|
export function requireAuth(req, res, next) {
|
|
const header = req.headers.authorization || '';
|
|
const [scheme, token] = header.split(' ');
|
|
if (scheme !== 'Bearer' || !token) {
|
|
return res.status(401).json({ error: 'Missing bearer token' });
|
|
}
|
|
try {
|
|
const payload = verifyToken(token);
|
|
req.user = { id: payload.sub, email: payload.email };
|
|
return next();
|
|
} catch {
|
|
return res.status(401).json({ error: 'Invalid or expired token' });
|
|
}
|
|
}
|