From 5ad17a2f07f420c50d7d5b4f62d9285e07fa8a09 Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Wed, 23 Oct 2024 11:30:21 +0200 Subject: [PATCH] Progress --- packages/wallet/backend/src/app.ts | 2 +- packages/wallet/backend/src/card/service.ts | 9 +- .../wallet/backend/src/card/validation.ts | 2 +- packages/wallet/backend/src/gatehub/client.ts | 121 ++++++++----- .../components/dialogs/UserCardPINDialog.tsx | 161 +++++++++--------- .../dialogs/UserCardSpendingLimitDialog.tsx | 6 +- .../components/userCards/UserCardActions.tsx | 107 ++++++++++-- .../components/userCards/UserCardSettings.tsx | 50 +++++- packages/wallet/frontend/src/lib/api/card.ts | 73 ++++---- 9 files changed, 354 insertions(+), 177 deletions(-) diff --git a/packages/wallet/backend/src/app.ts b/packages/wallet/backend/src/app.ts index 57a6151c8..164ba76e1 100644 --- a/packages/wallet/backend/src/app.ts +++ b/packages/wallet/backend/src/app.ts @@ -354,7 +354,7 @@ export class App { cardController.getPin ) router.get( - '/cards/:cardId/change-pin', + '/cards/:cardId/change-pin-token', this.ensureGateHubProductionEnv, isAuth, cardController.getTokenForPinChange diff --git a/packages/wallet/backend/src/card/service.ts b/packages/wallet/backend/src/card/service.ts index 471a27e73..5884ef8ba 100644 --- a/packages/wallet/backend/src/card/service.ts +++ b/packages/wallet/backend/src/card/service.ts @@ -81,14 +81,19 @@ export class CardService { ): Promise { const { cardId } = requestBody await this.ensureAccountExists(userId, cardId) + const gateHubUserId = await this.ensureGatehubUserUuid(userId) - return this.gateHubClient.getPin(requestBody) + return this.gateHubClient.getPin(gateHubUserId, requestBody) } async getTokenForPinChange(userId: string, cardId: string): Promise { await this.ensureAccountExists(userId, cardId) - const token = await this.gateHubClient.getTokenForPinChange(cardId) + const gateHubUserId = await this.ensureGatehubUserUuid(userId) + const token = await this.gateHubClient.getTokenForPinChange( + gateHubUserId, + cardId + ) return token } diff --git a/packages/wallet/backend/src/card/validation.ts b/packages/wallet/backend/src/card/validation.ts index f1314d9f9..3fd7fda8e 100644 --- a/packages/wallet/backend/src/card/validation.ts +++ b/packages/wallet/backend/src/card/validation.ts @@ -56,7 +56,7 @@ export const lockCardSchema = z.object({ ]) }), body: z.object({ - note: z.string().nullable() + note: z.string() }) }) diff --git a/packages/wallet/backend/src/gatehub/client.ts b/packages/wallet/backend/src/gatehub/client.ts index 75a445476..194eed0f3 100644 --- a/packages/wallet/backend/src/gatehub/client.ts +++ b/packages/wallet/backend/src/gatehub/client.ts @@ -481,24 +481,27 @@ export class GateHubClient { throw new Error('Failed to obtain token for card data retrieval') } - // const resp = await fetch( - // 'https://lab.dinitcs.com/uat/Stargate.Paywiser.Server/api/v3/ClientDevice/cardData', - // { method: 'GET', headers: { Authorization: `Bearer ${token}` } } - // ) - const cardDetailsUrl = `${this.apiUrl}/cards/v1/proxy/clientDevice/cardData` - const cardDetailsResponse = await this.request( - 'GET', - cardDetailsUrl, - undefined, - { - managedUserUuid, - token - } + const res = await fetch( + 'https://lab.dinitcs.com/uat/Stargate.Paywiser.Server/api/v3/ClientDevice/cardData', + { method: 'GET', headers: { Authorization: `Bearer ${token}` } } ) - return cardDetailsResponse - // const res = await resp.json() - // - // return res + // const cardDetailsUrl = `${this.apiUrl}/cards/v1/proxy/clientDevice/cardData` + // const cardDetailsResponse = await this.request( + // 'GET', + // cardDetailsUrl, + // undefined, + // { + // managedUserUuid, + // token + // } + // ) + // return cardDetailsResponse + if (!res.ok) { + throw new Error('Could not fetch card details') + } + const cardData = (await res.json()) as ICardDetailsResponse + + return cardData } async getCardTransactions( @@ -549,15 +552,16 @@ export class GateHubClient { } async getPin( + managedUserUuid: string, requestBody: ICardDetailsRequest ): Promise { - const url = `${this.apiUrl}/token/pin` - + const url = `${this.apiUrl}/cards/v1/token/pin` const response = await this.request( 'POST', url, JSON.stringify(requestBody), { + managedUserUuid, cardAppId: this.env.GATEHUB_CARD_APP_ID } ) @@ -567,29 +571,39 @@ export class GateHubClient { throw new Error('Failed to obtain token for card pin retrieval') } - // TODO change this to direct call to card managing entity - // Will get this from the GateHub proxy for now - const cardPinUrl = `${this.apiUrl}/v1/proxy/clientDevice/pin` - const cardPinResponse = await this.request( - 'GET', - cardPinUrl, - undefined, - { - token - } + const resp = await fetch( + 'https://lab.dinitcs.com/uat/Stargate.Paywiser.Server/api/v3/ClientDevice/pin', + { method: 'GET', headers: { Authorization: `Bearer ${token}` } } ) - return cardPinResponse + const res = await resp.json() + + return res + // const cardPinUrl = `${this.apiUrl}/v1/proxy/clientDevice/pin` + // const cardPinResponse = await this.request( + // 'GET', + // cardPinUrl, + // undefined, + // { + // token + // } + // ) + + // return cardPinResponse } - async getTokenForPinChange(cardId: string): Promise { - const url = `${this.apiUrl}/token/pin-change` + async getTokenForPinChange( + managedUserUuid: string, + cardId: string + ): Promise { + const url = `${this.apiUrl}/cards/v1/token/pin-change` const response = await this.request( 'POST', url, JSON.stringify({ cardId: cardId }), { + managedUserUuid, cardAppId: this.env.GATEHUB_CARD_APP_ID } ) @@ -603,17 +617,44 @@ export class GateHubClient { } async changePin(token: string, cypher: string): Promise { - // TODO change this to direct call to card managing entity - // Will get this from the GateHub proxy for now - const cardPinUrl = `${this.apiUrl}/cards/v1/proxy/clientDevice/pin` - await this.request( - 'POST', - cardPinUrl, - JSON.stringify({ cypher: cypher }), + const response = await fetch( + 'https://lab.dinitcs.com/uat/Stargate.Paywiser.Server/api/v3/ClientDevice/pin', { - token + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ cypher }) } ) + + if (!response.ok) { + let info = '' + if (response.headers.get('content-type') === 'application/json') { + info = await response.json() + } else { + info = await response.text() + } + this.logger.error( + `ClientDevice/pin call failed with status ${response.status}: ${info}` + ) + throw new Error('Could not change the card pin. Please try again') + } + + this.logger.info('Successfully changed card pin.') + + // TODO: Move to proxy when it's fixed + // const cardPinUrl = `${this.apiUrl}/cards/v1/proxy/clientDevice/pin` + // await this.request( + // 'POST', + // cardPinUrl, + // JSON.stringify({ cypher: cypher }), + // { + // managedUserUuid, + // token + // } + // ) } async lockCard( diff --git a/packages/wallet/frontend/src/components/dialogs/UserCardPINDialog.tsx b/packages/wallet/frontend/src/components/dialogs/UserCardPINDialog.tsx index f89ea3e30..98afb9bda 100644 --- a/packages/wallet/frontend/src/components/dialogs/UserCardPINDialog.tsx +++ b/packages/wallet/frontend/src/components/dialogs/UserCardPINDialog.tsx @@ -5,23 +5,19 @@ import { Transition, TransitionChild } from '@headlessui/react' -import { Fragment, useState } from 'react' +import { Fragment } from 'react' import type { DialogProps } from '@/lib/types/dialog' -import { cardServiceMock, changePinSchema, IUserCard } from '@/lib/api/card' import { UserCardFront } from '@/components/userCards/UserCard' -import { Button } from '@/ui/Button' -import { useZodForm } from '@/lib/hooks/useZodForm' -import { Form } from '@/ui/forms/Form' -import { useRouter } from 'next/router' -import { getObjectKeys } from '@/utils/helpers' -import { Input } from '@/ui/forms/Input' +import { ICardResponse } from '@wallet/shared' type UserCardPINDialogProos = Pick & { - card: IUserCard + card: ICardResponse + pin: string } export const UserCardPINDialog = ({ card, + pin, onClose }: UserCardPINDialogProos) => { return ( @@ -52,21 +48,18 @@ export const UserCardPINDialog = ({ > - Card PIN + Here is your card PIN -
+
-

