From 3720ca2e718206292e08eb753b5ddf9c78cd6de9 Mon Sep 17 00:00:00 2001 From: Munawwirul Jamal Date: Sat, 8 Nov 2025 06:07:14 +0700 Subject: [PATCH] feat/sso-auth: wip --- .../main-handler/authentication/handler.go | 28 +- .../main-use-case/authentication/case.go | 332 ++++++++++++------ .../main-use-case/authentication/helper.go | 122 +++++++ 3 files changed, 368 insertions(+), 114 deletions(-) diff --git a/internal/interface/main-handler/authentication/handler.go b/internal/interface/main-handler/authentication/handler.go index 709f33a3..fc36dc52 100644 --- a/internal/interface/main-handler/authentication/handler.go +++ b/internal/interface/main-handler/authentication/handler.go @@ -6,11 +6,13 @@ import ( d "github.com/karincake/dodol" rw "github.com/karincake/risoles" + sp "github.com/karincake/semprit" + sr "github.com/karincake/serabi" m "simrs-vx/internal/domain/main-entities/user" - s "simrs-vx/internal/use-case/main-use-case/authentication" - + mf "simrs-vx/internal/domain/main-entities/user-fes" pa "simrs-vx/internal/lib/auth" + s "simrs-vx/internal/use-case/main-use-case/authentication" ) func Login(w http.ResponseWriter, r *http.Request) { @@ -50,3 +52,25 @@ func GuardMW(next http.Handler) http.Handler { next.ServeHTTP(w, r.WithContext(ctx)) }) } + +func LoginFes(w http.ResponseWriter, r *http.Request) { + var input mf.LoginDto + err := sp.IOReaderJson(input, r.Body) + if err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) + } + + input.AuthPartner_Code = r.Header.Get("X-AuthPartner-Code") + input.AuthPartner_SecretKey = r.Header.Get("X-AuthPartner-SecretKey") + if err = (sr.Validate(input)); err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) + } + + // input.Position = Position + res, err := s.GenTokenFes(input) + if err != nil { + rw.WriteJSON(w, http.StatusUnauthorized, d.II{"errors": err}, nil) + } else { + rw.DataResponse(w, res, err) + } +} diff --git a/internal/use-case/main-use-case/authentication/case.go b/internal/use-case/main-use-case/authentication/case.go index bd9dcee6..061677d4 100644 --- a/internal/use-case/main-use-case/authentication/case.go +++ b/internal/use-case/main-use-case/authentication/case.go @@ -19,16 +19,9 @@ import ( pl "simrs-vx/pkg/logger" p "simrs-vx/pkg/password" - ed "simrs-vx/internal/domain/main-entities/doctor" - ee "simrs-vx/internal/domain/main-entities/employee" - "simrs-vx/internal/domain/main-entities/intern" - em "simrs-vx/internal/domain/main-entities/midwife" - en "simrs-vx/internal/domain/main-entities/nurse" - ep "simrs-vx/internal/domain/main-entities/pharmacist" eu "simrs-vx/internal/domain/main-entities/user" - + euf "simrs-vx/internal/domain/main-entities/user-fes" erc "simrs-vx/internal/domain/references/common" - erg "simrs-vx/internal/domain/references/organization" ) const source = "authentication" @@ -115,121 +108,150 @@ func GenToken(input eu.LoginDto) (*d.Data, error) { } // extra - role := []string{} - switch user.ContractPosition_Code { - case erg.CSCEmp: - // employee - employee := ee.Employee{} - dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) - if employee.Id == 0 { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} - } - atClaims["employee_id"] = employee.Id - outputData["employee_id"] = employee.Id - role = append(role, "emp-"+string(*employee.Position_Code)) + // role := []string{} + // switch user.ContractPosition_Code { + // case erg.CSCEmp: + // // employee + // employee := ee.Employee{} + // dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) + // if employee.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} + // } + // atClaims["employee_id"] = employee.Id + // outputData["employee_id"] = employee.Id + // role = append(role, "emp-"+string(*employee.Position_Code)) - //if employee.Division_Code != nil { - // atClaims["employee_division_code"] = employee.Division_Code - // outputData["employee_division_code"] = employee.Division_Code - //} + // //if employee.Division_Code != nil { + // // atClaims["employee_division_code"] = employee.Division_Code + // // outputData["employee_division_code"] = employee.Division_Code + // //} - // employee position - if employee.Id > 0 && employee.Position_Code != nil { - atClaims["employee_position_code"] = *employee.Position_Code - switch *employee.Position_Code { - case erg.EPCDoc: - doctor := ed.Doctor{} - dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) - if doctor.Id == 0 { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} - } - atClaims["doctor_code"] = doctor.Code - outputData["doctor_code"] = doctor.Code + // // employee position + // if employee.Id > 0 && employee.Position_Code != nil { + // atClaims["employee_position_code"] = *employee.Position_Code + // switch *employee.Position_Code { + // case erg.EPCDoc: + // doctor := ed.Doctor{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) + // if doctor.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} + // } + // atClaims["doctor_code"] = doctor.Code + // outputData["doctor_code"] = doctor.Code - // specialist - if doctor.Specialist_Code != nil { - atClaims["specialist_code"] = doctor.Specialist_Code - outputData["specialist_code"] = doctor.Specialist_Code - } - if doctor.Subspecialist_Code != nil { - atClaims["subspecialist_code"] = doctor.Subspecialist_Code - outputData["subspecialist_code"] = doctor.Subspecialist_Code - } - case erg.EPCNur: - empData := en.Nurse{} - dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - if empData.Id == 0 { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} - } - atClaims["nurse_code"] = empData.Code - outputData["nurse_code"] = empData.Code - case erg.EPCMwi: - empData := em.Midwife{} - dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - if empData.Id == 0 { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} - } - atClaims["midwife_code"] = empData.Code - outputData["midwife_code"] = empData.Code - case erg.EPCPha: - empData := ep.Pharmacist{} - dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) - if empData.Id == 0 { - return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noPharmacist", Message: pl.GenMessage("auth-noPharmacist")}} - } - atClaims["pharmacist_code"] = empData.Code - outputData["pharmacist_code"] = empData.Code - } + // // specialist + // if doctor.Specialist_Code != nil { + // atClaims["specialist_code"] = doctor.Specialist_Code + // outputData["specialist_code"] = doctor.Specialist_Code + // } + // if doctor.Subspecialist_Code != nil { + // atClaims["subspecialist_code"] = doctor.Subspecialist_Code + // outputData["subspecialist_code"] = doctor.Subspecialist_Code + // } + // case erg.EPCNur: + // empData := en.Nurse{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + // } + // atClaims["nurse_code"] = empData.Code + // outputData["nurse_code"] = empData.Code + // case erg.EPCMwi: + // empData := em.Midwife{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + // } + // atClaims["midwife_code"] = empData.Code + // outputData["midwife_code"] = empData.Code + // case erg.EPCPha: + // empData := ep.Pharmacist{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noPharmacist", Message: pl.GenMessage("auth-noPharmacist")}} + // } + // atClaims["pharmacist_code"] = empData.Code + // outputData["pharmacist_code"] = empData.Code + // } + // // specialist + // if doctor.Specialist_Code != nil { + // atClaims["specialist_code"] = doctor.Specialist_Code + // outputData["specialist_code"] = doctor.Specialist_Code + // } + // if doctor.Subspecialist_Code != nil { + // atClaims["subspecialist_code"] = doctor.Subspecialist_Code + // outputData["subspecialist_code"] = doctor.Subspecialist_Code + // } + // case erg.EPCNur: + // empData := en.Nurse{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + // } + // atClaims["nurse_code"] = empData.Code + // outputData["nurse_code"] = empData.Code + // case erg.EPCMwi: + // empData := em.Midwife{} + // dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + // if empData.Id == 0 { + // return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + // } + // atClaims["midwife_code"] = empData.Code + // outputData["midwife_code"] = empData.Code + // } - errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} + // errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} - // division position - divisionPositions, err := getDivisionPosition(employee.Id, &event) - if err != nil { - return nil, errorGetPosition - } + // // division position + // divisionPositions, err := getDivisionPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } - // installation position - installationPositions, err := getInstallationPosition(employee.Id, &event) - if err != nil { - return nil, errorGetPosition - } + // // installation position + // installationPositions, err := getInstallationPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } - // unit position - unitPositions, err := getUnitPosition(employee.Id, &event) - if err != nil { - return nil, errorGetPosition - } + // // unit position + // unitPositions, err := getUnitPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } - // specialist position - specialistPositions, err := getSpecialistPosition(employee.Id, &event) - if err != nil { - return nil, errorGetPosition - } + // // specialist position + // specialistPositions, err := getSpecialistPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } - // subspecialist position - subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) - if err != nil { - return nil, errorGetPosition - } + // // subspecialist position + // subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) + // if err != nil { + // return nil, errorGetPosition + // } - role = append(role, divisionPositions...) - role = append(role, installationPositions...) - role = append(role, unitPositions...) - role = append(role, specialistPositions...) - role = append(role, subspecialistPositions...) - // atClaims["division_positions"] = divsionPositions - // outputData["division_positions"] = divsionPositions - } - case erg.CSCInt: - intern := intern.Intern{} - dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) - role = append(role, "int-"+string(*intern.Position_Code)) - case erg.CSCSys: - role = append(role, "system") + // role = append(role, divisionPositions...) + // role = append(role, installationPositions...) + // role = append(role, unitPositions...) + // role = append(role, specialistPositions...) + // role = append(role, subspecialistPositions...) + // // atClaims["division_positions"] = divsionPositions + // // outputData["division_positions"] = divsionPositions + // } + // case erg.CSCInt: + // intern := intern.Intern{} + // dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) + // role = append(role, "int-"+string(*intern.Position_Code)) + // case erg.CSCSys: + // role = append(role, "system") + // } + // atClaims["roles"] = role + // outputData["roles"] = role + if err := populateRoles(user, atClaims, outputData, event); err != nil { + return nil, err } - atClaims["roles"] = role - outputData["roles"] = role // Generate jwt at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) @@ -339,3 +361,89 @@ func ExtractToken(r *http.Request, tokenType TokenType) (data *pa.AuthInfo, err func GetConfig() { a.ParseCfg(&authCfg) } + +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); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + if userFes.AuthPartner.SecretKey != input.AuthPartner_SecretKey { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: "auth-secretKey-invalid", Message: pl.GenMessage("auth-secretKey-invalid")}} + } + + user := &eu.User{Name: userFes.User_Name} + if errCode := getAndCheck(user, user); errCode != "" { + return nil, d.FieldErrors{"authentication": d.FieldError{Code: errCode, Message: pl.GenMessage(errCode)}} + } + + // Access token prep + id, err := uuid.NewRandom() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("uuid-gen-fail"), err)) + } + if input.Duration == 0 { + input.Duration = 24 * 60 + } + duration := time.Minute * time.Duration(input.Duration) + aUuid := id.String() + atExpires := time.Now().Add(duration).Unix() + atSecretKey := authCfg.AtSecretKey + + // Create Claim + atClaims := jwt.MapClaims{} + atClaims["user_id"] = user.Id + atClaims["user_name"] = user.Name + atClaims["user_contractPosition_code"] = user.ContractPosition_Code + atClaims["uuid"] = aUuid + atClaims["exp"] = atExpires + + // Create output + outputData := d.II{ + "user_id": strconv.Itoa(int(user.Id)), + "user_name": user.Name, + "user_contractPosition_code": user.ContractPosition_Code, + } + + // extra + if err := populateRoles(user, atClaims, outputData, event); err != nil { + return nil, err + } + + // Generate jwt + at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) + ats, err := at.SignedString([]byte(atSecretKey)) + if err != nil { + return nil, d.FieldErrors{"user": d.FieldError{Code: "token-sign-err", Message: pl.GenMessage("token-sign-err")}} + } + outputData["accessToken"] = ats + + // Save to redis + now := time.Now() + atx := time.Unix(atExpires, 0) //converting Unix to UTC(to Time object) + err = ms.I.Set(aUuid, strconv.Itoa(int(user.Id)), atx.Sub(now)).Err() + if err != nil { + panic(fmt.Sprintf(l.I.Msg("redis-store-fail"), err.Error())) + } + + 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 +} diff --git a/internal/use-case/main-use-case/authentication/helper.go b/internal/use-case/main-use-case/authentication/helper.go index a919cfe9..0da9f943 100644 --- a/internal/use-case/main-use-case/authentication/helper.go +++ b/internal/use-case/main-use-case/authentication/helper.go @@ -3,13 +3,23 @@ package authentication import ( dg "github.com/karincake/apem/db-gorm-pg" + "github.com/golang-jwt/jwt" + d "github.com/karincake/dodol" + pl "simrs-vx/pkg/logger" edp "simrs-vx/internal/domain/main-entities/division-position" + ed "simrs-vx/internal/domain/main-entities/doctor" + ee "simrs-vx/internal/domain/main-entities/employee" eip "simrs-vx/internal/domain/main-entities/installation-position" + "simrs-vx/internal/domain/main-entities/intern" + em "simrs-vx/internal/domain/main-entities/midwife" + en "simrs-vx/internal/domain/main-entities/nurse" esp "simrs-vx/internal/domain/main-entities/specialist-position" essp "simrs-vx/internal/domain/main-entities/subspecialist-position" eup "simrs-vx/internal/domain/main-entities/unit-position" + eu "simrs-vx/internal/domain/main-entities/user" + erg "simrs-vx/internal/domain/references/organization" udp "simrs-vx/internal/use-case/main-use-case/division-position" uip "simrs-vx/internal/use-case/main-use-case/installation-position" @@ -162,3 +172,115 @@ func checkUntPtrClaims(claim map[string]interface{}, key string) *uint { } return nil } + +func populateRoles(user *eu.User, atClaims jwt.MapClaims, outputData d.II, event pl.Event) error { + roles := []string{} + switch user.ContractPosition_Code { + case erg.CSCEmp: + // employee + employee := ee.Employee{} + dg.I.Where("\"User_Id\" = ?", user.Id).First(&employee) + if employee.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noEmployee", Message: pl.GenMessage("auth-noEmployee")}} + } + atClaims["employee_id"] = employee.Id + outputData["employee_id"] = employee.Id + roles = append(roles, "emp-"+string(*employee.Position_Code)) + + //if employee.Division_Code != nil { + // atClaims["employee_division_code"] = employee.Division_Code + // outputData["employee_division_code"] = employee.Division_Code + //} + + // employee position + if employee.Id > 0 && employee.Position_Code != nil { + atClaims["employee_position_code"] = *employee.Position_Code + switch *employee.Position_Code { + case erg.EPCDoc: + doctor := ed.Doctor{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&doctor) + if doctor.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noDoctor", Message: pl.GenMessage("auth-noDoctor")}} + } + atClaims["doctor_code"] = doctor.Code + outputData["doctor_code"] = doctor.Code + + // specialist + if doctor.Specialist_Code != nil { + atClaims["specialist_code"] = doctor.Specialist_Code + outputData["specialist_code"] = doctor.Specialist_Code + } + if doctor.Subspecialist_Code != nil { + atClaims["subspecialist_code"] = doctor.Subspecialist_Code + outputData["subspecialist_code"] = doctor.Subspecialist_Code + } + case erg.EPCNur: + empData := en.Nurse{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noNurse", Message: pl.GenMessage("auth-noNurse")}} + } + atClaims["nurse_code"] = empData.Code + outputData["nurse_code"] = empData.Code + case erg.EPCMwi: + empData := em.Midwife{} + dg.I.Where("\"Employee_Id\" = ?", employee.Id).First(&empData) + if empData.Id == 0 { + return d.FieldErrors{"authentication": d.FieldError{Code: "auth-noMidwife", Message: pl.GenMessage("auth-noMidwife")}} + } + atClaims["midwife_code"] = empData.Code + outputData["midwife_code"] = empData.Code + } + + errorGetPosition := d.FieldErrors{"authentication": d.FieldError{Code: "auth-getData-failed", Message: pl.GenMessage("auth-getData-failed")}} + + // division position + divisionPositions, err := getDivisionPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // installation position + installationPositions, err := getInstallationPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // unit position + unitPositions, err := getUnitPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // specialist position + specialistPositions, err := getSpecialistPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + // subspecialist position + subspecialistPositions, err := getSubspecialistPosition(employee.Id, &event) + if err != nil { + return errorGetPosition + } + + roles = append(roles, divisionPositions...) + roles = append(roles, installationPositions...) + roles = append(roles, unitPositions...) + roles = append(roles, specialistPositions...) + roles = append(roles, subspecialistPositions...) + // atClaims["division_positions"] = divsionPositions + // outputData["division_positions"] = divsionPositions + } + case erg.CSCInt: + intern := intern.Intern{} + dg.I.Where("\"User_Id\" = ?", user.Id).First(&intern) + roles = append(roles, "int-"+string(*intern.Position_Code)) + case erg.CSCSys: + roles = append(roles, "system") + } + + atClaims["roles"] = roles + outputData["roles"] = roles + return nil +}