| 1 | // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt |
| 2 | |
| 3 | import { state, useSnapState } from './state' |
| 4 | import { createElement as h, Fragment, useEffect, useRef, useState } from 'react' |
| 5 | import { ALLOW_SESSION_IP_CHANGE, HTTP_FORBIDDEN, HTTP_UNAUTHORIZED, makeSessionRefresher, withSrpLib } from './misc' |
| 6 | import { BoolField, Form } from '@hfs/mui-grid-form' |
| 7 | import { apiCall } from './api' |
| 8 | import { srpClientSequence } from '../../src/srp' |
| 9 | import { Alert, Box } from '@mui/material' |
| 10 | import { Center } from './mui' |
| 11 | |
| 12 | export function LoginRequired({ children }: any) { |
| 13 | const { loginRequired } = useSnapState() |
| 14 | if (loginRequired === HTTP_FORBIDDEN) |
| 15 | return h(Center, {}, |
| 16 | h(Alert, { severity: 'error' }, "Admin-panel only for localhost"), |
| 17 | h(Box, { sx: { mt: 2, fontSize: 'small' } }, "because no admin account was configured") |
| 18 | ) |
| 19 | if (loginRequired) |
| 20 | return h(LoginForm) |
| 21 | return h(Fragment, {}, children) |
| 22 | } |
| 23 | |
| 24 | function LoginForm() { |
| 25 | const [values, setValues] = useState({ username: '', password: '', [ALLOW_SESSION_IP_CHANGE]: false }) |
| 26 | const [error, setError] = useState('') |
| 27 | const formRef = useRef<HTMLFormElement>() |
| 28 | const empty = formRef.current?.querySelector('input[value=""]') |
| 29 | useEffect(() => (empty as any)?.focus?.(), [empty]) |
| 30 | return h(Center, {}, |
| 31 | h(Form, { |
| 32 | formRef, |
| 33 | values, |
| 34 | sx: { m: 2, maxWidth: '25em' }, |
| 35 | set(v, k) { |
| 36 | setValues(values => ({ ...values, [k]: v })) |
| 37 | }, |
| 38 | fields: [ |
| 39 | { k: 'username', autoComplete: 'username', autoFocus: true, required: true }, |
| 40 | { k: 'password', type: 'password', autoComplete: 'current-password', required: true }, |
| 41 | { k: ALLOW_SESSION_IP_CHANGE, comp: BoolField, label: "Allow IP change during this session" }, |
| 42 | ], |
| 43 | addToBar: [ error && h(Alert, { severity: 'error', sx: { flex: 1 } }, error) ], |
| 44 | saveOnEnter: true, |
| 45 | save: { |
| 46 | children: "Enter", |
| 47 | startIcon: null, |
| 48 | async onClick() { |
| 49 | try { |
| 50 | setError('') |
| 51 | await login(values.username, values.password, { |
| 52 | [ALLOW_SESSION_IP_CHANGE]: values[ALLOW_SESSION_IP_CHANGE] |
| 53 | }) |
| 54 | } |
| 55 | catch(e) { |
| 56 | setError(String(e)) |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | }) |
| 61 | ) |
| 62 | } |
| 63 | |
| 64 | async function login(username: string, password: string, extra?: object) { |
| 65 | const res = await withSrpLib(srpClientSequence)(username, password, apiCall, extra).catch(err => { |
| 66 | throw err?.code === HTTP_UNAUTHORIZED ? err.message || "Wrong username or password" |
| 67 | : err === 'trust' ? "Login aborted: server identity cannot be trusted" |
| 68 | : err?.name === 'AbortError' ? "Server didn't respond" |
| 69 | : (err?.message || "Unknown error") |
| 70 | }) |
| 71 | if (!res.isAdmin) |
| 72 | throw "This account has no Admin access" |
| 73 | |
| 74 | // login was successful, update state |
| 75 | state.loginRequired = false |
| 76 | refreshSession(res) |
| 77 | } |
| 78 | |
| 79 | const refreshSession = makeSessionRefresher(state) |