import { Box, Button, Checkbox, CircularProgress, Container, FormControlLabel, Grid, Link, TextField, Typography } from "@material-ui/core"; import {makeStyles} from '@material-ui/core/styles'; import {Alert} from '@material-ui/lab'; import React, {useCallback, useContext, useEffect, useState} from "react"; import ReplayIcon from '@material-ui/icons/Replay'; import LanguageSelection from "../elements/language-selection"; import {decodeText, encodeText, getParameter, removeParameter} from "shared/util"; import Icon from "shared/elements/icon"; import {LocaleContext} from "shared/locale"; const useStyles = makeStyles((theme) => ({ paper: { marginTop: theme.spacing(8), display: 'flex', flexDirection: 'column', alignItems: 'center', }, avatar: { margin: theme.spacing(2), width: "60px", height: "60px" }, form: { width: '100%', // Fix IE 11 issue. marginTop: theme.spacing(1), }, submit: { margin: theme.spacing(3, 0, 2), }, logo: { marginRight: theme.spacing(3) }, headline: { width: "100%", }, container: { marginTop: theme.spacing(5), paddingBottom: theme.spacing(1), borderColor: theme.palette.primary.main, borderStyle: "solid", borderWidth: 1, borderRadius: 5 }, buttons2FA: { marginTop: theme.spacing(1), marginBottom: theme.spacing(1), }, error2FA: { marginTop: theme.spacing(2), marginBottom: theme.spacing(2), "& > div": { fontSize: 16 }, "& > button": { marginTop: theme.spacing(1) } } })); export default function LoginForm(props) { const api = props.api; const classes = useStyles(); let [username, setUsername] = useState(""); let [password, setPassword] = useState(""); let [rememberMe, setRememberMe] = useState(true); let [isLoggingIn, setLoggingIn] = useState(false); let [emailConfirmed, setEmailConfirmed] = useState(null); let [tfaCode, set2FACode] = useState(""); let [tfaState, set2FAState] = useState(0); // 0: not sent, 1: sent, 2: retry let [tfaError, set2FAError] = useState(""); let [error, setError] = useState(""); let [loaded, setLoaded] = useState(false); const {translate: L, currentLocale, requestModules} = useContext(LocaleContext); const onUpdateLocale = useCallback(() => { requestModules(api, ["general", "account"], currentLocale).then(data => { setLoaded(true); if (!data.success) { alert(data.msg); } }); }, [currentLocale]); useEffect(() => { onUpdateLocale(); }, [currentLocale]); const onLogin = useCallback(() => { if (!isLoggingIn) { setError(""); setLoggingIn(true); removeParameter("success"); api.login(username, password, rememberMe).then((res) => { set2FAState(0); setLoggingIn(false); setPassword(""); if (!res.success) { setEmailConfirmed(res.emailConfirmed); setError(res.msg); } else { props.onLogin(); } }); } }, [api, isLoggingIn, password, props, rememberMe, username]); const onSubmit2FA = useCallback(() => { setLoggingIn(true); props.onTotp2FA(tfaCode, (res) => { setLoggingIn(false); }); }, [tfaCode, props]); const onCancel2FA = useCallback(() => { props.onLogout(); }, [props]); useEffect(() => { if (!api.loggedIn || !api.user) { return; } let twoFactor = api.user["2fa"]; if (!twoFactor || !twoFactor.confirmed || twoFactor.authenticated || twoFactor.type !== "fido") { return; } if (tfaState === 0) { set2FAState(1); set2FAError(""); navigator.credentials.get({ publicKey: { challenge: encodeText(window.atob(twoFactor.challenge)), allowCredentials: [{ id: encodeText(window.atob(twoFactor.credentialID)), type: "public-key", }], userVerification: "discouraged", }, }).then((res) => { let credentialID = res.id; let clientDataJson = decodeText(res.response.clientDataJSON); let authData = window.btoa(decodeText(res.response.authenticatorData)); let signature = window.btoa(decodeText(res.response.signature)); props.onKey2FA(credentialID, clientDataJson, authData, signature, res => { if (!res.success) { set2FAState(2); } }); }).catch(e => { set2FAState(2); set2FAError(e.toString()); }); } }, [api.loggedIn, api.user, tfaState, props]); const createForm = () => { // 2FA if (api.loggedIn && api.user["2fa"]) { return <>