feat(api): implement API key management and authentication enhancements
CI / changes (push) Successful in 7s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 25s
CI / web (push) Successful in 28s
CI / go (push) Failing after 24s
CI / bird2 (push) Has been skipped
CI / release (push) Has been skipped
CI / changes (push) Successful in 7s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 25s
CI / web (push) Successful in 28s
CI / go (push) Failing after 24s
CI / bird2 (push) Has been skipped
CI / release (push) Has been skipped
- Added endpoints for managing API keys, including creation, retrieval, updating, and revocation. - Introduced a new Auth session endpoint to retrieve current tenant and role information. - Updated the authentication middleware to support API key-based authentication and track last used timestamps. - Enhanced documentation to reflect new API key functionalities and usage guidelines. - Improved logging for demo authentication scenarios.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBearerDevWithoutInsecureDev(t *testing.T) {
|
||||
srv, err := New(Options{SeedDemo: true, BundleSeedHex: testBundleSeed})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer srv.Close()
|
||||
|
||||
ts := httptest.NewServer(srv.Handler())
|
||||
defer ts.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, ts.URL+"/v1/modules?limit=1", nil)
|
||||
req.Header.Set("Authorization", "Bearer dev")
|
||||
resp, err := ts.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
t.Fatalf("status=%d body=%s", resp.StatusCode, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIKeysCRUDAndAuth(t *testing.T) {
|
||||
srv, err := New(Options{SeedDemo: true, BundleSeedHex: testBundleSeed})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer srv.Close()
|
||||
tenant, _, _, _, _ := srv.Store().DemoIDs()
|
||||
mustSetTestAPIKeys(t, srv, "opkey|"+tenant+"|operator")
|
||||
|
||||
ts := httptest.NewServer(srv.Handler())
|
||||
defer ts.Close()
|
||||
client := ts.Client()
|
||||
base := ts.URL
|
||||
|
||||
reqCreate, _ := http.NewRequest(http.MethodPost, base+"/v1/api-keys", strings.NewReader(`{"name":"ci","role":"editor"}`))
|
||||
reqCreate.Header.Set("Authorization", "Bearer opkey")
|
||||
reqCreate.Header.Set("Content-Type", "application/json")
|
||||
respCreate, err := client.Do(reqCreate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer respCreate.Body.Close()
|
||||
if respCreate.StatusCode != http.StatusCreated {
|
||||
b, _ := io.ReadAll(respCreate.Body)
|
||||
t.Fatalf("create status=%d body=%s", respCreate.StatusCode, b)
|
||||
}
|
||||
var created map[string]any
|
||||
if err := json.NewDecoder(respCreate.Body).Decode(&created); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
token, _ := created["token"].(string)
|
||||
if token == "" {
|
||||
t.Fatal("missing token in create response")
|
||||
}
|
||||
id, _ := created["id"].(string)
|
||||
if id == "" {
|
||||
t.Fatal("missing id")
|
||||
}
|
||||
|
||||
reqMod, _ := http.NewRequest(http.MethodGet, base+"/v1/modules?limit=1", nil)
|
||||
reqMod.Header.Set("Authorization", "Bearer "+token)
|
||||
respMod, err := client.Do(reqMod)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer respMod.Body.Close()
|
||||
if respMod.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(respMod.Body)
|
||||
t.Fatalf("modules status=%d body=%s", respMod.StatusCode, b)
|
||||
}
|
||||
|
||||
reqDel, _ := http.NewRequest(http.MethodDelete, base+"/v1/api-keys/"+id, nil)
|
||||
reqDel.Header.Set("Authorization", "Bearer opkey")
|
||||
respDel, err := client.Do(reqDel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer respDel.Body.Close()
|
||||
if respDel.StatusCode != http.StatusNoContent {
|
||||
t.Fatalf("delete status=%d", respDel.StatusCode)
|
||||
}
|
||||
|
||||
reqAfter, _ := http.NewRequest(http.MethodGet, base+"/v1/modules?limit=1", nil)
|
||||
reqAfter.Header.Set("Authorization", "Bearer "+token)
|
||||
respAfter, err := client.Do(reqAfter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer respAfter.Body.Close()
|
||||
if respAfter.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("expected 401 after revoke, got %d", respAfter.StatusCode)
|
||||
}
|
||||
|
||||
mustSetTestAPIKeys(t, srv, "nodekey|"+tenant+"|node,opkey|"+tenant+"|operator")
|
||||
reqNode2, _ := http.NewRequest(http.MethodGet, base+"/v1/api-keys", nil)
|
||||
reqNode2.Header.Set("Authorization", "Bearer nodekey")
|
||||
respNode, err := client.Do(reqNode2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer respNode.Body.Close()
|
||||
if respNode.StatusCode != http.StatusForbidden {
|
||||
t.Fatalf("node list api-keys status=%d want 403", respNode.StatusCode)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user