commit eb46ecbe00226531d9ad2818b77e8df5771b2cea
parent d1589a3eece6ef8390843ebde2fb8c166c695169
Author: Sebastian <sebasjm@taler-systems.com>
Date: Sun, 14 Dec 2025 20:16:14 -0300
fix #10599
Diffstat:
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,