diff --git a/openapi-public.yml b/openapi-public.yml index 7c81b888..a0213bc9 100644 --- a/openapi-public.yml +++ b/openapi-public.yml @@ -1586,7 +1586,8 @@ paths: $ref: '#/components/schemas/filesystem.CreateWatcherResponse' '502': *id003 security: - - *id005 + - SandboxAccessTokenAuth: [] + SandboxUserAuth: [] servers: - *id004 /filesystem.Filesystem/GetWatcherEvents: @@ -1654,7 +1655,8 @@ paths: $ref: '#/components/schemas/filesystem.ListDirResponse' '502': *id003 security: - - *id005 + - SandboxAccessTokenAuth: [] + SandboxUserAuth: [] servers: - *id004 /filesystem.Filesystem/MakeDir: @@ -1688,7 +1690,8 @@ paths: $ref: '#/components/schemas/filesystem.MakeDirResponse' '502': *id003 security: - - *id005 + - SandboxAccessTokenAuth: [] + SandboxUserAuth: [] servers: - *id004 /filesystem.Filesystem/Move: @@ -1722,7 +1725,8 @@ paths: $ref: '#/components/schemas/filesystem.MoveResponse' '502': *id003 security: - - *id005 + - SandboxAccessTokenAuth: [] + SandboxUserAuth: [] servers: - *id004 /filesystem.Filesystem/Remove: @@ -1756,7 +1760,8 @@ paths: $ref: '#/components/schemas/filesystem.RemoveResponse' '502': *id003 security: - - *id005 + - SandboxAccessTokenAuth: [] + SandboxUserAuth: [] servers: - *id004 /filesystem.Filesystem/RemoveWatcher: @@ -1824,7 +1829,8 @@ paths: $ref: '#/components/schemas/filesystem.StatResponse' '502': *id003 security: - - *id005 + - SandboxAccessTokenAuth: [] + SandboxUserAuth: [] servers: - *id004 /filesystem.Filesystem/WatchDir: @@ -1849,7 +1855,8 @@ paths: $ref: '#/components/schemas/filesystem.WatchDirResponse' '502': *id003 security: - - *id005 + - SandboxAccessTokenAuth: [] + SandboxUserAuth: [] parameters: - &id006 name: Connect-Protocol-Version @@ -2052,7 +2059,8 @@ paths: $ref: '#/components/schemas/process.StartResponse' '502': *id003 security: - - *id005 + - SandboxAccessTokenAuth: [] + SandboxUserAuth: [] parameters: - *id006 - *id007 @@ -2264,6 +2272,12 @@ components: (on connect), [POST /sandboxes/{sandboxID}/resume](/docs/api-reference/sandboxes/resume-a-sandbox) (on resume), and [GET /sandboxes/{sandboxID}](/docs/api-reference/sandboxes/get-a-sandbox) (for running or paused sandboxes).' + SandboxUserAuth: + type: http + scheme: basic + description: Optional system user for the operation. Sets file ownership and + resolves relative paths. Pass the desired username with no password. Defaults + to the sandbox's default user when omitted. parameters: FilePath: name: path diff --git a/scripts/generate_openapi_reference.py b/scripts/generate_openapi_reference.py index 5c79fcd1..440fdcf6 100755 --- a/scripts/generate_openapi_reference.py +++ b/scripts/generate_openapi_reference.py @@ -90,6 +90,7 @@ # Security scheme name for envd endpoints (must not collide with platform's AccessTokenAuth) SANDBOX_AUTH_SCHEME = "SandboxAccessTokenAuth" +SANDBOX_USER_SCHEME = "SandboxUserAuth" # --------------------------------------------------------------------------- # Proto parsing — auto-detect streaming RPCs @@ -575,6 +576,17 @@ def setup_sandbox_auth_scheme(spec: dict[str, Any]) -> None: "and [GET /sandboxes/{sandboxID}](/docs/api-reference/sandboxes/get-a-sandbox) (for running or paused sandboxes)." ), } + # Optional Basic auth for setting the system user on sandbox operations. + # The SDK sends: Authorization: Basic + schemes[SANDBOX_USER_SCHEME] = { + "type": "http", + "scheme": "basic", + "description": ( + "Optional system user for the operation. Sets file ownership and resolves " + "relative paths. Pass the desired username with no password. " + "Defaults to the sandbox's default user when omitted." + ), + } # Mapping of (path, method) to desired operationId for the public docs. @@ -616,6 +628,20 @@ def add_operation_ids(spec: dict[str, Any]) -> None: "/process.Process/StreamInput", } +# Connect-RPC endpoints that accept an optional user via Authorization header. +# The SDK sends: Authorization: Basic +# This is not part of the protobuf message — it must be added as an OpenAPI parameter. +USER_HEADER_ENDPOINTS = { + "/process.Process/Start", + "/filesystem.Filesystem/ListDir", + "/filesystem.Filesystem/MakeDir", + "/filesystem.Filesystem/Move", + "/filesystem.Filesystem/Remove", + "/filesystem.Filesystem/Stat", + "/filesystem.Filesystem/WatchDir", + "/filesystem.Filesystem/CreateWatcher", +} + def fix_spec_issues(spec: dict[str, Any]) -> None: """Fix known discrepancies between the source spec and the live API. @@ -1113,6 +1139,32 @@ def _singularize(word: str) -> str: print(f" {f}") +def add_user_auth_security(spec: dict[str, Any]) -> None: + """Add optional Basic auth (user) security to Connect-RPC endpoints that support it. + + The sandbox resolves user from an Authorization: Basic header where the + username encodes the desired OS user. This is a transport-level concern + not captured in the proto definitions, so we inject it during post-processing. + + Endpoints that support user get two security options (OR): + - SandboxAccessTokenAuth only (uses default user) + - SandboxAccessTokenAuth + SandboxUserAuth (custom user) + """ + paths = spec.get("paths", {}) + count = 0 + for ep_path in USER_HEADER_ENDPOINTS: + path_item = paths.get(ep_path, {}) + op = path_item.get("post") + if not op: + continue + op["security"] = [ + {SANDBOX_AUTH_SCHEME: [], SANDBOX_USER_SCHEME: []}, + ] + count += 1 + if count: + print(f"==> Added optional user auth (Basic) to {count} Connect-RPC endpoints") + + def _strip_supabase_security(path_item: dict[str, Any]) -> None: """Remove Supabase security entries from all operations in a path item. @@ -1455,6 +1507,7 @@ def main() -> None: setup_sandbox_auth_scheme(merged) add_operation_ids(merged) fix_spec_issues(merged) + add_user_auth_security(merged) # Remove internal/unwanted paths filter_paths(merged)