diff --git a/.env.local.keycloak-example b/.env.local.keycloak-example
new file mode 100644
index 00000000..737d7364
--- /dev/null
+++ b/.env.local.keycloak-example
@@ -0,0 +1,11 @@
+REACT_APP_OIDC_SERVER_TYPE=KEYCLOAK
+REACT_APP_OIDC_RETURN_TYPE="code"
+REACT_APP_OIDC_AUTHORITY=https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus/
+REACT_APP_OIDC_CLIENT_ID="kukkuu-admin-ui-dev"
+REACT_APP_OIDC_KUKKUU_API_CLIENT_ID="kukkuu-api-dev"
+REACT_APP_OIDC_SCOPE="openid profile"
+REACT_APP_OIDC_AUDIENCES=kukkuu-api-dev
+# REACT_APP_API_URI=https://kukkuu.api.test.hel.ninja/graphql
+REACT_APP_API_URI=http://localhost:8081/graphql
+REACT_APP_SENTRY_DSN=
+REACT_APP_FEATURE_FLAG_EXTERNAL_TICKET_SYSTEM_SUPPORT=true
diff --git a/Dockerfile b/Dockerfile
index e59e3157..e72e666c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -52,9 +52,13 @@ FROM appbase AS staticbuilder
# ===================================
ARG REACT_APP_API_URI
+ARG REACT_APP_OIDC_SERVER_TYPE
+ARG REACT_APP_OIDC_RETURN_TYPE
ARG REACT_APP_OIDC_AUTHORITY
ARG REACT_APP_OIDC_CLIENT_ID
+ARG REACT_APP_OIDC_KUKKUU_API_CLIENT_ID
ARG REACT_APP_OIDC_SCOPE
+ARG REACT_APP_OIDC_AUDIENCES
ARG REACT_APP_KUKKUU_API_OIDC_SCOPE
ARG REACT_APP_ENVIRONMENT
ARG REACT_APP_SENTRY_DSN
diff --git a/README.md b/README.md
index 1ef65780..2cf6ca8f 100644
--- a/README.md
+++ b/README.md
@@ -134,6 +134,36 @@ and execute the following four commands inside your docker container:
To make Kukkuu Admin use the local Tunnistamo set `REACT_APP_OIDC_AUTHORITY="http://tunnistamo-backend:8000"` for example in file `.env.local`.
+#### Using the Helsinki-Profile Keycloak instead of Tunnistamo
+
+> It is planned that the Tunnistamo will be replaced with Helsinki-Profile Keycloak during the summer of 2024.
+
+There is an [example of Keycloak environment variables](./.env.local.keycloak-example) that can be used, when a local Kukkuu Admin UI is wanted to be connected to the Helsinki-Profile Keycloak of a test environment.
+
+The example file should include some what the following variables, that are telling the app to change the behavior of the authorization provider a bit, compared to how it is with Tunnistamo.
+
+- `REACT_APP_OIDC_SERVER_TYPE=KEYCLOAK` is to add some parameters to the token-request that the Keycloak service needs. As a comparison, by default it is working as `REACT_APP_OIDC_SERVER_TYPE=TUNNISTAMO`).
+- `REACT_APP_OIDC_RETURN_TYPE=code` is to use authorization code flow instead of deprecated (and even removed from `oidc-client-ts`) implicit flow.
+- `REACT_APP_OIDC_AUTHORITY` tells where the authorization service is located and who the issuer of the JWT is.
+- `REACT_APP_OIDC_CLIENT_ID` is the unique client id that is used when the client is configured to auth service.
+- `REACT_APP_OIDC_SCOPE="openid profile"` tells that the Kukkuu Admin UI needs the openid and profile information to be included in the JWT.
+- `REACT_APP_OIDC_AUDIENCES=kukkuu-api-dev` means that when the authorization is given, the access is needed to these clients too, so the api-tokens needs to be generated.
+- `REACT_APP_OIDC_KUKKUU_API_CLIENT_ID` is used collect the proper auth token for communication between the Admin UI and the API.
+
+Example configuration when a local Kukkuu API is used with a local Kukkuu Admin UI and Helsinki-Profile Keycloak from the test environment:
+
+```shell
+REACT_APP_OIDC_SERVER_TYPE=KEYCLOAK
+REACT_APP_OIDC_RETURN_TYPE="code"
+REACT_APP_OIDC_AUTHORITY=https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus/
+REACT_APP_OIDC_CLIENT_ID="kukkuu-admin-ui-dev"
+REACT_APP_OIDC_KUKKUU_API_CLIENT_ID="kukkuu-api-dev"
+REACT_APP_OIDC_SCOPE="openid profile"
+REACT_APP_OIDC_AUDIENCES=kukkuu-api-dev
+# REACT_APP_API_URI=https://kukkuu.api.test.hel.ninja/graphql
+REACT_APP_API_URI=http://localhost:8081/graphql
+```
+
#### Install Kukkuu API locally
Clone the repository (https://github.com/City-of-Helsinki/kukkuu). Follow the instructions for running kukkuu with docker. Before running `docker-compose up` set the following settings in kukkuu roots `docker-compose.env.yaml`:
diff --git a/package.json b/package.json
index f87ef9ad..af93bd19 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
"history": "^5.3.0",
"moment": "^2.29.4",
"moment-timezone": "^0.5.43",
- "oidc-client": "^1.11.5",
+ "oidc-client-ts": "^3.0.1",
"prettier": "^2.8.8",
"prop-types": "^15.8.1",
"query-string": "^8.1.0",
diff --git a/public/silent_renew.html b/public/silent_renew.html
index fe58735c..bf88b1ac 100644
--- a/public/silent_renew.html
+++ b/public/silent_renew.html
@@ -1,13 +1,20 @@
-
-
-
-
-
-
-
+
+
+
+ Silent renewal
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/api/apolloClient/handleApolloError.ts b/src/api/apolloClient/handleApolloError.ts
index 162cd385..463c16a5 100644
--- a/src/api/apolloClient/handleApolloError.ts
+++ b/src/api/apolloClient/handleApolloError.ts
@@ -32,6 +32,7 @@ const handleApolloError: ErrorHandler = ({
) {
// If JWT is expired it means that we want people to log in again. We don't need to log this to sentry.
console.error('JWT expired');
+ authService.resetAuthState();
} else if (errorCode === 'PERMISSION_DENIED_ERROR') {
// Most permission errors happen when user authentication
// expires or when the user accesses the application before
diff --git a/src/domain/application/AppConfig.ts b/src/domain/application/AppConfig.ts
new file mode 100644
index 00000000..13180205
--- /dev/null
+++ b/src/domain/application/AppConfig.ts
@@ -0,0 +1,95 @@
+class AppConfig {
+ static get apiUrl() {
+ return getEnvOrError(process.env.REACT_APP_API_URI, 'REACT_APP_API_URI');
+ }
+
+ static get oidcAuthority() {
+ const origin = getEnvOrError(
+ process.env.REACT_APP_OIDC_AUTHORITY,
+ 'REACT_APP_OIDC_AUTHORITY'
+ );
+ return new URL(origin).href;
+ }
+
+ /**
+ * The audiences used in the OIDC.
+ *
+ * @example
+ * // In Tunnistamo it can be left as undefined,
+ * // because it is not included in the request done bythe OIDC client.
+ * ["https://api.hel.fi/auth/kukkuu"]
+ * // In Keycloak:
+ * [
+ 'kukkuu-api-test',
+ 'profile-api-test',
+ ]
+ */
+ static get oidcAudience() {
+ return process.env.REACT_APP_OIDC_AUDIENCES;
+ }
+
+ static get oidcClientId() {
+ return getEnvOrError(
+ process.env.REACT_APP_OIDC_CLIENT_ID,
+ 'REACT_APP_OIDC_CLIENT_ID'
+ );
+ }
+
+ static get oidcScope() {
+ return getEnvOrError(
+ process.env.REACT_APP_OIDC_SCOPE,
+ 'REACT_APP_OIDC_SCOPE,'
+ );
+ }
+
+ static get oidcReturnType() {
+ // "code" for authorization code flow.
+ return process.env.REACT_APP_OIDC_RETURN_TYPE ?? 'code';
+ }
+
+ static get oidcKukkuuApiClientId() {
+ return (
+ process.env.REACT_APP_OIDC_KUKKUU_API_CLIENT_ID ?? this.oidcKukkuuAPIScope
+ );
+ }
+
+ static get oidcKukkuuApiTokensUrl() {
+ return this.oidcServerType === 'KEYCLOAK'
+ ? `${this.oidcAuthority}protocol/openid-connect/token`
+ : `${this.oidcAuthority}api-tokens/`;
+ }
+
+ static get oidcKukkuuAPIScope() {
+ return getEnvOrError(
+ process.env.REACT_APP_KUKKUU_API_OIDC_SCOPE,
+ 'REACT_APP_KUKKUU_API_OIDC_SCOPE'
+ );
+ }
+
+ /**
+ * NOTE: The oidcServerType is not an OIDC client attribute.
+ * It's purely used to help to select a configuration for the LoginProvider.
+ * */
+ static get oidcServerType(): 'KEYCLOAK' | 'TUNNISTAMO' {
+ const oidcServerType =
+ process.env.REACT_APP_OIDC_SERVER_TYPE ?? 'TUNNISTAMO';
+ if (oidcServerType === 'KEYCLOAK' || oidcServerType === 'TUNNISTAMO') {
+ return oidcServerType;
+ }
+ throw new Error(`Invalid OIDC server type: ${oidcServerType}`);
+ }
+}
+
+// Accept both variable and name so that variable can be correctly replaced
+// by build.
+// process.env.VAR => value
+// process.env["VAR"] => no value
+// Name is used to make debugging easier.
+function getEnvOrError(variable?: string, name?: string) {
+ if (!variable) {
+ throw Error(`Environment variable with name ${name} was not found`);
+ }
+ return variable;
+}
+
+export default AppConfig;
diff --git a/src/domain/authentication/CallbackPage.tsx b/src/domain/authentication/CallbackPage.tsx
index 0ea154c5..b2199431 100644
--- a/src/domain/authentication/CallbackPage.tsx
+++ b/src/domain/authentication/CallbackPage.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { useTranslate, useDataProvider, Loading } from 'react-admin';
import * as Sentry from '@sentry/browser';
-import type { User } from 'oidc-client';
+import type { User } from 'oidc-client-ts';
import { useNavigate, useLocation } from 'react-router-dom';
import authService from './authService';
@@ -37,7 +37,7 @@ function CallBackPage() {
if (role === 'none') {
navigate('/unauthorized', { replace: true });
} else {
- navigate(getRedirectPath(user.state?.path, pathname), {
+ navigate(getRedirectPath(user.url_state, pathname), {
replace: true,
});
}
diff --git a/src/domain/authentication/__tests__/__snapshots__/authService.test.js.snap b/src/domain/authentication/__tests__/__snapshots__/authService.test.js.snap
index f71ac99f..f2f30834 100644
--- a/src/domain/authentication/__tests__/__snapshots__/authService.test.js.snap
+++ b/src/domain/authentication/__tests__/__snapshots__/authService.test.js.snap
@@ -1,13 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`authService fetchApiToken should call axios.get with the right arguments 1`] = `
+exports[`authService fetchApiToken should call axios with the right arguments 1`] = `
Array [
"https://tunnistamo.test.kuva.hel.ninja/api-tokens/",
Object {
- "baseURL": "https://tunnistamo.test.kuva.hel.ninja",
+ "baseURL": "https://tunnistamo.test.kuva.hel.ninja/",
+ "data": Object {},
"headers": Object {
+ "Accept": "application/json",
"Authorization": "bearer db237bc3-e197-43de-8c86-3feea4c5f886",
+ "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
},
+ "method": "post",
},
]
`;
diff --git a/src/domain/authentication/__tests__/authService.test.js b/src/domain/authentication/__tests__/authService.test.js
index 1c57b8bc..eebad461 100644
--- a/src/domain/authentication/__tests__/authService.test.js
+++ b/src/domain/authentication/__tests__/authService.test.js
@@ -3,12 +3,13 @@ import axios from 'axios';
import dataProvider from '../../../api/dataProvider';
import authService, { API_TOKEN } from '../authService';
import authorizationService from '../authorizationService';
+import AppConfig from '../../application/AppConfig';
jest.mock('axios');
describe('authService', () => {
const userManager = authService.userManager;
- const oidcUserKey = `oidc.user:${process.env.REACT_APP_OIDC_AUTHORITY}:${process.env.REACT_APP_OIDC_CLIENT_ID}`;
+ const oidcUserKey = `oidc.user:${AppConfig.oidcAuthority}:${AppConfig.oidcClientId}`;
beforeEach(() => {
jest.spyOn(dataProvider, 'getMyAdminProfile').mockResolvedValue({});
@@ -37,7 +38,7 @@ describe('authService', () => {
it('should get API_TOKENS from localStorage', () => {
authService.getToken();
- expect(localStorage.getItem).toHaveBeenNthCalledWith(2, API_TOKEN);
+ expect(localStorage.getItem).toHaveBeenNthCalledWith(1, API_TOKEN);
});
});
@@ -87,12 +88,14 @@ describe('authService', () => {
authService.login(path);
- expect(signinRedirect).toHaveBeenNthCalledWith(1, { data: { path } });
+ expect(signinRedirect).toHaveBeenNthCalledWith(1, {
+ url_state: path,
+ });
});
});
describe('endLogin', () => {
- axios.get.mockResolvedValue({ data: {} });
+ axios.mockResolvedValue({ data: {} });
const access_token = 'db237bc3-e197-43de-8c86-3feea4c5f886';
const mockUser = {
name: 'Penelope Krajcik',
@@ -235,9 +238,9 @@ describe('authService', () => {
};
beforeEach(() => {
- axios.get.mockReset();
+ axios.mockReset();
- axios.get.mockResolvedValue({
+ axios.mockResolvedValue({
data: {
firstToken: '71ffd52c-5985-46d3-b445-490554f4012a',
secondToken: 'de7c2a83-07f2-46bf-8417-8f648adbc7be',
@@ -245,12 +248,12 @@ describe('authService', () => {
});
});
- it('should call axios.get with the right arguments', async () => {
+ it('should call axios with the right arguments', async () => {
expect.assertions(2);
await authService.fetchApiToken(mockUser);
- expect(axios.get).toHaveBeenCalledTimes(1);
- expect(axios.get.mock.calls[0]).toMatchSnapshot();
+ expect(axios).toHaveBeenCalledTimes(1);
+ expect(axios.mock.calls[0]).toMatchSnapshot();
});
it('should call localStorage.setItem with the right arguments', async () => {
diff --git a/src/domain/authentication/authService.ts b/src/domain/authentication/authService.ts
index 149c2b7d..cbe8f0c3 100644
--- a/src/domain/authentication/authService.ts
+++ b/src/domain/authentication/authService.ts
@@ -1,42 +1,79 @@
-import type { User, UserManagerSettings } from 'oidc-client';
-import { UserManager, Log, WebStorageStateStore } from 'oidc-client';
+import type { User, UserManagerSettings } from 'oidc-client-ts';
+import { UserManager, Log, WebStorageStateStore } from 'oidc-client-ts';
import axios from 'axios';
import * as Sentry from '@sentry/browser';
import projectService from '../projects/projectService';
import authorizationService from './authorizationService';
+import AppConfig from '../application/AppConfig';
const origin = window.location.origin;
export const API_TOKEN = 'apiToken';
+export type ApiTokenClientProps = {
+ url: string;
+ queryProps?: { permission: string; grant_type: string };
+ audiences?: string[];
+};
+
export class AuthService {
private userManager: UserManager;
+ private apiTokensClientConfig: ApiTokenClientProps;
+ private authServerType: 'KEYCLOAK' | 'TUNNISTAMO';
+ private audience: string;
constructor() {
+ this.authServerType = AppConfig.oidcServerType;
+ this.audience = AppConfig.oidcAudience ?? AppConfig.oidcKukkuuApiClientId;
+
const settings: UserManagerSettings = {
loadUserInfo: true,
userStore: new WebStorageStateStore({ store: window.localStorage }),
- authority: process.env.REACT_APP_OIDC_AUTHORITY,
- client_id: process.env.REACT_APP_OIDC_CLIENT_ID,
+ response_type: AppConfig.oidcReturnType,
+ authority: AppConfig.oidcAuthority,
+ client_id: AppConfig.oidcClientId,
+ scope: AppConfig.oidcScope,
redirect_uri: `${origin}/callback`,
- // For debugging, set it to 1 minute by removing comment:
- // accessTokenExpiringNotificationTime: 59.65 * 60,
- automaticSilentRenew: false,
- silent_redirect_uri: `${origin}/silent_renew.html`,
- response_type: 'id_token token',
- scope: process.env.REACT_APP_OIDC_SCOPE,
post_logout_redirect_uri: `${origin}/`,
+ // TODO: The silent renew support needs to be added to the React-admin authProvider as well.
+ // More about this:
+ // - https://marmelab.com/blog/2020/07/02/manage-your-jwt-react-admin-authentication-in-memory.html
+ // - https://marmelab.com/react-admin/addRefreshAuthToAuthProvider.html
+ // - https://marmelab.com/react-admin/addRefreshAuthToDataProvider.html
+ automaticSilentRenew: false,
+ // silent_redirect_uri: `${origin}/silent_renew.html`,
};
- // Show oidc debugging info in the console only while developing
+ if (!settings.automaticSilentRenew) {
+ // eslint-disable-next-line no-console
+ console.info('Auth token silent renew is disabled.');
+ }
+
if (process.env.NODE_ENV === 'development') {
- Log.logger = console;
- Log.level = Log.INFO;
+ // Show oidc debugging info in the console only while developing
+ Log.setLogger(console);
+ Log.setLevel(Log.INFO);
}
// User Manager instance
this.userManager = new UserManager(settings);
+ // Api tokens client configuration
+ this.apiTokensClientConfig = {
+ url: AppConfig.oidcKukkuuApiTokensUrl,
+ queryProps:
+ this.authServerType === 'KEYCLOAK'
+ ? {
+ permission: '#access',
+ grant_type: 'urn:ietf:params:oauth:grant-type:uma-ticket',
+ }
+ : undefined,
+ audiences:
+ this.authServerType === 'KEYCLOAK' && AppConfig.oidcAudience
+ ? [AppConfig.oidcAudience]
+ : undefined,
+ };
+
// Public methods
this.getUser = this.getUser.bind(this);
this.getToken = this.getToken.bind(this);
@@ -46,6 +83,7 @@ export class AuthService {
this.renewToken = this.renewToken.bind(this);
this.logout = this.logout.bind(this);
this.resetAuthState = this.resetAuthState.bind(this);
+ this.fetchApiToken = this.fetchApiToken.bind(this);
// Events
this.userManager.events.addAccessTokenExpired(() => {
@@ -74,8 +112,12 @@ export class AuthService {
return localStorage.getItem(API_TOKEN);
}
+ public getUserStorageKey(): string {
+ return `oidc.user:${AppConfig.oidcAuthority}:${AppConfig.oidcClientId}`;
+ }
+
public isAuthenticated() {
- const userKey = `oidc.user:${process.env.REACT_APP_OIDC_AUTHORITY}:${process.env.REACT_APP_OIDC_CLIENT_ID}`;
+ const userKey = this.getUserStorageKey();
const oidcStorage = localStorage.getItem(userKey);
const apiTokens = this.getToken();
@@ -86,7 +128,7 @@ export class AuthService {
public async login(path = '/'): Promise {
try {
- return this.userManager.signinRedirect({ data: { path } });
+ return this.userManager.signinRedirect({ url_state: path });
} catch (error) {
if (error instanceof Error) {
if (error.message !== 'Network Error') {
@@ -105,7 +147,7 @@ export class AuthService {
return user;
}
- public renewToken(): Promise {
+ public renewToken(): Promise {
return this.userManager.signinSilent();
}
@@ -122,17 +164,36 @@ export class AuthService {
}
private async fetchApiToken(user: User): Promise {
- const url = `${process.env.REACT_APP_OIDC_AUTHORITY}/api-tokens/`;
- const { data: apiTokens } = await axios.get(url, {
- baseURL: process.env.REACT_APP_OIDC_AUTHORITY,
- headers: {
- Authorization: `bearer ${user.access_token}`,
- },
- });
- const apiToken =
- apiTokens[process.env.REACT_APP_KUKKUU_API_OIDC_SCOPE as string];
-
- localStorage.setItem(API_TOKEN, apiToken);
+ const accessToken = user.access_token;
+ try {
+ const { data } = await axios(this.apiTokensClientConfig.url, {
+ method: 'post',
+ baseURL: AppConfig.oidcAuthority,
+ headers: {
+ Authorization: `bearer ${accessToken}`,
+ Accept: 'application/json',
+ 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
+ },
+ data:
+ this.authServerType === 'KEYCLOAK'
+ ? {
+ audience: this.audience,
+ ...this.apiTokensClientConfig.queryProps,
+ }
+ : {},
+ });
+
+ const apiToken =
+ this.authServerType === 'KEYCLOAK'
+ ? data.access_token
+ : data[AppConfig.oidcKukkuuApiClientId];
+
+ localStorage.setItem(API_TOKEN, apiToken);
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('Failed to fetch API token', error);
+ Sentry.captureException(error);
+ }
}
}
diff --git a/yarn.lock b/yarn.lock
index 45621056..e2c8ec2d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4001,7 +4001,7 @@ acorn-walk@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
-acorn@^7.1.1, acorn@^7.4.1:
+acorn@^7.1.1:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
@@ -4630,7 +4630,7 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-base64-js@^1.3.1, base64-js@^1.5.1:
+base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -5370,7 +5370,7 @@ core-js@^2.4.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
-core-js@^3.19.2, core-js@^3.8.3:
+core-js@^3.19.2:
version "3.33.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.33.0.tgz#70366dbf737134761edb017990cf5ce6c6369c40"
integrity sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==
@@ -5442,11 +5442,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
-crypto-js@^4.0.0:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
- integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
-
crypto-md5@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/crypto-md5/-/crypto-md5-1.0.0.tgz#ccc8da750c753c7edcbabc542967472a384e86bb"
@@ -9223,6 +9218,11 @@ jss@10.10.0, jss@^10.10.0:
object.assign "^4.1.4"
object.values "^1.1.6"
+jwt-decode@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b"
+ integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
+
keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -9957,16 +9957,12 @@ obuf@^1.0.0, obuf@^1.1.2:
resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
-oidc-client@^1.11.5:
- version "1.11.5"
- resolved "https://registry.yarnpkg.com/oidc-client/-/oidc-client-1.11.5.tgz#020aa193d68a3e1f87a24fcbf50073b738de92bb"
- integrity sha512-LcKrKC8Av0m/KD/4EFmo9Sg8fSQ+WFJWBrmtWd+tZkNn3WT/sQG3REmPANE9tzzhbjW6VkTNy4xhAXCfPApAOg==
+oidc-client-ts@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz#be264fb87c89f74f73863646431c32cd06f5ceb7"
+ integrity sha512-xX8unZNtmtw3sOz4FPSqDhkLFnxCDsdo2qhFEH2opgWnF/iXMFoYdBQzkwCxAZVgt3FT3DnuBY3k80EZHT0RYg==
dependencies:
- acorn "^7.4.1"
- base64-js "^1.5.1"
- core-js "^3.8.3"
- crypto-js "^4.0.0"
- serialize-javascript "^4.0.0"
+ jwt-decode "^4.0.0"
on-finished@2.4.1:
version "2.4.1"