Add /, /health, /openapi.json, /user endpoints to Arc API

Add discovery, health check, OpenAPI spec, and current user endpoints.
The first three are public; /user requires authentication and returns
the login extracted from JWT sub claim, mTLS CN, or "demo" in demo mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-04 21:10:59 -05:00
parent 8a2595ad82
commit bb0fdfe972
13 changed files with 728 additions and 2 deletions

View file

@ -33,6 +33,7 @@ tower-service = "0.3"
x509-parser = "0.16"
serde.workspace = true
serde_json.workspace = true
serde_yaml = "0.9"
anyhow.workspace = true
clap.workspace = true
toml.workspace = true

View file

@ -109,6 +109,39 @@ pub fn resolve_auth_mode(
AuthMode::Strategies(strategies)
}
/// Extract the login from JWT claims.
fn extract_jwt_login(parts: &Parts, key: &DecodingKey, validation: &Validation) -> Option<String> {
let header = parts
.headers
.get("authorization")
.and_then(|v| v.to_str().ok())?;
let token = header.strip_prefix("Bearer ")?;
let token_data = jsonwebtoken::decode::<Claims>(token, key, validation).ok()?;
token_data
.claims
.sub
.as_deref()
.and_then(|s| s.rsplit('/').next())
.map(String::from)
}
/// Extract the CN from mTLS peer certificates.
fn extract_mtls_cn(parts: &Parts) -> Option<String> {
let peer_certs = parts
.extensions
.get::<PeerCertificates>()
.and_then(|pc| pc.0.as_ref())?;
let cert = peer_certs.first()?;
let (_, parsed) = x509_parser::parse_x509_certificate(cert).ok()?;
let cn = parsed
.subject()
.iter_common_name()
.next()
.and_then(|cn| cn.as_str().ok())
.map(String::from);
cn
}
/// Try to authenticate via JWT.
fn try_jwt(
parts: &Parts,
@ -222,6 +255,68 @@ impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedService {
}
}
/// Axum extractor that authenticates and extracts the user's login.
///
/// - Demo mode → `login: "demo"`
/// - JWT → login from the `sub` claim (last path segment of URL)
/// - mTLS → CN from the peer certificate
pub struct AuthenticatedUser {
pub login: String,
}
impl<S: Send + Sync> FromRequestParts<S> for AuthenticatedUser {
type Rejection = StatusCode;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let auth_mode = parts
.extensions
.get::<AuthMode>()
.expect("AuthMode extension must be added to the router");
let strategies = match auth_mode {
AuthMode::Disabled => {
return Ok(AuthenticatedUser {
login: "demo".to_string(),
})
}
AuthMode::Strategies(strategies) => strategies,
};
if strategies.is_empty() {
return Err(StatusCode::UNAUTHORIZED);
}
let mut last_err = StatusCode::UNAUTHORIZED;
for strategy in strategies {
match strategy {
AuthStrategy::Jwt {
key,
validation,
allowed_usernames,
} => {
if try_jwt(parts, key, validation, allowed_usernames).is_ok() {
if let Some(login) = extract_jwt_login(parts, key, validation) {
return Ok(AuthenticatedUser { login });
}
}
last_err = StatusCode::UNAUTHORIZED;
}
AuthStrategy::Mtls => {
if try_mtls(parts).is_ok() {
if let Some(login) = extract_mtls_cn(parts) {
return Ok(AuthenticatedUser { login });
}
}
last_err = StatusCode::UNAUTHORIZED;
}
}
}
Err(last_err)
}
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -16,7 +16,7 @@ use tracing::{error, info};
use arc_agent::LocalSandbox;
use crate::jwt_auth::{AuthMode, AuthenticatedService};
use crate::jwt_auth::{AuthMode, AuthenticatedService, AuthenticatedUser};
use arc_workflows::checkpoint::Checkpoint;
use arc_workflows::context::Context;
use arc_workflows::engine::{RunConfig, WorkflowRunEngine};
@ -57,7 +57,11 @@ pub struct AppState {
pub fn build_router(state: Arc<AppState>, auth_mode: AuthMode) -> Router {
let is_demo = state.is_demo;
let mut router = Router::new();
let mut router = Router::new()
.route("/", get(root))
.route("/health", get(health))
.route("/openapi.json", get(openapi_spec))
.route("/user", get(get_user));
if is_demo {
router = router
@ -190,6 +194,31 @@ async fn not_implemented() -> Response {
StatusCode::NOT_IMPLEMENTED.into_response()
}
async fn root() -> Response {
Json(serde_json::json!({
"urls": {
"openapi_url": "/openapi.json",
"current_user_url": "/user",
"health_url": "/health"
}
}))
.into_response()
}
async fn health() -> Response {
Json(serde_json::json!({"status": "ok"})).into_response()
}
async fn openapi_spec() -> Response {
let yaml = include_str!("../../../openapi/arc-api.yaml");
let value: serde_json::Value = serde_yaml::from_str(yaml).expect("embedded OpenAPI YAML is invalid");
Json(value).into_response()
}
async fn get_user(user: AuthenticatedUser) -> Response {
Json(serde_json::json!({"login": user.login})).into_response()
}
/// Create an `AppState` with the given registry factory and database pool.
///
/// The factory receives the run's `WebInterviewer` so it can wire it

View file

@ -5,6 +5,8 @@ info:
description: HTTP API for managing Arc workflow run executions.
tags:
- name: Discovery
description: API discovery and health
- name: Runs
description: Run management operations
- name: Human-in-the-Loop
@ -31,6 +33,65 @@ tags:
description: Platform configuration
paths:
# ── Discovery ────────────────────────────────────────────────────────
/:
get:
operationId: getRoot
tags: [Discovery]
summary: API Discovery
description: Returns discovery URLs for the API.
responses:
"200":
description: Discovery URLs
content:
application/json:
schema:
$ref: "#/components/schemas/RootResponse"
/health:
get:
operationId: getHealth
tags: [Discovery]
summary: Health Check
responses:
"200":
description: Service is healthy
content:
application/json:
schema:
$ref: "#/components/schemas/HealthResponse"
/openapi.json:
get:
operationId: getOpenApiSpec
tags: [Discovery]
summary: OpenAPI Specification
description: Returns the OpenAPI spec as JSON.
responses:
"200":
description: OpenAPI specification
content:
application/json:
schema:
type: object
/user:
get:
operationId: getUser
tags: [Discovery]
summary: Current User
description: Returns info about the authenticated user.
responses:
"200":
description: User info
content:
application/json:
schema:
$ref: "#/components/schemas/UserResponse"
"401":
description: Not authenticated
# ── Runs ──────────────────────────────────────────────────────────────
/runs:
@ -1834,3 +1895,43 @@ components:
type: string
name:
type: string
# ── Discovery Schemas ──────────────────────────────────────────────
RootResponseUrls:
type: object
required:
- openapi_url
- current_user_url
- health_url
properties:
openapi_url:
type: string
current_user_url:
type: string
health_url:
type: string
RootResponse:
type: object
required:
- urls
properties:
urls:
$ref: "#/components/schemas/RootResponseUrls"
HealthResponse:
type: object
required:
- status
properties:
status:
type: string
UserResponse:
type: object
required:
- login
properties:
login:
type: string

View file

@ -5,6 +5,8 @@ info:
description: HTTP API for managing Arc workflow run executions.
tags:
- name: Discovery
description: API discovery and health
- name: Runs
description: Run management operations
- name: Human-in-the-Loop
@ -31,6 +33,65 @@ tags:
description: Platform configuration
paths:
# ── Discovery ────────────────────────────────────────────────────────
/:
get:
operationId: getRoot
tags: [Discovery]
summary: API Discovery
description: Returns discovery URLs for the API.
responses:
"200":
description: Discovery URLs
content:
application/json:
schema:
$ref: "#/components/schemas/RootResponse"
/health:
get:
operationId: getHealth
tags: [Discovery]
summary: Health Check
responses:
"200":
description: Service is healthy
content:
application/json:
schema:
$ref: "#/components/schemas/HealthResponse"
/openapi.json:
get:
operationId: getOpenApiSpec
tags: [Discovery]
summary: OpenAPI Specification
description: Returns the OpenAPI spec as JSON.
responses:
"200":
description: OpenAPI specification
content:
application/json:
schema:
type: object
/user:
get:
operationId: getUser
tags: [Discovery]
summary: Current User
description: Returns info about the authenticated user.
responses:
"200":
description: User info
content:
application/json:
schema:
$ref: "#/components/schemas/UserResponse"
"401":
description: Not authenticated
# ── Runs ──────────────────────────────────────────────────────────────
/runs:
@ -1834,3 +1895,43 @@ components:
type: string
name:
type: string
# ── Discovery Schemas ──────────────────────────────────────────────
RootResponseUrls:
type: object
required:
- openapi_url
- current_user_url
- health_url
properties:
openapi_url:
type: string
current_user_url:
type: string
health_url:
type: string
RootResponse:
type: object
required:
- urls
properties:
urls:
$ref: "#/components/schemas/RootResponseUrls"
HealthResponse:
type: object
required:
- status
properties:
status:
type: string
UserResponse:
type: object
required:
- login
properties:
login:
type: string

View file

@ -1,4 +1,5 @@
api.ts
api/discovery-api.ts
api/human-in-the-loop-api.ts
api/insights-api.ts
api/projects-api.ts
@ -33,6 +34,7 @@ models/execute-query-request.ts
models/execute-query-response.ts
models/file-checkpoint.ts
models/file-diff.ts
models/health-response.ts
models/history-entry.ts
models/index.ts
models/preview-url-request.ts
@ -41,6 +43,8 @@ models/project.ts
models/recent-control-result.ts
models/retro-list-item.ts
models/retro-stats.ts
models/root-response-urls.ts
models/root-response.ts
models/run-files.ts
models/run-list-item-status.ts
models/run-list-item.ts
@ -74,6 +78,7 @@ models/tool-use.ts
models/usage-by-model.ts
models/usage-stage.ts
models/usage-totals.ts
models/user-response.ts
models/verification-category.ts
models/verification-control.ts
models/verification-detail-response.ts

View file

@ -14,6 +14,7 @@
export * from './api/discovery-api';
export * from './api/human-in-the-loop-api';
export * from './api/insights-api';
export * from './api/projects-api';

View file

@ -0,0 +1,304 @@
/* tslint:disable */
/* eslint-disable */
/**
* Arc Run API
* HTTP API for managing Arc workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from '../configuration';
import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
// Some imports not used depending on template conditions
// @ts-ignore
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction, replaceWithSerializableTypeIfNeeded } from '../common';
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base';
// @ts-ignore
import type { HealthResponse } from '../models';
// @ts-ignore
import type { RootResponse } from '../models';
// @ts-ignore
import type { UserResponse } from '../models';
/**
* DiscoveryApi - axios parameter creator
*/
export const DiscoveryApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
*
* @summary Health Check
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getHealth: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/health`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns the OpenAPI spec as JSON.
* @summary OpenAPI Specification
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getOpenApiSpec: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/openapi.json`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns discovery URLs for the API.
* @summary API Discovery
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getRoot: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns info about the authenticated user.
* @summary Current User
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getUser: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/user`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* DiscoveryApi - functional programming interface
*/
export const DiscoveryApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = DiscoveryApiAxiosParamCreator(configuration)
return {
/**
*
* @summary Health Check
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getHealth(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<HealthResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getHealth(options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['DiscoveryApi.getHealth']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns the OpenAPI spec as JSON.
* @summary OpenAPI Specification
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getOpenApiSpec(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getOpenApiSpec(options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['DiscoveryApi.getOpenApiSpec']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns discovery URLs for the API.
* @summary API Discovery
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getRoot(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<RootResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getRoot(options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['DiscoveryApi.getRoot']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns info about the authenticated user.
* @summary Current User
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getUser(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getUser(options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['DiscoveryApi.getUser']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
}
};
/**
* DiscoveryApi - factory interface
*/
export const DiscoveryApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = DiscoveryApiFp(configuration)
return {
/**
*
* @summary Health Check
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getHealth(options?: RawAxiosRequestConfig): AxiosPromise<HealthResponse> {
return localVarFp.getHealth(options).then((request) => request(axios, basePath));
},
/**
* Returns the OpenAPI spec as JSON.
* @summary OpenAPI Specification
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getOpenApiSpec(options?: RawAxiosRequestConfig): AxiosPromise<object> {
return localVarFp.getOpenApiSpec(options).then((request) => request(axios, basePath));
},
/**
* Returns discovery URLs for the API.
* @summary API Discovery
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getRoot(options?: RawAxiosRequestConfig): AxiosPromise<RootResponse> {
return localVarFp.getRoot(options).then((request) => request(axios, basePath));
},
/**
* Returns info about the authenticated user.
* @summary Current User
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getUser(options?: RawAxiosRequestConfig): AxiosPromise<UserResponse> {
return localVarFp.getUser(options).then((request) => request(axios, basePath));
},
};
};
/**
* DiscoveryApi - object-oriented interface
*/
export class DiscoveryApi extends BaseAPI {
/**
*
* @summary Health Check
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public getHealth(options?: RawAxiosRequestConfig) {
return DiscoveryApiFp(this.configuration).getHealth(options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns the OpenAPI spec as JSON.
* @summary OpenAPI Specification
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public getOpenApiSpec(options?: RawAxiosRequestConfig) {
return DiscoveryApiFp(this.configuration).getOpenApiSpec(options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns discovery URLs for the API.
* @summary API Discovery
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public getRoot(options?: RawAxiosRequestConfig) {
return DiscoveryApiFp(this.configuration).getRoot(options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns info about the authenticated user.
* @summary Current User
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public getUser(options?: RawAxiosRequestConfig) {
return DiscoveryApiFp(this.configuration).getUser(options).then((request) => request(this.axios, this.basePath));
}
}

View file

@ -0,0 +1,20 @@
/* tslint:disable */
/* eslint-disable */
/**
* Arc Run API
* HTTP API for managing Arc workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface HealthResponse {
'status': string;
}

View file

@ -17,6 +17,7 @@ export * from './execute-query-request';
export * from './execute-query-response';
export * from './file-checkpoint';
export * from './file-diff';
export * from './health-response';
export * from './history-entry';
export * from './preview-url-request';
export * from './preview-url-response';
@ -24,6 +25,8 @@ export * from './project';
export * from './recent-control-result';
export * from './retro-list-item';
export * from './retro-stats';
export * from './root-response';
export * from './root-response-urls';
export * from './run-files';
export * from './run-list-item';
export * from './run-list-item-status';
@ -57,6 +60,7 @@ export * from './tool-use';
export * from './usage-by-model';
export * from './usage-stage';
export * from './usage-totals';
export * from './user-response';
export * from './verification-category';
export * from './verification-control';
export * from './verification-detail-response';

View file

@ -0,0 +1,22 @@
/* tslint:disable */
/* eslint-disable */
/**
* Arc Run API
* HTTP API for managing Arc workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface RootResponseUrls {
'openapi_url': string;
'current_user_url': string;
'health_url': string;
}

View file

@ -0,0 +1,23 @@
/* tslint:disable */
/* eslint-disable */
/**
* Arc Run API
* HTTP API for managing Arc workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { RootResponseUrls } from './root-response-urls';
export interface RootResponse {
'urls': RootResponseUrls;
}

View file

@ -0,0 +1,20 @@
/* tslint:disable */
/* eslint-disable */
/**
* Arc Run API
* HTTP API for managing Arc workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface UserResponse {
'login': string;
}