Added e-mail alert settings, saveable but not integrated yet.

This commit is contained in:
2026-07-18 06:24:07 +02:00
parent 479b044798
commit 216e7f59ef
18 changed files with 231 additions and 75 deletions

View File

@@ -1,4 +1,4 @@
import { stringify } from '@brillout/json-serializer/stringify';
import crypto from 'node:crypto';
import IPCIDR from 'ip-cidr';
import argv from '../argv.js';
@@ -13,6 +13,46 @@ import {
import { fetchUser } from '../users.js';
// changes every time the process restarts, so a deploy invalidates all cached session users
// even if they're within the staleness window below
const runtimeId = crypto.randomUUID();
const sessionStaleThreshold = 5 * 60 * 1000;
// spread into a new object rather than mutating the user we're about to hand back to the
// client (loginApi/signupApi send this straight to the browser) — runtimeId/refreshedAt are
// session bookkeeping only, they shouldn't leak into the API response
function toSession(user) {
return {
...user,
runtimeId,
refreshedAt: Date.now(),
};
}
function isSessionStale(sessionUser) {
return sessionUser.runtimeId !== runtimeId || Date.now() - sessionUser.refreshedAt > sessionStaleThreshold;
}
// concurrent requests for the same user (e.g. the burst of asset/API requests a single page
// load fires) share one in-flight refresh instead of each independently hitting the DB
const pendingSessionRefreshes = new Map();
async function refreshSessionUser(sessionUser) {
const pending = pendingSessionRefreshes.get(sessionUser.id);
if (pending) {
return pending;
}
const refresh = fetchUser(sessionUser.id, {}, sessionUser)
.then((user) => toSession(user))
.finally(() => pendingSessionRefreshes.delete(sessionUser.id));
pendingSessionRefreshes.set(sessionUser.id, refresh);
return refresh;
}
function getIp(req) {
if (argv.ip) {
return argv.ip;
@@ -47,6 +87,10 @@ export async function setUserApi(req, res, next) {
req.userIp = ip;
if (req.session.user) {
if (isSessionStale(req.session.user)) {
req.session.user = await refreshSessionUser(req.session.user);
}
req.user = req.session.user;
req.user.ip = ip;
}
@@ -55,7 +99,7 @@ export async function setUserApi(req, res, next) {
}
export async function updateSessionUser(req) {
const user = await fetchUser(req.session.user.id, {}, req.session.user);
const user = toSession(await fetchUser(req.session.user.id, {}, req.session.user));
req.session.user = user;
@@ -66,8 +110,8 @@ export async function updateSessionUser(req) {
export async function loginApi(req, res) {
const user = await login(req.body, req.userIp);
req.session.user = user;
res.send(stringify(user));
req.session.user = toSession(user);
res.send(user);
}
export async function logoutApi(req, res) {
@@ -83,20 +127,20 @@ export async function logoutApi(req, res) {
export async function signupApi(req, res) {
const user = await signup(req.body, req.userIp);
req.session.user = user;
res.send(stringify(user));
req.session.user = toSession(user);
res.send(user);
}
export async function fetchUserKeysApi(req, res) {
const keys = await fetchUserKeys(req.user);
res.send(stringify(keys));
res.send(keys);
}
export async function createKeyApi(req, res) {
const key = await createKey(req.user);
res.send(stringify(key));
res.send(key);
}
export async function removeUserKeyApi(req, res) {