- Real email/password login backed by SQLite (better-sqlite3) - HttpOnly cookie sessions with 8h sliding TTL - Admin role: invite users via Mailgun magic-link, manage roles/status - Per-user One2Edit username mapping for job filtering - Self-service forgot-password / reset-password via email - Admin console (admin.html) with user table, invite modal, row actions - New pages: change-password, forgot-password, reset-password, accept-invite - Gated /api proxy: requires valid session, anti-hijack sessionId check - Bootstrap initial admins from INITIAL_ADMINS env var on first boot - Remove Oliver login button, SSO buttons, and legacy api.js/login.js - deploy.sh: add build-essential (for native module), npm install, data dir Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
59 lines
2.2 KiB
JavaScript
59 lines
2.2 KiB
JavaScript
const https = require('https');
|
|
|
|
const ONE2EDIT_API = 'https://oliver.one2edit.com/v3/Api.php';
|
|
const SERVICE_USERNAME = () => process.env.SERVICE_USERNAME;
|
|
const SERVICE_PASSWORD = () => process.env.SERVICE_PASSWORD;
|
|
|
|
function apiRequest(params) {
|
|
params.set('authDomain', 'local');
|
|
params.set('authUsername', SERVICE_USERNAME());
|
|
params.set('authPassword', SERVICE_PASSWORD());
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const url = `${ONE2EDIT_API}?${params}`;
|
|
const req = https.get(url, res => {
|
|
const chunks = [];
|
|
res.on('data', c => chunks.push(c));
|
|
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
});
|
|
req.on('error', reject);
|
|
req.setTimeout(15000, () => { req.destroy(new Error('One2Edit timeout')); });
|
|
});
|
|
}
|
|
|
|
function parseXml(xml) {
|
|
const get = tag => {
|
|
const m = xml.match(new RegExp(`<${tag}[^>]*>([^<]*)</${tag}>`));
|
|
return m ? m[1].trim() : null;
|
|
};
|
|
return { get };
|
|
}
|
|
|
|
async function userInfo(username) {
|
|
const params = new URLSearchParams({ command: 'user.info', clientId: '6', username, domain: 'local' });
|
|
const xml = await apiRequest(params);
|
|
const doc = parseXml(xml);
|
|
const errMsg = doc.get('message');
|
|
if (xml.includes('<error>') && errMsg) throw new Error(errMsg);
|
|
const userId = doc.get('id');
|
|
if (!userId) throw new Error('User not found in One2Edit');
|
|
return userId;
|
|
}
|
|
|
|
async function externSessionAdd(username, userId) {
|
|
const params = new URLSearchParams({ command: 'user.session.extern.add', clientId: '7', username, userId });
|
|
const xml = await apiRequest(params);
|
|
const doc = parseXml(xml);
|
|
const errMsg = doc.get('message');
|
|
if (xml.includes('<error>') && errMsg) throw new Error(errMsg);
|
|
const sessionId = doc.get('externSessionId');
|
|
if (!sessionId) throw new Error('No externSessionId returned');
|
|
return sessionId;
|
|
}
|
|
|
|
async function externSessionRemove(externSessionId) {
|
|
const params = new URLSearchParams({ command: 'user.session.extern.remove', clientId: '7', externSessionId });
|
|
try { await apiRequest(params); } catch { /* best-effort */ }
|
|
}
|
|
|
|
module.exports = { userInfo, externSessionAdd, externSessionRemove };
|