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
|
// @securityDefinitions.apikey BearerAuth
|
||||||
// @in header
|
// @in header
|
||||||
// @name Authorization
|
// @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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": []
|
"BearerAuth": [],
|
||||||
|
"OrgSlugAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
@@ -56,7 +57,8 @@
|
|||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": []
|
"BearerAuth": [],
|
||||||
|
"OrgSlugAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@@ -113,7 +115,8 @@
|
|||||||
"delete": {
|
"delete": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": []
|
"BearerAuth": [],
|
||||||
|
"OrgSlugAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
@@ -163,7 +166,8 @@
|
|||||||
"patch": {
|
"patch": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": []
|
"BearerAuth": [],
|
||||||
|
"OrgSlugAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@@ -233,7 +237,8 @@
|
|||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": []
|
"BearerAuth": [],
|
||||||
|
"OrgSlugAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
@@ -282,7 +287,8 @@
|
|||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"BearerAuth": []
|
"BearerAuth": [],
|
||||||
|
"OrgSlugAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
@@ -494,10 +500,22 @@
|
|||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
"BearerAuth": {
|
"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",
|
"type": "apiKey",
|
||||||
"name": "Authorization",
|
"name": "Authorization",
|
||||||
"in": "header"
|
"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'
|
$ref: '#/definitions/handlers.errorResponse'
|
||||||
security:
|
security:
|
||||||
- BearerAuth: []
|
- BearerAuth: []
|
||||||
|
OrgSlugAuth: []
|
||||||
summary: List schedules for a workspace
|
summary: List schedules for a workspace
|
||||||
tags:
|
tags:
|
||||||
- schedules
|
- schedules
|
||||||
@@ -173,6 +174,7 @@ paths:
|
|||||||
$ref: '#/definitions/handlers.errorResponse'
|
$ref: '#/definitions/handlers.errorResponse'
|
||||||
security:
|
security:
|
||||||
- BearerAuth: []
|
- BearerAuth: []
|
||||||
|
OrgSlugAuth: []
|
||||||
summary: Create a schedule
|
summary: Create a schedule
|
||||||
tags:
|
tags:
|
||||||
- schedules
|
- schedules
|
||||||
@@ -206,6 +208,7 @@ paths:
|
|||||||
$ref: '#/definitions/handlers.errorResponse'
|
$ref: '#/definitions/handlers.errorResponse'
|
||||||
security:
|
security:
|
||||||
- BearerAuth: []
|
- BearerAuth: []
|
||||||
|
OrgSlugAuth: []
|
||||||
summary: Delete a schedule
|
summary: Delete a schedule
|
||||||
tags:
|
tags:
|
||||||
- schedules
|
- schedules
|
||||||
@@ -250,6 +253,7 @@ paths:
|
|||||||
$ref: '#/definitions/handlers.errorResponse'
|
$ref: '#/definitions/handlers.errorResponse'
|
||||||
security:
|
security:
|
||||||
- BearerAuth: []
|
- BearerAuth: []
|
||||||
|
OrgSlugAuth: []
|
||||||
summary: Update a schedule
|
summary: Update a schedule
|
||||||
tags:
|
tags:
|
||||||
- schedules
|
- schedules
|
||||||
@@ -281,6 +285,7 @@ paths:
|
|||||||
$ref: '#/definitions/handlers.errorResponse'
|
$ref: '#/definitions/handlers.errorResponse'
|
||||||
security:
|
security:
|
||||||
- BearerAuth: []
|
- BearerAuth: []
|
||||||
|
OrgSlugAuth: []
|
||||||
summary: Get past runs of a schedule
|
summary: Get past runs of a schedule
|
||||||
tags:
|
tags:
|
||||||
- schedules
|
- schedules
|
||||||
@@ -314,6 +319,7 @@ paths:
|
|||||||
$ref: '#/definitions/handlers.errorResponse'
|
$ref: '#/definitions/handlers.errorResponse'
|
||||||
security:
|
security:
|
||||||
- BearerAuth: []
|
- BearerAuth: []
|
||||||
|
OrgSlugAuth: []
|
||||||
summary: Fire a schedule manually
|
summary: Fire a schedule manually
|
||||||
tags:
|
tags:
|
||||||
- schedules
|
- schedules
|
||||||
@@ -321,9 +327,23 @@ schemes:
|
|||||||
- https
|
- https
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
BearerAuth:
|
BearerAuth:
|
||||||
description: Bearer token. The platform also reads X-Molecule-Org-Slug or X-Molecule-Org-Id
|
description: Bearer token issued by Gitea (org-admin or persona PAT) or by the
|
||||||
for tenant routing.
|
platform's signup/SSO flow.
|
||||||
in: header
|
in: header
|
||||||
name: Authorization
|
name: Authorization
|
||||||
type: apiKey
|
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"
|
swagger: "2.0"
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ type scheduleResponse struct {
|
|||||||
// @Success 200 {array} scheduleResponse
|
// @Success 200 {array} scheduleResponse
|
||||||
// @Failure 500 {object} errorResponse
|
// @Failure 500 {object} errorResponse
|
||||||
// @Router /workspaces/{id}/schedules [get]
|
// @Router /workspaces/{id}/schedules [get]
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth && OrgSlugAuth
|
||||||
func (h *ScheduleHandler) List(c *gin.Context) {
|
func (h *ScheduleHandler) List(c *gin.Context) {
|
||||||
workspaceID := c.Param("id")
|
workspaceID := c.Param("id")
|
||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
@@ -140,7 +140,7 @@ type createScheduleRequest struct {
|
|||||||
// @Failure 400 {object} errorResponse
|
// @Failure 400 {object} errorResponse
|
||||||
// @Failure 500 {object} errorResponse
|
// @Failure 500 {object} errorResponse
|
||||||
// @Router /workspaces/{id}/schedules [post]
|
// @Router /workspaces/{id}/schedules [post]
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth && OrgSlugAuth
|
||||||
func (h *ScheduleHandler) Create(c *gin.Context) {
|
func (h *ScheduleHandler) Create(c *gin.Context) {
|
||||||
workspaceID := c.Param("id")
|
workspaceID := c.Param("id")
|
||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
@@ -222,7 +222,7 @@ type updateScheduleRequest struct {
|
|||||||
// @Failure 404 {object} errorResponse
|
// @Failure 404 {object} errorResponse
|
||||||
// @Failure 500 {object} errorResponse
|
// @Failure 500 {object} errorResponse
|
||||||
// @Router /workspaces/{id}/schedules/{scheduleId} [patch]
|
// @Router /workspaces/{id}/schedules/{scheduleId} [patch]
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth && OrgSlugAuth
|
||||||
func (h *ScheduleHandler) Update(c *gin.Context) {
|
func (h *ScheduleHandler) Update(c *gin.Context) {
|
||||||
scheduleID := c.Param("scheduleId")
|
scheduleID := c.Param("scheduleId")
|
||||||
workspaceID := c.Param("id") // #113: bind to owning workspace to prevent IDOR
|
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 404 {object} errorResponse
|
||||||
// @Failure 500 {object} errorResponse
|
// @Failure 500 {object} errorResponse
|
||||||
// @Router /workspaces/{id}/schedules/{scheduleId} [delete]
|
// @Router /workspaces/{id}/schedules/{scheduleId} [delete]
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth && OrgSlugAuth
|
||||||
func (h *ScheduleHandler) Delete(c *gin.Context) {
|
func (h *ScheduleHandler) Delete(c *gin.Context) {
|
||||||
scheduleID := c.Param("scheduleId")
|
scheduleID := c.Param("scheduleId")
|
||||||
workspaceID := c.Param("id") // #113: bind to owning workspace to prevent IDOR
|
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 404 {object} errorResponse
|
||||||
// @Failure 500 {object} errorResponse
|
// @Failure 500 {object} errorResponse
|
||||||
// @Router /workspaces/{id}/schedules/{scheduleId}/run [post]
|
// @Router /workspaces/{id}/schedules/{scheduleId}/run [post]
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth && OrgSlugAuth
|
||||||
func (h *ScheduleHandler) RunNow(c *gin.Context) {
|
func (h *ScheduleHandler) RunNow(c *gin.Context) {
|
||||||
scheduleID := c.Param("scheduleId")
|
scheduleID := c.Param("scheduleId")
|
||||||
workspaceID := c.Param("id")
|
workspaceID := c.Param("id")
|
||||||
@@ -381,7 +381,7 @@ func (h *ScheduleHandler) RunNow(c *gin.Context) {
|
|||||||
// @Success 200 {array} historyEntry
|
// @Success 200 {array} historyEntry
|
||||||
// @Failure 500 {object} errorResponse
|
// @Failure 500 {object} errorResponse
|
||||||
// @Router /workspaces/{id}/schedules/{scheduleId}/history [get]
|
// @Router /workspaces/{id}/schedules/{scheduleId}/history [get]
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth && OrgSlugAuth
|
||||||
func (h *ScheduleHandler) History(c *gin.Context) {
|
func (h *ScheduleHandler) History(c *gin.Context) {
|
||||||
scheduleID := c.Param("scheduleId")
|
scheduleID := c.Param("scheduleId")
|
||||||
workspaceID := c.Param("id")
|
workspaceID := c.Param("id")
|
||||||
|
|||||||
Reference in New Issue
Block a user