openapi: 3.0.3 info: title: Galaxy Auth / Session Service Public API version: v1 description: | This specification documents the implemented `galaxy/authsession` v1 public REST contract for the e-mail-code flow consumed by `galaxy/gateway`. Implemented public operations: - `POST /api/v1/public/auth/send-email-code` - `POST /api/v1/public/auth/confirm-email-code` Contract rules: - requests and responses are JSON only; - request schemas reject unknown fields via `additionalProperties: false`; - empty bodies, malformed JSON, multiple JSON objects, and unknown fields are rejected as `400 invalid_request`; - surrounding ASCII/Unicode whitespace is trimmed from input string fields before validation; - `send-email-code` remains success-shaped for existing, new, and blocked e-mail addresses; - `confirm-email-code` returns a ready `device_session_id` synchronously on success. tags: - name: PublicAuth description: Public unauthenticated e-mail-code authentication endpoints. paths: /api/v1/public/auth/send-email-code: post: tags: - PublicAuth operationId: sendEmailCode summary: Start a public e-mail login challenge description: | Accepts one client e-mail address and starts the public challenge flow. The outward result remains success-shaped even when the underlying policy suppresses mail delivery for anti-enumeration purposes. The JSON body stays unchanged. Gateway may additionally forward the optional public `Accept-Language` header so auth can derive the auth-mail locale and the create-only preferred-language candidate used later during first-user creation. Missing or unsupported values fall back to `en`. security: [] parameters: - $ref: "#/components/parameters/AcceptLanguage" 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. content: application/json: schema: $ref: "#/components/schemas/SendEmailCodeResponse" examples: accepted: value: challenge_id: challenge-123 "400": $ref: "#/components/responses/SendEmailCodeBadRequestError" "503": $ref: "#/components/responses/ServiceUnavailableError" /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`, validates the submitted verification code, registers the standard base64-encoded raw 32-byte Ed25519 `client_public_key`, validates the submitted IANA `time_zone`, and returns the created `device_session_id`. security: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ConfirmEmailCodeRequest" examples: default: value: challenge_id: challenge-123 code: "123456" client_public_key: 11qYAYdk8v3K6Yw8QK6ZlQ2nP4Wm8Cq5g1H0K8vT9no= time_zone: Europe/Kaliningrad responses: "200": description: The device session was created and is ready for use. content: application/json: schema: $ref: "#/components/schemas/ConfirmEmailCodeResponse" examples: accepted: value: device_session_id: device-session-123 "400": $ref: "#/components/responses/ConfirmEmailCodeBadRequestError" "403": $ref: "#/components/responses/BlockedByPolicyError" "404": $ref: "#/components/responses/ChallengeNotFoundError" "409": $ref: "#/components/responses/SessionLimitExceededError" "410": $ref: "#/components/responses/ChallengeExpiredError" "503": $ref: "#/components/responses/ServiceUnavailableError" components: parameters: AcceptLanguage: name: Accept-Language in: header required: false description: | Optional RFC 9110 `Accept-Language` header forwarded by gateway so auth can derive the auth-mail locale and create-only preferred-language candidate. The first supported BCP 47 tag wins; unsupported or missing values fall back to `en`. schema: type: string schemas: 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 - time_zone 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. time_zone: type: string description: Client-selected IANA time zone name forwarded as create-only registration context. 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. responses: SendEmailCodeBadRequestError: description: | Request body or field values are invalid. This includes empty bodies, malformed JSON, multiple JSON objects, unknown fields, and invalid `email`. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: invalidRequest: value: error: code: invalid_request message: email must be a single valid email address ConfirmEmailCodeBadRequestError: description: | Request body or field values are invalid. This includes malformed request payloads, invalid confirmation codes, and malformed `client_public_key` or `time_zone` values. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: invalidRequest: value: error: code: invalid_request message: challenge_id must not be empty invalidCode: value: error: code: invalid_code message: confirmation code is invalid invalidClientPublicKey: value: error: code: invalid_client_public_key message: client_public_key is not a valid base64-encoded raw 32-byte Ed25519 public key invalidTimeZone: value: error: code: invalid_request message: time_zone must be a valid IANA time zone name ChallengeNotFoundError: description: The referenced challenge does not exist. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: notFound: value: error: code: challenge_not_found message: challenge not found ChallengeExpiredError: description: The referenced challenge has expired and can no longer be confirmed. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: expired: value: error: code: challenge_expired message: challenge expired BlockedByPolicyError: description: The auth flow is denied by account or registration policy. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: blocked: value: error: code: blocked_by_policy message: authentication is blocked by policy SessionLimitExceededError: description: Creating another active device session would exceed the configured limit. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: limitExceeded: value: error: code: session_limit_exceeded message: active session limit would be exceeded ServiceUnavailableError: description: The service is temporarily unable to serve the request safely. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: unavailable: value: error: code: service_unavailable message: service is unavailable