summaryrefslogtreecommitdiff
path: root/packages/taler-harness/src/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-harness/src/index.ts')
-rw-r--r--packages/taler-harness/src/index.ts440
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.");