feat(backend): Express + Socket.IO API with PostgreSQL
- 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
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
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' });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user