diff --git a/.eslintrc.js b/.eslintrc.js index 90fbff819..5deb32bde 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -74,6 +74,7 @@ module.exports = { "@typescript-eslint/require-await": "off", "@typescript-eslint/restrict-template-expressions": "off", "@typescript-eslint/unbound-method": "off", + "arrow-body-style": "off", "no-unused-expressions": "off", "no-unused-vars": "off", "no-use-before-define": "off", diff --git a/app/App.tsx b/app/App.tsx index c505f4dc2..a79f3d8de 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -10,7 +10,7 @@ import Intercom from "react-intercom"; import { Helmet } from "react-helmet-async"; import { Redirect, Route, Switch, useHistory } from "react-router-dom"; -import { AppContext, GeoCoordinates, getLocation, whiteLabel } from "./utils"; +import { GeoCoordinates, getLocation, whiteLabel, AppProvider } from "./utils"; import { Banner, HamburgerMenu, @@ -19,6 +19,7 @@ import { PopupMessageProp, UserWay, } from "./components/ui"; + import config from "./config"; import MetaImage from "./assets/img/sfsg-preview.png"; @@ -43,6 +44,9 @@ import OrganizationEditPage from "./pages/OrganizationEditPage"; import { EditBreakingNewsPage } from "./pages/EditBreakingNewsPage"; import { ServiceDiscoveryForm } from "./pages/ServiceDiscoveryForm"; import { ServiceDiscoveryResults } from "./pages/ServiceDiscoveryResults"; +import { LoginPage } from "./pages/Auth/LoginPage"; +import { SignUpPage } from "./pages/Auth/SignUpPage"; +import { LogoutPage } from "./pages/Auth/LogoutPage"; import styles from "./App.module.scss"; @@ -99,8 +103,7 @@ export const App = () => { return (
- {/* eslint-disable-next-line react/jsx-no-constructed-context-values */} - + {title} @@ -144,7 +147,6 @@ export const App = () => { /> - {/* NB: /organizations/new must be listed before /organizations/:id or else the /new step will be interpreted as an ID and will thus break the OrganizationEditPage */} { path="/breaking-news/edit" component={EditBreakingNewsPage} /> + + + {/* UCSF white label paths */} { path="/find-services/:selectedResourceSlug" component={UcsfDiscoveryForm} /> - {/* Legacy redirects */} { path="/resource" component={RedirectToOrganizations} /> -
{popUpMessage && } - + ); }; diff --git a/app/components/ui/Navigation.tsx b/app/components/ui/Navigation.tsx index 68eb96991..c9263a65f 100644 --- a/app/components/ui/Navigation.tsx +++ b/app/components/ui/Navigation.tsx @@ -2,8 +2,8 @@ import React, { FormEvent, useState } from "react"; import { Link, useHistory } from "react-router-dom"; import cn from "classnames"; import qs from "qs"; +import { useAppContext, whiteLabel } from "utils"; import Translate from "./Translate"; -import whiteLabel from "../../utils/whitelabel"; import styles from "./Navigation.module.scss"; const { @@ -89,46 +89,59 @@ const SiteLogo = () => ); -const SiteLinks = () => ( - + ); +}; const SiteSearch = ({ query, diff --git a/app/config.ts b/app/config.ts index d9fa5ab6c..be2ecf972 100644 --- a/app/config.ts +++ b/app/config.ts @@ -3,20 +3,35 @@ declare global { } interface Config { + ALGOLIA_APPLICATION_ID: string; + ALGOLIA_INDEX_PREFIX: string; + ALGOLIA_READ_ONLY_API_KEY: string; + AUTH0_AUDIENCE: string; + AUTH0_CLIENT_ID: string; + AUTH0_DOMAIN: string; + AUTH0_REDIRECT_URI: string; GOOGLE_ANALYTICS_ID: string; GOOGLE_ANALYTICS_GA4_ID: string; + GOOGLE_API_KEY: string; + INTERCOM_APP_ID: string; LINKSF_DOMAIN: string; MOHCD_DOMAIN: string; MOHCD_SUBDOMAIN: string; + SENTRY_PROJECT_ID: string; + SENTRY_PUBLIC_KEY: string; SFFAMILIES_DOMAIN: string; + SFFAMILIES_USERWAY_APP_ID: string; UCSF_DOMAIN: string; - [key: string]: any; } const config: Config = { ALGOLIA_APPLICATION_ID: CONFIG.ALGOLIA_APPLICATION_ID, ALGOLIA_INDEX_PREFIX: CONFIG.ALGOLIA_INDEX_PREFIX, ALGOLIA_READ_ONLY_API_KEY: CONFIG.ALGOLIA_READ_ONLY_API_KEY, + AUTH0_AUDIENCE: CONFIG.AUTH0_AUDIENCE, + AUTH0_CLIENT_ID: CONFIG.AUTH0_CLIENT_ID, + AUTH0_DOMAIN: CONFIG.AUTH0_DOMAIN, + AUTH0_REDIRECT_URI: CONFIG.AUTH0_REDIRECT_URI, // When GA sunsets Universal Analytics wit GA4 in July 2023, this prop can be removed GOOGLE_ANALYTICS_ID: process.env.NODE_ENV === "production" ? "UA-116318550-1" : "UA-116318550-2", diff --git a/app/pages/Auth/Auth.module.scss b/app/pages/Auth/Auth.module.scss new file mode 100644 index 000000000..e20b8c042 --- /dev/null +++ b/app/pages/Auth/Auth.module.scss @@ -0,0 +1,28 @@ +@import "~styles/utils/_helpers.scss"; + +.authPage { + display: grid; + grid-column: 2 / span 10; + gap: 10px; + margin: 20px; +} + +.verificationDigits { + display: flex; + width: 400px; +} + +.title { + font-size: 24px; +} + +.authForm { + width: 66%; + display: grid; + gap: 8px; +} + +.authFormButton { + max-width: 300px; + margin-top: 8px; +} diff --git a/app/pages/Auth/LoginPage.tsx b/app/pages/Auth/LoginPage.tsx new file mode 100644 index 000000000..40aad0526 --- /dev/null +++ b/app/pages/Auth/LoginPage.tsx @@ -0,0 +1,57 @@ +import React, { useState } from "react"; +import { Link } from "react-router-dom"; +import { Button } from "components/ui/inline/Button/Button"; +import { useAppContext, passwordlessLogin, passwordlessStart } from "utils"; + +import { VerificationModal } from "./VerificationModal"; + +import styles from "./Auth.module.scss"; + +export const LoginPage = () => { + const [modalIsOpen, setModalIsOpen] = useState(false); + const [email, setEmail] = useState(""); + const { authClient } = useAppContext(); + + const logIn = (evt: React.SyntheticEvent) => { + evt.preventDefault(); + passwordlessStart(authClient, email).then(() => { + setModalIsOpen(true); + }); + }; + + return ( +
+

For Case Managers

+ New here? Sign up! +

+ We want to make sure that your account information is safe, so you will + be sent a verification code to your email each time you log in. Please + enter in your email address and then check your email to find a 6 digit + verification code. +

+
+ { + setEmail(evt.target.value); + }} + /> + +
+ + passwordlessLogin(authClient, email, code)} + resendCode={() => passwordlessStart(authClient, email)} + buttonText="Log in" + /> +
+ ); +}; diff --git a/app/pages/Auth/LogoutPage.tsx b/app/pages/Auth/LogoutPage.tsx new file mode 100644 index 000000000..a006582eb --- /dev/null +++ b/app/pages/Auth/LogoutPage.tsx @@ -0,0 +1,18 @@ +import React, { useEffect } from "react"; +import { Redirect } from "react-router-dom"; +import * as AuthService from "utils/AuthService"; +import { useAppContext } from "../../utils"; + +import Config from "../../config"; + +export const LogoutPage = () => { + const context = useAppContext(); + const { setAuthState } = context; + const { authClient } = context; + + useEffect(() => { + AuthService.logout(authClient, Config.AUTH0_CLIENT_ID, setAuthState); + }); + + return ; +}; diff --git a/app/pages/Auth/SignUpPage.tsx b/app/pages/Auth/SignUpPage.tsx new file mode 100644 index 000000000..2c93e1f05 --- /dev/null +++ b/app/pages/Auth/SignUpPage.tsx @@ -0,0 +1,84 @@ +import React, { useState } from "react"; +import * as Sentry from "@sentry/browser"; +import { Link } from "react-router-dom"; +import { Button } from "components/ui/inline/Button/Button"; +import * as AuthService from "utils/AuthService"; +import { useAppContext } from "utils"; + +import { VerificationModal } from "./VerificationModal"; + +import styles from "./Auth.module.scss"; + +export const SignUpPage = () => { + const [modalIsOpen, setModalIsOpen] = useState(false); + const [email, setEmail] = useState(""); + const [name, setName] = useState(""); + const [organization, setOrganization] = useState(""); + const { authClient } = useAppContext(); + const { passwordlessStart, completeUserSignup } = AuthService; + + const signUp = (evt: React.SyntheticEvent) => { + evt.preventDefault(); + passwordlessStart(authClient, email).then( + () => { + setModalIsOpen(true); + }, + (error) => { + if (error) { + // TODO: Inform user of the error? + Sentry.captureException(error); + } + } + ); + }; + + return ( +
+

For Case Managers

+ Already have an account? Log in! +
+ { + setName(evt.target.value); + }} + /> + { + setEmail(evt.target.value); + }} + /> + { + setOrganization(evt.target.value); + }} + /> + +
+ + + completeUserSignup(authClient, code, email, name, organization) + } + resendCode={() => passwordlessStart(authClient, email)} + buttonText="Sign up" + /> +
+ ); +}; diff --git a/app/pages/Auth/VerificationModal.tsx b/app/pages/Auth/VerificationModal.tsx new file mode 100644 index 000000000..809308119 --- /dev/null +++ b/app/pages/Auth/VerificationModal.tsx @@ -0,0 +1,116 @@ +import React, { useState, createRef } from "react"; +import { Modal } from "components/ui/Modal/Modal"; +import { Button } from "components/ui/inline/Button/Button"; + +import styles from "./Auth.module.scss"; + +export const VerificationModal = ({ + email, + verifyCode, + modalIsOpen, + setModalIsOpen, + resendCode, + buttonText, +}: { + email: string; + verifyCode: (code: string) => void; + modalIsOpen: boolean; + setModalIsOpen: (isOpen: boolean) => void; + resendCode: () => Promise; + buttonText: string; +}) => { + const initialVerificationCode = ["", "", "", "", "", ""]; + const [verificationCode, setVerificationCode] = useState( + initialVerificationCode + ); + const inputRefs: React.RefObject[] = + initialVerificationCode.map(() => createRef()); + + const handleVerificationCodeChange = (index: number, value: string) => { + const updatedVerificationCode = [...verificationCode]; + updatedVerificationCode[index] = value; + setVerificationCode(updatedVerificationCode); + + const nextRef = inputRefs[index + 1]; + if (value && nextRef && nextRef.current) { + nextRef.current.focus(); + } + }; + + const handlePaste = (event: React.ClipboardEvent) => { + event.preventDefault(); + const clipboardData = event.clipboardData || window.Clipboard; + const pastedData = clipboardData.getData("text"); + + if (pastedData.length === 6) { + const pastedCodeArray = pastedData.split(""); + const updatedVerificationCode = [...verificationCode]; + + pastedCodeArray.forEach((digit, index) => { + const inputRef = inputRefs[index]; + if (inputRef.current) { + updatedVerificationCode[index] = digit; + inputRef.current.value = digit; + } + }); + + setVerificationCode(updatedVerificationCode); + inputRefs?.[0]?.current?.focus(); + } + }; + + const onSubmitCode = () => { + const codeString = verificationCode.join(""); + verifyCode(codeString); + }; + + return ( + setModalIsOpen(false)} + > +
+

Please check your email

+

We've sent a code to {email}

+
+ {verificationCode.map((digit, index) => ( + + handleVerificationCodeChange(index, evt.target.value ?? "") + } + onPaste={handlePaste} + ref={inputRefs[index]} + maxLength={1} + /> + ))} +
+ + +
+
+ ); +}; + +const ResendCode = ({ resendCode }: { resendCode: () => Promise }) => { + return ( +
+

+ Didn't receive a code? Please check your spam folder. + +

+
+ ); +}; diff --git a/app/pages/HomePage/HomePage.tsx b/app/pages/HomePage/HomePage.tsx index 45a889eb7..b82ab8976 100644 --- a/app/pages/HomePage/HomePage.tsx +++ b/app/pages/HomePage/HomePage.tsx @@ -4,11 +4,12 @@ import qs from "qs"; import { getResourceCount } from "utils/DataService"; import { Footer, NewsArticles } from "components/ui"; +import * as AuthService from "utils/AuthService"; import { Partners } from "./components/Partners/Partners"; import { SearchBar } from "./components/SearchBar/SearchBar"; import { HomePageSection } from "./components/Section/Section"; import ResourceList from "./components/ResourceList/ResourceList"; -import { whiteLabel } from "../../utils"; +import { whiteLabel, useAppContext } from "../../utils"; const { showBreakingNews } = whiteLabel; @@ -68,6 +69,7 @@ const covidResources = [ ]; export const HomePage = () => { + const { authClient, setAuthState } = useAppContext(); const [resourceCount, setResourceCount] = useState(); const [searchValue, setSearchValue] = useState(""); const history = useHistory(); @@ -83,6 +85,21 @@ export const HomePage = () => { getResourceCount().then((count: number) => setResourceCount(count)); }, []); + useEffect(() => { + // TODO: This effect should be moved to the case worker UI homepage when that page is created + const { hash } = window.location; + if (!hash || !hash.includes("access_token")) return; + + AuthService.initializeUserSession( + window.location.hash, + authClient, + setAuthState + ); + + // Remove the url query params set by Auth0 + history.replace(window.location.pathname + window.location.search); + }, [history, setAuthState, authClient]); + return ( <> {showBreakingNews && } diff --git a/app/utils/AuthService.ts b/app/utils/AuthService.ts new file mode 100644 index 000000000..93ca00c34 --- /dev/null +++ b/app/utils/AuthService.ts @@ -0,0 +1,155 @@ +import type { WebAuth, Auth0Result } from "auth0-js"; +import * as Sentry from "@sentry/browser"; +import { post } from "utils/DataService"; +import type { AuthState, UserSignUpData } from "utils"; +import { setUserSignUpData } from "utils"; +import config from "../config"; + +/** + This file provides a set of methods that serve as an interface between our application + and the Auth0 servers where the user's auth state and data is stored. +*/ + +export const calculateSessionExpiration = (secondsUntilExpiration: number) => { + const currentTime = new Date(); + const expirationTime = new Date( + currentTime.getTime() + secondsUntilExpiration * 1000 + ); + + return expirationTime; +}; + +export const initializeUserSession = ( + hash: string, + authClient: WebAuth, + setAuthState: (a: AuthState) => void +) => { + authClient.parseHash({ hash }, (err, authResult) => { + if (err) { + // TODO: Inform user of the error? + Sentry.captureException(err); + } + + if (authResult?.accessToken) { + const { accessToken, expiresIn, idTokenPayload } = authResult; + const authObject = { + user: { + email: idTokenPayload.email, + id: idTokenPayload.sub, + }, + accessTokenObject: { + token: accessToken, + expiresAt: expiresIn + ? calculateSessionExpiration(expiresIn) + : new Date(1970, 1, 1), + }, + }; + + setAuthState(authObject); + } + }); +}; + +// Invokes the passwordlessLogin method and following that stores the new user data in sessionStorage +export const completeUserSignup = ( + authClient: WebAuth, + verificationCode: string, + email: string, + name: string, + organization: string | null = null +) => { + passwordlessLogin(authClient, email, verificationCode); + // Store user sign up data, which will be saved to our backend after Auth0 success redirect + setUserSignUpData({ email, name, organization }); +}; + +/** This method initiates the log-in/sign-up process by sending a code + to the user's inbox. +*/ +export const passwordlessStart = (authClient: WebAuth, email: string) => { + return new Promise((resolve, reject) => { + authClient.passwordlessStart( + { + connection: "email", + send: "code", + email, + }, + (err) => { + if (err) { + reject(err); + return; + } + + resolve(true); + } + ); + }); +}; + +/** This method passes the user's verification code to Auth0's server, which + completes their sign-up/log-in action. Upon success, the Auth0 library + will initiate a redirect, which is why this function doesn't have a + meaningful return value. +*/ +export const passwordlessLogin = ( + authClient: WebAuth, + email: string, + verificationCode: string +) => { + authClient.passwordlessLogin( + { + connection: "email", + email, + verificationCode, + }, + (err) => { + if (err) { + // TODO: Inform user of the error? + Sentry.captureException(err); + } + } + ); +}; + +export const logout = ( + authClient: WebAuth, + clientId: string, + setAuthState: (state: AuthState) => void +) => { + setAuthState(null); + + authClient.logout({ + returnTo: config.AUTH0_REDIRECT_URI, + clientID: clientId, + }); +}; + +export const hasAccessTokenExpired = (tokenExpiration: Date) => { + return !tokenExpiration || tokenExpiration < new Date(); +}; + +export const refreshAccessToken = ( + authClient: WebAuth +): Promise => { + return new Promise((resolve, reject) => { + authClient.checkSession({}, (err, authResult: Auth0Result) => { + if (err) { + reject(err); + } else { + resolve(authResult); + } + }); + }); +}; + +export const saveUser = ( + userSignUpData: UserSignUpData, + userExternalId: string, + authToken: string +) => { + return post( + "/api/users", + { ...userSignUpData, user_external_id: userExternalId }, + { Authorization: `Bearer ${authToken}` } + ); +}; diff --git a/app/utils/SessionCacher.ts b/app/utils/SessionCacher.ts new file mode 100644 index 000000000..859e831f0 --- /dev/null +++ b/app/utils/SessionCacher.ts @@ -0,0 +1,38 @@ +import type { AuthState, UserSignUpData } from "utils"; + +/** + This file provides methods to sync a user's auth state, which is managed by the AppProvider + component, with the browser's sessionStorage; this enables the app to get the user's auth data + upon refreshing the page, etc., and to then reset the auth state. +*/ + +export const getAuthObject = (): AuthState | null => { + const object = sessionStorage.getItem("authObject"); + return object ? JSON.parse(object) : null; +}; + +export const setAuthObject = (authObject: AuthState) => { + sessionStorage.setItem("authObject", JSON.stringify(authObject)); +}; + +export const clearSession = () => { + sessionStorage.removeItem("authObject"); +}; + +export const setUserSignUpData = (userData: UserSignUpData) => { + sessionStorage.setItem("userSignUpData", JSON.stringify(userData)); +}; + +export const hasUserSignupData = (): boolean => { + const object = sessionStorage.getItem("userSignUpData"); + return !!object; +}; + +export const getUserSignUpData = (): UserSignUpData | null => { + const object = sessionStorage.getItem("userSignUpData"); + return object ? JSON.parse(object) : null; +}; + +export const clearUserSignUpData = () => { + sessionStorage.removeItem("userSignUpData"); +}; diff --git a/app/utils/index.ts b/app/utils/index.ts index d70b57e5c..650951a31 100644 --- a/app/utils/index.ts +++ b/app/utils/index.ts @@ -2,4 +2,6 @@ export * from "./location"; export * from "./numbers"; export * from "./time"; export * from "./useAppContext"; +export * from "./SessionCacher"; +export * from "./AuthService"; export { default as whiteLabel } from "./whitelabel"; diff --git a/app/utils/useAppContext.ts b/app/utils/useAppContext.ts deleted file mode 100644 index 6667358b2..000000000 --- a/app/utils/useAppContext.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createContext, useContext } from "react"; -import { GeoCoordinates } from "./location"; - -export const AppContext = createContext({ - userLocation: null, -}); - -export const useAppContext = () => useContext(AppContext); diff --git a/app/utils/useAppContext.tsx b/app/utils/useAppContext.tsx new file mode 100644 index 000000000..35418a028 --- /dev/null +++ b/app/utils/useAppContext.tsx @@ -0,0 +1,132 @@ +import React, { + useState, + useMemo, + useEffect, + createContext, + useContext, +} from "react"; +import auth0, { Auth0Result, WebAuth } from "auth0-js"; +import * as Sentry from "@sentry/browser"; +import * as SessionCacher from "utils/SessionCacher"; +import * as AuthService from "utils/AuthService"; +import { GeoCoordinates } from "utils"; +import config from "../config"; + +export interface UserSignUpData { + email: string; + name: string; + organization: string | null; +} + +export type AuthState = { + user: { + id: string; + email: string; + }; + accessTokenObject: { + expiresAt: Date; + token: string; + }; +} | null; + +interface Context { + userLocation: GeoCoordinates | null; + authState: AuthState; + setAuthState: (state: AuthState) => void; + authClient: WebAuth; +} + +const authClient = new auth0.WebAuth({ + audience: config.AUTH0_AUDIENCE, + clientID: config.AUTH0_CLIENT_ID, + domain: config.AUTH0_DOMAIN, + redirectUri: config.AUTH0_REDIRECT_URI, + responseType: "token id_token", +}); + +export const AppContext = createContext({ + userLocation: null, + authState: null, + setAuthState: () => {}, + authClient, +}); + +export const useAppContext = () => useContext(AppContext); + +export const AppProvider = ({ + children, + userLocation, +}: { + children: React.ReactNode; + userLocation: GeoCoordinates | null; +}) => { + const authObject = SessionCacher.getAuthObject(); + const [authState, setAuthState] = useState(authObject); + + // We have to use useMemo here to manage the contextValue to ensure that the user's authState + // propagates downward after authentication. I couldn't find a way to get this to work with + // useState. Moreover, we can't use a simple object to define contextValue, as the object would + // be recreated at each render and thus force all of its child components to re-render as well. + const contextValue = useMemo(() => { + return { + userLocation, + authState, + setAuthState, + authClient, + }; + }, [authState, userLocation]); + + useEffect(() => { + // This effect runs after any changes to the AppContext's authState and syncs the changes + // to the authObject in sessionStorage. + SessionCacher.setAuthObject(authState); + + // If the SessionCacher has userSignUpData object, that means a user has just been created + // in Auth0 and a redirect has occurred. Therefore, we save the new user data to our database. + // The authState must also have propagated or else we won't have the token yet. + const newUserData = SessionCacher.getUserSignUpData(); + if (newUserData && authState) { + SessionCacher.clearUserSignUpData(); + AuthService.saveUser( + newUserData, + authState.user.id, + authState.accessTokenObject.token + ); + } + }, [authState]); + + if ( + authObject && + AuthService.hasAccessTokenExpired( + new Date(authObject.accessTokenObject.expiresAt) + ) + ) { + AuthService.refreshAccessToken(contextValue.authClient) + .then((authResult: Auth0Result) => { + if ( + authState && + authResult.accessToken && + authResult.expiresIn !== undefined + ) { + setAuthState({ + ...authState, + accessTokenObject: { + token: authResult.accessToken, + expiresAt: AuthService.calculateSessionExpiration( + authResult.expiresIn + ), + }, + }); + } else { + throw new Error("Token does not exist or is unexpected token"); + } + }) + .catch((err) => { + Sentry.captureException(err); + }); + } + + return ( + {children} + ); +}; diff --git a/config.example.yml b/config.example.yml index 0c170f951..ee577734e 100644 --- a/config.example.yml +++ b/config.example.yml @@ -19,3 +19,9 @@ MOHCD_SUBDOMAIN: "testing" # uncomment this to simulate sfserviceguide whitelabel UI locally, assuming that # you're pointing to localhost:8080 # MOHCD_DOMAIN: 'localhost:8080' + +# Auth0 config values +AUTH0_AUDIENCE: "http://localhost:8080/api" +AUTH0_CLIENT_ID: "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc" +AUTH0_DOMAIN: "dev-nykixf8szsm220fi.us.auth0.com" +AUTH0_REDIRECT_URI: "http://localhost:8080" diff --git a/package-lock.json b/package-lock.json index 08e52db0d..29b284678 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@babel/polyfill": "^7.4.4", "@sentry/browser": "^4.0.6", "algoliasearch": "^4.10.5", + "auth0-js": "^9.22.1", "classnames": "^2.2.6", "copy-webpack-plugin": "^11.0.0", "google-map-react": "^1.1.4", @@ -50,6 +51,7 @@ "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.13.0", "@babel/register": "^7.4.4", + "@types/auth0-js": "^9.21.0", "@types/chai": "^4.2.22", "@types/enzyme": "^3.10.9", "@types/google-map-react": "^2.1.3", @@ -4638,6 +4640,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/auth0-js": { + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@types/auth0-js/-/auth0-js-9.21.0.tgz", + "integrity": "sha512-tnF0BKFwI+Vzqwb9p7KgpaKStg/WHqbiGWz5GPpn+ZeBvJ1iY7NkmeNJUsHIN/4c7CF2zr8FT5JRhs3F5aAPNw==", + "dev": true + }, "node_modules/@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -6595,7 +6603,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/at-least-node": { @@ -6620,6 +6627,21 @@ "node": ">= 4.5.0" } }, + "node_modules/auth0-js": { + "version": "9.22.1", + "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.22.1.tgz", + "integrity": "sha512-AcyJiWhsyG5zdx40O9i/okpLLEvB23/6CivWynmGtP43s2C4GSq3E+XdCRw64ifmZ7t6ZK4Yzfpiqy5KVXEtJg==", + "dependencies": { + "base64-js": "^1.5.1", + "idtoken-verifier": "^2.2.2", + "js-cookie": "^2.2.0", + "minimist": "^1.2.5", + "qs": "^6.10.1", + "superagent": "^7.1.5", + "url-join": "^4.0.1", + "winchan": "^0.2.2" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -8984,7 +9006,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -9271,6 +9292,11 @@ "dev": true, "license": "MIT" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" + }, "node_modules/copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -9609,6 +9635,11 @@ "node": "*" } }, + "node_modules/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, "node_modules/crypto-md5": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-md5/-/crypto-md5-1.0.0.tgz", @@ -10634,7 +10665,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -10699,6 +10729,15 @@ "dev": true, "license": "MIT" }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -13060,6 +13099,11 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "license": "MIT" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, "node_modules/fast-xml-parser": { "version": "3.21.1", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz", @@ -13636,6 +13680,20 @@ "node": ">= 0.12" } }, + "node_modules/formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -14570,6 +14628,14 @@ "node": ">= 8" } }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "engines": { + "node": ">=8" + } + }, "node_modules/highlight-es": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/highlight-es/-/highlight-es-1.0.3.tgz", @@ -15076,6 +15142,24 @@ "postcss": "^8.1.0" } }, + "node_modules/idtoken-verifier": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-2.2.3.tgz", + "integrity": "sha512-hhpzB+MRgEvbwqzRLFdVbG55lKdXQVfeYEjAA2qu0UC72MSLeR0nX7P7rY5Dycz1aISHPOwq80hIPFoJ/+SItA==", + "dependencies": { + "base64-js": "^1.5.1", + "crypto-js": "^4.1.1", + "es6-promise": "^4.2.8", + "jsbn": "^1.1.0", + "unfetch": "^4.2.0", + "url-join": "^4.0.1" + } + }, + "node_modules/idtoken-verifier/node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -17108,6 +17192,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -18463,7 +18552,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -18636,7 +18724,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -25755,6 +25842,99 @@ "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", "peer": true }, + "node_modules/superagent": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.1.5.tgz", + "integrity": "sha512-HQYyGuDRFGmZ6GNC4hq2f37KnsY9Lr0/R1marNZTgMweVDQLTLJJ6DGQ9Tj/xVVs5HEnop9EMmTbywb5P30aqw==", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.3", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.0.1", + "methods": "^1.1.2", + "mime": "^2.5.0", + "qs": "^6.10.3", + "readable-stream": "^3.6.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/superagent/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/superagent/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -27716,6 +27896,11 @@ "through": "^2.3.8" } }, + "node_modules/unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, "node_modules/unherit": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", @@ -28004,6 +28189,11 @@ "querystring": "0.2.0" } }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -29493,6 +29683,11 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, + "node_modules/winchan": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/winchan/-/winchan-0.2.2.tgz", + "integrity": "sha512-pvN+IFAbRP74n/6mc6phNyCH8oVkzXsto4KCHPJ2AScniAnA1AmeLI03I2BzjePpaClGSI4GUMowzsD3qz5PRQ==" + }, "node_modules/windows-release": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-5.0.1.tgz", @@ -29708,8 +29903,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "1.10.2", @@ -33070,6 +33264,12 @@ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", "dev": true }, + "@types/auth0-js": { + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@types/auth0-js/-/auth0-js-9.21.0.tgz", + "integrity": "sha512-tnF0BKFwI+Vzqwb9p7KgpaKStg/WHqbiGWz5GPpn+ZeBvJ1iY7NkmeNJUsHIN/4c7CF2zr8FT5JRhs3F5aAPNw==", + "dev": true + }, "@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -34561,8 +34761,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "at-least-node": { "version": "1.0.0", @@ -34575,6 +34774,21 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "auth0-js": { + "version": "9.22.1", + "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.22.1.tgz", + "integrity": "sha512-AcyJiWhsyG5zdx40O9i/okpLLEvB23/6CivWynmGtP43s2C4GSq3E+XdCRw64ifmZ7t6ZK4Yzfpiqy5KVXEtJg==", + "requires": { + "base64-js": "^1.5.1", + "idtoken-verifier": "^2.2.2", + "js-cookie": "^2.2.0", + "minimist": "^1.2.5", + "qs": "^6.10.1", + "superagent": "^7.1.5", + "url-join": "^4.0.1", + "winchan": "^0.2.2" + } + }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -36357,7 +36571,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -36575,6 +36788,11 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" + }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -36822,6 +37040,11 @@ "randomfill": "^1.0.3" } }, + "crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + }, "crypto-md5": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-md5/-/crypto-md5-1.0.0.tgz", @@ -37583,8 +37806,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, "denodeify": { "version": "1.2.1", @@ -37632,6 +37854,15 @@ "integrity": "sha512-fYXbFSeilT7bnKWFi4OERSPHdtaEoDGn4aUhV5Nly6/I+Tp6JZ/6Icmd7LVIF5euyodGpxz2e/bfUmDnIdSIDw==", "dev": true }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -39384,6 +39615,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, "fast-xml-parser": { "version": "3.21.1", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz", @@ -39773,6 +40009,17 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "requires": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -40419,6 +40666,11 @@ } } }, + "hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==" + }, "highlight-es": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/highlight-es/-/highlight-es-1.0.3.tgz", @@ -40815,6 +41067,26 @@ "dev": true, "requires": {} }, + "idtoken-verifier": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/idtoken-verifier/-/idtoken-verifier-2.2.3.tgz", + "integrity": "sha512-hhpzB+MRgEvbwqzRLFdVbG55lKdXQVfeYEjAA2qu0UC72MSLeR0nX7P7rY5Dycz1aISHPOwq80hIPFoJ/+SItA==", + "requires": { + "base64-js": "^1.5.1", + "crypto-js": "^4.1.1", + "es6-promise": "^4.2.8", + "jsbn": "^1.1.0", + "unfetch": "^4.2.0", + "url-join": "^4.0.1" + }, + "dependencies": { + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + } + } + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -42225,6 +42497,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -43261,7 +43538,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -43388,8 +43664,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "metro": { "version": "0.70.3", @@ -48661,6 +48936,72 @@ "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", "peer": true }, + "superagent": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.1.5.tgz", + "integrity": "sha512-HQYyGuDRFGmZ6GNC4hq2f37KnsY9Lr0/R1marNZTgMweVDQLTLJJ6DGQ9Tj/xVVs5HEnop9EMmTbywb5P30aqw==", + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.3", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.0.1", + "methods": "^1.1.2", + "mime": "^2.5.0", + "qs": "^6.10.3", + "readable-stream": "^3.6.0", + "semver": "^7.3.7" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -50093,6 +50434,11 @@ "through": "^2.3.8" } }, + "unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, "unherit": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", @@ -50303,6 +50649,11 @@ } } }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, "url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -51396,6 +51747,11 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, + "winchan": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/winchan/-/winchan-0.2.2.tgz", + "integrity": "sha512-pvN+IFAbRP74n/6mc6phNyCH8oVkzXsto4KCHPJ2AScniAnA1AmeLI03I2BzjePpaClGSI4GUMowzsD3qz5PRQ==" + }, "windows-release": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-5.0.1.tgz", @@ -51536,8 +51892,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { "version": "1.10.2", diff --git a/package.json b/package.json index 86044618b..e60ec37a6 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@babel/polyfill": "^7.4.4", "@sentry/browser": "^4.0.6", "algoliasearch": "^4.10.5", + "auth0-js": "^9.22.1", "classnames": "^2.2.6", "copy-webpack-plugin": "^11.0.0", "google-map-react": "^1.1.4", @@ -49,6 +50,7 @@ "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.13.0", "@babel/register": "^7.4.4", + "@types/auth0-js": "^9.21.0", "@types/chai": "^4.2.22", "@types/enzyme": "^3.10.9", "@types/google-map-react": "^2.1.3",