diff --git a/apps/engine/src/common/schema.ts b/apps/engine/src/common/schema.ts index a859ae572f..e5a76f463f 100644 --- a/apps/engine/src/common/schema.ts +++ b/apps/engine/src/common/schema.ts @@ -25,7 +25,12 @@ export const GeneralErrorSchema = z.object({ }), }); -export const Providers = z.enum(["teller", "plaid", "gocardless"]); +export const Providers = z.enum([ + "teller", + "plaid", + "gocardless", + "enablebanking", +]); export const HeadersSchema = z.object({ authorization: z.string().openapi({ diff --git a/apps/engine/src/providers/enablebanking/enablebanking-api.ts b/apps/engine/src/providers/enablebanking/enablebanking-api.ts index ec70691a6b..db21b21f37 100644 --- a/apps/engine/src/providers/enablebanking/enablebanking-api.ts +++ b/apps/engine/src/providers/enablebanking/enablebanking-api.ts @@ -1,3 +1,5 @@ +import { Buffer } from "node:buffer"; +import fs from "node:fs"; import * as jose from "jose"; import xior, { type XiorInstance, type XiorRequestConfig } from "xior"; import type { ProviderParams } from "../types"; @@ -12,32 +14,71 @@ export class EnableBankingApi { #baseUrl = "https://api.enablebanking.com"; #applicationId: string; #keyContent: string; - #expiresIn = 23; + + // Maximum allowed TTL is 24 hours (86400 seconds) + #expiresIn = 23; // hours constructor(params: Omit) { this.#applicationId = params.envs.ENABLEBANKING_APPLICATION_ID; this.#keyContent = params.envs.ENABLE_BANKING_KEY_CONTENT; } - private async generateJWT() { - const privateKey = Buffer.from(this.#keyContent, "base64"); + private encodeData(data: object) { + return jose.base64url.encode(Buffer.from(JSON.stringify(data))); + } - const key = await jose.importPKCS8(privateKey.toString(), "RS256"); + private getJWTHeader() { + return this.encodeData({ + typ: "JWT", + alg: "RS256", + kid: this.#applicationId, + }); + } - const jwt = await new jose.SignJWT({ + private getJWTBody(exp: number) { + const timestamp = Math.floor(Date.now() / 1000); + return this.encodeData({ iss: "enablebanking.com", aud: "api.enablebanking.com", - }) - .setProtectedHeader({ - typ: "JWT", - alg: "RS256", - kid: this.#applicationId, - }) - .setIssuedAt() - .setExpirationTime(`${this.#expiresIn}h`) - .sign(key); - - return jwt; + iat: timestamp, + exp: timestamp + exp, + }); + } + + async signWithKey(data: string) { + const decodedKey = Buffer.from(this.#keyContent, "base64").toString( + "utf-8", + ); + + const pemKey = decodedKey.includes("BEGIN PRIVATE KEY") + ? decodedKey + : `-----BEGIN PRIVATE KEY-----\n${decodedKey}\n-----END PRIVATE KEY-----`; + + console.log(decodedKey.includes("BEGIN PRIVATE KEY")); + + const privateKey = await jose.importPKCS8(pemKey, "RS256"); + + // Sign directly with RSA-SHA256 + const signature = await crypto.subtle.sign( + { + name: "RSASSA-PKCS1-v1_5", + hash: { name: "SHA-256" }, + }, + // @ts-expect-error + privateKey, + new TextEncoder().encode(data), + ); + + return jose.base64url.encode(new Uint8Array(signature)); + } + + private async generateJWT() { + const exp = this.#expiresIn * 60 * 60; + const jwtHeaders = this.getJWTHeader(); + const jwtBody = this.getJWTBody(exp); + const jwtSignature = await this.signWithKey(`${jwtHeaders}.${jwtBody}`); + + return `${jwtHeaders}.${jwtBody}.${jwtSignature}`; } async #getApi(): Promise { @@ -94,10 +135,13 @@ export class EnableBankingApi { continuation_key?: string; }, ): Promise { - return this.#get( - `/accounts/${accountId}/transactions`, - params, - ); + // return this.#get( + // `/accounts/${accountId}/transactions`, + // params, + // ); + + const katt = await this.#get("/aspsps", params); + console.log(katt); } async deleteSession(sessionId: string): Promise { diff --git a/apps/engine/src/providers/enablebanking/enablebanking-provider.ts b/apps/engine/src/providers/enablebanking/enablebanking-provider.ts index 48287a3c81..e90126a928 100644 --- a/apps/engine/src/providers/enablebanking/enablebanking-provider.ts +++ b/apps/engine/src/providers/enablebanking/enablebanking-provider.ts @@ -15,7 +15,7 @@ import type { ProviderParams, } from "../types"; import { EnableBankingApi } from "./enablebanking-api"; -import { transformAccount, transformTransaction } from "./transform"; +// import { transformAccount, transformTransaction } from "./transform"; export class EnableBankingProvider implements Provider { #api: EnableBankingApi; @@ -32,33 +32,29 @@ export class EnableBankingProvider implements Provider { params: GetInstitutionsRequest, ): Promise { const response = await this.#api.getInstitutions(); - return response.aspsps.map(transformInstitution); + // return response.aspsps.map(transformInstitution); + return []; } async getAccounts(params: GetAccountsRequest): Promise { const response = await this.#api.getAccounts(params.id!); - return response.accounts.map(transformAccount); + // return response.accounts.map(transformAccount); + return []; } async getAccountBalance( params: GetAccountBalanceRequest, ): Promise { const response = await this.#api.getAccountBalance(params.accountId); - return transformBalance(response.balances[0]); + // return transformBalance(response.balances[0]); + return { currency: "EUR", amount: 100 }; } async getTransactions( params: GetTransactionsRequest, ): Promise { const response = await this.#api.getTransactions(params.accountId); - return { - transactions: response.transactions.map(transformTransaction), - paging: response.continuation_key - ? { - next: response.continuation_key, - } - : undefined, - }; + return []; } async getConnectionStatus( diff --git a/apps/engine/wrangler.toml b/apps/engine/wrangler.toml index 4778ce5bc0..86f8acf0bd 100644 --- a/apps/engine/wrangler.toml +++ b/apps/engine/wrangler.toml @@ -1,6 +1,7 @@ compatibility_date = "2024-11-11" workers_dev = false logpush = true +compatibility_flags = ["nodejs_compat_v2"] [observability] enabled = true