From aede083b1686eefadd6c927f1eddfdfdca750b08 Mon Sep 17 00:00:00 2001 From: washluis-alencar Date: Thu, 3 Jul 2025 19:39:55 -0300 Subject: [PATCH 1/2] feat: adjust return when encrypting multiple tokens --- demo/Collect.tsx | 2 +- src/model/EncryptTokenData.ts | 4 +- src/services/tokenEncryption.ts | 84 ++++++++++++++------------- tests/modules/tokenEncryption.test.ts | 30 +++++----- 4 files changed, 63 insertions(+), 57 deletions(-) diff --git a/demo/Collect.tsx b/demo/Collect.tsx index 478ac6f..4575084 100644 --- a/demo/Collect.tsx +++ b/demo/Collect.tsx @@ -33,7 +33,7 @@ export const Collect = () => { const [tokenizedData, setTokenizedData] = useState< TokenizeData | undefined >(); - const [encryptedToken, setEncryptedToken] = useState(); + const [encryptedToken, setEncryptedToken] = useState(); const [tokenId, setTokenId] = useState(''); diff --git a/src/model/EncryptTokenData.ts b/src/model/EncryptTokenData.ts index 79b6809..53eec24 100644 --- a/src/model/EncryptTokenData.ts +++ b/src/model/EncryptTokenData.ts @@ -39,7 +39,7 @@ type EncryptToken = { * Result of token encryption operation. * Contains the encrypted token string and its original type. */ -type EncryptedToken = { +type EncryptedSingleToken = { /** Base64-encoded encrypted token data */ encrypted: string; @@ -47,4 +47,6 @@ type EncryptedToken = { type: TokenBase['type']; }; +type EncryptedToken = EncryptedSingleToken | Record; + export { EncryptToken, EncryptedToken, TokenData, TokenDataWithRef }; diff --git a/src/services/tokenEncryption.ts b/src/services/tokenEncryption.ts index 61ae9dc..83d831b 100644 --- a/src/services/tokenEncryption.ts +++ b/src/services/tokenEncryption.ts @@ -7,10 +7,10 @@ import { EncryptedToken, EncryptToken, TokenData, - TokenDataWithRef, } from '../model/EncryptTokenData'; import { replaceElementRefs } from '../utils/dataManipulationUtils'; import { JWE } from '../utils/jwe'; +import { isNilOrEmpty } from '../utils/shared'; export class EncryptValidationError extends Error { public constructor(message: string) { @@ -63,19 +63,25 @@ const createJWE = async ( } }; +const isSingleToken = (tokenRequests: EncryptToken['tokenRequests']) => 'type' in tokenRequests; + /** - * Normalizes token requests to a consistent array format - * @param tokenRequests - Token requests in either single token or object format - * @returns Array of normalized token objects + * Validates if token requests contain valid tokens to encrypt. Tokens must have a type and data property. + * @param tokenRequests - The token requests to validate + * @throws {EncryptValidationError} When token requests are invalid */ -const normalizeTokenRequests = ( - tokenRequests: EncryptToken['tokenRequests'] -): Array => { - if ('type' in tokenRequests) { - return [tokenRequests as TokenDataWithRef]; +const validateTokenRequests = (tokenRequests: EncryptToken['tokenRequests']) => { + if (isNilOrEmpty(tokenRequests)) { + throw new EncryptValidationError('No valid tokens found to encrypt'); + } + + if (isSingleToken(tokenRequests) && !tokenRequests.data) { + throw new EncryptValidationError('No valid tokens found to encrypt'); } - return Object.values(tokenRequests); + if (!isSingleToken && Object.entries(tokenRequests).some(([_, value]) => !value.data || !value.type)) { + throw new EncryptValidationError('No valid tokens found to encrypt'); + } }; /** @@ -86,7 +92,7 @@ const normalizeTokenRequests = ( */ export const encryptToken = async ( payload: EncryptToken -): Promise => { +): Promise => { if (!payload) { throw new EncryptValidationError('Encryption payload is required'); } @@ -99,40 +105,38 @@ export const encryptToken = async ( throw new EncryptValidationError('Key ID is required'); } - if (!payload.tokenRequests) { - throw new EncryptValidationError('Token requests are required'); - } - try { - const tokensWithRef: TokenDataWithRef[] = normalizeTokenRequests( - payload.tokenRequests - ); - if (!tokensWithRef.length) { - throw new EncryptValidationError('No valid tokens found to encrypt'); + validateTokenRequests(payload.tokenRequests); + + if (isSingleToken(payload.tokenRequests)) { + const token = replaceElementRefs(payload.tokenRequests) + const tokenPayload = JSON.stringify(token.data); + const encrypted = await createJWE(tokenPayload, payload.publicKeyPEM, payload.keyId); + return { encrypted, type: token.type }; } - const tokens: TokenData[] = tokensWithRef.map((token) => - replaceElementRefs(token) + return Object.fromEntries( + await Promise.all( + Object.entries(payload.tokenRequests).map(async ([key, tokenData]) => { + const token = replaceElementRefs(tokenData) + + const tokenPayload = JSON.stringify(token.data); + const encrypted = await createJWE( + tokenPayload, + payload.publicKeyPEM, + payload.keyId + ); + + return [ + key, + { + encrypted, + type: token.type + } + ]; + })) ); - return await Promise.all( - tokens.map(async (token) => { - if (!token.type) { - throw new EncryptValidationError('Token type is required'); - } - - const tokenPayload = JSON.stringify(token.data); - const encrypted = await createJWE( - tokenPayload, - payload.publicKeyPEM, - payload.keyId - ); - return { - encrypted, - type: token.type, - }; - }) - ); } catch (error) { if (error instanceof EncryptValidationError) { throw error; diff --git a/tests/modules/tokenEncryption.test.ts b/tests/modules/tokenEncryption.test.ts index eef7c86..5ad07af 100644 --- a/tests/modules/tokenEncryption.test.ts +++ b/tests/modules/tokenEncryption.test.ts @@ -43,10 +43,10 @@ describe('tokens - Encrypt', () => { const result = await Tokens({} as BasisTheoryType).encrypt(encryptRequest); expect(result).toBeDefined(); - expect(result).toHaveLength(1); - expect(result![0].encrypted).toBeDefined(); - expect(result![0].encrypted.split('.').length).toBe(5); - expect(result![0].type).toBe('card'); + const singleResult = result as { encrypted: string; type: string }; + expect(singleResult.encrypted).toBeDefined(); + expect(singleResult.encrypted.split('.').length).toBe(5); + expect(singleResult.type).toBe('card'); }); test('calls bt encrypt with multiple token requests', async () => { @@ -75,13 +75,13 @@ describe('tokens - Encrypt', () => { const result = await Tokens({} as BasisTheoryType).encrypt(encryptRequest); expect(result).toBeDefined(); - expect(result).toHaveLength(2); - expect(result![0].encrypted).toBeDefined(); - expect(result![0].encrypted.split('.').length).toBe(5); - expect(result![0].type).toBe('card'); - expect(result![1].encrypted).toBeDefined(); - expect(result![1].encrypted.split('.').length).toBe(5); - expect(result![1].type).toBe('bank'); + const multipleResult = result as Record; + expect(multipleResult.card.encrypted).toBeDefined(); + expect(multipleResult.card.encrypted.split('.').length).toBe(5); + expect(multipleResult.card.type).toBe('card'); + expect(multipleResult.bank.encrypted).toBeDefined(); + expect(multipleResult.bank.encrypted.split('.').length).toBe(5); + expect(multipleResult.bank.type).toBe('bank'); }); test('handles nested objects in encrypt requests', async () => { @@ -107,10 +107,10 @@ describe('tokens - Encrypt', () => { const result = await Tokens({} as BasisTheoryType).encrypt(encryptRequest); expect(result).toBeDefined(); - expect(result).toHaveLength(1); - expect(result![0].encrypted).toBeDefined(); - expect(result![0].encrypted.split('.').length).toBe(5); - expect(result![0].type).toBe('card'); + const singleResult = result as { encrypted: string; type: string }; + expect(singleResult.encrypted).toBeDefined(); + expect(singleResult.encrypted.split('.').length).toBe(5); + expect(singleResult.type).toBe('card'); }); test('throws error when publicKeyPEM is null', async () => { From 6e4ebb634201f7b6b7f8dc096829bd53db43b33a Mon Sep 17 00:00:00 2001 From: washluis-alencar Date: Thu, 3 Jul 2025 19:49:48 -0300 Subject: [PATCH 2/2] feat: get lint fixed --- src/services/tokenEncryption.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/services/tokenEncryption.ts b/src/services/tokenEncryption.ts index 83d831b..236a351 100644 --- a/src/services/tokenEncryption.ts +++ b/src/services/tokenEncryption.ts @@ -127,9 +127,8 @@ export const encryptToken = async ( payload.keyId ); - return [ - key, - { + return [key, + { encrypted, type: token.type }