Physical Debit Card

-

- {card.number} -

+

{pin}

-
@@ -76,70 +69,70 @@ export const UserCardPINDialog = ({ ) } -const ChangePinForm = () => { - const [showForm, setShowForm] = useState(false) - const router = useRouter() - const form = useZodForm({ - schema: changePinSchema - }) - - if (!showForm) { - return ( - - ) - } - return ( -
{ - const response = await cardServiceMock.changePin(data) - - if (response.success) { - router.replace(router.asPath) - } else { - const { errors, message } = response - form.setError('root', { - message - }) - if (errors) { - getObjectKeys(errors).map((field) => - form.setError(field, { - message: errors[field] - }) - ) - } - } - }} - > - - - -
- ) -} +// const ChangePinForm = () => { +// const [showForm, setShowForm] = useState(false) +// const router = useRouter() +// const form = useZodForm({ +// schema: changePinSchema +// }) +// +// if (!showForm) { +// return ( +// +// ) +// } +// return ( +//
{ +// const response = await cardServiceMock.changePin(data) +// +// if (response.success) { +// router.replace(router.asPath) +// } else { +// const { errors, message } = response +// form.setError('root', { +// message +// }) +// if (errors) { +// getObjectKeys(errors).map((field) => +// form.setError(field, { +// message: errors[field] +// }) +// ) +// } +// } +// }} +// > +// +// +// +//
+// ) +// } diff --git a/packages/wallet/frontend/src/components/dialogs/UserCardSpendingLimitDialog.tsx b/packages/wallet/frontend/src/components/dialogs/UserCardSpendingLimitDialog.tsx index 38b040948..faaca1d53 100644 --- a/packages/wallet/frontend/src/components/dialogs/UserCardSpendingLimitDialog.tsx +++ b/packages/wallet/frontend/src/components/dialogs/UserCardSpendingLimitDialog.tsx @@ -8,7 +8,7 @@ import { import { Fragment } from 'react' import type { DialogProps } from '@/lib/types/dialog' import { - cardServiceMock, + cardService, dailySpendingLimitSchema, monthlySpendingLimitSchema } from '@/lib/api/card' @@ -78,7 +78,7 @@ const DailySpendingLimitForm = () => {
{ - const response = await cardServiceMock.setDailySpendingLimit(data) + const response = await cardService.setDailySpendingLimit(data) if (response.success) { router.replace(router.asPath) @@ -134,7 +134,7 @@ const MonthlySpendingLimitForm = () => { { - const response = await cardServiceMock.setMonthlySpendingLimit(data) + const response = await cardService.setMonthlySpendingLimit(data) if (response.success) { router.replace(router.asPath) diff --git a/packages/wallet/frontend/src/components/userCards/UserCardActions.tsx b/packages/wallet/frontend/src/components/userCards/UserCardActions.tsx index c0b5feca7..3d98c1c02 100644 --- a/packages/wallet/frontend/src/components/userCards/UserCardActions.tsx +++ b/packages/wallet/frontend/src/components/userCards/UserCardActions.tsx @@ -6,10 +6,11 @@ import { useCardContext, useKeysContext } from './UserCardContext' -import { cardService, cardServiceMock } from '@/lib/api/card' +import { cardService } from '@/lib/api/card' import { useRouter } from 'next/router' import { useToast } from '@/lib/hooks/useToast' import NodeRSA from 'node-rsa' +import crypto from 'crypto' export const FrozenCardActions = () => { const router = useRouter() @@ -56,7 +57,7 @@ export const FrozenCardActions = () => { aria-label="terminate card" className="group" onClick={async () => { - const response = await cardServiceMock.terminate() + const response = await cardService.terminate() if (!response.success) { throw new Error('CHANGE ME') @@ -79,13 +80,7 @@ export const FrozenCardActions = () => { const DefaultCardActions = () => { const router = useRouter() - const { - card, - showDetails, - setOptimisticFreeze, - setShowDetails, - setCardData - } = useCardContext() + const { card, showDetails, setShowDetails, setCardData } = useCardContext() const { keys } = useKeysContext() const { toast } = useToast() @@ -97,7 +92,6 @@ const DefaultCardActions = () => { aria-label="freeze" className="group" onClick={async () => { - setOptimisticFreeze(true) const response = await cardService.freeze(card.id) if (!response.success) { @@ -196,13 +190,104 @@ const DefaultCardActions = () => { export const UserCardActions = () => { const { card } = useCardContext() + const { keys } = useKeysContext() const isLocked = isLockedCard(card) return (
{isLocked ? : } - + +
) } +function parseJwt(token: string) { + const base64Url = token.split('.')[1] + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') + const jsonPayload = decodeURIComponent( + atob(base64) + .split('') + .map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + }) + .join('') + ) + + return JSON.parse(jsonPayload) +} + +// function importRsaKey(key: string) { +// const binaryDerString = atob(key) +// const binaryDer = str2ab(binaryDerString) +// +// return window.crypto.subtle.importKey( +// 'spki', +// binaryDer, +// { +// name: 'RSA-OAEP', +// hash: 'SHA-256' +// }, +// true, +// ['encrypt'] +// ) +// } +// +// function str2ab(str: string) { +// const buf = new ArrayBuffer(str.length) +// const bufView = new Uint8Array(buf) +// for (let i = 0, strLen = str.length; i < strLen; i++) { +// bufView[i] = str.charCodeAt(i) +// } +// return buf +// } diff --git a/packages/wallet/frontend/src/components/userCards/UserCardSettings.tsx b/packages/wallet/frontend/src/components/userCards/UserCardSettings.tsx index 10349a75f..8a1ce2a49 100644 --- a/packages/wallet/frontend/src/components/userCards/UserCardSettings.tsx +++ b/packages/wallet/frontend/src/components/userCards/UserCardSettings.tsx @@ -3,18 +3,21 @@ import { Limit } from '../icons/Limit' import { CardKey } from '../icons/Key' import { UserCardSpendingLimitDialog } from '@/components/dialogs/UserCardSpendingLimitDialog' import { UserCardPINDialog } from '@/components/dialogs/UserCardPINDialog' -import { useCardContext } from './UserCardContext' +import { useCardContext, useKeysContext } from './UserCardContext' +import { cardService } from '@/lib/api/card' +import { useToast } from '@/lib/hooks/useToast' +import NodeRSA from 'node-rsa' export const UserCardSettings = () => { return (
    -
) } -const SpendingLimit = () => { +// Unused at the moment +export const SpendingLimit = () => { const [openDialog, closeDialog] = useDialog() return ( @@ -45,13 +48,50 @@ const SpendingLimit = () => { const PinSettings = () => { const { card } = useCardContext() + const { keys } = useKeysContext() + const { toast } = useToast() const [openDialog, closeDialog] = useDialog() + if (!keys) return null + return (