Extend authenticated session activity
This commit is contained in:
+20
-3
@@ -7,7 +7,7 @@ import fastifyStatic from '@fastify/static';
|
||||
import { isValidPlay } from '../src/playRepository.js';
|
||||
import { createMongoStore, DEFAULT_MONGODB_DB, DEFAULT_MONGODB_URI } from './db.js';
|
||||
import { isDevelopmentOrigin, loadConfig } from './config.js';
|
||||
import { hashPassword, hashToken, newSessionToken, sessionExpiry, SESSION_COOKIE, verifyPassword, validEmail } from './security.js';
|
||||
import { hashPassword, hashToken, newSessionToken, sessionExpiry, SESSION_COOKIE, SESSION_SECONDS, verifyPassword, validEmail } from './security.js';
|
||||
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = path.resolve(here, '..');
|
||||
@@ -23,6 +23,13 @@ function errorPayload(code, message) { return { error: { code, message } }; }
|
||||
function fail(reply, status, code, message) { return reply.code(status).send(errorPayload(code, message)); }
|
||||
function parsePositiveId(value) { return typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value); }
|
||||
function isProtectedConfigPath(url) { return /(?:^|\/)\.config\.json(?:\.[^/]*)?$/.test(String(url).split('?')[0]); }
|
||||
function isRenewableApiRequest(request, allowedOrigins, mode) {
|
||||
const url = String(request.url).split('?')[0];
|
||||
if (!url.startsWith('/api/') || url === '/api/health') return false;
|
||||
if (url === '/api/auth/login' || url === '/api/auth/register' || url === '/api/auth/logout' || url === '/api/auth/me') return false;
|
||||
if (request.headers.origin && !requestOriginAllowed(request, allowedOrigins, mode)) return false;
|
||||
return true;
|
||||
}
|
||||
function requestOriginAllowed(request, allowedOrigins, mode) {
|
||||
const origin = request.headers.origin;
|
||||
if (!origin) return true;
|
||||
@@ -57,9 +64,17 @@ export async function buildServer(options = {}) {
|
||||
if (!token) return;
|
||||
const session = await store.sessions.findOne({ tokenHash: hashToken(token), expiresAt: { $gt: nowDate() } });
|
||||
if (!session) return;
|
||||
const now = nowDate();
|
||||
const lastActivityAt = session.lastActivityAt || session.createdAt;
|
||||
if (!lastActivityAt || now.getTime() - new Date(lastActivityAt).getTime() >= SESSION_SECONDS * 1000) return;
|
||||
const row = await store.users.findOne({ id: session.userId });
|
||||
if (!row || row.status === 'suspended') return;
|
||||
request.user = row;
|
||||
if (row.status === 'approved' && isRenewableApiRequest(request, allowedOrigins, mode)) {
|
||||
const expiresAt = new Date(now.getTime() + SESSION_SECONDS * 1000);
|
||||
await store.sessions.updateOne({ tokenHash: session.tokenHash, expiresAt: { $gt: now } }, { $set: { expiresAt, lastActivityAt: now } });
|
||||
reply.setCookie(SESSION_COOKIE, token, { path: '/', httpOnly: true, sameSite: 'lax', secure: secureCookie, maxAge: SESSION_SECONDS });
|
||||
}
|
||||
});
|
||||
|
||||
app.addHook('onClose', async () => { await store.close(); });
|
||||
@@ -129,8 +144,8 @@ export async function buildServer(options = {}) {
|
||||
if (row.status === 'suspended') return fail(reply, 403, 'account_suspended', '사용이 중지된 계정입니다');
|
||||
const token = newSessionToken(); const createdAt = nowDate();
|
||||
await store.sessions.deleteMany({ expiresAt: { $lte: createdAt } });
|
||||
await store.sessions.insertOne({ tokenHash: hashToken(token), userId: row.id, expiresAt: new Date(sessionExpiry()), createdAt });
|
||||
reply.setCookie(SESSION_COOKIE, token, { path: '/', httpOnly: true, sameSite: 'lax', secure: secureCookie, maxAge: 7 * 24 * 60 * 60 });
|
||||
await store.sessions.insertOne({ tokenHash: hashToken(token), userId: row.id, expiresAt: new Date(sessionExpiry(createdAt.getTime())), createdAt, lastActivityAt: createdAt });
|
||||
reply.setCookie(SESSION_COOKIE, token, { path: '/', httpOnly: true, sameSite: 'lax', secure: secureCookie, maxAge: SESSION_SECONDS });
|
||||
return { user: publicUser(row) };
|
||||
});
|
||||
|
||||
@@ -139,6 +154,8 @@ export async function buildServer(options = {}) {
|
||||
reply.clearCookie(SESSION_COOKIE, { path: '/' }); return { ok: true };
|
||||
});
|
||||
|
||||
app.post('/api/auth/activity', { preHandler: requireUser, schema: { body: bodySchema({}, []) } }, async () => ({ ok: true }));
|
||||
|
||||
app.get('/api/auth/me', { preHandler: requireUser }, async (request) => ({ user: publicUser(request.user) }));
|
||||
|
||||
app.get('/api/teams', { preHandler: requireUser }, async (request) => {
|
||||
|
||||
Reference in New Issue
Block a user