aboutsummaryrefslogtreecommitdiff
path: root/modern/src/login
diff options
context:
space:
mode:
authore-macgregor <122734173+e-macgregor@users.noreply.github.com>2023-10-29 17:32:50 -0600
committere-macgregor <122734173+e-macgregor@users.noreply.github.com>2023-10-29 17:32:50 -0600
commit39fad06c660518c5f308ee5b14a5111f4bcbcd31 (patch)
tree1c9ce97a49f73b8650402827f5e37a62d0b4fb5b /modern/src/login
parent242c434c90c33b792ffb5b1898d9d48ad29c9792 (diff)
downloadtrackermap-web-39fad06c660518c5f308ee5b14a5111f4bcbcd31.tar.gz
trackermap-web-39fad06c660518c5f308ee5b14a5111f4bcbcd31.tar.bz2
trackermap-web-39fad06c660518c5f308ee5b14a5111f4bcbcd31.zip
totp
Diffstat (limited to 'modern/src/login')
-rw-r--r--modern/src/login/LoginPage.jsx25
-rw-r--r--modern/src/login/RegisterPage.jsx28
2 files changed, 48 insertions, 5 deletions
diff --git a/modern/src/login/LoginPage.jsx b/modern/src/login/LoginPage.jsx
index 73104def..e196a18b 100644
--- a/modern/src/login/LoginPage.jsx
+++ b/modern/src/login/LoginPage.jsx
@@ -57,6 +57,7 @@ const LoginPage = () => {
const [email, setEmail] = usePersistedState('loginEmail', '');
const [password, setPassword] = useState('');
+ const [code, setCode] = useState('');
const registrationEnabled = useSelector((state) => state.session.server.registration);
const languageEnabled = useSelector((state) => !state.session.server.attributes['ui.disableLoginLanguage']);
@@ -64,6 +65,7 @@ const LoginPage = () => {
const emailEnabled = useSelector((state) => state.session.server.emailEnabled);
const openIdEnabled = useSelector((state) => state.session.server.openIdEnabled);
const openIdForced = useSelector((state) => state.session.server.openIdEnabled && state.session.server.openIdForce);
+ const [codeEnabled, setCodeEnabled] = useState(false);
const [announcementShown, setAnnouncementShown] = useState(false);
const announcement = useSelector((state) => state.session.server.announcement);
@@ -89,16 +91,21 @@ const LoginPage = () => {
const handlePasswordLogin = async (event) => {
event.preventDefault();
+ setFailed(false);
try {
+ const query = `email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`;
const response = await fetch('/api/session', {
method: 'POST',
- body: new URLSearchParams(`email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`),
+ body: new URLSearchParams(code.length ? query + `&code=${code}` : query),
});
+ console.log(response); // TODO check missing code
if (response.ok) {
const user = await response.json();
generateLoginToken();
dispatch(sessionActions.updateUser(user));
navigate('/');
+ } else if (response.status === 401 && response.headers.get('WWW-Authenticate') === 'TOTP') {
+ setCodeEnabled(true);
} else {
throw Error(await response.text());
}
@@ -120,7 +127,7 @@ const LoginPage = () => {
});
const handleSpecialKey = (e) => {
- if (e.keyCode === 13 && email && password) {
+ if (e.keyCode === 13 && email && password && (!codeEnabled || code)) {
handlePasswordLogin(e);
}
};
@@ -179,12 +186,24 @@ const LoginPage = () => {
onChange={(e) => setPassword(e.target.value)}
onKeyUp={handleSpecialKey}
/>
+ {codeEnabled && (
+ <TextField
+ required
+ error={failed}
+ label={t('loginTotpCode')}
+ name="code"
+ value={code}
+ type="number"
+ onChange={(e) => setCode(e.target.value)}
+ onKeyUp={handleSpecialKey}
+ />
+ )}
<Button
onClick={handlePasswordLogin}
onKeyUp={handleSpecialKey}
variant="contained"
color="secondary"
- disabled={!email || !password}
+ disabled={!email || !password || (codeEnabled && !code)}
>
{t('loginLogin')}
</Button>
diff --git a/modern/src/login/RegisterPage.jsx b/modern/src/login/RegisterPage.jsx
index 6dfe40a4..1ec791a1 100644
--- a/modern/src/login/RegisterPage.jsx
+++ b/modern/src/login/RegisterPage.jsx
@@ -9,7 +9,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import LoginLayout from './LoginLayout';
import { useTranslation } from '../common/components/LocalizationProvider';
import { snackBarDurationShortMs } from '../common/util/duration';
-import { useCatch } from '../reactHelper';
+import { useCatch, useEffectAsync } from '../reactHelper';
import { sessionActions } from '../store';
const useStyles = makeStyles((theme) => ({
@@ -37,17 +37,30 @@ const RegisterPage = () => {
const t = useTranslation();
const server = useSelector((state) => state.session.server);
+ const totpForce = useSelector((state) => state.session.server.attributes.totpForce);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
+ const [totpKey, setTotpKey] = useState('');
const [snackbarOpen, setSnackbarOpen] = useState(false);
+ useEffectAsync(async () => {
+ if (totpForce) {
+ const response = await fetch('/api/users/totp', { method: 'POST' });
+ if (response.ok) {
+ setTotpKey(await response.text());
+ } else {
+ throw Error(await response.text());
+ }
+ }
+ }, [totpForce, setTotpKey]);
+
const handleSubmit = useCatch(async () => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ name, email, password }),
+ body: JSON.stringify({ name, email, password, totpKey }),
});
if (response.ok) {
setSnackbarOpen(true);
@@ -96,6 +109,17 @@ const RegisterPage = () => {
autoComplete="current-password"
onChange={(event) => setPassword(event.target.value)}
/>
+ {totpForce && (
+ <TextField
+ required
+ label={t('loginTotpKey')}
+ name="totpKey"
+ value={totpKey}
+ InputProps={{
+ readOnly: true,
+ }}
+ />
+ )}
<Button
variant="contained"
color="secondary"