From 92cd98021078fd784b3fd3d97dd23c8073457f11 Mon Sep 17 00:00:00 2001
From: Brian Schroer
Date: Mon, 16 Oct 2023 12:58:44 -0700
Subject: [PATCH 01/25] Passwordless login prototype. First stab at adding auth
client to context object
---
.eslintrc.js | 1 +
app/App.tsx | 54 ++-
app/pages/Auth/Auth.module.scss | 28 ++
app/pages/Auth/PasswordlessAuthentication.tsx | 90 ++++
app/pages/Auth/SignInPage.tsx | 95 +++++
app/pages/Auth/SignUpPage.tsx | 111 +++++
app/pages/Auth/VerificationModal.tsx | 90 ++++
app/pages/HomePage/HomePage.tsx | 33 ++
app/utils/useAppContext.ts | 34 ++
package-lock.json | 389 +++++++++++++++++-
package.json | 2 +
11 files changed, 908 insertions(+), 19 deletions(-)
create mode 100644 app/pages/Auth/Auth.module.scss
create mode 100644 app/pages/Auth/PasswordlessAuthentication.tsx
create mode 100644 app/pages/Auth/SignInPage.tsx
create mode 100644 app/pages/Auth/SignUpPage.tsx
create mode 100644 app/pages/Auth/VerificationModal.tsx
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 d97a1726f..ecee7d2d5 100644
--- a/app/App.tsx
+++ b/app/App.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useMemo, useState } from "react";
// Todo: Once GA sunsets the UA analytics tracking come July 2023, we can remove the "react-ga"
// package and all references to it:
@@ -9,6 +9,7 @@ import ReactGA_4 from "react-ga4";
import Intercom from "react-intercom";
import { Helmet } from "react-helmet-async";
import { Redirect, Route, Switch, useHistory } from "react-router-dom";
+import auth0 from "auth0-js";
import { AppContext, GeoCoordinates, getLocation, whiteLabel } from "./utils";
import {
@@ -42,9 +43,14 @@ import OrganizationEditPage from "./pages/OrganizationEditPage";
import { EditBreakingNewsPage } from "./pages/EditBreakingNewsPage";
import { ServiceDiscoveryForm } from "./pages/ServiceDiscoveryForm";
import { ServiceDiscoveryResults } from "./pages/ServiceDiscoveryResults";
+import { SignInPage } from "./pages/Auth/SignInPage";
+import { SignUpPage } from "./pages/Auth/SignUpPage";
import styles from "./App.module.scss";
+
+
+
const {
homePageComponent,
intercom,
@@ -96,10 +102,52 @@ export const App = () => {
});
}, [history]);
+ console.log(AppContext);
+
+ const contextValue = useMemo(() => {
+
+
+ const passwordlessStart = (
+ evt: React.SyntheticEvent,
+ email: string,
+ callback: () => void
+ ) => {
+ evt.preventDefault();
+ webAuth.passwordlessStart(
+ {
+ connection: "email",
+ send: "code",
+ email,
+ },
+ (err) => {
+ if (err) {
+ console.log(err);
+ return;
+ }
+
+ callback();
+ }
+ );
+ };
+
+ return {
+ userLocation,
+ authState: {
+ isAuthenticated: false,
+ user: {
+ name: '',
+ email: '',
+ }
+ },
+ webAuth,
+ passwordlessStart,
+ }
+ }, [userLocation]);
+
return (
{/* eslint-disable-next-line react/jsx-no-constructed-context-values */}
-
+
{title}
@@ -213,6 +261,8 @@ export const App = () => {
path="/breaking-news/edit"
component={EditBreakingNewsPage}
/>
+
+
{/* UCSF white label paths */}
{
+ const [modalIsOpen, setModalIsOpen] = useState(false);
+ const [email, setEmail] = useState("");
+
+ const webAuth = new auth0.WebAuth({
+ clientID: "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc",
+ domain: "dev-nykixf8szsm220fi.us.auth0.com",
+ redirectUri: "http://localhost:8080",
+ responseType: "token id_token",
+ });
+
+ const startPasswordlessAuth = (evt: React.SyntheticEvent) => {
+ evt.preventDefault();
+
+ webAuth.passwordlessStart(
+ {
+ connection: "email",
+ send: "code",
+ email,
+ },
+ (err) => {
+ if (err) {
+ console.log(err);
+ return;
+ }
+
+ setModalIsOpen(true);
+ }
+ );
+ };
+
+ const verifyCode = (verificationCode: string) => {
+ webAuth.passwordlessLogin(
+ {
+ connection: "email",
+ email,
+ verificationCode,
+ },
+ (err) => {
+ if (err) {
+ console.log(err);
+ }
+ }
+ );
+ };
+
+ return (
+
+ );
+};
diff --git a/app/pages/Auth/SignInPage.tsx b/app/pages/Auth/SignInPage.tsx
new file mode 100644
index 000000000..93f231fa0
--- /dev/null
+++ b/app/pages/Auth/SignInPage.tsx
@@ -0,0 +1,95 @@
+import React, { useState } from "react";
+import auth0 from "auth0-js";
+import { Link } from "react-router-dom";
+import { Button } from "components/ui/inline/Button/Button";
+import { useAppContext } from "utils";
+
+
+import { VerificationModal } from "./VerificationModal";
+
+import styles from "./Auth.module.scss";
+
+export const SignInPage = () => {
+ const [modalIsOpen, setModalIsOpen] = useState(false);
+ const [email, setEmail] = useState("");
+ // const { passwordlessStart } = useAppContext();
+ const { passwordlessStart } = useAppContext();
+
+ const webAuth = new auth0.WebAuth({
+ audience: "http://localhost:8080/api",
+ clientID: "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc",
+ domain: "dev-nykixf8szsm220fi.us.auth0.com",
+ redirectUri: "http://localhost:8080",
+ responseType: "token id_token",
+ });
+
+
+ // const signIn = (evt: React.SyntheticEvent) => {
+ // evt.preventDefault();
+ // webAuth.passwordlessStart(
+ // {
+ // connection: "email",
+ // send: "code",
+ // email,
+ // },
+ // (err) => {
+ // if (err) {
+ // console.log(err);
+ // return;
+ // }
+
+ // setModalIsOpen(true);
+ // }
+ // );
+ // };
+
+ const signIn = (evt: React.SyntheticEvent) => {
+ evt.preventDefault();
+ console.log('hi!', passwordlessStart);
+ passwordlessStart(evt, email, () => setModalIsOpen(true));
+ };
+
+ const loginWithCode = (verificationCode: string) => {
+ webAuth.passwordlessLogin(
+ {
+ connection: "email",
+ email,
+ verificationCode,
+ },
+ (err) => {
+ if (err) {
+ console.log(err);
+ }
+ }
+ );
+ };
+
+ return (
+
+
For Case Managers
+ New here? Sign up!
+
+
+
+ );
+};
diff --git a/app/pages/Auth/SignUpPage.tsx b/app/pages/Auth/SignUpPage.tsx
new file mode 100644
index 000000000..47626ff89
--- /dev/null
+++ b/app/pages/Auth/SignUpPage.tsx
@@ -0,0 +1,111 @@
+import React, { useState } from "react";
+import auth0 from "auth0-js";
+import { Link } from "react-router-dom";
+import { Button } from "components/ui/inline/Button/Button";
+
+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 webAuth = new auth0.WebAuth({
+ clientID: "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc",
+ domain: "dev-nykixf8szsm220fi.us.auth0.com",
+ redirectUri: "http://localhost:8080",
+ responseType: "token id_token",
+ });
+
+ const startPasswordlessAuth = (evt: React.SyntheticEvent) => {
+ evt.preventDefault();
+ webAuth.passwordlessStart(
+ {
+ connection: "email",
+ send: "code",
+ email,
+ },
+ (err) => {
+ if (err) {
+ console.log(err);
+ return;
+ }
+
+ setModalIsOpen(true);
+ }
+ );
+ };
+
+ const verifyCode = (verificationCode: string) => {
+ webAuth.passwordlessLogin(
+ {
+ connection: "email",
+ email,
+ verificationCode,
+ },
+ (err) => {
+ if (err) {
+ console.log(err);
+ }
+ }
+ );
+ };
+
+ return (
+
+
For Case Managers
+ Already have an account? Log in!
+
+
+
+ );
+};
diff --git a/app/pages/Auth/VerificationModal.tsx b/app/pages/Auth/VerificationModal.tsx
new file mode 100644
index 000000000..1d7036b47
--- /dev/null
+++ b/app/pages/Auth/VerificationModal.tsx
@@ -0,0 +1,90 @@
+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 = ({
+ verifyCode,
+ modalIsOpen,
+ setModalIsOpen,
+}: {
+ verifyCode: (code: string) => void;
+ modalIsOpen: boolean;
+ setModalIsOpen: (isOpen: boolean) => void;
+}) => {
+ 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
+
+ {verificationCode.map((digit, index) => (
+
+ handleVerificationCodeChange(index, evt.target.value ?? "")
+ }
+ onPaste={handlePaste}
+ ref={inputRefs[index]}
+ maxLength={1}
+ />
+ ))}
+
+
Verify Account
+
+
+ );
+};
diff --git a/app/pages/HomePage/HomePage.tsx b/app/pages/HomePage/HomePage.tsx
index 45a889eb7..ed254af7e 100644
--- a/app/pages/HomePage/HomePage.tsx
+++ b/app/pages/HomePage/HomePage.tsx
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import qs from "qs";
+import auth0 from "auth0-js";
import { getResourceCount } from "utils/DataService";
import { Footer, NewsArticles } from "components/ui";
import { Partners } from "./components/Partners/Partners";
@@ -81,13 +82,45 @@ export const HomePage = () => {
useEffect(() => {
getResourceCount().then((count: number) => setResourceCount(count));
+
+
+
+ webAuth.parseHash({ hash: window.location.hash }, (err, authResult) => {
+ if (err) {
+ console.log(err);
+ }
+ console.log(authResult)
+ if (authResult?.accessToken) {
+ window.foobar = authResult.accessToken;
+ webAuth.client.userInfo(authResult.accessToken, (tokenErr, user) => {
+ if (tokenErr) {
+ console.log(tokenErr);
+ }
+ console.log(user);
+ // alert(`Welcome, ${user.email}!`);
+ });
+ }
+ });
}, []);
+ const secureApiCall = () => {
+ console.log('hi: ', window.foobar)
+ fetch("/api/resources/548", {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${window.foobar}`,
+ },
+ }).then(resp => {
+ console.log(resp)
+ });
+ };
+
return (
<>
{showBreakingNews && }
+ Hi Buddy!
void) => {
+ evt.preventDefault();
+ webAuth.passwordlessStart(
+ {
+ connection: "email",
+ send: "code",
+ email,
+ },
+ (err) => {
+ if (err) {
+ console.log(err);
+ return;
+ }
+
+ callback();
+ }
+ );
+};
+
+// export const InitialContext =
+
export const AppContext = createContext({
userLocation: null,
+ authState: {
+ isAuthenticated: false,
+ user: {
+ name: '',
+ email: '',
+ }
+ },
+ webAuth,
+ passwordlessStart
});
+console.log(AppContext)
export const useAppContext = () => useContext(AppContext);
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",
From f74af9feb7b68a4ddd8a3f902b308e09d58760df Mon Sep 17 00:00:00 2001
From: Brian Schroer
Date: Wed, 25 Oct 2023 15:09:43 -0700
Subject: [PATCH 02/25] Begin building out AuthService SessionStorage API and
Context object for managing user session
---
app/App.tsx | 59 +++------------------
app/components/AppProvider.tsx | 47 +++++++++++++++++
app/pages/Auth/SignInPage.tsx | 58 ++++++++-------------
app/pages/HomePage/HomePage.tsx | 41 +++------------
app/utils/AuthService.ts | 90 +++++++++++++++++++++++++++++++++
app/utils/SessionCacher.ts | 52 +++++++++++++++++++
app/utils/index.ts | 1 +
app/utils/useAppContext.ts | 37 +++-----------
8 files changed, 233 insertions(+), 152 deletions(-)
create mode 100644 app/components/AppProvider.tsx
create mode 100644 app/utils/AuthService.ts
create mode 100644 app/utils/SessionCacher.ts
diff --git a/app/App.tsx b/app/App.tsx
index ecee7d2d5..881ff6f2f 100644
--- a/app/App.tsx
+++ b/app/App.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useMemo, useState } from "react";
+import React, { useEffect, useState } from "react";
// Todo: Once GA sunsets the UA analytics tracking come July 2023, we can remove the "react-ga"
// package and all references to it:
@@ -9,9 +9,8 @@ import ReactGA_4 from "react-ga4";
import Intercom from "react-intercom";
import { Helmet } from "react-helmet-async";
import { Redirect, Route, Switch, useHistory } from "react-router-dom";
-import auth0 from "auth0-js";
-import { AppContext, GeoCoordinates, getLocation, whiteLabel } from "./utils";
+import { GeoCoordinates, getLocation, whiteLabel } from "./utils";
import {
Banner,
HamburgerMenu,
@@ -20,6 +19,10 @@ import {
PopupMessageProp,
UserWay,
} from "./components/ui";
+import {
+ AppProvider
+} from "./components/AppProvider";
+
import config from "./config";
import MetaImage from "./assets/img/sfsg-preview.png";
@@ -48,9 +51,6 @@ import { SignUpPage } from "./pages/Auth/SignUpPage";
import styles from "./App.module.scss";
-
-
-
const {
homePageComponent,
intercom,
@@ -102,52 +102,9 @@ export const App = () => {
});
}, [history]);
- console.log(AppContext);
-
- const contextValue = useMemo(() => {
-
-
- const passwordlessStart = (
- evt: React.SyntheticEvent,
- email: string,
- callback: () => void
- ) => {
- evt.preventDefault();
- webAuth.passwordlessStart(
- {
- connection: "email",
- send: "code",
- email,
- },
- (err) => {
- if (err) {
- console.log(err);
- return;
- }
-
- callback();
- }
- );
- };
-
- return {
- userLocation,
- authState: {
- isAuthenticated: false,
- user: {
- name: '',
- email: '',
- }
- },
- webAuth,
- passwordlessStart,
- }
- }, [userLocation]);
-
return (
- {/* eslint-disable-next-line react/jsx-no-constructed-context-values */}
-
+
{title}
@@ -289,7 +246,7 @@ export const App = () => {
{popUpMessage && }
-
+
);
};
diff --git a/app/components/AppProvider.tsx b/app/components/AppProvider.tsx
new file mode 100644
index 000000000..d11d1609a
--- /dev/null
+++ b/app/components/AppProvider.tsx
@@ -0,0 +1,47 @@
+import React, { useState, useMemo, useEffect } from "react";
+import auth0 from "auth0-js";
+
+import { AppContext, GeoCoordinates } from "utils";
+import SessionCacher from "utils/SessionCacher";
+
+export const AppProvider = ({
+ children,
+ userLocation,
+}: {
+ children: React.ReactNode;
+ userLocation: GeoCoordinates | null;
+}) => {
+ const [authState, setAuthState] = useState({
+ isAuthenticated: false,
+ user: {
+ id: "",
+ email: "",
+ },
+ accessToken: "",
+ });
+
+ useEffect(() => {
+ SessionCacher.setAuthObject(authState);
+ }, [authState]);
+
+ const contextValue = useMemo(() => {
+ const webAuth = new auth0.WebAuth({
+ audience: "http://localhost:8080/api",
+ clientID: "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc",
+ domain: "dev-nykixf8szsm220fi.us.auth0.com",
+ redirectUri: "http://localhost:8080",
+ responseType: "token id_token",
+ });
+
+ return {
+ userLocation,
+ authState,
+ setAuthState,
+ webAuth,
+ };
+ }, [authState, userLocation]);
+
+ return (
+ {children}
+ );
+};
diff --git a/app/pages/Auth/SignInPage.tsx b/app/pages/Auth/SignInPage.tsx
index 93f231fa0..9941c93b8 100644
--- a/app/pages/Auth/SignInPage.tsx
+++ b/app/pages/Auth/SignInPage.tsx
@@ -1,9 +1,7 @@
import React, { useState } from "react";
-import auth0 from "auth0-js";
import { Link } from "react-router-dom";
import { Button } from "components/ui/inline/Button/Button";
-import { useAppContext } from "utils";
-
+import { useAppContext, AuthService } from "utils";
import { VerificationModal } from "./VerificationModal";
@@ -12,42 +10,26 @@ import styles from "./Auth.module.scss";
export const SignInPage = () => {
const [modalIsOpen, setModalIsOpen] = useState(false);
const [email, setEmail] = useState("");
- // const { passwordlessStart } = useAppContext();
- const { passwordlessStart } = useAppContext();
-
- const webAuth = new auth0.WebAuth({
- audience: "http://localhost:8080/api",
- clientID: "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc",
- domain: "dev-nykixf8szsm220fi.us.auth0.com",
- redirectUri: "http://localhost:8080",
- responseType: "token id_token",
- });
-
-
- // const signIn = (evt: React.SyntheticEvent) => {
- // evt.preventDefault();
- // webAuth.passwordlessStart(
- // {
- // connection: "email",
- // send: "code",
- // email,
- // },
- // (err) => {
- // if (err) {
- // console.log(err);
- // return;
- // }
+ const { webAuth } = useAppContext();
- // setModalIsOpen(true);
- // }
- // );
- // };
+ const signIn = (evt: React.SyntheticEvent) => {
+ evt.preventDefault();
+ webAuth.passwordlessStart(
+ {
+ connection: "email",
+ send: "code",
+ email,
+ },
+ (err) => {
+ if (err) {
+ console.log(err);
+ return;
+ }
- const signIn = (evt: React.SyntheticEvent) => {
- evt.preventDefault();
- console.log('hi!', passwordlessStart);
- passwordlessStart(evt, email, () => setModalIsOpen(true));
- };
+ setModalIsOpen(true);
+ }
+ );
+ };
const loginWithCode = (verificationCode: string) => {
webAuth.passwordlessLogin(
@@ -69,7 +51,7 @@ export const SignInPage = () => {
For Case Managers
New here? Sign up!
loginWithCode(code)}
modalIsOpen={modalIsOpen}
setModalIsOpen={setModalIsOpen}
/>
diff --git a/app/pages/HomePage/HomePage.tsx b/app/pages/HomePage/HomePage.tsx
index ed254af7e..7bcf09dd8 100644
--- a/app/pages/HomePage/HomePage.tsx
+++ b/app/pages/HomePage/HomePage.tsx
@@ -2,14 +2,13 @@ import React, { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import qs from "qs";
-import auth0 from "auth0-js";
import { getResourceCount } from "utils/DataService";
import { Footer, NewsArticles } from "components/ui";
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, AuthService } from "../../utils";
const { showBreakingNews } = whiteLabel;
@@ -69,6 +68,7 @@ const covidResources = [
];
export const HomePage = () => {
+ const { webAuth, setAuthState } = useAppContext();
const [resourceCount, setResourceCount] = useState();
const [searchValue, setSearchValue] = useState("");
const history = useHistory();
@@ -82,45 +82,20 @@ export const HomePage = () => {
useEffect(() => {
getResourceCount().then((count: number) => setResourceCount(count));
+ });
+ useEffect(() => {
+ if (!window.location.hash) return;
-
- webAuth.parseHash({ hash: window.location.hash }, (err, authResult) => {
- if (err) {
- console.log(err);
- }
- console.log(authResult)
- if (authResult?.accessToken) {
- window.foobar = authResult.accessToken;
- webAuth.client.userInfo(authResult.accessToken, (tokenErr, user) => {
- if (tokenErr) {
- console.log(tokenErr);
- }
- console.log(user);
- // alert(`Welcome, ${user.email}!`);
- });
- }
- });
- }, []);
-
- const secureApiCall = () => {
- console.log('hi: ', window.foobar)
- fetch("/api/resources/548", {
- method: "GET",
- headers: {
- Authorization: `Bearer ${window.foobar}`,
- },
- }).then(resp => {
- console.log(resp)
- });
- };
+ AuthService.persistUser(window.location.hash, webAuth, setAuthState);
+ history.replace(window.location.pathname + window.location.search);
+ }, [history, setAuthState, webAuth]);
return (
<>
{showBreakingNews && }
- Hi Buddy!
{
+ if (err) {
+ console.log(err)
+ }
+
+ console.log(authResult);
+
+ if (authResult?.accessToken) {
+ const { accessToken, expiresIn, idTokenPayload } = authResult;
+ const authObject = {
+ isAuthenticated: true,
+ user: {
+ email: idTokenPayload.email,
+ id: idTokenPayload.sub,
+ },
+ accessTokenObject: {
+ token: accessToken,
+ expiresAt: expiresIn ? this.calculateExpirationTime(expiresIn) : null,
+ }
+ };
+
+ setAuthState(authObject);
+ // return Promise.resolve(null);
+ }
+
+ // return Promise.reject(err);
+ });
+ }
+
+ static passwordlessStart = (
+ evt: React.SyntheticEvent,
+ email: string,
+ webAuth: any,
+ callback: () => void
+ ) => {
+ console.log("calling from here!");
+ evt.preventDefault();
+ webAuth.passwordlessStart(
+ {
+ connection: "emaidl",
+ send: "code",
+ email,
+ },
+ (err: AuthError) => {
+ if (err) {
+ console.log(err);
+ return;
+ }
+
+ callback();
+ }
+ );
+ };
+
+ static passwordlessVerify = (email: string, verificationCode: string, webAuth: any) => {
+ webAuth.passwordlessLogin(
+ {
+ connection: "email",
+ email,
+ verificationCode,
+ },
+ (err: AuthError) => {
+ if (err) {
+ console.log(err);
+ }
+ }
+ );
+ };
+}
+
diff --git a/app/utils/SessionCacher.ts b/app/utils/SessionCacher.ts
new file mode 100644
index 000000000..2d8ef4e8c
--- /dev/null
+++ b/app/utils/SessionCacher.ts
@@ -0,0 +1,52 @@
+// interface AccessTokenObject {
+// token: string;
+// expiresAt: Date;
+// }
+
+// interface UserObject {
+// email: string;
+// id: string;
+// }
+
+interface AuthObject {
+ isAuthenticated: boolean;
+ user: {
+ id: string;
+ email: string;
+ };
+ accessToken: string;
+}
+
+export default class SessionCacher {
+ // static getAccessTokenObject() {
+ // const object = localStorage.getItem('accessTokenObject') || "";
+ // return JSON.parse(object);
+ // }
+
+ // static setAccessTokenObject(accessTokenObject: AccessTokenObject) {
+ // sessionStorage.setItem("accessTokenObject", JSON.stringify(accessTokenObject));
+ // }
+
+ // static getUserObject() {
+ // const object = localStorage.getItem('userObject') || "";
+ // return JSON.parse(object);
+ // }
+
+ // static setUserObject(userObject: UserObject) {
+ // sessionStorage.setItem("userObject", JSON.stringify(userObject));
+ // }
+
+
+ static getAuthObject() {
+ const object = localStorage.getItem('authObject') || "";
+ return JSON.parse(object);
+ }
+
+ static setAuthObject(authObject: AuthObject) {
+ sessionStorage.setItem("authObject", JSON.stringify(authObject));
+ }
+
+ static clearSession() {
+ sessionStorage.removeItem("authObject");
+ }
+}
diff --git a/app/utils/index.ts b/app/utils/index.ts
index d70b57e5c..a8259a73c 100644
--- a/app/utils/index.ts
+++ b/app/utils/index.ts
@@ -2,4 +2,5 @@ export * from "./location";
export * from "./numbers";
export * from "./time";
export * from "./useAppContext";
+export { default as AuthService } from "./AuthService";
export { default as whiteLabel } from "./whitelabel";
diff --git a/app/utils/useAppContext.ts b/app/utils/useAppContext.ts
index d1e3af628..69518866d 100644
--- a/app/utils/useAppContext.ts
+++ b/app/utils/useAppContext.ts
@@ -1,42 +1,19 @@
import { createContext, useContext } from "react";
-import auth0 from "auth0-js";
+import { WebAuth } from "auth0-js";
import { GeoCoordinates } from "./location";
-
-
-const passwordlessStart = (evt: React.SyntheticEvent, email: string, callback: () => void) => {
- evt.preventDefault();
- webAuth.passwordlessStart(
- {
- connection: "email",
- send: "code",
- email,
- },
- (err) => {
- if (err) {
- console.log(err);
- return;
- }
-
- callback();
- }
- );
-};
-
-// export const InitialContext =
-
export const AppContext = createContext({
userLocation: null,
authState: {
isAuthenticated: false,
user: {
- name: '',
- email: '',
- }
+ id: "",
+ email: "",
+ },
+ accessToken: ""
},
- webAuth,
- passwordlessStart
+ setAuthState: <(state: any) => void>null,
+ webAuth: null,
});
-console.log(AppContext)
export const useAppContext = () => useContext(AppContext);
From ac1abeee6580c19a748bcff984f58fb6286aa55b Mon Sep 17 00:00:00 2001
From: Brian Schroer
Date: Tue, 31 Oct 2023 14:25:02 -0700
Subject: [PATCH 03/25] Read sessionStorage for user session upon loading page
---
app/components/AppProvider.tsx | 13 ++-
app/pages/Auth/PasswordlessAuthentication.tsx | 90 -------------------
app/pages/Auth/SignInPage.tsx | 35 +-------
app/pages/HomePage/HomePage.tsx | 3 +-
app/utils/AuthService.ts | 23 +++--
app/utils/SessionCacher.ts | 21 ++---
app/utils/useAppContext.ts | 5 +-
7 files changed, 36 insertions(+), 154 deletions(-)
delete mode 100644 app/pages/Auth/PasswordlessAuthentication.tsx
diff --git a/app/components/AppProvider.tsx b/app/components/AppProvider.tsx
index d11d1609a..382f6c2bc 100644
--- a/app/components/AppProvider.tsx
+++ b/app/components/AppProvider.tsx
@@ -3,6 +3,7 @@ import auth0 from "auth0-js";
import { AppContext, GeoCoordinates } from "utils";
import SessionCacher from "utils/SessionCacher";
+import type AuthObject from "utils/SessionCacher";
export const AppProvider = ({
children,
@@ -11,14 +12,20 @@ export const AppProvider = ({
children: React.ReactNode;
userLocation: GeoCoordinates | null;
}) => {
- const [authState, setAuthState] = useState({
+ const defaultAuthObject: AuthObject = {
isAuthenticated: false,
user: {
id: "",
email: "",
},
- accessToken: "",
- });
+ accessToken: {
+ token: "",
+ expiresAt: new Date(1970, 0, 1),
+ },
+ };
+
+ const authObject = SessionCacher.getAuthObject() ?? defaultAuthObject;
+ const [authState, setAuthState] = useState(authObject);
useEffect(() => {
SessionCacher.setAuthObject(authState);
diff --git a/app/pages/Auth/PasswordlessAuthentication.tsx b/app/pages/Auth/PasswordlessAuthentication.tsx
deleted file mode 100644
index b1040a592..000000000
--- a/app/pages/Auth/PasswordlessAuthentication.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- Auth0's passwordlessAPI is the same for signing up and is it for signing in. As such,
- we can share this component across the two pages
-*/
-
-import React, { useState } from "react";
-import auth0 from "auth0-js";
-import { VerificationModal } from "./VerificationModal";
-
-import styles from "./Auth.module.scss";
-
-type Mode = "signIn" | "signUp";
-
-export const PasswordlessAuthentication = ({ mode }: { mode: Mode }) => {
- const [modalIsOpen, setModalIsOpen] = useState(false);
- const [email, setEmail] = useState("");
-
- const webAuth = new auth0.WebAuth({
- clientID: "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc",
- domain: "dev-nykixf8szsm220fi.us.auth0.com",
- redirectUri: "http://localhost:8080",
- responseType: "token id_token",
- });
-
- const startPasswordlessAuth = (evt: React.SyntheticEvent) => {
- evt.preventDefault();
-
- webAuth.passwordlessStart(
- {
- connection: "email",
- send: "code",
- email,
- },
- (err) => {
- if (err) {
- console.log(err);
- return;
- }
-
- setModalIsOpen(true);
- }
- );
- };
-
- const verifyCode = (verificationCode: string) => {
- webAuth.passwordlessLogin(
- {
- connection: "email",
- email,
- verificationCode,
- },
- (err) => {
- if (err) {
- console.log(err);
- }
- }
- );
- };
-
- return (
-
- );
-};
diff --git a/app/pages/Auth/SignInPage.tsx b/app/pages/Auth/SignInPage.tsx
index 9941c93b8..240ae0446 100644
--- a/app/pages/Auth/SignInPage.tsx
+++ b/app/pages/Auth/SignInPage.tsx
@@ -11,39 +11,10 @@ export const SignInPage = () => {
const [modalIsOpen, setModalIsOpen] = useState(false);
const [email, setEmail] = useState("");
const { webAuth } = useAppContext();
-
+ const { passwordlessStart, passwordlessVerify } = AuthService;
const signIn = (evt: React.SyntheticEvent) => {
evt.preventDefault();
- webAuth.passwordlessStart(
- {
- connection: "email",
- send: "code",
- email,
- },
- (err) => {
- if (err) {
- console.log(err);
- return;
- }
-
- setModalIsOpen(true);
- }
- );
- };
-
- const loginWithCode = (verificationCode: string) => {
- webAuth.passwordlessLogin(
- {
- connection: "email",
- email,
- verificationCode,
- },
- (err) => {
- if (err) {
- console.log(err);
- }
- }
- );
+ passwordlessStart(evt, webAuth, email, () => setModalIsOpen(true));
};
return (
@@ -51,7 +22,7 @@ export const SignInPage = () => {
For Case Managers
New here? Sign up!
loginWithCode(code)}
+ verifyCode={(code) => passwordlessVerify(webAuth, email, code)}
modalIsOpen={modalIsOpen}
setModalIsOpen={setModalIsOpen}
/>
diff --git a/app/pages/HomePage/HomePage.tsx b/app/pages/HomePage/HomePage.tsx
index 7bcf09dd8..84ffac7fb 100644
--- a/app/pages/HomePage/HomePage.tsx
+++ b/app/pages/HomePage/HomePage.tsx
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import qs from "qs";
+import type { WebAuth } from "auth0-js";
import { getResourceCount } from "utils/DataService";
import { Footer, NewsArticles } from "components/ui";
@@ -87,7 +88,7 @@ export const HomePage = () => {
useEffect(() => {
if (!window.location.hash) return;
- AuthService.persistUser(window.location.hash, webAuth, setAuthState);
+ AuthService.persistUser(window.location.hash, webAuth as WebAuth, setAuthState);
history.replace(window.location.pathname + window.location.search);
}, [history, setAuthState, webAuth]);
diff --git a/app/utils/AuthService.ts b/app/utils/AuthService.ts
index fcb49d4be..7ce53acc4 100644
--- a/app/utils/AuthService.ts
+++ b/app/utils/AuthService.ts
@@ -20,10 +20,9 @@ export default class AuthService {
static persistUser(hash: string, webAuth: WebAuth, setAuthState: any) {
webAuth.parseHash({ hash }, (err, authResult) => {
if (err) {
- console.log(err)
+ // TODO: Handle errors
}
- console.log(authResult);
if (authResult?.accessToken) {
const { accessToken, expiresIn, idTokenPayload } = authResult;
@@ -40,39 +39,37 @@ export default class AuthService {
};
setAuthState(authObject);
- // return Promise.resolve(null);
}
-
- // return Promise.reject(err);
});
}
static passwordlessStart = (
evt: React.SyntheticEvent,
- email: string,
webAuth: any,
- callback: () => void
+ email: string,
+ callback?: () => void
) => {
- console.log("calling from here!");
evt.preventDefault();
webAuth.passwordlessStart(
{
- connection: "emaidl",
+ connection: "email",
send: "code",
email,
},
(err: AuthError) => {
if (err) {
- console.log(err);
+ // TODO: Handle errors
return;
}
- callback();
+ if (callback) {
+ callback();
+ }
}
);
};
- static passwordlessVerify = (email: string, verificationCode: string, webAuth: any) => {
+ static passwordlessVerify = (webAuth: any, email: string, verificationCode: string) => {
webAuth.passwordlessLogin(
{
connection: "email",
@@ -81,7 +78,7 @@ export default class AuthService {
},
(err: AuthError) => {
if (err) {
- console.log(err);
+ // TODO: Handle errors
}
}
);
diff --git a/app/utils/SessionCacher.ts b/app/utils/SessionCacher.ts
index 2d8ef4e8c..8a3f23cb3 100644
--- a/app/utils/SessionCacher.ts
+++ b/app/utils/SessionCacher.ts
@@ -1,20 +1,13 @@
-// interface AccessTokenObject {
-// token: string;
-// expiresAt: Date;
-// }
-
-// interface UserObject {
-// email: string;
-// id: string;
-// }
-
interface AuthObject {
isAuthenticated: boolean;
user: {
id: string;
email: string;
};
- accessToken: string;
+ accessTokenObject: {
+ token: string;
+ expiresAt: Date;
+ };
}
export default class SessionCacher {
@@ -37,9 +30,9 @@ export default class SessionCacher {
// }
- static getAuthObject() {
- const object = localStorage.getItem('authObject') || "";
- return JSON.parse(object);
+ static getAuthObject(): AuthObject {
+ const object = sessionStorage.getItem('authObject');
+ return object ? JSON.parse(object) : null;
}
static setAuthObject(authObject: AuthObject) {
diff --git a/app/utils/useAppContext.ts b/app/utils/useAppContext.ts
index 69518866d..00169eb10 100644
--- a/app/utils/useAppContext.ts
+++ b/app/utils/useAppContext.ts
@@ -10,7 +10,10 @@ export const AppContext = createContext({
id: "",
email: "",
},
- accessToken: ""
+ accessTokenObject: {
+ expiresAt: new Date(1970, 0, 1),
+ token: "",
+ }
},
setAuthState: <(state: any) => void>null,
webAuth: null,
From 7d8f932d6ab160dde88f73a484d4d5c2ec7d545e Mon Sep 17 00:00:00 2001
From: Brian Schroer
Date: Wed, 1 Nov 2023 16:00:04 -0700
Subject: [PATCH 04/25] 689 Create logout functionality. Create call to Auth0
that resets access token when it has expired
---
app/components/AppProvider.tsx | 59 +++++++++++++++------
app/components/ui/Navigation.tsx | 89 ++++++++++++++++++++------------
app/utils/AuthService.ts | 53 ++++++++++++-------
app/utils/SessionCacher.ts | 19 -------
app/utils/index.ts | 1 +
5 files changed, 133 insertions(+), 88 deletions(-)
diff --git a/app/components/AppProvider.tsx b/app/components/AppProvider.tsx
index 382f6c2bc..0f14516f2 100644
--- a/app/components/AppProvider.tsx
+++ b/app/components/AppProvider.tsx
@@ -1,9 +1,20 @@
import React, { useState, useMemo, useEffect } from "react";
-import auth0 from "auth0-js";
+import auth0, { Auth0Result } from "auth0-js";
-import { AppContext, GeoCoordinates } from "utils";
-import SessionCacher from "utils/SessionCacher";
-import type AuthObject from "utils/SessionCacher";
+import { AppContext, GeoCoordinates, SessionCacher, AuthService } from "utils";
+import AuthObject from "utils/SessionCacher";
+
+export const defaultAuthObject: AuthObject = {
+ isAuthenticated: false,
+ user: {
+ id: "",
+ email: "",
+ },
+ accessTokenObject: {
+ token: "",
+ expiresAt: new Date(1970, 0, 1),
+ },
+};
export const AppProvider = ({
children,
@@ -12,22 +23,10 @@ export const AppProvider = ({
children: React.ReactNode;
userLocation: GeoCoordinates | null;
}) => {
- const defaultAuthObject: AuthObject = {
- isAuthenticated: false,
- user: {
- id: "",
- email: "",
- },
- accessToken: {
- token: "",
- expiresAt: new Date(1970, 0, 1),
- },
- };
-
const authObject = SessionCacher.getAuthObject() ?? defaultAuthObject;
const [authState, setAuthState] = useState(authObject);
-
useEffect(() => {
+ // This ensures that the sessionStorage authObject is synced to the AppContext's authState
SessionCacher.setAuthObject(authState);
}, [authState]);
@@ -48,6 +47,32 @@ export const AppProvider = ({
};
}, [authState, userLocation]);
+ if (
+ authObject.isAuthenticated && authObject.accessTokenObject.expiresAt &&
+ AuthService.tokenExpired(new Date(authObject.accessTokenObject.expiresAt))
+ ) {
+ AuthService.refreshAuthToken(contextValue.webAuth)
+ .then((result: unknown) => {
+ const authResult = result as Auth0Result;
+ if (authResult.accessToken && typeof authResult.expiresIn !== "undefined") {
+ setAuthState({
+ ...authState,
+ accessTokenObject: {
+ token: authResult.accessToken,
+ expiresAt: AuthService.calculateExpirationTime(
+ authResult.expiresIn
+ ),
+ },
+ });
+ } else {
+ throw new Error("Unexpected result format");
+ }
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ }
+
return (
{children}
);
diff --git a/app/components/ui/Navigation.tsx b/app/components/ui/Navigation.tsx
index 68eb96991..1eebfbf63 100644
--- a/app/components/ui/Navigation.tsx
+++ b/app/components/ui/Navigation.tsx
@@ -2,6 +2,7 @@ import React, { FormEvent, useState } from "react";
import { Link, useHistory } from "react-router-dom";
import cn from "classnames";
import qs from "qs";
+import { useAppContext, AuthService } from "utils";
import Translate from "./Translate";
import whiteLabel from "../../utils/whitelabel";
import styles from "./Navigation.module.scss";
@@ -89,46 +90,66 @@ const SiteLogo = () =>
);
-const SiteLinks = () => (
-
-
- About
-
-
-
- FAQ
-
-
-
-
- Contact Us
-
-
- {showReportCrisis && (
+const SiteLinks = () => {
+ const context = useAppContext();
+ const { authState, webAuth, setAuthState } = context;
+
+ return (
+
+ {authState.isAuthenticated && (
+
+
+ AuthService.logout(
+ webAuth,
+ "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc",
+ setAuthState
+ )
+ }
+ >
+ Sign Out
+
+
+ )}
+
+ About
+
+
+
+ FAQ
+
+
- Report Street Crisis
+ Contact Us
- )}
-
-
-);
+ {showReportCrisis && (
+
+
+ Report Street Crisis
+
+
+ )}
+
+
+ );};
const SiteSearch = ({
query,
diff --git a/app/utils/AuthService.ts b/app/utils/AuthService.ts
index 7ce53acc4..c176329d4 100644
--- a/app/utils/AuthService.ts
+++ b/app/utils/AuthService.ts
@@ -1,19 +1,11 @@
-import type { WebAuth, Auth0Error } from "auth0-js";
-
-
-// We need to make the error prop on the Auth0Error interface optional and this
-// is the only way that I could figure out how to do so w/o making the TS
-// compiler complain
-type OptionalAuth0Error = {
- [K in keyof Auth0Error]?: Auth0Error[K] | undefined;
-};
-interface AuthError extends OptionalAuth0Error {}
+import type { WebAuth, Auth0Result } from "auth0-js";
+import { defaultAuthObject } from "components/AppProvider";
export default class AuthService {
- private static calculateExpirationTime(secondsUntilExpiration: number) {
+ static calculateExpirationTime(secondsUntilExpiration: number) {
const currentTime = new Date();
const expirationTime = new Date(currentTime.getTime() + secondsUntilExpiration * 1000);
-
+ console.log(expirationTime);
return expirationTime;
}
@@ -23,7 +15,6 @@ export default class AuthService {
// TODO: Handle errors
}
-
if (authResult?.accessToken) {
const { accessToken, expiresIn, idTokenPayload } = authResult;
const authObject = {
@@ -45,7 +36,7 @@ export default class AuthService {
static passwordlessStart = (
evt: React.SyntheticEvent,
- webAuth: any,
+ webAuth: WebAuth,
email: string,
callback?: () => void
) => {
@@ -56,7 +47,7 @@ export default class AuthService {
send: "code",
email,
},
- (err: AuthError) => {
+ (err) => {
if (err) {
// TODO: Handle errors
return;
@@ -69,19 +60,45 @@ export default class AuthService {
);
};
- static passwordlessVerify = (webAuth: any, email: string, verificationCode: string) => {
+ static passwordlessVerify = (webAuth: WebAuth, email: string, verificationCode: string) => {
webAuth.passwordlessLogin(
{
connection: "email",
email,
verificationCode,
},
- (err: AuthError) => {
+ (err) => {
if (err) {
// TODO: Handle errors
}
}
);
};
-}
+ static logout = (webAuth: WebAuth, clientId: string, setAuthState) => {
+ // Resets authState which in turn triggers an effect that clears sessionStorage
+ setAuthState(defaultAuthObject);
+
+ webAuth.logout({
+ returnTo: 'http://localhost:8080',
+ clientID: clientId
+ });
+ };
+
+ static tokenExpired = (tokenExpiration: Date) => {
+ return tokenExpiration && (new Date(tokenExpiration) < new Date());
+ }
+
+ static refreshAuthToken = (webAuth: WebAuth) => {
+ return new Promise((resolve, reject) => {
+ webAuth.checkSession({}, (err, authResult: Auth0Result) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(authResult);
+ }
+ })
+ });
+ }
+
+}
diff --git a/app/utils/SessionCacher.ts b/app/utils/SessionCacher.ts
index 8a3f23cb3..b277287f4 100644
--- a/app/utils/SessionCacher.ts
+++ b/app/utils/SessionCacher.ts
@@ -11,25 +11,6 @@ interface AuthObject {
}
export default class SessionCacher {
- // static getAccessTokenObject() {
- // const object = localStorage.getItem('accessTokenObject') || "";
- // return JSON.parse(object);
- // }
-
- // static setAccessTokenObject(accessTokenObject: AccessTokenObject) {
- // sessionStorage.setItem("accessTokenObject", JSON.stringify(accessTokenObject));
- // }
-
- // static getUserObject() {
- // const object = localStorage.getItem('userObject') || "";
- // return JSON.parse(object);
- // }
-
- // static setUserObject(userObject: UserObject) {
- // sessionStorage.setItem("userObject", JSON.stringify(userObject));
- // }
-
-
static getAuthObject(): AuthObject {
const object = sessionStorage.getItem('authObject');
return object ? JSON.parse(object) : null;
diff --git a/app/utils/index.ts b/app/utils/index.ts
index a8259a73c..5b12e9608 100644
--- a/app/utils/index.ts
+++ b/app/utils/index.ts
@@ -3,4 +3,5 @@ export * from "./numbers";
export * from "./time";
export * from "./useAppContext";
export { default as AuthService } from "./AuthService";
+export { default as SessionCacher } from "./SessionCacher";
export { default as whiteLabel } from "./whitelabel";
From 4e46dbe9158563658f723e8056e063efd186d475 Mon Sep 17 00:00:00 2001
From: Brian Schroer
Date: Tue, 7 Nov 2023 15:41:57 -0800
Subject: [PATCH 05/25] 689 add sign out functionality and update
sign-in/sign-up UI and verification modal
---
app/App.tsx | 9 +-
app/components/AppProvider.tsx | 29 ++++--
app/components/ui/Navigation.tsx | 22 ++---
app/pages/Auth/SignInPage.tsx | 49 ++++++----
app/pages/Auth/SignOutPage.tsx | 20 ++++
app/pages/Auth/SignUpPage.tsx | 132 ++++++++++-----------------
app/pages/Auth/VerificationModal.tsx | 50 +++++++++-
app/pages/HomePage/HomePage.tsx | 6 +-
app/utils/AuthService.ts | 76 ++++++++-------
app/utils/SessionCacher.ts | 18 +---
app/utils/useAppContext.ts | 4 +-
11 files changed, 230 insertions(+), 185 deletions(-)
create mode 100644 app/pages/Auth/SignOutPage.tsx
diff --git a/app/App.tsx b/app/App.tsx
index 881ff6f2f..12e90b923 100644
--- a/app/App.tsx
+++ b/app/App.tsx
@@ -19,9 +19,7 @@ import {
PopupMessageProp,
UserWay,
} from "./components/ui";
-import {
- AppProvider
-} from "./components/AppProvider";
+import { AppProvider } from "./components/AppProvider";
import config from "./config";
import MetaImage from "./assets/img/sfsg-preview.png";
@@ -48,6 +46,7 @@ import { ServiceDiscoveryForm } from "./pages/ServiceDiscoveryForm";
import { ServiceDiscoveryResults } from "./pages/ServiceDiscoveryResults";
import { SignInPage } from "./pages/Auth/SignInPage";
import { SignUpPage } from "./pages/Auth/SignUpPage";
+import { SignOutPage } from "./pages/Auth/SignOutPage";
import styles from "./App.module.scss";
@@ -148,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 */}
{
/>
+
{/* UCSF white label paths */}
{
path="/find-services/:selectedResourceSlug"
component={UcsfDiscoveryForm}
/>
-
{/* Legacy redirects */}
{
path="/resource"
component={RedirectToOrganizations}
/>
-
diff --git a/app/components/AppProvider.tsx b/app/components/AppProvider.tsx
index 0f14516f2..732fe0657 100644
--- a/app/components/AppProvider.tsx
+++ b/app/components/AppProvider.tsx
@@ -1,10 +1,21 @@
import React, { useState, useMemo, useEffect } from "react";
import auth0, { Auth0Result } from "auth0-js";
-
+import * as Sentry from "@sentry/browser";
import { AppContext, GeoCoordinates, SessionCacher, AuthService } from "utils";
-import AuthObject from "utils/SessionCacher";
-export const defaultAuthObject: AuthObject = {
+export interface AuthState {
+ isAuthenticated: boolean;
+ user: {
+ id: string;
+ email: string;
+ };
+ accessTokenObject: {
+ token: string;
+ expiresAt: Date;
+ };
+}
+
+export const defaultAuthObject: AuthState = {
isAuthenticated: false,
user: {
id: "",
@@ -48,13 +59,17 @@ export const AppProvider = ({
}, [authState, userLocation]);
if (
- authObject.isAuthenticated && authObject.accessTokenObject.expiresAt &&
+ authObject.isAuthenticated &&
+ authObject.accessTokenObject.expiresAt &&
AuthService.tokenExpired(new Date(authObject.accessTokenObject.expiresAt))
) {
AuthService.refreshAuthToken(contextValue.webAuth)
.then((result: unknown) => {
const authResult = result as Auth0Result;
- if (authResult.accessToken && typeof authResult.expiresIn !== "undefined") {
+ if (
+ authResult.accessToken &&
+ typeof authResult.expiresIn !== "undefined"
+ ) {
setAuthState({
...authState,
accessTokenObject: {
@@ -65,11 +80,11 @@ export const AppProvider = ({
},
});
} else {
- throw new Error("Unexpected result format");
+ throw new Error("Token does not exist or is in unexpected token");
}
})
.catch((err) => {
- console.log(err);
+ Sentry.captureException(err);
});
}
diff --git a/app/components/ui/Navigation.tsx b/app/components/ui/Navigation.tsx
index 1eebfbf63..e20d0597c 100644
--- a/app/components/ui/Navigation.tsx
+++ b/app/components/ui/Navigation.tsx
@@ -2,9 +2,9 @@ import React, { FormEvent, useState } from "react";
import { Link, useHistory } from "react-router-dom";
import cn from "classnames";
import qs from "qs";
-import { useAppContext, AuthService } from "utils";
+import { WebAuth } from "auth0-js";
+import { useAppContext, AuthService, whiteLabel } from "utils";
import Translate from "./Translate";
-import whiteLabel from "../../utils/whitelabel";
import styles from "./Navigation.module.scss";
const {
@@ -92,24 +92,13 @@ const SiteLogo = () =>
const SiteLinks = () => {
const context = useAppContext();
- const { authState, webAuth, setAuthState } = context;
+ const { authState } = context;
return (
{authState.isAuthenticated && (
-
- AuthService.logout(
- webAuth,
- "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc",
- setAuthState
- )
- }
- >
- Sign Out
-
+ Sign Out
)}
@@ -149,7 +138,8 @@ const SiteLinks = () => {
)}
- );};
+ );
+};
const SiteSearch = ({
query,
diff --git a/app/pages/Auth/SignInPage.tsx b/app/pages/Auth/SignInPage.tsx
index 240ae0446..7e750712c 100644
--- a/app/pages/Auth/SignInPage.tsx
+++ b/app/pages/Auth/SignInPage.tsx
@@ -1,5 +1,6 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
+import { WebAuth } from "auth0-js";
import { Button } from "components/ui/inline/Button/Button";
import { useAppContext, AuthService } from "utils";
@@ -10,39 +11,49 @@ import styles from "./Auth.module.scss";
export const SignInPage = () => {
const [modalIsOpen, setModalIsOpen] = useState(false);
const [email, setEmail] = useState("");
- const { webAuth } = useAppContext();
+ const webAuth = useAppContext().webAuth as WebAuth;
const { passwordlessStart, passwordlessVerify } = AuthService;
+
const signIn = (evt: React.SyntheticEvent) => {
evt.preventDefault();
- passwordlessStart(evt, webAuth, email, () => setModalIsOpen(true));
+ passwordlessStart(webAuth, email).then(() => {
+ setModalIsOpen(true);
+ });
};
return (
For Case Managers
New here? Sign up!
-
passwordlessVerify(webAuth, email, code)}
- modalIsOpen={modalIsOpen}
- setModalIsOpen={setModalIsOpen}
- />
+
+ 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 5-number
+ verification code.
+
+
+ passwordlessVerify(webAuth, email, code)}
+ resendCode={() => passwordlessStart(webAuth, email)}
+ buttonText="Log in"
+ />
);
};
diff --git a/app/pages/Auth/SignOutPage.tsx b/app/pages/Auth/SignOutPage.tsx
new file mode 100644
index 000000000..4e0a4c7cf
--- /dev/null
+++ b/app/pages/Auth/SignOutPage.tsx
@@ -0,0 +1,20 @@
+import React, { useEffect } from "react";
+import { Redirect } from "react-router-dom";
+import { WebAuth } from "auth0-js";
+import { useAppContext, AuthService } from "../../utils";
+
+export const SignOutPage = () => {
+ const context = useAppContext();
+ const { setAuthState } = context;
+ const webAuth = context.webAuth as WebAuth;
+
+ useEffect(() => {
+ AuthService.logout(
+ webAuth,
+ "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc",
+ setAuthState
+ );
+ });
+
+ return ;
+};
diff --git a/app/pages/Auth/SignUpPage.tsx b/app/pages/Auth/SignUpPage.tsx
index 47626ff89..f06813594 100644
--- a/app/pages/Auth/SignUpPage.tsx
+++ b/app/pages/Auth/SignUpPage.tsx
@@ -1,7 +1,8 @@
import React, { useState } from "react";
-import auth0 from "auth0-js";
+import { WebAuth } from "auth0-js";
import { Link } from "react-router-dom";
import { Button } from "components/ui/inline/Button/Button";
+import { useAppContext, AuthService } from "utils";
import { VerificationModal } from "./VerificationModal";
@@ -12,100 +13,65 @@ export const SignUpPage = () => {
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const [organization, setOrganization] = useState("");
+ const webAuth = useAppContext().webAuth as WebAuth;
+ const { passwordlessStart, passwordlessVerify } = AuthService;
- const webAuth = new auth0.WebAuth({
- clientID: "UcnuRrX6S0SeDEhW9PRe01wEhcvIRuwc",
- domain: "dev-nykixf8szsm220fi.us.auth0.com",
- redirectUri: "http://localhost:8080",
- responseType: "token id_token",
- });
-
- const startPasswordlessAuth = (evt: React.SyntheticEvent) => {
+ const signUp = (evt: React.SyntheticEvent) => {
evt.preventDefault();
- webAuth.passwordlessStart(
- {
- connection: "email",
- send: "code",
- email,
- },
- (err) => {
- if (err) {
- console.log(err);
- return;
- }
-
- setModalIsOpen(true);
- }
- );
- };
-
- const verifyCode = (verificationCode: string) => {
- webAuth.passwordlessLogin(
- {
- connection: "email",
- email,
- verificationCode,
- },
- (err) => {
- if (err) {
- console.log(err);
- }
- }
- );
+ /* Todo: We will need to:
+ a) save the user's name/org to our database
+ b) check if the user email already exists and display an error message if so
+ */
+ passwordlessStart(webAuth, email).then(() => {
+ setModalIsOpen(true);
+ });
};
return (
For Case Managers
Already have an account? Log in!
-
-
+
+ passwordlessVerify(webAuth, email, code)}
+ resendCode={() => passwordlessStart(webAuth, email)}
+ buttonText="Sign up"
+ />
);
};
diff --git a/app/pages/Auth/VerificationModal.tsx b/app/pages/Auth/VerificationModal.tsx
index 1d7036b47..50e7c74f0 100644
--- a/app/pages/Auth/VerificationModal.tsx
+++ b/app/pages/Auth/VerificationModal.tsx
@@ -1,17 +1,23 @@
-import React, { useState, createRef } from "react";
+import React, { useState, createRef, useEffect } 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(
@@ -66,6 +72,7 @@ export const VerificationModal = ({
>
Please check your email
+
We've sent a code to {email}
{verificationCode.map((digit, index) => (
))}
-
Verify Account
+
+
{buttonText}
);
};
+
+const ResendCode = ({ resendCode }: { resendCode: () => Promise }) => {
+ const [timeLeft, setTimeLeft] = useState(60);
+ const [codeResent, setCodeResent] = useState(false);
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ if (timeLeft === 0) {
+ clearInterval(interval);
+ resendCode();
+ setCodeResent(true);
+ return;
+ }
+
+ setTimeLeft(timeLeft - 1);
+ }, 1000);
+
+ return () => clearInterval(interval);
+ }, [resendCode, setTimeLeft, timeLeft]);
+
+ return (
+
+ {codeResent ? (
+
+ Didn't receive a code?
+ {resendCode()}}>
+ Try again
+
+
+ ) : (
+ <>
+
Resend code in:
+ {timeLeft}
+ >
+ )}
+
+ );
+};
diff --git a/app/pages/HomePage/HomePage.tsx b/app/pages/HomePage/HomePage.tsx
index 84ffac7fb..c9b84de80 100644
--- a/app/pages/HomePage/HomePage.tsx
+++ b/app/pages/HomePage/HomePage.tsx
@@ -88,7 +88,11 @@ export const HomePage = () => {
useEffect(() => {
if (!window.location.hash) return;
- AuthService.persistUser(window.location.hash, webAuth as WebAuth, setAuthState);
+ AuthService.persistUser(
+ window.location.hash,
+ webAuth as WebAuth,
+ setAuthState
+ );
history.replace(window.location.pathname + window.location.search);
}, [history, setAuthState, webAuth]);
diff --git a/app/utils/AuthService.ts b/app/utils/AuthService.ts
index c176329d4..9dc7647d8 100644
--- a/app/utils/AuthService.ts
+++ b/app/utils/AuthService.ts
@@ -1,11 +1,14 @@
import type { WebAuth, Auth0Result } from "auth0-js";
import { defaultAuthObject } from "components/AppProvider";
+import type { AuthState } from "components/AppProvider";
export default class AuthService {
static calculateExpirationTime(secondsUntilExpiration: number) {
const currentTime = new Date();
- const expirationTime = new Date(currentTime.getTime() + secondsUntilExpiration * 1000);
- console.log(expirationTime);
+ const expirationTime = new Date(
+ currentTime.getTime() + secondsUntilExpiration * 1000
+ );
+
return expirationTime;
}
@@ -25,8 +28,10 @@ export default class AuthService {
},
accessTokenObject: {
token: accessToken,
- expiresAt: expiresIn ? this.calculateExpirationTime(expiresIn) : null,
- }
+ expiresAt: expiresIn
+ ? this.calculateExpirationTime(expiresIn)
+ : null,
+ },
};
setAuthState(authObject);
@@ -34,33 +39,31 @@ export default class AuthService {
});
}
- static passwordlessStart = (
- evt: React.SyntheticEvent,
- webAuth: WebAuth,
- email: string,
- callback?: () => void
- ) => {
- evt.preventDefault();
- webAuth.passwordlessStart(
- {
- connection: "email",
- send: "code",
- email,
- },
- (err) => {
- if (err) {
- // TODO: Handle errors
- return;
- }
+ static passwordlessStart = (webAuth: WebAuth, email: string) => {
+ return new Promise((resolve, reject) => {
+ webAuth.passwordlessStart(
+ {
+ connection: "email",
+ send: "code",
+ email,
+ },
+ (err) => {
+ if (err) {
+ reject(err);
+ return;
+ }
- if (callback) {
- callback();
+ resolve(true);
}
- }
- );
+ );
+ });
};
- static passwordlessVerify = (webAuth: WebAuth, email: string, verificationCode: string) => {
+ static passwordlessVerify = (
+ webAuth: WebAuth,
+ email: string,
+ verificationCode: string
+ ) => {
webAuth.passwordlessLogin(
{
connection: "email",
@@ -75,19 +78,23 @@ export default class AuthService {
);
};
- static logout = (webAuth: WebAuth, clientId: string, setAuthState) => {
+ static logout = (
+ webAuth: WebAuth,
+ clientId: string,
+ setAuthState: (state: AuthState) => void
+ ) => {
// Resets authState which in turn triggers an effect that clears sessionStorage
setAuthState(defaultAuthObject);
webAuth.logout({
- returnTo: 'http://localhost:8080',
- clientID: clientId
+ returnTo: "http://localhost:8080",
+ clientID: clientId,
});
};
static tokenExpired = (tokenExpiration: Date) => {
- return tokenExpiration && (new Date(tokenExpiration) < new Date());
- }
+ return tokenExpiration && new Date(tokenExpiration) < new Date();
+ };
static refreshAuthToken = (webAuth: WebAuth) => {
return new Promise((resolve, reject) => {
@@ -97,8 +104,7 @@ export default class AuthService {
} else {
resolve(authResult);
}
- })
+ });
});
- }
-
+ };
}
diff --git a/app/utils/SessionCacher.ts b/app/utils/SessionCacher.ts
index b277287f4..c89ab17d9 100644
--- a/app/utils/SessionCacher.ts
+++ b/app/utils/SessionCacher.ts
@@ -1,22 +1,12 @@
-interface AuthObject {
- isAuthenticated: boolean;
- user: {
- id: string;
- email: string;
- };
- accessTokenObject: {
- token: string;
- expiresAt: Date;
- };
-}
+import type { AuthState } from "components/AppProvider";
export default class SessionCacher {
- static getAuthObject(): AuthObject {
- const object = sessionStorage.getItem('authObject');
+ static getAuthObject(): AuthState {
+ const object = sessionStorage.getItem("authObject");
return object ? JSON.parse(object) : null;
}
- static setAuthObject(authObject: AuthObject) {
+ static setAuthObject(authObject: AuthState) {
sessionStorage.setItem("authObject", JSON.stringify(authObject));
}
diff --git a/app/utils/useAppContext.ts b/app/utils/useAppContext.ts
index 00169eb10..fd827a584 100644
--- a/app/utils/useAppContext.ts
+++ b/app/utils/useAppContext.ts
@@ -13,9 +13,9 @@ export const AppContext = createContext({
accessTokenObject: {
expiresAt: new Date(1970, 0, 1),
token: "",
- }
+ },
},
- setAuthState: <(state: any) => void>null,
+ setAuthState: <(state: any) => void>(null),
webAuth: null,
});
From 7bf9444edabb2a6e2bf555953af5d07603c306df Mon Sep 17 00:00:00 2001
From: Brian Schroer
Date: Thu, 7 Dec 2023 16:01:16 -0800
Subject: [PATCH 06/25] Complete basic user sign up flow and check if user
exists prior to signing user up
---
app/pages/Auth/SignInPage.tsx | 2 +-
app/pages/Auth/SignUpPage.tsx | 14 +++++++-------
app/pages/Auth/VerificationModal.tsx | 2 +-
app/utils/AuthService.ts | 28 ++++++++++++++++++++++++++++
4 files changed, 37 insertions(+), 9 deletions(-)
diff --git a/app/pages/Auth/SignInPage.tsx b/app/pages/Auth/SignInPage.tsx
index 7e750712c..658011b19 100644
--- a/app/pages/Auth/SignInPage.tsx
+++ b/app/pages/Auth/SignInPage.tsx
@@ -28,7 +28,7 @@ export const SignInPage = () => {
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 5-number
+ enter in your email address and then check your email to find a 6 digit
verification code.
-