Files
schemas/middleware/auth.go
T
argoyle 4468903535 feat(cache): implement hashed API key storage and retrieval
Adds a new hashed key storage mechanism for API keys in the cache. 
Replaces direct mapping to API keys with composite keys based on 
organizationId and name. Implements searching of API keys using 
hash comparisons for improved security. Updates related tests to 
ensure correct functionality and validate the hashing. Also, 
adds support for a new dependency `golang.org/x/crypto`.
2025-11-20 22:11:24 +01:00

92 lines
2.2 KiB
Go

package middleware
import (
"context"
"fmt"
"net/http"
"github.com/99designs/gqlgen/graphql"
"github.com/golang-jwt/jwt/v5"
"gitlab.com/unboundsoftware/schemas/domain"
)
const (
UserKey = ContextKey("user")
OrganizationKey = ContextKey("organization")
)
type Cache interface {
OrganizationByAPIKey(apiKey string) *domain.Organization
}
func NewAuth(cache Cache) *AuthMiddleware {
return &AuthMiddleware{
cache: cache,
}
}
type AuthMiddleware struct {
cache Cache
}
func (m *AuthMiddleware) Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
token, err := TokenFromContext(r.Context())
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Invalid JWT token format"))
return
}
if token != nil {
ctx = context.WithValue(ctx, UserKey, token.Claims.(jwt.MapClaims)["sub"])
}
apiKey, err := ApiKeyFromContext(r.Context())
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Invalid API Key format"))
return
}
// Cache handles hash comparison internally
organization := m.cache.OrganizationByAPIKey(apiKey)
if organization != nil {
ctx = context.WithValue(ctx, OrganizationKey, *organization)
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func UserFromContext(ctx context.Context) string {
if value := ctx.Value(UserKey); value != nil {
if u, ok := value.(string); ok {
return u
}
}
return ""
}
func OrganizationFromContext(ctx context.Context) string {
if value := ctx.Value(OrganizationKey); value != nil {
if u, ok := value.(domain.Organization); ok {
return u.ID.String()
}
}
return ""
}
func (m *AuthMiddleware) Directive(ctx context.Context, _ interface{}, next graphql.Resolver, user *bool, organization *bool) (res interface{}, err error) {
if user != nil && *user {
if u := UserFromContext(ctx); u == "" {
return nil, fmt.Errorf("no user available in request")
}
}
if organization != nil && *organization {
if orgId := OrganizationFromContext(ctx); orgId == "" {
return nil, fmt.Errorf("no organization available in request")
}
}
return next(ctx)
}