Turn lab IAM on, move portal to access-web, harden sessions and receipts.
Some checks are pending
offline / test (push) Waiting to run
Some checks are pending
offline / test (push) Waiting to run
Fleet and department doors now check STAFF_IAM_URL. Walkthrough: cs can credit (agent is the IAM user) and cannot export; operator can fleet POST; admin /me is owner. Portal is the public web door at :3021/portal/. IAM sessions persist; login is rate-limited per user; receipt PDF is branded. lan-134 stays disabled.
This commit is contained in:
parent
d299d245e8
commit
c32b65038a
20 changed files with 277 additions and 73 deletions
|
|
@ -3,16 +3,17 @@ import fs from 'node:fs';
|
|||
import http from 'node:http';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { allows, publicUser, ROLES, PERMISSIONS, expandRoles } from './roles.js';
|
||||
import { allows, publicUser, ROLES, PERMISSIONS } from './roles.js';
|
||||
import { verifyPassword } from './passwords.js';
|
||||
import { loadIam, saveIam } from './store.js';
|
||||
import {
|
||||
issueSession,
|
||||
getSession,
|
||||
revokeSession,
|
||||
cookieHeader,
|
||||
clearCookieHeader,
|
||||
tokenFromReq,
|
||||
loginLocked,
|
||||
loginFail,
|
||||
loginOk,
|
||||
clientKey,
|
||||
} from './sessions.js';
|
||||
|
||||
const PUBLIC = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'public');
|
||||
|
|
@ -41,7 +42,7 @@ function readBody(req) {
|
|||
|
||||
function actorOf(req) {
|
||||
const tok = tokenFromReq(req);
|
||||
const s = getSession(tok);
|
||||
const s = iam.getSession(tok);
|
||||
if (!s) return null;
|
||||
const u = iam.findById(s.userId);
|
||||
if (!u || !u.active) return null;
|
||||
|
|
@ -87,15 +88,22 @@ const server = http.createServer(async (req, res) => {
|
|||
const username = String(body.username || '').trim();
|
||||
const password = String(body.password || '');
|
||||
const next = body.next || '/';
|
||||
if (loginLocked(req, username)) {
|
||||
iam.log(username || 'unknown', 'login.lock', clientKey(req));
|
||||
saveIam(iam);
|
||||
return json(429, { error: 'too many login attempts; try again in 10 minutes' });
|
||||
}
|
||||
const user = iam.findByUsername(username);
|
||||
if (!user || !user.active || !verifyPassword(password, user.passwordHash)) {
|
||||
loginFail(req, username);
|
||||
iam.log(username || 'unknown', 'login.fail', username);
|
||||
saveIam(iam);
|
||||
if ((req.headers['content-type'] || '').includes('json')) return json(401, { error: 'invalid username or password' });
|
||||
res.writeHead(302, { location: '/login?error=1' });
|
||||
return res.end();
|
||||
}
|
||||
const token = issueSession(user.id);
|
||||
loginOk(req, username);
|
||||
const token = iam.issueSession(user.id);
|
||||
iam.log(username, 'login.ok', username);
|
||||
saveIam(iam);
|
||||
const loc = typeof next === 'string' && (next.startsWith('http') || next.startsWith('/')) ? next : '/';
|
||||
|
|
@ -110,7 +118,8 @@ const server = http.createServer(async (req, res) => {
|
|||
return res.end();
|
||||
}
|
||||
if (req.method === 'POST' && url.pathname === '/logout') {
|
||||
revokeSession(tokenFromReq(req));
|
||||
iam.revokeSession(tokenFromReq(req));
|
||||
saveIam(iam);
|
||||
if ((req.headers['content-type'] || '').includes('json')) {
|
||||
res.writeHead(200, { 'content-type': 'application/json', 'set-cookie': clearCookieHeader() });
|
||||
return res.end(JSON.stringify({ ok: true }));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue