feat: first time admin user creation through the ui
This commit is contained in:
+12
-12
@@ -2,7 +2,11 @@ import jwtDecode from 'jwt-decode'
|
||||
|
||||
const authProvider = {
|
||||
login: ({ username, password }) => {
|
||||
const request = new Request('/app/login', {
|
||||
let url = '/app/login'
|
||||
if (localStorage.getItem('initialAccountCreation')) {
|
||||
url = '/app/createAdmin'
|
||||
}
|
||||
const request = new Request(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ username, password }),
|
||||
headers: new Headers({ 'Content-Type': 'application/json' })
|
||||
@@ -17,6 +21,7 @@ const authProvider = {
|
||||
.then((response) => {
|
||||
// Validate token
|
||||
jwtDecode(response.token)
|
||||
localStorage.removeItem('initialAccountCreation')
|
||||
localStorage.setItem('token', response.token)
|
||||
localStorage.setItem('name', response.name)
|
||||
localStorage.setItem('username', response.username)
|
||||
@@ -39,19 +44,14 @@ const authProvider = {
|
||||
return Promise.resolve()
|
||||
},
|
||||
|
||||
checkAuth: () => {
|
||||
try {
|
||||
const expireTime = jwtDecode(localStorage.getItem('token')).exp * 1000
|
||||
const now = new Date().getTime()
|
||||
return now < expireTime ? Promise.resolve() : Promise.reject()
|
||||
} catch (e) {
|
||||
return Promise.reject()
|
||||
}
|
||||
},
|
||||
checkAuth: () =>
|
||||
localStorage.getItem('token') ? Promise.resolve() : Promise.reject(),
|
||||
|
||||
checkError: (error) => {
|
||||
const { status } = error
|
||||
// TODO Remove 403?
|
||||
const { status, message } = error
|
||||
if (message === 'no users created') {
|
||||
localStorage.setItem('initialAccountCreation', 'true')
|
||||
}
|
||||
if (status === 401 || status === 403) {
|
||||
removeItems()
|
||||
return Promise.reject()
|
||||
|
||||
@@ -13,6 +13,7 @@ const httpClient = (url, options = {}) => {
|
||||
const token = response.headers.get('authorization')
|
||||
if (token) {
|
||||
localStorage.setItem('token', token)
|
||||
localStorage.removeItem('initialAccountCreation')
|
||||
}
|
||||
return response
|
||||
})
|
||||
|
||||
+141
-32
@@ -71,40 +71,9 @@ const renderInput = ({
|
||||
/>
|
||||
)
|
||||
|
||||
const Login = ({ location }) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const FormLogin = ({ loading, handleSubmit, validate }) => {
|
||||
const translate = useTranslate()
|
||||
const classes = useStyles()
|
||||
const notify = useNotify()
|
||||
const login = useLogin()
|
||||
|
||||
const handleSubmit = (auth) => {
|
||||
setLoading(true)
|
||||
login(auth, location.state ? location.state.nextPathname : '/').catch(
|
||||
(error) => {
|
||||
setLoading(false)
|
||||
notify(
|
||||
typeof error === 'string'
|
||||
? error
|
||||
: typeof error === 'undefined' || !error.message
|
||||
? 'ra.auth.sign_in_error'
|
||||
: error.message,
|
||||
'warning'
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const validate = (values) => {
|
||||
const errors = {}
|
||||
if (!values.username) {
|
||||
errors.username = translate('ra.validation.required')
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = translate('ra.validation.required')
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
@@ -162,6 +131,146 @@ const Login = ({ location }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const FormSignUp = ({ loading, handleSubmit, validate }) => {
|
||||
const translate = useTranslate()
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
validate={validate}
|
||||
render={({ handleSubmit }) => (
|
||||
<form onSubmit={handleSubmit} noValidate>
|
||||
<div className={classes.main}>
|
||||
<Card className={classes.card}>
|
||||
<div className={classes.avatar}>
|
||||
<Avatar className={classes.icon}>
|
||||
<LockIcon />
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className={classes.systemName}>
|
||||
Thanks for installing Navidrome!
|
||||
</div>
|
||||
<div className={classes.systemName}>
|
||||
To start, create an admin user
|
||||
</div>
|
||||
<div className={classes.form}>
|
||||
<div className={classes.input}>
|
||||
<Field
|
||||
autoFocus
|
||||
name="username"
|
||||
component={renderInput}
|
||||
label={'Admin Username'}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.input}>
|
||||
<Field
|
||||
name="password"
|
||||
component={renderInput}
|
||||
label={translate('ra.auth.password')}
|
||||
type="password"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.input}>
|
||||
<Field
|
||||
name="confirmPassword"
|
||||
component={renderInput}
|
||||
label={'Confirm Password'}
|
||||
type="password"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CardActions className={classes.actions}>
|
||||
<Button
|
||||
variant="contained"
|
||||
type="submit"
|
||||
color="primary"
|
||||
disabled={loading}
|
||||
className={classes.button}
|
||||
fullWidth
|
||||
>
|
||||
{loading && <CircularProgress size={25} thickness={2} />}
|
||||
{translate('Create Admin')}
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
<Notification />
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const Login = ({ location }) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const translate = useTranslate()
|
||||
const notify = useNotify()
|
||||
const login = useLogin()
|
||||
|
||||
const handleSubmit = (auth) => {
|
||||
setLoading(true)
|
||||
login(auth, location.state ? location.state.nextPathname : '/').catch(
|
||||
(error) => {
|
||||
setLoading(false)
|
||||
notify(
|
||||
typeof error === 'string'
|
||||
? error
|
||||
: typeof error === 'undefined' || !error.message
|
||||
? 'ra.auth.sign_in_error'
|
||||
: error.message,
|
||||
'warning'
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const validateLogin = (values) => {
|
||||
const errors = {}
|
||||
if (!values.username) {
|
||||
errors.username = translate('ra.validation.required')
|
||||
}
|
||||
if (!values.password) {
|
||||
errors.password = translate('ra.validation.required')
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
const validateSignup = (values) => {
|
||||
const errors = validateLogin(values)
|
||||
const regex = /^\w+$/g
|
||||
if (values.username && !values.username.match(regex)) {
|
||||
errors.username = translate('Please only use letter and numbers')
|
||||
}
|
||||
if (!values.confirmPassword) {
|
||||
errors.confirmPassword = translate('ra.validation.required')
|
||||
}
|
||||
if (values.confirmPassword !== values.password) {
|
||||
errors.confirmPassword = 'Password does not match'
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
if (localStorage.getItem('initialAccountCreation') === 'true') {
|
||||
return (
|
||||
<FormSignUp
|
||||
handleSubmit={handleSubmit}
|
||||
validate={validateSignup}
|
||||
loading={loading}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<FormLogin
|
||||
handleSubmit={handleSubmit}
|
||||
validate={validateLogin}
|
||||
loading={loading}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
Login.propTypes = {
|
||||
authProvider: PropTypes.func,
|
||||
previousRoute: PropTypes.string
|
||||
|
||||
Reference in New Issue
Block a user