From a35de2bfd1a0e905ecd97aaf466ef74fbb608c61 Mon Sep 17 00:00:00 2001 From: Deluan Date: Wed, 28 Apr 2021 22:35:25 -0400 Subject: [PATCH] Allow regular users to change their info, including password. Should fix #199 --- core/auth/auth.go | 1 + persistence/user_repository.go | 6 +++- resources/i18n/pt.json | 5 ++- server/app/auth.go | 1 + ui/src/App.js | 4 +-- ui/src/authProvider.js | 3 ++ ui/src/i18n/en.json | 3 ++ ui/src/layout/AppBar.js | 23 ++++++++++++-- ui/src/user/UserEdit.js | 56 +++++++++++++++++++++++----------- 9 files changed, 77 insertions(+), 25 deletions(-) diff --git a/core/auth/auth.go b/core/auth/auth.go index 4a58edd4c..aa644ad8b 100644 --- a/core/auth/auth.go +++ b/core/auth/auth.go @@ -37,6 +37,7 @@ func CreateToken(u *model.User) (string, error) { claims := token.Claims.(jwt.MapClaims) claims["iss"] = consts.JWTIssuer claims["sub"] = u.UserName + claims["uid"] = u.ID claims["adm"] = u.IsAdmin return TouchToken(token) diff --git a/persistence/user_repository.go b/persistence/user_repository.go index a86913be8..bfcb42ee7 100644 --- a/persistence/user_repository.go +++ b/persistence/user_repository.go @@ -144,6 +144,10 @@ func (r *userRepository) Update(entity interface{}, cols ...string) error { if !usr.IsAdmin && usr.ID != u.ID { return rest.ErrPermissionDenied } + if !usr.IsAdmin { + u.IsAdmin = false + u.UserName = usr.UserName + } err := r.Put(u) if err == model.ErrNotFound { return rest.ErrNotFound @@ -153,7 +157,7 @@ func (r *userRepository) Update(entity interface{}, cols ...string) error { func (r *userRepository) Delete(id string) error { usr := loggedUser(r.ctx) - if !usr.IsAdmin && usr.ID != id { + if !usr.IsAdmin { return rest.ErrPermissionDenied } err := r.delete(Eq{"id": id}) diff --git a/resources/i18n/pt.json b/resources/i18n/pt.json index 7a6c1ea2f..79b9c3129 100644 --- a/resources/i18n/pt.json +++ b/resources/i18n/pt.json @@ -87,6 +87,9 @@ "name": "Nome", "password": "Senha", "createdAt": "Data de Criação" + }, + "helperTexts": { + "name": "Alterações no seu nome só serão refletidas no próximo login" } }, "player": { @@ -348,4 +351,4 @@ "toggle_love": "Marcar/desmarcar favorita" } } -} \ No newline at end of file +} diff --git a/server/app/auth.go b/server/app/auth.go index b9c5aa926..fc1d7b056 100644 --- a/server/app/auth.go +++ b/server/app/auth.go @@ -60,6 +60,7 @@ func handleLogin(ds model.DataStore, username string, password string, w http.Re payload := map[string]interface{}{ "message": "User '" + username + "' authenticated successfully", "token": tokenString, + "id": user.ID, "name": user.Name, "username": username, "isAdmin": user.IsAdmin, diff --git a/ui/src/App.js b/ui/src/App.js index 25cdd2942..91c2a55ff 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -93,9 +93,7 @@ const Admin = (props) => { {...playlist} options={{ subMenu: 'library' }} />, - permissions === 'admin' ? ( - - ) : null, + , { // Validate token jwtDecode(response.token) + // TODO Store all items in one object localStorage.setItem('token', response.token) + localStorage.setItem('userId', response.id) localStorage.setItem('name', response.name) localStorage.setItem('username', response.username) response.avatar && localStorage.setItem('avatar', response.avatar) @@ -94,6 +96,7 @@ const authProvider = { const removeItems = () => { localStorage.removeItem('token') + localStorage.removeItem('userId') localStorage.removeItem('name') localStorage.removeItem('username') localStorage.removeItem('avatar') diff --git a/ui/src/i18n/en.json b/ui/src/i18n/en.json index 44300c1fa..fa44d6b20 100644 --- a/ui/src/i18n/en.json +++ b/ui/src/i18n/en.json @@ -87,6 +87,9 @@ "name": "Name", "password": "Password", "createdAt": "Created at" + }, + "helperTexts": { + "name": "Changes to your name will only be reflected on next login" } }, "player": { diff --git a/ui/src/layout/AppBar.js b/ui/src/layout/AppBar.js index ab0cd8d7a..746a8abcb 100644 --- a/ui/src/layout/AppBar.js +++ b/ui/src/layout/AppBar.js @@ -58,6 +58,7 @@ const AboutMenuItem = forwardRef(({ onClick, ...rest }, ref) => { }) const settingsResources = (resource) => + resource.name !== 'user' && resource.hasList && resource.options && resource.options.subMenu === 'settings' @@ -68,16 +69,31 @@ const CustomUserMenu = ({ onClick, ...rest }) => { const classes = useStyles(rest) 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`, { - smart_count: 2, + smart_count: id ? 1 : 2, }) + const link = id ? `/${resource.name}/${id}` : `/${resource.name}` return ( @@ -94,6 +110,7 @@ const CustomUserMenu = ({ onClick, ...rest }) => { + {renderUserMenuItemLink()} {resources.filter(settingsResources).map(renderSettingsMenuItemLink)} diff --git a/ui/src/user/UserEdit.js b/ui/src/user/UserEdit.js index 33f83dcfe..09ee6e3e3 100644 --- a/ui/src/user/UserEdit.js +++ b/ui/src/user/UserEdit.js @@ -31,25 +31,47 @@ const UserTitle = ({ record }) => { const UserToolbar = (props) => ( - - + + {props.permissions === 'admin' && } ) -const UserEdit = (props) => ( - } {...props}> - }> - - - - - - - {/**/} - - - - -) +const UserEdit = (props) => { + const { permissions } = props + const translate = useTranslate() + + const getNameHelperText = () => + props.id === localStorage.getItem('userId') && { + helperText: translate('resources.user.helperTexts.name'), + } + + return ( + } {...props}> + } + redirect={permissions === 'admin' ? 'list' : false} + > + {permissions === 'admin' && ( + + )} + + + + {permissions === 'admin' && ( + + )} + + {/**/} + + + + + ) +} export default UserEdit