v0.12.0 migrate sqlite3 to psql18, updated auth, deploy docs, account and replay updates
test / test (push) Canceled after 0s

This commit is contained in:
lixen committed 2026-09-26 20:58:19 -04:00
1 parent 5d2be4abb2
commit d0326b059e
66 files changed
+4016 -2433

No files matched your search

+35 -45
View File
@@ -2,12 +2,12 @@ package http
import (
"errors"
"strings"
"chess/internal/server/core"
"chess/internal/server/service"
"github.com/gofiber/fiber/v2"
"github.com/lixenwraith/auth"
)
// TokenValidator validates JWT tokens
@@ -16,69 +16,59 @@ type TokenValidator func(token string) (userID string, claims map[string]any, er
// AuthRequired enforces JWT authentication for protected endpoints
func AuthRequired(validateToken TokenValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
token := extractBearerToken(c.Get("Authorization"))
if token == "" {
header := c.Get(fiber.HeaderAuthorization)
if header == "" {
return c.Status(fiber.StatusUnauthorized).JSON(core.ErrorResponse{
Error: "missing authorization token",
Code: core.ErrUnauthorized,
})
}
userID, claims, err := validateToken(token)
token, err := auth.ParseBearerToken(header)
if err != nil {
if errors.Is(err, service.ErrStorageDisabled) || errors.Is(err, service.ErrStorageUnavailable) {
return c.Status(fiber.StatusServiceUnavailable).JSON(core.ErrorResponse{
Error: "authentication storage unavailable", Code: core.ErrStorageUnavailable,
})
}
return c.Status(fiber.StatusUnauthorized).JSON(core.ErrorResponse{
Error: "invalid or expired token",
Code: core.ErrUnauthorized,
})
return invalidToken(c)
}
c.Locals("userID", userID)
if sessionID, ok := claims["session_id"].(string); ok {
c.Locals("sessionID", sessionID)
}
return c.Next()
return authenticate(c, validateToken, token)
}
}
// OptionalAuth permits an absent token but rejects an invalid token instead of
// silently downgrading an intended authenticated request to anonymous access.
// OptionalAuth permits an absent Authorization header but rejects a malformed
// or invalid one instead of silently downgrading an intended authenticated
// request to anonymous access.
func OptionalAuth(validateToken TokenValidator) fiber.Handler {
return func(c *fiber.Ctx) error {
token := extractBearerToken(c.Get("Authorization"))
if token == "" {
header := c.Get(fiber.HeaderAuthorization)
if header == "" {
return c.Next()
}
userID, claims, err := validateToken(token)
token, err := auth.ParseBearerToken(header)
if err != nil {
if errors.Is(err, service.ErrStorageDisabled) || errors.Is(err, service.ErrStorageUnavailable) {
return c.Status(fiber.StatusServiceUnavailable).JSON(core.ErrorResponse{
Error: "authentication storage unavailable", Code: core.ErrStorageUnavailable,
})
}
return c.Status(fiber.StatusUnauthorized).JSON(core.ErrorResponse{
Error: "invalid or expired token", Code: core.ErrUnauthorized,
return invalidToken(c)
}
return authenticate(c, validateToken, token)
}
}
func authenticate(c *fiber.Ctx, validateToken TokenValidator, token string) error {
userID, claims, err := validateToken(token)
if err != nil {
if errors.Is(err, service.ErrStorageDisabled) || errors.Is(err, service.ErrStorageUnavailable) {
return c.Status(fiber.StatusServiceUnavailable).JSON(core.ErrorResponse{
Error: "authentication storage unavailable", Code: core.ErrStorageUnavailable,
})
}
c.Locals("userID", userID)
if sessionID, ok := claims["session_id"].(string); ok {
c.Locals("sessionID", sessionID)
}
return c.Next()
return invalidToken(c)
}
c.Locals("userID", userID)
if sessionID, ok := claims["session_id"].(string); ok {
c.Locals("sessionID", sessionID)
}
return c.Next()
}
// extractBearerToken extracts JWT token from Authorization header
func extractBearerToken(header string) string {
const prefix = "Bearer "
if !strings.HasPrefix(header, prefix) {
return ""
}
return strings.TrimPrefix(header, prefix)
func invalidToken(c *fiber.Ctx) error {
return c.Status(fiber.StatusUnauthorized).JSON(core.ErrorResponse{
Error: "invalid or expired token", Code: core.ErrUnauthorized,
})
}