| package middleware |
|
|
| import ( |
| "errors" |
| "fmt" |
| "net/http" |
| "strings" |
| "time" |
|
|
| "service-warungpos-go/config" |
| "service-warungpos-go/models" |
|
|
| "github.com/gin-gonic/gin" |
| "github.com/golang-jwt/jwt/v5" |
| "golang.org/x/crypto/bcrypt" |
| ) |
|
|
| type JWTClaims struct { |
| Sub uint `json:"sub"` |
| Role string `json:"role"` |
| CompanyID *uint `json:"company_id"` |
| SessionToken string `json:"session_token,omitempty"` |
| jwt.RegisteredClaims |
| } |
|
|
| |
| func HashPassword(password string) (string, error) { |
| bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) |
| return string(bytes), err |
| } |
|
|
| func VerifyPassword(password, hash string) bool { |
| err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) |
| return err == nil |
| } |
|
|
| func CreateAccessToken(userID uint, role string, companyID *uint, sessionToken ...string) (string, error) { |
| expiryHours := config.GlobalConfig.JWTExpiryHours |
| if expiryHours == 0 { |
| expiryHours = 24 |
| } |
|
|
| claims := JWTClaims{ |
| Sub: userID, |
| Role: role, |
| CompanyID: companyID, |
| RegisteredClaims: jwt.RegisteredClaims{ |
| ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiryHours) * time.Hour)), |
| IssuedAt: jwt.NewNumericDate(time.Now()), |
| }, |
| } |
| if len(sessionToken) > 0 && sessionToken[0] != "" { |
| claims.SessionToken = sessionToken[0] |
| } |
|
|
| token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
| return token.SignedString([]byte(config.GlobalConfig.JWTSecretKey)) |
| } |
|
|
| func ParseToken(tokenString string) (*JWTClaims, error) { |
| token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) { |
| if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { |
| return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) |
| } |
| return []byte(config.GlobalConfig.JWTSecretKey), nil |
| }) |
|
|
| if err != nil { |
| return nil, err |
| } |
|
|
| if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid { |
| return claims, nil |
| } |
|
|
| return nil, errors.New("invalid token claims") |
| } |
|
|
| |
| func GetCurrentUser() gin.HandlerFunc { |
| return func(c *gin.Context) { |
| authHeader := c.GetHeader("Authorization") |
| if authHeader == "" { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Token otorisasi diperlukan"}) |
| c.Abort() |
| return |
| } |
|
|
| parts := strings.Split(authHeader, " ") |
| if len(parts) != 2 || parts[0] != "Bearer" { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Format token otorisasi salah"}) |
| c.Abort() |
| return |
| } |
|
|
| tokenString := parts[1] |
| claims, err := ParseToken(tokenString) |
| if err != nil { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": fmt.Sprintf("Token error: %v", err)}) |
| c.Abort() |
| return |
| } |
|
|
| var user models.User |
| if err := config.DB.Select("id, name, email, phone, role, company_id, branch_id, is_active, created_at").First(&user, claims.Sub).Error; err != nil { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "User tidak ditemukan"}) |
| c.Abort() |
| return |
| } |
|
|
| if !user.IsActive { |
| c.JSON(http.StatusForbidden, gin.H{"detail": "Akun Anda dinonaktifkan. Hubungi Administrator."}) |
| c.Abort() |
| return |
| } |
|
|
| |
| if user.Role == "kasir" && user.CompanyID != nil { |
| var company models.Company |
| if err := config.DB.Select("enforce_single_session").First(&company, *user.CompanyID).Error; err == nil { |
| if company.EnforceSingleSession { |
| |
| if claims.SessionToken == "" { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Sesi tidak valid, silakan login ulang.", "code": "SESSION_EXPIRED"}) |
| c.Abort() |
| return |
| } |
| |
| var session models.UserSession |
| err := config.DB.Where( |
| "user_id = ? AND session_token = ? AND expires_at > ?", |
| user.ID, claims.SessionToken, time.Now(), |
| ).First(&session).Error |
| if err != nil { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Sesi Anda sudah tidak aktif. Perangkat lain telah login dengan akun ini.", "code": "SESSION_EXPIRED"}) |
| c.Abort() |
| return |
| } |
| } |
| } |
| } |
|
|
| |
| c.Set("user", &user) |
| c.Next() |
| } |
| } |
|
|
| func RequireSuperAdmin() gin.HandlerFunc { |
| return func(c *gin.Context) { |
| userVal, exists := c.Get("user") |
| if !exists { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Hubungan otorisasi gagal"}) |
| c.Abort() |
| return |
| } |
|
|
| user := userVal.(*models.User) |
| if user.Role != "super_admin" { |
| c.JSON(http.StatusForbidden, gin.H{"detail": "Akses ditolak: Hanya super_admin yang memiliki izin."}) |
| c.Abort() |
| return |
| } |
|
|
| c.Next() |
| } |
| } |
|
|
| func RequireOwner() gin.HandlerFunc { |
| return func(c *gin.Context) { |
| userVal, exists := c.Get("user") |
| if !exists { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Hubungan otorisasi gagal"}) |
| c.Abort() |
| return |
| } |
|
|
| user := userVal.(*models.User) |
| if user.Role != "owner" && user.Role != "super_admin" { |
| c.JSON(http.StatusForbidden, gin.H{"detail": "Akses ditolak: Hanya owner yang memiliki izin."}) |
| c.Abort() |
| return |
| } |
|
|
| if user.CompanyID == nil { |
| c.JSON(http.StatusForbidden, gin.H{"detail": "Akses ditolak: Akun Anda tidak terikat ke perusahaan/toko manapun."}) |
| c.Abort() |
| return |
| } |
|
|
| c.Next() |
| } |
| } |
|
|
| func RequireTenantContext() gin.HandlerFunc { |
| return func(c *gin.Context) { |
| userVal, exists := c.Get("user") |
| if !exists { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Hubungan otorisasi gagal"}) |
| c.Abort() |
| return |
| } |
|
|
| user := userVal.(*models.User) |
| if user.CompanyID == nil { |
| c.JSON(http.StatusForbidden, gin.H{"detail": "Akses ditolak: User tidak terikat ke perusahaan/toko manapun."}) |
| c.Abort() |
| return |
| } |
|
|
| c.Next() |
| } |
| } |
|
|
| func RequireAuthenticated() gin.HandlerFunc { |
| return func(c *gin.Context) { |
| _, exists := c.Get("user") |
| if !exists { |
| c.JSON(http.StatusUnauthorized, gin.H{"detail": "Hubungan otorisasi gagal"}) |
| c.Abort() |
| return |
| } |
| c.Next() |
| } |
| } |
|
|