Files
MusicServer/internal/server/middleware/token_auth.go
T
Sansan 0ec2425f78 Return every error as {"error": "..."}
Echo errors, middleware and handlers used three different formats (message object, error object, bare string). One error handler now writes all of them the same way; status codes are unchanged.
2026-10-06 16:21:14 +02:00

63 lines
1.8 KiB
Go

package middleware
import (
"context"
"net/http"
"strings"
"time"
"music-server/internal/db/repository"
"music-server/internal/logging"
"github.com/labstack/echo/v5"
"go.uber.org/zap"
)
// BearerToken extracts the token from an "Authorization: Bearer <token>" header.
func BearerToken(c *echo.Context) (string, bool) {
token, ok := strings.CutPrefix(c.Request().Header.Get("Authorization"), "Bearer ")
return token, ok && token != "" && !strings.Contains(token, " ")
}
// TokenAuth validates session tokens. When disabled is true all requests are let through.
func TokenAuth(q repository.Querier, disabled bool) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error {
if disabled {
return next(c)
}
if c.Request().Header.Get("Authorization") == "" {
return echo.NewHTTPError(http.StatusUnauthorized, "Authorization header required")
}
token, ok := BearerToken(c)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid authorization format. Use: Bearer <token>")
}
ctx := c.Request().Context()
session, err := q.GetSession(ctx, token)
if err != nil {
logging.GetLogger().Warn("Invalid token attempt",
zap.String("ip", c.RealIP()),
zap.String("error", err.Error()),
)
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid or expired token")
}
if time.Now().After(session.ExpiresAt.Time) {
// Clean up in the background with a context that outlives the request.
go func(ctx context.Context) {
if err := q.DeleteSession(ctx, token); err != nil {
logging.GetLogger().Error("Failed to delete expired session", zap.Error(err))
}
}(context.WithoutCancel(ctx))
return echo.NewHTTPError(http.StatusUnauthorized, "Token expired")
}
c.Set("session", session)
return next(c)
}
}
}