old sso work

This commit is contained in:
zeppi 2021-12-16 09:59:49 -05:00
parent d2385b70ec
commit f4ed753e70
16 changed files with 260 additions and 128 deletions

View file

@ -53,6 +53,7 @@
"@mui/material": "^5.2.1",
"@silvermine/videojs-chromecast": "^1.3.3",
"@ungap/from-entries": "^0.2.1",
"@react-keycloak/web": "^3.4.0",
"auto-launch": "^5.0.5",
"core-js-pure": "^3.19.3",
"electron-dl": "^1.11.0",
@ -62,6 +63,7 @@
"express": "^4.17.1",
"humanize-duration": "^3.27.0",
"if-env": "^1.0.4",
"keycloak-js": "^15.0.2",
"match-sorter": "^6.3.0",
"parse-duration": "^1.0.0",
"player.js": "^0.1.0",

View file

@ -0,0 +1,6 @@
<html>
<body>
<h1>Hi</h1>
<script>parent.postMessage(location.href, location.origin)</script>
</body>
</html>

View file

@ -30,6 +30,9 @@ import {
STATUS_FAILING,
STATUS_DOWN,
} from 'web/effects/use-degraded-performance';
import { useKeycloak } from '@react-keycloak/web';
// @endif
import LANGUAGE_MIGRATIONS from 'constants/language-migrations';
const FileDrop = lazyImport(() => import('component/fileDrop' /* webpackChunkName: "fileDrop" */));
@ -129,7 +132,7 @@ function App(props: Props) {
const isRewardApproved = user && user.is_reward_approved;
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
const previousRewardApproved = usePrevious(isRewardApproved);
const { authenticated } = useKeycloak();
const [showAnalyticsNag, setShowAnalyticsNag] = usePersistedState('analytics-nag', true);
const [lbryTvApiStatus, setLbryTvApiStatus] = useState(STATUS_OK);
@ -148,6 +151,7 @@ function App(props: Props) {
const fromLbrytvParam = urlParams.get('sunset');
const sanitizedReferrerParam = rawReferrerParam && rawReferrerParam.replace(':', '#');
const shouldHideNag = pathname.startsWith(`/$/${PAGES.EMBED}`) || pathname.startsWith(`/$/${PAGES.AUTH_VERIFY}`);
// KC
const userId = user && user.id;
const hasMyChannels = myChannelClaimIds && myChannelClaimIds.length > 0;
const hasNoChannels = myChannelClaimIds && myChannelClaimIds.length === 0;
@ -211,6 +215,13 @@ function App(props: Props) {
}
}
useEffect(() => {
if (authenticated) {
console.log('IS KC AUTHED');
}
}, [authenticated]);
// @endif
// TODO KC HOWTO SETUSER
useEffect(() => {
if (userId) {
analytics.setUser(userId);
@ -451,6 +462,7 @@ function App(props: Props) {
};
}, [hasSignedIn, hasVerifiedEmail, syncLoop]);
// TODO KEYCLOAK ISAUTHENTICATED
useEffect(() => {
if (syncError && isAuthenticated && !pathname.includes(PAGES.AUTH_WALLET_PASSWORD) && !currentModal) {
history.push(`/$/${PAGES.AUTH_WALLET_PASSWORD}?redirect=${pathname}`);

View file

@ -0,0 +1,32 @@
import * as React from 'react';
import { useCallback } from 'react';
import { Redirect, useLocation } from 'react-router-dom';
import { useKeycloak } from '@react-keycloak/web';
const LoginPage = () => {
const location = useLocation();
const currentLocationState = location.state || {
from: { pathname: '/home' },
};
const { keycloak } = useKeycloak();
const login = useCallback(() => {
keycloak && keycloak.login().then((x) => console.log('cb', x));
}, [keycloak]);
if (keycloak && keycloak.authenticated) {
return <Redirect to={currentLocationState.from} />;
}
return (
<div>
<button type="button" onClick={login}>
Login
</button>
</div>
);
};
export default LoginPage;

View file

@ -20,7 +20,7 @@ const perform = (dispatch) => ({
setShareDataThirdParty: (share) => dispatch(doToggle3PAnalytics(share)),
signOut: () => dispatch(doSignOut()),
authenticateIfSharingData: () =>
dispatch(doAuthenticate(appVersion, undefined, undefined, true, undefined, undefined, DOMAIN)),
dispatch(doAuthenticate(appVersion, undefined, undefined, true)),
});
export default connect(select, perform)(PrivacyAgreement);

View file

@ -11,8 +11,9 @@ import { parseURI, isURIValid } from 'util/lbryURI';
import { SITE_TITLE, WELCOME_VERSION, SIMPLE_SITE } from 'config';
import LoadingBarOneOff from 'component/loadingBarOneOff';
import { GetLinksData } from 'util/buildHomepage';
import { useKeycloak } from '@react-keycloak/web';
import HomePage from 'page/home';
import Login from 'component/auth/login';
// @if TARGET='app'
const BackupPage = lazyImport(() => import('page/backup' /* webpackChunkName: "backup" */));
@ -25,7 +26,8 @@ const TOSPage = lazyImport(() => import('web/page/tos' /* webpackChunkName: "tos
const YouTubeTOSPage = lazyImport(() => import('web/page/youtubetos' /* webpackChunkName: "youtubetos" */));
// @endif
const SignInPage = lazyImport(() => import('page/signIn' /* webpackChunkName: "signIn" */));
// remove signin page
// const SignInPage = lazyImport(() => import('page/signIn' /* webpackChunkName: "signIn" */));
const SignInWalletPasswordPage = lazyImport(() =>
import('page/signInWalletPassword' /* webpackChunkName: "signInWalletPassword" */)
);
@ -139,14 +141,20 @@ function PrivateRoute(props: PrivateRouteProps) {
const { component: Component, isAuthenticated, ...rest } = props;
const urlSearchParams = new URLSearchParams(props.location.search);
const redirectUrl = urlSearchParams.get('redirect');
const { keycloak } = useKeycloak();
return (
<Route
{...rest}
render={(props) =>
isAuthenticated || !IS_WEB ? (
(keycloak && keycloak.authenticated) || !IS_WEB ? (
<Component {...props} />
) : (
<Redirect to={`/$/${PAGES.AUTH}?redirect=${redirectUrl || props.location.pathname}`} />
<Redirect
to={{
pathname: `/$/${PAGES.AUTH}?redirect=${redirectUrl || props.location.pathname}`,
state: { from: props.location },
}}
/>
)
}
/>
@ -279,10 +287,10 @@ function AppRouter(props: Props) {
{SIMPLE_SITE && <Route path={`/$/${PAGES.WILD_WEST}`} exact component={DiscoverPage} />}
{homeCategoryPages}
<Route path={`/$/${PAGES.AUTH_SIGNIN}`} exact component={SignInPage} />
<Route path={`/$/${PAGES.AUTH_SIGNIN}`} exact component={Login} />
<Route path={`/$/${PAGES.AUTH_PASSWORD_RESET}`} exact component={PasswordResetPage} />
<Route path={`/$/${PAGES.AUTH_PASSWORD_SET}`} exact component={PasswordSetPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={SignUpPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={Login} />
<Route path={`/$/${PAGES.AUTH}/*`} exact component={SignUpPage} />
<Route path={`/$/${PAGES.WELCOME}`} exact component={Welcome} />

View file

@ -13,7 +13,7 @@ import * as MODALS from 'constants/modal_types';
import React, { Fragment, useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { doDaemonReady, doAutoUpdate, doOpenModal, doHideModal, doToggle3PAnalytics } from 'redux/actions/app';
import { doLbryReady, doDaemonReady, doAutoUpdate, doOpenModal, doHideModal, doToggle3PAnalytics } from 'redux/actions/app';
import Lbry, { apiCall } from 'lbry';
import { isURIValid } from 'util/lbryURI';
import { setSearchApi } from 'redux/actions/search';
@ -28,11 +28,14 @@ import { formatLbryUrlForWeb, formatInAppUrl } from 'util/url';
import { PersistGate } from 'redux-persist/integration/react';
import analytics from 'analytics';
import { doToast } from 'redux/actions/notifications';
import { ReactKeycloakProvider } from '@react-keycloak/web';
import keycloak from 'util/keycloak';
import {
getAuthToken,
setAuthToken,
doDeprecatedPasswordMigrationMarch2020,
doAuthTokenRefresh,
getTokens,
} from 'util/saved-passwords';
import { X_LBRY_AUTH_TOKEN } from 'constants/token';
import { PROXY_URL, DEFAULT_LANGUAGE, LBRY_API_URL } from 'config';
@ -99,28 +102,30 @@ if (process.env.SEARCH_API_URL) {
setSearchApi(process.env.SEARCH_API_URL);
}
// Fix to make sure old users' cookies are set to the correct domain
// This can be removed after March 11th, 2021
// https://github.com/lbryio/lbry-desktop/pull/3830
doDeprecatedPasswordMigrationMarch2020();
doAuthTokenRefresh();
// TODO KEYCLOAK
// doAuthTokenRefresh();
// We need to override Lbryio for getting/setting the authToken
// We interact with ipcRenderer to get the auth key from a users keyring
// We keep a local variable for authToken because `ipcRenderer.send` does not
// contain a response, so there is no way to know when it's been set
let authToken;
// let authToken;
Lbryio.setOverride('setAuthToken', (authToken) => {
setAuthToken(authToken);
setAuthToken(authToken); // set the cookie to auth_token=
return authToken;
});
Lbryio.setOverride('getTokens', () =>
new Promise((resolve) => {
resolve(getTokens());
})
);
Lbryio.setOverride(
'getAuthToken',
() =>
new Promise((resolve) => {
const authTokenToReturn = authToken || getAuthToken();
resolve(authTokenToReturn);
resolve(getAuthToken());
})
);
@ -213,6 +218,7 @@ function AppWrapper() {
// Splash screen and sdk setup not needed on web
const [readyToLaunch, setReadyToLaunch] = useState(IS_WEB);
const [persistDone, setPersistDone] = useState(false);
const [keycloakReady, setKeycloakReady] = useState(false);
useEffect(() => {
// @if TARGET='app'
@ -244,8 +250,8 @@ function AppWrapper() {
}, [persistDone]);
useEffect(() => {
if (readyToLaunch && persistDone) {
app.store.dispatch(doDaemonReady());
if (readyToLaunch && persistDone && keycloakReady) {
app.store.dispatch(doLbryReady());
setTimeout(() => {
if (DEFAULT_LANGUAGE) {
@ -258,7 +264,18 @@ function AppWrapper() {
analytics.startupEvent(Date.now());
}
}, [readyToLaunch, persistDone]);
}, [readyToLaunch, persistDone, keycloakReady]);
useEffect(() => {
console.log('keycl', keycloak.token);
}, [keycloak])
const eventLogger = (event, error) => {
// console.log('onKeycloakEvent', event, error, keycloak);
if (event === 'onReady') {
setKeycloakReady(true);
}
};
return (
<Provider store={store}>
@ -269,12 +286,18 @@ function AppWrapper() {
>
<Fragment>
{readyToLaunch ? (
<ConnectedRouter history={history}>
<ErrorBoundary>
<App />
<SnackBar />
</ErrorBoundary>
</ConnectedRouter>
<ReactKeycloakProvider
authClient={keycloak}
onEvent={eventLogger}
initOptions={{ onLoad: 'check-sso', silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html' }} // from npmjs docs for @react-keycloak/web
>
<ConnectedRouter history={history}>
<ErrorBoundary>
<App />
<SnackBar />
</ErrorBoundary>
</ConnectedRouter>
</ReactKeycloakProvider>
) : (
<Fragment>
<SplashScreen onReadyToLaunch={() => setReadyToLaunch(true)} />

View file

@ -343,18 +343,20 @@ export function doAlertWaitingForSync() {
};
}
export function doDaemonReady() {
export function doLbryReady() {
return (dispatch, getState) => {
const state = getState();
// TODO: call doFetchDaemonSettings, then get usage data, and call doAuthenticate once they are loaded into the store
const shareUsageData = IS_WEB || window.localStorage.getItem(SHARE_INTERNAL) === 'true';
// TODO KEYCLOAK
/**
* doAuthenticate -> Lbryio.authenticate, then update redux with auth token.
* authenticate: Use auth token to get or create user.getCurrentUser. return user
*/
dispatch(
doAuthenticate(
appVersion,
undefined,
undefined,
shareUsageData,
(status) => {
const trendingAlgorithm =
@ -367,8 +369,7 @@ export function doDaemonReady() {
analytics.trendingAlgorithmEvent(trendingAlgorithm);
}
},
undefined,
DOMAIN
undefined
)
);
dispatch({ type: ACTIONS.DAEMON_READY });

View file

@ -48,11 +48,8 @@ export function doFetchInviteStatus(shouldCallRewardList = true) {
};
}
export function doInstallNew(appVersion, os = null, firebaseToken = null, callbackForUsersWhoAreSharingData, domain) {
export function doInstallNew(appVersion, callbackForUsersWhoAreSharingData, domain) {
const payload = { app_version: appVersion, domain };
if (firebaseToken) {
payload.firebase_token = firebaseToken;
}
Lbry.status().then((status) => {
payload.app_id =
@ -62,7 +59,7 @@ export function doInstallNew(appVersion, os = null, firebaseToken = null, callba
payload.node_id = status.lbry_id;
Lbry.version().then((version) => {
payload.daemon_version = version.lbrynet_version;
payload.operating_system = os || version.os_system;
payload.operating_system = version.os_system;
payload.platform = version.platform;
Lbryio.call('install', 'new', payload);
@ -73,30 +70,6 @@ export function doInstallNew(appVersion, os = null, firebaseToken = null, callba
});
}
export function doInstallNewWithParams(
appVersion,
installationId,
nodeId,
lbrynetVersion,
os,
platform,
firebaseToken = null
) {
return () => {
const payload = { app_version: appVersion };
if (firebaseToken) {
payload.firebase_token = firebaseToken;
}
payload.app_id = installationId;
payload.node_id = nodeId;
payload.daemon_version = lbrynetVersion;
payload.operating_system = os;
payload.platform = platform;
Lbryio.call('install', 'new', payload);
};
}
function checkAuthBusy() {
let time = Date.now();
return new Promise(function (resolve, reject) {
@ -129,47 +102,54 @@ function checkAuthBusy() {
}
// TODO: Call doInstallNew separately so we don't have to pass appVersion and os_system params?
/**
*
* @param appVersion
* @param shareUsageData
* @param callbackForUsersWhoAreSharingData
* @param callInstall
* @returns {Function}
*
* Does Lbryio.authenticate, i.e. call Authenticate() then update redux with auth token.
* Authenticate does getAuthToken then getCurrentUser, and if !user, call userNew and return the user
* telling redux the auth token is probably dumb. we will
* keycloak = useKeycloak(), keycloak.token whenever we want that
*
*/
export function doAuthenticate(
appVersion,
os = null,
firebaseToken = null,
shareUsageData = true,
callbackForUsersWhoAreSharingData,
callInstall = true,
domain = null
) {
return (dispatch) => {
return async (dispatch) => {
dispatch({
type: ACTIONS.AUTHENTICATION_STARTED,
});
checkAuthBusy()
.then(() => {
return Lbryio.authenticate(DOMAIN, getDefaultLanguage());
})
.then((user) => {
if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS);
Lbryio.getAuthToken().then((token) => {
dispatch({
type: ACTIONS.AUTHENTICATION_SUCCESS,
data: { user, accessToken: token },
});
if (shareUsageData) {
dispatch(doRewardList());
dispatch(doFetchInviteStatus(false));
if (callInstall) {
doInstallNew(appVersion, os, firebaseToken, callbackForUsersWhoAreSharingData, domain);
}
}
});
})
.catch((error) => {
if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS);
try {
await checkAuthBusy();
const user = await Lbryio.authenticate(DOMAIN, getDefaultLanguage());
console.log('USER', user)
if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS);
Lbryio.getTokens().then((tokens) => {
dispatch({
type: ACTIONS.AUTHENTICATION_FAILURE,
data: { error },
type: ACTIONS.AUTHENTICATION_SUCCESS,
data: { user, authToken: tokens.auth_token, accessToken: tokens.access_token },
});
if (shareUsageData) {
dispatch(doRewardList());
dispatch(doFetchInviteStatus(false));
if (callInstall) {
doInstallNew(appVersion, callbackForUsersWhoAreSharingData, DOMAIN);
}
}
});
} catch (error) {
if (sessionStorageAvailable) window.sessionStorage.removeItem(AUTH_IN_PROGRESS);
dispatch({
type: ACTIONS.AUTHENTICATION_FAILURE,
data: { error },
});
};
}
@ -639,6 +619,7 @@ export function doUserIdentityVerify(stripeToken) {
data: { error: error.toString() },
});
});
}
};
}

View file

@ -24,7 +24,7 @@ const defaultState = {
invitees: undefined,
referralLink: undefined,
referralCode: undefined,
user: undefined,
user: { has_verified_email: false },
accessToken: undefined,
youtubeChannelImportPending: false,
youtubeChannelImportErrorMessage: '',
@ -32,6 +32,7 @@ const defaultState = {
referrerSetError: '',
};
// does this do anything with user?
reducers[ACTIONS.AUTHENTICATION_STARTED] = state =>
Object.assign({}, state, {
authenticationIsPending: true,
@ -39,19 +40,25 @@ reducers[ACTIONS.AUTHENTICATION_STARTED] = state =>
accessToken: defaultState.accessToken,
});
reducers[ACTIONS.AUTHENTICATION_SUCCESS] = (state, action) =>
Object.assign({}, state, {
reducers[ACTIONS.AUTHENTICATION_SUCCESS] = (state, action) => {
const newUserState = Object.assign({}, state, {
authenticationIsPending: false,
userIsPending: false,
accessToken: action.data.accessToken,
user: action.data.user,
});
if (action.data && action.data.user) {
newUserState.user = action.data.user;
}
if (action.data && action.data.accessToken) {
newUserState.accessToken = action.data.accessToken;
}
return newUserState;
}
reducers[ACTIONS.AUTHENTICATION_FAILURE] = state =>
Object.assign({}, state, {
authenticationIsPending: false,
userIsPending: false,
user: null,
});
reducers[ACTIONS.USER_FETCH_STARTED] = state =>

View file

@ -13,7 +13,7 @@ import Lbry from 'lbry';
import { createAnalyticsMiddleware } from 'redux/middleware/analytics';
import { buildSharedStateMiddleware } from 'redux/middleware/shared-state';
import { doSyncLoop } from 'redux/actions/sync';
import { getAuthToken } from 'util/saved-passwords';
import { getTokens } from 'util/saved-passwords';
import { generateInitialUrl } from 'util/url';
import { X_LBRY_AUTH_TOKEN } from 'constants/token';
@ -192,11 +192,16 @@ const sharedStateCb = ({ dispatch, getState }) => {
const populateAuthTokenHeader = () => {
return (next) => (action) => {
if (
(action.type === ACTIONS.USER_FETCH_SUCCESS || action.type === ACTIONS.AUTHENTICATION_SUCCESS) &&
action.data.user.has_verified_email === true
(action.type === ACTIONS.USER_FETCH_SUCCESS || action.type === ACTIONS.AUTHENTICATION_SUCCESS)
) {
const authToken = getAuthToken();
Lbry.setApiHeader(X_LBRY_AUTH_TOKEN, authToken);
if (action.data) {
}
const tokens = getTokens();
// if (tokens.access_token) {
// Lbry.setApiHeader('Authorization', 'Bearer ' + tokens.access_token);
// }
Lbry.setApiHeader(X_LBRY_AUTH_TOKEN, tokens.auth_token);
}
return next(action);

14
ui/util/keycloak.js Normal file
View file

@ -0,0 +1,14 @@
import Keycloak from 'keycloak-js';
// Setup Keycloak instance as needed
// Pass initialization options as required or leave blank to load from 'keycloak.json'
const keycloak = Keycloak({
url: 'https://sso.odysee.com/auth',
realm: 'Users',
clientId: 'odysee.com',
pkceMethod: 'S256',
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
});
export default keycloak;

View file

@ -1,4 +1,4 @@
const { DOMAIN } = require('../../config.js');
const { DOMAIN } = require('config.js');
const AUTH_TOKEN = 'auth_token';
const SAVED_PASSWORD = 'saved_password';
const DEPRECATED_SAVED_PASSWORD = 'saved-password';
@ -6,6 +6,7 @@ const domain =
typeof window === 'object' && window.location.hostname.includes('localhost') ? window.location.hostname : DOMAIN;
const isProduction = process.env.NODE_ENV === 'production';
const maxExpiration = 2147483647;
const { default: keycloak } = require('util/keycloak');
let sessionPassword;
function setCookie(name, value, expirationDaysOnWeb) {
@ -59,7 +60,7 @@ function deleteCookie(name) {
}
function setSavedPassword(value, saveToDisk) {
return new Promise(resolve => {
return new Promise((resolve) => {
const password = value === undefined || value === null ? '' : value;
sessionPassword = password;
@ -74,17 +75,17 @@ function setSavedPassword(value, saveToDisk) {
}
function getSavedPassword() {
return new Promise(resolve => {
return new Promise((resolve) => {
if (sessionPassword) {
resolve(sessionPassword);
}
return getPasswordFromCookie().then(p => resolve(p));
return getPasswordFromCookie().then((p) => resolve(p));
});
}
function getPasswordFromCookie() {
return new Promise(resolve => {
return new Promise((resolve) => {
let password;
password = getCookie(SAVED_PASSWORD);
resolve(password);
@ -92,7 +93,7 @@ function getPasswordFromCookie() {
}
function deleteSavedPassword() {
return new Promise(resolve => {
return new Promise((resolve) => {
deleteCookie(SAVED_PASSWORD);
resolve();
});
@ -102,19 +103,23 @@ function getAuthToken() {
return getCookie(AUTH_TOKEN);
}
function getTokens() {
return { auth_token: getAuthToken(), access_token: keycloak.token };
}
function setAuthToken(value) {
return setCookie(AUTH_TOKEN, value, 365);
}
function deleteAuthToken() {
return new Promise(resolve => {
return new Promise((resolve) => {
deleteCookie(AUTH_TOKEN);
resolve();
});
}
function doSignOutCleanup() {
return new Promise(resolve => {
return new Promise((resolve) => {
deleteAuthToken();
deleteSavedPassword();
resolve();
@ -122,10 +127,10 @@ function doSignOutCleanup() {
}
function doAuthTokenRefresh() {
const authToken = getAuthToken();
const { auth_token: authToken } = getAuthToken();
if (authToken) {
deleteAuthToken();
setAuthToken(authToken);
setCookie(AUTH_TOKEN, authToken, 365);
}
}
@ -147,6 +152,7 @@ module.exports = {
deleteSavedPassword,
getAuthToken,
setAuthToken,
getTokens,
deleteAuthToken,
doSignOutCleanup,
doAuthTokenRefresh,

View file

@ -1,12 +0,0 @@
const fs = require('fs');
const path = require('path');
let robots;
async function getRobots(ctx) {
if (!robots) {
robots = fs.readFileSync(path.join(__dirname, '/../dist/public/robots.txt'), 'utf8');
}
return robots;
}
module.exports = { getRobots };

View file

@ -5,7 +5,7 @@ const { getHtml } = require('./html');
const { getOEmbed } = require('./oEmbed');
const { getRss } = require('./rss');
const { getTempFile } = require('./tempfile');
const { createReadStream } = require('fs');
const fetch = require('node-fetch');
const Router = require('@koa/router');
@ -63,6 +63,11 @@ router.get(`/$/api/content/v1/get`, async (ctx) => {
}
});
router.get(`/$/sso/silent-check-sso.html`, async (ctx) => {
ctx.type = 'html';
ctx.body = createReadStream('./silent-check-sso.html');
})
router.get(`/$/download/:claimName/:claimId`, async (ctx) => {
const streamUrl = getStreamUrl(ctx);
const downloadUrl = `${streamUrl}?download=1`;

View file

@ -1188,6 +1188,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.9.0":
version "7.16.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a"
integrity sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.1", "@babel/template@^7.8.3", "@babel/template@^7.8.6":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
@ -1801,6 +1808,22 @@
dependencies:
tslib "^2.0.0"
"@react-keycloak/core@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@react-keycloak/core/-/core-3.2.0.tgz#e46f1951b0d7873f7f2fcd73dd0c270cb0b18db8"
integrity sha512-1yzU7gQzs+6E1v6hGqxy0Q+kpMHg9sEcke2yxZR29WoU8KNE8E50xS6UbI8N7rWsgyYw8r9W1cUPCOF48MYjzw==
dependencies:
react-fast-compare "^3.2.0"
"@react-keycloak/web@^3.4.0":
version "3.4.0"
resolved "https://registry.yarnpkg.com/@react-keycloak/web/-/web-3.4.0.tgz#725d96fab8e5fa47faff9615cc08574e5dff2222"
integrity sha512-yKKSCyqBtn7dt+VckYOW1IM5NW999pPkxDZOXqJ6dfXPXstYhOQCkTZqh8l7UL14PkpsoaHDh7hSJH8whah01g==
dependencies:
"@babel/runtime" "^7.9.0"
"@react-keycloak/core" "^3.2.0"
hoist-non-react-statics "^3.3.2"
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
@ -3783,9 +3806,10 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
base64-js@^1.0.2:
base64-js@1.3.1, base64-js@^1.0.2:
version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
base@^0.11.1:
version "0.11.2"
@ -10163,6 +10187,11 @@ js-base64@^2.1.9, js-base64@^2.6.1:
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
js-sha256@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966"
integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==
js-tokens@^3.0.0, js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
@ -10403,6 +10432,14 @@ jszip@~2.5.0:
dependencies:
pako "~0.2.5"
keycloak-js@^15.0.2:
version "15.1.0"
resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-15.1.0.tgz#9ed0ba9b17ed29f321b88b0ef09b0001ef43a417"
integrity sha512-HXujilW0VFw9iudWDnE6ta9neixGQ3/RB3+L2YN51RKqJsMeJFYn2OJEE3p0hb9dUG8p3FnD9CcGyxli0UUYSg==
dependencies:
base64-js "1.3.1"
js-sha256 "0.9.0"
keycode@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04"
@ -13751,6 +13788,11 @@ react-error-overlay@^1.0.9:
settle-promise "1.0.0"
source-map "0.5.6"
react-fast-compare@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
react-fit@^1.0.3:
version "1.3.1"
resolved "https://registry.yarnpkg.com/react-fit/-/react-fit-1.3.1.tgz#850cb5d554fdfa4b27891f62a9d290d3e7eda57b"