Skip to content

feat/various style improvements and backwards compatible didit migration#883

Merged
coodos merged 9 commits intomainfrom
fix/styling-on-onboarding-buttons
Feb 25, 2026
Merged

feat/various style improvements and backwards compatible didit migration#883
coodos merged 9 commits intomainfrom
fix/styling-on-onboarding-buttons

Conversation

@coodos
Copy link
Contributor

@coodos coodos commented Feb 25, 2026

Description of change

Issue Number

Type of change

  • Update (a change which updates existing functionality)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added hardware signing fallback that automatically switches to software key when hardware signing fails
    • Introduced BottomSheet component for improved modal interactions
  • Updates

    • Updated verification API endpoints to v2
    • Improved responsive design with safe-area support for notched devices
    • Modernized UI component layouts and styling

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

Adds a BottomSheet UI and migrates many Drawer usages, bumps eid-wallet to 0.6.0, versioned KYC routes to /verification/v2, implements hardware signing fallback in KeyService wired to GlobalState+VaultController, and introduces a LegacyVerificationController to support legacy /verification routes alongside v2.

Changes

Cohort / File(s) Summary
Version & Tauri
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src-tauri/tauri.conf.json
Bump eid-wallet package to 0.6.0; increase Android versionCode and reformat security capabilities.
New BottomSheet UI
infrastructure/eid-wallet/src/lib/ui/BottomSheet/BottomSheet.svelte, infrastructure/eid-wallet/src/lib/ui/index.ts
Add accessible BottomSheet component (isOpen, dismissible, onOpenChange) and export it from UI index.
Drawer → BottomSheet Migration
infrastructure/eid-wallet/src/lib/ui/CameraPermissionDialog/CameraPermissionDialog.svelte, .../routes/(app)/main/+page.svelte, .../routes/(app)/settings/+page.svelte, .../settings/passphrase/+page.svelte, .../settings/pin/+page.svelte, .../scan-qr/components/*, .../(auth)/login/+page.svelte, .../(app)/ePassport/+page.svelte
Replace Drawer usages with BottomSheet across many routes/components; update binding prop from isPaneOpen→isOpen and adjust markup/styling/spacing accordingly.
Hardware Signing Fallback
infrastructure/eid-wallet/src/lib/global/controllers/key.ts, infrastructure/eid-wallet/src/lib/global/state.ts
Add HardwareFallbackEvent type and KeyService.setHardwareFallbackHandler; implement fallback to software signing on hardware failure and wire handler in GlobalState to attempt syncPublicKey via VaultController.
KYC API Versioning
infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte, infrastructure/eid-wallet/src/routes/(app)/ePassport/+page.svelte, infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte, tests
Switch verification endpoints to versioned paths (/verification/v2, /verification/v2/decision, /verification/v2/upgrade); update related tests.
Scan/Wallet Context Change
infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts
Change wallet-sdk context usage from onboarding → signing in auth and ensureKey calls.
Layout / Safe-area Adjustments
multiple layout pages .../+layout.svelte, .../(auth)/*
Change container sizing to min-h/[100svh], add safe-area-aware padding-top, and adjust overflow/padding for responsive viewports.
Verification Controllers & Compatibility
infrastructure/evault-core/src/controllers/LegacyVerificationController.ts, infrastructure/evault-core/src/controllers/VerificationController.ts, .../VerificationRoutesCompatibility.spec.ts, .../SessionIdValidation.spec.ts, infrastructure/evault-core/src/index.ts
Add LegacyVerificationController exposing legacy /verification endpoints (SSE, media, session create/patch/decision), update existing VerificationController routes to /verification/v2, add compatibility tests, and register legacy controller on startup.

Sequence Diagram(s)

sequenceDiagram
    participant App as Client/App
    participant KeySvc as KeyService
    participant GlobalState as GlobalState
    participant VaultCtrl as VaultController
    participant SDK as wallet-sdk

    App->>KeySvc: signPayload(payload)
    activate KeySvc
    KeySvc->>KeySvc: attempt hardware signing
    KeySvc--xKeySvc: hardware signing fails
    KeySvc->>KeySvc: perform software signing fallback
    KeySvc->>SDK: persist software-backed context
    KeySvc->>KeySvc: runHardwareFallbackCallback(keyId, context, error)
    KeySvc->>GlobalState: emit hardwareFallback(keyId, context, error)
    deactivate KeySvc

    activate GlobalState
    GlobalState->>GlobalState: log warning
    GlobalState->>VaultCtrl: get vault info
    VaultCtrl-->>GlobalState: return vault.ename
    GlobalState->>VaultCtrl: syncPublicKey(vault.ename)
    VaultCtrl->>SDK: update public key
    VaultCtrl-->>GlobalState: sync result
    GlobalState->>GlobalState: log result or error
    deactivate GlobalState
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #464: Modifies VaultController.syncPublicKey and its call sites — directly related to the hardware fallback wiring.
  • #567: Touches scan-qr/scanLogic.ts and auth/signing flows — overlaps with context changes in this PR.
  • #472: Adjusts Tauri opener/deeplink and mobile capabilities — related to the tauri.conf.json capability edits and deeplink auth flows.

Suggested labels

evault-refactor

Suggested reviewers

  • sosweetham

Poem

🐇 I dug a tunnel in the UI light,

Drawers slid out and sheets took flight,
When hardware trips, I softly sigh—then boot the key in code so spry,
Versions marched from one to two, hooray! —a rabbit hops to celebrate the day.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description follows the template structure with Type of change marked, but lacks specific details about testing methodology, issue reference, and implementation specifics given the scope of changes. Add details on testing approach (unit/integration/manual), provide specific issue number, and explain the backwards compatibility strategy for the Didit migration to clarify the scope.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: style improvements and a backwards-compatible migration from Didit v1 to v2 verification endpoints, directly reflecting the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/styling-on-onboarding-buttons

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
infrastructure/eid-wallet/src/routes/(app)/ePassport/+page.svelte (1)

248-252: ⚠️ Potential issue | 🟡 Minor

String.replace only substitutes the first space — use replaceAll or a regex.

rawStatus.toLowerCase().replace(" ", "_") replaces only the first space in the status string. While current known statuses ("approved", "declined", "in review") each contain at most one space, a backend change returning a multi-word status (e.g. "under review") would silently produce "under review" instead of "under_review", falling through to the declined UI without a clear signal.

🐛 Proposed fix
-        diditResult = rawStatus.toLowerCase().replace(" ", "_") as
+        diditResult = rawStatus.toLowerCase().replaceAll(" ", "_") as
             | "approved"
             | "declined"
             | "in_review";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`(app)/ePassport/+page.svelte around
lines 248 - 252, The code uses rawStatus.toLowerCase().replace(" ", "_") which
only replaces the first space; update the conversion that sets diditResult
(using decision.status and rawStatus) to replace all spaces (e.g., use
replaceAll(" ", "_") or rawStatus.replace(/\s+/g, "_")) before the cast so
multi-word statuses map correctly to "approved" | "declined" | "in_review";
ensure you normalize casing and whitespace consistently prior to assigning
diditResult.
infrastructure/eid-wallet/package.json (1)

45-45: ⚠️ Potential issue | 🟡 Minor

Remove unused "import": "^0.0.6" production dependency.

The import package is a legacy CLI tool from ~11 years ago, not a no-op stub. However, no usages of this package are found in the codebase. If it's not required, remove it from package.json to keep dependencies clean.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/package.json` at line 45, Remove the unused
production dependency "import": "^0.0.6" from package.json; search the
repository for any references to the import package to confirm it's unused, then
remove the entry for "import" and regenerate the lockfile by running your
package manager (npm install or yarn install) so package-lock.json / yarn.lock
is updated and CI installs reflect the change.
infrastructure/eid-wallet/src/lib/global/controllers/key.ts (1)

157-186: ⚠️ Potential issue | 🟠 Major

Remove or gate verbose diagnostic logging — payload hex and full public key are security-sensitive.

The signPayload method unconditionally logs:

  • The raw payload string and its full hex encoding (Lines 162–167), which can contain PII or credential material.
  • The complete public key (Line 179), which exposes key material.

These console.log calls fire on every sign operation in production.

🔒️ Proposed fix — gate behind a debug flag or remove
-        console.log(`Payload: "${payload}"`);
-        console.log(`Payload length: ${payload.length} bytes`);
-        const payloadHex = Array.from(new TextEncoder().encode(payload))
-            .map((b) => b.toString(16).padStart(2, "0"))
-            .join("");
-        console.log(`Payload (hex): ${payloadHex}`);
+        // Payload details omitted from production logs
-                console.log(`Public key: ${publicKey.substring(0, 60)}...`);
-                console.log(`Public key (full): ${publicKey}`);
+                console.log("Public key available");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/lib/global/controllers/key.ts` around lines 157
- 186, The signPayload method currently emits sensitive logs — remove or gate
the raw payload, its hex (payloadHex) and the full public key logs; instead only
log non-sensitive context (e.g., keyId, managerType) and a masked public key
(first/last few chars) or nothing at all. Replace the unconditional console.log
calls around payload, payloadHex and the full public key in signPayload and the
getPublicKey try/catch with either: (a) conditional debug-level logging behind a
debug flag / environment variable / logger.isDebugEnabled check, or (b) drop
them entirely; keep short, non-sensitive messages when needed and avoid printing
the full public key or entire payload. Ensure references to payloadHex,
console.log lines that print `Payload:`, `Payload (hex):`, and the `Public key
(full):` output are removed or gated.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

290-294: ⚠️ Potential issue | 🟡 Minor

String.replace() only replaces the first occurrence — use replaceAll or a regex.

If the Didit API ever returns a status like "in review" with multiple spaces (or changes its format), rawStatus.toLowerCase().replace(" ", "_") silently produces a wrong value that won't match any of the "approved" | "declined" | "in_review" union members, causing the UI to fall through to the else (Verification Failed) branch incorrectly.

🐛 Proposed fix
-        diditResult = rawStatus.toLowerCase().replace(" ", "_") as
+        diditResult = rawStatus.toLowerCase().replaceAll(" ", "_") as
             | "approved"
             | "declined"
             | "in_review";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
290 - 294, The code uses rawStatus.toLowerCase().replace(" ", "_") which only
replaces the first space and can produce incorrect diditResult; change it to
replace all spaces (e.g., rawStatus.toLowerCase().replaceAll(" ", "_") or
rawStatus.toLowerCase().replace(/\s+/g, "_")) when computing diditResult from
decision.status so the final value reliably matches "approved" | "declined" |
"in_review".
infrastructure/eid-wallet/src/routes/+layout.svelte (2)

317-322: ⚠️ Potential issue | 🟠 Major

checkAuth() is fire-and-forget — unhandled rejections silently swallowed.

checkAuth is an async function but is invoked without await (and without .catch) in all three deep-link action branches (auth, sign, reveal). Any rejection inside checkAuth — including failed goto calls or globalState.vaultController.vault access errors — is silently dropped. A .catch guard at minimum should be added.

🛡️ Proposed fix (apply to all three `checkAuth()` call sites)
-                    checkAuth();
+                    checkAuth().catch((error) => {
+                        console.error("Deep link checkAuth failed:", error);
+                    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`+layout.svelte around lines 317 - 322,
The call to the async function checkAuth() in the deep-link action branches
(auth, sign, reveal) is currently fire-and-forget and can swallow rejections
(e.g., from goto() or globalState.vaultController.vault); update each call site
to handle promise rejections by either awaiting checkAuth() where the caller is
async or chaining .catch(...) to log and handle errors (include the error in the
log), ensuring any navigation failures from goto() or vault access errors are
surfaced and handled.

631-631: ⚠️ Potential issue | 🟡 Minor

Remove debug $effect before shipping.

$effect(() => console.log("top", safeAreaTop)) will fire on every safeAreaTop change in production.

🔧 Proposed fix
-$effect(() => console.log("top", safeAreaTop));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`+layout.svelte at line 631, Remove the
debug $effect that logs safeAreaTop in production: delete the line $effect(() =>
console.log("top", safeAreaTop)) from the component, or if you want to keep it
for local debugging wrap it with a dev-only guard (import { dev } from
'$app/environment' and run the $effect only when dev is true) so logging of
safeAreaTop does not run in production.
infrastructure/eid-wallet/src/routes/(app)/settings/passphrase/+page.svelte (1)

164-177: ⚠️ Potential issue | 🟡 Minor

Pass dismissible={false} to prevent accidental dismissal without navigation.

The BottomSheet defaults to dismissible: true, allowing users to dismiss the sheet via backdrop click or gesture. Since this doesn't trigger the handleClose callback, the navigation to /settings is skipped. Either disable dismissal entirely with dismissible={false}, or wire onOpenChange to ensure navigation occurs when the sheet closes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`(app)/settings/passphrase/+page.svelte
around lines 164 - 177, The BottomSheet currently bound to showSuccessDrawer
allows backdrop/gesture dismissal which bypasses handleClose and navigation;
update the BottomSheet component (the BottomSheet element that binds
isOpen={showSuccessDrawer}) to either add dismissible={false} to prevent
accidental dismissal or add an onOpenChange handler that calls handleClose (or
triggers the same navigation) when isOpen becomes false so closing always runs
the navigation logic tied to handleClose.
🧹 Nitpick comments (13)
infrastructure/eid-wallet/src/routes/(app)/ePassport/+page.svelte (2)

592-597: startSocialBindingPolling creates a new interval without guarding against an already-running one.

If called when socialBindingPollInterval is non-null (e.g. due to an unexpected double-call path or future refactoring), a second interval is leaked and stopSocialBindingPolling will only clear the latest reference, leaving the first running silently.

♻️ Proposed fix — add a defensive stop at the top
 function startSocialBindingPolling() {
+    stopSocialBindingPolling();
     socialBindingPolling = true;
     socialBindingPollInterval = setInterval(() => {
         void runSocialBindingPoll();
     }, 3000);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`(app)/ePassport/+page.svelte around
lines 592 - 597, startSocialBindingPolling currently unconditionally creates a
new setInterval which can leak if one already exists; modify
startSocialBindingPolling to first guard against an existing interval (check
socialBindingPollInterval) and clear it (or call stopSocialBindingPolling())
before creating a new interval, then set socialBindingPolling = true and assign
socialBindingPollInterval = setInterval(() => { void runSocialBindingPoll(); },
3000); ensure the previous interval reference is nulled so
stopSocialBindingPolling can reliably clear the active timer.

620-620: {#if docData} is always truthy — the guard is misleading.

docData is initialised to {} on line 29 and never set to null/undefined, so this condition is always true. The block renders nothing when the object is empty (because Object.entries({}) yields no pairs), so there is no runtime bug — but the guard implies docData could be falsy, which may confuse future readers.

♻️ Proposed fix — guard on non-empty entries
-    {`#if` docData}
+    {`#if` Object.keys(docData).length > 0}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`(app)/ePassport/+page.svelte at line
620, The template guard {`#if` docData} is misleading because docData is
initialized to an empty object and never falsy; change the check to explicitly
test for non-empty object entries (e.g., use Object.keys(docData).length > 0 or
Object.entries(docData).length) or introduce a boolean like hasDocData derived
from docData and use that in the {`#if`} so the intent (render only when docData
contains properties) is clear; update the {`#if` docData} block and any related
logic that assumes a truthy/falsy docData accordingly.
infrastructure/eid-wallet/package.json (2)

28-28: @tailwindcss/container-queries is redundant with Tailwind CSS v4.

Container queries are built into Tailwind v4 natively—this plugin was only needed for v3 and is not registered in any config. Remove from dependencies.

♻️ Proposed removal
-        "@tailwindcss/container-queries": "^0.1.1",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/package.json` at line 28, Remove the redundant
dependency "@tailwindcss/container-queries" from package.json (it’s unnecessary
with Tailwind CSS v4), then reinstall/update lockfiles (npm/yarn/pnpm install)
to ensure the dependency is removed from node_modules and the lockfile; also
scan for any leftover references to "@tailwindcss/container-queries" in config
or code (none expected) and remove them if found.

75-75: Remove autoprefixer — Tailwind CSS v4 makes it redundant.

Tailwind v4 uses Lightning CSS for vendor prefixing by default, eliminating the need for the separate autoprefixer plugin. Keeping both can cause redundant prefixing work. Browser target detection comes from your Browserslist configuration.

♻️ Proposed removal
-        "autoprefixer": "^10.4.21",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/package.json` at line 75, Remove the redundant
"autoprefixer" dependency from package.json (delete the "autoprefixer" entry)
and run your package manager install to update lockfiles; also remove or adjust
any references to "autoprefixer" in build configs (e.g., postcss.config.js or
Vite/webpack config) so Tailwind v4's Lightning CSS handles vendor prefixing and
there are no leftover plugin entries.
infrastructure/eid-wallet/src/lib/global/controllers/key.ts (2)

63-69: reset() does not clear #onHardwareFallback.

If a KeyService instance is reset() (Lines 63–69) and then reused directly (outside the GlobalState.reset() flow), the stale fallback handler registered before the reset remains active and can reference deallocated or stale VaultController/vaultController state.

Since GlobalState.reset() creates a fresh KeyService this is safe for the primary usage path, but for any direct KeyService reuse (e.g., tests, isolated usage), this is a latent bug.

♻️ Proposed fix
     async reset(): Promise<void> {
         this.#managerCache.clear();
         this.#contexts.clear();
         await this.#store.delete(CONTEXTS_KEY);
         await this.#store.delete(READY_KEY);
         this.#ready = false;
+        this.#onHardwareFallback = null;
     }

Also applies to: 332-339

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/lib/global/controllers/key.ts` around lines 63
- 69, The reset() method in KeyService currently clears manager/cache and
contexts but doesn't clear the stored hardware fallback handler
(`#onHardwareFallback`), leaving a stale callback that can reference old
VaultController/vaultController state; update KeyService.reset() to also
clear/unset `#onHardwareFallback` (and any similar fields referenced elsewhere,
e.g., the other reset-like method) so the instance no longer retains the old
fallback handler when reused outside GlobalState.reset(), ensuring any
registered hardware fallback is removed on reset.

19-23: Export HardwareFallbackEvent to unblock callers of the public setHardwareFallbackHandler API.

HardwareFallbackEvent is used as the parameter type of the public setHardwareFallbackHandler method (Line 334) but is not exported. External consumers registering a typed handler cannot reference this type without TypeScript inferring it indirectly.

♻️ Proposed fix
-type HardwareFallbackEvent = {
+export type HardwareFallbackEvent = {
     keyId: string;
     context: KeyServiceContext;
     originalError: unknown;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/lib/global/controllers/key.ts` around lines 19
- 23, The type HardwareFallbackEvent is declared but not exported while it is
used as the parameter type for the public API setHardwareFallbackHandler, so
external callers cannot reference it; fix by exporting the type (export type
HardwareFallbackEvent = {...}) in the same file where HardwareFallbackEvent is
declared (so callers can import it) and ensure any local imports/usages of
HardwareFallbackEvent and the public function setHardwareFallbackHandler remain
consistent with the exported name.
infrastructure/eid-wallet/src/lib/global/state.ts (1)

54-79: Concurrent hardware fallback events can trigger simultaneous syncPublicKey calls.

If multiple signPayload calls fail on hardware in quick succession (e.g., during batch signing), each awaits the handler independently, resulting in N concurrent syncPublicKey(vault.ename) invocations. Depending on the syncPublicKey implementation (upsert semantics, server-side rate limits), this could cause duplicate operations or conflicts.

Consider debouncing or guarding with an in-progress flag:

♻️ Proposed guard
+    `#syncInProgress` = false;
+
     this.keyService.setHardwareFallbackHandler(
         async ({ keyId, context, originalError }) => {
             console.warn(/* ... */);

             try {
+                if (this.#syncInProgress) return;
+                this.#syncInProgress = true;
                 const vault = await this.vaultController.vault;
                 if (!vault?.ename) return;
                 await this.vaultController.syncPublicKey(vault.ename);
             } catch (syncError) {
                 console.error(
                     "[GlobalState] Failed to sync fallback public key:",
                     syncError,
                 );
+            } finally {
+                this.#syncInProgress = false;
             }
         },
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/lib/global/state.ts` around lines 54 - 79, The
hardware fallback handler registered via keyService.setHardwareFallbackHandler
can trigger concurrent calls to vaultController.syncPublicKey(vault.ename); add
a guard (e.g., an inProgress flag or mutex) inside that async handler to ensure
only one syncPublicKey runs at a time for a given vault. Specifically, within
the async ({ keyId, context, originalError }) => { ... } closure, check and set
a per-vault or global "syncing" marker before awaiting
this.vaultController.syncPublicKey(vault.ename), skip or await the existing
operation if already in progress, and clear the marker in finally so subsequent
fallbacks can proceed; apply this around the existing try/catch where
vaultController.vault and vaultController.syncPublicKey are used.
infrastructure/eid-wallet/src/routes/(auth)/review/+page.svelte (1)

7-7: Remove unused axios import.

axios is imported but never called in this file. This adds unnecessary bundle weight.

🔧 Proposed fix
-import axios from "axios";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`(auth)/review/+page.svelte at line 7,
Remove the unused axios import from the Svelte page: delete the line importing
axios (import axios from "axios";) from the +page.svelte file since there are no
references to axios in this module; ensure no other code relies on axios in this
file and run the build to confirm bundle size and lint warnings are resolved.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (2)

