From 72b04b421bec0233085e34dba8e958909f1e059a Mon Sep 17 00:00:00 2001
From: Alexey Lunin <alexey.lunin@vereign.com>
Date: Mon, 5 Feb 2024 08:08:45 +0000
Subject: [PATCH] feat: login with vc

---
 agent-swagger.json                            |  11 ++
 .../ci-cd/helm/templates/deployment.yaml      |  20 +++-
 apps/agent/deployment/ci-cd/helm/values.yaml  |  19 ++-
 apps/agent/src/app/app.module.ts              |   2 +
 apps/agent/src/main.ts                        |   2 +
 .../deployment/ci-cd/helm/values.yaml         |   4 +-
 apps/dashboard/src/components/App/index.tsx   |  12 +-
 .../src/components/RequireAuth/index.tsx      |  15 ++-
 .../src/components/Sidebar/index.tsx          |  10 +-
 .../src/components/VaultHeader/index.tsx      |   5 -
 .../components/VaultHeader/styles.module.scss |   6 -
 apps/dashboard/src/hooks/auth/context.ts      |   4 +-
 apps/dashboard/src/hooks/auth/useAuth.ts      |  40 +++++--
 apps/dashboard/src/hooks/withAuthorize.ts     |  31 -----
 .../src/modals/NewSchemaDialog/index.tsx      |   7 +-
 .../src/modals/RequestProofDialog/index.tsx   |  12 +-
 apps/dashboard/src/routes/config.ts           |   8 +-
 apps/dashboard/src/routes/index.tsx           |  21 ++--
 .../pages/LoginPage/AuthForm/AuthFormStore.ts |  19 ++-
 .../routes/pages/LoginPage/AuthForm/index.tsx |  52 ++++++---
 .../LoginPage/AuthForm/styles.module.scss     |   7 ++
 apps/dashboard/src/utils/displayError.tsx     |  12 +-
 libs/askar/src/agent.utils.ts                 |  81 ++++++-------
 .../askar/src/askar-rest/askar.rest.module.ts |  36 ++----
 libs/askar/src/askar-rest/auth/auth.guard.ts  |  83 ++++++++++++++
 libs/askar/src/askar-rest/auth/basic.guard.ts |   9 --
 .../src/askar-rest/auth/basic.middleware.ts   |  48 --------
 .../src/askar-rest/auth/basic.strategy.ts     |  30 -----
 libs/askar/src/askar-rest/rest.controller.ts  |   3 +
 libs/askar/src/askar/agent.service.ts         |   2 +-
 libs/askar/src/askar/askar.service.ts         |  12 +-
 libs/clients/src/ocmengine-client.ts          | 108 ++++++++++++------
 libs/config/src/config/agent.config.ts        |   9 +-
 libs/config/src/index.ts                      |   1 +
 .../src/interfaces/agent.config.interface.ts  |   8 +-
 libs/config/src/schemas/agent.schema.ts       |   2 -
 libs/config/src/schemas/auth.schema.ts        |   7 ++
 libs/nats/src/consumer.nats.service.ts        |   2 +-
 package.json                                  |   1 +
 yarn.lock                                     |  87 +++++++++++++-
 40 files changed, 528 insertions(+), 320 deletions(-)
 delete mode 100644 apps/dashboard/src/hooks/withAuthorize.ts
 create mode 100644 libs/askar/src/askar-rest/auth/auth.guard.ts
 delete mode 100644 libs/askar/src/askar-rest/auth/basic.guard.ts
 delete mode 100644 libs/askar/src/askar-rest/auth/basic.middleware.ts
 delete mode 100644 libs/askar/src/askar-rest/auth/basic.strategy.ts
 create mode 100644 libs/config/src/schemas/auth.schema.ts

