feat(client): add API key authentication for /authz endpoint (#294)
## Summary - Add `WithAPIKey(key string)` option to `PrivilegeHandler` - When set, `Fetch()` sends `Authorization: Bearer <key>` header - Backward compatible: no key = no header (existing behavior) ## Test plan - [x] Unit test verifying Authorization header is sent - [x] Unit test verifying no header without key - [x] Existing tests still pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #294
This commit was merged in pull request #294.
This commit is contained in:
@@ -28,6 +28,7 @@ type PrivilegeHandler struct {
|
|||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
client *http.Client
|
client *http.Client
|
||||||
baseURL string
|
baseURL string
|
||||||
|
apiKey string
|
||||||
privileges map[string]map[string]*CompanyPrivileges
|
privileges map[string]map[string]*CompanyPrivileges
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +42,13 @@ func WithBaseURL(url string) OptsFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAPIKey sets an API key used as a Bearer token when fetching privileges
|
||||||
|
func WithAPIKey(key string) OptsFunc {
|
||||||
|
return func(handler *PrivilegeHandler) {
|
||||||
|
handler.apiKey = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// New creates a new PrivilegeHandler. Pass OptsFuncs to configure.
|
// New creates a new PrivilegeHandler. Pass OptsFuncs to configure.
|
||||||
func New(opts ...OptsFunc) *PrivilegeHandler {
|
func New(opts ...OptsFunc) *PrivilegeHandler {
|
||||||
handler := &PrivilegeHandler{
|
handler := &PrivilegeHandler{
|
||||||
@@ -57,7 +65,16 @@ func New(opts ...OptsFunc) *PrivilegeHandler {
|
|||||||
|
|
||||||
// Fetch the initial set of privileges from an authz-service
|
// Fetch the initial set of privileges from an authz-service
|
||||||
func (h *PrivilegeHandler) Fetch() error {
|
func (h *PrivilegeHandler) Fetch() error {
|
||||||
resp, err := h.client.Get(fmt.Sprintf("%s/authz", h.baseURL))
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/authz", h.baseURL), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.apiKey != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+h.apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -87,13 +104,14 @@ func (h *PrivilegeHandler) Setup() []goamqp.Setup {
|
|||||||
|
|
||||||
// Process privilege-related events and update the internal state
|
// Process privilege-related events and update the internal state
|
||||||
func (h *PrivilegeHandler) Process(msg interface{}, _ goamqp.Headers) (interface{}, error) {
|
func (h *PrivilegeHandler) Process(msg interface{}, _ goamqp.Headers) (interface{}, error) {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
switch ev := msg.(type) {
|
switch ev := msg.(type) {
|
||||||
case *UserAdded:
|
case *UserAdded:
|
||||||
if priv, exists := h.privileges[ev.Email]; exists {
|
if priv, exists := h.privileges[ev.Email]; exists {
|
||||||
priv[ev.CompanyID] = &CompanyPrivileges{}
|
priv[ev.CompanyID] = &CompanyPrivileges{}
|
||||||
} else {
|
} else {
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
h.privileges[ev.Email] = map[string]*CompanyPrivileges{
|
h.privileges[ev.Email] = map[string]*CompanyPrivileges{
|
||||||
ev.CompanyID: {},
|
ev.CompanyID: {},
|
||||||
}
|
}
|
||||||
@@ -101,19 +119,13 @@ func (h *PrivilegeHandler) Process(msg interface{}, _ goamqp.Headers) (interface
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
case *UserRemoved:
|
case *UserRemoved:
|
||||||
if priv, exists := h.privileges[ev.Email]; exists {
|
if priv, exists := h.privileges[ev.Email]; exists {
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
delete(priv, ev.CompanyID)
|
delete(priv, ev.CompanyID)
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case *PrivilegeAdded:
|
case *PrivilegeAdded:
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, true)
|
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, true)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case *PrivilegeRemoved:
|
case *PrivilegeRemoved:
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, false)
|
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, false)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -251,6 +251,39 @@ func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T
|
|||||||
assert.True(t, result)
|
assert.True(t, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrivilegeHandler_Fetch_Sends_Authorization_Header_When_APIKey_Set(t *testing.T) {
|
||||||
|
var receivedAuth string
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
receivedAuth = r.Header.Get("Authorization")
|
||||||
|
_, _ = w.Write([]byte("{}"))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
handler := New(
|
||||||
|
WithBaseURL(server.URL),
|
||||||
|
WithAPIKey("my-secret-key"),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := handler.Fetch()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "Bearer my-secret-key", receivedAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivilegeHandler_Fetch_No_Authorization_Header_Without_APIKey(t *testing.T) {
|
||||||
|
var receivedAuth string
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
receivedAuth = r.Header.Get("Authorization")
|
||||||
|
_, _ = w.Write([]byte("{}"))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
handler := New(WithBaseURL(server.URL))
|
||||||
|
|
||||||
|
err := handler.Fetch()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, receivedAuth)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPrivilegeHandler_Fetch_Error_Response(t *testing.T) {
|
func TestPrivilegeHandler_Fetch_Error_Response(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
|
|||||||
Reference in New Issue
Block a user