Store Google avatar bytes and slim the session JWT to id only.

Download profile images on login into picturebinary, return them as base64 from /auth/me, and resolve sessions via getById.
This commit is contained in:
seb
2026-08-05 11:12:36 +02:00
parent c78237a1dd
commit fc2b136c46
12 changed files with 133 additions and 41 deletions

View File

@@ -16,17 +16,17 @@ export function clearTokenCookie(res) {
res.clearCookie('token', TOKEN_COOKIE);
}
export function getCachedUser(email) {
const cached = userCache.get(email);
export function getCachedUser(authId) {
const cached = userCache.get(authId);
if (cached && cached.expiresAt > Date.now()) {
return cached.user;
}
userCache.delete(email);
userCache.delete(authId);
return null;
}
export function setCachedUser(email, user) {
userCache.set(email, { user, expiresAt: Date.now() + USER_CACHE_TTL });
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) {
@@ -35,8 +35,8 @@ export function setCachedUser(email, user) {
}
}
export function clearCachedUser(email) {
userCache.delete(email);
export function clearCachedUser(authId) {
userCache.delete(authId);
}
function authMeJson(res, req, status, body) {
@@ -60,14 +60,24 @@ export const authMiddleware = async (req, res, next) => {
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(decoded.email);
let user = getCachedUser(authId);
if (!user) {
user = await db.getAllowedEmail(decoded.email);
if (user) setCachedUser(decoded.email, 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) {
if (decoded.email) clearCachedUser(decoded.email);
clearCachedUser(authId);
clearTokenCookie(res);
return authMeJson(res, req, 403, { error: 'User is no longer authorized' });
}
@@ -76,13 +86,11 @@ export const authMiddleware = async (req, res, next) => {
id: user.id,
email: user.email,
selectedCompanyId: user.selectedCompanyId,
...(user.picture && { picture: user.picture }),
};
const now = Math.floor(Date.now() / 1000);
if (decoded.exp - now < 2 * 60 * 60) {
const { iat, exp, ...payload } = decoded;
const newToken = jwt.sign(payload, env.jwtSecret, { expiresIn: '1d' });
const newToken = jwt.sign({ id: user.id }, env.jwtSecret, { expiresIn: '1d' });
res.cookie('token', newToken, {
...TOKEN_COOKIE,
maxAge: 24 * 60 * 60 * 1000,