update
This commit is contained in:
@@ -2,7 +2,7 @@ import * as plugins from './bunq.plugins.js';
|
|||||||
import { BunqApiContext } from './bunq.classes.apicontext.js';
|
import { BunqApiContext } from './bunq.classes.apicontext.js';
|
||||||
import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js';
|
import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js';
|
||||||
import { BunqUser } from './bunq.classes.user.js';
|
import { BunqUser } from './bunq.classes.user.js';
|
||||||
import { IBunqSessionServerResponse } from './bunq.interfaces.js';
|
import type { IBunqSessionServerResponse } from './bunq.interfaces.js';
|
||||||
|
|
||||||
export interface IBunqConstructorOptions {
|
export interface IBunqConstructorOptions {
|
||||||
deviceName: string;
|
deviceName: string;
|
||||||
|
@@ -2,7 +2,7 @@ import * as plugins from './bunq.plugins.js';
|
|||||||
import * as paths from './bunq.paths.js';
|
import * as paths from './bunq.paths.js';
|
||||||
import { BunqCrypto } from './bunq.classes.crypto.js';
|
import { BunqCrypto } from './bunq.classes.crypto.js';
|
||||||
import { BunqSession } from './bunq.classes.session.js';
|
import { BunqSession } from './bunq.classes.session.js';
|
||||||
import { IBunqApiContext } from './bunq.interfaces.js';
|
import type { IBunqApiContext } from './bunq.interfaces.js';
|
||||||
|
|
||||||
export interface IBunqApiContextOptions {
|
export interface IBunqApiContextOptions {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import * as plugins from './bunq.plugins.js';
|
import * as plugins from './bunq.plugins.js';
|
||||||
import { BunqAccount } from './bunq.classes.account.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 {
|
export class BunqCard {
|
||||||
private bunqAccount: BunqAccount;
|
private bunqAccount: BunqAccount;
|
||||||
|
@@ -81,35 +81,7 @@ export class BunqCrypto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the signing string for bunq API requests
|
* Create request signature header (signs only body per bunq docs)
|
||||||
*/
|
|
||||||
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
|
|
||||||
*/
|
*/
|
||||||
public createSignatureHeader(
|
public createSignatureHeader(
|
||||||
method: string,
|
method: string,
|
||||||
@@ -117,12 +89,12 @@ export class BunqCrypto {
|
|||||||
headers: { [key: string]: string },
|
headers: { [key: string]: string },
|
||||||
body: string = ''
|
body: string = ''
|
||||||
): string {
|
): string {
|
||||||
const signingString = this.createSigningString(method, endpoint, headers, body);
|
// According to bunq docs, only sign the request body
|
||||||
return this.signData(signingString);
|
return this.signData(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify response signature
|
* Verify response signature (signs only body per bunq API behavior)
|
||||||
*/
|
*/
|
||||||
public verifyResponseSignature(
|
public verifyResponseSignature(
|
||||||
statusCode: number,
|
statusCode: number,
|
||||||
@@ -135,20 +107,8 @@ export class BunqCrypto {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create signing string for response
|
// According to bunq API behavior, only the response body is signed
|
||||||
const sortedHeaderNames = Object.keys(headers)
|
return this.verifyData(body, responseSignature, serverPublicKey);
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from './bunq.plugins.js';
|
import * as plugins from './bunq.plugins.js';
|
||||||
import { BunqAccount } from './bunq.classes.account.js';
|
import { BunqAccount } from './bunq.classes.account.js';
|
||||||
import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js';
|
import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js';
|
||||||
import {
|
import type {
|
||||||
IBunqPaymentRequest,
|
IBunqPaymentRequest,
|
||||||
IBunqAmount,
|
IBunqAmount,
|
||||||
IBunqAlias,
|
IBunqAlias,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import * as plugins from './bunq.plugins.js';
|
import * as plugins from './bunq.plugins.js';
|
||||||
import { BunqCrypto } from './bunq.classes.crypto.js';
|
import { BunqCrypto } from './bunq.classes.crypto.js';
|
||||||
import {
|
import type {
|
||||||
IBunqApiContext,
|
IBunqApiContext,
|
||||||
IBunqError,
|
IBunqError,
|
||||||
IBunqRequestOptions
|
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(
|
const isValid = this.crypto.verifyResponseSignature(
|
||||||
response.statusCode,
|
response.statusCode,
|
||||||
stringHeaders,
|
stringHeaders,
|
||||||
response.body,
|
bodyString,
|
||||||
this.context.serverPublicKey
|
this.context.serverPublicKey
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -89,8 +94,18 @@ export class BunqHttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse response
|
// Parse response - smartrequest may already parse JSON automatically
|
||||||
const responseData = JSON.parse(response.body);
|
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
|
// Check for errors
|
||||||
if (responseData.Error) {
|
if (responseData.Error) {
|
||||||
@@ -104,7 +119,15 @@ export class BunqHttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle network errors
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ import * as plugins from './bunq.plugins.js';
|
|||||||
import { BunqAccount } from './bunq.classes.account.js';
|
import { BunqAccount } from './bunq.classes.account.js';
|
||||||
import { BunqTransaction } from './bunq.classes.transaction.js';
|
import { BunqTransaction } from './bunq.classes.transaction.js';
|
||||||
import { BunqPayment } from './bunq.classes.payment.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';
|
export type TAccountType = 'joint' | 'savings' | 'bank';
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import * as plugins from './bunq.plugins.js';
|
import * as plugins from './bunq.plugins.js';
|
||||||
import { BunqAccount } from './bunq.classes.account.js';
|
import { BunqAccount } from './bunq.classes.account.js';
|
||||||
import { IBunqNotificationFilter } from './bunq.interfaces.js';
|
import type { IBunqNotificationFilter } from './bunq.interfaces.js';
|
||||||
|
|
||||||
export class BunqNotification {
|
export class BunqNotification {
|
||||||
private bunqAccount: BunqAccount;
|
private bunqAccount: BunqAccount;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from './bunq.plugins.js';
|
import * as plugins from './bunq.plugins.js';
|
||||||
import { BunqAccount } from './bunq.classes.account.js';
|
import { BunqAccount } from './bunq.classes.account.js';
|
||||||
import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js';
|
import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js';
|
||||||
import {
|
import type {
|
||||||
IBunqPaymentRequest,
|
IBunqPaymentRequest,
|
||||||
IBunqPayment,
|
IBunqPayment,
|
||||||
IBunqAmount,
|
IBunqAmount,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from './bunq.plugins.js';
|
import * as plugins from './bunq.plugins.js';
|
||||||
import { BunqAccount } from './bunq.classes.account.js';
|
import { BunqAccount } from './bunq.classes.account.js';
|
||||||
import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js';
|
import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js';
|
||||||
import {
|
import type {
|
||||||
IBunqRequestInquiry,
|
IBunqRequestInquiry,
|
||||||
IBunqAmount,
|
IBunqAmount,
|
||||||
IBunqAlias,
|
IBunqAlias,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from './bunq.plugins.js';
|
import * as plugins from './bunq.plugins.js';
|
||||||
import { BunqAccount } from './bunq.classes.account.js';
|
import { BunqAccount } from './bunq.classes.account.js';
|
||||||
import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js';
|
import { BunqMonetaryAccount } from './bunq.classes.monetaryaccount.js';
|
||||||
import {
|
import type {
|
||||||
IBunqScheduledPaymentRequest,
|
IBunqScheduledPaymentRequest,
|
||||||
IBunqAmount,
|
IBunqAmount,
|
||||||
IBunqAlias,
|
IBunqAlias,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import * as plugins from './bunq.plugins.js';
|
import * as plugins from './bunq.plugins.js';
|
||||||
import { BunqHttpClient } from './bunq.classes.httpclient.js';
|
import { BunqHttpClient } from './bunq.classes.httpclient.js';
|
||||||
import { BunqCrypto } from './bunq.classes.crypto.js';
|
import { BunqCrypto } from './bunq.classes.crypto.js';
|
||||||
import {
|
import type {
|
||||||
IBunqApiContext,
|
IBunqApiContext,
|
||||||
IBunqInstallationResponse,
|
IBunqInstallationResponse,
|
||||||
IBunqDeviceServerResponse,
|
IBunqDeviceServerResponse,
|
||||||
@@ -39,7 +39,9 @@ export class BunqSession {
|
|||||||
*/
|
*/
|
||||||
private async createInstallation(): Promise<void> {
|
private async createInstallation(): Promise<void> {
|
||||||
// Generate RSA key pair if not already generated
|
// Generate RSA key pair if not already generated
|
||||||
if (!this.crypto.getPublicKey()) {
|
try {
|
||||||
|
this.crypto.getPublicKey();
|
||||||
|
} catch (error) {
|
||||||
await this.crypto.generateKeyPair();
|
await this.crypto.generateKeyPair();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +145,7 @@ export class BunqSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const now = new plugins.smarttime.TimeStamp();
|
const now = new plugins.smarttime.TimeStamp();
|
||||||
return this.sessionExpiryTime.isYoungerThanOtherTimeStamp(now);
|
return now.isOlderThan(this.sessionExpiryTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import * as plugins from './bunq.plugins.js';
|
import * as plugins from './bunq.plugins.js';
|
||||||
import { BunqApiContext } from './bunq.classes.apicontext.js';
|
import { BunqApiContext } from './bunq.classes.apicontext.js';
|
||||||
import { IBunqUser } from './bunq.interfaces.js';
|
import type { IBunqUser } from './bunq.interfaces.js';
|
||||||
|
|
||||||
export class BunqUser {
|
export class BunqUser {
|
||||||
private apiContext: BunqApiContext;
|
private apiContext: BunqApiContext;
|
||||||
|
@@ -1,4 +1,9 @@
|
|||||||
import * as plugins from './bunq.plugins.js';
|
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 packageDir = plugins.path.join(__dirname, '../');
|
||||||
export const nogitDir = plugins.path.join(packageDir, './.nogit/');
|
export const nogitDir = plugins.path.join(packageDir, './.nogit/');
|
||||||
|
Reference in New Issue
Block a user