taler-typescript-core

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

commit d1589a3eece6ef8390843ebde2fb8c166c695169
parent 50f768fa601e4a891cecd122687553fe89a72606
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Sun, 14 Dec 2025 17:07:48 -0300

fix #10639

Diffstat:
Mpackages/merchant-backoffice-ui/src/components/menu/SideBar.tsx | 7++++++-
Mpackages/merchant-backoffice-ui/src/hooks/preference.ts | 1+
Mpackages/merchant-backoffice-ui/src/paths/instance/accessTokens/create/CreatePage.tsx | 21+++++++++++++++++++--
Mpackages/taler-util/src/time.ts | 10++++++++++
4 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx @@ -414,7 +414,7 @@ const ALL_ELEMENTS = Object.values(UIElement).reduce((prev, ui) => { return prev; }, {} as ElementMap); -function getAvailableForPersona(p: MerchantPersona): ElementMap { +export function getAvailableForPersona(p: MerchantPersona): ElementMap { switch (p) { case "developer": return ALL_ELEMENTS; @@ -445,6 +445,7 @@ function getAvailableForPersona(p: MerchantPersona): ElementMap { [UIElement.sidebar_subscriptions]: false, [UIElement.sidebar_tokenFamilies]: false, [UIElement.option_ageRestriction]: false, + [UIElement.option_refreshableScopes]: false, }; case "offline-vending-machine": return { @@ -473,6 +474,7 @@ function getAvailableForPersona(p: MerchantPersona): ElementMap { [UIElement.action_useRevenueApi]: false, [UIElement.option_inventoryTaxes]: false, [UIElement.sidebar_statistics]: false, + [UIElement.option_refreshableScopes]: false, }; // case "inperson-vending-with-inventory": case "point-of-sale": @@ -502,6 +504,7 @@ function getAvailableForPersona(p: MerchantPersona): ElementMap { [UIElement.action_useRevenueApi]: false, [UIElement.option_inventoryTaxes]: false, [UIElement.sidebar_statistics]: false, + [UIElement.option_refreshableScopes]: false, }; case "digital-publishing": return { @@ -530,6 +533,7 @@ function getAvailableForPersona(p: MerchantPersona): ElementMap { [UIElement.option_ageRestriction]: false, [UIElement.action_useRevenueApi]: false, [UIElement.option_inventoryTaxes]: false, + [UIElement.option_refreshableScopes]: false, }; case "e-commerce": return { @@ -558,6 +562,7 @@ function getAvailableForPersona(p: MerchantPersona): ElementMap { [UIElement.option_ageRestriction]: false, [UIElement.action_useRevenueApi]: false, [UIElement.option_inventoryTaxes]: false, + [UIElement.option_refreshableScopes]: false, }; } } diff --git a/packages/merchant-backoffice-ui/src/hooks/preference.ts b/packages/merchant-backoffice-ui/src/hooks/preference.ts @@ -51,6 +51,7 @@ export enum UIElement { option_paymentTimeoutOnTemplate, option_ageRestriction, option_inventoryTaxes, + option_refreshableScopes, } export interface Preferences { 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 @@ -47,6 +47,8 @@ import { NotificationCard } from "../../../../components/menu/index.js"; import { SolveMFAChallenges } from "../../../../components/SolveMFA.js"; import { useSessionContext } from "../../../../context/session.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; +import { getAvailableForPersona } from "../../../../components/menu/SideBar.js"; +import { UIElement, usePreference } from "../../../../hooks/preference.js"; const TALER_SCREEN_ID = 29; @@ -62,7 +64,7 @@ interface Props { onBack?: () => void; } -const VALID_TOKEN_SCOPE = [ +const ALL_VALID_TOKEN_SCOPE = [ "", LoginTokenScope.ReadOnly, LoginTokenScope.OrderSimple, @@ -77,6 +79,15 @@ const VALID_TOKEN_SCOPE = [ LoginTokenScope.OrderFull_Refreshable, LoginTokenScope.All, ]; +const SANE_VALID_TOKEN_SCOPE = [ + "", + LoginTokenScope.ReadOnly, + LoginTokenScope.OrderSimple, + LoginTokenScope.OrderPos, + LoginTokenScope.OrderManagement, + LoginTokenScope.OrderFull, + LoginTokenScope.All, +]; export function CreatePage({ onCreated, onBack }: Props): VNode { const { i18n } = useTranslationContext(); @@ -90,6 +101,12 @@ export function CreatePage({ onCreated, onBack }: Props): VNode { description: !state.description ? i18n.str`Required` : undefined, scope: !state.scope ? i18n.str`Required` : undefined, }); + const [{ persona }] = usePreference(); + const scopeList = getAvailableForPersona(persona ?? "developer")[ + UIElement.option_refreshableScopes + ] + ? ALL_VALID_TOKEN_SCOPE + : SANE_VALID_TOKEN_SCOPE; const hasErrors = errors !== undefined; @@ -179,7 +196,7 @@ export function CreatePage({ onCreated, onBack }: Props): VNode { name="scope" label={i18n.str`Scope`} tooltip={i18n.str`The scope defines the set of permissions for the access token. Refreshable tokens has the permission to extend the expiration time.`} - values={VALID_TOKEN_SCOPE} + values={scopeList} help={((s) => { if (!s) return ""; switch (s) { diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts @@ -303,6 +303,16 @@ export namespace Duration { return -1; } + export function add(d1: Duration, d2: Duration): Duration { + if (d1.d_ms === "forever") { + return Duration.getForever(); + } + if (d2.d_ms === "forever") { + return Duration.getForever(); + } + return Duration.fromMilliseconds(d1.d_ms + d2.d_ms); + } + export function max(d1: Duration, d2: Duration): Duration { return durationMax(d1, d2); }