diff --git a/cmd/main.go b/cmd/main.go index 7d80cb5..f3a4b92 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -51,4 +51,4 @@ func main() { router.SetupRoutes(app) config.StartServer(app) -} +} \ No newline at end of file diff --git a/config/database.go b/config/database.go index 92a4356..abcb5ae 100644 --- a/config/database.go +++ b/config/database.go @@ -31,4 +31,4 @@ func ConnectDatabase() { if err := RunMigrations(DB); err != nil { log.Fatalf("Error performing auto-migration: %v", err) } -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index 36bd859..552a803 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,11 @@ require ( gorm.io/gorm v1.25.12 ) +require ( + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // direct +) + // require ( // golang.org/x/term v0.30.0 // indirect // rsc.io/qr v0.2.0 // indirect diff --git a/go.sum b/go.sum index 58846b8..e6077af 100644 --- a/go.sum +++ b/go.sum @@ -109,7 +109,11 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IV golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/internal/authentication/authentication_dto.go b/internal/authentication/authentication_dto.go index b29aee8..801ff71 100644 --- a/internal/authentication/authentication_dto.go +++ b/internal/authentication/authentication_dto.go @@ -256,6 +256,59 @@ type LoginAdminRequest struct { DeviceID string `json:"device_id"` } +type VerifyAdminOTPRequest struct { + Email string `json:"email" validate:"required,email"` + OTP string `json:"otp" validate:"required,len=6,numeric"` + DeviceID string `json:"device_id" validate:"required"` +} + +type ResendAdminOTPRequest struct { + Email string `json:"email" validate:"required,email"` +} + +type OTPAdminResponse struct { + Message string `json:"message"` + Email string `json:"email"` + ExpiresIn time.Duration `json:"expires_in_seconds"` + RemainingTime string `json:"remaining_time"` + CanResend bool `json:"can_resend"` + MaxAttempts int `json:"max_attempts"` +} + +type ForgotPasswordRequest struct { + Email string `json:"email" validate:"required,email"` +} + +type ResetPasswordRequest struct { + Email string `json:"email" validate:"required,email"` + Token string `json:"token" validate:"required"` + NewPassword string `json:"new_password" validate:"required,min=6"` +} + +type ResetPasswordResponse struct { + Message string `json:"message"` + Email string `json:"email"` + ExpiresIn time.Duration `json:"expires_in_seconds"` + RemainingTime string `json:"remaining_time"` +} + +type VerifyEmailRequest struct { + Email string `json:"email" validate:"required,email"` + Token string `json:"token" validate:"required"` +} + +type ResendVerificationRequest struct { + Email string `json:"email" validate:"required,email"` +} + +type EmailVerificationResponse struct { + Message string `json:"message"` + Email string `json:"email"` + ExpiresIn time.Duration `json:"expires_in_seconds"` + RemainingTime string `json:"remaining_time"` +} + + func (r *LoginorRegistRequest) ValidateLoginorRegistRequest() (map[string][]string, bool) { errors := make(map[string][]string) diff --git a/internal/authentication/authentication_handler.go b/internal/authentication/authentication_handler.go index 60a6a86..613a4cc 100644 --- a/internal/authentication/authentication_handler.go +++ b/internal/authentication/authentication_handler.go @@ -106,31 +106,144 @@ func (h *AuthenticationHandler) Login(c *fiber.Ctx) error { } -func (h *AuthenticationHandler) Register(c *fiber.Ctx) error { - +func (h *AuthenticationHandler) RegisterAdmin(c *fiber.Ctx) error { var req RegisterAdminRequest if err := c.BodyParser(&req); err != nil { return utils.BadRequest(c, "Invalid request format") } - if errs, ok := req.ValidateRegisterAdminRequest(); !ok { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "meta": fiber.Map{ - "status": fiber.StatusBadRequest, - "message": "periksa lagi inputan", - }, - "errors": errs, - }) - } + // if err := h.validator.Struct(&req); err != nil { + // return utils.BadRequest(c, "Validation failed: "+err.Error()) + // } - err := h.service.RegisterAdmin(c.Context(), &req) + response, err := h.service.RegisterAdmin(c.Context(), &req) if err != nil { - return utils.InternalServerError(c, err.Error()) + return utils.BadRequest(c, err.Error()) } - return utils.Success(c, "Registration successful, Please login") + return utils.SuccessWithData(c, "Admin registered successfully", response) } +// POST /auth/admin/verify-email - Verify email dari registration +func (h *AuthenticationHandler) VerifyEmail(c *fiber.Ctx) error { + var req VerifyEmailRequest + if err := c.BodyParser(&req); err != nil { + return utils.BadRequest(c, "Invalid request format") + } + + // if err := h.validator.Struct(&req); err != nil { + // return utils.BadRequest(c, "Validation failed: "+err.Error()) + // } + + err := h.service.VerifyEmail(c.Context(), &req) + if err != nil { + return utils.BadRequest(c, err.Error()) + } + + return utils.SuccessWithData(c, "Email berhasil diverifikasi. Sekarang Anda dapat login", nil) +} + +// POST /auth/admin/resend-verification - Resend verification email +func (h *AuthenticationHandler) ResendEmailVerification(c *fiber.Ctx) error { + var req ResendVerificationRequest + if err := c.BodyParser(&req); err != nil { + return utils.BadRequest(c, "Invalid request format") + } + + // if err := h.validator.Struct(&req); err != nil { + // return utils.BadRequest(c, "Validation failed: "+err.Error()) + // } + + response, err := h.service.ResendEmailVerification(c.Context(), &req) + if err != nil { + return utils.BadRequest(c, err.Error()) + } + + return utils.SuccessWithData(c, "Verification email resent", response) +} + + +func (h *AuthenticationHandler) VerifyAdminOTP(c *fiber.Ctx) error { + var req VerifyAdminOTPRequest + if err := c.BodyParser(&req); err != nil { + return utils.BadRequest(c, "Invalid request format") + } + + // if errs, ok := req.Valida(); !ok { + // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + // "meta": fiber.Map{ + // "status": fiber.StatusBadRequest, + // "message": "periksa lagi inputan", + // }, + // "errors": errs, + // }) + // } + + response, err := h.service.VerifyAdminOTP(c.Context(), &req) + if err != nil { + return utils.BadRequest(c, err.Error()) + } + + return utils.SuccessWithData(c, "OTP resent successfully", response) +} + +// POST /auth/admin/resend-otp - Resend OTP +func (h *AuthenticationHandler) ResendAdminOTP(c *fiber.Ctx) error { + var req ResendAdminOTPRequest + if err := c.BodyParser(&req); err != nil { + return utils.BadRequest(c, "Invalid request format") + } + + // if err := h.validator.Struct(&req); err != nil { + // return utils.BadRequest(c, "Validation failed: "+err.Error()) + // } + + response, err := h.service.ResendAdminOTP(c.Context(), &req) + if err != nil { + return utils.BadRequest(c, err.Error()) + } + + return utils.SuccessWithData(c, "OTP resent successfully", response) +} + +func (h *AuthenticationHandler) ForgotPassword(c *fiber.Ctx) error { + var req ForgotPasswordRequest + if err := c.BodyParser(&req); err != nil { + return utils.BadRequest(c, "Invalid request format") + } + + // if err := h.validator.Struct(&req); err != nil { + // return utils.BadRequest(c, "Validation failed: "+err.Error()) + // } + + response, err := h.service.ForgotPassword(c.Context(), &req) + if err != nil { + return utils.BadRequest(c, err.Error()) + } + + return utils.SuccessWithData(c, "Reset password email sent", response) +} + +// POST /auth/admin/reset-password - Step 2: Reset password dengan token +func (h *AuthenticationHandler) ResetPassword(c *fiber.Ctx) error { + var req ResetPasswordRequest + if err := c.BodyParser(&req); err != nil { + return utils.BadRequest(c, "Invalid request format") + } + + // if err := h.validator.Struct(&req); err != nil { + // return utils.BadRequest(c, "Validation failed: "+err.Error()) + // } + + err := h.service.ResetPassword(c.Context(), &req) + if err != nil { + return utils.BadRequest(c, err.Error()) + } + + return utils.SuccessWithData(c, "Password berhasil direset", nil) +} + + func (h *AuthenticationHandler) RequestOtpHandler(c *fiber.Ctx) error { var req LoginorRegistRequest if err := c.BodyParser(&req); err != nil { diff --git a/internal/authentication/authentication_route.go b/internal/authentication/authentication_route.go index 149a019..bb5058f 100644 --- a/internal/authentication/authentication_route.go +++ b/internal/authentication/authentication_route.go @@ -32,7 +32,17 @@ func AuthenticationRouter(api fiber.Router) { authRoute.Get("/cekapproval", middleware.AuthMiddleware(), authHandler.GetRegistrationStatus) authRoute.Post("/login/admin", authHandler.Login) - authRoute.Post("/register/admin", authHandler.Register) + authRoute.Post("/register/admin", authHandler.RegisterAdmin) + + authRoute.Post("/verify-email", authHandler.VerifyEmail) + authRoute.Post("/resend-verification", authHandler.ResendEmailVerification) + + authRoute.Post("/verify-otp-admin", authHandler.VerifyAdminOTP) + authRoute.Post("/resend-otp-admin", authHandler.ResendAdminOTP) + + authRoute.Post("/forgot-password", authHandler.ForgotPassword) + authRoute.Post("/reset-password", authHandler.ResetPassword) + authRoute.Post("/request-otp", authHandler.RequestOtpHandler) authRoute.Post("/verif-otp", authHandler.VerifyOtpHandler) authRoute.Post("/request-otp/register", authHandler.RequestOtpRegistHandler) diff --git a/internal/authentication/authentication_service.go b/internal/authentication/authentication_service.go index d984642..980c5c8 100644 --- a/internal/authentication/authentication_service.go +++ b/internal/authentication/authentication_service.go @@ -14,8 +14,17 @@ import ( type AuthenticationService interface { GetRegistrationStatus(ctx context.Context, userID, deviceID string) (*AuthResponse, error) - LoginAdmin(ctx context.Context, req *LoginAdminRequest) (*AuthResponse, error) - RegisterAdmin(ctx context.Context, req *RegisterAdminRequest) error + LoginAdmin(ctx context.Context, req *LoginAdminRequest) (*OTPAdminResponse, error) + RegisterAdmin(ctx context.Context, req *RegisterAdminRequest) (*EmailVerificationResponse, error) + + VerifyAdminOTP(ctx context.Context, req *VerifyAdminOTPRequest) (*AuthResponse, error) + ResendAdminOTP(ctx context.Context, req *ResendAdminOTPRequest) (*OTPAdminResponse, error) + + ForgotPassword(ctx context.Context, req *ForgotPasswordRequest) (*ResetPasswordResponse, error) + ResetPassword(ctx context.Context, req *ResetPasswordRequest) error + + VerifyEmail(ctx context.Context, req *VerifyEmailRequest) error + ResendEmailVerification(ctx context.Context, req *ResendVerificationRequest) (*EmailVerificationResponse, error) SendRegistrationOTP(ctx context.Context, req *LoginorRegistRequest) (*OTPResponse, error) VerifyRegistrationOTP(ctx context.Context, req *VerifyOtpRequest) (*AuthResponse, error) @@ -27,29 +36,34 @@ type AuthenticationService interface { } type authenticationService struct { - authRepo AuthenticationRepository - roleRepo role.RoleRepository + authRepo AuthenticationRepository + roleRepo role.RoleRepository + emailService *utils.EmailService } func NewAuthenticationService(authRepo AuthenticationRepository, roleRepo role.RoleRepository) AuthenticationService { - return &authenticationService{authRepo, roleRepo} -} - -func normalizeRoleName(roleName string) string { - switch strings.ToLower(roleName) { - case "administrator", "admin": - return utils.RoleAdministrator - case "pengelola": - return utils.RolePengelola - case "pengepul": - return utils.RolePengepul - case "masyarakat": - return utils.RoleMasyarakat - default: - return strings.ToLower(roleName) + return &authenticationService{ + authRepo: authRepo, + roleRepo: roleRepo, + emailService: utils.NewEmailService(), } } +// func normalizeRoleName(roleName string) string { +// switch strings.ToLower(roleName) { +// case "administrator", "admin": +// return utils.RoleAdministrator +// case "pengelola": +// return utils.RolePengelola +// case "pengepul": +// return utils.RolePengepul +// case "masyarakat": +// return utils.RoleMasyarakat +// default: +// return strings.ToLower(roleName) +// } +// } + type GetRegistrationStatusResponse struct { UserID string `json:"userId"` RegistrationStatus string `json:"registrationStatus"` @@ -122,31 +136,92 @@ func (s *authenticationService) GetRegistrationStatus(ctx context.Context, userI return nil, fmt.Errorf("unsupported registration status: %s", user.RegistrationStatus) } -func (s *authenticationService) LoginAdmin(ctx context.Context, req *LoginAdminRequest) (*AuthResponse, error) { +func (s *authenticationService) LoginAdmin(ctx context.Context, req *LoginAdminRequest) (*OTPAdminResponse, error) { + user, err := s.authRepo.FindUserByEmail(ctx, req.Email) if err != nil { - return nil, fmt.Errorf("user not found: %w", err) + return nil, fmt.Errorf("invalid credentials") } if user.Role == nil || user.Role.RoleName != "administrator" { - return nil, fmt.Errorf("user not found: %w", err) + return nil, fmt.Errorf("invalid credentials") } if user.RegistrationStatus != "completed" { - return nil, fmt.Errorf("user not found: %w", err) + return nil, fmt.Errorf("account not activated") } if !utils.CompareHashAndPlainText(user.Password, req.Password) { - return nil, fmt.Errorf("user not found: %w", err) + return nil, fmt.Errorf("invalid credentials") } - token, err := utils.GenerateTokenPair(user.ID, user.Role.RoleName, req.DeviceID, user.RegistrationStatus, int(user.RegistrationProgress)) + if utils.IsOTPValid(req.Email) { + remaining, _ := utils.GetOTPRemainingTime(req.Email) + return &OTPAdminResponse{ + Message: "OTP sudah dikirim sebelumnya", + Email: req.Email, + ExpiresIn: remaining, + RemainingTime: formatDuration(remaining), + CanResend: false, + MaxAttempts: utils.MAX_OTP_ATTEMPTS, + }, nil + } + + otp, err := utils.GenerateOTP() + if err != nil { + return nil, fmt.Errorf("failed to generate OTP") + } + + if err := utils.StoreOTP(req.Email, otp); err != nil { + return nil, fmt.Errorf("failed to store OTP") + } + + if err := s.emailService.SendOTPEmail(req.Email, user.Name, otp); err != nil { + log.Printf("Failed to send OTP email: %v", err) + return nil, fmt.Errorf("failed to send OTP email") + } + + return &OTPAdminResponse{ + Message: "Kode OTP berhasil dikirim ke email Anda", + Email: req.Email, + ExpiresIn: utils.OTP_EXPIRY, + RemainingTime: formatDuration(utils.OTP_EXPIRY), + CanResend: false, + MaxAttempts: utils.MAX_OTP_ATTEMPTS, + }, nil +} + +func (s *authenticationService) VerifyAdminOTP(ctx context.Context, req *VerifyAdminOTPRequest) (*AuthResponse, error) { + + if err := utils.ValidateOTP(req.Email, req.OTP); err != nil { + return nil, err + } + + user, err := s.authRepo.FindUserByEmail(ctx, req.Email) + if err != nil { + return nil, fmt.Errorf("user not found") + } + + if !user.EmailVerified { + user.EmailVerified = true + if err := s.authRepo.UpdateUser(ctx, user); err != nil { + log.Printf("Failed to update email verification status: %v", err) + } + } + + token, err := utils.GenerateTokenPair( + user.ID, + user.Role.RoleName, + req.DeviceID, + user.RegistrationStatus, + int(user.RegistrationProgress), + ) if err != nil { return nil, fmt.Errorf("failed to generate token: %w", err) } return &AuthResponse{ - Message: "login berhasil", + Message: "Login berhasil", AccessToken: token.AccessToken, RefreshToken: token.RefreshToken, RegistrationStatus: user.RegistrationStatus, @@ -154,42 +229,283 @@ func (s *authenticationService) LoginAdmin(ctx context.Context, req *LoginAdminR }, nil } -func (s *authenticationService) RegisterAdmin(ctx context.Context, req *RegisterAdminRequest) error { +func (s *authenticationService) ResendAdminOTP(ctx context.Context, req *ResendAdminOTPRequest) (*OTPAdminResponse, error) { - existingUser, _ := s.authRepo.FindUserByEmail(ctx, req.Email) - if existingUser != nil { - return fmt.Errorf("email already in use") + user, err := s.authRepo.FindUserByEmail(ctx, req.Email) + if err != nil { + return nil, fmt.Errorf("email not found") } - hashedPassword, err := utils.HashingPlainText(req.Password) + if user.Role == nil || user.Role.RoleName != "administrator" { + return nil, fmt.Errorf("not authorized") + } + + if utils.IsOTPValid(req.Email) { + remaining, _ := utils.GetOTPRemainingTime(req.Email) + return nil, fmt.Errorf("OTP masih berlaku. Tunggu %s untuk kirim ulang", formatDuration(remaining)) + } + + otp, err := utils.GenerateOTP() + if err != nil { + return nil, fmt.Errorf("failed to generate OTP") + } + + if err := utils.StoreOTP(req.Email, otp); err != nil { + return nil, fmt.Errorf("failed to store OTP") + } + + if err := s.emailService.SendOTPEmail(req.Email, user.Name, otp); err != nil { + log.Printf("Failed to send OTP email: %v", err) + return nil, fmt.Errorf("failed to send OTP email") + } + + return &OTPAdminResponse{ + Message: "Kode OTP baru berhasil dikirim", + Email: req.Email, + ExpiresIn: utils.OTP_EXPIRY, + RemainingTime: formatDuration(utils.OTP_EXPIRY), + CanResend: false, + MaxAttempts: utils.MAX_OTP_ATTEMPTS, + }, nil +} + +func (s *authenticationService) VerifyEmail(ctx context.Context, req *VerifyEmailRequest) error { + + verificationData, err := utils.ValidateEmailVerificationToken(req.Email, req.Token) + if err != nil { + return err + } + + user, err := s.authRepo.FindUserByEmail(ctx, req.Email) + if err != nil { + return fmt.Errorf("user not found") + } + + if user.ID != verificationData.UserID { + return fmt.Errorf("invalid verification token") + } + + if user.EmailVerified { + return fmt.Errorf("email sudah terverifikasi sebelumnya") + } + + user.EmailVerified = true + user.RegistrationStatus = utils.RegStatusComplete + // user.RegistrationProgress = 3 + + if err := s.authRepo.UpdateUser(ctx, user); err != nil { + return fmt.Errorf("failed to update user verification status: %w", err) + } + + if err := utils.MarkEmailVerificationTokenAsUsed(req.Email); err != nil { + log.Printf("Failed to mark verification token as used: %v", err) + } + + return nil +} + +func (s *authenticationService) ResendEmailVerification(ctx context.Context, req *ResendVerificationRequest) (*EmailVerificationResponse, error) { + + user, err := s.authRepo.FindUserByEmail(ctx, req.Email) + if err != nil { + return nil, fmt.Errorf("email not found") + } + + if user.Role == nil || user.Role.RoleName != "administrator" { + return nil, fmt.Errorf("not authorized") + } + + if user.EmailVerified { + return nil, fmt.Errorf("email sudah terverifikasi") + } + + if utils.IsEmailVerificationTokenValid(req.Email) { + remaining, _ := utils.GetEmailVerificationTokenRemainingTime(req.Email) + return &EmailVerificationResponse{ + Message: "Email verifikasi sudah dikirim sebelumnya", + Email: req.Email, + ExpiresIn: remaining, + RemainingTime: formatDuration(remaining), + }, nil + } + + token, err := utils.GenerateEmailVerificationToken() + if err != nil { + return nil, fmt.Errorf("failed to generate verification token") + } + + if err := utils.StoreEmailVerificationToken(req.Email, user.ID, token); err != nil { + return nil, fmt.Errorf("failed to store verification token") + } + + if err := s.emailService.SendEmailVerificationEmail(req.Email, user.Name, token); err != nil { + log.Printf("Failed to send verification email: %v", err) + return nil, fmt.Errorf("failed to send verification email") + } + + return &EmailVerificationResponse{ + Message: "Email verifikasi berhasil dikirim ulang", + Email: req.Email, + ExpiresIn: utils.EMAIL_VERIFICATION_TOKEN_EXPIRY, + RemainingTime: formatDuration(utils.EMAIL_VERIFICATION_TOKEN_EXPIRY), + }, nil +} + +func (s *authenticationService) ForgotPassword(ctx context.Context, req *ForgotPasswordRequest) (*ResetPasswordResponse, error) { + + user, err := s.authRepo.FindUserByEmail(ctx, req.Email) + if err != nil { + + return &ResetPasswordResponse{ + Message: "Jika email terdaftar, link reset password akan dikirim", + Email: req.Email, + ExpiresIn: utils.RESET_TOKEN_EXPIRY, + RemainingTime: formatDuration(utils.RESET_TOKEN_EXPIRY), + }, nil + } + + if user.Role == nil || user.Role.RoleName != "administrator" { + + return &ResetPasswordResponse{ + Message: "Jika email terdaftar, link reset password akan dikirim", + Email: req.Email, + ExpiresIn: utils.RESET_TOKEN_EXPIRY, + RemainingTime: formatDuration(utils.RESET_TOKEN_EXPIRY), + }, nil + } + + if utils.IsResetTokenValid(req.Email) { + remaining, _ := utils.GetResetTokenRemainingTime(req.Email) + return &ResetPasswordResponse{ + Message: "Link reset password sudah dikirim sebelumnya", + Email: req.Email, + ExpiresIn: remaining, + RemainingTime: formatDuration(remaining), + }, nil + } + + token, err := utils.GenerateResetToken() + if err != nil { + return nil, fmt.Errorf("failed to generate reset token") + } + + if err := utils.StoreResetToken(req.Email, user.ID, token); err != nil { + return nil, fmt.Errorf("failed to store reset token") + } + + if err := s.emailService.SendResetPasswordEmail(req.Email, user.Name, token); err != nil { + log.Printf("Failed to send reset password email: %v", err) + return nil, fmt.Errorf("failed to send reset password email") + } + + return &ResetPasswordResponse{ + Message: "Link reset password berhasil dikirim ke email Anda", + Email: req.Email, + ExpiresIn: utils.RESET_TOKEN_EXPIRY, + RemainingTime: formatDuration(utils.RESET_TOKEN_EXPIRY), + }, nil +} + +func (s *authenticationService) ResetPassword(ctx context.Context, req *ResetPasswordRequest) error { + + resetData, err := utils.ValidateResetToken(req.Email, req.Token) + if err != nil { + return err + } + + user, err := s.authRepo.FindUserByEmail(ctx, req.Email) + if err != nil { + return fmt.Errorf("user not found") + } + + if user.ID != resetData.UserID { + return fmt.Errorf("invalid reset token") + } + + hashedPassword, err := utils.HashingPlainText(req.NewPassword) if err != nil { return fmt.Errorf("failed to hash password: %w", err) } - role, err := s.roleRepo.FindRoleByName(ctx, "administrator") - if err != nil { - return fmt.Errorf("role name not found: %w", err) + user.Password = hashedPassword + if err := s.authRepo.UpdateUser(ctx, user); err != nil { + return fmt.Errorf("failed to update password: %w", err) } - user := &model.User{ - Name: req.Name, - Phone: req.Phone, - Email: req.Email, - Gender: req.Gender, - Dateofbirth: req.DateOfBirth, - Placeofbirth: req.PlaceOfBirth, - Password: hashedPassword, - RoleID: role.ID, - RegistrationStatus: "completed", + if err := utils.MarkResetTokenAsUsed(req.Email); err != nil { + log.Printf("Failed to mark reset token as used: %v", err) } - if err := s.authRepo.CreateUser(ctx, user); err != nil { - return fmt.Errorf("failed to create user: %w", err) + if err := utils.RevokeAllRefreshTokens(user.ID); err != nil { + log.Printf("Failed to revoke refresh tokens: %v", err) } return nil } +func (s *authenticationService) RegisterAdmin(ctx context.Context, req *RegisterAdminRequest) (*EmailVerificationResponse, error) { + + existingUser, _ := s.authRepo.FindUserByEmail(ctx, req.Email) + if existingUser != nil { + return nil, fmt.Errorf("email already in use") + } + + hashedPassword, err := utils.HashingPlainText(req.Password) + if err != nil { + return nil, fmt.Errorf("failed to hash password: %w", err) + } + + role, err := s.roleRepo.FindRoleByName(ctx, "administrator") + if err != nil { + return nil, fmt.Errorf("role name not found: %w", err) + } + + user := &model.User{ + Name: req.Name, + Phone: req.Phone, + Email: req.Email, + Gender: req.Gender, + Dateofbirth: req.DateOfBirth, + Placeofbirth: req.PlaceOfBirth, + Password: hashedPassword, + RoleID: role.ID, + RegistrationStatus: "pending_email_verification", + RegistrationProgress: 1, + EmailVerified: false, + } + + if err := s.authRepo.CreateUser(ctx, user); err != nil { + return nil, fmt.Errorf("failed to create user: %w", err) + } + + token, err := utils.GenerateEmailVerificationToken() + if err != nil { + return nil, fmt.Errorf("failed to generate verification token") + } + + if err := utils.StoreEmailVerificationToken(req.Email, user.ID, token); err != nil { + return nil, fmt.Errorf("failed to store verification token") + } + + if err := s.emailService.SendEmailVerificationEmail(req.Email, user.Name, token); err != nil { + log.Printf("Failed to send verification email: %v", err) + return nil, fmt.Errorf("failed to send verification email") + } + + return &EmailVerificationResponse{ + Message: "Admin berhasil didaftarkan. Silakan cek email untuk verifikasi", + Email: req.Email, + ExpiresIn: utils.EMAIL_VERIFICATION_TOKEN_EXPIRY, + RemainingTime: formatDuration(utils.EMAIL_VERIFICATION_TOKEN_EXPIRY), + }, nil +} + +func formatDuration(d time.Duration) string { + minutes := int(d.Minutes()) + seconds := int(d.Seconds()) % 60 + return fmt.Sprintf("%d:%02d", minutes, seconds) +} + func (s *authenticationService) SendRegistrationOTP(ctx context.Context, req *LoginorRegistRequest) (*OTPResponse, error) { normalizedRole := strings.ToLower(req.RoleName) @@ -418,7 +734,7 @@ func (s *authenticationService) VerifyLoginOTP(ctx context.Context, req *VerifyO var message string if user.RegistrationStatus == utils.RegStatusComplete { message = "verif pin" - nextStep = "verif_pin" + nextStep = "verif_pin" } else { message = "otp berhasil diverifikasi" } @@ -471,19 +787,19 @@ func sendOTP(phone, otp string) error { return nil } -func convertUserToResponse(user *model.User) *UserResponse { - return &UserResponse{ - ID: user.ID, - Name: user.Name, - Phone: user.Phone, - Email: user.Email, - Role: user.Role.RoleName, - RegistrationStatus: user.RegistrationStatus, - RegistrationProgress: user.RegistrationProgress, - PhoneVerified: user.PhoneVerified, - Avatar: user.Avatar, - } -} +// func convertUserToResponse(user *model.User) *UserResponse { +// return &UserResponse{ +// ID: user.ID, +// Name: user.Name, +// Phone: user.Phone, +// Email: user.Email, +// Role: user.Role.RoleName, +// RegistrationStatus: user.RegistrationStatus, +// RegistrationProgress: user.RegistrationProgress, +// PhoneVerified: user.PhoneVerified, +// Avatar: user.Avatar, +// } +// } func IsRegistrationComplete(role string, progress int) bool { switch role { diff --git a/middleware/api_key.go b/middleware/api_key.go deleted file mode 100644 index 1077eb0..0000000 --- a/middleware/api_key.go +++ /dev/null @@ -1,23 +0,0 @@ -package middleware - -import ( - "os" - - "rijig/utils" - - "github.com/gofiber/fiber/v2" -) - -func APIKeyMiddleware(c *fiber.Ctx) error { - apiKey := c.Get("x-api-key") - if apiKey == "" { - return utils.Unauthorized(c, "Unauthorized: API key is required") - } - - validAPIKey := os.Getenv("API_KEY") - if apiKey != validAPIKey { - return utils.Unauthorized(c, "Unauthorized: Invalid API key") - } - - return c.Next() -} diff --git a/middleware/middleware.go b/middleware/middleware.go index 9f736c0..6da7a10 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -3,6 +3,7 @@ package middleware import ( "crypto/subtle" "fmt" + "os" "rijig/utils" "time" @@ -122,6 +123,20 @@ func getStatusCodeForError(errorCode string) int { } } +func APIKeyMiddleware(c *fiber.Ctx) error { + apiKey := c.Get("x-api-key") + if apiKey == "" { + return utils.Unauthorized(c, "Unauthorized: API key is required") + } + + validAPIKey := os.Getenv("API_KEY") + if apiKey != validAPIKey { + return utils.Unauthorized(c, "Unauthorized: Invalid API key") + } + + return c.Next() +} + func AuthMiddleware(config ...AuthConfig) fiber.Handler { cfg := AuthConfig{} if len(config) > 0 { diff --git a/model/user_model.go b/model/user_model.go index faefec9..323c6ac 100644 --- a/model/user_model.go +++ b/model/user_model.go @@ -11,6 +11,7 @@ type User struct { Placeofbirth string `gorm:"not null" json:"placeofbirth"` Phone string `gorm:"not null;index" json:"phone"` Email string `json:"email,omitempty"` + EmailVerified bool `gorm:"default:false" json:"emailVerified"` PhoneVerified bool `gorm:"default:false" json:"phoneVerified"` Password string `json:"password,omitempty"` RoleID string `gorm:"not null" json:"roleId"` diff --git a/utils/email_utils.go b/utils/email_utils.go new file mode 100644 index 0000000..53f0926 --- /dev/null +++ b/utils/email_utils.go @@ -0,0 +1,179 @@ +package utils + +import ( + "fmt" + "os" + "strconv" + "time" + + "gopkg.in/gomail.v2" +) + +type EmailService struct { + host string + port int + username string + password string + from string + fromName string +} + +type OTPData struct { + Code string `json:"code"` + Email string `json:"email"` + ExpiresAt int64 `json:"expires_at"` + Attempts int `json:"attempts"` + CreatedAt int64 `json:"created_at"` +} + +const ( + OTP_LENGTH = 6 + OTP_EXPIRY = 5 * time.Minute + MAX_OTP_ATTEMPTS = 3 +) + +func NewEmailService() *EmailService { + port, _ := strconv.Atoi("587") + + return &EmailService{ + host: "smtp.gmail.com", + port: port, + username: os.Getenv("SMTP_FROM_EMAIL"), + password: os.Getenv("GMAIL_APP_PASSWORD"), + from: os.Getenv("SMTP_FROM_EMAIL"), + fromName: os.Getenv("SMTP_FROM_NAME"), + } +} + +func StoreOTP(email, otp string) error { + key := fmt.Sprintf("otp:admin:%s", email) + + data := OTPData{ + Code: otp, + Email: email, + ExpiresAt: time.Now().Add(OTP_EXPIRY).Unix(), + Attempts: 0, + CreatedAt: time.Now().Unix(), + } + + return SetCache(key, data, OTP_EXPIRY) +} + +func ValidateOTP(email, inputOTP string) error { + key := fmt.Sprintf("otp:admin:%s", email) + + var data OTPData + err := GetCache(key, &data) + if err != nil { + return fmt.Errorf("OTP tidak ditemukan atau sudah kadaluarsa") + } + + if time.Now().Unix() > data.ExpiresAt { + DeleteCache(key) + return fmt.Errorf("OTP sudah kadaluarsa") + } + + if data.Attempts >= MAX_OTP_ATTEMPTS { + DeleteCache(key) + return fmt.Errorf("OTP diblokir karena terlalu banyak percobaan salah") + } + + if data.Code != inputOTP { + + data.Attempts++ + SetCache(key, data, time.Until(time.Unix(data.ExpiresAt, 0))) + return fmt.Errorf("OTP tidak valid. Sisa percobaan: %d", MAX_OTP_ATTEMPTS-data.Attempts) + } + + DeleteCache(key) + return nil +} + +func (e *EmailService) SendOTPEmail(email, name, otp string) error { + m := gomail.NewMessage() + m.SetHeader("From", m.FormatAddress(e.from, e.fromName)) + m.SetHeader("To", email) + m.SetHeader("Subject", "Kode Verifikasi Login Administrator - Rijig") + + body := fmt.Sprintf(` + + + + + + + +
+
+

🔐 Kode Verifikasi Login

+
+
+

Halo %s,

+

Anda telah meminta untuk login sebagai Administrator. Gunakan kode verifikasi berikut:

+ +
%s
+ +

Penting:

+ + +

⚠️ Jika Anda tidak melakukan permintaan login ini, abaikan email ini.

+
+ +
+ + + `, name, otp) + + m.SetBody("text/html", body) + + d := gomail.NewDialer(e.host, e.port, e.username, e.password) + + if err := d.DialAndSend(m); err != nil { + return fmt.Errorf("failed to send email: %v", err) + } + + return nil +} + +func IsOTPValid(email string) bool { + key := fmt.Sprintf("otp:admin:%s", email) + + var data OTPData + err := GetCache(key, &data) + if err != nil { + return false + } + + return time.Now().Unix() <= data.ExpiresAt && data.Attempts < MAX_OTP_ATTEMPTS +} + +func GetOTPRemainingTime(email string) (time.Duration, error) { + key := fmt.Sprintf("otp:admin:%s", email) + + var data OTPData + err := GetCache(key, &data) + if err != nil { + return 0, err + } + + remaining := time.Until(time.Unix(data.ExpiresAt, 0)) + if remaining < 0 { + return 0, fmt.Errorf("OTP expired") + } + + return remaining, nil +} diff --git a/utils/email_verification.go b/utils/email_verification.go new file mode 100644 index 0000000..7f6dc02 --- /dev/null +++ b/utils/email_verification.go @@ -0,0 +1,208 @@ +package utils + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "time" + + "gopkg.in/gomail.v2" +) + +type EmailVerificationData struct { + Token string `json:"token"` + Email string `json:"email"` + UserID string `json:"user_id"` + ExpiresAt int64 `json:"expires_at"` + Used bool `json:"used"` + CreatedAt int64 `json:"created_at"` +} + +const ( + EMAIL_VERIFICATION_TOKEN_EXPIRY = 24 * time.Hour + EMAIL_VERIFICATION_TOKEN_LENGTH = 32 +) + +func GenerateEmailVerificationToken() (string, error) { + bytes := make([]byte, EMAIL_VERIFICATION_TOKEN_LENGTH) + _, err := rand.Read(bytes) + if err != nil { + return "", fmt.Errorf("failed to generate email verification token: %v", err) + } + return base64.URLEncoding.EncodeToString(bytes), nil +} + +func StoreEmailVerificationToken(email, userID, token string) error { + key := fmt.Sprintf("email_verification:%s", email) + + DeleteCache(key) + + data := EmailVerificationData{ + Token: token, + Email: email, + UserID: userID, + ExpiresAt: time.Now().Add(EMAIL_VERIFICATION_TOKEN_EXPIRY).Unix(), + Used: false, + CreatedAt: time.Now().Unix(), + } + + return SetCache(key, data, EMAIL_VERIFICATION_TOKEN_EXPIRY) +} + +func ValidateEmailVerificationToken(email, inputToken string) (*EmailVerificationData, error) { + key := fmt.Sprintf("email_verification:%s", email) + + var data EmailVerificationData + err := GetCache(key, &data) + if err != nil { + return nil, fmt.Errorf("token verifikasi tidak ditemukan atau sudah kadaluarsa") + } + + if time.Now().Unix() > data.ExpiresAt { + DeleteCache(key) + return nil, fmt.Errorf("token verifikasi sudah kadaluarsa") + } + + if data.Used { + return nil, fmt.Errorf("token verifikasi sudah digunakan") + } + + // Validate token + if !ConstantTimeCompare(data.Token, inputToken) { + return nil, fmt.Errorf("token verifikasi tidak valid") + } + + return &data, nil +} + +// Mark email verification token as used +func MarkEmailVerificationTokenAsUsed(email string) error { + key := fmt.Sprintf("email_verification:%s", email) + + var data EmailVerificationData + err := GetCache(key, &data) + if err != nil { + return err + } + + data.Used = true + remaining := time.Until(time.Unix(data.ExpiresAt, 0)) + + return SetCache(key, data, remaining) +} + +// Check if email verification token exists and still valid +func IsEmailVerificationTokenValid(email string) bool { + key := fmt.Sprintf("email_verification:%s", email) + + var data EmailVerificationData + err := GetCache(key, &data) + if err != nil { + return false + } + + return time.Now().Unix() <= data.ExpiresAt && !data.Used +} + +// Get remaining email verification token time +func GetEmailVerificationTokenRemainingTime(email string) (time.Duration, error) { + key := fmt.Sprintf("email_verification:%s", email) + + var data EmailVerificationData + err := GetCache(key, &data) + if err != nil { + return 0, err + } + + remaining := time.Until(time.Unix(data.ExpiresAt, 0)) + if remaining < 0 { + return 0, fmt.Errorf("token expired") + } + + return remaining, nil +} + +// Send email verification email +func (e *EmailService) SendEmailVerificationEmail(email, name, token string) error { + // Create verification URL - in real app this would be frontend URL + verificationURL := fmt.Sprintf("http://localhost:3000/verify-email?token=%s&email=%s", token, email) + + m := gomail.NewMessage() + m.SetHeader("From", m.FormatAddress(e.from, e.fromName)) + m.SetHeader("To", email) + m.SetHeader("Subject", "Verifikasi Email Administrator - Rijig") + + // Email template + body := fmt.Sprintf(` + + + + + + + +
+
+

✅ Verifikasi Email

+
+
+
🎉
+ +

Selamat %s!

+

Akun Administrator Anda telah berhasil dibuat. Untuk mengaktifkan akun dan mulai menggunakan sistem Rijig, silakan verifikasi email Anda dengan mengklik tombol di bawah ini:

+ +
+ Verifikasi Email Saya +
+ +

Atau copy paste link berikut ke browser Anda:

+
%s
+ +
+

Informasi Penting:

+
    +
  • Link verifikasi berlaku selama 24 jam
  • +
  • Setelah verifikasi, Anda dapat login ke sistem
  • +
  • Link hanya dapat digunakan sekali
  • +
  • Jangan bagikan link ini kepada siapapun
  • +
+
+ +

Langkah selanjutnya setelah verifikasi:

+
    +
  1. Login menggunakan email dan password
  2. +
  3. Masukkan kode OTP yang dikirim ke email
  4. +
  5. Mulai menggunakan sistem Rijig
  6. +
+ +

Jika Anda tidak membuat akun ini, abaikan email ini.

+
+ +
+ + + `, name, verificationURL, verificationURL) + + m.SetBody("text/html", body) + + d := gomail.NewDialer(e.host, e.port, e.username, e.password) + + if err := d.DialAndSend(m); err != nil { + return fmt.Errorf("failed to send email verification email: %v", err) + } + + return nil +} diff --git a/utils/reset_password.go b/utils/reset_password.go new file mode 100644 index 0000000..431987e --- /dev/null +++ b/utils/reset_password.go @@ -0,0 +1,202 @@ +package utils + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "time" + + "gopkg.in/gomail.v2" +) + +type ResetPasswordData struct { + Token string `json:"token"` + Email string `json:"email"` + UserID string `json:"user_id"` + ExpiresAt int64 `json:"expires_at"` + Used bool `json:"used"` + CreatedAt int64 `json:"created_at"` +} + +const ( + RESET_TOKEN_EXPIRY = 30 * time.Minute + RESET_TOKEN_LENGTH = 32 +) + +// Generate secure reset token +func GenerateResetToken() (string, error) { + bytes := make([]byte, RESET_TOKEN_LENGTH) + _, err := rand.Read(bytes) + if err != nil { + return "", fmt.Errorf("failed to generate reset token: %v", err) + } + return base64.URLEncoding.EncodeToString(bytes), nil +} + +// Store reset password token di Redis +func StoreResetToken(email, userID, token string) error { + key := fmt.Sprintf("reset_password:%s", email) + + // Delete any existing reset token for this email + DeleteCache(key) + + data := ResetPasswordData{ + Token: token, + Email: email, + UserID: userID, + ExpiresAt: time.Now().Add(RESET_TOKEN_EXPIRY).Unix(), + Used: false, + CreatedAt: time.Now().Unix(), + } + + return SetCache(key, data, RESET_TOKEN_EXPIRY) +} + +// Validate reset password token +func ValidateResetToken(email, inputToken string) (*ResetPasswordData, error) { + key := fmt.Sprintf("reset_password:%s", email) + + var data ResetPasswordData + err := GetCache(key, &data) + if err != nil { + return nil, fmt.Errorf("token reset tidak ditemukan atau sudah kadaluarsa") + } + + // Check if token is expired + if time.Now().Unix() > data.ExpiresAt { + DeleteCache(key) + return nil, fmt.Errorf("token reset sudah kadaluarsa") + } + + // Check if token is already used + if data.Used { + return nil, fmt.Errorf("token reset sudah digunakan") + } + + // Validate token + if !ConstantTimeCompare(data.Token, inputToken) { + return nil, fmt.Errorf("token reset tidak valid") + } + + return &data, nil +} + +// Mark reset token as used +func MarkResetTokenAsUsed(email string) error { + key := fmt.Sprintf("reset_password:%s", email) + + var data ResetPasswordData + err := GetCache(key, &data) + if err != nil { + return err + } + + data.Used = true + remaining := time.Until(time.Unix(data.ExpiresAt, 0)) + + return SetCache(key, data, remaining) +} + +// Check if reset token exists and still valid +func IsResetTokenValid(email string) bool { + key := fmt.Sprintf("reset_password:%s", email) + + var data ResetPasswordData + err := GetCache(key, &data) + if err != nil { + return false + } + + return time.Now().Unix() <= data.ExpiresAt && !data.Used +} + +// Get remaining reset token time +func GetResetTokenRemainingTime(email string) (time.Duration, error) { + key := fmt.Sprintf("reset_password:%s", email) + + var data ResetPasswordData + err := GetCache(key, &data) + if err != nil { + return 0, err + } + + remaining := time.Until(time.Unix(data.ExpiresAt, 0)) + if remaining < 0 { + return 0, fmt.Errorf("token expired") + } + + return remaining, nil +} + +// Send reset password email +func (e *EmailService) SendResetPasswordEmail(email, name, token string) error { + // Create reset URL - in real app this would be frontend URL + resetURL := fmt.Sprintf("http://localhost:3000/reset-password?token=%s&email=%s", token, email) + + m := gomail.NewMessage() + m.SetHeader("From", m.FormatAddress(e.from, e.fromName)) + m.SetHeader("To", email) + m.SetHeader("Subject", "Reset Password Administrator - Rijig") + + // Email template + body := fmt.Sprintf(` + + + + + + + +
+
+

🔐 Reset Password

+
+
+

Halo %s,

+

Kami menerima permintaan untuk reset password akun Administrator Anda.

+ +

Klik tombol di bawah ini untuk reset password:

+
+ Reset Password +
+ +

Atau copy paste link berikut ke browser Anda:

+
%s
+ +

Penting:

+ + +

⚠️ Jika Anda tidak melakukan permintaan reset password, abaikan email ini dan password Anda tidak akan berubah.

+
+ +
+ + + `, name, resetURL, resetURL) + + m.SetBody("text/html", body) + + d := gomail.NewDialer(e.host, e.port, e.username, e.password) + + if err := d.DialAndSend(m); err != nil { + return fmt.Errorf("failed to send reset password email: %v", err) + } + + return nil +} diff --git a/utils/response.go b/utils/response.go deleted file mode 100644 index a36fb64..0000000 --- a/utils/response.go +++ /dev/null @@ -1,107 +0,0 @@ -package utils - -// import ( -// "github.com/gofiber/fiber/v2" -// ) - -// type MetaData struct { -// Status int `json:"status"` -// Page int `json:"page,omitempty"` -// Limit int `json:"limit,omitempty"` -// Total int `json:"total,omitempty"` -// Message string `json:"message"` -// } - -// type APIResponse struct { -// Meta MetaData `json:"meta"` -// Data interface{} `json:"data,omitempty"` -// } - -// func PaginatedResponse(c *fiber.Ctx, data interface{}, page, limit, total int, message string) error { -// response := APIResponse{ -// Meta: MetaData{ -// Status: fiber.StatusOK, -// Page: page, -// Limit: limit, -// Total: total, -// Message: message, -// }, -// Data: data, -// } -// return c.Status(fiber.StatusOK).JSON(response) -// } - -// func NonPaginatedResponse(c *fiber.Ctx, data interface{}, total int, message string) error { -// response := APIResponse{ -// Meta: MetaData{ -// Status: fiber.StatusOK, -// Total: total, -// Message: message, -// }, -// Data: data, -// } -// return c.Status(fiber.StatusOK).JSON(response) -// } - -// func ErrorResponse(c *fiber.Ctx, message string) error { -// response := APIResponse{ -// Meta: MetaData{ -// Status: fiber.StatusNotFound, -// Message: message, -// }, -// } -// return c.Status(fiber.StatusNotFound).JSON(response) -// } - -// func ValidationErrorResponse(c *fiber.Ctx, errors map[string][]string) error { -// response := APIResponse{ -// Meta: MetaData{ -// Status: fiber.StatusBadRequest, -// Message: "invalid user request", -// }, -// Data: errors, -// } -// return c.Status(fiber.StatusBadRequest).JSON(response) -// } - -// func InternalServerErrorResponse(c *fiber.Ctx, message string) error { -// response := APIResponse{ -// Meta: MetaData{ -// Status: fiber.StatusInternalServerError, -// Message: message, -// }, -// } -// return c.Status(fiber.StatusInternalServerError).JSON(response) -// } - -// func GenericResponse(c *fiber.Ctx, status int, message string) error { -// response := APIResponse{ -// Meta: MetaData{ -// Status: status, -// Message: message, -// }, -// } -// return c.Status(status).JSON(response) -// } - -// func SuccessResponse(c *fiber.Ctx, data interface{}, message string) error { -// response := APIResponse{ -// Meta: MetaData{ -// Status: fiber.StatusOK, -// Message: message, -// }, -// Data: data, -// } -// return c.Status(fiber.StatusOK).JSON(response) -// } - -// func CreateResponse(c *fiber.Ctx, data interface{}, message string) error { -// response := APIResponse{ -// Meta: MetaData{ -// Status: fiber.StatusCreated, -// Message: message, -// }, -// Data: data, -// } -// return c.Status(fiber.StatusOK).JSON(response) -// } diff --git a/utils/role_permission.go b/utils/role_permission.go deleted file mode 100644 index a805ea9..0000000 --- a/utils/role_permission.go +++ /dev/null @@ -1,17 +0,0 @@ -package utils - -// RoleID based -/* const ( - RoleAdministrator = "42bdecce-f2ad-44ae-b3d6-883c1fbddaf7" - RolePengelola = "0bf86966-7042-410a-a88c-d01f70832348" - RolePengepul = "d7245535-0e9e-4d35-ab39-baece5c10b3c" - RoleMasyarakat = "60e5684e4-b214-4bd0-972f-3be80c4649a0" -) */ - -// RoleName based -const ( - RoleAdministrator = "administrator" - RolePengelola = "pengelola" - RolePengepul = "pengepul" - RoleMasyarakat = "masyarakat" -) diff --git a/utils/token_management.go b/utils/token_management.go index 95ca2c8..fa1eb5c 100644 --- a/utils/token_management.go +++ b/utils/token_management.go @@ -15,6 +15,13 @@ import ( type TokenType string +const ( + RoleAdministrator = "administrator" + RolePengelola = "pengelola" + RolePengepul = "pengepul" + RoleMasyarakat = "masyarakat" +) + const ( TokenTypePartial TokenType = "partial" TokenTypeFull TokenType = "full"