diff --git a/src/helpers/APIHelper.ts b/src/helpers/APIHelper.ts index ac67c9b..4e4ada5 100644 --- a/src/helpers/APIHelper.ts +++ b/src/helpers/APIHelper.ts @@ -25,12 +25,13 @@ export async function serverRequest(uri: string, args?: any): Promise { // TODO : add access token, once supported - const result = await ( - await fetch((await ConfigHelper.apiURL()) + uri, { - method: "POST", - body: fd, - }) - ).json(); + const result = await fetch((await ConfigHelper.apiURL()) + uri, { + method: "POST", + body: fd, + }); - return result; + if (result.status !== 200) + throw new Error("Request failed with status " + result.status); + + return await result.json(); } diff --git a/src/helpers/AccountHelper.ts b/src/helpers/AccountHelper.ts index 2f98272..98e1f2e 100644 --- a/src/helpers/AccountHelper.ts +++ b/src/helpers/AccountHelper.ts @@ -10,6 +10,8 @@ export interface AuthOptions { reset_token: string; } +const SESSION_STORAGE_TOKEN = "auth_token"; + export class AccountHelper { /** * Check whether access token are available @@ -30,4 +32,19 @@ export class AccountHelper { mail: mail, }); } + + /** + * Attempt to authenticate user using a reset token + * + * @param mail Email address of the admin + * @param token The reset token + */ + static async authWithResetToken(mail: string, token: string) { + const res = await serverRequest("accounts/auth_with_reset_token", { + mail: mail, + token: token, + }); + + sessionStorage.setItem(SESSION_STORAGE_TOKEN, res.token); + } } diff --git a/src/ui/routes/LoginRoute.tsx b/src/ui/routes/LoginRoute.tsx index a121def..7a5397b 100644 --- a/src/ui/routes/LoginRoute.tsx +++ b/src/ui/routes/LoginRoute.tsx @@ -5,24 +5,41 @@ */ import { - Typography, - Link, - makeStyles, + Avatar, + Box, + Button, Container, CssBaseline, - Avatar, + List, + ListItem, + ListItemAvatar, + ListItemText, + Paper, TextField, - FormControlLabel, - Checkbox, - Button, - Grid, - Box, + Typography, } from "@material-ui/core"; - +import { ErrorOutline, Lock, VpnKey } from "@material-ui/icons"; import LockOutlinedIcon from "@material-ui/icons/LockOutlined"; import React from "react"; -import { AccountHelper } from "../../helpers/AccountHelper"; -import { matAlert } from "../widgets/DialogsProvider"; +import { AccountHelper, AuthOptions } from "../../helpers/AccountHelper"; +import { input, matAlert } from "../widgets/DialogsProvider"; + +function ErrorGettingOptions() { + return ( + + +   Could not get your auth options! + + ); +} function Copyright() { return ( @@ -37,6 +54,8 @@ function Copyright() { interface LoginRouteState { currEmail: string; + errorGettingAuthOptions: boolean; + authOptions?: AuthOptions; } export class LoginRoute extends React.Component<{}, LoginRouteState> { @@ -45,6 +64,8 @@ export class LoginRoute extends React.Component<{}, LoginRouteState> { this.state = { currEmail: "", + errorGettingAuthOptions: false, + authOptions: undefined, }; this.handleChangedEmail = this.handleChangedEmail.bind(this); @@ -56,17 +77,31 @@ export class LoginRoute extends React.Component<{}, LoginRouteState> { } handleChangedEmail(e: React.ChangeEvent) { - this.setState({ currEmail: e.target.value }); + this.setState({ + currEmail: e.target.value, + errorGettingAuthOptions: false, + authOptions: undefined, + }); } - handleSubmitEmail(e: React.ChangeEvent) { - e.preventDefault(); + async handleSubmitEmail(e: React.ChangeEvent) { + try { + e.preventDefault(); - if (!this.canSubmit) return; + if (!this.canSubmit) return; - AccountHelper.getAuthOptions(this.state.currEmail).then((r) => - matAlert("reset token => " + r.reset_token) - ); + const options = await AccountHelper.getAuthOptions( + this.state.currEmail + ); + this.setState({ + errorGettingAuthOptions: false, + authOptions: options, + }); + } catch (e) { + console.error("Failed to get auth options!", e); + + this.setState({ errorGettingAuthOptions: true }); + } } render() { @@ -138,6 +173,23 @@ export class LoginRoute extends React.Component<{}, LoginRouteState> { Sign In + + {/* Login error (if any) */} + {this.state.errorGettingAuthOptions ? ( + ErrorGettingOptions() + ) : ( +
+ )} + + {/* Auth options */} + {this.state.authOptions ? ( + + ) : ( +
+ )} @@ -147,3 +199,75 @@ export class LoginRoute extends React.Component<{}, LoginRouteState> { ); } } + +interface AuthOptionsWidgetProps { + email: string; + options: AuthOptions; +} + +interface AuthOptionsWidgetState {} + +class AuthOptionsWidget extends React.Component< + AuthOptionsWidgetProps, + AuthOptionsWidgetState +> { + constructor(props: Readonly) { + super(props); + + this.state = {}; + + this.loginWithResetToken = this.loginWithResetToken.bind(this); + } + + async loginWithResetToken() { + try { + const token = await input({ + label: "Reset token", + message: "Please specify here your token:", + minLength: 2, + title: "Login using access token", + }); + + await AccountHelper.authWithResetToken(this.props.email, token); + + document.location.href = document.location.href + ""; + } catch (e) { + console.error(e); + matAlert("Authentication failed!"); + } + } + + render() { + return ( + + + {/* Password reset token */} + {this.props.options.reset_token ? ( + + + + + + + + + ) : ( + + )} + + + + + + + + + + + + ); + } +}