mirror of
https://github.com/navidrome/navidrome.git
synced 2025-06-03 17:11:08 +03:00
Allow regular users to change their info, including password.
Should fix #199
This commit is contained in:
parent
22582392a0
commit
a35de2bfd1
@ -37,6 +37,7 @@ func CreateToken(u *model.User) (string, error) {
|
|||||||
claims := token.Claims.(jwt.MapClaims)
|
claims := token.Claims.(jwt.MapClaims)
|
||||||
claims["iss"] = consts.JWTIssuer
|
claims["iss"] = consts.JWTIssuer
|
||||||
claims["sub"] = u.UserName
|
claims["sub"] = u.UserName
|
||||||
|
claims["uid"] = u.ID
|
||||||
claims["adm"] = u.IsAdmin
|
claims["adm"] = u.IsAdmin
|
||||||
|
|
||||||
return TouchToken(token)
|
return TouchToken(token)
|
||||||
|
@ -144,6 +144,10 @@ func (r *userRepository) Update(entity interface{}, cols ...string) error {
|
|||||||
if !usr.IsAdmin && usr.ID != u.ID {
|
if !usr.IsAdmin && usr.ID != u.ID {
|
||||||
return rest.ErrPermissionDenied
|
return rest.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
if !usr.IsAdmin {
|
||||||
|
u.IsAdmin = false
|
||||||
|
u.UserName = usr.UserName
|
||||||
|
}
|
||||||
err := r.Put(u)
|
err := r.Put(u)
|
||||||
if err == model.ErrNotFound {
|
if err == model.ErrNotFound {
|
||||||
return rest.ErrNotFound
|
return rest.ErrNotFound
|
||||||
@ -153,7 +157,7 @@ func (r *userRepository) Update(entity interface{}, cols ...string) error {
|
|||||||
|
|
||||||
func (r *userRepository) Delete(id string) error {
|
func (r *userRepository) Delete(id string) error {
|
||||||
usr := loggedUser(r.ctx)
|
usr := loggedUser(r.ctx)
|
||||||
if !usr.IsAdmin && usr.ID != id {
|
if !usr.IsAdmin {
|
||||||
return rest.ErrPermissionDenied
|
return rest.ErrPermissionDenied
|
||||||
}
|
}
|
||||||
err := r.delete(Eq{"id": id})
|
err := r.delete(Eq{"id": id})
|
||||||
|
@ -87,6 +87,9 @@
|
|||||||
"name": "Nome",
|
"name": "Nome",
|
||||||
"password": "Senha",
|
"password": "Senha",
|
||||||
"createdAt": "Data de Criação"
|
"createdAt": "Data de Criação"
|
||||||
|
},
|
||||||
|
"helperTexts": {
|
||||||
|
"name": "Alterações no seu nome só serão refletidas no próximo login"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"player": {
|
"player": {
|
||||||
|
@ -60,6 +60,7 @@ func handleLogin(ds model.DataStore, username string, password string, w http.Re
|
|||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"message": "User '" + username + "' authenticated successfully",
|
"message": "User '" + username + "' authenticated successfully",
|
||||||
"token": tokenString,
|
"token": tokenString,
|
||||||
|
"id": user.ID,
|
||||||
"name": user.Name,
|
"name": user.Name,
|
||||||
"username": username,
|
"username": username,
|
||||||
"isAdmin": user.IsAdmin,
|
"isAdmin": user.IsAdmin,
|
||||||
|
@ -93,9 +93,7 @@ const Admin = (props) => {
|
|||||||
{...playlist}
|
{...playlist}
|
||||||
options={{ subMenu: 'library' }}
|
options={{ subMenu: 'library' }}
|
||||||
/>,
|
/>,
|
||||||
permissions === 'admin' ? (
|
<Resource name="user" {...user} options={{ subMenu: 'settings' }} />,
|
||||||
<Resource name="user" {...user} options={{ subMenu: 'settings' }} />
|
|
||||||
) : null,
|
|
||||||
<Resource
|
<Resource
|
||||||
name="player"
|
name="player"
|
||||||
{...player}
|
{...player}
|
||||||
|
@ -26,7 +26,9 @@ const authProvider = {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
// Validate token
|
// Validate token
|
||||||
jwtDecode(response.token)
|
jwtDecode(response.token)
|
||||||
|
// TODO Store all items in one object
|
||||||
localStorage.setItem('token', response.token)
|
localStorage.setItem('token', response.token)
|
||||||
|
localStorage.setItem('userId', response.id)
|
||||||
localStorage.setItem('name', response.name)
|
localStorage.setItem('name', response.name)
|
||||||
localStorage.setItem('username', response.username)
|
localStorage.setItem('username', response.username)
|
||||||
response.avatar && localStorage.setItem('avatar', response.avatar)
|
response.avatar && localStorage.setItem('avatar', response.avatar)
|
||||||
@ -94,6 +96,7 @@ const authProvider = {
|
|||||||
|
|
||||||
const removeItems = () => {
|
const removeItems = () => {
|
||||||
localStorage.removeItem('token')
|
localStorage.removeItem('token')
|
||||||
|
localStorage.removeItem('userId')
|
||||||
localStorage.removeItem('name')
|
localStorage.removeItem('name')
|
||||||
localStorage.removeItem('username')
|
localStorage.removeItem('username')
|
||||||
localStorage.removeItem('avatar')
|
localStorage.removeItem('avatar')
|
||||||
|
@ -87,6 +87,9 @@
|
|||||||
"name": "Name",
|
"name": "Name",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"createdAt": "Created at"
|
"createdAt": "Created at"
|
||||||
|
},
|
||||||
|
"helperTexts": {
|
||||||
|
"name": "Changes to your name will only be reflected on next login"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"player": {
|
"player": {
|
||||||
|
@ -58,6 +58,7 @@ const AboutMenuItem = forwardRef(({ onClick, ...rest }, ref) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const settingsResources = (resource) =>
|
const settingsResources = (resource) =>
|
||||||
|
resource.name !== 'user' &&
|
||||||
resource.hasList &&
|
resource.hasList &&
|
||||||
resource.options &&
|
resource.options &&
|
||||||
resource.options.subMenu === 'settings'
|
resource.options.subMenu === 'settings'
|
||||||
@ -68,16 +69,31 @@ const CustomUserMenu = ({ onClick, ...rest }) => {
|
|||||||
const classes = useStyles(rest)
|
const classes = useStyles(rest)
|
||||||
const { permissions } = usePermissions()
|
const { permissions } = usePermissions()
|
||||||
|
|
||||||
const renderSettingsMenuItemLink = (resource) => {
|
const resourceDefinition = (resourceName) =>
|
||||||
|
resources.find((r) => r?.name === resourceName)
|
||||||
|
|
||||||
|
const renderUserMenuItemLink = () => {
|
||||||
|
const userResource = resourceDefinition('user')
|
||||||
|
if (!userResource) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return renderSettingsMenuItemLink(
|
||||||
|
userResource,
|
||||||
|
permissions !== 'admin' ? localStorage.getItem('userId') : null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderSettingsMenuItemLink = (resource, id) => {
|
||||||
const label = translate(`resources.${resource.name}.name`, {
|
const label = translate(`resources.${resource.name}.name`, {
|
||||||
smart_count: 2,
|
smart_count: id ? 1 : 2,
|
||||||
})
|
})
|
||||||
|
const link = id ? `/${resource.name}/${id}` : `/${resource.name}`
|
||||||
return (
|
return (
|
||||||
<MenuItemLink
|
<MenuItemLink
|
||||||
className={classes.root}
|
className={classes.root}
|
||||||
activeClassName={classes.active}
|
activeClassName={classes.active}
|
||||||
key={resource.name}
|
key={resource.name}
|
||||||
to={`/${resource.name}`}
|
to={link}
|
||||||
primaryText={label}
|
primaryText={label}
|
||||||
leftIcon={
|
leftIcon={
|
||||||
(resource.icon && createElement(resource.icon)) || <ViewListIcon />
|
(resource.icon && createElement(resource.icon)) || <ViewListIcon />
|
||||||
@ -94,6 +110,7 @@ const CustomUserMenu = ({ onClick, ...rest }) => {
|
|||||||
<UserMenu {...rest}>
|
<UserMenu {...rest}>
|
||||||
<PersonalMenu sidebarIsOpen={true} onClick={onClick} />
|
<PersonalMenu sidebarIsOpen={true} onClick={onClick} />
|
||||||
<Divider />
|
<Divider />
|
||||||
|
{renderUserMenuItemLink()}
|
||||||
{resources.filter(settingsResources).map(renderSettingsMenuItemLink)}
|
{resources.filter(settingsResources).map(renderSettingsMenuItemLink)}
|
||||||
<Divider />
|
<Divider />
|
||||||
<AboutMenuItem />
|
<AboutMenuItem />
|
||||||
|
@ -31,19 +31,40 @@ const UserTitle = ({ record }) => {
|
|||||||
|
|
||||||
const UserToolbar = (props) => (
|
const UserToolbar = (props) => (
|
||||||
<Toolbar {...props} classes={useStyles()}>
|
<Toolbar {...props} classes={useStyles()}>
|
||||||
<SaveButton />
|
<SaveButton disabled={props.pristine} />
|
||||||
<DeleteUserButton />
|
{props.permissions === 'admin' && <DeleteUserButton />}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
)
|
)
|
||||||
|
|
||||||
const UserEdit = (props) => (
|
const UserEdit = (props) => {
|
||||||
|
const { permissions } = props
|
||||||
|
const translate = useTranslate()
|
||||||
|
|
||||||
|
const getNameHelperText = () =>
|
||||||
|
props.id === localStorage.getItem('userId') && {
|
||||||
|
helperText: translate('resources.user.helperTexts.name'),
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Edit title={<UserTitle />} {...props}>
|
<Edit title={<UserTitle />} {...props}>
|
||||||
<SimpleForm variant={'outlined'} toolbar={<UserToolbar />}>
|
<SimpleForm
|
||||||
|
variant={'outlined'}
|
||||||
|
toolbar={<UserToolbar />}
|
||||||
|
redirect={permissions === 'admin' ? 'list' : false}
|
||||||
|
>
|
||||||
|
{permissions === 'admin' && (
|
||||||
<TextInput source="userName" validate={[required()]} />
|
<TextInput source="userName" validate={[required()]} />
|
||||||
<TextInput source="name" validate={[required()]} />
|
)}
|
||||||
|
<TextInput
|
||||||
|
source="name"
|
||||||
|
validate={[required()]}
|
||||||
|
{...getNameHelperText()}
|
||||||
|
/>
|
||||||
<TextInput source="email" validate={[email()]} />
|
<TextInput source="email" validate={[email()]} />
|
||||||
<PasswordInput source="password" validate={[required()]} />
|
<PasswordInput source="password" validate={[required()]} />
|
||||||
|
{permissions === 'admin' && (
|
||||||
<BooleanInput source="isAdmin" initialValue={false} />
|
<BooleanInput source="isAdmin" initialValue={false} />
|
||||||
|
)}
|
||||||
<DateField variant="body1" source="lastLoginAt" showTime />
|
<DateField variant="body1" source="lastLoginAt" showTime />
|
||||||
{/*<DateField source="lastAccessAt" showTime />*/}
|
{/*<DateField source="lastAccessAt" showTime />*/}
|
||||||
<DateField variant="body1" source="updatedAt" showTime />
|
<DateField variant="body1" source="updatedAt" showTime />
|
||||||
@ -51,5 +72,6 @@ const UserEdit = (props) => (
|
|||||||
</SimpleForm>
|
</SimpleForm>
|
||||||
</Edit>
|
</Edit>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default UserEdit
|
export default UserEdit
|
||||||
|
Loading…
x
Reference in New Issue
Block a user