ultisuite-backend/internal/api/middleware/auth.go
R3D347HR4Y 95196f7777 Add mail attachment and draft management features
- Introduced new functionality for managing email attachments and drafts in the mail API.
- Added handlers for listing, uploading, and downloading message attachments in `internal/api/mail/handlers_attachments.go`.
- Implemented draft management endpoints for creating, updating, and deleting drafts in `internal/api/mail/handlers_drafts.go`.
- Created new service methods for handling draft and attachment operations in `internal/api/mail/drafts.go` and `internal/api/mail/storage.go`.
- Added validation and error handling for draft and attachment operations.
- Included unit tests for draft and folder functionalities in `internal/api/mail/drafts_test.go` and `internal/api/mail/folders_test.go`.
- Updated API routes to support new draft and attachment features, enhancing overall mail management capabilities.
2026-05-22 17:14:36 +02:00

101 lines
3.0 KiB
Go

package middleware
import (
"context"
"log/slog"
"net/http"
"strings"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
"github.com/ultisuite/ulti-backend/internal/auth"
"github.com/ultisuite/ulti-backend/internal/securityaudit"
"github.com/ultisuite/ulti-backend/internal/users"
)
type ctxKey string
const claimsKey ctxKey = "claims"
func Auth(verifier *auth.Verifier, db *pgxpool.Pool, audit *securityaudit.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if verifier == nil {
apiresponse.WriteError(w, r, http.StatusServiceUnavailable, apiresponse.CodeAuthUnavailable, "authentication unavailable", nil)
if audit != nil {
audit.Log(r.Context(), "system", securityaudit.ActionTokenRejected, map[string]any{
"reason": "verifier_unavailable",
"path": r.URL.Path,
"method": r.Method,
})
}
return
}
header := r.Header.Get("Authorization")
if header == "" {
apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthMissingAuthorization, "missing authorization header", nil)
if audit != nil {
audit.Log(r.Context(), "anonymous", securityaudit.ActionTokenRejected, map[string]any{
"reason": "missing_authorization_header",
"path": r.URL.Path,
"method": r.Method,
})
}
return
}
token, found := strings.CutPrefix(header, "Bearer ")
if !found {
apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthInvalidAuthorization, "invalid authorization header", nil)
if audit != nil {
audit.Log(r.Context(), "anonymous", securityaudit.ActionTokenRejected, map[string]any{
"reason": "invalid_authorization_header",
"path": r.URL.Path,
"method": r.Method,
})
}
return
}
claims, err := verifier.Verify(r.Context(), token)
if err != nil {
apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthInvalidToken, "invalid token", nil)
if audit != nil {
audit.Log(r.Context(), "anonymous", securityaudit.ActionTokenRejected, map[string]any{
"reason": "token_verification_failed",
"path": r.URL.Path,
"method": r.Method,
})
}
return
}
if db != nil {
if _, err := users.EnsureUser(r.Context(), db, claims); err != nil {
slog.Error("provision user", "sub", claims.Sub, "error", err)
apiresponse.WriteError(w, r, http.StatusInternalServerError, apiresponse.CodeInternal, "failed to provision user", nil)
return
}
}
if audit != nil {
audit.Log(r.Context(), claims.Sub, securityaudit.ActionLogin, map[string]any{
"email": claims.Email,
"path": r.URL.Path,
"method": r.Method,
})
}
ctx := context.WithValue(r.Context(), claimsKey, claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func ClaimsFromContext(ctx context.Context) *auth.Claims {
claims, _ := ctx.Value(claimsKey).(*auth.Claims)
return claims
}