Skip to content

feat(accounts): ID verification Phase 0 — evidence, reviewer role, retention - #500

Open
islandbitcoin wants to merge 2 commits into
mainfrom
feat/id-verification-phase0
Open

feat(accounts): ID verification Phase 0 — evidence, reviewer role, retention#500
islandbitcoin wants to merge 2 commits into
mainfrom
feat/id-verification-phase0

Conversation

@islandbitcoin

Copy link
Copy Markdown
Contributor

Phase 0 (backend side) of the Level 2 / Level 3 ID-verification tool. Companion ERPNext PR: feat/id-verification-phase0 in lnflash/frappe-flash-admin — deploy that first so the ID Verification doctype exists before this starts posting to it (the POST is non-fatal if it doesn't, so ordering is about data, not outages).

What this adds

  • Reviewer role can complete approvals. ERPNext reviewers holding only Flash Admin were rejected by the admin shield (System Manager / Accounts Manager only), so accountUpdateLevel failed for them. New src/servers/authorization/admin-permissions.ts builds the shield from an explicit allowlist: Flash Admin is accepted on accountDetailsByUserPhone, idDocumentReadUrl and accountUpdateLevel only; every other admin field keeps the existing rule. Tests evaluate the real graphql-shield rules for allowed and denied fields.
  • Structured evidence on both upgrade mutations. Optional evidence: [UpgradeEvidenceInput!] (ID_FRONT, ID_BACK, SELFIE, LIVENESS_FRAME, BUSINESS_REGISTRATION, TRN, PROOF_OF_ADDRESS, BRIDGE_KYC). Legacy idDocument keeps working and is normalised to one ID_FRONT row. Strict rule (L2/L3 need ID front + selfie, or approved Bridge KYC) sits behind UPGRADE_EVIDENCE_STRICT (default false) until the new mobile capture ships. Pure domain logic in src/domain/accounts/upgrade-evidence.ts.
  • Bridge KYC satisfies identity. When the account's Bridge KYC is approved, a BRIDGE_KYC row is accepted (or synthesised when no evidence is sent) and a minimal customer snapshot (id, status, updated_at, endorsements) is recorded; snapshot failure never blocks the request.
  • Companion ERPNext record. After postUpgradeRequest, ErpNext.postIdVerification creates the ID Verification doc with evidence child rows. Failure → warn log, user request still succeeds; the legacy id_document key is still written so nothing regresses.
  • PII out of logs. Removed the console.log of the full upgrade request in the public query, and a second leak in postUpgradeRequest's error path that spread name/phone/email/address into the log.
  • Evidence deletion + retention. deleteIdDocument / listIdDocuments in the storage service (refuse anything outside id_documents/). src/domain/accounts/evidence-retention.ts: 7-year policy (EVIDENCE_RETENTION_YEARS), two clocks — approved: 7y after account closure (derived from statusHistory; reopened accounts restart the clock; locked is not closure), rejected/closed: 7y after reviewed_at. Cron evidenceRetentionJob in src/servers/cron.ts, EVIDENCE_RETENTION_DRY_RUN default true for the first release.
  • Env booleans for the three new flags use a real parser ("false" → false); the existing z.coerce.boolean() pattern was left untouched elsewhere.
  • Docs: docs/id-verification.md.

Tests

  • Unit: 231 suites, 2511 passed (8 new spec files, 108 new tests).
  • yarn tsc-check, yarn eslint-check clean.
  • yarn write-sdl: additions only (UpgradeEvidenceType, UpgradeEvidenceInput, evidence on both inputs); admin schema unchanged.

Not in this PR

Mobile capture (document picker, front/back, selfie) — next. When it sends documentType / issuingCountry, they must be the ERPNext registry code and the Frappe Country name, since both are Link fields.

🤖 Generated with Claude Code

https://claude.ai/code/session_01W5e4H8MF7oX2LPUxBRW3Pf

bobodread876 and others added 2 commits September 1, 2026 13:49
…tention

Backend half of the ID-verification tool (docs/id-verification.md).

- Upgrade mutations accept optional structured `evidence`
  ([UpgradeEvidenceInput!] with UpgradeEvidenceType); legacy `idDocument`
  keeps working unchanged and is folded in as an ID_FRONT row. Pure domain
  normalize/validate in src/domain/accounts/upgrade-evidence.ts; strict
  identity requirement (ID_FRONT+SELFIE or approved BRIDGE_KYC for L2/L3)
  behind UPGRADE_EVIDENCE_STRICT (default false).
- Bridge KYC: BRIDGE_KYC rows require an approved Bridge KYC; the Bridge
  customer is snapshotted (id/status/updated_at/endorsements only), never
  blocking the request.
- ERPNext: companion "ID Verification" document POSTed after the Account
  Upgrade Request (ErpNext.postIdVerification). Failure is warn-logged and
  non-fatal; id_document still carries the first ID_FRONT key.
- Admin authz: "Flash Admin" recognised and allowed only on
  accountDetailsByUserPhone, idDocumentReadUrl and accountUpdateLevel via an
  explicit allowlist (src/servers/authorization/admin-permissions.ts);
  everything else keeps System Manager / Accounts Manager.
- PII: drop the console.log dumping upgrade requests from the public
  accountUpgradeRequest query; postUpgradeRequest error log no longer
  spreads the request body.
- Retention: deleteIdDocument/listIdDocuments on storage (id_documents/
  only); pure policy in src/domain/accounts/evidence-retention.ts
  (7y default, EVIDENCE_RETENTION_YEARS; Approved → closed-at + N years,
  Rejected/Closed → decided-at + N years; closed-at derived from
  statusHistory); cron task runEvidenceRetention, dry-run by default
  (EVIDENCE_RETENTION_DRY_RUN=true).
- Env booleans for the new flags parse "false"/"0"/"no"/"off" as false.
- SDL regenerated (public schema + supergraph: additions only).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W5e4H8MF7oX2LPUxBRW3Pf
…ip check

Code review on PR #500:

- upgrade-evidence.ts: `startsWith(id_documents/<username>_)` had no
  delimiter boundary, so a user whose username is an exact prefix of
  another user's (`alice` vs. `alice_smith`) could pass the ownership
  check for the other account's uploaded file (usernames may contain
  `_`). Switch the username/filename separator to `/`, which
  UsernameRegex forbids in usernames, so the prefix can no longer be
  satisfied by a longer username. Updated the matching key generation
  in services/storage and the format documented in
  docs/id-verification.md.
- IdVerification.fromErpnext: log a warning (doc name, upgrade
  request, row, raw value) when an unrecognized evidence_type falls
  back to id_front, instead of silently reclassifying it.
- Added regression tests pinning the `alice` vs `alice_smith` prefix-
  collision shape in both upgrade-evidence.spec.ts and
  upgrade-request-evidence.spec.ts, and tests for the new warning log
  (and its absence on a recognized type) in IdVerification.spec.ts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EtQzyzQ38thfC2F8BLK4u8
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.

2 participants