Spreadsheet-UX op een echte relationele engine
Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.
import { createHash, randomBytes } from 'node:crypto';
import type { Account } from '@wellformed/shared';
import { toAccount } from './accounts.js';
import type { ControlDb } from './db.js';
export const SESSION_COOKIE = 'wellformed_session';
/**
* Sessions are stored hashed, so a leaked control database cannot be replayed.
*
* One session spans every workspace the account belongs to. Which workspace a
* request touches comes from its URL, and is authorised by a membership lookup
* on each request — never by anything carried in the cookie.
*/
function hashToken(token: string): string {
return createHash('sha256').update(token).digest('hex');
}
export interface IssuedSession {
token: string;
expiresAt: Date;
}
export function createSession(
control: ControlDb,
accountId: string,
ttlMs: number,
userAgent?: string,
): IssuedSession {
const token = randomBytes(32).toString('base64url');
const expiresAt = new Date(Date.now() + ttlMs);
control.sqlite
.prepare('INSERT INTO session (token_hash, account_id, expires_at, user_agent) VALUES (?, ?, ?, ?)')
.run(hashToken(token), accountId, expiresAt.toISOString(), userAgent?.slice(0, 300) ?? null);
return { token, expiresAt };
}
export function resolveSession(control: ControlDb, token: string | undefined): Account | null {
if (!token) return null;
const row = control.sqlite
.prepare<
[string],
{
id: string;
email: string;
name: string;
disabled: number;
created_at: string;
last_seen_at: string | null;
verified_at: string | null;
expires_at: string;
}
>(
`SELECT a.id, a.email, a.name, a.disabled, a.created_at, a.last_seen_at, a.verified_at,
s.expires_at
FROM session s JOIN account a ON a.id = s.account_id
WHERE s.token_hash = ?`,
)
.get(hashToken(token));
if (!row) return null;
if (row.disabled === 1) return null;
if (new Date(row.expires_at).getTime() <= Date.now()) {
destroySession(control, token);
return null;
}
return toAccount(row);
}
export function destroySession(control: ControlDb, token: string): void {
control.sqlite.prepare('DELETE FROM session WHERE token_hash = ?').run(hashToken(token));
}
export function destroyAllSessions(control: ControlDb, accountId: string): void {
control.sqlite.prepare('DELETE FROM session WHERE account_id = ?').run(accountId);
}
export function pruneExpiredSessions(control: ControlDb): number {
const info = control.sqlite
.prepare(`DELETE FROM session WHERE expires_at <= datetime('now')`)
.run();
return info.changes;
}