From 8aa9ce6d20b41b7eb9b438a56ccd34cb0da35f80 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 21 Mar 2024 12:11:31 -0300 Subject: wip --- .../merchant-backoffice-ui/src/hooks/session.ts | 185 +++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 packages/merchant-backoffice-ui/src/hooks/session.ts (limited to 'packages/merchant-backoffice-ui/src/hooks/session.ts') diff --git a/packages/merchant-backoffice-ui/src/hooks/session.ts b/packages/merchant-backoffice-ui/src/hooks/session.ts new file mode 100644 index 000000000..7b06ea290 --- /dev/null +++ b/packages/merchant-backoffice-ui/src/hooks/session.ts @@ -0,0 +1,185 @@ +/* + This file is part of GNU Taler + (C) 2021-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +import { + AccessToken, + Codec, + buildCodecForObject, + buildCodecForUnion, + codecForBoolean, + codecForConstString, + codecForConstTrue, + codecForString, + codecOptional, +} from "@gnu-taler/taler-util"; +import { buildStorageKey, useLocalStorage, useMerchantApiContext } from "@gnu-taler/web-util/browser"; +import { mutate } from "swr"; + +/** + * Has the information to reach and + * authenticate at the bank's backend. + */ +export type SessionState = LoggedIn | LoggedOut | Expired | Impersonate; + +interface LoggedIn { + status: "loggedIn"; + instance: string; + isAdmin: boolean; + token: AccessToken | undefined; +} +interface Expired { + status: "expired"; + instance: string; + isAdmin: boolean; +} +interface Impersonate { + status: "impersonate"; + instance: string; + isAdmin: true; + token: AccessToken | undefined; + originalInstance: string; + originalToken: AccessToken | undefined; +} +interface LoggedOut { + status: "loggedOut"; + instance: string; + isAdmin: boolean; +} + +export const codecForSessionStateLoggedIn = (): Codec => + buildCodecForObject() + .property("status", codecForConstString("loggedIn")) + .property("instance", codecForString()) + .property("token", codecOptional(codecForString() as Codec)) + .property("isAdmin", codecForBoolean()) + .build("SessionState.LoggedIn"); + +export const codecForSessionStateExpired = (): Codec => + buildCodecForObject() + .property("status", codecForConstString("expired")) + .property("instance", codecForString()) + .property("isAdmin", codecForBoolean()) + .build("SessionState.Expired"); + +export const codecForSessionStateLoggedOut = (): Codec => + buildCodecForObject() + .property("status", codecForConstString("loggedOut")) + .property("instance", codecForString()) + .property("isAdmin", codecForBoolean()) + .build("SessionState.LoggedOut"); + +export const codecForSessionStateImpresonate = (): Codec => + buildCodecForObject() + .property("status", codecForConstString("impersonate")) + .property("instance", codecForString()) + .property("isAdmin", codecForConstTrue()) + .property("token", codecOptional(codecForString() as Codec)) + .property("originalInstance", codecForString()) + .property("originalToken", codecOptional(codecForString() as Codec)) + .build("SessionState.Impersonate"); + +export const codecForSessionState = (): Codec => + buildCodecForUnion() + .discriminateOn("status") + .alternative("loggedIn", codecForSessionStateLoggedIn()) + .alternative("impersonate", codecForSessionStateImpresonate()) + .alternative("loggedOut", codecForSessionStateLoggedOut()) + .alternative("expired", codecForSessionStateExpired()) + .build("SessionState"); + +export const defaultState = (instance: string): SessionState => ({ + status: "loggedIn", + instance, + isAdmin: instance === DEFAULT_ADMIN_USERNAME, + token: undefined, +}); + +export interface SessionStateHandler { + state: SessionState; + logOut(): void; + expired(): void; + logIn(info: { token: AccessToken }): void; + impersonate(info: { instance: string; token: AccessToken }): void; +} + +const SESSION_STATE_KEY = buildStorageKey("merchant-session", codecForSessionState()); + +export const DEFAULT_ADMIN_USERNAME = "default" + +export const INSTANCE_ID_LOOKUP = /\/instances\/([^/]*)\/?$/; + +/** + * Return getters and setters for + * login credentials and backend's + * base URL. + */ +export function useSessionState(): SessionStateHandler { + const { url } = useMerchantApiContext(); + + const match = INSTANCE_ID_LOOKUP.exec(url.href); + const instanceName = !match || !match[1] ? DEFAULT_ADMIN_USERNAME : match[1]; + + const { value: state, update } = useLocalStorage( + SESSION_STATE_KEY, + defaultState(instanceName), + ); + + return { + state, + logOut() { + update(defaultState(instanceName)); + }, + impersonate(info) { + if (state.status === "loggedOut" || state.status === "expired") { + // can't impersonate if not loggedin + return; + } + const nextState: SessionState = { + status: "impersonate", + originalToken: state.token, + originalInstance: state.instance, + isAdmin: true, + instance: info.instance, + token: info.token, + }; + update(nextState); + }, + expired() { + if (state.status === "loggedOut") return; + const nextState: SessionState = { + status: "expired", + instance: state.instance, + isAdmin: state.instance === DEFAULT_ADMIN_USERNAME, + }; + update(nextState); + }, + logIn(info) { + // admin is defined by the username + const nextState: SessionState = { + status: "loggedIn", + instance: state.instance, + token: info.token, + isAdmin: state.instance === DEFAULT_ADMIN_USERNAME, + }; + update(nextState); + cleanAllCache(); + }, + }; +} + +function cleanAllCache(): void { + mutate(() => true, undefined, { revalidate: false }); +} -- cgit v1.2.3