security: remediate vulnerabilities and issues from codebase audit
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m15s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m15s
This commit is contained in:
parent
b60a09196a
commit
fb3f3a3393
15 changed files with 465 additions and 208 deletions
|
|
@ -21,9 +21,10 @@ func AuthMiddleware(jwtSecret string) fiber.Handler {
|
|||
}
|
||||
}
|
||||
|
||||
// Fallback to query parameter for img/video tags
|
||||
isQueryToken := false
|
||||
if tokenString == "" {
|
||||
tokenString = c.Query("token")
|
||||
isQueryToken = true
|
||||
}
|
||||
|
||||
if tokenString == "" {
|
||||
|
|
@ -52,6 +53,17 @@ func AuthMiddleware(jwtSecret string) fiber.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
tokenType, _ := claims["type"].(string)
|
||||
if isQueryToken {
|
||||
if tokenType != "download" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid token type for query parameter"})
|
||||
}
|
||||
} else {
|
||||
if tokenType != "access" {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid token type for authorization header"})
|
||||
}
|
||||
}
|
||||
|
||||
c.Locals("userID", claims["sub"])
|
||||
c.Locals("username", claims["username"])
|
||||
|
||||
|
|
@ -86,3 +98,39 @@ func GenerateRefreshToken(userID int, username, secret string) (string, error) {
|
|||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(secret))
|
||||
}
|
||||
|
||||
// GenerateDownloadToken creates a short-lived download token.
|
||||
func GenerateDownloadToken(userID int, username, secret string) (string, error) {
|
||||
claims := jwt.MapClaims{
|
||||
"sub": userID,
|
||||
"username": username,
|
||||
"exp": time.Now().Add(5 * time.Minute).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
"type": "download",
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(secret))
|
||||
}
|
||||
|
||||
// ValidateDownloadToken validates a download token
|
||||
func ValidateDownloadToken(tokenString, secret, expectedUsername string) error {
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "invalid token")
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok || claims["type"] != "download" {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "invalid token claims")
|
||||
}
|
||||
|
||||
if expectedUsername != "" && claims["username"] != expectedUsername {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "token belongs to different context")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,22 +46,21 @@ func (g *BruteForceGuard) Check() fiber.Handler {
|
|||
|
||||
g.mu.Lock()
|
||||
attempt, exists := g.attempts[ip]
|
||||
g.mu.Unlock()
|
||||
|
||||
if exists && attempt.Count >= g.maxAttempts {
|
||||
lockoutDuration := time.Duration(g.lockoutSecs) * time.Second
|
||||
if time.Since(attempt.LockedAt) < lockoutDuration {
|
||||
remaining := lockoutDuration - time.Since(attempt.LockedAt)
|
||||
g.mu.Unlock()
|
||||
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
|
||||
"error": "too many login attempts, account temporarily locked",
|
||||
"retry_after": int(remaining.Seconds()),
|
||||
})
|
||||
}
|
||||
// Lockout expired, reset
|
||||
g.mu.Lock()
|
||||
delete(g.attempts, ip)
|
||||
g.mu.Unlock()
|
||||
}
|
||||
g.mu.Unlock()
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue