Skip to content

Commit 88b6b44

Browse files
darshana-vsaucam
andauthored
Refactor: refactor out /api/v1 prefix from admin route paths (#62)
* fix: (refactor) remove /api/v1 prefix from admin route paths * fix: admin route path integration tests * fix: include updated admin routes in startup log message * fix: Use path prefix for clean separation * fix: Review comments --------- Co-authored-by: Yash Datta <yd2590@columbia.edu>
1 parent 76779a2 commit 88b6b44

16 files changed

Lines changed: 155 additions & 108 deletions

config.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import (
1414
"github.com/knadh/koanf/v2"
1515
)
1616

17+
// DefaultAdminPathPrefix is the default URL prefix for admin API routes.
18+
// Standalone ZeroID serves admin routes at /api/v1/*. Deployers can override
19+
// this via ServerConfig.AdminPathPrefix.
20+
const DefaultAdminPathPrefix = "/api/v1"
21+
1722
// Config holds the complete ZeroID service configuration.
1823
type Config struct {
1924
Server ServerConfig `koanf:"server"`
@@ -35,6 +40,25 @@ type ServerConfig struct {
3540
WriteTimeout string `koanf:"write_timeout"`
3641
IdleTimeout string `koanf:"idle_timeout"`
3742
ShutdownTimeoutSeconds int `koanf:"shutdown_timeout_seconds"`
43+
44+
// AdminPathPrefix is the URL prefix for admin API routes (identities, agents,
45+
// credentials, etc.). Defaults to "/api/v1" for standalone deployments.
46+
//
47+
// Deployers that mount ZeroID under their own path structure can override this.
48+
// For example, highflame-authn sets this to "" and mounts the router at "/v1/auth"
49+
// so admin routes become /v1/auth/identities/schema instead of /api/v1/identities/schema.
50+
//
51+
// Set to empty string ("") to register admin routes at the router root.
52+
AdminPathPrefix *string `koanf:"admin_path_prefix"`
53+
}
54+
55+
// GetAdminPathPrefix returns the admin route prefix. Defaults to "/api/v1"
56+
// when not explicitly set.
57+
func (s *ServerConfig) GetAdminPathPrefix() string {
58+
if s.AdminPathPrefix != nil {
59+
return *s.AdminPathPrefix
60+
}
61+
return DefaultAdminPathPrefix
3862
}
3963

4064
// DatabaseConfig holds PostgreSQL connection settings.
@@ -196,6 +220,9 @@ func loadDefaults(k *koanf.Koanf) error {
196220
"telemetry.service_name": "zeroid",
197221
"telemetry.sampling_rate": 1.0,
198222

223+
// Admin path prefix
224+
"server.admin_path_prefix": DefaultAdminPathPrefix,
225+
199226
// Logging
200227
"logging.level": "info",
201228
}
@@ -211,8 +238,9 @@ func loadDefaults(k *koanf.Koanf) error {
211238
func loadEnvVars(k *koanf.Koanf) error {
212239
envMapping := map[string]string{
213240
// Server
214-
"ZEROID_PORT": "server.port",
215-
"ZEROID_ENV": "server.env",
241+
"ZEROID_PORT": "server.port",
242+
"ZEROID_ENV": "server.env",
243+
"ZEROID_ADMIN_PATH_PREFIX": "server.admin_path_prefix",
216244

217245
// Database
218246
"ZEROID_DATABASE_URL": "database.url",
@@ -251,8 +279,8 @@ func loadEnvVars(k *koanf.Koanf) error {
251279
}
252280

253281
for envVar, configPath := range envMapping {
254-
value := os.Getenv(envVar)
255-
if value == "" {
282+
value, ok := os.LookupEnv(envVar)
283+
if !ok {
256284
continue
257285
}
258286

internal/handler/agent.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func (a *API) registerAgentRoutes(api huma.API) {
122122
huma.Register(api, huma.Operation{
123123
OperationID: "register-agent",
124124
Method: http.MethodPost,
125-
Path: "/api/v1/agents/register",
125+
Path: "/agents/register",
126126
Summary: "Register a new agent (creates identity + API key atomically)",
127127
Tags: []string{"Agents"},
128128
DefaultStatus: http.StatusCreated,
@@ -131,31 +131,31 @@ func (a *API) registerAgentRoutes(api huma.API) {
131131
huma.Register(api, huma.Operation{
132132
OperationID: "get-agent",
133133
Method: http.MethodGet,
134-
Path: "/api/v1/agents/registry/{id}",
134+
Path: "/agents/registry/{id}",
135135
Summary: "Get an agent by identity ID",
136136
Tags: []string{"Agents"},
137137
}, a.getAgentOp)
138138

139139
huma.Register(api, huma.Operation{
140140
OperationID: "list-agents",
141141
Method: http.MethodGet,
142-
Path: "/api/v1/agents/registry",
142+
Path: "/agents/registry",
143143
Summary: "List agents for the current tenant",
144144
Tags: []string{"Agents"},
145145
}, a.listAgentsOp)
146146

147147
huma.Register(api, huma.Operation{
148148
OperationID: "update-agent",
149149
Method: http.MethodPatch,
150-
Path: "/api/v1/agents/registry/{id}",
150+
Path: "/agents/registry/{id}",
151151
Summary: "Update mutable fields of an agent",
152152
Tags: []string{"Agents"},
153153
}, a.updateAgentOp)
154154

155155
huma.Register(api, huma.Operation{
156156
OperationID: "delete-agent",
157157
Method: http.MethodDelete,
158-
Path: "/api/v1/agents/registry/{id}",
158+
Path: "/agents/registry/{id}",
159159
Summary: "Deactivate an agent (soft delete) and revoke its keys",
160160
Tags: []string{"Agents"},
161161
DefaultStatus: http.StatusOK,
@@ -164,23 +164,23 @@ func (a *API) registerAgentRoutes(api huma.API) {
164164
huma.Register(api, huma.Operation{
165165
OperationID: "activate-agent",
166166
Method: http.MethodPost,
167-
Path: "/api/v1/agents/registry/{id}/activate",
167+
Path: "/agents/registry/{id}/activate",
168168
Summary: "Activate a previously deactivated agent",
169169
Tags: []string{"Agents"},
170170
}, a.activateAgentOp)
171171

172172
huma.Register(api, huma.Operation{
173173
OperationID: "deactivate-agent",
174174
Method: http.MethodPost,
175-
Path: "/api/v1/agents/registry/{id}/deactivate",
175+
Path: "/agents/registry/{id}/deactivate",
176176
Summary: "Deactivate an agent without deleting it",
177177
Tags: []string{"Agents"},
178178
}, a.deactivateAgentOp)
179179

180180
huma.Register(api, huma.Operation{
181181
OperationID: "rotate-agent-key",
182182
Method: http.MethodPost,
183-
Path: "/api/v1/agents/registry/{id}/rotate-key",
183+
Path: "/agents/registry/{id}/rotate-key",
184184
Summary: "Rotate an agent's API key (revokes old, issues new)",
185185
Tags: []string{"Agents"},
186186
}, a.rotateAgentKeyOp)

internal/handler/apikey.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (a *API) registerAPIKeyRoutes(api huma.API) {
7676
huma.Register(api, huma.Operation{
7777
OperationID: "create-api-key",
7878
Method: http.MethodPost,
79-
Path: "/api/v1/api-keys",
79+
Path: "/api-keys",
8080
Summary: "Create a new API key",
8181
Tags: []string{"API Keys"},
8282
DefaultStatus: http.StatusCreated,
@@ -85,23 +85,23 @@ func (a *API) registerAPIKeyRoutes(api huma.API) {
8585
huma.Register(api, huma.Operation{
8686
OperationID: "get-api-key",
8787
Method: http.MethodGet,
88-
Path: "/api/v1/api-keys/{id}",
88+
Path: "/api-keys/{id}",
8989
Summary: "Get an API key by ID",
9090
Tags: []string{"API Keys"},
9191
}, a.getAPIKeyOp)
9292

9393
huma.Register(api, huma.Operation{
9494
OperationID: "list-api-keys",
9595
Method: http.MethodGet,
96-
Path: "/api/v1/api-keys",
96+
Path: "/api-keys",
9797
Summary: "List API keys for the current tenant",
9898
Tags: []string{"API Keys"},
9999
}, a.listAPIKeysOp)
100100

101101
huma.Register(api, huma.Operation{
102102
OperationID: "revoke-api-key",
103103
Method: http.MethodPost,
104-
Path: "/api/v1/api-keys/{id}/revoke",
104+
Path: "/api-keys/{id}/revoke",
105105
Summary: "Revoke an API key",
106106
Tags: []string{"API Keys"},
107107
}, a.revokeAPIKeyOp)

internal/handler/attestation.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (a *API) registerAttestationRoutes(api huma.API) {
5050
huma.Register(api, huma.Operation{
5151
OperationID: "submit-attestation",
5252
Method: http.MethodPost,
53-
Path: "/api/v1/attestation/submit",
53+
Path: "/attestation/submit",
5454
Summary: "Submit an attestation proof for an agent identity",
5555
Tags: []string{"Attestation"},
5656
DefaultStatus: http.StatusCreated,
@@ -59,15 +59,15 @@ func (a *API) registerAttestationRoutes(api huma.API) {
5959
huma.Register(api, huma.Operation{
6060
OperationID: "verify-attestation",
6161
Method: http.MethodPost,
62-
Path: "/api/v1/attestation/verify",
62+
Path: "/attestation/verify",
6363
Summary: "Verify an attestation and promote trust level",
6464
Tags: []string{"Attestation"},
6565
}, a.verifyAttestationOp)
6666

6767
huma.Register(api, huma.Operation{
6868
OperationID: "get-attestation",
6969
Method: http.MethodGet,
70-
Path: "/api/v1/attestation/{id}",
70+
Path: "/attestation/{id}",
7171
Summary: "Get an attestation record by ID",
7272
Tags: []string{"Attestation"},
7373
}, a.getAttestationOp)

internal/handler/credential.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (a *API) registerCredentialRoutes(api huma.API) {
7171
huma.Register(api, huma.Operation{
7272
OperationID: "issue-credential",
7373
Method: http.MethodPost,
74-
Path: "/api/v1/credentials/issue",
74+
Path: "/credentials/issue",
7575
Summary: "Issue a short-lived JWT credential for an agent identity",
7676
Tags: []string{"Credentials"},
7777
DefaultStatus: http.StatusCreated,
@@ -80,31 +80,31 @@ func (a *API) registerCredentialRoutes(api huma.API) {
8080
huma.Register(api, huma.Operation{
8181
OperationID: "get-credential",
8282
Method: http.MethodGet,
83-
Path: "/api/v1/credentials/{id}",
83+
Path: "/credentials/{id}",
8484
Summary: "Get a credential record by ID",
8585
Tags: []string{"Credentials"},
8686
}, a.getCredentialOp)
8787

8888
huma.Register(api, huma.Operation{
8989
OperationID: "list-credentials",
9090
Method: http.MethodGet,
91-
Path: "/api/v1/credentials",
91+
Path: "/credentials",
9292
Summary: "List credentials for an identity",
9393
Tags: []string{"Credentials"},
9494
}, a.listCredentialsOp)
9595

9696
huma.Register(api, huma.Operation{
9797
OperationID: "revoke-credential",
9898
Method: http.MethodPost,
99-
Path: "/api/v1/credentials/{id}/revoke",
99+
Path: "/credentials/{id}/revoke",
100100
Summary: "Revoke a credential",
101101
Tags: []string{"Credentials"},
102102
}, a.revokeCredentialOp)
103103

104104
huma.Register(api, huma.Operation{
105105
OperationID: "rotate-credential",
106106
Method: http.MethodPost,
107-
Path: "/api/v1/credentials/{id}/rotate",
107+
Path: "/credentials/{id}/rotate",
108108
Summary: "Rotate a credential (revoke old + issue new)",
109109
Tags: []string{"Credentials"},
110110
DefaultStatus: http.StatusCreated,

internal/handler/credential_policy.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (a *API) registerCredentialPolicyRoutes(api huma.API) {
6464
huma.Register(api, huma.Operation{
6565
OperationID: "create-credential-policy",
6666
Method: http.MethodPost,
67-
Path: "/api/v1/credential-policies",
67+
Path: "/credential-policies",
6868
Summary: "Create a credential policy",
6969
Tags: []string{"Credential Policies"},
7070
DefaultStatus: http.StatusCreated,
@@ -73,31 +73,31 @@ func (a *API) registerCredentialPolicyRoutes(api huma.API) {
7373
huma.Register(api, huma.Operation{
7474
OperationID: "get-credential-policy",
7575
Method: http.MethodGet,
76-
Path: "/api/v1/credential-policies/{id}",
76+
Path: "/credential-policies/{id}",
7777
Summary: "Get a credential policy by ID",
7878
Tags: []string{"Credential Policies"},
7979
}, a.getPolicyOp)
8080

8181
huma.Register(api, huma.Operation{
8282
OperationID: "list-credential-policies",
8383
Method: http.MethodGet,
84-
Path: "/api/v1/credential-policies",
84+
Path: "/credential-policies",
8585
Summary: "List credential policies for the current tenant",
8686
Tags: []string{"Credential Policies"},
8787
}, a.listPoliciesOp)
8888

8989
huma.Register(api, huma.Operation{
9090
OperationID: "update-credential-policy",
9191
Method: http.MethodPatch,
92-
Path: "/api/v1/credential-policies/{id}",
92+
Path: "/credential-policies/{id}",
9393
Summary: "Update a credential policy",
9494
Tags: []string{"Credential Policies"},
9595
}, a.updatePolicyOp)
9696

9797
huma.Register(api, huma.Operation{
9898
OperationID: "delete-credential-policy",
9999
Method: http.MethodDelete,
100-
Path: "/api/v1/credential-policies/{id}",
100+
Path: "/credential-policies/{id}",
101101
Summary: "Delete a credential policy",
102102
Tags: []string{"Credential Policies"},
103103
DefaultStatus: http.StatusNoContent,

internal/handler/identity.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,15 @@ func (a *API) registerIdentityRoutes(api huma.API) {
9393
huma.Register(api, huma.Operation{
9494
OperationID: "identity-schema",
9595
Method: http.MethodGet,
96-
Path: "/api/v1/identities/schema",
96+
Path: "/identities/schema",
9797
Summary: "Get the identity type schema (valid types, sub-types, trust levels, statuses)",
9898
Tags: []string{"Identities"},
9999
}, a.identitySchemaOp)
100100

101101
huma.Register(api, huma.Operation{
102102
OperationID: "create-identity",
103103
Method: http.MethodPost,
104-
Path: "/api/v1/identities",
104+
Path: "/identities",
105105
Summary: "Register a new identity",
106106
Tags: []string{"Identities"},
107107
DefaultStatus: http.StatusCreated,
@@ -110,31 +110,31 @@ func (a *API) registerIdentityRoutes(api huma.API) {
110110
huma.Register(api, huma.Operation{
111111
OperationID: "get-identity",
112112
Method: http.MethodGet,
113-
Path: "/api/v1/identities/{id}",
113+
Path: "/identities/{id}",
114114
Summary: "Get an identity by ID",
115115
Tags: []string{"Identities"},
116116
}, a.getIdentityOp)
117117

118118
huma.Register(api, huma.Operation{
119119
OperationID: "list-identities",
120120
Method: http.MethodGet,
121-
Path: "/api/v1/identities",
121+
Path: "/identities",
122122
Summary: "List all identities for the current tenant",
123123
Tags: []string{"Identities"},
124124
}, a.listIdentitiesOp)
125125

126126
huma.Register(api, huma.Operation{
127127
OperationID: "update-identity",
128128
Method: http.MethodPatch,
129-
Path: "/api/v1/identities/{id}",
129+
Path: "/identities/{id}",
130130
Summary: "Update mutable fields of an identity",
131131
Tags: []string{"Identities"},
132132
}, a.updateIdentityOp)
133133

134134
huma.Register(api, huma.Operation{
135135
OperationID: "delete-identity",
136136
Method: http.MethodDelete,
137-
Path: "/api/v1/identities/{id}",
137+
Path: "/identities/{id}",
138138
Summary: "Deactivate an identity (soft delete)",
139139
Tags: []string{"Identities"},
140140
DefaultStatus: http.StatusNoContent,

internal/handler/oauth_client.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func (a *API) registerOAuthClientRoutes(api huma.API) {
8787
huma.Register(api, huma.Operation{
8888
OperationID: "create-oauth-client",
8989
Method: http.MethodPost,
90-
Path: "/api/v1/oauth/clients",
90+
Path: "/oauth/clients",
9191
Summary: "Register an OAuth2 client",
9292
Tags: []string{"OAuth Clients"},
9393
DefaultStatus: http.StatusCreated,
@@ -96,31 +96,31 @@ func (a *API) registerOAuthClientRoutes(api huma.API) {
9696
huma.Register(api, huma.Operation{
9797
OperationID: "get-oauth-client",
9898
Method: http.MethodGet,
99-
Path: "/api/v1/oauth/clients/{id}",
99+
Path: "/oauth/clients/{id}",
100100
Summary: "Get an OAuth2 client by ID",
101101
Tags: []string{"OAuth Clients"},
102102
}, a.getOAuthClientOp)
103103

104104
huma.Register(api, huma.Operation{
105105
OperationID: "list-oauth-clients",
106106
Method: http.MethodGet,
107-
Path: "/api/v1/oauth/clients",
107+
Path: "/oauth/clients",
108108
Summary: "List all registered OAuth2 clients",
109109
Tags: []string{"OAuth Clients"},
110110
}, a.listOAuthClientsOp)
111111

112112
huma.Register(api, huma.Operation{
113113
OperationID: "rotate-oauth-client-secret",
114114
Method: http.MethodPost,
115-
Path: "/api/v1/oauth/clients/{id}/rotate-secret",
115+
Path: "/oauth/clients/{id}/rotate-secret",
116116
Summary: "Rotate an OAuth2 client secret",
117117
Tags: []string{"OAuth Clients"},
118118
}, a.rotateOAuthClientSecretOp)
119119

120120
huma.Register(api, huma.Operation{
121121
OperationID: "delete-oauth-client",
122122
Method: http.MethodDelete,
123-
Path: "/api/v1/oauth/clients/{id}",
123+
Path: "/oauth/clients/{id}",
124124
Summary: "Delete an OAuth2 client",
125125
Tags: []string{"OAuth Clients"},
126126
}, a.deleteOAuthClientOp)

0 commit comments

Comments
 (0)