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:
@@ -25,8 +25,6 @@ Vue.config.ignoredElements = [/^cds-/];
|
||||
agent: { connected: false, version: '', captureAgentId: '', available: null, device: '' },
|
||||
domain: '',
|
||||
reference: '',
|
||||
otpLookup: '',
|
||||
otpSave: '',
|
||||
staff: null,
|
||||
busy: { lookup: false, save: false },
|
||||
refreshing: false,
|
||||
@@ -49,10 +47,10 @@ Vue.config.ignoredElements = [/^cds-/];
|
||||
this.fingers['right-index-finger'].status === 'done';
|
||||
},
|
||||
canLookup: function () {
|
||||
return !!(this.domain && this.reference && this.otpLookup) && !this.busy.lookup;
|
||||
return !!(this.domain && this.reference) && !this.busy.lookup;
|
||||
},
|
||||
canSave: function () {
|
||||
return this.bothCaptured && !!this.otpSave && !this.busy.save;
|
||||
return this.bothCaptured && !this.busy.save;
|
||||
},
|
||||
refreshUnlocksAt: function () {
|
||||
var ttl = (this.session && this.session.domainCacheTtlMs) || 5 * 60 * 1000;
|
||||
@@ -296,9 +294,7 @@ Vue.config.ignoredElements = [/^cds-/];
|
||||
if (!this.canLookup) return;
|
||||
this.busy.lookup = true;
|
||||
axios.post(
|
||||
'/api/' + encodeURIComponent(this.domain) + '/staff/' + encodeURIComponent(this.reference),
|
||||
null,
|
||||
{ params: { otp: this.otpLookup } }
|
||||
'/api/' + encodeURIComponent(this.domain) + '/staff/' + encodeURIComponent(this.reference)
|
||||
).then(function (r) {
|
||||
if (!r.data.ok) {
|
||||
self.showNotif('error', 'Not found', 'Operation failed. ' + (r.data.message || '') + ' (reference ' + self.reference + ')');
|
||||
@@ -322,8 +318,7 @@ Vue.config.ignoredElements = [/^cds-/];
|
||||
});
|
||||
axios.post(
|
||||
'/api/' + encodeURIComponent(this.domain) + '/staff/' + encodeURIComponent(this.reference) + '/fingerprints',
|
||||
{ fingerprints: fingerprints },
|
||||
{ params: { otp: this.otpSave } }
|
||||
{ fingerprints: fingerprints }
|
||||
).then(function (r) {
|
||||
if (r.data.ok) {
|
||||
self.showNotif('success', 'Enrollment saved',
|
||||
@@ -343,8 +338,6 @@ Vue.config.ignoredElements = [/^cds-/];
|
||||
newEnrollment: function () {
|
||||
this.staff = null;
|
||||
this.reference = '';
|
||||
this.otpLookup = '';
|
||||
this.otpSave = '';
|
||||
this.fingers = { 'right-thumb': newFinger(), 'right-index-finger': newFinger() };
|
||||
this.capturing = null;
|
||||
this.cancelling = false;
|
||||
|
||||
@@ -54,10 +54,6 @@
|
||||
<label class="cds--label" for="reference">Staff reference</label>
|
||||
<input id="reference" class="cds--text-input" v-model.trim="reference" v-on:keyup.enter="lookup" placeholder="e.g. UC/0231">
|
||||
</div>
|
||||
<div style="min-width:130px; max-width:170px;">
|
||||
<label class="cds--label" for="otpLookup">OTP</label>
|
||||
<input id="otpLookup" class="cds--text-input" v-model.trim="otpLookup" v-on:keyup.enter="lookup" autocomplete="one-time-code">
|
||||
</div>
|
||||
<cds-button v-bind:disabled="!canLookup" v-on:click="lookup">
|
||||
<svg slot="icon" width="16" height="16" viewBox="0 0 32 32" fill="currentColor" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><path d="M29,27.5859l-7.5521-7.5521a11.0177,11.0177,0,1,0-1.4141,1.4141L27.5859,29ZM4,13a9,9,0,1,1,9,9A9.01,9.01,0,0,1,4,13Z"/></svg>
|
||||
<span v-if="!busy.lookup">Get details</span>
|
||||
@@ -136,10 +132,6 @@
|
||||
<span class="hint">Both fingerprints will be saved for {{ staff ? staff.name : '' }} ({{ reference }})</span>
|
||||
</div>
|
||||
<div class="row-gap" style="align-items:flex-end;">
|
||||
<div style="min-width:150px; max-width:200px;">
|
||||
<label class="cds--label" for="otpSave">OTP</label>
|
||||
<input id="otpSave" class="cds--text-input" v-model.trim="otpSave" autocomplete="one-time-code">
|
||||
</div>
|
||||
<cds-button v-bind:disabled="!canSave" v-on:click="save">
|
||||
<svg slot="icon" width="16" height="16" viewBox="0 0 32 32" fill="currentColor" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><path d="M27.71,9.29l-5-5A1,1,0,0,0,22,4H6A2,2,0,0,0,4,6V26a2,2,0,0,0,2,2H26a2,2,0,0,0,2-2V10A1,1,0,0,0,27.71,9.29ZM12,6h8v4H12Zm8,20H12V18h8Zm2,0V18a2,2,0,0,0-2-2H12a2,2,0,0,0-2,2v8H6V6h4v4a2,2,0,0,0,2,2h8a2,2,0,0,0,2-2V6.41l4,4V26Z"/></svg>
|
||||
<span v-if="!busy.save">Save enrollment</span>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<p>Enroll staff fingerprints with the scanner attached to this laptop. Captures are authorized per domain.</p>
|
||||
<ol class="help-steps">
|
||||
<li>Open <strong>Fingerprint Agent</strong> from the landing page or the menu.</li>
|
||||
<li>Choose the domain, enter the staff reference and the OTP, then look up the profile.</li>
|
||||
<li>Choose the domain, enter the staff reference, then look up the profile.</li>
|
||||
<li>Confirm the profile is the right person.</li>
|
||||
<li>Follow the on-screen prompts and place each finger on the attached scanner when asked.</li>
|
||||
<li>Review the captured prints and save.</li>
|
||||
|
||||
Reference in New Issue
Block a user