taler-typescript-core

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

commit b9539673aafbab021fa2dd0ecd1dd781c8e4df4e
parent f61a654888b9ee3312c7064502fdde18ebf19088
Author: Sebastian <sebasjm@gmail.com>
Date:   Wed,  9 Jul 2025 11:40:26 -0300

fix #10108

Diffstat:
Mpackages/merchant-backoffice-ui/src/components/menu/SideBar.tsx | 2+-
Mpackages/merchant-backoffice-ui/src/components/modal/index.tsx | 18++++++++++++------
Mpackages/merchant-backoffice-ui/src/hooks/access-tokens.ts | 2+-
Mpackages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx | 71+++++++++++------------------------------------------------------------
Mpackages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/CreatePage.tsx | 13+++++++++++--
Mpackages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/index.tsx | 69++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
6 files changed, 94 insertions(+), 81 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx @@ -263,7 +263,7 @@ export function Sidebar({ mobile }: Props): VNode { <i class="mdi mdi-security" /> </span> <span class="menu-item-label"> - <i18n.Translate>Access Tokens</i18n.Translate> + <i18n.Translate>Active sessions</i18n.Translate> </span> </a> </li> diff --git a/packages/merchant-backoffice-ui/src/components/modal/index.tsx b/packages/merchant-backoffice-ui/src/components/modal/index.tsx @@ -62,7 +62,7 @@ export function ConfirmModal({ const { i18n } = useTranslationContext(); return ( <div class={active ? "modal is-active" : "modal"}> - <div class="modal-background " onClick={onCancel} /> + <div class="modal-background " onClick={onCancel ?? onConfirm} /> <div class="modal-card" style={{ maxWidth: 700 }}> <header class="modal-card-head"> {!description ? null : ( @@ -70,16 +70,22 @@ export function ConfirmModal({ <b>{description}</b> </p> )} - <button class="delete " aria-label="close" onClick={onCancel} /> + <button + class="delete " + aria-label="close" + onClick={onCancel ?? onConfirm} + /> </header> <section class="modal-card-body">{children}</section> <footer class="modal-card-foot"> <div class="buttons is-right" style={{ width: "100%" }}> {onConfirm ? ( <Fragment> - <button class="button " onClick={onCancel}> - <i18n.Translate>Cancel</i18n.Translate> - </button> + {onCancel ? ( + <button class="button " onClick={onCancel}> + <i18n.Translate>Cancel</i18n.Translate> + </button> + ) : undefined} <button class={danger ? "button is-danger " : "button is-info "} @@ -580,7 +586,7 @@ export function ValidBankAccount({ ); } -function Row({ +export function Row({ name, value, literal, diff --git a/packages/merchant-backoffice-ui/src/hooks/access-tokens.ts b/packages/merchant-backoffice-ui/src/hooks/access-tokens.ts @@ -53,7 +53,7 @@ export function useInstanceAccessTokens() { const { data, error } = useSWR< TalerMerchantManagementResultByMethod<"listAccessTokens">, TalerHttpError - >([state.token, "offset", "listAccessTokens"], fetcher); + >([state.token, offset, "listAccessTokens"], fetcher); if (error) return error; if (data === undefined) return undefined; 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; password: string; repeat: string }; +type TokenForm = { password: string; repeat: string }; export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { const [pref, updatePref] = usePreference(); @@ -124,20 +124,12 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { const hasErrors = errors !== undefined; const tokenFormErrors = undefinedIfEmpty<FormErrors<TokenForm>>({ - password: - tokenForm.accessControl === false - ? undefined - : !tokenForm.password - ? i18n.str`Required` - : undefined, - repeat: - tokenForm.accessControl === false - ? undefined - : !tokenForm.repeat - ? i18n.str`Required` - : tokenForm.repeat !== tokenForm.password - ? i18n.str`Doesn't match` - : undefined, + password: !tokenForm.password ? i18n.str`Required` : undefined, + repeat: !tokenForm.repeat + ? i18n.str`Required` + : tokenForm.repeat !== tokenForm.password + ? i18n.str`Doesn't match` + : undefined, }); const hasTokenErrors = tokenFormErrors === undefined; @@ -146,14 +138,11 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { // use conversion instead of this const newValue = structuredClone(value); - const accessControl = !!tokenForm.accessControl; newValue.auth_token = undefined; - newValue.auth = !accessControl - ? { method: MerchantAuthMethod.EXTERNAL } - : { - method: MerchantAuthMethod.TOKEN, - password: tokenForm.password!, - }; + newValue.auth = { + method: MerchantAuthMethod.TOKEN, + password: tokenForm.password!, + }; if (!newValue.address) newValue.address = {}; if (!newValue.jurisdiction) newValue.jurisdiction = {}; @@ -210,57 +199,19 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { object={tokenForm} valueHandler={setTokenForm} > - <InputToggle<TokenForm> - name="accessControl" - threeState={tokenForm.accessControl === undefined} - label={i18n.str`Enable access control`} - help={i18n.str`Choose if the backend server should authenticate access.`} - /> <Input<TokenForm> name="password" label={i18n.str`New password`} tooltip={i18n.str`Next password to be used`} - readonly={ - tokenForm.accessControl === false || - tokenForm.accessControl === undefined - } inputType="password" /> <Input<TokenForm> name="repeat" label={i18n.str`Repeat password`} tooltip={i18n.str`Confirm the same password`} - readonly={ - tokenForm.accessControl === false || - tokenForm.accessControl === undefined - } inputType="password" /> </FormProvider> - <div class="level"> - <div class="level-item has-text-centered"> - {tokenForm.accessControl === undefined ? ( - <p class="is-size-6"> - <i18n.Translate> - Access control is not yet decided. This instance can't be - created. - </i18n.Translate> - </p> - ) : !tokenForm.accessControl ? ( - <p class="is-size-6"> - <i18n.Translate> - Authorization must be handled externally. - </i18n.Translate> - </p> - ) : ( - <p class="is-size-6"> - <i18n.Translate> - Authorization is handled by the backend server. - </i18n.Translate> - </p> - )} - </div> - </div> <div class="buttons is-right mt-5"> {onBack && ( <button class="button" onClick={onBack}> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/CreatePage.tsx @@ -21,6 +21,7 @@ import { assertUnreachable, + Duration, LoginTokenScope, TalerMerchantApi, } from "@gnu-taler/taler-util"; @@ -38,7 +39,9 @@ import { InputDuration } from "../../../../components/form/InputDuration.js"; import { InputSelector } from "../../../../components/form/InputSelector.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; -type Entity = TalerMerchantApi.LoginTokenRequest & { +type Entity = { + scope: TalerMerchantApi.LoginTokenRequest["scope"]; + duration: Duration; password: string; } & TalerForm; @@ -57,6 +60,12 @@ const VALID_TOKEN_SCOPE = [ LoginTokenScope.OrderPos, LoginTokenScope.OrderSimple, LoginTokenScope.ReadOnly, + LoginTokenScope.All_Refreshable, + LoginTokenScope.OrderFull_Refreshable, + LoginTokenScope.OrderManagement_Refreshable, + LoginTokenScope.OrderPos_Refreshable, + LoginTokenScope.OrderSimple_Refreshable, + LoginTokenScope.ReadOnly_Refreshable, ]; export function CreatePage({ onCreate, onBack }: Props): VNode { @@ -77,7 +86,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { return onCreate(state.password!, { scope: state.scope!, - duration: state.duration!, + duration: Duration.toTalerProtocolDuration(state.duration!), }); }; diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/index.tsx @@ -19,18 +19,15 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { - TalerMerchantApi -} from "@gnu-taler/taler-util"; -import { - useTranslationContext -} from "@gnu-taler/web-util/browser"; +import { AbsoluteTime, TalerMerchantApi } from "@gnu-taler/taler-util"; +import { Time, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { NotificationCard } from "../../../../components/menu/index.js"; import { useSessionContext } from "../../../../context/session.js"; import { Notification } from "../../../../utils/types.js"; import { CreatePage } from "./CreatePage.js"; +import { ConfirmModal, Row } from "../../../../components/modal/index.js"; export type Entity = TalerMerchantApi.LoginTokenRequest; interface Props { @@ -38,14 +35,60 @@ interface Props { onConfirm: () => void; } -export default function AccessTokenCreatePage({ onConfirm, onBack }: Props): VNode { +export default function AccessTokenCreatePage({ + onConfirm, + onBack, +}: Props): VNode { const { state, lib } = useSessionContext(); const [notif, setNotif] = useState<Notification | undefined>(undefined); const { i18n } = useTranslationContext(); + const [ok, setOk] = useState<{ token: string; expiration: AbsoluteTime }>(); + return ( - <> + <Fragment> <NotificationCard notification={notif} /> + {!ok ? undefined : ( + <ConfirmModal + label={`I understand`} + active + onConfirm={() => onConfirm()} + > + <div class="table-container"> + <table> + <tbody> + <tr> + <td colSpan={3}> + <i18n.Translate> + Copy the access token velue and save it because you won't + be able to get it again. + </i18n.Translate> + </td> + </tr> + <Row name={i18n.str`Token`} value={ok.token} literal /> + + <tr> + <td colSpan={3}> + {AbsoluteTime.isNever(ok.expiration) ? ( + <i18n.Translate> + This token will never expire + </i18n.Translate> + ) : ( + <i18n.Translate> + This token will be available until{" "} + <Time + format="dd/MM/yyyy HH:mm:ss" + timestamp={ok.expiration} + /> + </i18n.Translate> + )} + </td> + </tr> + </tbody> + </table> + </div> + </ConfirmModal> + )} <CreatePage onBack={onBack} onCreate={async (pwd: string, request: Entity) => { @@ -60,7 +103,12 @@ export default function AccessTokenCreatePage({ onConfirm, onBack }: Props): VNo }); return; } - onConfirm(); + setOk({ + expiration: AbsoluteTime.fromProtocolTimestamp( + resp.body.expiration, + ), + token: resp.body.access_token, + }); }) .catch((error) => { setNotif({ @@ -72,7 +120,6 @@ export default function AccessTokenCreatePage({ onConfirm, onBack }: Props): VNo }); }} /> - </> + </Fragment> ); } -