463 lines
16 KiB
YAML
463 lines
16 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: Galaxy Edge Gateway Public REST API
|
|
version: v1
|
|
description: |
|
|
This specification documents the implemented `galaxy/gateway` v1 public
|
|
REST surface.
|
|
|
|
Implemented endpoints:
|
|
- `GET /healthz`
|
|
- `GET /readyz`
|
|
- `POST /api/v1/public/auth/send-email-code`
|
|
- `POST /api/v1/public/auth/confirm-email-code`
|
|
|
|
This specification intentionally excludes the private operational admin
|
|
listener and its `GET /metrics` endpoint. That endpoint is documented in
|
|
`README.md` because it is not part of the public REST contract.
|
|
|
|
Common runtime behavior:
|
|
- requests are unauthenticated;
|
|
- unknown routes return `404` with the JSON error envelope;
|
|
- unsupported methods on implemented routes and browser-shaped public paths
|
|
return `405` with the same JSON error envelope and an `Allow` header;
|
|
- request classification happens before route handling and depends on the
|
|
incoming method, path, and selected headers;
|
|
- the only stable public route classes are `public_auth`,
|
|
`browser_bootstrap`, `browser_asset`, and `public_misc`;
|
|
- any unsupported or empty classifier result is normalized to
|
|
`public_misc`;
|
|
- public REST policy derives its base bucket namespace from the normalized
|
|
class as `public_rest/class=<class>`;
|
|
- per-IP public REST rate limits use only `RemoteAddr`; `X-Forwarded-For`
|
|
and `Forwarded` are intentionally ignored;
|
|
- `public_auth` additionally applies normalized identity buckets by
|
|
`email` for `send-email-code` and by `challenge_id` for
|
|
`confirm-email-code`;
|
|
- oversized request bodies are rejected with `413 request_too_large`;
|
|
- public REST rate limits reject with `429 rate_limited` and a
|
|
`Retry-After` header;
|
|
- public auth routes delegate through `AuthServiceClient`;
|
|
- the default `cmd/gateway` wiring keeps the auth routes mounted and
|
|
returns `503 service_unavailable` until a concrete upstream auth adapter
|
|
is configured;
|
|
- injected public auth adapters may also project client-safe `4xx/5xx`
|
|
`AuthServiceError` envelopes, which the gateway preserves after
|
|
normalizing blank or invalid fields.
|
|
servers:
|
|
- url: http://localhost:8080
|
|
description: |
|
|
Example local public REST listener. The actual address is configured by
|
|
`GATEWAY_PUBLIC_HTTP_ADDR`.
|
|
tags:
|
|
- name: Probes
|
|
description: Unauthenticated public probe endpoints served by the gateway.
|
|
- name: PublicAuth
|
|
description: |
|
|
Unauthenticated public auth endpoints delegated to the Auth / Session
|
|
Service through `AuthServiceClient`.
|
|
paths:
|
|
/healthz:
|
|
get:
|
|
tags:
|
|
- Probes
|
|
operationId: getHealthz
|
|
summary: Public liveness probe
|
|
description: |
|
|
Returns a deterministic JSON payload confirming that the public REST
|
|
listener is alive and able to answer requests.
|
|
security: []
|
|
x-public-route-classification-note: |
|
|
Typical probe requests are classified as `public_misc`.
|
|
Requests that match browser bootstrap rules, for example because they
|
|
advertise `Accept: text/html`, are classified as `browser_bootstrap`
|
|
before the route handler runs.
|
|
responses:
|
|
"200":
|
|
description: Public REST listener is alive.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/HealthzResponse"
|
|
examples:
|
|
ok:
|
|
value:
|
|
status: ok
|
|
"413":
|
|
$ref: "#/components/responses/RequestTooLargeError"
|
|
"429":
|
|
$ref: "#/components/responses/RateLimitedError"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
/readyz:
|
|
get:
|
|
tags:
|
|
- Probes
|
|
operationId: getReadyz
|
|
summary: Public readiness probe
|
|
description: |
|
|
Returns a deterministic JSON payload confirming that the process is
|
|
ready to accept public REST traffic. Readiness is local-process only
|
|
and does not reflect downstream dependencies.
|
|
security: []
|
|
x-public-route-classification-note: |
|
|
Typical probe requests are classified as `public_misc`.
|
|
Requests that match browser bootstrap rules, for example because they
|
|
advertise `Accept: text/html`, are classified as `browser_bootstrap`
|
|
before the route handler runs.
|
|
responses:
|
|
"200":
|
|
description: Public REST listener is ready to accept traffic.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ReadyzResponse"
|
|
examples:
|
|
ready:
|
|
value:
|
|
status: ready
|
|
"413":
|
|
$ref: "#/components/responses/RequestTooLargeError"
|
|
"429":
|
|
$ref: "#/components/responses/RateLimitedError"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
/api/v1/public/auth/send-email-code:
|
|
post:
|
|
tags:
|
|
- PublicAuth
|
|
operationId: sendEmailCode
|
|
summary: Start a public e-mail login challenge
|
|
description: |
|
|
Accepts a single client e-mail address and delegates the command to the
|
|
Auth / Session Service. The response returns an opaque `challenge_id`
|
|
that must later be confirmed through
|
|
`POST /api/v1/public/auth/confirm-email-code`.
|
|
|
|
This route is unauthenticated and classified as `public_auth`.
|
|
Public REST anti-abuse applies a per-IP bucket derived from
|
|
`RemoteAddr` and an additional normalized identity bucket derived from
|
|
`email`.
|
|
|
|
In the default `cmd/gateway` process wiring the upstream auth adapter
|
|
is intentionally absent, so this route returns `503
|
|
service_unavailable` until a concrete `AuthServiceClient` is injected.
|
|
When an injected adapter returns a client-safe `AuthServiceError`, the
|
|
gateway preserves that projected `4xx/5xx` status and serialized error
|
|
envelope after normalizing blank or invalid fields.
|
|
security: []
|
|
x-public-route-classification-note: |
|
|
This route is always classified as `public_auth`.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SendEmailCodeRequest"
|
|
examples:
|
|
default:
|
|
value:
|
|
email: pilot@example.com
|
|
responses:
|
|
"200":
|
|
description: The login challenge was accepted by the Auth / Session Service.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SendEmailCodeResponse"
|
|
examples:
|
|
accepted:
|
|
value:
|
|
challenge_id: challenge-123
|
|
"400":
|
|
$ref: "#/components/responses/InvalidRequestError"
|
|
"413":
|
|
$ref: "#/components/responses/RequestTooLargeError"
|
|
"405":
|
|
$ref: "#/components/responses/MethodNotAllowedError"
|
|
"429":
|
|
$ref: "#/components/responses/RateLimitedError"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
"503":
|
|
$ref: "#/components/responses/ServiceUnavailableError"
|
|
default:
|
|
$ref: "#/components/responses/ProjectedAuthServiceError"
|
|
/api/v1/public/auth/confirm-email-code:
|
|
post:
|
|
tags:
|
|
- PublicAuth
|
|
operationId: confirmEmailCode
|
|
summary: Confirm a public e-mail login challenge
|
|
description: |
|
|
Completes a previously issued `challenge_id`, sends the verification
|
|
`code`, and registers the standard base64-encoded raw 32-byte Ed25519
|
|
`client_public_key` for the new device session. The response returns
|
|
the created `device_session_id`.
|
|
|
|
This route is unauthenticated and classified as `public_auth`.
|
|
Public REST anti-abuse applies a per-IP bucket derived from
|
|
`RemoteAddr` and an additional normalized identity bucket derived from
|
|
`challenge_id`.
|
|
|
|
In the default `cmd/gateway` process wiring the upstream auth adapter
|
|
is intentionally absent, so this route returns `503
|
|
service_unavailable` until a concrete `AuthServiceClient` is injected.
|
|
When an injected adapter returns a client-safe `AuthServiceError`, the
|
|
gateway preserves that projected `4xx/5xx` status and serialized error
|
|
envelope after normalizing blank or invalid fields.
|
|
security: []
|
|
x-public-route-classification-note: |
|
|
This route is always classified as `public_auth`.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ConfirmEmailCodeRequest"
|
|
examples:
|
|
default:
|
|
value:
|
|
challenge_id: challenge-123
|
|
code: "123456"
|
|
client_public_key: base64-encoded-raw-ed25519-public-key
|
|
responses:
|
|
"200":
|
|
description: The device session was created by the Auth / Session Service.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ConfirmEmailCodeResponse"
|
|
examples:
|
|
accepted:
|
|
value:
|
|
device_session_id: device-session-123
|
|
"400":
|
|
$ref: "#/components/responses/InvalidRequestError"
|
|
"413":
|
|
$ref: "#/components/responses/RequestTooLargeError"
|
|
"405":
|
|
$ref: "#/components/responses/MethodNotAllowedError"
|
|
"429":
|
|
$ref: "#/components/responses/RateLimitedError"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
"503":
|
|
$ref: "#/components/responses/ServiceUnavailableError"
|
|
default:
|
|
$ref: "#/components/responses/ProjectedAuthServiceError"
|
|
components:
|
|
schemas:
|
|
HealthzResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- status
|
|
properties:
|
|
status:
|
|
type: string
|
|
description: Deterministic liveness marker.
|
|
enum:
|
|
- ok
|
|
ReadyzResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- status
|
|
properties:
|
|
status:
|
|
type: string
|
|
description: Deterministic readiness marker.
|
|
enum:
|
|
- ready
|
|
SendEmailCodeRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- email
|
|
properties:
|
|
email:
|
|
type: string
|
|
description: Single client e-mail address that should receive the login code.
|
|
format: email
|
|
SendEmailCodeResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- challenge_id
|
|
properties:
|
|
challenge_id:
|
|
type: string
|
|
description: Opaque challenge identifier returned by the Auth / Session Service.
|
|
ConfirmEmailCodeRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- challenge_id
|
|
- code
|
|
- client_public_key
|
|
properties:
|
|
challenge_id:
|
|
type: string
|
|
description: Opaque challenge identifier previously returned by send-email-code.
|
|
code:
|
|
type: string
|
|
description: Verification code delivered to the client.
|
|
client_public_key:
|
|
type: string
|
|
description: Standard base64-encoded raw 32-byte Ed25519 public key registered for the new device session.
|
|
ConfirmEmailCodeResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- device_session_id
|
|
properties:
|
|
device_session_id:
|
|
type: string
|
|
description: Stable identifier of the created device session.
|
|
ErrorResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- error
|
|
properties:
|
|
error:
|
|
$ref: "#/components/schemas/ErrorBody"
|
|
ErrorBody:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- code
|
|
- message
|
|
properties:
|
|
code:
|
|
type: string
|
|
description: |
|
|
Stable gateway-generated or client-safe auth-adapter-projected
|
|
error code. Gateway-generated values include `invalid_request`,
|
|
`not_found`, `method_not_allowed`, `request_too_large`,
|
|
`rate_limited`, `internal_error`, and `service_unavailable`.
|
|
message:
|
|
type: string
|
|
description: Human-readable client-safe error description.
|
|
headers:
|
|
Allow:
|
|
description: Comma-separated list of allowed methods for the target route.
|
|
schema:
|
|
type: string
|
|
example: GET
|
|
Retry-After:
|
|
description: Seconds until the client should retry a rejected rate-limited request.
|
|
schema:
|
|
type: string
|
|
example: "3600"
|
|
responses:
|
|
InvalidRequestError:
|
|
description: Request body or field values are invalid for the target public auth route.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
examples:
|
|
invalidRequest:
|
|
value:
|
|
error:
|
|
code: invalid_request
|
|
message: email must be a single valid email address
|
|
NotFoundError:
|
|
description: Request path is not implemented on the current public REST surface.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
examples:
|
|
notFound:
|
|
value:
|
|
error:
|
|
code: not_found
|
|
message: resource was not found
|
|
MethodNotAllowedError:
|
|
description: Request method is not allowed for an implemented route.
|
|
headers:
|
|
Allow:
|
|
$ref: "#/components/headers/Allow"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
examples:
|
|
methodNotAllowed:
|
|
value:
|
|
error:
|
|
code: method_not_allowed
|
|
message: request method is not allowed for this route
|
|
RequestTooLargeError:
|
|
description: Request body exceeds the configured public REST body limit for the route class.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
examples:
|
|
requestTooLarge:
|
|
value:
|
|
error:
|
|
code: request_too_large
|
|
message: request body exceeds the configured limit
|
|
RateLimitedError:
|
|
description: Request is rejected by the public REST anti-abuse rate limiter.
|
|
headers:
|
|
Retry-After:
|
|
$ref: "#/components/headers/Retry-After"
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
examples:
|
|
rateLimited:
|
|
value:
|
|
error:
|
|
code: rate_limited
|
|
message: request rate limit exceeded
|
|
InternalError:
|
|
description: Internal gateway error while processing the request.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
examples:
|
|
internalError:
|
|
value:
|
|
error:
|
|
code: internal_error
|
|
message: internal server error
|
|
ServiceUnavailableError:
|
|
description: |
|
|
The public route is mounted, but the configured or default auth adapter
|
|
cannot currently serve the request.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
examples:
|
|
unavailable:
|
|
value:
|
|
error:
|
|
code: service_unavailable
|
|
message: auth service is unavailable
|
|
ProjectedAuthServiceError:
|
|
description: |
|
|
Client-safe `4xx/5xx` error envelope projected by an injected public
|
|
auth adapter through `AuthServiceError`. The gateway preserves the
|
|
projected status and serialized envelope after normalizing blank or
|
|
invalid fields.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
examples:
|
|
projectedRateLimit:
|
|
value:
|
|
error:
|
|
code: upstream_rate_limited
|
|
message: too many attempts for this email
|