commit f25215a33118c82260ced14f4867f081d786be1e
parent 22563a4f960716ff3a112f19f40bf521087c6225
Author: Sebastian <sebasjm@gmail.com>
Date: Sun, 22 Jun 2025 19:53:18 -0300
fix #10120
Diffstat:
4 files changed, 234 insertions(+), 211 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/components/modal/index.tsx b/packages/merchant-backoffice-ui/src/components/modal/index.tsx
@@ -767,192 +767,192 @@ export function PurgeModal({
);
}
-interface UpdateTokenModalProps {
- oldToken?: string;
- onCancel: () => void;
- onConfirm: (value: string) => void;
- onClear: () => void;
-}
-
-//FIXME: merge UpdateTokenModal with SetTokenNewInstanceModal
-export function UpdateTokenModal({
- onCancel,
- onClear,
- onConfirm,
- oldToken,
-}: UpdateTokenModalProps): VNode {
- type State = { old_token: string; new_token: string; repeat_token: string };
- const [form, setValue] = useState<Partial<State>>({
- old_token: "",
- new_token: "",
- repeat_token: "",
- });
- const { i18n } = useTranslationContext();
-
- const hasInputTheCorrectOldToken = oldToken && oldToken !== form.old_token;
- const errors = undefinedIfEmpty({
- old_token: hasInputTheCorrectOldToken
- ? i18n.str`Is not the same as the current password`
- : undefined,
- new_token: !form.new_token
- ? i18n.str`Required`
- : form.new_token === form.old_token
- ? i18n.str`Can't be the same as the old token`
- : undefined,
- repeat_token:
- form.new_token !== form.repeat_token
- ? i18n.str`Is not the same`
- : undefined,
- });
-
- const hasErrors = errors !== undefined;
-
- const { state } = useSessionContext();
-
- const text = i18n.str`You are updating the password for the instance with ID ${state.instance}`;
-
- return (
- <ClearConfirmModal
- description={text}
- onCancel={onCancel}
- onConfirm={!hasErrors ? () => onConfirm(form.new_token!) : undefined}
- onClear={!hasInputTheCorrectOldToken && oldToken ? onClear : undefined}
- >
- <div class="columns">
- <div class="column" />
- <div class="column is-four-fifths">
- <FormProvider errors={errors} object={form} valueHandler={setValue}>
- {oldToken && (
- <Input<State>
- name="old_token"
- label={i18n.str`Old password`}
- tooltip={i18n.str`Password currently in use`}
- inputType="password"
- />
- )}
- <Input<State>
- name="new_token"
- label={i18n.str`New password`}
- tooltip={i18n.str`Next password to be used`}
- inputType="password"
- />
- <Input<State>
- name="repeat_token"
- label={i18n.str`Repeat password`}
- tooltip={i18n.str`Confirm the same password`}
- inputType="password"
- />
- </FormProvider>
- <p>
- <i18n.Translate>
- Clearing the password will mean public access to the instance
- </i18n.Translate>
- </p>
- </div>
- <div class="column" />
- </div>
- </ClearConfirmModal>
- );
-}
-
-export function SetTokenNewInstanceModal({
- onCancel,
- onClear,
- onConfirm,
-}: UpdateTokenModalProps): VNode {
- type State = { old_token: string; new_token: string; repeat_token: string };
- const [form, setValue] = useState<Partial<State>>({
- new_token: "",
- repeat_token: "",
- });
- const { i18n } = useTranslationContext();
-
- const errors = undefinedIfEmpty({
- new_token: !form.new_token
- ? i18n.str`Required`
- : form.new_token === form.old_token
- ? i18n.str`Can't be the same as the old password`
- : undefined,
- repeat_token:
- form.new_token !== form.repeat_token
- ? i18n.str`Is not the same`
- : undefined,
- });
-
- const hasErrors = errors !== undefined;
-
- return (
- <div class="modal is-active">
- <div class="modal-background " onClick={onCancel} />
- <div class="modal-card">
- <header class="modal-card-head">
- <p class="modal-card-title">{i18n.str`You are setting the password for the new instance`}</p>
- <button class="delete " aria-label="close" onClick={onCancel} />
- </header>
- <section class="modal-card-body is-main-section">
- <div class="columns">
- <div class="column" />
- <div class="column is-four-fifths">
- <FormProvider
- errors={errors}
- object={form}
- valueHandler={setValue}
- >
- <Input<State>
- name="new_token"
- label={i18n.str`New password`}
- tooltip={i18n.str`Next password to be used`}
- inputType="password"
- />
- <Input<State>
- name="repeat_token"
- label={i18n.str`Repeat password`}
- tooltip={i18n.str`Confirm the same password`}
- inputType="password"
- />
- </FormProvider>
- <p>
- <i18n.Translate>
- With external authorization method no check will be done by
- the merchant backend
- </i18n.Translate>
- </p>
- </div>
- <div class="column" />
- </div>
- </section>
- <footer class="modal-card-foot">
- {onClear && (
- <button
- class="button is-danger"
- onClick={onClear}
- disabled={onClear === undefined}
- >
- <i18n.Translate>Set external authorization</i18n.Translate>
- </button>
- )}
- <div class="buttons is-right" style={{ width: "100%" }}>
- <button class="button " onClick={onCancel}>
- <i18n.Translate>Cancel</i18n.Translate>
- </button>
- <button
- class="button is-info"
- onClick={() => onConfirm(form.new_token!)}
- disabled={hasErrors}
- >
- <i18n.Translate>Set password</i18n.Translate>
- </button>
- </div>
- </footer>
- </div>
- <button
- class="modal-close is-large "
- aria-label="close"
- onClick={onCancel}
- />
- </div>
- );
-}
+// interface UpdateTokenModalProps {
+// oldToken?: string;
+// onCancel: () => void;
+// onConfirm: (value: string) => void;
+// onClear: () => void;
+// }
+
+// //FIXME: merge UpdateTokenModal with SetTokenNewInstanceModal
+// export function UpdateTokenModal({
+// onCancel,
+// onClear,
+// onConfirm,
+// oldToken,
+// }: UpdateTokenModalProps): VNode {
+// type State = { old_token: string; new_token: string; repeat_token: string };
+// const [form, setValue] = useState<Partial<State>>({
+// old_token: "",
+// new_token: "",
+// repeat_token: "",
+// });
+// const { i18n } = useTranslationContext();
+
+// const hasInputTheCorrectOldToken = oldToken && oldToken !== form.old_token;
+// const errors = undefinedIfEmpty({
+// old_token: hasInputTheCorrectOldToken
+// ? i18n.str`Is not the same as the current password`
+// : undefined,
+// new_token: !form.new_token
+// ? i18n.str`Required`
+// : form.new_token === form.old_token
+// ? i18n.str`Can't be the same as the old token`
+// : undefined,
+// repeat_token:
+// form.new_token !== form.repeat_token
+// ? i18n.str`Is not the same`
+// : undefined,
+// });
+
+// const hasErrors = errors !== undefined;
+
+// const { state } = useSessionContext();
+
+// const text = i18n.str`You are updating the password for the instance with ID ${state.instance}`;
+
+// return (
+// <ClearConfirmModal
+// description={text}
+// onCancel={onCancel}
+// onConfirm={!hasErrors ? () => onConfirm(form.new_token!) : undefined}
+// onClear={!hasInputTheCorrectOldToken && oldToken ? onClear : undefined}
+// >
+// <div class="columns">
+// <div class="column" />
+// <div class="column is-four-fifths">
+// <FormProvider errors={errors} object={form} valueHandler={setValue}>
+// {oldToken && (
+// <Input<State>
+// name="old_token"
+// label={i18n.str`Old password`}
+// tooltip={i18n.str`Password currently in use`}
+// inputType="password"
+// />
+// )}
+// <Input<State>
+// name="new_token"
+// label={i18n.str`New password`}
+// tooltip={i18n.str`Next password to be used`}
+// inputType="password"
+// />
+// <Input<State>
+// name="repeat_token"
+// label={i18n.str`Repeat password`}
+// tooltip={i18n.str`Confirm the same password`}
+// inputType="password"
+// />
+// </FormProvider>
+// <p>
+// <i18n.Translate>
+// Clearing the password will mean public access to the instance
+// </i18n.Translate>
+// </p>
+// </div>
+// <div class="column" />
+// </div>
+// </ClearConfirmModal>
+// );
+// }
+
+// export function SetTokenNewInstanceModal({
+// onCancel,
+// onClear,
+// onConfirm,
+// }: UpdateTokenModalProps): VNode {
+// type State = { old_token: string; new_token: string; repeat_token: string };
+// const [form, setValue] = useState<Partial<State>>({
+// new_token: "",
+// repeat_token: "",
+// });
+// const { i18n } = useTranslationContext();
+
+// const errors = undefinedIfEmpty({
+// new_token: !form.new_token
+// ? i18n.str`Required`
+// : form.new_token === form.old_token
+// ? i18n.str`Can't be the same as the old password`
+// : undefined,
+// repeat_token:
+// form.new_token !== form.repeat_token
+// ? i18n.str`Is not the same`
+// : undefined,
+// });
+
+// const hasErrors = errors !== undefined;
+
+// return (
+// <div class="modal is-active">
+// <div class="modal-background " onClick={onCancel} />
+// <div class="modal-card">
+// <header class="modal-card-head">
+// <p class="modal-card-title">{i18n.str`You are setting the password for the new instance`}</p>
+// <button class="delete " aria-label="close" onClick={onCancel} />
+// </header>
+// <section class="modal-card-body is-main-section">
+// <div class="columns">
+// <div class="column" />
+// <div class="column is-four-fifths">
+// <FormProvider
+// errors={errors}
+// object={form}
+// valueHandler={setValue}
+// >
+// <Input<State>
+// name="new_token"
+// label={i18n.str`New password`}
+// tooltip={i18n.str`Next password to be used`}
+// inputType="password"
+// />
+// <Input<State>
+// name="repeat_token"
+// label={i18n.str`Repeat password`}
+// tooltip={i18n.str`Confirm the same password`}
+// inputType="password"
+// />
+// </FormProvider>
+// <p>
+// <i18n.Translate>
+// With external authorization method no check will be done by
+// the merchant backend
+// </i18n.Translate>
+// </p>
+// </div>
+// <div class="column" />
+// </div>
+// </section>
+// <footer class="modal-card-foot">
+// {onClear && (
+// <button
+// class="button is-danger"
+// onClick={onClear}
+// disabled={onClear === undefined}
+// >
+// <i18n.Translate>Set external authorization</i18n.Translate>
+// </button>
+// )}
+// <div class="buttons is-right" style={{ width: "100%" }}>
+// <button class="button " onClick={onCancel}>
+// <i18n.Translate>Cancel</i18n.Translate>
+// </button>
+// <button
+// class="button is-info"
+// onClick={() => onConfirm(form.new_token!)}
+// disabled={hasErrors}
+// >
+// <i18n.Translate>Set password</i18n.Translate>
+// </button>
+// </div>
+// </footer>
+// </div>
+// <button
+// class="modal-close is-large "
+// aria-label="close"
+// onClick={onCancel}
+// />
+// </div>
+// );
+// }
export function LoadingModal({ onCancel }: { onCancel: () => void }): VNode {
const { i18n } = useTranslationContext();
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx
@@ -19,7 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { AccessToken, createRFC8959AccessTokenPlain } from "@gnu-taler/taler-util";
+import {
+ AccessToken,
+ createRFC8959AccessTokenPlain,
+} from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
@@ -68,7 +71,7 @@ export function DetailPage({
const hasErrors = errors !== undefined;
- const text = i18n.str`You are updating the password from instance with id "${instanceId}"`;
+ const text = i18n.str`You are updating the password for the instance with ID "${instanceId}"`;
async function submitForm() {
if (hasErrors) return;
@@ -96,7 +99,7 @@ export function DetailPage({
</section>
<hr />
- {!hasToken && (
+ {/* {!hasToken && (
<NotificationCard
notification={{
message: i18n.str`This instance does not have authentication password.`,
@@ -104,7 +107,7 @@ export function DetailPage({
type: "WARN",
}}
/>
- )}
+ )} */}
<div class="columns">
<div class="column" />
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx
@@ -28,7 +28,10 @@ import { ErrorLoadingMerchant } from "../../../components/ErrorLoadingMerchant.j
import { Loading } from "../../../components/exception/loading.js";
import { NotificationCard } from "../../../components/menu/index.js";
import { useSessionContext } from "../../../context/session.js";
-import { useInstanceDetails, useManagedInstanceDetails } from "../../../hooks/instance.js";
+import {
+ useInstanceDetails,
+ useManagedInstanceDetails,
+} from "../../../hooks/instance.js";
import { Notification } from "../../../utils/types.js";
import { LoginPage } from "../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../notfound/index.js";
@@ -40,31 +43,45 @@ export interface Props {
}
export default function Token(props: Props): VNode {
- const { lib } = useSessionContext();
- const updateCurrentInstanceAuthentication = lib.instance.updateCurrentInstanceAuthentication.bind(lib.instance);
- const createAuthTokenFromToken = lib.instance.createAuthTokenFromToken.bind(lib.instance);
+ const { lib, state } = useSessionContext();
+ const updateCurrentInstanceAuthentication =
+ lib.instance.updateCurrentInstanceAuthentication.bind(lib.instance);
+ const createAuthTokenFromToken = lib.instance.createAuthTokenFromToken.bind(
+ lib.instance,
+ );
const result = useInstanceDetails();
- return CommonToken(props, result, updateCurrentInstanceAuthentication, createAuthTokenFromToken, true)
+ return CommonToken(
+ { ...props, instanceId: state.instance },
+ result,
+ updateCurrentInstanceAuthentication,
+ createAuthTokenFromToken,
+ );
}
-export function AdminToken(props: Props& { instanceId: string }): VNode {
+export function AdminToken(props: Props & { instanceId: string }): VNode {
const { lib } = useSessionContext();
const subInstaceLib = lib.subInstanceApi(props.instanceId).instance;
- const updateCurrentInstanceAuthentication = subInstaceLib.updateCurrentInstanceAuthentication.bind(subInstaceLib);
- const createAuthTokenFromToken = subInstaceLib.createAuthTokenFromToken.bind(subInstaceLib);
+ const updateCurrentInstanceAuthentication =
+ subInstaceLib.updateCurrentInstanceAuthentication.bind(subInstaceLib);
+ const createAuthTokenFromToken =
+ subInstaceLib.createAuthTokenFromToken.bind(subInstaceLib);
const result = useManagedInstanceDetails(props.instanceId);
- return CommonToken(props, result, updateCurrentInstanceAuthentication, createAuthTokenFromToken, false)
+ return CommonToken(
+ props,
+ result,
+ updateCurrentInstanceAuthentication,
+ createAuthTokenFromToken,
+ );
}
function CommonToken(
- { onChange, onCancel }: Props,
+ { onChange, onCancel, instanceId }: Props & { instanceId: string },
result:
| TalerMerchantManagementResultByMethod<"getInstanceDetails">
| TalerError
| undefined,
updateCurrentInstanceAuthentication: typeof TalerMerchantInstanceHttpClient.prototype.updateCurrentInstanceAuthentication,
createAuthTokenFromToken: typeof TalerMerchantInstanceHttpClient.prototype.createAuthTokenFromToken,
- replaceSession: boolean,
): VNode {
const { i18n } = useTranslationContext();
const { state, logIn } = useSessionContext();
@@ -88,7 +105,10 @@ function CommonToken(
}
}
- const hasToken = result.body.auth.method === "token";
+ const adminChangingPwdForAnotherInstance = state.isAdmin && state.instance !== instanceId;
+ const hasToken =
+ result.body.auth.method === MerchantAuthMethod.TOKEN &&
+ !adminChangingPwdForAnotherInstance;
return (
<Fragment>
@@ -100,14 +120,13 @@ function CommonToken(
onNewToken={async (currentToken, newToken): Promise<void> => {
try {
{
- const resp =
- await updateCurrentInstanceAuthentication(
- currentToken,
- {
- token: newToken,
- method: MerchantAuthMethod.TOKEN,
- },
- );
+ const resp = await updateCurrentInstanceAuthentication(
+ state.isAdmin ? state.token : currentToken,
+ {
+ token: newToken,
+ method: MerchantAuthMethod.TOKEN,
+ },
+ );
if (resp.type === "fail") {
return setNotif({
message: i18n.str`Failed to set new password`,
@@ -124,7 +143,7 @@ function CommonToken(
refreshable: true,
});
if (resp.type === "ok") {
- if (replaceSession) {
+ if (!state.isAdmin) {
// only renew the session token if we are not the admin
logIn(state.instance, resp.body.token);
}
diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts
@@ -1747,6 +1747,7 @@ export interface QueryInstancesResponse {
export enum MerchantAuthMethod {
EXTERNAL = "external",
TOKEN = "token",
+ PASSWORD = "password",
}
export interface MerchantAccountKycRedirectsResponse {