110 lines
3.2 KiB
JavaScript
110 lines
3.2 KiB
JavaScript
import jwt from 'jsonwebtoken';
|
|
import env from '../../config/env.js';
|
|
import * as db from '../../db/googleAuth.js';
|
|
import { meJson } from '../auth/meJson.js';
|
|
|
|
const userCache = new Map();
|
|
const USER_CACHE_TTL = 10 * 60 * 1000;
|
|
|
|
const TOKEN_COOKIE = {
|
|
httpOnly: true,
|
|
secure: true,
|
|
sameSite: 'strict',
|
|
};
|
|
|
|
export function clearTokenCookie(res) {
|
|
res.clearCookie('token', TOKEN_COOKIE);
|
|
}
|
|
|
|
export function getCachedUser(authId) {
|
|
const cached = userCache.get(authId);
|
|
if (cached && cached.expiresAt > Date.now()) {
|
|
return cached.user;
|
|
}
|
|
userCache.delete(authId);
|
|
return null;
|
|
}
|
|
|
|
export function setCachedUser(authId, user) {
|
|
userCache.set(authId, { user, expiresAt: Date.now() + USER_CACHE_TTL });
|
|
if (userCache.size > 1000) {
|
|
const now = Date.now();
|
|
for (const [k, v] of userCache) {
|
|
if (v.expiresAt <= now) userCache.delete(k);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function clearCachedUser(authId) {
|
|
userCache.delete(authId);
|
|
}
|
|
|
|
function authMeJson(res, req, status, body) {
|
|
if (req.path === '/api/auth/me') {
|
|
return meJson(res, status, body);
|
|
}
|
|
return res.status(status).json(body);
|
|
}
|
|
|
|
export const authMiddleware = async (req, res, next) => {
|
|
if (req.path === '/' || req.path === '/api/auth/login' || req.path === '/api/auth/logout' || req.path === '/api/health') {
|
|
return next();
|
|
}
|
|
|
|
if (req.path.startsWith('/v1') || req.path.startsWith('/api/v1')) {
|
|
return next();
|
|
}
|
|
|
|
if (req.path.startsWith('/@') || req.path.startsWith('/node_modules') || req.path.startsWith('/src') || req.path === '/favicon.ico') {
|
|
return next();
|
|
}
|
|
|
|
const token = req.cookies.token;
|
|
if (!token) return authMeJson(res, req, 401, { error: 'Unauthorized' });
|
|
|
|
try {
|
|
const decoded = jwt.verify(token, env.jwtSecret);
|
|
const authId = Number(decoded.id);
|
|
if (!Number.isInteger(authId) || authId <= 0) {
|
|
clearTokenCookie(res);
|
|
return authMeJson(res, req, 401, { error: 'Invalid token' });
|
|
}
|
|
|
|
let user = getCachedUser(authId);
|
|
if (!user) {
|
|
const row = await db.getById(authId);
|
|
if (row) {
|
|
// Do not cache pictureBinary — large and only needed for /auth/me.
|
|
const { pictureBinary: _pictureBinary, ...rest } = row;
|
|
user = rest;
|
|
setCachedUser(authId, user);
|
|
}
|
|
}
|
|
if (!user) {
|
|
clearCachedUser(authId);
|
|
clearTokenCookie(res);
|
|
return authMeJson(res, req, 403, { error: 'User is no longer authorized' });
|
|
}
|
|
|
|
req.user = {
|
|
id: user.id,
|
|
email: user.email,
|
|
selectedCompanyId: user.selectedCompanyId,
|
|
};
|
|
|
|
const now = Math.floor(Date.now() / 1000);
|
|
if (decoded.exp - now < 2 * 60 * 60) {
|
|
const newToken = jwt.sign({ id: user.id }, env.jwtSecret, { expiresIn: '1d' });
|
|
res.cookie('token', newToken, {
|
|
...TOKEN_COOKIE,
|
|
maxAge: 24 * 60 * 60 * 1000,
|
|
});
|
|
}
|
|
|
|
next();
|
|
} catch {
|
|
clearTokenCookie(res);
|
|
return authMeJson(res, req, 401, { error: 'Invalid token' });
|
|
}
|
|
};
|