From f530fa639a3ebaeac32afdde291a304250fe33f2 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Fri, 18 Jul 2025 11:33:13 +0000 Subject: [PATCH] update --- ts/bunq.classes.account.ts | 2 +- ts/bunq.classes.apicontext.ts | 2 +- ts/bunq.classes.card.ts | 2 +- ts/bunq.classes.crypto.ts | 52 ++++-------------------------- ts/bunq.classes.draft.ts | 2 +- ts/bunq.classes.httpclient.ts | 33 ++++++++++++++++--- ts/bunq.classes.monetaryaccount.ts | 2 +- ts/bunq.classes.notification.ts | 2 +- ts/bunq.classes.payment.ts | 2 +- ts/bunq.classes.request.ts | 2 +- ts/bunq.classes.schedule.ts | 2 +- ts/bunq.classes.session.ts | 8 +++-- ts/bunq.classes.user.ts | 2 +- ts/bunq.paths.ts | 5 +++ 14 files changed, 54 insertions(+), 64 deletions(-) diff --git a/ts/bunq.classes.account.ts b/ts/bunq.classes.account.ts index 121dff0..b8284f4 100644 --- a/ts/bunq.classes.account.ts +++ b/ts/bunq.classes.account.ts @@ -2,7 +2,7 @@ import * as plugins from './bunq.plugins.js'; import { BunqApiContext } from './bunq.classes.apicontext.js'; import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js'; import { BunqUser } from './bunq.classes.user.js'; -import { IBunqSessionServerResponse } from './bunq.interfaces.js'; +import type { IBunqSessionServerResponse } from './bunq.interfaces.js'; export interface IBunqConstructorOptions { deviceName: string; diff --git a/ts/bunq.classes.apicontext.ts b/ts/bunq.classes.apicontext.ts index c8c06ea..50431c0 100644 --- a/ts/bunq.classes.apicontext.ts +++ b/ts/bunq.classes.apicontext.ts @@ -2,7 +2,7 @@ import * as plugins from './bunq.plugins.js'; import * as paths from './bunq.paths.js'; import { BunqCrypto } from './bunq.classes.crypto.js'; import { BunqSession } from './bunq.classes.session.js'; -import { IBunqApiContext } from './bunq.interfaces.js'; +import type { IBunqApiContext } from './bunq.interfaces.js'; export interface IBunqApiContextOptions { apiKey: string; diff --git a/ts/bunq.classes.card.ts b/ts/bunq.classes.card.ts index a257e0c..3d57fe5 100644 --- a/ts/bunq.classes.card.ts +++ b/ts/bunq.classes.card.ts @@ -1,6 +1,6 @@ import * as plugins from './bunq.plugins.js'; import { BunqAccount } from './bunq.classes.account.js'; -import { IBunqCard, IBunqAmount } from './bunq.interfaces.js'; +import type { IBunqCard, IBunqAmount } from './bunq.interfaces.js'; export class BunqCard { private bunqAccount: BunqAccount; diff --git a/ts/bunq.classes.crypto.ts b/ts/bunq.classes.crypto.ts index fbc9afa..d490a05 100644 --- a/ts/bunq.classes.crypto.ts +++ b/ts/bunq.classes.crypto.ts @@ -81,35 +81,7 @@ export class BunqCrypto { } /** - * Create the signing string for bunq API requests - */ - public createSigningString( - method: string, - endpoint: string, - headers: { [key: string]: string }, - body: string = '' - ): string { - const sortedHeaderNames = Object.keys(headers) - .filter(name => name.startsWith('X-Bunq-') || name === 'Cache-Control' || name === 'User-Agent') - .sort(); - - let signingString = `${method} ${endpoint}\n`; - - for (const headerName of sortedHeaderNames) { - signingString += `${headerName}: ${headers[headerName]}\n`; - } - - signingString += '\n'; - - if (body) { - signingString += body; - } - - return signingString; - } - - /** - * Create request signature headers + * Create request signature header (signs only body per bunq docs) */ public createSignatureHeader( method: string, @@ -117,12 +89,12 @@ export class BunqCrypto { headers: { [key: string]: string }, body: string = '' ): string { - const signingString = this.createSigningString(method, endpoint, headers, body); - return this.signData(signingString); + // According to bunq docs, only sign the request body + return this.signData(body); } /** - * Verify response signature + * Verify response signature (signs only body per bunq API behavior) */ public verifyResponseSignature( statusCode: number, @@ -135,20 +107,8 @@ export class BunqCrypto { return false; } - // Create signing string for response - const sortedHeaderNames = Object.keys(headers) - .filter(name => name.startsWith('x-bunq-') && name !== 'x-bunq-server-signature') - .sort(); - - let signingString = `${statusCode}\n`; - - for (const headerName of sortedHeaderNames) { - signingString += `${headerName}: ${headers[headerName]}\n`; - } - - signingString += '\n' + body; - - return this.verifyData(signingString, responseSignature, serverPublicKey); + // According to bunq API behavior, only the response body is signed + return this.verifyData(body, responseSignature, serverPublicKey); } /** diff --git a/ts/bunq.classes.draft.ts b/ts/bunq.classes.draft.ts index 6995c71..2dbf0b7 100644 --- a/ts/bunq.classes.draft.ts +++ b/ts/bunq.classes.draft.ts @@ -1,7 +1,7 @@ import * as plugins from './bunq.plugins.js'; import { BunqAccount } from './bunq.classes.account.js'; import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js'; -import { +import type { IBunqPaymentRequest, IBunqAmount, IBunqAlias, diff --git a/ts/bunq.classes.httpclient.ts b/ts/bunq.classes.httpclient.ts index ee796e9..1c3ee91 100644 --- a/ts/bunq.classes.httpclient.ts +++ b/ts/bunq.classes.httpclient.ts @@ -1,6 +1,6 @@ import * as plugins from './bunq.plugins.js'; import { BunqCrypto } from './bunq.classes.crypto.js'; -import { +import type { IBunqApiContext, IBunqError, IBunqRequestOptions @@ -77,10 +77,15 @@ export class BunqHttpClient { } } + // Convert body to string if needed for signature verification + const bodyString = typeof response.body === 'string' + ? response.body + : JSON.stringify(response.body); + const isValid = this.crypto.verifyResponseSignature( response.statusCode, stringHeaders, - response.body, + bodyString, this.context.serverPublicKey ); @@ -89,8 +94,18 @@ export class BunqHttpClient { } } - // Parse response - const responseData = JSON.parse(response.body); + // Parse response - smartrequest may already parse JSON automatically + let responseData; + if (typeof response.body === 'string') { + try { + responseData = JSON.parse(response.body); + } catch (parseError) { + throw new Error(`Failed to parse JSON response: ${parseError.message}`); + } + } else { + // Response is already parsed + responseData = response.body; + } // Check for errors if (responseData.Error) { @@ -104,7 +119,15 @@ export class BunqHttpClient { } // Handle network errors - throw new Error(`Request failed: ${error.message}`); + let errorMessage = 'Request failed: '; + if (error instanceof Error) { + errorMessage += error.message; + } else if (typeof error === 'string') { + errorMessage += error; + } else { + errorMessage += JSON.stringify(error); + } + throw new Error(errorMessage); } } diff --git a/ts/bunq.classes.monetaryaccount.ts b/ts/bunq.classes.monetaryaccount.ts index 5f2ed42..2e49b9a 100644 --- a/ts/bunq.classes.monetaryaccount.ts +++ b/ts/bunq.classes.monetaryaccount.ts @@ -2,7 +2,7 @@ import * as plugins from './bunq.plugins.js'; import { BunqAccount } from './bunq.classes.account.js'; import { BunqTransaction } from './bunq.classes.transaction.js'; import { BunqPayment } from './bunq.classes.payment.js'; -import { IBunqPaginationOptions, IBunqMonetaryAccountBank } from './bunq.interfaces.js'; +import type { IBunqPaginationOptions, IBunqMonetaryAccountBank } from './bunq.interfaces.js'; export type TAccountType = 'joint' | 'savings' | 'bank'; diff --git a/ts/bunq.classes.notification.ts b/ts/bunq.classes.notification.ts index 7dd8845..2d59308 100644 --- a/ts/bunq.classes.notification.ts +++ b/ts/bunq.classes.notification.ts @@ -1,6 +1,6 @@ import * as plugins from './bunq.plugins.js'; import { BunqAccount } from './bunq.classes.account.js'; -import { IBunqNotificationFilter } from './bunq.interfaces.js'; +import type { IBunqNotificationFilter } from './bunq.interfaces.js'; export class BunqNotification { private bunqAccount: BunqAccount; diff --git a/ts/bunq.classes.payment.ts b/ts/bunq.classes.payment.ts index 97e0079..7134290 100644 --- a/ts/bunq.classes.payment.ts +++ b/ts/bunq.classes.payment.ts @@ -1,7 +1,7 @@ import * as plugins from './bunq.plugins.js'; import { BunqAccount } from './bunq.classes.account.js'; import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js'; -import { +import type { IBunqPaymentRequest, IBunqPayment, IBunqAmount, diff --git a/ts/bunq.classes.request.ts b/ts/bunq.classes.request.ts index 5e2c17d..2eb89a1 100644 --- a/ts/bunq.classes.request.ts +++ b/ts/bunq.classes.request.ts @@ -1,7 +1,7 @@ import * as plugins from './bunq.plugins.js'; import { BunqAccount } from './bunq.classes.account.js'; import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js'; -import { +import type { IBunqRequestInquiry, IBunqAmount, IBunqAlias, diff --git a/ts/bunq.classes.schedule.ts b/ts/bunq.classes.schedule.ts index 5d879ca..6a1264e 100644 --- a/ts/bunq.classes.schedule.ts +++ b/ts/bunq.classes.schedule.ts @@ -1,7 +1,7 @@ import * as plugins from './bunq.plugins.js'; import { BunqAccount } from './bunq.classes.account.js'; import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js'; -import { +import type { IBunqScheduledPaymentRequest, IBunqAmount, IBunqAlias, diff --git a/ts/bunq.classes.session.ts b/ts/bunq.classes.session.ts index ac6e2db..d618494 100644 --- a/ts/bunq.classes.session.ts +++ b/ts/bunq.classes.session.ts @@ -1,7 +1,7 @@ import * as plugins from './bunq.plugins.js'; import { BunqHttpClient } from './bunq.classes.httpclient.js'; import { BunqCrypto } from './bunq.classes.crypto.js'; -import { +import type { IBunqApiContext, IBunqInstallationResponse, IBunqDeviceServerResponse, @@ -39,7 +39,9 @@ export class BunqSession { */ private async createInstallation(): Promise { // Generate RSA key pair if not already generated - if (!this.crypto.getPublicKey()) { + try { + this.crypto.getPublicKey(); + } catch (error) { await this.crypto.generateKeyPair(); } @@ -143,7 +145,7 @@ export class BunqSession { } const now = new plugins.smarttime.TimeStamp(); - return this.sessionExpiryTime.isYoungerThanOtherTimeStamp(now); + return now.isOlderThan(this.sessionExpiryTime); } /** diff --git a/ts/bunq.classes.user.ts b/ts/bunq.classes.user.ts index 34b8912..87e1c62 100644 --- a/ts/bunq.classes.user.ts +++ b/ts/bunq.classes.user.ts @@ -1,6 +1,6 @@ import * as plugins from './bunq.plugins.js'; import { BunqApiContext } from './bunq.classes.apicontext.js'; -import { IBunqUser } from './bunq.interfaces.js'; +import type { IBunqUser } from './bunq.interfaces.js'; export class BunqUser { private apiContext: BunqApiContext; diff --git a/ts/bunq.paths.ts b/ts/bunq.paths.ts index 9f86a8a..1914029 100644 --- a/ts/bunq.paths.ts +++ b/ts/bunq.paths.ts @@ -1,4 +1,9 @@ import * as plugins from './bunq.plugins.js'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); export const packageDir = plugins.path.join(__dirname, '../'); export const nogitDir = plugins.path.join(packageDir, './.nogit/');