taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit f25215a33118c82260ced14f4867f081d786be1e
parent 22563a4f960716ff3a112f19f40bf521087c6225
Author: Sebastian <sebasjm@gmail.com>
Date:   Sun, 22 Jun 2025 19:53:18 -0300

fix #10120

Diffstat:
Mpackages/merchant-backoffice-ui/src/components/modal/index.tsx | 372++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/token/DetailPage.tsx | 11+++++++----
Mpackages/merchant-backoffice-ui/src/paths/instance/token/index.tsx | 61++++++++++++++++++++++++++++++++++++++++---------------------
Mpackages/taler-util/src/types-taler-merchant.ts | 1+
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 {