Skip to content

✨ server: encrypted kyc - #948

Merged
cruzdanilo merged 15 commits into
mainfrom
encrypted-kyc
May 20, 2026
Merged

✨ server: encrypted kyc#948
cruzdanilo merged 15 commits into
mainfrom
encrypted-kyc

Conversation

@nfmelendez

@nfmelendez nfmelendez commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • KYC endpoints to submit, update, and check application status; card endpoints now block actions if KYC not approved.
    • Organization role field and KYC permission controls added.
  • Documentation

    • Guide for creating encrypted KYC payloads with SIWE and note on requesting KYC permissions.
  • Tests

    • Expanded tests and mocks covering KYC flows, encrypted submissions, permissions, and error mappings.
  • Chores

    • Spelling/config update and multiple release metadata entries.

@changeset-bot

changeset-bot Bot commented Apr 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f57da90

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@exactly/server Patch
@exactly/docs Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds KYC application workflow: new authenticated POST/PATCH/GET /application endpoints with SIWE verification and payload hashing (encrypted/plaintext), Panda submit/get/update wrappers and schemas, org role and ACL updates, DB column for organization role, docs for encrypted KYC payload, dependency add, and expanded tests/mocks.

Changes

Cohort / File(s) Summary
Changeset Metadata
.changeset/cuddly-streets-like.md, .changeset/four-numbers-worry.md, .changeset/sharp-squids-push.md, .changeset/upset-seas-sink.md, .changeset/calm-tigers-stop.md
Added multiple changeset files declaring patch releases and documenting KYC-related release notes.
Docs
docs/src/content/docs/organization-authentication.md
Added warning and a TypeScript example demonstrating encrypted KYC payload construction (AES-256-GCM + RSA-OAEP key wrap, Base64 fields, payload hash in SIWE statement).
API: KYC endpoints
server/api/kyc.ts
Introduced POST /application, PATCH /application, GET /application with OpenAPI/valibot schemas, SIWE signature/chainId/statement verification, payload canonicalization/hash, org membership/role checks, Panda submit/update/status integration, and error mapping.
API: Card routes
server/api/card.ts
Added KYC approval gating by calling getApplicationStatus() and expanded 403 responses to include kyc not approved in GET and POST handlers.
Panda utilities
server/utils/panda.ts
Removed default export of API key; added exported functions submitApplication, getApplicationStatus, updateApplication; added valibot schemas (Application, SubmitApplicationRequest, UpdateApplicationRequest, kycStatus) and response validation.
Auth & ACL
server/utils/auth.ts
Added kyc resource to access-control config, granted create to admin/owner, and added optional role field to organization plugin additionalFields.
Database schema
server/database/schema.ts
Added nullable exported role text column to organizations table.
Dependencies
server/package.json
Added canonicalize dependency (^2.1.0) for JSON canonicalization.
Tests
server/test/api/card.test.ts, server/test/api/kyc.test.ts, server/test/e2e.ts
Updated tests to stub/mock getApplicationStatus, added comprehensive KYC endpoint tests (SIWE flows, encrypted/plaintext submission, error mapping, updates/status), adjusted card tests to reflect new KYC gating, and extended e2e panda mock.
Config
cspell.json
Added "ciphertext" to spell-check dictionary.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client as Client
    participant KYC as KYC API
    participant DB as Database
    participant Auth as Auth Service
    participant Panda as Panda Service

    Client->>KYC: POST /application (SIWE + payload {ciphertext|JSON})
    KYC->>KYC: Parse SIWE, verify signature & chainId
    KYC->>KYC: Compute sha256(ciphertext or canonicalized JSON)
    KYC->>DB: Read credential & org membership
    KYC->>Auth: Verify org role includes kyc:create
    KYC->>Panda: submitApplication(payload, encrypted?)
    Panda-->>KYC: { id, applicationStatus }
    KYC->>DB: Persist credentials.pandaId & source
    KYC-->>Client: { status: applicationStatus }

    Client->>KYC: GET /application
    KYC->>DB: Read credentials.pandaId
    KYC->>Panda: getApplicationStatus(pandaId)
    Panda-->>KYC: { id, applicationStatus, reason? }
    KYC-->>Client: { code:"ok", status, reason }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • cruzdanilo
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '✨ server: encrypted kyc' is specific to the changeset, referencing the server package and encrypted KYC feature implementation, but could be more descriptive about the core changes (API endpoints, encryption support, role-based access control).

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch encrypted-kyc

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements a new KYC application submission and management flow, including support for encrypted payloads and organization-based permissions. The review identified potential issues with the current organization membership check, the SIWE message parsing, and the payload hashing logic. I have provided suggestions to improve the robustness of the membership lookup, handle potential parsing errors, and correct the hashing implementation.

