commit cfa8d17b80d4e5bdb87f02e95cccff1f3003a700 parent d092b8c29f5b8e30d4d4a4fae32e6f2d99090f16 Author: Sebastian <sebasjm@gmail.com> Date: Sun, 6 Jul 2025 03:01:09 -0300 fix #10108 Diffstat:
12 files changed, 269 insertions(+), 195 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx @@ -65,7 +65,7 @@ function with_defaults(id?: string): Partial<Entity> { }; } -type TokenForm = { accessControl: boolean; token: string; repeat: string }; +type TokenForm = { accessControl: boolean; password: string; repeat: string }; export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { const [pref, updatePref] = usePreference(); @@ -124,10 +124,10 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { const hasErrors = errors !== undefined; const tokenFormErrors = undefinedIfEmpty<FormErrors<TokenForm>>({ - token: + password: tokenForm.accessControl === false ? undefined - : !tokenForm.token + : !tokenForm.password ? i18n.str`Required` : undefined, repeat: @@ -135,7 +135,7 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { ? undefined : !tokenForm.repeat ? i18n.str`Required` - : tokenForm.repeat !== tokenForm.token + : tokenForm.repeat !== tokenForm.password ? i18n.str`Doesn't match` : undefined, }); @@ -152,7 +152,7 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { ? { method: MerchantAuthMethod.EXTERNAL } : { method: MerchantAuthMethod.TOKEN, - token: createRFC8959AccessTokenPlain(tokenForm.token!), + password: tokenForm.password!, }; if (!newValue.address) newValue.address = {}; if (!newValue.jurisdiction) newValue.jurisdiction = {}; @@ -217,7 +217,7 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { help={i18n.str`Choose if the backend server should authenticate access.`} /> <Input<TokenForm> - name="token" + name="password" label={i18n.str`New password`} tooltip={i18n.str`Next password to be used`} readonly={ diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/InstanceCreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/InstanceCreatedSuccessfully.tsx @@ -60,23 +60,6 @@ export function InstanceCreatedSuccessfully({ </div> </div> </div> - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - <i18n.Translate>Password</i18n.Translate> - </label> - </div> - <div class="field-body is-flex-grow-3"> - <div class="field"> - <p class="control"> - {entity.auth.method === "external" && "external"} - {entity.auth.method === "token" && ( - <input class="input" readonly value={entity.auth.token} /> - )} - </p> - </div> - </div> - </div> </CreatedSuccessfully> ); } diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx @@ -58,14 +58,15 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode { }); return; } - if (d.auth.token) { + if (d.auth.password) { //if auth has been updated, request a new access token - const result = await lib.instance.createAuthTokenFromToken( - d.auth.token, + const result = await lib.instance.createAccessToken( + d.id, + d.auth.password, FOREVER_REFRESHABLE_TOKEN, ); if (result.type === "ok") { - const { token } = result.body; + const { access_token:token } = result.body; logIn(state.instance, token); } } diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx @@ -34,7 +34,7 @@ import { useManagedInstanceDetails, } from "../../../hooks/instance.js"; import { Notification } from "../../../utils/types.js"; -import { FOREVER_REFRESHABLE_TOKEN, LoginPage } from "../../login/index.js"; +import { FOREVER_REFRESHABLE_TOKEN, LoginPage, TEMP_TEST_TOKEN } from "../../login/index.js"; import { NotFoundPageOrAdminCreate } from "../../notfound/index.js"; import { DetailPage } from "./DetailPage.js"; @@ -46,16 +46,28 @@ export interface Props { export default function Token(props: Props): VNode { const { lib, state, logIn } = useSessionContext(); const result = useInstanceDetails(); + const instanceId = state.instance; async function changeToken( - currentToken: AccessToken | undefined, - newToken: AccessToken, + currentPassword: AccessToken | undefined, + newPassword: AccessToken, ) { + if (currentPassword) { + const resp = await lib.instance.createAccessToken( + instanceId, + currentPassword, + TEMP_TEST_TOKEN, + ); + if (resp.type !== "ok") { + throw Error(resp.detail?.hint ?? "The current password is wrong"); + } + } + { const resp = await lib.instance.updateCurrentInstanceAuthentication( - currentToken, + state.token, { - token: newToken, + password: newPassword, method: MerchantAuthMethod.TOKEN, }, ); @@ -63,20 +75,21 @@ export default function Token(props: Props): VNode { throw Error(resp.detail?.hint ?? "The request failed"); } } - const resp = await lib.instance.createAuthTokenFromToken(newToken, FOREVER_REFRESHABLE_TOKEN); + + const resp = await lib.instance.createAccessToken( + instanceId, + newPassword, + FOREVER_REFRESHABLE_TOKEN, + ); if (resp.type === "ok") { - logIn(state.instance, resp.body.token); + logIn(state.instance, resp.body.access_token); return; } else { throw Error(resp.detail?.hint ?? "The new login failed"); } } - return CommonToken( - { ...props, instanceId: state.instance }, - result, - changeToken, - ); + return CommonToken({ ...props, instanceId }, result, changeToken); } export function AdminToken(props: Props & { instanceId: string }): VNode { @@ -85,16 +98,29 @@ export function AdminToken(props: Props & { instanceId: string }): VNode { const subInstanceLib = lib.subInstanceApi(props.instanceId).instance; const result = useManagedInstanceDetails(props.instanceId); + const instanceId = props.instanceId; + async function changeToken( - currentToken: AccessToken | undefined, - newToken: AccessToken, + currentPassword: AccessToken | undefined, + newPassword: AccessToken, ) { + if (currentPassword) { + const resp = await lib.instance.createAccessToken( + instanceId, + currentPassword, + TEMP_TEST_TOKEN, + ); + if (resp.type !== "ok") { + throw Error(resp.detail?.hint ?? "The current password is wrong"); + } + } + { const resp = await lib.instance.updateInstanceAuthentication( state.token, props.instanceId, { - token: newToken, + password: newPassword, method: MerchantAuthMethod.TOKEN, }, ); @@ -102,8 +128,9 @@ export function AdminToken(props: Props & { instanceId: string }): VNode { throw Error(resp.detail?.hint ?? "The request failed"); } } - const resp = await subInstanceLib.createAuthTokenFromToken( - newToken, + const resp = await subInstanceLib.createAccessToken( + instanceId, + newPassword, FOREVER_REFRESHABLE_TOKEN, ); if (resp.type === "ok") { @@ -154,6 +181,7 @@ function CommonToken( result.body.auth.method === MerchantAuthMethod.TOKEN && !adminChangingPwdForAnotherInstance; + const id = result.body.name; return ( <Fragment> <NotificationCard notification={notif} /> diff --git a/packages/merchant-backoffice-ui/src/paths/login/index.tsx b/packages/merchant-backoffice-ui/src/paths/login/index.tsx @@ -20,9 +20,10 @@ */ import { + Duration, HttpStatusCode, - TokenRequest, - createRFC8959AccessTokenEncoded, + LoginTokenRequest, + LoginTokenScope } from "@gnu-taler/taler-util"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; @@ -34,17 +35,19 @@ import { Notification } from "../../utils/types.js"; interface Props {} +export const TEMP_TEST_TOKEN = { + scope: LoginTokenScope.All, + duration: Duration.toTalerProtocolDuration(Duration.fromMilliseconds(100)), +} as LoginTokenRequest; + export const FOREVER_REFRESHABLE_TOKEN = { - scope: "write", - duration: { - d_us: "forever" as const, - }, - refreshable: true, -} as TokenRequest; + scope: LoginTokenScope.All_Refreshable, + duration: Duration.toTalerProtocolDuration(Duration.getForever()), +} as LoginTokenRequest; const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; export function LoginPage(_p: Props): VNode { - const [token, setToken] = useState(""); + const [password, setPassword] = useState(""); const [notif, setNotif] = useState<Notification | undefined>(undefined); const { lib, state, logIn, getInstanceForUsername } = useSessionContext(); const [username, setUsername] = useState(state.instance); @@ -52,14 +55,15 @@ export function LoginPage(_p: Props): VNode { const { i18n } = useTranslationContext(); async function doLoginImpl() { - const api = getInstanceForUsername(username) + const api = getInstanceForUsername(username); - const result = await api.createAuthTokenFromToken( - createRFC8959AccessTokenEncoded(token), + const result = await api.createAccessToken( + username, + password, FOREVER_REFRESHABLE_TOKEN, ); if (result.type === "ok") { - const { token } = result.body; + const { access_token: token } = result.body; logIn(username, token); return; } else { @@ -145,8 +149,10 @@ export function LoginPage(_p: Props): VNode { onKeyPress={(e) => e.keyCode === 13 ? doLoginImpl() : null } - value={token} - onInput={(e): void => setToken(e?.currentTarget.value)} + value={password} + onInput={(e): void => + setPassword(e?.currentTarget.value) + } /> </p> </div> diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -28,6 +28,7 @@ import { EddsaPrivP, HttpStatusCode, Logger, + LoginTokenScope, MerchantAuthMethod, PaytoString, TalerBankConversionHttpClient, @@ -208,12 +209,12 @@ testingCli const merchantBaseUrl = "https://backend.test.taler.net/instances/sandbox/"; const merchantApi = new TalerMerchantInstanceHttpClient(merchantBaseUrl); const tokResp = succeedOrThrow( - await merchantApi.createAuthTokenFromToken("secret-token:sandbox", { - scope: "write", + await merchantApi.createAccessToken("sandbox", "sandbox", { + scope: LoginTokenScope.All, }), ); - const tok: AccessToken = tokResp.token; + const tok: AccessToken = tokResp.access_token; const createResp = succeedOrThrow( await merchantApi.createOrder(tok, { @@ -952,7 +953,7 @@ deploymentCli address: {}, auth: { method: MerchantAuthMethod.TOKEN, - token: createRFC8959AccessTokenPlain(password), + password: password, }, default_pay_delay: Duration.toTalerProtocolDuration( Duration.fromSpec({ hours: 1 }), @@ -978,23 +979,26 @@ deploymentCli } } + const { access_token } = succeedOrThrow( + await merchantManager.createAccessToken(id, password, { + scope: LoginTokenScope.All, + }), + ); + let wireAccount: string; /** * link bank account and merchant */ { - 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, - }, + const resp = await merchantInstance.addBankAccount(access_token, { + 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}`, @@ -1112,12 +1116,18 @@ deploymentCli } } + const { access_token } = succeedOrThrow( + await merchantManager.createAccessToken(id, prevPassword, { + scope: LoginTokenScope.All, + }), + ); + { const resp = await merchantInstance.updateCurrentInstanceAuthentication( - createRFC8959AccessTokenEncoded(prevPassword), + access_token, { method: MerchantAuthMethod.TOKEN, - token: createRFC8959AccessTokenPlain(randomPassword), + password: randomPassword, }, ); if (resp.type === "fail") { @@ -1178,7 +1188,7 @@ deploymentCli }) .requiredArgument("merchantApiBaseUrl", clk.STRING) .requiredOption("managementToken", ["--management-token"], clk.STRING) - .requiredOption("instanceToken", ["--instance-token"], clk.STRING) + .requiredOption("instancePassword", ["--instance-password"], clk.STRING) .requiredOption("name", ["--name"], clk.STRING) .requiredOption("id", ["--id"], clk.STRING) .requiredOption("payto", ["--payto"], clk.STRING) @@ -1201,12 +1211,7 @@ deploymentCli const managementToken = createRFC8959AccessTokenEncoded( args.provisionMerchantInstance.managementToken, ); - const instanceTokenEnc = createRFC8959AccessTokenPlain( - args.provisionMerchantInstance.instanceToken, - ); - const instanceTokenPlain = createRFC8959AccessTokenPlain( - args.provisionMerchantInstance.instanceToken, - ); + const instancePassword = args.provisionMerchantInstance.instancePassword; const instanceId = args.provisionMerchantInstance.id; const instancceName = args.provisionMerchantInstance.name; const bankURL = args.provisionMerchantInstance.bankURL; @@ -1236,7 +1241,7 @@ deploymentCli address: {}, auth: { method: MerchantAuthMethod.TOKEN, - token: instanceTokenPlain, + password: instancePassword, }, default_pay_delay: Duration.toTalerProtocolDuration(defaultPayDelay), default_wire_transfer_delay: Duration.toTalerProtocolDuration( @@ -1266,21 +1271,24 @@ deploymentCli httpLib, ); - const createAccountResp = await instanceApi.addBankAccount( - instanceTokenEnc, - { - payto_uri: accountPayto, - credit_facade_url: bankURL, - credit_facade_credentials: - bankUser && bankPassword - ? { - type: "basic", - username: bankUser, - password: bankPassword, - } - : undefined, - }, + const { access_token } = succeedOrThrow( + await instanceApi.createAccessToken(instanceId, instancePassword, { + scope: LoginTokenScope.All, + }), ); + + const createAccountResp = await instanceApi.addBankAccount(access_token, { + payto_uri: accountPayto, + credit_facade_url: bankURL, + 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}`, diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts @@ -107,7 +107,7 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) { await merchantManagementClient.updateCurrentInstanceAuthentication(auth, { method: MerchantAuthMethod.TOKEN, - token: "secret-token:foobar" as AccessToken, + password: "foobar", }); // Check that deleting an instance checks the auth diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances-urls.ts @@ -20,6 +20,7 @@ import { AccessToken, Duration, + LoginTokenScope, MerchantAuthMethod, succeedOrThrow, TalerMerchantManagementHttpClient, @@ -76,32 +77,41 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) { name: "My Admin Instance", auth: { method: MerchantAuthMethod.TOKEN, - token: "secret-token:i-am-admin" as AccessToken, + password: "i-am-admin", }, }), ); + const { access_token: adminToken } = succeedOrThrow( + await clientForAdminInst.createAccessToken("admin", "i-am-admin", { + scope: LoginTokenScope.All, + }), + ); + succeedOrThrow( - await clientForAdminInst.createInstance( - "secret-token:i-am-admin" as AccessToken, - { - id: "myinst", - address: {}, - default_pay_delay: Duration.toTalerProtocolDuration( - Duration.fromSpec({ seconds: 60 }), - ), - use_stefan: true, - default_wire_transfer_delay: Duration.toTalerProtocolDuration( - Duration.fromSpec({ seconds: 60 }), - ), - jurisdiction: {}, - name: "My Second Instance", - auth: { - method: MerchantAuthMethod.TOKEN, - token: "secret-token:i-am-myinst" as AccessToken, - }, + await clientForAdminInst.createInstance(adminToken, { + id: "myinst", + address: {}, + default_pay_delay: Duration.toTalerProtocolDuration( + Duration.fromSpec({ seconds: 60 }), + ), + use_stefan: true, + default_wire_transfer_delay: Duration.toTalerProtocolDuration( + Duration.fromSpec({ seconds: 60 }), + ), + jurisdiction: {}, + name: "My Second Instance", + auth: { + method: MerchantAuthMethod.TOKEN, + password: "i-am-myinst", }, - ), + }), + ); + + const { access_token: myInstToken } = succeedOrThrow( + await clientForAdminInst.createAccessToken("myinst", "i-am-myinst", { + scope: LoginTokenScope.All, + }), ); async function check(url: string, token: string, expectedStatus: number) { @@ -116,13 +126,11 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) { t.assertDeepEqual(resp.status, expectedStatus); } - const tokAdmin = "secret-token:i-am-admin"; - const adminInstBaseUrl = merchant.makeInstanceBaseUrl(); await check( `${adminInstBaseUrl}private/instances/admin/instances/admin/config`, - tokAdmin, + adminToken, 404, ); @@ -154,31 +162,31 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) { await check( `${adminInstBaseUrl}private/instances/myinst/config`, - tokAdmin, + adminToken, 404, ); await check( `${adminInstBaseUrl}instances/myinst/private/orders`, - tokAdmin, + adminToken, 401, ); await check( `${adminInstBaseUrl}instances/myinst/private/orders`, - tokAdmin, + adminToken, 401, ); await check( `${adminInstBaseUrl}instances/myinst/private/orders`, - "secret-token:i-am-myinst", + myInstToken, 200, ); await check( `${adminInstBaseUrl}private/instances/myinst/orders`, - tokAdmin, + adminToken, 404, ); } diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances.ts @@ -20,6 +20,7 @@ import { AccessToken, HttpStatusCode, + LoginTokenScope, MerchantAuthMethod, succeedOrThrow, TalerMerchantManagementHttpClient, @@ -142,16 +143,20 @@ export async function runMerchantInstancesTest(t: GlobalTestState) { await merchantClient.updateCurrentInstanceAuthentication(undefined, { method: MerchantAuthMethod.TOKEN, - token: "secret-token:foobar" as AccessToken, + password: "foobar", }); + const { access_token: auth } = succeedOrThrow( + await merchantClient.createAccessToken("admin", "foobar", { + scope: LoginTokenScope.All, + }), + ); + console.log("requesting instances with no auth"); const exc = await merchantClient.listInstances(undefined); t.assertTrue(exc.type === "fail"); t.assertTrue(exc.case === HttpStatusCode.Unauthorized); - const auth = "secret-token:foobar" as AccessToken; - // With the new client auth settings, request should work again. succeedOrThrow(await merchantClient.listInstances(auth)); diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -20,6 +20,7 @@ import { FailCasesByMethod, HttpStatusCode, LibtoolVersion, + LoginTokenRequest, OperationAlternative, OperationFail, OperationOk, @@ -29,8 +30,6 @@ import { TalerErrorCode, TalerMerchantApi, TalerMerchantConfigResponse, - TokenRequest, - TokenSuccessResponseMerchant, codecForAbortResponse, codecForAccountAddResponse, codecForAccountKycRedirects, @@ -41,6 +40,7 @@ import { codecForClaimResponse, codecForInstancesResponse, codecForInventorySummaryResponse, + codecForLoginTokenSuccessResponse, codecForMerchantOrderPrivateStatusResponse, codecForMerchantPosProductDetail, codecForMerchantRefundResponse, @@ -64,7 +64,7 @@ import { codecForTemplateSummaryResponse, codecForTokenFamiliesList, codecForTokenFamilyDetails, - codecForTokenSuccessResponseMerchant, + codecForTokenInfoList, codecForWalletRefundResponse, codecForWalletTemplateDetails, codecForWebhookDetails, @@ -85,6 +85,7 @@ import { opSuccessFromHttp } from "../operation.js"; import { CacheEvictor, addPaginationParams, + makeBasicAuthHeader, makeBearerTokenAuthHeader, nullEvictor, } from "./utils.js"; @@ -222,34 +223,76 @@ export class TalerMerchantInstanceHttpClient { // /** - * Create an auth token from a login token. - * - * See https://bugs.gnunet.org/view.php?id=9556 to explain - * this weirdness. + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-token * - * In the future, we'll only create auth tokens from login - * credentials. */ - async createAuthTokenFromToken( - token: string, - body: TokenRequest, - ): Promise< - | OperationOk<TokenSuccessResponseMerchant> - | OperationFail<HttpStatusCode.NotFound> - | OperationFail<HttpStatusCode.Unauthorized> - > { + async createAccessToken(instance: string, password: string, body: LoginTokenRequest) { const url = new URL(`private/token`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", headers: { - Authorization: makeBearerTokenAuthHeader(token as AccessToken), + Authorization: makeBasicAuthHeader(instance, password), }, body, }); switch (resp.status) { case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForTokenSuccessResponseMerchant()); - //FIXME: missing in docs + return opSuccessFromHttp(resp, codecForLoginTokenSuccessResponse()); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-tokens + * + */ + async listAccessTokens(token: AccessToken, params: PaginationParams = {}) { + const url = new URL(`private/tokens`, this.baseUrl); + addPaginationParams(url, params); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + Authorization: makeBearerTokenAuthHeader(token), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForTokenInfoList()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ tokens: [] }); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Forbidden: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-tokens-$SERIAL + * + */ + async deleteAcessToken(token: AccessToken) { + const url = new URL(`private/token`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "DELETE", + headers: { + Authorization: makeBearerTokenAuthHeader(token), + }, + }); + switch (resp.status) { + case HttpStatusCode.NoContent: + return opEmptySuccess(); + case HttpStatusCode.Forbidden: + return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: diff --git a/packages/taler-util/src/types-taler-common.ts b/packages/taler-util/src/types-taler-common.ts @@ -360,17 +360,15 @@ export const codecForCoinHistoryResponse = () => .property("history", codecForAny()) .build("CoinHistoryResponse"); -export type TokenScope = BankTokenScope | MerchantTokenScope; export type BankTokenScope = | "readonly" | "readwrite" | "revenue" | "wiregateway"; -export type MerchantTokenScope = "write"; export interface TokenRequest { // Service-defined scope for the token. // Typical scopes would be "readonly" or "readwrite". - scope: TokenScope; + scope: BankTokenScope; // Server may impose its own upper bound // on the token validity duration @@ -393,20 +391,6 @@ export interface TokenSuccessResponse { // Opque access token. access_token: AccessToken; } -export interface TokenSuccessResponseMerchant { - // Expiration determined by the server. - // Can be based on the token_duration - // from the request, but ultimately the - // server decides the expiration. - expiration: Timestamp; - - // Opque access token. - token: AccessToken; - - scope: TokenScope; - - refreshable: boolean; -} export interface TokenInfos { tokens: TokenInfo[]; @@ -423,7 +407,7 @@ export interface TokenInfo { expiration: Timestamp; // Scope for the token. - scope: TokenScope; + scope: string; // Is the token refreshable into a new token during its // validity? @@ -473,22 +457,6 @@ export const codecForTokenSuccessResponse = (): Codec<TokenSuccessResponse> => .property("expiration", codecForTimestamp) .build("TalerAuthentication.TokenSuccessResponse"); -export const codecForTokenSuccessResponseMerchant = - (): Codec<TokenSuccessResponseMerchant> => - buildCodecForObject<TokenSuccessResponseMerchant>() - .property("token", codecForAccessToken()) - .property("expiration", codecForTimestamp) - .property( - "scope", - codecForEither( - codecForConstString("readonly"), - codecForConstString("write"), - codecForConstString("revenue"), - codecForConstString("wiregateway"), - ), - ) - .property("refreshable", codecForBoolean()) - .build("TalerAuthentication.TokenSuccessResponseMerchant"); // FIXME: implement this codec export const codecForURN = codecForString; diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts @@ -1620,7 +1620,7 @@ export interface InstanceAuthConfigurationMessage { // After the auth token has been set (with method "token"), // the value must be provided in a "Authorization: Bearer $token" // header. - token?: AccessToken; + // token?: AccessToken; // Since **v19**: For method "token", this field is mandatory. // Authentication against the /private/token endpoint @@ -1630,14 +1630,20 @@ export interface InstanceAuthConfigurationMessage { password?: string; } -export type LoginTokenScope = - | "readonly" - | "write" - | "all" - | "order-simple" - | "order-pos" - | "order-mgmt" - | "order-full"; +export enum LoginTokenScope { + ReadOnly = "readonly", + All = "all", + OrderSimple = "order-simple", + OrderPos = "order-pos", + OrderManagement = "order-mgmt", + OrderFull = "order-full", + ReadOnly_Refreshable = "readonly:refreshable", + All_Refreshable = "all:refreshable", + OrderSimple_Refreshable = "order-simple:refreshable", + OrderPos_Refreshable = "order-pos:refreshable", + OrderManagement_Refreshable = "order-mgmt:refreshable", + OrderFull_Refreshable = "order-full:refreshable", +} export interface LoginTokenRequest { // Scope of the token (which kinds of operations it will allow) @@ -1650,7 +1656,7 @@ export interface LoginTokenRequest { // Can this token be refreshed? // Defaults to false. Deprecated since **v19**. // Use ":refreshable" scope prefix instead. - refreshable?: boolean; + // refreshable?: boolean; } export interface LoginTokenSuccessResponse { @@ -1806,7 +1812,7 @@ export interface QueryInstancesResponse { export enum MerchantAuthMethod { EXTERNAL = "external", TOKEN = "token", - PASSWORD = "password", + // PASSWORD = "password", } export interface MerchantAccountKycRedirectsResponse { @@ -3737,6 +3743,24 @@ export const codecForMerchantAccountKycRedirect = .property("payto_kycauths", codecOptional(codecForList(codecForString()))) .build("TalerMerchantApi.MerchantAccountKycRedirect"); +export const codecForTokenScope = codecForEither( + codecForConstString(LoginTokenScope.All), + codecForConstString(LoginTokenScope.OrderFull), + codecForConstString(LoginTokenScope.OrderManagement), + codecForConstString(LoginTokenScope.OrderPos), + codecForConstString(LoginTokenScope.OrderSimple), + codecForConstString(LoginTokenScope.ReadOnly), +); + +export const codecForLoginTokenSuccessResponse = + (): Codec<LoginTokenSuccessResponse> => + buildCodecForObject<LoginTokenSuccessResponse>() + .property("scope", codecForTokenScope) + .property("access_token", codecForAccessToken()) + .property("expiration", codecForTimestamp) + .property("refreshable", codecForBoolean()) + .build("TalerMerchantApi.LoginTokenSuccessResponse"); + export const codecForExchangeKycTimeout = (): Codec<ExchangeKycTimeout> => buildCodecForObject<ExchangeKycTimeout>() .property("exchange_url", codecForURLString())