update how access token management is handled

This commit is contained in:
Sebastian 2023-09-14 12:14:21 -03:00
parent dd25740c91
commit 1653130de8
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
5 changed files with 80 additions and 58 deletions

View File

@ -114,7 +114,7 @@ export function ApplicationReadyRoutes(): VNode {
<NotificationCard <NotificationCard
notification={{ notification={{
message: i18n.str`Access denied`, message: i18n.str`Access denied`,
description: i18n.str`Check your token is valid 1`, description: i18n.str`Check your token is valid`,
type: "ERROR", type: "ERROR",
}} }}
/> />

View File

@ -266,7 +266,7 @@ export function useBackendBaseRequest(): useBackendBaseRequestType {
endpoint: string, endpoint: string,
options: RequestOptions = {}, options: RequestOptions = {},
): Promise<HttpResponseOk<T>> { ): Promise<HttpResponseOk<T>> {
return requestHandler<T>(backend, endpoint, { token, ...options }).then(res => { return requestHandler<T>(backend, endpoint, { ...options, token }).then(res => {
return res return res
}).catch(err => { }).catch(err => {
throw err throw err

View File

@ -36,8 +36,8 @@ interface InstanceAPI {
data: MerchantBackend.Instances.InstanceReconfigurationMessage, data: MerchantBackend.Instances.InstanceReconfigurationMessage,
) => Promise<void>; ) => Promise<void>;
deleteInstance: () => Promise<void>; deleteInstance: () => Promise<void>;
clearToken: () => Promise<void>; clearAccessToken: (currentToken: AccessToken | undefined) => Promise<void>;
setNewToken: (token: AccessToken) => Promise<void>; setNewAccessToken: (currentToken: AccessToken | undefined, token: AccessToken) => Promise<void>;
} }
export function useAdminAPI(): AdminAPI { export function useAdminAPI(): AdminAPI {
@ -111,18 +111,20 @@ export function useManagementAPI(instanceId: string): InstanceAPI {
mutateAll(/\/management\/instances/); mutateAll(/\/management\/instances/);
}; };
const clearToken = async (): Promise<void> => { const clearAccessToken = async (currentToken: AccessToken | undefined): Promise<void> => {
await request(`/management/instances/${instanceId}/auth`, { await request(`/management/instances/${instanceId}/auth`, {
method: "POST", method: "POST",
token: currentToken,
data: { method: "external" }, data: { method: "external" },
}); });
mutateAll(/\/management\/instances/); mutateAll(/\/management\/instances/);
}; };
const setNewToken = async (newToken: AccessToken): Promise<void> => { const setNewAccessToken = async (currentToken: AccessToken | undefined, newToken: AccessToken): Promise<void> => {
await request(`/management/instances/${instanceId}/auth`, { await request(`/management/instances/${instanceId}/auth`, {
method: "POST", method: "POST",
token: currentToken,
data: { method: "token", token: newToken }, data: { method: "token", token: newToken },
}); });
@ -137,7 +139,7 @@ export function useManagementAPI(instanceId: string): InstanceAPI {
mutateAll(/\/management\/instances/); mutateAll(/\/management\/instances/);
}; };
return { updateInstance, deleteInstance, setNewToken, clearToken }; return { updateInstance, deleteInstance, setNewAccessToken, clearAccessToken };
} }
export function useInstanceAPI(): InstanceAPI { export function useInstanceAPI(): InstanceAPI {
@ -172,18 +174,20 @@ export function useInstanceAPI(): InstanceAPI {
mutate([`/private/`], null); mutate([`/private/`], null);
}; };
const clearToken = async (): Promise<void> => { const clearAccessToken = async (currentToken: AccessToken | undefined): Promise<void> => {
await request(`/private/auth`, { await request(`/private/auth`, {
method: "POST", method: "POST",
token: currentToken,
data: { method: "external" }, data: { method: "external" },
}); });
mutate([`/private/`], null); mutate([`/private/`], null);
}; };
const setNewToken = async (newToken: AccessToken): Promise<void> => { const setNewAccessToken = async (currentToken: AccessToken | undefined, newToken: AccessToken): Promise<void> => {
await request(`/private/auth`, { await request(`/private/auth`, {
method: "POST", method: "POST",
token: currentToken,
data: { method: "token", token: newToken }, data: { method: "token", token: newToken },
}); });
@ -198,7 +202,7 @@ export function useInstanceAPI(): InstanceAPI {
mutate([`/private/`], null); mutate([`/private/`], null);
}; };
return { updateInstance, deleteInstance, setNewToken, clearToken }; return { updateInstance, deleteInstance, setNewAccessToken, clearAccessToken };
} }
export function useInstanceDetails(): HttpResponse< export function useInstanceDetails(): HttpResponse<

View File

@ -30,13 +30,13 @@ import { AccessToken } from "../../../declaration.js";
interface Props { interface Props {
instanceId: string; instanceId: string;
currentToken: string | undefined; hasToken: boolean | undefined;
onClearToken: () => void; onClearToken: (c: AccessToken | undefined) => void;
onNewToken: (s: AccessToken) => void; onNewToken: (c: AccessToken | undefined, s: AccessToken) => void;
onBack?: () => void; onBack?: () => void;
} }
export function DetailPage({ instanceId, currentToken: oldToken, onBack, onNewToken, onClearToken }: Props): VNode { export function DetailPage({ instanceId, hasToken, onBack, onNewToken, onClearToken }: Props): VNode {
type State = { old_token: string; new_token: string; repeat_token: string }; type State = { old_token: string; new_token: string; repeat_token: string };
const [form, setValue] = useState<Partial<State>>({ const [form, setValue] = useState<Partial<State>>({
old_token: "", old_token: "",
@ -45,11 +45,9 @@ export function DetailPage({ instanceId, currentToken: oldToken, onBack, onNewTo
}); });
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const hasOldtoken = !!oldToken
const hasInputTheCorrectOldToken = hasOldtoken && oldToken !== form.old_token;
const errors = { const errors = {
old_token: hasInputTheCorrectOldToken old_token: hasToken && !form.old_token
? i18n.str`is not the same as the current access token` ? i18n.str`you need your access token to perform the operation`
: undefined, : undefined,
new_token: !form.new_token new_token: !form.new_token
? i18n.str`cannot be empty` ? i18n.str`cannot be empty`
@ -72,8 +70,9 @@ export function DetailPage({ instanceId, currentToken: oldToken, onBack, onNewTo
async function submitForm() { async function submitForm() {
if (hasErrors) return; if (hasErrors) return;
const ot = hasToken ? `secret-token:${form.old_token}` as AccessToken : undefined;
const nt = `secret-token:${form.new_token}` as AccessToken; const nt = `secret-token:${form.new_token}` as AccessToken;
onNewToken(nt) onNewToken(ot, nt)
} }
return ( return (
@ -98,32 +97,38 @@ export function DetailPage({ instanceId, currentToken: oldToken, onBack, onNewTo
<div class="column" /> <div class="column" />
<div class="column is-four-fifths"> <div class="column is-four-fifths">
<FormProvider errors={errors} object={form} valueHandler={setValue}> <FormProvider errors={errors} object={form} valueHandler={setValue}>
{hasOldtoken && ( <Fragment>
<Input<State> {hasToken && (
name="old_token" <Fragment>
label={i18n.str`Current access token`} <Input<State>
tooltip={i18n.str`access token currently in use`} name="old_token"
inputType="password" label={i18n.str`Current access token`}
/> tooltip={i18n.str`access token currently in use`}
)} inputType="password"
{!hasInputTheCorrectOldToken && <Fragment> />
{hasOldtoken && <Fragment> <p>
<p> <i18n.Translate>
<i18n.Translate> Clearing the access token will mean public access to the instance.
Clearing the access token will mean public access to the instance. </i18n.Translate>
</i18n.Translate> </p>
</p> <div class="buttons is-right mt-5">
<div class="buttons is-right mt-5"> <button
<button class="button"
disabled={!!hasInputTheCorrectOldToken} onClick={() => {
class="button" if (hasToken) {
onClick={onClearToken} const ot = `secret-token:${form.old_token}` as AccessToken;
> onClearToken(ot)
<i18n.Translate>Clear token</i18n.Translate> } else {
</button> onClearToken(undefined)
</div> }
</Fragment> }}
} >
<i18n.Translate>Clear token</i18n.Translate>
</button>
</div>
</Fragment>
)}
<Input<State> <Input<State>
name="new_token" name="new_token"
@ -137,7 +142,7 @@ export function DetailPage({ instanceId, currentToken: oldToken, onBack, onNewTo
tooltip={i18n.str`confirm the same access token`} tooltip={i18n.str`confirm the same access token`}
inputType="password" inputType="password"
/> />
</Fragment>} </Fragment>
</FormProvider> </FormProvider>
<div class="buttons is-right mt-5"> <div class="buttons is-right mt-5">
{onBack && ( {onBack && (

View File

@ -33,8 +33,6 @@ interface Props {
onNotFound: () => VNode; onNotFound: () => VNode;
} }
const PREFIX = "secret-token:"
export default function Token({ export default function Token({
onLoadError, onLoadError,
onChange, onChange,
@ -44,21 +42,36 @@ export default function Token({
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
const [notif, setNotif] = useState<Notification | undefined>(undefined); const [notif, setNotif] = useState<Notification | undefined>(undefined);
const { clearToken, setNewToken } = useInstanceAPI(); const { clearAccessToken, setNewAccessToken } = useInstanceAPI();
const { token: rootToken } = useBackendContext(); const { id } = useInstanceContext();
const { token: instanceToken, id, admin } = useInstanceContext(); const result = useInstanceDetails()
if (result.loading) return <Loading />;
if (!result.ok) {
if (
result.type === ErrorType.CLIENT &&
result.status === HttpStatusCode.Unauthorized
)
return onUnauthorized();
if (
result.type === ErrorType.CLIENT &&
result.status === HttpStatusCode.NotFound
)
return onNotFound();
return onLoadError(result);
}
const hasToken = result.data.auth.method === "token"
const currentToken = !admin ? rootToken : instanceToken
const hasPrefix = currentToken !== undefined && currentToken.token.startsWith(PREFIX)
return ( return (
<Fragment> <Fragment>
<NotificationCard notification={notif} /> <NotificationCard notification={notif} />
<DetailPage <DetailPage
instanceId={id} instanceId={id}
currentToken={hasPrefix ? currentToken.token.substring(PREFIX.length) : currentToken?.token} hasToken={hasToken}
onClearToken={async (): Promise<void> => { onClearToken={async (currentToken): Promise<void> => {
try { try {
await clearToken(); await clearAccessToken(currentToken);
onChange(); onChange();
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
@ -70,9 +83,9 @@ export default function Token({
} }
} }
}} }}
onNewToken={async (newToken): Promise<void> => { onNewToken={async (currentToken, newToken): Promise<void> => {
try { try {
await setNewToken(newToken); await setNewAccessToken(currentToken, newToken);
onChange(); onChange();
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {