diff --git a/.gitignore b/.gitignore index 4d29575..0d9682f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +lib/node_modules \ No newline at end of file diff --git a/lib/.releaserc.json b/lib/.releaserc.json new file mode 100644 index 0000000..976750f --- /dev/null +++ b/lib/.releaserc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://json.schemastore.org/semantic-release.json", + "branches": ["main"] +} \ No newline at end of file diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..61a3347 --- /dev/null +++ b/lib/README.md @@ -0,0 +1,40 @@ +# ZITADEL React SDK + +Authenticate your [ZITADEL](https://zitadel.com) users within your React applications. + +![NPM Version](https://img.shields.io/npm/v/@zitadel/react) +![NPM License](https://img.shields.io/npm/l/@zitadel/react) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) + +## Getting Started + +- Check out the docs on how to [integrate ZITADEL into your existing React application](https://zitadel.com/docs/examples/login/react). +- Create a new React application with ZITADEL integration from scratch by following the example at [ZITADEL React example application](https://github.com/zitadel/zitadel-react/blob/main/README.md). + +## Features + +The NPM package [@zitadel/react](https://www.npmjs.com/package/@zitadel/react) wraps the NPM package [oidc-client-ts](https://github.com/authts/oidc-client-ts). +All [oidc-client-ts](https://github.com/authts/oidc-client-ts) features are available and the whole configuration can be overridden. + +The following features are added to [oidc-client-ts](https://github.com/authts/oidc-client-ts) + +- [@zitadel/react](https://www.npmjs.com/package/@zitadel/react) defaults as much configuration as possible. +- [@zitadel/react](https://www.npmjs.com/package/@zitadel/react) provides a simple way to check for user roles. + +The following is an example for a minimal OIDC configuration: + +```typescript +const zitadelAuth = createZitadelAuth({ + issuer: `${myZITADELInstancesOrigin}`, + client_id: `${myApplicationsClientID}`, + project_resource_id: `${myApplicationsProjectResourceID}`, +}); +``` + +The following defaults apply: + +- The OIDC Code Flow with PKCE is used for authentication at ZITADEL. +- ZITADELs user info endpoint is called to enrich the user profile. +- The access token is refreshed automatically by default before it expires. +- If you specify a _project_resource_id_, the scopes for retrieving the users roles from the user info endpoint are added automatically. + You can conveniently use `zitadelAuth.hasRole("someRoleKey")`. diff --git a/lib/dist/index.d.ts b/lib/dist/index.d.ts new file mode 100644 index 0000000..dfa0dfc --- /dev/null +++ b/lib/dist/index.d.ts @@ -0,0 +1 @@ +export { createZITADELAuth, ZitadelConfig } from "./zitadelAuth"; diff --git a/lib/dist/index.js b/lib/dist/index.js new file mode 100644 index 0000000..921a4f5 --- /dev/null +++ b/lib/dist/index.js @@ -0,0 +1 @@ +export { createZITADELAuth } from "./zitadelAuth"; diff --git a/lib/dist/lib/src/index.d.ts b/lib/dist/lib/src/index.d.ts new file mode 100644 index 0000000..dfa0dfc --- /dev/null +++ b/lib/dist/lib/src/index.d.ts @@ -0,0 +1 @@ +export { createZITADELAuth, ZitadelConfig } from "./zitadelAuth"; diff --git a/lib/dist/lib/src/index.js b/lib/dist/lib/src/index.js new file mode 100644 index 0000000..921a4f5 --- /dev/null +++ b/lib/dist/lib/src/index.js @@ -0,0 +1 @@ +export { createZITADELAuth } from "./zitadelAuth"; diff --git a/lib/dist/lib/src/zitadelAuth.d.ts b/lib/dist/lib/src/zitadelAuth.d.ts new file mode 100644 index 0000000..831795c --- /dev/null +++ b/lib/dist/lib/src/zitadelAuth.d.ts @@ -0,0 +1,13 @@ +import { UserManager } from "oidc-client-ts"; +export interface ZitadelConfig { + client_id: string; + issuer: string; + project_resource_id?: string; +} +interface ZitadelAuth { + authorize(): Promise; + clearAuth(): Promise; + userManager: UserManager; +} +export declare function createZITADELAuth(zitadelConfig: ZitadelConfig): ZitadelAuth; +export {}; diff --git a/lib/dist/lib/src/zitadelAuth.js b/lib/dist/lib/src/zitadelAuth.js new file mode 100644 index 0000000..4962ea2 --- /dev/null +++ b/lib/dist/lib/src/zitadelAuth.js @@ -0,0 +1,33 @@ +import { UserManager, WebStorageStateStore, } from "oidc-client-ts"; +export function createZITADELAuth(zitadelConfig) { + const authConfig = { + authority: `${zitadelConfig.issuer}`, //Replace with your issuer URL + client_id: `${zitadelConfig.client_id}`, //Replace with your client id + redirect_uri: "http://localhost:3000/callback", + response_type: "code", + scope: `openid profile email ${zitadelConfig.project_resource_id + ? `urn:zitadel:iam:org:project:id:${zitadelConfig.project_resource_id}:aud urn:zitadel:iam:org:projects:roles` + : ""}`, + post_logout_redirect_uri: "http://localhost:3000/", + // userinfo_endpoint: + // "https://instance-some_text.zitadel.cloud/oidc/v1/userinfo", + response_mode: "query", + // code_challenge_method: "S256", + }; + const userManager = new UserManager({ + userStore: new WebStorageStateStore({ store: window.localStorage }), + ...authConfig, + }); + const authorize = () => { + return userManager.signinRedirect(); + }; + const clearAuth = () => { + return userManager.signoutRedirect(); + }; + const oidc = { + authorize, + clearAuth, + userManager, + }; + return oidc; +} diff --git a/lib/dist/src/components/Callback.d.ts b/lib/dist/src/components/Callback.d.ts new file mode 100644 index 0000000..18b090a --- /dev/null +++ b/lib/dist/src/components/Callback.d.ts @@ -0,0 +1,11 @@ +/// +type Props = { + authenticated: boolean; + setAuth: (authenticated: boolean) => void; + userManager: any; + userInfo: any; + setUserInfo: any; + handleLogout: any; +}; +declare const Callback: ({ authenticated, setAuth, userManager, userInfo, setUserInfo, handleLogout, }: Props) => import("react").JSX.Element; +export default Callback; diff --git a/lib/dist/src/components/Callback.js b/lib/dist/src/components/Callback.js new file mode 100644 index 0000000..a724348 --- /dev/null +++ b/lib/dist/src/components/Callback.js @@ -0,0 +1,48 @@ +import { useEffect } from "react"; +const Callback = ({ authenticated, setAuth, userManager, userInfo, setUserInfo, handleLogout, }) => { + useEffect(() => { + if (authenticated === false) { + userManager + .signinRedirectCallback() + .then((user) => { + if (user) { + setAuth(true); + const access_token = user.access_token; + // Make a request to the user info endpoint using the access token + // fetch(authConfig.userinfo_endpoint, { + // headers: { + // 'Authorization': `Bearer ${access_token}` + // } + // }) + // .then(response => response.json()) + // .then(userInfo => { + // setUserInfo(userInfo); + // }); + } + else { + setAuth(false); + } + }) + .catch((error) => { + setAuth(false); + }); + } + }, [authenticated, userManager, setAuth]); + console.log(authenticated, userInfo); + if (authenticated === true && userInfo) { + return (
+

Welcome, {userInfo.name}!

+

Your ZITADEL Profile Information

+

Name: {userInfo.name}

+

Email: {userInfo.email}

+

Email Verified: {userInfo.email_verified ? "Yes" : "No"}

+

Locale: {userInfo.locale}

+ + +
); + } + else { + return
Loading...
; + } +}; +export default Callback; diff --git a/lib/dist/src/components/Login.d.ts b/lib/dist/src/components/Login.d.ts new file mode 100644 index 0000000..37efb5e --- /dev/null +++ b/lib/dist/src/components/Login.d.ts @@ -0,0 +1,7 @@ +/// +type Props = { + handleLogin: () => void; + authenticated: boolean; +}; +declare const Login: ({ authenticated, handleLogin }: Props) => import("react").JSX.Element; +export default Login; diff --git a/lib/dist/src/components/Login.js b/lib/dist/src/components/Login.js new file mode 100644 index 0000000..e78d88b --- /dev/null +++ b/lib/dist/src/components/Login.js @@ -0,0 +1,17 @@ +import { Navigate } from "react-router-dom"; +const Login = ({ authenticated, handleLogin }) => { + return (
+ {authenticated === null &&
Loading...
} + {authenticated === false && (
+

Welcome!

+ +
)} + {authenticated && } +
); +}; +export default Login; diff --git a/lib/dist/zitadelAuth.d.ts b/lib/dist/zitadelAuth.d.ts new file mode 100644 index 0000000..831795c --- /dev/null +++ b/lib/dist/zitadelAuth.d.ts @@ -0,0 +1,13 @@ +import { UserManager } from "oidc-client-ts"; +export interface ZitadelConfig { + client_id: string; + issuer: string; + project_resource_id?: string; +} +interface ZitadelAuth { + authorize(): Promise; + clearAuth(): Promise; + userManager: UserManager; +} +export declare function createZITADELAuth(zitadelConfig: ZitadelConfig): ZitadelAuth; +export {}; diff --git a/lib/dist/zitadelAuth.js b/lib/dist/zitadelAuth.js new file mode 100644 index 0000000..4962ea2 --- /dev/null +++ b/lib/dist/zitadelAuth.js @@ -0,0 +1,33 @@ +import { UserManager, WebStorageStateStore, } from "oidc-client-ts"; +export function createZITADELAuth(zitadelConfig) { + const authConfig = { + authority: `${zitadelConfig.issuer}`, //Replace with your issuer URL + client_id: `${zitadelConfig.client_id}`, //Replace with your client id + redirect_uri: "http://localhost:3000/callback", + response_type: "code", + scope: `openid profile email ${zitadelConfig.project_resource_id + ? `urn:zitadel:iam:org:project:id:${zitadelConfig.project_resource_id}:aud urn:zitadel:iam:org:projects:roles` + : ""}`, + post_logout_redirect_uri: "http://localhost:3000/", + // userinfo_endpoint: + // "https://instance-some_text.zitadel.cloud/oidc/v1/userinfo", + response_mode: "query", + // code_challenge_method: "S256", + }; + const userManager = new UserManager({ + userStore: new WebStorageStateStore({ store: window.localStorage }), + ...authConfig, + }); + const authorize = () => { + return userManager.signinRedirect(); + }; + const clearAuth = () => { + return userManager.signoutRedirect(); + }; + const oidc = { + authorize, + clearAuth, + userManager, + }; + return oidc; +} diff --git a/lib/package.json b/lib/package.json new file mode 100644 index 0000000..e17edef --- /dev/null +++ b/lib/package.json @@ -0,0 +1,36 @@ +{ + "name": "@zitadel/react", + "version": "0.0.0-development", + "description": "Wraps oidc-client-ts to provide a simple interface for ZITADEL", + "homepage": "https://zitadel.com/docs/examples/login/react", + "bugs": { + "url": "https://github.com/zitadel/zitadel-react/issues" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsc" + }, + "keywords": [ + "ZITADEL", + "React", + "authN", + "authZ", + "OIDC", + "IAM" + ], + "files": [ + "/dist" + ], + "author": "Max Peintner, ", + "license": "MIT", + "dependencies": { + "oidc-client-ts": "^2.4.0" + }, + "devDependencies": { + "typescript": "^5.3.3" + } +} diff --git a/lib/src/index.ts b/lib/src/index.ts new file mode 100644 index 0000000..dfa0dfc --- /dev/null +++ b/lib/src/index.ts @@ -0,0 +1 @@ +export { createZITADELAuth, ZitadelConfig } from "./zitadelAuth"; diff --git a/lib/src/zitadelAuth.ts b/lib/src/zitadelAuth.ts new file mode 100644 index 0000000..714460c --- /dev/null +++ b/lib/src/zitadelAuth.ts @@ -0,0 +1,58 @@ +import { + UserManagerSettings, + Logger, + UserManager, + WebStorageStateStore, +} from "oidc-client-ts"; + +export interface ZitadelConfig { + client_id: string; + issuer: string; + project_resource_id?: string; +} + +interface ZitadelAuth { + authorize(): Promise; + clearAuth(): Promise; + userManager: UserManager; +} + +export function createZITADELAuth(zitadelConfig: ZitadelConfig): ZitadelAuth { + const authConfig: UserManagerSettings = { + authority: `${zitadelConfig.issuer}`, //Replace with your issuer URL + client_id: `${zitadelConfig.client_id}`, //Replace with your client id + redirect_uri: "http://localhost:3000/callback", + response_type: "code", + scope: `openid profile email ${ + zitadelConfig.project_resource_id + ? `urn:zitadel:iam:org:project:id:${zitadelConfig.project_resource_id}:aud urn:zitadel:iam:org:projects:roles` + : "" + }`, + post_logout_redirect_uri: "http://localhost:3000/", + // userinfo_endpoint: + // "https://instance-some_text.zitadel.cloud/oidc/v1/userinfo", + response_mode: "query", + // code_challenge_method: "S256", + }; + + const userManager = new UserManager({ + userStore: new WebStorageStateStore({ store: window.localStorage }), + ...authConfig, + }); + + const authorize = () => { + return userManager.signinRedirect(); + }; + + const clearAuth = () => { + return userManager.signoutRedirect(); + }; + + const oidc = { + authorize, + clearAuth, + userManager, + }; + + return oidc; +} diff --git a/lib/tsconfig.json b/lib/tsconfig.json new file mode 100644 index 0000000..37e59a9 --- /dev/null +++ b/lib/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "ESNext", + "target": "es2019", + "declaration": true, + "outDir": "./dist", + "moduleResolution": "node", + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/lib/yarn.lock b/lib/yarn.lock new file mode 100644 index 0000000..b05ed27 --- /dev/null +++ b/lib/yarn.lock @@ -0,0 +1,26 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" + integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== + +oidc-client-ts@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz#764c8a33de542026e2798de9849ce8049047d7e5" + integrity sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w== + dependencies: + crypto-js "^4.2.0" + jwt-decode "^3.1.2" + +typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== diff --git a/package.json b/package.json index dedbacd..4910eb4 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,10 @@ "@types/node": "^16.7.13", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", + "@zitadel/react": "link:./lib", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.21.3", "react-scripts": "5.0.1", "typescript": "^4.4.2", "web-vitals": "^2.1.0" diff --git a/src/App.tsx b/src/App.tsx index a53698a..e7b96b7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,23 +1,70 @@ -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import React, { useEffect, useState } from "react"; +import logo from "./logo.svg"; +import "./App.css"; +import { createZITADELAuth, ZitadelConfig } from "@zitadel/react"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; + +import Login from "./components/Login"; +import Callback from "./components/Callback"; function App() { + const config: ZitadelConfig = { + issuer: "", + client_id: "", + }; + const oidc = createZITADELAuth(config); + + function login() { + oidc.authorize(); + } + + function signout() { + oidc.clearAuth(); + } + + const [authenticated, setAuthenticated] = useState(false); + const [userInfo, setUserInfo] = useState(null); + + useEffect(() => { + oidc.userManager.getUser().then((user) => { + if (user) { + setAuthenticated(true); + } else { + setAuthenticated(false); + } + }); + }, [oidc]); + return (
logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - +

Welcome to ZITADEL React

+ + + + + } + /> + + } + /> + +
); diff --git a/src/components/Callback.tsx b/src/components/Callback.tsx new file mode 100644 index 0000000..a2d1fc2 --- /dev/null +++ b/src/components/Callback.tsx @@ -0,0 +1,67 @@ +import { useEffect } from "react"; + +type Props = { + issuer: string; + authenticated: boolean; + setAuth: (authenticated: boolean) => void; + userManager: any; + userInfo: any; + setUserInfo: any; + handleLogout: any; +}; +const Callback = ({ + issuer, + authenticated, + setAuth, + userManager, + userInfo, + setUserInfo, + handleLogout, +}: Props) => { + useEffect(() => { + if (authenticated === false) { + userManager + .signinRedirectCallback() + .then((user: any) => { + if (user) { + setAuth(true); + const access_token = user.access_token; + const userInfoEndpoint = `${issuer}oidc/v1/userinfo`; + fetch(userInfoEndpoint, { + headers: { + Authorization: `Bearer ${access_token}`, + }, + }) + .then((response) => response.json()) + .then((userInfo) => { + setUserInfo(userInfo); + }); + } else { + setAuth(false); + } + }) + .catch((error: any) => { + setAuth(false); + }); + } + }, [authenticated, userManager, setAuth]); + console.log(authenticated, userInfo); + if (authenticated === true && userInfo) { + return ( +
+

Welcome, {userInfo.name}!

+

Your ZITADEL Profile Information

+

Name: {userInfo.name}

+

Email: {userInfo.email}

+

Email Verified: {userInfo.email_verified ? "Yes" : "No"}

+

Locale: {userInfo.locale}

+ + +
+ ); + } else { + return
Loading...
; + } +}; + +export default Callback; diff --git a/src/components/Login.tsx b/src/components/Login.tsx new file mode 100644 index 0000000..f23b434 --- /dev/null +++ b/src/components/Login.tsx @@ -0,0 +1,29 @@ +import { Navigate } from "react-router-dom"; + +type Props = { + handleLogin: () => void; + authenticated: boolean; +}; +const Login = ({ authenticated, handleLogin }: Props) => { + return ( +
+ {authenticated === null &&
Loading...
} + {authenticated === false && ( +
+

Welcome!

+ +
+ )} + {authenticated && } +
+ ); +}; + +export default Login; diff --git a/tsconfig.json b/tsconfig.json index a273b0c..f8dbe10 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -18,9 +14,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "baseUrl": "." }, - "include": [ - "src" - ] + "include": ["src"] } diff --git a/yarn.lock b/yarn.lock index e1beaa9..1e3cf10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1688,6 +1688,11 @@ schema-utils "^3.0.0" source-map "^0.7.3" +"@remix-run/router@1.14.2": + version "1.14.2" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.14.2.tgz#4d58f59908d9197ba3179310077f25c88e49ed17" + integrity sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg== + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -2503,6 +2508,10 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@zitadel/react@link:./lib": + version "0.0.0" + uid "" + abab@^2.0.3, abab@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" @@ -3479,6 +3488,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -6303,6 +6317,11 @@ jsonpointer@^5.0.0: object.assign "^4.1.4" object.values "^1.1.6" +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" + integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== + keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -6865,6 +6884,14 @@ 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-ts@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-2.4.0.tgz#764c8a33de542026e2798de9849ce8049047d7e5" + integrity sha512-WijhkTrlXK2VvgGoakWJiBdfIsVGz6CFzgjNNqZU1hPKV2kyeEaJgLs7RwuiSp2WhLfWBQuLvr2SxVlZnk3N1w== + dependencies: + crypto-js "^4.2.0" + jwt-decode "^3.1.2" + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -7878,6 +7905,21 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== +react-router-dom@^6.21.3: + version "6.21.3" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.21.3.tgz#ef3a7956a3699c7b82c21fcb3dbc63c313ed8c5d" + integrity sha512-kNzubk7n4YHSrErzjLK72j0B5i969GsuCGazRl3G6j1zqZBLjuSlYBdVdkDOgzGdPIffUOc9nmgiadTEVoq91g== + dependencies: + "@remix-run/router" "1.14.2" + react-router "6.21.3" + +react-router@6.21.3: + version "6.21.3" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.21.3.tgz#8086cea922c2bfebbb49c6594967418f1f167d70" + integrity sha512-a0H638ZXULv1OdkmiK6s6itNhoy33ywxmUFT/xtSoVyf9VnC7n7+VT4LjVzdIHSaF5TIh9ylUgxMXksHTgGrKg== + dependencies: + "@remix-run/router" "1.14.2" + react-scripts@5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003"