234-234: Magic 50 ms delay before SDK import is fragile.

This setTimeout(r, 50) is a workaround for a race condition (likely the kycStep state update not being reflected in the DOM before the SDK mounts into #didit-container-home). Document why this delay is needed, or use tick() from Svelte to flush pending DOM updates instead.

♻️ Proposed alternative using `tick`
-import { type Snippet, getContext, onMount } from "svelte";
+import { type Snippet, getContext, onMount, tick } from "svelte";
 ...
-        await new Promise((r) => setTimeout(r, 50));
+        await tick(); // flush DOM so `#didit-container-home` is rendered before SDK mounts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte at line 234,
Replace the fragile await new Promise((r) => setTimeout(r, 50)) hack with
Svelte's tick() to flush pending DOM/state updates before mounting the SDK;
specifically, import tick from 'svelte' and replace that sleep with await tick()
(or await tick() in a small loop if multiple microtasks are needed) in the block
that mounts the SDK into "#didit-container-home" and reference the kycStep state
update there; also add a short inline comment explaining that tick is used to
ensure the DOM reflects the latest kycStep before the SDK queries the container.

104-107: shareQR() is a stub — alert() will break on mobile Tauri.

alert() is a browser API that may not behave as expected (or at all) in Tauri's WebView context, and leaving a stub in production silently breaks the share flow.

Would you like me to open a tracking issue or generate a native share implementation using Tauri's share plugin?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
104 - 107, shareQR() currently calls alert(), which breaks in Tauri; replace the
stub so the function uses a real share flow: remove alert() and implement a
platform-aware share—first try the Web Share API (navigator.share) with the QR
payload, then if running in Tauri detect the Tauri environment and call the
native share plugin (e.g., invoke the share command or use the tauri share
plugin API) with the same payload; ensure you handle and log errors and always
set shareQRdrawerOpen = false in the finally/cleanup path so the drawer closes
even on failure.
infrastructure/eid-wallet/src/routes/(app)/scan-qr/components/AuthDrawer.svelte (1)

32-36: Add explicit dialog semantics on the BottomSheet for screen-reader clarity.

This keeps modal accessibility behavior consistent with other converted drawers.

♿ Suggested patch
     <BottomSheet
         isOpen={internalOpen}
         dismissible={false}
+        role="dialog"
+        aria-modal="true"
+        aria-labelledby="auth-drawer-title"
         class="gap-5"
     >
@@
-                <h4 class="text-xl font-bold">Code scanned!</h4>
+                <h4 id="auth-drawer-title" class="text-xl font-bold">Code scanned!</h4>

Also applies to: 57-60

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@infrastructure/eid-wallet/src/routes/`(app)/scan-qr/components/AuthDrawer.svelte
around lines 32 - 36, Add explicit dialog semantics to the BottomSheet instances
by adding role="dialog", aria-modal="true", and either an aria-label or
aria-labelledby prop (e.g., aria-label="Authentication Drawer" or
aria-labelledby referencing the drawer title element) to both BottomSheet usages
(the one with internalOpen/class="gap-5" and the other at lines 57-60) so screen
readers treat them as modal dialogs and accessibility matches other converted
drawers.
infrastructure/eid-wallet/src/routes/(app)/scan-qr/components/RevealDrawer.svelte (1)

32-36: Add explicit dialog labeling for accessibility consistency.

Other migrated drawers pass dialog semantics explicitly; this one should do the same.

♿ Suggested patch
     <BottomSheet
         isOpen={internalOpen}
         dismissible={false}
+        role="dialog"
+        aria-modal="true"
+        aria-labelledby="reveal-title"
         class="gap-5"
     >
@@
-                    <h4 class="text-xl font-bold text-green-800">
+                    <h4 id="reveal-title" class="text-xl font-bold text-green-800">
                         Vote Decrypted
                     </h4>
@@
-                    <h4 class="text-xl font-bold">Reveal Your Blind Vote</h4>
+                    <h4 id="reveal-title" class="text-xl font-bold">Reveal Your Blind Vote</h4>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@infrastructure/eid-wallet/src/routes/`(app)/scan-qr/components/RevealDrawer.svelte
around lines 32 - 36, Add explicit dialog semantics to the BottomSheet in
RevealDrawer.svelte: pass role="dialog" and aria-modal="true" and provide either
aria-label or aria-labelledby (create an id on the drawer header/title if
needed) so the BottomSheet receives the same explicit accessibility props other
migrated drawers use; update the BottomSheet JSX/props where
isOpen={internalOpen} is set to include these attributes.
infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte (1)

145-153: Consider centralizing provisioner v2 request setup.

The v2 base path + shared-secret header pattern is repeated in multiple calls. A small helper for URL/header construction would reduce drift risk between onboarding, decision, and upgrade flows.

Also applies to: 211-221, 481-492, 617-627

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/eid-wallet/src/routes/`(auth)/onboarding/+page.svelte around
lines 145 - 153, Create a small helper (e.g., getProvisionerRequestConfig or
buildProvisionerUrlAndHeaders) that accepts a path like "/verification/v2" and
returns the full URL (using PUBLIC_PROVISIONER_URL) plus the headers object
containing "x-shared-secret": PUBLIC_PROVISIONER_SHARED_SECRET (or a full axios
config). Replace inline uses of new URL(..., PUBLIC_PROVISIONER_URL).toString()
and the repeated headers object in the onboarding axios.post call and the other
occurrences referenced (decision and upgrade flows) to use this helper so all
provisioner v2 requests share a single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@infrastructure/eid-wallet/src-tauri/tauri.conf.json`:
- Line 31: The Android versionCode in tauri.conf.json was changed from 12 to 22
(property "versionCode") which burns 10 intermediate codes; confirm whether that
jump was intentional and if not set "versionCode" to the correct next
incremental value (e.g., 13) before publishing. Locate the "versionCode" entry
in the tauri.conf.json and update it to the intended strictly-increasing
integer, then verify build/release scripts or CI that may auto-bump it to avoid
reintroducing the jump.

In `@infrastructure/eid-wallet/src/lib/global/controllers/key.ts`:
- Around line 200-251: The fallback currently swallows sync errors causing
unverifiable signatures; update the hardware-fallback flow so failures
propagate: change KeyService.#runHardwareFallbackCallback to not swallow
exceptions (either remove the try/catch or rethrow after logging) so callers of
KeyService.signPayload receive the error, and update the GlobalState callback
(the handler that calls syncPublicKey) to rethrow any syncPublicKey errors
instead of just logging them; additionally guard the fallback path in
KeyService.signPayload so it refuses to fallback when context === "onboarding"
to preserve the existing onboarding invariant.

In `@infrastructure/eid-wallet/src/lib/ui/BottomSheet/BottomSheet.svelte`:
- Around line 23-33: The close handler handleClose currently sets isOpen = false
and also calls onOpenChange?.(false) directly, which causes a duplicate emission
because the reactive $effect watching isOpen will emit again; fix by removing
the direct onOpenChange?.(false) call from handleClose and let the $effect that
compares isOpen and lastReportedOpen handle emitting onOpenChange, or
alternatively update lastReportedOpen inside handleClose (set lastReportedOpen =
false) instead of calling onOpenChange directly so the $effect does not emit a
second time; update only handleClose or lastReportedOpen logic to ensure a
single onOpenChange emission per close.

In `@infrastructure/eid-wallet/src/routes/`(app)/scan-qr/scanLogic.ts:
- Around line 945-946: The comment is outdated and contradicts the call to
globalState.walletSdkAdapter.ensureKey("default", "signing"); — update or remove
the comment to reflect that a separate signing context is explicitly provided:
either change the text to state that the default key is ensured for the
"signing" context, or delete the comment entirely so it does not imply there is
no separate signing context, and keep the ensureKey("default","signing")
invocation as-is.

In `@infrastructure/eid-wallet/src/routes/`(auth)/onboarding/+page.svelte:
- Around line 213-214: The URL template literals interpolate raw IDs into path
segments (e.g., `/verification/v2/decision/${result.session.sessionId}` and the
other template using a session ID at the later occurrence); encode those IDs
with encodeURIComponent before inserting them into the path to avoid breaking
the URL when IDs contain reserved characters (replace
`${result.session.sessionId}` with
`${encodeURIComponent(result.session.sessionId)}` and do the same for the other
session/ID variable used in the second template).

In `@infrastructure/evault-core/src/controllers/LegacyVerificationController.ts`:
- Around line 54-60: The legacy write routes in LegacyVerificationController
(e.g., the POST "/verification/:id/media" handler and the decision endpoints
handling POST/PATCH) must be protected: add a middleware (e.g.,
requireLegacySecretMiddleware) that reads a server-side secret from config/env
(e.g., LEGACY_WRITE_SECRET) and rejects requests missing or with a mismatched
header (e.g., 'x-legacy-secret'); for webhook-style endpoints (e.g., the
"/verification/decisions" handler) add HMAC signature validation middleware
(e.g., validateWebhookSignature) that computes an HMAC of the raw request body
using the shared secret and compares it to a provided signature header (e.g.,
'x-signature') before any state mutation; apply these middlewares to all write
routes in this controller (POST/PATCH handlers) so unauthorized requests are
immediately 401/403 and do not reach handlers like the media upload or
decision-processing functions.
- Around line 85-90: The Veriff outbound calls in LegacyVerificationController
(the veriffClient.post calls) are not wrapped in try/catch, so wrap each
veriffClient.post invocation (the calls around the veriffId/media endpoints
found in LegacyVerificationController) in a try { await veriffClient.post(...) }
catch (err) { log the error and send an appropriate Express error response
(e.g., res.status(502/500).json({ error: 'Veriff request failed', details:
err.message })) to avoid unhandled promise rejections; ensure you handle all
three locations (the blocks around lines noted) and preserve existing success
flows inside the try block.
- Line 224: The assignment to documentId uses null but the
DeepPartial<Verification> expects string | undefined; update the assignment in
LegacyVerificationController (the line setting documentId from docNumber) to
return undefined when docNumber is not a string (e.g., use typeof docNumber ===
"string" ? docNumber : undefined) so the value matches the expected type and
satisfies strict null checks.

---

Outside diff comments:
In `@infrastructure/eid-wallet/package.json`:
- Line 45: Remove the unused production dependency "import": "^0.0.6" from
package.json; search the repository for any references to the import package to
confirm it's unused, then remove the entry for "import" and regenerate the
lockfile by running your package manager (npm install or yarn install) so
package-lock.json / yarn.lock is updated and CI installs reflect the change.

In `@infrastructure/eid-wallet/src/lib/global/controllers/key.ts`:
- Around line 157-186: The signPayload method currently emits sensitive logs —
remove or gate the raw payload, its hex (payloadHex) and the full public key
logs; instead only log non-sensitive context (e.g., keyId, managerType) and a
masked public key (first/last few chars) or nothing at all. Replace the
unconditional console.log calls around payload, payloadHex and the full public
key in signPayload and the getPublicKey try/catch with either: (a) conditional
debug-level logging behind a debug flag / environment variable /
logger.isDebugEnabled check, or (b) drop them entirely; keep short,
non-sensitive messages when needed and avoid printing the full public key or
entire payload. Ensure references to payloadHex, console.log lines that print
`Payload:`, `Payload (hex):`, and the `Public key (full):` output are removed or
gated.

In `@infrastructure/eid-wallet/src/routes/`(app)/ePassport/+page.svelte:
- Around line 248-252: The code uses rawStatus.toLowerCase().replace(" ", "_")
which only replaces the first space; update the conversion that sets diditResult
(using decision.status and rawStatus) to replace all spaces (e.g., use
replaceAll(" ", "_") or rawStatus.replace(/\s+/g, "_")) before the cast so
multi-word statuses map correctly to "approved" | "declined" | "in_review";
ensure you normalize casing and whitespace consistently prior to assigning
diditResult.

In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 290-294: The code uses rawStatus.toLowerCase().replace(" ", "_")
which only replaces the first space and can produce incorrect diditResult;
change it to replace all spaces (e.g., rawStatus.toLowerCase().replaceAll(" ",
"_") or rawStatus.toLowerCase().replace(/\s+/g, "_")) when computing diditResult
from decision.status so the final value reliably matches "approved" | "declined"
| "in_review".

In `@infrastructure/eid-wallet/src/routes/`(app)/settings/passphrase/+page.svelte:
- Around line 164-177: The BottomSheet currently bound to showSuccessDrawer
allows backdrop/gesture dismissal which bypasses handleClose and navigation;
update the BottomSheet component (the BottomSheet element that binds
isOpen={showSuccessDrawer}) to either add dismissible={false} to prevent
accidental dismissal or add an onOpenChange handler that calls handleClose (or
triggers the same navigation) when isOpen becomes false so closing always runs
the navigation logic tied to handleClose.

In `@infrastructure/eid-wallet/src/routes/`+layout.svelte:
- Around line 317-322: The call to the async function checkAuth() in the
deep-link action branches (auth, sign, reveal) is currently fire-and-forget and
can swallow rejections (e.g., from goto() or globalState.vaultController.vault);
update each call site to handle promise rejections by either awaiting
checkAuth() where the caller is async or chaining .catch(...) to log and handle
errors (include the error in the log), ensuring any navigation failures from
goto() or vault access errors are surfaced and handled.
- Line 631: Remove the debug $effect that logs safeAreaTop in production: delete
the line $effect(() => console.log("top", safeAreaTop)) from the component, or
if you want to keep it for local debugging wrap it with a dev-only guard (import
{ dev } from '$app/environment' and run the $effect only when dev is true) so
logging of safeAreaTop does not run in production.

---

Nitpick comments:
In `@infrastructure/eid-wallet/package.json`:
- Line 28: Remove the redundant dependency "@tailwindcss/container-queries" from
package.json (it’s unnecessary with Tailwind CSS v4), then reinstall/update
lockfiles (npm/yarn/pnpm install) to ensure the dependency is removed from
node_modules and the lockfile; also scan for any leftover references to
"@tailwindcss/container-queries" in config or code (none expected) and remove
them if found.
- Line 75: Remove the redundant "autoprefixer" dependency from package.json
(delete the "autoprefixer" entry) and run your package manager install to update
lockfiles; also remove or adjust any references to "autoprefixer" in build
configs (e.g., postcss.config.js or Vite/webpack config) so Tailwind v4's
Lightning CSS handles vendor prefixing and there are no leftover plugin entries.

In `@infrastructure/eid-wallet/src/lib/global/controllers/key.ts`:
- Around line 63-69: The reset() method in KeyService currently clears
manager/cache and contexts but doesn't clear the stored hardware fallback
handler (`#onHardwareFallback`), leaving a stale callback that can reference old
VaultController/vaultController state; update KeyService.reset() to also
clear/unset `#onHardwareFallback` (and any similar fields referenced elsewhere,
e.g., the other reset-like method) so the instance no longer retains the old
fallback handler when reused outside GlobalState.reset(), ensuring any
registered hardware fallback is removed on reset.
- Around line 19-23: The type HardwareFallbackEvent is declared but not exported
while it is used as the parameter type for the public API
setHardwareFallbackHandler, so external callers cannot reference it; fix by
exporting the type (export type HardwareFallbackEvent = {...}) in the same file
where HardwareFallbackEvent is declared (so callers can import it) and ensure
any local imports/usages of HardwareFallbackEvent and the public function
setHardwareFallbackHandler remain consistent with the exported name.

In `@infrastructure/eid-wallet/src/lib/global/state.ts`:
- Around line 54-79: The hardware fallback handler registered via
keyService.setHardwareFallbackHandler can trigger concurrent calls to
vaultController.syncPublicKey(vault.ename); add a guard (e.g., an inProgress
flag or mutex) inside that async handler to ensure only one syncPublicKey runs
at a time for a given vault. Specifically, within the async ({ keyId, context,
originalError }) => { ... } closure, check and set a per-vault or global
"syncing" marker before awaiting
this.vaultController.syncPublicKey(vault.ename), skip or await the existing
operation if already in progress, and clear the marker in finally so subsequent
fallbacks can proceed; apply this around the existing try/catch where
vaultController.vault and vaultController.syncPublicKey are used.

In `@infrastructure/eid-wallet/src/routes/`(app)/ePassport/+page.svelte:
- Around line 592-597: startSocialBindingPolling currently unconditionally
creates a new setInterval which can leak if one already exists; modify
startSocialBindingPolling to first guard against an existing interval (check
socialBindingPollInterval) and clear it (or call stopSocialBindingPolling())
before creating a new interval, then set socialBindingPolling = true and assign
socialBindingPollInterval = setInterval(() => { void runSocialBindingPoll(); },
3000); ensure the previous interval reference is nulled so
stopSocialBindingPolling can reliably clear the active timer.
- Line 620: The template guard {`#if` docData} is misleading because docData is
initialized to an empty object and never falsy; change the check to explicitly
test for non-empty object entries (e.g., use Object.keys(docData).length > 0 or
Object.entries(docData).length) or introduce a boolean like hasDocData derived
from docData and use that in the {`#if`} so the intent (render only when docData
contains properties) is clear; update the {`#if` docData} block and any related
logic that assumes a truthy/falsy docData accordingly.

In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Line 234: Replace the fragile await new Promise((r) => setTimeout(r, 50)) hack
with Svelte's tick() to flush pending DOM/state updates before mounting the SDK;
specifically, import tick from 'svelte' and replace that sleep with await tick()
(or await tick() in a small loop if multiple microtasks are needed) in the block
that mounts the SDK into "#didit-container-home" and reference the kycStep state
update there; also add a short inline comment explaining that tick is used to
ensure the DOM reflects the latest kycStep before the SDK queries the container.
- Around line 104-107: shareQR() currently calls alert(), which breaks in Tauri;
replace the stub so the function uses a real share flow: remove alert() and
implement a platform-aware share—first try the Web Share API (navigator.share)
with the QR payload, then if running in Tauri detect the Tauri environment and
call the native share plugin (e.g., invoke the share command or use the tauri
share plugin API) with the same payload; ensure you handle and log errors and
always set shareQRdrawerOpen = false in the finally/cleanup path so the drawer
closes even on failure.

In
`@infrastructure/eid-wallet/src/routes/`(app)/scan-qr/components/AuthDrawer.svelte:
- Around line 32-36: Add explicit dialog semantics to the BottomSheet instances
by adding role="dialog", aria-modal="true", and either an aria-label or
aria-labelledby prop (e.g., aria-label="Authentication Drawer" or
aria-labelledby referencing the drawer title element) to both BottomSheet usages
(the one with internalOpen/class="gap-5" and the other at lines 57-60) so screen
readers treat them as modal dialogs and accessibility matches other converted
drawers.

In
`@infrastructure/eid-wallet/src/routes/`(app)/scan-qr/components/RevealDrawer.svelte:
- Around line 32-36: Add explicit dialog semantics to the BottomSheet in
RevealDrawer.svelte: pass role="dialog" and aria-modal="true" and provide either
aria-label or aria-labelledby (create an id on the drawer header/title if
needed) so the BottomSheet receives the same explicit accessibility props other
migrated drawers use; update the BottomSheet JSX/props where
isOpen={internalOpen} is set to include these attributes.

In `@infrastructure/eid-wallet/src/routes/`(auth)/onboarding/+page.svelte:
- Around line 145-153: Create a small helper (e.g., getProvisionerRequestConfig
or buildProvisionerUrlAndHeaders) that accepts a path like "/verification/v2"
and returns the full URL (using PUBLIC_PROVISIONER_URL) plus the headers object
containing "x-shared-secret": PUBLIC_PROVISIONER_SHARED_SECRET (or a full axios
config). Replace inline uses of new URL(..., PUBLIC_PROVISIONER_URL).toString()
and the repeated headers object in the onboarding axios.post call and the other
occurrences referenced (decision and upgrade flows) to use this helper so all
provisioner v2 requests share a single source of truth.

In `@infrastructure/eid-wallet/src/routes/`(auth)/review/+page.svelte:
- Line 7: Remove the unused axios import from the Svelte page: delete the line
importing axios (import axios from "axios";) from the +page.svelte file since
there are no references to axios in this module; ensure no other code relies on
axios in this file and run the build to confirm bundle size and lint warnings
are resolved.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1fa923 and d115015.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • infrastructure/eid-wallet/package.json
  • infrastructure/eid-wallet/src-tauri/tauri.conf.json
  • infrastructure/eid-wallet/src/lib/global/controllers/key.ts
  • infrastructure/eid-wallet/src/lib/global/state.ts
  • infrastructure/eid-wallet/src/lib/ui/BottomSheet/BottomSheet.svelte
  • infrastructure/eid-wallet/src/lib/ui/CameraPermissionDialog/CameraPermissionDialog.svelte
  • infrastructure/eid-wallet/src/lib/ui/index.ts
  • infrastructure/eid-wallet/src/routes/(app)/+layout.svelte
  • infrastructure/eid-wallet/src/routes/(app)/ePassport/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/components/AuthDrawer.svelte
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/components/LoggedInDrawer.svelte
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/components/RevealDrawer.svelte
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/components/SigningDrawer.svelte
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/components/SocialBindingDrawer.svelte
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/passphrase/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/pin/+page.svelte
  • infrastructure/eid-wallet/src/routes/(auth)/e-passport/+page.svelte
  • infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
  • infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte
  • infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte
  • infrastructure/eid-wallet/src/routes/(auth)/review/+page.svelte
  • infrastructure/eid-wallet/src/routes/+layout.svelte
  • infrastructure/evault-core/src/controllers/LegacyVerificationController.ts
  • infrastructure/evault-core/src/controllers/SessionIdValidation.spec.ts
  • infrastructure/evault-core/src/controllers/VerificationController.ts
  • infrastructure/evault-core/src/controllers/VerificationRoutesCompatibility.spec.ts
  • infrastructure/evault-core/src/index.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
infrastructure/evault-core/src/controllers/LegacyVerificationController.ts (2)

54-60: ⚠️ Potential issue | 🔴 Critical

Protect all legacy write routes with server-side auth/signature checks.

POST/PATCH legacy endpoints are still mutable without route-level authentication, including the decisions webhook. This leaves verification state writable by unauthorized callers.

🔐 Suggested route hardening
+import {
+  requireLegacySecretMiddleware,
+  validateLegacyWebhookSignatureMiddleware,
+} from "../middleware/legacyAuth";

- app.post("/verification/:id/media", async (req: Request, res: Response) => {
+ app.post("/verification/:id/media", requireLegacySecretMiddleware, async (req: Request, res: Response) => {

- app.post("/verification", async (req: Request, res: Response) => {
+ app.post("/verification", requireLegacySecretMiddleware, async (req: Request, res: Response) => {

- app.patch("/verification/:id", async (req: Request, res: Response) => {
+ app.patch("/verification/:id", requireLegacySecretMiddleware, async (req: Request, res: Response) => {

- app.post("/verification/decisions", async (req: Request, res: Response) => {
+ app.post("/verification/decisions", validateLegacyWebhookSignatureMiddleware, async (req: Request, res: Response) => {

Also applies to: 104-112, 147-156, 179-186

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/evault-core/src/controllers/LegacyVerificationController.ts`
around lines 54 - 60, Legacy write routes in LegacyVerificationController (e.g.,
the POST /verification/:id/media handler and other POST/PATCH handlers) are
unprotected and must enforce server-side authentication/signature checks; update
each mutable route handler (handlers registered via app.post and app.patch in
LegacyVerificationController) to run the existing server auth middleware or a
new verifyServerSignature function before processing (reject with 401/403 on
failure), and ensure the decisions webhook and all mentioned endpoints (the
handlers around the other referenced blocks) use the same middleware so only
signed/authorized requests can mutate verification state.

85-90: ⚠️ Potential issue | 🔴 Critical

Handle Veriff API failures explicitly in async route handlers.

These outbound calls are not wrapped locally. On Express 4 setups without async-error wrappers, rejected promises can bypass consistent HTTP error responses and reduce reliability.

🛟 Suggested error-handling pattern
- await veriffClient.post(`/v1/sessions/${veriffId}/media`, veriffBody, { ... });
+ try {
+   await veriffClient.post(`/v1/sessions/${veriffId}/media`, veriffBody, { ... });
+ } catch (error) {
+   return res.status(502).json({ error: "Veriff request failed" });
+ }

Apply the same pattern to the calls on Line 126 and Line 169.

Use this read-only check to confirm whether the project already installs a global async wrapper (which would change severity/handling expectations):

#!/bin/bash
set -euo pipefail

echo "== Express dependency declarations =="
fd package.json -x rg -n '"express"\s*:' {}

echo
echo "== Common async error wrappers (if any) =="
rg -n --type ts --type js 'express-async-errors|express-async-handler|wrapAsync|catchAsync|asyncHandler'

echo
echo "== Async route handlers in LegacyVerificationController =="
rg -n --type ts 'app\.(get|post|patch)\([^,]+,\s*async\s*\(' infrastructure/evault-core/src/controllers/LegacyVerificationController.ts

Also applies to: 126-135, 169-174

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@infrastructure/evault-core/src/controllers/LegacyVerificationController.ts`
around lines 85 - 90, The Veriff API calls (the veriffClient.post(...)
invocations using veriffId, veriffBody and signature) must be wrapped in
try/catch blocks inside LegacyVerificationController async route handlers so
rejected promises don't escape; for each occurrence (the call shown plus the
ones around lines 126 and 169) catch errors and call next(err) (or map to
res.status(502).json({ message: 'Upstream Veriff error', error: err.message }))
to ensure Express error middleware handles failures consistently. Ensure you
reference the same variables (veriffId, veriffBody, signature) and don't swallow
the original error when rethrowing or passing to next().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@infrastructure/evault-core/src/controllers/LegacyVerificationController.ts`:
- Around line 80-83: The code currently casts process.env.VERIFF_HMAC_KEY with
"as string" when calling createHmacSignature (used in
LegacyVerificationController), which hides missing-config errors; instead
validate and fail-fast: read VERIFF_HMAC_KEY once (e.g., in module init or the
LegacyVerificationController constructor) and throw a clear error if undefined,
then pass that validated string to createHmacSignature and any other places
referenced (the other calls around the same controller at the spots matching
121-124 and 164-167). Optionally introduce a small helper like
getEnvOrThrow('VERIFF_HMAC_KEY') to centralize the check and replace all "as
string" casts with the validated value.
- Around line 114-145: The code currently calls verificationService.create(...)
before calling veriffClient.post(...), which can leave a DB record without
veriffId if the external call fails; change the flow so the external Veriff
session is created first (call createHmacSignature(...) and
veriffClient.post(...) to obtain veriffSession) and only then call
verificationService.create(...) with vendorData/veriffId set, or alternatively
perform verificationService.create(...) inside a transaction and roll
back/delete the created record if veriffClient.post(...) fails; update usages of
verification, verification.id, and findByIdAndUpdate(...) accordingly so no
partially-initialized verification remains on Veriff errors.

---

Duplicate comments:
In `@infrastructure/evault-core/src/controllers/LegacyVerificationController.ts`:
- Around line 54-60: Legacy write routes in LegacyVerificationController (e.g.,
the POST /verification/:id/media handler and other POST/PATCH handlers) are
unprotected and must enforce server-side authentication/signature checks; update
each mutable route handler (handlers registered via app.post and app.patch in
LegacyVerificationController) to run the existing server auth middleware or a
new verifyServerSignature function before processing (reject with 401/403 on
failure), and ensure the decisions webhook and all mentioned endpoints (the
handlers around the other referenced blocks) use the same middleware so only
signed/authorized requests can mutate verification state.
- Around line 85-90: The Veriff API calls (the veriffClient.post(...)
invocations using veriffId, veriffBody and signature) must be wrapped in
try/catch blocks inside LegacyVerificationController async route handlers so
rejected promises don't escape; for each occurrence (the call shown plus the
ones around lines 126 and 169) catch errors and call next(err) (or map to
res.status(502).json({ message: 'Upstream Veriff error', error: err.message }))
to ensure Express error middleware handles failures consistently. Ensure you
reference the same variables (veriffId, veriffBody, signature) and don't swallow
the original error when rethrowing or passing to next().

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d115015 and b833ec2.

📒 Files selected for processing (1)
  • infrastructure/evault-core/src/controllers/LegacyVerificationController.ts

@coodos coodos merged commit f1012f4 into main Feb 25, 2026
5 checks passed
@coodos coodos deleted the fix/styling-on-onboarding-buttons branch February 25, 2026 11:39
@coderabbitai coderabbitai bot mentioned this pull request Feb 25, 2026
6 tasks
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