github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/controllers/user/fucntions.go (about) 1 // Copyright 2023 IAC. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package user 16 17 import ( 18 "fmt" 19 "strconv" 20 21 "net/http" 22 "time" 23 24 "github.com/gin-gonic/gin" 25 dbconn "github.com/mdaxf/iac/databases" 26 27 "github.com/mdaxf/iac/config" 28 "github.com/mdaxf/iac/controllers/common" 29 "github.com/mdaxf/iac/framework/auth" 30 "github.com/mdaxf/iac/logger" 31 "golang.org/x/crypto/bcrypt" 32 ) 33 34 // execLogin is a function that handles the login process for a user. 35 // It takes in the following parameters: 36 // - ctx: The gin.Context object for handling HTTP requests and responses. 37 // - username: The username of the user. 38 // - password: The password of the user. 39 // - clienttoken: The client token for the user. 40 // - ClientID: The client ID for the user. 41 // - Renew: A boolean value indicating whether the session should be renewed. 42 // 43 // The function performs the following steps: 44 // 1. Logs the start time of the function execution. 45 // 2. Defer a function to log the performance duration of the function. 46 // 3. If Renew is true, it checks if the session exists in the session cache. 47 // - If the session exists, it checks if the session has expired. 48 // - If the session has expired, it renews the session and returns the updated user information. 49 // - If the session has not expired, it returns an error indicating that the session has already expired. 50 // - If the session does not exist, it returns an error indicating that the session renewal failed. 51 // 4. If Renew is false, it performs the login process. 52 // - It queries the database to retrieve the user information. 53 // - If the user is found and the password matches, it updates the last sign-on date in the database. 54 // - It generates an authentication token for the user and stores it in the session cache. 55 // - It returns the user information and the authentication token. 56 // 5. If any error occurs during the execution, it returns an error response. 57 58 func execLogin(ctx *gin.Context, username string, password string, clienttoken string, ClientID string, Renew bool) { 59 60 log := logger.Log{ModuleName: logger.API, User: username, ClientID: ClientID, ControllerName: "UserController.execLogin"} 61 62 startTime := time.Now() 63 defer func() { 64 elapsed := time.Since(startTime) 65 log.PerformanceWithDuration("controllers.user.execLogin", elapsed) 66 }() 67 68 defer func() { 69 if err := recover(); err != nil { 70 log.Error(fmt.Sprintf("execLogin defer error: %s", err)) 71 ctx.JSON(http.StatusBadRequest, gin.H{"error": err}) 72 } 73 }() 74 75 // log.Debug("Login execution function is called.") 76 log.Debug(fmt.Sprintf("login parameters:%s %s token: %s renew:%s", username, password, clienttoken, Renew)) 77 // fmt.Println("Session Timeout:", config.SessionCacheTimeout) 78 if Renew { 79 80 user, err := config.SessionCache.Get(ctx, clienttoken) 81 82 log.Debug(fmt.Sprintf("SessionCache user:%v for token:%s", user, clienttoken)) 83 84 if err != nil { 85 log.Error(fmt.Sprintf("SessionCache error:%s for token", err.Error(), clienttoken)) 86 } else { 87 log.Debug(fmt.Sprintf("SessionCache user:%v for token:%s", user, clienttoken)) 88 if user != nil { 89 var tokenuser User 90 if val, ok := user.(User); ok { 91 tokenuser = val 92 } else { 93 // log.Debug(fmt.Sprintf("SessionCache error for token:%s", clienttoken)) 94 for key, value := range user.(map[string]interface{}) { 95 log.Debug(fmt.Sprintf("key:%s value:%v", key, value)) 96 if key == "token" { 97 tokenuser.Token = value.(string) 98 } else if key == "expirateon" { 99 tokenuser.ExpirateOn = value.(string) 100 } else if key == "id" { 101 tokenuser.ID = int(value.(int32)) 102 } else if key == "username" { 103 tokenuser.Username = value.(string) 104 } else if key == "language" { 105 tokenuser.Language = value.(string) 106 } else if key == "timezone" { 107 tokenuser.TimeZone = value.(string) 108 } else if key == "clientid" { 109 tokenuser.ClientID = value.(string) 110 } 111 } 112 } 113 114 if tokenuser.Token == clienttoken { 115 layout := "2006-01-02 15:04:05" 116 parsedTime, err := time.Parse(layout, tokenuser.ExpirateOn) 117 log.Debug(fmt.Sprintf("token %s expirate on:%s expired? %s", tokenuser.Token, parsedTime, parsedTime.Before(time.Now()))) 118 if err != nil { 119 log.Error(fmt.Sprintf("SessionCache error:%s for token:%s", err.Error(), clienttoken)) 120 } else if parsedTime.Before(time.Now()) { 121 log.Debug(fmt.Sprintf("renew the session for user:%s, token: %s", username, tokenuser.Token)) 122 123 token, createdt, expdt, err := auth.Extendexptime(tokenuser.Token) 124 if err != nil { 125 log.Error(fmt.Sprintf("SessionCache error:%s for token:%s", err.Error(), clienttoken)) 126 } else { 127 ID := tokenuser.ID 128 Username := tokenuser.Username 129 // hasedPassword := user.(User).Password 130 language := tokenuser.Language 131 timezone := tokenuser.TimeZone 132 user = User{ID: ID, Username: Username, Language: language, TimeZone: timezone, ClientID: ClientID, CreatedOn: createdt, ExpirateOn: expdt, Token: token} 133 config.SessionCache.Delete(ctx, clienttoken) 134 config.SessionCache.Put(ctx, token, user, time.Duration(config.SessionCacheTimeout)*time.Second) 135 ctx.JSON(http.StatusOK, user) 136 return 137 } 138 } 139 log.Debug(fmt.Sprintf("token %s already expried", clienttoken)) 140 } 141 } 142 143 } 144 log.Error(fmt.Sprintf("Renew session failed for user:%s", username)) 145 146 ctx.JSON(http.StatusNotFound, "Renew failed") 147 148 return 149 } 150 151 //hasedPassword, err := hashPassword(password) 152 querystr := fmt.Sprintf(LoginQuery, username) 153 154 log.Debug(fmt.Sprintf("Query:%s", querystr)) 155 156 iDBTx, err := dbconn.DB.Begin() 157 defer iDBTx.Rollback() 158 159 if err != nil { 160 log.Error(fmt.Sprintf("Begin error:%s", err.Error())) 161 ctx.JSON(http.StatusInternalServerError, "Login failed") 162 } 163 164 dboperation := dbconn.NewDBOperation(username, iDBTx, "User Login") 165 /* 166 rows, err := dboperation.Query(querystr) 167 defer rows.Close() 168 if err != nil { 169 170 log.Error(fmt.Sprintf("Query error:%s", err.Error())) 171 ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 172 return 173 } 174 log.Debug(fmt.Sprintf("Query result:%v", rows)) 175 176 */ 177 jdata, err := dboperation.Query_Json(querystr) 178 179 if err != nil { 180 181 log.Error(fmt.Sprintf("Query error:%s", err.Error())) 182 ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 183 return 184 } 185 186 log.Debug(fmt.Sprintf("Query result:%v", jdata)) 187 188 if jdata != nil { 189 var ID int 190 var Name string 191 var FamilyName string 192 var storedPassword string 193 /* 194 err = rows.Scan(&ID, &Name, &FamilyName) 195 if err != nil { 196 log.Error(fmt.Sprintf("Row Scan error:%s", err.Error())) 197 ctx.JSON(http.StatusInternalServerError, "Login failed") 198 return 199 } */ 200 201 if len(jdata) == 0 { 202 log.Error(fmt.Sprintf("User:%s not found", username)) 203 ctx.JSON(http.StatusNotFound, "Login failed") 204 return 205 } 206 207 ID = int(jdata[0]["ID"].(int64)) 208 Name = jdata[0]["Name"].(string) 209 FamilyName = jdata[0]["LastName"].(string) 210 storedPassword = jdata[0]["Password"].(string) 211 language := jdata[0]["LanguageCode"].(string) 212 timezone := jdata[0]["TimeZoneCode"].(string) 213 214 if jdata[0]["Password"] != nil && storedPassword != "" { 215 err = bcrypt.CompareHashAndPassword([]byte(storedPassword), []byte(password)) 216 if err != nil { 217 log.Error(fmt.Sprintf("Password compare error:%s", err.Error())) 218 ctx.JSON(http.StatusNotFound, "Login failed") 219 return 220 } 221 } 222 223 log.Debug(fmt.Sprintf("ID:%d Name:%s FamilyName:%s", ID, Name, FamilyName)) 224 225 Columns := []string{"LastSignOnDate", "UpdatedOn", "UpdatedBy"} 226 Values := []string{time.Now().UTC().Format("2006-01-02 15:04:05"), time.Now().UTC().Format("2006-01-02 15:04:05"), username} 227 datatypes := []int{0, 0, 0} 228 Wherestr := fmt.Sprintf("ID= %d", ID) 229 230 index, errr := dboperation.TableUpdate(TableName, Columns, Values, datatypes, Wherestr) 231 232 if errr != nil { 233 log.Error(fmt.Sprintf("TableUpdate error:%s", errr.Error())) 234 ctx.JSON(http.StatusInternalServerError, "Login failed") 235 return 236 } 237 log.Debug(fmt.Sprintf("index:%d", index)) 238 239 iDBTx.Commit() 240 241 token, createdt, expdt, err := auth.Generate_authentication_token(string(rune(ID)), username, ClientID) 242 243 sessionid := token 244 exist, err := config.SessionCache.IsExist(ctx, sessionid) 245 246 if err != nil && exist { 247 config.SessionCache.Delete(ctx, sessionid) 248 249 } 250 user := User{ID: ID, Username: username, Language: language, TimeZone: timezone, ClientID: ClientID, CreatedOn: createdt, ExpirateOn: expdt, Token: token} 251 252 log.Debug(fmt.Sprintf("user:%v", user)) 253 254 config.SessionCache.Put(ctx, sessionid, user, time.Duration(config.SessionCacheTimeout)*time.Second) 255 log.Debug(fmt.Sprintf("User:%s login successful!", user)) 256 257 ctx.JSON(http.StatusOK, user) 258 return 259 } 260 261 iDBTx.Rollback() 262 log.Error(fmt.Sprintf("Login failed for user:%s", username)) 263 264 ctx.JSON(http.StatusNotFound, "Login failed") 265 266 } 267 268 // getUserImage retrieves the user's image URL from the database. 269 // It takes the username and client ID as parameters and returns the image URL as a string. 270 // If an error occurs during the execution, it returns an error. 271 272 func getUserImage(username string, clientid string) (string, error) { 273 log := logger.Log{ModuleName: logger.API, User: username, ClientID: clientid, ControllerName: "UserController.getUserImage"} 274 startTime := time.Now() 275 defer func() { 276 elapsed := time.Since(startTime) 277 log.PerformanceWithDuration("controllers.user.getUserImage", elapsed) 278 }() 279 /* 280 defer func() { 281 if err := recover(); err != nil { 282 log.Error(fmt.Sprintf("getUserImage defer error: %s", err)) 283 //ctx.JSON(http.StatusBadRequest, gin.H{"error": err}) 284 } 285 }() 286 */ 287 log.Debug("Get User Image execution function is called.") 288 289 querystr := fmt.Sprintf(GetUserImageQuery, username) 290 291 log.Debug(fmt.Sprintf("Get User image Query:%s", querystr)) 292 293 iDBTx, err := dbconn.DB.Begin() 294 defer iDBTx.Rollback() 295 296 if err != nil { 297 log.Error(fmt.Sprintf("Begin error:%s", err.Error())) 298 return "", err 299 } 300 301 dboperation := dbconn.NewDBOperation(username, iDBTx, "User Login") 302 303 jdata, err := dboperation.Query_Json(querystr) 304 305 if err != nil { 306 307 log.Error(fmt.Sprintf("Query error:%s", err.Error())) 308 return "", err 309 310 } 311 312 log.Debug(fmt.Sprintf("Query result:%v", jdata)) 313 314 if jdata != nil { 315 var PictureUrl string 316 317 if len(jdata) == 0 { 318 return "", nil 319 } 320 321 if jdata[0]["PictureUrl"] == nil { 322 return "", nil 323 } 324 325 PictureUrl = jdata[0]["PictureUrl"].(string) 326 327 iDBTx.Commit() 328 329 log.Debug(fmt.Sprintf("PictureUrl:%s", PictureUrl)) 330 331 return PictureUrl, nil 332 333 } 334 return "", nil 335 } 336 337 // getUserMenus retrieves the menus for a user based on the provided parameters. 338 // It takes the userID, isMobile flag, parentID, username, and clientid as input. 339 // The function returns a slice of maps representing the menus and an error if any. 340 341 func getUserMenus(userID int, isMobile bool, parentID int, username string, clientid string) ([]map[string]interface{}, error) { 342 log := logger.Log{ModuleName: logger.API, User: username, ClientID: clientid, ControllerName: "UserController"} 343 344 startTime := time.Now() 345 defer func() { 346 elapsed := time.Since(startTime) 347 log.PerformanceWithDuration("controllers.user.getUserMenus", elapsed) 348 }() 349 350 defer func() { 351 if err := recover(); err != nil { 352 log.Error(fmt.Sprintf("getUserMenus defer error: %s", err)) 353 //ctx.JSON(http.StatusBadRequest, gin.H{"error": err}) 354 } 355 }() 356 357 log.Debug("get user menus execution function is called.") 358 359 Mobile := 0 360 Desktop := 1 361 362 if isMobile { 363 Mobile = 1 364 Desktop = 0 365 } 366 367 querystr := fmt.Sprintf(GetUserMenusQuery, userID, Mobile, Desktop, parentID, userID) 368 369 log.Debug(fmt.Sprintf("Query:%s", querystr)) 370 371 iDBTx, err := dbconn.DB.Begin() 372 373 if err != nil { 374 log.Error(fmt.Sprintf("Begin error:%s", err.Error())) 375 return nil, err 376 } 377 378 defer iDBTx.Rollback() 379 380 dboperation := dbconn.NewDBOperation(strconv.Itoa(userID), iDBTx, "User Menus") 381 382 jdata, err := dboperation.Query_Json(querystr) 383 384 if err != nil { 385 log.Error(fmt.Sprintf("Query menu error:%s", err.Error())) 386 return jdata, err 387 } 388 389 log.Debug(fmt.Sprintf("Query result:%v", jdata)) 390 391 return jdata, nil 392 } 393 394 // execChangePassword is a function that handles the execution of changing a user's password. 395 // It takes the following parameters: 396 // - ctx: The gin.Context object for handling HTTP requests and responses. 397 // - username: The username of the user whose password is being changed. 398 // - oldpassword: The old password of the user. 399 // - newpassword: The new password to be set for the user. 400 // - clientid: The client ID associated with the user. 401 // It returns an error if any error occurs during the password change process. 402 403 func execChangePassword(ctx *gin.Context, username string, oldpassword string, newpassword string, clientid string) error { 404 log := logger.Log{ModuleName: logger.API, User: username, ClientID: clientid, ControllerName: "UserController"} 405 startTime := time.Now() 406 defer func() { 407 elapsed := time.Since(startTime) 408 log.PerformanceWithDuration("controllers.user.execChangePassword", elapsed) 409 }() 410 411 defer func() { 412 if err := recover(); err != nil { 413 log.Error(fmt.Sprintf("execChangePassword defer error: %s", err)) 414 //ctx.JSON(http.StatusBadRequest, gin.H{"error": err}) 415 } 416 }() 417 418 log.Debug("execChangePassword execution function is called.") 419 420 result, jdata, err := validatePassword(username, oldpassword) 421 422 if err != nil { 423 log.Error(fmt.Sprintf("validatePassword error:%s", err.Error())) 424 425 ctx.JSON(http.StatusInternalServerError, "Validate old password failed") 426 return err 427 } 428 429 if result == false { 430 log.Error(fmt.Sprintf("validatePassword error:%s", err.Error())) 431 ctx.JSON(http.StatusInternalServerError, "Validate old password failed") 432 return err 433 } 434 435 hashedPassword, err := hashPassword(newpassword) 436 437 if jdata != nil { 438 439 ID := int(jdata[0]["ID"].(int64)) 440 441 log.Debug(fmt.Sprintf("user ID:%d new hashed password: %s ", ID, hashedPassword)) 442 443 Columns := []string{"Password", "PasswordLastChangeDate", "UpdatedOn", "UpdatedBy"} 444 Values := []string{hashedPassword, time.Now().Format("2006-01-02 15:04:05"), time.Now().Format("2006-01-02 15:04:05"), username} 445 datatypes := []int{0, 0, 0} 446 Wherestr := fmt.Sprintf("ID= %d", ID) 447 448 iDBTx, err := dbconn.DB.Begin() 449 defer iDBTx.Rollback() 450 451 if err != nil { 452 log.Error(fmt.Sprintf("Begin error:%s", err.Error())) 453 ctx.JSON(http.StatusInternalServerError, "Change password failed") 454 return err 455 } 456 457 dboperation := dbconn.NewDBOperation(username, iDBTx, "User ChangePassword") 458 459 index, errr := dboperation.TableUpdate(TableName, Columns, Values, datatypes, Wherestr) 460 461 if errr != nil { 462 log.Error(fmt.Sprintf("TableUpdate error:%s", errr.Error())) 463 ctx.JSON(http.StatusInternalServerError, "Change password failed") 464 return errr 465 } 466 467 if index == 0 { 468 log.Error(fmt.Sprintf("TableUpdate error:%s", errr.Error())) 469 ctx.JSON(http.StatusInternalServerError, "Change password failed") 470 return errr 471 } 472 473 iDBTx.Commit() 474 ctx.JSON(http.StatusOK, "OK") 475 return nil 476 } 477 478 ctx.JSON(http.StatusInternalServerError, "Change password failed") 479 return nil 480 } 481 482 // execLogout is a function that handles the logout process for a user. 483 // It takes a gin.Context object and a token string as parameters. 484 // It returns a string "OK" and an error if any. 485 486 func execLogout(ctx *gin.Context, token string) (string, error) { 487 log := logger.Log{ModuleName: logger.API, User: "System", ControllerName: "UserController"} 488 489 startTime := time.Now() 490 defer func() { 491 elapsed := time.Since(startTime) 492 log.PerformanceWithDuration("controllers.user.execLogout", elapsed) 493 }() 494 495 defer func() { 496 if err := recover(); err != nil { 497 log.Error(fmt.Sprintf("execLogout defer error: %s", err)) 498 ctx.JSON(http.StatusBadRequest, gin.H{"error": err}) 499 } 500 }() 501 ID, user, clientid, err := common.GetRequestUser(ctx) 502 if err != nil { 503 log.Error(fmt.Sprintf("Get user information Error: %v", err)) 504 505 return "", err 506 } 507 log.ClientID = clientid 508 log.User = user 509 log.Debug(fmt.Sprintf("execLogout execution function is called. token: %s, %s ", token, ID)) 510 511 Columns := []string{"LastSignOffDate", "UpdatedOn", "UpdatedBy"} 512 Values := []string{time.Now().UTC().Format("2006-01-02 15:04:05"), time.Now().UTC().Format("2006-01-02 15:04:05"), user} 513 datatypes := []int{0, 0, 0} 514 Wherestr := fmt.Sprintf("LoginName= '%s'", user) 515 516 iDBTx, err := dbconn.DB.Begin() 517 defer iDBTx.Rollback() 518 519 if err != nil { 520 log.Error(fmt.Sprintf("Begin error:%s", err.Error())) 521 ctx.JSON(http.StatusInternalServerError, "Login failed") 522 } 523 524 dboperation := dbconn.NewDBOperation(user, iDBTx, "User Logout") 525 526 index, errr := dboperation.TableUpdate(TableName, Columns, Values, datatypes, Wherestr) 527 528 if errr != nil { 529 log.Error(fmt.Sprintf("TableUpdate error:%s", errr.Error())) 530 ctx.JSON(http.StatusInternalServerError, "Logout failed") 531 return "", errr 532 } 533 log.Debug(fmt.Sprintf("index:%d", index)) 534 535 config.SessionCache.Delete(ctx, token) 536 537 iDBTx.Commit() 538 539 return "OK", nil 540 } 541 542 // hashPassword takes a password string and returns the hashed password string. 543 // It uses bcrypt.GenerateFromPassword to generate a secure hash of the password. 544 // The bcrypt.DefaultCost is used to determine the cost factor of the hashing algorithm. 545 // Returns the hashed password string and any error encountered during the hashing process. 546 547 func hashPassword(password string) (string, error) { 548 hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 549 if err != nil { 550 return "", err 551 } 552 553 hashedPassword := string(hashedPasswordBytes) 554 return hashedPassword, nil 555 } 556 557 // validatePassword validates the password for a given username. 558 // It returns a boolean indicating whether the password is valid, 559 // a slice of maps containing user data if the password is valid, 560 // and an error if any error occurs during the validation process. 561 562 func validatePassword(username string, password string) (bool, []map[string]interface{}, error) { 563 log := logger.Log{ModuleName: logger.API, User: "System", ControllerName: "UserController"} 564 565 startTime := time.Now() 566 defer func() { 567 elapsed := time.Since(startTime) 568 log.PerformanceWithDuration("controllers.user.validatePassword", elapsed) 569 }() 570 571 defer func() { 572 if err := recover(); err != nil { 573 log.Error(fmt.Sprintf("validatePassword defer error: %s", err)) 574 // ctx.JSON(http.StatusBadRequest, gin.H{"error": err}) 575 } 576 }() 577 578 log.Debug(fmt.Sprintf("validate password function is called. username: %s ", username)) 579 580 // hashedPassword, err := hashPassword(password) 581 582 jdata := []map[string]interface{}{} 583 /* 584 if err != nil { 585 log.Error(fmt.Sprintf("hashPassword error:%s", err.Error())) 586 return false, nil, err 587 } 588 */ 589 querystr := fmt.Sprintf(LoginQuery, username) 590 591 log.Debug(fmt.Sprintf("Query:%s", querystr)) 592 593 iDBTx, err := dbconn.DB.Begin() 594 defer iDBTx.Rollback() 595 596 if err != nil { 597 log.Error(fmt.Sprintf("Begin error:%s", err.Error())) 598 return false, nil, err 599 } 600 601 dboperation := dbconn.NewDBOperation(username, iDBTx, "User Login") 602 603 jdata, err = dboperation.Query_Json(querystr) 604 605 if err != nil { 606 607 log.Error(fmt.Sprintf("Query error:%s", err.Error())) 608 return false, nil, err 609 610 } 611 612 log.Debug(fmt.Sprintf("Query result:%v", jdata)) 613 614 if jdata != nil { 615 616 iDBTx.Commit() 617 618 if len(jdata) == 0 { 619 return false, nil, err 620 } 621 622 storedPassword := jdata[0]["Password"].(string) 623 624 if storedPassword == "" || jdata[0]["Password"] == nil { 625 return true, jdata, nil 626 } 627 628 err := bcrypt.CompareHashAndPassword([]byte(storedPassword), []byte(password)) 629 if err != nil { 630 log.Error(fmt.Sprintf("CompareHashAndPassword error:%s", err.Error())) 631 return false, nil, err 632 } 633 634 return true, jdata, nil 635 } 636 return false, nil, nil 637 }