mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-06 14:26:22 +00:00
381 lines
10 KiB
Go
381 lines
10 KiB
Go
package security
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
// Optional: Uncomment if you want to use JWT authentication
|
|
// "github.com/golang-jwt/jwt/v5"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Example 1: Simple Header-Based Authenticator
|
|
// =============================================
|
|
|
|
type HeaderAuthenticatorExample struct {
|
|
// Optional: Add any dependencies here (e.g., database, cache)
|
|
}
|
|
|
|
func NewHeaderAuthenticatorExample() *HeaderAuthenticatorExample {
|
|
return &HeaderAuthenticatorExample{}
|
|
}
|
|
|
|
func (a *HeaderAuthenticatorExample) Login(ctx context.Context, req LoginRequest) (*LoginResponse, error) {
|
|
// For header-based auth, login might not be used
|
|
// Could validate credentials against a database here
|
|
return nil, fmt.Errorf("header authentication does not support login")
|
|
}
|
|
|
|
func (a *HeaderAuthenticatorExample) Logout(ctx context.Context, req LogoutRequest) error {
|
|
// For header-based auth, logout is a no-op
|
|
return nil
|
|
}
|
|
|
|
func (a *HeaderAuthenticatorExample) Authenticate(r *http.Request) (*UserContext, error) {
|
|
userIDStr := r.Header.Get("X-User-ID")
|
|
if userIDStr == "" {
|
|
return nil, fmt.Errorf("X-User-ID header required")
|
|
}
|
|
|
|
userID, err := strconv.Atoi(userIDStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid user ID: %w", err)
|
|
}
|
|
|
|
return &UserContext{
|
|
UserID: userID,
|
|
UserName: r.Header.Get("X-User-Name"),
|
|
UserLevel: parseIntHeader(r, "X-User-Level", 0),
|
|
SessionID: r.Header.Get("X-Session-ID"),
|
|
RemoteID: r.Header.Get("X-Remote-ID"),
|
|
Email: r.Header.Get("X-User-Email"),
|
|
Roles: parseRoles(r.Header.Get("X-User-Roles")),
|
|
}, nil
|
|
}
|
|
|
|
// Example 2: JWT Token Authenticator
|
|
// ====================================
|
|
// NOTE: To use this, uncomment the jwt import and install: go get github.com/golang-jwt/jwt/v5
|
|
|
|
type JWTAuthenticatorExample struct {
|
|
secretKey []byte
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewJWTAuthenticatorExample(secretKey string, db *gorm.DB) *JWTAuthenticatorExample {
|
|
return &JWTAuthenticatorExample{
|
|
secretKey: []byte(secretKey),
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
func (a *JWTAuthenticatorExample) Login(ctx context.Context, req LoginRequest) (*LoginResponse, error) {
|
|
// Validate credentials against database
|
|
var user struct {
|
|
ID int
|
|
Username string
|
|
Email string
|
|
Password string // Should be hashed
|
|
UserLevel int
|
|
Roles string
|
|
}
|
|
|
|
err := a.db.WithContext(ctx).
|
|
Table("users").
|
|
Where("username = ?", req.Username).
|
|
First(&user).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid credentials")
|
|
}
|
|
|
|
// TODO: Verify password hash
|
|
// if !verifyPassword(user.Password, req.Password) {
|
|
// return nil, fmt.Errorf("invalid credentials")
|
|
// }
|
|
|
|
// Create JWT token
|
|
expiresAt := time.Now().Add(24 * time.Hour)
|
|
|
|
// Uncomment when using JWT:
|
|
// token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
// "user_id": user.ID,
|
|
// "username": user.Username,
|
|
// "email": user.Email,
|
|
// "user_level": user.UserLevel,
|
|
// "roles": user.Roles,
|
|
// "exp": expiresAt.Unix(),
|
|
// })
|
|
// tokenString, err := token.SignedString(a.secretKey)
|
|
// if err != nil {
|
|
// return nil, fmt.Errorf("failed to generate token: %w", err)
|
|
// }
|
|
|
|
// Placeholder token for example (replace with actual JWT)
|
|
tokenString := fmt.Sprintf("token_%d_%d", user.ID, expiresAt.Unix())
|
|
|
|
return &LoginResponse{
|
|
Token: tokenString,
|
|
User: &UserContext{
|
|
UserID: user.ID,
|
|
UserName: user.Username,
|
|
Email: user.Email,
|
|
UserLevel: user.UserLevel,
|
|
Roles: parseRoles(user.Roles),
|
|
},
|
|
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
|
}, nil
|
|
}
|
|
|
|
func (a *JWTAuthenticatorExample) Logout(ctx context.Context, req LogoutRequest) error {
|
|
// For JWT, logout could involve token blacklisting
|
|
// Add token to blacklist table
|
|
// err := a.db.WithContext(ctx).Table("token_blacklist").Create(map[string]interface{}{
|
|
// "token": req.Token,
|
|
// "expires_at": time.Now().Add(24 * time.Hour),
|
|
// }).Error
|
|
return nil
|
|
}
|
|
|
|
func (a *JWTAuthenticatorExample) Authenticate(r *http.Request) (*UserContext, error) {
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader == "" {
|
|
return nil, fmt.Errorf("authorization header required")
|
|
}
|
|
|
|
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
|
if tokenString == authHeader {
|
|
return nil, fmt.Errorf("bearer token required")
|
|
}
|
|
|
|
// Uncomment when using JWT:
|
|
// token, err := jwt.Parse(tokenString, 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 a.secretKey, nil
|
|
// })
|
|
//
|
|
// if err != nil || !token.Valid {
|
|
// return nil, fmt.Errorf("invalid token: %w", err)
|
|
// }
|
|
//
|
|
// claims, ok := token.Claims.(jwt.MapClaims)
|
|
// if !ok {
|
|
// return nil, fmt.Errorf("invalid token claims")
|
|
// }
|
|
//
|
|
// return &UserContext{
|
|
// UserID: int(claims["user_id"].(float64)),
|
|
// UserName: getString(claims, "username"),
|
|
// Email: getString(claims, "email"),
|
|
// UserLevel: getInt(claims, "user_level"),
|
|
// Roles: parseRoles(getString(claims, "roles")),
|
|
// Claims: claims,
|
|
// }, nil
|
|
|
|
// Placeholder implementation (replace with actual JWT parsing)
|
|
return nil, fmt.Errorf("JWT parsing not implemented - uncomment JWT code above")
|
|
}
|
|
|
|
// Example 3: Database Session Authenticator
|
|
// ==========================================
|
|
|
|
type DatabaseAuthenticatorExample struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewDatabaseAuthenticatorExample(db *gorm.DB) *DatabaseAuthenticatorExample {
|
|
return &DatabaseAuthenticatorExample{db: db}
|
|
}
|
|
|
|
func (a *DatabaseAuthenticatorExample) Login(ctx context.Context, req LoginRequest) (*LoginResponse, error) {
|
|
// Query user from database
|
|
var user struct {
|
|
ID int
|
|
Username string
|
|
Email string
|
|
Password string // Should be hashed with bcrypt
|
|
UserLevel int
|
|
Roles string
|
|
IsActive bool
|
|
}
|
|
|
|
err := a.db.WithContext(ctx).
|
|
Table("users").
|
|
Where("username = ? AND is_active = true", req.Username).
|
|
First(&user).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid credentials")
|
|
}
|
|
|
|
// TODO: Verify password with bcrypt
|
|
// if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
|
|
// return nil, fmt.Errorf("invalid credentials")
|
|
// }
|
|
|
|
// Generate session token
|
|
sessionToken := fmt.Sprintf("sess_%s_%d", generateRandomString(32), time.Now().Unix())
|
|
expiresAt := time.Now().Add(24 * time.Hour)
|
|
|
|
// Create session in database
|
|
err = a.db.WithContext(ctx).Table("user_sessions").Create(map[string]any{
|
|
"session_token": sessionToken,
|
|
"user_id": user.ID,
|
|
"expires_at": expiresAt,
|
|
"created_at": time.Now(),
|
|
"ip_address": req.Claims["ip_address"],
|
|
"user_agent": req.Claims["user_agent"],
|
|
}).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create session: %w", err)
|
|
}
|
|
|
|
return &LoginResponse{
|
|
Token: sessionToken,
|
|
User: &UserContext{
|
|
UserID: user.ID,
|
|
UserName: user.Username,
|
|
Email: user.Email,
|
|
UserLevel: user.UserLevel,
|
|
Roles: parseRoles(user.Roles),
|
|
},
|
|
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
|
}, nil
|
|
}
|
|
|
|
func (a *DatabaseAuthenticatorExample) Logout(ctx context.Context, req LogoutRequest) error {
|
|
// Delete session from database
|
|
err := a.db.WithContext(ctx).
|
|
Table("user_sessions").
|
|
Where("session_token = ? AND user_id = ?", req.Token, req.UserID).
|
|
Delete(nil).Error
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete session: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *DatabaseAuthenticatorExample) Authenticate(r *http.Request) (*UserContext, error) {
|
|
// Extract session token from header or cookie
|
|
sessionToken := r.Header.Get("Authorization")
|
|
if sessionToken == "" {
|
|
// Try cookie
|
|
cookie, err := r.Cookie("session_token")
|
|
if err == nil {
|
|
sessionToken = cookie.Value
|
|
}
|
|
} else {
|
|
// Remove "Bearer " prefix if present
|
|
sessionToken = strings.TrimPrefix(sessionToken, "Bearer ")
|
|
}
|
|
|
|
if sessionToken == "" {
|
|
return nil, fmt.Errorf("session token required")
|
|
}
|
|
|
|
// Query session and user from database
|
|
var session struct {
|
|
SessionToken string
|
|
UserID int
|
|
ExpiresAt time.Time
|
|
Username string
|
|
Email string
|
|
UserLevel int
|
|
Roles string
|
|
}
|
|
|
|
query := `
|
|
SELECT
|
|
s.session_token,
|
|
s.user_id,
|
|
s.expires_at,
|
|
u.username,
|
|
u.email,
|
|
u.user_level,
|
|
u.roles
|
|
FROM user_sessions s
|
|
JOIN users u ON s.user_id = u.id
|
|
WHERE s.session_token = ?
|
|
AND s.expires_at > ?
|
|
AND u.is_active = true
|
|
`
|
|
|
|
err := a.db.Raw(query, sessionToken, time.Now()).Scan(&session).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid or expired session")
|
|
}
|
|
|
|
// Update last activity timestamp
|
|
go a.updateSessionActivity(sessionToken)
|
|
|
|
return &UserContext{
|
|
UserID: session.UserID,
|
|
UserName: session.Username,
|
|
Email: session.Email,
|
|
UserLevel: session.UserLevel,
|
|
SessionID: sessionToken,
|
|
Roles: parseRoles(session.Roles),
|
|
}, nil
|
|
}
|
|
|
|
// updateSessionActivity updates the last activity timestamp for the session
|
|
func (a *DatabaseAuthenticatorExample) updateSessionActivity(sessionToken string) {
|
|
a.db.Table("user_sessions").
|
|
Where("session_token = ?", sessionToken).
|
|
Update("last_activity_at", time.Now())
|
|
}
|
|
|
|
// Optional: Implement Refreshable interface
|
|
func (a *DatabaseAuthenticatorExample) RefreshToken(ctx context.Context, refreshToken string) (*LoginResponse, error) {
|
|
// Query the refresh token
|
|
var session struct {
|
|
UserID int
|
|
Username string
|
|
Email string
|
|
}
|
|
|
|
err := a.db.WithContext(ctx).Raw(`
|
|
SELECT u.id as user_id, u.username, u.email
|
|
FROM user_sessions s
|
|
JOIN users u ON s.user_id = u.id
|
|
WHERE s.session_token = ? AND s.expires_at > ?
|
|
`, refreshToken, time.Now()).Scan(&session).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid refresh token")
|
|
}
|
|
|
|
// Generate new session token
|
|
newSessionToken := fmt.Sprintf("sess_%s_%d", generateRandomString(32), time.Now().Unix())
|
|
expiresAt := time.Now().Add(24 * time.Hour)
|
|
|
|
// Create new session
|
|
err = a.db.WithContext(ctx).Table("user_sessions").Create(map[string]any{
|
|
"session_token": newSessionToken,
|
|
"user_id": session.UserID,
|
|
"expires_at": expiresAt,
|
|
"created_at": time.Now(),
|
|
}).Error
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create new session: %w", err)
|
|
}
|
|
|
|
// Delete old session
|
|
a.db.WithContext(ctx).Table("user_sessions").Where("session_token = ?", refreshToken).Delete(nil)
|
|
|
|
return &LoginResponse{
|
|
Token: newSessionToken,
|
|
User: &UserContext{
|
|
UserID: session.UserID,
|
|
UserName: session.Username,
|
|
Email: session.Email,
|
|
},
|
|
ExpiresIn: int64(24 * time.Hour.Seconds()),
|
|
}, nil
|
|
}
|
|
|