Address CR2 review: add OrgSlugAuth + OrgIdAuth securityDefinitions
CI / Canvas Deploy Reminder (pull_request) Blocked by required conditions
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 12s
CI / Python Lint & Test (pull_request) Successful in 11s
E2E API Smoke Test / detect-changes (pull_request) Successful in 6s
E2E Chat / detect-changes (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 12s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 10s
Harness Replays / detect-changes (pull_request) Successful in 11s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 4s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 5s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
gate-check-v3 / gate-check (pull_request) Failing after 7s
qa-review / approved (pull_request) Failing after 5s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 9s
security-review / approved (pull_request) Failing after 10s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m8s
CI / Platform (Go) (pull_request) Failing after 2m6s
CI / Canvas (Next.js) (pull_request) Successful in 5s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 4s
CI / all-required (pull_request) Failing after 9m0s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 11s
Harness Replays / Harness Replays (pull_request) Successful in 11s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2m17s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2m24s
CI / Canvas Deploy Reminder (pull_request) Blocked by required conditions
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 12s
CI / Python Lint & Test (pull_request) Successful in 11s
E2E API Smoke Test / detect-changes (pull_request) Successful in 6s
E2E Chat / detect-changes (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 12s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 10s
Harness Replays / detect-changes (pull_request) Successful in 11s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 4s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 5s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
gate-check-v3 / gate-check (pull_request) Failing after 7s
qa-review / approved (pull_request) Failing after 5s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 9s
security-review / approved (pull_request) Failing after 10s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m8s
CI / Platform (Go) (pull_request) Failing after 2m6s
CI / Canvas (Next.js) (pull_request) Successful in 5s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 4s
CI / all-required (pull_request) Failing after 9m0s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 11s
Harness Replays / Harness Replays (pull_request) Successful in 11s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2m17s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2m24s
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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 (
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user