2026-05-07 23:46:59 -06:00
|
|
|
package httpauthfirebase
|
2026-03-19 13:44:45 +00:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"firebase.google.com/go/v4/auth"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// --- mocks ---
|
|
|
|
|
|
|
|
|
|
type mockVerifier struct {
|
|
|
|
|
token *auth.Token
|
|
|
|
|
err error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *mockVerifier) VerifyIDTokenAndCheckRevoked(_ context.Context, _ string) (*auth.Token, error) {
|
|
|
|
|
return m.token, m.err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func chain(mw func(http.Handler) http.Handler, h http.HandlerFunc) http.Handler {
|
|
|
|
|
return mw(h)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- AuthMiddleware ---
|
|
|
|
|
|
|
|
|
|
func TestAuthMiddleware_ValidToken(t *testing.T) {
|
|
|
|
|
mv := &mockVerifier{token: &auth.Token{UID: "uid123", Claims: map[string]any{"name": "Alice"}}}
|
refactor(httpauth-firebase)!: delegate enrichment and authz to httpauth v0.1.0
EnrichmentMiddleware, AuthzMiddleware, IdentityEnricher, PermissionProvider,
and related types are removed from this module. They now live in
code.nochebuena.dev/go/httpauth, the provider-agnostic middleware layer.
AuthMiddleware is updated to call httpauth.SetTokenData, fulfilling the
integration contract between provider-specific auth and generic middleware.
This module now has a single responsibility: Firebase JWT verification.
BREAKING CHANGE: IdentityEnricher, PermissionProvider, EnrichmentMiddleware,
AuthzMiddleware, and WithTenantHeader are no longer exported from this package.
Import code.nochebuena.dev/go/httpauth for those identifiers.
2026-05-07 21:57:01 -06:00
|
|
|
reached := false
|
2026-03-19 13:44:45 +00:00
|
|
|
h := chain(AuthMiddleware(mv, nil), func(w http.ResponseWriter, r *http.Request) {
|
refactor(httpauth-firebase)!: delegate enrichment and authz to httpauth v0.1.0
EnrichmentMiddleware, AuthzMiddleware, IdentityEnricher, PermissionProvider,
and related types are removed from this module. They now live in
code.nochebuena.dev/go/httpauth, the provider-agnostic middleware layer.
AuthMiddleware is updated to call httpauth.SetTokenData, fulfilling the
integration contract between provider-specific auth and generic middleware.
This module now has a single responsibility: Firebase JWT verification.
BREAKING CHANGE: IdentityEnricher, PermissionProvider, EnrichmentMiddleware,
AuthzMiddleware, and WithTenantHeader are no longer exported from this package.
Import code.nochebuena.dev/go/httpauth for those identifiers.
2026-05-07 21:57:01 -06:00
|
|
|
reached = true
|
2026-03-19 13:44:45 +00:00
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
})
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api", nil)
|
|
|
|
|
req.Header.Set("Authorization", "Bearer valid-token")
|
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
|
h.ServeHTTP(rec, req)
|
|
|
|
|
if rec.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("want 200, got %d", rec.Code)
|
|
|
|
|
}
|
refactor(httpauth-firebase)!: delegate enrichment and authz to httpauth v0.1.0
EnrichmentMiddleware, AuthzMiddleware, IdentityEnricher, PermissionProvider,
and related types are removed from this module. They now live in
code.nochebuena.dev/go/httpauth, the provider-agnostic middleware layer.
AuthMiddleware is updated to call httpauth.SetTokenData, fulfilling the
integration contract between provider-specific auth and generic middleware.
This module now has a single responsibility: Firebase JWT verification.
BREAKING CHANGE: IdentityEnricher, PermissionProvider, EnrichmentMiddleware,
AuthzMiddleware, and WithTenantHeader are no longer exported from this package.
Import code.nochebuena.dev/go/httpauth for those identifiers.
2026-05-07 21:57:01 -06:00
|
|
|
if !reached {
|
|
|
|
|
t.Error("inner handler was not called")
|
2026-03-19 13:44:45 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestAuthMiddleware_InvalidToken(t *testing.T) {
|
|
|
|
|
mv := &mockVerifier{err: errors.New("token invalid")}
|
|
|
|
|
h := chain(AuthMiddleware(mv, nil), func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
})
|
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api", nil)
|
|
|
|
|
req.Header.Set("Authorization", "Bearer bad-token")
|
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
|
h.ServeHTTP(rec, req)
|
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
|
|
|
t.Errorf("want 401, got %d", rec.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestAuthMiddleware_MissingHeader(t *testing.T) {
|
|
|
|
|
mv := &mockVerifier{}
|
|
|
|
|
h := chain(AuthMiddleware(mv, nil), func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
})
|
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api", nil))
|
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
|
|
|
t.Errorf("want 401, got %d", rec.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestAuthMiddleware_PublicPath(t *testing.T) {
|
|
|
|
|
mv := &mockVerifier{err: errors.New("should not be called")}
|
|
|
|
|
h := chain(AuthMiddleware(mv, []string{"/health"}), func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
})
|
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/health", nil))
|
|
|
|
|
if rec.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("want 200, got %d", rec.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestAuthMiddleware_PublicPathWildcard(t *testing.T) {
|
|
|
|
|
mv := &mockVerifier{err: errors.New("should not be called")}
|
|
|
|
|
h := chain(AuthMiddleware(mv, []string{"/public/*"}), func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
})
|
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/public/resource", nil))
|
|
|
|
|
if rec.Code != http.StatusOK {
|
|
|
|
|
t.Errorf("want 200, got %d", rec.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|