update user profile

main
alex 2023-06-13 19:15:47 +02:00
parent 8444724a58
commit 4612764b0b
4 changed files with 95 additions and 28 deletions

View File

@ -11,6 +11,7 @@ type User struct {
Email string
Password string
LastOnline time.Time
UpdatedAt time.Time
CreatedAt time.Time
}

View File

@ -3,6 +3,8 @@ package utils
const (
minUsername = "2"
maxUsername = "20"
MinUsername = 2
MaxUsername = 20
minPassword = "6"
MinPassword = 6
maxPassword = "64"
@ -18,7 +20,7 @@ const (
MaxAvatarSize = 5 * 1024 * 1024 // 5 MB
GroupTaskLockedTime = 3 // seconds - need to be equal with web
GroupTaskLockedTime = 3
SessionExpiresAtTime = 7 * 24 * 60 * 60 // 1 week
)

View File

@ -8,6 +8,7 @@ import (
"janex/admin-dashboard-backend/modules/utils"
"time"
"github.com/gofiber/websocket/v2"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt"
)
@ -55,6 +56,14 @@ func SendMessageToUser(userId string, ignoreUserSessionId string, sendSocketMess
}
}
func SendMessageOnlyToSessionId(sessionId string, sendSocketMessage structs.SendSocketMessage) {
for _, client := range cache.GetSocketClients() {
if client.SessionId == sessionId {
client.SendMessage(sendSocketMessage)
}
}
}
// This close all connections that are connected with one session id.
// For example when a user has two browser tabs opened
func CloseAllUserSessionConnections(sessionId string) {
@ -65,6 +74,18 @@ func CloseAllUserSessionConnections(sessionId string) {
}
}
// Used to close all user connections
// For example when a user changed his password
func CloseAndDeleteAllUserConnections(userId string) {
for _, client := range cache.GetSocketClients() {
if client.UserId == userId {
client.SendSessionClosedMessage()
}
}
database.DB.Where("user_id = ?", userId).Delete(&structs.UserSession{})
}
func GetUserSessions(userId string) []structs.UserSessionSocket {
var userSessions []structs.UserSession
@ -168,20 +189,25 @@ func isEmailAvailable(email string) bool {
return user.Email == ""
}
func UpdateUserProfile(userId string, changes map[string]interface{}) {
log.Debug().Msgf("changes: %v", changes)
func UpdateUserProfile(conn *websocket.Conn, changes map[string]interface{}) {
sessionId := conn.Locals("sessionId").(string)
userId := conn.Locals("userId").(string)
var user structs.User
var updates = make(map[string]interface{})
// TODO: validate length of username and email
var changesResult = make(map[string]uint8)
if changes["username"] != nil {
username := changes["username"].(string)
if isUsernameAvailable(username) {
user.Username = username
updates["Username"] = username
if isUsernameLengthValid(username) { // only affected if username was manipulated as min and max is provided in web ui
if isUsernameAvailable(username) {
user.Username = username
updates["Username"] = username
changesResult["Username"] = 0
} else {
changesResult["Username"] = 1
}
}
}
@ -191,37 +217,71 @@ func UpdateUserProfile(userId string, changes map[string]interface{}) {
if isEmailAvailable(email) {
user.Email = email
updates["Email"] = email
changesResult["Email"] = 0
} else {
changesResult["Email"] = 1
}
}
if changes["password"] != nil {
log.Debug().Msg("update password")
password := changes["password"].(string)
if changes["oldPassword"] != nil && changes["newPassword"] != nil {
oldPassword := changes["oldPassword"].(string)
newPassword := changes["newPassword"].(string)
decodedPassword, err := base64.StdEncoding.DecodeString(changes["password"].(string))
decodedOldPassword, err := base64.StdEncoding.DecodeString(oldPassword)
decodedNewPassword, err1 := base64.StdEncoding.DecodeString(newPassword)
if err != nil {
log.Error().Msg("Failed to decode base64 password, err: " + err.Error())
}
if err == nil && err1 == nil {
if utils.IsPasswordLengthValid(string(decodedOldPassword)) { // only affected if username was manipulated as min and max is provided in web ui
database.DB.Select("password").First(&user, "id = ?", userId)
if utils.IsPasswordLengthValid(password) {
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), decodedPassword); err != nil {
log.Error().Msg("Incorrect password")
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), decodedOldPassword); err == nil {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(decodedNewPassword), bcrypt.DefaultCost)
if err == nil {
user.Password = string(hashedPassword)
} else {
log.Error().Msgf("Failed to generate hash password %s", err.Error())
}
} else {
log.Error().Msg("Incorrect password")
changesResult["Password"] = 1
}
}
} else {
if err != nil {
log.Error().Msgf("Failed to decode old password %s", err.Error())
}
if err1 != nil {
log.Error().Msgf("Failed to decode new password %s", err1.Error())
}
}
// TODO: logout all client user sessions
}
log.Debug().Msgf("len %v", len(changes))
// TODO: dont sent change message if user changed password
if len(changes) > 0 {
// TODO: update user last updated timestamp
user.UpdatedAt = time.Now()
database.DB.Model(&structs.User{}).Where("id = ?", userId).Updates(user)
if changes["username"] != nil || changes["email"] != nil {
BroadcastMessage(structs.SendSocketMessage{
if changes["username"] != nil || changes["email"] != nil || changes["oldPassword"] != nil && changes["newPassword"] != nil {
if changes["oldPassword"] != nil && changes["newPassword"] != nil {
// user has changed password - logout all his sessions
CloseAndDeleteAllUserConnections(userId)
} else {
SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
Cmd: utils.SentCmdUserProfileUpdated,
Body: struct {
UserId string
Changes map[string]interface{}
Result map[string]uint8
}{
UserId: userId,
Changes: updates,
Result: changesResult,
},
})
}
BroadcastMessageExceptUserSessionId(sessionId, structs.SendSocketMessage{
Cmd: utils.SentCmdUserProfileUpdated,
Body: struct {
UserId string
@ -233,5 +293,9 @@ func UpdateUserProfile(userId string, changes map[string]interface{}) {
})
}
}
// TODO: sent feedback back to user for ui notification message
}
func isUsernameLengthValid(username string) bool {
l := len(username)
return l > utils.MinUsername && l < utils.MaxUsername
}

View File

@ -187,7 +187,7 @@ func RunHub() {
})
break
case utils.ReceivedCmdUpdateUserProfile:
socketclients.UpdateUserProfile(data.Conn.Locals("userId").(string), receivedMessage.Body["changes"].(map[string]interface{}))
socketclients.UpdateUserProfile(data.Conn, receivedMessage.Body["changes"].(map[string]interface{}))
break
default:
log.Error().Msgf("Received unknown message: %v", receivedMessage)