Use compute auth service and fix static code analyzer warnings (#15)

* Clean up formatting

* Use new compute_auth service
Implment SSO
Implement token refresh
Clean up unit tests

* Fix unit tests

* Fix auth test
Fix warnings

* Update default settings for compute_auth
This commit is contained in:
John Wu
2021-03-04 14:30:56 -08:00
committed by GitHub
parent e1f0006d5e
commit 39e779dc1d
34 changed files with 703 additions and 1462 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}

View File

@@ -1,6 +1,6 @@
import { Typography } from "@material-ui/core"; import { Typography } from "@material-ui/core";
import React from "react"; import React from "react";
import useStyles from '../Styles'; import useStyles from "../useStyles";
const PageNotFound = () => { const PageNotFound = () => {
const classes = useStyles(); const classes = useStyles();
@@ -12,6 +12,6 @@ const PageNotFound = () => {
</Typography> </Typography>
</div> </div>
); );
} };
export default PageNotFound; export default PageNotFound;

View File

@@ -1,7 +1,7 @@
jest.mock("../Contexts/UserContext"); jest.mock("../Contexts/UserContext");
jest.mock("../Contexts/FileUploadContext"); jest.mock("../Contexts/FileUploadContext");
import { render, screen, cleanup, waitForElementToBeRemoved, waitFor } from "@testing-library/react" import { render, screen, cleanup, waitForElementToBeRemoved } from "@testing-library/react";
import { setToken } from "../Contexts/UserContext"; import { setToken } from "../Contexts/UserContext";
import App from "."; import App from ".";
@@ -26,19 +26,13 @@ describe("App", () => {
it("Route / unauthenticated", async () => { it("Route / unauthenticated", async () => {
const container = await renderRoute("/"); const container = await renderRoute("/");
expect(container.querySelector("h1").innerHTML).toEqual("Sign in"); expect(container.querySelector("h1").innerHTML).toEqual("Fisker OTA Portal");
expect(container).toMatchSnapshot();
});
it("Route /signup unauthenticated", async () => {
const container = await renderRoute("/signup");
expect(container.querySelector("h1").innerHTML).toEqual("Sign up");
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
it("Route /home unauthenticated", async () => { it("Route /home unauthenticated", async () => {
const container = await renderRoute("/home"); const container = await renderRoute("/home");
expect(container.querySelector("h1").innerHTML).toEqual("Sign in"); expect(container.querySelector("h1").innerHTML).toEqual("Fisker OTA Portal");
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
@@ -49,13 +43,6 @@ describe("App", () => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
it("Route /signup authenticated", async () => {
setToken(TEST_TOKEN);
const container = await renderRoute("/signup");
expect(container.querySelector("h1").innerHTML).toEqual("Upload file");
expect(container).toMatchSnapshot();
});
it("Route /home authenticated", async () => { it("Route /home authenticated", async () => {
setToken(TEST_TOKEN); setToken(TEST_TOKEN);
const container = await renderRoute("/home"); const container = await renderRoute("/home");

View File

@@ -9,7 +9,7 @@ exports[`App Route / authenticated 1`] = `
class="MuiContainer-root MuiContainer-maxWidthXs" class="MuiContainer-root MuiContainer-maxWidthXs"
> >
<div <div
class="makeStyles-paper-25" class="makeStyles-paper-9"
> >
<h1 <h1
class="MuiTypography-root MuiTypography-h5" class="MuiTypography-root MuiTypography-h5"
@@ -20,7 +20,7 @@ exports[`App Route / authenticated 1`] = `
data-testid="mocked-fileuploadprovider" data-testid="mocked-fileuploadprovider"
> >
<form <form
class="makeStyles-form-27" class="makeStyles-form-11"
novalidate="" novalidate=""
> >
<div <div
@@ -98,109 +98,18 @@ exports[`App Route / unauthenticated 1`] = `
<h1 <h1
class="MuiTypography-root MuiTypography-h5" class="MuiTypography-root MuiTypography-h5"
> >
Sign in Fisker OTA Portal
</h1> </h1>
<form <form
action="{onSubmit}" action="{onSubmit}"
class="makeStyles-form-3" class="makeStyles-form-3"
novalidate="" novalidate=""
> >
<div <a
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth" aria-disabled="false"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
data-shrink="true"
for="email"
id="email-label"
>
Email Address
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
>
<input
aria-invalid="false"
autocomplete="email"
class="MuiInputBase-input MuiOutlinedInput-input"
id="email"
name="email"
required=""
type="text"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-7 PrivateNotchedOutline-legendNotched-8"
>
<span>
Email Address
 *
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
data-shrink="false"
for="password"
id="password-label"
>
Password
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
>
<input
aria-invalid="false"
autocomplete="current-password"
class="MuiInputBase-input MuiOutlinedInput-input"
id="password"
name="password"
required=""
type="password"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-7"
>
<span>
Password
 *
</span>
</legend>
</fieldset>
</div>
</div>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth" class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
href="https://cognito.com/authorize?redirect=https://example.com/callback"
tabindex="0" tabindex="0"
type="submit"
> >
<span <span
class="MuiButton-label" class="MuiButton-label"
@@ -210,21 +119,7 @@ exports[`App Route / unauthenticated 1`] = `
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
/> />
</button>
<div
class="MuiGrid-root MuiGrid-container"
>
<div
class="MuiGrid-root MuiGrid-item"
>
<a
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
href="/signup"
>
Don't have an account? Sign Up
</a> </a>
</div>
</div>
</form> </form>
</div> </div>
</main> </main>
@@ -241,7 +136,7 @@ exports[`App Route /home authenticated 1`] = `
class="MuiContainer-root MuiContainer-maxWidthXs" class="MuiContainer-root MuiContainer-maxWidthXs"
> >
<div <div
class="makeStyles-paper-33" class="makeStyles-paper-13"
> >
<h1 <h1
class="MuiTypography-root MuiTypography-h5" class="MuiTypography-root MuiTypography-h5"
@@ -252,7 +147,7 @@ exports[`App Route /home authenticated 1`] = `
data-testid="mocked-fileuploadprovider" data-testid="mocked-fileuploadprovider"
> >
<form <form
class="makeStyles-form-35" class="makeStyles-form-15"
novalidate="" novalidate=""
> >
<div <div
@@ -325,114 +220,23 @@ exports[`App Route /home unauthenticated 1`] = `
class="MuiContainer-root MuiContainer-maxWidthXs" class="MuiContainer-root MuiContainer-maxWidthXs"
> >
<div <div
class="makeStyles-paper-17" class="makeStyles-paper-5"
> >
<h1 <h1
class="MuiTypography-root MuiTypography-h5" class="MuiTypography-root MuiTypography-h5"
> >
Sign in Fisker OTA Portal
</h1> </h1>
<form <form
action="{onSubmit}" action="{onSubmit}"
class="makeStyles-form-19" class="makeStyles-form-7"
novalidate="" novalidate=""
> >
<div <a
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth" aria-disabled="false"
> class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-8 MuiButton-containedPrimary MuiButton-fullWidth"
<label href="https://cognito.com/authorize?redirect=https://example.com/callback"
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
data-shrink="true"
for="email"
id="email-label"
>
Email Address
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
>
<input
aria-invalid="false"
autocomplete="email"
class="MuiInputBase-input MuiOutlinedInput-input"
id="email"
name="email"
required=""
type="text"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-21 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-23 PrivateNotchedOutline-legendNotched-24"
>
<span>
Email Address
 *
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
data-shrink="false"
for="password"
id="password-label"
>
Password
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
>
<input
aria-invalid="false"
autocomplete="current-password"
class="MuiInputBase-input MuiOutlinedInput-input"
id="password"
name="password"
required=""
type="password"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-21 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-23"
>
<span>
Password
 *
</span>
</legend>
</fieldset>
</div>
</div>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-20 MuiButton-containedPrimary MuiButton-fullWidth"
tabindex="0" tabindex="0"
type="submit"
> >
<span <span
class="MuiButton-label" class="MuiButton-label"
@@ -442,21 +246,7 @@ exports[`App Route /home unauthenticated 1`] = `
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
/> />
</button>
<div
class="MuiGrid-root MuiGrid-container"
>
<div
class="MuiGrid-root MuiGrid-item"
>
<a
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
href="/signup"
>
Don't have an account? Sign Up
</a> </a>
</div>
</div>
</form> </form>
</div> </div>
</main> </main>
@@ -470,7 +260,7 @@ exports[`App Route /page-not-found authenticated 1`] = `
data-testid="mocked-userprovider" data-testid="mocked-userprovider"
> >
<div <div
class="makeStyles-paper-41" class="makeStyles-paper-21"
> >
<h1 <h1
class="MuiTypography-root MuiTypography-h2" class="MuiTypography-root MuiTypography-h2"
@@ -488,7 +278,7 @@ exports[`App Route /page-not-found unauthenticated 1`] = `
data-testid="mocked-userprovider" data-testid="mocked-userprovider"
> >
<div <div
class="makeStyles-paper-37" class="makeStyles-paper-17"
> >
<h1 <h1
class="MuiTypography-root MuiTypography-h2" class="MuiTypography-root MuiTypography-h2"
@@ -499,279 +289,3 @@ exports[`App Route /page-not-found unauthenticated 1`] = `
</div> </div>
</div> </div>
`; `;
exports[`App Route /signup authenticated 1`] = `
<div>
<div
data-testid="mocked-userprovider"
>
<main
class="MuiContainer-root MuiContainer-maxWidthXs"
>
<div
class="makeStyles-paper-29"
>
<h1
class="MuiTypography-root MuiTypography-h5"
>
Upload file
</h1>
<div
data-testid="mocked-fileuploadprovider"
>
<form
class="makeStyles-form-31"
novalidate=""
>
<div
class="MuiDropzoneArea-root"
tabindex="0"
>
<input
accept=""
autocomplete="off"
style="display: none;"
tabindex="-1"
type="file"
/>
<div
class="MuiDropzoneArea-textContainer"
>
<p
class="MuiTypography-root MuiDropzoneArea-text MuiTypography-h5"
>
Drag and drop a file here or click
</p>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiDropzoneArea-icon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"
/>
</svg>
</div>
</div>
</form>
</div>
<div
class="MuiGrid-root MuiGrid-container"
>
<div
class="MuiGrid-root MuiGrid-item"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-text"
tabindex="0"
type="button"
>
<span
class="MuiButton-label"
>
Sign Out
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
</main>
</div>
</div>
`;
exports[`App Route /signup unauthenticated 1`] = `
<div>
<div
data-testid="mocked-userprovider"
>
<main
class="MuiContainer-root MuiContainer-maxWidthXs"
>
<div
class="makeStyles-paper-9"
>
<h1
class="MuiTypography-root MuiTypography-h5"
>
Sign up
</h1>
<form
class="makeStyles-form-11"
novalidate=""
>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
data-shrink="true"
for="email"
id="email-label"
>
Email Address
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
>
<input
aria-invalid="false"
autocomplete="email"
class="MuiInputBase-input MuiOutlinedInput-input"
id="email"
name="email"
required=""
type="text"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-13 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-15 PrivateNotchedOutline-legendNotched-16"
>
<span>
Email Address
 *
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
data-shrink="false"
for="password"
id="password-label"
>
Password
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
>
<input
aria-invalid="false"
autocomplete="new-password"
class="MuiInputBase-input MuiOutlinedInput-input"
id="password"
name="password"
required=""
type="password"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-13 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-15"
>
<span>
Password
 *
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
data-shrink="false"
for="passwordConfirm"
id="passwordConfirm-label"
>
Confirm Password
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
>
<input
aria-invalid="false"
class="MuiInputBase-input MuiOutlinedInput-input"
id="passwordConfirm"
name="password"
required=""
type="password"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-13 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-15"
>
<span>
Confirm Password
 *
</span>
</legend>
</fieldset>
</div>
</div>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-12 MuiButton-containedPrimary MuiButton-fullWidth"
tabindex="0"
type="submit"
>
<span
class="MuiButton-label"
>
Sign Up
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<div
class="MuiGrid-root MuiGrid-container"
>
<div
class="MuiGrid-root MuiGrid-item"
>
<a
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
href="/"
>
Already have an account? Sign In
</a>
</div>
</div>
</form>
</div>
</main>
</div>
</div>
`;

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from "react";
import { UserProvider } from '../Contexts/UserContext'; import { UserProvider } from "../Contexts/UserContext";
import SiteRoutes from '../Routes/SiteRoutes'; import SiteRoutes from "../Routes/SiteRoutes";
function App() { function App() {
return ( return (

View File

@@ -26,8 +26,12 @@ export const FileUploadProvider = ({ children }) => {
const upload = async (files, accessToken) => { const upload = async (files, accessToken) => {
try { try {
if (!files || files.length === 0) throw new Error("File required"); if (!files || files.length === 0) {
if (!accessToken || accessToken.length === 0) throw new Error("Access token required") throw new Error("File required");
}
if (!accessToken || accessToken.length === 0) {
throw new Error("Access token required");
}
const file = files[0].file; const file = files[0].file;
const filename = file.name; const filename = file.name;
@@ -37,15 +41,21 @@ export const FileUploadProvider = ({ children }) => {
setStatus(`Uploading ${filename}`); setStatus(`Uploading ${filename}`);
setCancelUpload(getCancelToken()); setCancelUpload(getCancelToken());
const { data } = await uploadFile(file, accessToken, setProgress, cancelUpload); const { data } = await uploadFile(
if (data.message) throw new Error(`${data.error}. ${data.message}`); file,
const url = ((data && data.link) ? data.link : "No URL available"); accessToken,
setProgress,
cancelUpload
);
if (data.message) {
throw new Error(`${data.error}. ${data.message}`);
}
const url = data && data.link ? data.link : "No URL available";
setLinkURL(url); setLinkURL(url);
setStatus(`Uploaded ${filename}`); setStatus(`Uploaded ${filename}`);
setCancelUpload(null); setCancelUpload(null);
setProgress(100); setProgress(100);
} } catch (e) {
catch (e) {
setStatus(`Error occured: ${e.message}`); setStatus(`Error occured: ${e.message}`);
setProgress(-1); setProgress(-1);
} }
@@ -59,7 +69,8 @@ export const FileUploadProvider = ({ children }) => {
}; };
return ( return (
<FileUploadContext.Provider value={{ <FileUploadContext.Provider
value={{
uploading, uploading,
progress, progress,
status, status,
@@ -67,7 +78,8 @@ export const FileUploadProvider = ({ children }) => {
upload, upload,
cancel, cancel,
rejectedFile, rejectedFile,
}}> }}
>
{children} {children}
</FileUploadContext.Provider> </FileUploadContext.Provider>
); );

View File

@@ -1,14 +1,36 @@
jest.mock("../../services/uploadFile"); jest.mock("../../services/uploadFile");
import { setUploadFileDelay } from "../../services/uploadFile" import { setUploadFileDelay } from "../../services/uploadFile";
import { FileUploadProvider, useFileUploadContext } from "../Contexts/FileUploadContext"; import {
import {render, cleanup, screen, fireEvent, waitFor} from "@testing-library/react" FileUploadProvider,
useFileUploadContext,
} from "../Contexts/FileUploadContext";
import {
render,
cleanup,
screen,
fireEvent,
waitFor,
} from "@testing-library/react";
const checkState = (uploading, progress, status, linkURL) => {
expect(screen.getByTestId("uploading").innerHTML).toEqual(uploading);
expect(screen.getByTestId("progress").innerHTML).toEqual(progress);
expect(screen.getByTestId("status").innerHTML).toEqual(status);
expect(screen.getByTestId("linkURL").innerHTML).toEqual(linkURL);
};
describe("FileUploadContext", () => { describe("FileUploadContext", () => {
beforeEach(() => { beforeEach(() => {
const TestComp = () => { const TestComp = () => {
const { progress, uploading, status, linkURL, upload, cancel } = useFileUploadContext(); const {
progress,
uploading,
status,
linkURL,
upload,
cancel,
} = useFileUploadContext();
const TEST_FILE = [{ file: { name: "test.jpg", size: 0 } }]; const TEST_FILE = [{ file: { name: "test.jpg", size: 0 } }];
const TEST_ACCESSTOKEN = "ACCESSTOKEN"; const TEST_ACCESSTOKEN = "ACCESSTOKEN";
return ( return (
@@ -18,13 +40,23 @@ describe("FileUploadContext", () => {
<div data-testid="status">{status}</div> <div data-testid="status">{status}</div>
<div data-testid="linkURL">{linkURL}</div> <div data-testid="linkURL">{linkURL}</div>
<button data-testid="uploadNoFile" onClick={() => upload()} /> <button data-testid="uploadNoFile" onClick={() => upload()} />
<button data-testid="uploadNoToken" onClick={() => upload(TEST_FILE)}/> <button
<button data-testid="upload" onClick={() => upload(TEST_FILE, TEST_ACCESSTOKEN)}/> data-testid="uploadNoToken"
onClick={() => upload(TEST_FILE)}
/>
<button
data-testid="upload"
onClick={() => upload(TEST_FILE, TEST_ACCESSTOKEN)}
/>
<button data-testid="cancel" onClick={() => cancel()} /> <button data-testid="cancel" onClick={() => cancel()} />
</> </>
); );
}; };
render(<FileUploadProvider><TestComp /></FileUploadProvider>); render(
<FileUploadProvider>
<TestComp />
</FileUploadProvider>
);
}); });
afterEach(() => { afterEach(() => {
@@ -32,46 +64,39 @@ describe("FileUploadContext", () => {
}); });
it("Initial state", async () => { it("Initial state", async () => {
expect(screen.getByTestId("uploading").innerHTML).toEqual("false"); checkState("false", "0", "", "");
expect(screen.getByTestId("progress").innerHTML).toEqual("0"); });
expect(screen.getByTestId("status").innerHTML).toEqual("");
expect(screen.getByTestId("linkURL").innerHTML).toEqual("");
})
it("Upload no file", async () => { it("Upload no file", async () => {
fireEvent.click(screen.getByTestId("uploadNoFile")); fireEvent.click(screen.getByTestId("uploadNoFile"));
expect(screen.getByTestId("uploading").innerHTML).toEqual("false"); checkState("false", "-1", "Error occured: File required", "");
expect(screen.getByTestId("progress").innerHTML).toEqual("-1"); });
expect(screen.getByTestId("status").innerHTML).toEqual("Error occured: File required");
expect(screen.getByTestId("linkURL").innerHTML).toEqual("");
})
it("Upload no access token", async () => { it("Upload no access token", async () => {
fireEvent.click(screen.getByTestId("uploadNoToken")); fireEvent.click(screen.getByTestId("uploadNoToken"));
expect(screen.getByTestId("uploading").innerHTML).toEqual("false"); checkState("false", "-1", "Error occured: Access token required", "");
expect(screen.getByTestId("progress").innerHTML).toEqual("-1"); });
expect(screen.getByTestId("status").innerHTML).toEqual("Error occured: Access token required");
expect(screen.getByTestId("linkURL").innerHTML).toEqual("");
})
it("Upload file", async () => { it("Upload file", async () => {
fireEvent.click(screen.getByTestId("upload")); fireEvent.click(screen.getByTestId("upload"));
await waitFor(() => expect(screen.getByTestId("progress").innerHTML).toEqual("100")); await waitFor(() =>
expect(screen.getByTestId("uploading").innerHTML).toEqual("true"); expect(screen.getByTestId("progress").innerHTML).toEqual("100")
expect(screen.getByTestId("status").innerHTML).toEqual(`Uploaded test.jpg`); );
expect(screen.getByTestId("linkURL").innerHTML).toEqual(`CLOUDFRONT_URL`); checkState("true", "100", "Uploaded test.jpg", "CLOUDFRONT_URL");
}) });
it("Cancel upload", async () => { it("Cancel upload", async () => {
setUploadFileDelay(true); setUploadFileDelay(true);
fireEvent.click(screen.getByTestId("upload")); fireEvent.click(screen.getByTestId("upload"));
await waitFor(() => expect(screen.getByTestId("progress").innerHTML).toEqual("50")); await waitFor(() =>
expect(screen.getByTestId("uploading").innerHTML).toEqual("true"); expect(screen.getByTestId("progress").innerHTML).toEqual("50")
expect(screen.getByTestId("status").innerHTML).toEqual("Uploading test.jpg"); );
checkState("true", "50", "Uploading test.jpg", "");
fireEvent.click(screen.getByTestId("cancel")); fireEvent.click(screen.getByTestId("cancel"));
await waitFor(() => expect(screen.getByTestId("progress").innerHTML).toEqual("0")); await waitFor(() =>
expect(screen.getByTestId("uploading").innerHTML).toEqual("false"); expect(screen.getByTestId("progress").innerHTML).toEqual("0")
expect(screen.getByTestId("status").innerHTML).toEqual("Upload cancelled"); );
expect(screen.getByTestId("linkURL").innerHTML).toEqual(""); checkState("false", "0", "Upload cancelled", "");
}) });
}) });

View File

@@ -20,95 +20,83 @@ export const UserProvider = ({ children }) => {
useEffect(() => { useEffect(() => {
if (!token) return; if (!token) return;
const { idToken: { jwtToken }} = token; const {
idToken: { jwtToken },
} = token;
verifyToken(jwtToken); verifyToken(jwtToken);
return () => { return () => {
if (timer) timer.terminate(); if (timer) timer.terminate();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [token]) }, [token]);
const verifyToken = async (accessToken) => { const refreshTokens = async () => {
const result = await auth.verify(accessToken); if (!token || !token.refreshToken || !token.refreshToken.token) return null;
const result = await refresh(token.refreshToken.token);
if (!result.authenticated || !token.idToken.payload || !token.idToken.payload.exp) { return result;
signOut();
return;
} }
const duration = (1000 * token.idToken.payload.exp) - (new Date()).getTime(); const isError = (resp) => {
if (resp === null) return true;
if (resp && resp.error) return true;
return false;
}
const startSessionTimer = () => {
const duration = 1000 * token.idToken.payload.exp - new Date().getTime();
if (!timer) { if (!timer) {
timer = getTimerWorker(); timer = getTimerWorker();
timer.onMessage((e) => { timer.onMessage(async (e) => {
if (e.data === "timeout") { if (e.data === "timeout") {
const t = await refreshTokens();
if (!isError(t)) return;
signOut(); signOut();
} }
}) });
} }
timer.start(duration); timer.start(duration);
}; };
const signIn = async (username, password) => { const verifyToken = async (idToken) => {
try {
const result = await auth.verify(idToken);
if (
!result.authenticated ||
!token.idToken.payload ||
!token.idToken.payload.exp
) {
const t = await refreshTokens();
if (!isError(t)) return;
signOut();
return;
}
startSessionTimer();
}
catch (e) {
setError(e.message);
}
};
const signIn = async (code) => {
let result = null; let result = null;
try { try {
if (!username) throw new Error('Email is required'); if (!code) return;
if (!password) throw new Error('Password is required');
setFetching(true); setFetching(true);
setError(null); setError(null);
result = await auth.signIn(username, password); result = await auth.signIn(code);
if (result.message) {
throw new Error(result.message);
}
if (result.message) throw new Error(result.message);
signedIn(result); signedIn(result);
} } catch (err) {
catch (error) { setError(`Sign in error. ${err.message}`);
setError(error.message); } finally {
}
finally {
setFetching(false);
}
return result;
};
const signUp = async (username, password, confirmPassword) => {
let result = null;
try {
if (!username) throw new Error('Email is required');
if (!password) throw new Error('Password is required');
if (password !== confirmPassword) throw new Error('Passwords do not match');
setFetching(true);
setError(null);
result = await auth.signUp(username, password);
if (result.message) throw new Error(result.message);
}
catch (error) {
setError(error.message);
}
finally {
setFetching(false);
}
return result;
};
const signUpAndIn = async (username, password, confirmPassword) => {
let result = null;
try {
result = await signUp(username, password, confirmPassword);
if (result.message) throw new Error(result.message);
result = await signIn(username, password);
}
catch (error) {
setError(error.message);
}
finally {
setFetching(false); setFetching(false);
} }
@@ -117,27 +105,61 @@ export const UserProvider = ({ children }) => {
const signOut = () => { const signOut = () => {
setToken(null); setToken(null);
if (!localStorage) return; if (localStorage) {
localStorage.removeItem("token"); localStorage.removeItem("token");
}
return getLogoutURL();
}; };
const signedIn = (token) => { const signedIn = (value) => {
setToken(token); setToken(value);
if (!localStorage || !token || !token.idToken) return; if (!localStorage || !value || !value.idToken) return;
localStorage.setItem("token", JSON.stringify(token)); localStorage.setItem("token", JSON.stringify(value));
};
const refresh = async (value) => {
let result = null;
try {
if (!value) {
throw new Error("Token required");
}
setFetching(true);
setError(null);
// eslint-disable-next-line
result = await auth.refresh(value);
if (result.message) {
throw new Error(result.message);
}
signedIn(result);
} catch (err) {
setError(`Refresh error. ${err.message}`);
} finally {
setFetching(false);
} }
return result;
};
const getAuthorizeURL = () => auth.ssoAuthorize();
const getLogoutURL = () => auth.ssoLogout();
return ( return (
<UserContext.Provider value={{ <UserContext.Provider
value={{
fetching, fetching,
token, token,
error, error,
setError, setError,
signIn, signIn,
signUp,
signUpAndIn,
signOut, signOut,
}}> refresh,
getAuthorizeURL,
getLogoutURL,
}}
>
{children} {children}
</UserContext.Provider> </UserContext.Provider>
); );

View File

@@ -1,150 +1,130 @@
jest.mock("../../services/auth"); jest.mock("../../services/auth");
jest.mock("../../services/timer"); jest.mock("../../services/timer");
import {render, cleanup, screen, fireEvent, waitFor} from "@testing-library/react" import {
render,
cleanup,
screen,
fireEvent,
waitFor,
} from "@testing-library/react";
import { UserProvider, useUserContext } from "../Contexts/UserContext"; import { UserProvider, useUserContext } from "../Contexts/UserContext";
import auth from "../../services/auth"; import auth from "../../services/auth";
import getTimerWorker from "../../services/timer"; import getTimerWorker from "../../services/timer";
const TEST_TOKEN = { idToken: { const TEST_TOKEN = {
idToken: {
jwtToken: "TEST", jwtToken: "TEST",
payload: { payload: {
exp: (new Date().getTime() / 1000) exp: new Date().getTime() / 1000,
} },
}}; },
describe("UseContext", () => {
describe("Signup", () => {
beforeEach(() => {
const TestComp = () => {
const { signUp, error, fetching } = useUserContext();
return (
<>
<div data-testid="error">{error}</div>
<div data-testid="fetching">{fetching.toString()}</div>
<button data-testid="signUpNoEmail" onClick={() => signUp("")}/>
<button data-testid="signUpNoPassword" onClick={() => signUp("test@test.com", "")}/>
<button data-testid="signUpBadConfirm" onClick={() => signUp("test@test.com", "password", "")}/>
<button data-testid="signUp" onClick={() => signUp("test@test.com", "password", "password")}/>
</>
);
}; };
render(<UserProvider><TestComp /></UserProvider>);
});
afterEach(() => { const INVALID_TOKEN_RESPONSE = {
cleanup(); error: "Bad Request Error",
}); message: "Bad Request Message",
it("Initial state", () => {
expect(screen.getByTestId("error").innerHTML).toEqual("");
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
});
it("Error with no email address", () => {
fireEvent.click(screen.getByTestId("signUpNoEmail"));
expect(screen.getByTestId("error").innerHTML).toEqual("Email is required");
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
});
it("Error with no password", () => {
fireEvent.click(screen.getByTestId("signUpNoPassword"));
expect(screen.getByTestId("error").innerHTML).toEqual("Password is required");
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
});
it("Error with non-matching password", () => {
fireEvent.click(screen.getByTestId("signUpBadConfirm"));
expect(screen.getByTestId("error").innerHTML).toEqual("Passwords do not match");
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
});
it("No error sign up", async () => {
fireEvent.click(screen.getByTestId("signUp"));
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
expect(screen.getByTestId("error").innerHTML).toEqual("");
});
it("Handle server error", async () => {
auth.setSignInResponse({ message: "SIGN-IN-ERROR" })
auth.setSignUpResponse({ message: "SIGN-UP-ERROR", error: "ERR" });
fireEvent.click(screen.getByTestId("signUp"));
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
expect(screen.getByTestId("error").innerHTML).toEqual("SIGN-UP-ERROR");
auth.setSignUpResponse({});
});
});
describe("Signin", () => {
beforeEach(() => {
const TestComp = () => {
const { signIn, error, token, fetching } = useUserContext();
return (
<>
<div data-testid="error">{error}</div>
<div data-testid="fetching">{fetching.toString()}</div>
<div data-testid="token">{JSON.stringify(token)}</div>
<button data-testid="signInNoEmail" onClick={() => signIn("")}/>
<button data-testid="signInNoPassword" onClick={() => signIn("test@test.com", "")}/>
<button data-testid="signIn" onClick={() => signIn("test@test.com", "password", "password")}/>
</>
);
}; };
render(<UserProvider><TestComp /></UserProvider>);
});
afterEach(() => { const setupRefreshEnv = (refreshResponse, authenticated) => {
cleanup(); auth.setRefreshResponse(refreshResponse);
}); auth.setVerifyResponse({ authenticated });
};
it("Initial state", () => { const setupSignInEnv = (refreshResponse, authenticated) => {
expect(screen.getByTestId("error").innerHTML).toEqual(""); auth.setSignInResponse(refreshResponse);
expect(screen.getByTestId("fetching").innerHTML).toEqual("false"); auth.setVerifyResponse({ authenticated });
expect(screen.getByTestId("token").innerHTML).toEqual("null"); };
});
it("Error with no email address", () => { const checkBaseResults = (error, fetching, token) => {
fireEvent.click(screen.getByTestId("signInNoEmail")); expect(screen.getByTestId("error").innerHTML).toEqual(error);
expect(screen.getByTestId("error").innerHTML).toEqual("Email is required"); expect(screen.getByTestId("fetching").innerHTML).toEqual(fetching);
expect(screen.getByTestId("fetching").innerHTML).toEqual("false"); expect(screen.getByTestId("token").innerHTML).toEqual(token);
expect(screen.getByTestId("token").innerHTML).toEqual("null"); };
});
it("Error with no password", () => { const checkTokenResults = (timer, token) => {
fireEvent.click(screen.getByTestId("signInNoPassword"));
expect(screen.getByTestId("error").innerHTML).toEqual("Password is required");
expect(screen.getByTestId("fetching").innerHTML).toEqual("false");
expect(screen.getByTestId("token").innerHTML).toEqual("null");
});
it("No error sign in", async () => {
const TOKEN_STRING = JSON.stringify(TEST_TOKEN);
const timer = getTimerWorker();
auth.setSignInResponse(TEST_TOKEN);
auth.setVerifyResponse({ authenticated: true })
fireEvent.click(screen.getByTestId("signIn"));
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
expect(screen.getByTestId("error").innerHTML).toEqual("");
expect(screen.getByTestId("token").innerHTML).toEqual(TOKEN_STRING);
expect(timer.start.mock.calls.length).toEqual(1); expect(timer.start.mock.calls.length).toEqual(1);
expect(timer.onMessage.mock.calls.length).toEqual(1); expect(timer.onMessage.mock.calls.length).toEqual(1);
expect(timer.stop.mock.calls.length).toEqual(0); expect(timer.stop.mock.calls.length).toEqual(0);
expect(timer.terminate.mock.calls.length).toEqual(0); expect(timer.terminate.mock.calls.length).toEqual(0);
if (!localStorage) { if (!localStorage) {
expect(localStorage.getItem("token")).toEqual(TOKEN_STRING); expect(localStorage.getItem("token")).toEqual(token);
localStorage.removeItem("token"); localStorage.removeItem("token");
} }
};
describe("UseContext", () => {
describe("Signin", () => {
beforeEach(() => {
const TestComp = () => {
const {
signIn,
error,
token,
fetching,
} = useUserContext();
return (
<>
<div data-testid="error">{error}</div>
<div data-testid="fetching">{fetching.toString()}</div>
<div data-testid="token">{JSON.stringify(token)}</div>
<button data-testid="signInNoCode" onClick={() => signIn("")} />
<button
data-testid="signInInvalidCode"
onClick={() => signIn("INVALID_CODE")}
/>
<button data-testid="signIn" onClick={() => signIn("TEST_CODE")} />
</>
);
};
render(
<UserProvider>
<TestComp />
</UserProvider>
);
}); });
it("Handle server error", async () => { afterEach(() => {
auth.setSignInResponse({ message: "SERVER-ERROR", error: "ERR" }); cleanup();
});
it("Initial state", () => {
checkBaseResults("", "false", "null");
});
it("No auth code", () => {
fireEvent.click(screen.getByTestId("signInNoCode"));
checkBaseResults("", "false", "null");
});
it("Invalid auth code", async () => {
setupSignInEnv(INVALID_TOKEN_RESPONSE, false);
fireEvent.click(screen.getByTestId("signInInvalidCode"));
await waitFor(() =>
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
);
checkBaseResults("Sign in error. Bad Request Message", "false", "null");
});
it("Sign in form", async () => {
const TOKEN_STRING = JSON.stringify(TEST_TOKEN);
const timer = getTimerWorker();
setupSignInEnv(TEST_TOKEN, true);
fireEvent.click(screen.getByTestId("signIn")); fireEvent.click(screen.getByTestId("signIn"));
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false"));
expect(screen.getByTestId("error").innerHTML).toEqual("SERVER-ERROR"); await waitFor(() =>
auth.setSignUpResponse({}); expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
);
checkBaseResults("", "false", TOKEN_STRING);
checkTokenResults(timer, TOKEN_STRING);
}); });
}); });
@@ -157,15 +137,21 @@ describe("UseContext", () => {
<div data-testid="error">{error}</div> <div data-testid="error">{error}</div>
<div data-testid="fetching">{fetching.toString()}</div> <div data-testid="fetching">{fetching.toString()}</div>
<div data-testid="token">{JSON.stringify(token)}</div> <div data-testid="token">{JSON.stringify(token)}</div>
<button data-testid="signIn" onClick={() => signIn("test@test.com", "password", "password")}/> <button data-testid="signIn" onClick={() => signIn("TEST_CODE")} />
<button data-testid="signOut" onClick={() => signOut()} /> <button data-testid="signOut" onClick={() => signOut()} />
</> </>
); );
}; };
render(<UserProvider><TestComp /></UserProvider>); render(
<UserProvider>
<TestComp />
</UserProvider>
);
auth.setSignInResponse(TEST_TOKEN); auth.setSignInResponse(TEST_TOKEN);
fireEvent.click(screen.getByTestId("signIn")); fireEvent.click(screen.getByTestId("signIn"));
await waitFor(() => expect(screen.getByTestId("fetching").innerHTML).toEqual("false")); await waitFor(() =>
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
);
}); });
afterEach(() => { afterEach(() => {
@@ -175,11 +161,81 @@ describe("UseContext", () => {
it("Token cleared", () => { it("Token cleared", () => {
fireEvent.click(screen.getByTestId("signOut")); fireEvent.click(screen.getByTestId("signOut"));
expect(screen.getByTestId("error").innerHTML).toEqual("");
expect(screen.getByTestId("fetching").innerHTML).toEqual("false"); checkBaseResults("", "false", "null");
expect(screen.getByTestId("token").innerHTML).toEqual("null");
if (!localStorage) return; if (!localStorage) return;
expect(localStorage.getItem('token')).toBeNull(); expect(localStorage.getItem("token")).toBeNull();
}) });
}) });
describe("Refresh", () => {
beforeEach(() => {
const TestComp = () => {
const {
refresh,
error,
token,
fetching,
} = useUserContext();
return (
<>
<div data-testid="error">{error}</div>
<div data-testid="fetching">{fetching.toString()}</div>
<div data-testid="token">{JSON.stringify(token)}</div>
<button data-testid="refreshNoToken" onClick={() => refresh("")} />
<button
data-testid="refreshInvalidToken"
onClick={() => refresh("INVALID_TOKEN")}
/>
<button data-testid="refreshValidToken" onClick={() => refresh("TEST_TOKEN")} />
</>
);
};
render(
<UserProvider>
<TestComp />
</UserProvider>
);
});
afterEach(() => {
cleanup();
});
it("Initial state", () => {
checkBaseResults("", "false", "null");
});
it("No refresh token", () => {
fireEvent.click(screen.getByTestId("refreshNoToken"));
checkBaseResults("Refresh error. Token required", "false", "null");
});
it("Invalid refresh token", async () => {
setupRefreshEnv(INVALID_TOKEN_RESPONSE, false);
fireEvent.click(screen.getByTestId("refreshInvalidToken"));
await waitFor(() =>
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
);
checkBaseResults("Refresh error. Bad Request Message", "false", "null");
});
it("Valid refresh token", async () => {
const TOKEN_STRING = JSON.stringify(TEST_TOKEN);
const timer = getTimerWorker();
setupRefreshEnv(TEST_TOKEN, true);
fireEvent.click(screen.getByTestId("refreshValidToken"));
await waitFor(() =>
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
);
checkBaseResults("", "false", TOKEN_STRING);
checkTokenResults(timer, TOKEN_STRING);
});
});
}); });

View File

@@ -5,11 +5,7 @@ let progress = 0;
let status = null; let status = null;
export const FileUploadProvider = ({ children }) => { export const FileUploadProvider = ({ children }) => {
return ( return <div data-testid="mocked-fileuploadprovider">{children}</div>;
<div data-testid="mocked-fileuploadprovider">
{children}
</div>
);
}; };
export const useFileUploadContext = () => ({ export const useFileUploadContext = () => ({

View File

@@ -1,30 +1,27 @@
import React from 'react'; import React from "react";
let token = null; let token = null;
let fetching = false; let fetching = false;
let error = null; let error = null;
let signInResp = {}; let signInResp = {};
let signUpResp = {}; let authorizeURL = "https://cognito.com/authorize?redirect=https://example.com/callback";
let logoutURL = "https://cognito.com/logout?redirect=https://example.com/callback";
export const UserProvider = ({ children }) => { export const UserProvider = ({ children }) => {
return ( return <div data-testid="mocked-userprovider">{children}</div>;
<div data-testid="mocked-userprovider">
{children}
</div>
);
}; };
export const useUserContext = () => ({ export const useUserContext = () => ({
token, token,
fetching, fetching,
error, error,
signIn: jest.fn(() => signInResp),
signOut: jest.fn(),
getAuthorizeURL: jest.fn(() => authorizeURL),
getLogoutURL: jest.fn(() => logoutURL),
setError: jest.fn((value) => { setError: jest.fn((value) => {
error = value; error = value;
}), }),
signIn: jest.fn(() => signInResp),
signUp: jest.fn(() => signUpResp),
signOut: jest.fn(),
signUpAndIn: jest.fn(),
}); });
export const setToken = (val) => { export const setToken = (val) => {
@@ -38,7 +35,3 @@ export const setFetching = (val) => {
export const setError = (val) => { export const setError = (val) => {
error = val; error = val;
}; };
export const setSignUpResp = (val) => {
signUpResp = val;
};

View File

@@ -1,11 +1,11 @@
import React, { Component } from 'react'; import React, { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import { Typography } from '@material-ui/core'; import { Typography } from "@material-ui/core";
export default class ErrorBoundary extends Component { export default class ErrorBoundary extends Component {
state = { state = {
error: '', error: "",
errorInfo: '', errorInfo: "",
hasError: false, hasError: false,
}; };
static getDerivedStateFromError(error) { static getDerivedStateFromError(error) {
@@ -15,7 +15,12 @@ export default class ErrorBoundary extends Component {
this.setState({ errorInfo }); this.setState({ errorInfo });
} }
render() { render() {
if (this.state.hasError) return (<Typography variant="h3" align="center" >Oops. An React JS Error Occured.</Typography>); if (this.state.hasError)
return (
<Typography variant="h3" align="center">
Oops. An React JS Error Occured.
</Typography>
);
return this.props.children; return this.props.children;
} }
} }

View File

@@ -1,24 +1,39 @@
import React from "react"; import React from "react";
import { Button, Container, CssBaseline, Grid, Typography } from "@material-ui/core"; import {
Button,
Container,
CssBaseline,
Grid,
Typography,
} from "@material-ui/core";
import { DropzoneAreaBase } from "material-ui-dropzone"; import { DropzoneAreaBase } from "material-ui-dropzone";
import { useUserContext } from "../Contexts/UserContext"; import { useUserContext } from "../Contexts/UserContext";
import { useFileUploadContext, FileUploadProvider } from "../Contexts/FileUploadContext"; import {
useFileUploadContext,
FileUploadProvider,
} from "../Contexts/FileUploadContext";
import ModalProgressBar from "../ModalProgressBar"; import ModalProgressBar from "../ModalProgressBar";
import useStyles from "../Styles"; import useStyles from "../useStyles";
const FileUploadZone = ({ classes, token }) => { const FileUploadZone = ({ classes, token }) => {
const { upload, rejectedFile } = useFileUploadContext(); const { upload, rejectedFile } = useFileUploadContext();
const { token: { idToken: { jwtToken : authToken } } } = useUserContext(); const {
token: {
idToken: { jwtToken: authToken },
},
} = useUserContext();
return ( return (
<form className={classes.form} noValidate> <form className={classes.form} noValidate>
<DropzoneAreaBase <DropzoneAreaBase
id="dropzone" id="dropzone"
maxFileSize={1e+9} maxFileSize={1e9}
filesLimit={1} filesLimit={1}
showAlerts={false} showAlerts={false}
onAdd={(files) => upload(files, authToken)} onAdd={(files) => upload(files, authToken)}
onDropRejected={(files) => { rejectedFile(files) }} onDropRejected={(files) => {
rejectedFile(files);
}}
/> />
<ModalProgressBar /> <ModalProgressBar />
</form> </form>

View File

@@ -1,15 +1,18 @@
import React from 'react'; import React from "react";
import { Snackbar } from "@material-ui/core"; import { Snackbar } from "@material-ui/core";
import { useUserContext } from './Contexts/UserContext'; import { useUserContext } from "./Contexts/UserContext";
export const MessageBar = () => { export const MessageBar = () => {
const { error, setError } = useUserContext(); const { error, setError } = useUserContext();
const open = (error !== null); const open = error !== null;
return (<Snackbar return (
<Snackbar
open={open} open={open}
message={error} message={error}
anchorOrigin={{ vertical: "top", horizontal: "center" }} anchorOrigin={{ vertical: "top", horizontal: "center" }}
autoHideDuration={10000} autoHideDuration={10000}
onClose={() => setError(null)}/>) onClose={() => setError(null)}
} />
);
};

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Modal from '@material-ui/core/Modal'; import Modal from "@material-ui/core/Modal";
import { Button, LinearProgress } from "@material-ui/core"; import { Button, LinearProgress } from "@material-ui/core";
import { useFileUploadContext } from "../Contexts/FileUploadContext"; import { useFileUploadContext } from "../Contexts/FileUploadContext";
@@ -23,7 +23,13 @@ const getModalStyle = () => {
}; };
const ModalProgressBar = () => { const ModalProgressBar = () => {
const { uploading, progress, status, linkURL, cancel } = useFileUploadContext(); const {
uploading,
progress,
status,
linkURL,
cancel,
} = useFileUploadContext();
const modalStyle = getModalStyle(); const modalStyle = getModalStyle();
const onClickCancel = cancel; const onClickCancel = cancel;
@@ -32,7 +38,13 @@ const ModalProgressBar = () => {
<Modal open={uploading}> <Modal open={uploading}>
<div style={modalStyle}> <div style={modalStyle}>
{status && <p>{status}</p>} {status && <p>{status}</p>}
{linkURL && <p><a href={linkURL} target="_blank" rel="noreferrer">View</a></p>} {linkURL && (
<p>
<a href={linkURL} target="_blank" rel="noreferrer">
View
</a>
</p>
)}
<LinearProgress variant="determinate" value={progress} /> <LinearProgress variant="determinate" value={progress} />
<Button onClick={onClickCancel}> <Button onClick={onClickCancel}>
{progress === 100 || progress === -1 ? "Done" : "Cancel"} {progress === 100 || progress === -1 ? "Done" : "Cancel"}
@@ -40,7 +52,6 @@ const ModalProgressBar = () => {
</div> </div>
</Modal> </Modal>
); );
};
}
export default ModalProgressBar; export default ModalProgressBar;

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from "react";
import { Redirect, Route } from 'react-router-dom'; import { Redirect, Route } from "react-router-dom";
export const TYPES = { export const TYPES = {
PUBLIC: 0, PUBLIC: 0,
@@ -10,9 +10,8 @@ export const TYPES = {
export const AuthRoute = ({ token, type, ...others }) => { export const AuthRoute = ({ token, type, ...others }) => {
if (!token && type === TYPES.PROTECTED) { if (!token && type === TYPES.PROTECTED) {
return <Redirect to="/" />; return <Redirect to="/" />;
} } else if (token && type === TYPES.GUEST) {
else if (token && type === TYPES.GUEST) {
return <Redirect to="/home" />; return <Redirect to="/home" />;
} }
return <Route render {...others} />; return <Route render {...others} />;
} };

View File

@@ -1,13 +1,13 @@
import React from 'react'; import React from "react";
import { Redirect, Route } from 'react-router-dom'; import { Redirect, Route } from "react-router-dom";
import { useUserContext } from '../Contexts/UserContext'; import { useUserContext } from "../Contexts/UserContext";
export const ProtectedRoute = ({ render, ...others }) => { export const ProtectedRoute = ({ render, ...others }) => {
const context = useUserContext(); const context = useUserContext();
const { token, setError } = context; const { token, setError } = context;
if (!token) { if (!token) {
setError('Please sign in to access'); setError("Please sign in to access");
return <Redirect to="/" />; return <Redirect to="/" />;
} }
return <Route render {...others} />; return <Route render {...others} />;
} };

View File

@@ -1,17 +1,13 @@
import React, { Suspense } from 'react'; import React, { Suspense } from "react";
import { import { BrowserRouter, Switch } from "react-router-dom";
BrowserRouter,
Switch,
} from 'react-router-dom';
import { AuthRoute, TYPES } from '../Routes/AuthRoute' import { AuthRoute, TYPES } from "../Routes/AuthRoute";
import { MessageBar } from '../MessageBar'; import { MessageBar } from "../MessageBar";
import { useUserContext } from '../Contexts/UserContext'; import { useUserContext } from "../Contexts/UserContext";
const SignInForm = React.lazy(() => import('../SignInForm')); const SSOForm = React.lazy(() => import("../SSOForm"));
const SignUpForm = React.lazy(() => import('../SignUpForm')); const FileUploadForm = React.lazy(() => import("../FileUploadForm"));
const FileUploadForm = React.lazy(() => import('../FileUploadForm')); const PageNotFound = React.lazy(() => import("../404"));
const PageNotFound = React.lazy(() => import('../404'));
const SiteRoutes = () => { const SiteRoutes = () => {
const { token } = useUserContext(); const { token } = useUserContext();
@@ -20,9 +16,19 @@ const SiteRoutes = () => {
<MessageBar /> <MessageBar />
<BrowserRouter> <BrowserRouter>
<Switch> <Switch>
<AuthRoute path="/" exact render={() => <SignInForm />} type={TYPES.GUEST} token={token} /> <AuthRoute
<AuthRoute path="/signup" exact render={() => <SignUpForm />} type={TYPES.GUEST} token={token} /> path="/"
<AuthRoute path="/home" render={() => <FileUploadForm />} type={TYPES.PROTECTED} token={token} /> exact
render={() => <SSOForm />}
type={TYPES.GUEST}
token={token}
/>
<AuthRoute
path="/home"
render={() => <FileUploadForm />}
type={TYPES.PROTECTED}
token={token}
/>
<PageNotFound /> <PageNotFound />
</Switch> </Switch>
</BrowserRouter> </BrowserRouter>
@@ -30,5 +36,4 @@ const SiteRoutes = () => {
); );
}; };
export default SiteRoutes; export default SiteRoutes;

View File

@@ -0,0 +1,19 @@
jest.mock("../Contexts/UserContext");
import React from "react";
import { BrowserRouter } from "react-router-dom";
import { render, cleanup } from "@testing-library/react";
import SSOForm from "./index";
describe("Sign In Form", () => {
it("Should render", () => {
const { container } = render(
<BrowserRouter>
<SSOForm />
</BrowserRouter>
);
expect(container).toMatchSnapshot();
cleanup();
});
});

View File

@@ -0,0 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Sign In Form Should render 1`] = `
<div>
<main
class="MuiContainer-root MuiContainer-maxWidthXs"
>
<div
class="makeStyles-paper-1"
>
<h1
class="MuiTypography-root MuiTypography-h5"
>
Fisker OTA Portal
</h1>
<form
action="{onSubmit}"
class="makeStyles-form-3"
novalidate=""
>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
href="https://cognito.com/authorize?redirect=https://example.com/callback"
tabindex="0"
>
<span
class="MuiButton-label"
>
Sign In
</span>
<span
class="MuiTouchRipple-root"
/>
</a>
</form>
</div>
</main>
</div>
`;

View File

@@ -0,0 +1,45 @@
import React, { useEffect } from "react";
import { Button, Container, CssBaseline, Typography } from "@material-ui/core";
import { useUserContext } from "../Contexts/UserContext";
import useStyles from "../useStyles";
const getCode = (search) => {
if (!search) return null;
const s = new URLSearchParams(search);
return s.get("code");
};
export default function SignInForm() {
const classes = useStyles();
const { getAuthorizeURL, signIn } = useUserContext();
useEffect(() => {
const code = getCode(document.location.search);
if (!code) return;
signIn(code);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Typography component="h1" variant="h5">
Fisker OTA Portal
</Typography>
<form className={classes.form} noValidate action="{onSubmit}">
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
href={getAuthorizeURL()}
>
Sign In
</Button>
</form>
</div>
</Container>
);
}

View File

@@ -1,15 +0,0 @@
jest.mock("../Contexts/UserContext");
import React from "react";
import { BrowserRouter } from 'react-router-dom';
import { render, cleanup } from "@testing-library/react"
import SignInForm from './index';
describe("Sign In Form", () => {
it("Should render", () => {
const { container } = render(<BrowserRouter><SignInForm /></BrowserRouter>);
expect(container).toMatchSnapshot();
cleanup();
})
})

View File

@@ -1,145 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Sign In Form Should render 1`] = `
<div>
<main
class="MuiContainer-root MuiContainer-maxWidthXs"
>
<div
class="makeStyles-paper-1"
>
<h1
class="MuiTypography-root MuiTypography-h5"
>
Sign in
</h1>
<form
action="{onSubmit}"
class="makeStyles-form-3"
novalidate=""
>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
data-shrink="true"
for="email"
id="email-label"
>
Email Address
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
>
<input
aria-invalid="false"
autocomplete="email"
class="MuiInputBase-input MuiOutlinedInput-input"
id="email"
name="email"
required=""
type="text"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-7 PrivateNotchedOutline-legendNotched-8"
>
<span>
Email Address
 *
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
data-shrink="false"
for="password"
id="password-label"
>
Password
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
>
<input
aria-invalid="false"
autocomplete="current-password"
class="MuiInputBase-input MuiOutlinedInput-input"
id="password"
name="password"
required=""
type="password"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-7"
>
<span>
Password
 *
</span>
</legend>
</fieldset>
</div>
</div>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
tabindex="0"
type="submit"
>
<span
class="MuiButton-label"
>
Sign In
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<div
class="MuiGrid-root MuiGrid-container"
>
<div
class="MuiGrid-root MuiGrid-item"
>
<a
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
href="/signup"
>
Don't have an account? Sign Up
</a>
</div>
</div>
</form>
</div>
</main>
</div>
`;

View File

@@ -1,78 +0,0 @@
import React, { useRef } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Button, Container, CssBaseline, Grid, Link, TextField, Typography } from '@material-ui/core';
import { useUserContext } from '../Contexts/UserContext';
import useStyles from '../Styles';
export default function SignInForm() {
const classes = useStyles();
const emailEl = useRef(null);
const passwordEl = useRef(null);
const { fetching, signIn, setError } = useUserContext();
const onSubmit = async (event) => {
try {
event.preventDefault();
const username = emailEl.current.value;
const password = passwordEl.current.value;
await signIn(username, password);
}
catch (e) {
setError(e.message);
}
};
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} noValidate action="{onSubmit}">
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
inputRef={emailEl}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
inputRef={passwordEl}
/>
<Button
type="submit"
disabled={fetching}
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={onSubmit}
>
{ fetching ? "Signing In..." : "Sign In" }
</Button>
<Grid container>
<Grid item>
<Link component={RouterLink} to="/signup" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</div>
</Container>
);
}

View File

@@ -1,17 +0,0 @@
jest.mock("../Contexts/UserContext");
import { BrowserRouter } from 'react-router-dom';
import { render, cleanup, screen, fireEvent, waitFor } from "@testing-library/react";
import SignUpForm from './index';
describe.only("Sign Up Form", () => {
afterEach(() => {
cleanup();
});
it("Should render", () => {
const { container } = render(<BrowserRouter><SignUpForm /></BrowserRouter>);
expect(container).toMatchSnapshot();
});
})

View File

@@ -1,189 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Sign Up Form Should render 1`] = `
<div>
<main
class="MuiContainer-root MuiContainer-maxWidthXs"
>
<div
class="makeStyles-paper-1"
>
<h1
class="MuiTypography-root MuiTypography-h5"
>
Sign up
</h1>
<form
class="makeStyles-form-3"
novalidate=""
>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-focused Mui-focused Mui-required Mui-required"
data-shrink="true"
for="email"
id="email-label"
>
Email Address
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth Mui-focused Mui-focused MuiInputBase-formControl"
>
<input
aria-invalid="false"
autocomplete="email"
class="MuiInputBase-input MuiOutlinedInput-input"
id="email"
name="email"
required=""
type="text"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-7 PrivateNotchedOutline-legendNotched-8"
>
<span>
Email Address
 *
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
data-shrink="false"
for="password"
id="password-label"
>
Password
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
>
<input
aria-invalid="false"
autocomplete="new-password"
class="MuiInputBase-input MuiOutlinedInput-input"
id="password"
name="password"
required=""
type="password"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-7"
>
<span>
Password
 *
</span>
</legend>
</fieldset>
</div>
</div>
<div
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
data-shrink="false"
for="passwordConfirm"
id="passwordConfirm-label"
>
Confirm Password
<span
aria-hidden="true"
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
>
*
</span>
</label>
<div
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
>
<input
aria-invalid="false"
class="MuiInputBase-input MuiOutlinedInput-input"
id="passwordConfirm"
name="password"
required=""
type="password"
value=""
/>
<fieldset
aria-hidden="true"
class="PrivateNotchedOutline-root-5 MuiOutlinedInput-notchedOutline"
>
<legend
class="PrivateNotchedOutline-legendLabelled-7"
>
<span>
Confirm Password
 *
</span>
</legend>
</fieldset>
</div>
</div>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-containedPrimary MuiButton-fullWidth"
tabindex="0"
type="submit"
>
<span
class="MuiButton-label"
>
Sign Up
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
<div
class="MuiGrid-root MuiGrid-container"
>
<div
class="MuiGrid-root MuiGrid-item"
>
<a
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-body2 MuiTypography-colorPrimary"
href="/"
>
Already have an account? Sign In
</a>
</div>
</div>
</form>
</div>
</main>
</div>
`;

View File

@@ -1,90 +0,0 @@
import React, { useRef } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Button, Container, CssBaseline, Grid, Link, TextField, Typography } from '@material-ui/core';
import useStyles from '../Styles';
import { useUserContext } from '../Contexts/UserContext';
export default function SignInForm() {
const { signUpAndIn, fetching, setError } = useUserContext();
const classes = useStyles();
const emailEl = useRef(null);
const passwordEl = useRef(null);
const confirmEl = useRef(null);
const onSubmit = async (event) => {
try {
event.preventDefault();
const email = emailEl.current.value;
const password = passwordEl.current.value;
const confirm = confirmEl.current.value;
await signUpAndIn(email, password, confirm);
}
catch (e) {
setError(e.message);
}
};
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Typography component="h1" variant="h5">
Sign up
</Typography>
<form className={classes.form} noValidate onSubmit={onSubmit}>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
inputRef={emailEl}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="new-password"
inputRef={passwordEl}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Confirm Password"
type="password"
id="passwordConfirm"
inputRef={confirmEl}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
disabled={fetching}
>
{ fetching ? "Signing Up..." : "Sign Up" }
</Button>
<Grid container>
<Grid item>
<Link component={RouterLink} to="/" variant="body2">
{"Already have an account? Sign In"}
</Link>
</Grid>
</Grid>
</form>
</div>
</Container>
);
}

View File

@@ -1,18 +1,18 @@
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
paper: { paper: {
marginTop: theme.spacing(8), marginTop: theme.spacing(8),
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
alignItems: 'center', alignItems: "center",
}, },
avatar: { avatar: {
margin: theme.spacing(1), margin: theme.spacing(1),
backgroundColor: theme.palette.primary.main, backgroundColor: theme.palette.primary.main,
}, },
form: { form: {
width: '100%', // Fix IE 11 issue. width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(1), marginTop: theme.spacing(1),
}, },
submit: { submit: {

View File

@@ -1,13 +1,10 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
monospace;
} }

View File

@@ -1,16 +1,21 @@
const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL || "https://gw-dev.fiskerdps.com/compute_auth";
const CALLBACK_URL = process.env.REACT_APP_AUTH_CALLBACK_URL || "";
let signInResponse = {}; let signInResponse = {};
let signUpResponse = {};
let verifyResponse = {}; let verifyResponse = {};
let refreshResponse = {};
const logResponse = (response) => { const logResponse = (response) => {
return response; return response;
}; };
export default { export default {
ssoAuthorize: () => `${AUTH_URL}/authorize?redirect=${CALLBACK_URL}`,
ssoLogout: () => `${AUTH_URL}/logout?redirect=${CALLBACK_URL}`,
signIn: async (username, password) => logResponse(signInResponse), signIn: async (username, password) => logResponse(signInResponse),
signUp: async (username, password) => logResponse(signUpResponse), verify: async (idToken) => logResponse(verifyResponse),
verify: async (accessToken) => logResponse(verifyResponse), refresh: async (refreshToken) => logResponse(refreshResponse),
setSignInResponse: (value) => { signInResponse = value; }, setSignInResponse: (value) => { signInResponse = value; },
setSignUpResponse: (value) => { signUpResponse = value; },
setVerifyResponse: (value) => { verifyResponse = value; }, setVerifyResponse: (value) => { verifyResponse = value; },
setRefreshResponse: (value) => { refreshResponse = value; },
} }

View File

@@ -11,7 +11,7 @@ export const getCancelToken = () => {
return issuedCancelToken; return issuedCancelToken;
} }
export const uploadFile = async (file, token, onProgress, cancelToken, hash) => { export const uploadFile = async (file, token, onProgress, cancelToken) => {
if (!uploadFileDelay) return uploadFileResponse; if (!uploadFileDelay) return uploadFileResponse;
onProgress(50); onProgress(50);
await delay(10000); await delay(10000);

View File

@@ -1,34 +1,34 @@
const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL || "https://dev-auth.fiskerdps.com"; const AUTH_URL = process.env.REACT_APP_AUTH_SERVICE_URL || "https://gw-dev.fiskerdps.com/compute_auth";
const CALLBACK_URL = process.env.REACT_APP_AUTH_CALLBACK_URL || "https://dev-ota-admin.fiskerdps.com";
const auth = { const auth = {
signIn: (username, password) => fetch(`${AUTH_URL}/auth/login`, { ssoAuthorize: () => `${AUTH_URL}/authorize?redirect=${CALLBACK_URL}`,
ssoLogout: () => `${AUTH_URL}/logout?redirect=${CALLBACK_URL}`,
signIn: (code) => fetch(`${AUTH_URL}/token`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
body: JSON.stringify({ body: JSON.stringify({
username, code,
password, redirect: CALLBACK_URL,
}) })
}).then((response) => response.json()), }).then((response) => response.json()),
signUp: (username, password) => fetch(`${AUTH_URL}/auth/register`, { verify: (idToken) => fetch(`${AUTH_URL}/verify`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
body: JSON.stringify({ body: JSON.stringify({ token: idToken })
username,
password,
})
}).then((response) => response.json()), }).then((response) => response.json()),
verify: (accessToken) => fetch(`${AUTH_URL}/auth/verify`, { refresh: (refreshToken) => fetch(`${AUTH_URL}/refresh`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
body: JSON.stringify({ token: accessToken }) body: JSON.stringify({ refresh_token: refreshToken })
}).then((response) => response.json()), }).then((response) => response.json()),
}; };

23
src/services/auth.test.js Normal file
View File

@@ -0,0 +1,23 @@
import auth from "./auth";
const testAuthURL = (url, endpoint) => {
const u = new URL(url);
const path = u.pathname.split("/");
expect(u.protocol).toMatch(/^http/);
expect(u.host).toBeTruthy();
expect(path[path.length - 1]).toEqual(endpoint);
expect(u.searchParams.get("redirect")).toBeTruthy();
};
describe("Auth service", () => {
describe("Auth URLs", () => {
it("Authorize URL", () => {
testAuthURL(auth.ssoAuthorize(), "authorize");
});
it("Logout URL", () => {
testAuthURL(auth.ssoLogout(), "logout");
});
});
});

View File

@@ -2,4 +2,4 @@
// allows you to do things like: // allows you to do things like:
// expect(element).toHaveTextContent(/react/i) // expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom // learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom'; import "@testing-library/jest-dom";