Skip to content

feat(fdroid): FOSS liveness via Regula web Face SDK in a WebView - #666

Draft
dobby-coder[bot] wants to merge 2 commits into
regula-face-matchingfrom
feat/fdroid-web-face-liveness-665
Draft

feat(fdroid): FOSS liveness via Regula web Face SDK in a WebView#666
dobby-coder[bot] wants to merge 2 commits into
regula-face-matchingfrom
feat/fdroid-web-face-liveness-665

Conversation

@dobby-coder

@dobby-coder dobby-coder Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

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, so withLivenessTransaction received null and 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 BSD webview_flutter plugin 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's YiviFace channel messages to RegulaLivenessResult / 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 Yivi Scaffold/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.dartRegulaWebFaceService implementing RegulaFaceService; appends the active ?lang= and presents the route on the root navigator.
  • main.dart — injects RegulaWebFaceService().
  • Adds webview_flutter (+ webview_flutter_android) to the pubspec.
  • Updates the Unreleased changelog entry to describe the F-Droid web-based flow (it previously said F-Droid had face verification disabled).

The WebView chrome reuses existing Yivi widgets and theme tokens (IrmaAppBar, LoadingIndicator, TranslatedText) and the existing face_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 on master yet, so this PR targets the regula-face-matching branch and will not compile against master until #646 lands. Once #646 merges and its branch is deleted, GitHub retargets this PR to master.

Out of scope (separate deliverables in #665)

  • The Yivi-hosted capture page (embedding <face-liveness>, same-origin service URL, theming, dropping the selfie images payload) — a server-side deliverable, not part of the APK.
  • Face SDK Web Service origin / access-model configuration and the production host decision.
  • On-device QA (camera grant, passive pass, spoof fail, cancel, load failure, per locale) and the F-Droid listing NonFreeNet tag.
  • A full yivi_fdroid integration-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 the yivi_core/yivi_app face tests.

Testing

flutter analyze --no-fatal-infos and flutter test pass in yivi_fdroid. Added test/regula_web_face_service_test.dart:

  • the full message contract table (passed / failed / no id / empty id / cancelled / error / malformed / unknown status);
  • captureLiveness appends the language code (and defaults to en), returns a completed result, and throws on abort or a dismissed route.

Part of #665

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>
@dobby-coder
dobby-coder Bot requested a review from rubenhensen July 17, 2026 11:54

@dobby-coder dobby-coder Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 RegulaFaceService seam is reused unchanged; the YiviFace message-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) plus lang defaulting and abort-throwing.
  • _resolve guards double-pop; the async setState is mounted-guarded; the switch is exhaustive; JSON parsing is defensive. No Dart List/Uint8List reference-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());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

@rubenhensen
rubenhensen requested review from w-ensink and removed request for rubenhensen July 20, 2026 11:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant