feat(api): document bearer auth in openapi
Add a bearerAuth security scheme to the generated OpenAPI document and mark non-public operations as secured, while keeping /health explicitly public. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
f030665566
commit
9afaed4d7c
2 changed files with 50 additions and 0 deletions
25
openapi.go
25
openapi.go
|
|
@ -29,6 +29,11 @@ func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) {
|
|||
},
|
||||
"paths": sb.buildPaths(groups),
|
||||
"tags": sb.buildTags(groups),
|
||||
"security": []any{
|
||||
map[string]any{
|
||||
"bearerAuth": []any{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Add component schemas for the response envelope.
|
||||
|
|
@ -54,6 +59,13 @@ func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) {
|
|||
},
|
||||
},
|
||||
},
|
||||
"securitySchemes": map[string]any{
|
||||
"bearerAuth": map[string]any{
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return json.MarshalIndent(spec, "", " ")
|
||||
|
|
@ -98,6 +110,11 @@ func (sb *SpecBuilder) buildPaths(groups []RouteGroup) map[string]any {
|
|||
"description": rd.Description,
|
||||
"tags": rd.Tags,
|
||||
"operationId": operationID(method, fullPath, operationIDs),
|
||||
"security": []any{
|
||||
map[string]any{
|
||||
"bearerAuth": []any{},
|
||||
},
|
||||
},
|
||||
"responses": map[string]any{
|
||||
"200": map[string]any{
|
||||
"description": "Successful response",
|
||||
|
|
@ -141,6 +158,14 @@ func (sb *SpecBuilder) buildPaths(groups []RouteGroup) map[string]any {
|
|||
}
|
||||
}
|
||||
|
||||
// The built-in health check remains public, so override the inherited
|
||||
// default security requirement with an explicit empty array.
|
||||
if health, ok := paths["/health"].(map[string]any); ok {
|
||||
if op, ok := health["get"].(map[string]any); ok {
|
||||
op["security"] = []any{}
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,25 @@ func TestSpecBuilder_Good_EmptyGroups(t *testing.T) {
|
|||
if !found {
|
||||
t.Fatal("expected system tag in spec")
|
||||
}
|
||||
|
||||
components := spec["components"].(map[string]any)
|
||||
securitySchemes := components["securitySchemes"].(map[string]any)
|
||||
bearerAuth := securitySchemes["bearerAuth"].(map[string]any)
|
||||
if bearerAuth["type"] != "http" {
|
||||
t.Fatalf("expected bearerAuth.type=http, got %v", bearerAuth["type"])
|
||||
}
|
||||
if bearerAuth["scheme"] != "bearer" {
|
||||
t.Fatalf("expected bearerAuth.scheme=bearer, got %v", bearerAuth["scheme"])
|
||||
}
|
||||
|
||||
security := spec["security"].([]any)
|
||||
if len(security) != 1 {
|
||||
t.Fatalf("expected one default security requirement, got %d", len(security))
|
||||
}
|
||||
req := security[0].(map[string]any)
|
||||
if _, ok := req["bearerAuth"]; !ok {
|
||||
t.Fatal("expected default bearerAuth security requirement")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_WithDescribableGroup(t *testing.T) {
|
||||
|
|
@ -351,6 +370,12 @@ func TestSpecBuilder_Good_NonDescribableGroup(t *testing.T) {
|
|||
if health["operationId"] != "get_health" {
|
||||
t.Fatalf("expected operationId='get_health', got %v", health["operationId"])
|
||||
}
|
||||
if security := health["security"]; security == nil {
|
||||
t.Fatal("expected explicit public security override on /health")
|
||||
}
|
||||
if len(health["security"].([]any)) != 0 {
|
||||
t.Fatalf("expected /health security to be empty, got %v", health["security"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_ToolBridgeIntegration(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue