Allow regular users to change their info, including password.

Should fix #199
This commit is contained in:
Deluan 2021-04-28 22:35:25 -04:00
parent 22582392a0
commit a35de2bfd1
9 changed files with 77 additions and 25 deletions

View File

@ -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)

View File

@ -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})

View File

@ -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": {

View File

@ -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,

View File

@ -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}

View File

@ -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')

View File

@ -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": {

View File

@ -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 />

View File

@ -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