mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Complete rewrite of the UI.
This commit is contained in:
parent
694840790b
commit
605002a333
47
client-react/package-lock.json
generated
47
client-react/package-lock.json
generated
|
@ -3297,6 +3297,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz",
|
||||||
"integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg=="
|
"integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg=="
|
||||||
},
|
},
|
||||||
|
"connected-react-router": {
|
||||||
|
"version": "6.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/connected-react-router/-/connected-react-router-6.2.1.tgz",
|
||||||
|
"integrity": "sha512-7QFs0wPYvwrzA7NptHx0DgblNA/nVErX0TUjTiOCwXSaqj/1Ng+nEmEczrfdA8gw7kIzFIa08WJGMymdb7bAZA==",
|
||||||
|
"requires": {
|
||||||
|
"immutable": "^3.8.1",
|
||||||
|
"seamless-immutable": "^7.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"console-browserify": {
|
"console-browserify": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
|
||||||
|
@ -5957,13 +5966,11 @@
|
||||||
},
|
},
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0",
|
||||||
"concat-map": "0.0.1"
|
"concat-map": "0.0.1"
|
||||||
|
@ -5976,18 +5983,15 @@
|
||||||
},
|
},
|
||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"console-control-strings": {
|
"console-control-strings": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -6090,8 +6094,7 @@
|
||||||
},
|
},
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"ini": {
|
"ini": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
|
@ -6101,7 +6104,6 @@
|
||||||
"is-fullwidth-code-point": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"number-is-nan": "^1.0.0"
|
"number-is-nan": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
@ -6114,20 +6116,17 @@
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"safe-buffer": "^5.1.1",
|
"safe-buffer": "^5.1.1",
|
||||||
"yallist": "^3.0.0"
|
"yallist": "^3.0.0"
|
||||||
|
@ -6144,7 +6143,6 @@
|
||||||
"mkdirp": {
|
"mkdirp": {
|
||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
|
@ -6217,8 +6215,7 @@
|
||||||
},
|
},
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
|
@ -6228,7 +6225,6 @@
|
||||||
"once": {
|
"once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
|
@ -6334,7 +6330,6 @@
|
||||||
"string-width": {
|
"string-width": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"optional": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"code-point-at": "^1.0.0",
|
"code-point-at": "^1.0.0",
|
||||||
"is-fullwidth-code-point": "^1.0.0",
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
|
@ -7380,6 +7375,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/immer/-/immer-1.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/immer/-/immer-1.7.2.tgz",
|
||||||
"integrity": "sha512-4Urocwu9+XLDJw4Tc6ZCg7APVjjLInCFvO4TwGsAYV5zT6YYSor14dsZR0+0tHlDIN92cFUOq+i7fC00G5vTxA=="
|
"integrity": "sha512-4Urocwu9+XLDJw4Tc6ZCg7APVjjLInCFvO4TwGsAYV5zT6YYSor14dsZR0+0tHlDIN92cFUOq+i7fC00G5vTxA=="
|
||||||
},
|
},
|
||||||
|
"immutable": {
|
||||||
|
"version": "3.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
|
||||||
|
"integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM="
|
||||||
|
},
|
||||||
"import-cwd": {
|
"import-cwd": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
||||||
|
@ -15417,6 +15417,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"seamless-immutable": {
|
||||||
|
"version": "7.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/seamless-immutable/-/seamless-immutable-7.1.4.tgz",
|
||||||
|
"integrity": "sha512-XiUO1QP4ki4E2PHegiGAlu6r82o5A+6tRh7IkGGTVg/h+UoeX4nFBeCGPOhb4CYjvkqsfm/TUtvOMYC1xmV30A=="
|
||||||
|
},
|
||||||
"select-hose": {
|
"select-hose": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"@types/redux-thunk": "^2.1.0",
|
"@types/redux-thunk": "^2.1.0",
|
||||||
"await-to-js": "^2.1.1",
|
"await-to-js": "^2.1.1",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
"connected-react-router": "^6.2.1",
|
||||||
"jss": "^9.8.7",
|
"jss": "^9.8.7",
|
||||||
"node-sass": "^4.11.0",
|
"node-sass": "^4.11.0",
|
||||||
"qrcode.react": "^0.9.2",
|
"qrcode.react": "^0.9.2",
|
||||||
|
|
|
@ -4,22 +4,28 @@ import './App.css';
|
||||||
import { Router, Route, Switch } from "react-router-dom";
|
import { Router, Route, Switch } from "react-router-dom";
|
||||||
import { routes } from './routes/index';
|
import { routes } from './routes/index';
|
||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
import reducer from './reducers';
|
import reducer from './reducers';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
|
import { routerMiddleware, ConnectedRouter } from 'connected-react-router';
|
||||||
|
|
||||||
const history = createBrowserHistory();
|
const history = createBrowserHistory();
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
reducer,
|
reducer(history),
|
||||||
applyMiddleware(thunk)
|
compose(
|
||||||
|
applyMiddleware(
|
||||||
|
routerMiddleware(history),
|
||||||
|
thunk
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Router history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<Switch>
|
<Switch>
|
||||||
{routes.map((r, key) => {
|
{routes.map((r, key) => {
|
||||||
|
@ -27,7 +33,7 @@ class App extends Component {
|
||||||
})}
|
})}
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</ConnectedRouter>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { createStyles, Theme } from "@material-ui/core";
|
||||||
|
|
||||||
|
const styles = createStyles((theme: Theme) => ({
|
||||||
|
container: {
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
messageContainer: {
|
||||||
|
fontSize: theme.typography.fontSize,
|
||||||
|
marginTop: theme.spacing.unit * 2,
|
||||||
|
marginBottom: theme.spacing.unit * 2,
|
||||||
|
color: 'green',
|
||||||
|
display: 'inline-block',
|
||||||
|
marginLeft: theme.spacing.unit * 2,
|
||||||
|
textAlign: 'left',
|
||||||
|
|
||||||
|
},
|
||||||
|
successContainer: {
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
paddingTop: theme.spacing.unit * 2,
|
||||||
|
paddingBottom: theme.spacing.unit * 2,
|
||||||
|
marginTop: theme.spacing.unit * 2,
|
||||||
|
marginBottom: theme.spacing.unit * 3,
|
||||||
|
border: '1px solid #8ae48a',
|
||||||
|
borderRadius: '100px',
|
||||||
|
},
|
||||||
|
successLogoContainer: {
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
logoutButtonContainer: {
|
||||||
|
marginTop: theme.spacing.unit * 2,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default styles;
|
|
@ -3,7 +3,6 @@ import { createStyles, Theme } from "@material-ui/core";
|
||||||
const styles = createStyles((theme: Theme) => ({
|
const styles = createStyles((theme: Theme) => ({
|
||||||
fields: {
|
fields: {
|
||||||
marginTop: theme.spacing.unit * 2,
|
marginTop: theme.spacing.unit * 2,
|
||||||
marginBottom: theme.spacing.unit,
|
|
||||||
},
|
},
|
||||||
field: {
|
field: {
|
||||||
paddingBottom: theme.spacing.unit * 2,
|
paddingBottom: theme.spacing.unit * 2,
|
|
@ -9,7 +9,7 @@ const styles = createStyles((theme: Theme) => ({
|
||||||
},
|
},
|
||||||
messageContainer: {
|
messageContainer: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
fontSize: theme.typography.fontSize * 0.9,
|
fontSize: theme.typography.fontSize,
|
||||||
padding: theme.spacing.unit * 2,
|
padding: theme.spacing.unit * 2,
|
||||||
border: '1px solid red',
|
border: '1px solid red',
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
|
|
|
@ -29,7 +29,6 @@ const styles = createStyles((theme: Theme) => ({
|
||||||
paddingRight: theme.spacing.unit * 2,
|
paddingRight: theme.spacing.unit * 2,
|
||||||
border: '1px solid #e0e0e0',
|
border: '1px solid #e0e0e0',
|
||||||
borderRadius: '2px',
|
borderRadius: '2px',
|
||||||
textAlign: 'justify',
|
|
||||||
},
|
},
|
||||||
methodName: {
|
methodName: {
|
||||||
fontSize: theme.typography.fontSize * 1.2,
|
fontSize: theme.typography.fontSize * 1.2,
|
|
@ -18,7 +18,7 @@ const styles = createStyles((theme: Theme) => ({
|
||||||
title: {
|
title: {
|
||||||
fontSize: '1.4em',
|
fontSize: '1.4em',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
borderBottom: '1px solid #c7c7c7',
|
borderBottom: '5px solid ' + theme.palette.primary.main,
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
paddingRight: '10px',
|
paddingRight: '10px',
|
||||||
paddingBottom: '5px',
|
paddingBottom: '5px',
|
||||||
|
|
|
@ -7,10 +7,27 @@ const styles = createStyles((theme: Theme) => ({
|
||||||
field: {
|
field: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
button: {
|
buttonsContainer: {
|
||||||
marginTop: theme.spacing.unit * 2,
|
marginTop: theme.spacing.unit * 2,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}
|
},
|
||||||
|
buttonContainer: {
|
||||||
|
width: '50%',
|
||||||
|
display: 'inline-block',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
},
|
||||||
|
buttonConfirmContainer: {
|
||||||
|
paddingRight: theme.spacing.unit / 2,
|
||||||
|
},
|
||||||
|
buttonConfirm: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
buttonCancelContainer: {
|
||||||
|
paddingLeft: theme.spacing.unit / 2,
|
||||||
|
},
|
||||||
|
buttonCancel: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default styles;
|
export default styles;
|
|
@ -8,6 +8,20 @@ const styles = createStyles((theme: Theme) => ({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
marginBottom: theme.spacing.unit * 2,
|
marginBottom: theme.spacing.unit * 2,
|
||||||
},
|
},
|
||||||
|
buttonsContainer: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
buttonContainer: {
|
||||||
|
width: '50%',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
display: 'inline-block',
|
||||||
|
},
|
||||||
|
buttonResetContainer: {
|
||||||
|
paddingRight: theme.spacing.unit / 2,
|
||||||
|
},
|
||||||
|
buttonCancelContainer: {
|
||||||
|
paddingLeft: theme.spacing.unit / 2,
|
||||||
|
},
|
||||||
button: {
|
button: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
}
|
}
|
||||||
|
|
19
client-react/src/behaviors/FetchStateBehavior.ts
Normal file
19
client-react/src/behaviors/FetchStateBehavior.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import * as AutheliaService from '../services/AutheliaService';
|
||||||
|
import { fetchStateFailure, fetchStateSuccess } from "../reducers/Portal/Authentication/actions";
|
||||||
|
import to from "await-to-js";
|
||||||
|
|
||||||
|
export default async function(dispatch: Dispatch) {
|
||||||
|
let err, res;
|
||||||
|
[err, res] = await to(AutheliaService.fetchState());
|
||||||
|
if (err) {
|
||||||
|
await dispatch(fetchStateFailure(err.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!res) {
|
||||||
|
await dispatch(fetchStateFailure('No response'));
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await dispatch(fetchStateSuccess(res));
|
||||||
|
return res;
|
||||||
|
}
|
18
client-react/src/behaviors/LogoutBehavior.ts
Normal file
18
client-react/src/behaviors/LogoutBehavior.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import { logout, logoutFailure, logoutSuccess } from "../reducers/Portal/SecondFactor/actions";
|
||||||
|
import to from "await-to-js";
|
||||||
|
import * as AutheliaService from '../services/AutheliaService';
|
||||||
|
import fetchState from "./FetchStateBehavior";
|
||||||
|
|
||||||
|
export default async function(dispatch: Dispatch) {
|
||||||
|
await dispatch(logout());
|
||||||
|
let err, res;
|
||||||
|
[err, res] = await to(AutheliaService.postLogout());
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
await dispatch(logoutFailure(err.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await dispatch(logoutSuccess());
|
||||||
|
await fetchState(dispatch);
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
|
||||||
|
import styles from '../../assets/jss/components/AlreadyAuthenticated/AlreadyAuthenticated';
|
||||||
|
import { WithStyles, withStyles, Button } from "@material-ui/core";
|
||||||
|
import CircleLoader, { Status } from "../CircleLoader/CircleLoader";
|
||||||
|
|
||||||
|
export interface OwnProps {
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DispatchProps {
|
||||||
|
onLogoutClicked: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = OwnProps & DispatchProps & WithStyles;
|
||||||
|
|
||||||
|
class AlreadyAuthenticated extends Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<div className={classes.successContainer}>
|
||||||
|
<CircleLoader status={Status.SUCCESSFUL} />
|
||||||
|
<span className={classes.messageContainer}>
|
||||||
|
<b>{this.props.username}</b><br/>
|
||||||
|
you are authenticated
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>Close this tab or logout</div>
|
||||||
|
<div className={classes.logoutButtonContainer}>
|
||||||
|
<Button
|
||||||
|
onClick={this.props.onLogoutClicked}
|
||||||
|
variant="contained"
|
||||||
|
color="primary">
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(AlreadyAuthenticated);
|
|
@ -7,40 +7,38 @@ import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
import Checkbox from '@material-ui/core/Checkbox';
|
import Checkbox from '@material-ui/core/Checkbox';
|
||||||
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { RouterProps, RouteProps } from "react-router";
|
|
||||||
import { WithStyles, withStyles } from "@material-ui/core";
|
import { WithStyles, withStyles } from "@material-ui/core";
|
||||||
|
|
||||||
import firstFactorViewStyles from '../../assets/jss/views/FirstFactorView/FirstFactorView';
|
import styles from '../../assets/jss/components/FirstFactorForm/FirstFactorForm';
|
||||||
import FormNotification from "../../components/FormNotification/FormNotification";
|
import FormNotification from "../../components/FormNotification/FormNotification";
|
||||||
|
|
||||||
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'
|
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'
|
||||||
import CheckBoxIcon from '@material-ui/icons/CheckBox';
|
import CheckBoxIcon from '@material-ui/icons/CheckBox';
|
||||||
import StateSynchronizer from "../../containers/components/StateSynchronizer/StateSynchronizer";
|
|
||||||
import RemoteState from "../../reducers/Portal/RemoteState";
|
|
||||||
|
|
||||||
export interface Props extends RouteProps, RouterProps, WithStyles {
|
export interface StateProps {
|
||||||
|
formDisabled: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DispatchProps {
|
||||||
onAuthenticationRequested(username: string, password: string): void;
|
onAuthenticationRequested(username: string, password: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Props = StateProps & DispatchProps & WithStyles;
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
rememberMe: boolean;
|
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
loginButtonDisabled: boolean;
|
rememberMe: boolean;
|
||||||
errorMessage: string | null;
|
|
||||||
remoteState: RemoteState | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FirstFactorView extends Component<Props, State> {
|
class FirstFactorForm extends Component<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
rememberMe: false,
|
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
loginButtonDisabled: false,
|
rememberMe: false,
|
||||||
errorMessage: null,
|
|
||||||
remoteState: null,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,13 +66,13 @@ class FirstFactorView extends Component<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderWithState() {
|
render() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FormNotification
|
<FormNotification
|
||||||
show={this.state.errorMessage != null}>
|
show={this.props.error != null}>
|
||||||
{this.state.errorMessage || ''}
|
{this.props.error || ''}
|
||||||
</FormNotification>
|
</FormNotification>
|
||||||
<div className={classes.fields}>
|
<div className={classes.fields}>
|
||||||
<div className={classes.field}>
|
<div className={classes.field}>
|
||||||
|
@ -83,6 +81,7 @@ class FirstFactorView extends Component<Props, State> {
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
id="username"
|
id="username"
|
||||||
label="Username"
|
label="Username"
|
||||||
|
disabled={this.props.formDisabled}
|
||||||
onChange={this.onUsernameChanged}>
|
onChange={this.onUsernameChanged}>
|
||||||
</TextField>
|
</TextField>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,6 +92,7 @@ class FirstFactorView extends Component<Props, State> {
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
label="Password"
|
label="Password"
|
||||||
type="password"
|
type="password"
|
||||||
|
disabled={this.props.formDisabled}
|
||||||
onChange={this.onPasswordChanged}
|
onChange={this.onPasswordChanged}
|
||||||
onKeyPress={this.onPasswordKeyPressed}>
|
onKeyPress={this.onPasswordKeyPressed}>
|
||||||
</TextField>
|
</TextField>
|
||||||
|
@ -104,7 +104,7 @@ class FirstFactorView extends Component<Props, State> {
|
||||||
onClick={this.onLoginClicked}
|
onClick={this.onLoginClicked}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={this.state.loginButtonDisabled}>
|
disabled={this.props.formDisabled}>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,30 +132,11 @@ class FirstFactorView extends Component<Props, State> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<StateSynchronizer
|
|
||||||
onLoaded={(remoteState) => this.setState({remoteState})}/>
|
|
||||||
{this.state.remoteState ? this.renderWithState() : null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private authenticate() {
|
private authenticate() {
|
||||||
this.setState({loginButtonDisabled: true});
|
|
||||||
this.props.onAuthenticationRequested(
|
this.props.onAuthenticationRequested(
|
||||||
this.state.username,
|
this.state.username,
|
||||||
this.state.password);
|
this.state.password);
|
||||||
this.setState({errorMessage: null});
|
|
||||||
}
|
|
||||||
|
|
||||||
onFailure = (error: string) => {
|
|
||||||
this.setState({
|
|
||||||
loginButtonDisabled: false,
|
|
||||||
errorMessage: 'An error occured. Your username/password are probably wrong.'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(firstFactorViewStyles)(FirstFactorView);
|
export default withStyles(styles)(FirstFactorForm);
|
|
@ -1,38 +1,52 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component, KeyboardEvent, ChangeEvent } from 'react';
|
||||||
|
|
||||||
import { WithStyles, withStyles, Button, TextField } from '@material-ui/core';
|
import { WithStyles, withStyles, Button, TextField } from '@material-ui/core';
|
||||||
|
|
||||||
import styles from '../../assets/jss/views/SecondFactorView/SecondFactorView';
|
import styles from '../../assets/jss/components/SecondFactorForm/SecondFactorForm';
|
||||||
import StateSynchronizer from '../../containers/components/StateSynchronizer/StateSynchronizer';
|
|
||||||
import { RouterProps, Redirect } from 'react-router';
|
|
||||||
import RemoteState from '../../reducers/Portal/RemoteState';
|
|
||||||
import AuthenticationLevel from '../../types/AuthenticationLevel';
|
|
||||||
import { WithState } from '../../components/StateSynchronizer/WithState';
|
|
||||||
import CircleLoader, { Status } from '../../components/CircleLoader/CircleLoader';
|
import CircleLoader, { Status } from '../../components/CircleLoader/CircleLoader';
|
||||||
|
import FormNotification from '../FormNotification/FormNotification';
|
||||||
|
|
||||||
export interface Props extends WithStyles, RouterProps, WithState {
|
export interface OwnProps {
|
||||||
|
username: string;
|
||||||
|
redirection: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StateProps {
|
||||||
securityKeySupported: boolean;
|
securityKeySupported: boolean;
|
||||||
securityKeyVerified: boolean;
|
securityKeyVerified: boolean;
|
||||||
securityKeyError: string | null;
|
securityKeyError: string | null;
|
||||||
|
|
||||||
|
oneTimePasswordVerificationInProgress: boolean,
|
||||||
|
oneTimePasswordVerificationError: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DispatchProps {
|
||||||
|
onInit: () => void;
|
||||||
onLogoutClicked: () => void;
|
onLogoutClicked: () => void;
|
||||||
onRegisterSecurityKeyClicked: () => void;
|
onRegisterSecurityKeyClicked: () => void;
|
||||||
onRegisterOneTimePasswordClicked: () => void;
|
onRegisterOneTimePasswordClicked: () => void;
|
||||||
onStateLoaded: (state: RemoteState) => void;
|
|
||||||
};
|
onOneTimePasswordValidationRequested: (token: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = OwnProps & StateProps & DispatchProps & WithStyles;
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
remoteState: RemoteState | null;
|
oneTimePassword: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SecondFactorView extends Component<Props, State> {
|
class SecondFactorView extends Component<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
remoteState: null,
|
oneTimePassword: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.onInit();
|
||||||
|
}
|
||||||
|
|
||||||
private renderU2f(n: number) {
|
private renderU2f(n: number) {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
let u2fStatus = Status.LOADING;
|
let u2fStatus = Status.LOADING;
|
||||||
|
@ -58,17 +72,37 @@ class SecondFactorView extends Component<Props, State> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onOneTimePasswordChanged = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({oneTimePassword: e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTotpKeyPressed = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.onOneTimePasswordValidationRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onOneTimePasswordValidationRequested = () => {
|
||||||
|
if (this.props.oneTimePasswordVerificationInProgress) return;
|
||||||
|
this.props.onOneTimePasswordValidationRequested(this.state.oneTimePassword);
|
||||||
|
}
|
||||||
|
|
||||||
private renderTotp(n: number) {
|
private renderTotp(n: number) {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={classes.methodTotp} key='totp-method'>
|
<div className={classes.methodTotp} key='totp-method'>
|
||||||
<div className={classes.methodName}>Option {n} - One-Time Password</div>
|
<div className={classes.methodName}>Option {n} - One-Time Password</div>
|
||||||
|
<FormNotification show={this.props.oneTimePasswordVerificationError !== null}>
|
||||||
|
{this.props.oneTimePasswordVerificationError}
|
||||||
|
</FormNotification>
|
||||||
<TextField
|
<TextField
|
||||||
className={classes.totpField}
|
className={classes.totpField}
|
||||||
name="password"
|
name="totp-token"
|
||||||
id="password"
|
id="totp-token"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
label="One-Time Password">
|
label="One-Time Password"
|
||||||
|
onChange={this.onOneTimePasswordChanged}
|
||||||
|
onKeyPress={this.onTotpKeyPressed}>
|
||||||
</TextField>
|
</TextField>
|
||||||
<div className={classes.registerDeviceContainer}>
|
<div className={classes.registerDeviceContainer}>
|
||||||
<a className={classes.registerDevice} href="#"
|
<a className={classes.registerDevice} href="#"
|
||||||
|
@ -79,7 +113,9 @@ class SecondFactorView extends Component<Props, State> {
|
||||||
<Button
|
<Button
|
||||||
className={classes.totpButton}
|
className={classes.totpButton}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary">
|
color="primary"
|
||||||
|
onClick={this.onOneTimePasswordValidationRequested}
|
||||||
|
disabled={this.props.oneTimePasswordVerificationInProgress}>
|
||||||
OK
|
OK
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -103,16 +139,12 @@ class SecondFactorView extends Component<Props, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderWithState(state: RemoteState) {
|
render() {
|
||||||
if (state.authentication_level < AuthenticationLevel.ONE_FACTOR) {
|
|
||||||
return <Redirect to='/' key='redirect' />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={classes.container}>
|
<div className={classes.container}>
|
||||||
<div className={classes.header}>
|
<div className={classes.header}>
|
||||||
<div className={classes.hello}>Hello <b>{state.username}</b></div>
|
<div className={classes.hello}>Hello <b>{this.props.username}</b></div>
|
||||||
<div className={classes.logout}>
|
<div className={classes.logout}>
|
||||||
<a onClick={this.props.onLogoutClicked} href="#">Logout</a>
|
<a onClick={this.props.onLogoutClicked} href="#">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -123,21 +155,6 @@ class SecondFactorView extends Component<Props, State> {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onStateLoaded = (remoteState: RemoteState) => {
|
|
||||||
this.setState({remoteState});
|
|
||||||
this.props.onStateLoaded(remoteState);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<StateSynchronizer
|
|
||||||
onLoaded={this.onStateLoaded}/>
|
|
||||||
{this.state.remoteState ? this.renderWithState(this.state.remoteState) : null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(SecondFactorView);
|
export default withStyles(styles)(SecondFactorView);
|
|
@ -1,28 +0,0 @@
|
||||||
import React, { Component } from "react";
|
|
||||||
import RemoteState from "../../reducers/Portal/RemoteState";
|
|
||||||
import { WithState } from "./WithState";
|
|
||||||
|
|
||||||
export type OnLoaded = (state: RemoteState) => void;
|
|
||||||
export type OnError = (err: Error) => void;
|
|
||||||
|
|
||||||
export interface Props extends WithState {
|
|
||||||
fetch: (onloaded: OnLoaded, onerror: OnError) => void;
|
|
||||||
onLoaded: OnLoaded;
|
|
||||||
onError?: OnError;
|
|
||||||
}
|
|
||||||
|
|
||||||
class StateSynchronizer extends Component<Props> {
|
|
||||||
componentWillMount() {
|
|
||||||
this.props.fetch(
|
|
||||||
(state) => this.props.onLoaded(state),
|
|
||||||
(err: Error) => {
|
|
||||||
if (this.props.onError) this.props.onError(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default StateSynchronizer;
|
|
|
@ -1,7 +0,0 @@
|
||||||
import RemoteState from '../../reducers/Portal/RemoteState';
|
|
||||||
|
|
||||||
export interface WithState {
|
|
||||||
state: RemoteState | null;
|
|
||||||
stateError: string | null;
|
|
||||||
stateLoading: boolean;
|
|
||||||
}
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { RootState } from '../../../reducers';
|
||||||
|
import AlreadyAuthenticated, { DispatchProps } from '../../../components/AlreadyAuthenticated/AlreadyAuthenticated';
|
||||||
|
import LogoutBehavior from '../../../behaviors/LogoutBehavior';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState) => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
|
||||||
|
return {
|
||||||
|
onLogoutClicked: () => LogoutBehavior(dispatch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(AlreadyAuthenticated);
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { authenticateFailure, authenticateSuccess, authenticate } from '../../../reducers/Portal/FirstFactor/actions';
|
||||||
|
import FirstFactorForm, { StateProps } from '../../../components/FirstFactorForm/FirstFactorForm';
|
||||||
|
import { RootState } from '../../../reducers';
|
||||||
|
import * as AutheliaService from '../../../services/AutheliaService';
|
||||||
|
import to from 'await-to-js';
|
||||||
|
import FetchStateBehavior from '../../../behaviors/FetchStateBehavior';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState): StateProps => {
|
||||||
|
return {
|
||||||
|
error: state.firstFactor.error,
|
||||||
|
formDisabled: state.firstFactor.loading,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAuthenticationRequested(dispatch: Dispatch) {
|
||||||
|
return async (username: string, password: string) => {
|
||||||
|
let err, res;
|
||||||
|
|
||||||
|
// Validate first factor
|
||||||
|
dispatch(authenticate());
|
||||||
|
[err, res] = await to(AutheliaService.postFirstFactorAuth(username, password));
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
await dispatch(authenticateFailure(err.message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
await dispatch(authenticateFailure('No response'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
if ('error' in json) {
|
||||||
|
await dispatch(authenticateFailure(json['error']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(authenticateSuccess());
|
||||||
|
|
||||||
|
// fetch state
|
||||||
|
FetchStateBehavior(dispatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||||
|
return {
|
||||||
|
onAuthenticationRequested: onAuthenticationRequested(dispatch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(FirstFactorForm);
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { RootState } from '../../../reducers';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import u2fApi from 'u2f-api';
|
||||||
|
import to from 'await-to-js';
|
||||||
|
import { securityKeySignSuccess, securityKeySign, securityKeySignFailure, setSecurityKeySupported, oneTimePasswordVerification, oneTimePasswordVerificationFailure, oneTimePasswordVerificationSuccess } from '../../../reducers/Portal/SecondFactor/actions';
|
||||||
|
import SecondFactorForm, { OwnProps, StateProps } from '../../../components/SecondFactorForm/SecondFactorForm';
|
||||||
|
import * as AutheliaService from '../../../services/AutheliaService';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
import fetchState from '../../../behaviors/FetchStateBehavior';
|
||||||
|
import LogoutBehavior from '../../../behaviors/LogoutBehavior';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState): StateProps => ({
|
||||||
|
securityKeySupported: state.secondFactor.securityKeySupported,
|
||||||
|
securityKeyVerified: state.secondFactor.securityKeySignSuccess || false,
|
||||||
|
securityKeyError: state.secondFactor.error,
|
||||||
|
|
||||||
|
oneTimePasswordVerificationInProgress: state.secondFactor.oneTimePasswordVerificationLoading,
|
||||||
|
oneTimePasswordVerificationError: state.secondFactor.oneTimePasswordVerificationError,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function triggerSecurityKeySigning(dispatch: Dispatch) {
|
||||||
|
let err, result;
|
||||||
|
dispatch(securityKeySign());
|
||||||
|
[err, result] = await to(AutheliaService.requestSigning());
|
||||||
|
if (err) {
|
||||||
|
await dispatch(securityKeySignFailure(err.message));
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
await dispatch(securityKeySignFailure('No response'));
|
||||||
|
throw 'No response';
|
||||||
|
}
|
||||||
|
|
||||||
|
[err, result] = await to(u2fApi.sign(result, 60));
|
||||||
|
if (err) {
|
||||||
|
await dispatch(securityKeySignFailure(err.message));
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
await dispatch(securityKeySignFailure('No response'));
|
||||||
|
throw 'No response';
|
||||||
|
}
|
||||||
|
|
||||||
|
[err, result] = await to(AutheliaService.completeSecurityKeySigning(result));
|
||||||
|
if (err) {
|
||||||
|
await dispatch(securityKeySignFailure(err.message));
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
await dispatch(securityKeySignSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectOnSuccess(dispatch: Dispatch, ownProps: OwnProps, duration?: number) {
|
||||||
|
function redirect() {
|
||||||
|
if (ownProps.redirection) {
|
||||||
|
window.location.href = ownProps.redirection;
|
||||||
|
} else {
|
||||||
|
fetchState(dispatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duration) {
|
||||||
|
setTimeout(redirect, duration);
|
||||||
|
} else {
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps) => {
|
||||||
|
return {
|
||||||
|
onLogoutClicked: () => LogoutBehavior(dispatch),
|
||||||
|
onRegisterSecurityKeyClicked: async () => {
|
||||||
|
await AutheliaService.startU2FRegistrationIdentityProcess();
|
||||||
|
await dispatch(push('/confirmation-sent'));
|
||||||
|
},
|
||||||
|
onRegisterOneTimePasswordClicked: async () => {
|
||||||
|
await AutheliaService.startTOTPRegistrationIdentityProcess();
|
||||||
|
await dispatch(push('/confirmation-sent'));
|
||||||
|
},
|
||||||
|
onInit: async () => {
|
||||||
|
const isU2FSupported = await u2fApi.isSupported();
|
||||||
|
if (isU2FSupported) {
|
||||||
|
await dispatch(setSecurityKeySupported(true));
|
||||||
|
await triggerSecurityKeySigning(dispatch);
|
||||||
|
redirectOnSuccess(dispatch, ownProps, 1000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onOneTimePasswordValidationRequested: async (token: string) => {
|
||||||
|
let err, res;
|
||||||
|
dispatch(oneTimePasswordVerification());
|
||||||
|
[err, res] = await to(AutheliaService.verifyTotpToken(token));
|
||||||
|
if (err) {
|
||||||
|
dispatch(oneTimePasswordVerificationFailure(err.message));
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if (!res) {
|
||||||
|
dispatch(oneTimePasswordVerificationFailure('No response'));
|
||||||
|
throw 'No response';
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await res.json();
|
||||||
|
if ('error' in body) {
|
||||||
|
dispatch(oneTimePasswordVerificationFailure(body['error']));
|
||||||
|
throw body['error'];
|
||||||
|
}
|
||||||
|
dispatch(oneTimePasswordVerificationSuccess());
|
||||||
|
redirectOnSuccess(dispatch, ownProps);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(SecondFactorForm);
|
|
@ -1,31 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import StateSynchronizer, { OnLoaded, OnError } from '../../../components/StateSynchronizer/StateSynchronizer';
|
|
||||||
import { RootState } from '../../../reducers';
|
|
||||||
import { fetchStateSuccess, fetchState, fetchStateFailure } from '../../../reducers/Portal/FirstFactor/actions';
|
|
||||||
import RemoteState from '../../../reducers/Portal/RemoteState';
|
|
||||||
import { Dispatch } from 'redux';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: RootState) => ({
|
|
||||||
state: state.firstFactor.remoteState,
|
|
||||||
stateError: state.firstFactor.remoteStateError,
|
|
||||||
stateLoading: state.firstFactor.remoteStateLoading,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => {
|
|
||||||
return {
|
|
||||||
fetch: (onloaded: OnLoaded, onerror: OnError) => {
|
|
||||||
dispatch(fetchState());
|
|
||||||
fetch('/api/state').then(async (res) => {
|
|
||||||
const body = await res.json() as RemoteState;
|
|
||||||
await dispatch(fetchStateSuccess(body));
|
|
||||||
await onloaded(body);
|
|
||||||
})
|
|
||||||
.catch(async (err) => {
|
|
||||||
await dispatch(fetchStateFailure(err));
|
|
||||||
await onerror(err);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(StateSynchronizer);
|
|
|
@ -2,8 +2,6 @@ import { connect } from 'react-redux';
|
||||||
import PortalLayout from '../../../layouts/PortalLayout/PortalLayout';
|
import PortalLayout from '../../../layouts/PortalLayout/PortalLayout';
|
||||||
import { RootState } from '../../../reducers';
|
import { RootState } from '../../../reducers';
|
||||||
|
|
||||||
const mapStateToProps = (state: RootState) => ({
|
const mapStateToProps = (state: RootState) => ({});
|
||||||
authenticationLevel: (state.firstFactor.remoteState) ? state.firstFactor.remoteState.authentication_level : 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(PortalLayout);
|
export default connect(mapStateToProps)(PortalLayout);
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import AuthenticationView, {StateProps, Stage, DispatchProps} from '../../../views/AuthenticationView/AuthenticationView';
|
||||||
|
import { RootState } from '../../../reducers';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import AuthenticationLevel from '../../../types/AuthenticationLevel';
|
||||||
|
import FetchStateBehavior from '../../../behaviors/FetchStateBehavior';
|
||||||
|
import { setRedirectionUrl } from '../../../reducers/Portal/Authentication/actions';
|
||||||
|
|
||||||
|
function authenticationLevelToStage(level: AuthenticationLevel): Stage {
|
||||||
|
switch (level) {
|
||||||
|
case AuthenticationLevel.NOT_AUTHENTICATED:
|
||||||
|
return Stage.FIRST_FACTOR;
|
||||||
|
case AuthenticationLevel.ONE_FACTOR:
|
||||||
|
return Stage.SECOND_FACTOR;
|
||||||
|
case AuthenticationLevel.TWO_FACTOR:
|
||||||
|
return Stage.ALREADY_AUTHENTICATED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState): StateProps => {
|
||||||
|
const stage = (state.authentication.remoteState)
|
||||||
|
? authenticationLevelToStage(state.authentication.remoteState.authentication_level)
|
||||||
|
: Stage.FIRST_FACTOR;
|
||||||
|
return {
|
||||||
|
redirectionUrl: state.authentication.redirectionUrl,
|
||||||
|
remoteState: state.authentication.remoteState,
|
||||||
|
stage: stage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
|
||||||
|
return {
|
||||||
|
onInit: async (redirectionUrl?: string) => {
|
||||||
|
await FetchStateBehavior(dispatch);
|
||||||
|
if (redirectionUrl) {
|
||||||
|
await dispatch(setRedirectionUrl(redirectionUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(AuthenticationView);
|
|
@ -1,57 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import QueryString from 'query-string';
|
|
||||||
import FirstFactorView, { Props } from '../../../views/FirstFactorView/FirstFactorView';
|
|
||||||
import { Dispatch } from 'redux';
|
|
||||||
import { authenticateFailure, authenticateSuccess, authenticate } from '../../../reducers/Portal/FirstFactor/actions';
|
|
||||||
import { RootState } from '../../../reducers';
|
|
||||||
|
|
||||||
|
|
||||||
const mapStateToProps = (state: RootState) => ({});
|
|
||||||
|
|
||||||
function redirect2FA(props: Props) {
|
|
||||||
if (!props.location) {
|
|
||||||
props.history.push('/2fa');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const params = QueryString.parse(props.location.search);
|
|
||||||
|
|
||||||
if ('rd' in params) {
|
|
||||||
const rd = params['rd'] as string;
|
|
||||||
props.history.push(`/2fa?rd=${rd}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
props.history.push('/2fa');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAuthenticationRequested(dispatch: Dispatch, ownProps: Props) {
|
|
||||||
return async (username: string, password: string) => {
|
|
||||||
dispatch(authenticate());
|
|
||||||
fetch('/api/firstfactor', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
})
|
|
||||||
}).then(async (res) => {
|
|
||||||
const json = await res.json();
|
|
||||||
if ('error' in json) {
|
|
||||||
dispatch(authenticateFailure(json['error']));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(authenticateSuccess());
|
|
||||||
redirect2FA(ownProps);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => {
|
|
||||||
return {
|
|
||||||
onAuthenticationRequested: onAuthenticationRequested(dispatch, ownProps),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(FirstFactorView);
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { RootState } from '../../../reducers';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
import * as AutheliaService from '../../../services/AutheliaService';
|
||||||
|
import ForgotPasswordView from '../../../views/ForgotPasswordView/ForgotPasswordView';
|
||||||
|
import { forgotPasswordRequest, forgotPasswordSuccess, forgotPasswordFailure } from '../../../reducers/Portal/ForgotPassword/actions';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState) => ({
|
||||||
|
disabled: state.forgotPassword.loading,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||||
|
return {
|
||||||
|
onPasswordResetRequested: async (username: string) => {
|
||||||
|
try {
|
||||||
|
dispatch(forgotPasswordRequest());
|
||||||
|
await AutheliaService.initiatePasswordResetIdentityValidation(username);
|
||||||
|
dispatch(forgotPasswordSuccess());
|
||||||
|
await dispatch(push('/confirmation-sent'));
|
||||||
|
} catch (err) {
|
||||||
|
dispatch(forgotPasswordFailure(err.message));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancelClicked: async () => {
|
||||||
|
dispatch(push('/'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ForgotPasswordView);
|
|
@ -4,7 +4,7 @@ import { RootState } from '../../../reducers';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import {to} from 'await-to-js';
|
import {to} from 'await-to-js';
|
||||||
import { generateTotpSecret, generateTotpSecretSuccess, generateTotpSecretFailure } from '../../../reducers/Portal/OneTimePasswordRegistration/actions';
|
import { generateTotpSecret, generateTotpSecretSuccess, generateTotpSecretFailure } from '../../../reducers/Portal/OneTimePasswordRegistration/actions';
|
||||||
import { Props } from '../../../views/OneTimePasswordRegistrationView/OneTimePasswordRegistrationView';
|
import { push } from 'connected-react-router';
|
||||||
|
|
||||||
const mapStateToProps = (state: RootState) => ({
|
const mapStateToProps = (state: RootState) => ({
|
||||||
error: state.oneTimePasswordRegistration.error,
|
error: state.oneTimePasswordRegistration.error,
|
||||||
|
@ -46,7 +46,7 @@ async function tryGenerateTotpSecret(dispatch: Dispatch, token: string) {
|
||||||
dispatch(generateTotpSecretSuccess(result));
|
dispatch(generateTotpSecretSuccess(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => {
|
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||||
let internalToken: string;
|
let internalToken: string;
|
||||||
return {
|
return {
|
||||||
onInit: async (token: string) => {
|
onInit: async (token: string) => {
|
||||||
|
@ -57,10 +57,10 @@ const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => {
|
||||||
await tryGenerateTotpSecret(dispatch, internalToken);
|
await tryGenerateTotpSecret(dispatch, internalToken);
|
||||||
},
|
},
|
||||||
onCancelClicked: () => {
|
onCancelClicked: () => {
|
||||||
ownProps.history.push('/2fa');
|
dispatch(push('/'));
|
||||||
},
|
},
|
||||||
onLoginClicked: () => {
|
onLoginClicked: () => {
|
||||||
ownProps.history.push('/2fa');
|
dispatch(push('/'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { RootState } from '../../../reducers';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
import * as AutheliaService from '../../../services/AutheliaService';
|
||||||
|
import ResetPasswordView, { StateProps } from '../../../views/ResetPasswordView/ResetPasswordView';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState): StateProps => ({
|
||||||
|
disabled: state.resetPassword.loading,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||||
|
return {
|
||||||
|
onInit: async (token: string) => {
|
||||||
|
await AutheliaService.completePasswordResetIdentityValidation(token);
|
||||||
|
},
|
||||||
|
onPasswordResetRequested: async (newPassword: string) => {
|
||||||
|
await AutheliaService.resetPassword(newPassword);
|
||||||
|
await dispatch(push('/'));
|
||||||
|
},
|
||||||
|
onCancelClicked: async () => {
|
||||||
|
await dispatch(push('/'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ResetPasswordView);
|
|
@ -1,136 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import QueryString from 'query-string';
|
|
||||||
import SecondFactorView, {Props} from '../../../views/SecondFactorView/SecondFactorView';
|
|
||||||
import { RootState } from '../../../reducers';
|
|
||||||
import { Dispatch } from 'redux';
|
|
||||||
import u2fApi, { SignResponse } from 'u2f-api';
|
|
||||||
import to from 'await-to-js';
|
|
||||||
import { logoutSuccess, logoutFailure, logout, securityKeySignSuccess, securityKeySign, securityKeySignFailure, setSecurityKeySupported } from '../../../reducers/Portal/SecondFactor/actions';
|
|
||||||
import AuthenticationLevel from '../../../types/AuthenticationLevel';
|
|
||||||
import RemoteState from '../../../reducers/Portal/RemoteState';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: RootState) => ({
|
|
||||||
state: state.firstFactor.remoteState,
|
|
||||||
stateError: state.firstFactor.remoteStateError,
|
|
||||||
securityKeySupported: state.secondFactor.securityKeySupported,
|
|
||||||
securityKeyVerified: state.secondFactor.securityKeySignSuccess || false,
|
|
||||||
securityKeyError: state.secondFactor.error,
|
|
||||||
});
|
|
||||||
|
|
||||||
async function requestSigning() {
|
|
||||||
return fetch('/api/u2f/sign_request')
|
|
||||||
.then(async (res) => {
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error('Status code ' + res.status);
|
|
||||||
}
|
|
||||||
return res.json();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function completeSecurityKeySigning(response: u2fApi.SignResponse) {
|
|
||||||
return fetch('/api/u2f/sign', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(response),
|
|
||||||
})
|
|
||||||
.then(async (res) => {
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error('Status code ' + res.status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function triggerSecurityKeySigning(dispatch: Dispatch, props: Props) {
|
|
||||||
let err, result;
|
|
||||||
dispatch(securityKeySign());
|
|
||||||
[err, result] = await to(requestSigning());
|
|
||||||
if (err) {
|
|
||||||
dispatch(securityKeySignFailure(err.message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
[err, result] = await to(u2fApi.sign(result, 60));
|
|
||||||
if (err) {
|
|
||||||
dispatch(securityKeySignFailure(err.message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
[err, result] = await to(completeSecurityKeySigning(result as SignResponse));
|
|
||||||
if (err) {
|
|
||||||
dispatch(securityKeySignFailure(err.message));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dispatch(securityKeySignSuccess());
|
|
||||||
await redirectUponAuthentication(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function redirectUponAuthentication(props: Props) {
|
|
||||||
const params = QueryString.parse(props.history.location.search);
|
|
||||||
if ('rd' in params) {
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.replace(params['rd'] as string);
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch, ownProps: Props) => {
|
|
||||||
return {
|
|
||||||
onLogoutClicked: () => {
|
|
||||||
dispatch(logout());
|
|
||||||
fetch('/api/logout', {
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
.then(async (res) => {
|
|
||||||
if (res.status != 200) {
|
|
||||||
throw new Error('Status code ' + res.status);
|
|
||||||
}
|
|
||||||
await dispatch(logoutSuccess());
|
|
||||||
ownProps.history.push('/');
|
|
||||||
})
|
|
||||||
.catch(async (err: string) => {
|
|
||||||
console.error(err);
|
|
||||||
await dispatch(logoutFailure(err));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onRegisterSecurityKeyClicked: () => {
|
|
||||||
fetch('/api/secondfactor/u2f/identity/start', {
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
.then(async (res) => {
|
|
||||||
if (res.status != 200) {
|
|
||||||
throw new Error('Status code ' + res.status);
|
|
||||||
}
|
|
||||||
ownProps.history.push('/confirmation-sent');
|
|
||||||
})
|
|
||||||
.catch((err) => console.error(err));
|
|
||||||
},
|
|
||||||
onRegisterOneTimePasswordClicked: () => {
|
|
||||||
fetch('/api/secondfactor/totp/identity/start', {
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
.then(async (res) => {
|
|
||||||
if (res.status != 200) {
|
|
||||||
throw new Error('Status code ' + res.status);
|
|
||||||
}
|
|
||||||
ownProps.history.push('/confirmation-sent');
|
|
||||||
})
|
|
||||||
.catch((err) => console.error(err));
|
|
||||||
},
|
|
||||||
onStateLoaded: async (state: RemoteState) => {
|
|
||||||
if (state.authentication_level < AuthenticationLevel.ONE_FACTOR) {
|
|
||||||
ownProps.history.push('/');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const isU2FSupported = await u2fApi.isSupported();
|
|
||||||
if (isU2FSupported) {
|
|
||||||
await dispatch(setSecurityKeySupported(true));
|
|
||||||
await triggerSecurityKeySigning(dispatch, ownProps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(SecondFactorView);
|
|
|
@ -7,11 +7,8 @@ import { AUTHELIA_GITHUB_URL } from "../../constants";
|
||||||
import { WithStyles, withStyles } from "@material-ui/core";
|
import { WithStyles, withStyles } from "@material-ui/core";
|
||||||
|
|
||||||
import styles from '../../assets/jss/layouts/PortalLayout/PortalLayout';
|
import styles from '../../assets/jss/layouts/PortalLayout/PortalLayout';
|
||||||
import AuthenticationLevel from "../../types/AuthenticationLevel";
|
|
||||||
|
|
||||||
interface Props extends RouterProps, RouteProps, WithStyles {
|
interface Props extends RouterProps, RouteProps, WithStyles {}
|
||||||
authenticationLevel: AuthenticationLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
class PortalLayout extends Component<Props> {
|
class PortalLayout extends Component<Props> {
|
||||||
private renderTitle() {
|
private renderTitle() {
|
||||||
|
|
25
client-react/src/reducers/Portal/Authentication/actions.ts
Normal file
25
client-react/src/reducers/Portal/Authentication/actions.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { createAction } from 'typesafe-actions';
|
||||||
|
import {
|
||||||
|
FETCH_STATE_REQUEST,
|
||||||
|
FETCH_STATE_SUCCESS,
|
||||||
|
FETCH_STATE_FAILURE,
|
||||||
|
SET_REDIRECTION_URL,
|
||||||
|
} from "../../constants";
|
||||||
|
import RemoteState from '../../../views/AuthenticationView/RemoteState';
|
||||||
|
|
||||||
|
/* FETCH_STATE */
|
||||||
|
export const fetchState = createAction(FETCH_STATE_REQUEST);
|
||||||
|
export const fetchStateSuccess = createAction(FETCH_STATE_SUCCESS, resolve => {
|
||||||
|
return (state: RemoteState) => {
|
||||||
|
return resolve(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export const fetchStateFailure = createAction(FETCH_STATE_FAILURE, resolve => {
|
||||||
|
return (err: string) => {
|
||||||
|
return resolve(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setRedirectionUrl = createAction(SET_REDIRECTION_URL, resolve => {
|
||||||
|
return (url: string) => resolve(url);
|
||||||
|
})
|
51
client-react/src/reducers/Portal/Authentication/reducer.ts
Normal file
51
client-react/src/reducers/Portal/Authentication/reducer.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
import * as Actions from './actions';
|
||||||
|
import { ActionType, getType } from 'typesafe-actions';
|
||||||
|
import RemoteState from '../../../views/AuthenticationView/RemoteState';
|
||||||
|
|
||||||
|
export type Action = ActionType<typeof Actions>;
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
redirectionUrl : string | null;
|
||||||
|
remoteState: RemoteState | null;
|
||||||
|
remoteStateLoading: boolean;
|
||||||
|
remoteStateError: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: State = {
|
||||||
|
redirectionUrl: null,
|
||||||
|
|
||||||
|
remoteState: null,
|
||||||
|
remoteStateLoading: false,
|
||||||
|
remoteStateError: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (state = initialState, action: Action): State => {
|
||||||
|
switch(action.type) {
|
||||||
|
case getType(Actions.fetchState):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
remoteState: null,
|
||||||
|
remoteStateError: null,
|
||||||
|
remoteStateLoading: true,
|
||||||
|
};
|
||||||
|
case getType(Actions.fetchStateSuccess):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
remoteState: action.payload,
|
||||||
|
remoteStateLoading: false,
|
||||||
|
};
|
||||||
|
case getType(Actions.fetchStateFailure):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
remoteStateError: action.payload,
|
||||||
|
remoteStateLoading: false,
|
||||||
|
};
|
||||||
|
case getType(Actions.setRedirectionUrl):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
redirectionUrl: action.payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
|
@ -2,25 +2,8 @@ import { createAction } from 'typesafe-actions';
|
||||||
import {
|
import {
|
||||||
AUTHENTICATE_REQUEST,
|
AUTHENTICATE_REQUEST,
|
||||||
AUTHENTICATE_SUCCESS,
|
AUTHENTICATE_SUCCESS,
|
||||||
AUTHENTICATE_FAILURE,
|
AUTHENTICATE_FAILURE
|
||||||
FETCH_STATE_REQUEST,
|
|
||||||
FETCH_STATE_SUCCESS,
|
|
||||||
FETCH_STATE_FAILURE,
|
|
||||||
} from "../../constants";
|
} from "../../constants";
|
||||||
import RemoteState from '../RemoteState';
|
|
||||||
|
|
||||||
/* FETCH_STATE */
|
|
||||||
export const fetchState = createAction(FETCH_STATE_REQUEST);
|
|
||||||
export const fetchStateSuccess = createAction(FETCH_STATE_SUCCESS, resolve => {
|
|
||||||
return (state: RemoteState) => {
|
|
||||||
return resolve(state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
export const fetchStateFailure = createAction(FETCH_STATE_FAILURE, resolve => {
|
|
||||||
return (err: string) => {
|
|
||||||
return resolve(err);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/* AUTHENTICATE_REQUEST */
|
/* AUTHENTICATE_REQUEST */
|
||||||
export const authenticate = createAction(AUTHENTICATE_REQUEST);
|
export const authenticate = createAction(AUTHENTICATE_REQUEST);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
|
|
||||||
import * as Actions from './actions';
|
import * as Actions from './actions';
|
||||||
import { ActionType, getType, StateType } from 'typesafe-actions';
|
import { ActionType, getType } from 'typesafe-actions';
|
||||||
import RemoteState from '../RemoteState';
|
|
||||||
|
|
||||||
export type FirstFactorAction = ActionType<typeof Actions>;
|
export type FirstFactorAction = ActionType<typeof Actions>;
|
||||||
|
|
||||||
|
@ -11,28 +10,19 @@ enum Result {
|
||||||
FAILURE,
|
FAILURE,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface FirstFactorState {
|
||||||
lastResult: Result;
|
lastResult: Result;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
|
||||||
remoteState: RemoteState | null;
|
|
||||||
remoteStateLoading: boolean;
|
|
||||||
remoteStateError: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const firstFactorInitialState: FirstFactorState = {
|
||||||
lastResult: Result.NONE,
|
lastResult: Result.NONE,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
remoteState: null,
|
|
||||||
remoteStateLoading: false,
|
|
||||||
remoteStateError: null,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PortalState = StateType<State>;
|
export default (state = firstFactorInitialState, action: FirstFactorAction): FirstFactorState => {
|
||||||
|
|
||||||
export default (state = initialState, action: FirstFactorAction) => {
|
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case getType(Actions.authenticate):
|
case getType(Actions.authenticate):
|
||||||
return {
|
return {
|
||||||
|
@ -54,26 +44,6 @@ export default (state = initialState, action: FirstFactorAction) => {
|
||||||
loading: false,
|
loading: false,
|
||||||
error: action.payload,
|
error: action.payload,
|
||||||
};
|
};
|
||||||
|
|
||||||
case getType(Actions.fetchState):
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
remoteState: null,
|
|
||||||
remoteStateError: null,
|
|
||||||
remoteStateLoading: true,
|
|
||||||
};
|
|
||||||
case getType(Actions.fetchStateSuccess):
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
remoteState: action.payload,
|
|
||||||
remoteStateLoading: false,
|
|
||||||
};
|
|
||||||
case getType(Actions.fetchStateFailure):
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
remoteStateError: action.payload,
|
|
||||||
remoteStateLoading: false,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
13
client-react/src/reducers/Portal/ForgotPassword/actions.ts
Normal file
13
client-react/src/reducers/Portal/ForgotPassword/actions.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { createAction } from 'typesafe-actions';
|
||||||
|
import {
|
||||||
|
FORGOT_PASSWORD_REQUEST,
|
||||||
|
FORGOT_PASSWORD_SUCCESS,
|
||||||
|
FORGOT_PASSWORD_FAILURE
|
||||||
|
} from "../../constants";
|
||||||
|
|
||||||
|
/* AUTHENTICATE_REQUEST */
|
||||||
|
export const forgotPasswordRequest = createAction(FORGOT_PASSWORD_REQUEST);
|
||||||
|
export const forgotPasswordSuccess = createAction(FORGOT_PASSWORD_SUCCESS);
|
||||||
|
export const forgotPasswordFailure = createAction(FORGOT_PASSWORD_FAILURE, resolve => {
|
||||||
|
return (error: string) => resolve(error);
|
||||||
|
});
|
44
client-react/src/reducers/Portal/ForgotPassword/reducer.ts
Normal file
44
client-react/src/reducers/Portal/ForgotPassword/reducer.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
import * as Actions from './actions';
|
||||||
|
import { ActionType, getType } from 'typesafe-actions';
|
||||||
|
|
||||||
|
export type Action = ActionType<typeof Actions>;
|
||||||
|
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
loading: boolean;
|
||||||
|
success: boolean | null;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: State = {
|
||||||
|
loading: false,
|
||||||
|
success: null,
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (state = initialState, action: Action): State => {
|
||||||
|
switch(action.type) {
|
||||||
|
case getType(Actions.forgotPasswordRequest):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
case getType(Actions.forgotPasswordSuccess):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
success: true,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
case getType(Actions.forgotPasswordFailure):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
success: false,
|
||||||
|
loading: false,
|
||||||
|
error: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
|
@ -4,28 +4,19 @@ import { Secret } from "../../../views/OneTimePasswordRegistrationView/Secret";
|
||||||
|
|
||||||
type OneTimePasswordRegistrationAction = ActionType<typeof Actions>
|
type OneTimePasswordRegistrationAction = ActionType<typeof Actions>
|
||||||
|
|
||||||
export interface State {
|
export interface OneTimePasswordRegistrationState {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
secret: Secret | null;
|
secret: Secret | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let initialState: State = {
|
let oneTimePasswordRegistrationInitialState: OneTimePasswordRegistrationState = {
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null,
|
error: null,
|
||||||
secret: null,
|
secret: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
initialState = {
|
export default (state = oneTimePasswordRegistrationInitialState, action: OneTimePasswordRegistrationAction): OneTimePasswordRegistrationState => {
|
||||||
secret: {
|
|
||||||
base32_secret: 'PBSFWU2RM42HG3TNIRHUQMKSKVUW6NCNOBNFOLCFJZATS6CTI47A',
|
|
||||||
otpauth_url: 'PBSFWU2RM42HG3TNIRHUQMKSKVUW6NCNOBNFOLCFJZATS6CTI47A',
|
|
||||||
},
|
|
||||||
error: null,
|
|
||||||
loading: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (state = initialState, action: OneTimePasswordRegistrationAction) => {
|
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case getType(Actions.generateTotpSecret):
|
case getType(Actions.generateTotpSecret):
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { createAction } from 'typesafe-actions';
|
||||||
|
import { RESET_PASSWORD_REQUEST, RESET_PASSWORD_SUCCESS, RESET_PASSWORD_FAILURE } from "../../constants";
|
||||||
|
|
||||||
|
/* AUTHENTICATE_REQUEST */
|
||||||
|
export const resetPasswordRequest = createAction(RESET_PASSWORD_REQUEST);
|
||||||
|
export const resetPasswordSuccess = createAction(RESET_PASSWORD_SUCCESS);
|
||||||
|
export const resetPasswordFailure = createAction(RESET_PASSWORD_FAILURE, resolve => {
|
||||||
|
return (error: string) => resolve(error);
|
||||||
|
});
|
44
client-react/src/reducers/Portal/ResetPassword/reducer.ts
Normal file
44
client-react/src/reducers/Portal/ResetPassword/reducer.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
import * as Actions from './actions';
|
||||||
|
import { ActionType, getType } from 'typesafe-actions';
|
||||||
|
|
||||||
|
export type Action = ActionType<typeof Actions>;
|
||||||
|
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
loading: boolean;
|
||||||
|
success: boolean | null;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: State = {
|
||||||
|
loading: false,
|
||||||
|
success: null,
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (state = initialState, action: Action): State => {
|
||||||
|
switch(action.type) {
|
||||||
|
case getType(Actions.resetPasswordRequest):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
case getType(Actions.resetPasswordSuccess):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
success: true,
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
case getType(Actions.resetPasswordFailure):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
success: false,
|
||||||
|
loading: false,
|
||||||
|
error: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
|
@ -6,7 +6,10 @@ import {
|
||||||
SECURITY_KEY_SIGN,
|
SECURITY_KEY_SIGN,
|
||||||
SECURITY_KEY_SIGN_SUCCESS,
|
SECURITY_KEY_SIGN_SUCCESS,
|
||||||
SECURITY_KEY_SIGN_FAILURE,
|
SECURITY_KEY_SIGN_FAILURE,
|
||||||
SET_SECURITY_KEY_SUPPORTED
|
SET_SECURITY_KEY_SUPPORTED,
|
||||||
|
ONE_TIME_PASSWORD_VERIFICATION_REQUEST,
|
||||||
|
ONE_TIME_PASSWORD_VERIFICATION_SUCCESS,
|
||||||
|
ONE_TIME_PASSWORD_VERIFICATION_FAILURE
|
||||||
} from "../../constants";
|
} from "../../constants";
|
||||||
|
|
||||||
export const setSecurityKeySupported = createAction(SET_SECURITY_KEY_SUPPORTED, resolve => {
|
export const setSecurityKeySupported = createAction(SET_SECURITY_KEY_SUPPORTED, resolve => {
|
||||||
|
@ -17,7 +20,14 @@ export const securityKeySign = createAction(SECURITY_KEY_SIGN);
|
||||||
export const securityKeySignSuccess = createAction(SECURITY_KEY_SIGN_SUCCESS);
|
export const securityKeySignSuccess = createAction(SECURITY_KEY_SIGN_SUCCESS);
|
||||||
export const securityKeySignFailure = createAction(SECURITY_KEY_SIGN_FAILURE, resolve => {
|
export const securityKeySignFailure = createAction(SECURITY_KEY_SIGN_FAILURE, resolve => {
|
||||||
return (error: string) => resolve(error);
|
return (error: string) => resolve(error);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
export const oneTimePasswordVerification = createAction(ONE_TIME_PASSWORD_VERIFICATION_REQUEST);
|
||||||
|
export const oneTimePasswordVerificationSuccess = createAction(ONE_TIME_PASSWORD_VERIFICATION_SUCCESS);
|
||||||
|
export const oneTimePasswordVerificationFailure = createAction(ONE_TIME_PASSWORD_VERIFICATION_FAILURE, resolve => {
|
||||||
|
return (err: string) => resolve(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
export const logout = createAction(LOGOUT_REQUEST);
|
export const logout = createAction(LOGOUT_REQUEST);
|
||||||
export const logoutSuccess = createAction(LOGOUT_SUCCESS);
|
export const logoutSuccess = createAction(LOGOUT_SUCCESS);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { ActionType, getType, StateType } from 'typesafe-actions';
|
||||||
|
|
||||||
export type SecondFactorAction = ActionType<typeof Actions>;
|
export type SecondFactorAction = ActionType<typeof Actions>;
|
||||||
|
|
||||||
interface State {
|
interface SecondFactorState {
|
||||||
logoutLoading: boolean;
|
logoutLoading: boolean;
|
||||||
logoutSuccess: boolean | null;
|
logoutSuccess: boolean | null;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
@ -12,9 +12,13 @@ interface State {
|
||||||
securityKeySupported: boolean;
|
securityKeySupported: boolean;
|
||||||
securityKeySignLoading: boolean;
|
securityKeySignLoading: boolean;
|
||||||
securityKeySignSuccess: boolean | null;
|
securityKeySignSuccess: boolean | null;
|
||||||
|
|
||||||
|
oneTimePasswordVerificationLoading: boolean,
|
||||||
|
oneTimePasswordVerificationSuccess: boolean | null,
|
||||||
|
oneTimePasswordVerificationError: string | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const secondFactorInitialState: SecondFactorState = {
|
||||||
logoutLoading: false,
|
logoutLoading: false,
|
||||||
logoutSuccess: null,
|
logoutSuccess: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
@ -22,11 +26,15 @@ const initialState: State = {
|
||||||
securityKeySupported: false,
|
securityKeySupported: false,
|
||||||
securityKeySignLoading: false,
|
securityKeySignLoading: false,
|
||||||
securityKeySignSuccess: null,
|
securityKeySignSuccess: null,
|
||||||
|
|
||||||
|
oneTimePasswordVerificationLoading: false,
|
||||||
|
oneTimePasswordVerificationError: null,
|
||||||
|
oneTimePasswordVerificationSuccess: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PortalState = StateType<State>;
|
export type PortalState = StateType<SecondFactorState>;
|
||||||
|
|
||||||
export default (state = initialState, action: SecondFactorAction): State => {
|
export default (state = secondFactorInitialState, action: SecondFactorAction): SecondFactorState => {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case getType(Actions.logout):
|
case getType(Actions.logout):
|
||||||
return {
|
return {
|
||||||
|
@ -47,6 +55,7 @@ export default (state = initialState, action: SecondFactorAction): State => {
|
||||||
logoutLoading: false,
|
logoutLoading: false,
|
||||||
error: action.payload,
|
error: action.payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
case getType(Actions.securityKeySign):
|
case getType(Actions.securityKeySign):
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -65,11 +74,31 @@ export default (state = initialState, action: SecondFactorAction): State => {
|
||||||
securityKeySignLoading: false,
|
securityKeySignLoading: false,
|
||||||
securityKeySignSuccess: false,
|
securityKeySignSuccess: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
case getType(Actions.setSecurityKeySupported):
|
case getType(Actions.setSecurityKeySupported):
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
securityKeySupported: action.payload,
|
securityKeySupported: action.payload,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case getType(Actions.oneTimePasswordVerification):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
oneTimePasswordVerificationLoading: true,
|
||||||
|
oneTimePasswordVerificationError: null,
|
||||||
|
}
|
||||||
|
case getType(Actions.oneTimePasswordVerificationSuccess):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
oneTimePasswordVerificationLoading: false,
|
||||||
|
oneTimePasswordVerificationSuccess: true,
|
||||||
|
}
|
||||||
|
case getType(Actions.oneTimePasswordVerificationFailure):
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
oneTimePasswordVerificationLoading: false,
|
||||||
|
oneTimePasswordVerificationError: action.payload,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
|
@ -3,17 +3,17 @@ import * as Actions from './actions';
|
||||||
|
|
||||||
type SecurityKeyRegistrationAction = ActionType<typeof Actions>
|
type SecurityKeyRegistrationAction = ActionType<typeof Actions>
|
||||||
|
|
||||||
export interface State {
|
export interface SecurityKeyRegistrationState {
|
||||||
error: string | null;
|
error: string | null;
|
||||||
success: boolean | null;
|
success: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let initialState: State = {
|
let securityKeyRegistrationInitialState: SecurityKeyRegistrationState = {
|
||||||
error: null,
|
error: null,
|
||||||
success: null,
|
success: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (state = initialState, action: SecurityKeyRegistrationAction): State => {
|
export default (state = securityKeyRegistrationInitialState, action: SecurityKeyRegistrationAction): SecurityKeyRegistrationState => {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case getType(Actions.registerSecurityKey):
|
case getType(Actions.registerSecurityKey):
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,10 +4,25 @@ import FirstFactorReducer from './FirstFactor/reducer';
|
||||||
import SecondFactorReducer from './SecondFactor/reducer';
|
import SecondFactorReducer from './SecondFactor/reducer';
|
||||||
import OneTimePasswordRegistrationReducer from './OneTimePasswordRegistration/reducer';
|
import OneTimePasswordRegistrationReducer from './OneTimePasswordRegistration/reducer';
|
||||||
import SecurityKeyRegistrationReducer from './SecurityKeyRegistration/reducer';
|
import SecurityKeyRegistrationReducer from './SecurityKeyRegistration/reducer';
|
||||||
|
import AuthenticationReducer from './Authentication/reducer';
|
||||||
|
import ForgotPasswordReducer from './ForgotPassword/reducer';
|
||||||
|
import ResetPasswordReducer from './ResetPassword/reducer';
|
||||||
|
|
||||||
export default combineReducers({
|
import { connectRouter } from 'connected-react-router'
|
||||||
firstFactor: FirstFactorReducer,
|
import { History } from 'history';
|
||||||
secondFactor: SecondFactorReducer,
|
|
||||||
oneTimePasswordRegistration: OneTimePasswordRegistrationReducer,
|
function reducer(history: History) {
|
||||||
securityKeyRegistration: SecurityKeyRegistrationReducer,
|
return combineReducers({
|
||||||
});
|
router: connectRouter(history),
|
||||||
|
authentication: AuthenticationReducer,
|
||||||
|
firstFactor: FirstFactorReducer,
|
||||||
|
secondFactor: SecondFactorReducer,
|
||||||
|
oneTimePasswordRegistration: OneTimePasswordRegistrationReducer,
|
||||||
|
securityKeyRegistration: SecurityKeyRegistrationReducer,
|
||||||
|
forgotPassword: ForgotPasswordReducer,
|
||||||
|
resetPassword: ResetPasswordReducer,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default reducer;
|
|
@ -3,6 +3,10 @@ export const FETCH_STATE_REQUEST = '@portal/fetch_state_request';
|
||||||
export const FETCH_STATE_SUCCESS = '@portal/fetch_state_success';
|
export const FETCH_STATE_SUCCESS = '@portal/fetch_state_success';
|
||||||
export const FETCH_STATE_FAILURE = '@portal/fetch_state_failure';
|
export const FETCH_STATE_FAILURE = '@portal/fetch_state_failure';
|
||||||
|
|
||||||
|
// AUTHENTICATION PROCESS
|
||||||
|
|
||||||
|
export const SET_REDIRECTION_URL = '@portal/authenticate/set_redirection_url';
|
||||||
|
|
||||||
export const AUTHENTICATE_REQUEST = '@portal/authenticate_request';
|
export const AUTHENTICATE_REQUEST = '@portal/authenticate_request';
|
||||||
export const AUTHENTICATE_SUCCESS = '@portal/authenticate_success';
|
export const AUTHENTICATE_SUCCESS = '@portal/authenticate_success';
|
||||||
export const AUTHENTICATE_FAILURE = '@portal/authenticate_failure';
|
export const AUTHENTICATE_FAILURE = '@portal/authenticate_failure';
|
||||||
|
@ -14,6 +18,10 @@ export const SECURITY_KEY_SIGN = '@portal/second_factor/security_key_sign';
|
||||||
export const SECURITY_KEY_SIGN_SUCCESS = '@portal/second_factor/security_key_sign_success';
|
export const SECURITY_KEY_SIGN_SUCCESS = '@portal/second_factor/security_key_sign_success';
|
||||||
export const SECURITY_KEY_SIGN_FAILURE = '@portal/second_factor/security_key_sign_failure';
|
export const SECURITY_KEY_SIGN_FAILURE = '@portal/second_factor/security_key_sign_failure';
|
||||||
|
|
||||||
|
export const ONE_TIME_PASSWORD_VERIFICATION_REQUEST = '@portal/second_factor/one_time_password_verification_request';
|
||||||
|
export const ONE_TIME_PASSWORD_VERIFICATION_SUCCESS = '@portal/second_factor/one_time_password_verification_success';
|
||||||
|
export const ONE_TIME_PASSWORD_VERIFICATION_FAILURE = '@portal/second_factor/one_time_password_verification_failure';
|
||||||
|
|
||||||
export const LOGOUT_REQUEST = '@portal/logout_request';
|
export const LOGOUT_REQUEST = '@portal/logout_request';
|
||||||
export const LOGOUT_SUCCESS = '@portal/logout_success';
|
export const LOGOUT_SUCCESS = '@portal/logout_success';
|
||||||
export const LOGOUT_FAILURE = '@portal/logout_failure';
|
export const LOGOUT_FAILURE = '@portal/logout_failure';
|
||||||
|
@ -26,4 +34,14 @@ export const GENERATE_TOTP_SECRET_FAILURE = '@portal/generate_totp_secret_failur
|
||||||
// U2F REGISTRATION
|
// U2F REGISTRATION
|
||||||
export const REGISTER_SECURITY_KEY_REQUEST = '@portal/security_key_registration/register_request';
|
export const REGISTER_SECURITY_KEY_REQUEST = '@portal/security_key_registration/register_request';
|
||||||
export const REGISTER_SECURITY_KEY_SUCCESS = '@portal/security_key_registration/register_success';
|
export const REGISTER_SECURITY_KEY_SUCCESS = '@portal/security_key_registration/register_success';
|
||||||
export const REGISTER_SECURITY_KEY_FAILURE = '@portal/security_key_registration/register_failed';
|
export const REGISTER_SECURITY_KEY_FAILURE = '@portal/security_key_registration/register_failed';
|
||||||
|
|
||||||
|
// FORGOT PASSWORD
|
||||||
|
export const FORGOT_PASSWORD_REQUEST = '@portal/forgot_password/forgot_password_request';
|
||||||
|
export const FORGOT_PASSWORD_SUCCESS = '@portal/forgot_password/forgot_password_success';
|
||||||
|
export const FORGOT_PASSWORD_FAILURE = '@portal/forgot_password/forgot_password_failure';
|
||||||
|
|
||||||
|
// FORGOT PASSWORD
|
||||||
|
export const RESET_PASSWORD_REQUEST = '@portal/forgot_password/reset_password_request';
|
||||||
|
export const RESET_PASSWORD_SUCCESS = '@portal/forgot_password/reset_password_success';
|
||||||
|
export const RESET_PASSWORD_FAILURE = '@portal/forgot_password/reset_password_failure';
|
|
@ -1,6 +1,11 @@
|
||||||
import PortalReducer from './Portal';
|
import PortalReducer from './Portal';
|
||||||
import { StateType } from 'typesafe-actions';
|
import { StateType } from 'typesafe-actions';
|
||||||
|
|
||||||
export type RootState = StateType<typeof PortalReducer>;
|
function getReturnType<R> (f: (...args: any[]) => R): R {
|
||||||
|
return null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = getReturnType(PortalReducer)
|
||||||
|
export type RootState = StateType<typeof t>;
|
||||||
|
|
||||||
export default PortalReducer;
|
export default PortalReducer;
|
|
@ -1,19 +1,14 @@
|
||||||
import FirstFactorView from "../containers/views/FirstFactorView/FirstFactorView";
|
|
||||||
import SecondFactorView from "../containers/views/SecondFactorView/SecondFactorView";
|
|
||||||
import ConfirmationSentView from "../views/ConfirmationSentView/ConfirmationSentView";
|
import ConfirmationSentView from "../views/ConfirmationSentView/ConfirmationSentView";
|
||||||
import OneTimePasswordRegistrationView from "../containers/views/OneTimePasswordRegistrationView/OneTimePasswordRegistrationView";
|
import OneTimePasswordRegistrationView from "../containers/views/OneTimePasswordRegistrationView/OneTimePasswordRegistrationView";
|
||||||
import SecurityKeyRegistrationView from "../containers/views/SecurityKeyRegistrationView/SecurityKeyRegistrationView";
|
import SecurityKeyRegistrationView from "../containers/views/SecurityKeyRegistrationView/SecurityKeyRegistrationView";
|
||||||
import ForgotPasswordView from "../views/ForgotPasswordView/ForgotPasswordView";
|
import ForgotPasswordView from "../containers/views/ForgotPasswordView/ForgotPasswordView";
|
||||||
import ResetPasswordView from "../views/ResetPasswordView/ResetPasswordView";
|
import ResetPasswordView from "../containers/views/ResetPasswordView/ResetPasswordView";
|
||||||
|
import AuthenticationView from "../containers/views/AuthenticationView/AuthenticationView";
|
||||||
|
|
||||||
export const routes = [{
|
export const routes = [{
|
||||||
path: '/',
|
path: '/',
|
||||||
title: 'Login',
|
title: 'Login',
|
||||||
component: FirstFactorView,
|
component: AuthenticationView,
|
||||||
}, {
|
|
||||||
path: '/2fa',
|
|
||||||
title: '2-factor',
|
|
||||||
component: SecondFactorView,
|
|
||||||
}, {
|
}, {
|
||||||
path: '/confirmation-sent',
|
path: '/confirmation-sent',
|
||||||
title: 'e-mail sent',
|
title: 'e-mail sent',
|
||||||
|
|
117
client-react/src/services/AutheliaService.ts
Normal file
117
client-react/src/services/AutheliaService.ts
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import RemoteState from "../views/AuthenticationView/RemoteState";
|
||||||
|
import u2fApi, { SignRequest } from "u2f-api";
|
||||||
|
|
||||||
|
async function fetchSafe(url: string, options?: RequestInit) {
|
||||||
|
return fetch(url, options)
|
||||||
|
.then(async (res) => {
|
||||||
|
if (res.status !== 200 && res.status !== 204) {
|
||||||
|
throw new Error('Status code ' + res.status);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch current authentication state.
|
||||||
|
*/
|
||||||
|
export async function fetchState() {
|
||||||
|
return fetchSafe('/api/state')
|
||||||
|
.then(async (res) => {
|
||||||
|
const body = await res.json() as RemoteState;
|
||||||
|
return body;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postFirstFactorAuth(username: string, password: string) {
|
||||||
|
return fetchSafe('/api/firstfactor', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postLogout() {
|
||||||
|
return fetchSafe('/api/logout', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startU2FRegistrationIdentityProcess() {
|
||||||
|
return fetchSafe('/api/secondfactor/u2f/identity/start', {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startTOTPRegistrationIdentityProcess() {
|
||||||
|
return fetchSafe('/api/secondfactor/totp/identity/start', {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requestSigning() {
|
||||||
|
return fetchSafe('/api/u2f/sign_request')
|
||||||
|
.then(async (res) => {
|
||||||
|
const body = await res.json();
|
||||||
|
return body as SignRequest;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function completeSecurityKeySigning(response: u2fApi.SignResponse) {
|
||||||
|
return fetchSafe('/api/u2f/sign', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(response),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifyTotpToken(token: string) {
|
||||||
|
return fetchSafe('/api/totp', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({token}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initiatePasswordResetIdentityValidation(username: string) {
|
||||||
|
return fetchSafe('/api/password-reset/identity/start', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({username})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function completePasswordResetIdentityValidation(token: string) {
|
||||||
|
return fetch(`/api/password-reset/identity/finish?token=${token}`, {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resetPassword(newPassword: string) {
|
||||||
|
return fetchSafe('/api/password-reset', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({password: newPassword})
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import AlreadyAuthenticated from "../../containers/components/AlreadyAuthenticated/AlreadyAuthenticated";
|
||||||
|
import FirstFactorForm from "../../containers/components/FirstFactorForm/FirstFactorForm";
|
||||||
|
import SecondFactorForm from "../../containers/components/SecondFactorForm/SecondFactorForm";
|
||||||
|
import RemoteState from "./RemoteState";
|
||||||
|
import { RouterProps, Redirect } from "react-router";
|
||||||
|
import queryString from 'query-string';
|
||||||
|
|
||||||
|
export enum Stage {
|
||||||
|
FIRST_FACTOR,
|
||||||
|
SECOND_FACTOR,
|
||||||
|
ALREADY_AUTHENTICATED,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StateProps {
|
||||||
|
stage: Stage;
|
||||||
|
remoteState: RemoteState | null;
|
||||||
|
redirectionUrl: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DispatchProps {
|
||||||
|
onInit: (redirectionUrl?: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = StateProps & DispatchProps & RouterProps;
|
||||||
|
|
||||||
|
class AuthenticationView extends Component<Props> {
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.history.location) {
|
||||||
|
const params = queryString.parse(this.props.history.location.search);
|
||||||
|
if ('rd' in params) {
|
||||||
|
this.props.onInit(params['rd'] as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.props.onInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.props.remoteState) return null;
|
||||||
|
|
||||||
|
if (this.props.stage === Stage.SECOND_FACTOR) {
|
||||||
|
return <SecondFactorForm
|
||||||
|
username={this.props.remoteState.username}
|
||||||
|
redirection={this.props.redirectionUrl} />;
|
||||||
|
} else if (this.props.stage === Stage.ALREADY_AUTHENTICATED) {
|
||||||
|
return <AlreadyAuthenticated
|
||||||
|
username={this.props.remoteState.username}/>;
|
||||||
|
}
|
||||||
|
return <FirstFactorForm />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthenticationView;
|
|
@ -1,31 +1,84 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component, ChangeEvent, KeyboardEvent } from "react";
|
||||||
import { TextField, WithStyles, withStyles, Button } from "@material-ui/core";
|
import { TextField, WithStyles, withStyles, Button } from "@material-ui/core";
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import styles from '../../assets/jss/views/ForgotPasswordView/ForgotPasswordView';
|
import styles from '../../assets/jss/views/ForgotPasswordView/ForgotPasswordView';
|
||||||
import { RouterProps } from "react-router";
|
|
||||||
|
|
||||||
interface Props extends WithStyles, RouterProps {}
|
export interface StateProps {
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DispatchProps {
|
||||||
|
onPasswordResetRequested: (username: string) => void;
|
||||||
|
onCancelClicked: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = StateProps & DispatchProps & WithStyles;
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForgotPasswordView extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
username: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onUsernameChanged = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({username: e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onKeyPressed = (e: KeyboardEvent) => {
|
||||||
|
if (e.key == 'Enter') {
|
||||||
|
this.onPasswordResetRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPasswordResetRequested = () => {
|
||||||
|
if (this.state.username.length == 0) return;
|
||||||
|
this.props.onPasswordResetRequested(this.state.username);
|
||||||
|
}
|
||||||
|
|
||||||
class ForgotPasswordView extends Component<Props> {
|
|
||||||
render() {
|
render() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>What's you e-mail address?</div>
|
<div>What's your username?</div>
|
||||||
<div className={classes.form}>
|
<div className={classes.form}>
|
||||||
<TextField
|
<TextField
|
||||||
className={classes.field}
|
className={classes.field}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
id="email"
|
id="username"
|
||||||
label="E-mail">
|
label="Username"
|
||||||
|
onChange={this.onUsernameChanged}
|
||||||
|
onKeyPress={this.onKeyPressed}
|
||||||
|
value={this.state.username}
|
||||||
|
disabled={this.props.disabled}>
|
||||||
</TextField>
|
</TextField>
|
||||||
<Button
|
<div className={classes.buttonsContainer}>
|
||||||
onClick={() => this.props.history.push('/confirmation-sent')}
|
<div className={classnames(classes.buttonContainer, classes.buttonConfirmContainer)}>
|
||||||
variant="contained"
|
<Button
|
||||||
color="primary"
|
onClick={this.onPasswordResetRequested}
|
||||||
className={classes.button}>
|
variant="contained"
|
||||||
Next
|
color="primary"
|
||||||
</Button>
|
className={classes.buttonConfirm}
|
||||||
|
disabled={this.props.disabled}>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={classnames(classes.buttonContainer, classes.buttonCancelContainer)}>
|
||||||
|
<Button
|
||||||
|
onClick={this.props.onCancelClicked}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.buttonCancel}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,16 +1,86 @@
|
||||||
import React, { Component } from "react";
|
import React, { Component, KeyboardEvent, ChangeEvent } from "react";
|
||||||
import { TextField, Button, WithStyles, withStyles } from "@material-ui/core";
|
import { TextField, Button, WithStyles, withStyles } from "@material-ui/core";
|
||||||
import { RouterProps } from "react-router";
|
import { RouterProps } from "react-router";
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import QueryString from 'query-string';
|
||||||
|
|
||||||
import styles from '../../assets/jss/views/ResetPasswordView/ResetPasswordView';
|
import styles from '../../assets/jss/views/ResetPasswordView/ResetPasswordView';
|
||||||
|
import FormNotification from "../../components/FormNotification/FormNotification";
|
||||||
|
|
||||||
interface Props extends RouterProps, WithStyles {};
|
export interface StateProps {
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DispatchProps {
|
||||||
|
onInit: (token: string) => void;
|
||||||
|
onPasswordResetRequested: (password: string) => void;
|
||||||
|
onCancelClicked: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Props = StateProps & DispatchProps & RouterProps & WithStyles;
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
password1: string;
|
||||||
|
password2: string;
|
||||||
|
error: string | null,
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResetPasswordView extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
password1: '',
|
||||||
|
password2: '',
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
if (!this.props.history.location) {
|
||||||
|
console.error('There is no location to retrieve query params from...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const params = QueryString.parse(this.props.history.location.search);
|
||||||
|
if (!('token' in params)) {
|
||||||
|
console.error('Token parameter is expected and not provided');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.onInit(params['token'] as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPasswordResetRequested() {
|
||||||
|
if (this.state.password1 && this.state.password1 === this.state.password2) {
|
||||||
|
this.props.onPasswordResetRequested(this.state.password1);
|
||||||
|
} else {
|
||||||
|
this.setState({error: 'The passwords are different.'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onKeyPressed = (e: KeyboardEvent) => {
|
||||||
|
if (e.key == 'Enter') {
|
||||||
|
this.onPasswordResetRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onResetClicked = () => {
|
||||||
|
this.onPasswordResetRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPassword1Changed = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({password1: e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPassword2Changed = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({password2: e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
class ResetPasswordView extends Component<Props> {
|
|
||||||
render() {
|
render() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<FormNotification show={this.state.error !== null}>
|
||||||
|
{this.state.error}
|
||||||
|
</FormNotification>
|
||||||
<div>Enter your new password</div>
|
<div>Enter your new password</div>
|
||||||
<div className={classes.form}>
|
<div className={classes.form}>
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -18,6 +88,9 @@ class ResetPasswordView extends Component<Props> {
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
type="password"
|
type="password"
|
||||||
id="password1"
|
id="password1"
|
||||||
|
value={this.state.password1}
|
||||||
|
onChange={this.onPassword1Changed}
|
||||||
|
disabled={this.props.disabled}
|
||||||
label="New password">
|
label="New password">
|
||||||
</TextField>
|
</TextField>
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -25,15 +98,33 @@ class ResetPasswordView extends Component<Props> {
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
type="password"
|
type="password"
|
||||||
id="password2"
|
id="password2"
|
||||||
|
value={this.state.password2}
|
||||||
|
onKeyPress={this.onKeyPressed}
|
||||||
|
onChange={this.onPassword2Changed}
|
||||||
|
disabled={this.props.disabled}
|
||||||
label="Confirm password">
|
label="Confirm password">
|
||||||
</TextField>
|
</TextField>
|
||||||
<Button
|
<div className={classes.buttonsContainer}>
|
||||||
onClick={() => this.props.history.push('/')}
|
<div className={classnames(classes.buttonContainer, classes.buttonResetContainer)}>
|
||||||
variant="contained"
|
<Button
|
||||||
color="primary"
|
onClick={this.onResetClicked}
|
||||||
className={classes.button}>
|
variant="contained"
|
||||||
Next
|
color="primary"
|
||||||
</Button>
|
disabled={this.props.disabled}
|
||||||
|
className={classnames(classes.button, classes.buttonReset)}>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={classnames(classes.buttonContainer, classes.buttonCancelContainer)}>
|
||||||
|
<Button
|
||||||
|
onClick={this.props.onCancelClicked}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classnames(classes.button, classes.buttonCancel)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -94,8 +94,8 @@ export default function (vars: ServerVariables) {
|
||||||
})
|
})
|
||||||
.catch(Exceptions.LdapBindError, function (err: Error) {
|
.catch(Exceptions.LdapBindError, function (err: Error) {
|
||||||
vars.regulator.mark(username, false);
|
vars.regulator.mark(username, false);
|
||||||
return ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.OPERATION_FAILED)(err);
|
return ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.AUTHENTICATION_FAILED)(err);
|
||||||
})
|
})
|
||||||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.OPERATION_FAILED));
|
.catch(ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.AUTHENTICATION_FAILED));
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@ export default class PasswordResetHandler implements IdentityValidable {
|
||||||
preValidationInit(req: express.Request): BluebirdPromise<Identity> {
|
preValidationInit(req: express.Request): BluebirdPromise<Identity> {
|
||||||
const that = this;
|
const that = this;
|
||||||
const userid: string =
|
const userid: string =
|
||||||
objectPath.get<express.Request, string>(req, "query.userid");
|
objectPath.get<express.Request, string>(req, "body.username");
|
||||||
return BluebirdPromise.resolve()
|
return BluebirdPromise.resolve()
|
||||||
.then(function () {
|
.then(function () {
|
||||||
that.logger.debug(req, "User '%s' requested a password reset", userid);
|
that.logger.debug(req, "User '%s' requested a password reset", userid);
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
|
|
||||||
import express = require("express");
|
import express = require("express");
|
||||||
import BluebirdPromise = require("bluebird");
|
|
||||||
import objectPath = require("object-path");
|
|
||||||
import exceptions = require("../../../Exceptions");
|
|
||||||
|
|
||||||
import Constants = require("./../constants");
|
|
||||||
|
|
||||||
const TEMPLATE_NAME = "password-reset-request";
|
const TEMPLATE_NAME = "password-reset-request";
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default function (vars: ServerVariables) {
|
||||||
return Bluebird.resolve();
|
return Bluebird.resolve();
|
||||||
})
|
})
|
||||||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
||||||
UserMessages.OPERATION_FAILED));
|
UserMessages.AUTHENTICATION_TOTP_FAILED));
|
||||||
}
|
}
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default function (vars: ServerVariables) {
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
||||||
UserMessages.OPERATION_FAILED));
|
UserMessages.AUTHENTICATION_U2F_FAILED));
|
||||||
}
|
}
|
||||||
|
|
||||||
return handler;
|
return handler;
|
||||||
|
|
|
@ -187,7 +187,7 @@ export const RESET_PASSWORD_FORM_POST = "/api/password-reset";
|
||||||
*
|
*
|
||||||
* @apiDescription Serve a page that requires the username.
|
* @apiDescription Serve a page that requires the username.
|
||||||
*/
|
*/
|
||||||
export const RESET_PASSWORD_REQUEST_GET = "/password-reset/request";
|
export const RESET_PASSWORD_REQUEST_GET = "/api/password-reset/request";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ export const RESET_PASSWORD_REQUEST_GET = "/password-reset/request";
|
||||||
*
|
*
|
||||||
* @apiDescription Start password reset request.
|
* @apiDescription Start password reset request.
|
||||||
*/
|
*/
|
||||||
export const RESET_PASSWORD_IDENTITY_START_GET = "/password-reset/identity/start";
|
export const RESET_PASSWORD_IDENTITY_START_GET = "/api/password-reset/identity/start";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ export const RESET_PASSWORD_IDENTITY_START_GET = "/password-reset/identity/start
|
||||||
*
|
*
|
||||||
* @apiDescription Start password reset request.
|
* @apiDescription Start password reset request.
|
||||||
*/
|
*/
|
||||||
export const RESET_PASSWORD_IDENTITY_FINISH_GET = "/password-reset/identity/finish";
|
export const RESET_PASSWORD_IDENTITY_FINISH_GET = "/api/password-reset/identity/finish";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user