This commit is contained in:
2026-02-11 17:46:22 +03:00
commit eacfacb13b
266 changed files with 51337 additions and 0 deletions

47
svc/auth/claims.go Normal file
View File

@@ -0,0 +1,47 @@
package auth
import (
"strings"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
// ==================================================
// 🔑 IDENTITY
// ==================================================
ID int64 `json:"id"`
Username string `json:"username"`
RoleID int64 `json:"role_id"`
RoleCode string `json:"role_code"`
DepartmentCodes []string `json:"department_codes"`
// ==================================================
// 🧾 NEBIM (frontend filtre & backend guard için)
// ==================================================
V3Username string `json:"v3_username"`
V3UserGroup string `json:"v3_usergroup"`
// ==================================================
// 🔐 SESSION
// ==================================================
SessionID string `json:"session_id"`
// ==================================================
// ⚠️ SECURITY
// ==================================================
ForcePasswordChange bool `json:"force_password_change"`
jwt.RegisteredClaims
}
func (c *Claims) IsAdmin() bool {
if c == nil {
return false
}
role := strings.ToLower(strings.TrimSpace(c.RoleCode))
return role == "admin"
}

36
svc/auth/claims_mapper.go Normal file
View File

@@ -0,0 +1,36 @@
package auth
import (
"time"
"bssapp-backend/models"
"github.com/golang-jwt/jwt/v5"
)
func BuildClaimsFromUser(u *models.MkUser, ttl time.Duration) Claims {
now := time.Now()
return Claims{
// 🔴 mk_dfusr.id
ID: u.ID,
Username: u.Username,
RoleCode: u.RoleCode,
RoleID: u.RoleID,
// ✅ BURASI
DepartmentCodes: u.DepartmentCodes,
SessionID: u.SessionID,
ForcePasswordChange: u.ForcePasswordChange,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "bssapp",
Subject: u.Username,
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(ttl)),
},
}
}

15
svc/auth/context.go Normal file
View File

@@ -0,0 +1,15 @@
package auth
import (
"bssapp-backend/ctxkeys"
"context"
)
func WithClaims(ctx context.Context, claims *Claims) context.Context {
return context.WithValue(ctx, ctxkeys.UserContextKey, claims)
}
func GetClaimsFromContext(ctx context.Context) (*Claims, bool) {
claims, ok := ctx.Value(ctxkeys.UserContextKey).(*Claims)
return claims, ok
}

53
svc/auth/jwt.go Normal file
View File

@@ -0,0 +1,53 @@
package auth
import (
"errors"
"os"
"github.com/golang-jwt/jwt/v5"
)
// package auth
func jwtSecret() ([]byte, error) {
sec := os.Getenv("JWT_SECRET")
if len(sec) < 10 {
return nil, errors.New("JWT_SECRET environment boş veya çok kısa")
}
return []byte(sec), nil
}
// ✅ TEK VE DOĞRU TOKEN ÜRETİCİ
func GenerateToken(claims Claims, username string, change bool) (string, error) {
secret, err := jwtSecret()
if err != nil {
return "", err
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secret)
}
func ValidateToken(tokenStr string) (*Claims, error) {
secret, err := jwtSecret()
if err != nil {
return nil, err
}
token, err := jwt.ParseWithClaims(
tokenStr,
&Claims{},
func(token *jwt.Token) (interface{}, error) {
return secret, nil
},
)
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, errors.New("token geçersiz")
}
return claims, nil
}

44
svc/auth/logout.go Normal file
View File

@@ -0,0 +1,44 @@
package auth
import (
"bssapp-backend/internal/auditlog"
"bssapp-backend/repository"
"database/sql"
"encoding/json"
"net/http"
"time"
)
func LogoutAllHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
claims, ok := GetClaimsFromContext(r.Context())
if !ok || claims == nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
userID := claims.ID
_ = repository.NewRefreshTokenRepository(db).
RevokeAllForUser(userID)
http.SetCookie(w, &http.Cookie{
Name: "mk_refresh",
Value: "",
Path: "/",
Expires: time.Unix(0, 0),
HttpOnly: true,
})
auditlog.Write(auditlog.ActivityLog{
UserID: auditlog.IntUserIDToUUID(int(userID)),
ActionType: "logout_all",
ActionCategory: "auth",
Description: "user logged out from all devices",
IsSuccess: true,
})
_ = json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
}