Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/calm-tigers-stop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@exactly/server": patch
"@exactly/docs": patch
---

🔥 drop header from encrypted kyc
6 changes: 6 additions & 0 deletions .changeset/cuddly-streets-like.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@exactly/server": patch
"@exactly/docs": patch
---

✨ improve nonce usage for encrypted kyc
5 changes: 5 additions & 0 deletions .changeset/four-numbers-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/server": patch
---

✨ implement kyc data submission
5 changes: 5 additions & 0 deletions .changeset/sharp-squids-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/server": patch
---

🗃️ add role to organization database table
5 changes: 5 additions & 0 deletions .changeset/upset-seas-sink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@exactly/server": patch
---

✨ add kyc role to organizations
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"checksummed",
"CLABE",
"clippy",
"ciphertext",
"codegen",
"codepoint",
"colocating",
Expand Down
107 changes: 107 additions & 0 deletions docs/src/content/docs/organization-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Then the owner can add members with admin role and those admins will be able to

Better auth client and viem are the recommended libraries to use for authentication and signing using SIWE.

> ⚠️ **Note:**
> If you need to perform an encrypted KYC operation, please ask the exa team for `kyc` permissions.

## SIWE Authentication

Example code to authenticate using SIWE, it will create the user if doesn't exist.
Expand Down Expand Up @@ -219,3 +222,107 @@ authClient.siwe
});

```

## How to create the encrypted KYC payload with SIWE statement

<!-- cspell:ignore oaep pkcs cipheriv -->
```typescript
import crypto from "node:crypto";
import { getAddress, sha256 } from "viem";
import { mnemonicToAccount } from "viem/accounts";
import { optimismSepolia } from "viem/chains";
import { createSiweMessage, generateSiweNonce } from "viem/siwe";

const chainId = optimismSepolia.id;

const owner = mnemonicToAccount("test test test test test test test test test test test siwe");

function encrypt(payload: string) {
const aesKey = crypto.randomBytes(32);
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv("aes-256-gcm", aesKey, iv);

const ciphertext = Buffer.concat([cipher.update(payload, "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();

const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyZixoAuo015iMt+JND0y
usAvU2iJhtKRM+7uAxd8iXq7Z/3kXlGmoOJAiSNfpLnBAG0SCWslNCBzxf9+2p5t
HGbQUkZGkfrYvpAzmXKsoCrhWkk1HKk9f7hMHsyRlOmXbFmIgQHggEzEArjhkoXD
pl2iMP1ykCY0YAS+ni747DqcDOuFqLrNA138AxLNZdFsySHbxn8fzcfd3X0J/m/T
2dZuy6ChfDZhGZxSJMjJcintFyXKv7RkwrYdtXuqD3IQYakY3u6R1vfcKVZl0yGY
S2kN/NOykbyVL4lgtUzf0IfkwpCHWOrrpQA4yKk3kQRAenP7rOZThdiNNzz4U2BE
2wIDAQAB
-----END PUBLIC KEY-----`;

const key = crypto.publicEncrypt(
{
key: publicKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: "sha256",
},
aesKey,
);

return {
key: key.toString("base64"),
iv: iv.toString("base64"),
ciphertext: ciphertext.toString("base64"),
tag: tag.toString("base64"),
hash: sha256(ciphertext),
};
}

const data = {
email: "john.doe@example.com",
lastName: "Doe",
firstName: "John",
nationalId: "123456789",
birthDate: "1990-05-15",
countryOfIssue: "US",
phoneCountryCode: "1",
phoneNumber: "5551234567",
address: {
line1: "123 Main Street",
line2: "Apt 4B",
city: "New York",
region: "NY",
postalCode: "10001",
countryCode: "US",
},
ipAddress: "192.168.1.100",
occupation: "11-1011",
annualSalary: "75000",
accountPurpose: "Personal Banking",
expectedMonthlyVolume: "5000",
isTermsOfServiceAccepted: true,
};
const encryptedPayload = encrypt(JSON.stringify(data));
const exaAccountUserAddress = "0xa7d5e73027844145A538F4bfD7b8d9b41d8B89d3";
const statement = `I apply for KYC approval on behalf of address ${getAddress(exaAccountUserAddress)} with payload hash ${encryptedPayload.hash}`;
const message = createSiweMessage({
statement,
resources: ["https://exactly.github.io/exa"],
nonce: generateSiweNonce(),
uri: `https://sandbox.exactly.app`,
address: owner.address,
chainId,
scheme: "https",
version: "1",
domain: "sandbox.exactly.app",
});
owner.signMessage({ message })
.then((signature) => {
const verify = {
message,
signature,
walletAddress: owner.address,
chainId,
};
const { hash, ...payload } = encryptedPayload;
console.log("application payload", { ...payload, verify });
})
.catch((error: unknown) => {
console.error("error", error);
});
Comment thread
nfmelendez marked this conversation as resolved.
```
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 21 additions & 2 deletions server/api/card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,17 @@ import { Address } from "@exactly/common/validation";
import database, { cards, credentials } from "../database";
import auth from "../middleware/auth";
import { sendPushNotification } from "../utils/onesignal";
import { autoCredit, createCard, getCard, getPIN, getSecrets, getUser, setPIN, updateCard } from "../utils/panda";
import {
autoCredit,
createCard,
getApplicationStatus,
getCard,
getPIN,
getSecrets,
getUser,
setPIN,
updateCard,
} from "../utils/panda";
Comment thread
nfmelendez marked this conversation as resolved.
import { addCapita, deriveAssociateId } from "../utils/pax";
import { getAccount } from "../utils/persona";
import { customer } from "../utils/sardine";
Expand Down Expand Up @@ -292,7 +302,12 @@ function decrypt(base64Secret: string, base64Iv: string, secretKey: string): str
403: {
description: "Forbidden",
content: {
"application/json": { schema: resolver(object({ code: literal("no panda") }), { errorMode: "ignore" }) },
"application/json": {
schema: resolver(
union([object({ code: literal("no panda") }), object({ code: literal("kyc not approved") })]),
{ errorMode: "ignore" },
),
},
},
},
},
Expand All @@ -317,6 +332,10 @@ function decrypt(base64Secret: string, base64Iv: string, secretKey: string): str
setUser({ id: account });

if (!credential.pandaId) return c.json({ code: "no panda" }, 403);
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,
Expand Down
Loading
Loading