taler-typescript-core

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

commit eb46ecbe00226531d9ad2818b77e8df5771b2cea
parent d1589a3eece6ef8390843ebde2fb8c166c695169
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Sun, 14 Dec 2025 20:16:14 -0300

fix #10599

Diffstat:
Mpackages/merchant-backoffice-ui/src/paths/instance/password/DetailPage.tsx | 65+++++++++++++++++++++++++++++++++--------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/password/index.tsx | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mpackages/merchant-backoffice-ui/src/paths/login/index.tsx | 2+-
3 files changed, 112 insertions(+), 56 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/paths/instance/password/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/password/DetailPage.tsx @@ -22,7 +22,7 @@ import { ButtonBetterBulma, SafeHandlerTemplate, - useTranslationContext + useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -35,39 +35,39 @@ const TALER_SCREEN_ID = 53; interface Props { instanceId: string; onBack?: () => void; - changePassword: SafeHandlerTemplate<[pass: string], any>; + withoutCurrentPassword?: boolean; + changePassword: SafeHandlerTemplate<[current: string, new: string], any>; } export function DetailPage({ instanceId, onBack, changePassword, + withoutCurrentPassword, }: Props): VNode { type State = { - // old_token: string; - new_token: string; - repeat_token: string; + current: string; + next: string; + repeat: string; }; const [form, setValue] = useState<Partial<State>>({ - // old_token: "", - new_token: "", - repeat_token: "", + current: "", + next: "", + repeat: "", }); const { i18n } = useTranslationContext(); const errors = undefinedIfEmpty({ - // old_token: - // hasPassword && !form.old_token - // ? i18n.str`You need your password to perform the operation` - // : undefined, - new_token: !form.new_token + current: withoutCurrentPassword + ? undefined + : !form.current + ? i18n.str`Required` + : undefined, + next: !form.next ? i18n.str`Required` : undefined, + repeat: !form.repeat ? 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 + : form.next !== form.repeat ? i18n.str`Is not the same` : undefined, }); @@ -97,25 +97,22 @@ export function DetailPage({ <div class="column is-four-fifths"> <FormProvider errors={errors} object={form} valueHandler={setValue}> <Fragment> - {/* {hasPassword && ( - <Fragment> - <Input<State> - name="old_token" - label={i18n.str`Current password`} - tooltip={i18n.str`Password currently in use`} - inputType="password" - /> - </Fragment> - )} */} - + {withoutCurrentPassword ? undefined : ( + <Input<State> + name="current" + label={i18n.str`Current password`} + tooltip={i18n.str`In order to verify that you have access.`} + inputType="password" + /> + )} <Input<State> - name="new_token" + name="next" label={i18n.str`New password`} tooltip={i18n.str`Next password to be used`} inputType="password" /> <Input<State> - name="repeat_token" + name="repeat" label={i18n.str`Repeat password`} tooltip={i18n.str`Confirm the same password`} inputType="password" @@ -134,7 +131,11 @@ export function DetailPage({ ? i18n.str`Please complete the marked fields` : i18n.str`Confirm operation` } - onClick={changePassword} + onClick={ + hasErrors + ? changePassword + : changePassword.withArgs(form.current ?? "", form.next!) + } > <i18n.Translate>Confirm change</i18n.Translate> </ButtonBetterBulma> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/password/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/password/index.tsx @@ -19,6 +19,7 @@ import { MerchantAuthMethod, TalerError, assertUnreachable, + opKnownFailure, } from "@gnu-taler/taler-util"; import { LocalNotificationBannerBulma, @@ -35,7 +36,7 @@ import { useInstanceDetails, useManagedInstanceDetails, } from "../../../hooks/instance.js"; -import { LoginPage } from "../../login/index.js"; +import { LoginPage, TEMP_TEST_TOKEN } from "../../login/index.js"; import { NotFoundPageOrAdminCreate } from "../../notfound/index.js"; import { DetailPage } from "./DetailPage.js"; @@ -73,16 +74,37 @@ export default function PasswordPage({ onCancel, onChange }: Props): VNode { const mfa = useChallengeHandler(); const changePassword = safeFunctionHandler( - (token: AccessToken, pwd: string, challengeIds: string[]) => - lib.instance.updateCurrentInstanceAuthentication( + async ( + token: AccessToken, + current: string, + next: string, + challengeIds: string[], + ) => { + const resp = await lib.instance.createAccessToken( + instanceId, + current, + TEMP_TEST_TOKEN(i18n.str`Testing password change`), + ); + if (resp.type === "fail") { + switch (resp.case) { + case HttpStatusCode.Unauthorized: + return opKnownFailure("bad-current-pwd"); + case HttpStatusCode.NotFound: + return resp; + case HttpStatusCode.Accepted: + break; //2fa required but the pwd is ok, continue + } + } + return lib.instance.updateCurrentInstanceAuthentication( token, { method: MerchantAuthMethod.TOKEN, - password: pwd, + password: next, }, { challengeIds }, - ), - !session.token ? undefined : [session.token, "", []], + ); + }, + !session.token ? undefined : [session.token, "", "", []], ); changePassword.onSuccess = (suc) => { onChange(); @@ -97,12 +119,15 @@ export default function PasswordPage({ onCancel, onChange }: Props): VNode { return i18n.str`Unauthorized.`; case HttpStatusCode.NotFound: return i18n.str`Not found.`; + case "bad-current-pwd": + return i18n.str`The current password is wrong.`; } }; const retry = changePassword.lambda((ids: string[]) => [ changePassword.args![0], changePassword.args![1], + changePassword.args![2], ids, ]); if (mfa.pendingChallenge) { @@ -121,11 +146,14 @@ export default function PasswordPage({ onCancel, onChange }: Props): VNode { <DetailPage onBack={onCancel} instanceId={result.body.name} - changePassword={changePassword.lambda((pass: string) => [ - changePassword.args![0], - pass, - changePassword.args![2], - ])} + changePassword={changePassword.lambda( + (current: string, next: string) => [ + changePassword.args![0], + current, + next, + changePassword.args![3], + ], + )} /> </Fragment> ); @@ -163,8 +191,28 @@ export function AdminPassword({ const { i18n } = useTranslationContext(); const mfa = useChallengeHandler(); const changePassword = safeFunctionHandler( - (token: AccessToken, id: string, pwd: string, challengeIds: string[]) => - lib.instance.updateInstanceAuthentication( + async ( + token: AccessToken, + id: string, + pwd: string, + challengeIds: string[], + ) => { + // const resp = await subInstanceLib.createAccessToken( + // id, + // current, + // TEMP_TEST_TOKEN(i18n.str`Testing password change`), + // ); + // if (resp.type === "fail") { + // switch (resp.case) { + // case HttpStatusCode.Unauthorized: + // return opKnownFailure("bad-current-pwd"); + // case HttpStatusCode.NotFound: + // return resp; + // case HttpStatusCode.Accepted: + // break; //2fa required but the pwd is ok, continue + // } + // } + return await lib.instance.updateInstanceAuthentication( token, id, { @@ -172,7 +220,9 @@ export function AdminPassword({ password: pwd, }, { challengeIds }, - ), + ); + }, + !session.token ? undefined : [session.token, instanceId, "", []], ); changePassword.onSuccess = (suc) => { onChange(); @@ -184,14 +234,16 @@ export function AdminPassword({ mfa.onChallengeRequired(fail.body); return undefined; case HttpStatusCode.Unauthorized: - return i18n.str`Unauthorized.`; + return i18n.str`No enough rights to change the password.`; case HttpStatusCode.NotFound: - return i18n.str`Not found.`; + return i18n.str`Account not found.`; + default: + assertUnreachable(fail); } }; const retry = changePassword.lambda((ids: string[]) => [ changePassword.args![0], - changePassword.args![2], + changePassword.args![1], changePassword.args![2], ids, ]); @@ -211,12 +263,15 @@ export function AdminPassword({ <DetailPage onBack={onCancel} instanceId={result.body.name} - changePassword={changePassword.lambda((pass: string) => [ - changePassword.args![0], - changePassword.args![1], - pass, - changePassword.args![3], - ])} + changePassword={changePassword.lambda( + (current: string, next: string) => [ + changePassword.args![0], + changePassword.args![1], + next, + changePassword.args![3], + ], + )} + withoutCurrentPassword /> </Fragment> ); diff --git a/packages/merchant-backoffice-ui/src/paths/login/index.tsx b/packages/merchant-backoffice-ui/src/paths/login/index.tsx @@ -74,7 +74,7 @@ export function LoginPage({ showCreateAccount }: Props): VNode { const login = safeFunctionHandler( (usr: string, pwd: string, challengeIds: string[]) => { - const api = getInstanceForUsername(username); + const api = getInstanceForUsername(usr); return api.createAccessToken( usr, pwd,