feat(fpagent): authenticate the wizard's a2loft calls with the session token
The fingerprint wizard's staff-lookup and fingerprint-save proxies forwarded the page's OTP with the session phone. a2loft resolves that phone against the domain being addressed, so an operator who holds only capture_biometric was rejected unless they were a domain super-user. The cropper already saves with the session token. - tools/server.js: both a2loft proxies send Authorization: Bearer <session token>, no phone/otp; validateProxy drops the OTP and phone checks. - tools/public/assets/fp: both OTP fields, otpLookup/otpSave state, request params and resets removed; canLookup/canSave no longer gate on OTP. - Tests assert the Bearer header, no query auth, no OTP field in the served page, and both gates without a credential (66/66 pass). - README and Help page updated. Requires the a2loft fingerprint-token-auth change deployed first. OpenSpec change: fingerprint-token-auth.
This commit is contained in:
+15
-24
@@ -704,19 +704,11 @@ export function createApp(overrides = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
// ── a2loft proxies: same-origin, phone injected from the session ──────────
|
||||
const a2loftUrl = (domain, reference, otp, phone) => {
|
||||
const q = new URLSearchParams({ phone, otp });
|
||||
return `${cfg.a2loftBase.replace(/\/+$/, '')}/identity/${encodeURIComponent(domain)}/${encodeURIComponent(
|
||||
reference
|
||||
)}?${q.toString()}`;
|
||||
};
|
||||
const a2loftSaveUrl = (domain, reference, otp, phone) => {
|
||||
const q = new URLSearchParams({ phone, otp });
|
||||
return `${cfg.a2loftBase.replace(/\/+$/, '')}/identity/${encodeURIComponent(domain)}/fingerprints/${encodeURIComponent(
|
||||
reference
|
||||
)}?${q.toString()}`;
|
||||
};
|
||||
// ── a2loft proxies: same-origin, bearer token from the session ────────────
|
||||
const a2loftUrl = (domain, reference) =>
|
||||
`${cfg.a2loftBase.replace(/\/+$/, '')}/identity/${encodeURIComponent(domain)}/${encodeURIComponent(reference)}`;
|
||||
const a2loftSaveUrl = (domain, reference) =>
|
||||
`${cfg.a2loftBase.replace(/\/+$/, '')}/identity/${encodeURIComponent(domain)}/fingerprints/${encodeURIComponent(reference)}`;
|
||||
|
||||
const validateProxy = (req, res) => {
|
||||
const { domain, reference } = req.params;
|
||||
@@ -728,21 +720,15 @@ export function createApp(overrides = {}) {
|
||||
res.status(400).json({ ok: false, message: 'Missing staff reference.' });
|
||||
return false;
|
||||
}
|
||||
if (!req.query.otp) {
|
||||
res.status(400).json({ ok: false, message: 'OTP is required.' });
|
||||
return false;
|
||||
}
|
||||
if (!req.session.phone) {
|
||||
res.status(400).json({ ok: false, message: 'The signed-in account has no phone number.' });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
app.post('/api/:domain/staff/:reference', requireApi, async (req, res) => {
|
||||
if (!validateProxy(req, res)) return;
|
||||
try {
|
||||
const r = await cfg.fetch(a2loftUrl(req.params.domain, req.params.reference, req.query.otp, req.session.phone));
|
||||
const r = await cfg.fetch(a2loftUrl(req.params.domain, req.params.reference), {
|
||||
headers: { authorization: `Bearer ${req.session.token}` },
|
||||
});
|
||||
res.status(r.status).type('json').send(await r.text());
|
||||
} catch {
|
||||
res.status(502).json({ ok: false, message: 'Could not reach the identity service.' });
|
||||
@@ -755,8 +741,13 @@ export function createApp(overrides = {}) {
|
||||
return res.status(400).json({ ok: false, message: 'A fingerprints array is required.' });
|
||||
}
|
||||
try {
|
||||
const r = await cfg.fetch(a2loftSaveUrl(req.params.domain, req.params.reference, req.query.otp, req.session.phone), {
|
||||
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(req.body)
|
||||
const r = await cfg.fetch(a2loftSaveUrl(req.params.domain, req.params.reference), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
authorization: `Bearer ${req.session.token}`,
|
||||
},
|
||||
body: JSON.stringify(req.body),
|
||||
});
|
||||
res.status(r.status).type('json').send(await r.text());
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user