diff options
Diffstat (limited to 'packages/taler-wallet-core')
-rw-r--r-- | packages/taler-wallet-core/src/crypto/cryptoImplementation.ts | 16 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 2 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/deposits.ts | 109 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/pay-merchant.ts | 4 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/pay-peer-push-credit.ts | 3 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/withdraw.ts | 8 |
6 files changed, 117 insertions, 25 deletions
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts index 69081d234..ce6193b83 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts @@ -170,7 +170,7 @@ export interface TalerCryptoInterface { req: ContractTermsValidationRequest, ): Promise<ValidationResult>; - createEddsaKeypair(req: {}): Promise<EddsaKeypair>; + createEddsaKeypair(req: {}): Promise<EddsaKeypairStrings>; eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaGetPublicResponse>; @@ -333,10 +333,12 @@ export const nullCrypto: TalerCryptoInterface = { ): Promise<ValidationResult> { throw new Error("Function not implemented."); }, - createEddsaKeypair: function (req: unknown): Promise<EddsaKeypair> { + createEddsaKeypair: function (req: unknown): Promise<EddsaKeypairStrings> { throw new Error("Function not implemented."); }, - eddsaGetPublic: function (req: EddsaGetPublicRequest): Promise<EddsaKeypair> { + eddsaGetPublic: function ( + req: EddsaGetPublicRequest, + ): Promise<EddsaKeypairStrings> { throw new Error("Function not implemented."); }, unblindDenominationSignature: function ( @@ -600,7 +602,7 @@ export interface WireAccountValidationRequest { creditRestrictions?: any[]; } -export interface EddsaKeypair { +export interface EddsaKeypairStrings { priv: string; pub: string; } @@ -1081,7 +1083,9 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { /** * Create a new EdDSA key pair. */ - async createEddsaKeypair(tci: TalerCryptoInterfaceR): Promise<EddsaKeypair> { + async createEddsaKeypair( + tci: TalerCryptoInterfaceR, + ): Promise<EddsaKeypairStrings> { const eddsaPriv = encodeCrock(getRandomBytes(32)); const eddsaPubRes = await tci.eddsaGetPublic(tci, { priv: eddsaPriv, @@ -1095,7 +1099,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { async eddsaGetPublic( tci: TalerCryptoInterfaceR, req: EddsaGetPublicRequest, - ): Promise<EddsaKeypair> { + ): Promise<EddsaKeypairStrings> { return { priv: req.priv, pub: encodeCrock(eddsaGetPublic(decodeCrock(req.priv))), diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 21ec3f033..99b69135a 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1831,7 +1831,7 @@ export interface DepositGroupRecord { } export interface DepositKycInfo { - accessToken: string; + accessToken?: string; paytoHash: string; exchangeBaseUrl: string; } diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts index 34b19df80..da69530f7 100644 --- a/packages/taler-wallet-core/src/deposits.ts +++ b/packages/taler-wallet-core/src/deposits.ts @@ -61,6 +61,7 @@ import { canonicalJson, checkDbInvariant, checkLogicInvariant, + codecForAccountKycStatus, codecForBatchDepositSuccess, codecForTackTransactionAccepted, codecForTackTransactionWired, @@ -72,7 +73,12 @@ import { parsePaytoUri, stringToBytes, } from "@gnu-taler/taler-util"; -import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; +import { + readResponseJsonOrThrow, + readSuccessResponseJsonOrThrow, + readTalerErrorResponse, + throwUnexpectedRequestError, +} from "@gnu-taler/taler-util/http"; import { selectPayCoins, selectPayCoinsInTx } from "./coinSelection.js"; import { PendingTaskType, @@ -94,6 +100,7 @@ import { RefreshOperationStatus, WalletDbAllStoresReadOnlyTransaction, WalletDbReadWriteTransaction, + timestampAbsoluteFromDb, timestampPreciseFromDb, timestampPreciseToDb, timestampProtocolFromDb, @@ -104,6 +111,7 @@ import { getExchangeWireFee, getScopeForAllExchanges, } from "./exchanges.js"; +import { EddsaKeypairStrings } from "./index.js"; import { extractContractData, generateDepositPermissions, @@ -217,6 +225,14 @@ export class DepositTransactionContext implements TransactionContext { depositGroupId: dg.depositGroupId, trackingState, deposited, + kycPaytoHash: dg.kycInfo?.paytoHash, + kycAccessToken: dg.kycInfo?.accessToken, + kycUrl: dg.kycInfo + ? new URL( + `kyc-spa/${dg.kycInfo.accessToken}`, + dg.kycInfo.exchangeBaseUrl, + ).href + : undefined, ...(ort?.lastError ? { error: ort.lastError } : {}), }; } @@ -778,6 +794,19 @@ async function processDepositGroupPendingKyc( throw Error("invalid DB state, in pending(kyc), but no kycInfo present"); } + const lastReserveKeypair = await getLastWithdrawalKeyPair( + wex, + kycInfo.exchangeBaseUrl, + ); + if (!lastReserveKeypair) { + // Need to do a KYC transfer + throw Error("not supported yet"); + } + const sigResp = await wex.cryptoApi.signWalletKycAuth({ + accountPriv: lastReserveKeypair.priv, + accountPub: lastReserveKeypair.pub, + }); + const url = new URL( `kyc-check/${kycInfo.paytoHash}`, kycInfo.exchangeBaseUrl, @@ -792,6 +821,9 @@ async function processDepositGroupPendingKyc( return await wex.http.fetch(url.href, { method: "GET", cancellationToken: wex.cancellationToken, + headers: { + ["Account-Owner-Signature"]: sigResp.sig, + }, }); }, ); @@ -832,6 +864,42 @@ async function processDepositGroupPendingKyc( } /** + * Finds the reserve key pair of the most recent withdrawal + * with the given exchange. + * Returns undefined if no such withdrawal exists. + */ +async function getLastWithdrawalKeyPair( + wex: WalletExecutionContext, + exchangeBaseUrl: string, +): Promise<EddsaKeypairStrings | undefined> { + let candidateTimestamp: AbsoluteTime | undefined = undefined; + let candidateRes: EddsaKeypairStrings | undefined = undefined; + await wex.db.runAllStoresReadOnlyTx({}, async (tx) => { + const withdrawalRecs = + await tx.withdrawalGroups.indexes.byExchangeBaseUrl.getAll( + exchangeBaseUrl, + ); + for (const rec of withdrawalRecs) { + if (!rec.timestampFinish) { + continue; + } + const currTimestamp = timestampAbsoluteFromDb(rec.timestampFinish); + if ( + candidateTimestamp == null || + AbsoluteTime.cmp(currTimestamp, candidateTimestamp) > 0 + ) { + candidateTimestamp = currTimestamp; + candidateRes = { + priv: rec.reservePriv, + pub: rec.reservePub, + }; + } + } + }); + return candidateRes; +} + +/** * Tracking information from the exchange indicated that * KYC is required. We need to check the KYC info * and transition the transaction to the KYC required state. @@ -846,17 +914,33 @@ async function transitionToKycRequired( const ctx = new DepositTransactionContext(wex, depositGroupId); + const lastReserveKeypair = await getLastWithdrawalKeyPair(wex, exchangeUrl); + if (!lastReserveKeypair) { + // Need to do a KYC transfer + throw Error("not supported yet"); + } + const sigResp = await wex.cryptoApi.signWalletKycAuth({ + accountPriv: lastReserveKeypair.priv, + accountPub: lastReserveKeypair.pub, + }); + const url = new URL(`kyc-check/${kycInfo.paytoHash}`, exchangeUrl); logger.info(`kyc url ${url.href}`); - const kycStatusReq = await wex.http.fetch(url.href, { + const kycStatusResp = await wex.http.fetch(url.href, { method: "GET", + headers: { + ["Account-Owner-Signature"]: sigResp.sig, + }, }); - if (kycStatusReq.status === HttpStatusCode.Ok) { + logger.trace(`response status of initial kyc-check: ${kycStatusResp.status}`); + if (kycStatusResp.status === HttpStatusCode.Ok) { logger.warn("kyc requested, but already fulfilled"); return TaskRunResult.backoff(); - } else if (kycStatusReq.status === HttpStatusCode.Accepted) { - const kycStatus = await kycStatusReq.json(); - logger.info(`kyc status: ${j2s(kycStatus)}`); + } else if (kycStatusResp.status === HttpStatusCode.Accepted) { + const statusResp = await readResponseJsonOrThrow( + kycStatusResp, + codecForAccountKycStatus(), + ); const transitionInfo = await wex.db.runReadWriteTx( { storeNames: ["depositGroups", "transactionsMeta"] }, async (tx) => { @@ -868,10 +952,11 @@ async function transitionToKycRequired( return undefined; } const oldTxState = computeDepositTransactionStatus(dg); + dg.operationStatus = DepositOperationStatus.PendingKyc; dg.kycInfo = { exchangeBaseUrl: exchangeUrl, paytoHash: kycInfo.paytoHash, - accessToken: null as any, // FIXME! + accessToken: statusResp.access_token, }; await tx.depositGroups.put(dg); await ctx.updateTransactionMeta(tx); @@ -880,9 +965,12 @@ async function transitionToKycRequired( }, ); notifyTransition(wex, ctx.transactionId, transitionInfo); - return TaskRunResult.finished(); + return TaskRunResult.progress(); } else { - throw Error(`unexpected response from kyc-check (${kycStatusReq.status})`); + throwUnexpectedRequestError( + kycStatusResp, + await readTalerErrorResponse(kycStatusResp), + ); } } @@ -932,6 +1020,7 @@ async function processDepositGroupPendingTrack( exchangeBaseUrl, ); + logger.trace(`track response: ${j2s(track)}`); if (track.type === "accepted") { if (!track.kyc_ok && track.requirement_row !== undefined) { const paytoHash = encodeCrock( @@ -940,7 +1029,7 @@ async function processDepositGroupPendingTrack( const kycInfo: DepositKycInfo = { paytoHash, exchangeBaseUrl: exchangeBaseUrl, - accessToken: null as any, // FIXME! + accessToken: undefined, }; return transitionToKycRequired( wex, diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts index 5be153367..12cfedc9e 100644 --- a/packages/taler-wallet-core/src/pay-merchant.ts +++ b/packages/taler-wallet-core/src/pay-merchant.ts @@ -123,7 +123,7 @@ import { TransactionContext, TransitionResultType, } from "./common.js"; -import { EddsaKeypair } from "./crypto/cryptoImplementation.js"; +import { EddsaKeypairStrings } from "./crypto/cryptoImplementation.js"; import { CoinRecord, DbCoinSelection, @@ -1196,7 +1196,7 @@ async function createOrReusePurchase( return oldProposal.proposalId; } - let noncePair: EddsaKeypair; + let noncePair: EddsaKeypairStrings; let shared = false; if (noncePriv) { shared = true; diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts index d18f8712d..fd6d40bcf 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -869,12 +869,11 @@ async function processPeerPushCreditKycRequired( logger.warn("kyc requested, but already fulfilled"); return TaskRunResult.finished(); } else if (kycStatusRes.status === HttpStatusCode.Accepted) { - const kycStatus = await kycStatusRes.json(); - logger.info(`kyc status: ${j2s(kycStatus)}`); const statusResp = await readResponseJsonOrThrow( kycStatusRes, codecForAccountKycStatus(), ); + logger.info(`kyc status: ${j2s(statusResp)}`); const { transitionInfo, result } = await wex.db.runReadWriteTx( { storeNames: ["peerPushCredit", "transactionsMeta"] }, async (tx) => { diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 0d47e08c7..1ed600265 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -129,7 +129,7 @@ import { requireExchangeTosAcceptedOrThrow, runWithClientCancellation, } from "./common.js"; -import { EddsaKeypair } from "./crypto/cryptoImplementation.js"; +import { EddsaKeypairStrings } from "./crypto/cryptoImplementation.js"; import { CoinRecord, CoinSourceType, @@ -3060,7 +3060,7 @@ export async function internalPrepareCreateWithdrawalGroup( exchangeBaseUrl: string | undefined; forcedWithdrawalGroupId?: string; forcedDenomSel?: ForcedDenomSel; - reserveKeyPair?: EddsaKeypair; + reserveKeyPair?: EddsaKeypairStrings; restrictAge?: number; wgInfo: WgInfo; }, @@ -3256,7 +3256,7 @@ export async function internalCreateWithdrawalGroup( amount?: AmountJson; forcedWithdrawalGroupId?: string; forcedDenomSel?: ForcedDenomSel; - reserveKeyPair?: EddsaKeypair; + reserveKeyPair?: EddsaKeypairStrings; restrictAge?: number; wgInfo: WgInfo; }, @@ -3846,7 +3846,7 @@ export async function createManualWithdrawal( ); } - let reserveKeyPair: EddsaKeypair; + let reserveKeyPair: EddsaKeypairStrings; if (req.forceReservePriv) { const pubResp = await wex.cryptoApi.eddsaGetPublic({ priv: req.forceReservePriv, |