Comment thread server/api/kyc.ts
Comment thread server/api/kyc.ts Outdated
Comment thread server/api/kyc.ts
@sentry

sentry Bot commented Apr 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.21%. Comparing base (6e77f1d) to head (f57da90).
⚠️ Report is 15 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
server/api/kyc.ts 83.11% 3 Missing and 10 partials ⚠️
server/database/schema.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #948      +/-   ##
==========================================
+ Coverage   72.81%   73.21%   +0.40%     
==========================================
  Files         241      241              
  Lines       10013    10245     +232     
  Branches     3265     3362      +97     
==========================================
+ Hits         7291     7501     +210     
- Misses       2429     2440      +11     
- Partials      293      304      +11     
Flag Coverage Δ
e2e 73.21% <83.33%> (+0.40%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
server/utils/auth.ts (1)

20-64: ⚠️ Potential issue | 🟡 Minor

Remove unused delete and read kyc actions or implement permission enforcement for them.

Lines 20 define kyc: ["create", "delete", "read"], but only create is granted to admin/owner roles (lines 58, 63), and no KYC endpoints enforce these actions via hasPermission(). The global action definitions are unused dead code—either remove delete and read to match the intended role permissions, or add permission checks to enforce them in KYC endpoints (currently they bypass the access control system).


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 12eeea5f-41ad-44e3-98ac-07be8bf882b0

📥 Commits

Reviewing files that changed from the base of the PR and between 2598ece and 125a498.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • .changeset/cuddly-streets-like.md
  • .changeset/four-numbers-worry.md
  • .changeset/sharp-squids-push.md
  • .changeset/upset-seas-sink.md
  • cspell.json
  • docs/src/content/docs/organization-authentication.md
  • server/api/card.ts
  • server/api/kyc.ts
  • server/database/schema.ts
  • server/package.json
  • server/test/api/card.test.ts
  • server/test/api/kyc.test.ts
  • server/test/e2e.ts
  • server/utils/auth.ts
  • server/utils/panda.ts

Comment thread server/api/card.ts
Comment thread server/api/kyc.ts
Comment thread server/api/kyc.ts Outdated
Comment thread server/api/kyc.ts Outdated
Comment thread server/utils/panda.ts
@cruzdanilo cruzdanilo changed the title Encrypted kyc ✨ server: encrypted kyc Apr 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
server/test/api/kyc.test.ts (1)

1440-1440: ⚠️ Potential issue | 🟠 Major

These test signatures still hash the wrong bytes.

This is the same canonicalize bug flagged earlier: canonicalize() already returns the canonical JSON string, so wrapping it in JSON.stringify() changes the bytes being signed. The plaintext application tests no longer match the server-side hash computation.

💡 Proposed fix
- sha256(Buffer.from(JSON.stringify(canonicalize(applicationPayload)), "utf8"))
+ sha256(Buffer.from(canonicalize(applicationPayload) ?? "", "utf8"))

Also applies to: 1504-1504, 1557-1557, 1739-1739, 1776-1776, 1813-1813, 1842-1842


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4b887372-b7ce-4860-af1c-ac3e93fea439

📥 Commits

Reviewing files that changed from the base of the PR and between 125a498 and aeaa4ee.

📒 Files selected for processing (10)
  • .changeset/cuddly-streets-like.md
  • .changeset/sharp-squids-push.md
  • .changeset/upset-seas-sink.md
  • cspell.json
  • docs/src/content/docs/organization-authentication.md
  • server/api/kyc.ts
  • server/database/schema.ts
  • server/test/api/kyc.test.ts
  • server/utils/auth.ts
  • server/utils/panda.ts

Comment thread server/api/kyc.ts
Comment thread server/api/kyc.ts
Comment thread server/api/kyc.ts
Comment thread server/database/schema.ts
Comment thread server/utils/panda.ts Outdated
Comment thread server/utils/panda.ts
Comment thread server/utils/panda.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c1ad266c-f3b0-4847-9461-6c6e351826af

📥 Commits

Reviewing files that changed from the base of the PR and between aeaa4ee and 7fafac5.

📒 Files selected for processing (10)
  • .changeset/cuddly-streets-like.md
  • .changeset/sharp-squids-push.md
  • .changeset/upset-seas-sink.md
  • cspell.json
  • docs/src/content/docs/organization-authentication.md
  • server/api/kyc.ts
  • server/database/schema.ts
  • server/test/api/kyc.test.ts
  • server/utils/auth.ts
  • server/utils/panda.ts

Comment thread docs/src/content/docs/organization-authentication.md
Comment thread server/api/kyc.ts Outdated
Comment thread server/test/api/kyc.test.ts Outdated
Comment thread server/utils/auth.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
server/api/card.ts (1)

334-338: ⚠️ Potential issue | 🟠 Major

Move the KYC lookup behind the local already created branch.

This preflight now runs before we decide whether the user already has an active/frozen card, so duplicate POST /card calls can regress from 400 { code: "already created" } to 403/500 when Panda is denied, stale, or unavailable. Putting getApplicationStatus(...) inside the existing try after if (cardCount > 0) keeps the endpoint idempotent and reuses the current noUser(...) mapping for stale pandaIds.

♻️ Proposed fix
-          const kyc = await getApplicationStatus(credential.pandaId);
-          if (kyc.applicationStatus !== "approved") {
-            return c.json({ code: "kyc not approved" }, 403);
-          }
-
           let isUpgradeFromPlatinum = credential.cards.some(
             ({ status, productId }) => status === "DELETED" && productId === PLATINUM_PRODUCT_ID,
           );
@@
           }
           if (cardCount > 0) return c.json({ code: "already created" }, 400);
           try {
+            const kyc = await getApplicationStatus(credential.pandaId);
+            if (kyc.applicationStatus !== "approved") return c.json({ code: "kyc not approved" }, 403);
             const card = await createCard(credential.pandaId, SIGNATURE_PRODUCT_ID);
server/utils/panda.ts (1)

425-431: ⚠️ Potential issue | 🟡 Minor

new Date(...) still accepts impossible calendar dates.

Values like 2026-02-31 pass this check because JavaScript normalizes overflowed dates instead of rejecting them. Round-trip the parsed year/month/day with Date.UTC(...) so only real calendar dates reach Panda.

🛠️ Proposed fix
   birthDate: pipe(
     string(),
     regex(/^\d{4}-\d{2}-\d{2}$/, "must be YYYY-MM-DD format"),
     check((value) => {
-      const date = new Date(value);
-      return !Number.isNaN(date.getTime());
+      const [year, month, day] = value.split("-").map(Number);
+      if (year === undefined || month === undefined || day === undefined) return false;
+      const date = new Date(Date.UTC(year, month - 1, day));
+      return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
     }, "must be a valid date"),
Does JavaScript `new Date("2026-02-31")` normalize overflowed ISO dates instead of returning `Invalid Date`?

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1fb0a294-b798-4017-b6f4-d185461cd025

📥 Commits

Reviewing files that changed from the base of the PR and between 7fafac5 and 9058e28.

📒 Files selected for processing (15)
  • .changeset/calm-tigers-stop.md
  • .changeset/cuddly-streets-like.md
  • .changeset/four-numbers-worry.md
  • .changeset/sharp-squids-push.md
  • .changeset/upset-seas-sink.md
  • cspell.json
  • docs/src/content/docs/organization-authentication.md
  • server/api/card.ts
  • server/api/kyc.ts
  • server/database/schema.ts
  • server/test/api/card.test.ts
  • server/test/api/kyc.test.ts
  • server/test/e2e.ts
  • server/utils/auth.ts
  • server/utils/panda.ts

Comment thread server/api/kyc.ts Outdated
Comment thread server/test/api/kyc.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
docs/src/content/docs/organization-authentication.md (1)

226-327: ⚠️ Potential issue | 🟡 Minor

Add the actual /application submit example with encrypted: "true" header.

The sample currently stops at payload logging (Line 323). For encrypted KYC, readers also need the submission contract; otherwise this is easy to copy into a failing integration.

📄 Suggested doc patch
 owner.signMessage({ message })
-  .then((signature) => {
+  .then(async (signature) => {
     const verify = {
       message,
       signature,
       walletAddress: owner.address,
       chainId,
     };
     const { hash, ...payload } = encryptedPayload;
-    console.log("application payload", { ...payload, verify });
+    const applicationPayload = { ...payload, verify };
+    console.log("application payload", applicationPayload);
+
+    const response = await fetch("https://sandbox.exactly.app/api/application", {
+      method: "POST",
+      headers: { "content-type": "application/json", encrypted: "true" },
+      body: JSON.stringify(applicationPayload),
+    });
+    console.log("application submit status", response.status);
   })

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8500d69f-ac57-471b-95c2-d4e1e46f3edb

📥 Commits

Reviewing files that changed from the base of the PR and between 9058e28 and 98f2903.

📒 Files selected for processing (11)
  • .changeset/calm-tigers-stop.md
  • .changeset/cuddly-streets-like.md
  • .changeset/sharp-squids-push.md
  • .changeset/upset-seas-sink.md
  • cspell.json
  • docs/src/content/docs/organization-authentication.md
  • server/api/kyc.ts
  • server/database/schema.ts
  • server/test/api/kyc.test.ts
  • server/utils/auth.ts
  • server/utils/panda.ts

Comment thread server/test/api/kyc.test.ts
@nfmelendez
nfmelendez force-pushed the webhook branch 2 times, most recently from 8c026bc to fc57eea Compare April 15, 2026 15:36
@nfmelendez
nfmelendez force-pushed the webhook branch 2 times, most recently from c985bf7 to 43dc358 Compare April 15, 2026 19:26
@nfmelendez
nfmelendez force-pushed the encrypted-kyc branch 2 times, most recently from b095e06 to 987fa1c Compare April 15, 2026 20:39
@nfmelendez

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cruzdanilo
cruzdanilo force-pushed the encrypted-kyc branch 2 times, most recently from 295a971 to 6842c4b Compare May 20, 2026 16:47

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f57da9051f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread server/api/kyc.ts
Comment on lines +504 to +505
const account = await database.query.walletAddresses.findFirst({
where: eq(walletAddresses.address, address),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter wallet address lookup by chain id

Use the request chain context when resolving the signing wallet. The new KYC submit flow queries wallet_addresses with findFirst on address only, even though the table stores chainId and the payload includes verify.chainId; if the same address exists in multiple rows (e.g., different chain/user records), this can select the wrong membership arbitrarily and either deny valid submissions or authorize against the wrong organization membership.

Useful? React with 👍 / 👎.

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.

3 participants