feat(fdroid): FOSS liveness via Regula web Face SDK in a WebView - #666
feat(fdroid): FOSS liveness via Regula web Face SDK in a WebView#666dobby-coder[bot] wants to merge 2 commits into
Conversation
The F-Droid flavor injected no RegulaFaceService, so the shared liveness step was silently skipped during document issuance. Supply a FOSS implementation that runs Regula's web Face SDK inside an embedded webview_flutter (BSD) WebView loading a Yivi-hosted capture page, so no proprietary native binaries ship in the APK. - face_liveness_message.dart: pure mapping from the capture page's YiviFace channel messages to RegulaLivenessResult / abort, matching the native implementation's outcomes. - face_capture_webview.dart: themed, chrome-less WebView route that grants the in-page camera request and resolves back/cancel/load-failure as an abort (a throw, as native). - regula_web_face_service.dart: RegulaWebFaceService implementing the existing seam; presents the route on the root navigator. - Wire RegulaWebFaceService() into yivi_fdroid main.dart. Server-side capture page, deployment, and on-device QA are separate deliverables tracked in the issue. Refs #665 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
VERDICT: approve
Rules Dobby 2 gate — cycle 1. Sign-off complete; kept in draft (see below).
Reviewed the full diff at HEAD dedf626: a per-rule sweep (Dart equality traps, correctness/null-safety, WebView security, PR-title/changelog/closing-keyword process) plus reconciliation of the finding handed forward by the review step. No blocking issues.
What's solid
- The
RegulaFaceServiceseam is reused unchanged; theYiviFacemessage-contract mapping and the abort→throw behaviour match the native build. Unit/service tests cover the full status table (passed / failed / no-id / empty-id / cancelled / error / malformed / unknown) pluslangdefaulting and abort-throwing. _resolveguards double-pop; the asyncsetStateismounted-guarded; theswitchis exhaustive; JSON parsing is defensive. No DartList/Uint8Listreference-equality traps.- Changelog and PR body are accurate.
Part of #665(non-closing) is correct — #665 is a multi-part epic and this is one deliverable, with the server-side capture page, on-device QA and an integration-test harness explicitly out of scope.
Non-blocking (hardening, not a defect) — see the inline note. Harmless today because the page is first-party Yivi-hosted and the OS camera grant is obtained earlier in the issuance flow; worth tightening before release rather than looping now.
Kept in draft deliberately — this PR is stacked on the unmerged #646 (base regula-face-matching), will not compile against master until #646 lands, and on-device liveness QA is still pending. Per default-prs-to-ready those are legitimate reasons to stay draft, so I am not flipping it ready.
| // that we forward to the already-granted app-level camera permission. | ||
| final platform = controller.platform; | ||
| if (platform is AndroidWebViewController) { | ||
| platform.setOnPlatformPermissionRequest((request) => request.grant()); |
There was a problem hiding this comment.
Hardening (non-blocking): the Android handler grants every requested WebView permission (request.grant() with no request.types check), and no onNavigationRequest allowlist pins the WebView to the capture origin while JavaScript is unrestricted — so whatever origin the WebView reached would get silent access. Low impact today (first-party faceapi.*.yivi.app page, OS camera already granted earlier), but for a security-first build consider granting only WebViewPermissionResourceType.camera and rejecting navigation away from the capture origin. Best folded in before release. (I did not push the fix directly: this flavor can't be built/tested here — it's stacked on unmerged #646 — and a mis-typed permission filter would silently break the camera grant.)
Gives the F-Droid build a working liveness check during document issuance without shipping any proprietary native code, per the plan in #665.
What this does
The F-Droid flavor injected no
RegulaFaceService, sowithLivenessTransactionreceivednulland the shared liveness step was silently skipped. This adds a FOSS implementation that runs Regula's web Face SDK inside an embedded WebView loading a Yivi-hosted capture page. The APK gains only the BSDwebview_flutterplugin and a URL; all proprietary code runs remotely.It reuses the existing provider seam unchanged, so the shared issuance flow, the intro screen, and the NFC flow are untouched. The Play Store / App Store native flow is untouched.
Changes (all in
yivi_fdroid)face_liveness_message.dart— pure mapping from the capture page'sYiviFacechannel messages toRegulaLivenessResult/ abort, following the plan's result contract (passed/failed forward the transaction id; cancel and error abort; no id means fail-open).face_capture_webview.dart— chrome-less WebView route wrapped in the standard YiviScaffold/app bar (no URL bar). Grants the in-page camera request on Android, shows a Yivi loading state, and resolves back/cancel/page-load-failure as an abort, which the service turns into a throw so the flow lands on the generic issuance error screen, exactly as the native build.regula_web_face_service.dart—RegulaWebFaceServiceimplementingRegulaFaceService; appends the active?lang=and presents the route on the root navigator.main.dart— injectsRegulaWebFaceService().webview_flutter(+webview_flutter_android) to the pubspec.The WebView chrome reuses existing Yivi widgets and theme tokens (
IrmaAppBar,LoadingIndicator,TranslatedText) and the existingface_verification.*translations, so no new copy or styling is introduced.Stacked on #646
The seam this builds on (
RegulaFaceService,RegulaLivenessResult,withLivenessTransaction) lives in #646 and is not onmasteryet, so this PR targets theregula-face-matchingbranch and will not compile againstmasteruntil #646 lands. Once #646 merges and its branch is deleted, GitHub retargets this PR tomaster.Out of scope (separate deliverables in #665)
<face-liveness>, same-origin service URL, theming, dropping the selfieimagespayload) — a server-side deliverable, not part of the APK.yivi_fdroidintegration-test harness. That flavor has no integration-test infrastructure today; standing one up is its own change. The new liveness logic is covered by unit and service tests instead (see below), and the shared flow is already covered by theyivi_core/yivi_appface tests.Testing
flutter analyze --no-fatal-infosandflutter testpass inyivi_fdroid. Addedtest/regula_web_face_service_test.dart:captureLivenessappends the language code (and defaults toen), returns a completed result, and throws on abort or a dismissed route.Part of #665