89 lines
3.0 KiB
JavaScript
89 lines
3.0 KiB
JavaScript
import bcrypt from 'bcryptjs';
|
|
import { randomBytes } from 'node:crypto';
|
|
import { db, getSetting, setSetting } from './db.js';
|
|
import { config } from './config.js';
|
|
|
|
export const COOKIE_NAME = 'pg_session';
|
|
|
|
// Resolve a stable cookie-signing secret (persisted so sessions survive restarts).
|
|
export function getCookieSecret() {
|
|
if (config.cookieSecret) return config.cookieSecret;
|
|
let secret = getSetting('cookie_secret');
|
|
if (!secret) {
|
|
secret = randomBytes(32).toString('hex');
|
|
setSetting('cookie_secret', secret);
|
|
}
|
|
return secret;
|
|
}
|
|
|
|
// Resolve the bcrypt password hash. The environment (PG_PASSWORD / PG_PASSWORD_HASH)
|
|
// is authoritative: when set, it overwrites the stored hash on every boot so that
|
|
// platform-managed password changes (e.g. the StartOS "Set Login Password" action,
|
|
// which restarts the service with a new PG_PASSWORD) actually take effect.
|
|
export function initPassword() {
|
|
if (config.passwordHash) {
|
|
setSetting('password_hash', config.passwordHash);
|
|
return config.passwordHash;
|
|
}
|
|
|
|
const stored = getSetting('password_hash');
|
|
|
|
if (config.password) {
|
|
// Re-hash only when the password actually changed (bcrypt salts differ each run).
|
|
if (!stored || !bcrypt.compareSync(config.password, stored)) {
|
|
const hash = bcrypt.hashSync(config.password, 10);
|
|
setSetting('password_hash', hash);
|
|
// Password changed out from under existing sessions — invalidate them.
|
|
if (stored) db.prepare('DELETE FROM sessions').run();
|
|
return hash;
|
|
}
|
|
return stored;
|
|
}
|
|
|
|
if (stored) return stored;
|
|
|
|
const hash = bcrypt.hashSync('gunner', 10);
|
|
setSetting('password_hash', hash);
|
|
console.warn('\n⚠ No PG_PASSWORD set — using default password "gunner". Set PG_PASSWORD before deploying.\n');
|
|
return hash;
|
|
}
|
|
|
|
export function verifyPassword(plain) {
|
|
const hash = config.passwordHash || getSetting('password_hash');
|
|
if (!hash) return false;
|
|
return bcrypt.compareSync(String(plain || ''), hash);
|
|
}
|
|
|
|
export function setPassword(plain) {
|
|
const hash = bcrypt.hashSync(String(plain), 10);
|
|
setSetting('password_hash', hash);
|
|
// Invalidate existing sessions when the password changes.
|
|
db.prepare('DELETE FROM sessions').run();
|
|
}
|
|
|
|
export function createSession() {
|
|
const token = randomBytes(32).toString('hex');
|
|
const expires = new Date(Date.now() + config.sessionDays * 86400_000).toISOString();
|
|
db.prepare('INSERT INTO sessions (token, expires_at) VALUES (?, ?)').run(token, expires);
|
|
return token;
|
|
}
|
|
|
|
export function isValidSession(token) {
|
|
if (!token) return false;
|
|
const row = db.prepare('SELECT expires_at FROM sessions WHERE token = ?').get(token);
|
|
if (!row) return false;
|
|
if (new Date(row.expires_at).getTime() < Date.now()) {
|
|
db.prepare('DELETE FROM sessions WHERE token = ?').run(token);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function destroySession(token) {
|
|
if (token) db.prepare('DELETE FROM sessions WHERE token = ?').run(token);
|
|
}
|
|
|
|
export function cleanupExpiredSessions() {
|
|
db.prepare("DELETE FROM sessions WHERE expires_at < datetime('now')").run();
|
|
}
|