diff --git a/agent-swagger.json b/agent-swagger.json
index 29bddefd..5abd221f 100644
--- a/agent-swagger.json
+++ b/agent-swagger.json
@@ -990,6 +990,17 @@
   "tags": [],
   "servers": [],
   "components": {
+    "securitySchemes": {
+      "bearer": {
+        "scheme": "bearer",
+        "bearerFormat": "JWT",
+        "type": "http"
+      },
+      "basic": {
+        "type": "http",
+        "scheme": "basic"
+      }
+    },
     "schemas": {
       "CreateInvitationResponseDto": {
         "type": "object",
diff --git a/apps/agent/deployment/ci-cd/helm/templates/deployment.yaml b/apps/agent/deployment/ci-cd/helm/templates/deployment.yaml
index d23d200c..fb4c5daf 100644
--- a/apps/agent/deployment/ci-cd/helm/templates/deployment.yaml
+++ b/apps/agent/deployment/ci-cd/helm/templates/deployment.yaml
@@ -69,6 +69,12 @@ spec:
             value: {{ .Values.ocm.agent.rateLimit | quote }}
           - name: ALLOWED_ORIGINS
             value: {{ .Values.ocm.agent.allowedOrigin | quote }}
+          - name: AUTH_BASIC_USER
+            value: {{ .Values.ocm.agent.api.basic.user | quote }}
+          - name: AUTH_BASIC_PASS
+            value: {{ .Values.ocm.agent.api.basic.pass | quote }}
+          - name: AUTH_JWT_PUBLIC_KEY
+            value: {{ .Values.ocm.agent.api.jwt.publicKey | quote }}
         {{- else if eq .Release.Namespace "ocm-test" }}
           - name: LEDGERS
             value: {{ .Values.ocmtest.agent.ledgers | quote }}
@@ -102,6 +108,12 @@ spec:
             value: {{ .Values.ocmtest.agent.rateLimit | quote }}
           - name: ALLOWED_ORIGINS
             value: {{ .Values.ocmtest.agent.allowedOrigin | quote }}
+          - name: AUTH_BASIC_USER
+            value: {{ .Values.ocmtest.agent.api.basic.user | quote }}
+          - name: AUTH_BASIC_PASS
+            value: {{ .Values.ocmtest.agent.api.basic.pass | quote }}
+          - name: AUTH_JWT_PUBLIC_KEY
+            value: {{ .Values.ocmtest.agent.api.jwt.publicKey | quote }}
         {{- else if eq .Release.Namespace "hin" }}
           - name: LEDGERS
             value: {{ .Values.hin.agent.ledgers | quote }}
@@ -138,7 +150,13 @@ spec:
           - name: ALLOWED_ORIGINS
             value: {{ .Values.hin.agent.allowedOrigin | quote }}
           - name: AGENT_OOB_GOALS 
-            value: {{ .Values.hin.agent.invitationGoals| quote }}
+            value: {{ .Values.hin.agent.invitationGoals | quote }}
+          - name: AUTH_BASIC_USER
+            value: {{ .Values.hin.agent.api.basic.user | quote }}
+          - name: AUTH_BASIC_PASS
+            value: {{ .Values.hin.agent.api.basic.pass | quote }}
+          - name: AUTH_JWT_PUBLIC_KEY
+            value: {{ .Values.hin.agent.api.jwt.publicKey | quote }}
         {{- end }}
 {{- if .Values.extraVars }}
 {{ toYaml .Values.extraVars | indent 8 }}
diff --git a/apps/agent/deployment/ci-cd/helm/values.yaml b/apps/agent/deployment/ci-cd/helm/values.yaml
index 1144237a..9bc4c9fe 100644
--- a/apps/agent/deployment/ci-cd/helm/values.yaml
+++ b/apps/agent/deployment/ci-cd/helm/values.yaml
@@ -81,6 +81,12 @@ ocm:
     maxMessage: 10
     rateLimit: 5
     allowedOrigin: "*"
+    api:
+      jwt:
+        publicKey: ""
+      basic:
+        user: "ocm-admin"
+        pass: ""
 
   # nats:
   #   server: "nats.ocm:4222"
@@ -120,6 +126,12 @@ ocmtest:
     maxMessage: 10
     rateLimit: 5
     allowedOrigin: "*"
+    api:
+      jwt:
+        publicKey: ""
+      basic:
+        user: "ocmtest-admin"
+        pass: ""    
 
 
   # nats:
@@ -165,7 +177,12 @@ hin:
       pass: ""
     allowedOrigin: "*"
     invitationGoals: "connection.exchange"
-       
+    api:
+      jwt:
+        publicKey: ""
+      basic:
+        user: "ocmhin-admin"
+        pass: ""       
 
 service:
   port: 8080
diff --git a/apps/agent/src/app/app.module.ts b/apps/agent/src/app/app.module.ts
index 151009b8..203fd082 100644
--- a/apps/agent/src/app/app.module.ts
+++ b/apps/agent/src/app/app.module.ts
@@ -5,6 +5,7 @@ import { ConfigModule } from "@nestjs/config";
 import {
   agentConfig,
   agentSchema,
+  authSchema,
   gatewayConfig,
   gatewaySchema,
   ledgersConfig,
@@ -16,6 +17,7 @@ import Joi from "joi";
 
 const validationSchema = Joi.object({
   agent: agentSchema,
+  auth: authSchema,
   ledgers: ledgersSchema,
   nats: natsSchema,
   gateway: gatewaySchema,
diff --git a/apps/agent/src/main.ts b/apps/agent/src/main.ts
index 426c955e..0e76b0d0 100644
--- a/apps/agent/src/main.ts
+++ b/apps/agent/src/main.ts
@@ -32,6 +32,8 @@ async function bootstrap() {
       .setTitle("Agent")
       .setDescription("Agent API")
       .setVersion("1.0")
+      .addBearerAuth()
+      .addBasicAuth()
       .build();
 
     const document = SwaggerModule.createDocument(app, config);
diff --git a/apps/dashboard/deployment/ci-cd/helm/values.yaml b/apps/dashboard/deployment/ci-cd/helm/values.yaml
index 9d19349e..71840be4 100644
--- a/apps/dashboard/deployment/ci-cd/helm/values.yaml
+++ b/apps/dashboard/deployment/ci-cd/helm/values.yaml
@@ -62,7 +62,7 @@ ocm:
     agent: 
       ws: "wss://ssi-dev.vereign.com/api-issuer"
       http: "https://ssi-dev.vereign.com/api-issuer"
-    tsa: "https://tsa.vereign.com"
+    tsa: "https://ssi-dev.vereign.com/ocm/login"
     basepath: "/ocm/dashboard"
 
 ocmtest:
@@ -70,7 +70,7 @@ ocmtest:
     agent: 
       ws: "wss://ssi-dev.vereign.com/api-holder"
       http: "https://ssi-dev.vereign.com/api-holder"
-    tsa: "https://tsa.vereign.com"
+    tsa: "https://ssi-dev.vereign.com/ocm-test/login"
     basepath: "/ocm-test/dashboard"
 
 service:
diff --git a/apps/dashboard/src/components/App/index.tsx b/apps/dashboard/src/components/App/index.tsx
index 902d5a12..4050dbcd 100644
--- a/apps/dashboard/src/components/App/index.tsx
+++ b/apps/dashboard/src/components/App/index.tsx
@@ -3,23 +3,27 @@ import { ToastContainer } from "react-toastify";
 import PageRoutes from "@dashboard/routes";
 import modalStore from "@dashboard/store/modalStore";
 import { observer } from "mobx-react";
-import s from "./styles.module.scss";
 import useGlobalAuth from "@dashboard/hooks/useGlobalAuth";
 import { setConfig as setOcmEngineConfig } from "@dashboard/engine-api";
 import getConfig from "@dashboard/utils/getConfig";
+import s from "./styles.module.scss";
 
 const App = observer(() => {
   const auth = useGlobalAuth();
-  const [isAppLoading] = useState(false);
+  const [isAppLoading, setAppLoading] = useState(true);
 
   useEffect(() => {
     const config = getConfig();
     setOcmEngineConfig({
+      onUnauthorized: () => {
+        auth.setToken(null);
+      },
       wsUrl: config.OCMENGINE_WS_URL,
       httpUrl: config.OCMENGINE_HTTP_URL,
-      getToken: async () => auth.token || "",
+      getToken: async () => auth.getToken() || "",
     });
-  }, [auth.token]);
+    setAppLoading(false);
+  }, [auth]);
 
   let content;
   if (isAppLoading) {
diff --git a/apps/dashboard/src/components/RequireAuth/index.tsx b/apps/dashboard/src/components/RequireAuth/index.tsx
index f9911f3f..0690a2d8 100644
--- a/apps/dashboard/src/components/RequireAuth/index.tsx
+++ b/apps/dashboard/src/components/RequireAuth/index.tsx
@@ -7,9 +7,10 @@ import { Spin } from "antd";
 
 export interface RequireAuthProps {
   children: React.JSX.Element;
+  restricted: boolean;
 }
 
-const RequireAuth = observer(({ children }: RequireAuthProps) => {
+const RequireAuth = observer(({ children, restricted }: RequireAuthProps) => {
   const { loading, authorized } = useGlobalAuth();
   const location = useLocation();
 
@@ -17,11 +18,11 @@ const RequireAuth = observer(({ children }: RequireAuthProps) => {
     return <Spin />;
   }
 
-  if (!authorized) {
-    // Redirect them to the /login page, but save the current location they were
-    // trying to go to when they were redirected. This allows us to send them
-    // along to that page after they login, which is a nicer user experience
-    // than dropping them off on the home page.
+  if ((authorized && restricted) || (!authorized && !restricted)) {
+    return children;
+  } else if (authorized && !restricted) {
+    return <Navigate to={config.connection_list.getLink()} replace />;
+  } else {
     return (
       <Navigate
         to={config.login.getLink()}
@@ -30,8 +31,6 @@ const RequireAuth = observer(({ children }: RequireAuthProps) => {
       />
     );
   }
-
-  return children;
 });
 
 export default RequireAuth;
diff --git a/apps/dashboard/src/components/Sidebar/index.tsx b/apps/dashboard/src/components/Sidebar/index.tsx
index 3ccd9cf6..2b6c4b2f 100644
--- a/apps/dashboard/src/components/Sidebar/index.tsx
+++ b/apps/dashboard/src/components/Sidebar/index.tsx
@@ -1,9 +1,11 @@
 import React from "react";
 import { Link } from "react-router-dom";
 import config from "@dashboard/routes/config";
+import useGlobalAuth from "@dashboard/hooks/useGlobalAuth";
 import s from "./styles.module.scss";
 
 const Sidebar = () => {
+  const auth = useGlobalAuth();
   return (
     <div className={s.sidebar}>
       <div className={s.sidebarBar} />
@@ -34,7 +36,13 @@ const Sidebar = () => {
         VCM self-issued credentials
       </Link>
       <div className={s.space} />
-      <Link className={s.logoutLink} to={config.welcome.getLink()}>
+      <Link
+        onClick={() => {
+          auth.setToken(null);
+        }}
+        className={s.logoutLink}
+        to={config.welcome.getLink()}
+      >
         Logout
       </Link>
     </div>
diff --git a/apps/dashboard/src/components/VaultHeader/index.tsx b/apps/dashboard/src/components/VaultHeader/index.tsx
index 27d829d3..d40456f9 100644
--- a/apps/dashboard/src/components/VaultHeader/index.tsx
+++ b/apps/dashboard/src/components/VaultHeader/index.tsx
@@ -31,11 +31,6 @@ const VaultHeader: React.FC<Props> = ({ hideTry }) => {
             )}
           </div>
           <div className={s.column}>
-            <div className={s.toDashboard}>
-              <Link to={config.connection_list.getLink()}>
-                Jump to dashboard
-              </Link>
-            </div>
             <div>
               <img
                 className={s.vaultImage}
diff --git a/apps/dashboard/src/components/VaultHeader/styles.module.scss b/apps/dashboard/src/components/VaultHeader/styles.module.scss
index d60adbf7..377f11ce 100644
--- a/apps/dashboard/src/components/VaultHeader/styles.module.scss
+++ b/apps/dashboard/src/components/VaultHeader/styles.module.scss
@@ -76,9 +76,3 @@
   color: #14b7cc;
   font-weight: bold;
 }
-
-.toDashboard {
-  position: absolute;
-  top: 40px;
-  right: 150px;
-}
diff --git a/apps/dashboard/src/hooks/auth/context.ts b/apps/dashboard/src/hooks/auth/context.ts
index 2bb8a4d1..7a8eeef3 100644
--- a/apps/dashboard/src/hooks/auth/context.ts
+++ b/apps/dashboard/src/hooks/auth/context.ts
@@ -3,7 +3,7 @@ import { createContext } from "react";
 interface Auth {
   authorized: boolean;
   loading: boolean;
-  token: string | null;
+  getToken: () => string | null;
   setToken: (token: string | null) => void;
 }
 
@@ -13,7 +13,7 @@ const authContext = createContext<Auth>({
   setToken: () => {
     console.warn("Context not overwritten");
   },
-  token: null,
+  getToken: () => null,
 });
 
 export default authContext;
diff --git a/apps/dashboard/src/hooks/auth/useAuth.ts b/apps/dashboard/src/hooks/auth/useAuth.ts
index 82e205e3..2eda3980 100644
--- a/apps/dashboard/src/hooks/auth/useAuth.ts
+++ b/apps/dashboard/src/hooks/auth/useAuth.ts
@@ -2,16 +2,41 @@ import { useEffect, useState } from "react";
 
 const LS_KEY = "token";
 
+function isJwtValid(token: string) {
+  try {
+    const parts = token.split(".");
+    if (parts.length !== 3) {
+      throw new Error("Invalid JWT token");
+    }
+
+    const payload = JSON.parse(atob(parts[1]));
+
+    if (!payload.exp) {
+      throw new Error("JWT token does not contain expiration time");
+    }
+
+    const currentTime = Math.floor(Date.now() / 1000);
+    return payload.exp > currentTime;
+  } catch (e: unknown) {
+    if (e instanceof Error) {
+      console.error(e.message);
+    }
+    return false;
+  }
+}
+
 const useAuth = () => {
   const [authorized, setAuthorized] = useState(false);
-  const [token, setToken] = useState<string | null>(null);
   const [loading, setLoading] = useState(true);
 
   useEffect(() => {
     const lsToken = localStorage.getItem(LS_KEY);
     if (lsToken) {
-      setToken(lsToken);
-      setAuthorized(true);
+      if (isJwtValid(lsToken)) {
+        setAuthorized(true);
+      } else {
+        localStorage.removeItem(LS_KEY);
+      }
     }
     setLoading(false);
   }, []);
@@ -19,20 +44,17 @@ const useAuth = () => {
   const updateToken = (token: string | null) => {
     if (token) {
       localStorage.setItem(LS_KEY, token);
-      setToken(token);
       setAuthorized(true);
     } else {
       localStorage.removeItem(LS_KEY);
-      setToken(null);
       setAuthorized(false);
     }
   };
 
   return {
-    token,
-    // TODO disable login functionality
-    authorized: true,
-    loading: false,
+    authorized,
+    loading,
+    getToken: () => localStorage.getItem(LS_KEY),
     setToken: updateToken,
   };
 };
diff --git a/apps/dashboard/src/hooks/withAuthorize.ts b/apps/dashboard/src/hooks/withAuthorize.ts
deleted file mode 100644
index b6b018d4..00000000
--- a/apps/dashboard/src/hooks/withAuthorize.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { useEffect, useState } from "react";
-
-const LS_KEY = "token";
-
-const useAuth = () => {
-  const [authorized, setAuthorized] = useState(false);
-  const [loading, setLoading] = useState(true);
-
-  useEffect(() => {
-    localStorage.getItem(LS_KEY) && setAuthorized(true);
-    setLoading(false);
-  }, []);
-
-  const setToken = (token: string | null) => {
-    if (token) {
-      localStorage.setItem(LS_KEY, token);
-      setAuthorized(true);
-    } else {
-      localStorage.removeItem(LS_KEY);
-      setAuthorized(false);
-    }
-  };
-
-  return {
-    authorized,
-    loading,
-    setToken,
-  };
-};
-
-export default useAuth;
diff --git a/apps/dashboard/src/modals/NewSchemaDialog/index.tsx b/apps/dashboard/src/modals/NewSchemaDialog/index.tsx
index c31e905e..40e3c035 100644
--- a/apps/dashboard/src/modals/NewSchemaDialog/index.tsx
+++ b/apps/dashboard/src/modals/NewSchemaDialog/index.tsx
@@ -62,12 +62,11 @@ const NewSchemaDialog = observer(
                 { required: true, message: "Please enter version!" },
                 {
                   pattern: /^(\d+\.)?(\d+\.)?(\*|\d+)$/,
-                  message: 'Invalid format. Please enter the version number in the format \'major.minor.patch\', e.g., \'1.0.0\', \'1.0\' or \'1\'.',
-                }
+                  message:
+                    "Invalid format. Please enter the version number in the format 'major.minor.patch', e.g., '1.0.0', '1.0' or '1'.",
+                },
               ]}
             >
-
-
               <Input />
             </Form.Item>
             <Form.Item
diff --git a/apps/dashboard/src/modals/RequestProofDialog/index.tsx b/apps/dashboard/src/modals/RequestProofDialog/index.tsx
index 98426a3d..09b3d483 100644
--- a/apps/dashboard/src/modals/RequestProofDialog/index.tsx
+++ b/apps/dashboard/src/modals/RequestProofDialog/index.tsx
@@ -36,9 +36,7 @@ const RequestProofDialog = observer(
                 <span>{rowIndex + 1}.</span>
               </div>
               <div className={s.field}>
-                <div className={s.field__label}>
-                  Credential definition
-                </div>
+                <div className={s.field__label}>Credential definition</div>
                 <Select
                   placeholder="Select credential"
                   // style={{ width: 300 }}
@@ -51,12 +49,10 @@ const RequestProofDialog = observer(
                   }))}
                 />
               </div>
-              {row.schemaLoading && <Spin/>}
+              {row.schemaLoading && <Spin />}
               {row.schema && (
                 <div className={s.field}>
-                  <div className={s.field__label}>
-                    Attribute
-                  </div>
+                  <div className={s.field__label}>Attribute</div>
                   <Select
                     placeholder="Select attribute"
                     // style={{ width: 300 }}
@@ -80,7 +76,7 @@ const RequestProofDialog = observer(
                 </Button>
               </div>
             </Space>
-            ))}
+          ))}
           <Space direction="vertical" className={s.actions}>
             <Button onClick={() => store.addRow()}>Add row</Button>
             <Button
diff --git a/apps/dashboard/src/routes/config.ts b/apps/dashboard/src/routes/config.ts
index 7c58f039..f5f42ebe 100644
--- a/apps/dashboard/src/routes/config.ts
+++ b/apps/dashboard/src/routes/config.ts
@@ -66,13 +66,13 @@ const routes = {
     Component: CreatedDidListPage,
     route: "/created-dids",
     getLink: () => "/created-dids",
-    requireAuth: false,
+    requireAuth: true,
   },
   createdInvitations: {
     Component: CreatedInvitationListPage,
     route: "/created-invitations",
     getLink: () => "/created-invitations",
-    requireAuth: false,
+    requireAuth: true,
   },
   login: {
     Component: LoginPage,
@@ -84,13 +84,13 @@ const routes = {
     Component: ResolveDidPage,
     route: "/resolve-did",
     getLink: () => "/resolve-did",
-    requireAuth: false,
+    requireAuth: true,
   },
   vcmSelfIssue: {
     Component: VcmSelfIssuePage,
     route: "/self-issued-creds-in-vcm",
     getLink: () => "/self-issued-creds-in-vcm",
-    requireAuth: false,
+    requireAuth: true,
   },
   authToken: {
     Component: RedirectWithTokenPage,
diff --git a/apps/dashboard/src/routes/index.tsx b/apps/dashboard/src/routes/index.tsx
index 369a730a..5ee65a6b 100644
--- a/apps/dashboard/src/routes/index.tsx
+++ b/apps/dashboard/src/routes/index.tsx
@@ -6,20 +6,17 @@ import config from "./config";
 function PageRoutes() {
   return (
     <Routes>
-      {Object.values(config).map((item) => {
-        let component;
-        if (item.requireAuth) {
-          component = (
-            <RequireAuth>
+      {Object.values(config).map((item) => (
+        <Route
+          key={item.route}
+          path={item.route}
+          element={
+            <RequireAuth restricted={item.requireAuth}>
               <item.Component />
             </RequireAuth>
-          );
-        } else {
-          component = <item.Component />;
-        }
-
-        return <Route key={item.route} path={item.route} element={component} />;
-      })}
+          }
+        />
+      ))}
     </Routes>
   );
 }
diff --git a/apps/dashboard/src/routes/pages/LoginPage/AuthForm/AuthFormStore.ts b/apps/dashboard/src/routes/pages/LoginPage/AuthForm/AuthFormStore.ts
index 216ca223..df563de9 100644
--- a/apps/dashboard/src/routes/pages/LoginPage/AuthForm/AuthFormStore.ts
+++ b/apps/dashboard/src/routes/pages/LoginPage/AuthForm/AuthFormStore.ts
@@ -2,6 +2,7 @@ import { makeAutoObservable, runInAction } from "mobx";
 import tsaApi from "@dashboard/tsa-api";
 import { toast } from "react-toastify";
 import displayError from "@dashboard/utils/displayError";
+import { ApiException } from "@dashboard/engine-api";
 
 type SetToken = (token: string) => void;
 
@@ -36,7 +37,11 @@ class AuthFormStore {
   };
 
   public requestTsaProof = async () => {
-    runInAction(() => (this.loading = true));
+    runInAction(() => {
+      this.loading = true;
+      this.acceptanceAwaiting = false;
+      this.acceptanceExpired = false;
+    });
     try {
       const { proofRecordId, proofUrl, proofUrlShort } =
         await tsaApi.loginLoginInvitation();
@@ -66,7 +71,17 @@ class AuthFormStore {
         this.acceptanceAwaiting = false;
       });
     } catch (e: unknown) {
-      displayError(e);
+      if (
+        e instanceof ApiException ||
+        (e as { isApiException: boolean }).isApiException
+      ) {
+        const apiExError = e as ApiException;
+        if (apiExError.status !== 504) {
+          displayError(e);
+        }
+      } else {
+        displayError(e);
+      }
       runInAction(() => {
         this.acceptanceAwaiting = false;
         this.acceptanceExpired = true;
diff --git a/apps/dashboard/src/routes/pages/LoginPage/AuthForm/index.tsx b/apps/dashboard/src/routes/pages/LoginPage/AuthForm/index.tsx
index ce060719..1c970046 100644
--- a/apps/dashboard/src/routes/pages/LoginPage/AuthForm/index.tsx
+++ b/apps/dashboard/src/routes/pages/LoginPage/AuthForm/index.tsx
@@ -2,16 +2,15 @@ import React, { useState } from "react";
 import { observer } from "mobx-react";
 import AuthFormStore from "./AuthFormStore";
 import { Button, Form, Input, QRCode } from "antd";
-import s from "./styles.module.scss";
 import useGlobalAuth from "@dashboard/hooks/useGlobalAuth";
+import s from "./styles.module.scss";
 
 const LoginPage = observer(() => {
   const auth = useGlobalAuth();
   const [store] = useState(() => new AuthFormStore(auth.setToken));
 
   const handleEnterEmail = ({ email }: { email: string }) => {
-    // TODO Functional deactivated. Auth will be implemented in another branch
-    // store.requestTsaAuthEmail(email);
+    store.requestTsaAuthEmail(email);
   };
 
   return (
@@ -53,10 +52,10 @@ const LoginPage = observer(() => {
 
           <br />
           <Button
+            type="link"
             onClick={() => {
               store.mode = "vc";
-              // Functional deactivated
-              // store.requestTsaProof();
+              store.requestTsaProof();
             }}
           >
             Login using VC
@@ -69,21 +68,34 @@ const LoginPage = observer(() => {
           <div className={s.qrWrapper}>
             <QRCode
               status={
-                !store.proofUrlShort || store.loading ? "loading" : "active"
+                store.acceptanceExpired
+                  ? "expired"
+                  : !store.proofUrlShort || store.loading
+                  ? "loading"
+                  : "active"
               }
               value={store.proofUrlShort || "loading"}
               size={400}
-              icon={`${window.BASE_PATH}/assets/Vereign_Logo_ICON_BLACK.png}`}
+              icon={getFullHttpPath("/assets/Vereign_Logo_ICON_BLACK.png")}
             />
           </div>
-          <div>
-            In case you have another OCM with VC. You can copy this url and
-            accept it
-            <br />
-            <div>{store.proofUrl}</div>
-          </div>
+          {store.acceptanceExpired && (
+            <>
+              <br />
+              <Button
+                type="link"
+                onClick={() => {
+                  store.requestTsaProof();
+                }}
+              >
+                Refresh
+              </Button>
+            </>
+          )}
+
           <br />
           <Button
+            type="link"
             onClick={() => {
               store.mode = "email";
             }}
@@ -96,4 +108,18 @@ const LoginPage = observer(() => {
   );
 });
 
+function getFullHttpPath(partialPath: string) {
+  const domain = window.location.origin;
+  const BASE_PATH = window.BASE_PATH;
+
+  const formattedBasePath = BASE_PATH.endsWith("/")
+    ? BASE_PATH
+    : `${BASE_PATH}/`;
+  const formattedPartialPath = partialPath.startsWith("/")
+    ? partialPath.substring(1)
+    : partialPath;
+
+  return `${domain}${formattedBasePath}${formattedPartialPath}`;
+}
+
 export default LoginPage;
diff --git a/apps/dashboard/src/routes/pages/LoginPage/AuthForm/styles.module.scss b/apps/dashboard/src/routes/pages/LoginPage/AuthForm/styles.module.scss
index a04fe6e0..a147f487 100644
--- a/apps/dashboard/src/routes/pages/LoginPage/AuthForm/styles.module.scss
+++ b/apps/dashboard/src/routes/pages/LoginPage/AuthForm/styles.module.scss
@@ -10,6 +10,7 @@
   border: 3px solid transparent;
   display: flex;
   flex-direction: column;
+  position: relative;
 }
 .formTitle {
   text-align: center;
@@ -30,3 +31,9 @@
   margin: 0 0 100px 0;
   text-align: center;
 }
+
+.adminLogin {
+  position: absolute;
+  right: 0;
+  top: 0;
+}
diff --git a/apps/dashboard/src/utils/displayError.tsx b/apps/dashboard/src/utils/displayError.tsx
index 7f1e87d1..89e89e4a 100644
--- a/apps/dashboard/src/utils/displayError.tsx
+++ b/apps/dashboard/src/utils/displayError.tsx
@@ -25,23 +25,27 @@ function combine(msg1: undefined | string, msg2: string | string[]) {
 const displayError = (e: unknown, msg?: string) => {
   console.error(e);
 
-  if (e instanceof ApiException) {
+  if (
+    e instanceof ApiException ||
+    (e as { isApiException: boolean }).isApiException
+  ) {
+    const apiExError = e as ApiException;
     let serverResponse: {
       message: string | string[];
       statusCode: number;
     } | null;
     try {
-      serverResponse = JSON.parse(e.response);
+      serverResponse = JSON.parse(apiExError.response);
     } catch (parseError: unknown) {
       console.error("Can not parse the server response");
-      toast.error(combine(msg, e.message));
+      toast.error(combine(msg, apiExError.message));
       return;
     }
 
     if (serverResponse && serverResponse.message) {
       toast.error(combine(msg, serverResponse.message));
     } else {
-      toast.error(combine(msg, e.message));
+      toast.error(combine(msg, apiExError.message));
     }
     return;
   }
diff --git a/libs/askar/src/agent.utils.ts b/libs/askar/src/agent.utils.ts
index ca9c060d..4ec26178 100644
--- a/libs/askar/src/agent.utils.ts
+++ b/libs/askar/src/agent.utils.ts
@@ -129,7 +129,7 @@ export const generateKey = async ({
 export const generateDidWeb = async ({
   seed,
   agent,
-  peerAddress
+  peerAddress,
 }: {
   seed: string;
   agent: Agent;
@@ -141,7 +141,7 @@ export const generateDidWeb = async ({
   const parsedUrl = url.parse(peerAddress);
   let hostname = parsedUrl.hostname!;
   const port = parsedUrl.port;
-  let pathname = parsedUrl.pathname?.replace(/^\/+|\/+$/g, '');
+  const pathname = parsedUrl.pathname?.replace(/^\/+|\/+$/g, "");
 
   // If port is specified, encode it
   if (port) {
@@ -150,7 +150,7 @@ export const generateDidWeb = async ({
   // Convert URLs to 'did:web' form
   let didWeb = `did:web:${hostname}`;
   if (pathname) {
-    didWeb += `:${pathname.replace(/\//g, ':')}`;
+    didWeb += `:${pathname.replace(/\//g, ":")}`;
   }
 
   const verificationMethodKey0Id = `${didWeb}#jwt-key0`;
@@ -158,29 +158,23 @@ export const generateDidWeb = async ({
   const jsonDidDoc = {
     "@context": [
       "https://www.w3.org/ns/did/v1",
-      "https://w3id.org/security/suites/ed25519-2018/v1"
+      "https://w3id.org/security/suites/ed25519-2018/v1",
     ],
-    "id": didWeb,
-    "verificationMethod": [
+    id: didWeb,
+    verificationMethod: [
       {
-        "id": verificationMethodKey0Id,
-        "type": "Ed25519VerificationKey2018",
-        "controller": didWeb,
-        "publicKeyBase58": pubKey.publicKeyBase58
+        id: verificationMethodKey0Id,
+        type: "Ed25519VerificationKey2018",
+        controller: didWeb,
+        publicKeyBase58: pubKey.publicKeyBase58,
       },
     ],
-    "authentication": [
-      verificationMethodKey0Id
-    ],
-    "assertionMethod": [
-      verificationMethodKey0Id
-    ],
-    "keyAgreement": [
-      verificationMethodKey0Id
-    ]
+    authentication: [verificationMethodKey0Id],
+    assertionMethod: [verificationMethodKey0Id],
+    keyAgreement: [verificationMethodKey0Id],
   };
 
-  const didDocumentInstance = JsonTransformer.fromJSON(jsonDidDoc, DidDocument)
+  const didDocumentInstance = JsonTransformer.fromJSON(jsonDidDoc, DidDocument);
 
   const recordId = "did:web";
   const existingRecord = await agent.genericRecords.findById(recordId);
@@ -189,13 +183,13 @@ export const generateDidWeb = async ({
   }
   await agent.genericRecords.save({
     id: recordId,
-    content: jsonDidDoc
+    content: jsonDidDoc,
   });
 
   await agent.dids.import({
     did: didWeb,
     didDocument: didDocumentInstance,
-    overwrite: false
+    overwrite: false,
   });
 
   console.log("Generated did:web");
@@ -254,7 +248,7 @@ export const getAskarAnonCredsIndyModules = (networks: any) => {
         new KeyDidResolver(),
         new PeerDidResolver(),
         new WebDidResolver(),
-        new JwkDidResolver()
+        new JwkDidResolver(),
       ],
     }),
     askar: new AskarModule({
@@ -571,9 +565,13 @@ export const attachShortUrlHandler = (server: Express, agent: Agent): void => {
   );
 };
 
-export const attachDidWebHandler = (server: Express, agent: Agent, agentPeerAddress: string): void => {
+export const attachDidWebHandler = (
+  server: Express,
+  agent: Agent,
+  agentPeerAddress: string,
+): void => {
   const parsedUrl = url.parse(agentPeerAddress);
-  const pathname = parsedUrl.pathname?.replace(/^\/+|\/+$/g, '');
+  const pathname = parsedUrl.pathname?.replace(/^\/+|\/+$/g, "");
 
   let serverDidWebPath: string;
   if (pathname) {
@@ -582,26 +580,21 @@ export const attachDidWebHandler = (server: Express, agent: Agent, agentPeerAddr
     serverDidWebPath = "/.well-known/did.json";
   }
 
-  console.log('Listen did web requests on path ' + serverDidWebPath);
-  server.get(
-    serverDidWebPath,
-    async (req: Request, res: Response) => {
-      try {
-        const didWebRecord = await agent.genericRecords.findById("did:web");
+  console.log("Listen did web requests on path " + serverDidWebPath);
+  server.get(serverDidWebPath, async (req: Request, res: Response) => {
+    try {
+      const didWebRecord = await agent.genericRecords.findById("did:web");
 
-        if (!didWebRecord) {
-          return res.status(404).send("Not found");
-        }
+      if (!didWebRecord) {
+        return res.status(404).send("Not found");
+      }
 
-        const didWebDoc = didWebRecord.content;
+      const didWebDoc = didWebRecord.content;
 
-        return res
-          .header("Content-Type", "application/json")
-          .send(didWebDoc);
-      } catch (error) {
-        console.error(error);
-        return res.status(500).send("Internal Server Error");
-      }
-    },
-  );
+      return res.header("Content-Type", "application/json").send(didWebDoc);
+    } catch (error) {
+      console.error(error);
+      return res.status(500).send("Internal Server Error");
+    }
+  });
 };
diff --git a/libs/askar/src/askar-rest/askar.rest.module.ts b/libs/askar/src/askar-rest/askar.rest.module.ts
index 6aff9fe7..6a320c0f 100644
--- a/libs/askar/src/askar-rest/askar.rest.module.ts
+++ b/libs/askar/src/askar-rest/askar.rest.module.ts
@@ -1,20 +1,21 @@
-import {
-  Module,
-  ValidationPipe,
-  MiddlewareConsumer,
-  RequestMethod,
-} from "@nestjs/common";
+import { Module, ValidationPipe } from "@nestjs/common";
 import { AgentService } from "../askar/agent.service";
-import { ConfigModule, ConfigService } from "@nestjs/config";
+import { ConfigModule } from "@nestjs/config";
 import { LedgersModule } from "@ocm-engine/ledgers";
 import { APP_PIPE } from "@nestjs/core";
 import { RestController } from "./rest.controller";
 import { PassportModule } from "@nestjs/passport";
-import { BasicAuthMiddleware } from "./auth/basic.middleware";
-import { IConfAgent } from "@ocm-engine/config";
+import { JwtModule } from "@nestjs/jwt";
 
 @Module({
-  imports: [ConfigModule, LedgersModule, PassportModule],
+  imports: [
+    ConfigModule,
+    LedgersModule,
+    PassportModule,
+    JwtModule.register({
+      global: true,
+    }),
+  ],
   providers: [
     AgentService,
     {
@@ -26,17 +27,4 @@ import { IConfAgent } from "@ocm-engine/config";
   ],
   controllers: [RestController],
 })
-export class AskarRestModule {
-  constructor(private readonly configService: ConfigService) {}
-
-  configure(consumer: MiddlewareConsumer) {
-    const config: IConfAgent | undefined =
-      this.configService.get<IConfAgent>("agent");
-
-    if (config?.agentIsSVDX) {
-      consumer
-        .apply(BasicAuthMiddleware)
-        .forRoutes({ path: "*messages", method: RequestMethod.ALL });
-    }
-  }
-}
+export class AskarRestModule {}
diff --git a/libs/askar/src/askar-rest/auth/auth.guard.ts b/libs/askar/src/askar-rest/auth/auth.guard.ts
new file mode 100644
index 00000000..1279878a
--- /dev/null
+++ b/libs/askar/src/askar-rest/auth/auth.guard.ts
@@ -0,0 +1,83 @@
+import {
+  CanActivate,
+  ExecutionContext,
+  Injectable,
+  Logger,
+  UnauthorizedException,
+} from "@nestjs/common";
+import { JwtService } from "@nestjs/jwt";
+import { Request } from "express";
+import { ConfigService } from "@nestjs/config";
+import { IConfAgent } from "@ocm-engine/config";
+
+@Injectable()
+export class AuthGuard implements CanActivate {
+  private readonly logger: Logger = new Logger(AuthGuard.name);
+
+  constructor(
+    private readonly jwtService: JwtService,
+    private readonly configService: ConfigService,
+  ) {}
+
+  async canActivate(context: ExecutionContext): Promise<boolean> {
+    const config: IConfAgent = this.configService.get<IConfAgent>("agent")!;
+
+    if (!(config.agentAuthBasicEnabled || config.agentAuthJwtEnabled)) {
+      return true;
+    }
+    // Auth enabled
+
+    const request = context.switchToHttp().getRequest();
+    const [type, token] = this.extractAuthHeader(request);
+    if (!type || !token) {
+      throw new UnauthorizedException();
+    }
+
+    switch (type) {
+      case "Bearer":
+        if (!config.agentAuthJwtEnabled) throw new UnauthorizedException();
+        await this.verifyBearerToken(token, config);
+        break;
+      case "Basic":
+        if (!config.agentAuthBasicEnabled) throw new UnauthorizedException();
+        await this.verifyBasicToken(token, config);
+        break;
+      default:
+        throw new UnauthorizedException();
+    }
+
+    return true;
+  }
+
+  private async verifyBearerToken(token: string, config: IConfAgent) {
+    try {
+      await this.jwtService.verifyAsync(token, {
+        publicKey: config.agentAuthJwtPublicKey,
+      });
+    } catch (e) {
+      this.logger.log("Token verification Error");
+      this.logger.log(e);
+      throw new UnauthorizedException();
+    }
+  }
+
+  private async verifyBasicToken(token: string, config: IConfAgent) {
+    const [username, password] = Buffer.from(token, "base64")
+      .toString()
+      .split(":");
+
+    if (
+      username !== config.agentAuthBasicUser ||
+      password !== config.agentAuthBasicPass
+    ) {
+      throw new UnauthorizedException();
+    }
+  }
+
+  private extractAuthHeader(
+    request: Request,
+  ): [string | undefined, string | undefined] {
+    const [type, token] = request.headers.authorization?.split(" ") ?? [];
+    return [type, token];
+  }
+}
diff --git a/libs/askar/src/askar-rest/auth/basic.guard.ts b/libs/askar/src/askar-rest/auth/basic.guard.ts
deleted file mode 100644
index 354ea9ed..00000000
--- a/libs/askar/src/askar-rest/auth/basic.guard.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Injectable } from "@nestjs/common";
-import { AuthGuard } from "@nestjs/passport";
-
-/**
- * Basic guard is NOT currently used, it is left for the  time when we implement jwt auth for the rest of the routes
- */
-
-@Injectable()
-export class BasicGuard extends AuthGuard("basic") {}
diff --git a/libs/askar/src/askar-rest/auth/basic.middleware.ts b/libs/askar/src/askar-rest/auth/basic.middleware.ts
deleted file mode 100644
index 25099e3a..00000000
--- a/libs/askar/src/askar-rest/auth/basic.middleware.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import {
-  Injectable,
-  Logger,
-  NestMiddleware,
-  UnauthorizedException,
-} from "@nestjs/common";
-import { Request, Response, NextFunction } from "express";
-import { ConfigService } from "@nestjs/config";
-import { IConfAgent } from "@ocm-engine/config";
-
-@Injectable()
-export class BasicAuthMiddleware implements NestMiddleware {
-  private readonly logger: Logger = new Logger(BasicAuthMiddleware.name);
-
-  constructor(private readonly configService: ConfigService) {}
-
-  use(req: Request, res: Response, next: NextFunction) {
-    const config: IConfAgent | undefined =
-      this.configService.get<IConfAgent>("agent");
-
-    if (!config?.agentIsSVDX) {
-      this.logger.log("Agent is REST only turning of basic auth");
-
-      return next();
-    }
-
-    this.logger.log("Agent is SVDX turning basic auth middleware on");
-
-    const authHeader = req.headers["authorization"];
-
-    if (!authHeader) {
-      return next(new UnauthorizedException());
-    }
-
-    const [username, password] = Buffer.from(authHeader.split(" ")[1], "base64")
-      .toString()
-      .split(":");
-
-    if (
-      username === config.agentSVDXBasicUser &&
-      password === config.agentSVDXBasicPass
-    ) {
-      return next();
-    }
-
-    return next(new UnauthorizedException());
-  }
-}
diff --git a/libs/askar/src/askar-rest/auth/basic.strategy.ts b/libs/askar/src/askar-rest/auth/basic.strategy.ts
deleted file mode 100644
index f454f0af..00000000
--- a/libs/askar/src/askar-rest/auth/basic.strategy.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { ConfigService } from "@nestjs/config";
-import { IConfAgent } from "@ocm-engine/config";
-import { BasicStrategy as Strategy } from "passport-http";
-import { PassportStrategy } from "@nestjs/passport";
-import { Injectable, UnauthorizedException } from "@nestjs/common";
-
-/**
- * Basic strategy is NOT currently used, it is left for the  time when we implement jwt auth for the rest of the routes
- */
-@Injectable()
-export class BasicStrategy extends PassportStrategy(Strategy, "basic") {
-  constructor(private readonly configService: ConfigService) {
-    super();
-  }
-
-  async validate(username: string, password: string): Promise<boolean> {
-    console.log(username, password);
-    const config: IConfAgent | undefined =
-      this.configService.get<IConfAgent>("agent");
-
-    if (
-      config?.agentSVDXBasicUser === username &&
-      config?.agentSVDXBasicPass === password
-    ) {
-      return true;
-    }
-
-    throw new UnauthorizedException();
-  }
-}
diff --git a/libs/askar/src/askar-rest/rest.controller.ts b/libs/askar/src/askar-rest/rest.controller.ts
index 29c8cf03..a39330a4 100644
--- a/libs/askar/src/askar-rest/rest.controller.ts
+++ b/libs/askar/src/askar-rest/rest.controller.ts
@@ -7,6 +7,7 @@ import {
   Param,
   Post,
   UseFilters,
+  UseGuards,
 } from "@nestjs/common";
 
 import { AgentService } from "../askar/agent.service";
@@ -29,9 +30,11 @@ import {
 } from "@ocm-engine/dtos";
 import { AllExceptionsHandler } from "./exception.handler";
 import { DidResolutionResult } from "@aries-framework/core";
+import { AuthGuard } from "./auth/auth.guard";
 
 @UseFilters(AllExceptionsHandler)
 @Controller("v1")
+@UseGuards(AuthGuard)
 export class RestController {
   constructor(private readonly agentService: AgentService) {}
 
diff --git a/libs/askar/src/askar/agent.service.ts b/libs/askar/src/askar/agent.service.ts
index 464e9c4e..54b66690 100644
--- a/libs/askar/src/askar/agent.service.ts
+++ b/libs/askar/src/askar/agent.service.ts
@@ -1029,7 +1029,7 @@ export class AgentService {
 
   getCreatedDids = async (): Promise<DidRecordDto[]> => {
     const didRecords = await this.askar.agent.dids.getCreatedDids();
-    return didRecords.map(p => {
+    return didRecords.map((p) => {
       const dto = new DidRecordDto();
       const tags = p.getTags();
 
diff --git a/libs/askar/src/askar/askar.service.ts b/libs/askar/src/askar/askar.service.ts
index 846af055..789ac58e 100644
--- a/libs/askar/src/askar/askar.service.ts
+++ b/libs/askar/src/askar/askar.service.ts
@@ -80,7 +80,11 @@ export class AskarService implements OnModuleInit, OnModuleDestroy {
 
     //handler for short url invitations, look at agent service createInvitation
     attachShortUrlHandler(this.server, this.agent);
-    attachDidWebHandler(this.server, this.agent, this.agentConfig.agentPeerAddress);
+    attachDidWebHandler(
+      this.server,
+      this.agent,
+      this.agentConfig.agentPeerAddress,
+    );
 
     this.agent.registerInboundTransport(
       new HttpInboundTransport({
@@ -117,13 +121,15 @@ export class AskarService implements OnModuleInit, OnModuleDestroy {
     const didWebs = await this.agent.dids.getCreatedDids({ method: "web" });
     if (didWebs.length) {
       for (const didsKey in didWebs) {
-        this.logger.debug(`agent already have ${didWebs[didsKey].did} registered`);
+        this.logger.debug(
+          `agent already have ${didWebs[didsKey].did} registered`,
+        );
       }
     } else {
       await generateDidWeb({
         agent: this.agent,
         seed: this.agentConfig.agentDidSeed,
-        peerAddress: this.agentConfig.agentPeerAddress
+        peerAddress: this.agentConfig.agentPeerAddress,
       });
     }
 
diff --git a/libs/clients/src/ocmengine-client.ts b/libs/clients/src/ocmengine-client.ts
index d3ae8f16..e927c062 100644
--- a/libs/clients/src/ocmengine-client.ts
+++ b/libs/clients/src/ocmengine-client.ts
@@ -1,36 +1,26 @@
 import {
   AcceptCredentialDto,
   AcceptProofDto,
-  ConnectionRecordDto,
   CreateCredentialDefinitionRequestDto,
   CreateInvitationRequestDto,
-  CreateInvitationResponseDto,
   CreateSchemaRequestDto,
-  CreddefRecordDto,
-  CredentialFormatDataDto,
-  CredentialOfferResponseDto,
-  CredentialRecordDto,
   IdReqDto,
   MakeBasicMessageRequestDto,
-  MessageRecordDto,
   OfferCredentialRequestDto,
-  ProofFormatDataDto,
-  ProofRecordDto,
   RequestProofDto,
-  RequestProofResponseDto,
   RestControllerClient,
   Role,
   Roles,
-  SchemaRecordDto,
   States,
   States2,
   States3,
-  AcceptInvitationRequestDto
+  AcceptInvitationRequestDto,
 } from "./frontend/agent_gen";
 
 export * from "./frontend/agent_gen";
 
 export interface Config {
+  onUnauthorized?: () => void;
   wsUrl: string;
   httpUrl: string;
   getToken: () => Promise<string>;
@@ -44,39 +34,73 @@ class ApiClient {
   private _ws!: WebSocket;
   private _rest!: RestControllerClient;
 
-  public fetchInvitations = (states: States[] | undefined, roles: Roles[] | undefined) => this._rest.fetchInvitations(states, roles);
-  public createInvitation = (body: CreateInvitationRequestDto) => this._rest.createInvitation(body);
+  public fetchInvitations = (
+    states: States[] | undefined,
+    roles: Roles[] | undefined,
+  ) => this._rest.fetchInvitations(states, roles);
+  public createInvitation = (body: CreateInvitationRequestDto) =>
+    this._rest.createInvitation(body);
   public getInvitationById = (id: string) => this._rest.getInvitationById(id);
-  public deleteInvitationById = (id: string) => this._rest.deleteInvitationById(id);
-  public acceptInvitation = (body: AcceptInvitationRequestDto) => this._rest.acceptInvitation(body);
+  public deleteInvitationById = (id: string) =>
+    this._rest.deleteInvitationById(id);
+  public acceptInvitation = (body: AcceptInvitationRequestDto) =>
+    this._rest.acceptInvitation(body);
   public fetchConnections = () => this._rest.fetchConnections();
   public getConnectionById = (id: string) => this._rest.getConnectionById(id);
-  public deleteConnectionById = (id: string) => this._rest.deleteConnectionById(id);
-  public getConnectionByOobId = (id: string) => this._rest.getConnectionByOobId(id);
-  public createSchema = (body: CreateSchemaRequestDto) => this._rest.createSchema(body);
+  public deleteConnectionById = (id: string) =>
+    this._rest.deleteConnectionById(id);
+  public getConnectionByOobId = (id: string) =>
+    this._rest.getConnectionByOobId(id);
+  public createSchema = (body: CreateSchemaRequestDto) =>
+    this._rest.createSchema(body);
   public fetchSchemas = () => this._rest.fetchSchemas();
   public getSchemaById = (body: IdReqDto) => this._rest.getSchemaById(body);
-  public fetchCredentialDefinitions = () => this._rest.fetchCredentialDefinitions();
-  public createCredentialDefinition = (body: CreateCredentialDefinitionRequestDto) => this._rest.createCredentialDefinition(body);
-  public getCredentialDefinitionById = (body: IdReqDto) => this._rest.getCredentialDefinitionById(body);
-  public offerCredential = (body: OfferCredentialRequestDto) => this._rest.offerCredential(body);
-  public fetchCredentials = (states: States2[] | undefined, connectionId: string | undefined) => this._rest.fetchCredentials(states, connectionId);
+  public fetchCredentialDefinitions = () =>
+    this._rest.fetchCredentialDefinitions();
+  public createCredentialDefinition = (
+    body: CreateCredentialDefinitionRequestDto,
+  ) => this._rest.createCredentialDefinition(body);
+  public getCredentialDefinitionById = (body: IdReqDto) =>
+    this._rest.getCredentialDefinitionById(body);
+  public offerCredential = (body: OfferCredentialRequestDto) =>
+    this._rest.offerCredential(body);
+  public fetchCredentials = (
+    states: States2[] | undefined,
+    connectionId: string | undefined,
+  ) => this._rest.fetchCredentials(states, connectionId);
   public getCredentialById = (id: string) => this._rest.getCredentialById(id);
-  public deleteCredentialById = (id: string) => this._rest.deleteCredentialById(id);
-  public getCredentialFormatDataById = (id: string) => this._rest.getCredentialFormatDataById(id);
-  public acceptCredential = (body: AcceptCredentialDto) => this._rest.acceptCredential(body);
-  public declineCredential = (credential_record_id: string) => this._rest.declineCredential(credential_record_id);
-  public sendMessage = (body: MakeBasicMessageRequestDto) => this._rest.sendMessage(body);
-  public fetchBasicMessages = (role: Role | undefined, connectionId: string | undefined) => this._rest.fetchBasicMessages(role, connectionId);
+  public deleteCredentialById = (id: string) =>
+    this._rest.deleteCredentialById(id);
+  public getCredentialFormatDataById = (id: string) =>
+    this._rest.getCredentialFormatDataById(id);
+  public acceptCredential = (body: AcceptCredentialDto) =>
+    this._rest.acceptCredential(body);
+  public declineCredential = (credential_record_id: string) =>
+    this._rest.declineCredential(credential_record_id);
+  public sendMessage = (body: MakeBasicMessageRequestDto) =>
+    this._rest.sendMessage(body);
+  public fetchBasicMessages = (
+    role: Role | undefined,
+    connectionId: string | undefined,
+  ) => this._rest.fetchBasicMessages(role, connectionId);
   public deleteBasicMessage = (id: string) => this._rest.deleteBasicMessage(id);
-  public fetchProofs = (states: States3[] | undefined, connectionId: string | undefined) => this._rest.fetchProofs(states, connectionId);
-  public getProofById = (proof_record_id: string) => this._rest.getProofById(proof_record_id);
-  public deleteProofById = (proof_record_id: string) => this._rest.deleteProofById(proof_record_id);
-  public getProofFormatDataById = (proof_record_id: string) => this._rest.getProofFormatDataById(proof_record_id);
-  public proofAcceptanceWait = (proof_record_id: string) => this._rest.proofAcceptanceWait(proof_record_id);
-  public requestProof = (body: RequestProofDto) => this._rest.requestProof(body);
+  public fetchProofs = (
+    states: States3[] | undefined,
+    connectionId: string | undefined,
+  ) => this._rest.fetchProofs(states, connectionId);
+  public getProofById = (proof_record_id: string) =>
+    this._rest.getProofById(proof_record_id);
+  public deleteProofById = (proof_record_id: string) =>
+    this._rest.deleteProofById(proof_record_id);
+  public getProofFormatDataById = (proof_record_id: string) =>
+    this._rest.getProofFormatDataById(proof_record_id);
+  public proofAcceptanceWait = (proof_record_id: string) =>
+    this._rest.proofAcceptanceWait(proof_record_id);
+  public requestProof = (body: RequestProofDto) =>
+    this._rest.requestProof(body);
   public acceptProof = (body: AcceptProofDto) => this._rest.acceptProof(body);
-  public declineProofRequest = (proof_record_id: string) => this._rest.declineProofRequest(proof_record_id);
+  public declineProofRequest = (proof_record_id: string) =>
+    this._rest.declineProofRequest(proof_record_id);
   public resolveDid = (body: IdReqDto) => this._rest.resolveDid(body);
   public getCreatedDids = () => this._rest.getCreatedDids();
 
@@ -113,7 +137,15 @@ class ApiClient {
       },
     });
 
-    return fetch(url, init);
+    const response = await fetch(url, init);
+
+    if (response.status === 401) {
+      if (config.onUnauthorized) {
+        config.onUnauthorized();
+      }
+    }
+
+    return response;
   }
 
   public jsonParseReviver = (key: string, value: unknown): unknown => {
diff --git a/libs/config/src/config/agent.config.ts b/libs/config/src/config/agent.config.ts
index 9922cae8..489e8376 100644
--- a/libs/config/src/config/agent.config.ts
+++ b/libs/config/src/config/agent.config.ts
@@ -15,7 +15,7 @@ export const agentConfig = registerAs(
     agentDbPass: process.env["AGENT_DB_PASS"]!,
     agentIsRest: process.env["AGENT_IS_REST"] === "true",
     agentConsumerName: process.env["AGENT_CONSUMER_NAME"]!,
-    agentConsumerMaxMessagess:
+    agentConsumerMaxMessages:
       parseInt(process.env["AGENT_MAX_MESSAGES"]!) || 10,
     agentConsumerRateLimit: parseInt(process.env["AGENT_RETE_LIMIT"]!) || 5,
     agentPort: parseInt(process.env["AGENT_PORT"]!),
@@ -27,5 +27,12 @@ export const agentConfig = registerAs(
     agentSVDXWebHook: process.env["AGENT_SVDX_WEBHOOK_URL"]!,
     agentSVDXBasicUser: process.env["AGENT_SVDX_BASIC_USER"]!,
     agentSVDXBasicPass: process.env["AGENT_SVDX_BASIC_PASS"]!,
+
+    agentAuthBasicEnabled:
+      !!process.env["AUTH_BASIC_USER"] && !!process.env["AUTH_BASIC_PASS"],
+    agentAuthBasicUser: process.env["AUTH_BASIC_USER"]!,
+    agentAuthBasicPass: process.env["AUTH_BASIC_PASS"]!,
+    agentAuthJwtEnabled: !!process.env["AUTH_JWT_PUBLIC_KEY"],
+    agentAuthJwtPublicKey: process.env["AUTH_JWT_PUBLIC_KEY"]!,
   }),
 );
diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts
index 1a892f81..630047fd 100644
--- a/libs/config/src/index.ts
+++ b/libs/config/src/index.ts
@@ -16,6 +16,7 @@ export * from "./interfaces/proof.manager.config.interface";
 
 export * from "./schemas/nats.schema";
 export * from "./schemas/agent.schema";
+export * from "./schemas/auth.schema";
 export * from "./schemas/ledgers.schema";
 export * from "./schemas/gateway.schema";
 export * from "./schemas/connection.manager.schema";
diff --git a/libs/config/src/interfaces/agent.config.interface.ts b/libs/config/src/interfaces/agent.config.interface.ts
index 59d42606..2027c8ba 100644
--- a/libs/config/src/interfaces/agent.config.interface.ts
+++ b/libs/config/src/interfaces/agent.config.interface.ts
@@ -9,7 +9,7 @@ export interface IConfAgent {
   agentDidSeed: string;
   agentIsRest: boolean;
   agentConsumerName: string;
-  agentConsumerMaxMessagess: number;
+  agentConsumerMaxMessages: number;
   agentConsumerRateLimit: number;
   agentPort: number;
   agentOobGoals: Array<string>;
@@ -18,4 +18,10 @@ export interface IConfAgent {
   agentSVDXWebHook: string;
   agentSVDXBasicUser: string;
   agentSVDXBasicPass: string;
+
+  agentAuthBasicEnabled: boolean;
+  agentAuthBasicUser: string;
+  agentAuthBasicPass: string;
+  agentAuthJwtEnabled: boolean;
+  agentAuthJwtPublicKey: string;
 }
diff --git a/libs/config/src/schemas/agent.schema.ts b/libs/config/src/schemas/agent.schema.ts
index 46b36990..364b4e3b 100644
--- a/libs/config/src/schemas/agent.schema.ts
+++ b/libs/config/src/schemas/agent.schema.ts
@@ -14,7 +14,5 @@ export const agentSchema = Joi.object({
   AGENT_RETE_LIMIT: Joi.string().required(),
   AGENT_PORT: Joi.string().required(),
   AGENT_IS_SVDX: Joi.string().optional(),
-  AGENT_SVDX_SCHEMA_ID: Joi.string().optional(),
-  AGENT_SVDX_CRED_DEF_ID: Joi.string().optional(),
   AGENT_SVDX_WEBHOOK_URL: Joi.string().optional(),
 });
diff --git a/libs/config/src/schemas/auth.schema.ts b/libs/config/src/schemas/auth.schema.ts
new file mode 100644
index 00000000..b8616bb4
--- /dev/null
+++ b/libs/config/src/schemas/auth.schema.ts
@@ -0,0 +1,7 @@
+import Joi from "joi";
+
+export const authSchema = Joi.object({
+  AUTH_BASIC_USER: Joi.string(),
+  AUTH_BASIC_PASS: Joi.string(),
+  AUTH_JWT_PUBLIC_KEY: Joi.string(),
+});
diff --git a/libs/nats/src/consumer.nats.service.ts b/libs/nats/src/consumer.nats.service.ts
index d6d6ce5d..a3ffc541 100644
--- a/libs/nats/src/consumer.nats.service.ts
+++ b/libs/nats/src/consumer.nats.service.ts
@@ -56,7 +56,7 @@ export class ConsumerService extends NatsBaseService {
     const consumer = await this.registerConsumer(this.streamConfig.name);
 
     const messages = await consumer.consume({
-      max_messages: this.agentConfig.agentConsumerMaxMessagess,
+      max_messages: this.agentConfig.agentConsumerMaxMessages,
     });
 
     for await (const message of messages) {
diff --git a/package.json b/package.json
index 377e36f2..426c6f54 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
     "@nestjs/common": "^9.0.0",
     "@nestjs/config": "^2.3.1",
     "@nestjs/core": "^9.0.0",
+    "@nestjs/jwt": "^10.2.0",
     "@nestjs/microservices": "^9.4.2",
     "@nestjs/passport": "^10.0.1",
     "@nestjs/platform-express": "^9.0.0",
diff --git a/yarn.lock b/yarn.lock
index c7ef67c3..9ae15a04 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1814,6 +1814,14 @@
     path-to-regexp "3.2.0"
     tslib "2.5.3"
 
+"@nestjs/jwt@^10.2.0":
+  version "10.2.0"
+  resolved "https://registry.yarnpkg.com/@nestjs/jwt/-/jwt-10.2.0.tgz#6aa35a04922d19c6426efced4671620f92e6dbd0"
+  integrity sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==
+  dependencies:
+    "@types/jsonwebtoken" "9.0.5"
+    jsonwebtoken "9.0.2"
+
 "@nestjs/mapped-types@1.2.2":
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-1.2.2.tgz#d9ddb143776e309dbc1a518ac1607fddac1e140e"
@@ -3465,6 +3473,13 @@
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
   integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
 
+"@types/jsonwebtoken@9.0.5":
+  version "9.0.5"
+  resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz#0bd9b841c9e6c5a937c17656e2368f65da025588"
+  integrity sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==
+  dependencies:
+    "@types/node" "*"
+
 "@types/keyv@^3.1.4":
   version "3.1.4"
   resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6"
@@ -4873,6 +4888,11 @@ bser@2.1.1:
   dependencies:
     node-int64 "^0.4.0"
 
+buffer-equal-constant-time@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+  integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
+
 buffer-from@^1.0.0:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
@@ -6258,6 +6278,13 @@ eastasianwidth@^0.2.0:
   resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
   integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
 
+ecdsa-sig-formatter@1.0.11:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
+  integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
+  dependencies:
+    safe-buffer "^5.0.1"
+
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -9140,6 +9167,22 @@ jsonparse@^1.2.0, jsonparse@^1.3.1:
   resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
   integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
 
+jsonwebtoken@9.0.2:
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
+  integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
+  dependencies:
+    jws "^3.2.2"
+    lodash.includes "^4.3.0"
+    lodash.isboolean "^3.0.3"
+    lodash.isinteger "^4.0.4"
+    lodash.isnumber "^3.0.3"
+    lodash.isplainobject "^4.0.6"
+    lodash.isstring "^4.0.1"
+    lodash.once "^4.0.0"
+    ms "^2.1.1"
+    semver "^7.5.4"
+
 "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3:
   version "3.3.5"
   resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a"
@@ -9160,6 +9203,23 @@ just-diff@^6.0.0:
   resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-6.0.2.tgz#03b65908543ac0521caf6d8eb85035f7d27ea285"
   integrity sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==
 
+jwa@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
+  integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
+  dependencies:
+    buffer-equal-constant-time "1.0.1"
+    ecdsa-sig-formatter "1.0.11"
+    safe-buffer "^5.0.1"
+
+jws@^3.2.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
+  integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
+  dependencies:
+    jwa "^1.4.1"
+    safe-buffer "^5.0.1"
+
 keyv@^4.0.0, keyv@^4.5.3:
   version "4.5.4"
   resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -9465,11 +9525,31 @@ lodash.escaperegexp@^4.1.2:
   resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
   integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==
 
+lodash.includes@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
+  integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
+
+lodash.isboolean@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+  integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
+
+lodash.isinteger@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
+  integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
+
 lodash.ismatch@^4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
   integrity sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==
 
+lodash.isnumber@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
+  integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
+
 lodash.isplainobject@^4.0.6:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
@@ -9490,6 +9570,11 @@ lodash.merge@^4.6.2:
   resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
   integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
 
+lodash.once@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+  integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
+
 lodash.uniq@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
@@ -12476,7 +12561,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
+safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
-- 
GitLab