Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
+126
@@ -0,0 +1,126 @@
|
||||
// Generated, do not edit. sha256:b90a0342814d40cb5e6f51f946a82feca678a01b63d4cdb3e948bcbb7e125654
|
||||
import { createHash, timingSafeEqual } from 'node:crypto';
|
||||
import { CONFIG } from '../shared/config.js';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { readSecretsStore, writeSecretsStore } from './keys.mjs';
|
||||
import { installDir } from './portfolios.mjs';
|
||||
import { fail } from '../shared/app-error.js';
|
||||
|
||||
const AUTH_SLEUTEL = 'assetapp_auth';
|
||||
|
||||
const LOOPBACK = new Set(['127.0.0.1', '::1', 'localhost']);
|
||||
|
||||
const digest = (waarde) => createHash('sha256').update(String(waarde), 'utf-8').digest();
|
||||
|
||||
export function passwordMatches(presented, expected) {
|
||||
if (!expected || !expected.password) return false;
|
||||
if (typeof presented !== 'string' || !presented) return false;
|
||||
return timingSafeEqual(digest(presented), digest(expected.password));
|
||||
}
|
||||
|
||||
export function parseCookies(header) {
|
||||
const uit = {};
|
||||
if (typeof header !== 'string') return uit;
|
||||
for (const deel of header.split(';')) {
|
||||
const i = deel.indexOf('=');
|
||||
if (i < 0) continue;
|
||||
const naam = deel.slice(0, i).trim();
|
||||
if (!naam || naam in uit) continue;
|
||||
uit[naam] = deel.slice(i + 1).trim();
|
||||
}
|
||||
return uit;
|
||||
}
|
||||
|
||||
const vlaggen = (secure) => `Path=/; HttpOnly; SameSite=Lax${secure ? '; Secure' : ''}`;
|
||||
|
||||
export function sessionCookie(token, { secure = false } = {}) {
|
||||
const seconden = Math.floor(CONFIG.AUTH.SESSION_TTL_MS / 1000);
|
||||
return `${CONFIG.AUTH.COOKIE_NAME}=${token}; Max-Age=${seconden}; ${vlaggen(secure)}`;
|
||||
}
|
||||
|
||||
export function clearedSessionCookie({ secure = false } = {}) {
|
||||
return `${CONFIG.AUTH.COOKIE_NAME}=; Max-Age=0; ${vlaggen(secure)}`;
|
||||
}
|
||||
|
||||
export function sessionTokenOf(req) {
|
||||
return parseCookies(req?.headers?.cookie)[CONFIG.AUTH.COOKIE_NAME] || null;
|
||||
}
|
||||
|
||||
export function sendUnauthorized(res, { document = false } = {}) {
|
||||
if (document) {
|
||||
res.writeHead(302, {
|
||||
Location: CONFIG.AUTH.LOGIN_PATH,
|
||||
'Cache-Control': 'no-store',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
});
|
||||
return res.end();
|
||||
}
|
||||
res.writeHead(401, {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'Cache-Control': 'no-store',
|
||||
});
|
||||
res.end(JSON.stringify({ error: 'Not logged in.' }));
|
||||
}
|
||||
|
||||
export async function loadCredentials() {
|
||||
const { ASSETAPP_PASSWORD: password } = process.env;
|
||||
if (password) return { password };
|
||||
const opgeslagen = (await readSecretsStore())[AUTH_SLEUTEL];
|
||||
if (opgeslagen && opgeslagen.password) return { password: String(opgeslagen.password) };
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function credentialsStatus() {
|
||||
const { ASSETAPP_PASSWORD: password } = process.env;
|
||||
if (password) return { ingesteld: true, bron: 'omgeving', wijzigbaar: false };
|
||||
const opgeslagen = (await readSecretsStore())[AUTH_SLEUTEL];
|
||||
const ingesteld = Boolean(opgeslagen && opgeslagen.password);
|
||||
return { ingesteld, bron: ingesteld ? 'portefeuille' : 'geen', wijzigbaar: true };
|
||||
}
|
||||
|
||||
export async function setCredentials(password) {
|
||||
const w = String(password ?? '');
|
||||
if (!w) fail('portfolios.passwordRequired', null, 'A password is required.');
|
||||
const store = await readSecretsStore();
|
||||
await writeSecretsStore({ ...store, [AUTH_SLEUTEL]: { password: w } });
|
||||
return { ...await credentialsStatus(), naHerstart: true };
|
||||
}
|
||||
|
||||
export async function clearCredentials() {
|
||||
const store = await readSecretsStore();
|
||||
if (!(AUTH_SLEUTEL in store)) return { ...await credentialsStatus(), naHerstart: false };
|
||||
delete store[AUTH_SLEUTEL];
|
||||
await writeSecretsStore(store);
|
||||
return { ...await credentialsStatus(), naHerstart: true };
|
||||
}
|
||||
|
||||
const leesOfNull = async (pad) => {
|
||||
try { return await readFile(pad); }
|
||||
catch (e) { if (e.code === 'ENOENT') return null; throw e; }
|
||||
};
|
||||
|
||||
export async function loadTlsOptions() {
|
||||
const certPad = process.env.ASSETAPP_TLS_CERT || join(installDir(), 'tls', 'cert.pem');
|
||||
const keyPad = process.env.ASSETAPP_TLS_KEY || join(installDir(), 'tls', 'key.pem');
|
||||
const [cert, key] = await Promise.all([leesOfNull(certPad), leesOfNull(keyPad)]);
|
||||
if (!cert && !key) return null;
|
||||
if (!cert) throw new Error(`TLS half configured: key found (${keyPad}) but no certificate (${certPad}).`);
|
||||
if (!key) throw new Error(`TLS half configured: certificate found (${certPad}) but no key (${keyPad}).`);
|
||||
return { cert, key };
|
||||
}
|
||||
|
||||
export function resolveBinding({ tls = false, credentials = null, host = null } = {}) {
|
||||
const beschermd = Boolean(tls) && Boolean(credentials);
|
||||
if (host) {
|
||||
if (LOOPBACK.has(host) || beschermd) return { host, waarschuwing: null };
|
||||
const mist = [Boolean(tls) ? null : 'TLS', credentials ? null : 'inloggegevens'].filter(Boolean).join(' en ');
|
||||
return {
|
||||
host,
|
||||
waarschuwing: `ASSETAPP_HOST=${host} stelt de app open voor het hele netwerk terwijl ${mist} ontbreekt. `
|
||||
+ 'Iedereen die deze machine kan bereiken, kan de app openen.',
|
||||
};
|
||||
}
|
||||
return { host: beschermd ? '0.0.0.0' : '127.0.0.1', waarschuwing: null };
|
||||
}
|
||||
Reference in new issue
Block a user