feat(users): add API key install sessions

This commit is contained in:
RWDai
2026-05-08 23:16:16 +08:00
parent 9a84a6ff6c
commit da46eb4791
6 changed files with 620 additions and 7 deletions

View File

@@ -24,5 +24,7 @@ pub(crate) fn mount_public_support_routes(router: Router<AppState>) -> Router<Ap
.route("/api/capabilities", get(proxy_request))
.route("/api/capabilities/user-configurable", get(proxy_request))
.route("/api/capabilities/model/{*model_path}", get(proxy_request))
.route("/install/{*install_path}", get(proxy_request))
.route("/i/{*install_path}", get(proxy_request))
.route("/", get(proxy_request))
}

View File

@@ -468,6 +468,20 @@ pub(super) fn classify_public_support_route(
"user:self",
false,
))
} else if method == http::Method::POST
&& has_single_nested_suffix_after_prefix(
normalized_path,
"/api/users/me/api-keys/",
"install-sessions",
)
{
Some(classified(
"public_support",
"users_me",
"api_key_install_session_create",
"user:self",
false,
))
} else if method == http::Method::POST
&& normalized_path.starts_with("/api/me/management-tokens/")
&& normalized_path.ends_with("/regenerate")
@@ -659,6 +673,17 @@ pub(super) fn classify_public_support_route(
"public:system_catalog",
false,
))
} else if method == http::Method::GET
&& (has_single_segment_after_prefix(normalized_path, "/install/")
|| has_single_segment_after_prefix(normalized_path, "/i/"))
{
Some(classified(
"public_support",
"install",
"script",
"public:install",
false,
))
} else if method == http::Method::GET && normalized_path == "/test-connection" {
Some(classified(
"public_support",

View File

@@ -28,6 +28,8 @@ mod support_announcements;
mod support_auth;
#[path = "support/dashboard.rs"]
mod support_dashboard;
#[path = "support/install.rs"]
mod support_install;
#[path = "support/models.rs"]
mod support_models;
#[path = "support/monitoring.rs"]
@@ -61,6 +63,10 @@ use self::support_auth::{
build_auth_settings_payload, extract_client_device_id, maybe_build_local_auth_response,
};
use self::support_dashboard::maybe_build_local_dashboard_response;
use self::support_install::{
handle_users_me_api_key_install_session_create, maybe_build_local_install_response,
users_me_api_key_install_sessions_path_matches,
};
use self::support_models::{
build_models_auth_error_response, maybe_build_local_models_response, models_api_format,
};
@@ -146,6 +152,10 @@ pub(crate) async fn maybe_build_local_public_support_response(
.await;
}
if decision.route_family.as_deref() == Some("install") {
return maybe_build_local_install_response(state, request_context).await;
}
if decision.route_family.as_deref() == Some("payment_callback") {
return maybe_build_local_payment_callback_response(
state,

View File

@@ -0,0 +1,561 @@
use axum::{
body::Body,
http,
response::{IntoResponse, Response},
Json,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use super::{
build_auth_error_response, decrypt_catalog_secret_with_fallbacks,
resolve_authenticated_local_user, AppState, GatewayPublicRequestContext,
};
const INSTALL_SESSION_TTL_SECS: u64 = 15 * 60;
const INSTALL_SESSION_KEY_PREFIX: &str = "install:session:";
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
enum InstallTargetCli {
ClaudeCode,
CodexCli,
GeminiCli,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
enum InstallTargetSystem {
Macos,
Linux,
Windows,
Auto,
}
#[derive(Debug, Deserialize)]
struct UsersMeCreateInstallSessionRequest {
target_cli: InstallTargetCli,
target_system: InstallTargetSystem,
}
#[derive(Debug, Serialize, Deserialize)]
struct StoredInstallSession {
api_key_id: String,
api_key_name: String,
api_key: String,
base_url: String,
target_cli: InstallTargetCli,
target_system: InstallTargetSystem,
expires_at_unix_secs: u64,
}
pub(super) fn users_me_api_key_install_sessions_path_matches(request_path: &str) -> bool {
users_me_api_key_install_session_id_from_path(request_path).is_some()
}
fn users_me_api_key_install_session_id_from_path(request_path: &str) -> Option<String> {
let raw = request_path
.strip_prefix("/api/users/me/api-keys/")?
.trim()
.trim_matches('/');
let mut segments = raw.split('/').map(str::trim);
let api_key_id = segments.next()?.to_string();
let suffix = segments.next()?;
(suffix == "install-sessions" && segments.next().is_none()).then_some(api_key_id)
}
fn install_code_from_path(request_path: &str) -> Option<(String, bool)> {
let raw = request_path
.strip_prefix("/install/")
.or_else(|| request_path.strip_prefix("/i/"))?
.trim()
.trim_matches('/');
if raw.is_empty() || raw.contains('/') {
return None;
}
let is_powershell = raw.ends_with(".ps1");
let code = raw.strip_suffix(".ps1").unwrap_or(raw).trim();
(!code.is_empty()).then(|| (code.to_string(), is_powershell))
}
fn install_session_runtime_key(code: &str) -> String {
format!("{INSTALL_SESSION_KEY_PREFIX}{code}")
}
fn generate_install_code() -> String {
uuid::Uuid::new_v4()
.simple()
.to_string()
.chars()
.take(24)
.collect()
}
fn unix_secs_now() -> u64 {
chrono::Utc::now().timestamp().max(0) as u64
}
fn base_url_from_request(
headers: &http::HeaderMap,
request_context: &GatewayPublicRequestContext,
) -> String {
if let Some(value) = std::env::var("AETHER_PUBLIC_BASE_URL")
.ok()
.or_else(|| std::env::var("PUBLIC_BASE_URL").ok())
.map(|value| value.trim().trim_end_matches('/').to_string())
.filter(|value| value.starts_with("https://") || value.starts_with("http://"))
{
return value;
}
let host = crate::headers::header_value_str(headers, "x-forwarded-host")
.or_else(|| request_context.host_header.clone())
.map(|value| value.trim().trim_end_matches('/').to_string())
.filter(|value| {
!value.is_empty()
&& !value.contains('/')
&& !value.contains('\\')
&& !value.contains('@')
&& !value.contains(char::is_whitespace)
})
.unwrap_or_else(|| "localhost".to_string());
let proto = crate::headers::header_value_str(headers, "x-forwarded-proto")
.map(|value| value.trim().trim_end_matches(':').to_ascii_lowercase())
.filter(|value| value == "http" || value == "https")
.unwrap_or_else(|| "http".to_string());
format!("{proto}://{host}")
}
fn shell_single_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "'\\''"))
}
fn powershell_single_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "''"))
}
fn cli_label(target_cli: InstallTargetCli) -> &'static str {
match target_cli {
InstallTargetCli::ClaudeCode => "Claude Code",
InstallTargetCli::CodexCli => "Codex CLI",
InstallTargetCli::GeminiCli => "Gemini CLI",
}
}
fn system_label(target_system: InstallTargetSystem) -> &'static str {
match target_system {
InstallTargetSystem::Macos => "macOS",
InstallTargetSystem::Linux => "Linux",
InstallTargetSystem::Windows => "Windows",
InstallTargetSystem::Auto => "Auto",
}
}
fn npm_package(target_cli: InstallTargetCli) -> &'static str {
match target_cli {
InstallTargetCli::ClaudeCode => "@anthropic-ai/claude-code",
InstallTargetCli::CodexCli => "@openai/codex",
InstallTargetCli::GeminiCli => "@google/gemini-cli",
}
}
fn cli_binary(target_cli: InstallTargetCli) -> &'static str {
match target_cli {
InstallTargetCli::ClaudeCode => "claude",
InstallTargetCli::CodexCli => "codex",
InstallTargetCli::GeminiCli => "gemini",
}
}
fn build_unix_script(session: &StoredInstallSession) -> String {
let target_cli = match session.target_cli {
InstallTargetCli::ClaudeCode => "claude_code",
InstallTargetCli::CodexCli => "codex_cli",
InstallTargetCli::GeminiCli => "gemini_cli",
};
let target_system = match session.target_system {
InstallTargetSystem::Macos => "macos",
InstallTargetSystem::Linux => "linux",
InstallTargetSystem::Windows => "windows",
InstallTargetSystem::Auto => "auto",
};
format!(
r###"#!/bin/sh
set -eu
TARGET_CLI={target_cli}
TARGET_SYSTEM={target_system}
AETHER_BASE_URL={base_url}
AETHER_API_KEY={api_key}
CLI_LABEL={label}
CLI_BIN={binary}
NPM_PACKAGE={npm_package}
say() {{ printf '%s\n' "[Aether] $1"; }}
fail() {{ printf '%s\n' "[Aether] $1" >&2; exit 1; }}
os="$(uname -s 2>/dev/null || printf unknown)"
case "$os" in
Darwin) actual_system=macos ;;
Linux) actual_system=linux ;;
MINGW*|MSYS*|CYGWIN*) fail "检测到 Windows shell请在 PowerShell 中使用 Windows 命令irm <url>.ps1 | iex" ;;
*) fail "不支持的系统:$os" ;;
esac
if [ "$TARGET_SYSTEM" = "windows" ]; then
fail "该 install code 绑定 Windows请复制 PowerShell 命令执行。"
fi
if [ "$TARGET_SYSTEM" != "auto" ] && [ "$TARGET_SYSTEM" != "$actual_system" ]; then
fail "所选系统 $TARGET_SYSTEM 与当前系统 $actual_system 不一致,请回到 Aether 重新选择目标系统。"
fi
say "准备安装/复用 $CLI_LABEL"
if ! command -v "$CLI_BIN" >/dev/null 2>&1; then
command -v npm >/dev/null 2>&1 || fail "未找到 $CLI_BIN也未找到 npm。请先安装 Node.js/npm 后重试。"
say "未找到 $CLI_BIN正在通过 npm 安装 $NPM_PACKAGE"
npm install -g "$NPM_PACKAGE"
fi
umask 077
mkdir -p "$HOME/.aether"
cat > "$HOME/.aether/client.env" <<EOF
AETHER_BASE_URL=$AETHER_BASE_URL
AETHER_API_KEY=$AETHER_API_KEY
EOF
chmod 600 "$HOME/.aether/client.env" 2>/dev/null || true
case "$TARGET_CLI" in
claude_code)
mkdir -p "$HOME/.claude"
python3 - "$HOME/.claude/settings.json" "$AETHER_BASE_URL" "$AETHER_API_KEY" <<'PY'
import json, pathlib, sys
path = pathlib.Path(sys.argv[1])
data = json.loads(path.read_text() or '{{}}') if path.exists() else {{}}
env = data.setdefault('env', {{}})
env['ANTHROPIC_BASE_URL'] = sys.argv[2]
env['ANTHROPIC_AUTH_TOKEN'] = sys.argv[3]
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n')
PY
chmod 600 "$HOME/.claude/settings.json" 2>/dev/null || true
;;
codex_cli)
mkdir -p "$HOME/.codex"
cat > "$HOME/.codex/auth.json" <<EOF
{{"OPENAI_API_KEY":"$AETHER_API_KEY"}}
EOF
cat > "$HOME/.codex/config.toml" <<EOF
# Managed by Aether
model_provider = "aether"
[model_providers.aether]
name = "Aether"
base_url = "$AETHER_BASE_URL/v1"
env_key = "OPENAI_API_KEY"
wire_api = "chat"
EOF
chmod 600 "$HOME/.codex/auth.json" "$HOME/.codex/config.toml" 2>/dev/null || true
;;
gemini_cli)
mkdir -p "$HOME/.gemini"
cat > "$HOME/.gemini/.env" <<EOF
GEMINI_API_KEY=$AETHER_API_KEY
GOOGLE_API_KEY=$AETHER_API_KEY
GOOGLE_GEMINI_BASE_URL=$AETHER_BASE_URL
AETHER_BASE_URL=$AETHER_BASE_URL
EOF
python3 - "$HOME/.gemini/settings.json" "$AETHER_BASE_URL" <<'PY'
import json, pathlib, sys
path = pathlib.Path(sys.argv[1])
data = json.loads(path.read_text() or '{{}}') if path.exists() else {{}}
data.setdefault('aether', {{}})['baseUrl'] = sys.argv[2]
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n')
PY
chmod 600 "$HOME/.gemini/.env" "$HOME/.gemini/settings.json" 2>/dev/null || true
;;
esac
say "$CLI_LABEL 已配置到 Aether。执行 $CLI_BIN --version 验证安装。"
"###,
target_cli = target_cli,
target_system = target_system,
base_url = shell_single_quote(&session.base_url),
api_key = shell_single_quote(&session.api_key),
label = shell_single_quote(cli_label(session.target_cli)),
binary = shell_single_quote(cli_binary(session.target_cli)),
npm_package = shell_single_quote(npm_package(session.target_cli)),
)
}
fn build_powershell_script(session: &StoredInstallSession) -> String {
let target_cli = match session.target_cli {
InstallTargetCli::ClaudeCode => "claude_code",
InstallTargetCli::CodexCli => "codex_cli",
InstallTargetCli::GeminiCli => "gemini_cli",
};
format!(
r###"$ErrorActionPreference = 'Stop'
$TargetCli = {target_cli}
$TargetSystem = {target_system}
$AetherBaseUrl = {base_url}
$AetherApiKey = {api_key}
$CliLabel = {label}
$CliBin = {binary}
$NpmPackage = {npm_package}
function Say($Message) {{ Write-Host "[Aether] $Message" }}
function Fail($Message) {{ Write-Error "[Aether] $Message"; exit 1 }}
if ($TargetSystem -ne 'auto' -and $TargetSystem -ne 'windows') {{ Fail "该 install code 绑定 $TargetSystem请复制 macOS/Linux 命令执行。" }}
Say "准备安装/复用 $CliLabel"
if (-not (Get-Command $CliBin -ErrorAction SilentlyContinue)) {{
if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {{ Fail "未找到 $CliBin也未找到 npm。请先安装 Node.js/npm 后重试。" }}
Say "未找到 $CliBin正在通过 npm 安装 $NpmPackage"
npm install -g $NpmPackage
}}
$HomeDir = [Environment]::GetFolderPath('UserProfile')
$AetherDir = Join-Path $HomeDir '.aether'
New-Item -ItemType Directory -Force -Path $AetherDir | Out-Null
Set-Content -Path (Join-Path $AetherDir 'client.env') -Value "AETHER_BASE_URL=$AetherBaseUrl`nAETHER_API_KEY=$AetherApiKey`n" -Encoding UTF8
if ($TargetCli -eq 'claude_code') {{
$Dir = Join-Path $HomeDir '.claude'; New-Item -ItemType Directory -Force -Path $Dir | Out-Null
$Path = Join-Path $Dir 'settings.json'
$Data = if (Test-Path $Path) {{ Get-Content $Path -Raw | ConvertFrom-Json -AsHashtable }} else {{ @{{}} }}
if (-not $Data.ContainsKey('env')) {{ $Data.env = @{{}} }}
$Data.env.ANTHROPIC_BASE_URL = $AetherBaseUrl
$Data.env.ANTHROPIC_AUTH_TOKEN = $AetherApiKey
$Data | ConvertTo-Json -Depth 8 | Set-Content $Path -Encoding UTF8
}} elseif ($TargetCli -eq 'codex_cli') {{
$Dir = Join-Path $HomeDir '.codex'; New-Item -ItemType Directory -Force -Path $Dir | Out-Null
Set-Content (Join-Path $Dir 'auth.json') -Value (@{{ OPENAI_API_KEY = $AetherApiKey }} | ConvertTo-Json) -Encoding UTF8
Set-Content (Join-Path $Dir 'config.toml') -Value "# Managed by Aether`nmodel_provider = \"aether\"`n`n[model_providers.aether]`nname = \"Aether\"`nbase_url = \"$AetherBaseUrl/v1\"`nenv_key = \"OPENAI_API_KEY\"`nwire_api = \"chat\"`n" -Encoding UTF8
}} elseif ($TargetCli -eq 'gemini_cli') {{
$Dir = Join-Path $HomeDir '.gemini'; New-Item -ItemType Directory -Force -Path $Dir | Out-Null
Set-Content (Join-Path $Dir '.env') -Value "GEMINI_API_KEY=$AetherApiKey`nGOOGLE_API_KEY=$AetherApiKey`nGOOGLE_GEMINI_BASE_URL=$AetherBaseUrl`nAETHER_BASE_URL=$AetherBaseUrl`n" -Encoding UTF8
$Path = Join-Path $Dir 'settings.json'
$Data = if (Test-Path $Path) {{ Get-Content $Path -Raw | ConvertFrom-Json -AsHashtable }} else {{ @{{}} }}
$Data.aether = @{{ baseUrl = $AetherBaseUrl }}
$Data | ConvertTo-Json -Depth 8 | Set-Content $Path -Encoding UTF8
}}
Say "$CliLabel 已配置到 Aether。执行 $CliBin --version 验证安装。"
"###,
target_cli = powershell_single_quote(target_cli),
target_system = powershell_single_quote(match session.target_system {
InstallTargetSystem::Macos => "macos",
InstallTargetSystem::Linux => "linux",
InstallTargetSystem::Windows => "windows",
InstallTargetSystem::Auto => "auto",
}),
base_url = powershell_single_quote(&session.base_url),
api_key = powershell_single_quote(&session.api_key),
label = powershell_single_quote(cli_label(session.target_cli)),
binary = powershell_single_quote(cli_binary(session.target_cli)),
npm_package = powershell_single_quote(npm_package(session.target_cli)),
)
}
pub(super) async fn handle_users_me_api_key_install_session_create(
state: &AppState,
request_context: &GatewayPublicRequestContext,
headers: &http::HeaderMap,
request_body: Option<&axum::body::Bytes>,
) -> Response<Body> {
let auth = match resolve_authenticated_local_user(state, request_context, headers).await {
Ok(value) => value,
Err(response) => return response,
};
let Some(api_key_id) =
users_me_api_key_install_session_id_from_path(&request_context.request_path)
else {
return build_auth_error_response(http::StatusCode::NOT_FOUND, "API密钥不存在", false);
};
let Some(request_body) = request_body else {
return build_auth_error_response(http::StatusCode::BAD_REQUEST, "请求数据验证失败", false);
};
let payload = match serde_json::from_slice::<UsersMeCreateInstallSessionRequest>(request_body) {
Ok(value) => value,
Err(_) => {
return build_auth_error_response(
http::StatusCode::BAD_REQUEST,
"请求数据验证失败",
false,
)
}
};
let records = match state
.list_auth_api_key_export_records_by_user_ids(std::slice::from_ref(&auth.user.id))
.await
{
Ok(value) => value,
Err(err) => {
return build_auth_error_response(
http::StatusCode::INTERNAL_SERVER_ERROR,
format!("user api key lookup failed: {err:?}"),
false,
)
}
};
let Some(record) = records
.into_iter()
.find(|record| !record.is_standalone && record.api_key_id == api_key_id)
else {
return build_auth_error_response(http::StatusCode::NOT_FOUND, "API密钥不存在", false);
};
let Some(ciphertext) = record
.key_encrypted
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
else {
return build_auth_error_response(
http::StatusCode::BAD_REQUEST,
"该密钥没有存储完整密钥信息",
false,
);
};
let Some(api_key) = decrypt_catalog_secret_with_fallbacks(state.encryption_key(), ciphertext)
else {
return build_auth_error_response(
http::StatusCode::INTERNAL_SERVER_ERROR,
"解密密钥失败",
false,
);
};
let code = generate_install_code();
let expires_at_unix_secs = unix_secs_now().saturating_add(INSTALL_SESSION_TTL_SECS);
let session = StoredInstallSession {
api_key_id: record.api_key_id.clone(),
api_key_name: record.name.unwrap_or_else(|| "API Key".to_string()),
api_key,
base_url: base_url_from_request(headers, request_context),
target_cli: payload.target_cli,
target_system: payload.target_system,
expires_at_unix_secs,
};
let serialized = match serde_json::to_string(&session) {
Ok(value) => value,
Err(err) => {
return build_auth_error_response(
http::StatusCode::INTERNAL_SERVER_ERROR,
format!("install session serialize failed: {err:?}"),
false,
)
}
};
if let Err(err) = state
.runtime_kv_setex(
&install_session_runtime_key(&code),
&serialized,
INSTALL_SESSION_TTL_SECS,
)
.await
{
return build_auth_error_response(
http::StatusCode::INTERNAL_SERVER_ERROR,
format!("install session create failed: {err:?}"),
false,
);
}
let base_url = session.base_url.trim_end_matches('/');
Json(json!({
"install_code": code,
"expires_at_unix_secs": expires_at_unix_secs,
"expires_in_seconds": INSTALL_SESSION_TTL_SECS,
"target_cli": session.target_cli,
"target_cli_label": cli_label(session.target_cli),
"target_system": session.target_system,
"target_system_label": system_label(session.target_system),
"unix_command": format!("curl -fsSL {base_url}/install/{code} | sh"),
"powershell_command": format!("irm {base_url}/install/{code}.ps1 | iex"),
}))
.into_response()
}
pub(super) async fn maybe_build_local_install_response(
state: &AppState,
request_context: &GatewayPublicRequestContext,
) -> Option<Response<Body>> {
let decision = request_context.control_decision.as_ref()?;
if decision.route_family.as_deref() != Some("install") {
return None;
}
let Some((code, wants_powershell)) = install_code_from_path(&request_context.request_path)
else {
return Some(build_auth_error_response(
http::StatusCode::NOT_FOUND,
"install code 不存在或已失效",
false,
));
};
let raw = match state
.runtime_kv_getdel(&install_session_runtime_key(&code))
.await
{
Ok(Some(value)) => value,
Ok(None) => {
return Some(build_auth_error_response(
http::StatusCode::NOT_FOUND,
"install code 不存在、已过期或已使用",
false,
))
}
Err(err) => {
return Some(build_auth_error_response(
http::StatusCode::INTERNAL_SERVER_ERROR,
format!("install session lookup failed: {err:?}"),
false,
))
}
};
let session = match serde_json::from_str::<StoredInstallSession>(&raw) {
Ok(value) => value,
Err(_) => {
return Some(build_auth_error_response(
http::StatusCode::BAD_REQUEST,
"install code 数据无效",
false,
))
}
};
if session.expires_at_unix_secs <= unix_secs_now() {
return Some(build_auth_error_response(
http::StatusCode::NOT_FOUND,
"install code 已过期",
false,
));
}
let body = if wants_powershell {
build_powershell_script(&session)
} else {
build_unix_script(&session)
};
let content_type = if wants_powershell {
"text/plain; charset=utf-8"
} else {
"text/x-shellscript; charset=utf-8"
};
let mut response = Response::new(Body::from(body));
response.headers_mut().insert(
http::header::CONTENT_TYPE,
http::HeaderValue::from_static(content_type),
);
response.headers_mut().insert(
http::header::CACHE_CONTROL,
http::HeaderValue::from_static("no-store"),
);
response.headers_mut().insert(
http::header::PRAGMA,
http::HeaderValue::from_static("no-cache"),
);
response.headers_mut().insert(
http::header::HeaderName::from_static("x-content-type-options"),
http::HeaderValue::from_static("nosniff"),
);
Some(response)
}

