Adding New Endpoint for register and user management
This commit is contained in:
parent
caee765b8b
commit
f84ec65fbb
|
|
@ -0,0 +1,104 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"users_management/m/model/dto"
|
||||||
|
"users_management/m/usecase"
|
||||||
|
"users_management/m/utils/common"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthController struct {
|
||||||
|
authUC usecase.AuthUsecase
|
||||||
|
rg *gin.RouterGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthController(authUC usecase.AuthUsecase, rg *gin.RouterGroup) *AuthController {
|
||||||
|
return &AuthController{
|
||||||
|
authUC: authUC,
|
||||||
|
rg: rg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthController) Route() {
|
||||||
|
auth := c.rg.Group("/auth")
|
||||||
|
{
|
||||||
|
auth.POST("/login", c.login)
|
||||||
|
auth.POST("/logout", c.logout)
|
||||||
|
auth.POST("/validate", c.validateToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthController) login(ctx *gin.Context) {
|
||||||
|
var loginDTO dto.UserLoginDTO
|
||||||
|
if err := ctx.ShouldBindJSON(&loginDTO); err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, role, name, err := c.authUC.Login(loginDTO)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusUnauthorized, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"token": token,
|
||||||
|
"user": gin.H{
|
||||||
|
"name": name,
|
||||||
|
"username": loginDTO.Username,
|
||||||
|
"role": role,
|
||||||
|
},
|
||||||
|
"expires_in": "24h",
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Login successful", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthController) logout(ctx *gin.Context) {
|
||||||
|
token := ctx.GetHeader("Authorization")
|
||||||
|
if token == "" {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Authorization token required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove "Bearer " prefix
|
||||||
|
if len(token) > 7 && token[:7] == "Bearer " {
|
||||||
|
token = token[7:]
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.authUC.Logout(token)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Logout successful", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthController) validateToken(ctx *gin.Context) {
|
||||||
|
token := ctx.GetHeader("Authorization")
|
||||||
|
if token == "" {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Authorization token required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove "Bearer " prefix
|
||||||
|
if len(token) > 7 && token[:7] == "Bearer " {
|
||||||
|
token = token[7:]
|
||||||
|
}
|
||||||
|
|
||||||
|
username, err := c.authUC.ValidateToken(token)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusUnauthorized, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"valid": true,
|
||||||
|
"username": username,
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Token is valid", response)
|
||||||
|
}
|
||||||
|
|
@ -24,8 +24,8 @@ func (bc *BackboneController) Route() {
|
||||||
rg.GET("", bc.GetBackbone())
|
rg.GET("", bc.GetBackbone())
|
||||||
rg.GET("/:uuid", bc.GetBackboneByID())
|
rg.GET("/:uuid", bc.GetBackboneByID())
|
||||||
|
|
||||||
rg.POST("", middleware.ConditionalRequireAnyRole(bc.cfg, "Teknisi", "Admin", "Superadmin"), bc.CreateBackbone())
|
rg.POST("", middleware.ConditionalRequireAnyRole(bc.cfg, "Teknisi", "Admin", "Super Admin"), bc.CreateBackbone())
|
||||||
rg.PUT("/:uuid", middleware.ConditionalRequireAnyRole(bc.cfg, "Teknisi", "Admin", "Superadmin"), bc.UpdateBackbone())
|
rg.PUT("/:uuid", middleware.ConditionalRequireAnyRole(bc.cfg, "Teknisi", "Admin", "Super Admin"), bc.UpdateBackbone())
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
"users_management/m/model/entity"
|
||||||
|
"users_management/m/usecase"
|
||||||
|
"users_management/m/utils"
|
||||||
|
"users_management/m/utils/common"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BypassController struct {
|
||||||
|
userUC usecase.UsersUsecase
|
||||||
|
rg *gin.RouterGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBypassController(userUC usecase.UsersUsecase, rg *gin.RouterGroup) *BypassController {
|
||||||
|
return &BypassController{
|
||||||
|
userUC: userUC,
|
||||||
|
rg: rg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BypassController) Route() {
|
||||||
|
bypass := c.rg.Group("/bypass")
|
||||||
|
{
|
||||||
|
// Dangerous endpoint - should be secured or removed in production
|
||||||
|
bypass.POST("/create-super-admin", c.createSuperAdmin)
|
||||||
|
bypass.GET("/roles", c.getRoles) // Helper to get role IDs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSuperAdminRequest struct {
|
||||||
|
Name string `json:"name" binding:"required"`
|
||||||
|
Username string `json:"username" binding:"required"`
|
||||||
|
Password string `json:"password" binding:"required,min=6"`
|
||||||
|
NomorInduk string `json:"nomor_induk" binding:"required"`
|
||||||
|
SecretKey string `json:"secret_key" binding:"required"` // Extra security
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BypassController) createSuperAdmin(ctx *gin.Context) {
|
||||||
|
var req CreateSuperAdminRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple secret key check (change this in production)
|
||||||
|
if req.SecretKey != "create-super-admin-secret-2024" {
|
||||||
|
common.ErrorResponses(ctx, http.StatusForbidden, "Invalid secret key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if username already exists
|
||||||
|
existingUser, err := c.userUC.GetUserByUsernameWithStatus(req.Username)
|
||||||
|
if err == nil && existingUser.ID != uuid.Nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Username already exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if nomor_induk already exists
|
||||||
|
if req.NomorInduk != "" {
|
||||||
|
existingUser, err := c.userUC.GetUserByNomorInduk(req.NomorInduk)
|
||||||
|
if err == nil && existingUser.ID != uuid.Nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Nomor induk already exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or create Super Admin role
|
||||||
|
superAdminRoleID, err := c.userUC.GetRoleByDepartment("Super Admin")
|
||||||
|
if err != nil {
|
||||||
|
// If Super Admin role doesn't exist, try Admin
|
||||||
|
superAdminRoleID, err = c.userUC.GetRoleByDepartment("Admin")
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, "No admin role found in system")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password
|
||||||
|
hashedPassword, err := utils.HashPassword(req.Password)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, "Failed to hash password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create super admin user directly (bypass normal registration flow)
|
||||||
|
user := entity.User{
|
||||||
|
ID: uuid.New(),
|
||||||
|
Name: req.Name,
|
||||||
|
Username: req.Username,
|
||||||
|
Password: hashedPassword,
|
||||||
|
NomorInduk: &req.NomorInduk,
|
||||||
|
RoleID: superAdminRoleID,
|
||||||
|
Status: entity.UserStatusApproved, // Directly approved
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.userUC.CreateSuperAdminBypass(user)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, "Failed to create super admin: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"message": "Super admin created successfully",
|
||||||
|
"user": gin.H{
|
||||||
|
"id": user.ID,
|
||||||
|
"name": user.Name,
|
||||||
|
"username": user.Username,
|
||||||
|
"nomor_induk": user.NomorInduk,
|
||||||
|
"status": string(user.Status),
|
||||||
|
},
|
||||||
|
"warning": "This bypass endpoint should be disabled in production",
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Super admin created", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BypassController) getRoles(ctx *gin.Context) {
|
||||||
|
// Helper endpoint to see available roles
|
||||||
|
roles := []gin.H{
|
||||||
|
{"name": "Super Admin", "description": "Full system access"},
|
||||||
|
{"name": "Admin", "description": "Administrative access"},
|
||||||
|
{"name": "Teknisi", "description": "Technical user access"},
|
||||||
|
{"name": "Manager", "description": "Management access"},
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"roles": roles,
|
||||||
|
"note": "Use the role name to get role ID via GetRoleByDepartment",
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Available roles", response)
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ func NewDeviceDetailsController(deviceDetailsUC usecase.DeviceDetailsUseCase, rg
|
||||||
|
|
||||||
func (c *DeviceDetailsController) Route() {
|
func (c *DeviceDetailsController) Route() {
|
||||||
deviceDetails := c.rg.Group("/device-details")
|
deviceDetails := c.rg.Group("/device-details")
|
||||||
deviceDetails.Use(middleware.ConditionalRequireAnyRole(c.cfg, "Teknisi", "Admin", "Superadmin"))
|
deviceDetails.Use(middleware.ConditionalRequireAnyRole(c.cfg, "Teknisi", "Admin", "Super Admin"))
|
||||||
{
|
{
|
||||||
deviceDetails.GET("", c.getAllDeviceDetails)
|
deviceDetails.GET("", c.getAllDeviceDetails)
|
||||||
deviceDetails.POST("", c.createDeviceDetails)
|
deviceDetails.POST("", c.createDeviceDetails)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ func NewDeviceController(du usecase.DeviceUseCase, rg *gin.RouterGroup, cfg *con
|
||||||
|
|
||||||
func (dc *DeviceController) Route() {
|
func (dc *DeviceController) Route() {
|
||||||
rg := dc.rg.Group("/devices")
|
rg := dc.rg.Group("/devices")
|
||||||
rg.Use(middleware.ConditionalRequireAnyRole(dc.cfg,"Teknisi", "Admin", "Superadmin"))
|
rg.Use(middleware.ConditionalRequireAnyRole(dc.cfg,"Teknisi", "Admin", "Super Admin"))
|
||||||
{
|
{
|
||||||
rg.POST("", dc.CreateDevice())
|
rg.POST("", dc.CreateDevice())
|
||||||
rg.GET("", dc.GetAllDevices())
|
rg.GET("", dc.GetAllDevices())
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"users_management/m/config"
|
"users_management/m/config"
|
||||||
"users_management/m/middleware"
|
"users_management/m/middleware"
|
||||||
|
|
@ -29,7 +28,7 @@ func NewFishboneController(fu usecase.FishboneUseCase, rg *gin.RouterGroup, cfg
|
||||||
|
|
||||||
func (fc *FishboneController) Route() {
|
func (fc *FishboneController) Route() {
|
||||||
rg := fc.rg.Group("/fishbone")
|
rg := fc.rg.Group("/fishbone")
|
||||||
rg.Use(middleware.ConditionalRequireAnyRole(fc.cfg,"Teknisi", "Admin", "Superadmin"))
|
rg.Use(middleware.ConditionalRequireAnyRole(fc.cfg,"Teknisi", "Admin", "Super Admin"))
|
||||||
// Apply middleware to all routes
|
// Apply middleware to all routes
|
||||||
{
|
{
|
||||||
rg.GET("", fc.GetFishbone())
|
rg.GET("", fc.GetFishbone())
|
||||||
|
|
@ -67,7 +66,6 @@ func (fc *FishboneController) GetFishboneByBackboneID() gin.HandlerFunc {
|
||||||
|
|
||||||
func (fc *FishboneController) GetFishbone() gin.HandlerFunc {
|
func (fc *FishboneController) GetFishbone() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
log.Println("Fetching all fishbones")
|
|
||||||
fishbones, err := fc.fu.GetAllFishbone()
|
fishbones, err := fc.fu.GetAllFishbone()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ func NewNearestDeviceController(nearestDeviceUC usecase.NearestDeviceUseCase, rg
|
||||||
|
|
||||||
func (c *NearestDeviceController) Route() {
|
func (c *NearestDeviceController) Route() {
|
||||||
nearestDevices := c.rg.Group("/nearest-devices")
|
nearestDevices := c.rg.Group("/nearest-devices")
|
||||||
nearestDevices.Use(middleware.ConditionalRequireAnyRole(c.cfg,"Teknisi", "Admin", "Superadmin"))
|
nearestDevices.Use(middleware.ConditionalRequireAnyRole(c.cfg,"Teknisi", "Admin", "Super Admin"))
|
||||||
{
|
{
|
||||||
nearestDevices.POST("/search", c.getNearestDevices)
|
nearestDevices.POST("/search", c.getNearestDevices)
|
||||||
nearestDevices.GET("/:id", c.getNearestDeviceByID)
|
nearestDevices.GET("/:id", c.getNearestDeviceByID)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ type TowerController struct {
|
||||||
|
|
||||||
func (tc *TowerController) Route() {
|
func (tc *TowerController) Route() {
|
||||||
rg := tc.rg.Group("/tower")
|
rg := tc.rg.Group("/tower")
|
||||||
rg.Use(middleware.ConditionalRequireAnyRole(tc.cfg,"Teknisi", "Admin", "Superadmin")) // Apply middleware to all routes
|
rg.Use(middleware.ConditionalRequireAnyRole(tc.cfg,"Teknisi", "Admin","Super Admin")) // Apply middleware to all routes
|
||||||
{
|
{
|
||||||
rg.GET("", tc.GetTower())
|
rg.GET("", tc.GetTower())
|
||||||
rg.POST("", tc.CreateTower())
|
rg.POST("", tc.CreateTower())
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"users_management/m/model/dto"
|
||||||
|
"users_management/m/usecase"
|
||||||
|
"users_management/m/utils/common"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRegistrationController struct {
|
||||||
|
userUC usecase.UsersUsecase
|
||||||
|
rg *gin.RouterGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserRegistrationController(userUC usecase.UsersUsecase, rg *gin.RouterGroup) *UserRegistrationController {
|
||||||
|
return &UserRegistrationController{
|
||||||
|
userUC: userUC,
|
||||||
|
rg: rg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRegistrationController) Route() {
|
||||||
|
registration := c.rg.Group("/user-registration")
|
||||||
|
{
|
||||||
|
// Public registration endpoint
|
||||||
|
registration.POST("/register", c.registerUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
adminRegistration := c.rg.Group("/user-registration")
|
||||||
|
adminRegistration.GET("/pending", c.getPendingUsers)
|
||||||
|
adminRegistration.PUT("/approve/:id", c.approveUser)
|
||||||
|
adminRegistration.PUT("/reject/:id", c.rejectUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRegistrationController) RegisterUser(ctx *gin.Context) {
|
||||||
|
c.registerUser(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRegistrationController) GetPendingUsers(ctx *gin.Context) {
|
||||||
|
c.getPendingUsers(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRegistrationController) ApproveUser(ctx *gin.Context) {
|
||||||
|
c.approveUser(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRegistrationController) RejectUser(ctx *gin.Context) {
|
||||||
|
c.rejectUser(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRegistrationController) registerUser(ctx *gin.Context) {
|
||||||
|
var registerDTO dto.UserRegisterDTO
|
||||||
|
if err := ctx.ShouldBindJSON(®isterDTO); err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.userUC.RegisterUser(registerDTO)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"message": "Registration successful. Your account is pending approval from an administrator.",
|
||||||
|
"status": "pending",
|
||||||
|
"next_steps": "Please wait for an administrator to approve your account before you can log in.",
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "User registered successfully", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRegistrationController) getPendingUsers(ctx *gin.Context) {
|
||||||
|
|
||||||
|
users, err := c.userUC.GetPendingUsers()
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := gin.H{
|
||||||
|
"users": users,
|
||||||
|
"total": len(users),
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "Pending users retrieved successfully", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRegistrationController) approveUser(ctx *gin.Context) {
|
||||||
|
userID, err := uuid.Parse(ctx.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.userUC.ApproveUser(userID)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "User approved successfully", gin.H{
|
||||||
|
"user_id": userID,
|
||||||
|
"status": "approved",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserRegistrationController) rejectUser(ctx *gin.Context) {
|
||||||
|
userID, err := uuid.Parse(ctx.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid user ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.userUC.RejectUser(userID)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SingleResponses(ctx, "User rejected successfully", gin.H{
|
||||||
|
"user_id": userID,
|
||||||
|
"status": "rejected",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -34,7 +34,6 @@ func NewServer() *Server {
|
||||||
engine := gin.Default()
|
engine := gin.Default()
|
||||||
|
|
||||||
engine.Use(middleware.CORSMiddleware())
|
engine.Use(middleware.CORSMiddleware())
|
||||||
engine.Use(middleware.ConditionalAuthMiddleware(ucManager.NewUserUsecase(), cfg))
|
|
||||||
engine.Use(middleware.ConditionalActivityLoggingMiddleware(ucManager.NewActivityLogUsecase(), cfg))
|
engine.Use(middleware.ConditionalActivityLoggingMiddleware(ucManager.NewActivityLogUsecase(), cfg))
|
||||||
|
|
||||||
engine.Use(common.LoggingToFile())
|
engine.Use(common.LoggingToFile())
|
||||||
|
|
@ -50,20 +49,49 @@ func NewServer() *Server {
|
||||||
|
|
||||||
func (s *Server) setupController() {
|
func (s *Server) setupController() {
|
||||||
|
|
||||||
s.engine.Static("/uploads", "./uploads")
|
s.engine.Static("/uploads", "./uploads")
|
||||||
|
|
||||||
|
rg := s.engine.Group("/api/v1")
|
||||||
|
|
||||||
|
// Add bypass controller FIRST - before any auth middleware
|
||||||
|
controller.NewBypassController(s.ucManager.NewUserUsecase(), rg).Route()
|
||||||
|
|
||||||
|
// Add auth controller (public endpoints)
|
||||||
|
controller.NewAuthController(s.ucManager.NewAuthUsecase(), rg).Route()
|
||||||
|
|
||||||
|
publicRegistration := rg.Group("/user-registration")
|
||||||
|
{
|
||||||
|
publicRegistration.POST("/register", controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), rg).RegisterUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Add user registration controller (public endpoints)
|
||||||
|
// controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), rg).Route()
|
||||||
|
|
||||||
|
// Now apply auth middleware to protected routes
|
||||||
|
protected := rg.Use(middleware.ConditionalAuthMiddleware(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(), s.cfg)).(*gin.RouterGroup)
|
||||||
|
{
|
||||||
|
protectedRegistration := protected.Group("/user-registration")
|
||||||
|
{
|
||||||
|
protectedRegistration.GET("/pending", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).GetPendingUsers)
|
||||||
|
protectedRegistration.PUT("/approve/:id", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).ApproveUser)
|
||||||
|
protectedRegistration.PUT("/reject/:id", middleware.RequireAdminRole(), controller.NewUserRegistrationController(s.ucManager.NewUserUsecase(), protected).RejectUser)
|
||||||
|
}
|
||||||
|
controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(), protected).Route()
|
||||||
|
controller.NewCountAssetsController(s.ucManager.NewCountAssetsUsecase(), protected).Route()
|
||||||
|
controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), protected, s.cfg).Route()
|
||||||
|
controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), protected, s.cfg).Route()
|
||||||
|
controller.NewFishboneController(s.ucManager.NewFishboneUsecase(), protected, s.cfg).Route()
|
||||||
|
controller.NewTowerController(s.ucManager.NewTowerUsecase(), protected, s.cfg).Route()
|
||||||
|
controller.NewDevicePortController(s.ucManager.NewDevicePortUsecase(), protected).Route()
|
||||||
|
controller.NewActivityLogController(s.ucManager.NewActivityLogUsecase(), protected, s.cfg).Route()
|
||||||
|
controller.NewDeviceInspectionController(s.ucManager.NewDeviceInspectionUsecase(), protected).Route()
|
||||||
|
controller.NewNearestDeviceController(s.ucManager.NewNearestDeviceUsecase(), protected, s.cfg).Route()
|
||||||
|
controller.NewDeviceDetailsController(s.ucManager.NewDeviceDetailsUsecase(), protected, s.cfg).Route()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
rg := s.engine.Group("/api/v1")
|
|
||||||
controller.NewUsersController(s.ucManager.NewUserUsecase(), s.ucManager.NewAuthUsecase(),rg).Route()
|
|
||||||
controller.NewCountAssetsController(s.ucManager.NewCountAssetsUsecase(), rg).Route()
|
|
||||||
controller.NewDeviceController(s.ucManager.NewDeviceUsecase(), rg,s.cfg).Route()
|
|
||||||
controller.NewBackboneController(s.ucManager.NewBackboneUsecase(), rg,s.cfg).Route()
|
|
||||||
controller.NewFishboneController(s.ucManager.NewFishboneUsecase(), rg,s.cfg).Route()
|
|
||||||
controller.NewTowerController(s.ucManager.NewTowerUsecase(), rg,s.cfg).Route()
|
|
||||||
controller.NewDevicePortController(s.ucManager.NewDevicePortUsecase(), rg).Route()
|
|
||||||
controller.NewActivityLogController(s.ucManager.NewActivityLogUsecase(), rg,s.cfg).Route()
|
|
||||||
controller.NewDeviceInspectionController(s.ucManager.NewDeviceInspectionUsecase(), rg).Route()
|
|
||||||
controller.NewNearestDeviceController(s.ucManager.NewNearestDeviceUsecase(), rg,s.cfg).Route()
|
|
||||||
controller.NewDeviceDetailsController(s.ucManager.NewDeviceDetailsUsecase(), rg,s.cfg).Route()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -5,6 +5,7 @@ go 1.23.1
|
||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/go-playground/validator/v10 v10.24.0
|
github.com/go-playground/validator/v10 v10.24.0
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
golang.org/x/crypto v0.33.0
|
golang.org/x/crypto v0.33.0
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -26,6 +26,8 @@ github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE
|
||||||
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"users_management/m/config"
|
"users_management/m/config"
|
||||||
"users_management/m/model/dto/res"
|
|
||||||
"users_management/m/model/entity"
|
"users_management/m/model/entity"
|
||||||
"users_management/m/usecase"
|
"users_management/m/usecase"
|
||||||
|
"users_management/m/utils"
|
||||||
"users_management/m/utils/common"
|
"users_management/m/utils/common"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
@ -19,7 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConditionalAuthMiddleware checks config to decide whether to apply auth
|
// ConditionalAuthMiddleware checks config to decide whether to apply auth
|
||||||
func ConditionalAuthMiddleware(userUC usecase.UsersUsecase, cfg *config.Config) gin.HandlerFunc {
|
func ConditionalAuthMiddleware(userUC usecase.UsersUsecase, authUC usecase.AuthUsecase, cfg *config.Config) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
// If auth is disabled, skip authentication but set default values
|
// If auth is disabled, skip authentication but set default values
|
||||||
if !cfg.AuthConfig.UserAuthEnabled {
|
if !cfg.AuthConfig.UserAuthEnabled {
|
||||||
|
|
@ -35,85 +32,62 @@ func ConditionalAuthMiddleware(userUC usecase.UsersUsecase, cfg *config.Config)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If auth is enabled, run the normal auth middleware
|
// If auth is enabled, run the local auth middleware
|
||||||
AuthMiddleware(userUC)(c)
|
LocalAuthMiddleware(userUC, authUC)(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocalAuthMiddleware handles local JWT authentication
|
||||||
func AuthMiddleware(userUC usecase.UsersUsecase) gin.HandlerFunc {
|
func LocalAuthMiddleware(userUC usecase.UsersUsecase, authUC usecase.AuthUsecase) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
token := c.GetHeader("Authorization")
|
token := c.GetHeader("Authorization")
|
||||||
|
|
||||||
if token == "" {
|
if token == "" {
|
||||||
common.ErrorResponses(c, http.StatusUnauthorized, "authorization token required")
|
common.ErrorResponses(c, http.StatusUnauthorized, "Authorization token required")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token = strings.TrimPrefix(token, "Bearer ")
|
token = strings.TrimPrefix(token, "Bearer ")
|
||||||
|
|
||||||
c.Set("token", token)
|
c.Set("token", token)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", "https://demo.api-hrm.winteraccess.id/api/v2/auth/me", nil)
|
// Validate JWT token locally
|
||||||
|
_, username, err := utils.ValidateJWT(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
log.Printf("JWT validation error: %v", err)
|
||||||
|
common.ErrorResponses(c, http.StatusUnauthorized, "Invalid or expired token")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
// Get user from local database
|
||||||
req.Header.Set("Accept", "application/json")
|
user, err := userUC.GetUserByUsername(username)
|
||||||
|
|
||||||
|
|
||||||
var client *http.Client
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
client = &http.Client{
|
|
||||||
Transport: tr,
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
log.Printf("User not found: %s", username)
|
||||||
|
common.ErrorResponses(c, http.StatusUnauthorized, "User not found")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
// Check if user is approved
|
||||||
if resp.StatusCode != http.StatusOK {
|
switch user.Status {
|
||||||
common.ErrorResponses(c, http.StatusUnauthorized, "Unauthorized")
|
case entity.UserStatusPending:
|
||||||
|
common.ErrorResponses(c, http.StatusForbidden, "Your account is pending approval from an administrator. Please wait for approval before accessing the system.")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
case entity.UserStatusRejected:
|
||||||
|
common.ErrorResponses(c, http.StatusForbidden, "Your account has been rejected. Please contact an administrator for more information.")
|
||||||
var authResponse res.AuthMeResponse
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&authResponse); err != nil {
|
|
||||||
common.ErrorResponses(c, http.StatusInternalServerError, err.Error())
|
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
case entity.UserStatusApproved:
|
||||||
|
// User is approved, continue with normal flow
|
||||||
// Set basic user info from external API
|
c.Set("userID", user.ID)
|
||||||
var user entity.User
|
c.Set("userName", user.Username)
|
||||||
|
|
||||||
|
|
||||||
// Check if user exists in local database
|
|
||||||
user, err = userUC.GetUserByUsername(strings.ToLower(authResponse.Data.Username))
|
|
||||||
c.Set("userID", user.ID)
|
|
||||||
c.Set("userName", user.Username)
|
|
||||||
log.Println("User data from local DB:", user.ID)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
defaultRole := "Teknisi"
|
|
||||||
c.Set("userRole", defaultRole)
|
|
||||||
} else {
|
|
||||||
// User exists in local DB, use their assigned role
|
|
||||||
c.Set("userRole", user.Role.Name)
|
c.Set("userRole", user.Role.Name)
|
||||||
|
default:
|
||||||
|
common.ErrorResponses(c, http.StatusForbidden, "Your account status is invalid. Please contact an administrator.")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|
|
||||||
|
|
@ -72,12 +72,12 @@ func RequireAnyRole(roles ...string) gin.HandlerFunc {
|
||||||
|
|
||||||
// RequireAdminRole middleware for admin-only access
|
// RequireAdminRole middleware for admin-only access
|
||||||
func RequireAdminRole() gin.HandlerFunc {
|
func RequireAdminRole() gin.HandlerFunc {
|
||||||
return RequireRole("Admin", "Superadmin")
|
return RequireRole("Admin", "Super Admin")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequireSuperAdminRole middleware for superadmin-only access
|
// RequireSuperAdminRole middleware for superadmin-only access
|
||||||
func RequireSuperAdminRole() gin.HandlerFunc {
|
func RequireSuperAdminRole() gin.HandlerFunc {
|
||||||
return RequireRole("Superadmin")
|
return RequireRole("Super Admin")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequireTeknisiRole middleware for teknisi access
|
// RequireTeknisiRole middleware for teknisi access
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,39 @@
|
||||||
package dto
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserStatus defines the possible status values for a user
|
||||||
|
type UserStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserStatusApproved UserStatus = "approved"
|
||||||
|
UserStatusRejected UserStatus = "rejected"
|
||||||
|
UserStatusPending UserStatus = "pending"
|
||||||
|
)
|
||||||
|
|
||||||
type UserRegisterDTO struct {
|
type UserRegisterDTO struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Username string `json:"username" validate:"required"`
|
Username string `json:"username" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required,min=6"`
|
||||||
RoleID string `json:"role_id" validate:"required,uuid"`
|
NomorInduk string `json:"nomor_induk" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserLoginDTO struct {
|
type UserLoginDTO struct {
|
||||||
Username string `json:"username" validate:"required"`
|
Username string `json:"username" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
|
|
||||||
|
}
|
||||||
|
type PendingUserResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
NomorInduk *string `json:"nomor_induk"`
|
||||||
|
RoleName string `json:"role_name"`
|
||||||
|
Status UserStatus `json:"status"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
"github.com/google/uuid"
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserStatusPending UserStatus = "pending"
|
||||||
|
UserStatusApproved UserStatus = "approved"
|
||||||
|
UserStatusRejected UserStatus = "rejected"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
|
@ -13,6 +22,7 @@ type User struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Username string `json:"username" gorm:"unique"`
|
Username string `json:"username" gorm:"unique"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
Status UserStatus `json:"status" gorm:"type:varchar(20);default:'pending'"` // Add status field
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
@ -20,3 +30,4 @@ type User struct {
|
||||||
func (User) TableName() string {
|
func (User) TableName() string {
|
||||||
return "users"
|
return "users"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"users_management/m/model/entity"
|
"users_management/m/model/entity"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -56,14 +55,12 @@ func (r *fishboneRepo) GetByBackboneID(backboneID uuid.UUID) ([]entity.Fishbone,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *fishboneRepo) GetAll() ([]entity.Fishbone, error) {
|
func (r *fishboneRepo) GetAll() ([]entity.Fishbone, error) {
|
||||||
log.Print("Fetching all fishbones with relations")
|
|
||||||
var fishbones []entity.Fishbone
|
var fishbones []entity.Fishbone
|
||||||
err := r.db.Preload("DeviceStart").Preload("DeviceEnd").Preload("Backbone").Find(&fishbones).Error
|
err := r.db.Preload("DeviceStart").Preload("DeviceEnd").Preload("Backbone").Find(&fishbones).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fishbones, err
|
return fishbones, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print(fishbones)
|
|
||||||
return fishbones, nil
|
return fishbones, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"users_management/m/model/entity"
|
"users_management/m/model/entity"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UsersRepo interface {
|
type UsersRepo interface {
|
||||||
|
|
@ -15,6 +15,12 @@ type UsersRepo interface {
|
||||||
CreateUserFromExternal(user entity.User) error // Add this
|
CreateUserFromExternal(user entity.User) error // Add this
|
||||||
UpdateUserRole(nomorInduk string, roleID uuid.UUID) error // Add this
|
UpdateUserRole(nomorInduk string, roleID uuid.UUID) error // Add this
|
||||||
GetAllUsers() ([]entity.User, error) // Add this
|
GetAllUsers() ([]entity.User, error) // Add this
|
||||||
|
RegisterUser(user entity.User) error
|
||||||
|
GetPendingUsers() ([]entity.User, error)
|
||||||
|
UpdateUserStatus(userID uuid.UUID, status entity.UserStatus) error
|
||||||
|
GetUserByID(userID uuid.UUID) (entity.User, error)
|
||||||
|
GetUserByUsernameWithStatus(username string) (entity.User, error)
|
||||||
|
CreateSuperAdminBypass(user entity.User) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type usersRepo struct {
|
type usersRepo struct {
|
||||||
|
|
@ -27,6 +33,36 @@ func NewUsersRepo(db *gorm.DB) UsersRepo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *usersRepo) CreateSuperAdminBypass(user entity.User) error {
|
||||||
|
return r.db.Create(&user).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *usersRepo) RegisterUser(user entity.User) error {
|
||||||
|
return r.db.Create(&user).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *usersRepo) GetPendingUsers() ([]entity.User, error) {
|
||||||
|
var users []entity.User
|
||||||
|
err := r.db.Where("status = ?", entity.UserStatusPending).Preload("Role").Find(&users).Error
|
||||||
|
return users, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *usersRepo) UpdateUserStatus(userID uuid.UUID, status entity.UserStatus) error {
|
||||||
|
return r.db.Model(&entity.User{}).Where("id = ?", userID).Update("status", status).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *usersRepo) GetUserByID(userID uuid.UUID) (entity.User, error) {
|
||||||
|
var user entity.User
|
||||||
|
err := r.db.Where("id = ?", userID).Preload("Role").First(&user).Error
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *usersRepo) GetUserByUsernameWithStatus(username string) (entity.User, error) {
|
||||||
|
var user entity.User
|
||||||
|
err := r.db.Where("username = ?", username).Preload("Role").First(&user).Error
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *usersRepo) Post(user entity.User) error {
|
func (r *usersRepo) Post(user entity.User) error {
|
||||||
err := r.db.Create(&user).Error
|
err := r.db.Create(&user).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
|
||||||
"users_management/m/model/entity"
|
"users_management/m/model/entity"
|
||||||
"users_management/m/repository"
|
"users_management/m/repository"
|
||||||
|
|
||||||
|
|
@ -68,7 +67,6 @@ func (uc *activityLogUseCase) LogActivity(userID uuid.UUID, action, resource str
|
||||||
|
|
||||||
func (uc *activityLogUseCase) GetUserLogs(userID uuid.UUID, page, limit int) ([]entity.ActivityLog, int64, error) {
|
func (uc *activityLogUseCase) GetUserLogs(userID uuid.UUID, page, limit int) ([]entity.ActivityLog, int64, error) {
|
||||||
offset := (page - 1) * limit
|
offset := (page - 1) * limit
|
||||||
log.Printf("Fetching logs for user %s, page: %d, limit: %d, offset: %d", userID, page, limit, offset)
|
|
||||||
logs, err := uc.activityLogRepo.GetByUserID(userID, limit, offset)
|
logs, err := uc.activityLogRepo.GetByUserID(userID, limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
|
|
|
||||||
|
|
@ -1,205 +1,95 @@
|
||||||
package usecase
|
package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"crypto/tls"
|
"users_management/m/config"
|
||||||
"encoding/json"
|
"users_management/m/model/dto"
|
||||||
"errors"
|
"users_management/m/model/entity"
|
||||||
"io/ioutil"
|
"users_management/m/repository"
|
||||||
"net/http"
|
"users_management/m/utils"
|
||||||
"time"
|
|
||||||
"users_management/m/config"
|
|
||||||
"users_management/m/model/dto"
|
|
||||||
"users_management/m/model/dto/res"
|
|
||||||
"users_management/m/model/entity"
|
|
||||||
"users_management/m/repository"
|
|
||||||
"users_management/m/utils"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthUsecase interface {
|
type AuthUsecase interface {
|
||||||
Login(login dto.UserLoginDTO) (string,string, string ,error)
|
Login(login dto.UserLoginDTO) (string, string, string, error)
|
||||||
Logout(token string) error
|
Logout(token string) error
|
||||||
|
ValidateToken(token string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type authUsecase struct {
|
type authUsecase struct {
|
||||||
userRepo repository.UsersRepo
|
userRepo repository.UsersRepo
|
||||||
validate *validator.Validate
|
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
client *http.Client
|
validate *validator.Validate
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthUsecase(userRepo repository.UsersRepo, cfg *config.Config) AuthUsecase {
|
func NewAuthUsecase(userRepo repository.UsersRepo, cfg *config.Config) AuthUsecase {
|
||||||
var client *http.Client
|
|
||||||
|
|
||||||
if cfg.LoginConfig.SkipSSLVerification {
|
|
||||||
// Create HTTP client with SSL verification disabled
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
client = &http.Client{
|
|
||||||
Transport: tr,
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Use standard HTTP client with SSL verification enabled
|
|
||||||
client = &http.Client{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &authUsecase{
|
return &authUsecase{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
validate: validator.New(),
|
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
client: client,
|
validate: validator.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *authUsecase) Login(login dto.UserLoginDTO) (string, string, string, error) {
|
func (u *authUsecase) Login(login dto.UserLoginDTO) (string, string, string, error) {
|
||||||
err := u.validate.Struct(login)
|
// Validate input
|
||||||
|
if err := u.validate.Struct(login); err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user from database (only approved users can login)
|
||||||
|
user, err := u.userRepo.GetUserByUsername(login.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "","","", err
|
return "", "", "", errors.New("invalid username or password")
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := json.Marshal(login)
|
// Check user status
|
||||||
if err != nil {
|
if user.Status != entity.UserStatusApproved {
|
||||||
return "","","", err
|
switch user.Status {
|
||||||
}
|
case entity.UserStatusPending:
|
||||||
|
return "", "", "", errors.New("your account is pending approval from an administrator")
|
||||||
req, err := http.NewRequest("POST", u.cfg.LoginAPI, bytes.NewBuffer(payload))
|
case entity.UserStatusRejected:
|
||||||
if err != nil {
|
return "", "", "", errors.New("your account has been rejected. Please contact an administrator")
|
||||||
return "","","", err
|
default:
|
||||||
}
|
return "", "", "", errors.New("your account is not active")
|
||||||
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
// client := &http.Client{}
|
|
||||||
resp, err := u.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "","","", err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return "","","", errors.New("wrong password or username")
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "","","", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var authResponse res.AuthResponses
|
|
||||||
err = json.Unmarshal(body, &authResponse)
|
|
||||||
if err != nil {
|
|
||||||
return "","","", err
|
|
||||||
}
|
|
||||||
|
|
||||||
token := authResponse.Token
|
|
||||||
|
|
||||||
meReq, err := http.NewRequest("POST", u.cfg.AuthMeAPI, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "","","", err
|
|
||||||
}
|
|
||||||
meReq.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
meReq.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
meResp, err := u.client.Do(meReq)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "","","", err
|
|
||||||
}
|
|
||||||
defer meResp.Body.Close()
|
|
||||||
|
|
||||||
if meResp.StatusCode != http.StatusOK {
|
|
||||||
return "","","", errors.New("failed to validate token: " + meResp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
meBody, err := ioutil.ReadAll(meResp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "","","", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var meResponse res.AuthMeResponse
|
|
||||||
err = json.Unmarshal(meBody, &meResponse)
|
|
||||||
if err != nil {
|
|
||||||
return "","","", err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// departemen := meResponse.Data.Departemen
|
|
||||||
// if departemen != "TECHNICAL PRORGAMMER" {
|
|
||||||
// return "","","", errors.New("user is not a technician")
|
|
||||||
// }
|
|
||||||
|
|
||||||
isUserExist, err := u.userRepo.GetUserByUsername(login.Username)
|
|
||||||
if isUserExist.ID != uuid.Nil {
|
|
||||||
// Validate the password
|
|
||||||
if !utils.CheckPasswordHash(login.Password, isUserExist.Password) {
|
|
||||||
return "","","", errors.New("incorrect password")
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return "","","", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, isUserExist.Role.Name, isUserExist.Name, nil
|
|
||||||
}else if err != nil && err != gorm.ErrRecordNotFound {
|
|
||||||
return "ERROR WHILE SEARCHING USERNAME","", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
password, err := utils.HashPassword(login.Password)
|
|
||||||
if err != nil {
|
|
||||||
return "error while hasing password: ","", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
default_role := "Teknisi"
|
|
||||||
role, err := u.userRepo.GetRoleByDepartment(default_role)
|
|
||||||
if err != nil {
|
|
||||||
return "","","", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user := entity.User{
|
// Verify password
|
||||||
ID: uuid.New(),
|
if !utils.CheckPasswordHash(login.Password, user.Password) {
|
||||||
Name: meResponse.Data.Nama,
|
return "", "", "", errors.New("invalid username or password")
|
||||||
Username: login.Username,
|
}
|
||||||
Password: password,
|
|
||||||
RoleID: role.Id,
|
// Generate JWT token
|
||||||
CreatedAt: time.Now(),
|
token, err := utils.GenerateJWT(user.ID.String(), user.Username, user.Role.Name)
|
||||||
UpdatedAt: time.Now(),
|
if err != nil {
|
||||||
}
|
return "", "", "", err
|
||||||
err = u.userRepo.Post(user)
|
}
|
||||||
if err != nil {
|
|
||||||
return "","","", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, user.Role.Name, user.Name, nil
|
return token, user.Role.Name, user.Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *authUsecase) Logout(token string) error {
|
func (u *authUsecase) Logout(token string) error {
|
||||||
req, err := http.NewRequest("POST", u.cfg.LogoutAPI, nil)
|
// For JWT, we could implement a blacklist mechanism here
|
||||||
if err != nil {
|
// For now, we'll just return success since JWT tokens expire naturally
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
func (u *authUsecase) ValidateToken(token string) (string, error) {
|
||||||
req.Header.Set("Accept", "application/json")
|
_, username, err := utils.ValidateJWT(token)
|
||||||
|
if err != nil {
|
||||||
// client := &http.Client{}
|
return "", err
|
||||||
resp, err := u.client.Do(req)
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
// Verify user still exists and is approved
|
||||||
}
|
user, err := u.userRepo.GetUserByUsername(username)
|
||||||
defer resp.Body.Close()
|
if err != nil {
|
||||||
|
return "", errors.New("user not found")
|
||||||
if resp.StatusCode != http.StatusOK {
|
}
|
||||||
return errors.New("failed to logout: " + resp.Status)
|
|
||||||
}
|
if user.Status != entity.UserStatusApproved {
|
||||||
|
return "", errors.New("user account is not active")
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
return username, nil
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
package usecase
|
package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"errors"
|
||||||
"users_management/m/model/entity"
|
"time"
|
||||||
"users_management/m/repository"
|
"users_management/m/model/dto"
|
||||||
|
"users_management/m/model/entity"
|
||||||
|
"users_management/m/repository"
|
||||||
|
"users_management/m/utils"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UsersUsecase interface {
|
type UsersUsecase interface {
|
||||||
|
|
@ -15,18 +19,140 @@ type UsersUsecase interface {
|
||||||
CreateUserFromExternal(nomorInduk, name, roleName string) error // Add this
|
CreateUserFromExternal(nomorInduk, name, roleName string) error // Add this
|
||||||
UpdateUserRole(nomorInduk, roleName string) error // Add this
|
UpdateUserRole(nomorInduk, roleName string) error // Add this
|
||||||
GetAllUsers() ([]entity.User, error) // Add this
|
GetAllUsers() ([]entity.User, error) // Add this
|
||||||
|
RegisterUser(registerDTO dto.UserRegisterDTO) error
|
||||||
|
GetPendingUsers() ([]dto.PendingUserResponse, error)
|
||||||
|
ApproveUser(userID uuid.UUID) error
|
||||||
|
RejectUser(userID uuid.UUID) error
|
||||||
|
GetUserByUsernameWithStatus(username string) (entity.User, error)
|
||||||
|
CreateSuperAdminBypass(user entity.User) error
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type usersUsecase struct {
|
type usersUsecase struct {
|
||||||
userRepo repository.UsersRepo
|
userRepo repository.UsersRepo
|
||||||
|
validate *validator.Validate
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUsersUsecase(userRepo repository.UsersRepo) UsersUsecase {
|
func NewUsersUsecase(userRepo repository.UsersRepo) UsersUsecase {
|
||||||
return &usersUsecase{
|
return &usersUsecase{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
|
validate: validator.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (u *usersUsecase) CreateSuperAdminBypass(user entity.User) error {
|
||||||
|
return u.userRepo.CreateSuperAdminBypass(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usersUsecase) RegisterUser(registerDTO dto.UserRegisterDTO) error {
|
||||||
|
// Validate input
|
||||||
|
if err := u.validate.Struct(registerDTO); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if username already exists
|
||||||
|
existingUser, err := u.userRepo.GetUserByUsernameWithStatus(registerDTO.Username)
|
||||||
|
if err == nil && existingUser.ID != uuid.Nil {
|
||||||
|
return errors.New("username already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if nomor_induk already exists
|
||||||
|
if registerDTO.NomorInduk != "" {
|
||||||
|
existingUser, err := u.userRepo.GetUserByNomorInduk(registerDTO.NomorInduk)
|
||||||
|
if err == nil && existingUser.ID != uuid.Nil {
|
||||||
|
return errors.New("nomor induk already exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password
|
||||||
|
hashedPassword, err := utils.HashPassword(registerDTO.Password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
teknisiRoleID, err := u.GetRoleByDepartment("Teknisi")
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to get Teknisi role: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Create user with pending status
|
||||||
|
user := entity.User{
|
||||||
|
ID: uuid.New(),
|
||||||
|
Name: registerDTO.Name,
|
||||||
|
Username: registerDTO.Username,
|
||||||
|
Password: hashedPassword,
|
||||||
|
NomorInduk: ®isterDTO.NomorInduk,
|
||||||
|
RoleID: teknisiRoleID,
|
||||||
|
Status: entity.UserStatusPending, // Set status to pending
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.userRepo.RegisterUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usersUsecase) GetPendingUsers() ([]dto.PendingUserResponse, error) {
|
||||||
|
users, err := u.userRepo.GetPendingUsers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var responses []dto.PendingUserResponse
|
||||||
|
for _, user := range users {
|
||||||
|
response := dto.PendingUserResponse{
|
||||||
|
ID: user.ID,
|
||||||
|
Name: user.Name,
|
||||||
|
Username: user.Username,
|
||||||
|
NomorInduk: user.NomorInduk,
|
||||||
|
RoleName: user.Role.Name,
|
||||||
|
Status: dto.UserStatus(user.Status),
|
||||||
|
CreatedAt: user.CreatedAt,
|
||||||
|
UpdatedAt: user.UpdatedAt,
|
||||||
|
}
|
||||||
|
responses = append(responses, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usersUsecase) ApproveUser(userID uuid.UUID) error {
|
||||||
|
// Check if user exists and is pending
|
||||||
|
user, err := u.userRepo.GetUserByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Status != entity.UserStatusPending {
|
||||||
|
return errors.New("user is not in pending status")
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.userRepo.UpdateUserStatus(userID, entity.UserStatusApproved)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usersUsecase) RejectUser(userID uuid.UUID) error {
|
||||||
|
// Check if user exists and is pending
|
||||||
|
user, err := u.userRepo.GetUserByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Status != entity.UserStatusPending {
|
||||||
|
return errors.New("user is not in pending status")
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.userRepo.UpdateUserStatus(userID, entity.UserStatusRejected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *usersUsecase) GetUserByUsernameWithStatus(username string) (entity.User, error) {
|
||||||
|
return u.userRepo.GetUserByUsernameWithStatus(username)
|
||||||
|
}
|
||||||
|
|
||||||
func (u *usersUsecase) GetRoleByDepartment(departmentName string) (uuid.UUID, error) {
|
func (u *usersUsecase) GetRoleByDepartment(departmentName string) (uuid.UUID, error) {
|
||||||
role, err := u.userRepo.GetRoleByDepartment(departmentName)
|
role, err := u.userRepo.GetRoleByDepartment(departmentName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ func (g *CachedGeocoder) GetAddressFromCoordinates(latitude, longitude float64)
|
||||||
}
|
}
|
||||||
g.mutex.RUnlock()
|
g.mutex.RUnlock()
|
||||||
|
|
||||||
log.Printf("CACHE MISS: Coordinates (%.6f,%.6f) not in cache, fetching from service", latitude, longitude)
|
|
||||||
atomic.AddInt64(&g.misses, 1)
|
atomic.AddInt64(&g.misses, 1)
|
||||||
|
|
||||||
// Not in cache, call the underlying service
|
// Not in cache, call the underlying service
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "golang.org/x/crypto/bcrypt"
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
func HashPassword(password string) (string, error) {
|
func HashPassword(password string) (string, error) {
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
|
@ -15,3 +21,46 @@ func CheckPasswordHash(password, hash string) bool {
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jwtSecret = []byte("your-secret-key-change-this-in-production") // Change this in production
|
||||||
|
|
||||||
|
type Claims struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateJWT(userID, username, role string) (string, error) {
|
||||||
|
claims := Claims{
|
||||||
|
UserID: userID,
|
||||||
|
Username: username,
|
||||||
|
Role: role,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // Token expires in 24 hours
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
return token.SignedString(jwtSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateJWT(tokenString string) (string, string, error) {
|
||||||
|
claims := &Claims{}
|
||||||
|
|
||||||
|
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return jwtSecret, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.Valid {
|
||||||
|
return "", "", errors.New("invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims.UserID, claims.Username, nil
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue