Skip to content

Commit

Permalink
Add idOS verify step (#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
luistorres authored May 20, 2024
1 parent d109f0b commit 94af5ec
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 26 deletions.
82 changes: 66 additions & 16 deletions packages/web-app/app/_server/idos/idos-grantee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { ethers } from 'ethers';
import nacl from 'tweetnacl';
import { decodeBase58, toBeHex } from 'ethers';
import { EthSigner } from '@kwilteam/kwil-js/dist/core/builders';
import { idOS } from '@idos-network/idos-sdk';

const PLAYGROUND_FRACTAL_ISSUER = 'https://vc-issuers.next.fractal.id/idos';

export function implicitAddressFromPublicKey(publicKey: string) {
const key_without_prefix = publicKey.replace(/^ed25519:/, '');
Expand Down Expand Up @@ -151,15 +154,50 @@ export class idOSGrantee {
}

async getSharedCredentialContentDecrypted(dataId: string): Promise<string> {
const credentialCopy = await this.fetchSharedCredentialFromIdos<{
content: string;
encryption_public_key: string;
}>(dataId);

return await this.noncedBox.decrypt(
credentialCopy.content,
credentialCopy.encryption_public_key,
);
try {
const credentialCopy = await this.fetchSharedCredentialFromIdos<{
content: string;
encryption_public_key: string;
}>(dataId);

return await this.noncedBox.decrypt(
credentialCopy.content,
credentialCopy.encryption_public_key,
);
} catch (error) {
console.log(
'Error fetching or decrypting shared credential from idos for dataId:',
dataId,
);
throw error;
}
}

async isValidCredential(credential: any): Promise<boolean> {
try {
// Ignore for now due to minification issues from idOS SDK side
// let result: any;

// if (process.env.NEXT_PUBLIC_ENABLE_TESTNETS === 'true') {
// result = await idOS.verifiableCredentials.verify(credential, {
// allowedIssuers: [PLAYGROUND_FRACTAL_ISSUER],
// });
// } else {
// result = await idOS.verifiableCredentials.verify(credential);
// }

// if (result !== true) {
// throw new Error('Invalid credential', result);
// }

return true;
} catch (error) {
console.log('Error verifying credential');
console.log(error);
return new Promise((resolve, _reject) => {
resolve(false);
});
}
}

async fetchUserCountriesFromSharedPlusCredential(
Expand All @@ -169,17 +207,29 @@ export class idOSGrantee {
await this.getSharedCredentialContentDecrypted(dataId);
const credential = JSON.parse(credentialString);

if (credential?.level !== 'plus')
const isValid = await this.isValidCredential(credential);
const isApproved = credential?.status === 'approved';

if (credential?.level && credential.level !== 'plus') {
console.log('Credential is not plus level, for dataId:', dataId);
}

if (!isApproved) {
console.log('Credential is not approved, for dataId:', dataId);
}

if (credential?.level === 'plus' && isValid && isApproved) {
return {
residentialCountry: undefined,
idDocumentCountry: undefined,
residentialCountry:
credential?.credentialSubject?.residential_address_country,
idDocumentCountry:
credential?.credentialSubject?.identification_document_country,
};
}

return {
residentialCountry:
credential?.credentialSubject?.residential_address_country,
idDocumentCountry:
credential?.credentialSubject?.identification_document_country,
residentialCountry: undefined,
idDocumentCountry: undefined,
};
}

Expand Down
82 changes: 72 additions & 10 deletions packages/web-app/app/_server/idos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,39 @@ const userFilter = async (grantee: idOSGrantee, userAddress: string) => {
});
const grants = grantsSchema.parse(grantsResult);

if (!grants.length) return null;
if (!grants.length) {
console.log('No grants found for user', userAddress);
return null;
}

const { residentialCountry, idDocumentCountry } =
await grantee.fetchUserCountriesFromSharedPlusCredential(
grants[0].dataId,
);

if (!residentialCountry || !idDocumentCountry) return null;
if (!residentialCountry || !idDocumentCountry) {
console.log(
'No residential or idDocument country found for user',
userAddress,
);
return null;
}

const isBlockedCountry = blockedCountries.some(
(blockedCountry) =>
blockedCountry === residentialCountry ||
blockedCountry === idDocumentCountry,
);

if (isBlockedCountry) return null;
//expected scenario
if (isBlockedCountry) {
console.log('Blocked country found for user', userAddress);
return null;
}

return userAddress;
} catch (error) {
console.log(error);
if (error instanceof Error) {
console.error(error.message);
}
Expand All @@ -49,10 +63,54 @@ const userFilter = async (grantee: idOSGrantee, userAddress: string) => {
return null;
};

const filterApplicants = async (addresses: string[], grantee: idOSGrantee) => {
const allowed = new Set<string>();
const notAllowed = new Set<string>();
const batches = Math.ceil(addresses.length / 10);

// run in batches of 10
for (let i = 0; i < batches; i++) {
console.log('==>BATCH', i);

let batch = addresses.slice(i * 10, (i + 1) * 10);
let promises = batch.map(
async (address): Promise<string | null> =>
new Promise((resolve) => resolve(userFilter(grantee, address))),
);

let results = await Promise.all(promises);

results.forEach((result, index) => {
if (result !== null) {
allowed.add(result);
} else {
notAllowed.add(batch[index]);
}
});
}

return { allowed, notAllowed };
};

const retryFailed = async (
allowed: Set<string>,
notAllowed: Set<string>,
grantee: idOSGrantee,
) => {
for (const address of notAllowed) {
const result = await userFilter(grantee, address);
if (result !== null) {
allowed.add(result);
notAllowed.delete(address);
}
}
};

export const getAllowedProjectApplicants = async (projectAddress: string) => {
try {
const applicantsResult = await getProjectApplicants(projectAddress);
const addresses = addressesListSchema.parse(applicantsResult);

const grantee = await idOSGrantee.init({
granteeSigner: evmGrantee,
encryptionSecret: ENCRYPTION_KEY_PAIR.secretKey,
Expand All @@ -61,16 +119,20 @@ export const getAllowedProjectApplicants = async (projectAddress: string) => {
? undefined
: 'x44250024a9bf9599ad7c3fcdb220d2100357dbf263014485174a1ae3',
});
const allowed = [];

for (const address of addresses) {
const result = await userFilter(grantee, address);
if (result !== null) {
allowed.push(result);
}
// first run to populate the lists
let { allowed, notAllowed } = await filterApplicants(addresses, grantee);
// retry 2 times failed addresses
let retry = 2;
while (retry > 0 && notAllowed.size > 0) {
retryFailed(allowed, notAllowed, grantee);
retry--;
}

return allowed as string[];
console.log('==>', 'NOT ALLOWED:');
console.log(notAllowed);

return Array.from(allowed);
} catch (error) {
console.error(error);

Expand Down

0 comments on commit 94af5ec

Please sign in to comment.