diff options
Diffstat (limited to 'web/src')
80 files changed, 1063 insertions, 897 deletions
diff --git a/web/src/App.test.tsx b/web/src/App.test.tsx index 2e29af42b..67a662c24 100644 --- a/web/src/App.test.tsx +++ b/web/src/App.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { shallow } from "enzyme"; -import App from './App'; -it('renders without crashing', () => { - shallow(<App />); +import App from "./App"; + +it("renders without crashing", () => { + shallow(<App />); }); diff --git a/web/src/App.tsx b/web/src/App.tsx index 07bdcaf22..5cf7b2744 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,26 +1,29 @@ -import React, { useState } from 'react'; -import { - BrowserRouter as Router, Route, Switch, Redirect -} from "react-router-dom"; -import ResetPasswordStep1 from './views/ResetPassword/ResetPasswordStep1'; -import ResetPasswordStep2 from './views/ResetPassword/ResetPasswordStep2'; -import RegisterSecurityKey from './views/DeviceRegistration/RegisterSecurityKey'; -import RegisterOneTimePassword from './views/DeviceRegistration/RegisterOneTimePassword'; +import React, { useState } from "react"; + +import { config as faConfig } from "@fortawesome/fontawesome-svg-core"; +import { BrowserRouter as Router, Route, Switch, Redirect } from "react-router-dom"; + +import NotificationBar from "./components/NotificationBar"; +import NotificationsContext from "./hooks/NotificationsContext"; +import { Notification } from "./models/Notifications"; import { - FirstFactorRoute, ResetPasswordStep2Route, - ResetPasswordStep1Route, RegisterSecurityKeyRoute, + FirstFactorRoute, + ResetPasswordStep2Route, + ResetPasswordStep1Route, + RegisterSecurityKeyRoute, RegisterOneTimePasswordRoute, LogoutRoute, } from "./Routes"; -import LoginPortal from './views/LoginPortal/LoginPortal'; -import NotificationsContext from './hooks/NotificationsContext'; -import { Notification } from './models/Notifications'; -import NotificationBar from './components/NotificationBar'; -import SignOut from './views/LoginPortal/SignOut/SignOut'; -import { getRememberMe, getResetPassword } from './utils/Configuration'; -import '@fortawesome/fontawesome-svg-core/styles.css' -import { config as faConfig } from '@fortawesome/fontawesome-svg-core'; -import { getBasePath } from './utils/BasePath'; +import { getBasePath } from "./utils/BasePath"; +import { getRememberMe, getResetPassword } from "./utils/Configuration"; +import RegisterOneTimePassword from "./views/DeviceRegistration/RegisterOneTimePassword"; +import RegisterSecurityKey from "./views/DeviceRegistration/RegisterSecurityKey"; +import LoginPortal from "./views/LoginPortal/LoginPortal"; +import SignOut from "./views/LoginPortal/SignOut/SignOut"; +import ResetPasswordStep1 from "./views/ResetPassword/ResetPasswordStep1"; +import ResetPasswordStep2 from "./views/ResetPassword/ResetPasswordStep2"; + +import "@fortawesome/fontawesome-svg-core/styles.css"; faConfig.autoAddCss = false; @@ -28,7 +31,7 @@ const App: React.FC = () => { const [notification, setNotification] = useState(null as Notification | null); return ( - <NotificationsContext.Provider value={{ notification, setNotification }} > + <NotificationsContext.Provider value={{ notification, setNotification }}> <Router basename={getBasePath()}> <NotificationBar onClose={() => setNotification(null)} /> <Switch> @@ -48,9 +51,7 @@ const App: React.FC = () => { <SignOut /> </Route> <Route path={FirstFactorRoute}> - <LoginPortal - rememberMe={getRememberMe()} - resetPassword={getResetPassword()} /> + <LoginPortal rememberMe={getRememberMe()} resetPassword={getResetPassword()} /> </Route> <Route path="/"> <Redirect to={FirstFactorRoute} /> @@ -59,6 +60,6 @@ const App: React.FC = () => { </Router> </NotificationsContext.Provider> ); -} +}; export default App; diff --git a/web/src/Routes.ts b/web/src/Routes.ts index 093712e61..3e3ee0ac5 100644 --- a/web/src/Routes.ts +++ b/web/src/Routes.ts @@ -1,4 +1,3 @@ - export const FirstFactorRoute = "/"; export const AuthenticatedRoute = "/authenticated"; @@ -11,4 +10,4 @@ export const ResetPasswordStep1Route = "/reset-password/step1"; export const ResetPasswordStep2Route = "/reset-password/step2"; export const RegisterSecurityKeyRoute = "/security-key/register"; export const RegisterOneTimePasswordRoute = "/one-time-password/register"; -export const LogoutRoute = "/logout";
\ No newline at end of file +export const LogoutRoute = "/logout"; diff --git a/web/src/components/AppStoreBadges.test.tsx b/web/src/components/AppStoreBadges.test.tsx index c5e2de0ee..54fa554d8 100644 --- a/web/src/components/AppStoreBadges.test.tsx +++ b/web/src/components/AppStoreBadges.test.tsx @@ -1,12 +1,11 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; +import React from "react"; + +import ReactDOM from "react-dom"; + import AppStoreBadges from "./AppStoreBadges"; -it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(<AppStoreBadges - iconSize={32} - appleStoreLink="http://apple" - googlePlayLink="http://google" />, div); +it("renders without crashing", () => { + const div = document.createElement("div"); + ReactDOM.render(<AppStoreBadges iconSize={32} appleStoreLink="http://apple" googlePlayLink="http://google" />, div); ReactDOM.unmountComponentAtNode(div); }); diff --git a/web/src/components/AppStoreBadges.tsx b/web/src/components/AppStoreBadges.tsx index 8b5718454..66417dc59 100644 --- a/web/src/components/AppStoreBadges.tsx +++ b/web/src/components/AppStoreBadges.tsx @@ -1,8 +1,10 @@ import React from "react"; -import GooglePlay from "../assets/images/googleplay-badge.svg"; -import AppleStore from "../assets/images/applestore-badge.svg"; + import { Link } from "@material-ui/core"; +import AppleStore from "../assets/images/applestore-badge.svg"; +import GooglePlay from "../assets/images/googleplay-badge.svg"; + export interface Props { iconSize: number; googlePlayLink: string; @@ -25,8 +27,8 @@ const AppStoreBadges = function (props: Props) { <Link href={props.appleStoreLink} target={target}> <img src={AppleStore} alt="apple store" style={{ width }} /> </Link> - </div > - ) -} + </div> + ); +}; -export default AppStoreBadges
\ No newline at end of file +export default AppStoreBadges; diff --git a/web/src/components/ColoredSnackbarContent.test.tsx b/web/src/components/ColoredSnackbarContent.test.tsx index 26bd1aea1..3ffbe4e23 100644 --- a/web/src/components/ColoredSnackbarContent.test.tsx +++ b/web/src/components/ColoredSnackbarContent.test.tsx @@ -1,23 +1,25 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { mount, shallow } from "enzyme"; +import React from "react"; + +import { SnackbarContent } from "@material-ui/core"; import { expect } from "chai"; +import { mount, shallow } from "enzyme"; +import ReactDOM from "react-dom"; + import ColoredSnackbarContent from "./ColoredSnackbarContent"; -import { SnackbarContent } from '@material-ui/core'; -it('renders without crashing', () => { - const div = document.createElement('div'); +it("renders without crashing", () => { + const div = document.createElement("div"); ReactDOM.render(<ColoredSnackbarContent level="success" message="this is a success" />, div); ReactDOM.unmountComponentAtNode(div); }); -it('should contain the message', () => { +it("should contain the message", () => { const el = mount(<ColoredSnackbarContent level="success" message="this is a success" />); expect(el.text()).to.contain("this is a success"); }); /* eslint-disable @typescript-eslint/no-unused-expressions */ -it('should have correct color', () => { +it("should have correct color", () => { let el = shallow(<ColoredSnackbarContent level="success" message="this is a success" />); expect(el.find(SnackbarContent).props().className!.indexOf("success") > -1).to.be.true; @@ -30,4 +32,4 @@ it('should have correct color', () => { el = shallow(<ColoredSnackbarContent level="warning" message="this is an warning" />); expect(el.find(SnackbarContent).props().className!.indexOf("warning") > -1).to.be.true; }); -/* eslint-enable @typescript-eslint/no-unused-expressions */
\ No newline at end of file +/* eslint-enable @typescript-eslint/no-unused-expressions */ diff --git a/web/src/components/ColoredSnackbarContent.tsx b/web/src/components/ColoredSnackbarContent.tsx index 5adda3cea..4cb5b5e40 100644 --- a/web/src/components/ColoredSnackbarContent.tsx +++ b/web/src/components/ColoredSnackbarContent.tsx @@ -1,13 +1,13 @@ import React from "react"; -import CheckCircleIcon from '@material-ui/icons/CheckCircle'; -import ErrorIcon from '@material-ui/icons/Error'; -import InfoIcon from '@material-ui/icons/Info'; -import WarningIcon from '@material-ui/icons/Warning'; import { makeStyles, SnackbarContent } from "@material-ui/core"; -import { amber, green } from '@material-ui/core/colors'; -import classnames from "classnames"; +import { amber, green } from "@material-ui/core/colors"; import { SnackbarContentProps } from "@material-ui/core/SnackbarContent"; +import CheckCircleIcon from "@material-ui/icons/CheckCircle"; +import ErrorIcon from "@material-ui/icons/Error"; +import InfoIcon from "@material-ui/icons/Info"; +import WarningIcon from "@material-ui/icons/Warning"; +import classnames from "classnames"; const variantIcon = { success: CheckCircleIcon, @@ -39,13 +39,14 @@ const ColoredSnackbarContent = function (props: Props) { {message} </span> } - {...others} /> - ) -} + {...others} + /> + ); +}; -export default ColoredSnackbarContent +export default ColoredSnackbarContent; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ success: { backgroundColor: green[600], }, @@ -66,7 +67,7 @@ const useStyles = makeStyles(theme => ({ marginRight: theme.spacing(1), }, message: { - display: 'flex', - alignItems: 'center', + display: "flex", + alignItems: "center", }, -}))
\ No newline at end of file +})); diff --git a/web/src/components/FailureIcon.test.tsx b/web/src/components/FailureIcon.test.tsx index df028b48f..e01698170 100644 --- a/web/src/components/FailureIcon.test.tsx +++ b/web/src/components/FailureIcon.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { mount } from "enzyme"; + import FailureIcon from "./FailureIcon"; -it('renders without crashing', () => { +it("renders without crashing", () => { mount(<FailureIcon />); -});
\ No newline at end of file +}); diff --git a/web/src/components/FailureIcon.tsx b/web/src/components/FailureIcon.tsx index 7069646ec..bc64792b7 100644 --- a/web/src/components/FailureIcon.tsx +++ b/web/src/components/FailureIcon.tsx @@ -1,13 +1,12 @@ import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + import { faTimesCircle } from "@fortawesome/free-regular-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -export interface Props { } +export interface Props {} const FailureIcon = function (props: Props) { - return ( - <FontAwesomeIcon icon={faTimesCircle} size="4x" color="red" className="failure-icon" /> - ) -} + return <FontAwesomeIcon icon={faTimesCircle} size="4x" color="red" className="failure-icon" />; +}; -export default FailureIcon
\ No newline at end of file +export default FailureIcon; diff --git a/web/src/components/FingerTouchIcon.test.tsx b/web/src/components/FingerTouchIcon.test.tsx index d566e6830..31446e026 100644 --- a/web/src/components/FingerTouchIcon.test.tsx +++ b/web/src/components/FingerTouchIcon.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { mount } from "enzyme"; + import FingerTouchIcon from "./FingerTouchIcon"; -it('renders without crashing', () => { +it("renders without crashing", () => { mount(<FingerTouchIcon size={32} />); -});
\ No newline at end of file +}); diff --git a/web/src/components/FingerTouchIcon.tsx b/web/src/components/FingerTouchIcon.tsx index e905dad27..d7c9ad5aa 100644 --- a/web/src/components/FingerTouchIcon.tsx +++ b/web/src/components/FingerTouchIcon.tsx @@ -1,21 +1,32 @@ import React from "react"; -import style from "./FingerTouchIcon.module.css"; + import classnames from "classnames"; +import style from "./FingerTouchIcon.module.css"; + export interface Props { - size: number; + size: number; - animated?: boolean; - strong?: boolean; + animated?: boolean; + strong?: boolean; } const FingerTouchIcon = function (props: Props) { - const shakingClass = (props.animated) ? style.shaking : undefined; - const strong = (props.strong) ? style.strong : undefined; + const shakingClass = props.animated ? style.shaking : undefined; + const strong = props.strong ? style.strong : undefined; return ( - <svg x="0px" y="0px" viewBox="0 0 500 500" width={props.size} height={props.size} className={classnames(style.hand, strong)}> - <path className={shakingClass} d="M438.827,186.347l-80.213-88.149c-15.872-15.872-41.728-15.893-57.749,0.128c-5.077,5.077-8.533,11.157-10.325,17.643 + <svg + x="0px" + y="0px" + viewBox="0 0 500 500" + width={props.size} + height={props.size} + className={classnames(style.hand, strong)} + > + <path + className={shakingClass} + d="M438.827,186.347l-80.213-88.149c-15.872-15.872-41.728-15.893-57.749,0.128c-5.077,5.077-8.533,11.157-10.325,17.643 c-15.957-12.224-38.976-11.008-53.675,3.691c-5.056,5.077-8.512,11.157-10.347,17.621c-15.957-12.181-38.976-10.987-53.653,3.712 c-4.971,4.971-8.384,10.901-10.24,17.216l-37.803-37.803c-15.872-15.872-41.728-15.893-57.749,0.128 c-15.893,15.872-15.893,41.728,0,57.621l145.237,145.237l-86.144,13.525c-23.275,3.328-40.832,23.552-40.832,47.083 @@ -31,14 +42,17 @@ const FingerTouchIcon = function (props: Props) { c0.021,0.021,0.021,0.021,0.021,0.021h0.021c0.021,0,0.021,0.021,0.021,0.021c4.181,3.968,10.795,3.883,14.869-0.213 c4.16-4.16,4.16-10.923,0-15.083l-0.917-0.917c-3.669-3.669-5.696-8.555-5.696-13.739s2.005-10.048,5.803-13.845 c7.595-7.552,19.883-7.531,27.115-0.363l79.872,87.787C439.125,218.389,448,241.301,448,265.216 - C448,290.816,438.037,314.88,419.925,332.992z"/> - <path className={style.wave} d="M183.381,109.931C167.851,75.563,133.547,53.333,96,53.333c-52.928,0-96,43.072-96,96 + C448,290.816,438.037,314.88,419.925,332.992z" + /> + <path + className={style.wave} + d="M183.381,109.931C167.851,75.563,133.547,53.333,96,53.333c-52.928,0-96,43.072-96,96 c0,37.547,22.229,71.851,56.597,87.403c1.429,0.64,2.923,0.939,4.395,0.939c4.053,0,7.936-2.347,9.728-6.272 c2.411-5.376,0.021-11.691-5.333-14.123c-26.752-12.096-44.053-38.763-44.053-67.947c0-41.173,33.493-74.667,74.667-74.667 c29.184,0,55.851,17.301,67.947,44.053c2.411,5.376,8.747,7.787,14.101,5.333C183.424,121.621,185.813,115.307,183.381,109.931z" - /> + /> </svg> - ) -} + ); +}; -export default FingerTouchIcon
\ No newline at end of file +export default FingerTouchIcon; diff --git a/web/src/components/FixedTextField.test.tsx b/web/src/components/FixedTextField.test.tsx index 4b7e19da5..af6b8a4ab 100644 --- a/web/src/components/FixedTextField.test.tsx +++ b/web/src/components/FixedTextField.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { mount } from "enzyme"; + import FixedTextField from "./FixedTextField"; -it('renders without crashing', () => { +it("renders without crashing", () => { mount(<FixedTextField />); -});
\ No newline at end of file +}); diff --git a/web/src/components/FixedTextField.tsx b/web/src/components/FixedTextField.tsx index 5e9c0f3b6..54febc1d5 100644 --- a/web/src/components/FixedTextField.tsx +++ b/web/src/components/FixedTextField.tsx @@ -1,34 +1,37 @@ import React from "react"; -import TextField, { TextFieldProps } from "@material-ui/core/TextField"; + import { makeStyles } from "@material-ui/core"; +import TextField, { TextFieldProps } from "@material-ui/core/TextField"; /** * This component fixes outlined TextField * https://github.com/mui-org/material-ui/issues/14530#issuecomment-463576879 - * + * * @param props the TextField props */ const FixedTextField = function (props: TextFieldProps) { const style = useStyles(); return ( - <TextField {...props} + <TextField + {...props} InputLabelProps={{ classes: { - root: style.label - } + root: style.label, + }, }} - inputProps={{autoCapitalize: props.autoCapitalize}}> + inputProps={{ autoCapitalize: props.autoCapitalize }} + > {props.children} </TextField> ); -} +}; -export default FixedTextField +export default FixedTextField; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ label: { backgroundColor: theme.palette.background.default, paddingLeft: theme.spacing(0.1), paddingRight: theme.spacing(0.1), - } -}));
\ No newline at end of file + }, +})); diff --git a/web/src/components/InformationIcon.test.tsx b/web/src/components/InformationIcon.test.tsx index d1fa2b5c9..8bd2ca559 100644 --- a/web/src/components/InformationIcon.test.tsx +++ b/web/src/components/InformationIcon.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { mount } from "enzyme"; + import InformationIcon from "./InformationIcon"; -it('renders without crashing', () => { +it("renders without crashing", () => { mount(<InformationIcon />); -});
\ No newline at end of file +}); diff --git a/web/src/components/InformationIcon.tsx b/web/src/components/InformationIcon.tsx index 357cd5e66..91b3f033a 100644 --- a/web/src/components/InformationIcon.tsx +++ b/web/src/components/InformationIcon.tsx @@ -1,13 +1,12 @@ import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -export interface Props { } +export interface Props {} const InformationIcon = function (props: Props) { - return ( - <FontAwesomeIcon icon={faInfoCircle} size="4x" color="#5858ff" className="information-icon" /> - ) -} + return <FontAwesomeIcon icon={faInfoCircle} size="4x" color="#5858ff" className="information-icon" />; +}; -export default InformationIcon
\ No newline at end of file +export default InformationIcon; diff --git a/web/src/components/LinearProgressBar.test.tsx b/web/src/components/LinearProgressBar.test.tsx index 25441a003..e436bc5bb 100644 --- a/web/src/components/LinearProgressBar.test.tsx +++ b/web/src/components/LinearProgressBar.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { mount } from "enzyme"; + import LinearProgressBar from "./LinearProgressBar"; -it('renders without crashing', () => { +it("renders without crashing", () => { mount(<LinearProgressBar value={40} />); -});
\ No newline at end of file +}); diff --git a/web/src/components/LinearProgressBar.tsx b/web/src/components/LinearProgressBar.tsx index b28276226..96fae97ab 100644 --- a/web/src/components/LinearProgressBar.tsx +++ b/web/src/components/LinearProgressBar.tsx @@ -1,4 +1,5 @@ import React from "react"; + import { makeStyles, LinearProgress } from "@material-ui/core"; import { CSSProperties } from "@material-ui/styles"; @@ -10,13 +11,13 @@ export interface Props { } const LinearProgressBar = function (props: Props) { - const style = makeStyles(theme => ({ + const style = makeStyles((theme) => ({ progressRoot: { height: props.height ? props.height : theme.spacing(), }, transition: { transition: "transform .2s linear", - } + }, }))(); return ( <LinearProgress @@ -24,11 +25,12 @@ const LinearProgressBar = function (props: Props) { variant="determinate" classes={{ root: style.progressRoot, - bar1Determinate: style.transition + bar1Determinate: style.transition, }} value={props.value} - className={props.className} /> - ) -} + className={props.className} + /> + ); +}; -export default LinearProgressBar
\ No newline at end of file +export default LinearProgressBar; diff --git a/web/src/components/NotificationBar.test.tsx b/web/src/components/NotificationBar.test.tsx index bc0b536fa..26fc81f44 100644 --- a/web/src/components/NotificationBar.test.tsx +++ b/web/src/components/NotificationBar.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { mount } from "enzyme"; + import NotificationBar from "./NotificationBar"; -it('renders without crashing', () => { - mount(<NotificationBar onClose={() => { }} />); -});
\ No newline at end of file +it("renders without crashing", () => { + mount(<NotificationBar onClose={() => {}} />); +}); diff --git a/web/src/components/NotificationBar.tsx b/web/src/components/NotificationBar.tsx index 119692665..f4202bf4e 100644 --- a/web/src/components/NotificationBar.tsx +++ b/web/src/components/NotificationBar.tsx @@ -1,8 +1,10 @@ import React, { useState, useEffect } from "react"; + import { Snackbar } from "@material-ui/core"; -import ColoredSnackbarContent from "./ColoredSnackbarContent"; + import { useNotifications } from "../hooks/NotificationsContext"; import { Notification } from "../models/Notifications"; +import ColoredSnackbarContent from "./ColoredSnackbarContent"; export interface Props { onClose: () => void; @@ -26,13 +28,15 @@ const NotificationBar = function (props: Props) { anchorOrigin={{ vertical: "top", horizontal: "right" }} autoHideDuration={tmpNotification ? tmpNotification.timeout * 1000 : 10000} onClose={props.onClose} - onExited={() => setTmpNotification(null)}> + onExited={() => setTmpNotification(null)} + > <ColoredSnackbarContent className="notification" level={tmpNotification ? tmpNotification.level : "info"} - message={tmpNotification ? tmpNotification.message : ""} /> + message={tmpNotification ? tmpNotification.message : ""} + /> </Snackbar> - ) -} + ); +}; -export default NotificationBar
\ No newline at end of file +export default NotificationBar; diff --git a/web/src/components/PieChartIcon.test.tsx b/web/src/components/PieChartIcon.test.tsx index ee9a6b8c8..2a59a6c7c 100644 --- a/web/src/components/PieChartIcon.test.tsx +++ b/web/src/components/PieChartIcon.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { mount } from "enzyme"; + import PieChartIcon from "./PieChartIcon"; -it('renders without crashing', () => { +it("renders without crashing", () => { mount(<PieChartIcon progress={40} />); -});
\ No newline at end of file +}); diff --git a/web/src/components/PieChartIcon.tsx b/web/src/components/PieChartIcon.tsx index f04448bad..8d6e35ccd 100644 --- a/web/src/components/PieChartIcon.tsx +++ b/web/src/components/PieChartIcon.tsx @@ -23,13 +23,18 @@ const PieChartIcon = function (props: Props) { <svg height={`${width}`} width={`${height}`} viewBox="0 0 26 26"> <circle r="12" cx="13" cy="13" fill="none" stroke={backgroundColor} strokeWidth="2" /> <circle r="9" cx="13" cy="13" fill={backgroundColor} stroke="transparent" /> - <circle r="5" cx="13" cy="13" fill="none" + <circle + r="5" + cx="13" + cy="13" + fill="none" stroke={color} strokeWidth="10" strokeDasharray={`${props.progress} ${maxProgress}`} - transform="rotate(-90) translate(-26)" /> + transform="rotate(-90) translate(-26)" + /> </svg> - ) -} + ); +}; -export default PieChartIcon
\ No newline at end of file +export default PieChartIcon; diff --git a/web/src/components/PushNotificationIcon.test.tsx b/web/src/components/PushNotificationIcon.test.tsx index de0453057..e11dad583 100644 --- a/web/src/components/PushNotificationIcon.test.tsx +++ b/web/src/components/PushNotificationIcon.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { mount } from "enzyme"; + import PushNotificationIcon from "./PushNotificationIcon"; -it('renders without crashing', () => { +it("renders without crashing", () => { mount(<PushNotificationIcon width={32} height={32} />); -});
\ No newline at end of file +}); diff --git a/web/src/components/PushNotificationIcon.tsx b/web/src/components/PushNotificationIcon.tsx index 209e5caef..9842b7ec9 100644 --- a/web/src/components/PushNotificationIcon.tsx +++ b/web/src/components/PushNotificationIcon.tsx @@ -1,6 +1,7 @@ import React from "react"; + +import { useIntermittentClass } from "../hooks/IntermittentClass"; import style from "./PushNotificationIcon.module.css"; -import {useIntermittentClass} from "../hooks/IntermittentClass"; export interface Props { width: number; @@ -13,34 +14,59 @@ const PushNotificationIcon = function (props: Props) { const idleMilliseconds = 2500; const wiggleMilliseconds = 500; const startMilliseconds = 500; - const wiggleClass = useIntermittentClass((props.animated) ? style.wiggle : "", wiggleMilliseconds, idleMilliseconds, startMilliseconds); + const wiggleClass = useIntermittentClass( + props.animated ? style.wiggle : "", + wiggleMilliseconds, + idleMilliseconds, + startMilliseconds, + ); return ( <svg x="0px" y="0px" viewBox="0 0 60 60" width={props.width} height={props.height} className={wiggleClass}> <g> - <path className="case" d="M42.595,0H17.405C14.977,0,13,1.977,13,4.405v51.189C13,58.023,14.977,60,17.405,60h25.189C45.023,60,47,58.023,47,55.595 + <path + className="case" + d="M42.595,0H17.405C14.977,0,13,1.977,13,4.405v51.189C13,58.023,14.977,60,17.405,60h25.189C45.023,60,47,58.023,47,55.595 V4.405C47,1.977,45.023,0,42.595,0z M15,8h30v38H15V8z M17.405,2h25.189C43.921,2,45,3.079,45,4.405V6H15V4.405 - C15,3.079,16.079,2,17.405,2z M42.595,58H17.405C16.079,58,15,56.921,15,55.595V48h30v7.595C45,56.921,43.921,58,42.595,58z"/> - <path className="button" d="M30,49c-2.206,0-4,1.794-4,4s1.794,4,4,4s4-1.794,4-4S32.206,49,30,49z M30,55c-1.103,0-2-0.897-2-2s0.897-2,2-2 - s2,0.897,2,2S31.103,55,30,55z"/> - <path className="speaker" d="M26,5h4c0.553,0,1-0.447,1-1s-0.447-1-1-1h-4c-0.553,0-1,0.447-1,1S25.447,5,26,5z"/> - <path className="camera" d="M33,5h1c0.553,0,1-0.447,1-1s-0.447-1-1-1h-1c-0.553,0-1,0.447-1,1S32.447,5,33,5z"/> + C15,3.079,16.079,2,17.405,2z M42.595,58H17.405C16.079,58,15,56.921,15,55.595V48h30v7.595C45,56.921,43.921,58,42.595,58z" + /> + <path + className="button" + d="M30,49c-2.206,0-4,1.794-4,4s1.794,4,4,4s4-1.794,4-4S32.206,49,30,49z M30,55c-1.103,0-2-0.897-2-2s0.897-2,2-2 + s2,0.897,2,2S31.103,55,30,55z" + /> + <path + className="speaker" + d="M26,5h4c0.553,0,1-0.447,1-1s-0.447-1-1-1h-4c-0.553,0-1,0.447-1,1S25.447,5,26,5z" + /> + <path + className="camera" + d="M33,5h1c0.553,0,1-0.447,1-1s-0.447-1-1-1h-1c-0.553,0-1,0.447-1,1S32.447,5,33,5z" + /> </g> - - <path d="M56.612,4.569c-0.391-0.391-1.023-0.391-1.414,0s-0.391,1.023,0,1.414c3.736,3.736,3.736,9.815,0,13.552 + + <path + d="M56.612,4.569c-0.391-0.391-1.023-0.391-1.414,0s-0.391,1.023,0,1.414c3.736,3.736,3.736,9.815,0,13.552 c-0.391,0.391-0.391,1.023,0,1.414c0.195,0.195,0.451,0.293,0.707,0.293s0.512-0.098,0.707-0.293 - C61.128,16.434,61.128,9.085,56.612,4.569z"/> - <path d="M52.401,6.845c-0.391-0.391-1.023-0.391-1.414,0s-0.391,1.023,0,1.414c1.237,1.237,1.918,2.885,1.918,4.639 + C61.128,16.434,61.128,9.085,56.612,4.569z" + /> + <path + d="M52.401,6.845c-0.391-0.391-1.023-0.391-1.414,0s-0.391,1.023,0,1.414c1.237,1.237,1.918,2.885,1.918,4.639 s-0.681,3.401-1.918,4.638c-0.391,0.391-0.391,1.023,0,1.414c0.195,0.195,0.451,0.293,0.707,0.293s0.512-0.098,0.707-0.293 - c1.615-1.614,2.504-3.764,2.504-6.052S54.017,8.459,52.401,6.845z"/> - <path d="M4.802,5.983c0.391-0.391,0.391-1.023,0-1.414s-1.023-0.391-1.414,0c-4.516,4.516-4.516,11.864,0,16.38 + c1.615-1.614,2.504-3.764,2.504-6.052S54.017,8.459,52.401,6.845z" + /> + <path + d="M4.802,5.983c0.391-0.391,0.391-1.023,0-1.414s-1.023-0.391-1.414,0c-4.516,4.516-4.516,11.864,0,16.38 c0.195,0.195,0.451,0.293,0.707,0.293s0.512-0.098,0.707-0.293c0.391-0.391,0.391-1.023,0-1.414 - C1.065,15.799,1.065,9.72,4.802,5.983z"/> - <path d="M9.013,6.569c-0.391-0.391-1.023-0.391-1.414,0c-1.615,1.614-2.504,3.764-2.504,6.052s0.889,4.438,2.504,6.053 + C1.065,15.799,1.065,9.72,4.802,5.983z" + /> + <path + d="M9.013,6.569c-0.391-0.391-1.023-0.391-1.414,0c-1.615,1.614-2.504,3.764-2.504,6.052s0.889,4.438,2.504,6.053 c0.195,0.195,0.451,0.293,0.707,0.293s0.512-0.098,0.707-0.293c0.391-0.391,0.391-1.023,0-1.414 - c-1.237-1.237-1.918-2.885-1.918-4.639S7.775,9.22,9.013,7.983C9.403,7.593,9.403,6.96,9.013,6.569z"/> + c-1.237-1.237-1.918-2.885-1.918-4.639S7.775,9.22,9.013,7.983C9.403,7.593,9.403,6.96,9.013,6.569z" + /> </svg> - ) -} + ); +}; -export default PushNotificationIcon
\ No newline at end of file +export default PushNotificationIcon; diff --git a/web/src/components/SuccessIcon.test.tsx b/web/src/components/SuccessIcon.test.tsx index 15d796210..b7e460936 100644 --- a/web/src/components/SuccessIcon.test.tsx +++ b/web/src/components/SuccessIcon.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { mount } from "enzyme"; + import SuccessIcon from "./SuccessIcon"; -it('renders without crashing', () => { +it("renders without crashing", () => { mount(<SuccessIcon />); -});
\ No newline at end of file +}); diff --git a/web/src/components/SuccessIcon.tsx b/web/src/components/SuccessIcon.tsx index b01482d04..fd4deaa77 100644 --- a/web/src/components/SuccessIcon.tsx +++ b/web/src/components/SuccessIcon.tsx @@ -1,11 +1,10 @@ import React from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; const SuccessIcon = function () { - return ( - <FontAwesomeIcon icon={faCheckCircle} size="4x" color="green" className="success-icon" /> - ) -} + return <FontAwesomeIcon icon={faCheckCircle} size="4x" color="green" className="success-icon" />; +}; -export default SuccessIcon
\ No newline at end of file +export default SuccessIcon; diff --git a/web/src/components/TimerIcon.test.tsx b/web/src/components/TimerIcon.test.tsx index 692cd74e8..9c2a8a36d 100644 --- a/web/src/components/TimerIcon.test.tsx +++ b/web/src/components/TimerIcon.test.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React from "react"; + import { mount } from "enzyme"; + import TimerIcon from "./TimerIcon"; -it('renders without crashing', () => { +it("renders without crashing", () => { mount(<TimerIcon width={32} height={32} />); -});
\ No newline at end of file +}); diff --git a/web/src/components/TimerIcon.tsx b/web/src/components/TimerIcon.tsx index 9a185f005..48d3510e0 100644 --- a/web/src/components/TimerIcon.tsx +++ b/web/src/components/TimerIcon.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; + import PieChartIcon from "./PieChartIcon"; export interface Props { @@ -16,21 +17,26 @@ const TimerIcon = function (props: Props) { useEffect(() => { // Get the current number of seconds to initialize timer. - const initialValue = (new Date().getTime() / 1000) % props.period / props.period * radius; + const initialValue = (((new Date().getTime() / 1000) % props.period) / props.period) * radius; setTimeProgress(initialValue); const interval = setInterval(() => { - const value = (new Date().getTime() / 1000) % props.period / props.period * radius; + const value = (((new Date().getTime() / 1000) % props.period) / props.period) * radius; setTimeProgress(value); }, 100); return () => clearInterval(interval); }, [props]); return ( - <PieChartIcon width={props.width} height={props.height} - progress={timeProgress} maxProgress={radius} - backgroundColor={props.backgroundColor} color={props.color} /> - ) -} + <PieChartIcon + width={props.width} + height={props.height} + progress={timeProgress} + maxProgress={radius} + backgroundColor={props.backgroundColor} + color={props.color} + /> + ); +}; -export default TimerIcon
\ No newline at end of file +export default TimerIcon; diff --git a/web/src/constants.ts b/web/src/constants.ts index bcb675931..3ed6cf701 100644 --- a/web/src/constants.ts +++ b/web/src/constants.ts @@ -1,5 +1,4 @@ - export const GoogleAuthenticator = { googlePlay: "https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en_us", appleStore: "https://apps.apple.com/us/app/google-authenticator/id388497605", -};
\ No newline at end of file +}; diff --git a/web/src/hooks/Configuration.ts b/web/src/hooks/Configuration.ts index 921532b3b..bc3f6a3ec 100644 --- a/web/src/hooks/Configuration.ts +++ b/web/src/hooks/Configuration.ts @@ -1,6 +1,6 @@ -import { useRemoteCall } from "./RemoteCall"; import { getConfiguration } from "../services/Configuration"; +import { useRemoteCall } from "./RemoteCall"; export function useConfiguration() { return useRemoteCall(getConfiguration, []); -}
\ No newline at end of file +} diff --git a/web/src/hooks/IntermittentClass.ts b/web/src/hooks/IntermittentClass.ts index 88bcaed57..4cb028d63 100644 --- a/web/src/hooks/IntermittentClass.ts +++ b/web/src/hooks/IntermittentClass.ts @@ -4,7 +4,8 @@ export function useIntermittentClass( classname: string, activeMilliseconds: number, inactiveMillisecond: number, - startMillisecond?: number) { + startMillisecond?: number, +) { const [currentClass, setCurrentClass] = useState(""); const [firstTime, setFirstTime] = useState(true); @@ -34,4 +35,4 @@ export function useIntermittentClass( }, [currentClass, classname, activeMilliseconds, inactiveMillisecond, startMillisecond, firstTime]); return currentClass; -}
\ No newline at end of file +} diff --git a/web/src/hooks/Mounted.ts b/web/src/hooks/Mounted.ts index be4ab3a55..96efb7c2b 100644 --- a/web/src/hooks/Mounted.ts +++ b/web/src/hooks/Mounted.ts @@ -4,7 +4,9 @@ export function useIsMountedRef() { const isMountedRef = useRef(false); useEffect(() => { isMountedRef.current = true; - return () => { isMountedRef.current = false }; + return () => { + isMountedRef.current = false; + }; }); return isMountedRef; -}
\ No newline at end of file +} diff --git a/web/src/hooks/NotificationsContext.ts b/web/src/hooks/NotificationsContext.ts index 48ff7b0a5..0cacc3c53 100644 --- a/web/src/hooks/NotificationsContext.ts +++ b/web/src/hooks/NotificationsContext.ts @@ -1,33 +1,33 @@ -import { Level } from "../components/ColoredSnackbarContent"; import { useCallback, createContext, useContext } from "react"; + +import { Level } from "../components/ColoredSnackbarContent"; import { Notification } from "../models/Notifications"; const defaultOptions = { timeout: 5, -} +}; interface NotificationContextProps { notification: Notification | null; setNotification: (n: Notification | null) => void; } -const NotificationsContext = createContext<NotificationContextProps>( - { notification: null, setNotification: () => { } }); +const NotificationsContext = createContext<NotificationContextProps>({ notification: null, setNotification: () => {} }); export default NotificationsContext; - export function useNotifications() { let useNotificationsProps = useContext(NotificationsContext); const notificationBuilder = (level: Level) => { return (message: string, timeout?: number) => { useNotificationsProps.setNotification({ - level, message, - timeout: timeout ? timeout : defaultOptions.timeout + level, + message, + timeout: timeout ? timeout : defaultOptions.timeout, }); - } - } + }; + }; const resetNotification = () => useNotificationsProps.setNotification(null); /* eslint-disable react-hooks/exhaustive-deps */ @@ -38,7 +38,6 @@ export function useNotifications() { /* eslint-enable react-hooks/exhaustive-deps */ const isActive = useNotificationsProps.notification !== null; - return { notification: useNotificationsProps.notification, resetNotification, @@ -46,6 +45,6 @@ export function useNotifications() { createSuccessNotification, createWarnNotification, createErrorNotification, - isActive - } -}
\ No newline at end of file + isActive, + }; +} diff --git a/web/src/hooks/RedirectionURL.ts b/web/src/hooks/RedirectionURL.ts index 670be718a..311cc27ba 100644 --- a/web/src/hooks/RedirectionURL.ts +++ b/web/src/hooks/RedirectionURL.ts @@ -4,7 +4,5 @@ import { useLocation } from "react-router"; export function useRedirectionURL() { const location = useLocation(); const queryParams = queryString.parse(location.search); - return (queryParams && "rd" in queryParams) - ? queryParams["rd"] as string - : undefined; -}
\ No newline at end of file + return queryParams && "rd" in queryParams ? (queryParams["rd"] as string) : undefined; +} diff --git a/web/src/hooks/RemoteCall.ts b/web/src/hooks/RemoteCall.ts index 8136aa46a..a33afb37f 100644 --- a/web/src/hooks/RemoteCall.ts +++ b/web/src/hooks/RemoteCall.ts @@ -1,9 +1,11 @@ import { useState, useCallback, DependencyList } from "react"; -type PromisifiedFunction<Ret> = (...args: any) => Promise<Ret> +type PromisifiedFunction<Ret> = (...args: any) => Promise<Ret>; -export function useRemoteCall<Ret>(fn: PromisifiedFunction<Ret>, deps: DependencyList) - : [Ret | undefined, PromisifiedFunction<void>, boolean, Error | undefined] { +export function useRemoteCall<Ret>( + fn: PromisifiedFunction<Ret>, + deps: DependencyList, +): [Ret | undefined, PromisifiedFunction<void>, boolean, Error | undefined] { const [data, setData] = useState(undefined as Ret | undefined); const [inProgress, setInProgress] = useState(false); const [error, setError] = useState(undefined as Error | undefined); @@ -22,10 +24,5 @@ export function useRemoteCall<Ret>(fn: PromisifiedFunction<Ret>, deps: Dependenc } }, [setInProgress, setError, fnCallback]); - return [ - data, - triggerCallback, - inProgress, - error, - ] -}
\ No newline at end of file + return [data, triggerCallback, inProgress, error]; +} diff --git a/web/src/hooks/State.ts b/web/src/hooks/State.ts index c33a6cf7b..f3c16e8b9 100644 --- a/web/src/hooks/State.ts +++ b/web/src/hooks/State.ts @@ -3,4 +3,4 @@ import { useRemoteCall } from "./RemoteCall"; export function useAutheliaState() { return useRemoteCall(getState, []); -}
\ No newline at end of file +} diff --git a/web/src/hooks/Timer.ts b/web/src/hooks/Timer.ts index 5e3c95074..63515f36b 100644 --- a/web/src/hooks/Timer.ts +++ b/web/src/hooks/Timer.ts @@ -21,8 +21,8 @@ export function useTimer(timeoutMs: number): [number, () => void, () => void] { } const intervalNode = setInterval(() => { - const elapsedMs = (startDate) ? new Date().getTime() - startDate.getTime() : 0; - let p = elapsedMs / timeoutMs * 100.0; + const elapsedMs = startDate ? new Date().getTime() - startDate.getTime() : 0; + let p = (elapsedMs / timeoutMs) * 100.0; if (p >= 100) { p = 100; setStartDate(undefined); @@ -33,9 +33,5 @@ export function useTimer(timeoutMs: number): [number, () => void, () => void] { return () => clearInterval(intervalNode); }, [startDate, setPercent, setStartDate, timeoutMs]); - return [ - percent, - trigger, - clear, - ] -}
\ No newline at end of file + return [percent, trigger, clear]; +} diff --git a/web/src/hooks/UserInfo.ts b/web/src/hooks/UserInfo.ts index ad00a0864..96e34f443 100644 --- a/web/src/hooks/UserInfo.ts +++ b/web/src/hooks/UserInfo.ts @@ -3,4 +3,4 @@ import { useRemoteCall } from "./RemoteCall"; export function useUserPreferences() { return useRemoteCall(getUserPreferences, []); -}
\ No newline at end of file +} diff --git a/web/src/index.tsx b/web/src/index.tsx index cff565f93..1fb7ad7b9 100644 --- a/web/src/index.tsx +++ b/web/src/index.tsx @@ -1,11 +1,13 @@ -import './utils/AssetPath'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; -import * as serviceWorker from './serviceWorker'; - -ReactDOM.render(<App />, document.getElementById('root')); +import "./utils/AssetPath"; +import React from "react"; + +import ReactDOM from "react-dom"; + +import "./index.css"; +import App from "./App"; +import * as serviceWorker from "./serviceWorker"; + +ReactDOM.render(<App />, document.getElementById("root")); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. diff --git a/web/src/layouts/LoginLayout.tsx b/web/src/layouts/LoginLayout.tsx index a5da78b89..20017c388 100644 --- a/web/src/layouts/LoginLayout.tsx +++ b/web/src/layouts/LoginLayout.tsx @@ -1,8 +1,9 @@ import React, { ReactNode } from "react"; + import { Grid, makeStyles, Container, Typography, Link } from "@material-ui/core"; -import { ReactComponent as UserSvg } from "../assets/images/user.svg"; import { grey } from "@material-ui/core/colors"; +import { ReactComponent as UserSvg } from "../assets/images/user.svg"; export interface Props { id?: string; @@ -14,13 +15,7 @@ export interface Props { const LoginLayout = function (props: Props) { const style = useStyles(); return ( - <Grid - id={props.id} - className={style.root} - container - spacing={0} - alignItems="center" - justify="center"> + <Grid id={props.id} className={style.root} container spacing={0} alignItems="center" justify="center"> <Container maxWidth="xs" className={style.rootContainer}> <Grid container> <Grid item xs={12}> @@ -34,27 +29,28 @@ const LoginLayout = function (props: Props) { <Grid item xs={12} className={style.body}> {props.children} </Grid> - {props.showBrand ? <Grid item xs={12}> - <Link - href="https://github.com/authelia/authelia" - target="_blank" - className={style.poweredBy}> - Powered by Authelia - </Link> - </Grid> - : null - } + {props.showBrand ? ( + <Grid item xs={12}> + <Link + href="https://github.com/authelia/authelia" + target="_blank" + className={style.poweredBy} + > + Powered by Authelia + </Link> + </Grid> + ) : null} </Grid> </Container> </Grid> ); -} +}; -export default LoginLayout +export default LoginLayout; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { - minHeight: '90vh', + minHeight: "90vh", textAlign: "center", // marginTop: theme.spacing(10), }, @@ -71,5 +67,5 @@ const useStyles = makeStyles(theme => ({ poweredBy: { fontSize: "0.7em", color: grey[500], - } -}))
\ No newline at end of file + }, +})); diff --git a/web/src/models/Configuration.ts b/web/src/models/Configuration.ts index ba04e7f96..01bd10ef0 100644 --- a/web/src/models/Configuration.ts +++ b/web/src/models/Configuration.ts @@ -4,4 +4,4 @@ export interface Configuration { available_methods: Set<SecondFactorMethod>; second_factor_enabled: boolean; totp_period: number; -}
\ No newline at end of file +} diff --git a/web/src/models/Methods.ts b/web/src/models/Methods.ts index ab80212bb..e075e9c7c 100644 --- a/web/src/models/Methods.ts +++ b/web/src/models/Methods.ts @@ -1,6 +1,5 @@ - export enum SecondFactorMethod { TOTP = 1, U2F = 2, - MobilePush = 3 + MobilePush = 3, } diff --git a/web/src/models/Notifications.ts b/web/src/models/Notifications.ts index 6b7d5d347..321a0c087 100644 --- a/web/src/models/Notifications.ts +++ b/web/src/models/Notifications.ts @@ -4,4 +4,4 @@ export interface Notification { message: string; level: Level; timeout: number; -}
\ No newline at end of file +} diff --git a/web/src/react-app-env.d.ts b/web/src/react-app-env.d.ts index ab49f1b52..2c004b416 100644 --- a/web/src/react-app-env.d.ts +++ b/web/src/react-app-env.d.ts @@ -1,2 +1,2 @@ /// <reference types="react-scripts" /> -declare var __webpack_public_path__: string;
\ No newline at end of file +declare var __webpack_public_path__: string; diff --git a/web/src/serviceWorker.ts b/web/src/serviceWorker.ts index 15d90cb81..56a6df78c 100644 --- a/web/src/serviceWorker.ts +++ b/web/src/serviceWorker.ts @@ -11,133 +11,123 @@ // opt-in, read https://bit.ly/CRA-PWA const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) + window.location.hostname === "localhost" || + // [::1] is the IPv6 localhost address. + window.location.hostname === "[::1]" || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/), ); type Config = { - onSuccess?: (registration: ServiceWorkerRegistration) => void; - onUpdate?: (registration: ServiceWorkerRegistration) => void; + onSuccess?: (registration: ServiceWorkerRegistration) => void; + onUpdate?: (registration: ServiceWorkerRegistration) => void; }; export function register(config?: Config) { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL( - (process as { env: { [key: string]: string } }).env.PUBLIC_URL, - window.location.href - ); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebook/create-react-app/issues/2374 - return; - } + if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL((process as { env: { [key: string]: string } }).env.PUBLIC_URL, window.location.href); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + window.addEventListener("load", () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - if (isLocalhost) { - // This is running on localhost. Let's check if a service worker still exists or not. - checkValidServiceWorker(swUrl, config); + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); - // Add some additional logging to localhost, pointing developers to the - // service worker/PWA documentation. - navigator.serviceWorker.ready.then(() => { - console.log( - 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' - ); + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + "This web app is being served cache-first by a service " + + "worker. To learn more, visit https://bit.ly/CRA-PWA", + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } }); - } else { - // Is not localhost. Just register service worker - registerValidSW(swUrl, config); - } - }); - } + } } function registerValidSW(swUrl: string, config?: Config) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - if (installingWorker == null) { - return; - } - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the updated precached content has been fetched, - // but the previous service worker will still serve the older - // content until all client tabs are closed. - console.log( - 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' - ); + navigator.serviceWorker + .register(swUrl) + .then((registration) => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { + return; + } + installingWorker.onstatechange = () => { + if (installingWorker.state === "installed") { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + "New content is available and will be used when all " + + "tabs for this page are closed. See https://bit.ly/CRA-PWA.", + ); - // Execute callback - if (config && config.onUpdate) { - config.onUpdate(registration); - } - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log("Content is cached for offline use."); - // Execute callback - if (config && config.onSuccess) { - config.onSuccess(registration); - } - } - } - }; - }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch((error) => { + console.error("Error during service worker registration:", error); + }); } function checkValidServiceWorker(swUrl: string, config?: Config) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - const contentType = response.headers.get('content-type'); - if ( - response.status === 404 || - (contentType != null && contentType.indexOf('javascript') === -1) - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then((response) => { + // Ensure service worker exists, and that we really are getting a JS file. + const contentType = response.headers.get("content-type"); + if (response.status === 404 || (contentType != null && contentType.indexOf("javascript") === -1)) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then((registration) => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log("No internet connection found. App is running in offline mode."); }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl, config); - } - }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); } export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); - } + if ("serviceWorker" in navigator) { + navigator.serviceWorker.ready.then((registration) => { + registration.unregister(); + }); + } } diff --git a/web/src/services/Api.ts b/web/src/services/Api.ts index 0ec8ae90f..2b5be2ce8 100644 --- a/web/src/services/Api.ts +++ b/web/src/services/Api.ts @@ -1,4 +1,5 @@ import { AxiosResponse } from "axios"; + import { getBasePath } from "../utils/BasePath"; const basePath = getBasePath(); @@ -14,13 +15,13 @@ export const CompleteU2FRegistrationStep2Path = basePath + "/api/secondfactor/u2 export const InitiateU2FSignInPath = basePath + "/api/secondfactor/u2f/sign_request"; export const CompleteU2FSignInPath = basePath + "/api/secondfactor/u2f/sign"; -export const CompletePushNotificationSignInPath = basePath + "/api/secondfactor/duo" -export const CompleteTOTPSignInPath = basePath + "/api/secondfactor/totp" +export const CompletePushNotificationSignInPath = basePath + "/api/secondfactor/duo"; +export const CompleteTOTPSignInPath = basePath + "/api/secondfactor/totp"; export const InitiateResetPasswordPath = basePath + "/api/reset-password/identity/start"; export const CompleteResetPasswordPath = basePath + "/api/reset-password/identity/finish"; // Do the password reset during completion. -export const ResetPasswordPath = basePath + "/api/reset-password" +export const ResetPasswordPath = basePath + "/api/reset-password"; export const LogoutPath = basePath + "/api/logout"; export const StatePath = basePath + "/api/state"; @@ -52,7 +53,7 @@ export function toData<T>(resp: AxiosResponse<ServiceResponse<T>>): T | undefine if (resp.data && "status" in resp.data && resp.data["status"] === "OK") { return resp.data.data as T; } - return undefined + return undefined; } export function hasServiceError<T>(resp: AxiosResponse<ServiceResponse<T>>) { @@ -61,4 +62,4 @@ export function hasServiceError<T>(resp: AxiosResponse<ServiceResponse<T>>) { return { errored: true, message: errResp.message }; } return { errored: false, message: null }; -}
\ No newline at end of file +} diff --git a/web/src/services/Client.ts b/web/src/services/Client.ts index e30d10148..56681446c 100644 --- a/web/src/services/Client.ts +++ b/web/src/services/Client.ts @@ -1,4 +1,5 @@ import axios from "axios"; + import { ServiceResponse, hasServiceError, toData } from "./Api"; export async function PostWithOptionalResponse<T = undefined>(path: string, body?: any) { @@ -30,4 +31,4 @@ export async function Get<T = undefined>(path: string): Promise<T> { throw new Error("unexpected type of response"); } return d; -}
\ No newline at end of file +} diff --git a/web/src/services/Configuration.ts b/web/src/services/Configuration.ts index 1c9651ab7..0d6bcdd32 100644 --- a/web/src/services/Configuration.ts +++ b/web/src/services/Configuration.ts @@ -1,7 +1,7 @@ -import { Get } from "./Client"; +import { Configuration } from "../models/Configuration"; import { ConfigurationPath } from "./Api"; +import { Get } from "./Client"; import { toEnum, Method2FA } from "./UserPreferences"; -import { Configuration } from "../models/Configuration"; interface ConfigurationPayload { available_methods: Method2FA[]; @@ -12,4 +12,4 @@ interface ConfigurationPayload { export async function getConfiguration(): Promise<Configuration> { const config = await Get<ConfigurationPayload>(ConfigurationPath); return { ...config, available_methods: new Set(config.available_methods.map(toEnum)) }; -}
\ No newline at end of file +} diff --git a/web/src/services/FirstFactor.ts b/web/src/services/FirstFactor.ts index 91c29b36d..3d9c24989 100644 --- a/web/src/services/FirstFactor.ts +++ b/web/src/services/FirstFactor.ts @@ -9,17 +9,16 @@ interface PostFirstFactorBody { targetURL?: string; } -export async function postFirstFactor( - username: string, password: string, - rememberMe: boolean, targetURL?: string) { +export async function postFirstFactor(username: string, password: string, rememberMe: boolean, targetURL?: string) { const data: PostFirstFactorBody = { - username, password, - keepMeLoggedIn: rememberMe + username, + password, + keepMeLoggedIn: rememberMe, }; if (targetURL) { data.targetURL = targetURL; } const res = await PostWithOptionalResponse<SignInResponse>(FirstFactorPath, data); - return res ? res : {} as SignInResponse; -}
\ No newline at end of file + return res ? res : ({} as SignInResponse); +} diff --git a/web/src/services/OneTimePassword.ts b/web/src/services/OneTimePassword.ts index fa892f9cc..687a6a863 100644 --- a/web/src/services/OneTimePassword.ts +++ b/web/src/services/OneTimePassword.ts @@ -1,5 +1,5 @@ -import { PostWithOptionalResponse } from "./Client"; import { CompleteTOTPSignInPath } from "./Api"; +import { PostWithOptionalResponse } from "./Client"; import { SignInResponse } from "./SignIn"; interface CompleteU2FSigninBody { @@ -13,4 +13,4 @@ export function completeTOTPSignIn(passcode: string, targetURL: string | undefin body.targetURL = targetURL; } return PostWithOptionalResponse<SignInResponse>(CompleteTOTPSignInPath, body); -}
\ No newline at end of file +} diff --git a/web/src/services/PushNotification.ts b/web/src/services/PushNotification.ts index a70ccef38..66bfefca6 100644 --- a/web/src/services/PushNotification.ts +++ b/web/src/services/PushNotification.ts @@ -1,5 +1,5 @@ -import { PostWithOptionalResponse } from "./Client"; import { CompletePushNotificationSignInPath } from "./Api"; +import { PostWithOptionalResponse } from "./Client"; import { SignInResponse } from "./SignIn"; interface CompleteU2FSigninBody { @@ -12,4 +12,4 @@ export function completePushNotificationSignIn(targetURL: string | undefined) { body.targetURL = targetURL; } return PostWithOptionalResponse<SignInResponse>(CompletePushNotificationSignInPath, body); -}
\ No newline at end of file +} diff --git a/web/src/services/RegisterDevice.ts b/web/src/services/RegisterDevice.ts index 67a57e358..ce4cb7614 100644 --- a/web/src/services/RegisterDevice.ts +++ b/web/src/services/RegisterDevice.ts @@ -1,9 +1,12 @@ +import U2fApi from "u2f-api"; + import { - InitiateTOTPRegistrationPath, CompleteTOTPRegistrationPath, - InitiateU2FRegistrationPath, CompleteU2FRegistrationStep1Path, - CompleteU2FRegistrationStep2Path + InitiateTOTPRegistrationPath, + CompleteTOTPRegistrationPath, + InitiateU2FRegistrationPath, + CompleteU2FRegistrationStep1Path, + CompleteU2FRegistrationStep2Path, } from "./Api"; -import U2fApi from "u2f-api"; import { Post, PostWithOptionalResponse } from "./Client"; export async function initiateTOTPRegistrationProcess() { @@ -16,28 +19,27 @@ interface CompleteTOTPRegistrationResponse { } export async function completeTOTPRegistrationProcess(processToken: string) { - return Post<CompleteTOTPRegistrationResponse>( - CompleteTOTPRegistrationPath, { token: processToken }); + return Post<CompleteTOTPRegistrationResponse>(CompleteTOTPRegistrationPath, { token: processToken }); } - export async function initiateU2FRegistrationProcess() { return PostWithOptionalResponse(InitiateU2FRegistrationPath); } interface U2RRegistrationStep1Response { - appId: string, - registerRequests: [{ - version: string, - challenge: string, - }] + appId: string; + registerRequests: [ + { + version: string; + challenge: string; + }, + ]; } export async function completeU2FRegistrationProcessStep1(processToken: string) { - return Post<U2RRegistrationStep1Response>( - CompleteU2FRegistrationStep1Path, { token: processToken }); + return Post<U2RRegistrationStep1Response>(CompleteU2FRegistrationStep1Path, { token: processToken }); } export async function completeU2FRegistrationProcessStep2(response: U2fApi.RegisterResponse) { return PostWithOptionalResponse(CompleteU2FRegistrationStep2Path, response); -}
\ No newline at end of file +} diff --git a/web/src/services/ResetPassword.ts b/web/src/services/ResetPassword.ts index 2d4819175..48612a892 100644 --- a/web/src/services/ResetPassword.ts +++ b/web/src/services/ResetPassword.ts @@ -1,7 +1,6 @@ import { InitiateResetPasswordPath, CompleteResetPasswordPath, ResetPasswordPath } from "./Api"; import { PostWithOptionalResponse } from "./Client"; - export async function initiateResetPasswordProcess(username: string) { return PostWithOptionalResponse(InitiateResetPasswordPath, { username }); } @@ -12,4 +11,4 @@ export async function completeResetPasswordProcess(token: string) { export async function resetPassword(newPassword: string) { return PostWithOptionalResponse(ResetPasswordPath, { password: newPassword }); -}
\ No newline at end of file +} diff --git a/web/src/services/SecurityKey.ts b/web/src/services/SecurityKey.ts index 8a593d962..7db823b5e 100644 --- a/web/src/services/SecurityKey.ts +++ b/web/src/services/SecurityKey.ts @@ -1,16 +1,17 @@ -import { Post, PostWithOptionalResponse } from "./Client"; -import { InitiateU2FSignInPath, CompleteU2FSignInPath } from "./Api"; import u2fApi from "u2f-api"; + +import { InitiateU2FSignInPath, CompleteU2FSignInPath } from "./Api"; +import { Post, PostWithOptionalResponse } from "./Client"; import { SignInResponse } from "./SignIn"; interface InitiateU2FSigninResponse { - appId: string, - challenge: string, + appId: string; + challenge: string; registeredKeys: { - appId: string, - keyHandle: string, - version: string, - }[] + appId: string; + keyHandle: string; + version: string; + }[]; } export async function initiateU2FSignin() { @@ -28,4 +29,4 @@ export function completeU2FSignin(signResponse: u2fApi.SignResponse, targetURL: body.targetURL = targetURL; } return PostWithOptionalResponse<SignInResponse>(CompleteU2FSignInPath, body); -}
\ No newline at end of file +} diff --git a/web/src/services/SignIn.ts b/web/src/services/SignIn.ts index 2dfceac68..219ffd846 100644 --- a/web/src/services/SignIn.ts +++ b/web/src/services/SignIn.ts @@ -1,2 +1 @@ - -export type SignInResponse = { redirect: string } | undefined;
\ No newline at end of file +export type SignInResponse = { redirect: string } | undefined; diff --git a/web/src/services/SignOut.ts b/web/src/services/SignOut.ts index a376ceb65..6209a132b 100644 --- a/web/src/services/SignOut.ts +++ b/web/src/services/SignOut.ts @@ -1,6 +1,6 @@ -import { PostWithOptionalResponse } from "./Client"; import { LogoutPath } from "./Api"; +import { PostWithOptionalResponse } from "./Client"; export async function signOut() { return PostWithOptionalResponse(LogoutPath); -}
\ No newline at end of file +} diff --git a/web/src/services/State.ts b/web/src/services/State.ts index 5b1830983..b653952e8 100644 --- a/web/src/services/State.ts +++ b/web/src/services/State.ts @@ -1,5 +1,5 @@ -import { Get } from "./Client"; import { StatePath } from "./Api"; +import { Get } from "./Client"; export enum AuthenticationLevel { Unauthenticated = 0, @@ -9,9 +9,9 @@ export enum AuthenticationLevel { export interface AutheliaState { username: string; - authentication_level: AuthenticationLevel + authentication_level: AuthenticationLevel; } export async function getState(): Promise<AutheliaState> { return Get<AutheliaState>(StatePath); -}
\ No newline at end of file +} diff --git a/web/src/services/UserPreferences.ts b/web/src/services/UserPreferences.ts index c63db1b52..9fbb2a81d 100644 --- a/web/src/services/UserPreferences.ts +++ b/web/src/services/UserPreferences.ts @@ -1,7 +1,7 @@ -import { Get, PostWithOptionalResponse } from "./Client"; -import { UserInfoPath, UserInfo2FAMethodPath } from "./Api"; import { SecondFactorMethod } from "../models/Methods"; import { UserInfo } from "../models/UserInfo"; +import { UserInfoPath, UserInfo2FAMethodPath } from "./Api"; +import { Get, PostWithOptionalResponse } from "./Client"; export type Method2FA = "u2f" | "totp" | "mobile_push"; @@ -44,6 +44,5 @@ export async function getUserPreferences(): Promise<UserInfo> { } export function setPreferred2FAMethod(method: SecondFactorMethod) { - return PostWithOptionalResponse(UserInfo2FAMethodPath, - { method: toString(method) } as MethodPreferencePayload); -}
\ No newline at end of file + return PostWithOptionalResponse(UserInfo2FAMethodPath, { method: toString(method) } as MethodPreferencePayload); +} diff --git a/web/src/setupTests.js b/web/src/setupTests.js index 21fc6bb60..81b86e1d0 100644 --- a/web/src/setupTests.js +++ b/web/src/setupTests.js @@ -1,5 +1,5 @@ -import { configure } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; +import { configure } from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; document.body.setAttribute("data-basepath", ""); document.body.setAttribute("data-rememberme", "true"); document.body.setAttribute("data-resetpassword", "true"); diff --git a/web/src/utils/AssetPath.ts b/web/src/utils/AssetPath.ts index 358130b89..8ff5e9f47 100644 --- a/web/src/utils/AssetPath.ts +++ b/web/src/utils/AssetPath.ts @@ -1,7 +1,7 @@ import { getBasePath } from "./BasePath"; -__webpack_public_path__ = "/" +__webpack_public_path__ = "/"; if (getBasePath() !== "") { - __webpack_public_path__ = getBasePath() + "/" -}
\ No newline at end of file + __webpack_public_path__ = getBasePath() + "/"; +} diff --git a/web/src/utils/BasePath.ts b/web/src/utils/BasePath.ts index 5c5553c94..f09d231ef 100644 --- a/web/src/utils/BasePath.ts +++ b/web/src/utils/BasePath.ts @@ -2,4 +2,4 @@ import { getEmbeddedVariable } from "./Configuration"; export function getBasePath() { return getEmbeddedVariable("basepath"); -}
\ No newline at end of file +} diff --git a/web/src/utils/Configuration.ts b/web/src/utils/Configuration.ts index e8d42946d..81e279974 100644 --- a/web/src/utils/Configuration.ts +++ b/web/src/utils/Configuration.ts @@ -1,16 +1,16 @@ export function getEmbeddedVariable(variableName: string) { - const value = document.body.getAttribute(`data-${variableName}`); - if (value === null) { - throw new Error(`No ${variableName} embedded variable detected`); - } + const value = document.body.getAttribute(`data-${variableName}`); + if (value === null) { + throw new Error(`No ${variableName} embedded variable detected`); + } - return value; + return value; } export function getRememberMe() { - return getEmbeddedVariable("rememberme") === "true"; + return getEmbeddedVariable("rememberme") === "true"; } export function getResetPassword() { - return getEmbeddedVariable("resetpassword") === "true"; -}
\ No newline at end of file + return getEmbeddedVariable("resetpassword") === "true"; +} diff --git a/web/src/utils/IdentityToken.ts b/web/src/utils/IdentityToken.ts index 67269d478..7e1960015 100644 --- a/web/src/utils/IdentityToken.ts +++ b/web/src/utils/IdentityToken.ts @@ -2,7 +2,5 @@ import queryString from "query-string"; export function extractIdentityToken(locationSearch: string) { const queryParams = queryString.parse(locationSearch); - return (queryParams && "token" in queryParams) - ? queryParams["token"] as string - : null; -}
\ No newline at end of file + return queryParams && "token" in queryParams ? (queryParams["token"] as string) : null; +} diff --git a/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx b/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx index 93219dbc4..9f126e139 100644 --- a/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx +++ b/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx @@ -1,18 +1,20 @@ import React, { useEffect, useCallback, useState } from "react"; -import LoginLayout from "../../layouts/LoginLayout"; -import classnames from "classnames"; + +import { IconDefinition, faCopy, faKey, faTimesCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { makeStyles, Typography, Button, IconButton, Link, CircularProgress, TextField } from "@material-ui/core"; -import QRCode from 'qrcode.react'; +import { red } from "@material-ui/core/colors"; +import classnames from "classnames"; +import QRCode from "qrcode.react"; +import { useHistory, useLocation } from "react-router"; + import AppStoreBadges from "../../components/AppStoreBadges"; import { GoogleAuthenticator } from "../../constants"; -import { useHistory, useLocation } from "react-router"; -import { completeTOTPRegistrationProcess } from "../../services/RegisterDevice"; import { useNotifications } from "../../hooks/NotificationsContext"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { IconDefinition, faCopy, faKey, faTimesCircle } from "@fortawesome/free-solid-svg-icons"; -import { red } from "@material-ui/core/colors"; -import { extractIdentityToken } from "../../utils/IdentityToken"; +import LoginLayout from "../../layouts/LoginLayout"; import { FirstFactorRoute } from "../../Routes"; +import { completeTOTPRegistrationProcess } from "../../services/RegisterDevice"; +import { extractIdentityToken } from "../../utils/IdentityToken"; const RegisterOneTimePassword = function () { const style = useStyles(); @@ -32,7 +34,7 @@ const RegisterOneTimePassword = function () { const handleDoneClick = () => { history.push(FirstFactorRoute); - } + }; const completeRegistrationProcess = useCallback(async () => { if (!processToken) { @@ -52,74 +54,77 @@ const RegisterOneTimePassword = function () { setIsLoading(false); }, [processToken, createErrorNotification]); - useEffect(() => { completeRegistrationProcess() }, [completeRegistrationProcess]); + useEffect(() => { + completeRegistrationProcess(); + }, [completeRegistrationProcess]); function SecretButton(text: string | undefined, action: string, icon: IconDefinition) { return ( - <IconButton - className={style.secretButtons} - color="primary" - onClick={() => { - navigator.clipboard.writeText(`${text}`); - createSuccessNotification(`${action}`); - }} - > - <FontAwesomeIcon icon={icon} /> - </IconButton> - ) + <IconButton + className={style.secretButtons} + color="primary" + onClick={() => { + navigator.clipboard.writeText(`${text}`); + createSuccessNotification(`${action}`); + }} + > + <FontAwesomeIcon icon={icon} /> + </IconButton> + ); } - const qrcodeFuzzyStyle = (isLoading || hasErrored) ? style.fuzzy : undefined + const qrcodeFuzzyStyle = isLoading || hasErrored ? style.fuzzy : undefined; return ( - <LoginLayout title="Scan QRCode"> - <div className={style.root}> - <div className={style.googleAuthenticator}> - <Typography className={style.googleAuthenticatorText}>Need Google Authenticator?</Typography> - <AppStoreBadges - iconSize={128} - targetBlank - className={style.googleAuthenticatorBadges} - googlePlayLink={GoogleAuthenticator.googlePlay} - appleStoreLink={GoogleAuthenticator.appleStore} /> - </div> - <div className={style.qrcodeContainer}> - <Link href={secretURL}> - <QRCode - value={secretURL} - className={classnames(qrcodeFuzzyStyle, style.qrcode)} - size={256} /> - {!hasErrored && isLoading ? <CircularProgress className={style.loader} size={128} /> : null} - {hasErrored ? <FontAwesomeIcon className={style.failureIcon} icon={faTimesCircle} /> : null} - </Link> - </div> - <div> - {secretURL !== "empty" - ? <TextField - id="secret-url" - label="Secret" - className={style.secret} - value={secretURL} - InputProps={{ - readOnly: true - }} /> : null} - {secretBase32 ? SecretButton(secretBase32, "OTP Secret copied to clipboard.", faKey) : null} - {secretURL !== "empty" ? SecretButton(secretURL, "OTP URL copied to clipboard.", faCopy) : null} - </div> - <Button - variant="contained" - color="primary" - className={style.doneButton} - onClick={handleDoneClick} - disabled={isLoading}> - Done - </Button> - </div> - </LoginLayout> - ) -} + <LoginLayout title="Scan QRCode"> + <div className={style.root}> + <div className={style.googleAuthenticator}> + <Typography className={style.googleAuthenticatorText}>Need Google Authenticator?</Typography> + <AppStoreBadges + iconSize={128} + targetBlank + className={style.googleAuthenticatorBadges} + googlePlayLink={GoogleAuthenticator.googlePlay} + appleStoreLink={GoogleAuthenticator.appleStore} + /> + </div> + <div className={style.qrcodeContainer}> + <Link href={secretURL}> + <QRCode value={secretURL} className={classnames(qrcodeFuzzyStyle, style.qrcode)} size={256} /> + {!hasErrored && isLoading ? <CircularProgress className={style.loader} size={128} /> : null} + {hasErrored ? <FontAwesomeIcon className={style.failureIcon} icon={faTimesCircle} /> : null} + </Link> + </div> + <div> + {secretURL !== "empty" ? ( + <TextField + id="secret-url" + label="Secret" + className={style.secret} + value={secretURL} + InputProps={{ + readOnly: true, + }} + /> + ) : null} + {secretBase32 ? SecretButton(secretBase32, "OTP Secret copied to clipboard.", faKey) : null} + {secretURL !== "empty" ? SecretButton(secretURL, "OTP URL copied to clipboard.", faCopy) : null} + </div> + <Button + variant="contained" + color="primary" + className={style.doneButton} + onClick={handleDoneClick} + disabled={isLoading} + > + Done + </Button> + </div> + </LoginLayout> + ); +}; -export default RegisterOneTimePassword +export default RegisterOneTimePassword; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { paddingTop: theme.spacing(4), paddingBottom: theme.spacing(4), @@ -129,7 +134,7 @@ const useStyles = makeStyles(theme => ({ marginBottom: theme.spacing(2), }, fuzzy: { - filter: "blur(10px)" + filter: "blur(10px)", }, secret: { marginTop: theme.spacing(1), @@ -163,5 +168,5 @@ const useStyles = makeStyles(theme => ({ left: "calc(128px - 64px)", color: red[400], fontSize: "128px", - } -}))
\ No newline at end of file + }, +})); diff --git a/web/src/views/DeviceRegistration/RegisterSecurityKey.tsx b/web/src/views/DeviceRegistration/RegisterSecurityKey.tsx index b58e5f7f5..11cbea3b4 100644 --- a/web/src/views/DeviceRegistration/RegisterSecurityKey.tsx +++ b/web/src/views/DeviceRegistration/RegisterSecurityKey.tsx @@ -1,13 +1,18 @@ import React, { useState, useEffect, useCallback } from "react"; -import LoginLayout from "../../layouts/LoginLayout"; -import FingerTouchIcon from "../../components/FingerTouchIcon"; + import { makeStyles, Typography, Button } from "@material-ui/core"; import { useHistory, useLocation } from "react-router"; +import u2fApi from "u2f-api"; + +import FingerTouchIcon from "../../components/FingerTouchIcon"; +import { useNotifications } from "../../hooks/NotificationsContext"; +import LoginLayout from "../../layouts/LoginLayout"; import { FirstFactorPath } from "../../services/Api"; +import { + completeU2FRegistrationProcessStep1, + completeU2FRegistrationProcessStep2, +} from "../../services/RegisterDevice"; import { extractIdentityToken } from "../../utils/IdentityToken"; -import { completeU2FRegistrationProcessStep1, completeU2FRegistrationProcessStep2 } from "../../services/RegisterDevice"; -import { useNotifications } from "../../hooks/NotificationsContext"; -import u2fApi from "u2f-api"; const RegisterSecurityKey = function () { const style = useStyles(); @@ -18,10 +23,9 @@ const RegisterSecurityKey = function () { const processToken = extractIdentityToken(location.search); - const handleBackClick = () => { history.push(FirstFactorPath); - } + }; const registerStep1 = useCallback(async () => { if (!processToken) { @@ -37,7 +41,7 @@ const RegisterSecurityKey = function () { appId: res.appId, challenge: r.challenge, version: r.version, - }) + }); } const registerResponse = await u2fApi.register(registerRequests, [], 60); await completeU2FRegistrationProcessStep2(registerResponse); @@ -45,8 +49,9 @@ const RegisterSecurityKey = function () { history.push(FirstFactorPath); } catch (err) { console.error(err); - createErrorNotification("Failed to register your security key. " + - "The identity verification process might have timed out."); + createErrorNotification( + "Failed to register your security key. The identity verification process might have timed out.", + ); } }, [processToken, createErrorNotification, history]); @@ -60,20 +65,24 @@ const RegisterSecurityKey = function () { <FingerTouchIcon size={64} animated /> </div> <Typography className={style.instruction}>Touch the token on your security key</Typography> - <Button color="primary" onClick={handleBackClick}>Retry</Button> - <Button color="primary" onClick={handleBackClick}>Cancel</Button> + <Button color="primary" onClick={handleBackClick}> + Retry + </Button> + <Button color="primary" onClick={handleBackClick}> + Cancel + </Button> </LoginLayout> - ) -} + ); +}; -export default RegisterSecurityKey +export default RegisterSecurityKey; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ icon: { paddingTop: theme.spacing(4), paddingBottom: theme.spacing(4), }, instruction: { paddingBottom: theme.spacing(4), - } -}))
\ No newline at end of file + }, +})); diff --git a/web/src/views/LoadingPage/LoadingPage.tsx b/web/src/views/LoadingPage/LoadingPage.tsx index e923c1d7b..6ae4ac779 100644 --- a/web/src/views/LoadingPage/LoadingPage.tsx +++ b/web/src/views/LoadingPage/LoadingPage.tsx @@ -1,6 +1,7 @@ import React from "react"; -import ReactLoading from "react-loading"; + import { Typography, Grid } from "@material-ui/core"; +import ReactLoading from "react-loading"; const LoadingPage = function () { return ( @@ -11,6 +12,6 @@ const LoadingPage = function () { </Grid> </Grid> ); -} +}; -export default LoadingPage
\ No newline at end of file +export default LoadingPage; diff --git a/web/src/views/LoginPortal/Authenticated.tsx b/web/src/views/LoginPortal/Authenticated.tsx index ec17f14f8..b7a246c48 100644 --- a/web/src/views/LoginPortal/Authenticated.tsx +++ b/web/src/views/LoginPortal/Authenticated.tsx @@ -1,7 +1,9 @@ import React from "react"; -import SuccessIcon from "../../components/SuccessIcon"; + import { Typography, makeStyles } from "@material-ui/core"; +import SuccessIcon from "../../components/SuccessIcon"; + const Authenticated = function () { const classes = useStyles(); return ( @@ -11,14 +13,14 @@ const Authenticated = function () { </div> <Typography>Authenticated</Typography> </div> - ) -} + ); +}; -export default Authenticated +export default Authenticated; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ iconContainer: { marginBottom: theme.spacing(2), - flex: "0 0 100%" - } -}))
\ No newline at end of file + flex: "0 0 100%", + }, +})); diff --git a/web/src/views/LoginPortal/AuthenticatedView/AuthenticatedView.tsx b/web/src/views/LoginPortal/AuthenticatedView/AuthenticatedView.tsx index 9b4b55002..2c4a91bb4 100644 --- a/web/src/views/LoginPortal/AuthenticatedView/AuthenticatedView.tsx +++ b/web/src/views/LoginPortal/AuthenticatedView/AuthenticatedView.tsx @@ -1,6 +1,8 @@ import React from "react"; + import { Grid, makeStyles, Button } from "@material-ui/core"; import { useHistory } from "react-router"; + import LoginLayout from "../../../layouts/LoginLayout"; import { LogoutRoute as SignOutRoute } from "../../../Routes"; import Authenticated from "../Authenticated"; @@ -15,13 +17,10 @@ const AuthenticatedView = function (props: Props) { const handleLogoutClick = () => { history.push(SignOutRoute); - } + }; return ( - <LoginLayout - id="authenticated-stage" - title={`Hi ${props.name}`} - showBrand> + <LoginLayout id="authenticated-stage" title={`Hi ${props.name}`} showBrand> <Grid container> <Grid item xs={12}> <Button color="secondary" onClick={handleLogoutClick} id="logout-button"> @@ -33,17 +32,17 @@ const AuthenticatedView = function (props: Props) { </Grid> </Grid> </LoginLayout> - ) -} + ); +}; -export default AuthenticatedView +export default AuthenticatedView; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ mainContainer: { border: "1px solid #d6d6d6", borderRadius: "10px", padding: theme.spacing(4), marginTop: theme.spacing(2), marginBottom: theme.spacing(2), - } -})) + }, +})); diff --git a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx index 6529676a7..4552c9c5f 100644 --- a/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx +++ b/web/src/views/LoginPortal/FirstFactor/FirstFactorForm.tsx @@ -1,13 +1,15 @@ import React, { MutableRefObject, useEffect, useRef, useState } from "react"; -import classnames from "classnames"; + import { makeStyles, Grid, Button, FormControlLabel, Checkbox, Link } from "@material-ui/core"; +import classnames from "classnames"; import { useHistory } from "react-router"; -import LoginLayout from "../../../layouts/LoginLayout"; + +import FixedTextField from "../../../components/FixedTextField"; import { useNotifications } from "../../../hooks/NotificationsContext"; -import { postFirstFactor } from "../../../services/FirstFactor"; -import { ResetPasswordStep1Route } from "../../../Routes"; import { useRedirectionURL } from "../../../hooks/RedirectionURL"; -import FixedTextField from "../../../components/FixedTextField"; +import LoginLayout from "../../../layouts/LoginLayout"; +import { ResetPasswordStep1Route } from "../../../Routes"; +import { postFirstFactor } from "../../../services/FirstFactor"; export interface Props { disabled: boolean; @@ -47,7 +49,7 @@ const FirstFactorForm = function (props: Props) { const handleSignIn = async () => { if (username === "" || password === "") { if (username === "") { - setUsernameError(true) + setUsernameError(true); } if (password === "") { @@ -62,8 +64,7 @@ const FirstFactorForm = function (props: Props) { props.onAuthenticationSuccess(res ? res.redirect : undefined); } catch (err) { console.error(err); - createErrorNotification( - "Incorrect username or password."); + createErrorNotification("Incorrect username or password."); props.onAuthenticationFailure(); setPassword(""); passwordRef.current.focus(); @@ -75,10 +76,7 @@ const FirstFactorForm = function (props: Props) { }; return ( - <LoginLayout - id="first-factor-stage" - title="Sign in" - showBrand> + <LoginLayout id="first-factor-stage" title="Sign in" showBrand> <Grid container spacing={2} className={style.root}> <Grid item xs={12}> <FixedTextField @@ -92,21 +90,22 @@ const FirstFactorForm = function (props: Props) { error={usernameError} disabled={disabled} fullWidth - onChange={v => setUsername(v.target.value)} + onChange={(v) => setUsername(v.target.value)} onFocus={() => setUsernameError(false)} autoCapitalize="none" onKeyPress={(ev) => { - if (ev.key === 'Enter') { + if (ev.key === "Enter") { if (!username.length) { - setUsernameError(true) + setUsernameError(true); } else if (username.length && password.length) { handleSignIn(); } else { - setUsernameError(false) + setUsernameError(false); passwordRef.current.focus(); } } - }} /> + }} + /> </Grid> <Grid item xs={12}> <FixedTextField @@ -120,11 +119,11 @@ const FirstFactorForm = function (props: Props) { disabled={disabled} value={password} error={passwordError} - onChange={v => setPassword(v.target.value)} + onChange={(v) => setPassword(v.target.value)} onFocus={() => setPasswordError(false)} type="password" onKeyPress={(ev) => { - if (ev.key === 'Enter') { + if (ev.key === "Enter") { if (!username.length) { usernameRef.current.focus(); } else if (!password.length) { @@ -133,13 +132,20 @@ const FirstFactorForm = function (props: Props) { handleSignIn(); ev.preventDefault(); } - }} /> + }} + /> </Grid> - {props.rememberMe || props.resetPassword ? - <Grid item xs={12} className={props.rememberMe - ? classnames(style.leftAlign, style.actionRow) - : classnames(style.leftAlign, style.flexEnd, style.actionRow)}> - {props.rememberMe ? + {props.rememberMe || props.resetPassword ? ( + <Grid + item + xs={12} + className={ + props.rememberMe + ? classnames(style.leftAlign, style.actionRow) + : classnames(style.leftAlign, style.flexEnd, style.actionRow) + } + > + {props.rememberMe ? ( <FormControlLabel control={ <Checkbox @@ -148,7 +154,7 @@ const FirstFactorForm = function (props: Props) { checked={rememberMe} onChange={handleRememberMeChange} onKeyPress={(ev) => { - if (ev.key === 'Enter') { + if (ev.key === "Enter") { if (!username.length) { usernameRef.current.focus(); } else if (!password.length) { @@ -158,20 +164,25 @@ const FirstFactorForm = function (props: Props) { } }} value="rememberMe" - color="primary"/> + color="primary" + /> } className={style.rememberMe} label="Remember me" - /> : null} - {props.resetPassword ? + /> + ) : null} + {props.resetPassword ? ( <Link id="reset-password-button" component="button" onClick={handleResetPasswordClick} - className={style.resetLink}> + className={style.resetLink} + > Reset password? - </Link> : null} - </Grid> : null} + </Link> + ) : null} + </Grid> + ) : null} <Grid item xs={12}> <Button id="sign-in-button" @@ -179,18 +190,19 @@ const FirstFactorForm = function (props: Props) { color="primary" fullWidth disabled={disabled} - onClick={handleSignIn}> + onClick={handleSignIn} + > Sign in </Button> </Grid> </Grid> </LoginLayout> - ) -} + ); +}; -export default FirstFactorForm +export default FirstFactorForm; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { marginTop: theme.spacing(), marginBottom: theme.spacing(), @@ -219,4 +231,4 @@ const useStyles = makeStyles(theme => ({ textAlign: "right", verticalAlign: "bottom", }, -}));
\ No newline at end of file +})); diff --git a/web/src/views/LoginPortal/LoginPortal.tsx b/web/src/views/LoginPortal/LoginPortal.tsx index f93bc00d0..47a9a0ae2 100644 --- a/web/src/views/LoginPortal/LoginPortal.tsx +++ b/web/src/views/LoginPortal/LoginPortal.tsx @@ -1,20 +1,26 @@ import React, { useEffect, Fragment, ReactNode, useState, useCallback } from "react"; + import { Switch, Route, Redirect, useHistory, useLocation } from "react-router"; -import FirstFactorForm from "./FirstFactor/FirstFactorForm"; -import SecondFactorForm from "./SecondFactor/SecondFactorForm"; -import { - FirstFactorRoute, SecondFactorRoute, SecondFactorTOTPRoute, - SecondFactorPushRoute, SecondFactorU2FRoute, AuthenticatedRoute -} from "../../Routes"; -import { useAutheliaState } from "../../hooks/State"; -import LoadingPage from "../LoadingPage/LoadingPage"; -import { AuthenticationLevel } from "../../services/State"; + +import { useConfiguration } from "../../hooks/Configuration"; import { useNotifications } from "../../hooks/NotificationsContext"; import { useRedirectionURL } from "../../hooks/RedirectionURL"; +import { useAutheliaState } from "../../hooks/State"; import { useUserPreferences as userUserInfo } from "../../hooks/UserInfo"; import { SecondFactorMethod } from "../../models/Methods"; -import { useConfiguration } from "../../hooks/Configuration"; +import { + FirstFactorRoute, + SecondFactorRoute, + SecondFactorTOTPRoute, + SecondFactorPushRoute, + SecondFactorU2FRoute, + AuthenticatedRoute, +} from "../../Routes"; +import { AuthenticationLevel } from "../../services/State"; +import LoadingPage from "../LoadingPage/LoadingPage"; import AuthenticatedView from "./AuthenticatedView/AuthenticatedView"; +import FirstFactorForm from "./FirstFactor/FirstFactorForm"; +import SecondFactorForm from "./SecondFactor/SecondFactorForm"; export interface Props { rememberMe: boolean; @@ -35,7 +41,9 @@ const LoginPortal = function (props: Props) { const redirect = useCallback((url: string) => history.push(url), [history]); // Fetch the state when portal is mounted. - useEffect(() => { fetchState() }, [fetchState]); + useEffect(() => { + fetchState(); + }, [fetchState]); // Fetch preferences and configuration when user is authenticated. useEffect(() => { @@ -76,9 +84,7 @@ const LoginPortal = function (props: Props) { // Redirect to the correct stage if not enough authenticated useEffect(() => { if (state) { - const redirectionSuffix = redirectionURL - ? `?rd=${encodeURIComponent(redirectionURL)}` - : ''; + const redirectionSuffix = redirectionURL ? `?rd=${encodeURIComponent(redirectionURL)}` : ""; if (state.authentication_level === AuthenticationLevel.Unauthenticated) { setFirstFactorDisabled(false); @@ -107,9 +113,10 @@ const LoginPortal = function (props: Props) { // Refresh state fetchState(); } - } + }; - const firstFactorReady = state !== undefined && + const firstFactorReady = + state !== undefined && state.authentication_level === AuthenticationLevel.Unauthenticated && location.pathname === FirstFactorRoute; @@ -123,16 +130,20 @@ const LoginPortal = function (props: Props) { resetPassword={props.resetPassword} onAuthenticationStart={() => setFirstFactorDisabled(true)} onAuthenticationFailure={() => setFirstFactorDisabled(false)} - onAuthenticationSuccess={handleAuthSuccess} /> + onAuthenticationSuccess={handleAuthSuccess} + /> </ComponentOrLoading> </Route> <Route path={SecondFactorRoute}> - {state && userInfo && configuration ? <SecondFactorForm - authenticationLevel={state.authentication_level} - userInfo={userInfo} - configuration={configuration} - onMethodChanged={() => fetchUserInfo()} - onAuthenticationSuccess={handleAuthSuccess} /> : null} + {state && userInfo && configuration ? ( + <SecondFactorForm + authenticationLevel={state.authentication_level} + userInfo={userInfo} + configuration={configuration} + onMethodChanged={() => fetchUserInfo()} + onAuthenticationSuccess={handleAuthSuccess} + /> + ) : null} </Route> <Route path={AuthenticatedRoute} exact> {userInfo ? <AuthenticatedView name={userInfo.display_name} /> : null} @@ -141,10 +152,10 @@ const LoginPortal = function (props: Props) { <Redirect to={FirstFactorRoute} /> </Route> </Switch> - ) -} + ); +}; -export default LoginPortal +export default LoginPortal; interface ComponentOrLoadingProps { ready: boolean; @@ -160,5 +171,5 @@ function ComponentOrLoading(props: ComponentOrLoadingProps) { </div> {props.ready ? props.children : null} </Fragment> - ) -}
\ No newline at end of file + ); +} diff --git a/web/src/views/LoginPortal/SecondFactor/IconWithContext.tsx b/web/src/views/LoginPortal/SecondFactor/IconWithContext.tsx index 8bc3ba178..8ada92ed3 100644 --- a/web/src/views/LoginPortal/SecondFactor/IconWithContext.tsx +++ b/web/src/views/LoginPortal/SecondFactor/IconWithContext.tsx @@ -1,4 +1,5 @@ import React, { ReactNode } from "react"; + import { makeStyles } from "@material-ui/core"; import classnames from "classnames"; @@ -11,7 +12,7 @@ interface IconWithContextProps { const IconWithContext = function (props: IconWithContextProps) { const iconSize = 64; - const style = makeStyles(theme => ({ + const style = makeStyles((theme) => ({ root: {}, iconContainer: { display: "flex", @@ -24,21 +25,17 @@ const IconWithContext = function (props: IconWithContextProps) { }, context: { display: "block", - } + }, }))(); return ( <div className={classnames(props.className, style.root)}> <div className={style.iconContainer}> - <div className={style.icon}> - {props.icon} - </div> - </div> - <div className={style.context}> - {props.context} + <div className={style.icon}>{props.icon}</div> </div> + <div className={style.context}>{props.context}</div> </div> - ) -} + ); +}; -export default IconWithContext
\ No newline at end of file +export default IconWithContext; diff --git a/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx b/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx index 3dedce99e..d73904788 100644 --- a/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx +++ b/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx @@ -1,13 +1,15 @@ import React, { ReactNode, Fragment } from "react"; + import { makeStyles, Typography, Link, useTheme } from "@material-ui/core"; -import InformationIcon from "../../../components/InformationIcon"; import classnames from "classnames"; + +import InformationIcon from "../../../components/InformationIcon"; import Authenticated from "../Authenticated"; export enum State { ALREADY_AUTHENTICATED = 1, NOT_REGISTERED = 2, - METHOD = 3 + METHOD = 3, } export interface Props { @@ -24,47 +26,40 @@ const DefaultMethodContainer = function (props: Props) { const style = useStyles(); let container: ReactNode; - let stateClass: string = ''; + let stateClass: string = ""; switch (props.state) { case State.ALREADY_AUTHENTICATED: - container = <Authenticated /> + container = <Authenticated />; stateClass = "state-already-authenticated"; break; case State.NOT_REGISTERED: - container = <NotRegisteredContainer /> + container = <NotRegisteredContainer />; stateClass = "state-not-registered"; break; case State.METHOD: - container = <MethodContainer explanation={props.explanation}> - {props.children} - </MethodContainer> + container = <MethodContainer explanation={props.explanation}>{props.children}</MethodContainer>; stateClass = "state-method"; break; } - return ( <div id={props.id}> <Typography variant="h6">{props.title}</Typography> <div className={classnames(style.container, stateClass)} id="2fa-container"> - <div className={style.containerFlex}> - {container} - </div> + <div className={style.containerFlex}>{container}</div> </div> - {props.onRegisterClick - ? <Link component="button" - id="register-link" - onClick={props.onRegisterClick}> + {props.onRegisterClick ? ( + <Link component="button" id="register-link" onClick={props.onRegisterClick}> Not registered yet? </Link> - : null} + ) : null} </div> - ) -} + ); +}; -export default DefaultMethodContainer +export default DefaultMethodContainer; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ container: { height: "200px", }, @@ -76,17 +71,21 @@ const useStyles = makeStyles(theme => ({ alignItems: "center", alignContent: "center", justifyContent: "center", - } + }, })); function NotRegisteredContainer() { const theme = useTheme(); return ( <Fragment> - <div style={{ marginBottom: theme.spacing(2), flex: "0 0 100%" }}><InformationIcon /></div> - <Typography style={{ color: "#5858ff" }}>Register your first device by clicking on the link below</Typography> + <div style={{ marginBottom: theme.spacing(2), flex: "0 0 100%" }}> + <InformationIcon /> + </div> + <Typography style={{ color: "#5858ff" }}> + Register your first device by clicking on the link below + </Typography> </Fragment> - ) + ); } interface MethodContainerProps { @@ -101,5 +100,5 @@ function MethodContainer(props: MethodContainerProps) { <div style={{ marginBottom: theme.spacing(2) }}>{props.children}</div> <Typography>{props.explanation}</Typography> </Fragment> - ) -}
\ No newline at end of file + ); +} diff --git a/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx b/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx index 1e636fbda..b2ebaf9f0 100644 --- a/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx +++ b/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx @@ -1,9 +1,20 @@ import React, { ReactNode } from "react"; -import { Dialog, Grid, makeStyles, DialogContent, Button, DialogActions, Typography, useTheme } from "@material-ui/core"; -import PushNotificationIcon from "../../../components/PushNotificationIcon"; + +import { + Dialog, + Grid, + makeStyles, + DialogContent, + Button, + DialogActions, + Typography, + useTheme, +} from "@material-ui/core"; + +import FingerTouchIcon from "../../../components/FingerTouchIcon"; import PieChartIcon from "../../../components/PieChartIcon"; +import PushNotificationIcon from "../../../components/PushNotificationIcon"; import { SecondFactorMethod } from "../../../models/Methods"; -import FingerTouchIcon from "../../../components/FingerTouchIcon"; export interface Props { open: boolean; @@ -18,37 +29,45 @@ const MethodSelectionDialog = function (props: Props) { const style = useStyles(); const theme = useTheme(); - const pieChartIcon = <PieChartIcon width={24} height={24} maxProgress={1000} progress={150} - color={theme.palette.primary.main} backgroundColor={"white"} /> + const pieChartIcon = ( + <PieChartIcon + width={24} + height={24} + maxProgress={1000} + progress={150} + color={theme.palette.primary.main} + backgroundColor={"white"} + /> + ); return ( - <Dialog - open={props.open} - className={style.root} - onClose={props.onClose}> + <Dialog open={props.open} className={style.root} onClose={props.onClose}> <DialogContent> <Grid container justify="center" spacing={1} id="methods-dialog"> - {props.methods.has(SecondFactorMethod.TOTP) - ? <MethodItem + {props.methods.has(SecondFactorMethod.TOTP) ? ( + <MethodItem id="one-time-password-option" method="One-Time Password" icon={pieChartIcon} - onClick={() => props.onClick(SecondFactorMethod.TOTP)} /> - : null} - {props.methods.has(SecondFactorMethod.U2F) && props.u2fSupported - ? <MethodItem + onClick={() => props.onClick(SecondFactorMethod.TOTP)} + /> + ) : null} + {props.methods.has(SecondFactorMethod.U2F) && props.u2fSupported ? ( + <MethodItem id="security-key-option" method="Security Key" icon={<FingerTouchIcon size={32} />} - onClick={() => props.onClick(SecondFactorMethod.U2F)} /> - : null} - {props.methods.has(SecondFactorMethod.MobilePush) - ? <MethodItem + onClick={() => props.onClick(SecondFactorMethod.U2F)} + /> + ) : null} + {props.methods.has(SecondFactorMethod.MobilePush) ? ( + <MethodItem id="push-notification-option" method="Push Notification" icon={<PushNotificationIcon width={32} height={32} />} - onClick={() => props.onClick(SecondFactorMethod.MobilePush)} /> - : null} + onClick={() => props.onClick(SecondFactorMethod.MobilePush)} + /> + ) : null} </Grid> </DialogContent> <DialogActions> @@ -57,16 +76,16 @@ const MethodSelectionDialog = function (props: Props) { </Button> </DialogActions> </Dialog> - ) -} + ); +}; -export default MethodSelectionDialog +export default MethodSelectionDialog; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { textAlign: "center", - } -})) + }, +})); interface MethodItemProps { id: string; @@ -77,7 +96,7 @@ interface MethodItemProps { } function MethodItem(props: MethodItemProps) { - const style = makeStyles(theme => ({ + const style = makeStyles((theme) => ({ item: { paddingTop: theme.spacing(4), paddingBottom: theme.spacing(4), @@ -89,18 +108,23 @@ function MethodItem(props: MethodItemProps) { }, buttonRoot: { display: "block", - } + }, }))(); return ( <Grid item xs={12} className="method-option" id={props.id}> - <Button className={style.item} color="primary" + <Button + className={style.item} + color="primary" classes={{ root: style.buttonRoot }} variant="contained" - onClick={props.onClick}> + onClick={props.onClick} + > <div className={style.icon}>{props.icon}</div> - <div><Typography>{props.method}</Typography></div> + <div> + <Typography>{props.method}</Typography> + </div> </Button> </Grid> - ) -}
\ No newline at end of file + ); +} diff --git a/web/src/views/LoginPortal/SecondFactor/OTPDial.tsx b/web/src/views/LoginPortal/SecondFactor/OTPDial.tsx index 9a8429fe5..7e88a0f16 100644 --- a/web/src/views/LoginPortal/SecondFactor/OTPDial.tsx +++ b/web/src/views/LoginPortal/SecondFactor/OTPDial.tsx @@ -1,16 +1,18 @@ import React, { Fragment } from "react"; -import OtpInput from "react-otp-input"; -import TimerIcon from "../../../components/TimerIcon"; + import { makeStyles } from "@material-ui/core"; import classnames from "classnames"; +import OtpInput from "react-otp-input"; + +import SuccessIcon from "../../../components/SuccessIcon"; +import TimerIcon from "../../../components/TimerIcon"; import IconWithContext from "./IconWithContext"; import { State } from "./OneTimePasswordMethod"; -import SuccessIcon from "../../../components/SuccessIcon"; export interface Props { passcode: string; state: State; - period: number + period: number; onChange: (passcode: string) => void; } @@ -26,22 +28,18 @@ const OTPDial = function (props: Props) { numInputs={6} isDisabled={props.state === State.InProgress || props.state === State.Success} hasErrored={props.state === State.Failure} - inputStyle={classnames(style.otpDigitInput, props.state === State.Failure ? style.inputError : "")} /> + inputStyle={classnames(style.otpDigitInput, props.state === State.Failure ? style.inputError : "")} + /> </span> - ) + ); - return ( - <IconWithContext - icon={<Icon state={props.state} period={props.period} />} - context={dial} /> - ) -} + return <IconWithContext icon={<Icon state={props.state} period={props.period} />} context={dial} />; +}; -export default OTPDial +export default OTPDial; -const useStyles = makeStyles(theme => ({ - timeProgress: { - }, +const useStyles = makeStyles((theme) => ({ + timeProgress: {}, register: { marginTop: theme.spacing(), }, @@ -59,7 +57,7 @@ const useStyles = makeStyles(theme => ({ }, inputError: { border: "1px solid rgba(255, 2, 2, 0.95)", - } + }, })); interface IconProps { @@ -70,8 +68,10 @@ interface IconProps { function Icon(props: IconProps) { return ( <Fragment> - {props.state !== State.Success ? <TimerIcon backgroundColor="#000" color="#FFFFFF" width={64} height={64} period={props.period} /> : null} + {props.state !== State.Success ? ( + <TimerIcon backgroundColor="#000" color="#FFFFFF" width={64} height={64} period={props.period} /> + ) : null} {props.state === State.Success ? <SuccessIcon /> : null} </Fragment> - ) -}
\ No newline at end of file + ); +} diff --git a/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx b/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx index e4bae1737..5e099b48a 100644 --- a/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx +++ b/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx @@ -1,9 +1,10 @@ import React, { useState, useEffect, useCallback } from "react"; -import MethodContainer, { State as MethodContainerState } from "./MethodContainer"; -import OTPDial from "./OTPDial"; -import { completeTOTPSignIn } from "../../../services/OneTimePassword"; + import { useRedirectionURL } from "../../../hooks/RedirectionURL"; +import { completeTOTPSignIn } from "../../../services/OneTimePassword"; import { AuthenticationLevel } from "../../../services/State"; +import MethodContainer, { State as MethodContainerState } from "./MethodContainer"; +import OTPDial from "./OTPDial"; export enum State { Idle = 1, @@ -16,7 +17,7 @@ export interface Props { id: string; authenticationLevel: AuthenticationLevel; registered: boolean; - totp_period: number + totp_period: number; onRegisterClick: () => void; onSignInError: (err: Error) => void; @@ -25,9 +26,9 @@ export interface Props { const OneTimePasswordMethod = function (props: Props) { const [passcode, setPasscode] = useState(""); - const [state, setState] = useState(props.authenticationLevel === AuthenticationLevel.TwoFactor - ? State.Success - : State.Idle); + const [state, setState] = useState( + props.authenticationLevel === AuthenticationLevel.TwoFactor ? State.Success : State.Idle, + ); const redirectionURL = useRedirectionURL(); const { onSignInSuccess, onSignInError } = props; @@ -67,7 +68,9 @@ const OneTimePasswordMethod = function (props: Props) { } }, [props.authenticationLevel, setState]); - useEffect(() => { signInFunc() }, [signInFunc]); + useEffect(() => { + signInFunc(); + }, [signInFunc]); let methodState = MethodContainerState.METHOD; if (props.authenticationLevel === AuthenticationLevel.TwoFactor) { @@ -82,14 +85,11 @@ const OneTimePasswordMethod = function (props: Props) { title="One-Time Password" explanation="Enter one-time password" state={methodState} - onRegisterClick={props.onRegisterClick}> - <OTPDial - passcode={passcode} - onChange={setPasscode} - state={state} - period={props.totp_period} /> + onRegisterClick={props.onRegisterClick} + > + <OTPDial passcode={passcode} onChange={setPasscode} state={state} period={props.totp_period} /> </MethodContainer> - ) -} + ); +}; -export default OneTimePasswordMethod
\ No newline at end of file +export default OneTimePasswordMethod; diff --git a/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx b/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx index 3a6a0100b..53acd7290 100644 --- a/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx +++ b/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx @@ -1,13 +1,15 @@ import React, { useEffect, useCallback, useState, ReactNode } from "react"; -import MethodContainer, { State as MethodContainerState } from "./MethodContainer"; -import PushNotificationIcon from "../../../components/PushNotificationIcon"; -import { completePushNotificationSignIn } from "../../../services/PushNotification"; + import { Button, makeStyles } from "@material-ui/core"; -import { useRedirectionURL } from "../../../hooks/RedirectionURL"; -import { useIsMountedRef } from "../../../hooks/Mounted"; -import SuccessIcon from "../../../components/SuccessIcon"; + import FailureIcon from "../../../components/FailureIcon"; +import PushNotificationIcon from "../../../components/PushNotificationIcon"; +import SuccessIcon from "../../../components/SuccessIcon"; +import { useIsMountedRef } from "../../../hooks/Mounted"; +import { useRedirectionURL } from "../../../hooks/RedirectionURL"; +import { completePushNotificationSignIn } from "../../../services/PushNotification"; import { AuthenticationLevel } from "../../../services/State"; +import MethodContainer, { State as MethodContainerState } from "./MethodContainer"; export enum State { SignInInProgress = 1, @@ -50,7 +52,7 @@ const PushNotificationMethod = function (props: Props) { setState(State.Success); setTimeout(() => { if (!mounted.current) return; - onSignInSuccessCallback(res ? res.redirect : undefined) + onSignInSuccessCallback(res ? res.redirect : undefined); }, 1500); } catch (err) { // If the request was initiated and the user changed 2FA method in the meantime, @@ -63,7 +65,9 @@ const PushNotificationMethod = function (props: Props) { } }, [onSignInErrorCallback, onSignInSuccessCallback, setState, redirectionURL, mounted, props.authenticationLevel]); - useEffect(() => { signInFunc() }, [signInFunc]); + useEffect(() => { + signInFunc(); + }, [signInFunc]); // Set successful state if user is already authenticated. useEffect(() => { @@ -94,23 +98,24 @@ const PushNotificationMethod = function (props: Props) { id={props.id} title="Push Notification" explanation="A notification has been sent to your smartphone" - state={methodState}> - <div className={style.icon}> - {icon} - </div> - <div className={(state !== State.Failure) ? "hidden" : ""}> - <Button color="secondary" onClick={signInFunc}>Retry</Button> + state={methodState} + > + <div className={style.icon}>{icon}</div> + <div className={state !== State.Failure ? "hidden" : ""}> + <Button color="secondary" onClick={signInFunc}> + Retry + </Button> </div> </MethodContainer> - ) -} + ); +}; -export default PushNotificationMethod +export default PushNotificationMethod; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ icon: { width: "64px", height: "64px", display: "inline-block", - } -}))
\ No newline at end of file + }, +})); diff --git a/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx b/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx index 7218f1762..212a3dcc8 100644 --- a/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx +++ b/web/src/views/LoginPortal/SecondFactor/SecondFactorForm.tsx @@ -1,26 +1,28 @@ import React, { useState, useEffect } from "react"; + import { Grid, makeStyles, Button } from "@material-ui/core"; -import MethodSelectionDialog from "./MethodSelectionDialog"; -import { SecondFactorMethod } from "../../../models/Methods"; import { useHistory, Switch, Route, Redirect } from "react-router"; -import LoginLayout from "../../../layouts/LoginLayout"; +import u2fApi from "u2f-api"; + import { useNotifications } from "../../../hooks/NotificationsContext"; +import LoginLayout from "../../../layouts/LoginLayout"; +import { Configuration } from "../../../models/Configuration"; +import { SecondFactorMethod } from "../../../models/Methods"; +import { UserInfo } from "../../../models/UserInfo"; import { - initiateTOTPRegistrationProcess, - initiateU2FRegistrationProcess -} from "../../../services/RegisterDevice"; -import SecurityKeyMethod from "./SecurityKeyMethod"; -import OneTimePasswordMethod from "./OneTimePasswordMethod"; -import PushNotificationMethod from "./PushNotificationMethod"; -import { - LogoutRoute as SignOutRoute, SecondFactorTOTPRoute, - SecondFactorPushRoute, SecondFactorU2FRoute, SecondFactorRoute + LogoutRoute as SignOutRoute, + SecondFactorTOTPRoute, + SecondFactorPushRoute, + SecondFactorU2FRoute, + SecondFactorRoute, } from "../../../Routes"; -import { setPreferred2FAMethod } from "../../../services/UserPreferences"; -import { UserInfo } from "../../../models/UserInfo"; -import { Configuration } from "../../../models/Configuration"; -import u2fApi from "u2f-api"; +import { initiateTOTPRegistrationProcess, initiateU2FRegistrationProcess } from "../../../services/RegisterDevice"; import { AuthenticationLevel } from "../../../services/State"; +import { setPreferred2FAMethod } from "../../../services/UserPreferences"; +import MethodSelectionDialog from "./MethodSelectionDialog"; +import OneTimePasswordMethod from "./OneTimePasswordMethod"; +import PushNotificationMethod from "./PushNotificationMethod"; +import SecurityKeyMethod from "./SecurityKeyMethod"; const EMAIL_SENT_NOTIFICATION = "An email has been sent to your address to complete the process."; @@ -46,7 +48,8 @@ const SecondFactorForm = function (props: Props) { useEffect(() => { u2fApi.ensureSupport().then( () => setU2fSupported(true), - () => console.error("U2F not supported")); + () => console.error("U2F not supported"), + ); }, [setU2fSupported]); const initiateRegistration = (initiateRegistrationFunc: () => Promise<void>) => { @@ -63,12 +66,12 @@ const SecondFactorForm = function (props: Props) { createErrorNotification("There was a problem initiating the registration process"); } setRegistrationInProgress(false); - } - } + }; + }; const handleMethodSelectionClick = () => { setMethodSelectionOpen(true); - } + }; const handleMethodSelected = async (method: SecondFactorMethod) => { try { @@ -79,23 +82,21 @@ const SecondFactorForm = function (props: Props) { console.error(err); createErrorNotification("There was an issue updating preferred second factor method"); } - } + }; const handleLogoutClick = () => { history.push(SignOutRoute); - } + }; return ( - <LoginLayout - id="second-factor-stage" - title={`Hi ${props.userInfo.display_name}`} - showBrand> + <LoginLayout id="second-factor-stage" title={`Hi ${props.userInfo.display_name}`} showBrand> <MethodSelectionDialog open={methodSelectionOpen} methods={props.configuration.available_methods} u2fSupported={u2fSupported} onClose={() => setMethodSelectionOpen(false)} - onClick={handleMethodSelected} /> + onClick={handleMethodSelected} + /> <Grid container> <Grid item xs={12}> <Button color="secondary" onClick={handleLogoutClick} id="logout-button"> @@ -116,8 +117,9 @@ const SecondFactorForm = function (props: Props) { registered={props.userInfo.has_totp} totp_period={props.configuration.totp_period} onRegisterClick={initiateRegistration(initiateTOTPRegistrationProcess)} - onSignInError={err => createErrorNotification(err.message)} - onSignInSuccess={props.onAuthenticationSuccess} /> + onSignInError={(err) => createErrorNotification(err.message)} + onSignInSuccess={props.onAuthenticationSuccess} + /> </Route> <Route path={SecondFactorU2FRoute} exact> <SecurityKeyMethod @@ -126,15 +128,17 @@ const SecondFactorForm = function (props: Props) { // Whether the user has a U2F device registered already registered={props.userInfo.has_u2f} onRegisterClick={initiateRegistration(initiateU2FRegistrationProcess)} - onSignInError={err => createErrorNotification(err.message)} - onSignInSuccess={props.onAuthenticationSuccess} /> + onSignInError={(err) => createErrorNotification(err.message)} + onSignInSuccess={props.onAuthenticationSuccess} + /> </Route> <Route path={SecondFactorPushRoute} exact> <PushNotificationMethod id="push-notification-method" authenticationLevel={props.authenticationLevel} - onSignInError={err => createErrorNotification(err.message)} - onSignInSuccess={props.onAuthenticationSuccess} /> + onSignInError={(err) => createErrorNotification(err.message)} + onSignInSuccess={props.onAuthenticationSuccess} + /> </Route> <Route path={SecondFactorRoute}> <Redirect to={SecondFactorTOTPRoute} /> @@ -143,12 +147,12 @@ const SecondFactorForm = function (props: Props) { </Grid> </Grid> </LoginLayout> - ) -} + ); +}; -export default SecondFactorForm +export default SecondFactorForm; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ methodContainer: { border: "1px solid #d6d6d6", borderRadius: "10px", @@ -156,4 +160,4 @@ const useStyles = makeStyles(theme => ({ marginTop: theme.spacing(2), marginBottom: theme.spacing(2), }, -})) +})); diff --git a/web/src/views/LoginPortal/SecondFactor/SecurityKeyMethod.tsx b/web/src/views/LoginPortal/SecondFactor/SecurityKeyMethod.tsx index 97a4e22b0..b8c825e94 100644 --- a/web/src/views/LoginPortal/SecondFactor/SecurityKeyMethod.tsx +++ b/web/src/views/LoginPortal/SecondFactor/SecurityKeyMethod.tsx @@ -1,17 +1,19 @@ import React, { useCallback, useEffect, useState, Fragment } from "react"; -import MethodContainer, { State as MethodContainerState } from "./MethodContainer"; + import { makeStyles, Button, useTheme } from "@material-ui/core"; -import { initiateU2FSignin, completeU2FSignin } from "../../../services/SecurityKey"; +import { CSSProperties } from "@material-ui/styles"; import u2fApi from "u2f-api"; -import { useRedirectionURL } from "../../../hooks/RedirectionURL"; + +import FailureIcon from "../../../components/FailureIcon"; +import FingerTouchIcon from "../../../components/FingerTouchIcon"; +import LinearProgressBar from "../../../components/LinearProgressBar"; import { useIsMountedRef } from "../../../hooks/Mounted"; +import { useRedirectionURL } from "../../../hooks/RedirectionURL"; import { useTimer } from "../../../hooks/Timer"; -import LinearProgressBar from "../../../components/LinearProgressBar"; -import FingerTouchIcon from "../../../components/FingerTouchIcon"; -import FailureIcon from "../../../components/FailureIcon"; -import IconWithContext from "./IconWithContext"; -import { CSSProperties } from "@material-ui/styles"; +import { initiateU2FSignin, completeU2FSignin } from "../../../services/SecurityKey"; import { AuthenticationLevel } from "../../../services/State"; +import IconWithContext from "./IconWithContext"; +import MethodContainer, { State as MethodContainerState } from "./MethodContainer"; export enum State { WaitTouch = 1, @@ -35,7 +37,7 @@ const SecurityKeyMethod = function (props: Props) { const style = useStyles(); const redirectionURL = useRedirectionURL(); const mounted = useIsMountedRef(); - const [timerPercent, triggerTimer,] = useTimer(signInTimeout * 1000 - 500); + const [timerPercent, triggerTimer] = useTimer(signInTimeout * 1000 - 500); const { onSignInSuccess, onSignInError } = props; /* eslint-disable react-hooks/exhaustive-deps */ @@ -61,7 +63,7 @@ const SecurityKeyMethod = function (props: Props) { challenge: signRequest.challenge, keyHandle: r.keyHandle, version: r.version, - }) + }); } const signResponse = await u2fApi.sign(signRequests, signInTimeout); // If the request was initiated and the user changed 2FA method in the meantime, @@ -79,9 +81,19 @@ const SecurityKeyMethod = function (props: Props) { onSignInErrorCallback(new Error("Failed to initiate security key sign in process")); setState(State.Failure); } - }, [onSignInSuccessCallback, onSignInErrorCallback, redirectionURL, mounted, triggerTimer, props.authenticationLevel, props.registered]); - - useEffect(() => { doInitiateSignIn() }, [doInitiateSignIn]); + }, [ + onSignInSuccessCallback, + onSignInErrorCallback, + redirectionURL, + mounted, + triggerTimer, + props.authenticationLevel, + props.registered, + ]); + + useEffect(() => { + doInitiateSignIn(); + }, [doInitiateSignIn]); let methodState = MethodContainerState.METHOD; if (props.authenticationLevel === AuthenticationLevel.TwoFactor) { @@ -96,20 +108,21 @@ const SecurityKeyMethod = function (props: Props) { title="Security Key" explanation="Touch the token of your security key" state={methodState} - onRegisterClick={props.onRegisterClick}> + onRegisterClick={props.onRegisterClick} + > <div className={style.icon}> <Icon state={state} timer={timerPercent} onRetryClick={doInitiateSignIn} /> </div> </MethodContainer> - ) -} + ); +}; -export default SecurityKeyMethod +export default SecurityKeyMethod; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ icon: { display: "inline-block", - } + }, })); interface IconProps { @@ -125,22 +138,32 @@ function Icon(props: IconProps) { const progressBarStyle: CSSProperties = { marginTop: theme.spacing(), - } - - const touch = <IconWithContext - icon={<FingerTouchIcon size={64} animated strong />} - context={<LinearProgressBar value={props.timer} style={progressBarStyle} height={theme.spacing(2)} />} - className={state === State.WaitTouch ? undefined : "hidden"} /> - - const failure = <IconWithContext - icon={<FailureIcon />} - context={<Button color="secondary" onClick={props.onRetryClick}>Retry</Button>} - className={state === State.Failure ? undefined : "hidden"} /> + }; + + const touch = ( + <IconWithContext + icon={<FingerTouchIcon size={64} animated strong />} + context={<LinearProgressBar value={props.timer} style={progressBarStyle} height={theme.spacing(2)} />} + className={state === State.WaitTouch ? undefined : "hidden"} + /> + ); + + const failure = ( + <IconWithContext + icon={<FailureIcon />} + context={ + <Button color="secondary" onClick={props.onRetryClick}> + Retry + </Button> + } + className={state === State.Failure ? undefined : "hidden"} + /> + ); return ( <Fragment> {touch} {failure} </Fragment> - ) + ); } diff --git a/web/src/views/LoginPortal/SignOut/SignOut.tsx b/web/src/views/LoginPortal/SignOut/SignOut.tsx index 056ac5de5..b44a4b73a 100644 --- a/web/src/views/LoginPortal/SignOut/SignOut.tsx +++ b/web/src/views/LoginPortal/SignOut/SignOut.tsx @@ -1,14 +1,16 @@ import React, { useEffect, useCallback, useState } from "react"; -import LoginLayout from "../../../layouts/LoginLayout"; -import { useNotifications } from "../../../hooks/NotificationsContext"; -import { signOut } from "../../../services/SignOut"; + import { Typography, makeStyles } from "@material-ui/core"; import { Redirect } from "react-router"; -import { FirstFactorRoute } from "../../../Routes"; -import { useRedirectionURL } from "../../../hooks/RedirectionURL"; + import { useIsMountedRef } from "../../../hooks/Mounted"; +import { useNotifications } from "../../../hooks/NotificationsContext"; +import { useRedirectionURL } from "../../../hooks/RedirectionURL"; +import LoginLayout from "../../../layouts/LoginLayout"; +import { FirstFactorRoute } from "../../../Routes"; +import { signOut } from "../../../services/SignOut"; -export interface Props { } +export interface Props {} const SignOut = function (props: Props) { const mounted = useIsMountedRef(); @@ -33,29 +35,29 @@ const SignOut = function (props: Props) { } }, [createErrorNotification, setTimedOut, mounted]); - useEffect(() => { doSignOut() }, [doSignOut]); + useEffect(() => { + doSignOut(); + }, [doSignOut]); if (timedOut) { if (redirectionURL) { window.location.href = redirectionURL; } else { - return <Redirect to={FirstFactorRoute} /> + return <Redirect to={FirstFactorRoute} />; } } return ( <LoginLayout title="Sign out"> - <Typography className={style.typo} > - You're being signed out and redirected... - </Typography> + <Typography className={style.typo}>You're being signed out and redirected...</Typography> </LoginLayout> - ) -} + ); +}; -export default SignOut +export default SignOut; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ typo: { padding: theme.spacing(), - } -}))
\ No newline at end of file + }, +})); diff --git a/web/src/views/ResetPassword/ResetPasswordStep1.tsx b/web/src/views/ResetPassword/ResetPasswordStep1.tsx index 2709cb8c7..97e52f39e 100644 --- a/web/src/views/ResetPassword/ResetPasswordStep1.tsx +++ b/web/src/views/ResetPassword/ResetPasswordStep1.tsx @@ -1,11 +1,13 @@ import React, { useState } from "react"; -import LoginLayout from "../../layouts/LoginLayout"; + import { Grid, Button, makeStyles } from "@material-ui/core"; -import { useNotifications } from "../../hooks/NotificationsContext"; import { useHistory } from "react-router"; -import { initiateResetPasswordProcess } from "../../services/ResetPassword"; -import { FirstFactorRoute } from "../../Routes"; + import FixedTextField from "../../components/FixedTextField"; +import { useNotifications } from "../../hooks/NotificationsContext"; +import LoginLayout from "../../layouts/LoginLayout"; +import { FirstFactorRoute } from "../../Routes"; +import { initiateResetPasswordProcess } from "../../services/ResetPassword"; const ResetPasswordStep1 = function () { const style = useStyles(); @@ -26,15 +28,15 @@ const ResetPasswordStep1 = function () { } catch (err) { createErrorNotification("There was an issue initiating the password reset process."); } - } + }; const handleResetClick = () => { doInitiateResetPasswordProcess(); - } + }; const handleCancelClick = () => { history.push(FirstFactorRoute); - } + }; return ( <LoginLayout title="Reset password" id="reset-password-step1-stage"> @@ -49,19 +51,17 @@ const ResetPasswordStep1 = function () { value={username} onChange={(e) => setUsername(e.target.value)} onKeyPress={(ev) => { - if (ev.key === 'Enter') { + if (ev.key === "Enter") { doInitiateResetPasswordProcess(); ev.preventDefault(); } - }} /> + }} + /> </Grid> <Grid item xs={6}> - <Button - id="reset-button" - variant="contained" - color="primary" - fullWidth - onClick={handleResetClick}>Reset</Button> + <Button id="reset-button" variant="contained" color="primary" fullWidth onClick={handleResetClick}> + Reset + </Button> </Grid> <Grid item xs={6}> <Button @@ -69,18 +69,21 @@ const ResetPasswordStep1 = function () { variant="contained" color="primary" fullWidth - onClick={handleCancelClick}>Cancel</Button> + onClick={handleCancelClick} + > + Cancel + </Button> </Grid> </Grid> </LoginLayout> - ) -} + ); +}; -export default ResetPasswordStep1 +export default ResetPasswordStep1; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { marginTop: theme.spacing(2), marginBottom: theme.spacing(2), }, -}))
\ No newline at end of file +})); diff --git a/web/src/views/ResetPassword/ResetPasswordStep2.tsx b/web/src/views/ResetPassword/ResetPasswordStep2.tsx index 43c4b8a7f..af3dc5b7a 100644 --- a/web/src/views/ResetPassword/ResetPasswordStep2.tsx +++ b/web/src/views/ResetPassword/ResetPasswordStep2.tsx @@ -1,13 +1,15 @@ import React, { useState, useCallback, useEffect } from "react"; -import LoginLayout from "../../layouts/LoginLayout"; -import classnames from "classnames"; + import { Grid, Button, makeStyles } from "@material-ui/core"; -import { useNotifications } from "../../hooks/NotificationsContext"; +import classnames from "classnames"; import { useHistory, useLocation } from "react-router"; -import { completeResetPasswordProcess, resetPassword } from "../../services/ResetPassword"; + +import FixedTextField from "../../components/FixedTextField"; +import { useNotifications } from "../../hooks/NotificationsContext"; +import LoginLayout from "../../layouts/LoginLayout"; import { FirstFactorRoute } from "../../Routes"; +import { completeResetPasswordProcess, resetPassword } from "../../services/ResetPassword"; import { extractIdentityToken } from "../../utils/IdentityToken"; -import FixedTextField from "../../components/FixedTextField"; const ResetPasswordStep2 = function () { const style = useStyles(); @@ -36,8 +38,9 @@ const ResetPasswordStep2 = function () { setFormDisabled(false); } catch (err) { console.error(err); - createErrorNotification("There was an issue completing the process. " + - "The verification token might have expired."); + createErrorNotification( + "There was an issue completing the process. The verification token might have expired.", + ); setFormDisabled(true); } }, [processToken, createErrorNotification]); @@ -54,11 +57,11 @@ const ResetPasswordStep2 = function () { if (password2 === "") { setErrorPassword2(true); } - return + return; } if (password1 !== password2) { setErrorPassword1(true); - setErrorPassword2(true) + setErrorPassword2(true); createErrorNotification("Passwords do not match."); return; } @@ -76,13 +79,11 @@ const ResetPasswordStep2 = function () { createErrorNotification("There was an issue resetting the password."); } } - } + }; - const handleResetClick = () => - doResetPassword(); + const handleResetClick = () => doResetPassword(); - const handleCancelClick = () => - history.push(FirstFactorRoute); + const handleCancelClick = () => history.push(FirstFactorRoute); return ( <LoginLayout title="Enter new password" id="reset-password-step2-stage"> @@ -95,9 +96,10 @@ const ResetPasswordStep2 = function () { type="password" value={password1} disabled={formDisabled} - onChange={e => setPassword1(e.target.value)} + onChange={(e) => setPassword1(e.target.value)} error={errorPassword1} - className={classnames(style.fullWidth)} /> + className={classnames(style.fullWidth)} + /> </Grid> <Grid item xs={12}> <FixedTextField @@ -107,15 +109,16 @@ const ResetPasswordStep2 = function () { type="password" disabled={formDisabled} value={password2} - onChange={e => setPassword2(e.target.value)} + onChange={(e) => setPassword2(e.target.value)} error={errorPassword2} onKeyPress={(ev) => { - if (ev.key === 'Enter') { + if (ev.key === "Enter") { doResetPassword(); ev.preventDefault(); } }} - className={classnames(style.fullWidth)} /> + className={classnames(style.fullWidth)} + /> </Grid> <Grid item xs={6}> <Button @@ -125,7 +128,10 @@ const ResetPasswordStep2 = function () { name="password1" disabled={formDisabled} onClick={handleResetClick} - className={style.fullWidth}>Reset</Button> + className={style.fullWidth} + > + Reset + </Button> </Grid> <Grid item xs={6}> <Button @@ -134,21 +140,24 @@ const ResetPasswordStep2 = function () { color="primary" name="password2" onClick={handleCancelClick} - className={style.fullWidth}>Cancel</Button> + className={style.fullWidth} + > + Cancel + </Button> </Grid> </Grid> </LoginLayout> - ) -} + ); +}; -export default ResetPasswordStep2 +export default ResetPasswordStep2; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { marginTop: theme.spacing(2), marginBottom: theme.spacing(2), }, fullWidth: { width: "100%", - } -}))
\ No newline at end of file + }, +})); |
