-
Notifications
You must be signed in to change notification settings - Fork 4
Feat/wallet more verification docs support #357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughSplits Drawer lifecycle into separate init and open/close effects; adds document-type selection UI and state; implements back-side document capture, retake and upload error handling; defers selfie progression to SSE/websocket; tightens websocket typing and key-manager guards; adds notification support (client service, tauri commands, backend entities/controllers/migrations); bumps package versions and mobile config. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Drawer
participant VerifyPage
participant Stores as Stores (verifStep, documentType, DocFront/DocBack...)
participant Camera
participant NotificationSvc as NotificationService (client)
participant Backend as Backend/SSE
participant Provisioner
rect rgba(200,220,255,0.12)
note right of Drawer: Drawer init separated from open/close
Drawer->>Drawer: onMount -> create CupertinoPane (init)
Drawer-->>Drawer: onDestroy -> destroy pane
Drawer->>Drawer: reactive open state -> present / destroy (post-init)
end
rect rgba(220,255,220,0.12)
User->>VerifyPage: Open verification
VerifyPage->>Stores: verifStep = 0
VerifyPage->>User: Render DocumentType
User->>VerifyPage: Select document type
VerifyPage->>Stores: documentType = chosen / verifStep = 1
end
rect rgba(255,245,200,0.12)
VerifyPage->>Camera: Start stream
User->>Camera: Capture front -> upload -> Stores.DocFront
alt documentType != passport
User->>Camera: Capture back -> upload -> Stores.DocBack
end
User->>VerifyPage: Continue to selfie
VerifyPage->>Camera: Stop stream
Note right of VerifyPage: Await SSE/websocket to advance verifStep
end
rect rgba(255,220,220,0.12)
VerifyPage->>Backend: Subscribe websocket/SSE
Backend-->>VerifyPage: Events (progress, duplicate {w3id}, completed)
alt Duplicate
VerifyPage->>Stores: reset DocFront/DocBack/Selfie, set verifStep per duplicate flow
Backend->>Provisioner: resolve eVault via w3id
else Normal
Backend-->>Stores: set verifStep to next step(s)
end
end
rect rgba(240,240,255,0.12)
VerifyPage->>NotificationSvc: requestPermissions / registerDevice (post-auth)
NotificationSvc->>Provisioner: POST /api/devices/register
Provisioner-->>NotificationSvc: registration result
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (6)
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte (1)
65-67
: Progression via SSE/WebSocket only — add a fallbackGood to defer step advancement to server events. Consider a timeout fallback (e.g., show “Retry” or re-enable capture) if no event arrives to avoid UI dead-ends.
Confirm the event that sets verifStep=3 is always emitted after PATCH; if not, add a fallback timer.
infrastructure/eid-wallet/src/routes/(auth)/verify/store.ts (1)
11-11
: documentType union looks good; consider centralizing the typeOptional: export a DocumentType type and reuse it across components to avoid union drift.
export type DocumentType = "passport" | "id" | "permit" | "dl"; export const documentType = writable<DocumentType | null>(null);infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (2)
51-94
: Camera selection complexity (torch probing) likely unnecessaryYou detect torch support but never enable it; iterating all devices with getUserMedia can be heavy and prompt repeatedly on some browsers. Consider simplifying to facingMode: "environment" and avoid per-device streams. If torch is needed, use applyConstraints when available.
// Simplified approach async function getMainCameraStream() { return navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }); }Ensure iOS/Safari behavior meets your UX with the simpler constraint; otherwise, add torch via track.applyConstraints({ advanced: [{ torch: true }] }) when low-light is detected.
141-141
: Remove debug logs or guard behind dev flagconsole.log in production UI can be noisy.
- console.log("Switched to back image capture, image =", image); + if (import.meta.env?.DEV) console.debug("Switched to back image capture", { image });Also applies to: 173-173
infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (2)
272-279
: Encode w3id when building the resolve URLUse URLSearchParams to avoid malformed queries if w3id contains special characters.
- const response = await axios.get( - new URL( - `resolve?w3id=${existingW3id}`, - PUBLIC_REGISTRY_URL, - ).toString(), - ); + const url = new URL("resolve", PUBLIC_REGISTRY_URL); + url.searchParams.set("w3id", existingW3id); + const response = await axios.get(url.toString());
118-145
: Close the EventSource on component destroy to prevent leaksPersist the EventSource and close it in onDestroy.
- import { getContext, onMount } from "svelte"; + import { getContext, onMount, onDestroy } from "svelte";+ let verificationEventSource: EventSource | null = null;
- const eventSource = new EventSource(sseUrl); + verificationEventSource = new EventSource(sseUrl);- eventSource.onopen = () => { + verificationEventSource.onopen = () => { @@ - eventSource.onmessage = (e) => { + verificationEventSource.onmessage = (e) => {+ onDestroy(() => { + verificationEventSource?.close(); + verificationEventSource = null; + });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
infrastructure/eid-wallet/src-tauri/gen/android/.idea/kotlinc.xml
is excluded by!**/gen/**
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (7)
infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte
(2 hunks)infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
(7 hunks)infrastructure/eid-wallet/src/routes/(auth)/verify/steps/document-type.svelte
(1 hunks)infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte
(3 hunks)infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte
(1 hunks)infrastructure/eid-wallet/src/routes/(auth)/verify/store.ts
(1 hunks)package.json
(1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Check Code
package.json
[error] 1-1: turbo run check failed: Failed to add workspace 'rest-express'; it already exists at 'platforms/dreamSync/package.json'.
🪛 GitHub Actions: Check Format
package.json
[error] 1-1: Failed to add workspace 'rest-express' from 'platforms/dreamSync/package.json'; it already exists at 'platforms/marketplace/package.json'.
🪛 GitHub Actions: Check Lint
package.json
[error] 1-1: Turbo: Failed to add workspace 'rest-express' from 'platforms/dreamSync/package.json'; it already exists at 'platforms/marketplace/package.json'.
🔇 Additional comments (8)
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/document-type.svelte (1)
1-50
: LGTM — clear UX and state flowSelecting a document type updates the store and advances to capture step. Matches store union.
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (2)
118-134
: Good: front image upload now wrapped in try/catch with user-facing errorError path halts loading and provides feedback. Solid improvement.
144-177
: Good: back image capture/upload with error handling and state updatesBack side flow and previews align with non-passport types.
infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte (1)
30-50
: Confirm Svelte 5 migration and legacy syntax support
Svelte ^5.0.0 is declared and runes ($effect, $props, $state, $derived, $bindable) are used widely, yet legacyexport let
remains in many *.svelte files. Ensure your build config supports both syntaxes or complete migration to runes to avoid build errors.infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (4)
22-22
: Good: reset back-side doc and selfie on resubmissionDocBack reset complements DocFront/Selfie resets to avoid stale state. LGTM.
Also applies to: 140-141
105-105
: Narrowed websocketData type is saferTyping to { w3id?: string } | null is an improvement. Please confirm the backend always includes w3id for duplicate events.
188-191
: Key manager guards and init check look goodExplicit not-initialized throws and the exists→generate flow reduce runtime surprises. LGTM.
Also applies to: 207-210, 232-236
143-144
: Confirm step index change to 3Jumping to step 3 on any message bypasses step 2. Verify this aligns with the updated step model (DocumentType=0, Passport=1, Selfie=2, Results=3) for approved/duplicate/resubmission flows.
// Handle open/close state separately | ||
$effect(() => { | ||
if (!pane) return; | ||
if (isPaneOpen) { | ||
pane.present({ animate: true }); | ||
} else { | ||
pane.destroy({ animate: true }); | ||
} | ||
return () => pane.destroy(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Destroying pane on close prevents reopening (pane not re-initialized)
Calling pane.destroy() when isPaneOpen=false removes the instance; later openings won’t recreate it because the init effect doesn’t rerun. Use hide/close (non-destructive) on close, and reserve destroy for unmount.
Apply:
- if (isPaneOpen) {
- pane.present({ animate: true });
- } else {
- pane.destroy({ animate: true });
- }
+ if (isPaneOpen) {
+ pane.present({ animate: true });
+ } else {
+ // Prefer non-destructive close
+ if (typeof (pane as any).hide === "function") {
+ (pane as any).hide({ animate: true });
+ } else {
+ // As a fallback, keep the instance; avoid destroy here
+ }
+ }
Option B: if hide isn’t available, gate destroy to unmount and re-init pane when reopening.
🌐 Web query:
Does CupertinoPane expose a non-destructive method (e.g., hide/close/dismiss) to close the pane without destroying it?
💡 Result:
Yes. CupertinoPane provides a non‑destructive hide() that removes the pane from view but keeps it in the DOM (you can check isHidden() and call present() to show it again). [1][2]
Sources:
- GitHub docs (public methods: hide, destroy, isHidden). [1]
- npm package docs (showing hide(), destroy(), present(), isHidden()). [2]
Replace pane.destroy() with pane.hide() on close
CupertinoPane provides hide(), which non-destructively closes the pane and lets you call present() to reopen it.
File: infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte lines 51–60
- if (isPaneOpen) {
- pane.present({ animate: true });
- } else {
- pane.destroy({ animate: true });
- }
+ if (isPaneOpen) {
+ pane.present({ animate: true });
+ } else {
+ pane.hide({ animate: true });
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// Handle open/close state separately | |
$effect(() => { | |
if (!pane) return; | |
if (isPaneOpen) { | |
pane.present({ animate: true }); | |
} else { | |
pane.destroy({ animate: true }); | |
} | |
return () => pane.destroy(); | |
}); | |
// Handle open/close state separately | |
$effect(() => { | |
if (!pane) return; | |
if (isPaneOpen) { | |
pane.present({ animate: true }); | |
} else { | |
pane.hide({ animate: true }); | |
} | |
}); |
🤖 Prompt for AI Agents
In infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte around lines 51 to
60, the code currently calls pane.destroy({ animate: true }) when closing;
replace that with pane.hide({ animate: true }) so the pane is closed
non-destructively and can be reopened with pane.present(), keeping the existing
null check for pane; ensure the call signature matches the CupertinoPane API
(use hide with the animate option) and remove any logic that assumes the pane is
destroyed.
import DocumentType from "./steps/document-type.svelte"; | ||
import Passport from "./steps/passport.svelte"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Generalize UI and stored labels for non-passport docs
Hardcoded “Passport” persists in user metadata and UI, which mislabels non-passport flows. Import documentType, add a label map, and switch labels to be document‑type aware. Also generalize the hero subtitle and alt text.
Apply:
- import {
- DocFront,
- DocBack,
- Selfie as SelfiePic,
- reason,
- status,
- verifStep,
- verificaitonId,
- } from "./store";
+ import {
+ DocFront,
+ DocBack,
+ Selfie as SelfiePic,
+ reason,
+ status,
+ verifStep,
+ verificaitonId,
+ documentType,
+ } from "./store";
- let hardwareKeyCheckComplete = $state(false);
+ let hardwareKeyCheckComplete = $state(false);
+ const docLabel = {
+ passport: "Passport",
+ id: "National ID",
+ permit: "Residence Permit",
+ dl: "Driver's License",
+ } as const;
- "ID submitted": `Passport - ${person.nationality.value}`,
- "Passport Number": document.number.value,
+ "ID submitted": `${docLabel[$documentType ?? "passport"]} - ${person.nationality.value}`,
+ "Document Number": document.number.value,
- Get your passport ready. You’ll be directed to present your
- passport and take a quick selfie.
+ Get your document ready. You’ll be directed to present your
+ chosen ID and take a quick selfie.
- <img class="mx-auto mt-20" src="images/Passport.svg" alt="passport" />
+ <img class="mx-auto mt-20" src="images/Passport.svg" alt="identity document" />
Also applies to: 358-362
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte
Show resolved
Hide resolved
export const verifStep = writable(0); | ||
export const permissionGranted = writable<boolean>(false); | ||
export const DocFront = writable<string | null>(); | ||
export const DocBack = writable<string | null>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
writable requires an initial value
This will fail type-checking and may break at runtime. Initialize to null.
-export const DocBack = writable<string | null>();
+export const DocBack = writable<string | null>(null);
Also align adjacent stores for consistency:
// Outside this hunk but recommended
export const DocFront = writable<string | null>(null);
export const Selfie = writable<string | null>(null);
export const status = writable<string>("");
export const reason = writable<string>("");
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/verify/store.ts around line 6,
the DocBack writable is declared without an initial value which fails TypeScript
checks and can break at runtime; initialize it to null (writable<string |
null>(null)) and while here, make adjacent store declarations consistent by
explicitly initializing DocFront, Selfie to null and status, reason to empty
strings as shown in the comment.
"onlyBuiltDependencies": [ | ||
"@firebase/util", | ||
"@tailwindcss/oxide", | ||
"bufferutil", | ||
"core-js", | ||
"@biomejs/biome", | ||
"cpu-features", | ||
"es5-ext", | ||
"esbuild", | ||
"msw", | ||
"protobufjs", | ||
"sharp", | ||
"ssh2", | ||
"svelte-preprocess", | ||
"unrs-resolver" | ||
"svelte-preprocess" | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
CI is failing: duplicate workspace name; fix before merge
Turbo reports a duplicate workspace "rest-express" (dreamSync vs marketplace). Rename one or adjust its package.json "name" to be unique, then update references.
Run to locate duplicates:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# List all package.json names and paths, highlight duplicates
fd -H -t f package.json \
| xargs -I '{}' sh -c 'printf "%s\t%s\n" "$(jq -r .name "{}")" "{}"' \
| sort \
| awk -F'\t' '{a[$1]=a[$1] ? a[$1]" | "$2 : $2} END{for (k in a) if (split(a[k], b, /\|/) > 1) printf "DUPLICATE\t%s\t%s\n", k, a[k]}'
Length of output: 193
Fix duplicate workspace names
CI fails due to duplicate workspaces; ensure each name
in package.json is unique:
web3-adapter
(ininfrastructure/web3-adapter/package.json
&services/web3-adapter/package.json
)rest-express
(inplatforms/dreamSync/package.json
&platforms/marketplace/package.json
)
Rename or adjust one instance of each before merging.
🤖 Prompt for AI Agents
In package.json around lines 25 to 34, CI is failing due to duplicate workspace
package names: web3-adapter appears in infrastructure/web3-adapter/package.json
and services/web3-adapter/package.json, and rest-express appears in
platforms/dreamSync/package.json and platforms/marketplace/package.json; fix by
renaming one of each pair (or adjusting package.json "name" fields) to unique,
descriptive names (e.g., web3-adapter-infra or web3-adapter-service,
dreamSync-rest-express or marketplace-rest-express) and update any internal
references/imports or workspace globs that rely on the old names to match the
new names before merging.
"@biomejs/biome", | ||
"cpu-features", | ||
"es5-ext", | ||
"esbuild", | ||
"msw", | ||
"protobufjs", | ||
"sharp", | ||
"ssh2", | ||
"svelte-preprocess", | ||
"unrs-resolver" | ||
"svelte-preprocess" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
onlyBuiltDependencies changes look fine
The additions align with the toolchain. Ensure all entries listed here are actual deps in the repo to avoid noise.
Quick check:
🏁 Script executed:
#!/bin/bash
for p in "@biomejs/biome" "es5-ext" "svelte-preprocess"; do
rg -nP "\"$p\"" -g '**/package.json'
done
Length of output: 334
🏁 Script executed:
#!/usr/bin/env bash
python3 - << 'PYCODE'
import json
with open('package.json') as f:
data = json.load(f)
declared = set()
for scope in ['dependencies','devDependencies','peerDependencies','optionalDependencies']:
declared.update(data.get(scope, {}).keys())
pkgs = ["@biomejs/biome","cpu-features","es5-ext","esbuild","msw","protobufjs","ssh2","svelte-preprocess"]
missing = [p for p in pkgs if p not in declared]
print("Missing:", missing)
PYCODE
Length of output: 97
Remove or declare missing onlyBuiltDependencies entries
The following packages in onlyBuiltDependencies aren’t declared in package.json: cpu-features, es5-ext, esbuild, msw, protobufjs, ssh2, svelte-preprocess. Either add them to dependencies/devDependencies or remove them to avoid noise.
🤖 Prompt for AI Agents
In package.json around lines 26 to 33, the onlyBuiltDependencies list references
packages (cpu-features, es5-ext, esbuild, msw, protobufjs, ssh2,
svelte-preprocess) that are not declared in dependencies or devDependencies;
update package.json by either adding each referenced package to the appropriate
dependencies/devDependencies entry with sensible version specifiers (or moving
existing build-only tools to devDependencies) or remove the unused names from
onlyBuiltDependencies to keep the manifest consistent and avoid noise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (1)
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (1)
241-242
: Instruction text logic is still incorrect (duplicate issue).This issue was flagged in the previous review but remains unfixed. The instruction text incorrectly uses
$DocFront ? "back" : "photo"
, which:
- Shows "photo" initially (when $DocFront is null), which is wrong for non-passport documents
- Shows "back" after front capture for all documents, which is wrong for passports
For passports, it should always show "photo" (single-page document). For other documents, it should show "front" initially, then "back" after the front is captured.
Apply this diff to fix the instruction text:
<p> Please place your document's - {$DocFront ? "back" : "photo"} + {#if $documentType === "passport"} + photo + {:else} + {image === 1 ? "front" : "back"} + {/if} page within the rectangle and press the take photo button </p>
🧹 Nitpick comments (1)
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (1)
22-23
: Optional: Consider using plain reactive variables for local state.
image1Captured
andimage2Captured
are only used within this component and don't need to be writable stores. Plain reactive variables would be simpler and more performant for local-only state.Apply this diff to simplify the state management:
-let image1Captured = writable(false); -let image2Captured = writable(false); +let image1Captured = false; +let image2Captured = false;Then update the usages:
- image1Captured.set(true); + image1Captured = true;- image2Captured.set(true); + image2Captured = true;- image1Captured.set(false); - image2Captured.set(false); + image1Captured = false; + image2Captured = false;And in the template:
-{#if ($documentType !== "passport" && $image1Captured && $image2Captured) || ($documentType === "passport" && $image1Captured)} +{#if ($documentType !== "passport" && image1Captured && image2Captured) || ($documentType === "passport" && image1Captured)}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte
(3 hunks)
🔇 Additional comments (6)
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (6)
7-14
: LGTM! Imports support multi-document verification.The addition of
DocBack
anddocumentType
imports correctly supports the extended verification flow for multiple document types with front/back capture.
180-208
: LGTM! Camera lifecycle and retake flow are well-implemented.The helper functions properly manage camera lifecycle and enable retake functionality:
retakeImages
comprehensively resets capture statestopCamera
properly releases media trackscontinueToSelfie
cleanly transitions to the next step
210-217
: LGTM! Proper cleanup prevents resource leaks.The onMount cleanup properly releases camera resources when the component unmounts, following Svelte best practices.
222-224
: LGTM! Improved error display.The styled error message block provides clear user feedback when upload failures occur.
229-239
: LGTM! Dynamic heading supports multiple document types.The heading correctly adapts to the selected document type, enhancing user clarity.
291-320
: LGTM! Conditional button logic correctly handles all document types.The button rendering logic properly handles:
- Showing "Retake" and "Continue to Selfie" when all required photos are captured (both for non-passport, single for passport)
- Showing appropriate capture button text during the capture process
try { | ||
await axios.post( | ||
new URL( | ||
`/verification/${$verificaitonId}/media`, | ||
PUBLIC_PROVISIONER_URL, | ||
).toString(), | ||
{ | ||
img: dataUrl, | ||
type: "document-front", | ||
}, | ||
); | ||
} catch (err) { | ||
console.error("Failed to upload front image:", err); | ||
error = "Failed to upload image. Please try again."; | ||
loading = false; | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good error handling, but remove debug logging.
The try-catch error handling for image uploads is excellent and prevents silent failures with user-facing error messages.
However, remove the console.log statement at line 141 for production:
loading = false;
image1Captured.set(true);
-
- // Don't stop camera - keep it running for retake or back photo
if ($documentType !== "passport") {
image = 2; // Go to back image capture for non-passport documents
- console.log("Switched to back image capture, image =", image);
}
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte
around lines 118-134, remove the debug console.error("Failed to upload front
image:", err) call from the catch block; keep the existing error = "Failed to
upload image. Please try again."; loading = false; and return statements so
user-facing handling remains, or replace the console call with a production-safe
logger/reporting call if required by the app's logging strategy.
try { | ||
await axios.post( | ||
new URL( | ||
`/verification/${$verificaitonId}/media`, | ||
PUBLIC_PROVISIONER_URL, | ||
).toString(), | ||
{ | ||
img: dataUrl, | ||
type: "document-back", | ||
}, | ||
); | ||
} catch (err) { | ||
console.error("Failed to upload back image:", err); | ||
error = "Failed to upload image. Please try again."; | ||
loading = false; | ||
return; | ||
} | ||
verifStep.set(1); | ||
loading = false; | ||
image2Captured.set(true); | ||
console.log("Back image captured"); | ||
// Don't stop camera - keep it running for retake option | ||
// Camera will be stopped when navigating away or on component cleanup | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove debug logging from back image capture.
The back image capture implementation correctly mirrors the front image logic with proper error handling.
Remove the console.log statement at line 173:
loading = false;
image2Captured.set(true);
- console.log("Back image captured");
// Don't stop camera - keep it running for retake option
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
try { | |
await axios.post( | |
new URL( | |
`/verification/${$verificaitonId}/media`, | |
PUBLIC_PROVISIONER_URL, | |
).toString(), | |
{ | |
img: dataUrl, | |
type: "document-back", | |
}, | |
); | |
} catch (err) { | |
console.error("Failed to upload back image:", err); | |
error = "Failed to upload image. Please try again."; | |
loading = false; | |
return; | |
} | |
verifStep.set(1); | |
loading = false; | |
image2Captured.set(true); | |
console.log("Back image captured"); | |
// Don't stop camera - keep it running for retake option | |
// Camera will be stopped when navigating away or on component cleanup | |
} | |
} | |
try { | |
await axios.post( | |
new URL( | |
`/verification/${$verificaitonId}/media`, | |
PUBLIC_PROVISIONER_URL, | |
).toString(), | |
{ | |
img: dataUrl, | |
type: "document-back", | |
}, | |
); | |
} catch (err) { | |
console.error("Failed to upload back image:", err); | |
error = "Failed to upload image. Please try again."; | |
loading = false; | |
return; | |
} | |
loading = false; | |
image2Captured.set(true); | |
// Don't stop camera - keep it running for retake option | |
// Camera will be stopped when navigating away or on component cleanup | |
} | |
} |
🤖 Prompt for AI Agents
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte around
lines 154 to 177: remove the debug console.log("Back image captured") on line
173 so debug output is not left in production; keep the surrounding logic, error
handling, and state updates intact (do not alter other console.error or
functional code).
<div class="text-center text-xs text-gray-400 mb-4 px-4"> | ||
<!-- Accepted documents: Driver's License, Residence Permit, Passport, ID Card. --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove commented-out code.
Line 264 contains commented-out text that should be removed to keep the codebase clean.
Apply this diff:
<div class="text-center text-xs text-gray-400 mb-4 px-4">
- <!-- Accepted documents: Driver's License, Residence Permit, Passport, ID Card. -->
{#if $documentType === "passport"}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<div class="text-center text-xs text-gray-400 mb-4 px-4"> | |
<!-- Accepted documents: Driver's License, Residence Permit, Passport, ID Card. --> | |
<div class="text-center text-xs text-gray-400 mb-4 px-4"> | |
{#if $documentType === "passport"} |
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte
around lines 263-264, remove the commented-out HTML comment ("<!-- Accepted
documents: Driver's License, Residence Permit, Passport, ID Card. -->") so the
component doesn't contain dead/commented code; simply delete that comment line
and ensure surrounding indentation and markup remain valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (6)
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (6)
130-130
: Remove debug logging for production.The
console.error
statement should be removed or replaced with a production-safe logging mechanism.
141-141
: Remove debug logging for production.The
console.log
statement should be removed to avoid debug output in production.
166-166
: Remove debug logging for production.The
console.error
statement should be removed or replaced with a production-safe logging mechanism.
173-173
: Remove debug logging for production.The
console.log
statement should be removed to avoid debug output in production.
264-264
: Remove commented-out code.The commented-out HTML comment should be removed to keep the codebase clean.
241-242
: Instruction text logic is still incorrect.Despite the past review comment being marked as addressed, the instruction text still uses
{$DocFront ? "back" : "photo"}
which is incorrect. For passports, it should always show "photo" (single page). For other documents, it should show "front" or "back" based on the current capture state (theimage
variable), not based on whether$DocFront
exists.Apply this diff to fix the logic:
<p> - Please place your document's {$DocFront ? "back" : "photo"} page within the rectangle + Please place your document's + {#if $documentType === "passport"} + photo + {:else} + {image === 1 ? "front" : "back"} + {/if} + page within the rectangle and press the take photo button </p>
🧹 Nitpick comments (2)
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (2)
241-242
: Redundant instruction text in two locations.The component has instruction text at both lines 241-242 (brief) and lines 263-289 (detailed). The detailed version (lines 263-289) provides better context by showing the current capture state (front/back). Consider removing or simplifying the instruction at lines 241-242 to avoid confusion, or consolidate both into a single, clear instruction.
Also applies to: 263-289
1-1
: Consider renaming the component file.The component is named
passport.svelte
but now handles multiple document types (passport, ID card, driver's license, residence permit). Consider renaming it to something more generic likedocument-capture.svelte
to better reflect its purpose.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte
(3 hunks)
🔇 Additional comments (3)
infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (3)
221-225
: Good error handling UI.The styled error message block provides clear feedback to users when uploads fail. This is a significant improvement over silent failures.
291-320
: Conditional button logic is well-implemented.The button logic correctly shows different controls based on the capture state: retake/continue buttons when all required photos are captured, or a single capture button otherwise. The button text also dynamically updates based on the current capture stage.
321-375
: Preview thumbnails enhance UX.The preview implementation for front/back document images is well-designed, with clear visual indicators for captured vs. pending photos. The passport exemption from previews is also correctly handled.
There was a problem hiding this 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/eid-wallet/src/routes/(auth)/verify/+page.svelte (2)
17-28
: ImportdocumentType
from the store.The
documentType
store is still not imported despite the past review comment. This store is needed to make user-facing labels document-type aware (as suggested in the previous review for lines 252-258).Add the import:
import { DocFront, DocBack, Selfie as SelfiePic, reason, status, verifStep, verificaitonId, + documentType, } from "./store";
247-258
: UsedocumentType
store for consistent labeling.The code now handles different document types from the backend response (
document.type.value
), but it doesn't use thedocumentType
store that captures the user's selection. This creates a potential inconsistency if the backend classification differs from what the user selected.As suggested in the previous review, import and use the
documentType
store:"ID submitted": - document.type.value === "passport" - ? `Passport - ${document.country.value}` - : document.type.value === "drivers_license" - ? `Driving License - ${document.country.value}` - : `ID Card - ${document.country.value}`, + (() => { + const docLabel = { + passport: "Passport", + id: "National ID", + permit: "Residence Permit", + dl: "Driver's License", + } as const; + return `${docLabel[$documentType ?? "passport"]} - ${document.country.value}`; + })(),
🧹 Nitpick comments (1)
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte (1)
94-94
: Terminology update aligns with multi-document support.The change from "DEMO PASSPORT"/"VERIFIED PASSPORT" to "DEMO ID"/"VERIFIED ID" appropriately generalizes the badge text to support multiple document types beyond passports.
Consider extracting these strings to an i18n/localization file if the application supports multiple languages.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
infrastructure/eid-wallet/src-tauri/gen/android/app/build.gradle.kts
is excluded by!**/gen/**
📒 Files selected for processing (5)
infrastructure/eid-wallet/package.json
(1 hunks)infrastructure/eid-wallet/src-tauri/tauri.conf.json
(1 hunks)infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
(2 hunks)infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
(1 hunks)infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
(9 hunks)
✅ Files skipped from review due to trivial changes (1)
- infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
🔇 Additional comments (6)
infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (6)
138-143
: LGTM!The resubmission handling correctly resets all captured document images (front, back, and selfie) and advances to the results step. The step number change from 2 to 3 properly reflects the new step mapping that includes DocumentType selection at step 0.
188-190
: LGTM!The explicit guard after
initializeKeyManager()
is good defensive programming. The clear error message "Key manager not initialized" will help with debugging if initialization silently fails.
207-209
: LGTM!Consistent defensive guard with a clear error message, matching the pattern in
generateApplicationKeyPair()
.
232-237
: LGTM!The conditional check for key existence only when
keyManager
is defined prevents null reference errors and aligns with the defensive programming pattern used throughout the component.
330-331
: LGTM!The subtitle text has been generalized to support multiple document types, addressing the past review concern about hardcoded "Passport" references.
362-367
: LGTM!The step mapping correctly implements the new flow with document type selection as the initial step. The sequence (0: DocumentType → 1: Passport → 2: Selfie → 3+: Results) aligns with the changes throughout the component, including the
verifStep.set(3)
at line 143.
"targets": "all", | ||
"android": { | ||
"versionCode": 2 | ||
"versionCode": 6 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep Tauri app version in sync with the release.
versionCode
is bumped, but the same file still reports "version": "0.1.8"
, while package.json
is now 0.3.0
. This mismatch will surface in the built app (and any distribution metadata). Please update the Tauri version
field to 0.3.0
so the Android bundle and desktop binaries advertise the intended release number.
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src-tauri/tauri.conf.json around line 29, the Tauri
config's "version" field is out of sync with package.json (file shows "version":
"0.1.8" while package.json is 0.3.0); update the "version" value to "0.3.0" so
it matches package.json/versionCode and ensure any other version fields in this
file match the new release number before committing.
let loading = $state(false); | ||
let keyManager: KeyManager | null = $state(null); | ||
let websocketData: any = $state(null); // Store websocket data for duplicate case | ||
let websocketData: { w3id?: string } | null = $state(null); // Store websocket data for duplicate case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type narrowing conflicts with stored data.
websocketData
is typed as { w3id?: string } | null
, but line 137 assigns the full websocket message (which includes status
, reason
, person
, document
, and other properties). This type mismatch defeats the purpose of type safety.
Choose one of these solutions:
Option 1 (if only w3id
is needed): Store only the required property.
- websocketData = data; // Store the full websocket data
+ websocketData = data.w3id ? { w3id: data.w3id } : null;
Option 2 (if full data is needed): Define and use the complete type.
+ type WebsocketData = {
+ status: string;
+ reason: string;
+ person: Person;
+ document: Document;
+ w3id?: string;
+ };
+
- let websocketData: { w3id?: string } | null = $state(null);
+ let websocketData: WebsocketData | null = $state(null);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
let websocketData: { w3id?: string } | null = $state(null); // Store websocket data for duplicate case | |
// in your WebSocket message handler (around line 137) | |
websocketData = data.w3id ? { w3id: data.w3id } : null; |
let websocketData: { w3id?: string } | null = $state(null); // Store websocket data for duplicate case | |
<script lang="ts"> | |
// … other imports | |
// Define the full shape of the incoming message | |
type WebsocketData = { | |
status: string; | |
reason: string; | |
person: Person; | |
document: Document; | |
w3id?: string; | |
}; | |
-// Store only w3id (too narrow) | |
// Store the full payload with proper typing | |
let websocketData: WebsocketData | null = $state(null); | |
// … rest of your code | |
</script> |
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte around line
105 (and referenced assignment at line 137), websocketData is declared as {
w3id?: string } | null but later assigned the entire websocket message (which
includes status, reason, person, document, etc.), causing a type mismatch;
either restrict stored value to only the needed property (Option 1) by changing
assignments to extract and store just message.w3id and keep the current type, or
switch to Option 2 by declaring a full interface/type that matches the websocket
message shape and update websocketData’s type to that interface so the full
object can be stored safely—pick the appropriate option and update both the type
declaration and all assignments/usages accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
♻️ Duplicate comments (1)
infrastructure/evault-provisioner/test-real-notifications.js (1)
12-12
: Consider a stronger default secret for testing.Similar to test-notification.js, the default value
'your-secret-here'
is weak. While this is a test script, consider using a more complex default or requiring the environment variable to be set.-const SHARED_SECRET = process.env.NOTIFICATION_SHARED_SECRET || 'your-secret-here'; +const SHARED_SECRET = process.env.NOTIFICATION_SHARED_SECRET || (() => { throw new Error('NOTIFICATION_SHARED_SECRET must be set'); })();
🧹 Nitpick comments (5)
infrastructure/evault-provisioner/test-notification.js (1)
15-15
: Consider a stronger default secret for testing.The default value
'your-secret-here'
is weak. While this is a test script, consider using a more complex default or requiring the environment variable to be set to avoid accidental misuse.-const SHARED_SECRET = process.env.NOTIFICATION_SHARED_SECRET || 'your-secret-here'; +const SHARED_SECRET = process.env.NOTIFICATION_SHARED_SECRET || (() => { throw new Error('NOTIFICATION_SHARED_SECRET must be set'); })();infrastructure/evault-provisioner/src/migrations/1759924736417-migration.ts (1)
6-11
: Consider adding indexes for device queries.The new device-related columns are queried in NotificationService (e.g.,
getDevicesByEName
at lines 75-78 queries bylinkedEName
anddeviceActive
). Adding indexes would improve query performance as device registrations scale.Apply this diff to add a composite index:
public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.query(`ALTER TABLE "verification" ADD "deviceId" character varying`); await queryRunner.query(`ALTER TABLE "verification" ADD "platform" character varying`); await queryRunner.query(`ALTER TABLE "verification" ADD "fcmToken" character varying`); await queryRunner.query(`ALTER TABLE "verification" ADD "deviceActive" boolean NOT NULL DEFAULT true`); + await queryRunner.query(`CREATE INDEX "IDX_verification_linkedename_deviceactive" ON "verification" ("linkedEName", "deviceActive")`); }
And update the down migration:
public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(`DROP INDEX "IDX_verification_linkedename_deviceactive"`); await queryRunner.query(`ALTER TABLE "verification" DROP COLUMN "deviceActive"`); await queryRunner.query(`ALTER TABLE "verification" DROP COLUMN "fcmToken"`); await queryRunner.query(`ALTER TABLE "verification" DROP COLUMN "platform"`); await queryRunner.query(`ALTER TABLE "verification" DROP COLUMN "deviceId"`); }
infrastructure/evault-provisioner/src/controllers/NotificationController.ts (3)
9-14
: Use entity classes instead of string literals for type safety.Using string literals
"Verification"
and"Notification"
to retrieve repositories is less type-safe than using the entity classes directly. This makes the code more prone to typos and refactoring errors.Apply this diff:
+import { Verification } from "../entities/Verification"; + export class NotificationController { private notificationService: NotificationService; constructor() { this.notificationService = new NotificationService( - AppDataSource.getRepository("Verification"), - AppDataSource.getRepository("Notification") + AppDataSource.getRepository(Verification), + AppDataSource.getRepository(Notification) ); }
142-145
: Optimize notification marking with bulk update.Marking notifications as delivered sequentially in a loop is inefficient and creates multiple database round-trips. Consider using a bulk update operation.
Update NotificationService to add a bulk marking method:
// In NotificationService.ts async markNotificationsAsDelivered(notificationIds: string[]): Promise<void> { await this.notificationRepository.update( { id: In(notificationIds) }, { delivered: true, deliveredAt: new Date() } ); }Then update the controller:
// Get undelivered notifications for this eName const notifications = await this.notificationService.getUndeliveredNotifications(eName); // Mark notifications as delivered - for (const notification of notifications) { - await this.notificationService.markNotificationAsDelivered(notification.id); - } + if (notifications.length > 0) { + await this.notificationService.markNotificationsAsDelivered( + notifications.map(n => n.id) + ); + }
128-167
: Consider adding pagination and rate limiting.The
checkNotifications
endpoint could return a large number of notifications without pagination, and the notification endpoints lack rate limiting, which could lead to performance issues or abuse.Consider:
- Adding pagination parameters (limit/offset or cursor-based) to
checkNotifications
- Implementing rate limiting middleware (e.g., using express-rate-limit) on all notification endpoints
- Adding request size limits to prevent large payload attacks
Example for rate limiting:
import rateLimit from 'express-rate-limit'; const notificationLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs message: 'Too many requests from this IP' }); registerRoutes(app: any) { app.post("/api/notifications/check", notificationLimiter, this.checkNotifications.bind(this)); // ... other routes }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (7)
infrastructure/eid-wallet/src-tauri/Cargo.lock
is excluded by!**/*.lock
infrastructure/eid-wallet/src-tauri/gen/android/.idea/gradle.xml
is excluded by!**/gen/**
infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/app-arm64-release.aab
is excluded by!**/gen/**
infrastructure/eid-wallet/src-tauri/gen/android/app/src/main/AndroidManifest.xml
is excluded by!**/gen/**
infrastructure/eid-wallet/src-tauri/gen/apple/eid-wallet.xcodeproj/project.pbxproj
is excluded by!**/gen/**
infrastructure/eid-wallet/src-tauri/gen/apple/eid-wallet_iOS/Info.plist
is excluded by!**/gen/**
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (18)
infrastructure/eid-wallet/package.json
(2 hunks)infrastructure/eid-wallet/src-tauri/Cargo.toml
(1 hunks)infrastructure/eid-wallet/src-tauri/capabilities/mobile.json
(1 hunks)infrastructure/eid-wallet/src-tauri/src/lib.rs
(3 hunks)infrastructure/eid-wallet/src/lib/global/controllers/evault.ts
(5 hunks)infrastructure/eid-wallet/src/lib/global/state.ts
(2 hunks)infrastructure/eid-wallet/src/lib/services/NotificationService.ts
(1 hunks)infrastructure/eid-wallet/src/routes/(app)/+layout.svelte
(1 hunks)infrastructure/evault-provisioner/src/config/database.ts
(2 hunks)infrastructure/evault-provisioner/src/controllers/NotificationController.ts
(1 hunks)infrastructure/evault-provisioner/src/entities/Notification.ts
(1 hunks)infrastructure/evault-provisioner/src/entities/Verification.ts
(1 hunks)infrastructure/evault-provisioner/src/index.ts
(3 hunks)infrastructure/evault-provisioner/src/migrations/1759924736417-migration.ts
(1 hunks)infrastructure/evault-provisioner/src/migrations/1759926271076-migration.ts
(1 hunks)infrastructure/evault-provisioner/src/services/NotificationService.ts
(1 hunks)infrastructure/evault-provisioner/test-notification.js
(1 hunks)infrastructure/evault-provisioner/test-real-notifications.js
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- infrastructure/eid-wallet/src/lib/global/state.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- infrastructure/eid-wallet/package.json
🧰 Additional context used
🧬 Code graph analysis (9)
infrastructure/evault-provisioner/test-real-notifications.js (1)
infrastructure/evault-provisioner/test-notification.js (3)
PROVISIONER_URL
(14-14)SHARED_SECRET
(15-15)TEST_ENAME
(18-18)
infrastructure/evault-provisioner/src/entities/Notification.ts (1)
infrastructure/evault-provisioner/src/entities/Verification.ts (1)
Entity
(9-52)
infrastructure/evault-provisioner/src/migrations/1759924736417-migration.ts (4)
infrastructure/evault-provisioner/src/migrations/1758389959600-migration.ts (1)
Migration1758389959600
(3-14)infrastructure/evault-provisioner/src/migrations/1748966722767-migration.ts (1)
Migration1748966722767
(3-14)infrastructure/evault-provisioner/src/migrations/1748932757644-migration.ts (2)
Migration1748932757644
(3-14)up
(6-8)infrastructure/evault-provisioner/src/migrations/1748968097591-migration.ts (1)
Migration1748968097591
(3-14)
infrastructure/eid-wallet/src/lib/global/controllers/evault.ts (2)
infrastructure/evault-provisioner/src/services/NotificationService.ts (1)
NotificationService
(25-138)infrastructure/eid-wallet/src/lib/global/controllers/user.ts (1)
UserController
(32-146)
infrastructure/evault-provisioner/src/services/NotificationService.ts (1)
infrastructure/eid-wallet/src/lib/services/NotificationService.ts (2)
DeviceRegistration
(5-11)NotificationPayload
(13-17)
infrastructure/evault-provisioner/src/controllers/NotificationController.ts (2)
infrastructure/evault-provisioner/src/services/NotificationService.ts (1)
NotificationService
(25-138)infrastructure/evault-provisioner/src/config/database.ts (1)
AppDataSource
(10-18)
infrastructure/eid-wallet/src/lib/services/NotificationService.ts (3)
infrastructure/evault-provisioner/src/services/NotificationService.ts (3)
DeviceRegistration
(5-11)NotificationPayload
(13-17)NotificationService
(25-138)infrastructure/evault-provisioner/src/controllers/NotificationController.ts (1)
sendNotification
(94-126)infrastructure/eid-wallet/src/lib/global/controllers/evault.ts (2)
vault
(241-327)vault
(329-342)
infrastructure/evault-provisioner/test-notification.js (3)
infrastructure/evault-provisioner/test-real-notifications.js (3)
PROVISIONER_URL
(11-11)SHARED_SECRET
(12-12)TEST_ENAME
(15-15)infrastructure/evault-provisioner/src/controllers/NotificationController.ts (1)
getDeviceStats
(169-183)infrastructure/evault-provisioner/src/services/NotificationService.ts (1)
getDeviceStats
(123-137)
infrastructure/evault-provisioner/src/index.ts (1)
infrastructure/evault-provisioner/src/controllers/NotificationController.ts (1)
NotificationController
(6-184)
🔇 Additional comments (1)
infrastructure/evault-provisioner/src/migrations/1759926271076-migration.ts (1)
6-8
: Enable theuuid-ossp
PostgreSQL extension before running this migration.
Migration calls uuid_generate_v4(); repo scan found no "uuid-ossp" or CREATE EXTENSION references — add a prior migration or DB-init step to run: CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; (file: infrastructure/evault-provisioner/src/migrations/1759926271076-migration.ts)
/// Gets a unique device identifier | ||
#[tauri::command] | ||
async fn get_device_id() -> Result<String, String> { | ||
// Try to get device ID from environment or generate one | ||
if let Ok(device_id) = env::var("DEVICE_ID") { | ||
Ok(device_id) | ||
} else { | ||
// Generate a UUID as device ID | ||
Ok(Uuid::new_v4().to_string()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Device ID lacks persistence mechanism.
The function generates a new UUID every time DEVICE_ID
is not set in the environment. This means each app restart will produce a different device ID, which could break device registration and notification delivery.
Consider persisting the generated device ID to storage (e.g., using Tauri's store plugin) or ensure DEVICE_ID
is consistently set in the environment across restarts.
use tauri::Manager;
use std::fs;
#[tauri::command]
async fn get_device_id(app: tauri::AppHandle) -> Result<String, String> {
let device_id_path = app.path_resolver()
.app_data_dir()
.ok_or("Failed to get app data dir")?
.join("device_id");
// Try to read existing device ID
if let Ok(device_id) = fs::read_to_string(&device_id_path) {
return Ok(device_id.trim().to_string());
}
// Generate and persist new device ID
let device_id = Uuid::new_v4().to_string();
fs::create_dir_all(device_id_path.parent().ok_or("Invalid path")?).map_err(|e| e.to_string())?;
fs::write(&device_id_path, &device_id).map_err(|e| e.to_string())?;
Ok(device_id)
}
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src-tauri/src/lib.rs around lines 38 to 48, the
current get_device_id generates a new UUID on every run when DEVICE_ID isn't in
the environment; change it to accept a tauri::AppHandle, resolve the app data
dir via app.path_resolver().app_data_dir(), attempt to read a "device_id" file
and return its trimmed contents if present, otherwise generate
Uuid::new_v4().to_string(), ensure the parent directory exists (create_dir_all),
write the new ID to the file, and propagate filesystem errors as String for the
Result so the device ID is persisted across restarts.
// Register device for notifications | ||
this.registerDeviceForNotifications(vault.ename); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing await
on async call.
The call to registerDeviceForNotifications
is not awaited in the non-Promise path. This means device registration will happen asynchronously without waiting for completion, potentially causing race conditions or failures that go unnoticed.
Apply this diff to add the missing await
:
// Register device for notifications
- this.registerDeviceForNotifications(vault.ename);
+ await this.registerDeviceForNotifications(vault.ename);
However, note that adding await
here requires the enclosing context to handle the async call properly. Since this is in a non-Promise setter branch, you may need to wrap this in an immediately invoked async function similar to the profile creation logic below it.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/lib/global/controllers/evault.ts around lines
285-286, the call to registerDeviceForNotifications(vault.ename) is missing an
await causing the registration to run asynchronously; change this to await the
call and because this branch is not async, wrap the awaited call in an
immediately-invoked async function (async () => { await
this.registerDeviceForNotifications(vault.ename); })().catch(err => {/*
log/handle error */}) so the registration completes and errors are surfaced,
matching the pattern used for profile creation below.
this.provisionerEndpoint = PUBLIC_PROVISIONER_URL | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle missing provisioner endpoint
PUBLIC_PROVISIONER_URL
resolves to undefined
when the env var isn’t set (SvelteKit simply injects undefined
). Every fetch in this service will then blow up with “Failed to parse URL from undefined…”, killing registration and notification polling. Either fail fast with a clear error or provide a real default before using it. For example:
- this.provisionerEndpoint = PUBLIC_PROVISIONER_URL
+ this.provisionerEndpoint = PUBLIC_PROVISIONER_URL || "http://localhost:8080";
+ if (!this.provisionerEndpoint) {
+ throw new Error("Provisioner endpoint is not configured.");
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
this.provisionerEndpoint = PUBLIC_PROVISIONER_URL | |
} | |
this.provisionerEndpoint = PUBLIC_PROVISIONER_URL || "http://localhost:8080"; | |
if (!this.provisionerEndpoint) { | |
throw new Error("Provisioner endpoint is not configured."); | |
} | |
} |
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/lib/services/NotificationService.ts around
lines 26 to 27, PUBLIC_PROVISIONER_URL can be undefined causing fetches to
throw; update the constructor to validate the env var and either throw a clear
error immediately if it's missing or assign a safe default URL before using it;
specifically, check if PUBLIC_PROVISIONER_URL is truthy and if not throw an
Error with a descriptive message (e.g. "Missing PUBLIC_PROVISIONER_URL
environment variable") or set a documented default value, and ensure all
subsequent usages rely on this validated/normalized value.
// Verify shared secret | ||
if (sharedSecret !== process.env.NOTIFICATION_SHARED_SECRET) { | ||
return res.status(401).json({ | ||
success: false, | ||
error: "Invalid shared secret" | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use constant-time comparison for shared secret.
The equality check for sharedSecret
uses !==
which is vulnerable to timing attacks. An attacker could measure response times to guess the secret character by character.
Use a constant-time comparison function:
+import * as crypto from 'crypto';
+
+function constantTimeCompare(a: string, b: string): boolean {
+ if (a.length !== b.length) return false;
+ return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
+}
+
private async sendNotification(req: Request, res: Response) {
try {
const { eName, notification, sharedSecret } = req.body;
// Verify shared secret
- if (sharedSecret !== process.env.NOTIFICATION_SHARED_SECRET) {
+ if (!constantTimeCompare(sharedSecret || '', process.env.NOTIFICATION_SHARED_SECRET || '')) {
return res.status(401).json({
success: false,
error: "Invalid shared secret"
});
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// Verify shared secret | |
if (sharedSecret !== process.env.NOTIFICATION_SHARED_SECRET) { | |
return res.status(401).json({ | |
success: false, | |
error: "Invalid shared secret" | |
}); | |
} | |
import * as crypto from 'crypto'; | |
function constantTimeCompare(a: string, b: string): boolean { | |
if (a.length !== b.length) return false; | |
return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)); | |
} | |
private async sendNotification(req: Request, res: Response) { | |
try { | |
const { eName, notification, sharedSecret } = req.body; | |
// Verify shared secret | |
if (!constantTimeCompare(sharedSecret || '', process.env.NOTIFICATION_SHARED_SECRET || '')) { | |
return res.status(401).json({ | |
success: false, | |
error: "Invalid shared secret" | |
}); | |
} | |
// …rest of implementation… | |
} catch (err) { | |
// error handling… | |
} | |
} |
🤖 Prompt for AI Agents
in infrastructure/evault-provisioner/src/controllers/NotificationController.ts
around lines 98 to 104, replace the plain !== string comparison with a
constant-time check: import Node's crypto, compute fixed-length digests (e.g.,
SHA-256) or convert both values to Buffers of equal, known length, and use
crypto.timingSafeEqual to compare them; ensure you handle null/undefined values
safely (treat missing values as empty string or short-circuit to 401), and keep
the response logic the same when the timing-safe comparison fails.
@Column({ nullable: true }) | ||
deliveredAt!: Date; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix type inconsistency for deliveredAt
.
The deliveredAt
field is marked as nullable in the decorator but uses the non-null assertion operator (!
) in the type declaration. This is inconsistent. Since the field is nullable, it should be marked as optional with ?
.
Apply this diff:
@Column({ nullable: true })
- deliveredAt!: Date;
+ deliveredAt?: Date;
🤖 Prompt for AI Agents
In infrastructure/evault-provisioner/src/entities/Notification.ts around lines
23 to 24, the deliveredAt field is decorated as nullable but declared with a
non-null assertion; change the property to be optional and allow null by
replacing the non-null assertion with an optional/nullable type (e.g.,
deliveredAt?: Date | null) so the TypeScript type matches @Column({ nullable:
true }) and update any usages to handle undefined/null accordingly.
// Register notification routes | ||
notificationController.registerRoutes(app); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fail closed when notification shared secret is unset.
NotificationController.sendNotification
only rejects when sharedSecret !== process.env.NOTIFICATION_SHARED_SECRET
. If that env var is missing (undefined), any caller omitting sharedSecret
slips through because undefined === undefined
. We should abort startup when the secret is absent (or otherwise guard before route registration) so the endpoint never runs without authentication.
Apply this diff near the top-level initialization to enforce the check:
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
+if (!process.env.NOTIFICATION_SHARED_SECRET) {
+ console.error("NOTIFICATION_SHARED_SECRET must be configured before starting the provisioner API");
+ process.exit(1);
+}
+
const app = express();
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
// Register notification routes | |
notificationController.registerRoutes(app); | |
// Load environment variables | |
dotenv.config({ path: path.resolve(__dirname, "../../../.env") }); | |
if (!process.env.NOTIFICATION_SHARED_SECRET) { | |
console.error("NOTIFICATION_SHARED_SECRET must be configured before starting the provisioner API"); | |
process.exit(1); | |
} | |
const app = express(); | |
// …rest of your initialization… |
🤖 Prompt for AI Agents
In infrastructure/evault-provisioner/src/index.ts around lines 176-177, the app
registers notification routes while process.env.NOTIFICATION_SHARED_SECRET may
be undefined, allowing unauthenticated requests (because undefined ===
undefined); before registering routes (at top-level initialization) validate
that process.env.NOTIFICATION_SHARED_SECRET exists and is non-empty, and if not,
log an error and abort startup (throw or process.exit(1)) so the service never
starts without the notification shared secret.
public async up(queryRunner: QueryRunner): Promise<void> { | ||
await queryRunner.query(`CREATE TABLE "notifications" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "eName" character varying NOT NULL, "title" character varying NOT NULL, "body" character varying NOT NULL, "data" jsonb, "delivered" boolean NOT NULL DEFAULT false, "deliveredAt" TIMESTAMP, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_6a72c3c0f683f6462415e653c3a" PRIMARY KEY ("id"))`); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add indexes for query performance.
The notifications table lacks indexes on eName
and delivered
columns, which are used in the getUndeliveredNotifications
query (see NotificationService.ts:105-108). Without indexes, queries will perform full table scans as notification count grows.
Apply this diff to add a composite index:
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "notifications" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "eName" character varying NOT NULL, "title" character varying NOT NULL, "body" character varying NOT NULL, "data" jsonb, "delivered" boolean NOT NULL DEFAULT false, "deliveredAt" TIMESTAMP, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_6a72c3c0f683f6462415e653c3a" PRIMARY KEY ("id"))`);
+ await queryRunner.query(`CREATE INDEX "IDX_notifications_ename_delivered" ON "notifications" ("eName", "delivered")`);
}
And update the down migration:
public async down(queryRunner: QueryRunner): Promise<void> {
+ await queryRunner.query(`DROP INDEX "IDX_notifications_ename_delivered"`);
await queryRunner.query(`DROP TABLE "notifications"`);
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
public async up(queryRunner: QueryRunner): Promise<void> { | |
await queryRunner.query(`CREATE TABLE "notifications" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "eName" character varying NOT NULL, "title" character varying NOT NULL, "body" character varying NOT NULL, "data" jsonb, "delivered" boolean NOT NULL DEFAULT false, "deliveredAt" TIMESTAMP, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_6a72c3c0f683f6462415e653c3a" PRIMARY KEY ("id"))`); | |
} | |
// infrastructure/evault-provisioner/src/migrations/1759926271076-migration.ts | |
import { MigrationInterface, QueryRunner } from "typeorm"; | |
export class Migration1759926271076 implements MigrationInterface { | |
public async up(queryRunner: QueryRunner): Promise<void> { | |
await queryRunner.query(` | |
CREATE TABLE "notifications" ( | |
"id" uuid NOT NULL DEFAULT uuid_generate_v4(), | |
"eName" character varying NOT NULL, | |
"title" character varying NOT NULL, | |
"body" character varying NOT NULL, | |
"data" jsonb, | |
"delivered" boolean NOT NULL DEFAULT false, | |
"deliveredAt" TIMESTAMP, | |
"createdAt" TIMESTAMP NOT NULL DEFAULT now(), | |
CONSTRAINT "PK_6a72c3c0f683f6462415e653c3a" PRIMARY KEY ("id") | |
) | |
`); | |
// add composite index for faster lookups of undelivered notifications per eName | |
await queryRunner.query(` | |
CREATE INDEX "IDX_notifications_ename_delivered" | |
ON "notifications" ("eName", "delivered") | |
`); | |
} | |
public async down(queryRunner: QueryRunner): Promise<void> { | |
await queryRunner.query(` | |
DROP INDEX "IDX_notifications_ename_delivered" | |
`); | |
await queryRunner.query(` | |
DROP TABLE "notifications" | |
`); | |
} | |
} |
🤖 Prompt for AI Agents
in infrastructure/evault-provisioner/src/migrations/1759926271076-migration.ts
around lines 6 to 8, the migration creates the notifications table but does not
add indexes used by getUndeliveredNotifications; modify the up method to create
a composite index on ( "eName", "delivered" ) to speed queries (e.g., CREATE
INDEX IDX_notifications_eName_delivered ON "notifications" ("eName",
"delivered")), and update the down method to DROP that index (e.g., DROP INDEX
IF EXISTS IDX_notifications_eName_delivered) so the migration cleanly reverts;
ensure index name matches your DB naming conventions and wrap both statements in
await queryRunner.query calls consistent with the file.
Description of change
adds ui flows to support documents other than passport
Issue Number
closes #356
Type of change
How the change has been tested
Manual
Change checklist
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Chores