[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] 07/07: refactor backend mutate api to use to
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] 07/07: refactor backend mutate api to use token from instance id, implemented new passwod endpoint |
Date: |
Wed, 03 Mar 2021 18:44:14 +0100 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository merchant-backoffice.
commit f3aea323f63199f6726583bf3f458a6134b0d9f8
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Mar 3 14:43:40 2021 -0300
refactor backend mutate api to use token from instance id, implemented new
passwod endpoint
---
packages/frontend/src/context/backend.ts | 19 ++--
packages/frontend/src/declaration.d.ts | 39 +++++--
packages/frontend/src/hooks/backend.ts | 117 +++++++++++++--------
packages/frontend/src/hooks/index.ts | 9 +-
packages/frontend/src/index.tsx | 44 ++++----
packages/frontend/src/messages/en.po | 3 +
.../src/routes/instances/create/CreatePage.tsx | 49 +++------
.../frontend/src/routes/instances/create/index.tsx | 4 +-
.../src/routes/instances/details/DetailPage.tsx | 31 +-----
.../src/routes/instances/details/index.tsx | 11 +-
.../frontend/src/routes/instances/list/index.tsx | 38 ++-----
.../src/routes/instances/update/UpdatePage.tsx | 37 +++++--
.../frontend/src/routes/instances/update/index.tsx | 23 ++--
packages/frontend/src/schemas/index.ts | 9 +-
packages/frontend/src/utils/constants.ts | 3 +
15 files changed, 216 insertions(+), 220 deletions(-)
diff --git a/packages/frontend/src/context/backend.ts
b/packages/frontend/src/context/backend.ts
index a41c32d..5b9b648 100644
--- a/packages/frontend/src/context/backend.ts
+++ b/packages/frontend/src/context/backend.ts
@@ -20,6 +20,7 @@ export interface BackendContextType {
url: string;
token?: string;
changeBackend: (url: string) => void;
+ resetBackend: () => void;
// clearTokens: () => void;
// addTokenCleaner: (c: StateUpdater<string | undefined>) => void;
updateToken: (token?:string) => void;
@@ -35,25 +36,29 @@ export interface ConfigContextType {
export interface InstanceContextType {
id: string;
token?: string;
+ admin?: boolean;
}
-export const BackendContext = createContext<BackendContextType>({
+const BackendContext = createContext<BackendContextType>({
url: '',
lang: 'en',
token: undefined,
changeBackend: () => null,
+ resetBackend: () => null,
// clearTokens: () => null,
// addTokenCleaner: () => null,
updateToken: () => null,
setLang: () => null,
})
-export const ConfigContext = createContext<ConfigContextType>(null!)
+const ConfigContext = createContext<ConfigContextType>(null!)
-export const useConfigContext = () => useContext(ConfigContext);
+const InstanceContext = createContext<InstanceContextType>({} as any)
+export const ConfigContextProvider = ConfigContext.Provider
+export const useConfigContext = () => useContext(ConfigContext);
+export const BackendContextProvider = BackendContext.Provider
+export const useBackendContext = () => useContext(BackendContext);
+export const InstanceContextProvider = InstanceContext.Provider
+export const useInstanceContext = () => useContext(InstanceContext);
-export const InstanceContext = createContext<InstanceContextType>({
- id: '',
- token: undefined,
-})
\ No newline at end of file
diff --git a/packages/frontend/src/declaration.d.ts
b/packages/frontend/src/declaration.d.ts
index a1d4b65..6a08212 100644
--- a/packages/frontend/src/declaration.d.ts
+++ b/packages/frontend/src/declaration.d.ts
@@ -14,10 +14,10 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
@@ -101,6 +101,24 @@ export namespace MerchantBackend {
}
namespace Instances {
+ //POST /private/instances/$INSTANCE/auth
+ interface InstanceAuthConfigurationMessage {
+ // Type of authentication.
+ // "external": The mechant backend does not do
+ // any authentication checks. Instead an API
+ // gateway must do the authentication.
+ // "token": The merchant checks an auth token.
+ // See "token" for details.
+ method: "external" | "token";
+
+ // For method "external", this field is mandatory.
+ // The token MUST begin with the string "secret-token:".
+ // After the auth token has been set (with method "token"),
+ // the value must be provided in a "Authorization: Bearer $token"
+ // header.
+ token?: string;
+
+ }
//POST /private/instances
interface InstanceConfigurationMessage {
// The URI where the wallet will send coins. A merchant may have
@@ -121,7 +139,7 @@ export namespace MerchantBackend {
// Optional, if not given authentication will be disabled for
// this instance (hopefully authentication checks are still
// done by some reverse proxy).
- auth_token?: string;
+ auth: InstanceAuthConfigurationMessage;
// The merchant's physical address (to be put into contracts).
address: Location;
@@ -164,12 +182,6 @@ export namespace MerchantBackend {
// Merchant name corresponding to this instance.
name: string;
- // "Authentication" header required to authorize management access
the instance.
- // Optional, if not given authentication will be disabled for
- // this instance (hopefully authentication checks are still
- // done by some reverse proxy).
- auth_token?: string;
-
// The merchant's physical address (to be put into contracts).
address: Location;
@@ -265,6 +277,11 @@ export namespace MerchantBackend {
// offers we make be valid by default?
default_pay_deadline: RelativeTime;
+ // Authentication configuration.
+ // Does not contain the token when token auth is configured.
+ auth: {
+ method: "external" | "token";
+ };
}
interface MerchantAccount {
diff --git a/packages/frontend/src/hooks/backend.ts
b/packages/frontend/src/hooks/backend.ts
index 8781e87..56c85e0 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -23,7 +23,7 @@ import useSWR, { mutate } from 'swr';
import axios from 'axios'
import { MerchantBackend } from '../declaration';
import { useContext } from 'preact/hooks';
-import { BackendContext, InstanceContext } from '../context/backend';
+import { useBackendContext, useInstanceContext } from '../context/backend';
type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError;
@@ -41,6 +41,7 @@ export interface SwrError {
interface HttpResponseError {
data: undefined;
unauthorized: boolean;
+ notfound: boolean;
error?: SwrError;
}
@@ -56,9 +57,17 @@ interface RequestOptions {
async function request(url: string, options: RequestOptions = {}):
Promise<any> {
- const headers = options.token ? { Authorization: `${options.token}` } :
undefined
+ const headers = options.token ? { Authorization: `Bearer ${options.token}` }
: undefined
try {
+ // http://localhost:9966/instances/blog/private/instances
+ // Hack, endpoint should respond 404
+ if (/^\/instances\/[^/]*\/private\/instances$/.test(new
URL(url).pathname)) {
+ console.warn(`HACK: Not going to query ${url}, instead return 404`)
+ throw ({ response: { status: 404 }, message: 'not found' })
+ }
+
+
const res = await axios({
method: options.method || 'get',
url,
@@ -79,18 +88,12 @@ function fetcher(url: string, token: string, backend:
string) {
return request(`${backend}${url}`, { token })
}
-interface BackendMutateAPI {
+interface AdminMutateAPI {
createInstance: (data:
MerchantBackend.Instances.InstanceConfigurationMessage) => Promise<void>;
+ deleteInstance: (id: string) => Promise<void>;
}
-interface BackendInstaceMutateAPI {
- updateInstance: (data:
MerchantBackend.Instances.InstanceReconfigurationMessage) => Promise<void>;
- deleteInstance: () => Promise<void>;
- clearToken: () => Promise<void>;
- setNewToken: (token: string) => Promise<void>;
-}
-
-export function useBackendMutateAPI(): BackendMutateAPI {
- const { url, token } = useContext(BackendContext)
+export function useAdminMutateAPI(): AdminMutateAPI {
+ const { url, token } = useBackendContext()
const createInstance = async (instance:
MerchantBackend.Instances.InstanceConfigurationMessage): Promise<void> => {
await request(`${url}/private/instances`, {
@@ -99,77 +102,105 @@ export function useBackendMutateAPI(): BackendMutateAPI {
data: instance
})
- mutate('/private/instances')
+ mutate(['/private/instances', token, url], null)
+ }
+
+ const deleteInstance = async (id: string): Promise<void> => {
+ await request(`${url}/private/`, {
+ method: 'delete',
+ token,
+ })
+
+ mutate(['/private/instances', token, url], null)
}
- return { createInstance }
+
+ return { createInstance, deleteInstance }
+}
+
+interface InstaceMutateAPI {
+ updateInstance: (data:
MerchantBackend.Instances.InstanceReconfigurationMessage, a?:
MerchantBackend.Instances.InstanceAuthConfigurationMessage) => Promise<void>;
+ deleteInstance: () => Promise<void>;
+ clearToken: () => Promise<void>;
+ setNewToken: (token: string) => Promise<void>;
}
-export function useBackendInstanceMutateAPI(): BackendInstaceMutateAPI {
- const { url } = useContext(BackendContext)
- const { id, token } = useContext(InstanceContext)
+export function useInstanceMutateAPI(): InstaceMutateAPI {
+ const { url: baseUrl, token: adminToken } = useBackendContext()
+ const { token, id, admin } = useInstanceContext()
+
+ const url = !admin ? baseUrl: `${baseUrl}/instances/${id}`
- const updateInstance = async (instance:
MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => {
- await request(`${url}/private/instances/${id}`, {
+ const updateInstance = async (instance:
MerchantBackend.Instances.InstanceReconfigurationMessage, auth?:
MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void> => {
+ await request(`${url}/private/`, {
method: 'patch',
token,
data: instance
})
- mutate('/private/instances', null)
- mutate(`/private/instances/${id}`, null)
+ if (auth) await request(`${url}/private/auth`, {
+ method: 'post',
+ token,
+ data: auth
+ })
+
+ if (adminToken) mutate(['/private/instances', adminToken, baseUrl], null)
+ mutate([`/private/`, token, url], null)
};
-
+
const deleteInstance = async (): Promise<void> => {
- await request(`${url}/private/instances/${id}`, {
+ await request(`${url}/private/`, {
method: 'delete',
- token,
+ token: adminToken,
})
- mutate('/private/instances', null)
- mutate(`/private/instances/${id}`, null)
+ if (adminToken) mutate(['/private/instances', adminToken, baseUrl], null)
+ mutate([`/private/`, token, url], null)
}
const clearToken = async (): Promise<void> => {
- await request(`${url}/private/instances/${id}`, {
- method: 'patch',
+ await request(`${url}/private/auth`, {
+ method: 'post',
token,
- data: { auth_token: null }
+ data: { method: 'external' }
})
- mutate(`/private/instances/${id}`, null)
+ mutate([`/private/`, token, url], null)
}
- const setNewToken = async (token: string): Promise<void> => {
- await request(`${url}/private/instances/${id}`, {
- method: 'patch',
+ const setNewToken = async (newToken: string): Promise<void> => {
+ await request(`${url}/private/auth`, {
+ method: 'post',
token,
- data: { auth_token: token }
+ data: { method: 'token', token: newToken }
})
- mutate(`/private/instances/${id}`, null)
+ mutate([`/private/`, token, url], null)
}
return { updateInstance, deleteInstance, setNewToken, clearToken }
}
export function useBackendInstances():
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
- const { url, token } = useContext(BackendContext)
+ const { url, token } = useBackendContext()
const { data, error } = useSWR<MerchantBackend.Instances.InstancesResponse,
SwrError>(['/private/instances', token, url], fetcher)
- return { data, unauthorized: error?.status === 401, error }
+ return { data, unauthorized: error?.status === 401, notfound: error?.status
=== 404, error }
}
export function useBackendInstance():
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> {
- const { url } = useContext(BackendContext);
- const { id, token } = useContext(InstanceContext);
- const { data, error } =
useSWR<MerchantBackend.Instances.QueryInstancesResponse,
SwrError>([`/private/instances/${id}`, token, url], fetcher)
+ const { url: baseUrl } = useBackendContext();
+ const { token, id, admin } = useInstanceContext();
+
+ const url = !admin ? baseUrl: `${baseUrl}/instances/${id}`
+
+ const { data, error } =
useSWR<MerchantBackend.Instances.QueryInstancesResponse,
SwrError>([`/private/`, token, url], fetcher)
- return { data, unauthorized: error?.status === 401, error }
+ return { data, unauthorized: error?.status === 401, notfound: error?.status
=== 404, error }
}
export function useBackendConfig():
HttpResponse<MerchantBackend.VersionResponse> {
- const { url, token } = useContext(BackendContext)
+ const { url, token } = useBackendContext()
const { data, error } = useSWR<MerchantBackend.VersionResponse,
SwrError>(['/config', token, url], fetcher)
- return { data, unauthorized: error?.status === 401, error }
+ return { data, unauthorized: error?.status === 401, notfound: error?.status
=== 404, error }
}
diff --git a/packages/frontend/src/hooks/index.ts
b/packages/frontend/src/hooks/index.ts
index edd76b8..514a458 100644
--- a/packages/frontend/src/hooks/index.ts
+++ b/packages/frontend/src/hooks/index.ts
@@ -25,16 +25,17 @@ import { ValueOrFunction } from '../utils/types';
export function useBackendContextState() {
const [lang, setLang] = useLang()
- const [url, changeBackend] = useBackendURL();
+ const [url, changeBackend, resetBackend] = useBackendURL();
const [token, updateToken] = useBackendDefaultToken();
- return { url, token, changeBackend, updateToken, lang, setLang }
+ return { url, token, changeBackend, updateToken, lang, setLang, resetBackend
}
}
-export function useBackendURL(): [string, StateUpdater<string>] {
+export function useBackendURL(): [string, StateUpdater<string>, () => void] {
const [value, setter] = useNotNullLocalStorage('backend-url', typeof window
!== 'undefined' ? window.location.origin : '')
const checkedSetter = (v: ValueOrFunction<string>) => setter(p => (v
instanceof Function ? v(p) : v).replace(/\/$/, ''))
- return [value, checkedSetter]
+ const reset = () => checkedSetter(typeof window !== 'undefined' ?
window.location.origin : '')
+ return [value, checkedSetter, reset]
}
export function useBackendDefaultToken(): [string | undefined,
StateUpdater<string | undefined>] {
return useLocalStorage('backend-token')
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index 3624af2..3a047b5 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -30,7 +30,7 @@ import { Notifications } from './components/notifications';
import * as messages from './messages'
import { useBackendContextState } from './hooks';
import { useNotifications } from "./hooks/notifications";
-import { BackendContext, ConfigContext } from './context/backend';
+import { BackendContextProvider, ConfigContextProvider, useBackendContext }
from './context/backend';
import { useBackendConfig } from "./hooks/backend";
import { hasKey, onTranslationError } from "./utils/functions";
@@ -46,7 +46,8 @@ export enum RootPaths {
}
export enum InstancePaths {
- update = '/instance/:id/update',
+ details = '/',
+ update = '/update',
}
export function Redirect({ to }: { to: string }): null {
@@ -60,25 +61,29 @@ export default function Application(): VNode {
const state = useBackendContextState()
return (
- <BackendContext.Provider value={state}>
+ <BackendContextProvider value={state}>
<MessageProvider locale={state.lang} onError={onTranslationError}
messages={hasKey(messages, state.lang) ? messages[state.lang] : messages.en}
pathSep={null as any} >
<ApplicationStatusRoutes />
</MessageProvider >
- </BackendContext.Provider>
+ </BackendContextProvider>
);
}
function ApplicationStatusRoutes(): VNode {
const { notifications, pushNotification, removeNotification } =
useNotifications()
- const { changeBackend, updateToken, token } = useContext(BackendContext)
+ const { changeBackend, updateToken, resetBackend } = useBackendContext()
const backendConfig = useBackendConfig();
const i18n = useMessageTemplate()
- const cleaner = useCallback(() => { updateToken(undefined) }, [])
- const [cleaners, setCleaners] = useState([cleaner])
+ const tokenCleaner = useCallback(() => { updateToken(undefined) }, [])
+ const [cleaners, setCleaners] = useState([tokenCleaner])
const addTokenCleaner = (c: () => void) => setCleaners(cs => [...cs, c])
- const addTokenCleanerNemo = useCallback((c: () => void) => {
addTokenCleaner(c) }, [cleaner])
+ const addTokenCleanerMemo = useCallback((c: () => void) => {
addTokenCleaner(c) }, [tokenCleaner])
+ const clearAllTokens = () => {
+ cleaners.forEach(c => c())
+ resetBackend()
+ }
const v = `${backendConfig.data?.currency} ${backendConfig.data?.version}`
const ctx = useMemo(() => ({ currency: backendConfig.data?.currency || '',
version: backendConfig.data?.version || '' }), [v])
@@ -89,10 +94,7 @@ function ApplicationStatusRoutes(): VNode {
if (backendConfig.unauthorized) {
return <div id="app">
- <Menu onLogout={() => {
- cleaners.forEach(c => c())
- route(RootPaths.list_instances)
- }} />
+ <Menu />
<LoginPage
onConfirm={(url: string, token?: string) => {
changeBackend(url)
@@ -104,15 +106,12 @@ function ApplicationStatusRoutes(): VNode {
}
return <div id="app">
- <Menu onLogout={() => {
- cleaners.forEach(c => c())
- route(RootPaths.list_instances)
- }} />
+ <Menu />
<LoginPage
withMessage={{
message: i18n`Couldnt access the server`,
type: 'ERROR',
- description: !backendConfig.data && backendConfig.error ? i18n`Got
message: ${backendConfig.error.message} from: ${backendConfig.error.backend}
(hasToken: ${backendConfig.error.hasToken})` : undefined,
+ description: i18n`Got message: ${backendConfig.error.message} from:
${backendConfig.error.backend} (hasToken: ${backendConfig.error.hasToken})`,
}}
onConfirm={(url: string, token?: string) => {
changeBackend(url)
@@ -124,14 +123,9 @@ function ApplicationStatusRoutes(): VNode {
}
return <div id="app" class="has-navbar-fixed-top">
- <ConfigContext.Provider value={ctx}>
- <Menu sidebar onLogout={() => {
- cleaners.forEach(c => c())
- route(RootPaths.list_instances)
- }} />
+ <ConfigContextProvider value={ctx}>
<Notifications notifications={notifications}
removeNotification={removeNotification} />
- <Route default component={ApplicationReadyRoutes}
pushNotification={pushNotification} addTokenCleaner={addTokenCleanerNemo} /> :
- {/* <Route default component={LoginWithError} /> */}
- </ConfigContext.Provider>
+ <Route default component={ApplicationReadyRoutes}
pushNotification={pushNotification} addTokenCleaner={addTokenCleanerMemo}
clearAllTokens={clearAllTokens}/> :
+ </ConfigContextProvider>
</div>
}
diff --git a/packages/frontend/src/messages/en.po
b/packages/frontend/src/messages/en.po
index 8e34568..717e4f0 100644
--- a/packages/frontend/src/messages/en.po
+++ b/packages/frontend/src/messages/en.po
@@ -73,6 +73,9 @@ msgstr "Wire fee Amortization"
msgid "fields.instance.address.label"
msgstr "Address"
+msgid "Could not infer instance id from url %s"
+msgstr "Could not infer instance id from url %s"
+
msgid "fields.instance.address.country.label"
msgstr "Country"
diff --git a/packages/frontend/src/routes/instances/create/CreatePage.tsx
b/packages/frontend/src/routes/instances/create/CreatePage.tsx
index 2a595a6..ae76796 100644
--- a/packages/frontend/src/routes/instances/create/CreatePage.tsx
+++ b/packages/frontend/src/routes/instances/create/CreatePage.tsx
@@ -20,7 +20,7 @@
*/
import { h, VNode } from "preact";
-import { useContext, useState } from "preact/hooks";
+import { useState } from "preact/hooks";
import { MerchantBackend } from "../../../declaration";
import * as yup from 'yup';
import { FormErrors, FormProvider } from "../../../components/form/Field"
@@ -30,12 +30,12 @@ import { Input } from "../../../components/form/Input";
import { InputSecured } from "../../../components/form/InputSecured";
import { InputWithAddon } from "../../../components/form/InputWithAddon";
import { InputGroup } from "../../../components/form/InputGroup";
-import { BackendContext, ConfigContext } from "../../../context/backend";
+import { useConfigContext, useBackendContext } from "../../../context/backend";
import { InputDuration } from "../../../components/form/InputDuration";
import { InputCurrency } from "../../../components/form/InputCurrency";
import { InputPayto } from "../../../components/form/InputPayto";
-type Entity = MerchantBackend.Instances.InstanceConfigurationMessage
+type Entity = MerchantBackend.Instances.InstanceConfigurationMessage &
{auth_token?: string}
interface Props {
onCreate: (d: Entity) => void;
@@ -61,6 +61,11 @@ export function CreatePage({ onCreate, isLoading, onBack }:
Props): VNode {
const submit = (): void => {
try {
+ // use conversion instead of this
+ const newToken = value.auth_token;
+ value.auth_token = undefined;
+ value.auth = newToken === null || newToken === undefined ? { method:
"external" } : { method: "token", token: `secret-token:${newToken}` };
+ // remove above use conversion
schema.validateSync(value, { abortEarly: false })
onCreate(schema.cast(value) as Entity);
} catch (err) {
@@ -69,40 +74,10 @@ export function CreatePage({ onCreate, isLoading, onBack }:
Props): VNode {
setErrors(pathMessages)
}
}
- const backend = useContext(BackendContext)
- const config = useContext(ConfigContext)
+ const backend = useBackendContext()
+ const config = useConfigContext()
return <div>
- <section class="section is-title-bar">
-
- <div class="level">
- <div class="level-left">
- <div class="level-item">
- <ul>
- <li><Message id="Merchant" /></li>
- <li><Message id="Instances" /></li>
- </ul>
- </div>
- </div>
- </div>
- </section>
-
- <section class={isLoading ? "hero is-hero-bar" : "hero is-hero-bar
is-loading"}>
- <div class="hero-body">
- <div class="level">
- <div class="level-left">
- <div class="level-item">
- <h1 class="title">
- <Message id="Create new instances" />
- </h1>
- </div>
- </div>
- <div class="level-right" style="display: none;">
- <div class="level-item" />
- </div>
- </div>
- </div>
- </section>
<section class="section is-main-section">
<div class="columns">
@@ -137,10 +112,12 @@ export function CreatePage({ onCreate, isLoading, onBack
}: Props): VNode {
<InputDuration<Entity> name="default_wire_transfer_delay" />
</FormProvider>
- <div class="buttons is-right">
+
+ <div class="buttons is-right mt-5">
<button class="button" onClick={onBack} ><Message id="Cancel"
/></button>
<button class="button is-success" onClick={submit} ><Message
id="Confirm" /></button>
</div>
+
</div>
<div class="column" />
</div>
diff --git a/packages/frontend/src/routes/instances/create/index.tsx
b/packages/frontend/src/routes/instances/create/index.tsx
index 819b549..f5591dd 100644
--- a/packages/frontend/src/routes/instances/create/index.tsx
+++ b/packages/frontend/src/routes/instances/create/index.tsx
@@ -15,7 +15,7 @@
*/
import { h, VNode } from "preact";
import { MerchantBackend } from "../../../declaration";
-import { useBackendMutateAPI } from "../../../hooks/backend";
+import { useAdminMutateAPI } from "../../../hooks/backend";
import { CreatePage } from "./CreatePage";
interface Props {
@@ -25,7 +25,7 @@ interface Props {
}
export default function Create({ onBack, onConfirm, onError }: Props): VNode {
- const { createInstance } = useBackendMutateAPI();
+ const { createInstance } = useAdminMutateAPI();
return <CreatePage
onBack={onBack}
diff --git a/packages/frontend/src/routes/instances/details/DetailPage.tsx
b/packages/frontend/src/routes/instances/details/DetailPage.tsx
index 9eccd99..687610a 100644
--- a/packages/frontend/src/routes/instances/details/DetailPage.tsx
+++ b/packages/frontend/src/routes/instances/details/DetailPage.tsx
@@ -32,7 +32,6 @@ interface Props {
onUpdate: () => void;
onDelete: () => void;
selected: MerchantBackend.Instances.QueryInstancesResponse;
- isLoading: boolean;
}
interface KeyValue {
@@ -50,32 +49,18 @@ function convert(from:
MerchantBackend.Instances.QueryInstancesResponse): Entity
return { ...defaults, ...rest, payto_uris };
}
-export function DetailPage({ onUpdate, isLoading, selected, onDelete }:
Props): VNode {
+export function DetailPage({ onUpdate, selected, onDelete }: Props): VNode {
const [value, valueHandler] = useState<Partial<Entity>>(convert(selected))
const [errors, setErrors] = useState<KeyValue>({})
return <div>
- <section class="section is-title-bar">
-
- <div class="level">
- <div class="level-left">
- <div class="level-item">
- <ul>
- <li><Message id="Merchant" /></li>
- <li><Message id="Instances" /></li>
- </ul>
- </div>
- </div>
- </div>
- </section>
-
- <section class={isLoading ? "hero is-hero-bar" : "hero is-hero-bar
is-loading"}>
+ <section class="hero is-hero-bar">
<div class="hero-body">
<div class="level">
<div class="level-left">
<div class="level-item">
<h1 class="title">
- <Message id="Instance details" />
+ Here goes the instance description
</h1>
</div>
</div>
@@ -96,16 +81,6 @@ export function DetailPage({ onUpdate, isLoading, selected,
onDelete }: Props):
<Input<Entity> name="payto_uris" readonly />
</FormProvider>
- <div class="buttons is-right">
- <button class="button is-danger" onClick={() => onDelete()} >
- <span class="icon"><i class="mdi mdi-delete" /></span>
- <span><Message id="delete" /></span>
- </button>
- <button class="button is-success" onClick={() => onUpdate()} >
- <span class="icon"><i class="mdi mdi-pen" /></span>
- <span><Message id="update" /></span>
- </button>
- </div>
</div>
<div class="column" />
</div>
diff --git a/packages/frontend/src/routes/instances/details/index.tsx
b/packages/frontend/src/routes/instances/details/index.tsx
index 32b5a4b..e0a3248 100644
--- a/packages/frontend/src/routes/instances/details/index.tsx
+++ b/packages/frontend/src/routes/instances/details/index.tsx
@@ -14,10 +14,10 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { Fragment, h, VNode } from "preact";
-import { useContext, useState } from "preact/hooks";
-import { InstanceContext } from "../../../context/backend";
+import { useState } from "preact/hooks";
+import { useInstanceContext } from "../../../context/backend";
import { Notification } from "../../../utils/types";
-import { useBackendInstance, useBackendInstanceMutateAPI, SwrError } from
"../../../hooks/backend";
+import { useBackendInstance, useInstanceMutateAPI, SwrError } from
"../../../hooks/backend";
import { DetailPage } from "./DetailPage";
import { DeleteModal } from "../../../components/modal";
@@ -30,11 +30,11 @@ interface Props {
}
export default function Detail({ onUpdate, onLoadError, onUnauthorized,
pushNotification, onDelete }: Props): VNode {
- const { id } = useContext(InstanceContext)
+ const { id } = useInstanceContext()
const details = useBackendInstance()
const [deleting, setDeleting] = useState<boolean>(false)
- const { deleteInstance } = useBackendInstanceMutateAPI()
+ const { deleteInstance } = useInstanceMutateAPI()
if (!details.data) {
if (details.unauthorized) return onUnauthorized()
@@ -46,7 +46,6 @@ export default function Detail({ onUpdate, onLoadError,
onUnauthorized, pushNoti
return <Fragment>
<DetailPage
- isLoading={false}
selected={details.data}
onUpdate={onUpdate}
onDelete={() => setDeleting(true)}
diff --git a/packages/frontend/src/routes/instances/list/index.tsx
b/packages/frontend/src/routes/instances/list/index.tsx
index 81e920a..19e3696 100644
--- a/packages/frontend/src/routes/instances/list/index.tsx
+++ b/packages/frontend/src/routes/instances/list/index.tsx
@@ -21,7 +21,7 @@
import { Fragment, h, VNode } from 'preact';
import { View } from './View';
-import { useBackendInstances, useBackendInstanceMutateAPI, SwrError } from
'../../../hooks/backend';
+import { useAdminMutateAPI } from '../../../hooks/backend';
import { useState } from 'preact/hooks';
import { MerchantBackend } from '../../../declaration';
import { Notification } from '../../../utils/types';
@@ -29,42 +29,18 @@ import { DeleteModal } from '../../../components/modal';
interface Props {
pushNotification: (n: Notification) => void;
- onUnauthorized: () => VNode;
- onQueryError: (e: SwrError) => VNode;
onCreate: () => void;
onUpdate: (id: string) => void;
+ instances: MerchantBackend.Instances.Instance[]
}
-export default function Instances({ pushNotification, onUnauthorized,
onQueryError, onCreate, onUpdate }: Props): VNode {
- const list = useBackendInstances()
+export default function Instances({ pushNotification, instances, onCreate,
onUpdate }: Props): VNode {
const [deleting, setDeleting] = useState<MerchantBackend.Instances.Instance
| null>(null)
- const { deleteInstance } = useBackendInstanceMutateAPI()
-
- const isLoadingTheList = (!list.data && !list.error)
- const error = !list.data && list.error
-
- if (!list.data) {
- if (list.unauthorized) return onUnauthorized()
- if (list.error) return onQueryError(list.error)
- return <div id="app">
- <section class="section is-title-bar">
-
- <div class="level">
- <div class="level-left">
- <div class="level-item">
- <ul>
- <li>loading</li>
- </ul>
- </div>
- </div>
- </div>
- </section>
- </div>
- }
+ const { deleteInstance } = useAdminMutateAPI()
return <Fragment>
- <View instances={list.data.instances}
- isLoading={isLoadingTheList}
+ <View instances={instances}
+ isLoading={false}
onDelete={setDeleting}
onCreate={onCreate}
onUpdate={onUpdate}
@@ -75,7 +51,7 @@ export default function Instances({ pushNotification,
onUnauthorized, onQueryErr
onCancel={() => setDeleting(null)}
onConfirm={async (): Promise<void> => {
try {
- await deleteInstance()
+ await deleteInstance(deleting.id)
pushNotification({ message: 'delete_success', type: 'SUCCESS' })
} catch (e) {
pushNotification({ message: 'delete_error', type: 'ERROR' })
diff --git a/packages/frontend/src/routes/instances/update/UpdatePage.tsx
b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
index 1a7554f..6fde628 100644
--- a/packages/frontend/src/routes/instances/update/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
@@ -30,21 +30,21 @@ import { InstanceUpdateSchema as schema } from
'../../../schemas'
import { Message } from "preact-messages";
import { Input } from "../../../components/form/Input";
import { InputSecured } from "../../../components/form/InputSecured";
-import { ConfigContext } from "../../../context/backend";
+import { useConfigContext, useInstanceContext } from
"../../../context/backend";
import { InputDuration } from "../../../components/form/InputDuration";
import { InputCurrency } from "../../../components/form/InputCurrency";
import { InputPayto } from "../../../components/form/InputPayto";
-type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage
+type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage &
{auth_token?: string}
interface Props {
- onUpdate: (d: Entity) => void;
+ onUpdate: (d: Entity, auth?:
MerchantBackend.Instances.InstanceAuthConfigurationMessage) => void;
selected: MerchantBackend.Instances.QueryInstancesResponse;
isLoading: boolean;
onBack: () => void;
}
-function convert(from: MerchantBackend.Instances.QueryInstancesResponse):
Entity {
+function convert(from: MerchantBackend.Instances.QueryInstancesResponse,
token?: string): Entity {
const { accounts, ...rest } = from
const payto_uris = accounts.filter(a => a.active).map(a => a.payto_uri)
const defaults = {
@@ -52,19 +52,35 @@ function convert(from:
MerchantBackend.Instances.QueryInstancesResponse): Entity
default_pay_delay: { d_ms: 1000 * 60 * 60 }, //one hour
default_wire_transfer_delay: { d_ms: 1000 * 60 * 60 * 2 }, //two hours
}
- return { ...defaults, ...rest, payto_uris };
+ return { ...defaults, ...rest, payto_uris, auth_token: from.auth.method ===
"external" ? undefined : token };
}
-
+function getTokenValuePart(t?: string): string | undefined {
+ if (!t) return t
+ const match = /secret-token:(.*)/.exec(t);
+ if (!match || !match[1]) return undefined;
+ return match[1]
+}
export function UpdatePage({ onUpdate, isLoading, selected, onBack }: Props):
VNode {
- const [value, valueHandler] = useState<Partial<Entity>>(convert(selected))
+ const { token } = useInstanceContext()
+ const currentTokenValue = getTokenValuePart(token)
+ const [value, valueHandler] = useState<Partial<Entity>>(convert(selected,
currentTokenValue))
const [errors, setErrors] = useState<FormErrors<Entity>>({})
const submit = (): void => {
try {
+ // use conversion instead of this
+ const newToken = value.auth_token;
+ value.auth_token = undefined;
+ const auth: MerchantBackend.Instances.InstanceAuthConfigurationMessage |
undefined =
+ newToken === currentTokenValue ? undefined : (newToken === null ?
+ { method: "external" } :
+ { method: "token", token: `secret-token:${newToken}` });
+
+ // remove above use conversion
schema.validateSync(value, { abortEarly: false })
- onUpdate(schema.cast(value));
+ onUpdate(schema.cast(value), auth);
onBack()
} catch (err) {
const errors = err.inner as yup.ValidationError[]
@@ -72,10 +88,9 @@ export function UpdatePage({ onUpdate, isLoading, selected,
onBack }: Props): VN
setErrors(pathMessages)
}
}
- const config = useContext(ConfigContext)
+ const config = useConfigContext()
return <div>
-
<section class="section is-main-section">
<div class="columns">
<div class="column" />
@@ -108,7 +123,7 @@ export function UpdatePage({ onUpdate, isLoading, selected,
onBack }: Props): VN
</FormProvider>
- <div class="buttons is-right">
+ <div class="buttons is-right mt-4">
<button class="button" onClick={onBack} ><Message id="Cancel"
/></button>
<button class="button is-success" onClick={submit} ><Message
id="Confirm" /></button>
</div>
diff --git a/packages/frontend/src/routes/instances/update/index.tsx
b/packages/frontend/src/routes/instances/update/index.tsx
index ff2473a..2f75258 100644
--- a/packages/frontend/src/routes/instances/update/index.tsx
+++ b/packages/frontend/src/routes/instances/update/index.tsx
@@ -14,11 +14,11 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { Fragment, h, VNode } from "preact";
-import { useContext, useState } from "preact/hooks";
+import { useState } from "preact/hooks";
import { UpdateTokenModal } from "../../../components/modal";
-import { InstanceContext } from "../../../context/backend";
+import { useInstanceContext } from "../../../context/backend";
import { MerchantBackend } from "../../../declaration";
-import { SwrError, useBackendInstance, useBackendInstanceMutateAPI } from
"../../../hooks/backend";
+import { SwrError, useBackendInstance, useInstanceMutateAPI } from
"../../../hooks/backend";
import { UpdatePage } from "./UpdatePage";
interface Props {
@@ -33,10 +33,10 @@ interface Props {
}
export default function Update({ onBack, onConfirm, onLoadError,
onUpdateError, onUnauthorized }: Props): VNode {
- const { updateInstance, setNewToken, clearToken } =
useBackendInstanceMutateAPI();
- const [updatingToken, setUpdatingToken] = useState<string | null>(null)
+ const { updateInstance, setNewToken, clearToken } = useInstanceMutateAPI();
+ const [updatingToken, setUpdatingToken] = useState<boolean>(false)
const details = useBackendInstance()
- const { id } = useContext(InstanceContext)
+ const { id, token } = useInstanceContext()
if (!details.data) {
if (details.unauthorized) return onUnauthorized()
@@ -51,15 +51,16 @@ export default function Update({ onBack, onConfirm,
onLoadError, onUpdateError,
onBack={onBack}
isLoading={false}
selected={details.data}
- onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage):
Promise<void> => {
- return updateInstance(d).then(onConfirm).catch(onUpdateError)
+ onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage,
t?: MerchantBackend.Instances.InstanceAuthConfigurationMessage): Promise<void>
=> {
+ return updateInstance(d,t).then(onConfirm).catch(onUpdateError)
}} />
+ <button class="button" onClick={() => setUpdatingToken(true)}>auth</button>
{updatingToken && <UpdateTokenModal
- oldToken={updatingToken}
+ oldToken={token}
element={{ id, name: details.data.name }}
- onCancel={() => setUpdatingToken(null)}
+ onCancel={() => setUpdatingToken(false)}
onClear={() => clearToken()}
- onConfirm={(newToken) => setNewToken(newToken) }
+ onConfirm={(newToken) => setNewToken(newToken)}
/>}
</Fragment>
}
\ No newline at end of file
diff --git a/packages/frontend/src/schemas/index.ts
b/packages/frontend/src/schemas/index.ts
index f772cf3..c595ea5 100644
--- a/packages/frontend/src/schemas/index.ts
+++ b/packages/frontend/src/schemas/index.ts
@@ -48,11 +48,10 @@ function currencyWithAmountIsValid(value?: string): boolean
{
export const InstanceSchema = yup.object().shape({
id: yup.string().required().meta({type: 'url'}),
name: yup.string().required(),
- auth_token: yup.string()
- .min(8).max(20)
- .optional()
- .nullable()
- .meta({type: 'secured'}),
+ auth: yup.object().shape({
+ method: yup.string().matches(/^(external|token)$/),
+ token: yup.string().optional().nullable(),
+ }),
payto_uris: yup.array().of(yup.string())
.min(1)
.meta({type: 'array'})
diff --git a/packages/frontend/src/utils/constants.ts
b/packages/frontend/src/utils/constants.ts
index a560612..b8c9cdd 100644
--- a/packages/frontend/src/utils/constants.ts
+++ b/packages/frontend/src/utils/constants.ts
@@ -21,4 +21,7 @@
//https://tools.ietf.org/html/rfc8905
export const
PAYTO_REGEX=/^payto:\/\/[a-zA-Z][a-zA-Z0-9-.]+(\/[a-zA-Z0-9\-\.\~\(\)@_%:!$&'*+,;=]*)*\??((amount|receiver-name|sender-name|instruction|message)=[a-zA-Z0-9\-\.\~\(\)@_%:!$'*+,;=]*&?)*$/
+
export const AMOUNT_REGEX=/^[a-zA-Z][a-zA-Z]*:[0-9][0-9,]*\.?[0-9,]*$/
+
+export const INSTANCE_ID_LOOKUP = /^\/instances\/([^/]*)\/?$/
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-merchant-backoffice] branch master updated (6c363c9 -> f3aea32), gnunet, 2021/03/03
- [taler-merchant-backoffice] 06/07: add left label to inputsecured, gnunet, 2021/03/03
- [taler-merchant-backoffice] 04/07: login modal should be sent on update, gnunet, 2021/03/03
- [taler-merchant-backoffice] 02/07: lang selector behavior, gnunet, 2021/03/03
- [taler-merchant-backoffice] 05/07: improve sidebar to show instance id and navbar to show a title, gnunet, 2021/03/03
- [taler-merchant-backoffice] 01/07: fixed some comments, gnunet, 2021/03/03
- [taler-merchant-backoffice] 03/07: improve app routing, gnunet, 2021/03/03
- [taler-merchant-backoffice] 07/07: refactor backend mutate api to use token from instance id, implemented new passwod endpoint,
gnunet <=