diff options
Diffstat (limited to 'packages/taler-harness/src/index.ts')
-rw-r--r-- | packages/taler-harness/src/index.ts | 440 |
1 files changed, 289 insertions, 151 deletions
diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts index 4b0319a3e..99b5502d8 100644 --- a/packages/taler-harness/src/index.ts +++ b/packages/taler-harness/src/index.ts @@ -30,10 +30,11 @@ import { TalerAuthenticationHttpClient, TalerBankConversionHttpClient, TalerCoreBankHttpClient, - TalerErrorCode, TalerMerchantInstanceHttpClient, TalerMerchantManagementHttpClient, TransactionsResponse, + createRFC8959AccessTokenEncoded, + createRFC8959AccessTokenPlain, decodeCrock, encodeCrock, generateIban, @@ -41,8 +42,7 @@ import { randomBytes, rsaBlind, setGlobalLogLevelFromString, - setPrintHttpRequestAsCurl, - stringifyPayTemplateUri + stringifyPayTemplateUri, } from "@gnu-taler/taler-util"; import { clk } from "@gnu-taler/taler-util/clk"; import { @@ -56,7 +56,7 @@ import { } from "@gnu-taler/taler-wallet-core"; import { downloadExchangeInfo, - topupReserveWithDemobank, + topupReserveWithBank, } from "@gnu-taler/taler-wallet-core/dbless"; import { deepStrictEqual } from "assert"; import fs from "fs"; @@ -79,7 +79,6 @@ import { } from "./harness/helpers.js"; import { getTestInfo, runTests } from "./integrationtests/testrunner.js"; import { lintExchangeDeployment } from "./lint.js"; -import { randomUUID } from "crypto"; const logger = new Logger("taler-harness:index.ts"); @@ -355,25 +354,46 @@ advancedCli ); }); -const configCli = testingCli.subcommand("configArgs", "config", { - help: "Subcommands for handling the Taler configuration.", -}); +const configCli = testingCli + .subcommand("configArgs", "config", { + help: "Subcommands for handling the Taler configuration.", + }) + .maybeOption("configEntryFile", ["-c", "--config"], clk.STRING, { + help: "Configuration file to use.", + }) + .maybeOption("project", ["--project"], clk.STRING, { + help: `Selection of the project to inspect/change the config (default: taler).`, + }); -configCli.subcommand("show", "show").action(async (args) => { - const config = Configuration.load(); - const cfgStr = config.stringify({ - diagnostics: true, +configCli + .subcommand("show", "show", { + help: "Show the current configuration.", + }) + .action(async (args) => { + const config = Configuration.load( + args.configArgs.configEntryFile, + args.configArgs.project, + ); + const cfgStr = config.stringify({ + diagnostics: true, + }); + console.log(cfgStr); }); - console.log(cfgStr); -}); configCli - .subcommand("get", "get") + .subcommand("get", "get", { + help: "Get a configuration option.", + }) .requiredArgument("section", clk.STRING) .requiredArgument("option", clk.STRING) - .flag("file", ["-f"]) + .flag("file", ["-f"], { + help: "Treat the value as a filename, expanding placeholders.", + }) .action(async (args) => { - const config = Configuration.load(); + const config = Configuration.load( + args.configArgs.configEntryFile, + args.configArgs.project, + ); let res; if (args.get.file) { res = config.getPath(args.get.section, args.get.option); @@ -388,6 +408,35 @@ configCli } }); +configCli + .subcommand("set", "set", { + help: "Set a configuration option.", + }) + .requiredArgument("section", clk.STRING) + .requiredArgument("option", clk.STRING) + .requiredArgument("value", clk.STRING) + .flag("dry", ["--dry"], { + help: "Do not write the changed config to disk, only write it to stdout.", + }) + .action(async (args) => { + const config = Configuration.load( + args.configArgs.configEntryFile, + args.configArgs.project, + ); + config.setString(args.set.section, args.set.option, args.set.value); + if (args.set.dry) { + console.log( + config.stringify({ + excludeDefaults: true, + }), + ); + } else { + config.write({ + excludeDefaults: true, + }); + } + }); + const deploymentCli = testingCli.subcommand("deploymentArgs", "deployment", { help: "Subcommands for handling GNU Taler deployments.", }); @@ -403,7 +452,7 @@ deploymentCli const reserveKeyPair = await cryptoApi.createEddsaKeypair({}); const exchangeBaseUrl = "https://exchange.demo.taler.net/"; const exchangeInfo = await downloadExchangeInfo(exchangeBaseUrl, http); - await topupReserveWithDemobank({ + await topupReserveWithBank({ amount: "KUDOS:10" as AmountString, corebankApiBaseUrl: "https://bank.demo.taler.net/", exchangeInfo, @@ -431,7 +480,7 @@ deploymentCli const reserveKeyPair = await cryptoApi.createEddsaKeypair({}); const exchangeBaseUrl = "https://exchange.test.taler.net/"; const exchangeInfo = await downloadExchangeInfo(exchangeBaseUrl, http); - await topupReserveWithDemobank({ + await topupReserveWithBank({ amount: "TESTKUDOS:10" as AmountString, corebankApiBaseUrl: "https://bank.test.taler.net/", exchangeInfo, @@ -460,7 +509,7 @@ deploymentCli const reserveKeyPair = await cryptoApi.createEddsaKeypair({}); const exchangeBaseUrl = "http://localhost:8081/"; const exchangeInfo = await downloadExchangeInfo(exchangeBaseUrl, http); - await topupReserveWithDemobank({ + await topupReserveWithBank({ amount: "TESTKUDOS:10" as AmountString, corebankApiBaseUrl: "http://localhost:8082/taler-bank-access/", exchangeInfo, @@ -601,65 +650,99 @@ deploymentCli help: "Provision a bank account, merchant instance and link them together.", }) .requiredArgument("merchantApiBaseUrl", clk.STRING, { - help: "URL location of the merchant backend" + help: "URL location of the merchant backend", }) .requiredArgument("corebankApiBaseUrl", clk.STRING, { - help: "URL location of the libeufin bank backend" - }) - .requiredOption("merchantToken", ["--merchant-management-token"], clk.STRING, { - help: "acces token of the default instance in the merchant backend" + help: "URL location of the libeufin bank backend", }) + .requiredOption( + "merchantToken", + ["--merchant-management-token"], + clk.STRING, + { + help: "access token of the default instance in the merchant backend", + }, + ) .maybeOption("bankToken", ["--bank-admin-token"], clk.STRING, { - help: "libeufin bank admin's password if the account creation is restricted" + help: "libeufin bank admin's token if the account creation is restricted", + }) + .maybeOption("bankPassword", ["--bank-admin-password"], clk.STRING, { + help: "libeufin bank admin's password if the account creation is restricted, it will override --bank-admin-token", }) .requiredOption("name", ["--legal-name"], clk.STRING, { - help: "legal name of the merchant" + help: "legal name of the merchant", }) .maybeOption("email", ["--email"], clk.STRING, { - help: "email contact of the merchant" + help: "email contact of the merchant", }) .maybeOption("phone", ["--phone"], clk.STRING, { - help: "phone contact of the merchant" + help: "phone contact of the merchant", }) .requiredOption("id", ["--id"], clk.STRING, { - help: "login id for the bank account and instance id of the merchant backend" + help: "login id for the bank account and instance id of the merchant backend", }) .flag("template", ["--create-template"], { - help: "use this flag to create a default template for the merchant with fixed summary" + help: "use this flag to create a default template for the merchant with fixed summary", }) .requiredOption("password", ["--password"], clk.STRING, { - help: "password of the accounts in libeufin bank and merchant backend" + help: "password of the accounts in libeufin bank and merchant backend", }) .flag("randomPassword", ["--set-random-password"], { - help: "if everything worked ok, change the password of the accounts at the end" + help: "if everything worked ok, change the password of the accounts at the end", }) .action(async (args) => { - const managementToken = args.provisionBankMerchant.merchantToken as AccessToken; - const bankAdminPassword = args.provisionBankMerchant.bankToken as AccessToken; + const managementToken = createRFC8959AccessTokenPlain( + args.provisionBankMerchant.merchantToken, + ); + const bankAdminPassword = args.provisionBankMerchant.bankPassword; + const bankAdminTokenArg = args.provisionBankMerchant.bankToken + ? createRFC8959AccessTokenPlain(args.provisionBankMerchant.bankToken) + : undefined; const id = args.provisionBankMerchant.id; const name = args.provisionBankMerchant.name; const email = args.provisionBankMerchant.email; const phone = args.provisionBankMerchant.phone; const password = args.provisionBankMerchant.password; - const httpLib = createPlatformHttpLib({}); - const merchantManager = new TalerMerchantManagementHttpClient(args.provisionBankMerchant.merchantApiBaseUrl, httpLib); - const bank = new TalerCoreBankHttpClient(args.provisionBankMerchant.corebankApiBaseUrl, httpLib); - const instanceURL = merchantManager.getSubInstanceAPI(id).href - const merchantInstance = new TalerMerchantInstanceHttpClient(instanceURL, httpLib); - const conv = new TalerBankConversionHttpClient(bank.getConversionInfoAPI().href, httpLib) - const bankAuth = new TalerAuthenticationHttpClient(bank.getAuthenticationAPI(id).href, httpLib) - + const merchantManager = new TalerMerchantManagementHttpClient( + args.provisionBankMerchant.merchantApiBaseUrl, + httpLib, + ); + const bank = new TalerCoreBankHttpClient( + args.provisionBankMerchant.corebankApiBaseUrl, + httpLib, + ); + const instanceURL = merchantManager.getSubInstanceAPI(id).href; + const merchantInstance = new TalerMerchantInstanceHttpClient( + instanceURL, + httpLib, + ); + const conv = new TalerBankConversionHttpClient( + bank.getConversionInfoAPI().href, + httpLib, + ); + const bankAuth = new TalerAuthenticationHttpClient( + bank.getAuthenticationAPI(id).href, + httpLib, + ); - const bc = await bank.getConfig() + const bc = await bank.getConfig(); + if (bc.type === "fail") { + logger.error(`couldn't get bank config. ${bc.detail.hint}`); + return; + } if (!bank.isCompatible(bc.body.version)) { logger.error( `bank server version is not compatible: ${bc.body.version}, client version: ${bank.PROTOCOL_VERSION}`, ); return; } - const mc = await merchantManager.getConfig() + const mc = await merchantManager.getConfig(); + if (mc.type === "fail") { + logger.error(`couldn't get merchant config. ${mc.detail.hint}`); + return; + } if (!merchantManager.isCompatible(mc.body.version)) { logger.error( `merchant server version is not compatible: ${mc.body.version}, client version: ${merchantManager.PROTOCOL_VERSION}`, @@ -667,27 +750,59 @@ deploymentCli return; } + let bankAdminToken: AccessToken | undefined; + if (bankAdminPassword) { + const adminAuth = new TalerAuthenticationHttpClient( + bank.getAuthenticationAPI("admin").href, + httpLib, + ); + + const resp = await adminAuth.createAccessTokenBasic( + "admin", + bankAdminPassword, + { + scope: "write", + duration: { + d_us: 1000 * 1000 * 10, //10 secs + }, + refreshable: false, + }, + ); + if (resp.type === "fail") { + logger.error(`could not get bank admin token from password.`); + return; + } + bankAdminToken = resp.body.access_token; + } else { + bankAdminToken = bankAdminTokenArg; + } + /** * create bank account */ let accountPayto: PaytoString; { - const resp = await bank.createAccount(bankAdminPassword, { + const resp = await bank.createAccount(bankAdminToken, { name: name, password: password, username: id, - contact_data: email || phone ? { - email: email, - phone: phone, - } : undefined, - }) + contact_data: + email || phone + ? { + email: email, + phone: phone, + } + : undefined, + }); if (resp.type === "fail") { - logger.error(`unable to provision bank account, HTTP response status ${resp.case}`); + logger.error( + `unable to provision bank account, HTTP response status ${resp.case}`, + ); process.exit(2); } logger.info(`account ${id} successfully provisioned`); - accountPayto = resp.body.internal_payto_uri + accountPayto = resp.body.internal_payto_uri; } /** @@ -698,7 +813,7 @@ deploymentCli address: {}, auth: { method: "token", - token: `secret-token:${password}`, + token: createRFC8959AccessTokenPlain(password), }, default_pay_delay: Duration.toTalerProtocolDuration( Duration.fromSpec({ hours: 1 }), @@ -710,7 +825,7 @@ deploymentCli jurisdiction: {}, name: name, use_stefan: true, - }) + }); if (resp.type === "ok") { logger.info(`instance ${id} created successfully`); @@ -729,21 +844,26 @@ deploymentCli * link bank account and merchant */ { - const resp = await merchantInstance.addAccount(password as AccessToken, { - payto_uri: accountPayto, - credit_facade_url: bank.getRevenueAPI(id).href, - credit_facade_credentials: { - type: "basic", - username: id, - password: password, - } - }) + const resp = await merchantInstance.addBankAccount( + createRFC8959AccessTokenEncoded(password), + { + payto_uri: accountPayto, + credit_facade_url: bank.getRevenueAPI(id).href, + credit_facade_credentials: { + type: "basic", + username: id, + password: password, + }, + }, + ); if (resp.type === "fail") { - console.error(`unable to configure bank account for instance ${id}, status ${resp.case}`) + console.error( + `unable to configure bank account for instance ${id}, status ${resp.case}`, + ); console.error(j2s(resp.detail)); process.exit(2); } - wireAccount = resp.body.h_wire + wireAccount = resp.body.h_wire; } logger.info(`successfully configured bank account for ${id}`); @@ -757,32 +877,38 @@ deploymentCli if (bc.body.allow_conversion) { const cc = await conv.getConfig(); if (cc.type === "ok") { - currency = cc.body.fiat_currency + currency = cc.body.fiat_currency; } else { - console.error( - `could not get fiat currency status ${cc.case}`, - ); + console.error(`could not get fiat currency status ${cc.case}`); console.error(j2s(cc.detail)); } } else { - console.log(`conversion is disabled, using bank currency`) + console.log(`conversion is disabled, using bank currency`); } { - const resp = await merchantInstance.addTemplate(password as AccessToken, { - template_id: "default", - template_description: "First template", - template_contract: { - pay_duration: Duration.toTalerProtocolDuration( - Duration.fromSpec({ hours: 1 }), - ), - minimum_age: 0, - currency, - summary: "Pay me!" - } - }) + const resp = await merchantInstance.addTemplate( + createRFC8959AccessTokenEncoded(password), + { + template_id: "default", + template_description: "First template", + template_contract: { + pay_duration: Duration.toTalerProtocolDuration( + Duration.fromSpec({ hours: 1 }), + ), + minimum_age: 0, + currency, + summary: "Pay me!", + }, + editable_defaults: { + amount: currency, + }, + }, + ); if (resp.type === "fail") { - console.error(`unable to create template for insntaince ${id}, status ${resp.case}`) + console.error( + `unable to create template for insntaince ${id}, status ${resp.case}`, + ); console.error(j2s(resp.detail)); process.exit(2); } @@ -792,26 +918,27 @@ deploymentCli templateURI = stringifyPayTemplateUri({ merchantBaseUrl: instanceURL, templateId: "default", - templateParams: { - amount: currency - } - }) + }); } let finalPassword = password; if (args.provisionBankMerchant.randomPassword) { - const prevPassword = password as AccessToken + const prevPassword = password; const randomPassword = encodeCrock(randomBytes(16)); - logger.info("random password: ", randomPassword) + logger.info("random password: ", randomPassword); let token: AccessToken; { const resp = await bankAuth.createAccessTokenBasic(id, prevPassword, { scope: "readwrite", - duration: Duration.toTalerProtocolDuration(Duration.fromSpec({ minutes: 1 })), + duration: Duration.toTalerProtocolDuration( + Duration.fromSpec({ minutes: 1 }), + ), refreshable: false, - }) + }); if (resp.type === "fail") { - console.error(`unable to login into bank accountfor user ${id}, status ${resp.case}`) + console.error( + `unable to login into bank accountfor user ${id}, status ${resp.case}`, + ); console.error(j2s(resp.detail)); process.exit(2); } @@ -819,42 +946,56 @@ deploymentCli } { - const resp = await bank.updatePassword({ username: id, token }, { - old_password: prevPassword, - new_password: randomPassword, - }); + const resp = await bank.updatePassword( + { username: id, token }, + { + old_password: prevPassword, + new_password: randomPassword, + }, + ); if (resp.type === "fail") { - console.error(`unable to change bank pasword for user ${id}, status ${resp.case}`) + console.error( + `unable to change bank password for user ${id}, status ${resp.case}`, + ); if (resp.case !== HttpStatusCode.Accepted) { console.error(j2s(resp.detail)); } else { - console.error("2FA required") + console.error("2FA required"); } process.exit(2); } } { - const resp = await merchantInstance.updateCurrentInstanceAuthentication(prevPassword, { - method: "token", - token: `secret-token:${randomPassword}` as AccessToken - }) + const resp = await merchantInstance.updateCurrentInstanceAuthentication( + createRFC8959AccessTokenEncoded(prevPassword), + { + method: "token", + token: createRFC8959AccessTokenPlain(randomPassword), + }, + ); if (resp.type === "fail") { - console.error(`unable to change merchant password for instance ${id}, status ${resp.case}`) + console.error( + `unable to change merchant password for instance ${id}, status ${resp.case}`, + ); console.error(j2s(resp.detail)); process.exit(2); } } { - const resp = await merchantInstance.updateAccount(randomPassword as AccessToken, wireAccount, { - credit_facade_url: bank.getRevenueAPI(id).href, - credit_facade_credentials: { - type: "basic", - username: id, - password: randomPassword, - } - }) + const resp = await merchantInstance.updateBankAccount( + createRFC8959AccessTokenEncoded(randomPassword), + wireAccount, + { + credit_facade_url: bank.getRevenueAPI(id).href, + credit_facade_credentials: { + type: "basic", + username: id, + password: randomPassword, + }, + }, + ); if (resp.type != "ok") { console.error( `unable to update bank account for instance ${id}, status ${resp.case}`, @@ -870,17 +1011,21 @@ deploymentCli /** * show result */ - console.log(JSON.stringify({ - bankUser: id, - bankURL: args.provisionBankMerchant.corebankApiBaseUrl, - merchantURL: instanceURL, - templateURI, - password: finalPassword, - }, undefined, 2)) - + console.log( + JSON.stringify( + { + bankUser: id, + bankURL: args.provisionBankMerchant.corebankApiBaseUrl, + merchantURL: instanceURL, + templateURI, + password: finalPassword, + }, + undefined, + 2, + ), + ); }); - deploymentCli .subcommand("provisionMerchantInstance", "provision-merchant-instance", { help: "Provision a merchant backend instance.", @@ -897,9 +1042,16 @@ deploymentCli .action(async (args) => { const httpLib = createPlatformHttpLib({}); const baseUrl = args.provisionMerchantInstance.merchantApiBaseUrl; - const api = new TalerMerchantManagementHttpClient(baseUrl, httpLib) - const managementToken = args.provisionMerchantInstance.managementToken as AccessToken; - const instanceToken = args.provisionMerchantInstance.instanceToken as AccessToken; + const api = new TalerMerchantManagementHttpClient(baseUrl, httpLib); + const managementToken = createRFC8959AccessTokenEncoded( + args.provisionMerchantInstance.managementToken, + ); + const instanceTokenEnc = createRFC8959AccessTokenPlain( + args.provisionMerchantInstance.instanceToken, + ); + const instanceTokenPlain = createRFC8959AccessTokenPlain( + args.provisionMerchantInstance.instanceToken, + ); const instanceId = args.provisionMerchantInstance.id; const instancceName = args.provisionMerchantInstance.name; const bankURL = args.provisionMerchantInstance.bankURL; @@ -911,7 +1063,7 @@ deploymentCli address: {}, auth: { method: "token", - token: `secret-token:${instanceToken}`, + token: instanceTokenPlain, }, default_pay_delay: Duration.toTalerProtocolDuration( Duration.fromSpec({ hours: 1 }), @@ -921,7 +1073,7 @@ deploymentCli jurisdiction: {}, name: instancceName, use_stefan: true, - }) + }); if (createResp.type === "ok") { logger.info(`instance ${instanceId} created successfully`); @@ -934,15 +1086,18 @@ deploymentCli process.exit(2); } - const createAccountResp = await api.addAccount(instanceToken, { + const createAccountResp = await api.addBankAccount(instanceTokenEnc, { payto_uri: accountPayto, credit_facade_url: bankURL, - credit_facade_credentials: bankUser && bankPassword ? { - type: "basic", - username: bankUser, - password: bankPassword, - } : undefined - }) + credit_facade_credentials: + bankUser && bankPassword + ? { + type: "basic", + username: bankUser, + password: bankPassword, + } + : undefined, + }); if (createAccountResp.type != "ok") { console.error( `unable to configure bank account for instance ${instanceId}, status ${createAccountResp.case}`, @@ -977,7 +1132,7 @@ deploymentCli is_public: !!args.provisionBankAccount.public, is_taler_exchange: !!args.provisionBankAccount.exchange, payto_uri: args.provisionBankAccount.internalPayto as PaytoString, - }) + }); if (resp.type === "ok") { logger.info(`account ${accountLogin} successfully provisioned`); @@ -1043,23 +1198,6 @@ deploymentCli console.log(out); }); -const deploymentConfigCli = deploymentCli.subcommand("configArgs", "config", { - help: "Subcommands the Taler configuration.", -}); - -deploymentConfigCli - .subcommand("show", "show") - .flag("diagnostics", ["-d", "--diagnostics"]) - .maybeArgument("cfgfile", clk.STRING, {}) - .action(async (args) => { - const cfg = Configuration.load(args.show.cfgfile); - console.log( - cfg.stringify({ - diagnostics: args.show.diagnostics, - }), - ); - }); - testingCli.subcommand("logtest", "logtest").action(async (args) => { logger.trace("This is a trace message."); logger.info("This is an info message."); |