From f01636cd76b7495a3ed69ef8d151e27d180f7bad Mon Sep 17 00:00:00 2001 From: hongming-ceo-delegated Date: Fri, 22 May 2026 23:51:54 -0700 Subject: [PATCH] Address CR2 review: add OrgSlugAuth + OrgIdAuth securityDefinitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CR2 (5-axis review) flagged: the original PR mentioned X-Molecule-Org-Slug and X-Molecule-Org-Id only in the BearerAuth @description prose. Generated SDK clients (per RFC #1706 Phases 3-6) wouldn't actually send those headers, so any client built from the spec would 401/403 against prod. Fix: - Add OrgSlugAuth + OrgIdAuth as formal apikey securityDefinitions in cmd/server/main.go - Bind every annotated endpoint with `@Security BearerAuth && OrgSlugAuth` (AND semantics — generated clients send both headers, not OR) - Regenerate docs/openapi/swagger.{yaml,json} Verified: swagger.yaml security blocks now show - BearerAuth: [] OrgSlugAuth: [] (single requirement object with both schemes = AND in OpenAPI 2.0). No backend behavior change. Handler tests pass. Refs: PR #1707, RFC #1706, CR2 review comment. Co-Authored-By: Claude Opus 4.7 (1M context) --- workspace-server/cmd/server/main.go | 12 ++++++- workspace-server/docs/openapi/swagger.json | 32 +++++++++++++++---- workspace-server/docs/openapi/swagger.yaml | 24 ++++++++++++-- .../internal/handlers/schedules.go | 12 +++---- 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/workspace-server/cmd/server/main.go b/workspace-server/cmd/server/main.go index 01391446..87deeebf 100644 --- a/workspace-server/cmd/server/main.go +++ b/workspace-server/cmd/server/main.go @@ -10,7 +10,17 @@ // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization -// @description Bearer token. The platform also reads X-Molecule-Org-Slug or X-Molecule-Org-Id for tenant routing. +// @description Bearer token issued by Gitea (org-admin or persona PAT) or by the platform's signup/SSO flow. +// +// @securityDefinitions.apikey OrgSlugAuth +// @in header +// @name X-Molecule-Org-Slug +// @description Tenant routing header — required on every /workspaces/{id}/* request so the platform edge can route to the correct per-tenant workspace-server. Either X-Molecule-Org-Slug (human-readable, e.g. "agents-team") or X-Molecule-Org-Id (UUID) must be sent; slug is preferred for client code. +// +// @securityDefinitions.apikey OrgIdAuth +// @in header +// @name X-Molecule-Org-Id +// @description Tenant routing header (UUID form). Alternative to X-Molecule-Org-Slug. At least one of OrgSlugAuth or OrgIdAuth must be sent alongside BearerAuth. package main import ( diff --git a/workspace-server/docs/openapi/swagger.json b/workspace-server/docs/openapi/swagger.json index 3aaf5c44..6ed94f16 100644 --- a/workspace-server/docs/openapi/swagger.json +++ b/workspace-server/docs/openapi/swagger.json @@ -16,7 +16,8 @@ "get": { "security": [ { - "BearerAuth": [] + "BearerAuth": [], + "OrgSlugAuth": [] } ], "produces": [ @@ -56,7 +57,8 @@ "post": { "security": [ { - "BearerAuth": [] + "BearerAuth": [], + "OrgSlugAuth": [] } ], "consumes": [ @@ -113,7 +115,8 @@ "delete": { "security": [ { - "BearerAuth": [] + "BearerAuth": [], + "OrgSlugAuth": [] } ], "produces": [ @@ -163,7 +166,8 @@ "patch": { "security": [ { - "BearerAuth": [] + "BearerAuth": [], + "OrgSlugAuth": [] } ], "consumes": [ @@ -233,7 +237,8 @@ "get": { "security": [ { - "BearerAuth": [] + "BearerAuth": [], + "OrgSlugAuth": [] } ], "produces": [ @@ -282,7 +287,8 @@ "post": { "security": [ { - "BearerAuth": [] + "BearerAuth": [], + "OrgSlugAuth": [] } ], "produces": [ @@ -494,10 +500,22 @@ }, "securityDefinitions": { "BearerAuth": { - "description": "Bearer token. The platform also reads X-Molecule-Org-Slug or X-Molecule-Org-Id for tenant routing.", + "description": "Bearer token issued by Gitea (org-admin or persona PAT) or by the platform's signup/SSO flow.", "type": "apiKey", "name": "Authorization", "in": "header" + }, + "OrgIdAuth": { + "description": "Tenant routing header (UUID form). Alternative to X-Molecule-Org-Slug. At least one of OrgSlugAuth or OrgIdAuth must be sent alongside BearerAuth.", + "type": "apiKey", + "name": "X-Molecule-Org-Id", + "in": "header" + }, + "OrgSlugAuth": { + "description": "Tenant routing header — required on every /workspaces/{id}/* request so the platform edge can route to the correct per-tenant workspace-server. Either X-Molecule-Org-Slug (human-readable, e.g. \"agents-team\") or X-Molecule-Org-Id (UUID) must be sent; slug is preferred for client code.", + "type": "apiKey", + "name": "X-Molecule-Org-Slug", + "in": "header" } } } \ No newline at end of file diff --git a/workspace-server/docs/openapi/swagger.yaml b/workspace-server/docs/openapi/swagger.yaml index 42b0a041..b66a87fa 100644 --- a/workspace-server/docs/openapi/swagger.yaml +++ b/workspace-server/docs/openapi/swagger.yaml @@ -138,6 +138,7 @@ paths: $ref: '#/definitions/handlers.errorResponse' security: - BearerAuth: [] + OrgSlugAuth: [] summary: List schedules for a workspace tags: - schedules @@ -173,6 +174,7 @@ paths: $ref: '#/definitions/handlers.errorResponse' security: - BearerAuth: [] + OrgSlugAuth: [] summary: Create a schedule tags: - schedules @@ -206,6 +208,7 @@ paths: $ref: '#/definitions/handlers.errorResponse' security: - BearerAuth: [] + OrgSlugAuth: [] summary: Delete a schedule tags: - schedules @@ -250,6 +253,7 @@ paths: $ref: '#/definitions/handlers.errorResponse' security: - BearerAuth: [] + OrgSlugAuth: [] summary: Update a schedule tags: - schedules @@ -281,6 +285,7 @@ paths: $ref: '#/definitions/handlers.errorResponse' security: - BearerAuth: [] + OrgSlugAuth: [] summary: Get past runs of a schedule tags: - schedules @@ -314,6 +319,7 @@ paths: $ref: '#/definitions/handlers.errorResponse' security: - BearerAuth: [] + OrgSlugAuth: [] summary: Fire a schedule manually tags: - schedules @@ -321,9 +327,23 @@ schemes: - https securityDefinitions: BearerAuth: - description: Bearer token. The platform also reads X-Molecule-Org-Slug or X-Molecule-Org-Id - for tenant routing. + description: Bearer token issued by Gitea (org-admin or persona PAT) or by the + platform's signup/SSO flow. in: header name: Authorization type: apiKey + OrgIdAuth: + description: Tenant routing header (UUID form). Alternative to X-Molecule-Org-Slug. + At least one of OrgSlugAuth or OrgIdAuth must be sent alongside BearerAuth. + in: header + name: X-Molecule-Org-Id + type: apiKey + OrgSlugAuth: + description: Tenant routing header — required on every /workspaces/{id}/* request + so the platform edge can route to the correct per-tenant workspace-server. Either + X-Molecule-Org-Slug (human-readable, e.g. "agents-team") or X-Molecule-Org-Id + (UUID) must be sent; slug is preferred for client code. + in: header + name: X-Molecule-Org-Slug + type: apiKey swagger: "2.0" diff --git a/workspace-server/internal/handlers/schedules.go b/workspace-server/internal/handlers/schedules.go index 62a9a6e3..351b2215 100644 --- a/workspace-server/internal/handlers/schedules.go +++ b/workspace-server/internal/handlers/schedules.go @@ -81,7 +81,7 @@ type scheduleResponse struct { // @Success 200 {array} scheduleResponse // @Failure 500 {object} errorResponse // @Router /workspaces/{id}/schedules [get] -// @Security BearerAuth +// @Security BearerAuth && OrgSlugAuth func (h *ScheduleHandler) List(c *gin.Context) { workspaceID := c.Param("id") ctx := c.Request.Context() @@ -140,7 +140,7 @@ type createScheduleRequest struct { // @Failure 400 {object} errorResponse // @Failure 500 {object} errorResponse // @Router /workspaces/{id}/schedules [post] -// @Security BearerAuth +// @Security BearerAuth && OrgSlugAuth func (h *ScheduleHandler) Create(c *gin.Context) { workspaceID := c.Param("id") ctx := c.Request.Context() @@ -222,7 +222,7 @@ type updateScheduleRequest struct { // @Failure 404 {object} errorResponse // @Failure 500 {object} errorResponse // @Router /workspaces/{id}/schedules/{scheduleId} [patch] -// @Security BearerAuth +// @Security BearerAuth && OrgSlugAuth func (h *ScheduleHandler) Update(c *gin.Context) { scheduleID := c.Param("scheduleId") workspaceID := c.Param("id") // #113: bind to owning workspace to prevent IDOR @@ -308,7 +308,7 @@ func (h *ScheduleHandler) Update(c *gin.Context) { // @Failure 404 {object} errorResponse // @Failure 500 {object} errorResponse // @Router /workspaces/{id}/schedules/{scheduleId} [delete] -// @Security BearerAuth +// @Security BearerAuth && OrgSlugAuth func (h *ScheduleHandler) Delete(c *gin.Context) { scheduleID := c.Param("scheduleId") workspaceID := c.Param("id") // #113: bind to owning workspace to prevent IDOR @@ -341,7 +341,7 @@ func (h *ScheduleHandler) Delete(c *gin.Context) { // @Failure 404 {object} errorResponse // @Failure 500 {object} errorResponse // @Router /workspaces/{id}/schedules/{scheduleId}/run [post] -// @Security BearerAuth +// @Security BearerAuth && OrgSlugAuth func (h *ScheduleHandler) RunNow(c *gin.Context) { scheduleID := c.Param("scheduleId") workspaceID := c.Param("id") @@ -381,7 +381,7 @@ func (h *ScheduleHandler) RunNow(c *gin.Context) { // @Success 200 {array} historyEntry // @Failure 500 {object} errorResponse // @Router /workspaces/{id}/schedules/{scheduleId}/history [get] -// @Security BearerAuth +// @Security BearerAuth && OrgSlugAuth func (h *ScheduleHandler) History(c *gin.Context) { scheduleID := c.Param("scheduleId") workspaceID := c.Param("id")