| Availability |
Odoo Online
Odoo.sh
On Premise
|
| Odoo Apps Dependencies |
•
Attendances (hr_attendance)
• Discuss (mail) • Employees (hr) |
| Community Apps Dependencies | Show |
| Lines of code | 4705 |
| Technical Name |
eh_hr_face_liveness |
| License | LGPL-3 |
| Website | https://www.erpheritage.com.au/ |
| Versions | 16.0 17.0 18.0 19.0 |
Attended Face Capture
An auditable attended-capture step for the face kiosk, honest about what it is and what it is not.
Why this module
Attended Face Capture
What it is, and what it is not
The outcome is computed in the browser and recorded as reported. The server runs no liveness detection, no anti-spoofing, no ML check. A printed photo or a screen at the camera is not caught. We say so in the manifest, the field help, and here, so a recorded pass is never mistaken for proof of a real person.
A clean record of every attempt
Each attempt writes one row to eh.hr.liveness.check with outcome, prompt text, duration, reported blink count, device, site, and company. Rows are create-and-read only through the UI, so the trail stands up for attendance dispute and review rather than being quietly editable after the fact.
LGPL-3 source on disk
No activation key, no phone-home, no recurring licence. The blink detection, the endpoints, and the retention logic are all readable on disk. Extend it, or sit it beneath a licensed server-side liveness model as the attendance-audit layer it is built to be.
Day in the life
A site supervisor reviews a disputed clock-in.
An employee says the kiosk let someone else clock in under their face last Tuesday. The supervisor opens Attended captures, filters to that site and day, and sees the row: a passed outcome at 07:42, two blinks reported, 2.4 seconds on the device, against a named terminal. The record is read-only, so it has not been edited since it was written. Because the column header and the field help both mark the outcome client-asserted, the supervisor knows exactly how much weight it carries: it confirms a capture attempt happened on that device at that time, not that the camera proved a live person. That honesty is the point. The supervisor pairs it with the face-match record from the kiosk and resolves the dispute on evidence rather than on a green tick the system never earned. Stale rows past the company retention horizon are already gone, swept nightly by a batch-capped cron so the table stays lean.
Edge cases
The cases most modules quietly ignore.
In the shipped code today, each one a place where a cheaper module silently does the wrong thing.
The outcome is whatever the kiosk browser reports. The endpoint validates that it is one of passed, failed, or aborted and stores it, but performs no verification. The record and its field help both stamp it client-asserted, so nobody downstream treats a pass as server-proven.
Both the config and log endpoints require a valid X-EH-Kiosk-Token matching an active kiosk terminal. An unknown or missing token gets a 401, so a random caller cannot read settings or write fake capture rows.
Each endpoint is throttled at 120 hits per window through the shared eh.hr.rate.limit service, keyed on the device token or client IP. A device flooding the log endpoint gets a 429 instead of filling the audit table.
The nightly sweep walks each company with its own retention horizon (default 180 days), deletes only rows past the cutoff, and stops at a per-run batch cap of 2000 so one run cannot stall the worker. It reports progress through the cron commit-progress hook for clean isolation.
A global record rule scopes every capture row to the user's allowed companies, and each row carries its company from the device that wrote it. One tenant cannot read another tenant's capture trail.
The list view is create=false, edit=false and the ACLs grant no write to any group. Managers and auditors read; only admin may delete. Once written, a capture row cannot be quietly altered.
If the browser blocks the camera or face-api.js is not loaded, the client reports aborted or failed with a reason rather than hanging, and a config call that is disabled or fails simply lets check-in proceed without blocking the kiosk.
What is inside
Built to do the job, end to end.
- eh.hr.liveness.check model. One row per attempt: outcome, recorded-on timestamp, prompt text, duration in ms, reported blink count, fail reason, device, site, optional employee, and company. The client-asserted flag defaults true and the record is read-only by design.
- Kiosk endpoints. /eh_hr/kiosk/liveness/config returns the per-company enabled flag, required blinks, and timeout to the kiosk. /eh_hr/kiosk/liveness/log records the reported outcome. Both are token-authenticated and rate-limited, and the log call also writes a kiosk event.
- In-browser blink prompt. A liveness screen injected into the kiosk shell runs eye-aspect-ratio detection over face-api.js landmarks, counts blinks toward the required count, and shows a fail-and-retry screen on timeout. Detection runs entirely client-side.
- Per-company settings. Settings expose the attended-capture toggle, prompted blink count, capture timeout in ms, and retention days, each related to res.company so multi-company groups configure them independently.
- Retention sweep cron. A daily code cron calls _cron_retention_sweep, which prunes rows past each company horizon under a batch limit and reports progress for cron isolation.
- Tests. Python tests cover record creation, the retention sweep deleting aged rows, the 401 on a missing token, config returning company settings, log creating both a check and a kiosk event for pass and fail, and a 400 on an invalid outcome.
Honest about the edges
What this does not do, so nothing surprises you.
- This is not a liveness or anti-spoofing control. The server does no liveness detection and no ML verification; it records the outcome the browser reports.
- A printed photo, a video, or a screen held to the camera is not detected. Anyone controlling the kiosk device can report any outcome.
- A recorded passed outcome is evidence that a capture attempt occurred on that device at that time, not proof that a real, live person was present.
- Blink detection runs only in the browser and depends on face-api.js and camera access being available on the kiosk device.
- The capture row is not linked to an employee at log time; the employee is set later by the kiosk face match, and may be blank on standalone rows.
- Requires the ERP Heritage face kiosk and attendance base modules; it extends that kiosk and is not a standalone attendance system.
- For a genuine anti-spoofing control, pair it with a separately licensed server-side passive-liveness model; this module is the attendance-audit layer beneath such a model, not a substitute for it.
attended face capture odoo, face kiosk check-in audit, hr_attendance face capture, odoo 19 community face attendance, kiosk attendance audit log, blink prompt liveness ux, client-asserted capture record, per-company attendance retention, rate-limited kiosk endpoint, face kiosk audit trail
Please log in to comment on this module