diff --git a/docker-compose.yml b/docker-compose.yml index 424844e4a..376ecfa3a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,4 +19,11 @@ services: # Change './actual-data' below to the path to the folder you want Actual to store its data in on your server. # '/data' is the path Actual will look for its files in by default, so leave that as-is. - ./actual-data:/data + healthcheck: + # Enable health check for the instance + test: ["CMD-SHELL", "node src/scripts/health-check.js"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 20s restart: unless-stopped diff --git a/src/account-db.js b/src/account-db.js index c7fff76ed..409f00099 100644 --- a/src/account-db.js +++ b/src/account-db.js @@ -48,14 +48,18 @@ export function getActiveLoginMethod() { export function getLoginMethod(req) { if ( typeof req !== 'undefined' && - (req.body || { loginMethod: null }).loginMethod + (req.body || { loginMethod: null }).loginMethod && + config.allowedLoginMethods.includes(req.body.loginMethod) ) { return req.body.loginMethod; } - const activeMethod = getActiveLoginMethod(); + if (config.loginMethod) { + return config.loginMethod; + } - return config.loginMethod || activeMethod || 'password'; + const activeMethod = getActiveLoginMethod(); + return activeMethod || 'password'; } export async function bootstrap(loginSettings) { diff --git a/src/app-gocardless/bank-factory.js b/src/app-gocardless/bank-factory.js index 33e83a78d..5a3fb7280 100644 --- a/src/app-gocardless/bank-factory.js +++ b/src/app-gocardless/bank-factory.js @@ -8,11 +8,12 @@ import BelfiusGkccbebb from './banks/belfius_gkccbebb.js'; import BerlinerSparkasseBeladebexxx from './banks/berliner_sparkasse_beladebexxx.js'; import BnpBeGebabebb from './banks/bnp_be_gebabebb.js'; import CbcCregbebb from './banks/cbc_cregbebb.js'; +import CommerzbankCobadeff from './banks/commerzbank_cobadeff.js'; import DanskebankDabno22 from './banks/danskebank_dabno22.js'; +import DirektHeladef1822 from './banks/direkt_heladef1822.js'; import EasybankBawaatww from './banks/easybank_bawaatww.js'; import EntercardSwednokk from './banks/entercard_swednokk.js'; import FortuneoFtnofrp1xxx from './banks/fortuneo_ftnofrp1xxx.js'; -import HanseaticHstbdehh from './banks/hanseatic_hstbdehh.js'; import HypeHyeeit22 from './banks/hype_hyeeit22.js'; import IngIngbrobu from './banks/ing_ingbrobu.js'; import IngIngddeff from './banks/ing_ingddeff.js'; @@ -20,6 +21,7 @@ import IngPlIngbplpw from './banks/ing_pl_ingbplpw.js'; import IntegrationBank from './banks/integration-bank.js'; import IsyBankItbbitmm from './banks/isybank_itbbitmm.js'; import KbcKredbebb from './banks/kbc_kredbebb.js'; +import LhvLhvbee22 from './banks/lhv-lhvbee22.js'; import MbankRetailBrexplpw from './banks/mbank_retail_brexplpw.js'; import NationwideNaiagb21 from './banks/nationwide_naiagb21.js'; import NbgEthngraaxxx from './banks/nbg_ethngraaxxx.js'; @@ -47,17 +49,19 @@ export const banks = [ BerlinerSparkasseBeladebexxx, BnpBeGebabebb, CbcCregbebb, + CommerzbankCobadeff, DanskebankDabno22, + DirektHeladef1822, EasybankBawaatww, EntercardSwednokk, FortuneoFtnofrp1xxx, - HanseaticHstbdehh, HypeHyeeit22, IngIngbrobu, IngIngddeff, IngPlIngbplpw, IsyBankItbbitmm, KbcKredbebb, + LhvLhvbee22, MbankRetailBrexplpw, NationwideNaiagb21, NbgEthngraaxxx, @@ -86,6 +90,7 @@ export const BANKS_WITH_LIMITED_HISTORY = [ 'BANKINTER_BKBKESMM', 'BBVA_BBVAESMM', 'BRED_BREDFRPPXXX', + 'CAIXA_GERAL_DEPOSITOS_CGDIPTPL', 'CAIXABANK_CAIXESBB', 'CARTALIS_CIMTITR1', 'CESKA_SPORITELNA_LONG_GIBACZPX', diff --git a/src/app-gocardless/banks/abanca_caglesmm.js b/src/app-gocardless/banks/abanca_caglesmm.js index bd485331b..905175098 100644 --- a/src/app-gocardless/banks/abanca_caglesmm.js +++ b/src/app-gocardless/banks/abanca_caglesmm.js @@ -6,9 +6,11 @@ import { formatPayeeName } from '../../util/payee-name.js'; export default { ...Fallback, - institutionIds: ['ABANCA_CAGLESMM', 'ABANCA_CAGLPTPL'], - - accessValidForDays: 180, + institutionIds: [ + 'ABANCA_CAGLESMM', + 'ABANCA_CAGLPTPL', + 'ABANCA_CORP_CAGLPTPL', + ], // Abanca transactions doesn't get the creditorName/debtorName properly normalizeTransaction(transaction, _booked) { diff --git a/src/app-gocardless/banks/abnamro_abnanl2a.js b/src/app-gocardless/banks/abnamro_abnanl2a.js index 0bf6e216f..2b260953f 100644 --- a/src/app-gocardless/banks/abnamro_abnanl2a.js +++ b/src/app-gocardless/banks/abnamro_abnanl2a.js @@ -9,8 +9,6 @@ export default { institutionIds: ['ABNAMRO_ABNANL2A'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { // There is no remittanceInformationUnstructured, so we'll make it transaction.remittanceInformationUnstructured = diff --git a/src/app-gocardless/banks/american_express_aesudef1.js b/src/app-gocardless/banks/american_express_aesudef1.js index 2ae8168df..dcf6a20bc 100644 --- a/src/app-gocardless/banks/american_express_aesudef1.js +++ b/src/app-gocardless/banks/american_express_aesudef1.js @@ -9,8 +9,6 @@ export default { institutionIds: ['AMERICAN_EXPRESS_AESUDEF1'], - accessValidForDays: 180, - normalizeAccount(account) { return { ...Fallback.normalizeAccount(account), diff --git a/src/app-gocardless/banks/bancsabadell_bsabesbbb.js b/src/app-gocardless/banks/bancsabadell_bsabesbbb.js index 832fb8953..1aa41e911 100644 --- a/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +++ b/src/app-gocardless/banks/bancsabadell_bsabesbbb.js @@ -8,8 +8,6 @@ export default { institutionIds: ['BANCSABADELL_BSABESBB'], - accessValidForDays: 180, - // Sabadell transactions don't get the creditorName/debtorName properly normalizeTransaction(transaction, _booked) { const amount = transaction.transactionAmount.amount; diff --git a/src/app-gocardless/banks/bank.interface.ts b/src/app-gocardless/banks/bank.interface.ts index 21e875815..18de65523 100644 --- a/src/app-gocardless/banks/bank.interface.ts +++ b/src/app-gocardless/banks/bank.interface.ts @@ -7,8 +7,6 @@ import { Transaction, Balance } from '../gocardless-node.types.js'; export interface IBank { institutionIds: string[]; - accessValidForDays: number; - /** * Returns normalized object with required data for the frontend */ diff --git a/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js b/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js index 6e677775d..959a0fd98 100644 --- a/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +++ b/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js @@ -6,8 +6,6 @@ export default { institutionIds: ['BANK_OF_IRELAND_B365_BOFIIE2D'], - accessValidForDays: 180, - normalizeTransaction(transaction, booked) { transaction.remittanceInformationUnstructured = fixupPayee( transaction.remittanceInformationUnstructured, diff --git a/src/app-gocardless/banks/bankinter_bkbkesmm.js b/src/app-gocardless/banks/bankinter_bkbkesmm.js index 92672d6de..8d1c32975 100644 --- a/src/app-gocardless/banks/bankinter_bkbkesmm.js +++ b/src/app-gocardless/banks/bankinter_bkbkesmm.js @@ -8,8 +8,6 @@ export default { institutionIds: ['BANKINTER_BKBKESMM'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { transaction.remittanceInformationUnstructured = transaction.remittanceInformationUnstructured diff --git a/src/app-gocardless/banks/belfius_gkccbebb.js b/src/app-gocardless/banks/belfius_gkccbebb.js index ebec54e12..594689edc 100644 --- a/src/app-gocardless/banks/belfius_gkccbebb.js +++ b/src/app-gocardless/banks/belfius_gkccbebb.js @@ -8,8 +8,6 @@ export default { institutionIds: ['BELFIUS_GKCCBEBB'], - accessValidForDays: 180, - // The problem is that we have transaction with duplicated transaction ids. // This is not expected and the nordigen api has a work-around for some backs // They will set an internalTransactionId which is unique diff --git a/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js b/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js index 2ef27d7d6..9c8c3d9f6 100644 --- a/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +++ b/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js @@ -9,8 +9,6 @@ export default { institutionIds: ['BERLINER_SPARKASSE_BELADEBEXXX'], - accessValidForDays: 180, - /** * Following the GoCardless documentation[0] we should prefer `bookingDate` * here, though some of their bank integrations uses the date field diff --git a/src/app-gocardless/banks/bnp_be_gebabebb.js b/src/app-gocardless/banks/bnp_be_gebabebb.js index 9af2fa674..c00601939 100644 --- a/src/app-gocardless/banks/bnp_be_gebabebb.js +++ b/src/app-gocardless/banks/bnp_be_gebabebb.js @@ -12,8 +12,6 @@ export default { 'BNP_BE_GEBABEBB', ], - accessValidForDays: 180, - /** BNP_BE_GEBABEBB provides a lot of useful information via the 'additionalField' * There does not seem to be a specification of this field, but the following information is contained in its subfields: * - for pending transactions: the 'atmPosName' diff --git a/src/app-gocardless/banks/cbc_cregbebb.js b/src/app-gocardless/banks/cbc_cregbebb.js index 94ab20285..bf881dacf 100644 --- a/src/app-gocardless/banks/cbc_cregbebb.js +++ b/src/app-gocardless/banks/cbc_cregbebb.js @@ -7,8 +7,6 @@ export default { institutionIds: ['CBC_CREGBEBB'], - accessValidForDays: 180, - /** * For negative amounts, the only payee information we have is returned in * remittanceInformationUnstructured. diff --git a/src/app-gocardless/banks/commerzbank_cobadeff.js b/src/app-gocardless/banks/commerzbank_cobadeff.js new file mode 100644 index 000000000..eaf061c9b --- /dev/null +++ b/src/app-gocardless/banks/commerzbank_cobadeff.js @@ -0,0 +1,54 @@ +import Fallback from './integration-bank.js'; +import { formatPayeeName } from '../../util/payee-name.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['COMMERZBANK_COBADEFF'], + + normalizeTransaction(transaction, _booked) { + // remittanceInformationUnstructured is limited to 140 chars thus ... + // ... missing information form remittanceInformationUnstructuredArray ... + // ... so we recreate it. + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructuredArray.join(' '); + + // The limitations of remittanceInformationUnstructuredArray ... + // ... can result in split keywords. We fix these. Other ... + // ... splits will need to be fixed by user with rules. + const keywords = [ + 'End-to-End-Ref.:', + 'Mandatsref:', + 'Gläubiger-ID:', + 'SEPA-BASISLASTSCHRIFT', + 'Kartenzahlung', + 'Dauerauftrag', + ]; + keywords.forEach((keyword) => { + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured.replace( + // There can be spaces in keywords + RegExp(keyword.split('').join('\\s*'), 'gi'), + ', ' + keyword + ' ', + ); + }); + + // Clean up remittanceInformation, deduplicate payee (removing slashes ... + // ... that are added to the remittanceInformation field), and ... + // ... remove clutter like "End-to-End-Ref.: NOTPROVIDED" + const payee = transaction.creditorName || transaction.debtorName || ''; + transaction.remittanceInformationUnstructured = + transaction.remittanceInformationUnstructured + .replace(/\s*(,)?\s+/g, '$1 ') + .replace(RegExp(payee.split(' ').join('(/*| )'), 'gi'), ' ') + .replace(', End-to-End-Ref.: NOTPROVIDED', '') + .trim(); + + return { + ...transaction, + payeeName: formatPayeeName(transaction), + date: transaction.bookingDate, + }; + }, +}; diff --git a/src/app-gocardless/banks/danskebank_dabno22.js b/src/app-gocardless/banks/danskebank_dabno22.js index 3d83ea950..39a1f13b3 100644 --- a/src/app-gocardless/banks/danskebank_dabno22.js +++ b/src/app-gocardless/banks/danskebank_dabno22.js @@ -9,8 +9,6 @@ export default { institutionIds: ['DANSKEBANK_DABANO22'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { /** * Danske Bank appends the EndToEndID: NOTPROVIDED to diff --git a/src/app-gocardless/banks/1822_direkt_heladef1822.js b/src/app-gocardless/banks/direkt_heladef1822.js similarity index 100% rename from src/app-gocardless/banks/1822_direkt_heladef1822.js rename to src/app-gocardless/banks/direkt_heladef1822.js diff --git a/src/app-gocardless/banks/easybank_bawaatww.js b/src/app-gocardless/banks/easybank_bawaatww.js index f93ab951f..635cbf33f 100644 --- a/src/app-gocardless/banks/easybank_bawaatww.js +++ b/src/app-gocardless/banks/easybank_bawaatww.js @@ -10,8 +10,6 @@ export default { institutionIds: ['EASYBANK_BAWAATWW'], - accessValidForDays: 179, - // If date is same, sort by transactionId sortTransactions: (transactions = []) => transactions.sort((a, b) => { diff --git a/src/app-gocardless/banks/entercard_swednokk.js b/src/app-gocardless/banks/entercard_swednokk.js index daafbfb89..c71c306b2 100644 --- a/src/app-gocardless/banks/entercard_swednokk.js +++ b/src/app-gocardless/banks/entercard_swednokk.js @@ -11,8 +11,6 @@ export default { institutionIds: ['ENTERCARD_SWEDNOKK'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { // GoCardless's Entercard integration returns forex transactions with the // foreign amount in `transactionAmount`, but at least the amount actually diff --git a/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js b/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js index e05634e4e..c695c0f1f 100644 --- a/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +++ b/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js @@ -8,8 +8,6 @@ export default { institutionIds: ['FORTUNEO_FTNOFRP1XXX'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { const date = transaction.bookingDate || diff --git a/src/app-gocardless/banks/hanseatic_hstbdehh.js b/src/app-gocardless/banks/hanseatic_hstbdehh.js deleted file mode 100644 index 143f92bf4..000000000 --- a/src/app-gocardless/banks/hanseatic_hstbdehh.js +++ /dev/null @@ -1,10 +0,0 @@ -import Fallback from './integration-bank.js'; - -/** @type {import('./bank.interface.js').IBank} */ -export default { - ...Fallback, - - institutionIds: ['HANSEATIC_HSTBDEHH'], - - accessValidForDays: 89, -}; diff --git a/src/app-gocardless/banks/hype_hyeeit22.js b/src/app-gocardless/banks/hype_hyeeit22.js index d69efda8e..fdbfc0218 100644 --- a/src/app-gocardless/banks/hype_hyeeit22.js +++ b/src/app-gocardless/banks/hype_hyeeit22.js @@ -8,8 +8,6 @@ export default { institutionIds: ['HYPE_HYEEIT22'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { /** Online card payments - identified by "crd" transaction code * always start with PAGAMENTO PRESSO + diff --git a/src/app-gocardless/banks/ing_ingbrobu.js b/src/app-gocardless/banks/ing_ingbrobu.js index 4f90da51c..b514d78e8 100644 --- a/src/app-gocardless/banks/ing_ingbrobu.js +++ b/src/app-gocardless/banks/ing_ingbrobu.js @@ -6,8 +6,6 @@ export default { institutionIds: ['ING_INGBROBU'], - accessValidForDays: 180, - normalizeTransaction(transaction, booked) { //Merchant transactions all have the same transactionId of 'NOTPROVIDED'. //For booked transactions, this can be set to the internalTransactionId diff --git a/src/app-gocardless/banks/ing_ingddeff.js b/src/app-gocardless/banks/ing_ingddeff.js index 3eabb9908..57907ee76 100644 --- a/src/app-gocardless/banks/ing_ingddeff.js +++ b/src/app-gocardless/banks/ing_ingddeff.js @@ -9,8 +9,6 @@ export default { institutionIds: ['ING_INGDDEFF'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { const remittanceInformationMatch = /remittanceinformation:(.*)$/.exec( transaction.remittanceInformationUnstructured, diff --git a/src/app-gocardless/banks/ing_pl_ingbplpw.js b/src/app-gocardless/banks/ing_pl_ingbplpw.js index 248068cb8..611f67cab 100644 --- a/src/app-gocardless/banks/ing_pl_ingbplpw.js +++ b/src/app-gocardless/banks/ing_pl_ingbplpw.js @@ -9,8 +9,6 @@ export default { institutionIds: ['ING_PL_INGBPLPW'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { return { ...transaction, diff --git a/src/app-gocardless/banks/integration-bank.js b/src/app-gocardless/banks/integration-bank.js index c96b08501..70f3c1cf3 100644 --- a/src/app-gocardless/banks/integration-bank.js +++ b/src/app-gocardless/banks/integration-bank.js @@ -20,13 +20,6 @@ const SORTED_BALANCE_TYPE_LIST = [ export default { institutionIds: ['IntegrationBank'], - // EEA need to allow at least 180 days now but this doesn't apply to UK - // banks, and it's possible that there are EEA banks which still don't follow - // the new requirements. See: - // - https://nordigen.zendesk.com/hc/en-gb/articles/13239212055581-EEA-180-day-access - // - https://nordigen.zendesk.com/hc/en-gb/articles/6760902653085-Extended-history-and-continuous-access-edge-cases - accessValidForDays: 90, - normalizeAccount(account) { console.debug( 'Available account properties for new institution integration', diff --git a/src/app-gocardless/banks/isybank_itbbitmm.js b/src/app-gocardless/banks/isybank_itbbitmm.js index cc7e78ac3..a6685ccae 100644 --- a/src/app-gocardless/banks/isybank_itbbitmm.js +++ b/src/app-gocardless/banks/isybank_itbbitmm.js @@ -6,8 +6,6 @@ export default { institutionIds: ['ISYBANK_ITBBITMM'], - accessValidForDays: 180, - // It has been reported that valueDate is more accurate than booking date // when it is provided normalizeTransaction(transaction, booked) { diff --git a/src/app-gocardless/banks/kbc_kredbebb.js b/src/app-gocardless/banks/kbc_kredbebb.js index 6818a2f55..739734073 100644 --- a/src/app-gocardless/banks/kbc_kredbebb.js +++ b/src/app-gocardless/banks/kbc_kredbebb.js @@ -7,8 +7,6 @@ export default { institutionIds: ['KBC_KREDBEBB'], - accessValidForDays: 180, - /** * For negative amounts, the only payee information we have is returned in * remittanceInformationUnstructured. diff --git a/src/app-gocardless/banks/lhv-lhvbee22.js b/src/app-gocardless/banks/lhv-lhvbee22.js new file mode 100644 index 000000000..8f948c650 --- /dev/null +++ b/src/app-gocardless/banks/lhv-lhvbee22.js @@ -0,0 +1,40 @@ +import d from 'date-fns'; + +import Fallback from './integration-bank.js'; + +/** @type {import('./bank.interface.js').IBank} */ +export default { + ...Fallback, + + institutionIds: ['LHV_LHVBEE22'], + + normalizeTransaction(transaction, booked) { + // extract bookingDate and creditorName for card transactions, e.g. + // (..1234) 2025-01-02 09:32 CrustumOU\Poordi 3\Tallinn\10156 ESTEST + // bookingDate: 2025-01-02 + // creditorName: CrustumOU + const cardTxRegex = + /^\(\.\.(\d{4})\) (\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}) (.+)$/g; + const cardTxMatch = cardTxRegex.exec( + transaction?.remittanceInformationUnstructured, + ); + + if (cardTxMatch) { + const extractedDate = d.parse(cardTxMatch[2], 'yyyy-MM-dd', new Date()); + + transaction = { + ...transaction, + creditorName: cardTxMatch[4].split('\\')[0].trim(), + }; + + if (booked && d.isValid(extractedDate)) { + transaction = { + ...transaction, + bookingDate: d.format(extractedDate, 'yyyy-MM-dd'), + }; + } + } + + return Fallback.normalizeTransaction(transaction, booked); + }, +}; diff --git a/src/app-gocardless/banks/mbank_retail_brexplpw.js b/src/app-gocardless/banks/mbank_retail_brexplpw.js index 2f6e3c943..27764e40f 100644 --- a/src/app-gocardless/banks/mbank_retail_brexplpw.js +++ b/src/app-gocardless/banks/mbank_retail_brexplpw.js @@ -9,8 +9,6 @@ export default { institutionIds: ['MBANK_RETAIL_BREXPLPW'], - accessValidForDays: 179, - normalizeTransaction(transaction, _booked) { return { ...transaction, diff --git a/src/app-gocardless/banks/nbg_ethngraaxxx.js b/src/app-gocardless/banks/nbg_ethngraaxxx.js index b915ee3bd..3bdb4b465 100644 --- a/src/app-gocardless/banks/nbg_ethngraaxxx.js +++ b/src/app-gocardless/banks/nbg_ethngraaxxx.js @@ -9,8 +9,6 @@ export default { institutionIds: ['NBG_ETHNGRAAXXX'], - accessValidForDays: 180, - /** * Fixes for the pending transactions: * - Corrects amount to negative (nbg erroneously omits the minus sign in pending transactions) diff --git a/src/app-gocardless/banks/norwegian_xx_norwnok1.js b/src/app-gocardless/banks/norwegian_xx_norwnok1.js index 0a00a34e9..67900828b 100644 --- a/src/app-gocardless/banks/norwegian_xx_norwnok1.js +++ b/src/app-gocardless/banks/norwegian_xx_norwnok1.js @@ -16,8 +16,6 @@ export default { 'NORWEGIAN_FI_NORWNOK1', ], - accessValidForDays: 180, - normalizeTransaction(transaction, booked) { if (booked) { return { diff --git a/src/app-gocardless/banks/revolut_revolt21.js b/src/app-gocardless/banks/revolut_revolt21.js index 61b27c5a2..c2c440841 100644 --- a/src/app-gocardless/banks/revolut_revolt21.js +++ b/src/app-gocardless/banks/revolut_revolt21.js @@ -8,8 +8,6 @@ export default { institutionIds: ['REVOLUT_REVOLT21'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { if ( transaction.remittanceInformationUnstructuredArray[0].startsWith( diff --git a/src/app-gocardless/banks/sandboxfinance_sfin0000.js b/src/app-gocardless/banks/sandboxfinance_sfin0000.js index 0debc6030..65bdeafdb 100644 --- a/src/app-gocardless/banks/sandboxfinance_sfin0000.js +++ b/src/app-gocardless/banks/sandboxfinance_sfin0000.js @@ -9,8 +9,6 @@ export default { institutionIds: ['SANDBOXFINANCE_SFIN0000'], - accessValidForDays: 180, - /** * Following the GoCardless documentation[0] we should prefer `bookingDate` * here, though some of their bank integrations uses the date field diff --git a/src/app-gocardless/banks/seb_kort_bank_ab.js b/src/app-gocardless/banks/seb_kort_bank_ab.js index 3b465641f..c159a1f05 100644 --- a/src/app-gocardless/banks/seb_kort_bank_ab.js +++ b/src/app-gocardless/banks/seb_kort_bank_ab.js @@ -13,8 +13,6 @@ export default { 'SEB_CARD_ESSESESS', ], - accessValidForDays: 180, - /** * Sign of transaction amount needs to be flipped for SEB credit cards */ diff --git a/src/app-gocardless/banks/seb_privat.js b/src/app-gocardless/banks/seb_privat.js index 0ff079ebf..de6a4a416 100644 --- a/src/app-gocardless/banks/seb_privat.js +++ b/src/app-gocardless/banks/seb_privat.js @@ -10,8 +10,6 @@ export default { institutionIds: ['SEB_ESSESESS_PRIVATE'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { const date = transaction.bookingDate || diff --git a/src/app-gocardless/banks/sparnord_spnodk22.js b/src/app-gocardless/banks/sparnord_spnodk22.js index 37980fafb..5b3179cfe 100644 --- a/src/app-gocardless/banks/sparnord_spnodk22.js +++ b/src/app-gocardless/banks/sparnord_spnodk22.js @@ -12,8 +12,6 @@ export default { 'ANDELSKASSEN_FALLESKASSEN_FAELDKK1', ], - accessValidForDays: 180, - /** * Banks on the BEC backend only give information regarding the transaction in additionalInformation */ diff --git a/src/app-gocardless/banks/spk_karlsruhe_karsde66.js b/src/app-gocardless/banks/spk_karlsruhe_karsde66.js index 9b8e6a215..7974c52ae 100644 --- a/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +++ b/src/app-gocardless/banks/spk_karlsruhe_karsde66.js @@ -9,8 +9,6 @@ export default { institutionIds: ['SPK_KARLSRUHE_KARSDE66XXX'], - accessValidForDays: 180, - /** * Following the GoCardless documentation[0] we should prefer `bookingDate` * here, though some of their bank integrations uses the date field diff --git a/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js b/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js index 3491b131b..00b4fbe22 100644 --- a/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +++ b/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js @@ -10,8 +10,6 @@ export default { institutionIds: ['SPK_MARBURG_BIEDENKOPF_HELADEF1MAR'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { const date = transaction.bookingDate || diff --git a/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js b/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js index 0d38a0244..d2c153c50 100644 --- a/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +++ b/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js @@ -8,8 +8,6 @@ export default { institutionIds: ['SPK_WORMS_ALZEY_RIED_MALADE51WOR'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { const date = transaction.bookingDate || transaction.valueDate; if (!date) { diff --git a/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js b/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js index 2e320d366..1de9f4848 100644 --- a/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +++ b/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js @@ -6,8 +6,6 @@ export default { institutionIds: ['SSK_DUSSELDORF_DUSSDEDDXXX'], - accessValidForDays: 180, - normalizeTransaction(transaction, _booked) { // Prioritize unstructured information, falling back to structured formats let remittanceInformationUnstructured = diff --git a/src/app-gocardless/banks/swedbank_habalv22.js b/src/app-gocardless/banks/swedbank_habalv22.js index 8bea1360a..205bf0455 100644 --- a/src/app-gocardless/banks/swedbank_habalv22.js +++ b/src/app-gocardless/banks/swedbank_habalv22.js @@ -8,8 +8,6 @@ export default { institutionIds: ['SWEDBANK_HABALV22'], - accessValidForDays: 180, - /** * The actual transaction date for card transactions is only available in the remittanceInformationUnstructured field when the transaction is booked. */ diff --git a/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js b/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js new file mode 100644 index 000000000..9667ce226 --- /dev/null +++ b/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js @@ -0,0 +1,110 @@ +import CommerzbankCobadeff from '../commerzbank_cobadeff.js'; + +describe('CommerzbankCobadeff', () => { + describe('#normalizeTransaction', () => { + it('correctly formats remittanceInformationUnstructured', () => { + const transaction = { + endToEndId: '1234567890', + mandateId: '321654', + bookingDate: '2024-12-20', + valueDate: '2024-12-20', + transactionAmount: { + amount: '-12.34', + currency: 'EUR', + }, + creditorName: 'SHOP NAME CITY DE', + remittanceInformationUnstructured: + 'SHOP NAME//CITY/DE\n2024-12-19T15:34:31 KFN 1 AB 1234\nKartenzahlung', + remittanceInformationUnstructuredArray: [ + 'SHOP NAME//CITY/DE', + '2024-12-19T15:34:31 KFN 1 AB 1234', + 'Kartenzahlung', + ], + remittanceInformationStructured: + 'SHOP NAME//CITY/DE 2024-12-19T15:34:31 KFN 1 AB 1234 Kartenzahlung', + internalTransactionId: '3815213adb654baeadfb231c853', + }; + const normalizedTransaction = CommerzbankCobadeff.normalizeTransaction( + transaction, + false, + ); + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + '2024-12-19T15:34:31 KFN 1 AB 1234, Kartenzahlung', + ); + }); + + it('correctly formats remittanceInformationUnstructured; repair split keyword', () => { + const transaction = { + endToEndId: '901234567890', + mandateId: 'ABC123DEF456', + bookingDate: '2024-10-11', + valueDate: '2024-10-11', + transactionAmount: { + amount: '-56.78', + currency: 'EUR', + }, + creditorName: 'Long payee name that is eaxtly 35ch', + remittanceInformationUnstructured: + 'Long payee name that is eaxtly 35ch\n901234567890/. Long description tha\nt gets cut and is very long, did I\nmention it is long\nEnd-to-En', + remittanceInformationUnstructuredArray: [ + 'Long payee name that is eaxtly 35ch', + '901234567890/. Long description tha', + 't gets cut and is very long, did I', + 'mention it is long', + 'End-to-En', + 'd-Ref.: 901234567890', + 'Mandatsref: ABC123DEF456', + 'Gläubiger-ID:', + 'AB12CDE0000000000000000012', + 'SEPA-BASISLASTSCHRIFT wiederholend', + ], + remittanceInformationStructured: + 'Long payee name that is eaxtly 35ch 901234567890/. Long description tha t gets cut and is very long, did I mention it is long End-to-En', + internalTransactionId: '812354cfdea36465asdfe', + }; + const normalizedTransaction = CommerzbankCobadeff.normalizeTransaction( + transaction, + false, + ); + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + '901234567890/. Long description tha t gets cut and is very long, did I mention it is long, End-to-End-Ref.: 901234567890, Mandatsref: ABC123DEF456, Gläubiger-ID: AB12CDE0000000000000000012, SEPA-BASISLASTSCHRIFT wiederholend', + ); + }); + + it('correctly formats remittanceInformationUnstructured; removing NOTPROVIDED', () => { + const transaction = { + endToEndId: 'NOTPROVIDED', + bookingDate: '2024-12-02', + valueDate: '2024-12-02', + transactionAmount: { + amount: '-9', + currency: 'EUR', + }, + creditorName: 'CREDITOR NAME', + creditorAccount: { + iban: 'CREDITOR000IBAN', + }, + remittanceInformationUnstructured: + 'CREDITOR NAME\nCREDITOR00BIC\nCREDITOR000IBAN\nDESCRIPTION\nEnd-to-End-Ref.: NOTPROVIDED\nDauerauftrag', + remittanceInformationUnstructuredArray: [ + 'CREDITOR NAME', + 'CREDITOR00BIC', + 'CREDITOR000IBAN', + 'DESCRIPTION', + 'End-to-End-Ref.: NOTPROVIDED', + 'Dauerauftrag', + ], + remittanceInformationStructured: + 'CREDITOR NAME CREDITOR00BIC CREDITOR000IBAN DESCRIPTION End-to-End-Ref.: NOTPROVIDED Dauerauftrag', + internalTransactionId: 'f617dc31ab77622bf13d6c95d6dd8b4a', + }; + const normalizedTransaction = CommerzbankCobadeff.normalizeTransaction( + transaction, + false, + ); + expect(normalizedTransaction.remittanceInformationUnstructured).toEqual( + 'CREDITOR00BIC CREDITOR000IBAN DESCRIPTION, Dauerauftrag', + ); + }); + }); +}); diff --git a/src/app-gocardless/banks/tests/ing_ingddeff.spec.js b/src/app-gocardless/banks/tests/ing_ingddeff.spec.js index 156875bdf..f1279d3a4 100644 --- a/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +++ b/src/app-gocardless/banks/tests/ing_ingddeff.spec.js @@ -20,6 +20,7 @@ describe('IngIngddeff', () => { name: 'ING', bic: 'INGDDEFFXXX', transaction_total_days: '390', + max_access_valid_for_days: '90', countries: ['DE'], logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/ing.png', supported_payments: { @@ -59,6 +60,7 @@ describe('IngIngddeff', () => { 'single-payment': ['SCT'], }, transaction_total_days: '390', + max_access_valid_for_days: '90', }, mask: '5030', name: 'Girokonto (XXX 5030) EUR', diff --git a/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js b/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js index a38a61646..a61d580ee 100644 --- a/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +++ b/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js @@ -26,6 +26,7 @@ describe('IngPlIngbplpw', () => { name: 'ING', bic: 'INGBPLPW', transaction_total_days: '365', + max_access_valid_for_days: '90', countries: ['PL'], logo: 'https://cdn.nordigen.com/ais/ING_PL_INGBPLPW.png', supported_payments: {}, @@ -43,34 +44,35 @@ describe('IngPlIngbplpw', () => { it('returns normalized account data returned to Frontend', () => { const normalizedAccount = IngPlIngbplpw.normalizeAccount(accountRaw); expect(normalizedAccount).toMatchInlineSnapshot(` - { - "account_id": "d3eccc94-9536-48d3-98be-813f79199ee3", - "iban": "PL00000000000000000987654321", - "institution": { - "bic": "INGBPLPW", - "countries": [ - "PL", - ], - "id": "ING_PL_INGBPLPW", - "logo": "https://cdn.nordigen.com/ais/ING_PL_INGBPLPW.png", - "name": "ING", - "supported_features": [ - "access_scopes", - "business_accounts", - "card_accounts", - "corporate_accounts", - "pending_transactions", - "private_accounts", - ], - "supported_payments": {}, - "transaction_total_days": "365", - }, - "mask": "4321", - "name": "Current Account for Individuals (Retail) (XXX 4321) PLN", - "official_name": "Current Account for Individuals (Retail)", - "type": "checking", - } - `); + { + "account_id": "d3eccc94-9536-48d3-98be-813f79199ee3", + "iban": "PL00000000000000000987654321", + "institution": { + "bic": "INGBPLPW", + "countries": [ + "PL", + ], + "id": "ING_PL_INGBPLPW", + "logo": "https://cdn.nordigen.com/ais/ING_PL_INGBPLPW.png", + "max_access_valid_for_days": "90", + "name": "ING", + "supported_features": [ + "access_scopes", + "business_accounts", + "card_accounts", + "corporate_accounts", + "pending_transactions", + "private_accounts", + ], + "supported_payments": {}, + "transaction_total_days": "365", + }, + "mask": "4321", + "name": "Current Account for Individuals (Retail) (XXX 4321) PLN", + "official_name": "Current Account for Individuals (Retail)", + "type": "checking", + } + `); }); }); diff --git a/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js b/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js new file mode 100644 index 000000000..72e61fb38 --- /dev/null +++ b/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js @@ -0,0 +1,80 @@ +import LhvLhvbee22 from '../lhv-lhvbee22.js'; + +describe('#normalizeTransaction', () => { + const bookedCardTransaction = { + transactionId: '2025010300000000-1', + bookingDate: '2025-01-03', + valueDate: '2025-01-03', + transactionAmount: { + amount: '-22.99', + currency: 'EUR', + }, + creditorName: null, + remittanceInformationUnstructured: + '(..1234) 2025-01-02 09:32 CrustumOU\\Poordi 3\\Tallinn\\10156 ESTEST', + bankTransactionCode: 'PMNT-CCRD-POSD', + internalTransactionId: 'fa000f86afb2cc7678bcff0000000000', + }; + + it('extracts booked card transaction creditor name', () => { + expect( + LhvLhvbee22.normalizeTransaction(bookedCardTransaction, true) + .creditorName, + ).toEqual('CrustumOU'); + }); + + it('extracts booked card transaction date', () => { + expect( + LhvLhvbee22.normalizeTransaction(bookedCardTransaction, true).bookingDate, + ).toEqual('2025-01-02'); + + expect( + LhvLhvbee22.normalizeTransaction(bookedCardTransaction, true).date, + ).toEqual('2025-01-02'); + }); + + it.each([ + ['regular text', 'Some info'], + ['partial card text', 'PIRKUMS xxx'], + ['null value', null], + ['invalid date', '(..1234) 2025-13-45 09:32 Merchant\\Address'], + ])('normalizes non-card transaction with %s', (_, remittanceInfo) => { + const transaction = { + ...bookedCardTransaction, + remittanceInformationUnstructured: remittanceInfo, + }; + const normalized = LhvLhvbee22.normalizeTransaction(transaction, true); + + expect(normalized.bookingDate).toEqual('2025-01-03'); + expect(normalized.date).toEqual('2025-01-03'); + }); + + const pendingCardTransaction = { + transactionId: '2025010300000000-1', + valueDate: '2025-01-03', + transactionAmount: { + amount: '-22.99', + currency: 'EUR', + }, + remittanceInformationUnstructured: + '(..1234) 2025-01-02 09:32 CrustumOU\\Poordi 3\\Tallinn\\10156 ESTEST', + }; + + it('extracts pending card transaction creditor name', () => { + expect( + LhvLhvbee22.normalizeTransaction(pendingCardTransaction, false) + .creditorName, + ).toEqual('CrustumOU'); + }); + + it('extracts pending card transaction date', () => { + expect( + LhvLhvbee22.normalizeTransaction(pendingCardTransaction, false) + .bookingDate, + ).toEqual(undefined); + + expect( + LhvLhvbee22.normalizeTransaction(pendingCardTransaction, false).date, + ).toEqual('2025-01-03'); + }); +}); diff --git a/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js b/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js index d4113df8f..b99eb1b69 100644 --- a/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +++ b/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js @@ -26,6 +26,7 @@ describe('MbankRetailBrexplpw', () => { name: 'mBank Retail', bic: 'BREXPLPW', transaction_total_days: '90', + max_access_valid_for_days: '90', countries: ['PL'], logo: 'https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png', supported_payments: {}, @@ -42,34 +43,35 @@ describe('MbankRetailBrexplpw', () => { it('returns normalized account data returned to Frontend', () => { expect(MbankRetailBrexplpw.normalizeAccount(accountRaw)) .toMatchInlineSnapshot(` - { - "account_id": "d3eccc94-9536-48d3-98be-813f79199ee3", - "iban": "PL00000000000000000987654321", - "institution": { - "bic": "BREXPLPW", - "countries": [ - "PL", - ], - "id": "MBANK_RETAIL_BREXPLPW", - "logo": "https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png", - "name": "mBank Retail", - "supported_features": [ - "access_scopes", - "business_accounts", - "card_accounts", - "corporate_accounts", - "pending_transactions", - "private_accounts", - ], - "supported_payments": {}, - "transaction_total_days": "90", - }, - "mask": "4321", - "name": "EKONTO (XXX 4321) PLN", - "official_name": "RACHUNEK BIEŻĄCY", - "type": "checking", - } - `); + { + "account_id": "d3eccc94-9536-48d3-98be-813f79199ee3", + "iban": "PL00000000000000000987654321", + "institution": { + "bic": "BREXPLPW", + "countries": [ + "PL", + ], + "id": "MBANK_RETAIL_BREXPLPW", + "logo": "https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png", + "max_access_valid_for_days": "90", + "name": "mBank Retail", + "supported_features": [ + "access_scopes", + "business_accounts", + "card_accounts", + "corporate_accounts", + "pending_transactions", + "private_accounts", + ], + "supported_payments": {}, + "transaction_total_days": "90", + }, + "mask": "4321", + "name": "EKONTO (XXX 4321) PLN", + "official_name": "RACHUNEK BIEŻĄCY", + "type": "checking", + } + `); }); }); diff --git a/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js b/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js index a0ac23c1f..af3612c0c 100644 --- a/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +++ b/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js @@ -22,6 +22,7 @@ describe('SandboxfinanceSfin0000', () => { name: 'Sandbox Finance', bic: 'SFIN0000', transaction_total_days: '90', + max_access_valid_for_days: '90', countries: ['XX'], logo: 'https://cdn.nordigen.com/ais/SANDBOXFINANCE_SFIN0000.png', supported_payments: {}, @@ -32,27 +33,28 @@ describe('SandboxfinanceSfin0000', () => { it('returns normalized account data returned to Frontend', () => { expect(SandboxfinanceSfin0000.normalizeAccount(accountRaw)) .toMatchInlineSnapshot(` - { - "account_id": "99a0bfe2-0bef-46df-bff2-e9ae0c6c5838", - "iban": "GL0865354374424724", - "institution": { - "bic": "SFIN0000", - "countries": [ - "XX", - ], - "id": "SANDBOXFINANCE_SFIN0000", - "logo": "https://cdn.nordigen.com/ais/SANDBOXFINANCE_SFIN0000.png", - "name": "Sandbox Finance", - "supported_features": [], - "supported_payments": {}, - "transaction_total_days": "90", - }, - "mask": "4724", - "name": "Main Account (XXX 4724) EUR", - "official_name": "Checkings", - "type": "checking", - } - `); + { + "account_id": "99a0bfe2-0bef-46df-bff2-e9ae0c6c5838", + "iban": "GL0865354374424724", + "institution": { + "bic": "SFIN0000", + "countries": [ + "XX", + ], + "id": "SANDBOXFINANCE_SFIN0000", + "logo": "https://cdn.nordigen.com/ais/SANDBOXFINANCE_SFIN0000.png", + "max_access_valid_for_days": "90", + "name": "Sandbox Finance", + "supported_features": [], + "supported_payments": {}, + "transaction_total_days": "90", + }, + "mask": "4724", + "name": "Main Account (XXX 4724) EUR", + "official_name": "Checkings", + "type": "checking", + } + `); }); }); diff --git a/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js b/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js index bf3bf365b..7cac1ab81 100644 --- a/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +++ b/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js @@ -22,6 +22,7 @@ describe('SpkMarburgBiedenkopfHeladef1mar', () => { name: 'Sparkasse Marburg-Biedenkopf', bic: 'HELADEF1MAR', transaction_total_days: '360', + max_access_valid_for_days: '90', countries: ['DE'], logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/sparkasse.png', supported_payments: { @@ -57,6 +58,7 @@ describe('SpkMarburgBiedenkopfHeladef1mar', () => { 'single-payment': ['SCT', 'ISCT'], }, transaction_total_days: '360', + max_access_valid_for_days: '90', }, mask: '6789', name: 'Sichteinlagen (XXX 6789) EUR', diff --git a/src/app-gocardless/gocardless-node.types.ts b/src/app-gocardless/gocardless-node.types.ts index 539c91e4c..c7d3809b9 100644 --- a/src/app-gocardless/gocardless-node.types.ts +++ b/src/app-gocardless/gocardless-node.types.ts @@ -253,6 +253,12 @@ export type Institution = { */ logo: string; + /** + * The total number of days that a requisition stays valid before requiring + * renewal + */ + max_access_valid_for_days: string; + supported_payments?: object; supported_features?: string[]; }; @@ -356,7 +362,11 @@ export type Transaction = { /** * Account reference, conditional */ - creditorAccount?: string; + creditorAccount?: + | string + | { + iban?: string; + }; /** * BICFI diff --git a/src/app-gocardless/services/gocardless-service.js b/src/app-gocardless/services/gocardless-service.js index 754e36d11..81e5c77d5 100644 --- a/src/app-gocardless/services/gocardless-service.js +++ b/src/app-gocardless/services/gocardless-service.js @@ -313,7 +313,6 @@ export const goCardlessService = { await goCardlessService.setToken(); const institution = await goCardlessService.getInstitution(institutionId); - const bank = BankFactory(institutionId); let response; try { @@ -321,7 +320,7 @@ export const goCardlessService = { redirectUrl: host + '/gocardless/link', institutionId, referenceId: uuid.v4(), - accessValidForDays: bank.accessValidForDays, + accessValidForDays: institution.max_access_valid_for_days, maxHistoricalDays: BANKS_WITH_LIMITED_HISTORY.includes(institutionId) ? Number(institution.transaction_total_days) >= 90 ? '89' diff --git a/src/app-gocardless/services/tests/fixtures.js b/src/app-gocardless/services/tests/fixtures.js index 5e373e5a3..40c34501a 100644 --- a/src/app-gocardless/services/tests/fixtures.js +++ b/src/app-gocardless/services/tests/fixtures.js @@ -104,6 +104,7 @@ export const mockInstitution = { name: 'N26 Bank', bic: 'NTSBDEB1', transaction_total_days: '90', + max_access_valid_for_days: '90', countries: ['GB', 'NO', 'SE'], logo: 'https://cdn.nordigen.com/ais/N26_SANDBOX_NTSBDEB1.png', }; diff --git a/src/app.js b/src/app.js index 72db69fd1..4fe2c738e 100644 --- a/src/app.js +++ b/src/app.js @@ -23,6 +23,7 @@ process.on('unhandledRejection', (reason) => { app.disable('x-powered-by'); app.use(cors()); +app.set('trust proxy', config.trustedProxies); app.use( rateLimit({ windowMs: 60 * 1000, diff --git a/src/config-types.ts b/src/config-types.ts index 3feecc9ec..464c2dc3f 100644 --- a/src/config-types.ts +++ b/src/config-types.ts @@ -1,9 +1,13 @@ import { ServerOptions } from 'https'; +type LoginMethod = 'password' | 'header' | 'openid'; + export interface Config { mode: 'test' | 'development'; - loginMethod: 'password' | 'header' | 'openid'; + loginMethod: LoginMethod; + allowedLoginMethods: LoginMethod[]; trustedProxies: string[]; + trustedAuthProxies?: string[]; dataDir: string; projectRoot: string; port: number; diff --git a/src/load-config.js b/src/load-config.js index 9c8ee34f9..f87e2b6cc 100644 --- a/src/load-config.js +++ b/src/load-config.js @@ -54,7 +54,8 @@ if (process.env.ACTUAL_CONFIG_PATH) { /** @type {Omit} */ let defaultConfig = { loginMethod: 'password', - // assume local networks are trusted for header authentication + allowedLoginMethods: ['password', 'header', 'openid'], + // assume local networks are trusted trustedProxies: [ '10.0.0.0/8', '172.16.0.0/12', @@ -62,6 +63,9 @@ let defaultConfig = { 'fc00::/7', '::1/128', ], + // fallback to trustedProxies, but in the future trustedProxies will only be used for express trust + // and trustedAuthProxies will just be for header auth + trustedAuthProxies: null, port: 5006, hostname: '::', webRoot: path.join( @@ -116,9 +120,21 @@ const finalConfig = { return value === 'true'; })() : config.multiuser, + allowedLoginMethods: process.env.ACTUAL_ALLOWED_LOGIN_METHODS + ? process.env.ACTUAL_ALLOWED_LOGIN_METHODS.split(',') + .map((q) => q.trim().toLowerCase()) + .filter(Boolean) + : config.allowedLoginMethods, trustedProxies: process.env.ACTUAL_TRUSTED_PROXIES - ? process.env.ACTUAL_TRUSTED_PROXIES.split(',').map((q) => q.trim()) + ? process.env.ACTUAL_TRUSTED_PROXIES.split(',') + .map((q) => q.trim()) + .filter(Boolean) : config.trustedProxies, + trustedAuthProxies: process.env.ACTUAL_TRUSTED_AUTH_PROXIES + ? process.env.ACTUAL_TRUSTED_AUTH_PROXIES.split(',') + .map((q) => q.trim()) + .filter(Boolean) + : config.trustedAuthProxies, port: +process.env.ACTUAL_PORT || +process.env.PORT || config.port, hostname: process.env.ACTUAL_HOSTNAME || config.hostname, serverFiles: process.env.ACTUAL_SERVER_FILES || config.serverFiles, @@ -208,6 +224,11 @@ debug(`using user files directory ${finalConfig.userFiles}`); debug(`using web root directory ${finalConfig.webRoot}`); debug(`using login method ${finalConfig.loginMethod}`); debug(`using trusted proxies ${finalConfig.trustedProxies.join(', ')}`); +debug( + `using trusted auth proxies ${ + finalConfig.trustedAuthProxies?.join(', ') ?? 'same as trusted proxies' + }`, +); if (finalConfig.https) { debug(`using https key: ${'*'.repeat(finalConfig.https.key.length)}`); diff --git a/src/util/validate-user.js b/src/util/validate-user.js index a84389e6f..934bc9806 100644 --- a/src/util/validate-user.js +++ b/src/util/validate-user.js @@ -1,5 +1,4 @@ import config from '../load-config.js'; -import proxyaddr from 'proxy-addr'; import ipaddr from 'ipaddr.js'; import { getSession } from '../account-db.js'; @@ -45,24 +44,23 @@ export default function validateSession(req, res) { } export function validateAuthHeader(req) { - if (config.trustedProxies.length == 0) { - return true; - } - - let sender = proxyaddr(req, 'uniquelocal'); - let sender_ip = ipaddr.process(sender); + // fallback to trustedProxies when trustedAuthProxies not set + const trustedAuthProxies = config.trustedAuthProxies ?? config.trustedProxies; + // ensure the first hop from our server is trusted + let peer = req.socket.remoteAddress; + let peerIp = ipaddr.process(peer); const rangeList = { - allowed_ips: config.trustedProxies.map((q) => ipaddr.parseCIDR(q)), + allowed_ips: trustedAuthProxies.map((q) => ipaddr.parseCIDR(q)), }; /* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore : there is an error in the ts definition for the function, but this is valid - var matched = ipaddr.subnetMatch(sender_ip, rangeList, 'fail'); + var matched = ipaddr.subnetMatch(peerIp, rangeList, 'fail'); /* eslint-enable @typescript-eslint/ban-ts-comment */ if (matched == 'allowed_ips') { - console.info(`Header Auth Login permitted from ${sender}`); + console.info(`Header Auth Login permitted from ${peer}`); return true; } else { - console.warn(`Header Auth Login attempted from ${sender}`); + console.warn(`Header Auth Login attempted from ${peer}`); return false; } } diff --git a/upcoming-release-notes/499.md b/upcoming-release-notes/499.md new file mode 100644 index 000000000..6c41db698 --- /dev/null +++ b/upcoming-release-notes/499.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [twk3] +--- + +Fix the auth proxy trust by ensuring the proxy is in the trust diff --git a/upcoming-release-notes/537.md b/upcoming-release-notes/537.md new file mode 100644 index 000000000..0d34ffdf3 --- /dev/null +++ b/upcoming-release-notes/537.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [nsulzer] +--- + +Add GoCardless integration for COMMERZBANK_COBADEFF \ No newline at end of file diff --git a/upcoming-release-notes/542.md b/upcoming-release-notes/542.md new file mode 100644 index 000000000..8b20a730d --- /dev/null +++ b/upcoming-release-notes/542.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [lnagel] +--- + +Add GoCardless formatter for LHV Estonia (`LHV_LHVBEE22`). diff --git a/upcoming-release-notes/546.md b/upcoming-release-notes/546.md new file mode 100644 index 000000000..4ca7cb84e --- /dev/null +++ b/upcoming-release-notes/546.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [Knocks83] +--- + +Add health check section to the existing `docker-compose.yml` file. diff --git a/upcoming-release-notes/547.md b/upcoming-release-notes/547.md new file mode 100644 index 000000000..19922aec3 --- /dev/null +++ b/upcoming-release-notes/547.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [ihhha] +--- + +Add "Caixa Geral De Depositos" PT to banks with limited history diff --git a/upcoming-release-notes/550.md b/upcoming-release-notes/550.md new file mode 100644 index 000000000..fe13d172d --- /dev/null +++ b/upcoming-release-notes/550.md @@ -0,0 +1,6 @@ +--- +category: Features +authors: [sergiofmreis] +--- + +Add support for `ABANCA_CORP_CAGLPTPL` payee name diff --git a/upcoming-release-notes/551.md b/upcoming-release-notes/551.md new file mode 100644 index 000000000..c335d9f55 --- /dev/null +++ b/upcoming-release-notes/551.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [matt-fidd] +--- + +Use the maximum access validity time provided by GoCardless diff --git a/upcoming-release-notes/554.md b/upcoming-release-notes/554.md new file mode 100644 index 000000000..41b0fdb20 --- /dev/null +++ b/upcoming-release-notes/554.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [matt-fidd] +--- + +Add handler for `direkt_heladef1822`