diff --git a/config/conf.go b/config/conf.go index d98d6b5..30f53cf 100644 --- a/config/conf.go +++ b/config/conf.go @@ -1,9 +1,5 @@ package config -import ( - "legalgo-BE-go/internal/utilities/utils" -) - var ( APP_PORT int GRACEFULL_TIMEOUT int @@ -13,16 +9,19 @@ var ( DB_USER, DB_PASSWORD, DB_NAME, - DB_PORT string + DB_PORT, + SALT_SECURITY string ) func InitEnv() { - DB_HOST = utils.GetOrDefault("DB_HOST", "localhost") - DB_USER = utils.GetOrDefault("DB_USER", "") - DB_PASSWORD = utils.GetOrDefault("DB_PASSWORD", "") - DB_NAME = utils.GetOrDefault("DB_NAME", "") - DB_PORT = utils.GetOrDefault("DB_PORT", "") + DB_HOST = GetOrDefault("DB_HOST", "localhost") + DB_USER = GetOrDefault("DB_USER", "") + DB_PASSWORD = GetOrDefault("DB_PASSWORD", "") + DB_NAME = GetOrDefault("DB_NAME", "") + DB_PORT = GetOrDefault("DB_PORT", "") - APP_PORT = utils.GetOrDefault("APP_PORT", 3000) - GRACEFULL_TIMEOUT = utils.GetOrDefault("GRACEFULL_TIMEOUT", 10) + SALT_SECURITY = GetOrDefault("SALT_SECURITY", "legalgo") + + APP_PORT = GetOrDefault("APP_PORT", 3000) + GRACEFULL_TIMEOUT = GetOrDefault("GRACEFULL_TIMEOUT", 10) } diff --git a/internal/utilities/utils/get_or_default.go b/config/get_or_default.go similarity index 97% rename from internal/utilities/utils/get_or_default.go rename to config/get_or_default.go index 4e0ab06..d2d7445 100644 --- a/internal/utilities/utils/get_or_default.go +++ b/config/get_or_default.go @@ -1,4 +1,4 @@ -package utils +package config import ( "os" diff --git a/internal/accessor/user_repository/get_user_profile.go b/internal/accessor/user_repository/get_user_profile.go new file mode 100644 index 0000000..028a025 --- /dev/null +++ b/internal/accessor/user_repository/get_user_profile.go @@ -0,0 +1,29 @@ +package userrepository + +import ( + "errors" + authdomain "legalgo-BE-go/internal/domain/auth" + + "gorm.io/gorm" +) + +func (ur *UserRepository) GetUserProfile(email string) (*authdomain.UserProfile, error) { + var users []authdomain.UserProfile + + if email == "" { + return nil, errors.New("email is empty") + } + + if err := ur.DB.Table("users u"). + Select("u.email, u.id, s.status as subscribe_status, sp.code as subscribe_plan_code, sp.name as subscribe_plan_name"). + Joins("join subscribes s on s.id = u.subscribe_id"). + Joins("join subscribe_plans sp on s.subscribe_plan_id = sp.id"). + Scan(&users).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errors.New("user not found") + } + return nil, err + } + + return &users[0], nil +} diff --git a/internal/accessor/user_repository/impl.go b/internal/accessor/user_repository/impl.go index 3d47b1a..c4239bb 100644 --- a/internal/accessor/user_repository/impl.go +++ b/internal/accessor/user_repository/impl.go @@ -12,6 +12,7 @@ type UserRepository struct { type UserIntf interface { GetUserByEmail(string) (*authdomain.User, error) GetUserByID(string) (*authdomain.UserProfile, error) + GetUserProfile(string) (*authdomain.UserProfile, error) CreateUser(*authdomain.User) (*authdomain.User, error) } diff --git a/internal/api/http/auth/profile.go b/internal/api/http/auth/profile.go index 92e2859..663d2a1 100644 --- a/internal/api/http/auth/profile.go +++ b/internal/api/http/auth/profile.go @@ -4,7 +4,9 @@ import ( "errors" authsvc "legalgo-BE-go/internal/services/auth" "legalgo-BE-go/internal/utilities/response" + "legalgo-BE-go/internal/utilities/utils" "net/http" + "strings" "github.com/go-chi/chi/v5" ) @@ -13,14 +15,15 @@ func GetStaffProfile( router chi.Router, authSvc authsvc.AuthIntf, ) { - router.Get("/staff/{id}/profile", func(w http.ResponseWriter, r *http.Request) { + router.Get("/staff/profile", func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - id := chi.URLParam(r, "id") - if id == "" { + authHeader := r.Header.Get("Authorization") + + if authHeader == "" { response.ResponseWithErrorCode( ctx, w, - errors.New("provided id is empty"), + errors.New("provided auth is empty"), response.ErrBadRequest.Code, response.ErrBadRequest.HttpCode, "required params is not provided", @@ -28,7 +31,45 @@ func GetStaffProfile( return } - staffProfile, err := authSvc.GetStaffProfile(id) + if !strings.HasPrefix(authHeader, "Bearer") { + response.ResponseWithErrorCode( + ctx, + w, + errors.New("invalid authorization token"), + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + "invalid required token", + ) + return + } + + token := strings.Split(authHeader, " ") + if len(token) < 2 { + response.ResponseWithErrorCode( + ctx, + w, + errors.New("invalid authorization"), + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + "invalid required token", + ) + return + } + + destructedToken, err := utils.DestructToken(token[1]) + if err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + staffProfile, err := authSvc.GetStaffProfile(destructedToken.Email) if err != nil { response.ResponseWithErrorCode( ctx, @@ -49,14 +90,15 @@ func GetUserProfile( router chi.Router, authSvc authsvc.AuthIntf, ) { - router.Get("/user/{id}/profile", func(w http.ResponseWriter, r *http.Request) { + router.Get("/user/profile", func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - id := chi.URLParam(r, "id") - if id == "" { + authHeader := r.Header.Get("Authorization") + + if authHeader == "" { response.ResponseWithErrorCode( ctx, w, - errors.New("provided id is empty"), + errors.New("provided auth is empty"), response.ErrBadRequest.Code, response.ErrBadRequest.HttpCode, "required params is not provided", @@ -64,7 +106,45 @@ func GetUserProfile( return } - userProfile, err := authSvc.GetUserProfile(id) + if !strings.HasPrefix(authHeader, "Bearer") { + response.ResponseWithErrorCode( + ctx, + w, + errors.New("invalid authorization token"), + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + "invalid required token", + ) + return + } + + token := strings.Split(authHeader, " ") + if len(token) < 2 { + response.ResponseWithErrorCode( + ctx, + w, + errors.New("invalid authorization"), + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + "invalid required token", + ) + return + } + + destructedToken, err := utils.DestructToken(token[1]) + if err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + userProfile, err := authSvc.GetUserProfile(destructedToken.Email) if err != nil { response.ResponseWithErrorCode( ctx, diff --git a/internal/domain/auth/token.go b/internal/domain/auth/token.go new file mode 100644 index 0000000..55bab03 --- /dev/null +++ b/internal/domain/auth/token.go @@ -0,0 +1,6 @@ +package authdomain + +type AuthToken struct { + Email string + SessionID string +} diff --git a/internal/enums/jwt/jwt_claims.go b/internal/enums/jwt/jwt_claims.go index c6a5ab5..50bb4f1 100644 --- a/internal/enums/jwt/jwt_claims.go +++ b/internal/enums/jwt/jwt_claims.go @@ -4,7 +4,7 @@ type JWTClaim string const ( TYPE JWTClaim = "type" - USERNAME JWTClaim = "username" + EMAIL JWTClaim = "email" EXPIRED_AT JWTClaim = "exp" SESSION_ID JWTClaim = "sid" ISSUED_AT JWTClaim = "iat" diff --git a/internal/services/auth/get_staff.go b/internal/services/auth/get_staff.go index 8f891a1..5d0d58a 100644 --- a/internal/services/auth/get_staff.go +++ b/internal/services/auth/get_staff.go @@ -2,8 +2,8 @@ package authsvc import authdomain "legalgo-BE-go/internal/domain/auth" -func (as *AuthSvc) GetStaffProfile(id string) (*authdomain.StaffProfile, error) { - staff, err := as.staffRepo.GetStaffByID(id) +func (as *AuthSvc) GetStaffProfile(email string) (*authdomain.StaffProfile, error) { + staff, err := as.staffRepo.GetStaffByEmail(email) if err != nil { return nil, err } diff --git a/internal/services/auth/get_user.go b/internal/services/auth/get_user.go index 793e4df..9b407c4 100644 --- a/internal/services/auth/get_user.go +++ b/internal/services/auth/get_user.go @@ -2,8 +2,8 @@ package authsvc import authdomain "legalgo-BE-go/internal/domain/auth" -func (as *AuthSvc) GetUserProfile(id string) (*authdomain.UserProfile, error) { - user, err := as.userRepo.GetUserByID(id) +func (as *AuthSvc) GetUserProfile(email string) (*authdomain.UserProfile, error) { + user, err := as.userRepo.GetUserProfile(email) if err != nil { return nil, err } diff --git a/internal/services/auth/login_as_staff.go b/internal/services/auth/login_as_staff.go index 6ba97fa..4f2cc8a 100644 --- a/internal/services/auth/login_as_staff.go +++ b/internal/services/auth/login_as_staff.go @@ -5,6 +5,8 @@ import ( authdomain "legalgo-BE-go/internal/domain/auth" "legalgo-BE-go/internal/utilities/utils" + + "github.com/google/uuid" ) func (sv *AuthSvc) LoginAsStaff(spec authdomain.LoginReq) (string, error) { @@ -18,7 +20,12 @@ func (sv *AuthSvc) LoginAsStaff(spec authdomain.LoginReq) (string, error) { return "", errors.New("wrong password") } - token, err := utils.GenerateToken(staff.Email) + authToken := authdomain.AuthToken{ + Email: staff.Email, + SessionID: uuid.NewString(), + } + + token, err := utils.GenerateToken(authToken) if err != nil { return "", errors.New(err.Error()) } diff --git a/internal/services/auth/login_as_user.go b/internal/services/auth/login_as_user.go index 0c23bbb..e99c9c4 100644 --- a/internal/services/auth/login_as_user.go +++ b/internal/services/auth/login_as_user.go @@ -5,6 +5,8 @@ import ( authdomain "legalgo-BE-go/internal/domain/auth" "legalgo-BE-go/internal/utilities/utils" + + "github.com/google/uuid" ) func (a *AuthSvc) LoginAsUser(spec authdomain.LoginReq) (string, error) { @@ -18,7 +20,12 @@ func (a *AuthSvc) LoginAsUser(spec authdomain.LoginReq) (string, error) { return "", errors.New("wrong password") } - token, err := utils.GenerateToken(user.Email) + authToken := authdomain.AuthToken{ + Email: user.Email, + SessionID: uuid.NewString(), + } + + token, err := utils.GenerateToken(authToken) if err != nil { return "", errors.New(err.Error()) } diff --git a/internal/services/auth/register_staff.go b/internal/services/auth/register_staff.go index e846a49..48380cc 100644 --- a/internal/services/auth/register_staff.go +++ b/internal/services/auth/register_staff.go @@ -19,19 +19,24 @@ func (a *AuthSvc) RegisterStaff(spec authdomain.RegisterStaffReq) (string, error return "", err } - user := authdomain.Staff{ + staff := authdomain.Staff{ ID: uuid.NewString(), Email: spec.Email, Password: hashedPwd, Username: spec.Username, } - _, err = a.staffRepo.Create(&user) + _, err = a.staffRepo.Create(&staff) if err != nil { return "", errors.New(err.Error()) } - token, err := utils.GenerateToken(spec.Email) + authToken := authdomain.AuthToken{ + Email: staff.Email, + SessionID: uuid.NewString(), + } + + token, err := utils.GenerateToken(authToken) if err != nil { return "", errors.New(err.Error()) } diff --git a/internal/services/auth/register_user.go b/internal/services/auth/register_user.go index f5f3372..1f5f883 100644 --- a/internal/services/auth/register_user.go +++ b/internal/services/auth/register_user.go @@ -48,7 +48,12 @@ func (a *AuthSvc) RegisterUser(spec authdomain.RegisterUserReq) (string, error) return "", errors.New(err.Error()) } - token, err := utils.GenerateToken(spec.Email) + authToken := authdomain.AuthToken{ + Email: user.Email, + SessionID: uuid.NewString(), + } + + token, err := utils.GenerateToken(authToken) if err != nil { return "", errors.New(err.Error()) } diff --git a/internal/utilities/utils/jwt.go b/internal/utilities/utils/jwt.go index 399834d..7851e38 100644 --- a/internal/utilities/utils/jwt.go +++ b/internal/utilities/utils/jwt.go @@ -1,15 +1,18 @@ package utils import ( + "errors" + "fmt" "time" + "legalgo-BE-go/config" + authdomain "legalgo-BE-go/internal/domain/auth" + jwtclaimenum "legalgo-BE-go/internal/enums/jwt" timeutils "legalgo-BE-go/internal/utilities/time_utils" "github.com/golang-jwt/jwt/v5" ) -var jwtSecret = []byte("secret jwt key") // TODO: change later from env - type ClaimOption func(options jwt.MapClaims) // func GenerateToken(options ...ClaimOption) (string, error) { @@ -28,12 +31,60 @@ type ClaimOption func(options jwt.MapClaims) // return token.SignedString(jwtSecret) // } -func GenerateToken(email string) (string, error) { +func GenerateToken(data authdomain.AuthToken) (string, error) { now := timeutils.Now() - token := jwt.New(jwt.SigningMethodHS256) - claims := token.Claims.(jwt.MapClaims) - claims["email"] = email - claims["exp"] = now.Add(time.Hour).Unix() - return token.SignedString(jwtSecret) + claims := jwt.MapClaims{ + "email": data.Email, + "session_id": data.SessionID, + "exp": now.Add(1 * time.Hour).Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + return token.SignedString([]byte(config.SALT_SECURITY)) +} + +func parseToken(s string) (*jwt.Token, error) { + return jwt.Parse(s, func(t *jwt.Token) (any, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + errMsg := fmt.Sprintf("unexpected signing method: %v", t.Header["alg"]) + return nil, errors.New(errMsg) + } + + return []byte(config.SALT_SECURITY), nil + }) +} + +func DestructToken(s string) (authdomain.AuthToken, error) { + var data authdomain.AuthToken + + token, err := parseToken(s) + if err != nil { + return data, err + } + + claims, ok := token.Claims.(jwt.MapClaims) + + if !ok { + return data, errors.New("failed to parse token") + } + + if !token.Valid { + return data, errors.New("invalid token") + } + + email, ok := claims[string(jwtclaimenum.EMAIL)].(string) + if !ok { + return data, errors.New("invalid email") + } + + sessionId, ok := claims[string(jwtclaimenum.SESSION_ID)].(string) + + data = authdomain.AuthToken{ + Email: email, + SessionID: sessionId, + } + + return data, nil } diff --git a/openapi.yml b/openapi.yml index eff9256..71c6c37 100644 --- a/openapi.yml +++ b/openapi.yml @@ -120,7 +120,7 @@ paths: message: type: string - /staff/{id}/profile: + /staff/profile: get: summary: "get staff profile" tags: @@ -164,7 +164,7 @@ paths: message: type: string - /user/{id}/profile: + /user/profile: get: summary: "get staff profile" tags: