feat: auth system hardening with token revocation, password management, and error sanitization
- Add token revocation with in-memory blacklist and periodic cleanup (SEC-M1)
- Add POST /api/auth/logout endpoint
- Fix OIDC auth_token cookie to HttpOnly with exchange endpoint (SEC-H3)
- Add password complexity validation (min 8 chars) (SEC-M2)
- Prevent admin self-deletion (SEC-M3)
- Add PUT /api/auth/users/{uid} for role/email updates (FUNC-M1)
- Add PUT /api/auth/users/{uid}/password for password changes (FUNC-H1)
- Sanitize error messages in auth handlers (SEC-M4)
This commit is contained in:
+43
-1
@@ -3,8 +3,10 @@ package auth
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
@@ -31,6 +33,8 @@ type jwtClaims struct {
|
||||
// LocalAuth handles password hashing and JWT token management for local auth mode.
|
||||
type LocalAuth struct {
|
||||
jwtSecret []byte
|
||||
mu sync.RWMutex
|
||||
blacklist map[string]time.Time // token hash -> expiry time
|
||||
}
|
||||
|
||||
// NewLocalAuth creates a LocalAuth deriving the JWT signing key from the encryption key
|
||||
@@ -38,8 +42,46 @@ type LocalAuth struct {
|
||||
func NewLocalAuth(encKey [32]byte) *LocalAuth {
|
||||
mac := hmac.New(sha256.New, encKey[:])
|
||||
mac.Write([]byte("docker-watcher-jwt-secret"))
|
||||
return &LocalAuth{
|
||||
la := &LocalAuth{
|
||||
jwtSecret: mac.Sum(nil),
|
||||
blacklist: make(map[string]time.Time),
|
||||
}
|
||||
// Periodically clean expired blacklist entries.
|
||||
go la.cleanBlacklist()
|
||||
return la
|
||||
}
|
||||
|
||||
// RevokeToken adds a token to the blacklist.
|
||||
func (la *LocalAuth) RevokeToken(tokenString string) {
|
||||
hash := sha256.Sum256([]byte(tokenString))
|
||||
key := hex.EncodeToString(hash[:])
|
||||
la.mu.Lock()
|
||||
defer la.mu.Unlock()
|
||||
la.blacklist[key] = time.Now().Add(TokenExpiry)
|
||||
}
|
||||
|
||||
// IsRevoked checks if a token has been revoked.
|
||||
func (la *LocalAuth) IsRevoked(tokenString string) bool {
|
||||
hash := sha256.Sum256([]byte(tokenString))
|
||||
key := hex.EncodeToString(hash[:])
|
||||
la.mu.RLock()
|
||||
defer la.mu.RUnlock()
|
||||
_, exists := la.blacklist[key]
|
||||
return exists
|
||||
}
|
||||
|
||||
// cleanBlacklist removes expired entries from the blacklist every hour.
|
||||
func (la *LocalAuth) cleanBlacklist() {
|
||||
ticker := time.NewTicker(1 * time.Hour)
|
||||
for range ticker.C {
|
||||
la.mu.Lock()
|
||||
now := time.Now()
|
||||
for k, expiry := range la.blacklist {
|
||||
if now.After(expiry) {
|
||||
delete(la.blacklist, k)
|
||||
}
|
||||
}
|
||||
la.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user