package authentication import ( "fmt" "net/http" "strconv" "strings" "time" "github.com/golang-jwt/jwt" a "github.com/karincake/apem" dg "github.com/karincake/apem/db-gorm-pg" ms "github.com/karincake/apem/ms-redis" d "github.com/karincake/dodol" l "github.com/karincake/lepet" pa "simrs-vx/internal/lib/auth" pl "simrs-vx/pkg/logger" p "simrs-vx/pkg/password" eap "simrs-vx/internal/domain/main-entities/auth-partner" eu "simrs-vx/internal/domain/main-entities/user" euf "simrs-vx/internal/domain/main-entities/user-fes" erc "simrs-vx/internal/domain/references/common" ) const source = "authentication" var authCfg AuthCfg func init() { a.RegisterExtCall(GetConfig) } // Generates token and store in redis at one place // just return the error code func GenToken(input eu.LoginDto) (*d.Data, error) { event := pl.Event{ Feature: "Create", Source: source, } // Get User user := &eu.User{Name: input.Name} if errCode := getAndCheck(user, user, nil); errCode != "" { return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} } // Check login attempt if user.LoginAttemptCount > 5 { if user.LastSuccessLogin != nil { now := time.Now() lastAllowdLogin := user.LastAllowdLogin if lastAllowdLogin.After(now.Add(-time.Hour * 1)) { return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-tooMany", Message: pl.GenMessage("auth-login-tooMany")}} } else { tn := time.Now() user.LastAllowdLogin = &tn user.LoginAttemptCount = 0 dg.I.Save(&user) } } else { tn := time.Now() user.LastAllowdLogin = &tn dg.I.Save(&user) return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-tooMany", Message: pl.GenMessage("auth-login-tooMany")}} } } // Check password if !p.Check(input.Password, user.Password) { user.LoginAttemptCount++ dg.I.Save(&user) return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-incorrect", Message: pl.GenMessage("auth-login-incorrect")}} } else if user.Status_Code == erc.USCBlocked { return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-blocked", Message: pl.GenMessage("auth-login-blocked")}} } else if user.Status_Code == erc.USCNew { return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-login-unverified", Message: pl.GenMessage("auth-login-unverified")}} } // Data and output population atClaims := jwt.MapClaims{} outputData := d.II{} if err := populateRoles(user, input, atClaims, outputData, event); err != nil { return nil, err } // Only manual login tn := time.Now() user.LoginAttemptCount = 0 user.LastSuccessLogin = &tn user.LastAllowdLogin = &tn dg.I.Save(&user) // Current data return &d.Data{ Meta: d.IS{ "source": "authentication", "structure": "single-data", "status": "verified", }, Data: outputData, }, nil } func GenTokenFes(input euf.LoginDto) (*d.Data, error) { event := pl.Event{ Feature: "Create", Source: source, } // Get User Fes userFes := &euf.UserFes{Name: input.Name, AuthPartner_Code: input.AuthPartner_Code} if errCode := getAndCheck(userFes, userFes, "AuthPartner"); errCode != "" { return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} } // Preload above fails and I am so done, do it manually authPartner := eap.AuthPartner{} if err := dg.I.Where("\"Code\" = ?", userFes.AuthPartner_Code).First(&authPartner).Error; err != nil { return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-secretKey-invalid", Message: pl.GenMessage("auth-secretKey-invalid")}} } if authPartner.SecretKey != input.AuthPartner_SecretKey { return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-secretKey-invalid", Message: pl.GenMessage("auth-secretKey-invalid")}} } // Get User user := &eu.User{Name: userFes.User_Name} if errCode := getAndCheck(user, user, nil); errCode != "" { return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} } // Data and output population atClaims := jwt.MapClaims{} outputData := d.II{} primInput := eu.LoginDto{Duration: input.Duration} if err := populateRoles(user, primInput, atClaims, outputData, event); err != nil { return nil, err } // Current data return &d.Data{ Meta: d.IS{ "source": "authentication", "structure": "single-data", "status": "verified", }, Data: outputData, }, nil } func RevokeToken(uuid string) { ms.I.Del(uuid) } func VerifyToken(r *http.Request, tokenType TokenType) (data *jwt.Token, errCode, errDetail string) { auth := r.Header.Get("Authorization") if auth == "" { return nil, "auth-missingHeader", "" } authArr := strings.Split(auth, " ") if len(authArr) == 2 { auth = authArr[1] } token, err := jwt.Parse(auth, func(token *jwt.Token) (any, error) { //Make sure that the token method conform to "SigningMethodHMAC" if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf(l.I.Msg("token-sign-unexcpeted"), token.Header["alg"]) } if tokenType == AccessToken { return []byte(authCfg.AtSecretKey), nil } else { return []byte(authCfg.RtSecretKey), nil } }) if err != nil { return nil, "token-parse-fail", err.Error() } return token, "", "" } func GetAuthInfoByUserName(userName string) (data *pa.AuthInfo, err error) { return getAuthByUserName(userName) } func ExtractToken(r *http.Request, tokenType TokenType) (data *pa.AuthInfo, err error) { token, errCode, errDetail := VerifyToken(r, tokenType) if errCode != "" { return nil, d.FieldError{Code: errCode, Message: pl.GenMessage(errCode, errDetail)} } claims, ok := token.Claims.(jwt.MapClaims) if ok && token.Valid { accessUuid, ok := claims["uuid"].(string) if !ok { return nil, d.FieldError{Code: "token-invalid", Message: pl.GenMessage("token-invalid", "uuid not available")} } user_id, myErr := strconv.ParseInt(fmt.Sprintf("%.f", claims["user_id"]), 10, 64) if myErr != nil { return nil, d.FieldError{Code: "token-invalid", Message: pl.GenMessage("token-invalid", "uuid is not available")} } accessUuidRedis := ms.I.Get(accessUuid) if accessUuidRedis.String() == "" { return nil, d.FieldError{Code: "token-unidentified", Message: pl.GenMessage("token-unidentified")} } data = &pa.AuthInfo{ Uuid: accessUuid, User_Id: uint(user_id), User_Name: fmt.Sprintf("%v", claims["user_name"]), } data.User_ContractPosition_Code = checkStrClaims(claims, "contractPosition_code") data.Employee_Position_Code = checkStrPtrClaims(claims, "employee_position_code") data.Doctor_Code = checkStrPtrClaims(claims, "doctor_code") data.Nurse_Code = checkStrPtrClaims(claims, "nurse_code") data.Midwife_Code = checkStrPtrClaims(claims, "midwife_code") data.Nutritionist_Code = checkStrPtrClaims(claims, "nutritionist_code") data.Laborant_Code = checkStrPtrClaims(claims, "laborant_code") data.Pharmachist_Code = checkStrPtrClaims(claims, "pharmachist_code") data.Intern_Position_Code = checkStrPtrClaims(claims, "intern_position_code") data.Employee_Id = checkUntPtrClaims(claims, "employee_id") return } return nil, d.FieldError{Code: "token", Message: "token-invalid"} } func GetConfig() { a.ParseCfg(&authCfg) }