View File

@@ -1,9 +1,10 @@
use super::{
auth_password_policy_level, build_auth_error_response, build_auth_wallet_summary_payload,
decrypt_catalog_secret_with_fallbacks, encrypt_catalog_secret_with_fallbacks, handle_auth_me,
query_param_optional_bool, query_param_value, resolve_authenticated_local_user,
unix_secs_to_rfc3339, validate_auth_register_password, AppState, AuthenticatedLocalUserContext,
GatewayPublicRequestContext, PUBLIC_CAPABILITY_DEFINITIONS,
handle_users_me_api_key_install_session_create, query_param_optional_bool, query_param_value,
resolve_authenticated_local_user, unix_secs_to_rfc3339,
users_me_api_key_install_sessions_path_matches, validate_auth_register_password, AppState,
AuthenticatedLocalUserContext, GatewayPublicRequestContext, PUBLIC_CAPABILITY_DEFINITIONS,
};
use crate::admin_api::build_admin_endpoint_health_status_payload;
use crate::handlers::internal::build_management_token_payload;

View File

@@ -4,8 +4,9 @@ use axum::{body::Body, http, response::Response};
use super::{
handle_auth_me, handle_users_me_api_key_capabilities_put, handle_users_me_api_key_create,
handle_users_me_api_key_delete, handle_users_me_api_key_detail_get,
handle_users_me_api_key_patch, handle_users_me_api_key_providers_put,
handle_users_me_api_key_update, handle_users_me_api_keys_get, handle_users_me_available_models,
handle_users_me_api_key_install_session_create, handle_users_me_api_key_patch,
handle_users_me_api_key_providers_put, handle_users_me_api_key_update,
handle_users_me_api_keys_get, handle_users_me_available_models,
handle_users_me_delete_other_sessions, handle_users_me_delete_session,
handle_users_me_detail_put, handle_users_me_endpoint_status_get,
handle_users_me_management_token_create, handle_users_me_management_token_delete,
@@ -17,8 +18,8 @@ use super::{
handle_users_me_providers_get, handle_users_me_sessions_get, handle_users_me_update_session,
handle_users_me_usage_active_get, handle_users_me_usage_get, handle_users_me_usage_heatmap_get,
handle_users_me_usage_interval_timeline_get, users_me_api_key_capabilities_path_matches,
users_me_api_key_detail_path_matches, users_me_api_key_providers_path_matches,
users_me_management_token_detail_path_matches,
users_me_api_key_detail_path_matches, users_me_api_key_install_sessions_path_matches,
users_me_api_key_providers_path_matches, users_me_management_token_detail_path_matches,
users_me_management_token_regenerate_path_matches,
users_me_management_token_toggle_path_matches, users_me_management_tokens_root,
users_me_session_detail_path_matches, AppState, GatewayPublicRequestContext,
@@ -167,6 +168,19 @@ pub(crate) async fn maybe_build_local_users_me_response(
.await,
)
}
Some("api_key_install_session_create")
if users_me_api_key_install_sessions_path_matches(&request_context.request_path) =>
{
Some(
handle_users_me_api_key_install_session_create(
state,
request_context,
headers,
request_body,
)
.await,
)
}
Some("management_token_regenerate")
if users_me_management_token_regenerate_path_matches(&request_context.request_path) =>
{