gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]