Skip to content
Clone

fix(identity): embed the OAuth identity service and add scheduled deletion

- vendor KenOAuth as the in-process `kenoauth` crate; `kenauth` now calls the
  local AuthSvc instead of a remote gRPC/HTTP endpoint, and JWKS, token
  exchange and refresh are served in process
- boot the identity HTTP/gRPC servers from the API binary, build the login
  frontend in the Docker image, and ship ports 7021/7022
- add the `deletion` crate plus account_deletion / space_deletion tables to
  schedule deletions behind a grace period
- expose GET/POST/DELETE /me/deletion and POST /spaces/{space_name}/restore,
  and run a worker that purges expired targets
- extend the authority proto with DeleteAccount, CancelAccountDeletion,
  PurgeAccount and a filtered ListUsers query for the admin console
- regenerate openapi.json and replace the remote OAuth/email RPC wiring with
  the in-process service
ZhenYi committed
parent acbf7ea commit dd4650a Browse files

300 files changed

+17282 −899

This change is very large, so some diffs are not shown. Clone the repository to see the full change.

File tree 300
Modified app/src/cli.rs View app/src/cli.rs
@@ -11,6 +11,7 @@
1111pub enum Command {
1212 Serve,
1313 Migrate,
14 IdentityOpenapi,
1415 Openapi {
1516 #[arg(long)]
1617 check: bool,
Modified app/src/main.rs View app/src/main.rs
@@ -15,6 +15,7 @@
1515 Command::Serve => serve(),
1616 Command::Migrate => migrate(),
1717 Command::Openapi { check } => openapi(check),
18 Command::IdentityOpenapi => identity_openapi(),
1819 }
1920}
2021
@@ -36,8 +37,39 @@
3637 return Ok(());
3738 }
3839
39 info!("connecting to database / redis / storage / email");
40 let state = export::AppState::with_config(cfg.clone().into_app_config())
40 let cancel = CancellationToken::new();
41 let app_config = cfg.clone().into_app_config();
42 let database = std::sync::Arc::new(
43 export::AppDatabase::connect(app_config.database.clone())
44 .await
45 .context("connecting to the shared database")?,
46 );
47 let identity = match identity_config()? {
48 Some(identity_config) => {
49 let db =
50 kenoauth::db::DbConn::new(database.primary().clone(), database.replicas().to_vec());
51 Some(
52 kenoauth::oauth::start(&identity_config, db)
53 .await
54 .context("starting identity service")?,
55 )
56 }
57 None => None,
58 };
59 let client = identity.as_ref().map(|runtime| {
60 std::sync::Arc::new(kenauth::KenAuthClient::local(
61 runtime.service().clone(),
62 cfg.oauth.http_timeout_ms,
63 ))
64 });
65 if let Some(runtime) = &identity
66 && cfg.oauth.enabled
67 && runtime.service().signing().issuer().trim_end_matches('/')
68 != cfg.oauth.issuer.trim_end_matches('/')
69 {
70 anyhow::bail!("oauth.issuer must match identity.jwt.issuer");
71 }
72 let state = export::AppState::with_identity(app_config, database, client)
4173 .await
4274 .context("building AppState")?;
4375 info!("dependencies ready");
@@ -54,22 +86,24 @@
5486 );
5587
5688 tokio::spawn(chat::worker::run_background_tasks(state.clone()));
89 tokio::spawn(deletion::run_worker(state.clone()));
5790
5891 let conn = queue::RocketMqConn::init(&cfg.rocketmq)
5992 .await
6093 .map_err(|err| anyhow::anyhow!("failed to start the rocketmq producer: {err}"))?;
61 tokio::spawn(queue::relay::run(
94 let relay_task = tokio::spawn(queue::relay::run(
6295 state.database.primary().clone(),
63 queue::Publisher::new(conn),
64 CancellationToken::new(),
96 queue::Publisher::new(conn.clone()),
97 cancel.clone(),
6598 ));
6699 info!("event outbox relay started");
67100
101 let mut push_tasks = Vec::new();
68102 if cfg.push.enabled {
69103 for _ in 0..cfg.push.workers.max(1) {
70 let worker = push::PushWorker::new(state.clone(), CancellationToken::new())
104 let worker = push::PushWorker::new(state.clone(), cancel.clone())
71105 .map_err(|err| anyhow::anyhow!("failed to start push worker: {err}"))?;
72 tokio::spawn(worker.run());
106 push_tasks.push(tokio::spawn(worker.run()));
73107 }
74108 info!(
75109 workers = cfg.push.workers.max(1),
@@ -95,10 +129,39 @@
95129 .with_context(|| format!("binding {addr}"))?;
96130 info!(%addr, "apihost listening");
97131
98 axum::serve(listener, api_router.into_make_service())
99 .with_graceful_shutdown(export::telemetry::shutdown_signal())
100 .await
101 .context("axum server error")?;
132 let api_cancel = cancel.clone();
133 let api = async move {
134 let result = axum::serve(listener, api_router.into_make_service())
135 .with_graceful_shutdown(api_cancel.clone().cancelled_owned())
136 .await
137 .context("axum server error");
138 api_cancel.cancel();
139 result
140 };
141 let identity_cancel = cancel.clone();
142 let identity_server = async move {
143 match identity {
144 Some(runtime) => runtime.serve_until(identity_cancel).await,
145 None => {
146 identity_cancel.cancelled().await;
147 Ok(())
148 }
149 }
150 };
151 let signal = async {
152 tokio::select! {
153 _ = export::telemetry::shutdown_signal() => cancel.cancel(),
154 _ = cancel.cancelled() => {},
155 }
156 };
157 let (api_result, identity_result, ()) = tokio::join!(api, identity_server, signal);
158 let _ = relay_task.await;
159 for task in push_tasks {
160 let _ = task.await;
161 }
162 conn.shutdown().await;
163 api_result?;
164 identity_result?;
102165
103166 export::telemetry::shutdown_telemetry();
104167
@@ -134,6 +197,13 @@
134197
135198 info!("running schema migration");
136199 sync_schema(&db).await.context("running schema sync")?;
200 if let Some(identity) = identity_config()? {
201 let identity_db = kenoauth::db::DbConn::new(db.clone(), Vec::new());
202 kenoauth::auth::migrate::sync_schema(&identity_db)
203 .await
204 .context("synchronizing identity schema")?;
205 drop(identity);
206 }
137207 info!("schema migration completed");
138208
139209 Ok(())
@@ -187,3 +257,26 @@
187257 println!("wrote {}", web_path.display());
188258 Ok(())
189259}
260
261fn identity_config() -> anyhow::Result<Option<kenoauth::config::Config>> {
262 let figment = config::loader::figment();
263 let enabled = figment
264 .extract_inner::<bool>("identity.enabled")
265 .context("loading identity.enabled")?;
266 if !enabled {
267 return Ok(None);
268 }
269 let value: serde_json::Value = figment
270 .extract_inner("identity")
271 .context("loading [identity] configuration")?;
272 Ok(Some(kenoauth::config::Config::from_value(value)))
273}
274
275fn identity_openapi() -> anyhow::Result<()> {
276 use utoipa_identity::OpenApi;
277 let doc = kenoauth::auth::openapi::ApiDoc::openapi();
278 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../lib/identity/OpenApi.Auth.json");
279 std::fs::write(&path, doc.to_pretty_json()?).context("writing identity OpenAPI")?;
280 println!("wrote {}", path.display());
281 Ok(())
282}
Modified app/Cargo.toml View app/Cargo.toml
@@ -5,12 +5,16 @@
55publish = false
66
77[dependencies]
8kenauth = { workspace = true }
9serde_json = { workspace = true }
10kenoauth = { workspace = true }
811anyhow = { workspace = true }
912auth = { workspace = true }
1013axum = { workspace = true }
1114chat = { workspace = true }
1215clap = { workspace = true }
1316config = { workspace = true }
17deletion = { workspace = true }
1418export = { workspace = true }
1519jwt = { workspace = true }
1620models = { workspace = true }
@@ -24,3 +28,5 @@
2428utoipa = { workspace = true }
2529utoipa-swagger-ui = { workspace = true }
2630webapi = { workspace = true }
31
32utoipa-identity = { package = "utoipa", version = "6.0.0" }
Modified config/default.toml View config/default.toml
@@ -104,6 +104,7 @@
104104allow_self_join_groups = false
105105join_request_cooldown_hours = 24
106106max_dir_nodes = 5000
107deletion_grace_days = 14
107108
108109[chat]
109110max_pagination = 200
@@ -259,3 +260,203 @@
259260presence_ttl_secs = 43200
260261hand_ttl_secs = 3600
261262recording_enabled = false
263
264[identity]
265enabled = true
266
267[[identity.apps]]
268id = "api"
269audience = "api"
270active_key_id = "app-example-1"
271
272[[identity.apps.keys]]
273key_id = "app-example-1"
274algorithm = "rsa_oaep256_a256_gcm"
275enabled = true
276
277[identity.apps.keys.public_key]
278file = "config/api/keys/app-example-1_public.pem"
279
280[identity.auth]
281verify_code_ttl_secs = 600
282verify_code_len = 6
283reset_token_ttl_secs = 1800
284reset_token_bytes = 32
285email_code_rate_limit_secs = 60
286max_verify_attempts = 5
287min_age = 13
288totp_encryption_key = "REPLACE_WITH_TOTP_ENCRYPTION_KEY_BASE64_32_BYTES"
289
290[identity.auth.oauth]
291public_url = "https://oauth.gitdata.ai"
292rpc_key = "REPLACE_WITH_RPC_KEY_AT_LEAST_32_CHARS"
293socket_origins = [
294 "https://gitdata.ai",
295]
296
297[[identity.auth.oauth.clients]]
298client_id = "api"
299name = "GitDataAi API"
300kind = "public"
301redirect_uris = [
302 "https://gitdata.ai/oauth/callback",
303]
304post_logout_redirect_uris = [
305 "https://gitdata.ai/",
306]
307scopes = [
308 "openid",
309 "user_name",
310 "ssh_key",
311 "access_key",
312 "assets",
313 "email",
314 "profile",
315 "theme",
316 "guild",
317 "event",
318 "avatar",
319]
320
321[[identity.auth.oauth.clients]]
322client_id = "api-service"
323name = "GitDataAi Internal Service"
324kind = "confidential"
325enabled = false
326secret = "REPLACE_WITH_CLIENT_SECRET_AT_LEAST_32_CHARS"
327scopes = [
328 "access_key",
329 "repo_read",
330 "repo_write",
331]
332service_scopes = [
333 "access_key",
334]
335
336[identity.auth.password_policy]
337min_length = 8
338require_upper = true
339require_lower = true
340require_digit = true
341require_symbol = false
342
343[[identity.auth.agreements]]
344key = "terms"
345version = "0.1.0"
346title = "Terms of Service"
347content_md = "Placeholder terms of service."
348
349[[identity.auth.agreements]]
350key = "privacy"
351version = "0.1.0"
352title = "Privacy Policy"
353content_md = "Placeholder privacy policy."
354
355[identity.jwt]
356issuer = "https://oauth.gitdata.ai"
357leeway_secs = 30
358key_id = "k1"
359private_key = "REPLACE_WITH_JWT_ED25519_PRIVATE_KEY_BASE64URL"
360retired = []
361
362[identity.service_auth]
363service_id = "oauth"
364key = "REPLACE_WITH_OAUTH_SERVICE_SECRET_32_BYTES_MINIMUM"
365
366[identity.service_auth.peers]
367api = "sha256:REPLACE_WITH_API_SERVICE_SECRET_SHA256_HEX"
368socket = "sha256:REPLACE_WITH_SOCKET_SERVICE_SECRET_SHA256_HEX"
369oauth = "sha256:REPLACE_WITH_OAUTH_SERVICE_SECRET_SHA256_HEX"
370git = "sha256:REPLACE_WITH_GIT_SERVICE_SECRET_SHA256_HEX"
371
372[identity.rocketmq]
373name_server = "127.0.0.1:9876"
374producer_group = "kenoauth"
375topic_prefix = "oauth"
376send_timeout_ms = 5000
377max_message_bytes = 4194304
378
379[identity.email]
380workers = 4
381capacity = 1024
382enqueue_timeout_ms = 5000
383retry_delay_ms = 2000
384max_attempts = 3
385
386[identity.pow]
387difficulty = 16
388max_difficulty = 24
389expires_secs = 60
390mac_key = "REPLACE_WITH_POW_MAC_KEY_BASE64_32_BYTES"
391
392[identity.redis]
393host = "127.0.0.1"
394port = 6379
395db = 0
396type = "Standard"
397nodes = []
398tls = false
399key_prefix = "oauth"
400default_ttl_secs = 300
401
402[identity.redis.pool]
403max_size = 16
404wait_timeout_secs = 5
405create_timeout_secs = 5
406recycle_timeout_secs = 5
407
408[identity.server]
409http_addr = "127.0.0.1:7021"
410grpc_addr = "127.0.0.1:7022"
411
412[identity.session]
413cookie_name = "session"
414max_age = 2592000
415anonymous_max_age = 300
416path = "/"
417secure = false
418same_site = "Lax"
419http_only = true
420trust_proxy = false
421
422[identity.smtp]
423host = "127.0.0.1"
424port = 1025
425security = "plain"
426from = "no-reply@gitdata.local"
427timeout_seconds = 30
428max_attachment_bytes = 26214400
429
430[identity.web]
431web_base_url = "https://git.example.com"
432cors_allow_origins = [
433 "https://git.example.com",
434]
435cors_allow_methods = [
436 "GET",
437 "POST",
438 "PUT",
439 "PATCH",
440 "DELETE",
441 "HEAD",
442 "OPTIONS",
443]
444cors_allow_headers = [
445 "accept",
446 "authorization",
447 "content-type",
448 "x-csrf-token",
449 "x-log-id",
450 "x-request-id",
451]
452cors_expose_headers = [
453 "cache-control",
454 "content-disposition",
455 "etag",
456 "location",
457 "retry-after",
458 "x-log-id",
459 "x-request-id",
460]
461cors_allow_credentials = false
462cors_max_age_secs = 600
Modified deploy/compose.yaml View deploy/compose.yaml
@@ -3,11 +3,11 @@
33# docker compose up -d # 用已有镜像启动
44# docker compose up -d --build # 从仓库根目录 Dockerfile 重新构建
55#
6# 端口(7xxx 段):7051 = apihost(REST + /healthz + /readyz + /openapi)
6# 端口:7051 = API;7021 = OAuth HTTP/登录页;7022 = 兼容外部服务的认证 gRPC。
77# config 里另留了 7052(githost HTTP)与 7053(git SSH)两个口,但当前 apihost
88# 二进制不监听它们(git 读写走 KenShell),改由 KenShell 的 7011/7012 承担。
99#
10# 为什么用 host 网络:PostgreSQL / Redis / RustFS / KenOAuth / KenShell 都在
10# 为什么用 host 网络:PostgreSQL / Redis / RustFS / KenShell 都在
1111# 宿主机 127.0.0.1 上(与其它服务一致)。
1212#
1313# 首次部署前置(本机已执行):
@@ -16,7 +16,7 @@
1616# docker run --rm --network host -v "$PWD/config:/app/config:ro" \
1717# --entrypoint /usr/local/bin/kenapi kenapi:local migrate
1818# 2. S3 桶(RustFS):gitdata(与 config/local.toml 的 [s3].bucket 一致)
19# 3. KenOAuth 里要有 client_id = gitdata-web 的公开客户端(见 KenOAuth 配置)
19# 3. [identity.auth.oauth] 配置 gitdata-web 客户端;OAuth 已内嵌,无需独立服务。
2020#
2121# 配置:镜像内 config/default.toml + 挂载的 config/local.toml(本机地址与密钥)。
2222
@@ -44,6 +44,7 @@
4444 # default.toml 已烘进镜像;local.toml 提供本机数据库/Redis/S3/OAuth 地址。
4545 # 整个 config 目录只读挂载,顺便保留未来新增的本地配置文件。
4646 - ../config:/app/config:ro
47 stop_grace_period: 120s
4748 working_dir: /app
4849 logging:
4950 driver: json-file
Added lib/auth/src/handlers/account_deletion.rs View lib/auth/src/handlers/account_deletion.rs
@@ -0,0 +1,98 @@
1use axum::extract::State;
2use export::rest::{AppResponseData, NoData};
3use jwt::Session;
4use jwt::share::session::require_uid;
5use serde::Serialize;
6use time::OffsetDateTime;
7use utoipa::ToSchema;
8
9use crate::errors::{ApiError, AuthError};
10use crate::services::AuthService;
11
12#[derive(Debug, Clone, Serialize, ToSchema)]
13pub struct AccountDeletionDto {
14 pub status: String,
15 pub requested_at: OffsetDateTime,
16 pub purge_after: OffsetDateTime,
17 pub last_error: Option<String>,
18}
19
20impl From<deletion::AccountDeletionStatus> for AccountDeletionDto {
21 fn from(value: deletion::AccountDeletionStatus) -> Self {
22 Self {
23 status: value.status,
24 requested_at: value.requested_at,
25 purge_after: value.purge_after,
26 last_error: value.last_error,
27 }
28 }
29}
30
31#[utoipa::path(
32 get,
33 path = "/me/deletion",
34 tag = "account",
35 description = "读取当前账号的删除计划;没有计划时 data 为 null。",
36 responses(
37 (status = 200, description = "deletion status", body = AppResponseData<AccountDeletionDto>),
38 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
39 )
40)]
41pub async fn account_deletion_status(
42 State(service): State<AuthService>,
43 session: Session,
44) -> Result<AppResponseData<Option<AccountDeletionDto>>, ApiError> {
45 let uid = require_uid(&session)?;
46 let status = deletion::account_deletion_status(&service.state, uid)
47 .await
48 .map_err(AuthError::from)?;
49 Ok(AppResponseData::ok(status.map(AccountDeletionDto::from)))
50}
51
52#[utoipa::path(
53 post,
54 path = "/me/deletion",
55 tag = "account",
56 description = "申请删除当前账号:立即软删并撤销全部会话/令牌/SSH 公钥,宽限期后匿名化。仍有活跃组织 OWNER 身份时返回 409。",
57 responses(
58 (status = 200, description = "deletion scheduled", body = AppResponseData<AccountDeletionDto>),
59 (status = 409, description = "account still owns an active organization", body = AppResponseData<NoData>),
60 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
61 )
62)]
63pub async fn request_account_deletion(
64 State(service): State<AuthService>,
65 session: Session,
66) -> Result<AppResponseData<AccountDeletionDto>, ApiError> {
67 let uid = require_uid(&session)?;
68 let status = deletion::request_account_deletion(
69 &service.state,
70 uid,
71 "user requested account deletion",
72 )
73 .await
74 .map_err(AuthError::from)?;
75 Ok(AppResponseData::ok(status.into()))
76}
77
78#[utoipa::path(
79 delete,
80 path = "/me/deletion",
81 tag = "account",
82 description = "撤销尚未执行的账号删除计划;已撤销的会话与令牌不会恢复,需重新登录。",
83 responses(
84 (status = 200, description = "deletion cancelled", body = AppResponseData<NoData>),
85 (status = 404, description = "no deletion is pending", body = AppResponseData<NoData>),
86 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
87 )
88)]
89pub async fn cancel_account_deletion(
90 State(service): State<AuthService>,
91 session: Session,
92) -> Result<AppResponseData<NoData>, ApiError> {
93 let uid = require_uid(&session)?;
94 deletion::cancel_account_deletion(&service.state, uid)
95 .await
96 .map_err(AuthError::from)?;
97 Ok(AppResponseData::empty())
98}
Modified lib/auth/src/handlers/mod.rs View lib/auth/src/handlers/mod.rs
@@ -1,4 +1,5 @@
11pub mod accept_agreement;
2pub mod account_deletion;
23pub mod agreement_page;
34pub mod create_report;
45pub mod delete_notification;
Modified lib/auth/src/oauth/mod.rs View lib/auth/src/oauth/mod.rs
@@ -68,10 +68,7 @@
6868 }
6969}
7070
71pub fn redirect_with_cookies(
72 location: &str,
73 cookies: &[String],
74) -> Response {
71pub fn redirect_with_cookies(location: &str, cookies: &[String]) -> Response {
7572 let mut response = Redirect::to(location).into_response();
7673 for cookie in cookies {
7774 append_cookie(&mut response, cookie);
@@ -86,30 +83,20 @@
8683 redirect_uri: &str,
8784) -> Result<TokenResponse, AuthError> {
8885 let config = &service.config().oauth;
89 let form = [
90 ("grant_type", "authorization_code"),
91 ("code", code),
92 ("redirect_uri", redirect_uri),
93 ("client_id", config.client_id.as_str()),
94 ("code_verifier", verifier),
95 ];
96 let response = reqwest::Client::new()
97 .post(config.token_endpoint())
98 .form(&form)
99 .send()
100 .await
101 .map_err(|error| AuthError::Internal(format!("token endpoint request failed: {error}")))?;
102 if !response.status().is_success() {
103 let status = response.status();
104 let body = response.text().await.unwrap_or_default();
105 return Err(AuthError::BadRequest(format!(
106 "token endpoint rejected the grant ({status}): {body}"
107 )));
108 }
109 response
110 .json::<TokenResponse>()
111 .await
112 .map_err(|error| AuthError::Internal(format!("invalid token response: {error}")))
86 local_grant(
87 service,
88 kenoauth::auth::protocol::TokenRequest {
89 grant_type: "authorization_code".to_owned(),
90 code: Some(code.to_owned()),
91 redirect_uri: Some(redirect_uri.to_owned()),
92 code_verifier: Some(verifier.to_owned()),
93 refresh_token: None,
94 client_id: Some(config.client_id.clone()),
95 client_secret: (!config.client_secret.is_empty()).then(|| config.client_secret.clone()),
96 scope: None,
97 },
98 )
99 .await
113100}
114101
115102pub async fn refresh_grant(
@@ -117,26 +104,41 @@
117104 refresh_token: &str,
118105) -> Result<TokenResponse, AuthError> {
119106 let config = &service.config().oauth;
120 let form = [
121 ("grant_type", "refresh_token"),
122 ("refresh_token", refresh_token),
123 ("client_id", config.client_id.as_str()),
124 ];
125 let response = reqwest::Client::new()
126 .post(config.token_endpoint())
127 .form(&form)
128 .send()
129 .await
130 .map_err(|error| AuthError::Internal(format!("token endpoint request failed: {error}")))?;
131 if !response.status().is_success() {
132 let body = response.text().await.unwrap_or_default();
133 return Err(AuthError::BadRequest(format!(
134 "refresh token rejected: {body}"
135 )));
136 }
137 response
138 .json::<TokenResponse>()
107 local_grant(
108 service,
109 kenoauth::auth::protocol::TokenRequest {
110 grant_type: "refresh_token".to_owned(),
111 code: None,
112 redirect_uri: None,
113 code_verifier: None,
114 refresh_token: Some(refresh_token.to_owned()),
115 client_id: Some(config.client_id.clone()),
116 client_secret: (!config.client_secret.is_empty()).then(|| config.client_secret.clone()),
117 scope: None,
118 },
119 )
120 .await
121}
122
123async fn local_grant(
124 service: &AuthService,
125 form: kenoauth::auth::protocol::TokenRequest,
126) -> Result<TokenResponse, AuthError> {
127 let client = service
128 .state
129 .kenauth
130 .as_ref()
131 .ok_or_else(|| AuthError::Internal("identity service is unavailable".to_owned()))?;
132 let value = kenoauth::auth::protocol::exchange_local(client.service(), &form)
139133 .await
134 .map_err(|error| {
135 if error.status >= 500 {
136 AuthError::Internal(error.message)
137 } else {
138 AuthError::BadRequest(error.message)
139 }
140 })?;
141 serde_json::from_value(value)
140142 .map_err(|error| AuthError::Internal(format!("invalid token response: {error}")))
141143}
142144/// 本次请求应该用哪个 `redirect_uri`。
@@ -153,7 +155,14 @@
153155 .and_then(|value| value.to_str().ok())
154156 })
155157 // 代理链可能给逗号分隔的列表,取第一跳。
156 .map(|value| value.split(',').next().unwrap_or(value).trim().to_ascii_lowercase());
158 .map(|value| {
159 value
160 .split(',')
161 .next()
162 .unwrap_or(value)
163 .trim()
164 .to_ascii_lowercase()
165 });
157166
158167 let Some(host) = host else {
159168 return config.redirect_uri.clone();
Modified lib/auth/src/errors.rs View lib/auth/src/errors.rs
@@ -42,6 +42,24 @@
4242
4343impl std::error::Error for AuthError {}
4444
45impl From<deletion::DeletionError> for AuthError {
46 fn from(error: deletion::DeletionError) -> Self {
47 use deletion::DeletionError;
48 match error {
49 DeletionError::Database(error) => Self::from(error),
50 DeletionError::Conflict(message) => Self::Conflict(message),
51 DeletionError::NotFound(message) => {
52 tracing::debug!(%message, "deletion target not found");
53 Self::NotFound
54 }
55 DeletionError::KenAuth(message)
56 | DeletionError::KenShell(message)
57 | DeletionError::Config(message) => Self::Internal(message),
58 }
59 }
60}
61
62
4563export::db_error_conversions!(AuthError);
4664export::redis_error_conversion!(AuthError);
4765export::export_error_conversion!(AuthError);
Modified lib/auth/src/openapi.rs View lib/auth/src/openapi.rs
@@ -18,6 +18,9 @@
1818 info(title = "gitdata auth api", version = "1.0"),
1919 paths(
2020 handlers::accept_agreement::accept_agreement,
21 handlers::account_deletion::account_deletion_status,
22 handlers::account_deletion::request_account_deletion,
23 handlers::account_deletion::cancel_account_deletion,
2124 handlers::create_report::create_report,
2225 handlers::delete_notification::delete_notification,
2326 handlers::get_preferences::get_preferences,
Modified lib/auth/src/routes.rs View lib/auth/src/routes.rs
@@ -3,6 +3,7 @@
33use axum::routing::{delete, get, post, put};
44
55use crate::handlers::accept_agreement::accept_agreement;
6use crate::handlers::account_deletion::{account_deletion_status, cancel_account_deletion, request_account_deletion};
67use crate::handlers::agreement_page::agreement_page;
78use crate::handlers::create_report::create_report;
89use crate::handlers::delete_notification::delete_notification;
@@ -36,6 +37,12 @@
3637 )
3738 .route("/users/{user_id}", get(get_user))
3839 .route("/me", get(me).patch(update_profile))
40 .route(
41 "/me/deletion",
42 get(account_deletion_status)
43 .post(request_account_deletion)
44 .delete(cancel_account_deletion),
45 )
3946 .route("/auth/jwt-request", get(crate::handlers::jwt_request::jwt_request))
4047 .route("/me/username", put(users::update_username))
4148 .route(
Modified lib/auth/Cargo.toml View lib/auth/Cargo.toml
@@ -4,8 +4,10 @@
44edition.workspace = true
55
66[dependencies]
7kenoauth = { workspace = true }
78async-trait = { workspace = true }
89config = { workspace = true }
10deletion = { workspace = true }
911jwt = { workspace = true }
1012export = { workspace = true }
1113models = { workspace = true }
Modified lib/config/src/space.rs View lib/config/src/space.rs
@@ -13,6 +13,7 @@
1313 pub allow_self_join_groups: bool,
1414 pub join_request_cooldown_hours: i64,
1515 pub max_dir_nodes: u64,
16 pub deletion_grace_days: u32,
1617}
1718
1819impl Default for SpaceConfig {
@@ -28,6 +29,7 @@
2829 allow_self_join_groups: false,
2930 join_request_cooldown_hours: 24,
3031 max_dir_nodes: 5000,
32 deletion_grace_days: 14,
3133 }
3234 }
3335}
Added lib/deletion/src/lib.rs View lib/deletion/src/lib.rs
@@ -0,0 +1,706 @@
1use std::time::Duration;
2
3use export::AppState;
4use kenauth::KenAuthError;
5use kenshell::KenShellClient;
6use models::dir::{dir_overwrite_group, dir_overwrite_users, dirs};
7use models::repo::repos;
8use models::room::rooms;
9use models::space::{
10 space_bans, space_extras, space_group_permissions, space_groups, space_invite,
11 space_invite_groups, space_join_request, space_join_rule, space_member_extras,
12 space_member_groups, space_warn,
13};
14use models::system::{account_deletion, space_deletion};
15use models::users::{
16 user, user_agreement_acceptances, user_blocks, user_external_identity, user_favorite_memes,
17 user_followers, user_notifications, user_preferences, user_profiles, user_push_subscriptions,
18 user_recent_mentions, user_saved_messages,
19};
20use sea_orm::{
21 ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, Set,
22};
23use serde::Serialize;
24use time::OffsetDateTime;
25use uuid::Uuid;
26
27const RETRY_BASE_SECS: u64 = 30;
28const RETRY_MAX_SECS: u64 = 3600;
29
30#[derive(Debug, thiserror::Error)]
31pub enum DeletionError {
32 #[error("database: {0}")]
33 Database(#[from] sea_orm::DbErr),
34 #[error("{0}")]
35 Conflict(String),
36 #[error("{0}")]
37 NotFound(String),
38 #[error("kenoauth: {0}")]
39 KenAuth(String),
40 #[error("kenshell: {0}")]
41 KenShell(String),
42 #[error("config: {0}")]
43 Config(String),
44}
45
46impl DeletionError {
47 pub fn is_retryable(&self) -> bool {
48 !matches!(self, Self::Conflict(_) | Self::NotFound(_) | Self::Config(_))
49 }
50}
51
52fn ken_auth(error: KenAuthError) -> DeletionError {
53 match error {
54 KenAuthError::NotFound => DeletionError::NotFound("account or organization not found".into()),
55 KenAuthError::Rejected(message) => DeletionError::Conflict(message),
56 other => DeletionError::KenAuth(other.to_string()),
57 }
58}
59
60fn ken_shell(error: kenshell::KenShellError) -> DeletionError {
61 DeletionError::KenShell(error.to_string())
62}
63
64fn client(state: &AppState) -> Result<&kenauth::KenAuthClient, DeletionError> {
65 state
66 .kenauth
67 .as_deref()
68 .ok_or_else(|| DeletionError::Config("kenoauth is not configured".into()))
69}
70
71fn shell_client(state: &AppState) -> Result<KenShellClient, DeletionError> {
72 let cfg = &state.config.git;
73 KenShellClient::connect_with_key(
74 &cfg.kenshell_endpoint,
75 &cfg.kenshell_rpc_key,
76 Duration::from_secs(cfg.kenshell_timeout_secs.max(1)),
77 )
78 .map_err(ken_shell)
79}
80
81fn now() -> OffsetDateTime {
82 OffsetDateTime::now_utc()
83}
84
85fn grace_until(days: u32) -> OffsetDateTime {
86 now() + time::Duration::days(i64::from(days.max(1)))
87}
88
89fn retry_at(attempts: i32) -> OffsetDateTime {
90 let exponent = attempts.clamp(1, 8) as u32;
91 let delay = (RETRY_BASE_SECS.saturating_mul(1u64 << exponent)).min(RETRY_MAX_SECS);
92 now() + time::Duration::seconds(delay as i64)
93}
94
95#[derive(Debug, Clone, Serialize)]
96pub struct AccountDeletionStatus {
97 pub status: String,
98 pub requested_at: OffsetDateTime,
99 pub purge_after: OffsetDateTime,
100 pub last_error: Option<String>,
101}
102
103#[derive(Debug, Clone, Serialize)]
104pub struct SpaceDeletionStatus {
105 pub status: String,
106 pub requested_at: OffsetDateTime,
107 pub purge_after: OffsetDateTime,
108 pub last_error: Option<String>,
109}
110
111pub async fn request_account_deletion(
112 state: &AppState,
113 user_id: i64,
114 reason: &str,
115) -> Result<AccountDeletionStatus, DeletionError> {
116 let db = state.database.write();
117 if let Some(existing) = account_deletion::Entity::find()
118 .filter(account_deletion::Column::UserId.eq(user_id))
119 .filter(account_deletion::Column::Status.eq("pending"))
120 .one(db)
121 .await?
122 {
123 return Ok(status_of_account(&existing));
124 }
125
126 let client = client(state)?;
127 let organizations = client
128 .list_organizations(
129 user_id,
130 user_id,
131 true,
132 kenauth::proto::common::v1::PageRequest {
133 page_size: 100,
134 page_token: String::new(),
135 },
136 )
137 .await
138 .map_err(ken_auth)?
139 .organizations;
140 for organization in organizations
141 .iter()
142 .filter(|organization| organization.owner_user_id == user_id)
143 {
144 let space_id = Uuid::parse_str(&organization.id)
145 .map_err(|_| DeletionError::Config("organization id is not a uuid".into()))?;
146 let pending = space_deletion::Entity::find()
147 .filter(space_deletion::Column::SpaceId.eq(space_id))
148 .filter(space_deletion::Column::Status.eq("pending"))
149 .one(db)
150 .await?;
151 if pending.is_none() {
152 return Err(DeletionError::Conflict(
153 "delete or transfer owned spaces before deleting the account".into(),
154 ));
155 }
156 }
157 let status = client.get_user_status(user_id).await.map_err(ken_auth)?;
158 if status.deletion_requested_at.is_none() && status.deleted_at.is_none() {
159 client
160 .delete_account(user_id, reason)
161 .await
162 .map_err(ken_auth)?;
163 }
164
165 let now = now();
166 let purge_after = grace_until(state.config.user.deletion_grace_days);
167 let row = match account_deletion::Entity::find()
168 .filter(account_deletion::Column::UserId.eq(user_id))
169 .one(db)
170 .await?
171 {
172 Some(existing) => {
173 let mut active: account_deletion::ActiveModel = existing.into();
174 active.status = Set("pending".into());
175 active.requested_at = Set(now);
176 active.purge_after = Set(purge_after);
177 active.available_at = Set(purge_after);
178 active.last_error = Set(None);
179 active.attempts = Set(0);
180 active.updated_at = Set(now);
181 active.update(db).await?
182 }
183 None => {
184 account_deletion::ActiveModel {
185 id: Set(Uuid::now_v7()),
186 user_id: Set(user_id),
187 requested_at: Set(now),
188 purge_after: Set(purge_after),
189 status: Set("pending".into()),
190 last_error: Set(None),
191 attempts: Set(0),
192 available_at: Set(purge_after),
193 created_at: Set(now),
194 updated_at: Set(now),
195 }
196 .insert(db)
197 .await?
198 }
199 };
200 Ok(status_of_account(&row))
201}
202
203pub async fn cancel_account_deletion(
204 state: &AppState,
205 user_id: i64,
206) -> Result<(), DeletionError> {
207 client(state)?
208 .cancel_account_deletion(user_id)
209 .await
210 .map_err(ken_auth)?;
211 account_deletion::Entity::delete_many()
212 .filter(account_deletion::Column::UserId.eq(user_id))
213 .exec(state.database.write())
214 .await?;
215 Ok(())
216}
217
218pub async fn account_deletion_status(
219 state: &AppState,
220 user_id: i64,
221) -> Result<Option<AccountDeletionStatus>, DeletionError> {
222 Ok(account_deletion::Entity::find()
223 .filter(account_deletion::Column::UserId.eq(user_id))
224 .one(state.database.read())
225 .await?
226 .as_ref()
227 .map(status_of_account))
228}
229
230pub async fn request_space_deletion(
231 state: &AppState,
232 actor: i64,
233 space_id: Uuid,
234 reason: &str,
235) -> Result<SpaceDeletionStatus, DeletionError> {
236 let db = state.database.write();
237 let client = client(state)?;
238 let organization = client
239 .get_organization(actor, &space_id.to_string(), "")
240 .await
241 .map_err(ken_auth)?
242 .organization
243 .ok_or_else(|| DeletionError::NotFound("organization not found".into()))?;
244 if organization.status != kenauth::proto::v1::OrganizationStatus::Archived as i32 {
245 client
246 .archive_organization(actor, &space_id.to_string(), reason)
247 .await
248 .map_err(ken_auth)?;
249 }
250
251 let now = now();
252 let purge_after = grace_until(state.config.space.deletion_grace_days);
253 let row = match space_deletion::Entity::find()
254 .filter(space_deletion::Column::SpaceId.eq(space_id))
255 .one(db)
256 .await?
257 {
258 Some(existing) => {
259 let mut active: space_deletion::ActiveModel = existing.into();
260 active.requested_by = Set(actor);
261 active.requested_at = Set(now);
262 active.purge_after = Set(purge_after);
263 active.status = Set("pending".into());
264 active.available_at = Set(purge_after);
265 active.last_error = Set(None);
266 active.attempts = Set(0);
267 active.updated_at = Set(now);
268 active.update(db).await?
269 }
270 None => {
271 space_deletion::ActiveModel {
272 id: Set(Uuid::now_v7()),
273 space_id: Set(space_id),
274 requested_by: Set(actor),
275 requested_at: Set(now),
276 purge_after: Set(purge_after),
277 status: Set("pending".into()),
278 last_error: Set(None),
279 attempts: Set(0),
280 available_at: Set(purge_after),
281 created_at: Set(now),
282 updated_at: Set(now),
283 }
284 .insert(db)
285 .await?
286 }
287 };
288 Ok(status_of_space(&row))
289}
290
291pub async fn cancel_space_deletion(
292 state: &AppState,
293 actor: i64,
294 space_id: Uuid,
295) -> Result<(), DeletionError> {
296 client(state)?
297 .restore_organization(actor, &space_id.to_string())
298 .await
299 .map_err(ken_auth)?;
300 space_deletion::Entity::delete_many()
301 .filter(space_deletion::Column::SpaceId.eq(space_id))
302 .exec(state.database.write())
303 .await?;
304 Ok(())
305}
306
307pub async fn space_deletion_status(
308 state: &AppState,
309 space_id: Uuid,
310) -> Result<Option<SpaceDeletionStatus>, DeletionError> {
311 Ok(space_deletion::Entity::find()
312 .filter(space_deletion::Column::SpaceId.eq(space_id))
313 .one(state.database.read())
314 .await?
315 .as_ref()
316 .map(status_of_space))
317}
318
319fn status_of_account(row: &account_deletion::Model) -> AccountDeletionStatus {
320 AccountDeletionStatus {
321 status: row.status.clone(),
322 requested_at: row.requested_at,
323 purge_after: row.purge_after,
324 last_error: row.last_error.clone(),
325 }
326}
327
328fn status_of_space(row: &space_deletion::Model) -> SpaceDeletionStatus {
329 SpaceDeletionStatus {
330 status: row.status.clone(),
331 requested_at: row.requested_at,
332 purge_after: row.purge_after,
333 last_error: row.last_error.clone(),
334 }
335}
336
337pub async fn run_worker(state: AppState) {
338 tracing::info!("deletion worker started");
339 let mut interval = tokio::time::interval(Duration::from_secs(30));
340 interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
341 loop {
342 interval.tick().await;
343 if let Err(error) = purge_due(&state).await {
344 tracing::warn!(%error, "deletion worker pass failed");
345 }
346 }
347}
348
349async fn purge_due(state: &AppState) -> Result<(), DeletionError> {
350 let db = state.database.read();
351 let cutoff = now();
352 let spaces = space_deletion::Entity::find()
353 .filter(space_deletion::Column::Status.eq("pending"))
354 .filter(space_deletion::Column::AvailableAt.lte(cutoff))
355 .all(db)
356 .await?;
357 for row in spaces {
358 if let Err(error) = purge_space(state, &row).await {
359 record_space_failure(state, &row, &error).await?;
360 }
361 }
362
363 let accounts = account_deletion::Entity::find()
364 .filter(account_deletion::Column::Status.eq("pending"))
365 .filter(account_deletion::Column::AvailableAt.lte(cutoff))
366 .all(db)
367 .await?;
368 for row in accounts {
369 if let Err(error) = purge_account(state, &row).await {
370 record_account_failure(state, &row, &error).await?;
371 }
372 }
373 Ok(())
374}
375
376async fn purge_space(state: &AppState, row: &space_deletion::Model) -> Result<(), DeletionError> {
377 let client = client(state)?;
378 // Never delete an organization whose ownership changed after the request.
379 let organization = match client
380 .get_organization(0, &row.space_id.to_string(), "")
381 .await
382 {
383 Ok(response) => match response.organization {
384 Some(organization) => organization,
385 None => {
386 space_deletion::Entity::delete_by_id(row.id)
387 .exec(state.database.write())
388 .await?;
389 return Ok(());
390 }
391 },
392 Err(KenAuthError::NotFound) => {
393 space_deletion::Entity::delete_by_id(row.id)
394 .exec(state.database.write())
395 .await?;
396 return Ok(());
397 }
398 Err(error) => return Err(ken_auth(error)),
399 };
400 if organization.owner_user_id != row.requested_by {
401 client
402 .restore_organization(organization.owner_user_id, &row.space_id.to_string())
403 .await
404 .map_err(ken_auth)?;
405 space_deletion::Entity::delete_by_id(row.id)
406 .exec(state.database.write())
407 .await?;
408 return Ok(());
409 }
410
411 // 1. KenShell is the owner of repository/placement/LFS bytes.
412 let shell = shell_client(state)?;
413 let repos = shell
414 .list_repositories_for_org(&row.space_id.to_string())
415 .await
416 .map_err(ken_shell)?;
417 for repo in repos {
418 match shell.delete_repository(&repo.id).await {
419 Ok(()) => {}
420 Err(error) if error.is_not_found() => {}
421 Err(error) => return Err(ken_shell(error)),
422 }
423 }
424
425 // 2. Local KenApi resources become unreachable and org-scoped metadata is removed.
426 archive_local_space(state, row.space_id).await?;
427
428 // 3. The authority plane removes the organization for good.
429 client
430 .delete_organization(
431 row.requested_by,
432 &row.space_id.to_string(),
433 true,
434 "scheduled space deletion",
435 )
436 .await
437 .map_err(ken_auth)?;
438
439 space_deletion::Entity::delete_by_id(row.id)
440 .exec(state.database.write())
441 .await?;
442 tracing::info!(space_id = %row.space_id, "space purged");
443 Ok(())
444}
445
446async fn archive_local_space(state: &AppState, space_id: Uuid) -> Result<(), DeletionError> {
447 let db = state.database.write();
448 let now = now();
449
450 rooms::Entity::update_many()
451 .col_expr(rooms::Column::IsActive, sea_orm::sea_query::Expr::value(false))
452 .filter(rooms::Column::SpaceId.eq(Some(space_id)))
453 .exec(db)
454 .await?;
455
456 // Only the root nodes carry `space_id`; walk `parent_dir_id` to collect the
457 // whole tree, otherwise nested directories/repos survive the purge.
458 let mut dir_ids: Vec<Uuid> = dirs::Entity::find()
459 .filter(dirs::Column::SpaceId.eq(Some(space_id)))
460 .select_only()
461 .column(dirs::Column::Id)
462 .into_tuple()
463 .all(db)
464 .await?;
465 let mut seen: std::collections::HashSet<Uuid> = dir_ids.iter().copied().collect();
466 let mut frontier = dir_ids.clone();
467 while !frontier.is_empty() {
468 let children: Vec<Uuid> = dirs::Entity::find()
469 .filter(dirs::Column::ParentDirId.is_in(frontier))
470 .select_only()
471 .column(dirs::Column::Id)
472 .into_tuple()
473 .all(db)
474 .await?;
475 let mut next = Vec::new();
476 for id in children {
477 if seen.insert(id) {
478 dir_ids.push(id);
479 next.push(id);
480 }
481 }
482 frontier = next;
483 }
484 if !dir_ids.is_empty() {
485 dirs::Entity::update_many()
486 .col_expr(dirs::Column::IsArchived, sea_orm::sea_query::Expr::value(true))
487 .col_expr(dirs::Column::ArchivedAt, sea_orm::sea_query::Expr::value(Some(now)))
488 .filter(dirs::Column::Id.is_in(dir_ids.clone()))
489 .exec(db)
490 .await?;
491 repos::Entity::update_many()
492 .col_expr(repos::Column::IsArchived, sea_orm::sea_query::Expr::value(true))
493 .filter(repos::Column::DirId.is_in(dir_ids.clone()))
494 .exec(db)
495 .await?;
496 dir_overwrite_group::Entity::delete_many()
497 .filter(dir_overwrite_group::Column::DirId.is_in(dir_ids.clone()))
498 .exec(db)
499 .await?;
500 dir_overwrite_users::Entity::delete_many()
501 .filter(dir_overwrite_users::Column::DirId.is_in(dir_ids))
502 .exec(db)
503 .await?;
504 }
505
506 let group_ids: Vec<Uuid> = space_groups::Entity::find()
507 .filter(space_groups::Column::SpaceId.eq(space_id))
508 .select_only()
509 .column(space_groups::Column::Id)
510 .into_tuple()
511 .all(db)
512 .await?;
513 if !group_ids.is_empty() {
514 space_group_permissions::Entity::delete_many()
515 .filter(space_group_permissions::Column::GroupId.is_in(group_ids))
516 .exec(db)
517 .await?;
518 }
519 let invite_ids: Vec<Uuid> = space_invite::Entity::find()
520 .filter(space_invite::Column::SpaceId.eq(space_id))
521 .select_only()
522 .column(space_invite::Column::Id)
523 .into_tuple()
524 .all(db)
525 .await?;
526 if !invite_ids.is_empty() {
527 space_invite_groups::Entity::delete_many()
528 .filter(space_invite_groups::Column::InviteId.is_in(invite_ids))
529 .exec(db)
530 .await?;
531 }
532
533 space_member_groups::Entity::delete_many()
534 .filter(space_member_groups::Column::SpaceId.eq(space_id))
535 .exec(db)
536 .await?;
537 space_groups::Entity::delete_many()
538 .filter(space_groups::Column::SpaceId.eq(space_id))
539 .exec(db)
540 .await?;
541 space_invite::Entity::delete_many()
542 .filter(space_invite::Column::SpaceId.eq(space_id))
543 .exec(db)
544 .await?;
545 space_join_request::Entity::delete_many()
546 .filter(space_join_request::Column::SpaceId.eq(space_id))
547 .exec(db)
548 .await?;
549 space_join_rule::Entity::delete_many()
550 .filter(space_join_rule::Column::SpaceId.eq(space_id))
551 .exec(db)
552 .await?;
553 space_warn::Entity::delete_many()
554 .filter(space_warn::Column::SpaceId.eq(space_id))
555 .exec(db)
556 .await?;
557 space_bans::Entity::delete_many()
558 .filter(space_bans::Column::SpaceId.eq(space_id))
559 .exec(db)
560 .await?;
561 space_extras::Entity::delete_by_id(space_id).exec(db).await?;
562 space_member_extras::Entity::delete_many()
563 .filter(space_member_extras::Column::SpaceId.eq(space_id))
564 .exec(db)
565 .await?;
566 Ok(())
567}
568
569async fn purge_account(
570 state: &AppState,
571 row: &account_deletion::Model,
572) -> Result<(), DeletionError> {
573 let client = client(state)?;
574 let organizations = client
575 .list_organizations(
576 row.user_id,
577 row.user_id,
578 true,
579 kenauth::proto::common::v1::PageRequest {
580 page_size: 100,
581 page_token: String::new(),
582 },
583 )
584 .await
585 .map_err(ken_auth)?
586 .organizations;
587 if organizations
588 .iter()
589 .any(|organization| organization.owner_user_id == row.user_id)
590 {
591 return Err(DeletionError::Conflict(
592 "owned spaces are still being purged".into(),
593 ));
594 }
595
596 let db = state.database.write();
597 let now = now();
598
599 user_preferences::Entity::delete_many()
600 .filter(user_preferences::Column::UserId.eq(row.user_id))
601 .exec(db)
602 .await?;
603 user_profiles::Entity::delete_many()
604 .filter(user_profiles::Column::UserId.eq(row.user_id))
605 .exec(db)
606 .await?;
607 user_notifications::Entity::delete_many()
608 .filter(user_notifications::Column::UserId.eq(row.user_id))
609 .exec(db)
610 .await?;
611 user_push_subscriptions::Entity::delete_many()
612 .filter(user_push_subscriptions::Column::UserId.eq(row.user_id))
613 .exec(db)
614 .await?;
615 user_favorite_memes::Entity::delete_many()
616 .filter(user_favorite_memes::Column::UserId.eq(row.user_id))
617 .exec(db)
618 .await?;
619 user_recent_mentions::Entity::delete_many()
620 .filter(user_recent_mentions::Column::UserId.eq(row.user_id))
621 .exec(db)
622 .await?;
623 user_saved_messages::Entity::delete_many()
624 .filter(user_saved_messages::Column::UserId.eq(row.user_id))
625 .exec(db)
626 .await?;
627 user_external_identity::Entity::delete_many()
628 .filter(user_external_identity::Column::UserId.eq(Some(row.user_id)))
629 .exec(db)
630 .await?;
631 user_agreement_acceptances::Entity::delete_many()
632 .filter(user_agreement_acceptances::Column::UserId.eq(row.user_id))
633 .exec(db)
634 .await?;
635 user_blocks::Entity::delete_many()
636 .filter(
637 sea_orm::Condition::any()
638 .add(user_blocks::Column::BlockerId.eq(row.user_id))
639 .add(user_blocks::Column::BlockedId.eq(row.user_id)),
640 )
641 .exec(db)
642 .await?;
643 user_followers::Entity::delete_many()
644 .filter(
645 sea_orm::Condition::any()
646 .add(user_followers::Column::FollowerId.eq(row.user_id))
647 .add(user_followers::Column::FollowedId.eq(row.user_id)),
648 )
649 .exec(db)
650 .await?;
651
652 if let Some(user) = user::Entity::find_by_id(row.user_id).one(db).await? {
653 let mut active: user::ActiveModel = user.into();
654 active.is_restricted = Set(true);
655 active.local_avatar_url = Set(String::new());
656 active.visibility = Set(models::users::user::ProfileVisibility::Private);
657 active.last_login_at = Set(None);
658 active.last_login_ip_hash = Set(None);
659 active.updated_at = Set(now);
660 active.update(db).await?;
661 }
662
663 match client.purge_account(row.user_id).await {
664 Ok(()) | Err(KenAuthError::NotFound) => {}
665 Err(error) => return Err(ken_auth(error)),
666 }
667 account_deletion::Entity::delete_by_id(row.id)
668 .exec(db)
669 .await?;
670 tracing::info!(user_id = %row.user_id, "account purged");
671 Ok(())
672}
673
674async fn record_space_failure(
675 state: &AppState,
676 row: &space_deletion::Model,
677 error: &DeletionError,
678) -> Result<(), DeletionError> {
679 let attempts = row.attempts.saturating_add(1);
680 let mut active: space_deletion::ActiveModel = row.clone().into();
681 active.attempts = Set(attempts);
682 active.available_at = Set(retry_at(attempts));
683 active.last_error = Set(Some(error.to_string()));
684 active.status = Set("pending".into());
685 active.updated_at = Set(now());
686 active.update(state.database.write()).await?;
687 tracing::warn!(space_id = %row.space_id, %error, attempts, "space purge failed; will retry");
688 Ok(())
689}
690
691async fn record_account_failure(
692 state: &AppState,
693 row: &account_deletion::Model,
694 error: &DeletionError,
695) -> Result<(), DeletionError> {
696 let attempts = row.attempts.saturating_add(1);
697 let mut active: account_deletion::ActiveModel = row.clone().into();
698 active.attempts = Set(attempts);
699 active.available_at = Set(retry_at(attempts));
700 active.last_error = Set(Some(error.to_string()));
701 active.status = Set("pending".into());
702 active.updated_at = Set(now());
703 active.update(state.database.write()).await?;
704 tracing::warn!(user_id = %row.user_id, %error, attempts, "account purge failed; will retry");
705 Ok(())
706}
Added lib/deletion/Cargo.toml View lib/deletion/Cargo.toml
@@ -0,0 +1,18 @@
1[package]
2name = "deletion"
3version.workspace = true
4edition.workspace = true
5
6[dependencies]
7config = { workspace = true }
8export = { workspace = true }
9kenauth = { workspace = true }
10kenshell = { workspace = true }
11models = { workspace = true }
12sea-orm = { workspace = true }
13serde = { workspace = true, features = ["derive"] }
14thiserror = { workspace = true }
15time = { workspace = true }
16tokio = { workspace = true, features = ["time"] }
17tracing = { workspace = true }
18uuid = { workspace = true }
Modified lib/export/src/lib.rs View lib/export/src/lib.rs
@@ -48,35 +48,30 @@
4848
4949 pub async fn with_config(config: AppConfig) -> Result<Self, ExportError> {
5050 let database = Arc::new(AppDatabase::connect(config.database.clone()).await?);
51 Self::with_identity(config, database, None).await
52 }
53
54 pub async fn with_identity(
55 config: AppConfig,
56 database: Arc<AppDatabase>,
57 identity: Option<Arc<kenauth::KenAuthClient>>,
58 ) -> Result<Self, ExportError> {
5159 let mut redis = AppRedis::connect(config.redis.clone()).await?;
5260 // 入队点遍布各业务 crate,但只有这里有配置:把开关写进句柄,让
5361 // `export::push::enqueue` 能自己判断该不该产生任务。
5462 redis.set_push_enabled(config.push.enabled);
5563 let storage = Arc::new(AppStorage::connect(config.s3.clone())?);
5664
57 let kenauth = if config.oauth.enabled {
58 let client = Arc::new(
59 kenauth::KenAuthClient::connect(&config.oauth)
60 .map_err(|err| ExportError::InvalidConfig(err.to_string()))?,
61 );
65 if config.oauth.enabled && identity.is_none() {
66 return Err(ExportError::InvalidConfig(
67 "oauth requires the in-process identity service".to_owned(),
68 ));
69 }
70 let kenauth = identity;
71 if let Some(client) = &kenauth {
6272 oauth_client::install(client.clone());
63 Some(client)
64 } else {
65 None
66 };
67
68 let email_rpc = if config.email.rpc.enabled() {
69 Some(Arc::new(
70 kenauth::KenAuthClient::connect_rpc(
71 &config.email.rpc.endpoint,
72 &config.email.rpc.rpc_key,
73 config.email.rpc.timeout_ms(),
74 )
75 .map_err(|err| ExportError::InvalidConfig(err.to_string()))?,
76 ))
77 } else {
78 kenauth.clone()
79 };
73 }
74 let email_rpc = kenauth.clone();
8075
8176 Ok(Self {
8277 database,
Added lib/identity/proto/common/v1/errors.proto View lib/identity/proto/common/v1/errors.proto

Diff too large to display.

Added lib/identity/proto/common/v1/status.proto View lib/identity/proto/common/v1/status.proto

Diff too large to display.

Added lib/identity/proto/common/v1/types.proto View lib/identity/proto/common/v1/types.proto

Diff too large to display.

Added lib/identity/proto/internal/v1/admin.proto View lib/identity/proto/internal/v1/admin.proto

Diff too large to display.

Added lib/identity/proto/internal/v1/authority.proto View lib/identity/proto/internal/v1/authority.proto

Diff too large to display.

Added lib/identity/proto/internal/v1/client.proto View lib/identity/proto/internal/v1/client.proto

Diff too large to display.

Added lib/identity/proto/internal/v1/email.proto View lib/identity/proto/internal/v1/email.proto

Diff too large to display.

Added lib/identity/proto/internal/v1/organization.proto View lib/identity/proto/internal/v1/organization.proto

Diff too large to display.

Added lib/identity/proto/v1/access_key.proto View lib/identity/proto/v1/access_key.proto

Diff too large to display.

Added lib/identity/proto/v1/agreement.proto View lib/identity/proto/v1/agreement.proto

Diff too large to display.

Added lib/identity/proto/v1/authorization.proto View lib/identity/proto/v1/authorization.proto

Diff too large to display.

Added lib/identity/proto/v1/events.proto View lib/identity/proto/v1/events.proto

Diff too large to display.

Added lib/identity/proto/v1/identity.proto View lib/identity/proto/v1/identity.proto

Diff too large to display.

Added lib/identity/proto/v1/mfa.proto View lib/identity/proto/v1/mfa.proto

Diff too large to display.

Added lib/identity/proto/v1/organization.proto View lib/identity/proto/v1/organization.proto

Diff too large to display.

Added lib/identity/proto/v1/session.proto View lib/identity/proto/v1/session.proto

Diff too large to display.

Added lib/identity/proto/v1/ssh_key.proto View lib/identity/proto/v1/ssh_key.proto

Diff too large to display.

Added lib/identity/proto/v1/token.proto View lib/identity/proto/v1/token.proto

Diff too large to display.

Added lib/identity/src/auth/access_token/handler.rs View lib/identity/src/auth/access_token/handler.rs

Diff too large to display.

Added lib/identity/src/auth/access_token/mod.rs View lib/identity/src/auth/access_token/mod.rs

Diff too large to display.

Added lib/identity/src/auth/access_token/service.rs View lib/identity/src/auth/access_token/service.rs

Diff too large to display.

Added lib/identity/src/auth/access_token/types.rs View lib/identity/src/auth/access_token/types.rs

Diff too large to display.

Added lib/identity/src/auth/app_token/handler.rs View lib/identity/src/auth/app_token/handler.rs

Diff too large to display.

Added lib/identity/src/auth/app_token/mod.rs View lib/identity/src/auth/app_token/mod.rs

Diff too large to display.

Added lib/identity/src/auth/app_token/service.rs View lib/identity/src/auth/app_token/service.rs

Diff too large to display.

Added lib/identity/src/auth/app_token/types.rs View lib/identity/src/auth/app_token/types.rs

Diff too large to display.

Added lib/identity/src/auth/challenge/handler.rs View lib/identity/src/auth/challenge/handler.rs

Diff too large to display.

Added lib/identity/src/auth/challenge/mod.rs View lib/identity/src/auth/challenge/mod.rs

Diff too large to display.

Added lib/identity/src/auth/challenge/service.rs View lib/identity/src/auth/challenge/service.rs

Diff too large to display.

Added lib/identity/src/auth/challenge/types.rs View lib/identity/src/auth/challenge/types.rs

Diff too large to display.

Added lib/identity/src/auth/client/mod.rs View lib/identity/src/auth/client/mod.rs

Diff too large to display.

Added lib/identity/src/auth/client/registry.rs View lib/identity/src/auth/client/registry.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/access_key.rs View lib/identity/src/auth/grpc/access_key.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/agreement.rs View lib/identity/src/auth/grpc/agreement.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/audit.rs View lib/identity/src/auth/grpc/audit.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/authority.rs View lib/identity/src/auth/grpc/authority.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/authorization.rs View lib/identity/src/auth/grpc/authorization.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/cache_maintenance.rs View lib/identity/src/auth/grpc/cache_maintenance.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/client.rs View lib/identity/src/auth/grpc/client.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/context.rs View lib/identity/src/auth/grpc/context.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/convert.rs View lib/identity/src/auth/grpc/convert.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/email.rs View lib/identity/src/auth/grpc/email.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/error.rs View lib/identity/src/auth/grpc/error.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/identity.rs View lib/identity/src/auth/grpc/identity.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/key_rotation.rs View lib/identity/src/auth/grpc/key_rotation.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/mfa.rs View lib/identity/src/auth/grpc/mfa.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/mod.rs View lib/identity/src/auth/grpc/mod.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/organization.rs View lib/identity/src/auth/grpc/organization.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/service_token.rs View lib/identity/src/auth/grpc/service_token.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/session.rs View lib/identity/src/auth/grpc/session.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/ssh_key.rs View lib/identity/src/auth/grpc/ssh_key.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/store.rs View lib/identity/src/auth/grpc/store.rs

Diff too large to display.

Added lib/identity/src/auth/grpc/token.rs View lib/identity/src/auth/grpc/token.rs

Diff too large to display.

Added lib/identity/src/auth/login/handler.rs View lib/identity/src/auth/login/handler.rs

Diff too large to display.

Added lib/identity/src/auth/login/mod.rs View lib/identity/src/auth/login/mod.rs

Diff too large to display.

Added lib/identity/src/auth/login/service.rs View lib/identity/src/auth/login/service.rs

Diff too large to display.

Added lib/identity/src/auth/login/types.rs View lib/identity/src/auth/login/types.rs

Diff too large to display.

Added lib/identity/src/auth/logout/handler.rs View lib/identity/src/auth/logout/handler.rs

Diff too large to display.

Added lib/identity/src/auth/logout/mod.rs View lib/identity/src/auth/logout/mod.rs

Diff too large to display.

Added lib/identity/src/auth/logout/service.rs View lib/identity/src/auth/logout/service.rs

Diff too large to display.

Added lib/identity/src/auth/logout/types.rs View lib/identity/src/auth/logout/types.rs

Diff too large to display.

Added lib/identity/src/auth/me/handler.rs View lib/identity/src/auth/me/handler.rs

Diff too large to display.

Added lib/identity/src/auth/me/mod.rs View lib/identity/src/auth/me/mod.rs

Diff too large to display.

Added lib/identity/src/auth/me/service.rs View lib/identity/src/auth/me/service.rs

Diff too large to display.

Added lib/identity/src/auth/me/types.rs View lib/identity/src/auth/me/types.rs

Diff too large to display.

Added lib/identity/src/auth/oauth/consent.rs View lib/identity/src/auth/oauth/consent.rs

Diff too large to display.

Added lib/identity/src/auth/oauth/id_token.rs View lib/identity/src/auth/oauth/id_token.rs

Diff too large to display.

Added lib/identity/src/auth/oauth/logout.rs View lib/identity/src/auth/oauth/logout.rs

Diff too large to display.

Added lib/identity/src/auth/oauth/mod.rs View lib/identity/src/auth/oauth/mod.rs

Diff too large to display.

Added lib/identity/src/auth/oauth/refresh.rs View lib/identity/src/auth/oauth/refresh.rs

Diff too large to display.

Added lib/identity/src/auth/oauth/userinfo.rs View lib/identity/src/auth/oauth/userinfo.rs

Diff too large to display.

Added lib/identity/src/auth/organization/error.rs View lib/identity/src/auth/organization/error.rs

Diff too large to display.

Added lib/identity/src/auth/organization/handler.rs View lib/identity/src/auth/organization/handler.rs

Diff too large to display.

Added lib/identity/src/auth/organization/invite.rs View lib/identity/src/auth/organization/invite.rs

Diff too large to display.

Added lib/identity/src/auth/organization/join.rs View lib/identity/src/auth/organization/join.rs

Diff too large to display.

Added lib/identity/src/auth/organization/member.rs View lib/identity/src/auth/organization/member.rs

Diff too large to display.

Added lib/identity/src/auth/organization/mod.rs View lib/identity/src/auth/organization/mod.rs

Diff too large to display.

Added lib/identity/src/auth/organization/permission.rs View lib/identity/src/auth/organization/permission.rs

Diff too large to display.

Added lib/identity/src/auth/organization/role.rs View lib/identity/src/auth/organization/role.rs

Diff too large to display.

Added lib/identity/src/auth/organization/service.rs View lib/identity/src/auth/organization/service.rs

Diff too large to display.

Added lib/identity/src/auth/organization/types.rs View lib/identity/src/auth/organization/types.rs

Diff too large to display.

Added lib/identity/src/auth/password_reset/handler.rs View lib/identity/src/auth/password_reset/handler.rs

Diff too large to display.

Added lib/identity/src/auth/password_reset/mod.rs View lib/identity/src/auth/password_reset/mod.rs

Diff too large to display.

Added lib/identity/src/auth/password_reset/service.rs View lib/identity/src/auth/password_reset/service.rs

Diff too large to display.

Added lib/identity/src/auth/password_reset/types.rs View lib/identity/src/auth/password_reset/types.rs

Diff too large to display.

Added lib/identity/src/auth/pow/hash.rs View lib/identity/src/auth/pow/hash.rs

Diff too large to display.

Added lib/identity/src/auth/pow/mod.rs View lib/identity/src/auth/pow/mod.rs

Diff too large to display.

Added lib/identity/src/auth/pow/store.rs View lib/identity/src/auth/pow/store.rs

Diff too large to display.

Added lib/identity/src/auth/register/age.rs View lib/identity/src/auth/register/age.rs

Diff too large to display.

Added lib/identity/src/auth/register/handler.rs View lib/identity/src/auth/register/handler.rs

Diff too large to display.

Added lib/identity/src/auth/register/mod.rs View lib/identity/src/auth/register/mod.rs

Diff too large to display.

Added lib/identity/src/auth/register/service.rs View lib/identity/src/auth/register/service.rs

Diff too large to display.

Added lib/identity/src/auth/register/types.rs View lib/identity/src/auth/register/types.rs

Diff too large to display.

Added lib/identity/src/auth/share/audit.rs View lib/identity/src/auth/share/audit.rs

Diff too large to display.

Added lib/identity/src/auth/share/codes.rs View lib/identity/src/auth/share/codes.rs

Diff too large to display.

Added lib/identity/src/auth/share/crypto.rs View lib/identity/src/auth/share/crypto.rs

Diff too large to display.

Added lib/identity/src/auth/share/issue.rs View lib/identity/src/auth/share/issue.rs

Diff too large to display.

Added lib/identity/src/auth/share/mfa.rs View lib/identity/src/auth/share/mfa.rs

Diff too large to display.

Added lib/identity/src/auth/share/mod.rs View lib/identity/src/auth/share/mod.rs

Diff too large to display.

Added lib/identity/src/auth/share/net.rs View lib/identity/src/auth/share/net.rs

Diff too large to display.

Added lib/identity/src/auth/share/password.rs View lib/identity/src/auth/share/password.rs

Diff too large to display.

Added lib/identity/src/auth/share/rate.rs View lib/identity/src/auth/share/rate.rs

Diff too large to display.

Added lib/identity/src/auth/share/totp.rs View lib/identity/src/auth/share/totp.rs

Diff too large to display.

Added lib/identity/src/auth/share/types.rs View lib/identity/src/auth/share/types.rs

Diff too large to display.

Added lib/identity/src/auth/share/user.rs View lib/identity/src/auth/share/user.rs

Diff too large to display.

Added lib/identity/src/auth/share/validate.rs View lib/identity/src/auth/share/validate.rs

Diff too large to display.

Added lib/identity/src/auth/ssh_key/handler.rs View lib/identity/src/auth/ssh_key/handler.rs

Diff too large to display.

Added lib/identity/src/auth/ssh_key/mod.rs View lib/identity/src/auth/ssh_key/mod.rs

Diff too large to display.

Added lib/identity/src/auth/ssh_key/service.rs View lib/identity/src/auth/ssh_key/service.rs

Diff too large to display.

Added lib/identity/src/auth/ssh_key/types.rs View lib/identity/src/auth/ssh_key/types.rs

Diff too large to display.

Added lib/identity/src/auth/token/handler.rs View lib/identity/src/auth/token/handler.rs

Diff too large to display.

Added lib/identity/src/auth/token/mod.rs View lib/identity/src/auth/token/mod.rs

Diff too large to display.

Added lib/identity/src/auth/token/service.rs View lib/identity/src/auth/token/service.rs

Diff too large to display.

Added lib/identity/src/auth/token/types.rs View lib/identity/src/auth/token/types.rs

Diff too large to display.

Added lib/identity/src/auth/account_lifecycle.rs View lib/identity/src/auth/account_lifecycle.rs

Diff too large to display.

Added lib/identity/src/auth/authority.rs View lib/identity/src/auth/authority.rs

Diff too large to display.

Added lib/identity/src/auth/errors.rs View lib/identity/src/auth/errors.rs

Diff too large to display.

Added lib/identity/src/auth/health.rs View lib/identity/src/auth/health.rs

Diff too large to display.

Added lib/identity/src/auth/keys.rs View lib/identity/src/auth/keys.rs

Diff too large to display.

Added lib/identity/src/auth/migrate.rs View lib/identity/src/auth/migrate.rs

Diff too large to display.

Added lib/identity/src/auth/mod.rs View lib/identity/src/auth/mod.rs

Diff too large to display.

Added lib/identity/src/auth/openapi.rs View lib/identity/src/auth/openapi.rs

Diff too large to display.

Added lib/identity/src/auth/protocol.rs View lib/identity/src/auth/protocol.rs

Diff too large to display.

Added lib/identity/src/auth/router.rs View lib/identity/src/auth/router.rs

Diff too large to display.

Added lib/identity/src/auth/socket_ticket.rs View lib/identity/src/auth/socket_ticket.rs

Diff too large to display.

Added lib/identity/src/config/app/key.rs View lib/identity/src/config/app/key.rs

Diff too large to display.

Added lib/identity/src/config/app/mod.rs View lib/identity/src/config/app/mod.rs

Diff too large to display.

Added lib/identity/src/config/auth.rs View lib/identity/src/config/auth.rs

Diff too large to display.

Added lib/identity/src/config/core.rs View lib/identity/src/config/core.rs

Diff too large to display.

Added lib/identity/src/config/database.rs View lib/identity/src/config/database.rs

Diff too large to display.

Added lib/identity/src/config/email.rs View lib/identity/src/config/email.rs

Diff too large to display.

Added lib/identity/src/config/error.rs View lib/identity/src/config/error.rs

Diff too large to display.

Added lib/identity/src/config/jwt.rs View lib/identity/src/config/jwt.rs

Diff too large to display.

Added lib/identity/src/config/loader.rs View lib/identity/src/config/loader.rs

Diff too large to display.

Added lib/identity/src/config/mod.rs View lib/identity/src/config/mod.rs

Diff too large to display.

Added lib/identity/src/config/pow.rs View lib/identity/src/config/pow.rs

Diff too large to display.

Added lib/identity/src/config/redis.rs View lib/identity/src/config/redis.rs

Diff too large to display.

Added lib/identity/src/config/rocketmq.rs View lib/identity/src/config/rocketmq.rs

Diff too large to display.

Added lib/identity/src/config/server.rs View lib/identity/src/config/server.rs

Diff too large to display.

Added lib/identity/src/config/service_auth.rs View lib/identity/src/config/service_auth.rs

Diff too large to display.

Added lib/identity/src/config/session.rs View lib/identity/src/config/session.rs

Diff too large to display.

Added lib/identity/src/config/smtp.rs View lib/identity/src/config/smtp.rs

Diff too large to display.

Added lib/identity/src/config/web.rs View lib/identity/src/config/web.rs

Diff too large to display.

Added lib/identity/src/crypt/app.rs View lib/identity/src/crypt/app.rs

Diff too large to display.

Added lib/identity/src/crypt/envelope.rs View lib/identity/src/crypt/envelope.rs

Diff too large to display.

Added lib/identity/src/crypt/error.rs View lib/identity/src/crypt/error.rs

Diff too large to display.

Added lib/identity/src/crypt/key.rs View lib/identity/src/crypt/key.rs

Diff too large to display.

Added lib/identity/src/crypt/mod.rs View lib/identity/src/crypt/mod.rs

Diff too large to display.

Added lib/identity/src/db/conn.rs View lib/identity/src/db/conn.rs

Diff too large to display.

Added lib/identity/src/db/error.rs View lib/identity/src/db/error.rs

Diff too large to display.

Added lib/identity/src/db/exec.rs View lib/identity/src/db/exec.rs

Diff too large to display.

Added lib/identity/src/db/migrate.rs View lib/identity/src/db/migrate.rs

Diff too large to display.

Added lib/identity/src/db/mod.rs View lib/identity/src/db/mod.rs

Diff too large to display.

Added lib/identity/src/db/routing.rs View lib/identity/src/db/routing.rs

Diff too large to display.

Added lib/identity/src/email/i18n/catalog.rs View lib/identity/src/email/i18n/catalog.rs

Diff too large to display.

Added lib/identity/src/email/i18n/locale.rs View lib/identity/src/email/i18n/locale.rs

Diff too large to display.

Added lib/identity/src/email/i18n/mod.rs View lib/identity/src/email/i18n/mod.rs

Diff too large to display.

Added lib/identity/src/email/templates/email_change_confirm.rs View lib/identity/src/email/templates/email_change_confirm.rs

Diff too large to display.

Added lib/identity/src/email/templates/mod.rs View lib/identity/src/email/templates/mod.rs

Diff too large to display.

Added lib/identity/src/email/templates/reset_link.rs View lib/identity/src/email/templates/reset_link.rs

Diff too large to display.

Added lib/identity/src/email/templates/verify_code.rs View lib/identity/src/email/templates/verify_code.rs

Diff too large to display.

Added lib/identity/src/email/error.rs View lib/identity/src/email/error.rs

Diff too large to display.

Added lib/identity/src/email/events.rs View lib/identity/src/email/events.rs

Diff too large to display.

Added lib/identity/src/email/metrics.rs View lib/identity/src/email/metrics.rs

Diff too large to display.

Added lib/identity/src/email/mod.rs View lib/identity/src/email/mod.rs

Diff too large to display.

Added lib/identity/src/email/queue.rs View lib/identity/src/email/queue.rs

Diff too large to display.

Added lib/identity/src/email/service.rs View lib/identity/src/email/service.rs

Diff too large to display.

Added lib/identity/src/email/template.rs View lib/identity/src/email/template.rs

Diff too large to display.

Added lib/identity/src/email/transport.rs View lib/identity/src/email/transport.rs

Diff too large to display.

Added lib/identity/src/envelope/context.rs View lib/identity/src/envelope/context.rs

Diff too large to display.

Added lib/identity/src/envelope/cors.rs View lib/identity/src/envelope/cors.rs

Diff too large to display.

Added lib/identity/src/envelope/middleware.rs View lib/identity/src/envelope/middleware.rs

Diff too large to display.

Added lib/identity/src/envelope/mod.rs View lib/identity/src/envelope/mod.rs

Diff too large to display.

Added lib/identity/src/envelope/response.rs View lib/identity/src/envelope/response.rs

Diff too large to display.

Added lib/identity/src/events/event/agent.rs View lib/identity/src/events/event/agent.rs

Diff too large to display.

Added lib/identity/src/events/event/auth.rs View lib/identity/src/events/event/auth.rs

Diff too large to display.

Added lib/identity/src/events/event/chat.rs View lib/identity/src/events/event/chat.rs

Diff too large to display.

Added lib/identity/src/events/event/email.rs View lib/identity/src/events/event/email.rs

Diff too large to display.

Added lib/identity/src/events/event/git.rs View lib/identity/src/events/event/git.rs

Diff too large to display.

Added lib/identity/src/events/event/guild.rs View lib/identity/src/events/event/guild.rs

Diff too large to display.

Added lib/identity/src/events/event/issue.rs View lib/identity/src/events/event/issue.rs

Diff too large to display.

Added lib/identity/src/events/event/macros.rs View lib/identity/src/events/event/macros.rs

Diff too large to display.

Added lib/identity/src/events/event/mod.rs View lib/identity/src/events/event/mod.rs

Diff too large to display.

Added lib/identity/src/events/event/notify.rs View lib/identity/src/events/event/notify.rs

Diff too large to display.

Added lib/identity/src/events/event/object.rs View lib/identity/src/events/event/object.rs

Diff too large to display.

Added lib/identity/src/events/event/organization.rs View lib/identity/src/events/event/organization.rs

Diff too large to display.

Added lib/identity/src/events/event/pull_request.rs View lib/identity/src/events/event/pull_request.rs

Diff too large to display.

Added lib/identity/src/events/event/realtime.rs View lib/identity/src/events/event/realtime.rs

Diff too large to display.

Added lib/identity/src/events/event/user.rs View lib/identity/src/events/event/user.rs

Diff too large to display.

Added lib/identity/src/events/event/webhook.rs View lib/identity/src/events/event/webhook.rs

Diff too large to display.

Added lib/identity/src/events/handler.rs View lib/identity/src/events/handler.rs

Diff too large to display.

Added lib/identity/src/events/mod.rs View lib/identity/src/events/mod.rs

Diff too large to display.

Added lib/identity/src/events/outbox.rs View lib/identity/src/events/outbox.rs

Diff too large to display.

Added lib/identity/src/events/route.rs View lib/identity/src/events/route.rs

Diff too large to display.

Added lib/identity/src/jwt/authority.rs View lib/identity/src/jwt/authority.rs

Diff too large to display.

Added lib/identity/src/jwt/authority_grpc.rs View lib/identity/src/jwt/authority_grpc.rs

Diff too large to display.

Added lib/identity/src/jwt/claims.rs View lib/identity/src/jwt/claims.rs

Diff too large to display.

Added lib/identity/src/jwt/codec.rs View lib/identity/src/jwt/codec.rs

Diff too large to display.

Added lib/identity/src/jwt/error.rs View lib/identity/src/jwt/error.rs

Diff too large to display.

Added lib/identity/src/jwt/extract.rs View lib/identity/src/jwt/extract.rs

Diff too large to display.

Added lib/identity/src/jwt/jwks.rs View lib/identity/src/jwt/jwks.rs

Diff too large to display.

Added lib/identity/src/jwt/middleware.rs View lib/identity/src/jwt/middleware.rs

Diff too large to display.

Added lib/identity/src/jwt/mod.rs View lib/identity/src/jwt/mod.rs

Diff too large to display.

Added lib/identity/src/jwt/scope.rs View lib/identity/src/jwt/scope.rs

Diff too large to display.

Added lib/identity/src/jwt/service.rs View lib/identity/src/jwt/service.rs

Diff too large to display.

Added lib/identity/src/jwt/state.rs View lib/identity/src/jwt/state.rs

Diff too large to display.

Added lib/identity/src/kvcache/conn.rs View lib/identity/src/kvcache/conn.rs

Diff too large to display.

Added lib/identity/src/kvcache/error.rs View lib/identity/src/kvcache/error.rs

Diff too large to display.

Added lib/identity/src/kvcache/exec.rs View lib/identity/src/kvcache/exec.rs

Diff too large to display.

Added lib/identity/src/kvcache/hash.rs View lib/identity/src/kvcache/hash.rs

Diff too large to display.

Added lib/identity/src/kvcache/json.rs View lib/identity/src/kvcache/json.rs

Diff too large to display.

Added lib/identity/src/kvcache/keys.rs View lib/identity/src/kvcache/keys.rs

Diff too large to display.

Added lib/identity/src/kvcache/kv.rs View lib/identity/src/kvcache/kv.rs

Diff too large to display.

Added lib/identity/src/kvcache/list.rs View lib/identity/src/kvcache/list.rs

Diff too large to display.

Added lib/identity/src/kvcache/lock.rs View lib/identity/src/kvcache/lock.rs

Diff too large to display.

Added lib/identity/src/kvcache/mod.rs View lib/identity/src/kvcache/mod.rs

Diff too large to display.

Added lib/identity/src/kvcache/pipeline.rs View lib/identity/src/kvcache/pipeline.rs

Diff too large to display.

Added lib/identity/src/kvcache/scripts.rs View lib/identity/src/kvcache/scripts.rs

Diff too large to display.

Added lib/identity/src/kvcache/set.rs View lib/identity/src/kvcache/set.rs

Diff too large to display.

Added lib/identity/src/kvcache/zset.rs View lib/identity/src/kvcache/zset.rs

Diff too large to display.

Added lib/identity/src/models/organization/mod.rs View lib/identity/src/models/organization/mod.rs

Diff too large to display.

Added lib/identity/src/models/organization/organization_invite_redemptions.rs View lib/identity/src/models/organization/organization_invite_redemptions.rs

Diff too large to display.

Added lib/identity/src/models/organization/organization_invites.rs View lib/identity/src/models/organization/organization_invites.rs

Diff too large to display.

Added lib/identity/src/models/organization/organization_join_requests.rs View lib/identity/src/models/organization/organization_join_requests.rs

Diff too large to display.

Added lib/identity/src/models/organization/organization_join_rules.rs View lib/identity/src/models/organization/organization_join_rules.rs

Diff too large to display.

Added lib/identity/src/models/organization/organization_member_permissions.rs View lib/identity/src/models/organization/organization_member_permissions.rs

Diff too large to display.

Added lib/identity/src/models/organization/organization_member_roles.rs View lib/identity/src/models/organization/organization_member_roles.rs

Diff too large to display.

Added lib/identity/src/models/organization/organization_members.rs View lib/identity/src/models/organization/organization_members.rs

Diff too large to display.

Added lib/identity/src/models/organization/organization_role_permissions.rs View lib/identity/src/models/organization/organization_role_permissions.rs

Diff too large to display.

Added lib/identity/src/models/organization/organization_roles.rs View lib/identity/src/models/organization/organization_roles.rs

Diff too large to display.

Added lib/identity/src/models/organization/organizations.rs View lib/identity/src/models/organization/organizations.rs

Diff too large to display.

Added lib/identity/src/models/user/mod.rs View lib/identity/src/models/user/mod.rs

Diff too large to display.

Added lib/identity/src/models/user/oauth_clients.rs View lib/identity/src/models/user/oauth_clients.rs

Diff too large to display.

Added lib/identity/src/models/user/oauth_consents.rs View lib/identity/src/models/user/oauth_consents.rs

Diff too large to display.

Added lib/identity/src/models/user/oauth_refresh_tokens.rs View lib/identity/src/models/user/oauth_refresh_tokens.rs

Diff too large to display.

Added lib/identity/src/models/user/service_token_revocations.rs View lib/identity/src/models/user/service_token_revocations.rs

Diff too large to display.

Added lib/identity/src/models/user/user_access_tokens.rs View lib/identity/src/models/user/user_access_tokens.rs

Diff too large to display.

Added lib/identity/src/models/user/user_events.rs View lib/identity/src/models/user/user_events.rs

Diff too large to display.

Added lib/identity/src/models/user/user_mfa.rs View lib/identity/src/models/user/user_mfa.rs

Diff too large to display.

Added lib/identity/src/models/user/user_mfa_backup_codes.rs View lib/identity/src/models/user/user_mfa_backup_codes.rs

Diff too large to display.

Added lib/identity/src/models/user/user_sessions.rs View lib/identity/src/models/user/user_sessions.rs

Diff too large to display.

Added lib/identity/src/models/user/user_ssh_keys.rs View lib/identity/src/models/user/user_ssh_keys.rs

Diff too large to display.

Added lib/identity/src/models/user/user_verification_tokens.rs View lib/identity/src/models/user/user_verification_tokens.rs

Diff too large to display.

Added lib/identity/src/models/user/user_webauthn_credentials.rs View lib/identity/src/models/user/user_webauthn_credentials.rs

Diff too large to display.

Added lib/identity/src/models/event_outbox.rs View lib/identity/src/models/event_outbox.rs

Diff too large to display.

Added lib/identity/src/models/mod.rs View lib/identity/src/models/mod.rs

Diff too large to display.

Added lib/identity/src/lib.rs View lib/identity/src/lib.rs

Diff too large to display.

Added lib/identity/biome.json View lib/identity/biome.json
@@ -0,0 +1,67 @@
1{
2 "$schema": "https://biomejs.dev/schemas/2.5.15/schema.json",
3 "vcs": {
4 "enabled": true,
5 "clientKind": "git",
6 "useIgnoreFile": true
7 },
8 "files": {
9 "ignoreUnknown": true,
10 "includes": [
11 "**",
12 "!**/dist",
13 "!**/node_modules",
14 "!**/target"
15 ]
16 },
17 "formatter": {
18 "enabled": true,
19 "indentStyle": "space",
20 "indentWidth": 2,
21 "lineWidth": 120,
22 "lineEnding": "lf"
23 },
24 "javascript": {
25 "formatter": {
26 "quoteStyle": "single",
27 "jsxQuoteStyle": "double",
28 "semicolons": "asNeeded",
29 "trailingCommas": "all",
30 "arrowParentheses": "always",
31 "bracketSpacing": true,
32 "bracketSameLine": false
33 }
34 },
35 "json": {
36 "formatter": {
37 "enabled": true,
38 "indentWidth": 2,
39 "lineWidth": 120
40 }
41 },
42 "css": {
43 "parser": {
44 "tailwindDirectives": true
45 },
46 "formatter": {
47 "enabled": true,
48 "indentWidth": 2,
49 "lineWidth": 120,
50 "quoteStyle": "double"
51 }
52 },
53 "linter": {
54 "enabled": true,
55 "rules": {
56 "preset": "recommended"
57 }
58 },
59 "assist": {
60 "enabled": true,
61 "actions": {
62 "source": {
63 "organizeImports": "on"
64 }
65 }
66 }
67}
Added lib/identity/build.rs View lib/identity/build.rs
@@ -0,0 +1,350 @@
1use std::fmt::Write as _;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5/// 前端构建产物目录(`pnpm build` 的输出)
6const WEB_DIST: &str = "view/dist";
7
8/// 需要序列化(serde)与 ToSchema 的 proto 包
9const JSON_PACKAGES: [&str; 5] = [
10 "v1",
11 "common.v1",
12 "admin.v1",
13 "cluster.v1",
14 "cluster.internal.v1",
15];
16
17/// protobuf 字段的 serde 路径:合并成单 crate 后 serde 助手在 `crate::protos::serde_helpers`。
18const SERDE_HELPERS: &str = "crate::protos::serde_helpers";
19
20/// protoc 的 descriptor 临时输出(相对路径,避免非 ASCII 绝对路径)。
21const DESCRIPTOR_TMP: &str = ".descriptor.bin";
22
23fn main() -> Result<(), Box<dyn std::error::Error>> {
24 let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
25 let out_dir = PathBuf::from(std::env::var("OUT_DIR")?);
26
27 build_protos(manifest_dir, &out_dir)?;
28 build_web_assets(manifest_dir, &out_dir);
29
30 Ok(())
31}
32
33fn build_protos(manifest_dir: &Path, out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
34 let proto_dir = PathBuf::from("proto");
35 let proto_root = manifest_dir.join(&proto_dir);
36
37 let mut files = Vec::new();
38 collect_protos(&proto_root, &mut files)?;
39 files.sort();
40 if files.is_empty() {
41 return Err(format!("no .proto found under {}", proto_root.display()).into());
42 }
43 let relative: Vec<PathBuf> = files
44 .iter()
45 .map(|file| proto_dir.join(file.strip_prefix(&proto_root).expect("scanned under root")))
46 .collect();
47
48 println!("cargo:rerun-if-changed={}", proto_root.display());
49 for file in &files {
50 println!("cargo:rerun-if-changed={}", file.display());
51 }
52
53 let mut builder = tonic_prost_build::configure()
54 .build_client(true)
55 .build_server(true)
56 // 先写相对路径再由 build.rs 搬到 OUT_DIR:protoc 无法写入非 ASCII 的绝对路径(如 C:\工程\...)。
57 .file_descriptor_set_path(DESCRIPTOR_TMP)
58 .enum_attribute(".", "#[allow(clippy::large_enum_variant)]")
59 .include_file("_include.rs");
60
61 for package in JSON_PACKAGES {
62 let prefix = format!(".{package}");
63 builder = builder
64 .type_attribute(&prefix, "#[derive(serde::Serialize, serde::Deserialize)]")
65 .message_attribute(&prefix, "#[serde(default)]")
66 .enum_attribute(&prefix, "#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]");
67 }
68
69 let bytes_helper = format!(
70 "#[serde(with = \"{SERDE_HELPERS}::bytes\")] #[schema(value_type = String)] #[doc = \"Hex encoded bytes (two hex digits per byte).\"]"
71 );
72 let bytes_list_helper = format!(
73 "#[serde(with = \"{SERDE_HELPERS}::bytes_list\")] #[schema(value_type = Vec<String>)]"
74 );
75 let timestamp_helper = format!(
76 "#[serde(with = \"{SERDE_HELPERS}::timestamp_opt\")] #[schema(value_type = Option<String>, default = json!(null))]"
77 );
78
79 for file in &files {
80 let text = fs::read_to_string(file)?;
81 for decl in scan_proto(&text) {
82 builder = match decl {
83 Decl::Schema(path) => builder.type_attribute(path, "#[derive(utoipa::ToSchema)]"),
84 Decl::Oneof(holder) => builder
85 .field_attribute(holder, "#[schema(value_type = Option<serde_json::Value>)]"),
86 Decl::Field(path, FieldKind::Bytes) => builder.field_attribute(path, &bytes_helper),
87 Decl::Field(path, FieldKind::BytesList) => {
88 builder.field_attribute(path, &bytes_list_helper)
89 }
90 Decl::Field(path, FieldKind::Timestamp) => {
91 builder.field_attribute(path, &timestamp_helper)
92 }
93 };
94 }
95 }
96
97 builder.compile_protos(&relative, std::slice::from_ref(&proto_dir))?;
98
99 std::fs::copy(DESCRIPTOR_TMP, out_dir.join("descriptor.bin"))?;
100 let _ = std::fs::remove_file(DESCRIPTOR_TMP);
101 Ok(())
102}
103
104fn collect_protos(dir: &Path, out: &mut Vec<PathBuf>) -> std::io::Result<()> {
105 for entry in fs::read_dir(dir)? {
106 let path = entry?.path();
107 if path.is_dir() {
108 collect_protos(&path, out)?;
109 } else if path.extension().is_some_and(|ext| ext == "proto") {
110 out.push(path);
111 }
112 }
113 Ok(())
114}
115
116enum Decl {
117 Schema(String),
118 Oneof(String),
119 Field(String, FieldKind),
120}
121
122enum FieldKind {
123 Bytes,
124 BytesList,
125 Timestamp,
126}
127
128enum Block {
129 Message(String),
130 Other,
131}
132
133fn scan_proto(text: &str) -> Vec<Decl> {
134 let text = strip_comments(text);
135 let Some(package) = package_of(&text) else {
136 return Vec::new();
137 };
138 if !JSON_PACKAGES.contains(&package.as_str()) {
139 return Vec::new();
140 }
141
142 let mut decls = Vec::new();
143 let mut stack: Vec<Block> = Vec::new();
144 for raw in text.lines() {
145 let line = raw.trim();
146 if line.is_empty() {
147 continue;
148 }
149 if let Some(head) = line.strip_suffix('{') {
150 let head = head.trim();
151 let mut tokens = head.split_whitespace();
152 let keyword = tokens.next().unwrap_or_default();
153 let name = tokens.next().unwrap_or_default().to_string();
154 match keyword {
155 "message" => {
156 decls.push(Decl::Schema(path_of(&package, &stack, &name)));
157 stack.push(Block::Message(name));
158 }
159 "enum" => {
160 decls.push(Decl::Schema(path_of(&package, &stack, &name)));
161 stack.push(Block::Other);
162 }
163 "oneof" => {
164 decls.push(Decl::Oneof(path_of(&package, &stack, &name)));
165 stack.push(Block::Other);
166 }
167 _ => stack.push(Block::Other),
168 }
169 continue;
170 }
171 if let Some(head) = line.strip_suffix("{}") {
172 let head = head.trim();
173 let mut tokens = head.split_whitespace();
174 let keyword = tokens.next().unwrap_or_default();
175 let name = tokens.next().unwrap_or_default();
176 if matches!(keyword, "message" | "enum") && !name.is_empty() {
177 decls.push(Decl::Schema(path_of(&package, &stack, name)));
178 }
179 continue;
180 }
181 if line == "}" {
182 stack.pop();
183 continue;
184 }
185 if !matches!(stack.last(), Some(Block::Message(_))) {
186 continue;
187 }
188 let Some((ty, name, repeated)) = parse_field(line) else {
189 continue;
190 };
191 let kind = match (ty.as_str(), repeated) {
192 ("google.protobuf.Timestamp", _) => FieldKind::Timestamp,
193 ("bytes", true) => FieldKind::BytesList,
194 ("bytes", false) => FieldKind::Bytes,
195 _ => continue,
196 };
197 decls.push(Decl::Field(path_of(&package, &stack, &name), kind));
198 }
199 decls
200}
201
202fn package_of(text: &str) -> Option<String> {
203 text.lines()
204 .find_map(|line| line.trim().strip_prefix("package "))
205 .map(|rest| rest.trim().trim_end_matches(';').trim().to_string())
206}
207
208fn path_of(package: &str, stack: &[Block], name: &str) -> String {
209 let mut path = format!(".{package}");
210 for block in stack {
211 if let Block::Message(outer) = block {
212 path.push('.');
213 path.push_str(outer);
214 }
215 }
216 path.push('.');
217 path.push_str(name);
218 path
219}
220
221fn parse_field(line: &str) -> Option<(String, String, bool)> {
222 let body = line.strip_suffix(';')?;
223 let tokens: Vec<&str> = body.split_whitespace().collect();
224 let eq = tokens.iter().position(|token| *token == "=")?;
225 if eq < 2 {
226 return None;
227 }
228 let name = tokens[eq - 1];
229 let ty = tokens[eq - 2];
230 if ty.contains('<') || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
231 return None;
232 }
233 let repeated = eq >= 3 && tokens[eq - 3] == "repeated";
234 Some((ty.to_string(), name.to_string(), repeated))
235}
236
237fn strip_comments(text: &str) -> String {
238 let mut out = String::with_capacity(text.len());
239 let mut chars = text.chars().peekable();
240 let mut line_comment = false;
241 let mut block_comment = false;
242 while let Some(current) = chars.next() {
243 if line_comment {
244 if current == '\n' {
245 line_comment = false;
246 out.push('\n');
247 }
248 continue;
249 }
250 if block_comment {
251 if current == '*' && chars.peek() == Some(&'/') {
252 chars.next();
253 block_comment = false;
254 }
255 continue;
256 }
257 if current == '/' && chars.peek() == Some(&'/') {
258 chars.next();
259 line_comment = true;
260 continue;
261 }
262 if current == '/' && chars.peek() == Some(&'*') {
263 chars.next();
264 block_comment = true;
265 continue;
266 }
267 out.push(current);
268 }
269 out
270}
271
272/// 把 `view/dist` 下的前端产物编进二进制:认证中心自带登录页,不依赖外部静态站点。
273fn build_web_assets(manifest_dir: &Path, out_dir: &Path) {
274 println!("cargo:rerun-if-changed={WEB_DIST}");
275 println!("cargo:rerun-if-changed=build.rs");
276 println!("cargo:rerun-if-env-changed=KENOAUTH_SKIP_WEB_ASSETS");
277
278 if std::env::var_os("KENOAUTH_SKIP_WEB_ASSETS").is_some() {
279 // 纯后端编译(未装 pnpm 的环境)跳过前端资产,服务启动后退化为 501 页面。
280 let source = "// web assets skipped via KENOAUTH_SKIP_WEB_ASSETS\npub static ASSETS: &[Asset] = &[];\n";
281 fs::write(out_dir.join("assets.rs"), source).expect("writing generated asset table");
282 return;
283 }
284
285 let root = manifest_dir.join(WEB_DIST);
286 let mut files = Vec::new();
287 collect_assets(&root, &root, &mut files);
288 files.sort();
289
290 let mut source = String::from(concat!(
291 "// generated by build.rs from ",
292 "view/dist\n",
293 "pub static ASSETS: &[Asset] = &[\n"
294 ));
295 for file in &files {
296 let absolute = root.join(file);
297 let absolute = absolute
298 .canonicalize()
299 .unwrap_or(absolute)
300 .to_string_lossy()
301 .replace('\\', "/");
302 let _ = writeln!(
303 source,
304 " Asset {{ path: {file:?}, content_type: {:?}, bytes: include_bytes!({absolute:?}) }},",
305 content_type(file)
306 );
307 }
308 source.push_str("];\n");
309
310 fs::write(out_dir.join("assets.rs"), source).expect("writing generated asset table");
311}
312
313fn collect_assets(root: &Path, dir: &Path, files: &mut Vec<String>) {
314 let Ok(entries) = fs::read_dir(dir) else {
315 return;
316 };
317
318 for entry in entries.flatten() {
319 let path = entry.path();
320 if path.is_dir() {
321 collect_assets(root, &path, files);
322 } else if let Ok(relative) = path.strip_prefix(root) {
323 files.push(relative.to_string_lossy().replace('\\', "/"));
324 }
325 }
326}
327
328fn content_type(file: &str) -> &'static str {
329 match file.rsplit_once('.').map(|(_, extension)| extension) {
330 Some("html" | "htm") => "text/html; charset=utf-8",
331 Some("js" | "mjs") => "text/javascript; charset=utf-8",
332 Some("css") => "text/css; charset=utf-8",
333 Some("json" | "map") => "application/json",
334 Some("svg") => "image/svg+xml",
335 Some("png") => "image/png",
336 Some("jpg" | "jpeg") => "image/jpeg",
337 Some("webp") => "image/webp",
338 Some("gif") => "image/gif",
339 Some("ico") => "image/x-icon",
340 Some("txt") => "text/plain; charset=utf-8",
341 Some("woff") => "font/woff",
342 Some("woff2") => "font/woff2",
343 Some("ttf") => "font/ttf",
344 Some("otf") => "font/otf",
345 Some("wasm") => "application/wasm",
346 Some("xml") => "application/xml",
347 Some("pdf") => "application/pdf",
348 _ => "application/octet-stream",
349 }
350}
Added lib/identity/Cargo.toml View lib/identity/Cargo.toml
@@ -0,0 +1,105 @@
1[package]
2name = "kenoauth"
3version = "1.0.0"
4edition = "2024"
5publish = false
6autobins = false
7autoexamples = false
8description = "KenOAuth 认证中心:账号、会话、令牌与 SSH 公钥的唯一入口"
9license = "MIT"
10
11[lib]
12name = "kenoauth"
13path = "src/lib.rs"
14
15[dependencies]
16tokio-stream = { version = "0.1", features = ["net"] }
17actix-web = "4.16.0"
18aes-gcm = "0.11.1"
19anyhow = "1.0.104"
20argon2 = "0.6.0"
21async-trait = "0.1.92"
22base64 = "0.23.1"
23bytes = "1.12.1"
24chrono = { version = "0.4.45", features = ["serde"] }
25deadpool-redis = { version = "0.23.1", features = [
26 "cluster",
27 "cluster-async",
28 "script",
29 "tokio-comp",
30] }
31ed25519-dalek = "2.2.0"
32figment = { version = "0.10", default-features = false, features = [
33 "toml",
34 "json",
35 "yaml",
36 "env",
37] }
38futures-util = "0.3.34"
39hmac = "0.12.1"
40http = "1.5.0"
41http-body = "1.1.0"
42lettre = { version = "0.11.23", default-features = false, features = [
43 "smtp-transport",
44 "tokio1-rustls",
45 "rustls-tls",
46 "builder",
47 "pool",
48] }
49maud = "0.27.0"
50opentelemetry = "0.32.0"
51opentelemetry-appender-tracing = { version = "0.32.0", features = [
52 "experimental_span_attributes",
53] }
54opentelemetry-otlp = { version = "0.32.0", default-features = false, features = [
55 "http-proto",
56 "reqwest-blocking-client",
57 "internal-logs",
58 "logs",
59 "metrics",
60 "trace",
61] }
62opentelemetry_sdk = "0.32.1"
63percent-encoding = "2.3.2"
64prost = "0.14.4"
65prost-types = "0.14.4"
66rand = "0.10.3"
67rand_core = { version = "0.6.4", features = ["getrandom"] }
68reqwest = { version = "0.13.5", features = ["json", "stream", "form"] }
69rsa = { version = "0.9.10", features = ["sha2", "getrandom"] }
70rocketmq-client-rust = "0.9.0"
71rocketmq-common = "0.9.0"
72sea-orm = { version = "2.0.4", default-features = false, features = [
73 "macros",
74 "with-chrono",
75 "with-json",
76 "with-uuid",
77 "with-time",
78 "postgres-array",
79 "schema-sync",
80 "entity-registry",
81 "sqlx-postgres",
82 "runtime-tokio-rustls",
83] }
84serde = { version = "1.0.229", features = ["derive"] }
85serde_json = "1.0.154"
86sha1 = "0.10.7"
87sha2 = "0.10.9"
88sqlparser = { version = "0.63.0", default-features = false, features = ["std"] }
89thiserror = "2.0.21"
90tokio = { version = "1.53.3", features = ["full"] }
91tokio-util = "0.7.20"
92tonic = { version = "0.14.6", features = ["tls-ring"] }
93tonic-health = "0.14.6"
94tonic-prost = "0.14.6"
95tonic-reflection = "0.14.6"
96tower = "0.5.3"
97tracing = "0.1.44"
98tracing-opentelemetry = "0.33.0"
99tracing-subscriber = { version = "0.3.23", features = ["env-filter", "fmt"] }
100utoipa = { version = "6.0.0", features = ["actix_extras", "chrono", "uuid"] }
101uuid = { version = "1.28.0", features = ["serde", "v4", "v7"] }
102
103[build-dependencies]
104tonic-prost-build = "0.14.6"
105
Added lib/identity/LICENSE View lib/identity/LICENSE
@@ -0,0 +1,21 @@
1MIT License
2
3Copyright (c) 2026 KenOAuth contributors
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in all
13copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21SOFTWARE.
Added lib/identity/OpenApi.Auth.json View lib/identity/OpenApi.Auth.json
@@ -0,0 +1,8681 @@
1{
2 "openapi": "3.1.0",
3 "info": {
4 "title": "auth",
5 "description": "认证与账号服务:工作量证明、口令与验证码登录、二次验证、注册、口令重置、访问令牌与 SSH 公钥。",
6 "license": {
7 "name": "MIT",
8 "identifier": "MIT"
9 },
10 "version": "1.0.0"
11 },
12 "paths": {
13 "/.well-known/oauth-authorization-server": {
14 "get": {
15 "tags": [
16 "oauth"
17 ],
18 "summary": "OAuth 授权服务器元数据(含 OIDC 字段)",
19 "operationId": "oauth_metadata",
20 "responses": {
21 "200": {
22 "description": "RFC 8414 元数据"
23 }
24 }
25 }
26 },
27 "/.well-known/openid-configuration": {
28 "get": {
29 "tags": [
30 "oauth"
31 ],
32 "summary": "OIDC 发现文档",
33 "operationId": "oauth_openid_configuration",
34 "responses": {
35 "200": {
36 "description": "OpenID Provider 元数据"
37 }
38 }
39 }
40 },
41 "/api/v1/oauth/access-tokens": {
42 "get": {
43 "tags": [
44 "auth"
45 ],
46 "summary": "列示访问令牌",
47 "description": "按创建时间倒序返回当前账号的令牌摘要,只给指纹与状态,不回吐令牌原文。分页参数由 `page` / `page_size` 控制。查询串不是合法分页参数或路径/方法不匹配时返回 400 / 404 由 actix-web 直接给出。",
48 "operationId": "auth_access_token_list",
49 "parameters": [
50 {
51 "name": "page",
52 "in": "query",
53 "description": "页码,从 1 开始;默认 1",
54 "required": false,
55 "schema": {
56 "type": "integer",
57 "format": "int64",
58 "minimum": 0
59 }
60 },
61 {
62 "name": "page_size",
63 "in": "query",
64 "description": "每页条数,默认 20,最多 100",
65 "required": false,
66 "schema": {
67 "type": "integer",
68 "format": "int64",
69 "minimum": 0
70 }
71 }
72 ],
73 "responses": {
74 "200": {
75 "description": "令牌摘要列表",
76 "content": {
77 "application/json": {
78 "schema": {
79 "$ref": "#/components/schemas/Envelope_Vec_AccessTokenSummary"
80 },
81 "example": {
82 "code": 200,
83 "data": [
84 {
85 "created_at": "2026-01-02T03:04:05Z",
86 "description": "本机命令行",
87 "expires_at": null,
88 "fingerprint": "3f9c1a7b2d4e6058",
89 "id": "018f2b1c-4d5e-7f80-9a1b-2c3d4e5f6071",
90 "last_used_at": null,
91 "name": "cli",
92 "revoked_at": null,
93 "scope": [
94 "ssh_key",
95 "assets"
96 ]
97 }
98 ],
99 "message": "success"
100 }
101 }
102 }
103 },
104 "401": {
105 "description": "会话未绑定账号(`unauthenticated`)",
106 "content": {
107 "application/json": {
108 "schema": {
109 "$ref": "#/components/schemas/Envelope_NoData"
110 },
111 "example": {
112 "code": "unauthenticated",
113 "message": "authentication required"
114 }
115 }
116 }
117 },
118 "500": {
119 "description": "服务端故障(`internal`、`database`、`session`、`serialization`)",
120 "content": {
121 "application/json": {
122 "schema": {
123 "$ref": "#/components/schemas/Envelope_NoData"
124 },
125 "example": {
126 "code": "internal",
127 "message": "internal server error"
128 }
129 }
130 }
131 }
132 }
133 },
134 "post": {
135 "tags": [
136 "auth"
137 ],
138 "summary": "签发访问令牌",
139 "description": "名称 1~64 字符、备注最长 255 字符,`scope` 必须全部命中白名单(最多 32 项),`expires_in_secs` 取值 60 ~ 31536000(不填表示长期有效)。同一账号未吊销的令牌上限 50 个。签发成功记 `AccessKeyAdded` 审计事件。",
140 "operationId": "auth_access_token_create",
141 "requestBody": {
142 "content": {
143 "application/json": {
144 "schema": {
145 "$ref": "#/components/schemas/AccessTokenCreateRequest"
146 }
147 }
148 },
149 "required": true
150 },
151 "responses": {
152 "200": {
153 "description": "令牌已签发,`token` 只在此响应里出现一次",
154 "content": {
155 "application/json": {
156 "schema": {
157 "$ref": "#/components/schemas/Envelope_AccessTokenCreateResponse"
158 },
159 "example": {
160 "code": 200,
161 "data": {
162 "created_at": "2026-01-02T03:04:05Z",
163 "expires_at": null,
164 "id": "018f2b1c-4d5e-7f80-9a1b-2c3d4e5f6071",
165 "name": "cli",
166 "scope": [
167 "ssh_key",
168 "assets"
169 ],
170 "token": "Zk9tQ2h5cFJ2TjB3U3gxbUhhcTRkRmc2WXpVMEJqTDlLbVNu"
171 },
172 "message": "success"
173 }
174 }
175 }
176 },
177 "400": {
178 "description": "名称、备注、范围或有效期不合法(`invalid_access_key_name`、`malformed_request`、`invalid_scope`、`invalid_expiry`)",
179 "content": {
180 "application/json": {
181 "schema": {
182 "$ref": "#/components/schemas/Envelope_NoData"
183 },
184 "example": {
185 "code": "invalid_scope",
186 "message": "access token scope is invalid"
187 }
188 }
189 }
190 },
191 "401": {
192 "description": "会话未绑定账号,或账号已删除 / 不允许使用(`unauthenticated`)",
193 "content": {
194 "application/json": {
195 "schema": {
196 "$ref": "#/components/schemas/Envelope_NoData"
197 },
198 "example": {
199 "code": "unauthenticated",
200 "message": "authentication required"
201 }
202 }
203 }
204 },
205 "429": {
206 "description": "未吊销的令牌已达上限(`too_many_access_keys`)",
207 "content": {
208 "application/json": {
209 "schema": {
210 "$ref": "#/components/schemas/Envelope_NoData"
211 },
212 "example": {
213 "code": "too_many_access_keys",
214 "message": "too many access keys"
215 }
216 }
217 }
218 },
219 "500": {
220 "description": "服务端故障(`internal`、`database`、`session`、`serialization`)",
221 "content": {
222 "application/json": {
223 "schema": {
224 "$ref": "#/components/schemas/Envelope_NoData"
225 },
226 "example": {
227 "code": "internal",
228 "message": "internal server error"
229 }
230 }
231 }
232 }
233 }
234 }
235 },
236 "/api/v1/oauth/access-tokens/{token_id}": {
237 "delete": {
238 "tags": [
239 "auth"
240 ],
241 "summary": "吊销访问令牌",
242 "description": "把令牌标记为已吊销,记录保留不硬删;重复调用幂等。令牌不存在或不属于当前账号返回 `access_token_not_found`(按 `errors.rs` 的归类为 401)。吊销成功记 `AccessKeyRemoved` 审计事件。",
243 "operationId": "auth_access_token_revoke",
244 "parameters": [
245 {
246 "name": "token_id",
247 "in": "path",
248 "description": "令牌编号",
249 "required": true,
250 "schema": {
251 "type": "string",
252 "format": "uuid"
253 }
254 }
255 ],
256 "responses": {
257 "200": {
258 "description": "已吊销;重复调用同样返回成功",
259 "content": {
260 "application/json": {
261 "schema": {
262 "$ref": "#/components/schemas/Envelope_NoData"
263 },
264 "example": {
265 "code": 200,
266 "data": null,
267 "message": "success"
268 }
269 }
270 }
271 },
272 "401": {
273 "description": "会话未绑定账号(`unauthenticated`),或令牌不属于当前账号 / 不存在(`access_token_not_found`)",
274 "content": {
275 "application/json": {
276 "schema": {
277 "$ref": "#/components/schemas/Envelope_NoData"
278 },
279 "example": {
280 "code": "access_token_not_found",
281 "message": "access token does not exist"
282 }
283 }
284 }
285 },
286 "500": {
287 "description": "服务端故障(`internal`、`database`、`session`、`serialization`)",
288 "content": {
289 "application/json": {
290 "schema": {
291 "$ref": "#/components/schemas/Envelope_NoData"
292 },
293 "example": {
294 "code": "internal",
295 "message": "internal server error"
296 }
297 }
298 }
299 }
300 }
301 }
302 },
303 "/api/v1/oauth/app-tokens": {
304 "post": {
305 "tags": [
306 "auth"
307 ],
308 "summary": "签发内部应用令牌",
309 "description": "认证中心用 Ed25519 私钥签发 JWT(`alg=EdDSA`),`aud` 为目标 app 的 audience,`client_id` 为 app 标识。任何服务只需 `/.well-known/jwks.json` 的公钥即可本地验签。`ttl_seconds` 取值 1 ~ 3600,不填或填 0 用默认 900。会话未绑定账号返回 `unauthenticated`(401);app 未在 `apps` 清单里返回 `app_not_found`(404);私钥未配置返回 `unavailable`(503)。",
310 "operationId": "auth_app_token_issue",
311 "requestBody": {
312 "content": {
313 "application/json": {
314 "schema": {
315 "$ref": "#/components/schemas/AppTokenRequest"
316 }
317 }
318 },
319 "required": true
320 },
321 "responses": {
322 "200": {
323 "description": "令牌已签发,`token` 是 EdDSA JWT",
324 "content": {
325 "application/json": {
326 "schema": {
327 "$ref": "#/components/schemas/Envelope_AppTokenResponse"
328 },
329 "example": {
330 "code": 200,
331 "data": {
332 "app_id": "api",
333 "audience": "api",
334 "expires_at": "2026-01-02T03:19:05Z",
335 "issued_at": "2026-01-02T03:04:05Z",
336 "key_id": "app-local-1",
337 "token": "eyJhbGciOiJFZERTQSIsImtpZCI6ImsxIiwidHlwIjoiSldUIn0.…"
338 },
339 "message": "success"
340 }
341 }
342 }
343 },
344 "400": {
345 "description": "app_id 为空或 ttl_seconds 超出上限(`malformed_request`、`invalid_expiry`)",
346 "content": {
347 "application/json": {
348 "schema": {
349 "$ref": "#/components/schemas/Envelope_NoData"
350 },
351 "example": {
352 "code": "invalid_expiry",
353 "message": "expires_at is invalid"
354 }
355 }
356 }
357 },
358 "401": {
359 "description": "会话未绑定账号(`unauthenticated`)",
360 "content": {
361 "application/json": {
362 "schema": {
363 "$ref": "#/components/schemas/Envelope_NoData"
364 },
365 "example": {
366 "code": "unauthenticated",
367 "message": "authentication required"
368 }
369 }
370 }
371 },
372 "404": {
373 "description": "app_id 不在认证中心的 `apps` 清单里(`app_not_found`)",
374 "content": {
375 "application/json": {
376 "schema": {
377 "$ref": "#/components/schemas/Envelope_NoData"
378 },
379 "example": {
380 "code": "app_not_found",
381 "message": "app does not exist"
382 }
383 }
384 }
385 },
386 "500": {
387 "description": "服务端故障(`internal`、`serialization`)",
388 "content": {
389 "application/json": {
390 "schema": {
391 "$ref": "#/components/schemas/Envelope_NoData"
392 },
393 "example": {
394 "code": "internal",
395 "message": "internal server error"
396 }
397 }
398 }
399 },
400 "503": {
401 "description": "签名密钥未配置(`unavailable`)",
402 "content": {
403 "application/json": {
404 "schema": {
405 "$ref": "#/components/schemas/Envelope_NoData"
406 },
407 "example": {
408 "code": "unavailable",
409 "message": "dependency is unavailable: jwt.private_key is required to issue tokens"
410 }
411 }
412 }
413 }
414 }
415 }
416 },
417 "/api/v1/oauth/authorize": {
418 "get": {
419 "tags": [
420 "oauth"
421 ],
422 "summary": "授权端点(授权码 + PKCE S256)",
423 "description": "未登录跳到 `/login`;首次授权跳 `/consent`;`prompt=none` 直接返回 `login_required` / `consent_required`。",
424 "operationId": "oauth_authorize",
425 "parameters": [
426 {
427 "name": "response_type",
428 "in": "query",
429 "description": "固定为 `code`",
430 "required": true,
431 "schema": {
432 "type": "string"
433 }
434 },
435 {
436 "name": "client_id",
437 "in": "query",
438 "description": "已注册的客户端 id",
439 "required": true,
440 "schema": {
441 "type": "string"
442 }
443 },
444 {
445 "name": "redirect_uri",
446 "in": "query",
447 "description": "已注册的回调地址,支持子域通配",
448 "required": true,
449 "schema": {
450 "type": "string"
451 }
452 },
453 {
454 "name": "state",
455 "in": "query",
456 "description": "16..=512 字符的不透明串",
457 "required": true,
458 "schema": {
459 "type": "string"
460 }
461 },
462 {
463 "name": "code_challenge",
464 "in": "query",
465 "description": "PKCE S256 challenge(43 字符 base64url)",
466 "required": true,
467 "schema": {
468 "type": "string"
469 }
470 },
471 {
472 "name": "code_challenge_method",
473 "in": "query",
474 "description": "固定为 `S256`",
475 "required": true,
476 "schema": {
477 "type": "string"
478 }
479 },
480 {
481 "name": "scope",
482 "in": "query",
483 "description": "空格分隔,必须 ⊆ 客户端登记 scope",
484 "required": false,
485 "schema": {
486 "type": "string"
487 }
488 },
489 {
490 "name": "nonce",
491 "in": "query",
492 "description": "OIDC nonce,原样写入 id_token",
493 "required": false,
494 "schema": {
495 "type": "string"
496 }
497 },
498 {
499 "name": "prompt",
500 "in": "query",
501 "description": "`none` / `login` / `consent`",
502 "required": false,
503 "schema": {
504 "type": "string"
505 }
506 },
507 {
508 "name": "max_age",
509 "in": "query",
510 "description": "会话最大年龄(秒),超出就重新登录",
511 "required": false,
512 "schema": {
513 "type": "integer",
514 "format": "int64"
515 }
516 }
517 ],
518 "responses": {
519 "302": {
520 "description": "重定向回 `redirect_uri`,带 `code` / `error` / `state` / `iss`"
521 },
522 "400": {
523 "description": "`invalid_request`(客户端或回调未注册)"
524 }
525 }
526 }
527 },
528 "/api/v1/oauth/challenge": {
529 "post": {
530 "tags": [
531 "auth"
532 ],
533 "summary": "签发工作量证明挑战",
534 "description": "把挑战写进当前会话并返回。客户端需要为 `context` 找出一枚满足 `difficulty`(前导零比特数)的 nonce;`pow.v1` 算法与前端求解器一致。",
535 "operationId": "auth_challenge",
536 "responses": {
537 "200": {
538 "description": "挑战已签发并写入会话",
539 "content": {
540 "application/json": {
541 "schema": {
542 "$ref": "#/components/schemas/Envelope_ChallengeResponse"
543 },
544 "example": {
545 "code": 200,
546 "data": {
547 "context": "3xJk9Qp2mV7sLbT1rZa8nHc4wDf6YgU0",
548 "difficulty": 16,
549 "expires_at": 1767139500,
550 "issued_at": 1767139200
551 },
552 "message": "success"
553 }
554 }
555 }
556 },
557 "500": {
558 "description": "`internal`(挑战无法写入会话)/ `cache` / `session` / `serialization`",
559 "content": {
560 "application/json": {
561 "schema": {
562 "$ref": "#/components/schemas/Envelope_NoData"
563 },
564 "example": {
565 "code": "internal",
566 "message": "internal server error"
567 }
568 }
569 }
570 }
571 }
572 }
573 },
574 "/api/v1/oauth/consent": {
575 "get": {
576 "tags": [
577 "oauth"
578 ],
579 "summary": "同意页所需的客户端与 scope 预览",
580 "operationId": "oauth_consent_preview",
581 "responses": {
582 "200": {
583 "description": "客户端名称与待授权 scope",
584 "content": {
585 "application/json": {
586 "schema": {
587 "$ref": "#/components/schemas/ConsentPreview"
588 }
589 }
590 }
591 },
592 "400": {
593 "description": "`invalid_request`"
594 },
595 "401": {
596 "description": "`login_required`"
597 }
598 }
599 },
600 "post": {
601 "tags": [
602 "oauth"
603 ],
604 "summary": "提交同意决定",
605 "description": "授权参数从查询串读取;`decision=allow` 落库并回 `/api/v1/oauth/authorize?...`,`deny` 回客户端并带 `error=access_denied`。",
606 "operationId": "oauth_consent_decide",
607 "requestBody": {
608 "content": {
609 "application/json": {
610 "schema": {
611 "$ref": "#/components/schemas/ConsentForm"
612 }
613 }
614 },
615 "required": true
616 },
617 "responses": {
618 "200": {
619 "description": "下一步地址",
620 "content": {
621 "application/json": {
622 "schema": {
623 "$ref": "#/components/schemas/ConsentDecision"
624 }
625 }
626 }
627 },
628 "400": {
629 "description": "`invalid_request`"
630 },
631 "401": {
632 "description": "`login_required`"
633 }
634 }
635 }
636 },
637 "/api/v1/oauth/login": {
638 "post": {
639 "tags": [
640 "auth"
641 ],
642 "summary": "口令登录",
643 "description": "用口令登录。`identity` 可以是用户名、邮箱(大小写不敏感)或用户编号,`password_hash` 是客户端先算好的口令摘要,工作量证明的载荷必须是 `identity`。账号启用了多因子时返回 `mfa_required: true` 与 `challenge`,此时会话尚未绑定,需再调 `/api/v1/oauth/login/verify`。",
644 "operationId": "auth_login",
645 "requestBody": {
646 "content": {
647 "application/json": {
648 "schema": {
649 "$ref": "#/components/schemas/UserLoginRequest"
650 }
651 }
652 },
653 "required": true
654 },
655 "responses": {
656 "200": {
657 "description": "登录成功;`mfa_required` 为真时还需二次验证",
658 "content": {
659 "application/json": {
660 "schema": {
661 "$ref": "#/components/schemas/Envelope_UserLoginResponse"
662 },
663 "example": {
664 "code": 200,
665 "data": {
666 "challenge": null,
667 "displayname": "Ada",
668 "mfa_required": false,
669 "user_id": 1024,
670 "username": "ada"
671 },
672 "message": "success"
673 }
674 }
675 }
676 },
677 "400": {
678 "description": "工作量证明过期、载荷不匹配或请求不合法(`expired`、`invalid_proof`、`invalid_payload`、`malformed_request`)",
679 "content": {
680 "application/json": {
681 "schema": {
682 "$ref": "#/components/schemas/Envelope_NoData"
683 },
684 "example": {
685 "code": "expired",
686 "message": "challenge expired"
687 }
688 }
689 }
690 },
691 "401": {
692 "description": "身份或口令不正确(`invalid_credentials`)",
693 "content": {
694 "application/json": {
695 "schema": {
696 "$ref": "#/components/schemas/Envelope_NoData"
697 },
698 "example": {
699 "code": "invalid_credentials",
700 "message": "invalid credentials"
701 }
702 }
703 }
704 },
705 "403": {
706 "description": "账号被禁止登录,原因见 `message`(`login_forbidden`)",
707 "content": {
708 "application/json": {
709 "schema": {
710 "$ref": "#/components/schemas/Envelope_NoData"
711 },
712 "example": {
713 "code": "login_forbidden",
714 "message": "account is banned"
715 }
716 }
717 }
718 },
719 "429": {
720 "description": "失败次数超出配额(`rate_limited`)",
721 "content": {
722 "application/json": {
723 "schema": {
724 "$ref": "#/components/schemas/Envelope_NoData"
725 },
726 "example": {
727 "code": "rate_limited",
728 "message": "too many attempts"
729 }
730 }
731 }
732 },
733 "500": {
734 "description": "服务端故障(`internal`、`database`、`cache`、`session`、`serialization`)",
735 "content": {
736 "application/json": {
737 "schema": {
738 "$ref": "#/components/schemas/Envelope_NoData"
739 },
740 "example": {
741 "code": "internal",
742 "message": "internal server error"
743 }
744 }
745 }
746 }
747 }
748 }
749 },
750 "/api/v1/oauth/login/code": {
751 "post": {
752 "tags": [
753 "auth"
754 ],
755 "summary": "发送邮箱登录验证码",
756 "description": "向账号邮箱发送 6 位登录验证码,`expires_in_secs` 为有效期。工作量证明的载荷必须是 `email`。同一账号的发码间隔受 `email_code_rate_limit_secs` 限制。",
757 "operationId": "auth_login_code",
758 "requestBody": {
759 "content": {
760 "application/json": {
761 "schema": {
762 "$ref": "#/components/schemas/LoginCodeRequest"
763 }
764 }
765 },
766 "required": true
767 },
768 "responses": {
769 "200": {
770 "description": "验证码已发送",
771 "content": {
772 "application/json": {
773 "schema": {
774 "$ref": "#/components/schemas/Envelope_LoginCodeResponse"
775 },
776 "example": {
777 "code": 200,
778 "data": {
779 "expires_in_secs": 600
780 },
781 "message": "success"
782 }
783 }
784 }
785 },
786 "400": {
787 "description": "邮箱格式不合法或工作量证明过期、载荷不匹配(`invalid_email`、`expired`、`invalid_proof`、`invalid_payload`、`malformed_request`)",
788 "content": {
789 "application/json": {
790 "schema": {
791 "$ref": "#/components/schemas/Envelope_NoData"
792 },
793 "example": {
794 "code": "invalid_email",
795 "message": "invalid email address"
796 }
797 }
798 }
799 },
800 "401": {
801 "description": "邮箱未注册或账号不可用(`invalid_credentials`)",
802 "content": {
803 "application/json": {
804 "schema": {
805 "$ref": "#/components/schemas/Envelope_NoData"
806 },
807 "example": {
808 "code": "invalid_credentials",
809 "message": "invalid credentials"
810 }
811 }
812 }
813 },
814 "403": {
815 "description": "账号被禁止登录,原因见 `message`(`login_forbidden`)",
816 "content": {
817 "application/json": {
818 "schema": {
819 "$ref": "#/components/schemas/Envelope_NoData"
820 },
821 "example": {
822 "code": "login_forbidden",
823 "message": "account is banned"
824 }
825 }
826 }
827 },
828 "429": {
829 "description": "发码过于频繁(`rate_limited`)",
830 "content": {
831 "application/json": {
832 "schema": {
833 "$ref": "#/components/schemas/Envelope_NoData"
834 },
835 "example": {
836 "code": "rate_limited",
837 "message": "too many requests"
838 }
839 }
840 }
841 },
842 "500": {
843 "description": "服务端故障,含邮件发送失败(`internal`、`database`、`cache`、`session`、`email`、`serialization`)",
844 "content": {
845 "application/json": {
846 "schema": {
847 "$ref": "#/components/schemas/Envelope_NoData"
848 },
849 "example": {
850 "code": "internal",
851 "message": "internal server error"
852 }
853 }
854 }
855 }
856 }
857 }
858 },
859 "/api/v1/oauth/login/code/verify": {
860 "post": {
861 "tags": [
862 "auth"
863 ],
864 "summary": "邮箱验证码换会话",
865 "description": "用邮箱登录验证码换会话。工作量证明的载荷必须是 `email`。验证码一旦核对通过即作废,连续输错 `max_verify_attempts` 次后需重新申请。账号启用了多因子时返回 `mfa_required: true` 与 `challenge`。",
866 "operationId": "auth_login_code_verify",
867 "requestBody": {
868 "content": {
869 "application/json": {
870 "schema": {
871 "$ref": "#/components/schemas/LoginCodeVerifyRequest"
872 }
873 }
874 },
875 "required": true
876 },
877 "responses": {
878 "200": {
879 "description": "登录成功;`mfa_required` 为真时还需二次验证",
880 "content": {
881 "application/json": {
882 "schema": {
883 "$ref": "#/components/schemas/Envelope_UserLoginResponse"
884 },
885 "example": {
886 "code": 200,
887 "data": {
888 "challenge": null,
889 "displayname": "Ada",
890 "mfa_required": false,
891 "user_id": 1024,
892 "username": "ada"
893 },
894 "message": "success"
895 }
896 }
897 }
898 },
899 "400": {
900 "description": "验证码不正确或已过期,或邮箱格式、工作量证明不合法(`invalid_email_code`、`invalid_email`、`expired`、`invalid_proof`、`invalid_payload`、`malformed_request`)",
901 "content": {
902 "application/json": {
903 "schema": {
904 "$ref": "#/components/schemas/Envelope_NoData"
905 },
906 "example": {
907 "code": "invalid_email_code",
908 "message": "invalid email code"
909 }
910 }
911 }
912 },
913 "401": {
914 "description": "邮箱未注册或账号不可用(`invalid_credentials`)",
915 "content": {
916 "application/json": {
917 "schema": {
918 "$ref": "#/components/schemas/Envelope_NoData"
919 },
920 "example": {
921 "code": "invalid_credentials",
922 "message": "invalid credentials"
923 }
924 }
925 }
926 },
927 "403": {
928 "description": "账号被禁止登录,原因见 `message`(`login_forbidden`)",
929 "content": {
930 "application/json": {
931 "schema": {
932 "$ref": "#/components/schemas/Envelope_NoData"
933 },
934 "example": {
935 "code": "login_forbidden",
936 "message": "account is banned"
937 }
938 }
939 }
940 },
941 "429": {
942 "description": "验证码尝试次数超限或请求过频(`email_code_attempts_exceeded`、`rate_limited`)",
943 "content": {
944 "application/json": {
945 "schema": {
946 "$ref": "#/components/schemas/Envelope_NoData"
947 },
948 "example": {
949 "code": "email_code_attempts_exceeded",
950 "message": "too many attempts"
951 }
952 }
953 }
954 },
955 "500": {
956 "description": "服务端故障(`internal`、`database`、`cache`、`session`、`serialization`)",
957 "content": {
958 "application/json": {
959 "schema": {
960 "$ref": "#/components/schemas/Envelope_NoData"
961 },
962 "example": {
963 "code": "internal",
964 "message": "internal server error"
965 }
966 }
967 }
968 }
969 }
970 }
971 },
972 "/api/v1/oauth/login/verify": {
973 "post": {
974 "tags": [
975 "auth"
976 ],
977 "summary": "二次验证换会话",
978 "description": "用 6 位 TOTP 动态码或一枚未使用的恢复码完成二次验证并绑定会话。`challenge` 必须与 `/api/v1/oauth/login` 或 `/api/v1/oauth/login/code/verify` 返回的一致,挑战有效期 300 秒,同一动态码时间步不能重复使用,恢复码用掉即作废。",
979 "operationId": "auth_login_verify",
980 "requestBody": {
981 "content": {
982 "application/json": {
983 "schema": {
984 "$ref": "#/components/schemas/LoginVerifyRequest"
985 }
986 }
987 },
988 "required": true
989 },
990 "responses": {
991 "200": {
992 "description": "登录成功,会话已绑定",
993 "content": {
994 "application/json": {
995 "schema": {
996 "$ref": "#/components/schemas/Envelope_UserLoginResponse"
997 },
998 "example": {
999 "code": 200,
1000 "data": {
1001 "challenge": null,
1002 "displayname": "Ada",
1003 "mfa_required": false,
1004 "user_id": 1024,
1005 "username": "ada"
1006 },
1007 "message": "success"
1008 }
1009 }
1010 }
1011 },
1012 "400": {
1013 "description": "挑战过期或对不上、动态码错误、动态码时间步已用过、恢复码无效(`expired`、`malformed_request`、`totp_code_invalid`、`totp_step_reused`、`recovery_code_invalid`)",
1014 "content": {
1015 "application/json": {
1016 "schema": {
1017 "$ref": "#/components/schemas/Envelope_NoData"
1018 },
1019 "example": {
1020 "code": "totp_code_invalid",
1021 "message": "invalid totp code"
1022 }
1023 }
1024 }
1025 },
1026 "401": {
1027 "description": "会话里没有待验证挑战、账号不可用或多因子记录已被撤销(`unauthenticated`、`invalid_credentials`、`mfa_required`)",
1028 "content": {
1029 "application/json": {
1030 "schema": {
1031 "$ref": "#/components/schemas/Envelope_NoData"
1032 },
1033 "example": {
1034 "code": "mfa_required",
1035 "message": "multi-factor authentication required"
1036 }
1037 }
1038 }
1039 },
1040 "403": {
1041 "description": "账号被禁止登录,原因见 `message`(`login_forbidden`)",
1042 "content": {
1043 "application/json": {
1044 "schema": {
1045 "$ref": "#/components/schemas/Envelope_NoData"
1046 },
1047 "example": {
1048 "code": "login_forbidden",
1049 "message": "account is banned"
1050 }
1051 }
1052 }
1053 },
1054 "429": {
1055 "description": "尝试次数超限或恢复码已用尽(`rate_limited`、`recovery_codes_exhausted`)",
1056 "content": {
1057 "application/json": {
1058 "schema": {
1059 "$ref": "#/components/schemas/Envelope_NoData"
1060 },
1061 "example": {
1062 "code": "rate_limited",
1063 "message": "too many attempts"
1064 }
1065 }
1066 }
1067 },
1068 "500": {
1069 "description": "服务端故障(`internal`、`database`、`cache`、`session`、`serialization`)",
1070 "content": {
1071 "application/json": {
1072 "schema": {
1073 "$ref": "#/components/schemas/Envelope_NoData"
1074 },
1075 "example": {
1076 "code": "internal",
1077 "message": "internal server error"
1078 }
1079 }
1080 }
1081 }
1082 }
1083 }
1084 },
1085 "/api/v1/oauth/logout": {
1086 "get": {
1087 "tags": [
1088 "oauth"
1089 ],
1090 "summary": "单点登出(RP-Initiated Logout)",
1091 "description": "撤销当前会话与其全客户端刷新令牌,并向 `backchannel_logout_uris` 发 `logout_token`;最后回跳已注册的 `post_logout_redirect_uri`。GET 带验证通过的 `id_token_hint`(签名有效且绑定当前会话)时免确认直接登出,否则返回确认页,确认后 POST 完成登出。",
1092 "operationId": "oauth_logout",
1093 "parameters": [
1094 {
1095 "name": "id_token_hint",
1096 "in": "query",
1097 "description": "之前签发的 id_token(校验签名且须绑定当前会话)",
1098 "required": false,
1099 "schema": {
1100 "type": "string"
1101 }
1102 },
1103 {
1104 "name": "post_logout_redirect_uri",
1105 "in": "query",
1106 "description": "必须在客户端注册过",
1107 "required": false,
1108 "schema": {
1109 "type": "string"
1110 }
1111 },
1112 {
1113 "name": "state",
1114 "in": "query",
1115 "description": "原样回传",
1116 "required": false,
1117 "schema": {
1118 "type": "string"
1119 }
1120 },
1121 {
1122 "name": "client_id",
1123 "in": "query",
1124 "description": "无 id_token_hint 时可用",
1125 "required": false,
1126 "schema": {
1127 "type": "string"
1128 }
1129 }
1130 ],
1131 "responses": {
1132 "200": {
1133 "description": "GET 未验证 hint 时返回确认页"
1134 },
1135 "302": {
1136 "description": "回到 `post_logout_redirect_uri`,否则回站点首页"
1137 }
1138 }
1139 }
1140 },
1141 "/api/v1/oauth/me": {
1142 "get": {
1143 "tags": [
1144 "auth"
1145 ],
1146 "summary": "当前登录用户",
1147 "description": "通过会话 Cookie 或 Bearer 访问令牌返回用户自述信息,含头像地址。Bearer 令牌需具备 profile、email、avatar 权限,并校验账号与会话状态。结果在服务端缓存 300 秒,每第 5 次访问回源刷新;登出与改密会主动失效该缓存。",
1148 "operationId": "auth_me",
1149 "responses": {
1150 "200": {
1151 "description": "当前登录用户",
1152 "content": {
1153 "application/json": {
1154 "schema": {
1155 "$ref": "#/components/schemas/Envelope_MeResponse"
1156 },
1157 "example": {
1158 "code": 200,
1159 "data": {
1160 "avatar_url": "https://cdn.example.com/avatar/ada.png",
1161 "created_at": "2025-11-20T02:15:00Z",
1162 "displayname": "Ada",
1163 "email": "ada@example.com",
1164 "email_verified_at": "2026-01-05T08:30:00Z",
1165 "is_admin": false,
1166 "user_id": 1024,
1167 "username": "ada",
1168 "version": 3
1169 },
1170 "message": "success"
1171 }
1172 }
1173 }
1174 },
1175 "401": {
1176 "description": "会话未绑定用户,或账号已被删除(`unauthenticated`)",
1177 "content": {
1178 "application/json": {
1179 "schema": {
1180 "$ref": "#/components/schemas/Envelope_NoData"
1181 },
1182 "example": {
1183 "code": "unauthenticated",
1184 "message": "authentication required"
1185 }
1186 }
1187 }
1188 },
1189 "403": {
1190 "description": "访问令牌权限不足(`scope_insufficient`)",
1191 "content": {
1192 "application/json": {
1193 "schema": {
1194 "$ref": "#/components/schemas/Envelope_NoData"
1195 }
1196 }
1197 }
1198 },
1199 "500": {
1200 "description": "服务端故障(`internal`、`database`、`cache`、`session`、`serialization`)",
1201 "content": {
1202 "application/json": {
1203 "schema": {
1204 "$ref": "#/components/schemas/Envelope_NoData"
1205 },
1206 "example": {
1207 "code": "internal",
1208 "message": "internal server error"
1209 }
1210 }
1211 }
1212 }
1213 }
1214 }
1215 },
1216 "/api/v1/oauth/password/reset": {
1217 "post": {
1218 "tags": [
1219 "auth"
1220 ],
1221 "summary": "申请密码重置",
1222 "description": "校验工作量证明(payload = 邮箱的 UTF-8 字节)后,作废该用户其余未消费的重置令牌,签发新令牌(长度 `reset_token_bytes`,有效期 `reset_token_ttl_secs`),库内只存 `sha256_hex(token)` 且 `hash_key_id = \"sha256-v1\"`,同时把摘要写入缓存键 `auth:code:reset:{email}`,发信限流键 `auth:rate:reset:{email}`。",
1223 "operationId": "auth_password_reset_request",
1224 "requestBody": {
1225 "content": {
1226 "application/json": {
1227 "schema": {
1228 "$ref": "#/components/schemas/PasswordResetRequest"
1229 }
1230 }
1231 },
1232 "required": true
1233 },
1234 "responses": {
1235 "200": {
1236 "description": "申请已受理;是否真的发信不对外披露",
1237 "content": {
1238 "application/json": {
1239 "schema": {
1240 "$ref": "#/components/schemas/Envelope_PasswordResetResponse"
1241 },
1242 "example": {
1243 "code": 200,
1244 "data": {
1245 "expires_in_secs": 1800
1246 },
1247 "message": "success"
1248 }
1249 }
1250 }
1251 },
1252 "400": {
1253 "description": "`invalid_email` / `expired` / `invalid_payload`(payload 与邮箱不一致)/ `invalid_proof`",
1254 "content": {
1255 "application/json": {
1256 "schema": {
1257 "$ref": "#/components/schemas/Envelope_NoData"
1258 },
1259 "example": {
1260 "code": "invalid_email",
1261 "message": "email is invalid"
1262 }
1263 }
1264 }
1265 },
1266 "429": {
1267 "description": "`rate_limited`(同一邮箱申请过于频繁)",
1268 "content": {
1269 "application/json": {
1270 "schema": {
1271 "$ref": "#/components/schemas/Envelope_NoData"
1272 },
1273 "example": {
1274 "code": "rate_limited",
1275 "message": "too many requests, retry after 60s"
1276 }
1277 }
1278 }
1279 },
1280 "500": {
1281 "description": "`email`(投递失败)/ `database` / `cache` / `session` / `serialization` / `internal`",
1282 "content": {
1283 "application/json": {
1284 "schema": {
1285 "$ref": "#/components/schemas/Envelope_NoData"
1286 },
1287 "example": {
1288 "code": "internal",
1289 "message": "internal server error"
1290 }
1291 }
1292 }
1293 }
1294 }
1295 }
1296 },
1297 "/api/v1/oauth/password/reset/confirm": {
1298 "post": {
1299 "tags": [
1300 "auth"
1301 ],
1302 "summary": "确认密码重置",
1303 "description": "校验工作量证明(payload = `token` 的 UTF-8 字节)与缓存键 `auth:code:reset:{email}`(尝试上限 `max_verify_attempts`,错误计入 `invalid_email_code`)后,条件消费数据库令牌行(`consumed_at` 单次占用),Argon2 重哈希新口令,更新 `password_hash`/`password_updated_at`/`password_must_change`/`security_version + 1`,作废其余令牌并记 `PasswordChange` 审计事件。",
1304 "operationId": "auth_password_reset",
1305 "requestBody": {
1306 "content": {
1307 "application/json": {
1308 "schema": {
1309 "$ref": "#/components/schemas/PasswordResetConfirmRequest"
1310 }
1311 }
1312 },
1313 "required": true
1314 },
1315 "responses": {
1316 "200": {
1317 "description": "口令已改写;旧口令与其余重置令牌失效",
1318 "content": {
1319 "application/json": {
1320 "schema": {
1321 "$ref": "#/components/schemas/Envelope_PasswordResetConfirmResponse"
1322 },
1323 "example": {
1324 "code": 200,
1325 "data": {
1326 "user_id": 1234567
1327 },
1328 "message": "success"
1329 }
1330 }
1331 }
1332 },
1333 "400": {
1334 "description": "`invalid_reset_token`(令牌不存在/已消费/已作废/已过期/账号不可用或凭据代数不匹配)/ `invalid_email_code`(缓存内摘要不匹配或已过期)/ `invalid_password` / `expired` / `invalid_payload` / `invalid_proof`",
1335 "content": {
1336 "application/json": {
1337 "schema": {
1338 "$ref": "#/components/schemas/Envelope_NoData"
1339 },
1340 "example": {
1341 "code": "invalid_reset_token",
1342 "message": "password reset token is invalid"
1343 }
1344 }
1345 }
1346 },
1347 "429": {
1348 "description": "`email_code_attempts_exceeded`(令牌尝试次数用尽)",
1349 "content": {
1350 "application/json": {
1351 "schema": {
1352 "$ref": "#/components/schemas/Envelope_NoData"
1353 },
1354 "example": {
1355 "code": "email_code_attempts_exceeded",
1356 "message": "too many invalid email verification attempts"
1357 }
1358 }
1359 }
1360 },
1361 "500": {
1362 "description": "`password_hash`(口令重哈希失败)/ `database` / `cache` / `session` / `serialization` / `internal`",
1363 "content": {
1364 "application/json": {
1365 "schema": {
1366 "$ref": "#/components/schemas/Envelope_NoData"
1367 },
1368 "example": {
1369 "code": "internal",
1370 "message": "internal server error"
1371 }
1372 }
1373 }
1374 }
1375 }
1376 }
1377 },
1378 "/api/v1/oauth/register": {
1379 "post": {
1380 "tags": [
1381 "auth"
1382 ],
1383 "summary": "提交注册",
1384 "description": "校验工作量证明(payload = 用户名的 UTF-8 字节)、用户名/展示名/出生日期/邮箱、邮箱验证码(键 `auth:code:register:{email}`,尝试上限 `max_verify_attempts`)后,Argon2 重哈希口令并创建用户与初始档案。成功后写会话(user_id、security_version、renew)并记审计事件。",
1385 "operationId": "auth_register",
1386 "requestBody": {
1387 "content": {
1388 "application/json": {
1389 "schema": {
1390 "$ref": "#/components/schemas/UserRegisterRequest"
1391 }
1392 }
1393 },
1394 "required": true
1395 },
1396 "responses": {
1397 "200": {
1398 "description": "注册成功且已登录",
1399 "content": {
1400 "application/json": {
1401 "schema": {
1402 "$ref": "#/components/schemas/Envelope_UserRegisterResponse"
1403 },
1404 "example": {
1405 "code": 200,
1406 "data": {
1407 "displayname": "Alice",
1408 "user_id": 1234567,
1409 "username": "alice"
1410 },
1411 "message": "success"
1412 }
1413 }
1414 }
1415 },
1416 "400": {
1417 "description": "`invalid_username` / `invalid_displayname` / `invalid_email` / `invalid_birth_date` / `invalid_password` / `invalid_email_code`(验证码错误或已过期)/ `expired` / `invalid_payload` / `invalid_proof`",
1418 "content": {
1419 "application/json": {
1420 "schema": {
1421 "$ref": "#/components/schemas/Envelope_NoData"
1422 },
1423 "example": {
1424 "code": "invalid_email_code",
1425 "message": "email verification code is invalid"
1426 }
1427 }
1428 }
1429 },
1430 "403": {
1431 "description": "`under_minimum_age`(未满最小年龄)",
1432 "content": {
1433 "application/json": {
1434 "schema": {
1435 "$ref": "#/components/schemas/Envelope_NoData"
1436 },
1437 "example": {
1438 "code": "under_minimum_age",
1439 "message": "you must be at least 13 years old"
1440 }
1441 }
1442 }
1443 },
1444 "409": {
1445 "description": "`username_taken` / `email_taken`",
1446 "content": {
1447 "application/json": {
1448 "schema": {
1449 "$ref": "#/components/schemas/Envelope_NoData"
1450 },
1451 "example": {
1452 "code": "username_taken",
1453 "message": "username is already taken"
1454 }
1455 }
1456 }
1457 },
1458 "429": {
1459 "description": "`email_code_attempts_exceeded`(验证码尝试次数用尽)",
1460 "content": {
1461 "application/json": {
1462 "schema": {
1463 "$ref": "#/components/schemas/Envelope_NoData"
1464 },
1465 "example": {
1466 "code": "email_code_attempts_exceeded",
1467 "message": "too many invalid email verification attempts"
1468 }
1469 }
1470 }
1471 },
1472 "500": {
1473 "description": "`password_hash`(口令重哈希失败)/ `database` / `cache` / `session` / `serialization` / `internal`",
1474 "content": {
1475 "application/json": {
1476 "schema": {
1477 "$ref": "#/components/schemas/Envelope_NoData"
1478 },
1479 "example": {
1480 "code": "internal",
1481 "message": "internal server error"
1482 }
1483 }
1484 }
1485 }
1486 }
1487 }
1488 },
1489 "/api/v1/oauth/register/code": {
1490 "post": {
1491 "tags": [
1492 "auth"
1493 ],
1494 "summary": "发送注册验证码",
1495 "description": "校验工作量证明(payload = 邮箱的 UTF-8 字节)与邮箱格式后,把 6 位验证码写入缓存并寄出。缓存键 `auth:code:register:{email}`,有效期由 `verify_code_ttl_secs` 决定,发码限流键 `auth:rate:register:{email}`。",
1496 "operationId": "auth_register_code",
1497 "requestBody": {
1498 "content": {
1499 "application/json": {
1500 "schema": {
1501 "$ref": "#/components/schemas/RegisterCodeRequest"
1502 }
1503 }
1504 },
1505 "required": true
1506 },
1507 "responses": {
1508 "200": {
1509 "description": "验证码已写入缓存并寄出",
1510 "content": {
1511 "application/json": {
1512 "schema": {
1513 "$ref": "#/components/schemas/Envelope_RegisterCodeResponse"
1514 },
1515 "example": {
1516 "code": 200,
1517 "data": {
1518 "expires_in_secs": 600
1519 },
1520 "message": "success"
1521 }
1522 }
1523 }
1524 },
1525 "400": {
1526 "description": "`invalid_email`(邮箱格式)/ `expired`(挑战过期)/ `invalid_payload`(payload 与邮箱不一致)/ `invalid_proof`(未满足难度)",
1527 "content": {
1528 "application/json": {
1529 "schema": {
1530 "$ref": "#/components/schemas/Envelope_NoData"
1531 },
1532 "example": {
1533 "code": "invalid_email",
1534 "message": "email is invalid"
1535 }
1536 }
1537 }
1538 },
1539 "409": {
1540 "description": "`email_taken`(邮箱已注册)",
1541 "content": {
1542 "application/json": {
1543 "schema": {
1544 "$ref": "#/components/schemas/Envelope_NoData"
1545 },
1546 "example": {
1547 "code": "email_taken",
1548 "message": "email is already taken"
1549 }
1550 }
1551 }
1552 },
1553 "429": {
1554 "description": "`rate_limited`(同一邮箱发码过于频繁)",
1555 "content": {
1556 "application/json": {
1557 "schema": {
1558 "$ref": "#/components/schemas/Envelope_NoData"
1559 },
1560 "example": {
1561 "code": "rate_limited",
1562 "message": "too many requests, retry after 60s"
1563 }
1564 }
1565 }
1566 },
1567 "500": {
1568 "description": "`email`(投递失败)/ `cache` / `session` / `serialization` / `internal`",
1569 "content": {
1570 "application/json": {
1571 "schema": {
1572 "$ref": "#/components/schemas/Envelope_NoData"
1573 },
1574 "example": {
1575 "code": "internal",
1576 "message": "internal server error"
1577 }
1578 }
1579 }
1580 }
1581 }
1582 }
1583 },
1584 "/api/v1/oauth/revoke": {
1585 "post": {
1586 "tags": [
1587 "oauth"
1588 ],
1589 "summary": "令牌撤销(RFC 7009)",
1590 "operationId": "oauth_revoke",
1591 "requestBody": {
1592 "content": {
1593 "application/json": {
1594 "schema": {
1595 "$ref": "#/components/schemas/RevokeRequest"
1596 }
1597 }
1598 },
1599 "required": true
1600 },
1601 "responses": {
1602 "200": {
1603 "description": "已处理(无论令牌是否存在)"
1604 },
1605 "401": {
1606 "description": "`invalid_client`"
1607 }
1608 }
1609 }
1610 },
1611 "/api/v1/oauth/session/logout": {
1612 "post": {
1613 "tags": [
1614 "auth"
1615 ],
1616 "summary": "登出",
1617 "description": "销毁当前会话,并清掉该用户的 `/api/v1/oauth/me` 缓存。匿名调用同样成功,此时 `user_id` 为 `null`。",
1618 "operationId": "auth_logout",
1619 "responses": {
1620 "200": {
1621 "description": "会话已销毁",
1622 "content": {
1623 "application/json": {
1624 "schema": {
1625 "$ref": "#/components/schemas/Envelope_LogoutResponse"
1626 },
1627 "example": {
1628 "code": 200,
1629 "data": {
1630 "user_id": 1024
1631 },
1632 "message": "success"
1633 }
1634 }
1635 }
1636 },
1637 "500": {
1638 "description": "服务端故障(`internal`、`database`、`cache`、`session`、`serialization`)",
1639 "content": {
1640 "application/json": {
1641 "schema": {
1642 "$ref": "#/components/schemas/Envelope_NoData"
1643 },
1644 "example": {
1645 "code": "internal",
1646 "message": "internal server error"
1647 }
1648 }
1649 }
1650 }
1651 }
1652 }
1653 },
1654 "/api/v1/oauth/session/token": {
1655 "post": {
1656 "tags": [
1657 "auth"
1658 ],
1659 "summary": "用会话换取平台访问令牌",
1660 "description": "认证中心用 Ed25519 私钥签发 JWT(`alg=EdDSA`),`aud` 固定为 `gitdata.ai`,`azp`(`app_id`)可选。任何服务只需 `/.well-known/jwks.json` 的公钥即可本地验签。`ttl_seconds` 取值 1 ~ 3600,不填或填 0 用默认 900。",
1661 "operationId": "auth_token_issue",
1662 "requestBody": {
1663 "content": {
1664 "application/json": {
1665 "schema": {
1666 "$ref": "#/components/schemas/TokenRequest"
1667 }
1668 }
1669 },
1670 "required": true
1671 },
1672 "responses": {
1673 "200": {
1674 "description": "令牌已签发",
1675 "content": {
1676 "application/json": {
1677 "schema": {
1678 "$ref": "#/components/schemas/Envelope_TokenResponse"
1679 }
1680 }
1681 }
1682 },
1683 "400": {
1684 "description": "ttl 超出上限(`invalid_expiry`)",
1685 "content": {
1686 "application/json": {
1687 "schema": {
1688 "$ref": "#/components/schemas/Envelope_NoData"
1689 }
1690 }
1691 }
1692 },
1693 "401": {
1694 "description": "会话未绑定账号(`unauthenticated`)",
1695 "content": {
1696 "application/json": {
1697 "schema": {
1698 "$ref": "#/components/schemas/Envelope_NoData"
1699 }
1700 }
1701 }
1702 },
1703 "404": {
1704 "description": "app_id 不在清单里(`app_not_found`)",
1705 "content": {
1706 "application/json": {
1707 "schema": {
1708 "$ref": "#/components/schemas/Envelope_NoData"
1709 }
1710 }
1711 }
1712 }
1713 }
1714 }
1715 },
1716 "/api/v1/oauth/ssh-keys": {
1717 "get": {
1718 "tags": [
1719 "auth"
1720 ],
1721 "summary": "列示 SSH 公钥",
1722 "description": "按创建时间倒序返回当前账号的公钥,含已吊销与已过期的记录。分页参数由 `page` / `page_size` 控制。查询串不是合法分页参数时由 actix-web 直接返回 400。",
1723 "operationId": "auth_ssh_key_list",
1724 "parameters": [
1725 {
1726 "name": "page",
1727 "in": "query",
1728 "description": "页码,从 1 开始;默认 1",
1729 "required": false,
1730 "schema": {
1731 "type": "integer",
1732 "format": "int64",
1733 "minimum": 0
1734 }
1735 },
1736 {
1737 "name": "page_size",
1738 "in": "query",
1739 "description": "每页条数,默认 20,最多 100",
1740 "required": false,
1741 "schema": {
1742 "type": "integer",
1743 "format": "int64",
1744 "minimum": 0
1745 }
1746 }
1747 ],
1748 "responses": {
1749 "200": {
1750 "description": "公钥列表",
1751 "content": {
1752 "application/json": {
1753 "schema": {
1754 "$ref": "#/components/schemas/Envelope_Vec_SshKeySummary"
1755 },
1756 "example": {
1757 "code": 200,
1758 "data": [
1759 {
1760 "algorithm": "ed25519",
1761 "created_at": "2026-01-02T03:04:05Z",
1762 "description": "笔记本",
1763 "expires_at": null,
1764 "fingerprint": "SHA256:9pQ2mLk1v3sT6yZx8cV0bN4mR7wQ2eH5jK9aD1sF3gU",
1765 "id": "018f2b1c-4d5e-7f80-9a1b-2c3d4e5f6072",
1766 "last_used_at": null,
1767 "name": "thinkpad",
1768 "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL9kQ2mXv7Tb1sYz8cV0bN4mR7wQ2eH5jK9aD1sF3gU thinkpad",
1769 "revoked_at": null
1770 }
1771 ],
1772 "message": "success"
1773 }
1774 }
1775 }
1776 },
1777 "401": {
1778 "description": "会话未绑定账号(`unauthenticated`)",
1779 "content": {
1780 "application/json": {
1781 "schema": {
1782 "$ref": "#/components/schemas/Envelope_NoData"
1783 },
1784 "example": {
1785 "code": "unauthenticated",
1786 "message": "authentication required"
1787 }
1788 }
1789 }
1790 },
1791 "500": {
1792 "description": "服务端故障(`internal`、`database`、`session`、`serialization`)",
1793 "content": {
1794 "application/json": {
1795 "schema": {
1796 "$ref": "#/components/schemas/Envelope_NoData"
1797 },
1798 "example": {
1799 "code": "internal",
1800 "message": "internal server error"
1801 }
1802 }
1803 }
1804 }
1805 }
1806 },
1807 "post": {
1808 "tags": [
1809 "auth"
1810 ],
1811 "summary": "添加 SSH 公钥",
1812 "description": "名称 1~64 字符、备注最长 255 字符,公钥 32~8192 字节且必须是受支持的单行 OpenSSH 公钥,`expires_at` 必须晚于当前时间。重复公钥(指纹唯一)返回 `ssh_key_taken`。添加成功记 `PublicKeyAdded` 审计事件。",
1813 "operationId": "auth_ssh_key_add",
1814 "requestBody": {
1815 "content": {
1816 "application/json": {
1817 "schema": {
1818 "$ref": "#/components/schemas/SshKeyAddRequest"
1819 }
1820 }
1821 },
1822 "required": true
1823 },
1824 "responses": {
1825 "200": {
1826 "description": "公钥已登记",
1827 "content": {
1828 "application/json": {
1829 "schema": {
1830 "$ref": "#/components/schemas/Envelope_SshKeySummary"
1831 },
1832 "example": {
1833 "code": 200,
1834 "data": {
1835 "algorithm": "ed25519",
1836 "created_at": "2026-01-02T03:04:05Z",
1837 "description": "笔记本",
1838 "expires_at": null,
1839 "fingerprint": "SHA256:9pQ2mLk1v3sT6yZx8cV0bN4mR7wQ2eH5jK9aD1sF3gU",
1840 "id": "018f2b1c-4d5e-7f80-9a1b-2c3d4e5f6072",
1841 "last_used_at": null,
1842 "name": "thinkpad",
1843 "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL9kQ2mXv7Tb1sYz8cV0bN4mR7wQ2eH5jK9aD1sF3gU thinkpad",
1844 "revoked_at": null
1845 },
1846 "message": "success"
1847 }
1848 }
1849 }
1850 },
1851 "400": {
1852 "description": "名称、备注、公钥或过期时间不合法(`ssh_key_name_invalid`、`malformed_request`、`ssh_key_invalid`、`invalid_expiry`)",
1853 "content": {
1854 "application/json": {
1855 "schema": {
1856 "$ref": "#/components/schemas/Envelope_NoData"
1857 },
1858 "example": {
1859 "code": "ssh_key_invalid",
1860 "message": "public key is invalid"
1861 }
1862 }
1863 }
1864 },
1865 "401": {
1866 "description": "会话未绑定账号,或账号已删除 / 不允许使用(`unauthenticated`)",
1867 "content": {
1868 "application/json": {
1869 "schema": {
1870 "$ref": "#/components/schemas/Envelope_NoData"
1871 },
1872 "example": {
1873 "code": "unauthenticated",
1874 "message": "authentication required"
1875 }
1876 }
1877 }
1878 },
1879 "409": {
1880 "description": "同一把公钥已经登记过(`ssh_key_taken`)",
1881 "content": {
1882 "application/json": {
1883 "schema": {
1884 "$ref": "#/components/schemas/Envelope_NoData"
1885 },
1886 "example": {
1887 "code": "ssh_key_taken",
1888 "message": "public key is already registered"
1889 }
1890 }
1891 }
1892 },
1893 "500": {
1894 "description": "服务端故障(`internal`、`database`、`session`、`serialization`)",
1895 "content": {
1896 "application/json": {
1897 "schema": {
1898 "$ref": "#/components/schemas/Envelope_NoData"
1899 },
1900 "example": {
1901 "code": "internal",
1902 "message": "internal server error"
1903 }
1904 }
1905 }
1906 }
1907 }
1908 }
1909 },
1910 "/api/v1/oauth/ssh-keys/{key_id}": {
1911 "delete": {
1912 "tags": [
1913 "auth"
1914 ],
1915 "summary": "吊销 SSH 公钥",
1916 "description": "把公钥标记为已吊销,记录保留不硬删;重复调用幂等。公钥不存在或不属于当前账号返回 `ssh_key_not_found`。吊销成功记 `PublicKeyRemoved` 审计事件,并递增 `security_version` 让该账号既有的访问令牌一并失效。`key_id` 不是合法 UUID 时由 actix-web 的路径提取器直接返回 400。",
1917 "operationId": "auth_ssh_key_delete",
1918 "parameters": [
1919 {
1920 "name": "key_id",
1921 "in": "path",
1922 "description": "公钥编号",
1923 "required": true,
1924 "schema": {
1925 "type": "string",
1926 "format": "uuid"
1927 }
1928 }
1929 ],
1930 "responses": {
1931 "200": {
1932 "description": "已吊销;重复调用同样返回成功",
1933 "content": {
1934 "application/json": {
1935 "schema": {
1936 "$ref": "#/components/schemas/Envelope_NoData"
1937 },
1938 "example": {
1939 "code": 200,
1940 "data": null,
1941 "message": "success"
1942 }
1943 }
1944 }
1945 },
1946 "401": {
1947 "description": "会话未绑定账号(`unauthenticated`)",
1948 "content": {
1949 "application/json": {
1950 "schema": {
1951 "$ref": "#/components/schemas/Envelope_NoData"
1952 },
1953 "example": {
1954 "code": "unauthenticated",
1955 "message": "authentication required"
1956 }
1957 }
1958 }
1959 },
1960 "404": {
1961 "description": "公钥不存在或不属于当前账号(`ssh_key_not_found`)",
1962 "content": {
1963 "application/json": {
1964 "schema": {
1965 "$ref": "#/components/schemas/Envelope_NoData"
1966 },
1967 "example": {
1968 "code": "ssh_key_not_found",
1969 "message": "ssh key does not exist"
1970 }
1971 }
1972 }
1973 },
1974 "500": {
1975 "description": "服务端故障(`internal`、`database`、`session`、`serialization`)",
1976 "content": {
1977 "application/json": {
1978 "schema": {
1979 "$ref": "#/components/schemas/Envelope_NoData"
1980 },
1981 "example": {
1982 "code": "internal",
1983 "message": "internal server error"
1984 }
1985 }
1986 }
1987 }
1988 }
1989 }
1990 },
1991 "/api/v1/oauth/token": {
1992 "post": {
1993 "tags": [
1994 "oauth"
1995 ],
1996 "summary": "令牌端点",
1997 "description": "支持 `authorization_code`(PKCE)、`refresh_token`(轮换 + 重放检测)、`client_credentials`(机密客户端)。带 `openid` scope 时响应额外包含 `id_token`。",
1998 "operationId": "oauth_token",
1999 "requestBody": {
2000 "content": {
2001 "application/json": {
2002 "schema": {
2003 "$ref": "#/components/schemas/TokenRequest"
2004 }
2005 }
2006 },
2007 "required": true
2008 },
2009 "responses": {
2010 "200": {
2011 "description": "令牌响应",
2012 "content": {
2013 "application/json": {
2014 "schema": {}
2015 }
2016 }
2017 },
2018 "400": {
2019 "description": "`invalid_grant` / `invalid_scope` / `unsupported_grant_type`"
2020 },
2021 "401": {
2022 "description": "`invalid_client`"
2023 }
2024 }
2025 }
2026 },
2027 "/api/v1/oauth/userinfo": {
2028 "get": {
2029 "tags": [
2030 "oauth"
2031 ],
2032 "summary": "OIDC userinfo",
2033 "description": "需要 `openid` scope;`profile` 返回 `preferred_username` / `name`,`email` 返回 `email` / `email_verified`。令牌只接受 `Authorization: Bearer` 头(RFC 6750 §2.3),查询串携带一律 401。",
2034 "operationId": "oauth_userinfo",
2035 "responses": {
2036 "200": {
2037 "description": "标准 claims",
2038 "content": {
2039 "application/json": {
2040 "schema": {}
2041 }
2042 }
2043 },
2044 "401": {
2045 "description": "`invalid_token`"
2046 },
2047 "403": {
2048 "description": "`insufficient_scope`"
2049 }
2050 }
2051 }
2052 },
2053 "/api/v1/organizations": {
2054 "get": {
2055 "tags": [
2056 "auth"
2057 ],
2058 "summary": "列示我加入的组织",
2059 "description": "默认只返回 ACTIVE 组织;`include_archived=true` 时一并返回已归档组织。",
2060 "operationId": "auth_organization_list",
2061 "responses": {
2062 "200": {
2063 "description": "组织列表",
2064 "content": {
2065 "application/json": {
2066 "schema": {
2067 "$ref": "#/components/schemas/Envelope_Vec_OrganizationSummary"
2068 }
2069 }
2070 }
2071 },
2072 "401": {
2073 "description": "会话未绑定账号",
2074 "content": {
2075 "application/json": {
2076 "schema": {
2077 "$ref": "#/components/schemas/Envelope_NoData"
2078 }
2079 }
2080 }
2081 }
2082 }
2083 },
2084 "post": {
2085 "tags": [
2086 "auth"
2087 ],
2088 "summary": "新建组织",
2089 "description": "创建者自动成为 OWNER 并写入 owner/admin/member 三个内置角色。slug 全局唯一且只允许小写字母、数字与连字符。",
2090 "operationId": "auth_organization_create",
2091 "requestBody": {
2092 "content": {
2093 "application/json": {
2094 "schema": {
2095 "$ref": "#/components/schemas/CreateOrganizationBody"
2096 }
2097 }
2098 },
2099 "required": true
2100 },
2101 "responses": {
2102 "200": {
2103 "description": "组织已创建",
2104 "content": {
2105 "application/json": {
2106 "schema": {
2107 "$ref": "#/components/schemas/Envelope_OrganizationSummary"
2108 }
2109 }
2110 }
2111 },
2112 "401": {
2113 "description": "会话未绑定账号",
2114 "content": {
2115 "application/json": {
2116 "schema": {
2117 "$ref": "#/components/schemas/Envelope_NoData"
2118 }
2119 }
2120 }
2121 },
2122 "409": {
2123 "description": "slug 已被占用",
2124 "content": {
2125 "application/json": {
2126 "schema": {
2127 "$ref": "#/components/schemas/Envelope_NoData"
2128 }
2129 }
2130 }
2131 }
2132 }
2133 }
2134 },
2135 "/api/v1/organizations/invites/redeem": {
2136 "post": {
2137 "tags": [
2138 "auth"
2139 ],
2140 "summary": "兑换邀请",
2141 "description": "uses 递增与兑换记录写入同事务。定向邀请只能由目标用户兑换。",
2142 "operationId": "auth_organization_invite_redeem",
2143 "requestBody": {
2144 "content": {
2145 "application/json": {
2146 "schema": {
2147 "$ref": "#/components/schemas/RedeemInviteBody"
2148 }
2149 }
2150 },
2151 "required": true
2152 },
2153 "responses": {
2154 "200": {
2155 "description": "已加入",
2156 "content": {
2157 "application/json": {
2158 "schema": {
2159 "$ref": "#/components/schemas/Envelope_RedeemInviteResponse"
2160 }
2161 }
2162 }
2163 },
2164 "403": {
2165 "description": "定向邀请目标不匹配",
2166 "content": {
2167 "application/json": {
2168 "schema": {
2169 "$ref": "#/components/schemas/Envelope_NoData"
2170 }
2171 }
2172 }
2173 },
2174 "409": {
2175 "description": "邀请已用尽",
2176 "content": {
2177 "application/json": {
2178 "schema": {
2179 "$ref": "#/components/schemas/Envelope_NoData"
2180 }
2181 }
2182 }
2183 }
2184 }
2185 }
2186 },
2187 "/api/v1/organizations/{organization_id}": {
2188 "get": {
2189 "tags": [
2190 "auth"
2191 ],
2192 "summary": "组织详情",
2193 "operationId": "auth_organization_get",
2194 "parameters": [
2195 {
2196 "name": "organization_id",
2197 "in": "path",
2198 "description": "组织 id",
2199 "required": true,
2200 "schema": {
2201 "type": "string",
2202 "format": "uuid"
2203 }
2204 }
2205 ],
2206 "responses": {
2207 "200": {
2208 "description": "组织详情",
2209 "content": {
2210 "application/json": {
2211 "schema": {
2212 "$ref": "#/components/schemas/Envelope_OrganizationSummary"
2213 }
2214 }
2215 }
2216 },
2217 "404": {
2218 "description": "组织不存在",
2219 "content": {
2220 "application/json": {
2221 "schema": {
2222 "$ref": "#/components/schemas/Envelope_NoData"
2223 }
2224 }
2225 }
2226 }
2227 }
2228 },
2229 "delete": {
2230 "tags": [
2231 "auth"
2232 ],
2233 "summary": "删除组织",
2234 "description": "仍存在活跃成员且未指定 `force=true` 时返回 409。删除会清理成员、角色与邀请。",
2235 "operationId": "auth_organization_delete",
2236 "parameters": [
2237 {
2238 "name": "organization_id",
2239 "in": "path",
2240 "description": "组织 id",
2241 "required": true,
2242 "schema": {
2243 "type": "string",
2244 "format": "uuid"
2245 }
2246 },
2247 {
2248 "name": "force",
2249 "in": "query",
2250 "description": "是否连成员一并清理",
2251 "required": false,
2252 "schema": {
2253 "type": "boolean"
2254 }
2255 },
2256 {
2257 "name": "reason",
2258 "in": "query",
2259 "description": "删除原因,最长 512 字符",
2260 "required": false,
2261 "schema": {
2262 "type": "string"
2263 }
2264 }
2265 ],
2266 "responses": {
2267 "200": {
2268 "description": "已删除",
2269 "content": {
2270 "application/json": {
2271 "schema": {
2272 "$ref": "#/components/schemas/Envelope_bool"
2273 }
2274 }
2275 }
2276 },
2277 "403": {
2278 "description": "权限不足",
2279 "content": {
2280 "application/json": {
2281 "schema": {
2282 "$ref": "#/components/schemas/Envelope_NoData"
2283 }
2284 }
2285 }
2286 },
2287 "409": {
2288 "description": "仍有活跃成员",
2289 "content": {
2290 "application/json": {
2291 "schema": {
2292 "$ref": "#/components/schemas/Envelope_NoData"
2293 }
2294 }
2295 }
2296 }
2297 }
2298 },
2299 "patch": {
2300 "tags": [
2301 "auth"
2302 ],
2303 "summary": "修改组织",
2304 "description": "需要 `org.update`。`expected_version` 与当前 version 不一致时返回 409。",
2305 "operationId": "auth_organization_update",
2306 "parameters": [
2307 {
2308 "name": "organization_id",
2309 "in": "path",
2310 "description": "组织 id",
2311 "required": true,
2312 "schema": {
2313 "type": "string",
2314 "format": "uuid"
2315 }
2316 }
2317 ],
2318 "requestBody": {
2319 "content": {
2320 "application/json": {
2321 "schema": {
2322 "$ref": "#/components/schemas/UpdateOrganizationBody"
2323 }
2324 }
2325 },
2326 "required": true
2327 },
2328 "responses": {
2329 "200": {
2330 "description": "已更新",
2331 "content": {
2332 "application/json": {
2333 "schema": {
2334 "$ref": "#/components/schemas/Envelope_OrganizationSummary"
2335 }
2336 }
2337 }
2338 },
2339 "403": {
2340 "description": "权限不足",
2341 "content": {
2342 "application/json": {
2343 "schema": {
2344 "$ref": "#/components/schemas/Envelope_NoData"
2345 }
2346 }
2347 }
2348 },
2349 "409": {
2350 "description": "版本冲突",
2351 "content": {
2352 "application/json": {
2353 "schema": {
2354 "$ref": "#/components/schemas/Envelope_NoData"
2355 }
2356 }
2357 }
2358 }
2359 }
2360 }
2361 },
2362 "/api/v1/organizations/{organization_id}/archive": {
2363 "post": {
2364 "tags": [
2365 "auth"
2366 ],
2367 "summary": "归档组织",
2368 "description": "需要 `org.archive`。归档后全部权限判定返回 false,可恢复。",
2369 "operationId": "auth_organization_archive",
2370 "parameters": [
2371 {
2372 "name": "organization_id",
2373 "in": "path",
2374 "description": "组织 id",
2375 "required": true,
2376 "schema": {
2377 "type": "string",
2378 "format": "uuid"
2379 }
2380 }
2381 ],
2382 "requestBody": {
2383 "content": {
2384 "application/json": {
2385 "schema": {
2386 "$ref": "#/components/schemas/ReasonBody"
2387 }
2388 }
2389 },
2390 "required": true
2391 },
2392 "responses": {
2393 "200": {
2394 "description": "已归档",
2395 "content": {
2396 "application/json": {
2397 "schema": {
2398 "$ref": "#/components/schemas/Envelope_OrganizationSummary"
2399 }
2400 }
2401 }
2402 },
2403 "403": {
2404 "description": "权限不足",
2405 "content": {
2406 "application/json": {
2407 "schema": {
2408 "$ref": "#/components/schemas/Envelope_NoData"
2409 }
2410 }
2411 }
2412 }
2413 }
2414 }
2415 },
2416 "/api/v1/organizations/{organization_id}/bans": {
2417 "get": {
2418 "tags": [
2419 "auth"
2420 ],
2421 "summary": "封禁列表",
2422 "description": "需要 `member.ban`。按封禁时间倒序。",
2423 "operationId": "auth_organization_ban_list",
2424 "parameters": [
2425 {
2426 "name": "organization_id",
2427 "in": "path",
2428 "description": "组织 id",
2429 "required": true,
2430 "schema": {
2431 "type": "string",
2432 "format": "uuid"
2433 }
2434 },
2435 {
2436 "name": "page",
2437 "in": "query",
2438 "description": "页码,从 1 开始",
2439 "required": false,
2440 "schema": {
2441 "type": "integer",
2442 "format": "int64",
2443 "minimum": 0
2444 }
2445 },
2446 {
2447 "name": "page_size",
2448 "in": "query",
2449 "description": "每页条数",
2450 "required": false,
2451 "schema": {
2452 "type": "integer",
2453 "format": "int64",
2454 "minimum": 0
2455 }
2456 }
2457 ],
2458 "responses": {
2459 "200": {
2460 "description": "封禁列表",
2461 "content": {
2462 "application/json": {
2463 "schema": {
2464 "$ref": "#/components/schemas/Envelope_Vec_MemberSummary"
2465 }
2466 }
2467 }
2468 },
2469 "403": {
2470 "description": "权限不足",
2471 "content": {
2472 "application/json": {
2473 "schema": {
2474 "$ref": "#/components/schemas/Envelope_NoData"
2475 }
2476 }
2477 }
2478 }
2479 }
2480 }
2481 },
2482 "/api/v1/organizations/{organization_id}/bans/{user_id}": {
2483 "get": {
2484 "tags": [
2485 "auth"
2486 ],
2487 "summary": "封禁详情",
2488 "description": "需要 `member.ban`。未封禁返回 404。",
2489 "operationId": "auth_organization_ban_get",
2490 "parameters": [
2491 {
2492 "name": "organization_id",
2493 "in": "path",
2494 "description": "组织 id",
2495 "required": true,
2496 "schema": {
2497 "type": "string",
2498 "format": "uuid"
2499 }
2500 },
2501 {
2502 "name": "user_id",
2503 "in": "path",
2504 "description": "被封禁用户 id",
2505 "required": true,
2506 "schema": {
2507 "type": "integer",
2508 "format": "int64"
2509 }
2510 }
2511 ],
2512 "responses": {
2513 "200": {
2514 "description": "封禁详情",
2515 "content": {
2516 "application/json": {
2517 "schema": {
2518 "$ref": "#/components/schemas/Envelope_MemberSummary"
2519 }
2520 }
2521 }
2522 },
2523 "404": {
2524 "description": "未封禁或成员不存在",
2525 "content": {
2526 "application/json": {
2527 "schema": {
2528 "$ref": "#/components/schemas/Envelope_NoData"
2529 }
2530 }
2531 }
2532 }
2533 }
2534 },
2535 "delete": {
2536 "tags": [
2537 "auth"
2538 ],
2539 "summary": "按用户解封",
2540 "description": "需要 `member.ban`。等价于按会籍 id 解封,便于按用户维度操作。",
2541 "operationId": "auth_organization_ban_delete",
2542 "parameters": [
2543 {
2544 "name": "organization_id",
2545 "in": "path",
2546 "description": "组织 id",
2547 "required": true,
2548 "schema": {
2549 "type": "string",
2550 "format": "uuid"
2551 }
2552 },
2553 {
2554 "name": "user_id",
2555 "in": "path",
2556 "description": "被解封用户 id",
2557 "required": true,
2558 "schema": {
2559 "type": "integer",
2560 "format": "int64"
2561 }
2562 }
2563 ],
2564 "responses": {
2565 "200": {
2566 "description": "已解封",
2567 "content": {
2568 "application/json": {
2569 "schema": {
2570 "$ref": "#/components/schemas/Envelope_MemberSummary"
2571 }
2572 }
2573 }
2574 },
2575 "404": {
2576 "description": "成员不存在",
2577 "content": {
2578 "application/json": {
2579 "schema": {
2580 "$ref": "#/components/schemas/Envelope_NoData"
2581 }
2582 }
2583 }
2584 }
2585 }
2586 },
2587 "patch": {
2588 "tags": [
2589 "auth"
2590 ],
2591 "summary": "更新封禁",
2592 "description": "需要 `member.ban`。改原因、到期时间与四个放行位;未封禁返回 404。",
2593 "operationId": "auth_organization_ban_update",
2594 "parameters": [
2595 {
2596 "name": "organization_id",
2597 "in": "path",
2598 "description": "组织 id",
2599 "required": true,
2600 "schema": {
2601 "type": "string",
2602 "format": "uuid"
2603 }
2604 },
2605 {
2606 "name": "user_id",
2607 "in": "path",
2608 "description": "被封禁用户 id",
2609 "required": true,
2610 "schema": {
2611 "type": "integer",
2612 "format": "int64"
2613 }
2614 }
2615 ],
2616 "requestBody": {
2617 "content": {
2618 "application/json": {
2619 "schema": {
2620 "$ref": "#/components/schemas/UpdateBanBody"
2621 }
2622 }
2623 },
2624 "required": true
2625 },
2626 "responses": {
2627 "200": {
2628 "description": "已更新",
2629 "content": {
2630 "application/json": {
2631 "schema": {
2632 "$ref": "#/components/schemas/Envelope_MemberSummary"
2633 }
2634 }
2635 }
2636 },
2637 "404": {
2638 "description": "未封禁或成员不存在",
2639 "content": {
2640 "application/json": {
2641 "schema": {
2642 "$ref": "#/components/schemas/Envelope_NoData"
2643 }
2644 }
2645 }
2646 }
2647 }
2648 }
2649 },
2650 "/api/v1/organizations/{organization_id}/invites": {
2651 "get": {
2652 "tags": [
2653 "auth"
2654 ],
2655 "summary": "列示邀请",
2656 "description": "需要 `invite.view`。不含 token 明文。",
2657 "operationId": "auth_organization_invite_list",
2658 "parameters": [
2659 {
2660 "name": "organization_id",
2661 "in": "path",
2662 "description": "组织 id",
2663 "required": true,
2664 "schema": {
2665 "type": "string",
2666 "format": "uuid"
2667 }
2668 },
2669 {
2670 "name": "include_revoked",
2671 "in": "query",
2672 "description": "包含已撤销邀请",
2673 "required": false,
2674 "schema": {
2675 "type": "boolean"
2676 }
2677 }
2678 ],
2679 "responses": {
2680 "200": {
2681 "description": "邀请列表",
2682 "content": {
2683 "application/json": {
2684 "schema": {
2685 "$ref": "#/components/schemas/Envelope_Vec_InviteSummary"
2686 }
2687 }
2688 }
2689 },
2690 "403": {
2691 "description": "权限不足",
2692 "content": {
2693 "application/json": {
2694 "schema": {
2695 "$ref": "#/components/schemas/Envelope_NoData"
2696 }
2697 }
2698 }
2699 }
2700 }
2701 },
2702 "post": {
2703 "tags": [
2704 "auth"
2705 ],
2706 "summary": "创建邀请",
2707 "description": "需要 `invite.create`。明文 token 只在创建响应里返回一次,之后只存 sha256。",
2708 "operationId": "auth_organization_invite_create",
2709 "parameters": [
2710 {
2711 "name": "organization_id",
2712 "in": "path",
2713 "description": "组织 id",
2714 "required": true,
2715 "schema": {
2716 "type": "string",
2717 "format": "uuid"
2718 }
2719 }
2720 ],
2721 "requestBody": {
2722 "content": {
2723 "application/json": {
2724 "schema": {
2725 "$ref": "#/components/schemas/CreateInviteBody"
2726 }
2727 }
2728 },
2729 "required": true
2730 },
2731 "responses": {
2732 "200": {
2733 "description": "邀请已创建",
2734 "content": {
2735 "application/json": {
2736 "schema": {
2737 "$ref": "#/components/schemas/Envelope_CreateInviteResponse"
2738 }
2739 }
2740 }
2741 },
2742 "403": {
2743 "description": "权限不足",
2744 "content": {
2745 "application/json": {
2746 "schema": {
2747 "$ref": "#/components/schemas/Envelope_NoData"
2748 }
2749 }
2750 }
2751 }
2752 }
2753 }
2754 },
2755 "/api/v1/organizations/{organization_id}/invites/{invite_id}": {
2756 "delete": {
2757 "tags": [
2758 "auth"
2759 ],
2760 "summary": "撤销邀请",
2761 "description": "需要 `invite.revoke`。已撤销的邀请再次撤销返回 false。",
2762 "operationId": "auth_organization_invite_revoke",
2763 "parameters": [
2764 {
2765 "name": "organization_id",
2766 "in": "path",
2767 "description": "组织 id",
2768 "required": true,
2769 "schema": {
2770 "type": "string",
2771 "format": "uuid"
2772 }
2773 },
2774 {
2775 "name": "invite_id",
2776 "in": "path",
2777 "description": "邀请 id",
2778 "required": true,
2779 "schema": {
2780 "type": "string",
2781 "format": "uuid"
2782 }
2783 }
2784 ],
2785 "responses": {
2786 "200": {
2787 "description": "已撤销",
2788 "content": {
2789 "application/json": {
2790 "schema": {
2791 "$ref": "#/components/schemas/Envelope_bool"
2792 }
2793 }
2794 }
2795 },
2796 "404": {
2797 "description": "邀请不存在",
2798 "content": {
2799 "application/json": {
2800 "schema": {
2801 "$ref": "#/components/schemas/Envelope_NoData"
2802 }
2803 }
2804 }
2805 }
2806 }
2807 }
2808 },
2809 "/api/v1/organizations/{organization_id}/leave": {
2810 "post": {
2811 "tags": [
2812 "auth"
2813 ],
2814 "summary": "退出组织",
2815 "description": "OWNER 必须先转移所有权,否则返回 409。",
2816 "operationId": "auth_organization_leave",
2817 "parameters": [
2818 {
2819 "name": "organization_id",
2820 "in": "path",
2821 "description": "组织 id",
2822 "required": true,
2823 "schema": {
2824 "type": "string",
2825 "format": "uuid"
2826 }
2827 }
2828 ],
2829 "responses": {
2830 "200": {
2831 "description": "已退出",
2832 "content": {
2833 "application/json": {
2834 "schema": {
2835 "$ref": "#/components/schemas/Envelope_bool"
2836 }
2837 }
2838 }
2839 },
2840 "409": {
2841 "description": "OWNER 需先转移所有权",
2842 "content": {
2843 "application/json": {
2844 "schema": {
2845 "$ref": "#/components/schemas/Envelope_NoData"
2846 }
2847 }
2848 }
2849 }
2850 }
2851 }
2852 },
2853 "/api/v1/organizations/{organization_id}/members": {
2854 "get": {
2855 "tags": [
2856 "auth"
2857 ],
2858 "summary": "列示组织成员",
2859 "description": "需要 `member.view`。默认只返回活跃成员。",
2860 "operationId": "auth_organization_member_list",
2861 "parameters": [
2862 {
2863 "name": "organization_id",
2864 "in": "path",
2865 "description": "组织 id",
2866 "required": true,
2867 "schema": {
2868 "type": "string",
2869 "format": "uuid"
2870 }
2871 },
2872 {
2873 "name": "include_inactive",
2874 "in": "query",
2875 "description": "包含已退出成员",
2876 "required": false,
2877 "schema": {
2878 "type": "boolean"
2879 }
2880 },
2881 {
2882 "name": "include_banned",
2883 "in": "query",
2884 "description": "包含已封禁成员",
2885 "required": false,
2886 "schema": {
2887 "type": "boolean"
2888 }
2889 }
2890 ],
2891 "responses": {
2892 "200": {
2893 "description": "成员列表",
2894 "content": {
2895 "application/json": {
2896 "schema": {
2897 "$ref": "#/components/schemas/Envelope_Vec_MemberSummary"
2898 }
2899 }
2900 }
2901 },
2902 "403": {
2903 "description": "权限不足",
2904 "content": {
2905 "application/json": {
2906 "schema": {
2907 "$ref": "#/components/schemas/Envelope_NoData"
2908 }
2909 }
2910 }
2911 }
2912 }
2913 },
2914 "post": {
2915 "tags": [
2916 "auth"
2917 ],
2918 "summary": "直接添加成员",
2919 "description": "需要 `member.add`。已退出的会籍按复活处理,已封禁的返回 403。",
2920 "operationId": "auth_organization_member_add",
2921 "parameters": [
2922 {
2923 "name": "organization_id",
2924 "in": "path",
2925 "description": "组织 id",
2926 "required": true,
2927 "schema": {
2928 "type": "string",
2929 "format": "uuid"
2930 }
2931 }
2932 ],
2933 "requestBody": {
2934 "content": {
2935 "application/json": {
2936 "schema": {
2937 "$ref": "#/components/schemas/AddMemberBody"
2938 }
2939 }
2940 },
2941 "required": true
2942 },
2943 "responses": {
2944 "200": {
2945 "description": "成员已添加",
2946 "content": {
2947 "application/json": {
2948 "schema": {
2949 "$ref": "#/components/schemas/Envelope_MemberSummary"
2950 }
2951 }
2952 }
2953 },
2954 "403": {
2955 "description": "权限不足或成员已封禁",
2956 "content": {
2957 "application/json": {
2958 "schema": {
2959 "$ref": "#/components/schemas/Envelope_NoData"
2960 }
2961 }
2962 }
2963 },
2964 "404": {
2965 "description": "用户不存在",
2966 "content": {
2967 "application/json": {
2968 "schema": {
2969 "$ref": "#/components/schemas/Envelope_NoData"
2970 }
2971 }
2972 }
2973 }
2974 }
2975 }
2976 },
2977 "/api/v1/organizations/{organization_id}/members/@me": {
2978 "patch": {
2979 "tags": [
2980 "auth"
2981 ],
2982 "summary": "更新自己的会籍资料",
2983 "description": "只允许修改自己的别名、头像与通知偏好;无需 `member.role.update`。",
2984 "operationId": "auth_organization_member_update_self",
2985 "parameters": [
2986 {
2987 "name": "organization_id",
2988 "in": "path",
2989 "description": "组织 id",
2990 "required": true,
2991 "schema": {
2992 "type": "string",
2993 "format": "uuid"
2994 }
2995 }
2996 ],
2997 "requestBody": {
2998 "content": {
2999 "application/json": {
3000 "schema": {
3001 "$ref": "#/components/schemas/UpdateMemberBody"
3002 }
3003 }
3004 },
3005 "required": true
3006 },
3007 "responses": {
3008 "200": {
3009 "description": "已更新",
3010 "content": {
3011 "application/json": {
3012 "schema": {
3013 "$ref": "#/components/schemas/Envelope_MemberSummary"
3014 }
3015 }
3016 }
3017 },
3018 "403": {
3019 "description": "不是活跃成员",
3020 "content": {
3021 "application/json": {
3022 "schema": {
3023 "$ref": "#/components/schemas/Envelope_NoData"
3024 }
3025 }
3026 }
3027 }
3028 }
3029 }
3030 },
3031 "/api/v1/organizations/{organization_id}/members/{member_id}": {
3032 "get": {
3033 "tags": [
3034 "auth"
3035 ],
3036 "summary": "成员详情",
3037 "description": "需要 `member.view`。包含别名、通知偏好与封禁附加信息。",
3038 "operationId": "auth_organization_member_get",
3039 "parameters": [
3040 {
3041 "name": "organization_id",
3042 "in": "path",
3043 "description": "组织 id",
3044 "required": true,
3045 "schema": {
3046 "type": "string",
3047 "format": "uuid"
3048 }
3049 },
3050 {
3051 "name": "member_id",
3052 "in": "path",
3053 "description": "会籍 id",
3054 "required": true,
3055 "schema": {
3056 "type": "string",
3057 "format": "uuid"
3058 }
3059 }
3060 ],
3061 "responses": {
3062 "200": {
3063 "description": "成员详情",
3064 "content": {
3065 "application/json": {
3066 "schema": {
3067 "$ref": "#/components/schemas/Envelope_MemberSummary"
3068 }
3069 }
3070 }
3071 },
3072 "404": {
3073 "description": "成员不存在",
3074 "content": {
3075 "application/json": {
3076 "schema": {
3077 "$ref": "#/components/schemas/Envelope_NoData"
3078 }
3079 }
3080 }
3081 }
3082 }
3083 },
3084 "delete": {
3085 "tags": [
3086 "auth"
3087 ],
3088 "summary": "移除成员",
3089 "description": "需要 `member.remove`。OWNER 不能移除,需先转移所有权。会籍行保留用于复活。",
3090 "operationId": "auth_organization_member_remove",
3091 "parameters": [
3092 {
3093 "name": "organization_id",
3094 "in": "path",
3095 "description": "组织 id",
3096 "required": true,
3097 "schema": {
3098 "type": "string",
3099 "format": "uuid"
3100 }
3101 },
3102 {
3103 "name": "member_id",
3104 "in": "path",
3105 "description": "会籍 id",
3106 "required": true,
3107 "schema": {
3108 "type": "string",
3109 "format": "uuid"
3110 }
3111 },
3112 {
3113 "name": "reason",
3114 "in": "query",
3115 "description": "移除原因",
3116 "required": false,
3117 "schema": {
3118 "type": "string"
3119 }
3120 }
3121 ],
3122 "responses": {
3123 "200": {
3124 "description": "已移除",
3125 "content": {
3126 "application/json": {
3127 "schema": {
3128 "$ref": "#/components/schemas/Envelope_bool"
3129 }
3130 }
3131 }
3132 },
3133 "403": {
3134 "description": "权限不足或目标是 OWNER",
3135 "content": {
3136 "application/json": {
3137 "schema": {
3138 "$ref": "#/components/schemas/Envelope_NoData"
3139 }
3140 }
3141 }
3142 }
3143 }
3144 },
3145 "patch": {
3146 "tags": [
3147 "auth"
3148 ],
3149 "summary": "更新成员资料",
3150 "description": "需要 `member.role.update`。修改别名、头像、通知偏好与免打扰;封禁中的会籍走封禁接口。",
3151 "operationId": "auth_organization_member_update",
3152 "parameters": [
3153 {
3154 "name": "organization_id",
3155 "in": "path",
3156 "description": "组织 id",
3157 "required": true,
3158 "schema": {
3159 "type": "string",
3160 "format": "uuid"
3161 }
3162 },
3163 {
3164 "name": "member_id",
3165 "in": "path",
3166 "description": "会籍 id",
3167 "required": true,
3168 "schema": {
3169 "type": "string",
3170 "format": "uuid"
3171 }
3172 }
3173 ],
3174 "requestBody": {
3175 "content": {
3176 "application/json": {
3177 "schema": {
3178 "$ref": "#/components/schemas/UpdateMemberBody"
3179 }
3180 }
3181 },
3182 "required": true
3183 },
3184 "responses": {
3185 "200": {
3186 "description": "已更新",
3187 "content": {
3188 "application/json": {
3189 "schema": {
3190 "$ref": "#/components/schemas/Envelope_MemberSummary"
3191 }
3192 }
3193 }
3194 },
3195 "403": {
3196 "description": "权限不足或目标已封禁",
3197 "content": {
3198 "application/json": {
3199 "schema": {
3200 "$ref": "#/components/schemas/Envelope_NoData"
3201 }
3202 }
3203 }
3204 },
3205 "409": {
3206 "description": "版本冲突",
3207 "content": {
3208 "application/json": {
3209 "schema": {
3210 "$ref": "#/components/schemas/Envelope_NoData"
3211 }
3212 }
3213 }
3214 }
3215 }
3216 }
3217 },
3218 "/api/v1/organizations/{organization_id}/members/{member_id}/ban": {
3219 "post": {
3220 "tags": [
3221 "auth"
3222 ],
3223 "summary": "封禁成员",
3224 "description": "写 banned_at 并清空角色绑定与直挂权限。封禁优先于一切授权。",
3225 "operationId": "auth_organization_member_ban",
3226 "parameters": [
3227 {
3228 "name": "organization_id",
3229 "in": "path",
3230 "description": "组织 id",
3231 "required": true,
3232 "schema": {
3233 "type": "string",
3234 "format": "uuid"
3235 }
3236 },
3237 {
3238 "name": "member_id",
3239 "in": "path",
3240 "description": "会籍 id",
3241 "required": true,
3242 "schema": {
3243 "type": "string",
3244 "format": "uuid"
3245 }
3246 }
3247 ],
3248 "requestBody": {
3249 "content": {
3250 "application/json": {
3251 "schema": {
3252 "$ref": "#/components/schemas/BanMemberBody"
3253 }
3254 }
3255 },
3256 "required": true
3257 },
3258 "responses": {
3259 "200": {
3260 "description": "已封禁",
3261 "content": {
3262 "application/json": {
3263 "schema": {
3264 "$ref": "#/components/schemas/Envelope_MemberSummary"
3265 }
3266 }
3267 }
3268 },
3269 "403": {
3270 "description": "权限不足",
3271 "content": {
3272 "application/json": {
3273 "schema": {
3274 "$ref": "#/components/schemas/Envelope_NoData"
3275 }
3276 }
3277 }
3278 }
3279 }
3280 }
3281 },
3282 "/api/v1/organizations/{organization_id}/members/{member_id}/permissions": {
3283 "put": {
3284 "tags": [
3285 "auth"
3286 ],
3287 "summary": "设置成员直挂权限",
3288 "description": "整表替换语义。需要 `role.permission.update`,OWNER 不接受直挂覆盖。",
3289 "operationId": "auth_organization_member_permissions",
3290 "parameters": [
3291 {
3292 "name": "organization_id",
3293 "in": "path",
3294 "description": "组织 id",
3295 "required": true,
3296 "schema": {
3297 "type": "string",
3298 "format": "uuid"
3299 }
3300 },
3301 {
3302 "name": "member_id",
3303 "in": "path",
3304 "description": "会籍 id",
3305 "required": true,
3306 "schema": {
3307 "type": "string",
3308 "format": "uuid"
3309 }
3310 }
3311 ],
3312 "requestBody": {
3313 "content": {
3314 "application/json": {
3315 "schema": {
3316 "$ref": "#/components/schemas/SetRolePermissionsBody"
3317 }
3318 }
3319 },
3320 "required": true
3321 },
3322 "responses": {
3323 "200": {
3324 "description": "已更新",
3325 "content": {
3326 "application/json": {
3327 "schema": {
3328 "$ref": "#/components/schemas/Envelope_Vec_String"
3329 }
3330 }
3331 }
3332 },
3333 "403": {
3334 "description": "权限不足",
3335 "content": {
3336 "application/json": {
3337 "schema": {
3338 "$ref": "#/components/schemas/Envelope_NoData"
3339 }
3340 }
3341 }
3342 }
3343 }
3344 }
3345 },
3346 "/api/v1/organizations/{organization_id}/members/{member_id}/role": {
3347 "patch": {
3348 "tags": [
3349 "auth"
3350 ],
3351 "summary": "改粗粒度角色",
3352 "description": "只接受 MEMBER 与 ADMIN。OWNER 只能通过转移所有权变更。",
3353 "operationId": "auth_organization_member_role",
3354 "parameters": [
3355 {
3356 "name": "organization_id",
3357 "in": "path",
3358 "description": "组织 id",
3359 "required": true,
3360 "schema": {
3361 "type": "string",
3362 "format": "uuid"
3363 }
3364 },
3365 {
3366 "name": "member_id",
3367 "in": "path",
3368 "description": "会籍 id",
3369 "required": true,
3370 "schema": {
3371 "type": "string",
3372 "format": "uuid"
3373 }
3374 }
3375 ],
3376 "requestBody": {
3377 "content": {
3378 "application/json": {
3379 "schema": {
3380 "$ref": "#/components/schemas/SetMemberRoleBody"
3381 }
3382 }
3383 },
3384 "required": true
3385 },
3386 "responses": {
3387 "200": {
3388 "description": "已更新",
3389 "content": {
3390 "application/json": {
3391 "schema": {
3392 "$ref": "#/components/schemas/Envelope_MemberSummary"
3393 }
3394 }
3395 }
3396 },
3397 "403": {
3398 "description": "权限不足",
3399 "content": {
3400 "application/json": {
3401 "schema": {
3402 "$ref": "#/components/schemas/Envelope_NoData"
3403 }
3404 }
3405 }
3406 },
3407 "409": {
3408 "description": "版本冲突",
3409 "content": {
3410 "application/json": {
3411 "schema": {
3412 "$ref": "#/components/schemas/Envelope_NoData"
3413 }
3414 }
3415 }
3416 }
3417 }
3418 }
3419 },
3420 "/api/v1/organizations/{organization_id}/members/{member_id}/unban": {
3421 "post": {
3422 "tags": [
3423 "auth"
3424 ],
3425 "summary": "解封成员",
3426 "description": "清空 banned_at。已清空的角色不会自动恢复。",
3427 "operationId": "auth_organization_member_unban",
3428 "parameters": [
3429 {
3430 "name": "organization_id",
3431 "in": "path",
3432 "description": "组织 id",
3433 "required": true,
3434 "schema": {
3435 "type": "string",
3436 "format": "uuid"
3437 }
3438 },
3439 {
3440 "name": "member_id",
3441 "in": "path",
3442 "description": "会籍 id",
3443 "required": true,
3444 "schema": {
3445 "type": "string",
3446 "format": "uuid"
3447 }
3448 }
3449 ],
3450 "responses": {
3451 "200": {
3452 "description": "已解封",
3453 "content": {
3454 "application/json": {
3455 "schema": {
3456 "$ref": "#/components/schemas/Envelope_MemberSummary"
3457 }
3458 }
3459 }
3460 },
3461 "403": {
3462 "description": "权限不足",
3463 "content": {
3464 "application/json": {
3465 "schema": {
3466 "$ref": "#/components/schemas/Envelope_NoData"
3467 }
3468 }
3469 }
3470 }
3471 }
3472 }
3473 },
3474 "/api/v1/organizations/{organization_id}/permissions/check": {
3475 "post": {
3476 "tags": [
3477 "auth"
3478 ],
3479 "summary": "单权限判定",
3480 "description": "拒绝不作为错误返回:`allowed=false` 附 `reason` 与 `matched_by`,便于调用方聚合。",
3481 "operationId": "auth_organization_permission_check",
3482 "parameters": [
3483 {
3484 "name": "organization_id",
3485 "in": "path",
3486 "description": "组织 id",
3487 "required": true,
3488 "schema": {
3489 "type": "string",
3490 "format": "uuid"
3491 }
3492 }
3493 ],
3494 "requestBody": {
3495 "content": {
3496 "application/json": {
3497 "schema": {
3498 "$ref": "#/components/schemas/CheckPermissionBody"
3499 }
3500 }
3501 },
3502 "required": true
3503 },
3504 "responses": {
3505 "200": {
3506 "description": "判定结果",
3507 "content": {
3508 "application/json": {
3509 "schema": {
3510 "$ref": "#/components/schemas/Envelope_PermissionCheck"
3511 }
3512 }
3513 }
3514 },
3515 "403": {
3516 "description": "查询他人权限但不是平台管理员",
3517 "content": {
3518 "application/json": {
3519 "schema": {
3520 "$ref": "#/components/schemas/Envelope_NoData"
3521 }
3522 }
3523 }
3524 }
3525 }
3526 }
3527 },
3528 "/api/v1/organizations/{organization_id}/restore": {
3529 "post": {
3530 "tags": [
3531 "auth"
3532 ],
3533 "summary": "恢复归档组织",
3534 "description": "仅 OWNER 或平台管理员可执行。",
3535 "operationId": "auth_organization_restore",
3536 "parameters": [
3537 {
3538 "name": "organization_id",
3539 "in": "path",
3540 "description": "组织 id",
3541 "required": true,
3542 "schema": {
3543 "type": "string",
3544 "format": "uuid"
3545 }
3546 }
3547 ],
3548 "responses": {
3549 "200": {
3550 "description": "已恢复",
3551 "content": {
3552 "application/json": {
3553 "schema": {
3554 "$ref": "#/components/schemas/Envelope_OrganizationSummary"
3555 }
3556 }
3557 }
3558 },
3559 "403": {
3560 "description": "权限不足",
3561 "content": {
3562 "application/json": {
3563 "schema": {
3564 "$ref": "#/components/schemas/Envelope_NoData"
3565 }
3566 }
3567 }
3568 }
3569 }
3570 }
3571 },
3572 "/api/v1/organizations/{organization_id}/roles": {
3573 "get": {
3574 "tags": [
3575 "auth"
3576 ],
3577 "summary": "列示组织角色",
3578 "description": "需要 `role.view`。含内置 owner/admin/member 三个系统角色。",
3579 "operationId": "auth_organization_role_list",
3580 "parameters": [
3581 {
3582 "name": "organization_id",
3583 "in": "path",
3584 "description": "组织 id",
3585 "required": true,
3586 "schema": {
3587 "type": "string",
3588 "format": "uuid"
3589 }
3590 }
3591 ],
3592 "responses": {
3593 "200": {
3594 "description": "角色列表",
3595 "content": {
3596 "application/json": {
3597 "schema": {
3598 "$ref": "#/components/schemas/Envelope_Vec_RoleSummary"
3599 }
3600 }
3601 }
3602 },
3603 "403": {
3604 "description": "权限不足",
3605 "content": {
3606 "application/json": {
3607 "schema": {
3608 "$ref": "#/components/schemas/Envelope_NoData"
3609 }
3610 }
3611 }
3612 }
3613 }
3614 },
3615 "post": {
3616 "tags": [
3617 "auth"
3618 ],
3619 "summary": "新建角色",
3620 "description": "需要 `role.create`。key 在组织内唯一,权限键必须在白名单内。",
3621 "operationId": "auth_organization_role_create",
3622 "parameters": [
3623 {
3624 "name": "organization_id",
3625 "in": "path",
3626 "description": "组织 id",
3627 "required": true,
3628 "schema": {
3629 "type": "string",
3630 "format": "uuid"
3631 }
3632 }
3633 ],
3634 "requestBody": {
3635 "content": {
3636 "application/json": {
3637 "schema": {
3638 "$ref": "#/components/schemas/CreateRoleBody"
3639 }
3640 }
3641 },
3642 "required": true
3643 },
3644 "responses": {
3645 "200": {
3646 "description": "角色已创建",
3647 "content": {
3648 "application/json": {
3649 "schema": {
3650 "$ref": "#/components/schemas/Envelope_RoleSummary"
3651 }
3652 }
3653 }
3654 },
3655 "409": {
3656 "description": "key 已占用",
3657 "content": {
3658 "application/json": {
3659 "schema": {
3660 "$ref": "#/components/schemas/Envelope_NoData"
3661 }
3662 }
3663 }
3664 }
3665 }
3666 }
3667 },
3668 "/api/v1/organizations/{organization_id}/roles/assign": {
3669 "post": {
3670 "tags": [
3671 "auth"
3672 ],
3673 "summary": "绑定角色到成员",
3674 "description": "需要 `role.assign`。重复绑定幂等。",
3675 "operationId": "auth_organization_role_assign",
3676 "parameters": [
3677 {
3678 "name": "organization_id",
3679 "in": "path",
3680 "description": "组织 id",
3681 "required": true,
3682 "schema": {
3683 "type": "string",
3684 "format": "uuid"
3685 }
3686 }
3687 ],
3688 "requestBody": {
3689 "content": {
3690 "application/json": {
3691 "schema": {
3692 "$ref": "#/components/schemas/AssignRoleBody"
3693 }
3694 }
3695 },
3696 "required": true
3697 },
3698 "responses": {
3699 "200": {
3700 "description": "已绑定",
3701 "content": {
3702 "application/json": {
3703 "schema": {
3704 "$ref": "#/components/schemas/Envelope_Vec_String"
3705 }
3706 }
3707 }
3708 },
3709 "404": {
3710 "description": "成员或角色不存在",
3711 "content": {
3712 "application/json": {
3713 "schema": {
3714 "$ref": "#/components/schemas/Envelope_NoData"
3715 }
3716 }
3717 }
3718 }
3719 }
3720 }
3721 },
3722 "/api/v1/organizations/{organization_id}/roles/unassign": {
3723 "post": {
3724 "tags": [
3725 "auth"
3726 ],
3727 "summary": "解绑成员角色",
3728 "description": "需要 `role.assign`。系统角色不可解绑。",
3729 "operationId": "auth_organization_role_unassign",
3730 "parameters": [
3731 {
3732 "name": "organization_id",
3733 "in": "path",
3734 "description": "组织 id",
3735 "required": true,
3736 "schema": {
3737 "type": "string",
3738 "format": "uuid"
3739 }
3740 }
3741 ],
3742 "requestBody": {
3743 "content": {
3744 "application/json": {
3745 "schema": {
3746 "$ref": "#/components/schemas/AssignRoleBody"
3747 }
3748 }
3749 },
3750 "required": true
3751 },
3752 "responses": {
3753 "200": {
3754 "description": "已解绑",
3755 "content": {
3756 "application/json": {
3757 "schema": {
3758 "$ref": "#/components/schemas/Envelope_Vec_String"
3759 }
3760 }
3761 }
3762 },
3763 "403": {
3764 "description": "系统角色不可解绑",
3765 "content": {
3766 "application/json": {
3767 "schema": {
3768 "$ref": "#/components/schemas/Envelope_NoData"
3769 }
3770 }
3771 }
3772 }
3773 }
3774 }
3775 },
3776 "/api/v1/organizations/{organization_id}/roles/{role_id}": {
3777 "get": {
3778 "tags": [
3779 "auth"
3780 ],
3781 "summary": "角色详情",
3782 "description": "需要 `role.view`。",
3783 "operationId": "auth_organization_role_get",
3784 "parameters": [
3785 {
3786 "name": "organization_id",
3787 "in": "path",
3788 "description": "组织 id",
3789 "required": true,
3790 "schema": {
3791 "type": "string",
3792 "format": "uuid"
3793 }
3794 },
3795 {
3796 "name": "role_id",
3797 "in": "path",
3798 "description": "角色 id",
3799 "required": true,
3800 "schema": {
3801 "type": "string",
3802 "format": "uuid"
3803 }
3804 }
3805 ],
3806 "responses": {
3807 "200": {
3808 "description": "角色详情",
3809 "content": {
3810 "application/json": {
3811 "schema": {
3812 "$ref": "#/components/schemas/Envelope_RoleSummary"
3813 }
3814 }
3815 }
3816 },
3817 "404": {
3818 "description": "角色不存在",
3819 "content": {
3820 "application/json": {
3821 "schema": {
3822 "$ref": "#/components/schemas/Envelope_NoData"
3823 }
3824 }
3825 }
3826 }
3827 }
3828 },
3829 "delete": {
3830 "tags": [
3831 "auth"
3832 ],
3833 "summary": "删除角色",
3834 "description": "需要 `role.delete`。系统角色不可删;仍有成员绑定时返回 409。",
3835 "operationId": "auth_organization_role_delete",
3836 "parameters": [
3837 {
3838 "name": "organization_id",
3839 "in": "path",
3840 "description": "组织 id",
3841 "required": true,
3842 "schema": {
3843 "type": "string",
3844 "format": "uuid"
3845 }
3846 },
3847 {
3848 "name": "role_id",
3849 "in": "path",
3850 "description": "角色 id",
3851 "required": true,
3852 "schema": {
3853 "type": "string",
3854 "format": "uuid"
3855 }
3856 }
3857 ],
3858 "responses": {
3859 "200": {
3860 "description": "已删除",
3861 "content": {
3862 "application/json": {
3863 "schema": {
3864 "$ref": "#/components/schemas/Envelope_bool"
3865 }
3866 }
3867 }
3868 },
3869 "409": {
3870 "description": "系统角色或仍被绑定",
3871 "content": {
3872 "application/json": {
3873 "schema": {
3874 "$ref": "#/components/schemas/Envelope_NoData"
3875 }
3876 }
3877 }
3878 }
3879 }
3880 },
3881 "patch": {
3882 "tags": [
3883 "auth"
3884 ],
3885 "summary": "修改角色",
3886 "description": "需要 `role.update`。OWNER 系统角色不允许改 rank 与 default_role。",
3887 "operationId": "auth_organization_role_update",
3888 "parameters": [
3889 {
3890 "name": "organization_id",
3891 "in": "path",
3892 "description": "组织 id",
3893 "required": true,
3894 "schema": {
3895 "type": "string",
3896 "format": "uuid"
3897 }
3898 },
3899 {
3900 "name": "role_id",
3901 "in": "path",
3902 "description": "角色 id",
3903 "required": true,
3904 "schema": {
3905 "type": "string",
3906 "format": "uuid"
3907 }
3908 }
3909 ],
3910 "requestBody": {
3911 "content": {
3912 "application/json": {
3913 "schema": {
3914 "$ref": "#/components/schemas/UpdateRoleBody"
3915 }
3916 }
3917 },
3918 "required": true
3919 },
3920 "responses": {
3921 "200": {
3922 "description": "已更新",
3923 "content": {
3924 "application/json": {
3925 "schema": {
3926 "$ref": "#/components/schemas/Envelope_RoleSummary"
3927 }
3928 }
3929 }
3930 },
3931 "409": {
3932 "description": "版本冲突",
3933 "content": {
3934 "application/json": {
3935 "schema": {
3936 "$ref": "#/components/schemas/Envelope_NoData"
3937 }
3938 }
3939 }
3940 }
3941 }
3942 }
3943 },
3944 "/api/v1/organizations/{organization_id}/roles/{role_id}/join": {
3945 "post": {
3946 "tags": [
3947 "auth"
3948 ],
3949 "summary": "自助加入角色",
3950 "description": "只允许 `self_assignable = true` 的非内置角色。",
3951 "operationId": "auth_organization_role_join",
3952 "parameters": [
3953 {
3954 "name": "organization_id",
3955 "in": "path",
3956 "description": "组织 id",
3957 "required": true,
3958 "schema": {
3959 "type": "string",
3960 "format": "uuid"
3961 }
3962 },
3963 {
3964 "name": "role_id",
3965 "in": "path",
3966 "description": "角色 id",
3967 "required": true,
3968 "schema": {
3969 "type": "string",
3970 "format": "uuid"
3971 }
3972 }
3973 ],
3974 "responses": {
3975 "200": {
3976 "description": "已加入",
3977 "content": {
3978 "application/json": {
3979 "schema": {
3980 "$ref": "#/components/schemas/Envelope_MemberSummary"
3981 }
3982 }
3983 }
3984 },
3985 "403": {
3986 "description": "角色不可自助加入或不是活跃成员",
3987 "content": {
3988 "application/json": {
3989 "schema": {
3990 "$ref": "#/components/schemas/Envelope_NoData"
3991 }
3992 }
3993 }
3994 }
3995 }
3996 },
3997 "delete": {
3998 "tags": [
3999 "auth"
4000 ],
4001 "summary": "自助退出角色",
4002 "description": "只允许 `self_assignable = true` 的非内置角色;重复退出视为成功。",
4003 "operationId": "auth_organization_role_leave",
4004 "parameters": [
4005 {
4006 "name": "organization_id",
4007 "in": "path",
4008 "description": "组织 id",
4009 "required": true,
4010 "schema": {
4011 "type": "string",
4012 "format": "uuid"
4013 }
4014 },
4015 {
4016 "name": "role_id",
4017 "in": "path",
4018 "description": "角色 id",
4019 "required": true,
4020 "schema": {
4021 "type": "string",
4022 "format": "uuid"
4023 }
4024 }
4025 ],
4026 "responses": {
4027 "200": {
4028 "description": "已退出",
4029 "content": {
4030 "application/json": {
4031 "schema": {
4032 "$ref": "#/components/schemas/Envelope_MemberSummary"
4033 }
4034 }
4035 }
4036 },
4037 "403": {
4038 "description": "角色不可自助加入或不是活跃成员",
4039 "content": {
4040 "application/json": {
4041 "schema": {
4042 "$ref": "#/components/schemas/Envelope_NoData"
4043 }
4044 }
4045 }
4046 }
4047 }
4048 }
4049 },
4050 "/api/v1/organizations/{organization_id}/roles/{role_id}/permissions": {
4051 "put": {
4052 "tags": [
4053 "auth"
4054 ],
4055 "summary": "设置角色权限",
4056 "description": "整体替换语义,是角色权限的唯一写入口。需要 `role.permission.update`。",
4057 "operationId": "auth_organization_role_permissions",
4058 "parameters": [
4059 {
4060 "name": "organization_id",
4061 "in": "path",
4062 "description": "组织 id",
4063 "required": true,
4064 "schema": {
4065 "type": "string",
4066 "format": "uuid"
4067 }
4068 },
4069 {
4070 "name": "role_id",
4071 "in": "path",
4072 "description": "角色 id",
4073 "required": true,
4074 "schema": {
4075 "type": "string",
4076 "format": "uuid"
4077 }
4078 }
4079 ],
4080 "requestBody": {
4081 "content": {
4082 "application/json": {
4083 "schema": {
4084 "$ref": "#/components/schemas/SetRolePermissionsBody"
4085 }
4086 }
4087 },
4088 "required": true
4089 },
4090 "responses": {
4091 "200": {
4092 "description": "已更新",
4093 "content": {
4094 "application/json": {
4095 "schema": {
4096 "$ref": "#/components/schemas/Envelope_RoleSummary"
4097 }
4098 }
4099 }
4100 },
4101 "403": {
4102 "description": "权限不足或系统 OWNER 角色",
4103 "content": {
4104 "application/json": {
4105 "schema": {
4106 "$ref": "#/components/schemas/Envelope_NoData"
4107 }
4108 }
4109 }
4110 }
4111 }
4112 }
4113 },
4114 "/api/v1/organizations/{organization_id}/transfer": {
4115 "post": {
4116 "tags": [
4117 "auth"
4118 ],
4119 "summary": "转移 OWNER",
4120 "description": "目标必须是活跃成员。原 Owner 降为 ADMIN。",
4121 "operationId": "auth_organization_transfer",
4122 "parameters": [
4123 {
4124 "name": "organization_id",
4125 "in": "path",
4126 "description": "组织 id",
4127 "required": true,
4128 "schema": {
4129 "type": "string",
4130 "format": "uuid"
4131 }
4132 }
4133 ],
4134 "requestBody": {
4135 "content": {
4136 "application/json": {
4137 "schema": {
4138 "$ref": "#/components/schemas/TransferBody"
4139 }
4140 }
4141 },
4142 "required": true
4143 },
4144 "responses": {
4145 "200": {
4146 "description": "已转移",
4147 "content": {
4148 "application/json": {
4149 "schema": {
4150 "$ref": "#/components/schemas/Envelope_OrganizationSummary"
4151 }
4152 }
4153 }
4154 },
4155 "403": {
4156 "description": "权限不足",
4157 "content": {
4158 "application/json": {
4159 "schema": {
4160 "$ref": "#/components/schemas/Envelope_NoData"
4161 }
4162 }
4163 }
4164 },
4165 "404": {
4166 "description": "目标不是活跃成员",
4167 "content": {
4168 "application/json": {
4169 "schema": {
4170 "$ref": "#/components/schemas/Envelope_NoData"
4171 }
4172 }
4173 }
4174 }
4175 }
4176 }
4177 },
4178 "/health": {
4179 "get": {
4180 "tags": [
4181 "health"
4182 ],
4183 "summary": "存活探针",
4184 "description": "只回答进程是否还在处理请求,不触碰任何外部依赖:数据库、Redis、RocketMQ 全都不可用时依然返回 200。用于区分「进程死了」和「依赖挂了」。",
4185 "operationId": "health_live",
4186 "responses": {
4187 "200": {
4188 "description": "进程存活",
4189 "content": {
4190 "application/json": {
4191 "schema": {
4192 "$ref": "#/components/schemas/Envelope_HealthResponse"
4193 }
4194 }
4195 }
4196 }
4197 }
4198 }
4199 },
4200 "/ready": {
4201 "get": {
4202 "tags": [
4203 "health"
4204 ],
4205 "summary": "就绪探针",
4206 "description": "并发探测启动时必须连上的三个依赖——PostgreSQL、Redis、RocketMQ,全部可用返回 200,任一不可用返回 503 并在 `checks` 里标出是哪一项。单项探测上限 2 秒,失败细节只写日志,不进响应体。",
4207 "operationId": "health_ready",
4208 "responses": {
4209 "200": {
4210 "description": "全部依赖可用",
4211 "content": {
4212 "application/json": {
4213 "schema": {
4214 "$ref": "#/components/schemas/Envelope_ReadinessResponse"
4215 }
4216 }
4217 }
4218 },
4219 "503": {
4220 "description": "至少一个依赖不可用(`unavailable`)",
4221 "content": {
4222 "application/json": {
4223 "schema": {
4224 "$ref": "#/components/schemas/Envelope_ReadinessResponse"
4225 }
4226 }
4227 }
4228 }
4229 }
4230 }
4231 }
4232 },
4233 "components": {
4234 "schemas": {
4235 "AccessTokenCreateRequest": {
4236 "type": "object",
4237 "required": [
4238 "name",
4239 "scope"
4240 ],
4241 "properties": {
4242 "description": {
4243 "type": [
4244 "string",
4245 "null"
4246 ]
4247 },
4248 "expires_in_secs": {
4249 "type": [
4250 "integer",
4251 "null"
4252 ],
4253 "format": "int64"
4254 },
4255 "name": {
4256 "type": "string"
4257 },
4258 "scope": {
4259 "type": "array",
4260 "items": {
4261 "type": "string"
4262 }
4263 }
4264 }
4265 },
4266 "AccessTokenCreateResponse": {
4267 "type": "object",
4268 "required": [
4269 "id",
4270 "name",
4271 "token",
4272 "scope",
4273 "created_at"
4274 ],
4275 "properties": {
4276 "created_at": {
4277 "type": "string",
4278 "format": "date-time"
4279 },
4280 "expires_at": {
4281 "type": [
4282 "string",
4283 "null"
4284 ],
4285 "format": "date-time"
4286 },
4287 "id": {
4288 "type": "string",
4289 "format": "uuid"
4290 },
4291 "name": {
4292 "type": "string"
4293 },
4294 "scope": {
4295 "type": "array",
4296 "items": {
4297 "type": "string"
4298 }
4299 },
4300 "token": {
4301 "type": "string"
4302 }
4303 }
4304 },
4305 "AccessTokenSummary": {
4306 "type": "object",
4307 "required": [
4308 "id",
4309 "name",
4310 "scope",
4311 "created_at"
4312 ],
4313 "properties": {
4314 "created_at": {
4315 "type": "string",
4316 "format": "date-time"
4317 },
4318 "description": {
4319 "type": [
4320 "string",
4321 "null"
4322 ]
4323 },
4324 "expires_at": {
4325 "type": [
4326 "string",
4327 "null"
4328 ],
4329 "format": "date-time"
4330 },
4331 "fingerprint": {
4332 "type": [
4333 "string",
4334 "null"
4335 ]
4336 },
4337 "id": {
4338 "type": "string",
4339 "format": "uuid"
4340 },
4341 "last_used_at": {
4342 "type": [
4343 "string",
4344 "null"
4345 ],
4346 "format": "date-time"
4347 },
4348 "name": {
4349 "type": "string"
4350 },
4351 "revoked_at": {
4352 "type": [
4353 "string",
4354 "null"
4355 ],
4356 "format": "date-time"
4357 },
4358 "scope": {
4359 "type": "array",
4360 "items": {
4361 "type": "string"
4362 }
4363 }
4364 }
4365 },
4366 "AddMemberBody": {
4367 "type": "object",
4368 "required": [
4369 "user_id"
4370 ],
4371 "properties": {
4372 "role": {
4373 "oneOf": [
4374 {
4375 "$ref": "#/components/schemas/OrgMemberRole"
4376 },
4377 {
4378 "type": "null"
4379 }
4380 ]
4381 },
4382 "role_keys": {
4383 "type": "array",
4384 "items": {
4385 "type": "string"
4386 }
4387 },
4388 "user_id": {
4389 "type": "integer",
4390 "format": "int64"
4391 }
4392 }
4393 },
4394 "AppTokenRequest": {
4395 "type": "object",
4396 "required": [
4397 "app_id"
4398 ],
4399 "properties": {
4400 "app_id": {
4401 "type": "string"
4402 },
4403 "ttl_seconds": {
4404 "type": [
4405 "integer",
4406 "null"
4407 ],
4408 "format": "int32",
4409 "minimum": 0
4410 }
4411 }
4412 },
4413 "AppTokenResponse": {
4414 "type": "object",
4415 "required": [
4416 "app_id",
4417 "audience",
4418 "key_id",
4419 "token",
4420 "issued_at",
4421 "expires_at"
4422 ],
4423 "properties": {
4424 "app_id": {
4425 "type": "string"
4426 },
4427 "audience": {
4428 "type": "string"
4429 },
4430 "expires_at": {
4431 "type": "string",
4432 "format": "date-time"
4433 },
4434 "issued_at": {
4435 "type": "string",
4436 "format": "date-time"
4437 },
4438 "key_id": {
4439 "type": "string"
4440 },
4441 "token": {
4442 "type": "string"
4443 }
4444 }
4445 },
4446 "AssignRoleBody": {
4447 "type": "object",
4448 "required": [
4449 "member_id",
4450 "role_key"
4451 ],
4452 "properties": {
4453 "member_id": {
4454 "type": "string",
4455 "format": "uuid"
4456 },
4457 "role_key": {
4458 "type": "string"
4459 }
4460 }
4461 },
4462 "BanMemberBody": {
4463 "type": "object",
4464 "properties": {
4465 "reason": {
4466 "type": [
4467 "string",
4468 "null"
4469 ]
4470 }
4471 }
4472 },
4473 "ChallengeResponse": {
4474 "type": "object",
4475 "required": [
4476 "context",
4477 "difficulty",
4478 "issued_at",
4479 "expires_at"
4480 ],
4481 "properties": {
4482 "context": {
4483 "type": "string"
4484 },
4485 "difficulty": {
4486 "type": "integer",
4487 "format": "int32",
4488 "minimum": 0
4489 },
4490 "expires_at": {
4491 "type": "integer",
4492 "format": "int64",
4493 "minimum": 0
4494 },
4495 "issued_at": {
4496 "type": "integer",
4497 "format": "int64",
4498 "minimum": 0
4499 }
4500 }
4501 },
4502 "CheckPermissionBody": {
4503 "type": "object",
4504 "required": [
4505 "permission"
4506 ],
4507 "properties": {
4508 "permission": {
4509 "type": "string"
4510 },
4511 "user_id": {
4512 "type": [
4513 "integer",
4514 "null"
4515 ],
4516 "format": "int64"
4517 }
4518 }
4519 },
4520 "ComponentCheck": {
4521 "type": "object",
4522 "required": [
4523 "component",
4524 "ready"
4525 ],
4526 "properties": {
4527 "component": {
4528 "type": "string"
4529 },
4530 "ready": {
4531 "type": "boolean"
4532 }
4533 }
4534 },
4535 "ConsentDecision": {
4536 "type": "object",
4537 "required": [
4538 "redirect"
4539 ],
4540 "properties": {
4541 "redirect": {
4542 "type": "string",
4543 "description": "下一步跳转:允许则回 `/api/v1/oauth/authorize?...`,拒绝则回客户端并带 `error=access_denied`。"
4544 }
4545 }
4546 },
4547 "ConsentForm": {
4548 "type": "object",
4549 "description": "同意页提交体:授权参数在查询串里,请求体只带决定。",
4550 "required": [
4551 "decision"
4552 ],
4553 "properties": {
4554 "decision": {
4555 "type": "string"
4556 }
4557 }
4558 },
4559 "ConsentPreview": {
4560 "type": "object",
4561 "required": [
4562 "client_id",
4563 "client_name",
4564 "scopes",
4565 "username"
4566 ],
4567 "properties": {
4568 "client_id": {
4569 "type": "string"
4570 },
4571 "client_name": {
4572 "type": "string"
4573 },
4574 "scopes": {
4575 "type": "array",
4576 "items": {
4577 "type": "string"
4578 }
4579 },
4580 "username": {
4581 "type": "string"
4582 }
4583 }
4584 },
4585 "CreateInviteBody": {
4586 "type": "object",
4587 "properties": {
4588 "max_uses": {
4589 "type": "integer",
4590 "format": "int32"
4591 },
4592 "role_id": {
4593 "type": [
4594 "string",
4595 "null"
4596 ],
4597 "format": "uuid"
4598 },
4599 "target_user_id": {
4600 "type": [
4601 "integer",
4602 "null"
4603 ],
4604 "format": "int64"
4605 },
4606 "ttl_secs": {
4607 "type": [
4608 "integer",
4609 "null"
4610 ],
4611 "format": "int64",
4612 "minimum": 0
4613 }
4614 }
4615 },
4616 "CreateInviteResponse": {
4617 "type": "object",
4618 "required": [
4619 "invite",
4620 "token"
4621 ],
4622 "properties": {
4623 "invite": {
4624 "$ref": "#/components/schemas/InviteSummary"
4625 },
4626 "token": {
4627 "type": "string"
4628 }
4629 }
4630 },
4631 "CreateOrganizationBody": {
4632 "type": "object",
4633 "required": [
4634 "slug",
4635 "name"
4636 ],
4637 "properties": {
4638 "allow_invite": {
4639 "type": [
4640 "boolean",
4641 "null"
4642 ]
4643 },
4644 "allow_join": {
4645 "type": [
4646 "boolean",
4647 "null"
4648 ]
4649 },
4650 "allow_join_level": {
4651 "type": [
4652 "integer",
4653 "null"
4654 ],
4655 "format": "int32"
4656 },
4657 "avatar_url": {
4658 "type": [
4659 "string",
4660 "null"
4661 ]
4662 },
4663 "description": {
4664 "type": [
4665 "string",
4666 "null"
4667 ]
4668 },
4669 "join_policy": {
4670 "oneOf": [
4671 {
4672 "$ref": "#/components/schemas/OrganizationJoinPolicy"
4673 },
4674 {
4675 "type": "null"
4676 }
4677 ]
4678 },
4679 "member_must_enable_2fa": {
4680 "type": [
4681 "boolean",
4682 "null"
4683 ]
4684 },
4685 "name": {
4686 "type": "string"
4687 },
4688 "require_email_verified": {
4689 "type": [
4690 "boolean",
4691 "null"
4692 ]
4693 },
4694 "require_mfa": {
4695 "type": [
4696 "boolean",
4697 "null"
4698 ]
4699 },
4700 "slug": {
4701 "type": "string"
4702 },
4703 "welcome_message": {
4704 "type": [
4705 "string",
4706 "null"
4707 ]
4708 }
4709 }
4710 },
4711 "CreateRoleBody": {
4712 "type": "object",
4713 "required": [
4714 "key",
4715 "name"
4716 ],
4717 "properties": {
4718 "default_role": {
4719 "type": "boolean"
4720 },
4721 "description": {
4722 "type": [
4723 "string",
4724 "null"
4725 ]
4726 },
4727 "key": {
4728 "type": "string"
4729 },
4730 "name": {
4731 "type": "string"
4732 },
4733 "permissions": {
4734 "type": "array",
4735 "items": {
4736 "$ref": "#/components/schemas/PermissionGrant"
4737 }
4738 },
4739 "rank": {
4740 "type": "integer",
4741 "format": "int32"
4742 },
4743 "self_assignable": {
4744 "type": "boolean"
4745 }
4746 }
4747 },
4748 "DeleteOrgQuery": {
4749 "type": "object",
4750 "properties": {
4751 "force": {
4752 "type": "boolean"
4753 },
4754 "reason": {
4755 "type": [
4756 "string",
4757 "null"
4758 ]
4759 }
4760 }
4761 },
4762 "Envelope_AccessTokenCreateResponse": {
4763 "type": "object",
4764 "required": [
4765 "code",
4766 "message"
4767 ],
4768 "properties": {
4769 "code": {
4770 "type": "integer",
4771 "format": "int32"
4772 },
4773 "data": {
4774 "type": "object",
4775 "required": [
4776 "id",
4777 "name",
4778 "token",
4779 "scope",
4780 "created_at"
4781 ],
4782 "properties": {
4783 "created_at": {
4784 "type": "string",
4785 "format": "date-time"
4786 },
4787 "expires_at": {
4788 "type": [
4789 "string",
4790 "null"
4791 ],
4792 "format": "date-time"
4793 },
4794 "id": {
4795 "type": "string",
4796 "format": "uuid"
4797 },
4798 "name": {
4799 "type": "string"
4800 },
4801 "scope": {
4802 "type": "array",
4803 "items": {
4804 "type": "string"
4805 }
4806 },
4807 "token": {
4808 "type": "string"
4809 }
4810 }
4811 },
4812 "error": {
4813 "type": [
4814 "string",
4815 "null"
4816 ]
4817 },
4818 "error_code": {
4819 "type": [
4820 "string",
4821 "null"
4822 ]
4823 },
4824 "message": {
4825 "type": "string"
4826 },
4827 "request_id": {
4828 "type": "string"
4829 }
4830 }
4831 },
4832 "Envelope_AppTokenResponse": {
4833 "type": "object",
4834 "required": [
4835 "code",
4836 "message"
4837 ],
4838 "properties": {
4839 "code": {
4840 "type": "integer",
4841 "format": "int32"
4842 },
4843 "data": {
4844 "type": "object",
4845 "required": [
4846 "app_id",
4847 "audience",
4848 "key_id",
4849 "token",
4850 "issued_at",
4851 "expires_at"
4852 ],
4853 "properties": {
4854 "app_id": {
4855 "type": "string"
4856 },
4857 "audience": {
4858 "type": "string"
4859 },
4860 "expires_at": {
4861 "type": "string",
4862 "format": "date-time"
4863 },
4864 "issued_at": {
4865 "type": "string",
4866 "format": "date-time"
4867 },
4868 "key_id": {
4869 "type": "string"
4870 },
4871 "token": {
4872 "type": "string"
4873 }
4874 }
4875 },
4876 "error": {
4877 "type": [
4878 "string",
4879 "null"
4880 ]
4881 },
4882 "error_code": {
4883 "type": [
4884 "string",
4885 "null"
4886 ]
4887 },
4888 "message": {
4889 "type": "string"
4890 },
4891 "request_id": {
4892 "type": "string"
4893 }
4894 }
4895 },
4896 "Envelope_ChallengeResponse": {
4897 "type": "object",
4898 "required": [
4899 "code",
4900 "message"
4901 ],
4902 "properties": {
4903 "code": {
4904 "type": "integer",
4905 "format": "int32"
4906 },
4907 "data": {
4908 "type": "object",
4909 "required": [
4910 "context",
4911 "difficulty",
4912 "issued_at",
4913 "expires_at"
4914 ],
4915 "properties": {
4916 "context": {
4917 "type": "string"
4918 },
4919 "difficulty": {
4920 "type": "integer",
4921 "format": "int32",
4922 "minimum": 0
4923 },
4924 "expires_at": {
4925 "type": "integer",
4926 "format": "int64",
4927 "minimum": 0
4928 },
4929 "issued_at": {
4930 "type": "integer",
4931 "format": "int64",
4932 "minimum": 0
4933 }
4934 }
4935 },
4936 "error": {
4937 "type": [
4938 "string",
4939 "null"
4940 ]
4941 },
4942 "error_code": {
4943 "type": [
4944 "string",
4945 "null"
4946 ]
4947 },
4948 "message": {
4949 "type": "string"
4950 },
4951 "request_id": {
4952 "type": "string"
4953 }
4954 }
4955 },
4956 "Envelope_CreateInviteResponse": {
4957 "type": "object",
4958 "required": [
4959 "code",
4960 "message"
4961 ],
4962 "properties": {
4963 "code": {
4964 "type": "integer",
4965 "format": "int32"
4966 },
4967 "data": {
4968 "type": "object",
4969 "required": [
4970 "invite",
4971 "token"
4972 ],
4973 "properties": {
4974 "invite": {
4975 "$ref": "#/components/schemas/InviteSummary"
4976 },
4977 "token": {
4978 "type": "string"
4979 }
4980 }
4981 },
4982 "error": {
4983 "type": [
4984 "string",
4985 "null"
4986 ]
4987 },
4988 "error_code": {
4989 "type": [
4990 "string",
4991 "null"
4992 ]
4993 },
4994 "message": {
4995 "type": "string"
4996 },
4997 "request_id": {
4998 "type": "string"
4999 }
5000 }
5001 },
5002 "Envelope_HealthResponse": {
5003 "type": "object",
5004 "required": [
5005 "code",
5006 "message"
5007 ],
5008 "properties": {
5009 "code": {
5010 "type": "integer",
5011 "format": "int32"
5012 },
5013 "data": {
5014 "type": "object",
5015 "required": [
5016 "status",
5017 "version"
5018 ],
5019 "properties": {
5020 "status": {
5021 "type": "string"
5022 },
5023 "version": {
5024 "type": "string"
5025 }
5026 }
5027 },
5028 "error": {
5029 "type": [
5030 "string",
5031 "null"
5032 ]
5033 },
5034 "error_code": {
5035 "type": [
5036 "string",
5037 "null"
5038 ]
5039 },
5040 "message": {
5041 "type": "string"
5042 },
5043 "request_id": {
5044 "type": "string"
5045 }
5046 }
5047 },
5048 "Envelope_LoginCodeResponse": {
5049 "type": "object",
5050 "required": [
5051 "code",
5052 "message"
5053 ],
5054 "properties": {
5055 "code": {
5056 "type": "integer",
5057 "format": "int32"
5058 },
5059 "data": {
5060 "type": "object",
5061 "required": [
5062 "expires_in_secs"
5063 ],
5064 "properties": {
5065 "expires_in_secs": {
5066 "type": "integer",
5067 "format": "int64",
5068 "minimum": 0
5069 }
5070 }
5071 },
5072 "error": {
5073 "type": [
5074 "string",
5075 "null"
5076 ]
5077 },
5078 "error_code": {
5079 "type": [
5080 "string",
5081 "null"
5082 ]
5083 },
5084 "message": {
5085 "type": "string"
5086 },
5087 "request_id": {
5088 "type": "string"
5089 }
5090 }
5091 },
5092 "Envelope_LogoutResponse": {
5093 "type": "object",
5094 "required": [
5095 "code",
5096 "message"
5097 ],
5098 "properties": {
5099 "code": {
5100 "type": "integer",
5101 "format": "int32"
5102 },
5103 "data": {
5104 "type": "object",
5105 "properties": {
5106 "user_id": {
5107 "type": [
5108 "integer",
5109 "null"
5110 ],
5111 "format": "int64"
5112 }
5113 }
5114 },
5115 "error": {
5116 "type": [
5117 "string",
5118 "null"
5119 ]
5120 },
5121 "error_code": {
5122 "type": [
5123 "string",
5124 "null"
5125 ]
5126 },
5127 "message": {
5128 "type": "string"
5129 },
5130 "request_id": {
5131 "type": "string"
5132 }
5133 }
5134 },
5135 "Envelope_MeResponse": {
5136 "type": "object",
5137 "required": [
5138 "code",
5139 "message"
5140 ],
5141 "properties": {
5142 "code": {
5143 "type": "integer",
5144 "format": "int32"
5145 },
5146 "data": {
5147 "type": "object",
5148 "required": [
5149 "user_id",
5150 "username",
5151 "email",
5152 "is_admin",
5153 "created_at",
5154 "version"
5155 ],
5156 "properties": {
5157 "avatar_url": {
5158 "type": [
5159 "string",
5160 "null"
5161 ]
5162 },
5163 "created_at": {
5164 "type": "string",
5165 "format": "date-time"
5166 },
5167 "displayname": {
5168 "type": [
5169 "string",
5170 "null"
5171 ]
5172 },
5173 "email": {
5174 "type": "string"
5175 },
5176 "email_verified_at": {
5177 "type": [
5178 "string",
5179 "null"
5180 ],
5181 "format": "date-time"
5182 },
5183 "is_admin": {
5184 "type": "boolean"
5185 },
5186 "user_id": {
5187 "type": "integer",
5188 "format": "int64"
5189 },
5190 "username": {
5191 "type": "string"
5192 },
5193 "version": {
5194 "type": "integer",
5195 "format": "int32"
5196 }
5197 }
5198 },
5199 "error": {
5200 "type": [
5201 "string",
5202 "null"
5203 ]
5204 },
5205 "error_code": {
5206 "type": [
5207 "string",
5208 "null"
5209 ]
5210 },
5211 "message": {
5212 "type": "string"
5213 },
5214 "request_id": {
5215 "type": "string"
5216 }
5217 }
5218 },
5219 "Envelope_MemberSummary": {
5220 "type": "object",
5221 "required": [
5222 "code",
5223 "message"
5224 ],
5225 "properties": {
5226 "code": {
5227 "type": "integer",
5228 "format": "int32"
5229 },
5230 "data": {
5231 "type": "object",
5232 "required": [
5233 "id",
5234 "organization_id",
5235 "user_id",
5236 "username",
5237 "role",
5238 "role_keys",
5239 "permissions",
5240 "joined_at",
5241 "join_method",
5242 "message_notifications",
5243 "muted",
5244 "mobile_push",
5245 "suppress_everyone",
5246 "suppress_roles",
5247 "hide_muted_channels",
5248 "ban_allow_agent_join",
5249 "ban_allow_speak",
5250 "ban_allow_push_repo",
5251 "ban_allow_comment_any",
5252 "version"
5253 ],
5254 "properties": {
5255 "alias_avatar_url": {
5256 "type": [
5257 "string",
5258 "null"
5259 ]
5260 },
5261 "alias_name": {
5262 "type": [
5263 "string",
5264 "null"
5265 ]
5266 },
5267 "ban_allow_agent_join": {
5268 "type": "boolean"
5269 },
5270 "ban_allow_comment_any": {
5271 "type": "boolean"
5272 },
5273 "ban_allow_push_repo": {
5274 "type": "boolean"
5275 },
5276 "ban_allow_speak": {
5277 "type": "boolean"
5278 },
5279 "ban_expires_at": {
5280 "type": [
5281 "string",
5282 "null"
5283 ],
5284 "format": "date-time"
5285 },
5286 "ban_reason": {
5287 "type": [
5288 "string",
5289 "null"
5290 ]
5291 },
5292 "banned_at": {
5293 "type": [
5294 "string",
5295 "null"
5296 ],
5297 "format": "date-time"
5298 },
5299 "displayname": {
5300 "type": [
5301 "string",
5302 "null"
5303 ]
5304 },
5305 "hide_muted_channels": {
5306 "type": "boolean"
5307 },
5308 "id": {
5309 "type": "string",
5310 "format": "uuid"
5311 },
5312 "invited_by": {
5313 "type": [
5314 "integer",
5315 "null"
5316 ],
5317 "format": "int64"
5318 },
5319 "join_method": {
5320 "$ref": "#/components/schemas/OrgJoinMethod"
5321 },
5322 "joined_at": {
5323 "type": "string",
5324 "format": "date-time"
5325 },
5326 "last_active_at": {
5327 "type": [
5328 "string",
5329 "null"
5330 ],
5331 "format": "date-time"
5332 },
5333 "left_at": {
5334 "type": [
5335 "string",
5336 "null"
5337 ],
5338 "format": "date-time"
5339 },
5340 "message_notifications": {
5341 "$ref": "#/components/schemas/MessageNotifications"
5342 },
5343 "mobile_push": {
5344 "type": "boolean"
5345 },
5346 "mute_until": {
5347 "type": [
5348 "string",
5349 "null"
5350 ],
5351 "format": "date-time"
5352 },
5353 "muted": {
5354 "type": "boolean"
5355 },
5356 "organization_id": {
5357 "type": "string",
5358 "format": "uuid"
5359 },
5360 "permissions": {
5361 "type": "array",
5362 "items": {
5363 "type": "string"
5364 }
5365 },
5366 "role": {
5367 "$ref": "#/components/schemas/OrgMemberRole"
5368 },
5369 "role_keys": {
5370 "type": "array",
5371 "items": {
5372 "type": "string"
5373 }
5374 },
5375 "suppress_everyone": {
5376 "type": "boolean"
5377 },
5378 "suppress_roles": {
5379 "type": "boolean"
5380 },
5381 "user_id": {
5382 "type": "integer",
5383 "format": "int64"
5384 },
5385 "username": {
5386 "type": "string"
5387 },
5388 "version": {
5389 "type": "integer",
5390 "format": "int32"
5391 }
5392 }
5393 },
5394 "error": {
5395 "type": [
5396 "string",
5397 "null"
5398 ]
5399 },
5400 "error_code": {
5401 "type": [
5402 "string",
5403 "null"
5404 ]
5405 },
5406 "message": {
5407 "type": "string"
5408 },
5409 "request_id": {
5410 "type": "string"
5411 }
5412 }
5413 },
5414 "Envelope_NoData": {
5415 "type": "object",
5416 "required": [
5417 "code",
5418 "message"
5419 ],
5420 "properties": {
5421 "code": {
5422 "type": "integer",
5423 "format": "int32"
5424 },
5425 "data": {
5426 "default": null
5427 },
5428 "error": {
5429 "type": [
5430 "string",
5431 "null"
5432 ]
5433 },
5434 "error_code": {
5435 "type": [
5436 "string",
5437 "null"
5438 ]
5439 },
5440 "message": {
5441 "type": "string"
5442 },
5443 "request_id": {
5444 "type": "string"
5445 }
5446 }
5447 },
5448 "Envelope_OrganizationSummary": {
5449 "type": "object",
5450 "required": [
5451 "code",
5452 "message"
5453 ],
5454 "properties": {
5455 "code": {
5456 "type": "integer",
5457 "format": "int32"
5458 },
5459 "data": {
5460 "type": "object",
5461 "required": [
5462 "id",
5463 "slug",
5464 "name",
5465 "status",
5466 "owner_user_id",
5467 "member_count",
5468 "version",
5469 "created_at",
5470 "updated_at",
5471 "join_policy",
5472 "allow_join",
5473 "require_email_verified",
5474 "require_mfa",
5475 "member_must_enable_2fa",
5476 "allow_invite",
5477 "allow_join_level"
5478 ],
5479 "properties": {
5480 "allow_invite": {
5481 "type": "boolean"
5482 },
5483 "allow_join": {
5484 "type": "boolean"
5485 },
5486 "allow_join_level": {
5487 "type": "integer",
5488 "format": "int32"
5489 },
5490 "archived_at": {
5491 "type": [
5492 "string",
5493 "null"
5494 ],
5495 "format": "date-time"
5496 },
5497 "avatar_url": {
5498 "type": [
5499 "string",
5500 "null"
5501 ]
5502 },
5503 "created_at": {
5504 "type": "string",
5505 "format": "date-time"
5506 },
5507 "description": {
5508 "type": [
5509 "string",
5510 "null"
5511 ]
5512 },
5513 "id": {
5514 "type": "string",
5515 "format": "uuid"
5516 },
5517 "join_policy": {
5518 "$ref": "#/components/schemas/OrganizationJoinPolicy"
5519 },
5520 "member_count": {
5521 "type": "integer",
5522 "format": "int64"
5523 },
5524 "member_must_enable_2fa": {
5525 "type": "boolean"
5526 },
5527 "my_role": {
5528 "oneOf": [
5529 {
5530 "$ref": "#/components/schemas/OrgMemberRole"
5531 },
5532 {
5533 "type": "null"
5534 }
5535 ]
5536 },
5537 "name": {
5538 "type": "string"
5539 },
5540 "owner_user_id": {
5541 "type": "integer",
5542 "format": "int64"
5543 },
5544 "require_email_verified": {
5545 "type": "boolean"
5546 },
5547 "require_mfa": {
5548 "type": "boolean"
5549 },
5550 "slug": {
5551 "type": "string"
5552 },
5553 "status": {
5554 "$ref": "#/components/schemas/OrganizationStatus"
5555 },
5556 "updated_at": {
5557 "type": "string",
5558 "format": "date-time"
5559 },
5560 "version": {
5561 "type": "integer",
5562 "format": "int32"
5563 },
5564 "welcome_message": {
5565 "type": [
5566 "string",
5567 "null"
5568 ]
5569 }
5570 }
5571 },
5572 "error": {
5573 "type": [
5574 "string",
5575 "null"
5576 ]
5577 },
5578 "error_code": {
5579 "type": [
5580 "string",
5581 "null"
5582 ]
5583 },
5584 "message": {
5585 "type": "string"
5586 },
5587 "request_id": {
5588 "type": "string"
5589 }
5590 }
5591 },
5592 "Envelope_PasswordResetConfirmResponse": {
5593 "type": "object",
5594 "required": [
5595 "code",
5596 "message"
5597 ],
5598 "properties": {
5599 "code": {
5600 "type": "integer",
5601 "format": "int32"
5602 },
5603 "data": {
5604 "type": "object",
5605 "required": [
5606 "user_id"
5607 ],
5608 "properties": {
5609 "user_id": {
5610 "type": "integer",
5611 "format": "int64"
5612 }
5613 }
5614 },
5615 "error": {
5616 "type": [
5617 "string",
5618 "null"
5619 ]
5620 },
5621 "error_code": {
5622 "type": [
5623 "string",
5624 "null"
5625 ]
5626 },
5627 "message": {
5628 "type": "string"
5629 },
5630 "request_id": {
5631 "type": "string"
5632 }
5633 }
5634 },
5635 "Envelope_PasswordResetResponse": {
5636 "type": "object",
5637 "required": [
5638 "code",
5639 "message"
5640 ],
5641 "properties": {
5642 "code": {
5643 "type": "integer",
5644 "format": "int32"
5645 },
5646 "data": {
5647 "type": "object",
5648 "required": [
5649 "expires_in_secs"
5650 ],
5651 "properties": {
5652 "expires_in_secs": {
5653 "type": "integer",
5654 "format": "int64",
5655 "minimum": 0
5656 }
5657 }
5658 },
5659 "error": {
5660 "type": [
5661 "string",
5662 "null"
5663 ]
5664 },
5665 "error_code": {
5666 "type": [
5667 "string",
5668 "null"
5669 ]
5670 },
5671 "message": {
5672 "type": "string"
5673 },
5674 "request_id": {
5675 "type": "string"
5676 }
5677 }
5678 },
5679 "Envelope_PermissionCheck": {
5680 "type": "object",
5681 "required": [
5682 "code",
5683 "message"
5684 ],
5685 "properties": {
5686 "code": {
5687 "type": "integer",
5688 "format": "int32"
5689 },
5690 "data": {
5691 "type": "object",
5692 "required": [
5693 "allowed",
5694 "reason",
5695 "matched_by",
5696 "organization_version"
5697 ],
5698 "properties": {
5699 "allowed": {
5700 "type": "boolean"
5701 },
5702 "matched_by": {
5703 "type": "string"
5704 },
5705 "organization_version": {
5706 "type": "integer",
5707 "format": "int32"
5708 },
5709 "reason": {
5710 "type": "string"
5711 }
5712 }
5713 },
5714 "error": {
5715 "type": [
5716 "string",
5717 "null"
5718 ]
5719 },
5720 "error_code": {
5721 "type": [
5722 "string",
5723 "null"
5724 ]
5725 },
5726 "message": {
5727 "type": "string"
5728 },
5729 "request_id": {
5730 "type": "string"
5731 }
5732 }
5733 },
5734 "Envelope_ReadinessResponse": {
5735 "type": "object",
5736 "required": [
5737 "code",
5738 "message"
5739 ],
5740 "properties": {
5741 "code": {
5742 "type": "integer",
5743 "format": "int32"
5744 },
5745 "data": {
5746 "type": "object",
5747 "required": [
5748 "status",
5749 "checks"
5750 ],
5751 "properties": {
5752 "checks": {
5753 "type": "array",
5754 "items": {
5755 "$ref": "#/components/schemas/ComponentCheck"
5756 }
5757 },
5758 "status": {
5759 "type": "string"
5760 }
5761 }
5762 },
5763 "error": {
5764 "type": [
5765 "string",
5766 "null"
5767 ]
5768 },
5769 "error_code": {
5770 "type": [
5771 "string",
5772 "null"
5773 ]
5774 },
5775 "message": {
5776 "type": "string"
5777 },
5778 "request_id": {
5779 "type": "string"
5780 }
5781 }
5782 },
5783 "Envelope_RedeemInviteResponse": {
5784 "type": "object",
5785 "required": [
5786 "code",
5787 "message"
5788 ],
5789 "properties": {
5790 "code": {
5791 "type": "integer",
5792 "format": "int32"
5793 },
5794 "data": {
5795 "type": "object",
5796 "required": [
5797 "organization_id",
5798 "member_id",
5799 "joined"
5800 ],
5801 "properties": {
5802 "joined": {
5803 "type": "boolean"
5804 },
5805 "member_id": {
5806 "type": "string",
5807 "format": "uuid"
5808 },
5809 "organization_id": {
5810 "type": "string",
5811 "format": "uuid"
5812 }
5813 }
5814 },
5815 "error": {
5816 "type": [
5817 "string",
5818 "null"
5819 ]
5820 },
5821 "error_code": {
5822 "type": [
5823 "string",
5824 "null"
5825 ]
5826 },
5827 "message": {
5828 "type": "string"
5829 },
5830 "request_id": {
5831 "type": "string"
5832 }
5833 }
5834 },
5835 "Envelope_RegisterCodeResponse": {
5836 "type": "object",
5837 "required": [
5838 "code",
5839 "message"
5840 ],
5841 "properties": {
5842 "code": {
5843 "type": "integer",
5844 "format": "int32"
5845 },
5846 "data": {
5847 "type": "object",
5848 "required": [
5849 "expires_in_secs"
5850 ],
5851 "properties": {
5852 "expires_in_secs": {
5853 "type": "integer",
5854 "format": "int64",
5855 "minimum": 0
5856 }
5857 }
5858 },
5859 "error": {
5860 "type": [
5861 "string",
5862 "null"
5863 ]
5864 },
5865 "error_code": {
5866 "type": [
5867 "string",
5868 "null"
5869 ]
5870 },
5871 "message": {
5872 "type": "string"
5873 },
5874 "request_id": {
5875 "type": "string"
5876 }
5877 }
5878 },
5879 "Envelope_RoleSummary": {
5880 "type": "object",
5881 "required": [
5882 "code",
5883 "message"
5884 ],
5885 "properties": {
5886 "code": {
5887 "type": "integer",
5888 "format": "int32"
5889 },
5890 "data": {
5891 "type": "object",
5892 "required": [
5893 "id",
5894 "organization_id",
5895 "key",
5896 "name",
5897 "rank",
5898 "system",
5899 "default_role",
5900 "self_assignable",
5901 "permissions",
5902 "member_count",
5903 "created_at",
5904 "updated_at",
5905 "version",
5906 "position"
5907 ],
5908 "properties": {
5909 "color": {
5910 "type": [
5911 "string",
5912 "null"
5913 ]
5914 },
5915 "created_at": {
5916 "type": "string",
5917 "format": "date-time"
5918 },
5919 "default_role": {
5920 "type": "boolean"
5921 },
5922 "description": {
5923 "type": [
5924 "string",
5925 "null"
5926 ]
5927 },
5928 "emoji": {
5929 "type": [
5930 "string",
5931 "null"
5932 ]
5933 },
5934 "icon_hash": {
5935 "type": [
5936 "string",
5937 "null"
5938 ]
5939 },
5940 "id": {
5941 "type": "string",
5942 "format": "uuid"
5943 },
5944 "key": {
5945 "type": "string"
5946 },
5947 "member_count": {
5948 "type": "integer",
5949 "format": "int64"
5950 },
5951 "name": {
5952 "type": "string"
5953 },
5954 "organization_id": {
5955 "type": "string",
5956 "format": "uuid"
5957 },
5958 "permissions": {
5959 "type": "array",
5960 "items": {
5961 "$ref": "#/components/schemas/PermissionGrant"
5962 }
5963 },
5964 "position": {
5965 "type": "integer",
5966 "format": "int64"
5967 },
5968 "rank": {
5969 "type": "integer",
5970 "format": "int32"
5971 },
5972 "self_assignable": {
5973 "type": "boolean"
5974 },
5975 "system": {
5976 "type": "boolean"
5977 },
5978 "updated_at": {
5979 "type": "string",
5980 "format": "date-time"
5981 },
5982 "version": {
5983 "type": "integer",
5984 "format": "int32"
5985 }
5986 }
5987 },
5988 "error": {
5989 "type": [
5990 "string",
5991 "null"
5992 ]
5993 },
5994 "error_code": {
5995 "type": [
5996 "string",
5997 "null"
5998 ]
5999 },
6000 "message": {
6001 "type": "string"
6002 },
6003 "request_id": {
6004 "type": "string"
6005 }
6006 }
6007 },
6008 "Envelope_SshKeySummary": {
6009 "type": "object",
6010 "required": [
6011 "code",
6012 "message"
6013 ],
6014 "properties": {
6015 "code": {
6016 "type": "integer",
6017 "format": "int32"
6018 },
6019 "data": {
6020 "type": "object",
6021 "required": [
6022 "id",
6023 "name",
6024 "algorithm",
6025 "fingerprint",
6026 "public_key",
6027 "created_at"
6028 ],
6029 "properties": {
6030 "algorithm": {
6031 "$ref": "#/components/schemas/SshKeyAlgorithm"
6032 },
6033 "created_at": {
6034 "type": "string",
6035 "format": "date-time"
6036 },
6037 "description": {
6038 "type": [
6039 "string",
6040 "null"
6041 ]
6042 },
6043 "expires_at": {
6044 "type": [
6045 "string",
6046 "null"
6047 ],
6048 "format": "date-time"
6049 },
6050 "fingerprint": {
6051 "type": "string"
6052 },
6053 "id": {
6054 "type": "string",
6055 "format": "uuid"
6056 },
6057 "last_used_at": {
6058 "type": [
6059 "string",
6060 "null"
6061 ],
6062 "format": "date-time"
6063 },
6064 "name": {
6065 "type": "string"
6066 },
6067 "public_key": {
6068 "type": "string"
6069 },
6070 "revoked_at": {
6071 "type": [
6072 "string",
6073 "null"
6074 ],
6075 "format": "date-time"
6076 }
6077 }
6078 },
6079 "error": {
6080 "type": [
6081 "string",
6082 "null"
6083 ]
6084 },
6085 "error_code": {
6086 "type": [
6087 "string",
6088 "null"
6089 ]
6090 },
6091 "message": {
6092 "type": "string"
6093 },
6094 "request_id": {
6095 "type": "string"
6096 }
6097 }
6098 },
6099 "Envelope_TokenResponse": {
6100 "type": "object",
6101 "required": [
6102 "code",
6103 "message"
6104 ],
6105 "properties": {
6106 "code": {
6107 "type": "integer",
6108 "format": "int32"
6109 },
6110 "data": {
6111 "type": "object",
6112 "required": [
6113 "token",
6114 "token_type",
6115 "kid",
6116 "issued_at",
6117 "expires_at"
6118 ],
6119 "properties": {
6120 "expires_at": {
6121 "type": "string",
6122 "format": "date-time"
6123 },
6124 "issued_at": {
6125 "type": "string",
6126 "format": "date-time"
6127 },
6128 "kid": {
6129 "type": "string"
6130 },
6131 "token": {
6132 "type": "string"
6133 },
6134 "token_type": {
6135 "type": "string"
6136 }
6137 }
6138 },
6139 "error": {
6140 "type": [
6141 "string",
6142 "null"
6143 ]
6144 },
6145 "error_code": {
6146 "type": [
6147 "string",
6148 "null"
6149 ]
6150 },
6151 "message": {
6152 "type": "string"
6153 },
6154 "request_id": {
6155 "type": "string"
6156 }
6157 }
6158 },
6159 "Envelope_UserLoginResponse": {
6160 "type": "object",
6161 "required": [
6162 "code",
6163 "message"
6164 ],
6165 "properties": {
6166 "code": {
6167 "type": "integer",
6168 "format": "int32"
6169 },
6170 "data": {
6171 "type": "object",
6172 "required": [
6173 "user_id",
6174 "username",
6175 "mfa_required"
6176 ],
6177 "properties": {
6178 "challenge": {
6179 "type": [
6180 "string",
6181 "null"
6182 ]
6183 },
6184 "displayname": {
6185 "type": [
6186 "string",
6187 "null"
6188 ]
6189 },
6190 "mfa_required": {
6191 "type": "boolean"
6192 },
6193 "user_id": {
6194 "type": "integer",
6195 "format": "int64"
6196 },
6197 "username": {
6198 "type": "string"
6199 }
6200 }
6201 },
6202 "error": {
6203 "type": [
6204 "string",
6205 "null"
6206 ]
6207 },
6208 "error_code": {
6209 "type": [
6210 "string",
6211 "null"
6212 ]
6213 },
6214 "message": {
6215 "type": "string"
6216 },
6217 "request_id": {
6218 "type": "string"
6219 }
6220 }
6221 },
6222 "Envelope_UserRegisterResponse": {
6223 "type": "object",
6224 "required": [
6225 "code",
6226 "message"
6227 ],
6228 "properties": {
6229 "code": {
6230 "type": "integer",
6231 "format": "int32"
6232 },
6233 "data": {
6234 "type": "object",
6235 "required": [
6236 "user_id",
6237 "username"
6238 ],
6239 "properties": {
6240 "displayname": {
6241 "type": [
6242 "string",
6243 "null"
6244 ]
6245 },
6246 "user_id": {
6247 "type": "integer",
6248 "format": "int64"
6249 },
6250 "username": {
6251 "type": "string"
6252 }
6253 }
6254 },
6255 "error": {
6256 "type": [
6257 "string",
6258 "null"
6259 ]
6260 },
6261 "error_code": {
6262 "type": [
6263 "string",
6264 "null"
6265 ]
6266 },
6267 "message": {
6268 "type": "string"
6269 },
6270 "request_id": {
6271 "type": "string"
6272 }
6273 }
6274 },
6275 "Envelope_Vec_AccessTokenSummary": {
6276 "type": "object",
6277 "required": [
6278 "code",
6279 "message"
6280 ],
6281 "properties": {
6282 "code": {
6283 "type": "integer",
6284 "format": "int32"
6285 },
6286 "data": {
6287 "type": "array",
6288 "items": {
6289 "type": "object",
6290 "required": [
6291 "id",
6292 "name",
6293 "scope",
6294 "created_at"
6295 ],
6296 "properties": {
6297 "created_at": {
6298 "type": "string",
6299 "format": "date-time"
6300 },
6301 "description": {
6302 "type": [
6303 "string",
6304 "null"
6305 ]
6306 },
6307 "expires_at": {
6308 "type": [
6309 "string",
6310 "null"
6311 ],
6312 "format": "date-time"
6313 },
6314 "fingerprint": {
6315 "type": [
6316 "string",
6317 "null"
6318 ]
6319 },
6320 "id": {
6321 "type": "string",
6322 "format": "uuid"
6323 },
6324 "last_used_at": {
6325 "type": [
6326 "string",
6327 "null"
6328 ],
6329 "format": "date-time"
6330 },
6331 "name": {
6332 "type": "string"
6333 },
6334 "revoked_at": {
6335 "type": [
6336 "string",
6337 "null"
6338 ],
6339 "format": "date-time"
6340 },
6341 "scope": {
6342 "type": "array",
6343 "items": {
6344 "type": "string"
6345 }
6346 }
6347 }
6348 }
6349 },
6350 "error": {
6351 "type": [
6352 "string",
6353 "null"
6354 ]
6355 },
6356 "error_code": {
6357 "type": [
6358 "string",
6359 "null"
6360 ]
6361 },
6362 "message": {
6363 "type": "string"
6364 },
6365 "request_id": {
6366 "type": "string"
6367 }
6368 }
6369 },
6370 "Envelope_Vec_InviteSummary": {
6371 "type": "object",
6372 "required": [
6373 "code",
6374 "message"
6375 ],
6376 "properties": {
6377 "code": {
6378 "type": "integer",
6379 "format": "int32"
6380 },
6381 "data": {
6382 "type": "array",
6383 "items": {
6384 "type": "object",
6385 "required": [
6386 "id",
6387 "organization_id",
6388 "method",
6389 "created_by",
6390 "max_uses",
6391 "uses",
6392 "created_at"
6393 ],
6394 "properties": {
6395 "created_at": {
6396 "type": "string",
6397 "format": "date-time"
6398 },
6399 "created_by": {
6400 "type": "integer",
6401 "format": "int64"
6402 },
6403 "email": {
6404 "type": [
6405 "string",
6406 "null"
6407 ]
6408 },
6409 "expires_at": {
6410 "type": [
6411 "string",
6412 "null"
6413 ],
6414 "format": "date-time"
6415 },
6416 "id": {
6417 "type": "string",
6418 "format": "uuid"
6419 },
6420 "max_uses": {
6421 "type": "integer",
6422 "format": "int32"
6423 },
6424 "method": {
6425 "$ref": "#/components/schemas/OrgJoinMethod"
6426 },
6427 "organization_id": {
6428 "type": "string",
6429 "format": "uuid"
6430 },
6431 "revoked_at": {
6432 "type": [
6433 "string",
6434 "null"
6435 ],
6436 "format": "date-time"
6437 },
6438 "role_id": {
6439 "type": [
6440 "string",
6441 "null"
6442 ],
6443 "format": "uuid"
6444 },
6445 "target_user_id": {
6446 "type": [
6447 "integer",
6448 "null"
6449 ],
6450 "format": "int64"
6451 },
6452 "uses": {
6453 "type": "integer",
6454 "format": "int32"
6455 }
6456 }
6457 }
6458 },
6459 "error": {
6460 "type": [
6461 "string",
6462 "null"
6463 ]
6464 },
6465 "error_code": {
6466 "type": [
6467 "string",
6468 "null"
6469 ]
6470 },
6471 "message": {
6472 "type": "string"
6473 },
6474 "request_id": {
6475 "type": "string"
6476 }
6477 }
6478 },
6479 "Envelope_Vec_MemberSummary": {
6480 "type": "object",
6481 "required": [
6482 "code",
6483 "message"
6484 ],
6485 "properties": {
6486 "code": {
6487 "type": "integer",
6488 "format": "int32"
6489 },
6490 "data": {
6491 "type": "array",
6492 "items": {
6493 "type": "object",
6494 "required": [
6495 "id",
6496 "organization_id",
6497 "user_id",
6498 "username",
6499 "role",
6500 "role_keys",
6501 "permissions",
6502 "joined_at",
6503 "join_method",
6504 "message_notifications",
6505 "muted",
6506 "mobile_push",
6507 "suppress_everyone",
6508 "suppress_roles",
6509 "hide_muted_channels",
6510 "ban_allow_agent_join",
6511 "ban_allow_speak",
6512 "ban_allow_push_repo",
6513 "ban_allow_comment_any",
6514 "version"
6515 ],
6516 "properties": {
6517 "alias_avatar_url": {
6518 "type": [
6519 "string",
6520 "null"
6521 ]
6522 },
6523 "alias_name": {
6524 "type": [
6525 "string",
6526 "null"
6527 ]
6528 },
6529 "ban_allow_agent_join": {
6530 "type": "boolean"
6531 },
6532 "ban_allow_comment_any": {
6533 "type": "boolean"
6534 },
6535 "ban_allow_push_repo": {
6536 "type": "boolean"
6537 },
6538 "ban_allow_speak": {
6539 "type": "boolean"
6540 },
6541 "ban_expires_at": {
6542 "type": [
6543 "string",
6544 "null"
6545 ],
6546 "format": "date-time"
6547 },
6548 "ban_reason": {
6549 "type": [
6550 "string",
6551 "null"
6552 ]
6553 },
6554 "banned_at": {
6555 "type": [
6556 "string",
6557 "null"
6558 ],
6559 "format": "date-time"
6560 },
6561 "displayname": {
6562 "type": [
6563 "string",
6564 "null"
6565 ]
6566 },
6567 "hide_muted_channels": {
6568 "type": "boolean"
6569 },
6570 "id": {
6571 "type": "string",
6572 "format": "uuid"
6573 },
6574 "invited_by": {
6575 "type": [
6576 "integer",
6577 "null"
6578 ],
6579 "format": "int64"
6580 },
6581 "join_method": {
6582 "$ref": "#/components/schemas/OrgJoinMethod"
6583 },
6584 "joined_at": {
6585 "type": "string",
6586 "format": "date-time"
6587 },
6588 "last_active_at": {
6589 "type": [
6590 "string",
6591 "null"
6592 ],
6593 "format": "date-time"
6594 },
6595 "left_at": {
6596 "type": [
6597 "string",
6598 "null"
6599 ],
6600 "format": "date-time"
6601 },
6602 "message_notifications": {
6603 "$ref": "#/components/schemas/MessageNotifications"
6604 },
6605 "mobile_push": {
6606 "type": "boolean"
6607 },
6608 "mute_until": {
6609 "type": [
6610 "string",
6611 "null"
6612 ],
6613 "format": "date-time"
6614 },
6615 "muted": {
6616 "type": "boolean"
6617 },
6618 "organization_id": {
6619 "type": "string",
6620 "format": "uuid"
6621 },
6622 "permissions": {
6623 "type": "array",
6624 "items": {
6625 "type": "string"
6626 }
6627 },
6628 "role": {
6629 "$ref": "#/components/schemas/OrgMemberRole"
6630 },
6631 "role_keys": {
6632 "type": "array",
6633 "items": {
6634 "type": "string"
6635 }
6636 },
6637 "suppress_everyone": {
6638 "type": "boolean"
6639 },
6640 "suppress_roles": {
6641 "type": "boolean"
6642 },
6643 "user_id": {
6644 "type": "integer",
6645 "format": "int64"
6646 },
6647 "username": {
6648 "type": "string"
6649 },
6650 "version": {
6651 "type": "integer",
6652 "format": "int32"
6653 }
6654 }
6655 }
6656 },
6657 "error": {
6658 "type": [
6659 "string",
6660 "null"
6661 ]
6662 },
6663 "error_code": {
6664 "type": [
6665 "string",
6666 "null"
6667 ]
6668 },
6669 "message": {
6670 "type": "string"
6671 },
6672 "request_id": {
6673 "type": "string"
6674 }
6675 }
6676 },
6677 "Envelope_Vec_OrganizationSummary": {
6678 "type": "object",
6679 "required": [
6680 "code",
6681 "message"
6682 ],
6683 "properties": {
6684 "code": {
6685 "type": "integer",
6686 "format": "int32"
6687 },
6688 "data": {
6689 "type": "array",
6690 "items": {
6691 "type": "object",
6692 "required": [
6693 "id",
6694 "slug",
6695 "name",
6696 "status",
6697 "owner_user_id",
6698 "member_count",
6699 "version",
6700 "created_at",
6701 "updated_at",
6702 "join_policy",
6703 "allow_join",
6704 "require_email_verified",
6705 "require_mfa",
6706 "member_must_enable_2fa",
6707 "allow_invite",
6708 "allow_join_level"
6709 ],
6710 "properties": {
6711 "allow_invite": {
6712 "type": "boolean"
6713 },
6714 "allow_join": {
6715 "type": "boolean"
6716 },
6717 "allow_join_level": {
6718 "type": "integer",
6719 "format": "int32"
6720 },
6721 "archived_at": {
6722 "type": [
6723 "string",
6724 "null"
6725 ],
6726 "format": "date-time"
6727 },
6728 "avatar_url": {
6729 "type": [
6730 "string",
6731 "null"
6732 ]
6733 },
6734 "created_at": {
6735 "type": "string",
6736 "format": "date-time"
6737 },
6738 "description": {
6739 "type": [
6740 "string",
6741 "null"
6742 ]
6743 },
6744 "id": {
6745 "type": "string",
6746 "format": "uuid"
6747 },
6748 "join_policy": {
6749 "$ref": "#/components/schemas/OrganizationJoinPolicy"
6750 },
6751 "member_count": {
6752 "type": "integer",
6753 "format": "int64"
6754 },
6755 "member_must_enable_2fa": {
6756 "type": "boolean"
6757 },
6758 "my_role": {
6759 "oneOf": [
6760 {
6761 "$ref": "#/components/schemas/OrgMemberRole"
6762 },
6763 {
6764 "type": "null"
6765 }
6766 ]
6767 },
6768 "name": {
6769 "type": "string"
6770 },
6771 "owner_user_id": {
6772 "type": "integer",
6773 "format": "int64"
6774 },
6775 "require_email_verified": {
6776 "type": "boolean"
6777 },
6778 "require_mfa": {
6779 "type": "boolean"
6780 },
6781 "slug": {
6782 "type": "string"
6783 },
6784 "status": {
6785 "$ref": "#/components/schemas/OrganizationStatus"
6786 },
6787 "updated_at": {
6788 "type": "string",
6789 "format": "date-time"
6790 },
6791 "version": {
6792 "type": "integer",
6793 "format": "int32"
6794 },
6795 "welcome_message": {
6796 "type": [
6797 "string",
6798 "null"
6799 ]
6800 }
6801 }
6802 }
6803 },
6804 "error": {
6805 "type": [
6806 "string",
6807 "null"
6808 ]
6809 },
6810 "error_code": {
6811 "type": [
6812 "string",
6813 "null"
6814 ]
6815 },
6816 "message": {
6817 "type": "string"
6818 },
6819 "request_id": {
6820 "type": "string"
6821 }
6822 }
6823 },
6824 "Envelope_Vec_RoleSummary": {
6825 "type": "object",
6826 "required": [
6827 "code",
6828 "message"
6829 ],
6830 "properties": {
6831 "code": {
6832 "type": "integer",
6833 "format": "int32"
6834 },
6835 "data": {
6836 "type": "array",
6837 "items": {
6838 "type": "object",
6839 "required": [
6840 "id",
6841 "organization_id",
6842 "key",
6843 "name",
6844 "rank",
6845 "system",
6846 "default_role",
6847 "self_assignable",
6848 "permissions",
6849 "member_count",
6850 "created_at",
6851 "updated_at",
6852 "version",
6853 "position"
6854 ],
6855 "properties": {
6856 "color": {
6857 "type": [
6858 "string",
6859 "null"
6860 ]
6861 },
6862 "created_at": {
6863 "type": "string",
6864 "format": "date-time"
6865 },
6866 "default_role": {
6867 "type": "boolean"
6868 },
6869 "description": {
6870 "type": [
6871 "string",
6872 "null"
6873 ]
6874 },
6875 "emoji": {
6876 "type": [
6877 "string",
6878 "null"
6879 ]
6880 },
6881 "icon_hash": {
6882 "type": [
6883 "string",
6884 "null"
6885 ]
6886 },
6887 "id": {
6888 "type": "string",
6889 "format": "uuid"
6890 },
6891 "key": {
6892 "type": "string"
6893 },
6894 "member_count": {
6895 "type": "integer",
6896 "format": "int64"
6897 },
6898 "name": {
6899 "type": "string"
6900 },
6901 "organization_id": {
6902 "type": "string",
6903 "format": "uuid"
6904 },
6905 "permissions": {
6906 "type": "array",
6907 "items": {
6908 "$ref": "#/components/schemas/PermissionGrant"
6909 }
6910 },
6911 "position": {
6912 "type": "integer",
6913 "format": "int64"
6914 },
6915 "rank": {
6916 "type": "integer",
6917 "format": "int32"
6918 },
6919 "self_assignable": {
6920 "type": "boolean"
6921 },
6922 "system": {
6923 "type": "boolean"
6924 },
6925 "updated_at": {
6926 "type": "string",
6927 "format": "date-time"
6928 },
6929 "version": {
6930 "type": "integer",
6931 "format": "int32"
6932 }
6933 }
6934 }
6935 },
6936 "error": {
6937 "type": [
6938 "string",
6939 "null"
6940 ]
6941 },
6942 "error_code": {
6943 "type": [
6944 "string",
6945 "null"
6946 ]
6947 },
6948 "message": {
6949 "type": "string"
6950 },
6951 "request_id": {
6952 "type": "string"
6953 }
6954 }
6955 },
6956 "Envelope_Vec_SshKeySummary": {
6957 "type": "object",
6958 "required": [
6959 "code",
6960 "message"
6961 ],
6962 "properties": {
6963 "code": {
6964 "type": "integer",
6965 "format": "int32"
6966 },
6967 "data": {
6968 "type": "array",
6969 "items": {
6970 "type": "object",
6971 "required": [
6972 "id",
6973 "name",
6974 "algorithm",
6975 "fingerprint",
6976 "public_key",
6977 "created_at"
6978 ],
6979 "properties": {
6980 "algorithm": {
6981 "$ref": "#/components/schemas/SshKeyAlgorithm"
6982 },
6983 "created_at": {
6984 "type": "string",
6985 "format": "date-time"
6986 },
6987 "description": {
6988 "type": [
6989 "string",
6990 "null"
6991 ]
6992 },
6993 "expires_at": {
6994 "type": [
6995 "string",
6996 "null"
6997 ],
6998 "format": "date-time"
6999 },
7000 "fingerprint": {
7001 "type": "string"
7002 },
7003 "id": {
7004 "type": "string",
7005 "format": "uuid"
7006 },
7007 "last_used_at": {
7008 "type": [
7009 "string",
7010 "null"
7011 ],
7012 "format": "date-time"
7013 },
7014 "name": {
7015 "type": "string"
7016 },
7017 "public_key": {
7018 "type": "string"
7019 },
7020 "revoked_at": {
7021 "type": [
7022 "string",
7023 "null"
7024 ],
7025 "format": "date-time"
7026 }
7027 }
7028 }
7029 },
7030 "error": {
7031 "type": [
7032 "string",
7033 "null"
7034 ]
7035 },
7036 "error_code": {
7037 "type": [
7038 "string",
7039 "null"
7040 ]
7041 },
7042 "message": {
7043 "type": "string"
7044 },
7045 "request_id": {
7046 "type": "string"
7047 }
7048 }
7049 },
7050 "Envelope_Vec_String": {
7051 "type": "object",
7052 "required": [
7053 "code",
7054 "message"
7055 ],
7056 "properties": {
7057 "code": {
7058 "type": "integer",
7059 "format": "int32"
7060 },
7061 "data": {
7062 "type": "array",
7063 "items": {
7064 "type": "string"
7065 }
7066 },
7067 "error": {
7068 "type": [
7069 "string",
7070 "null"
7071 ]
7072 },
7073 "error_code": {
7074 "type": [
7075 "string",
7076 "null"
7077 ]
7078 },
7079 "message": {
7080 "type": "string"
7081 },
7082 "request_id": {
7083 "type": "string"
7084 }
7085 }
7086 },
7087 "Envelope_bool": {
7088 "type": "object",
7089 "required": [
7090 "code",
7091 "message"
7092 ],
7093 "properties": {
7094 "code": {
7095 "type": "integer",
7096 "format": "int32"
7097 },
7098 "data": {
7099 "type": "boolean"
7100 },
7101 "error": {
7102 "type": [
7103 "string",
7104 "null"
7105 ]
7106 },
7107 "error_code": {
7108 "type": [
7109 "string",
7110 "null"
7111 ]
7112 },
7113 "message": {
7114 "type": "string"
7115 },
7116 "request_id": {
7117 "type": "string"
7118 }
7119 }
7120 },
7121 "HealthResponse": {
7122 "type": "object",
7123 "required": [
7124 "status",
7125 "version"
7126 ],
7127 "properties": {
7128 "status": {
7129 "type": "string"
7130 },
7131 "version": {
7132 "type": "string"
7133 }
7134 }
7135 },
7136 "InviteSummary": {
7137 "type": "object",
7138 "required": [
7139 "id",
7140 "organization_id",
7141 "method",
7142 "created_by",
7143 "max_uses",
7144 "uses",
7145 "created_at"
7146 ],
7147 "properties": {
7148 "created_at": {
7149 "type": "string",
7150 "format": "date-time"
7151 },
7152 "created_by": {
7153 "type": "integer",
7154 "format": "int64"
7155 },
7156 "email": {
7157 "type": [
7158 "string",
7159 "null"
7160 ]
7161 },
7162 "expires_at": {
7163 "type": [
7164 "string",
7165 "null"
7166 ],
7167 "format": "date-time"
7168 },
7169 "id": {
7170 "type": "string",
7171 "format": "uuid"
7172 },
7173 "max_uses": {
7174 "type": "integer",
7175 "format": "int32"
7176 },
7177 "method": {
7178 "$ref": "#/components/schemas/OrgJoinMethod"
7179 },
7180 "organization_id": {
7181 "type": "string",
7182 "format": "uuid"
7183 },
7184 "revoked_at": {
7185 "type": [
7186 "string",
7187 "null"
7188 ],
7189 "format": "date-time"
7190 },
7191 "role_id": {
7192 "type": [
7193 "string",
7194 "null"
7195 ],
7196 "format": "uuid"
7197 },
7198 "target_user_id": {
7199 "type": [
7200 "integer",
7201 "null"
7202 ],
7203 "format": "int64"
7204 },
7205 "uses": {
7206 "type": "integer",
7207 "format": "int32"
7208 }
7209 }
7210 },
7211 "LoginCodeRequest": {
7212 "type": "object",
7213 "required": [
7214 "email",
7215 "pow_payload"
7216 ],
7217 "properties": {
7218 "email": {
7219 "type": "string"
7220 },
7221 "pow_payload": {
7222 "$ref": "#/components/schemas/PowProofRequest"
7223 }
7224 }
7225 },
7226 "LoginCodeResponse": {
7227 "type": "object",
7228 "required": [
7229 "expires_in_secs"
7230 ],
7231 "properties": {
7232 "expires_in_secs": {
7233 "type": "integer",
7234 "format": "int64",
7235 "minimum": 0
7236 }
7237 }
7238 },
7239 "LoginCodeVerifyRequest": {
7240 "type": "object",
7241 "required": [
7242 "email",
7243 "email_code",
7244 "pow_payload"
7245 ],
7246 "properties": {
7247 "email": {
7248 "type": "string"
7249 },
7250 "email_code": {
7251 "type": "string"
7252 },
7253 "pow_payload": {
7254 "$ref": "#/components/schemas/PowProofRequest"
7255 }
7256 }
7257 },
7258 "LoginVerifyRequest": {
7259 "type": "object",
7260 "required": [
7261 "challenge",
7262 "code"
7263 ],
7264 "properties": {
7265 "challenge": {
7266 "type": "string"
7267 },
7268 "code": {
7269 "type": "string"
7270 }
7271 }
7272 },
7273 "LogoutResponse": {
7274 "type": "object",
7275 "properties": {
7276 "user_id": {
7277 "type": [
7278 "integer",
7279 "null"
7280 ],
7281 "format": "int64"
7282 }
7283 }
7284 },
7285 "MeResponse": {
7286 "type": "object",
7287 "required": [
7288 "user_id",
7289 "username",
7290 "email",
7291 "is_admin",
7292 "created_at",
7293 "version"
7294 ],
7295 "properties": {
7296 "avatar_url": {
7297 "type": [
7298 "string",
7299 "null"
7300 ]
7301 },
7302 "created_at": {
7303 "type": "string",
7304 "format": "date-time"
7305 },
7306 "displayname": {
7307 "type": [
7308 "string",
7309 "null"
7310 ]
7311 },
7312 "email": {
7313 "type": "string"
7314 },
7315 "email_verified_at": {
7316 "type": [
7317 "string",
7318 "null"
7319 ],
7320 "format": "date-time"
7321 },
7322 "is_admin": {
7323 "type": "boolean"
7324 },
7325 "user_id": {
7326 "type": "integer",
7327 "format": "int64"
7328 },
7329 "username": {
7330 "type": "string"
7331 },
7332 "version": {
7333 "type": "integer",
7334 "format": "int32"
7335 }
7336 }
7337 },
7338 "MemberListQuery": {
7339 "type": "object",
7340 "properties": {
7341 "include_banned": {
7342 "type": [
7343 "boolean",
7344 "null"
7345 ]
7346 },
7347 "include_inactive": {
7348 "type": [
7349 "boolean",
7350 "null"
7351 ]
7352 },
7353 "page": {
7354 "type": [
7355 "integer",
7356 "null"
7357 ],
7358 "format": "int64",
7359 "minimum": 0
7360 },
7361 "page_size": {
7362 "type": [
7363 "integer",
7364 "null"
7365 ],
7366 "format": "int64",
7367 "minimum": 0
7368 }
7369 }
7370 },
7371 "MemberSummary": {
7372 "type": "object",
7373 "required": [
7374 "id",
7375 "organization_id",
7376 "user_id",
7377 "username",
7378 "role",
7379 "role_keys",
7380 "permissions",
7381 "joined_at",
7382 "join_method",
7383 "message_notifications",
7384 "muted",
7385 "mobile_push",
7386 "suppress_everyone",
7387 "suppress_roles",
7388 "hide_muted_channels",
7389 "ban_allow_agent_join",
7390 "ban_allow_speak",
7391 "ban_allow_push_repo",
7392 "ban_allow_comment_any",
7393 "version"
7394 ],
7395 "properties": {
7396 "alias_avatar_url": {
7397 "type": [
7398 "string",
7399 "null"
7400 ]
7401 },
7402 "alias_name": {
7403 "type": [
7404 "string",
7405 "null"
7406 ]
7407 },
7408 "ban_allow_agent_join": {
7409 "type": "boolean"
7410 },
7411 "ban_allow_comment_any": {
7412 "type": "boolean"
7413 },
7414 "ban_allow_push_repo": {
7415 "type": "boolean"
7416 },
7417 "ban_allow_speak": {
7418 "type": "boolean"
7419 },
7420 "ban_expires_at": {
7421 "type": [
7422 "string",
7423 "null"
7424 ],
7425 "format": "date-time"
7426 },
7427 "ban_reason": {
7428 "type": [
7429 "string",
7430 "null"
7431 ]
7432 },
7433 "banned_at": {
7434 "type": [
7435 "string",
7436 "null"
7437 ],
7438 "format": "date-time"
7439 },
7440 "displayname": {
7441 "type": [
7442 "string",
7443 "null"
7444 ]
7445 },
7446 "hide_muted_channels": {
7447 "type": "boolean"
7448 },
7449 "id": {
7450 "type": "string",
7451 "format": "uuid"
7452 },
7453 "invited_by": {
7454 "type": [
7455 "integer",
7456 "null"
7457 ],
7458 "format": "int64"
7459 },
7460 "join_method": {
7461 "$ref": "#/components/schemas/OrgJoinMethod"
7462 },
7463 "joined_at": {
7464 "type": "string",
7465 "format": "date-time"
7466 },
7467 "last_active_at": {
7468 "type": [
7469 "string",
7470 "null"
7471 ],
7472 "format": "date-time"
7473 },
7474 "left_at": {
7475 "type": [
7476 "string",
7477 "null"
7478 ],
7479 "format": "date-time"
7480 },
7481 "message_notifications": {
7482 "$ref": "#/components/schemas/MessageNotifications"
7483 },
7484 "mobile_push": {
7485 "type": "boolean"
7486 },
7487 "mute_until": {
7488 "type": [
7489 "string",
7490 "null"
7491 ],
7492 "format": "date-time"
7493 },
7494 "muted": {
7495 "type": "boolean"
7496 },
7497 "organization_id": {
7498 "type": "string",
7499 "format": "uuid"
7500 },
7501 "permissions": {
7502 "type": "array",
7503 "items": {
7504 "type": "string"
7505 }
7506 },
7507 "role": {
7508 "$ref": "#/components/schemas/OrgMemberRole"
7509 },
7510 "role_keys": {
7511 "type": "array",
7512 "items": {
7513 "type": "string"
7514 }
7515 },
7516 "suppress_everyone": {
7517 "type": "boolean"
7518 },
7519 "suppress_roles": {
7520 "type": "boolean"
7521 },
7522 "user_id": {
7523 "type": "integer",
7524 "format": "int64"
7525 },
7526 "username": {
7527 "type": "string"
7528 },
7529 "version": {
7530 "type": "integer",
7531 "format": "int32"
7532 }
7533 }
7534 },
7535 "MessageNotifications": {
7536 "type": "string",
7537 "description": "会籍的通知偏好,对应旧 `space_members.message_notifications`。\n旧表用 0/1/2 存,这里用字符串存,便于跨服务读写时直接透传。",
7538 "enum": [
7539 "all",
7540 "mentions",
7541 "nothing"
7542 ]
7543 },
7544 "NoData": {
7545 "default": null
7546 },
7547 "OrgInvitePath": {
7548 "type": "object",
7549 "required": [
7550 "organization_id",
7551 "invite_id"
7552 ],
7553 "properties": {
7554 "invite_id": {
7555 "type": "string",
7556 "format": "uuid"
7557 },
7558 "organization_id": {
7559 "type": "string",
7560 "format": "uuid"
7561 }
7562 }
7563 },
7564 "OrgJoinMethod": {
7565 "type": "string",
7566 "enum": [
7567 "direct",
7568 "invite_link",
7569 "invite_targeted",
7570 "invite_email"
7571 ]
7572 },
7573 "OrgListQuery": {
7574 "type": "object",
7575 "properties": {
7576 "include_archived": {
7577 "type": [
7578 "boolean",
7579 "null"
7580 ]
7581 },
7582 "page": {
7583 "type": [
7584 "integer",
7585 "null"
7586 ],
7587 "format": "int64",
7588 "minimum": 0
7589 },
7590 "page_size": {
7591 "type": [
7592 "integer",
7593 "null"
7594 ],
7595 "format": "int64",
7596 "minimum": 0
7597 }
7598 }
7599 },
7600 "OrgMemberPath": {
7601 "type": "object",
7602 "required": [
7603 "organization_id",
7604 "member_id"
7605 ],
7606 "properties": {
7607 "member_id": {
7608 "type": "string",
7609 "format": "uuid"
7610 },
7611 "organization_id": {
7612 "type": "string",
7613 "format": "uuid"
7614 }
7615 }
7616 },
7617 "OrgMemberRole": {
7618 "type": "string",
7619 "enum": [
7620 "member",
7621 "admin",
7622 "owner"
7623 ]
7624 },
7625 "OrgPath": {
7626 "type": "object",
7627 "required": [
7628 "organization_id"
7629 ],
7630 "properties": {
7631 "organization_id": {
7632 "type": "string",
7633 "format": "uuid"
7634 }
7635 }
7636 },
7637 "OrgRolePath": {
7638 "type": "object",
7639 "required": [
7640 "organization_id",
7641 "role_id"
7642 ],
7643 "properties": {
7644 "organization_id": {
7645 "type": "string",
7646 "format": "uuid"
7647 },
7648 "role_id": {
7649 "type": "string",
7650 "format": "uuid"
7651 }
7652 }
7653 },
7654 "OrganizationJoinPolicy": {
7655 "type": "string",
7656 "enum": [
7657 "open",
7658 "approval",
7659 "invite_only",
7660 "private"
7661 ]
7662 },
7663 "OrganizationStatus": {
7664 "type": "string",
7665 "enum": [
7666 "active",
7667 "suspended",
7668 "archived"
7669 ]
7670 },
7671 "OrganizationSummary": {
7672 "type": "object",
7673 "required": [
7674 "id",
7675 "slug",
7676 "name",
7677 "status",
7678 "owner_user_id",
7679 "member_count",
7680 "version",
7681 "created_at",
7682 "updated_at",
7683 "join_policy",
7684 "allow_join",
7685 "require_email_verified",
7686 "require_mfa",
7687 "member_must_enable_2fa",
7688 "allow_invite",
7689 "allow_join_level"
7690 ],
7691 "properties": {
7692 "allow_invite": {
7693 "type": "boolean"
7694 },
7695 "allow_join": {
7696 "type": "boolean"
7697 },
7698 "allow_join_level": {
7699 "type": "integer",
7700 "format": "int32"
7701 },
7702 "archived_at": {
7703 "type": [
7704 "string",
7705 "null"
7706 ],
7707 "format": "date-time"
7708 },
7709 "avatar_url": {
7710 "type": [
7711 "string",
7712 "null"
7713 ]
7714 },
7715 "created_at": {
7716 "type": "string",
7717 "format": "date-time"
7718 },
7719 "description": {
7720 "type": [
7721 "string",
7722 "null"
7723 ]
7724 },
7725 "id": {
7726 "type": "string",
7727 "format": "uuid"
7728 },
7729 "join_policy": {
7730 "$ref": "#/components/schemas/OrganizationJoinPolicy"
7731 },
7732 "member_count": {
7733 "type": "integer",
7734 "format": "int64"
7735 },
7736 "member_must_enable_2fa": {
7737 "type": "boolean"
7738 },
7739 "my_role": {
7740 "oneOf": [
7741 {
7742 "$ref": "#/components/schemas/OrgMemberRole"
7743 },
7744 {
7745 "type": "null"
7746 }
7747 ]
7748 },
7749 "name": {
7750 "type": "string"
7751 },
7752 "owner_user_id": {
7753 "type": "integer",
7754 "format": "int64"
7755 },
7756 "require_email_verified": {
7757 "type": "boolean"
7758 },
7759 "require_mfa": {
7760 "type": "boolean"
7761 },
7762 "slug": {
7763 "type": "string"
7764 },
7765 "status": {
7766 "$ref": "#/components/schemas/OrganizationStatus"
7767 },
7768 "updated_at": {
7769 "type": "string",
7770 "format": "date-time"
7771 },
7772 "version": {
7773 "type": "integer",
7774 "format": "int32"
7775 },
7776 "welcome_message": {
7777 "type": [
7778 "string",
7779 "null"
7780 ]
7781 }
7782 }
7783 },
7784 "PasswordResetConfirmRequest": {
7785 "type": "object",
7786 "required": [
7787 "token",
7788 "password_hash",
7789 "pow_payload"
7790 ],
7791 "properties": {
7792 "password_hash": {
7793 "type": "string"
7794 },
7795 "pow_payload": {
7796 "$ref": "#/components/schemas/PowProofRequest"
7797 },
7798 "token": {
7799 "type": "string"
7800 }
7801 }
7802 },
7803 "PasswordResetConfirmResponse": {
7804 "type": "object",
7805 "required": [
7806 "user_id"
7807 ],
7808 "properties": {
7809 "user_id": {
7810 "type": "integer",
7811 "format": "int64"
7812 }
7813 }
7814 },
7815 "PasswordResetRequest": {
7816 "type": "object",
7817 "required": [
7818 "email",
7819 "pow_payload"
7820 ],
7821 "properties": {
7822 "email": {
7823 "type": "string"
7824 },
7825 "pow_payload": {
7826 "$ref": "#/components/schemas/PowProofRequest"
7827 }
7828 }
7829 },
7830 "PasswordResetResponse": {
7831 "type": "object",
7832 "required": [
7833 "expires_in_secs"
7834 ],
7835 "properties": {
7836 "expires_in_secs": {
7837 "type": "integer",
7838 "format": "int64",
7839 "minimum": 0
7840 }
7841 }
7842 },
7843 "PermissionCheck": {
7844 "type": "object",
7845 "required": [
7846 "allowed",
7847 "reason",
7848 "matched_by",
7849 "organization_version"
7850 ],
7851 "properties": {
7852 "allowed": {
7853 "type": "boolean"
7854 },
7855 "matched_by": {
7856 "type": "string"
7857 },
7858 "organization_version": {
7859 "type": "integer",
7860 "format": "int32"
7861 },
7862 "reason": {
7863 "type": "string"
7864 }
7865 }
7866 },
7867 "PermissionEffect": {
7868 "type": "string",
7869 "enum": [
7870 "allow",
7871 "deny"
7872 ]
7873 },
7874 "PermissionGrant": {
7875 "type": "object",
7876 "required": [
7877 "permission",
7878 "effect"
7879 ],
7880 "properties": {
7881 "effect": {
7882 "$ref": "#/components/schemas/PermissionEffect"
7883 },
7884 "permission": {
7885 "type": "string"
7886 }
7887 }
7888 },
7889 "PowProofRequest": {
7890 "type": "object",
7891 "required": [
7892 "nonce",
7893 "payload"
7894 ],
7895 "properties": {
7896 "nonce": {
7897 "type": "integer",
7898 "format": "int64",
7899 "minimum": 0
7900 },
7901 "payload": {
7902 "type": "array",
7903 "items": {
7904 "type": "integer",
7905 "format": "int32",
7906 "minimum": 0
7907 }
7908 }
7909 }
7910 },
7911 "ReadinessResponse": {
7912 "type": "object",
7913 "required": [
7914 "status",
7915 "checks"
7916 ],
7917 "properties": {
7918 "checks": {
7919 "type": "array",
7920 "items": {
7921 "$ref": "#/components/schemas/ComponentCheck"
7922 }
7923 },
7924 "status": {
7925 "type": "string"
7926 }
7927 }
7928 },
7929 "ReasonBody": {
7930 "type": "object",
7931 "properties": {
7932 "reason": {
7933 "type": [
7934 "string",
7935 "null"
7936 ]
7937 }
7938 }
7939 },
7940 "RedeemInviteBody": {
7941 "type": "object",
7942 "required": [
7943 "token"
7944 ],
7945 "properties": {
7946 "token": {
7947 "type": "string"
7948 }
7949 }
7950 },
7951 "RedeemInviteResponse": {
7952 "type": "object",
7953 "required": [
7954 "organization_id",
7955 "member_id",
7956 "joined"
7957 ],
7958 "properties": {
7959 "joined": {
7960 "type": "boolean"
7961 },
7962 "member_id": {
7963 "type": "string",
7964 "format": "uuid"
7965 },
7966 "organization_id": {
7967 "type": "string",
7968 "format": "uuid"
7969 }
7970 }
7971 },
7972 "RegisterCodeRequest": {
7973 "type": "object",
7974 "required": [
7975 "email",
7976 "pow_payload"
7977 ],
7978 "properties": {
7979 "email": {
7980 "type": "string"
7981 },
7982 "pow_payload": {
7983 "$ref": "#/components/schemas/PowProofRequest"
7984 }
7985 }
7986 },
7987 "RegisterCodeResponse": {
7988 "type": "object",
7989 "required": [
7990 "expires_in_secs"
7991 ],
7992 "properties": {
7993 "expires_in_secs": {
7994 "type": "integer",
7995 "format": "int64",
7996 "minimum": 0
7997 }
7998 }
7999 },
8000 "RevokeRequest": {
8001 "type": "object",
8002 "required": [
8003 "token"
8004 ],
8005 "properties": {
8006 "client_id": {
8007 "type": [
8008 "string",
8009 "null"
8010 ]
8011 },
8012 "client_secret": {
8013 "type": [
8014 "string",
8015 "null"
8016 ]
8017 },
8018 "token": {
8019 "type": "string"
8020 }
8021 }
8022 },
8023 "RoleSummary": {
8024 "type": "object",
8025 "required": [
8026 "id",
8027 "organization_id",
8028 "key",
8029 "name",
8030 "rank",
8031 "system",
8032 "default_role",
8033 "self_assignable",
8034 "permissions",
8035 "member_count",
8036 "created_at",
8037 "updated_at",
8038 "version",
8039 "position"
8040 ],
8041 "properties": {
8042 "color": {
8043 "type": [
8044 "string",
8045 "null"
8046 ]
8047 },
8048 "created_at": {
8049 "type": "string",
8050 "format": "date-time"
8051 },
8052 "default_role": {
8053 "type": "boolean"
8054 },
8055 "description": {
8056 "type": [
8057 "string",
8058 "null"
8059 ]
8060 },
8061 "emoji": {
8062 "type": [
8063 "string",
8064 "null"
8065 ]
8066 },
8067 "icon_hash": {
8068 "type": [
8069 "string",
8070 "null"
8071 ]
8072 },
8073 "id": {
8074 "type": "string",
8075 "format": "uuid"
8076 },
8077 "key": {
8078 "type": "string"
8079 },
8080 "member_count": {
8081 "type": "integer",
8082 "format": "int64"
8083 },
8084 "name": {
8085 "type": "string"
8086 },
8087 "organization_id": {
8088 "type": "string",
8089 "format": "uuid"
8090 },
8091 "permissions": {
8092 "type": "array",
8093 "items": {
8094 "$ref": "#/components/schemas/PermissionGrant"
8095 }
8096 },
8097 "position": {
8098 "type": "integer",
8099 "format": "int64"
8100 },
8101 "rank": {
8102 "type": "integer",
8103 "format": "int32"
8104 },
8105 "self_assignable": {
8106 "type": "boolean"
8107 },
8108 "system": {
8109 "type": "boolean"
8110 },
8111 "updated_at": {
8112 "type": "string",
8113 "format": "date-time"
8114 },
8115 "version": {
8116 "type": "integer",
8117 "format": "int32"
8118 }
8119 }
8120 },
8121 "SetMemberRoleBody": {
8122 "type": "object",
8123 "required": [
8124 "role"
8125 ],
8126 "properties": {
8127 "expected_version": {
8128 "type": "integer",
8129 "format": "int32"
8130 },
8131 "role": {
8132 "$ref": "#/components/schemas/OrgMemberRole"
8133 }
8134 }
8135 },
8136 "SetRolePermissionsBody": {
8137 "type": "object",
8138 "properties": {
8139 "permissions": {
8140 "type": "array",
8141 "items": {
8142 "$ref": "#/components/schemas/PermissionGrant"
8143 }
8144 }
8145 }
8146 },
8147 "SshKeyAddRequest": {
8148 "type": "object",
8149 "required": [
8150 "name",
8151 "public_key"
8152 ],
8153 "properties": {
8154 "description": {
8155 "type": [
8156 "string",
8157 "null"
8158 ]
8159 },
8160 "expires_at": {
8161 "type": [
8162 "string",
8163 "null"
8164 ],
8165 "format": "date-time"
8166 },
8167 "name": {
8168 "type": "string"
8169 },
8170 "public_key": {
8171 "type": "string"
8172 }
8173 }
8174 },
8175 "SshKeyAlgorithm": {
8176 "type": "string",
8177 "enum": [
8178 "rsa",
8179 "ed25519",
8180 "ecdsa"
8181 ]
8182 },
8183 "SshKeySummary": {
8184 "type": "object",
8185 "required": [
8186 "id",
8187 "name",
8188 "algorithm",
8189 "fingerprint",
8190 "public_key",
8191 "created_at"
8192 ],
8193 "properties": {
8194 "algorithm": {
8195 "$ref": "#/components/schemas/SshKeyAlgorithm"
8196 },
8197 "created_at": {
8198 "type": "string",
8199 "format": "date-time"
8200 },
8201 "description": {
8202 "type": [
8203 "string",
8204 "null"
8205 ]
8206 },
8207 "expires_at": {
8208 "type": [
8209 "string",
8210 "null"
8211 ],
8212 "format": "date-time"
8213 },
8214 "fingerprint": {
8215 "type": "string"
8216 },
8217 "id": {
8218 "type": "string",
8219 "format": "uuid"
8220 },
8221 "last_used_at": {
8222 "type": [
8223 "string",
8224 "null"
8225 ],
8226 "format": "date-time"
8227 },
8228 "name": {
8229 "type": "string"
8230 },
8231 "public_key": {
8232 "type": "string"
8233 },
8234 "revoked_at": {
8235 "type": [
8236 "string",
8237 "null"
8238 ],
8239 "format": "date-time"
8240 }
8241 }
8242 },
8243 "TokenRequest": {
8244 "type": "object",
8245 "required": [
8246 "grant_type"
8247 ],
8248 "properties": {
8249 "client_id": {
8250 "type": [
8251 "string",
8252 "null"
8253 ]
8254 },
8255 "client_secret": {
8256 "type": [
8257 "string",
8258 "null"
8259 ]
8260 },
8261 "code": {
8262 "type": [
8263 "string",
8264 "null"
8265 ]
8266 },
8267 "code_verifier": {
8268 "type": [
8269 "string",
8270 "null"
8271 ]
8272 },
8273 "grant_type": {
8274 "type": "string"
8275 },
8276 "redirect_uri": {
8277 "type": [
8278 "string",
8279 "null"
8280 ]
8281 },
8282 "refresh_token": {
8283 "type": [
8284 "string",
8285 "null"
8286 ]
8287 },
8288 "scope": {
8289 "type": [
8290 "string",
8291 "null"
8292 ]
8293 }
8294 }
8295 },
8296 "TokenResponse": {
8297 "type": "object",
8298 "required": [
8299 "token",
8300 "token_type",
8301 "kid",
8302 "issued_at",
8303 "expires_at"
8304 ],
8305 "properties": {
8306 "expires_at": {
8307 "type": "string",
8308 "format": "date-time"
8309 },
8310 "issued_at": {
8311 "type": "string",
8312 "format": "date-time"
8313 },
8314 "kid": {
8315 "type": "string"
8316 },
8317 "token": {
8318 "type": "string"
8319 },
8320 "token_type": {
8321 "type": "string"
8322 }
8323 }
8324 },
8325 "TransferBody": {
8326 "type": "object",
8327 "required": [
8328 "target_user_id"
8329 ],
8330 "properties": {
8331 "target_user_id": {
8332 "type": "integer",
8333 "format": "int64"
8334 }
8335 }
8336 },
8337 "UpdateBanBody": {
8338 "type": "object",
8339 "description": "封禁更新:原因、到期时间与四个放行位。缺失表示不变,null 表示清空。",
8340 "properties": {
8341 "allow_agent_join": {
8342 "type": [
8343 "boolean",
8344 "null"
8345 ]
8346 },
8347 "allow_comment_any": {
8348 "type": [
8349 "boolean",
8350 "null"
8351 ]
8352 },
8353 "allow_push_repo": {
8354 "type": [
8355 "boolean",
8356 "null"
8357 ]
8358 },
8359 "allow_speak": {
8360 "type": [
8361 "boolean",
8362 "null"
8363 ]
8364 },
8365 "expires_at": {
8366 "type": [
8367 "string",
8368 "null"
8369 ],
8370 "format": "date-time"
8371 },
8372 "reason": {
8373 "type": [
8374 "string",
8375 "null"
8376 ]
8377 }
8378 }
8379 },
8380 "UpdateMemberBody": {
8381 "type": "object",
8382 "description": "会籍更新:管理端可改别名、通知偏好、免打扰与封禁附加信息。\n所有字段缺失表示不变,显式 null 表示清空(仅限 Option 字段)。",
8383 "properties": {
8384 "alias_avatar_url": {
8385 "type": [
8386 "string",
8387 "null"
8388 ]
8389 },
8390 "alias_name": {
8391 "type": [
8392 "string",
8393 "null"
8394 ]
8395 },
8396 "expected_version": {
8397 "type": "integer",
8398 "format": "int32"
8399 },
8400 "hide_muted_channels": {
8401 "type": [
8402 "boolean",
8403 "null"
8404 ]
8405 },
8406 "message_notifications": {
8407 "oneOf": [
8408 {
8409 "$ref": "#/components/schemas/MessageNotifications"
8410 },
8411 {
8412 "type": "null"
8413 }
8414 ]
8415 },
8416 "mobile_push": {
8417 "type": [
8418 "boolean",
8419 "null"
8420 ]
8421 },
8422 "mute_until": {
8423 "type": [
8424 "string",
8425 "null"
8426 ],
8427 "format": "date-time"
8428 },
8429 "muted": {
8430 "type": [
8431 "boolean",
8432 "null"
8433 ]
8434 },
8435 "suppress_everyone": {
8436 "type": [
8437 "boolean",
8438 "null"
8439 ]
8440 },
8441 "suppress_roles": {
8442 "type": [
8443 "boolean",
8444 "null"
8445 ]
8446 }
8447 }
8448 },
8449 "UpdateOrganizationBody": {
8450 "type": "object",
8451 "properties": {
8452 "allow_invite": {
8453 "type": [
8454 "boolean",
8455 "null"
8456 ]
8457 },
8458 "allow_join": {
8459 "type": [
8460 "boolean",
8461 "null"
8462 ]
8463 },
8464 "allow_join_level": {
8465 "type": [
8466 "integer",
8467 "null"
8468 ],
8469 "format": "int32"
8470 },
8471 "avatar_url": {
8472 "type": [
8473 "string",
8474 "null"
8475 ]
8476 },
8477 "description": {
8478 "type": [
8479 "string",
8480 "null"
8481 ]
8482 },
8483 "expected_version": {
8484 "type": "integer",
8485 "format": "int32"
8486 },
8487 "join_policy": {
8488 "oneOf": [
8489 {
8490 "$ref": "#/components/schemas/OrganizationJoinPolicy"
8491 },
8492 {
8493 "type": "null"
8494 }
8495 ]
8496 },
8497 "member_must_enable_2fa": {
8498 "type": [
8499 "boolean",
8500 "null"
8501 ]
8502 },
8503 "name": {
8504 "type": [
8505 "string",
8506 "null"
8507 ]
8508 },
8509 "require_email_verified": {
8510 "type": [
8511 "boolean",
8512 "null"
8513 ]
8514 },
8515 "require_mfa": {
8516 "type": [
8517 "boolean",
8518 "null"
8519 ]
8520 },
8521 "welcome_message": {
8522 "type": [
8523 "string",
8524 "null"
8525 ]
8526 }
8527 }
8528 },
8529 "UpdateRoleBody": {
8530 "type": "object",
8531 "properties": {
8532 "default_role": {
8533 "type": [
8534 "boolean",
8535 "null"
8536 ]
8537 },
8538 "description": {
8539 "type": [
8540 "string",
8541 "null"
8542 ]
8543 },
8544 "expected_version": {
8545 "type": "integer",
8546 "format": "int32"
8547 },
8548 "name": {
8549 "type": [
8550 "string",
8551 "null"
8552 ]
8553 },
8554 "rank": {
8555 "type": [
8556 "integer",
8557 "null"
8558 ],
8559 "format": "int32"
8560 },
8561 "self_assignable": {
8562 "type": [
8563 "boolean",
8564 "null"
8565 ]
8566 }
8567 }
8568 },
8569 "UserLoginRequest": {
8570 "type": "object",
8571 "required": [
8572 "identity",
8573 "password_hash",
8574 "pow_payload"
8575 ],
8576 "properties": {
8577 "identity": {
8578 "type": "string"
8579 },
8580 "password_hash": {
8581 "type": "string"
8582 },
8583 "pow_payload": {
8584 "$ref": "#/components/schemas/PowProofRequest"
8585 }
8586 }
8587 },
8588 "UserLoginResponse": {
8589 "type": "object",
8590 "required": [
8591 "user_id",
8592 "username",
8593 "mfa_required"
8594 ],
8595 "properties": {
8596 "challenge": {
8597 "type": [
8598 "string",
8599 "null"
8600 ]
8601 },
8602 "displayname": {
8603 "type": [
8604 "string",
8605 "null"
8606 ]
8607 },
8608 "mfa_required": {
8609 "type": "boolean"
8610 },
8611 "user_id": {
8612 "type": "integer",
8613 "format": "int64"
8614 },
8615 "username": {
8616 "type": "string"
8617 }
8618 }
8619 },
8620 "UserRegisterRequest": {
8621 "type": "object",
8622 "required": [
8623 "username",
8624 "birth_date",
8625 "email",
8626 "password_hash",
8627 "email_code",
8628 "pow_payload"
8629 ],
8630 "properties": {
8631 "birth_date": {
8632 "type": "string"
8633 },
8634 "displayname": {
8635 "type": [
8636 "string",
8637 "null"
8638 ]
8639 },
8640 "email": {
8641 "type": "string"
8642 },
8643 "email_code": {
8644 "type": "string"
8645 },
8646 "password_hash": {
8647 "type": "string"
8648 },
8649 "pow_payload": {
8650 "$ref": "#/components/schemas/PowProofRequest"
8651 },
8652 "username": {
8653 "type": "string"
8654 }
8655 }
8656 },
8657 "UserRegisterResponse": {
8658 "type": "object",
8659 "required": [
8660 "user_id",
8661 "username"
8662 ],
8663 "properties": {
8664 "displayname": {
8665 "type": [
8666 "string",
8667 "null"
8668 ]
8669 },
8670 "user_id": {
8671 "type": "integer",
8672 "format": "int64"
8673 },
8674 "username": {
8675 "type": "string"
8676 }
8677 }
8678 }
8679 }
8680 }
8681}
No newline at end of file
Added lib/identity/orval.config.ts View lib/identity/orval.config.ts
@@ -0,0 +1,22 @@
1import { defineConfig } from 'orval'
2
3// 生成 view/src/shared/api/auth 下的 fetch 客户端;OpenApi.Auth.json 来自
4// `cargo run --bin openapi -- auth -o OpenApi.Auth.json`。
5export default defineConfig({
6 auth: {
7 input: './OpenApi.Auth.json',
8 output: {
9 target: './view/src/shared/api/auth',
10 mode: 'split',
11 client: 'fetch',
12 httpClient: 'fetch',
13 clean: true,
14 indexFiles: true,
15 formatter: 'prettier',
16 override: {
17 enumGenerationType: 'union',
18 mutator: { path: './view/src/shared/lib/jwtmanager/http.ts', name: 'authClient' },
19 },
20 },
21 },
22})
Added lib/identity/package-lock.json View lib/identity/package-lock.json

Diff too large to display.

Added lib/identity/package.json View lib/identity/package.json

Diff too large to display.

Added lib/identity/pnpm-lock.yaml View lib/identity/pnpm-lock.yaml

Diff too large to display.

Added lib/identity/pnpm-workspace.yaml View lib/identity/pnpm-workspace.yaml

Diff too large to display.

Modified lib/jwt/src/verifier.rs View lib/jwt/src/verifier.rs
@@ -18,6 +18,7 @@
1818}
1919
2020pub struct JwtVerifier {
21 identity: Option<kenoauth::auth::AuthSvc>,
2122 issuer: String,
2223 audience: String,
2324 chat_audience: String,
@@ -34,6 +35,13 @@
3435
3536impl JwtVerifier {
3637 pub fn new(config: &OAuthConfig) -> Result<Arc<Self>, SessionError> {
38 Self::with_identity(config, None)
39 }
40
41 pub fn with_identity(
42 config: &OAuthConfig,
43 identity: Option<kenoauth::auth::AuthSvc>,
44 ) -> Result<Arc<Self>, SessionError> {
3745 if config.issuer.is_empty() {
3846 return Err(SessionError::Verifier("oauth.issuer is empty".into()));
3947 }
@@ -43,6 +51,7 @@
4351 .map_err(|err| SessionError::Verifier(err.to_string()))?;
4452
4553 Ok(Arc::new(Self {
54 identity,
4655 issuer: config.issuer.trim_end_matches('/').to_string(),
4756 audience: config.audience.clone(),
4857 chat_audience: config.chat_audience.clone(),
@@ -124,8 +133,7 @@
124133 token: &str,
125134 audience: &str,
126135 ) -> Result<(T, String), SessionError> {
127 let header =
128 decode_header(token).map_err(|err| SessionError::Verifier(err.to_string()))?;
136 let header = decode_header(token).map_err(|err| SessionError::Verifier(err.to_string()))?;
129137 if header.alg != Algorithm::EdDSA {
130138 return Err(SessionError::Verifier(format!(
131139 "unexpected algorithm {:?}",
@@ -209,6 +217,12 @@
209217 }
210218
211219 async fn fetch_jwks(&self) -> Result<JwkSet, SessionError> {
220 if let Some(service) = &self.identity {
221 let value = serde_json::to_value(service.signing().jwks())
222 .map_err(|error| SessionError::Verifier(error.to_string()))?;
223 return serde_json::from_value(value)
224 .map_err(|error| SessionError::Verifier(error.to_string()));
225 }
212226 let response = self
213227 .http
214228 .get(&self.jwks_url)
@@ -235,4 +249,4 @@
235249 .map_err(|err| SessionError::Verifier(err.to_string())),
236250 None => Ok(None),
237251 }
238}
No newline at end of file
252}
Modified lib/jwt/Cargo.toml View lib/jwt/Cargo.toml
@@ -4,6 +4,7 @@
44edition.workspace = true
55
66[dependencies]
7kenoauth = { workspace = true }
78config = { workspace = true }
89axum = { workspace = true }
910serde = { workspace = true, features = ["derive"] }
Modified lib/kenauth/proto/common/v1/types.proto View lib/kenauth/proto/common/v1/types.proto
@@ -68,7 +68,7 @@
6868 TOKEN_AUDIENCE_REALTIME = 3; // realtime.gitdata.ai
6969 TOKEN_AUDIENCE_SFU = 4; // sfu.gitdata.ai(livekit)
7070 TOKEN_AUDIENCE_OBJECT = 5; // object.gitdata.ai
71 reserved 6;
71 TOKEN_AUDIENCE_AGENT = 6; // agent.gitdata.ai
7272 TOKEN_AUDIENCE_NOTIFY = 7; // notify.gitdata.ai
7373}
7474
@@ -120,6 +120,8 @@
120120 google.protobuf.Timestamp email_verified_at = 7;
121121 int32 security_version = 8;
122122 uint32 active_mfa_factors = 9; // 已启用的多因子数量,0 = 未开启
123 // 宽限期内的删除请求时间;非空表示已申请删除但尚未执行。
124 google.protobuf.Timestamp deletion_requested_at = 10;
123125}
124126
125127// 会话快照。密钥明文不出 auth:session_id 是服务端标识,凭据本身只在调用方持有。
@@ -196,7 +198,7 @@
196198message Credential {
197199 oneof kind {
198200 string session_key = 1; // 会话 cookie 的值(64 字符)
199 string access_token = 2; // 用户令牌明文,形如 "gd_..." 由客户端保存
201 string access_token = 2; // 用户令牌明文:opaque "gd_..." 或 OAuth/app JWT
200202 string service_token = 3; // 服务令牌(JWT)
201203 string ssh_public_key = 4; // OpenSSH 单行公钥
202204 string ssh_fingerprint = 5; // "SHA256:<base64 无填充>"
Modified lib/kenauth/proto/internal/v1/authority.proto View lib/kenauth/proto/internal/v1/authority.proto
@@ -2,11 +2,12 @@
22
33package auth.internal.v1;
44
5import "common/v1/types.proto";
56import "google/protobuf/timestamp.proto";
67
78// auth 授权面。调用方固定为 api(令牌校验、用户资料、账号更新)与 socket
8// (实时通道的会话复核)。本文件的五个接口取代旧的 /auth/internal/{operation}
9// HTTP 入口,语义一一对应,不做额外扩展。
9// (实时通道的会话复核),以及平台管理后台(账号列表)。本文件的接口取代旧的
10// 内部 HTTP 入口,语义一一对应,不做额外扩展。
1011//
1112// 与 auth.v1 的边界:auth.v1.IdentityService.Authenticate 回答"凭证是否有效",
1213// 返回 Principal(不含账号可用性字段);本文件回答"账号与会话现在处于什么状态",
@@ -30,6 +31,15 @@
3031
3132 // 以乐观锁更新账号自述字段。version 必须回填,版本冲突返回 ABORTED。
3233 rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
34 // 账号删除生命周期:软删 + 撤销全部凭据;有活跃组织的 OWNER 会被拒绝。
35 rpc DeleteAccount(DeleteAccountRequest) returns (DeleteAccountResponse);
36 rpc CancelAccountDeletion(CancelAccountDeletionRequest) returns (CancelAccountDeletionResponse);
37 // 宽限期结束后的匿名化清理(保留 user_id 供历史内容引用)。
38 rpc PurgeAccount(PurgeAccountRequest) returns (PurgeAccountResponse);
39
40 // 分页列出账号并筛选。平台管理面用:管理后台的账号总览。
41 // 与 IdentityService 的读取接口不重叠:那边按单/批编号取值,这里做全量分页与筛选。
42 rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
3343}
3444
3545message RedeemSocketTicketRequest {
@@ -135,3 +145,81 @@
135145message UpdateUserResponse {
136146 AuthorityUser user = 1;
137147}
148
149// 账号列表的筛选条件。未设置的字段表示不过滤。
150//
151// 文本类筛选一律按“包含”匹配且大小写不敏感:`query` 同时跨 username / displayname / email,
152// 由服务端统一做 LIKE。这比让调用方拼三份条件更省事,也让行为跨调用方一致。
153message ListUsersFilter {
154 // 跨 username、lowername、displayname、email 的子串匹配。空串 = 不过滤。
155 // 数值输入不会被当作 user_id,编号检索请用 user_ids。
156 string query = 1;
157
158 // 只返回这些编号的账号。为空 = 不过滤;上限 500。
159 repeated int64 user_ids = 2;
160
161 // 账号形态。UNSPECIFIED = 不过滤。
162 UserKind kind = 3;
163
164 // 状态分组。UNSPECIFIED = 不过滤(含已软删)。
165 UserState state = 4;
166
167 // true = 只返回平台管理员;false = 只返回非管理员;未设置 = 不过滤。
168 optional bool is_admin = 5;
169
170 // 只看邮箱已验证(true)/ 未验证(false);未设置 = 不过滤。
171 optional bool email_verified = 6;
172}
173
174enum UserKind {
175 USER_KIND_UNSPECIFIED = 0;
176 USER_KIND_HUMAN = 1; // bot=false 且 system=false
177 USER_KIND_BOT = 2; // bot=true
178 USER_KIND_SYSTEM = 3; // system=true
179}
180
181// 账号状态分组。与 users 表的三个布尔列对应,取值互斥且优先序固定:
182// DELETED > LOGIN_FORBIDDEN > UNAVAILABLE > ACTIVE。
183enum UserState {
184 USER_STATE_UNSPECIFIED = 0;
185 USER_STATE_ACTIVE = 1; // deleted_at 为空 && allow_use && !forbid_login
186 USER_STATE_UNAVAILABLE = 2; // allow_use = false
187 USER_STATE_LOGIN_FORBIDDEN = 3; // forbid_login = true
188 USER_STATE_DELETED = 4; // deleted_at 非空
189}
190
191message ListUsersRequest {
192 ListUsersFilter filter = 1;
193 auth.common.v1.PageRequest page = 2;
194 // 排序。UNSPECIFIED = CREATED_AT_DESC。
195 UserOrder order = 3;
196}
197
198enum UserOrder {
199 USER_ORDER_UNSPECIFIED = 0;
200 USER_ORDER_CREATED_AT_DESC = 1;
201 USER_ORDER_CREATED_AT_ASC = 2;
202 USER_ORDER_LAST_LOGIN_DESC = 3;
203 USER_ORDER_ID_ASC = 4;
204}
205
206message ListUsersResponse {
207 repeated AuthorityUser users = 1;
208 auth.common.v1.PageResponse page = 2;
209}
210
211message DeleteAccountRequest {
212 int64 user_id = 1;
213 string reason = 2;
214}
215message DeleteAccountResponse {}
216
217message CancelAccountDeletionRequest {
218 int64 user_id = 1;
219}
220message CancelAccountDeletionResponse {}
221
222message PurgeAccountRequest {
223 int64 user_id = 1;
224}
225message PurgeAccountResponse {}
Modified lib/kenauth/src/client.rs View lib/kenauth/src/client.rs
@@ -1,205 +1,83 @@
11use std::collections::HashMap;
22use std::time::Duration;
33
4use config::oauth::OAuthConfig;
5use tonic::metadata::{Ascii, MetadataValue};
6use tonic::service::Interceptor;
7use tonic::service::interceptor::InterceptedService;
8use tonic::transport::{Channel, Endpoint};
9use tonic::{Request, Status};
4use kenoauth::auth::AuthSvc;
5use tonic::Request;
106use uuid::Uuid;
117
128use crate::error::KenAuthError;
139use crate::proto;
1410
1511#[derive(Clone)]
16pub struct RpcInterceptor {
17 key: Option<MetadataValue<Ascii>>,
18}
19
20impl RpcInterceptor {
21 pub fn new(rpc_key: &str) -> Result<Self, KenAuthError> {
22 let key = if rpc_key.is_empty() {
23 None
24 } else {
25 Some(
26 MetadataValue::try_from(rpc_key)
27 .map_err(|_| KenAuthError::Config("rpc_key is not ASCII".into()))?,
28 )
29 };
30 Ok(Self { key })
31 }
32}
33
34impl Interceptor for RpcInterceptor {
35 fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
36 if let Some(key) = &self.key {
37 request
38 .metadata_mut()
39 .insert(crate::RPC_KEY_HEADER, key.clone());
40 }
41 if let Ok(value) = MetadataValue::try_from(Uuid::now_v7().to_string()) {
42 request
43 .metadata_mut()
44 .insert(crate::REQUEST_ID_HEADER, value);
45 }
46 Ok(request)
47 }
48}
49
50type Intercepted = InterceptedService<Channel, RpcInterceptor>;
51
52#[derive(Clone)]
5312pub struct KenAuthClient {
54 channel: Channel,
55 interceptor: RpcInterceptor,
13 service: AuthSvc,
5614 read_timeout: Duration,
5715 write_timeout: Duration,
5816}
5917
6018impl KenAuthClient {
61 pub fn connect(config: &OAuthConfig) -> Result<Self, KenAuthError> {
62 if !config.enabled {
63 return Err(KenAuthError::Config("oauth.enabled is false".into()));
64 }
65 Self::connect_rpc(
66 &config.grpc_endpoint,
67 &config.rpc_key,
68 config.http_timeout_ms,
69 )
70 }
71
72 pub fn connect_rpc(
73 grpc_endpoint: &str,
74 rpc_key: &str,
75 http_timeout_ms: u64,
76 ) -> Result<Self, KenAuthError> {
77 if grpc_endpoint.is_empty() {
78 return Err(KenAuthError::Config("grpc endpoint is empty".into()));
19 pub fn local(service: AuthSvc, timeout_ms: u64) -> Self {
20 Self {
21 service,
22 read_timeout: Duration::from_millis(300),
23 write_timeout: Duration::from_millis(timeout_ms.max(2000)),
7924 }
80 let timeout = Duration::from_millis(http_timeout_ms.max(200));
81 let endpoint = Endpoint::from_shared(grpc_endpoint.to_string())
82 .map_err(|err| KenAuthError::Config(err.to_string()))?
83 .connect_timeout(timeout)
84 .timeout(timeout);
85 let channel = endpoint.connect_lazy();
86 Ok(Self {
87 channel,
88 interceptor: RpcInterceptor::new(rpc_key)?,
89 read_timeout: Duration::from_millis(300),
90 write_timeout: timeout.max(Duration::from_secs(2)),
91 })
9225 }
9326
94 pub fn channel(&self) -> Channel {
95 self.channel.clone()
27 pub fn service(&self) -> &AuthSvc {
28 &self.service
9629 }
9730
9831 pub async fn check_health(&self) -> Result<(), KenAuthError> {
99 let mut client = tonic_health::pb::health_client::HealthClient::with_interceptor(
100 self.channel.clone(),
101 self.interceptor.clone(),
102 );
103 let mut request = Request::new(tonic_health::pb::HealthCheckRequest {
104 service: String::new(),
105 });
106 request.set_timeout(Duration::from_secs(2));
107 client
108 .check(request)
32 self.service
33 .cache()
34 .ping()
10935 .await
110 .map_err(KenAuthError::from_status)?;
111 Ok(())
36 .map_err(|error| KenAuthError::Unavailable(error.to_string()))
11237 }
11338
114 pub fn identity(
115 &self,
116 ) -> proto::v1::identity_service_client::IdentityServiceClient<Intercepted> {
117 proto::v1::identity_service_client::IdentityServiceClient::with_interceptor(
118 self.channel.clone(),
119 self.interceptor.clone(),
120 )
39 pub fn identity(&self) -> crate::local::IdentityClient {
40 crate::local::IdentityClient::new(self.service.clone())
12141 }
12242
123 pub fn authority(
124 &self,
125 ) -> proto::internal::v1::authority_service_client::AuthorityServiceClient<Intercepted> {
126 proto::internal::v1::authority_service_client::AuthorityServiceClient::with_interceptor(
127 self.channel.clone(),
128 self.interceptor.clone(),
129 )
43 pub fn authority(&self) -> crate::local::AuthorityClient {
44 crate::local::AuthorityClient::new(self.service.clone())
13045 }
13146
132 pub fn email(
133 &self,
134 ) -> proto::internal::v1::email_service_client::EmailServiceClient<Intercepted> {
135 proto::internal::v1::email_service_client::EmailServiceClient::with_interceptor(
136 self.channel.clone(),
137 self.interceptor.clone(),
138 )
47 pub fn email(&self) -> crate::local::EmailClient {
48 crate::local::EmailClient::new(self.service.clone())
13949 }
14050
141 pub fn session(&self) -> proto::v1::session_service_client::SessionServiceClient<Intercepted> {
142 proto::v1::session_service_client::SessionServiceClient::with_interceptor(
143 self.channel.clone(),
144 self.interceptor.clone(),
145 )
51 pub fn session(&self) -> crate::local::SessionClient {
52 crate::local::SessionClient::new(self.service.clone())
14653 }
14754
148 pub fn organization(
149 &self,
150 ) -> proto::v1::organization_service_client::OrganizationServiceClient<Intercepted> {
151 proto::v1::organization_service_client::OrganizationServiceClient::with_interceptor(
152 self.channel.clone(),
153 self.interceptor.clone(),
154 )
55 pub fn organization(&self) -> crate::local::OrganizationClient {
56 crate::local::OrganizationClient::new(self.service.clone())
15557 }
15658
157 pub fn token(&self) -> proto::v1::token_service_client::TokenServiceClient<Intercepted> {
158 proto::v1::token_service_client::TokenServiceClient::with_interceptor(
159 self.channel.clone(),
160 self.interceptor.clone(),
161 )
59 pub fn token(&self) -> crate::local::TokenClient {
60 crate::local::TokenClient::new(self.service.clone())
16261 }
16362
164 pub fn authorization(
165 &self,
166 ) -> proto::v1::authorization_service_client::AuthorizationServiceClient<Intercepted> {
167 proto::v1::authorization_service_client::AuthorizationServiceClient::with_interceptor(
168 self.channel.clone(),
169 self.interceptor.clone(),
170 )
63 pub fn authorization(&self) -> crate::local::AuthorizationClient {
64 crate::local::AuthorizationClient::new(self.service.clone())
17165 }
17266
173 pub fn access_key(
174 &self,
175 ) -> proto::v1::access_key_service_client::AccessKeyServiceClient<Intercepted> {
176 proto::v1::access_key_service_client::AccessKeyServiceClient::with_interceptor(
177 self.channel.clone(),
178 self.interceptor.clone(),
179 )
67 pub fn access_key(&self) -> crate::local::AccessKeyClient {
68 crate::local::AccessKeyClient::new(self.service.clone())
18069 }
18170
182 pub fn ssh_key(&self) -> proto::v1::ssh_key_service_client::SshKeyServiceClient<Intercepted> {
183 proto::v1::ssh_key_service_client::SshKeyServiceClient::with_interceptor(
184 self.channel.clone(),
185 self.interceptor.clone(),
186 )
71 pub fn ssh_key(&self) -> crate::local::SshKeyClient {
72 crate::local::SshKeyClient::new(self.service.clone())
18773 }
18874
189 pub fn mfa(&self) -> proto::v1::mfa_service_client::MfaServiceClient<Intercepted> {
190 proto::v1::mfa_service_client::MfaServiceClient::with_interceptor(
191 self.channel.clone(),
192 self.interceptor.clone(),
193 )
75 pub fn mfa(&self) -> crate::local::MfaClient {
76 crate::local::MfaClient::new(self.service.clone())
19477 }
19578
196 pub fn agreement(
197 &self,
198 ) -> proto::v1::agreement_service_client::AgreementServiceClient<Intercepted> {
199 proto::v1::agreement_service_client::AgreementServiceClient::with_interceptor(
200 self.channel.clone(),
201 self.interceptor.clone(),
202 )
79 pub fn agreement(&self) -> crate::local::AgreementClient {
80 crate::local::AgreementClient::new(self.service.clone())
20381 }
20482
20583 #[allow(clippy::too_many_arguments)]
@@ -558,6 +436,40 @@
558436 .map(|response| response.into_inner().access.unwrap_or_default())
559437 }
560438
439 pub async fn delete_account(&self, user_id: i64, reason: &str) -> Result<(), KenAuthError> {
440 let mut client = self.authority();
441 client
442 .delete_account(
443 self.write_request(proto::internal::v1::DeleteAccountRequest {
444 user_id,
445 reason: reason.to_string(),
446 }),
447 )
448 .await
449 .map_err(KenAuthError::from_status)?;
450 Ok(())
451 }
452
453 pub async fn cancel_account_deletion(&self, user_id: i64) -> Result<(), KenAuthError> {
454 let mut client = self.authority();
455 client
456 .cancel_account_deletion(
457 self.write_request(proto::internal::v1::CancelAccountDeletionRequest { user_id }),
458 )
459 .await
460 .map_err(KenAuthError::from_status)?;
461 Ok(())
462 }
463
464 pub async fn purge_account(&self, user_id: i64) -> Result<(), KenAuthError> {
465 let mut client = self.authority();
466 client
467 .purge_account(self.write_request(proto::internal::v1::PurgeAccountRequest { user_id }))
468 .await
469 .map_err(KenAuthError::from_status)?;
470 Ok(())
471 }
472
561473 pub async fn batch_get_authority_users(
562474 &self,
563475 user_ids: Vec<i64>,
Modified lib/kenauth/src/error.rs View lib/kenauth/src/error.rs
@@ -41,4 +41,4 @@
4141 pub fn is_unauthenticated(&self) -> bool {
4242 matches!(self, Self::Unauthenticated)
4343 }
44}
No newline at end of file
44}
Modified lib/kenauth/src/lib.rs View lib/kenauth/src/lib.rs
@@ -1,26 +1,8 @@
1pub mod proto {
2 pub mod common {
3 pub mod v1 {
4 tonic::include_proto!("auth.common.v1");
5 }
6 }
7
8 pub mod v1 {
9 tonic::include_proto!("auth.v1");
10 }
11
12 pub mod internal {
13 pub mod v1 {
14 tonic::include_proto!("auth.internal.v1");
15 }
16 }
17}
1pub use kenoauth::protos::auth as proto;
182
193mod client;
204mod error;
5mod local;
216
22pub use client::{KenAuthClient, RpcInterceptor};
7pub use client::KenAuthClient;
238pub use error::KenAuthError;
24
25pub const RPC_KEY_HEADER: &str = "x-rpc-key";
26pub const REQUEST_ID_HEADER: &str = "gitdata-request-id";
No newline at end of file
Modified lib/kenauth/Cargo.toml View lib/kenauth/Cargo.toml
@@ -4,6 +4,7 @@
44edition.workspace = true
55
66[dependencies]
7kenoauth = { workspace = true }
78config = { workspace = true }
89prost = { workspace = true }
910prost-types = { workspace = true }
@@ -14,6 +15,3 @@
1415tracing = { workspace = true }
1516tokio = { workspace = true, features = ["time"] }
1617uuid = { workspace = true }
17
18[build-dependencies]
19tonic-prost-build = { workspace = true }
No newline at end of file
Modified lib/kenshell/src/client.rs View lib/kenshell/src/client.rs
@@ -206,3 +206,54 @@
206206 >
207207 );
208208}
209
210impl KenShellClient {
211 /// Every repository owned by one organization, across admin pages.
212 pub async fn list_repositories_for_org(
213 &self,
214 owner_org_id: &str,
215 ) -> KenShellResult<Vec<proto::common::v1::Repository>> {
216 let mut client = self.repository_admin();
217 let mut out = Vec::new();
218 let mut token = String::new();
219 loop {
220 let mut request = Request::new(proto::internal::v1::ListRepositoriesRequest {
221 owner_org_id: owner_org_id.to_string(),
222 state: 0,
223 name_contains: String::new(),
224 page: Some(proto::common::v1::PageRequest {
225 page_size: 100,
226 page_token: token,
227 }),
228 });
229 self.set_timeout(&mut request);
230 let response = client
231 .list_repositories(request)
232 .await
233 .map_err(KenShellError::from_status)?
234 .into_inner();
235 out.extend(response.repositories);
236 token = response
237 .page
238 .map(|page| page.next_page_token)
239 .unwrap_or_default();
240 if token.is_empty() {
241 break;
242 }
243 }
244 Ok(out)
245 }
246
247 pub async fn delete_repository(&self, repository_id: &str) -> KenShellResult<()> {
248 let mut client = self.repository_admin();
249 let mut request = Request::new(proto::internal::v1::DeleteRepositoryRequest {
250 repository_id: repository_id.to_string(),
251 });
252 self.set_timeout(&mut request);
253 client
254 .delete_repository(request)
255 .await
256 .map_err(KenShellError::from_status)?;
257 Ok(())
258 }
259}
Modified lib/models/src/system/mod.rs View lib/models/src/system/mod.rs
@@ -1,4 +1,6 @@
1pub mod account_deletion;
12pub mod event_outbox;
3pub mod space_deletion;
24pub mod system_announcements;
35pub mod system_permissions;
46pub mod system_rate_limits;
Modified lib/models/src/users/user.rs View lib/models/src/users/user.rs
@@ -56,7 +56,7 @@
5656///
5757/// Identity and account state (username, display name, email, password, TOTP,
5858/// active/prohibit flags, bot/system, created_at) live **exclusively** in
59/// KenOAuth and are fetched over gRPC. This row exists as a stable local anchor
59/// the in-process identity domain and are fetched through kenauth. This row is a local anchor
6060/// for foreign keys plus data KenApi itself owns.
6161#[sea_orm::model]
6262#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
Modified lib/space/src/handlers/delete_space.rs View lib/space/src/handlers/delete_space.rs
@@ -29,3 +29,26 @@
2929 service.delete_space(uid, &path.space_name).await?;
3030 Ok(AppResponseData::empty())
3131}
32
33#[utoipa::path(
34 post,
35 path = "/spaces/{space_name}/restore",
36 tag = "space",
37 description = "撤销尚未执行的定时删除:恢复组织并移除删除任务。",
38 params(SpaceNamePath),
39 responses(
40 (status = 200, description = "space restored", body = AppResponseData<NoData>),
41 (status = 403, description = "forbidden", body = AppResponseData<NoData>),
42 (status = 404, description = "space not found", body = AppResponseData<NoData>),
43 (status = 401, description = "authentication required", body = AppResponseData<NoData>)
44 )
45)]
46pub async fn restore_space(
47 State(service): State<SpaceService>,
48 session: Session,
49 Path(path): Path<SpaceNamePath>,
50) -> Result<AppResponseData<NoData>, ApiError> {
51 let uid = require_uid(&session)?;
52 service.restore_space(uid, &path.space_name).await?;
53 Ok(AppResponseData::empty())
54}
Modified lib/space/src/services/spaces.rs View lib/space/src/services/spaces.rs
@@ -410,15 +410,31 @@
410410
411411 #[tracing::instrument(name = "space.delete_space", skip_all, fields(caller = %caller, space = %space_name))]
412412 pub async fn delete_space(&self, caller: i64, space_name: &str) -> Result<(), SpaceError> {
413 let space = lookup_space_by_name(self, caller, space_name).await?;
413 let lower = space_name.trim().to_lowercase();
414 if lower.is_empty() {
415 return Err(SpaceError::NotFound);
416 }
417 // Use the authoritative lookup (not the active-only one) so a retried
418 // DELETE stays idempotent after the organization has been archived.
419 let space = authority::space_by_slug(&self.state, caller, &lower).await?;
414420 if space.owner_id != caller {
415421 return Err(SpaceError::Forbidden);
416422 }
417423
418 authority::client(&self.state)?
419 .delete_organization(caller, &space.id.to_string(), true, "space deleted")
420 .await?;
421 authority::remove_extras(&self.state, space.id).await?;
424 deletion::request_space_deletion(&self.state, caller, space.id, "space deleted").await?;
425 Ok(())
426 }
427
428 pub async fn restore_space(&self, caller: i64, space_name: &str) -> Result<(), SpaceError> {
429 let lower = space_name.trim().to_lowercase();
430 if lower.is_empty() {
431 return Err(SpaceError::NotFound);
432 }
433 let space = authority::space_by_slug(&self.state, caller, &lower).await?;
434 if space.owner_id != caller {
435 return Err(SpaceError::Forbidden);
436 }
437 deletion::cancel_space_deletion(&self.state, caller, space.id).await?;
422438 Ok(())
423439 }
424440
Modified lib/space/src/errors.rs View lib/space/src/errors.rs
@@ -35,6 +35,24 @@
3535
3636impl std::error::Error for SpaceError {}
3737
38impl From<deletion::DeletionError> for SpaceError {
39 fn from(error: deletion::DeletionError) -> Self {
40 use deletion::DeletionError;
41 match error {
42 DeletionError::Database(error) => Self::from(error),
43 DeletionError::Conflict(message) => Self::Conflict(message),
44 DeletionError::NotFound(message) => {
45 tracing::debug!(%message, "deletion target not found");
46 Self::NotFound
47 }
48 DeletionError::KenAuth(message)
49 | DeletionError::KenShell(message)
50 | DeletionError::Config(message) => Self::Internal(message),
51 }
52 }
53}
54
55
3856impl From<kenauth::KenAuthError> for SpaceError {
3957 fn from(error: kenauth::KenAuthError) -> Self {
4058 match error {
Modified lib/space/src/openapi.rs View lib/space/src/openapi.rs
@@ -12,6 +12,7 @@
1212 crate::handlers::list_spaces::list_spaces,
1313 crate::handlers::update_space::update_space,
1414 crate::handlers::delete_space::delete_space,
15 crate::handlers::delete_space::restore_space,
1516 crate::handlers::upload_space_image::upload_space_image,
1617 crate::handlers::delete_space_image::delete_space_image,
1718 crate::handlers::explore_spaces::explore_spaces,
Modified lib/space/src/routes.rs View lib/space/src/routes.rs
@@ -12,7 +12,7 @@
1212use crate::handlers::create_space::create_space;
1313use crate::handlers::delete_group::delete_group;
1414use crate::handlers::delete_join_rule::delete_join_rule;
15use crate::handlers::delete_space::delete_space;
15use crate::handlers::delete_space::{delete_space, restore_space};
1616use crate::handlers::delete_space_image::delete_space_image;
1717use crate::handlers::explore_spaces::explore_spaces;
1818use crate::handlers::explore_spaces::{
@@ -76,6 +76,7 @@
7676 "/spaces/{space_name}",
7777 patch(update_space).delete(delete_space),
7878 )
79 .route("/spaces/{space_name}/restore", post(restore_space))
7980 .route(
8081 "/spaces/{space_name}/images/{kind}",
8182 put(upload_space_image).delete(delete_space_image).layer(
Modified lib/space/Cargo.toml View lib/space/Cargo.toml
@@ -6,6 +6,7 @@
66[dependencies]
77async-trait = { workspace = true }
88config = { workspace = true }
9deletion = { workspace = true }
910export = { workspace = true }
1011i18n = { workspace = true }
1112imaging = { workspace = true }
Modified lib/webapi/src/lib.rs View lib/webapi/src/lib.rs
@@ -83,7 +83,13 @@
8383 let pulls = pulls::services::service::PullService::new(state.clone());
8484 let internal_service_key = state.config.internal.service_key.clone();
8585 let jwt_verifier = if state.config.oauth.enabled {
86 match jwt::JwtVerifier::new(&state.config.oauth) {
86 match jwt::JwtVerifier::with_identity(
87 &state.config.oauth,
88 state
89 .kenauth
90 .as_ref()
91 .map(|client| client.service().clone()),
92 ) {
8793 Ok(verifier) => Some(verifier),
8894 Err(error) => {
8995 tracing::warn!(%error, "oauth is enabled but the JWT verifier could not be built");
Modified scripts/dev.ps1 View scripts/dev.ps1
@@ -27,7 +27,7 @@
2727
2828$Services = [ordered]@{
2929 migrate = @{ Exe = 'kenapi'; Args = 'migrate'; Port = 0; Health = $false }
30 apihost = @{ Exe = 'kenapi'; Args = 'serve'; Port = 4081; Health = $true }
30 apihost = @{ Exe = 'kenapi'; Args = 'serve'; Port = 7051; Health = $true }
3131}
3232$Order = @('migrate', 'apihost')
3333$Backends = @('migrate', 'apihost')
@@ -36,7 +36,7 @@
3636}
3737
3838$Urls = [ordered]@{
39 apihost = 'http://127.0.0.1:4081 (Swagger: /api/docs)'
39 apihost = 'http://127.0.0.1:7051 (Swagger: /api/docs)'
4040}
4141
4242$Infra = @(
Modified scripts/dev.sh View scripts/dev.sh
@@ -51,7 +51,7 @@
5151
5252port_of() {
5353 case "$1" in
54 apihost) echo 4081 ;;
54 apihost) echo 7051 ;;
5555 *) echo 0 ;;
5656 esac
5757}
@@ -71,7 +71,7 @@
7171
7272url_of() {
7373 case "$1" in
74 apihost) echo "http://127.0.0.1:4081 (Swagger: /api/docs)" ;;
74 apihost) echo "http://127.0.0.1:7051 (Swagger: /api/docs)" ;;
7575 esac
7676}
7777
@@ -457,7 +457,7 @@
457457 fi
458458 kill -TERM "$pid" 2>/dev/null
459459 local i=0
460 while [ "$i" -lt 10 ]; do
460 while [ "$i" -lt 120 ]; do
461461 kill -0 "$pid" 2>/dev/null || return 0
462462 sleep 1; i=$((i + 1))
463463 done
Modified .dockerignore View .dockerignore
@@ -12,3 +12,7 @@
1212*.iml
1313Dockerfile
1414.dockerignore
15
16lib/identity/node_modules
17lib/identity/view/dist
18config/api/keys
Modified .gitignore View .gitignore
@@ -12,4 +12,7 @@
1212.codegraph
1313sagent-plan.md
1414.playwright-mcp
15.codanna
No newline at end of file
15.codanna
16lib/identity/node_modules
17lib/identity/view/dist
18config/api/keys/
Modified Cargo.lock View Cargo.lock
@@ -3,6 +3,189 @@
33version = 4
44
55[[package]]
6name = "actix-codec"
7version = "0.5.4"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "4c13df95297bcf9014dc89162b0cc69431e192e34e3b419612fc124cfcd45dbf"
10dependencies = [
11 "bitflags 2.13.2",
12 "bytes",
13 "futures-core",
14 "futures-sink",
15 "memchr",
16 "pin-project-lite",
17 "tokio",
18 "tokio-util",
19 "tracing",
20]
21
22[[package]]
23name = "actix-http"
24version = "3.20.0"
25source = "registry+https://github.com/rust-lang/crates.io-index"
26checksum = "d20a22850ea7fecad12c5f8cd97d246c3af1f04ef400033977c2f13425c45816"
27dependencies = [
28 "actix-codec",
29 "actix-service",
30 "actix-utils",
31 "base64 0.22.1",
32 "bitflags 2.13.2",
33 "brotli 9.1.1",
34 "bytes",
35 "bytestring",
36 "derive_more",
37 "encoding_rs",
38 "flate2",
39 "foldhash 0.2.0",
40 "futures-core",
41 "h2 0.3.27",
42 "http 0.2.12",
43 "httparse",
44 "httpdate",
45 "itoa",
46 "language-tags 0.3.2",
47 "local-channel",
48 "mime 0.3.17",
49 "percent-encoding 2.3.2",
50 "pin-project-lite",
51 "rand 0.10.3",
52 "sha1 0.11.0",
53 "smallvec",
54 "tokio",
55 "tokio-util",
56 "tracing",
57 "zstd 0.14.3",
58]
59
60[[package]]
61name = "actix-macros"
62version = "0.2.5"
63source = "registry+https://github.com/rust-lang/crates.io-index"
64checksum = "367f814ad4afbac74f07df5001214da65f65e185c90ef56c4dd8df23f8695b9b"
65dependencies = [
66 "proc-macro2",
67 "quote",
68 "syn 3.0.9",
69]
70
71[[package]]
72name = "actix-router"
73version = "0.5.4"
74source = "registry+https://github.com/rust-lang/crates.io-index"
75checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7"
76dependencies = [
77 "bytestring",
78 "cfg-if",
79 "http 0.2.12",
80 "regex",
81 "regex-lite",
82 "serde",
83 "tracing",
84]
85
86[[package]]
87name = "actix-rt"
88version = "2.15.0"
89source = "registry+https://github.com/rust-lang/crates.io-index"
90checksum = "e5f794807f82bbd36430c12cd600c73bbab0f52fdde4f0ed49978df113f4807f"
91dependencies = [
92 "futures-core",
93 "tokio",
94]
95
96[[package]]
97name = "actix-server"
98version = "2.10.0"
99source = "registry+https://github.com/rust-lang/crates.io-index"
100checksum = "5e296908c4d6f3a191e6b9873dbdfec8023350a89129d2b470f61d17233931ee"
101dependencies = [
102 "actix-rt",
103 "actix-service",
104 "futures-core",
105 "futures-util",
106 "mio",
107 "socket2 0.6.5",
108 "tokio",
109 "tracing",
110]
111
112[[package]]
113name = "actix-service"
114version = "2.0.3"
115source = "registry+https://github.com/rust-lang/crates.io-index"
116checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f"
117dependencies = [
118 "futures-core",
119 "pin-project-lite",
120]
121
122[[package]]
123name = "actix-utils"
124version = "3.0.2"
125source = "registry+https://github.com/rust-lang/crates.io-index"
126checksum = "0128396dd7313f697ad05b21b1a7be7d4cbb81888704f55996e4a27db196bb4d"
127dependencies = [
128 "local-waker",
129 "pin-project-lite",
130]
131
132[[package]]
133name = "actix-web"
134version = "4.18.1"
135source = "registry+https://github.com/rust-lang/crates.io-index"
136checksum = "81f8da0d2ae4200dde8773d72ba47a65c220b251ba7b9248db5bd263c247e959"
137dependencies = [
138 "actix-codec",
139 "actix-http",
140 "actix-macros",
141 "actix-router",
142 "actix-rt",
143 "actix-server",
144 "actix-service",
145 "actix-utils",
146 "actix-web-codegen",
147 "bytes",
148 "bytestring",
149 "cfg-if",
150 "cookie",
151 "derive_more",
152 "encoding_rs",
153 "foldhash 0.2.0",
154 "futures-core",
155 "futures-util",
156 "impl-more",
157 "itoa",
158 "language-tags 0.3.2",
159 "log 0.4.34",
160 "mime 0.3.17",
161 "once_cell",
162 "pin-project-lite",
163 "regex",
164 "regex-lite",
165 "serde",
166 "serde_json",
167 "serde_urlencoded",
168 "smallvec",
169 "socket2 0.6.5",
170 "time 0.3.55",
171 "tokio",
172 "tracing",
173 "url 2.5.8",
174]
175
176[[package]]
177name = "actix-web-codegen"
178version = "4.5.0"
179source = "registry+https://github.com/rust-lang/crates.io-index"
180checksum = "f7c9f4c52b954a73c76757a3952d3437fca00a2fbcb49704f8a6ea9f767e73f0"
181dependencies = [
182 "actix-router",
183 "proc-macro2",
184 "quote",
185 "syn 3.0.9",
186]
187
188[[package]]
6189name = "adler2"
7190version = "2.0.1"
8191source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -14,7 +197,7 @@
14197source = "registry+https://github.com/rust-lang/crates.io-index"
15198checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
16199dependencies = [
17 "crypto-common 0.1.6",
200 "crypto-common 0.1.7",
18201 "generic-array",
19202]
20203
@@ -25,7 +208,7 @@
25208checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99"
26209dependencies = [
27210 "crypto-common 0.2.2",
28 "inout 0.2.2",
211 "inout 0.2.5",
29212]
30213
31214[[package]]
@@ -41,13 +224,13 @@
41224
42225[[package]]
43226name = "aes"
44version = "0.9.2"
227version = "0.9.3"
45228source = "registry+https://github.com/rust-lang/crates.io-index"
46checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58"
229checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32"
47230dependencies = [
48231 "cipher 0.5.2",
49232 "cpubits",
50 "cpufeatures 0.3.0",
233 "cpufeatures 0.3.1",
51234]
52235
53236[[package]]
@@ -66,16 +249,16 @@
66249
67250[[package]]
68251name = "aes-gcm"
69version = "0.11.0"
252version = "0.11.1"
70253source = "registry+https://github.com/rust-lang/crates.io-index"
71checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028"
254checksum = "7f2b8006a0c83f52b62ba44a97b58bf76fe2f70a329e588f67f89691d93d498f"
72255dependencies = [
73256 "aead 0.6.1",
74 "aes 0.9.2",
257 "aes 0.9.3",
75258 "cipher 0.5.2",
76259 "ctr 0.10.1",
260 "ctutils",
77261 "ghash 0.6.0",
78 "subtle",
79262]
80263
81264[[package]]
@@ -84,7 +267,7 @@
84267source = "registry+https://github.com/rust-lang/crates.io-index"
85268checksum = "10b6f24a1f796bc46415a1d0d18dc0a8203ccba088acf5def3291c4f61225522"
86269dependencies = [
87 "aes 0.9.2",
270 "aes 0.9.3",
88271 "byteorder",
89272]
90273
@@ -135,12 +318,27 @@
135318checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
136319
137320[[package]]
321name = "alloc-no-stdlib"
322version = "3.0.0"
323source = "registry+https://github.com/rust-lang/crates.io-index"
324checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a"
325
326[[package]]
138327name = "alloc-stdlib"
139328version = "0.2.4"
140329source = "registry+https://github.com/rust-lang/crates.io-index"
141330checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
142331dependencies = [
143 "alloc-no-stdlib",
332 "alloc-no-stdlib 2.0.4",
333]
334
335[[package]]
336name = "alloc-stdlib"
337version = "0.3.0"
338source = "registry+https://github.com/rust-lang/crates.io-index"
339checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1"
340dependencies = [
341 "alloc-no-stdlib 3.0.0",
144342]
145343
146344[[package]]
@@ -210,9 +408,9 @@
210408
211409[[package]]
212410name = "antidote"
213version = "1.0.0"
411version = "1.1.0"
214412source = "registry+https://github.com/rust-lang/crates.io-index"
215checksum = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
413checksum = "307f1158c6f649671b2c5b2939b7513de520500dfe92913a49d5d313e44a6ee7"
216414
217415[[package]]
218416name = "anyhow"
@@ -222,18 +420,18 @@
222420
223421[[package]]
224422name = "arbitrary"
225version = "1.4.2"
423version = "1.5.0"
226424source = "registry+https://github.com/rust-lang/crates.io-index"
227checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
425checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed"
228426dependencies = [
229427 "derive_arbitrary",
230428]
231429
232430[[package]]
233431name = "arc-swap"
234version = "1.9.2"
432version = "1.9.3"
235433source = "registry+https://github.com/rust-lang/crates.io-index"
236checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b"
434checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e"
237435dependencies = [
238436 "rustversion",
239437]
@@ -252,7 +450,7 @@
252450dependencies = [
253451 "base64ct",
254452 "blake2",
255 "cpufeatures 0.3.0",
453 "cpufeatures 0.3.1",
256454 "password-hash",
257455]
258456
@@ -270,9 +468,9 @@
270468
271469[[package]]
272470name = "arrow"
273version = "58.4.0"
471version = "60.0.0"
274472source = "registry+https://github.com/rust-lang/crates.io-index"
275checksum = "6cfdd0833e32a9874d2b55089333ad310c0be208aafa277385ce2461dec90be3"
473checksum = "234ce58e76c04e8162c2e4bc3fe53fa7c2418ef5fba7bb2838b8531b7d4fbf14"
276474dependencies = [
277475 "arrow-arith",
278476 "arrow-array",
@@ -288,9 +486,9 @@
288486
289487[[package]]
290488name = "arrow-arith"
291version = "58.4.0"
489version = "60.0.0"
292490source = "registry+https://github.com/rust-lang/crates.io-index"
293checksum = "0a41203398f0eaa6f7ec8e62c0da742a21abf282c148fc157f6c35c90e29981a"
491checksum = "2303ebedb1071f4f72d71171e383859a9a285c94d99e4892685a96fca9890720"
294492dependencies = [
295493 "arrow-array",
296494 "arrow-buffer",
@@ -302,9 +500,9 @@
302500
303501[[package]]
304502name = "arrow-array"
305version = "58.4.0"
503version = "60.0.0"
306504source = "registry+https://github.com/rust-lang/crates.io-index"
307checksum = "ae33dad492b7df00a217563a7b0ef2874df68a0deea1b1a3acf628152f7f7a69"
505checksum = "51dff6e4b9f158864a0aeb6a131ad53858ecbae7a2fc8989307b9b3c6b2f122e"
308506dependencies = [
309507 "ahash 0.8.12",
310508 "arrow-buffer",
@@ -320,21 +518,21 @@
320518
321519[[package]]
322520name = "arrow-buffer"
323version = "58.4.0"
521version = "60.0.0"
324522source = "registry+https://github.com/rust-lang/crates.io-index"
325checksum = "b9552f96391c005e6ab449fa941420935e7e062489b12b8b1b08879b2163f5b5"
523checksum = "c7235b863533e9db3ab86905b4521251de11226275b436ff3cb2d0010558d1c5"
326524dependencies = [
327525 "bytes",
328526 "half",
329 "num-bigint 0.4.8",
527 "num-bigint 0.5.1",
330528 "num-traits",
331529]
332530
333531[[package]]
334532name = "arrow-cast"
335version = "58.4.0"
533version = "60.0.0"
336534source = "registry+https://github.com/rust-lang/crates.io-index"
337checksum = "3a8a327c9649f30d8406995f27642b68df354713cca3baaaf100f076f18d5f34"
535checksum = "d52cf840f0c34aeeb7e8b05aba72d66878e6c001827f423331f0b260209e7b7e"
338536dependencies = [
339537 "arrow-array",
340538 "arrow-buffer",
@@ -342,8 +540,8 @@
342540 "arrow-ord",
343541 "arrow-schema",
344542 "arrow-select",
345 "atoi",
346 "base64 0.22.1",
543 "atoi 3.1.1",
544 "base64 0.23.1",
347545 "chrono",
348546 "half",
349547 "lexical-core",
@@ -352,10 +550,21 @@
352550]
353551
354552[[package]]
553name = "arrow-cmp"
554version = "60.0.0"
555source = "registry+https://github.com/rust-lang/crates.io-index"
556checksum = "73a0e9adb28d95739fabcdae637261a2c14e618caf58ba1b88857120f9ee2687"
557dependencies = [
558 "arrow-array",
559 "arrow-buffer",
560 "arrow-schema",
561]
562
563[[package]]
355564name = "arrow-data"
356version = "58.4.0"
565version = "60.0.0"
357566source = "registry+https://github.com/rust-lang/crates.io-index"
358checksum = "2b24852db04738907e06c04ea61e42fe7fda962a34513022dc0d0e754fb7976b"
567checksum = "5aa06d5e22786c4966ffc4d2f171d0cb65d95f6bdea108a14e8c335e9faa437f"
359568dependencies = [
360569 "arrow-buffer",
361570 "arrow-schema",
@@ -366,12 +575,13 @@
366575
367576[[package]]
368577name = "arrow-ord"
369version = "58.4.0"
578version = "60.0.0"
370579source = "registry+https://github.com/rust-lang/crates.io-index"
371checksum = "63a083ec750f5c043f02946b4baf05fcdbb55f4560a3277055caca5cc99f3eb0"
580checksum = "5a7ac2c6a9cdc782fa9548d3763461eaec8c93347e297fddbaa44807e70a3698"
372581dependencies = [
373582 "arrow-array",
374583 "arrow-buffer",
584 "arrow-cmp",
375585 "arrow-data",
376586 "arrow-schema",
377587 "arrow-select",
@@ -379,9 +589,9 @@
379589
380590[[package]]
381591name = "arrow-row"
382version = "58.4.0"
592version = "60.0.0"
383593source = "registry+https://github.com/rust-lang/crates.io-index"
384checksum = "514ba0ef0d4c5896202dae736251ce415abb43a950bed570fb7981b8716c0e4c"
594checksum = "758b87e9648d80001e0916c083334ed93074c677a81a96144d5c84bd2d8eab2a"
385595dependencies = [
386596 "arrow-array",
387597 "arrow-buffer",
@@ -392,19 +602,20 @@
392602
393603[[package]]
394604name = "arrow-schema"
395version = "58.4.0"
605version = "60.0.0"
396606source = "registry+https://github.com/rust-lang/crates.io-index"
397checksum = "21ca356ad6425cecb6eb7b28e4f659f1ee7880fbb1a16127de7dd62901efee9e"
607checksum = "f85a9a58cd03560526b0066487574aacb9008e413504fa0ee23d61141e96c338"
398608
399609[[package]]
400610name = "arrow-select"
401version = "58.4.0"
611version = "60.0.0"
402612source = "registry+https://github.com/rust-lang/crates.io-index"
403checksum = "c58da39eb3d8350ad4a549e5c2bc49284dac554016c69829310350f1731b0aad"
613checksum = "ba3482b7d03bf4a3bf0b195f08a7f2b071595ccd54897e35b975ac63d3104977"
404614dependencies = [
405615 "ahash 0.8.12",
406616 "arrow-array",
407617 "arrow-buffer",
618 "arrow-cmp",
408619 "arrow-data",
409620 "arrow-schema",
410621 "num-traits",
@@ -412,9 +623,9 @@
412623
413624[[package]]
414625name = "arrow-string"
415version = "58.4.0"
626version = "60.0.0"
416627source = "registry+https://github.com/rust-lang/crates.io-index"
417checksum = "b6789b388467525e3271326b6b4915666ecfdf5142aef09779445c954b67543c"
628checksum = "f300b4400b1e7cd58f7cbbd5dd2a0072cc59e73bfaee2a152b3ac660e46d9dd1"
418629dependencies = [
419630 "arrow-array",
420631 "arrow-buffer",
@@ -481,7 +692,7 @@
481692dependencies = [
482693 "proc-macro2",
483694 "quote",
484 "syn 3.0.3",
695 "syn 3.0.9",
485696]
486697
487698[[package]]
@@ -494,6 +705,15 @@
494705]
495706
496707[[package]]
708name = "atoi"
709version = "3.1.1"
710source = "registry+https://github.com/rust-lang/crates.io-index"
711checksum = "d12c37257553c19e9aee6cba77814fdc9e07844abfc8b3a2c377cd920ee5a993"
712dependencies = [
713 "num-traits",
714]
715
716[[package]]
497717name = "atomic"
498718version = "0.6.1"
499719source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -515,7 +735,7 @@
515735checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2"
516736dependencies = [
517737 "http 0.2.12",
518 "log 0.4.33",
738 "log 0.4.34",
519739 "native-tls",
520740 "serde",
521741 "serde_json",
@@ -532,28 +752,30 @@
532752 "axum",
533753 "base64 0.23.1",
534754 "config 1.0.0",
755 "deletion",
535756 "export",
536757 "hkdf 0.13.0",
537758 "hmac 0.13.0",
538759 "i18n",
539760 "jwt",
540761 "kenauth",
762 "kenoauth",
541763 "maud",
542764 "models",
543765 "pulldown-cmark",
544 "rand 0.8.7",
766 "rand 0.8.8",
545767 "redis",
546768 "reqwest 0.12.28",
547769 "sea-orm",
548770 "serde",
549771 "serde_json",
550 "sha2 0.11.0",
772 "sha2 0.11.1",
551773 "space",
552774 "time 0.3.55",
553775 "tokio",
554776 "tracing",
555777 "url 2.5.8",
556 "utoipa",
778 "utoipa 5.5.0",
557779 "uuid",
558780]
559781
@@ -571,7 +793,7 @@
571793dependencies = [
572794 "attohttpc",
573795 "dirs 5.0.1",
574 "log 0.4.33",
796 "log 0.4.34",
575797 "quick-xml",
576798 "rust-ini 0.18.0",
577799 "serde",
@@ -582,9 +804,9 @@
582804
583805[[package]]
584806name = "aws-lc-rs"
585version = "1.18.0"
807version = "1.19.0"
586808source = "registry+https://github.com/rust-lang/crates.io-index"
587checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e"
809checksum = "048006790404103c488e6c5616b3e7e702a71292aadda08e8f7698a9e0d2eb53"
588810dependencies = [
589811 "aws-lc-sys",
590812 "zeroize",
@@ -592,9 +814,9 @@
592814
593815[[package]]
594816name = "aws-lc-sys"
595version = "0.44.0"
817version = "0.47.0"
596818source = "registry+https://github.com/rust-lang/crates.io-index"
597checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483"
819checksum = "8bfbe32ac36b3c428696ce315464181bc4029d689fd58e3f2a4e58c252242f52"
598820dependencies = [
599821 "cc",
600822 "cmake",
@@ -626,7 +848,7 @@
626848 "http 1.5.0",
627849 "http-body 1.1.0",
628850 "http-body-util",
629 "hyper 1.11.0",
851 "hyper 1.12.0",
630852 "hyper-util",
631853 "itoa",
632854 "matchit",
@@ -732,9 +954,9 @@
732954
733955[[package]]
734956name = "bigdecimal"
735version = "0.4.10"
957version = "0.4.11"
736958source = "registry+https://github.com/rust-lang/crates.io-index"
737checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695"
959checksum = "8fa3f3d8cbf4dffcfe4991de61d012bef509a409ecbe9dd41049bfe32b4d4653"
738960dependencies = [
739961 "autocfg",
740962 "libm",
@@ -758,9 +980,9 @@
758980
759981[[package]]
760982name = "bitflags"
761version = "2.13.1"
983version = "2.13.2"
762984source = "registry+https://github.com/rust-lang/crates.io-index"
763checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
985checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
764986dependencies = [
765987 "serde_core",
766988]
@@ -825,9 +1047,9 @@
8251047
8261048[[package]]
8271049name = "borsh"
828version = "1.8.0"
1050version = "1.8.1"
8291051source = "registry+https://github.com/rust-lang/crates.io-index"
830checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f"
1052checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a"
8311053dependencies = [
8321054 "borsh-derive",
8331055 "bytes",
@@ -836,15 +1058,15 @@
8361058
8371059[[package]]
8381060name = "borsh-derive"
839version = "1.8.0"
1061version = "1.8.1"
8401062source = "registry+https://github.com/rust-lang/crates.io-index"
841checksum = "d8f347189c62a579b8cd5f80714efa178f52e461dc2e6d701d264f5ff22e566c"
1063checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56"
8421064dependencies = [
8431065 "once_cell",
8441066 "proc-macro-crate",
8451067 "proc-macro2",
8461068 "quote",
847 "syn 2.0.119",
1069 "syn 3.0.9",
8481070]
8491071
8501072[[package]]
@@ -853,9 +1075,20 @@
8531075source = "registry+https://github.com/rust-lang/crates.io-index"
8541076checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
8551077dependencies = [
856 "alloc-no-stdlib",
857 "alloc-stdlib",
858 "brotli-decompressor",
1078 "alloc-no-stdlib 2.0.4",
1079 "alloc-stdlib 0.2.4",
1080 "brotli-decompressor 5.0.3",
1081]
1082
1083[[package]]
1084name = "brotli"
1085version = "9.1.1"
1086source = "registry+https://github.com/rust-lang/crates.io-index"
1087checksum = "627b7cfb0578f0eafce087a90fcd40add1231900b69e4d4de2d456260a4f5d81"
1088dependencies = [
1089 "alloc-no-stdlib 3.0.0",
1090 "alloc-stdlib 0.3.0",
1091 "brotli-decompressor 6.2.0",
8591092]
8601093
8611094[[package]]
@@ -864,8 +1097,18 @@
8641097source = "registry+https://github.com/rust-lang/crates.io-index"
8651098checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
8661099dependencies = [
867 "alloc-no-stdlib",
868 "alloc-stdlib",
1100 "alloc-no-stdlib 2.0.4",
1101 "alloc-stdlib 0.2.4",
1102]
1103
1104[[package]]
1105name = "brotli-decompressor"
1106version = "6.2.0"
1107source = "registry+https://github.com/rust-lang/crates.io-index"
1108checksum = "34c6783f42c21c952ad7112b354f0ba2f10760e9decea5b1b60523d6e270faaf"
1109dependencies = [
1110 "alloc-no-stdlib 3.0.0",
1111 "alloc-stdlib 0.3.0",
8691112]
8701113
8711114[[package]]
@@ -885,28 +1128,6 @@
8851128checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
8861129
8871130[[package]]
888name = "bytecheck"
889version = "0.6.12"
890source = "registry+https://github.com/rust-lang/crates.io-index"
891checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
892dependencies = [
893 "bytecheck_derive",
894 "ptr_meta",
895 "simdutf8",
896]
897
898[[package]]
899name = "bytecheck_derive"
900version = "0.6.12"
901source = "registry+https://github.com/rust-lang/crates.io-index"
902checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
903dependencies = [
904 "proc-macro2",
905 "quote",
906 "syn 1.0.109",
907]
908
909[[package]]
9101131name = "bytemuck"
9111132version = "1.25.2"
9121133source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -923,7 +1144,7 @@
9231144dependencies = [
9241145 "proc-macro2",
9251146 "quote",
926 "syn 3.0.3",
1147 "syn 3.0.9",
9271148]
9281149
9291150[[package]]
@@ -945,6 +1166,15 @@
9451166checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
9461167
9471168[[package]]
1169name = "bytestring"
1170version = "1.5.1"
1171source = "registry+https://github.com/rust-lang/crates.io-index"
1172checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9"
1173dependencies = [
1174 "bytes",
1175]
1176
1177[[package]]
9481178name = "cache"
9491179version = "1.0.0"
9501180dependencies = [
@@ -956,9 +1186,9 @@
9561186
9571187[[package]]
9581188name = "cc"
959version = "1.4.3"
1189version = "1.10.0"
9601190source = "registry+https://github.com/rust-lang/crates.io-index"
961checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
1191checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0"
9621192dependencies = [
9631193 "find-msvc-tools",
9641194 "jobserver",
@@ -974,9 +1204,9 @@
9741204
9751205[[package]]
9761206name = "cfg-if"
977version = "1.0.4"
1207version = "1.0.5"
9781208source = "registry+https://github.com/rust-lang/crates.io-index"
979checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
1209checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
9801210
9811211[[package]]
9821212name = "cfg_aliases"
@@ -986,12 +1216,12 @@
9861216
9871217[[package]]
9881218name = "chacha20"
989version = "0.10.1"
1219version = "0.10.2"
9901220source = "registry+https://github.com/rust-lang/crates.io-index"
991checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
1221checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
9921222dependencies = [
9931223 "cfg-if",
994 "cpufeatures 0.3.0",
1224 "cpufeatures 0.3.1",
9951225 "rand_core 0.10.1",
9961226]
9971227
@@ -1017,13 +1247,13 @@
10171247 "livekit-protocol",
10181248 "models",
10191249 "queue",
1020 "rand 0.8.7",
1250 "rand 0.8.8",
10211251 "redis",
10221252 "reqwest 0.12.28",
10231253 "sea-orm",
10241254 "serde",
10251255 "serde_json",
1026 "sha2 0.11.0",
1256 "sha2 0.11.1",
10271257 "social",
10281258 "space",
10291259 "storage",
@@ -1032,7 +1262,7 @@
10321262 "tokio-util",
10331263 "tracing",
10341264 "url 2.5.8",
1035 "utoipa",
1265 "utoipa 5.5.0",
10361266 "uuid",
10371267]
10381268
@@ -1077,7 +1307,7 @@
10771307source = "registry+https://github.com/rust-lang/crates.io-index"
10781308checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
10791309dependencies = [
1080 "crypto-common 0.1.6",
1310 "crypto-common 0.1.7",
10811311 "inout 0.1.4",
10821312]
10831313
@@ -1089,7 +1319,7 @@
10891319dependencies = [
10901320 "block-buffer 0.12.1",
10911321 "crypto-common 0.2.2",
1092 "inout 0.2.2",
1322 "inout 0.2.5",
10931323]
10941324
10951325[[package]]
@@ -1123,7 +1353,7 @@
11231353 "heck 0.5.0",
11241354 "proc-macro2",
11251355 "quote",
1126 "syn 3.0.3",
1356 "syn 3.0.9",
11271357]
11281358
11291359[[package]]
@@ -1181,9 +1411,9 @@
11811411
11821412[[package]]
11831413name = "combine"
1184version = "4.6.7"
1414version = "4.6.9"
11851415source = "registry+https://github.com/rust-lang/crates.io-index"
1186checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
1416checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92"
11871417dependencies = [
11881418 "bytes",
11891419 "futures-core",
@@ -1199,7 +1429,7 @@
11991429source = "registry+https://github.com/rust-lang/crates.io-index"
12001430checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf"
12011431dependencies = [
1202 "brotli",
1432 "brotli 8.0.4",
12031433 "compression-core",
12041434 "flate2",
12051435 "memchr",
@@ -1218,7 +1448,7 @@
12181448checksum = "38e9fbc53afcec54441422f1c596cd054655b32e70e1cbac956651b7b175a404"
12191449dependencies = [
12201450 "async-trait",
1221 "convert_case",
1451 "convert_case 0.6.0",
12221452 "json5",
12231453 "pathdiff",
12241454 "ron",
@@ -1240,7 +1470,7 @@
12401470 "figment",
12411471 "sea-orm",
12421472 "serde",
1243 "thiserror 2.0.20",
1473 "thiserror 2.0.21",
12441474 "tracing",
12451475 "url 2.5.8",
12461476]
@@ -1293,6 +1523,26 @@
12931523]
12941524
12951525[[package]]
1526name = "convert_case"
1527version = "0.10.0"
1528source = "registry+https://github.com/rust-lang/crates.io-index"
1529checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
1530dependencies = [
1531 "unicode-segmentation",
1532]
1533
1534[[package]]
1535name = "cookie"
1536version = "0.16.2"
1537source = "registry+https://github.com/rust-lang/crates.io-index"
1538checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
1539dependencies = [
1540 "percent-encoding 2.3.2",
1541 "time 0.3.55",
1542 "version_check 0.9.5",
1543]
1544
1545[[package]]
12961546name = "core-foundation"
12971547version = "0.9.4"
12981548source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1319,6 +1569,12 @@
13191569checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
13201570
13211571[[package]]
1572name = "core_detect"
1573version = "1.0.0"
1574source = "registry+https://github.com/rust-lang/crates.io-index"
1575checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
1576
1577[[package]]
13221578name = "cpubits"
13231579version = "0.1.1"
13241580source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1335,9 +1591,9 @@
13351591
13361592[[package]]
13371593name = "cpufeatures"
1338version = "0.3.0"
1594version = "0.3.1"
13391595source = "registry+https://github.com/rust-lang/crates.io-index"
1340checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
1596checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
13411597dependencies = [
13421598 "libc",
13431599]
@@ -1365,9 +1621,9 @@
13651621
13661622[[package]]
13671623name = "crc32fast"
1368version = "1.5.0"
1624version = "1.5.3"
13691625source = "registry+https://github.com/rust-lang/crates.io-index"
1370checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
1626checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055"
13711627dependencies = [
13721628 "cfg-if",
13731629]
@@ -1404,18 +1660,18 @@
14041660
14051661[[package]]
14061662name = "crossbeam-queue"
1407version = "0.3.13"
1663version = "0.3.14"
14081664source = "registry+https://github.com/rust-lang/crates.io-index"
1409checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26"
1665checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae"
14101666dependencies = [
14111667 "crossbeam-utils",
14121668]
14131669
14141670[[package]]
14151671name = "crossbeam-utils"
1416version = "0.8.22"
1672version = "0.8.23"
14171673source = "registry+https://github.com/rust-lang/crates.io-index"
1418checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
1674checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
14191675
14201676[[package]]
14211677name = "crunchy"
@@ -1437,9 +1693,9 @@
14371693
14381694[[package]]
14391695name = "crypto-common"
1440version = "0.1.6"
1696version = "0.1.7"
14411697source = "registry+https://github.com/rust-lang/crates.io-index"
1442checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
1698checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
14431699dependencies = [
14441700 "generic-array",
14451701 "rand_core 0.6.4",
@@ -1483,14 +1739,41 @@
14831739
14841740[[package]]
14851741name = "ctutils"
1486version = "0.4.2"
1742version = "0.4.7"
14871743source = "registry+https://github.com/rust-lang/crates.io-index"
1488checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
1744checksum = "a2376ffd6f96d8a9db59875b4b6b0237d6900b5baf64279714e05493ce64828a"
14891745dependencies = [
14901746 "cmov",
14911747]
14921748
14931749[[package]]
1750name = "curve25519-dalek"
1751version = "4.1.3"
1752source = "registry+https://github.com/rust-lang/crates.io-index"
1753checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
1754dependencies = [
1755 "cfg-if",
1756 "cpufeatures 0.2.17",
1757 "curve25519-dalek-derive",
1758 "digest 0.10.7",
1759 "fiat-crypto",
1760 "rustc_version",
1761 "subtle",
1762 "zeroize",
1763]
1764
1765[[package]]
1766name = "curve25519-dalek-derive"
1767version = "0.1.1"
1768source = "registry+https://github.com/rust-lang/crates.io-index"
1769checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
1770dependencies = [
1771 "proc-macro2",
1772 "quote",
1773 "syn 2.0.119",
1774]
1775
1776[[package]]
14941777name = "darling"
14951778version = "0.20.11"
14961779source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1547,6 +1830,53 @@
15471830checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
15481831
15491832[[package]]
1833name = "deadpool"
1834version = "0.13.1"
1835source = "registry+https://github.com/rust-lang/crates.io-index"
1836checksum = "3e98a7e119cd347f4201e1159b19831029e203e2d8b790547708e8157b4acf1e"
1837dependencies = [
1838 "deadpool-runtime",
1839 "tokio",
1840]
1841
1842[[package]]
1843name = "deadpool-redis"
1844version = "0.23.1"
1845source = "registry+https://github.com/rust-lang/crates.io-index"
1846checksum = "243852fff546d8c5aac6ceaac7587f7137915b102c3fbec6256a64ce52452bd2"
1847dependencies = [
1848 "deadpool",
1849 "redis",
1850]
1851
1852[[package]]
1853name = "deadpool-runtime"
1854version = "0.3.1"
1855source = "registry+https://github.com/rust-lang/crates.io-index"
1856checksum = "2657f61fb1dd8bf37a8d51093cc7cee4e77125b22f7753f49b289f831bec2bae"
1857dependencies = [
1858 "tokio",
1859]
1860
1861[[package]]
1862name = "deletion"
1863version = "1.0.0"
1864dependencies = [
1865 "config 1.0.0",
1866 "export",
1867 "kenauth",
1868 "kenshell",
1869 "models",
1870 "sea-orm",
1871 "serde",
1872 "thiserror 2.0.21",
1873 "time 0.3.55",
1874 "tokio",
1875 "tracing",
1876 "uuid",
1877]
1878
1879[[package]]
15501880name = "der"
15511881version = "0.7.10"
15521882source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1559,9 +1889,9 @@
15591889
15601890[[package]]
15611891name = "der"
1562version = "0.8.1"
1892version = "0.8.2"
15631893source = "registry+https://github.com/rust-lang/crates.io-index"
1564checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
1894checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a"
15651895dependencies = [
15661896 "const-oid 0.10.2",
15671897 "zeroize",
@@ -1569,33 +1899,33 @@
15691899
15701900[[package]]
15711901name = "deranged"
1572version = "0.5.8"
1902version = "0.5.9"
15731903source = "registry+https://github.com/rust-lang/crates.io-index"
1574checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
1904checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e"
15751905dependencies = [
15761906 "serde_core",
15771907]
15781908
15791909[[package]]
15801910name = "derive-where"
1581version = "1.6.1"
1911version = "1.7.0"
15821912source = "registry+https://github.com/rust-lang/crates.io-index"
1583checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534"
1913checksum = "2e2b94854e8576378ccda7c8de8a66ed8b4e8acbd2c50ec3418ea6c8aaf4b567"
15841914dependencies = [
15851915 "proc-macro2",
15861916 "quote",
1587 "syn 2.0.119",
1917 "syn 3.0.9",
15881918]
15891919
15901920[[package]]
15911921name = "derive_arbitrary"
1592version = "1.4.2"
1922version = "1.5.0"
15931923source = "registry+https://github.com/rust-lang/crates.io-index"
1594checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
1924checksum = "1b034bd7d5f032402a2479444dcc6f74e36a03f31854d41680fb240ef682a1ac"
15951925dependencies = [
15961926 "proc-macro2",
15971927 "quote",
1598 "syn 2.0.119",
1928 "syn 3.0.9",
15991929]
16001930
16011931[[package]]
@@ -1644,6 +1974,7 @@
16441974source = "registry+https://github.com/rust-lang/crates.io-index"
16451975checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
16461976dependencies = [
1977 "convert_case 0.10.0",
16471978 "proc-macro2",
16481979 "quote",
16491980 "rustc_version",
@@ -1658,9 +1989,9 @@
16581989checksum = "a2ca8e71544c1b67dcdbc2699ab258828aff985e5bc8d5f6b486d90d7df2f848"
16591990dependencies = [
16601991 "core-foundation 0.10.1",
1661 "jni",
1992 "jni 0.21.1",
16621993 "libc",
1663 "thiserror 2.0.20",
1994 "thiserror 2.0.21",
16641995 "wasm-bindgen",
16651996 "web-sys",
16661997 "windows-sys 0.59.0",
@@ -1674,7 +2005,7 @@
16742005dependencies = [
16752006 "block-buffer 0.10.4",
16762007 "const-oid 0.9.6",
1677 "crypto-common 0.1.6",
2008 "crypto-common 0.1.7",
16782009 "subtle",
16792010]
16802011
@@ -1738,7 +2069,7 @@
17382069source = "registry+https://github.com/rust-lang/crates.io-index"
17392070checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
17402071dependencies = [
1741 "bitflags 2.13.1",
2072 "bitflags 2.13.2",
17422073 "objc2",
17432074]
17442075
@@ -1750,7 +2081,7 @@
17502081dependencies = [
17512082 "proc-macro2",
17522083 "quote",
1753 "syn 3.0.3",
2084 "syn 3.0.9",
17542085]
17552086
17562087[[package]]
@@ -1806,6 +2137,16 @@
18062137]
18072138
18082139[[package]]
2140name = "ed25519"
2141version = "2.2.3"
2142source = "registry+https://github.com/rust-lang/crates.io-index"
2143checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
2144dependencies = [
2145 "pkcs8 0.10.2",
2146 "signature 2.2.0",
2147]
2148
2149[[package]]
18092150name = "ed25519-compact"
18102151version = "2.4.2"
18112152source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1816,10 +2157,24 @@
18162157]
18172158
18182159[[package]]
2160name = "ed25519-dalek"
2161version = "2.2.0"
2162source = "registry+https://github.com/rust-lang/crates.io-index"
2163checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
2164dependencies = [
2165 "curve25519-dalek",
2166 "ed25519",
2167 "serde",
2168 "sha2 0.10.9",
2169 "subtle",
2170 "zeroize",
2171]
2172
2173[[package]]
18192174name = "either"
1820version = "1.17.0"
2175version = "1.19.0"
18212176source = "registry+https://github.com/rust-lang/crates.io-index"
1822checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
2177checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
18232178dependencies = [
18242179 "serde",
18252180]
@@ -1846,19 +2201,40 @@
18462201]
18472202
18482203[[package]]
2204name = "email-encoding"
2205version = "0.4.2"
2206source = "registry+https://github.com/rust-lang/crates.io-index"
2207checksum = "420b9da095f052ea597503e39073b5b3c522f7db933fbac202d91d24492693fd"
2208dependencies = [
2209 "base64 0.23.1",
2210 "memchr",
2211]
2212
2213[[package]]
2214name = "email_address"
2215version = "0.2.9"
2216source = "registry+https://github.com/rust-lang/crates.io-index"
2217checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
2218
2219[[package]]
18492220name = "encoding_rs"
1850version = "0.8.35"
2221version = "0.8.42"
18512222source = "registry+https://github.com/rust-lang/crates.io-index"
1852checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
2223checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679"
18532224dependencies = [
18542225 "cfg-if",
2226 "core_detect",
2227 "multiversion_no_op",
2228 "rustversion",
2229 "scopeguard",
2230 "simdutf8",
18552231]
18562232
18572233[[package]]
18582234name = "equivalent"
1859version = "1.0.2"
2235version = "1.0.3"
18602236source = "registry+https://github.com/rust-lang/crates.io-index"
1861checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
2237checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
18622238
18632239[[package]]
18642240name = "erased-serde"
@@ -1942,9 +2318,9 @@
19422318 "sea-orm",
19432319 "serde",
19442320 "serde_json",
1945 "sha2 0.11.0",
2321 "sha2 0.11.1",
19462322 "storage",
1947 "thiserror 2.0.20",
2323 "thiserror 2.0.21",
19482324 "time 0.3.55",
19492325 "tokio",
19502326 "tower",
@@ -1952,7 +2328,7 @@
19522328 "tracing",
19532329 "tracing-opentelemetry",
19542330 "tracing-subscriber",
1955 "utoipa",
2331 "utoipa 5.5.0",
19562332 "uuid",
19572333]
19582334
@@ -1991,6 +2367,12 @@
19912367]
19922368
19932369[[package]]
2370name = "fiat-crypto"
2371version = "0.2.9"
2372source = "registry+https://github.com/rust-lang/crates.io-index"
2373checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
2374
2375[[package]]
19942376name = "figment"
19952377version = "0.10.19"
19962378source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1999,6 +2381,8 @@
19992381 "atomic",
20002382 "pear",
20012383 "serde",
2384 "serde_json",
2385 "serde_yaml",
20022386 "toml 0.8.23",
20032387 "uncased",
20042388 "version_check 0.9.5",
@@ -2006,9 +2390,9 @@
20062390
20072391[[package]]
20082392name = "find-msvc-tools"
2009version = "0.1.11"
2393version = "0.1.15"
20102394source = "registry+https://github.com/rust-lang/crates.io-index"
2011checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
2395checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48"
20122396
20132397[[package]]
20142398name = "fixedbitset"
@@ -2017,14 +2401,20 @@
20172401checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
20182402
20192403[[package]]
2404name = "fixedbitset"
2405version = "0.5.7"
2406source = "registry+https://github.com/rust-lang/crates.io-index"
2407checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
2408
2409[[package]]
20202410name = "flate2"
2021version = "1.1.9"
2411version = "1.1.10"
20222412source = "registry+https://github.com/rust-lang/crates.io-index"
2023checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
2413checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
20242414dependencies = [
20252415 "crc32fast",
20262416 "libz-sys",
2027 "miniz_oxide",
2417 "miniz_oxide 0.9.1",
20282418 "zlib-rs",
20292419]
20302420
@@ -2048,6 +2438,12 @@
20482438
20492439[[package]]
20502440name = "foldhash"
2441version = "0.1.5"
2442source = "registry+https://github.com/rust-lang/crates.io-index"
2443checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
2444
2445[[package]]
2446name = "foldhash"
20512447version = "0.2.0"
20522448source = "registry+https://github.com/rust-lang/crates.io-index"
20532449checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
@@ -2164,7 +2560,7 @@
21642560dependencies = [
21652561 "proc-macro2",
21662562 "quote",
2167 "syn 3.0.3",
2563 "syn 3.0.9",
21682564]
21692565
21702566[[package]]
@@ -2198,9 +2594,9 @@
21982594
21992595[[package]]
22002596name = "generic-array"
2201version = "0.14.9"
2597version = "0.14.7"
22022598source = "registry+https://github.com/rust-lang/crates.io-index"
2203checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
2599checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
22042600dependencies = [
22052601 "typenum",
22062602 "version_check 0.9.5",
@@ -2306,21 +2702,21 @@
23062702 "kenshell",
23072703 "maud",
23082704 "models",
2309 "prost-types 0.14.1",
2705 "prost-types 0.14.4",
23102706 "pulldown-cmark",
23112707 "redis",
23122708 "sea-orm",
23132709 "serde",
23142710 "serde_json",
2315 "sha2 0.11.0",
2711 "sha2 0.11.1",
23162712 "space",
2317 "thiserror 2.0.20",
2713 "thiserror 2.0.21",
23182714 "time 0.3.55",
23192715 "tokio",
23202716 "tokio-util",
23212717 "tonic",
23222718 "tracing",
2323 "utoipa",
2719 "utoipa 5.5.0",
23242720 "uuid",
23252721]
23262722
@@ -2331,9 +2727,9 @@
23312727checksum = "d99fc21d493812643aae86d53b7bbd02f376434a90317e8a790bc209fdd6605e"
23322728dependencies = [
23332729 "bytemuck",
2334 "foldhash",
2730 "foldhash 0.2.0",
23352731 "hashbrown 0.17.1",
2336 "log 0.4.33",
2732 "log 0.4.34",
23372733 "peniko",
23382734 "png 0.18.1",
23392735 "skrifa",
@@ -2349,7 +2745,7 @@
23492745dependencies = [
23502746 "aho-corasick",
23512747 "bstr",
2352 "log 0.4.33",
2748 "log 0.4.34",
23532749 "regex-automata",
23542750 "regex-syntax",
23552751]
@@ -2395,9 +2791,9 @@
23952791
23962792[[package]]
23972793name = "h2"
2398version = "0.4.19"
2794version = "0.4.20"
23992795source = "registry+https://github.com/rust-lang/crates.io-index"
2400checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
2796checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118"
24012797dependencies = [
24022798 "atomic-waker",
24032799 "bytes",
@@ -2444,6 +2840,9 @@
24442840version = "0.15.5"
24452841source = "registry+https://github.com/rust-lang/crates.io-index"
24462842checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
2843dependencies = [
2844 "foldhash 0.1.5",
2845]
24472846
24482847[[package]]
24492848name = "hashbrown"
@@ -2453,7 +2852,7 @@
24532852dependencies = [
24542853 "allocator-api2",
24552854 "equivalent",
2456 "foldhash",
2855 "foldhash 0.2.0",
24572856]
24582857
24592858[[package]]
@@ -2462,7 +2861,7 @@
24622861source = "registry+https://github.com/rust-lang/crates.io-index"
24632862checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
24642863dependencies = [
2465 "foldhash",
2864 "foldhash 0.2.0",
24662865]
24672866
24682867[[package]]
@@ -2500,7 +2899,7 @@
25002899source = "registry+https://github.com/rust-lang/crates.io-index"
25012900checksum = "24d285dc30731c8485de5fa732fbdf2b3affdf01e4da7c2135022ca6fa664bf6"
25022901dependencies = [
2503 "brotli",
2902 "brotli 8.0.4",
25042903 "hayro-postscript",
25052904]
25062905
@@ -2510,7 +2909,7 @@
25102909source = "registry+https://github.com/rust-lang/crates.io-index"
25112910checksum = "f2613d0406898995042d0794c4245b2f2fba1246490c8ac593769bba6551129d"
25122911dependencies = [
2513 "bitflags 2.13.1",
2912 "bitflags 2.13.2",
25142913 "hayro-cmap",
25152914 "hayro-syntax",
25162915 "kurbo",
@@ -2578,9 +2977,9 @@
25782977
25792978[[package]]
25802979name = "hermit-abi"
2581version = "0.5.2"
2980version = "0.5.3"
25822981source = "registry+https://github.com/rust-lang/crates.io-index"
2583checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
2982checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
25842983
25852984[[package]]
25862985name = "hex"
@@ -2728,9 +3127,9 @@
27283127
27293128[[package]]
27303129name = "hybrid-array"
2731version = "0.4.14"
3130version = "0.4.15"
27323131source = "registry+https://github.com/rust-lang/crates.io-index"
2733checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
3132checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
27343133dependencies = [
27353134 "ctutils",
27363135 "typenum",
@@ -2744,7 +3143,7 @@
27443143dependencies = [
27453144 "base64 0.9.3",
27463145 "httparse",
2747 "language-tags",
3146 "language-tags 0.2.2",
27483147 "log 0.3.9",
27493148 "mime 0.2.6",
27503149 "num_cpus",
@@ -2781,15 +3180,15 @@
27813180
27823181[[package]]
27833182name = "hyper"
2784version = "1.11.0"
3183version = "1.12.0"
27853184source = "registry+https://github.com/rust-lang/crates.io-index"
2786checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
3185checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c"
27873186dependencies = [
27883187 "atomic-waker",
27893188 "bytes",
27903189 "futures-channel",
27913190 "futures-core",
2792 "h2 0.4.19",
3191 "h2 0.4.20",
27933192 "http 1.5.0",
27943193 "http-body 1.1.0",
27953194 "httparse",
@@ -2814,18 +3213,18 @@
28143213
28153214[[package]]
28163215name = "hyper-rustls"
2817version = "0.27.9"
3216version = "0.27.10"
28183217source = "registry+https://github.com/rust-lang/crates.io-index"
2819checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
3218checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53"
28203219dependencies = [
28213220 "http 1.5.0",
2822 "hyper 1.11.0",
3221 "hyper 1.12.0",
28233222 "hyper-util",
28243223 "rustls",
28253224 "tokio",
28263225 "tokio-rustls",
28273226 "tower-service",
2828 "webpki-roots 1.0.9",
3227 "webpki-roots 1.0.11",
28293228]
28303229
28313230[[package]]
@@ -2834,7 +3233,7 @@
28343233source = "registry+https://github.com/rust-lang/crates.io-index"
28353234checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
28363235dependencies = [
2837 "hyper 1.11.0",
3236 "hyper 1.12.0",
28383237 "hyper-util",
28393238 "pin-project-lite",
28403239 "tokio",
@@ -2856,17 +3255,18 @@
28563255
28573256[[package]]
28583257name = "hyper-util"
2859version = "0.1.20"
3258version = "0.1.21"
28603259source = "registry+https://github.com/rust-lang/crates.io-index"
2861checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
3260checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff"
28623261dependencies = [
2863 "base64 0.22.1",
3262 "base64 0.23.1",
28643263 "bytes",
28653264 "futures-channel",
28663265 "futures-util",
28673266 "http 1.5.0",
28683267 "http-body 1.1.0",
2869 "hyper 1.11.0",
3268 "httparse",
3269 "hyper 1.12.0",
28703270 "ipnet",
28713271 "libc",
28723272 "percent-encoding 2.3.2",
@@ -2898,7 +3298,7 @@
28983298 "core-foundation-sys",
28993299 "iana-time-zone-haiku",
29003300 "js-sys",
2901 "log 0.4.33",
3301 "log 0.4.34",
29023302 "wasm-bindgen",
29033303 "windows-core",
29043304]
@@ -2914,9 +3314,9 @@
29143314
29153315[[package]]
29163316name = "icu_collections"
2917version = "2.3.0"
3317version = "2.4.0"
29183318source = "registry+https://github.com/rust-lang/crates.io-index"
2919checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
3319checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd"
29203320dependencies = [
29213321 "displaydoc",
29223322 "potential_utf",
@@ -2928,9 +3328,9 @@
29283328
29293329[[package]]
29303330name = "icu_locale_core"
2931version = "2.3.0"
3331version = "2.4.0"
29323332source = "registry+https://github.com/rust-lang/crates.io-index"
2933checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
3333checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0"
29343334dependencies = [
29353335 "displaydoc",
29363336 "litemap",
@@ -2941,9 +3341,9 @@
29413341
29423342[[package]]
29433343name = "icu_normalizer"
2944version = "2.3.0"
3344version = "2.4.0"
29453345source = "registry+https://github.com/rust-lang/crates.io-index"
2946checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
3346checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599"
29473347dependencies = [
29483348 "icu_collections",
29493349 "icu_normalizer_data",
@@ -2955,15 +3355,15 @@
29553355
29563356[[package]]
29573357name = "icu_normalizer_data"
2958version = "2.3.0"
3358version = "2.4.0"
29593359source = "registry+https://github.com/rust-lang/crates.io-index"
2960checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
3360checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc"
29613361
29623362[[package]]
29633363name = "icu_properties"
2964version = "2.3.0"
3364version = "2.4.0"
29653365source = "registry+https://github.com/rust-lang/crates.io-index"
2966checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
3366checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f"
29673367dependencies = [
29683368 "displaydoc",
29693369 "icu_collections",
@@ -2976,15 +3376,15 @@
29763376
29773377[[package]]
29783378name = "icu_properties_data"
2979version = "2.3.0"
3379version = "2.4.0"
29803380source = "registry+https://github.com/rust-lang/crates.io-index"
2981checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
3381checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf"
29823382
29833383[[package]]
29843384name = "icu_provider"
2985version = "2.3.0"
3385version = "2.4.0"
29863386source = "registry+https://github.com/rust-lang/crates.io-index"
2987checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428"
3387checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514"
29883388dependencies = [
29893389 "displaydoc",
29903390 "icu_locale_core",
@@ -3068,14 +3468,20 @@
30683468 "serde",
30693469 "serde_json",
30703470 "tokio",
3071 "utoipa",
3471 "utoipa 5.5.0",
30723472]
30733473
30743474[[package]]
3475name = "impl-more"
3476version = "0.3.10"
3477source = "registry+https://github.com/rust-lang/crates.io-index"
3478checksum = "2d3a73c82a0b0747dba739b380c046a140b5ae747234bf701df3460282da7193"
3479
3480[[package]]
30753481name = "indexmap"
3076version = "2.14.0"
3482version = "2.14.2"
30773483source = "registry+https://github.com/rust-lang/crates.io-index"
3078checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
3484checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
30793485dependencies = [
30803486 "equivalent",
30813487 "hashbrown 0.17.1",
@@ -3100,27 +3506,27 @@
31003506
31013507[[package]]
31023508name = "inout"
3103version = "0.2.2"
3509version = "0.2.5"
31043510source = "registry+https://github.com/rust-lang/crates.io-index"
3105checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
3511checksum = "3b5cf4372d022092674a83b8febf5865f7a573cc0c5baec5e49c879b6a20978f"
31063512dependencies = [
31073513 "hybrid-array",
31083514]
31093515
31103516[[package]]
31113517name = "inventory"
3112version = "0.3.24"
3518version = "0.3.25"
31133519source = "registry+https://github.com/rust-lang/crates.io-index"
3114checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b"
3520checksum = "6928282826c822ad91bf1c9a1cb90a30ba1c26770749929b4656cd6be829cd7c"
31153521dependencies = [
31163522 "rustversion",
31173523]
31183524
31193525[[package]]
31203526name = "ipnet"
3121version = "2.12.1"
3527version = "2.12.2"
31223528source = "registry+https://github.com/rust-lang/crates.io-index"
3123checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
3529checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0"
31243530
31253531[[package]]
31263532name = "ipnetwork"
@@ -3154,7 +3560,7 @@
31543560 "space",
31553561 "time 0.3.55",
31563562 "tracing",
3157 "utoipa",
3563 "utoipa 5.5.0",
31583564 "uuid",
31593565]
31603566
@@ -3201,13 +3607,43 @@
32013607 "cfg-if",
32023608 "combine",
32033609 "jni-sys 0.3.1",
3204 "log 0.4.33",
3610 "log 0.4.34",
32053611 "thiserror 1.0.69",
32063612 "walkdir",
32073613 "windows-sys 0.45.0",
32083614]
32093615
32103616[[package]]
3617name = "jni"
3618version = "0.22.4"
3619source = "registry+https://github.com/rust-lang/crates.io-index"
3620checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
3621dependencies = [
3622 "cfg-if",
3623 "combine",
3624 "jni-macros",
3625 "jni-sys 0.4.1",
3626 "log 0.4.34",
3627 "simd_cesu8",
3628 "thiserror 2.0.21",
3629 "walkdir",
3630 "windows-link",
3631]
3632
3633[[package]]
3634name = "jni-macros"
3635version = "0.22.4"
3636source = "registry+https://github.com/rust-lang/crates.io-index"
3637checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
3638dependencies = [
3639 "proc-macro2",
3640 "quote",
3641 "rustc_version",
3642 "simd_cesu8",
3643 "syn 2.0.119",
3644]
3645
3646[[package]]
32113647name = "jni-sys"
32123648version = "0.3.1"
32133649source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3247,9 +3683,9 @@
32473683
32483684[[package]]
32493685name = "js-sys"
3250version = "0.3.104"
3686version = "0.3.106"
32513687source = "registry+https://github.com/rust-lang/crates.io-index"
3252checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
3688checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
32533689dependencies = [
32543690 "cfg-if",
32553691 "futures-util",
@@ -3304,10 +3740,11 @@
33043740 "base64 0.23.1",
33053741 "config 1.0.0",
33063742 "jsonwebtoken 9.3.1",
3743 "kenoauth",
33073744 "reqwest 0.12.28",
33083745 "serde",
33093746 "serde_json",
3310 "sha2 0.11.0",
3747 "sha2 0.11.1",
33113748 "tokio",
33123749 "tracing",
33133750 "uuid",
@@ -3331,11 +3768,11 @@
33313768 "k256",
33323769 "p256",
33333770 "p384",
3334 "rand 0.8.7",
3771 "rand 0.8.8",
33353772 "serde",
33363773 "serde_json",
33373774 "superboring",
3338 "thiserror 2.0.20",
3775 "thiserror 2.0.21",
33393776 "zeroize",
33403777]
33413778
@@ -3355,12 +3792,12 @@
33553792
33563793[[package]]
33573794name = "keccak"
3358version = "0.2.1"
3795version = "0.2.2"
33593796source = "registry+https://github.com/rust-lang/crates.io-index"
3360checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4"
3797checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598"
33613798dependencies = [
33623799 "cfg-if",
3363 "cpufeatures 0.3.0",
3800 "cpufeatures 0.3.1",
33643801]
33653802
33663803[[package]]
@@ -3373,17 +3810,22 @@
33733810 "chat",
33743811 "clap",
33753812 "config 1.0.0",
3813 "deletion",
33763814 "export",
33773815 "jwt",
3816 "kenauth",
3817 "kenoauth",
33783818 "models",
33793819 "push",
33803820 "queue",
33813821 "sea-orm",
3822 "serde_json",
33823823 "tokio",
33833824 "tokio-util",
33843825 "tracing",
33853826 "tracing-subscriber",
3386 "utoipa",
3827 "utoipa 5.5.0",
3828 "utoipa 6.0.0",
33873829 "utoipa-swagger-ui",
33883830 "webapi",
33893831]
@@ -3393,15 +3835,72 @@
33933835version = "1.0.0"
33943836dependencies = [
33953837 "config 1.0.0",
3838 "kenoauth",
33963839 "prost 0.14.4",
3397 "prost-types 0.14.1",
3398 "thiserror 2.0.20",
3840 "prost-types 0.14.4",
3841 "thiserror 2.0.21",
33993842 "tokio",
34003843 "tonic",
34013844 "tonic-health",
34023845 "tonic-prost",
3846 "tracing",
3847 "uuid",
3848]
3849
3850[[package]]
3851name = "kenoauth"
3852version = "1.0.0"
3853dependencies = [
3854 "actix-web",
3855 "aes-gcm 0.11.1",
3856 "anyhow",
3857 "argon2",
3858 "async-trait",
3859 "base64 0.23.1",
3860 "bytes",
3861 "chrono",
3862 "deadpool-redis",
3863 "ed25519-dalek",
3864 "figment",
3865 "futures-util",
3866 "hmac 0.12.1",
3867 "http 1.5.0",
3868 "http-body 1.1.0",
3869 "lettre",
3870 "maud",
3871 "opentelemetry",
3872 "opentelemetry-appender-tracing",
3873 "opentelemetry-otlp",
3874 "opentelemetry_sdk",
3875 "percent-encoding 2.3.2",
3876 "prost 0.14.4",
3877 "prost-types 0.14.4",
3878 "rand 0.10.3",
3879 "rand_core 0.6.4",
3880 "reqwest 0.13.5",
3881 "rocketmq-client-rust",
3882 "rocketmq-common",
3883 "rsa",
3884 "sea-orm",
3885 "serde",
3886 "serde_json",
3887 "sha1 0.10.7",
3888 "sha2 0.10.9",
3889 "sqlparser",
3890 "thiserror 2.0.21",
3891 "tokio",
3892 "tokio-stream",
3893 "tokio-util",
3894 "tonic",
3895 "tonic-health",
3896 "tonic-prost",
34033897 "tonic-prost-build",
3898 "tonic-reflection",
3899 "tower",
34043900 "tracing",
3901 "tracing-opentelemetry",
3902 "tracing-subscriber",
3903 "utoipa 6.0.0",
34053904 "uuid",
34063905]
34073906
@@ -3410,8 +3909,8 @@
34103909version = "1.0.0"
34113910dependencies = [
34123911 "prost 0.14.4",
3413 "prost-types 0.14.1",
3414 "thiserror 2.0.20",
3912 "prost-types 0.14.4",
3913 "thiserror 2.0.21",
34153914 "tokio",
34163915 "tonic",
34173916 "tonic-prost",
@@ -3439,15 +3938,48 @@
34393938checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
34403939
34413940[[package]]
3941name = "language-tags"
3942version = "0.3.2"
3943source = "registry+https://github.com/rust-lang/crates.io-index"
3944checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
3945
3946[[package]]
34423947name = "lazy_static"
3443version = "1.5.0"
3948version = "1.5.1"
34443949source = "registry+https://github.com/rust-lang/crates.io-index"
3445checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
3950checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
34463951dependencies = [
34473952 "spin",
34483953]
34493954
34503955[[package]]
3956name = "lettre"
3957version = "0.11.23"
3958source = "registry+https://github.com/rust-lang/crates.io-index"
3959checksum = "f2c646bd5cc763b1087b15493e29a64be6147ba8f19342004fa52048ee596eae"
3960dependencies = [
3961 "async-trait",
3962 "base64 0.23.1",
3963 "email-encoding",
3964 "email_address",
3965 "fastrand",
3966 "futures-io",
3967 "futures-util",
3968 "httpdate",
3969 "idna 1.1.0",
3970 "mime 0.3.17",
3971 "nom",
3972 "percent-encoding 2.3.2",
3973 "quoted_printable",
3974 "rustls",
3975 "socket2 0.6.5",
3976 "tokio",
3977 "tokio-rustls",
3978 "url 2.5.8",
3979 "webpki-roots 1.0.11",
3980]
3981
3982[[package]]
34513983name = "lexical-core"
34523984version = "1.0.6"
34533985source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3506,9 +4038,9 @@
35064038
35074039[[package]]
35084040name = "libc"
3509version = "0.2.189"
4041version = "0.2.191"
35104042source = "registry+https://github.com/rust-lang/crates.io-index"
3511checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
4043checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
35124044
35134045[[package]]
35144046name = "libm"
@@ -3518,9 +4050,9 @@
35184050
35194051[[package]]
35204052name = "libredox"
3521version = "0.1.20"
4053version = "0.1.25"
35224054source = "registry+https://github.com/rust-lang/crates.io-index"
3523checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
4055checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d"
35244056dependencies = [
35254057 "libc",
35264058]
@@ -3576,7 +4108,7 @@
35764108 "livekit-region",
35774109 "livekit-signaling",
35784110 "livekit-token",
3579 "log 0.4.33",
4111 "log 0.4.34",
35804112 "pbjson-types",
35814113 "prost 0.12.6",
35824114 "rand 0.9.5",
@@ -3584,7 +4116,7 @@
35844116 "serde",
35854117 "serde_json",
35864118 "sha2 0.10.9",
3587 "thiserror 2.0.20",
4119 "thiserror 2.0.21",
35884120 "tokio",
35894121 "url 2.5.8",
35904122]
@@ -3606,7 +4138,7 @@
36064138dependencies = [
36074139 "async-trait",
36084140 "http 1.5.0",
3609 "log 0.4.33",
4141 "log 0.4.34",
36104142 "reqwest 0.12.28",
36114143 "tokio-tungstenite",
36124144 "url 2.5.8",
@@ -3647,12 +4179,12 @@
36474179 "livekit-net",
36484180 "livekit-protocol",
36494181 "livekit-region",
3650 "log 0.4.33",
4182 "log 0.4.34",
36514183 "os_info",
36524184 "parking_lot",
36534185 "prost 0.12.6",
36544186 "serde_json",
3655 "thiserror 2.0.20",
4187 "thiserror 2.0.21",
36564188 "tokio",
36574189 "url 2.5.8",
36584190]
@@ -3666,10 +4198,21 @@
36664198 "hmac 0.12.1",
36674199 "jsonwebtoken 10.4.0",
36684200 "livekit-protocol",
3669 "serde",
3670 "sha2 0.10.9",
3671 "signature 2.2.0",
3672 "thiserror 2.0.20",
4201 "serde",
4202 "sha2 0.10.9",
4203 "signature 2.2.0",
4204 "thiserror 2.0.21",
4205]
4206
4207[[package]]
4208name = "local-channel"
4209version = "0.1.5"
4210source = "registry+https://github.com/rust-lang/crates.io-index"
4211checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8"
4212dependencies = [
4213 "futures-core",
4214 "futures-sink",
4215 "local-waker",
36734216]
36744217
36754218[[package]]
@@ -3684,6 +4227,12 @@
36844227]
36854228
36864229[[package]]
4230name = "local-waker"
4231version = "0.1.4"
4232source = "registry+https://github.com/rust-lang/crates.io-index"
4233checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487"
4234
4235[[package]]
36874236name = "lock_api"
36884237version = "0.4.14"
36894238source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3698,20 +4247,20 @@
36984247source = "registry+https://github.com/rust-lang/crates.io-index"
36994248checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
37004249dependencies = [
3701 "log 0.4.33",
4250 "log 0.4.34",
37024251]
37034252
37044253[[package]]
37054254name = "log"
3706version = "0.4.33"
4255version = "0.4.34"
37074256source = "registry+https://github.com/rust-lang/crates.io-index"
3708checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
4257checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
37094258
37104259[[package]]
37114260name = "lru-slab"
3712version = "0.1.2"
4261version = "0.1.3"
37134262source = "registry+https://github.com/rust-lang/crates.io-index"
3714checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
4263checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f"
37154264
37164265[[package]]
37174266name = "lz4_flex"
@@ -3721,13 +4270,13 @@
37214270
37224271[[package]]
37234272name = "mac_address"
3724version = "1.1.8"
4273version = "1.2.1"
37254274source = "registry+https://github.com/rust-lang/crates.io-index"
3726checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
4275checksum = "f3d0581a75c45969c63afd2bb87b1d8b25cfcc556c7a918cf9bb13b0fe8d2381"
37274276dependencies = [
3728 "nix 0.29.0",
4277 "nix",
37294278 "serde",
3730 "winapi",
4279 "windows-sys 0.61.2",
37314280]
37324281
37334282[[package]]
@@ -3838,7 +4387,7 @@
38384387checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
38394388dependencies = [
38404389 "mime 0.3.17",
3841 "unicase 2.9.0",
4390 "unicase 2.10.0",
38424391]
38434392
38444393[[package]]
@@ -3861,12 +4410,23 @@
38614410]
38624411
38634412[[package]]
4413name = "miniz_oxide"
4414version = "0.9.1"
4415source = "registry+https://github.com/rust-lang/crates.io-index"
4416checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
4417dependencies = [
4418 "adler2",
4419 "simd-adler32",
4420]
4421
4422[[package]]
38644423name = "mio"
3865version = "1.2.2"
4424version = "1.2.4"
38664425source = "registry+https://github.com/rust-lang/crates.io-index"
3867checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
4426checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee"
38684427dependencies = [
38694428 "libc",
4429 "log 0.4.34",
38704430 "wasi 0.11.1+wasi-snapshot-preview1",
38714431 "windows-sys 0.61.2",
38724432]
@@ -3896,7 +4456,7 @@
38964456 "serde_json",
38974457 "time 0.3.55",
38984458 "tokio",
3899 "utoipa",
4459 "utoipa 5.5.0",
39004460 "uuid",
39014461]
39024462
@@ -3945,13 +4505,19 @@
39454505checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
39464506
39474507[[package]]
4508name = "multiversion_no_op"
4509version = "1.0.0"
4510source = "registry+https://github.com/rust-lang/crates.io-index"
4511checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
4512
4513[[package]]
39484514name = "native-tls"
39494515version = "0.2.18"
39504516source = "registry+https://github.com/rust-lang/crates.io-index"
39514517checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
39524518dependencies = [
39534519 "libc",
3954 "log 0.4.33",
4520 "log 0.4.34",
39554521 "openssl",
39564522 "openssl-probe",
39574523 "openssl-sys",
@@ -3967,13 +4533,13 @@
39674533source = "registry+https://github.com/rust-lang/crates.io-index"
39684534checksum = "6b152f4f18819e638ea350a740b16de75099302b5ec3a58f4f8766ef2fcf695d"
39694535dependencies = [
3970 "bitflags 2.13.1",
4536 "bitflags 2.13.2",
39714537 "byteorder",
39724538 "derive_builder",
39734539 "getset",
39744540 "libc",
39754541 "linux-raw-sys",
3976 "log 0.4.33",
4542 "log 0.4.34",
39774543 "neli-proc-macros",
39784544 "parking_lot",
39794545]
@@ -3993,11 +4559,11 @@
39934559
39944560[[package]]
39954561name = "nix"
3996version = "0.29.0"
4562version = "0.31.3"
39974563source = "registry+https://github.com/rust-lang/crates.io-index"
3998checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
4564checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
39994565dependencies = [
4000 "bitflags 2.13.1",
4566 "bitflags 2.13.2",
40014567 "cfg-if",
40024568 "cfg_aliases",
40034569 "libc",
@@ -4005,15 +4571,12 @@
40054571]
40064572
40074573[[package]]
4008name = "nix"
4009version = "0.31.3"
4574name = "nom"
4575version = "8.0.0"
40104576source = "registry+https://github.com/rust-lang/crates.io-index"
4011checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
4577checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
40124578dependencies = [
4013 "bitflags 2.13.1",
4014 "cfg-if",
4015 "cfg_aliases",
4016 "libc",
4579 "memchr",
40174580]
40184581
40194582[[package]]
@@ -4056,7 +4619,7 @@
40564619 "num-integer",
40574620 "num-iter",
40584621 "num-traits",
4059 "rand 0.8.7",
4622 "rand 0.8.8",
40604623 "smallvec",
40614624 "zeroize",
40624625]
@@ -4117,9 +4680,9 @@
41174680
41184681[[package]]
41194682name = "objc2"
4120version = "0.6.4"
4683version = "0.6.5"
41214684source = "registry+https://github.com/rust-lang/crates.io-index"
4122checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
4685checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8"
41234686dependencies = [
41244687 "objc2-encode",
41254688]
@@ -4130,7 +4693,7 @@
41304693source = "registry+https://github.com/rust-lang/crates.io-index"
41314694checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
41324695dependencies = [
4133 "bitflags 2.13.1",
4696 "bitflags 2.13.2",
41344697 "objc2",
41354698 "objc2-foundation",
41364699]
@@ -4151,7 +4714,7 @@
41514714source = "registry+https://github.com/rust-lang/crates.io-index"
41524715checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
41534716dependencies = [
4154 "bitflags 2.13.1",
4717 "bitflags 2.13.2",
41554718 "dispatch2",
41564719 "objc2",
41574720]
@@ -4162,7 +4725,7 @@
41624725source = "registry+https://github.com/rust-lang/crates.io-index"
41634726checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
41644727dependencies = [
4165 "bitflags 2.13.1",
4728 "bitflags 2.13.2",
41664729 "dispatch2",
41674730 "objc2",
41684731 "objc2-core-foundation",
@@ -4195,7 +4758,7 @@
41954758source = "registry+https://github.com/rust-lang/crates.io-index"
41964759checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
41974760dependencies = [
4198 "bitflags 2.13.1",
4761 "bitflags 2.13.2",
41994762 "objc2",
42004763 "objc2-core-foundation",
42014764 "objc2-core-graphics",
@@ -4213,7 +4776,7 @@
42134776source = "registry+https://github.com/rust-lang/crates.io-index"
42144777checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
42154778dependencies = [
4216 "bitflags 2.13.1",
4779 "bitflags 2.13.2",
42174780 "block2",
42184781 "libc",
42194782 "objc2",
@@ -4226,7 +4789,7 @@
42264789source = "registry+https://github.com/rust-lang/crates.io-index"
42274790checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
42284791dependencies = [
4229 "bitflags 2.13.1",
4792 "bitflags 2.13.2",
42304793 "objc2",
42314794 "objc2-core-foundation",
42324795]
@@ -4237,7 +4800,7 @@
42374800source = "registry+https://github.com/rust-lang/crates.io-index"
42384801checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
42394802dependencies = [
4240 "bitflags 2.13.1",
4803 "bitflags 2.13.2",
42414804 "objc2",
42424805 "objc2-core-foundation",
42434806 "objc2-foundation",
@@ -4249,7 +4812,7 @@
42494812source = "registry+https://github.com/rust-lang/crates.io-index"
42504813checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
42514814dependencies = [
4252 "bitflags 2.13.1",
4815 "bitflags 2.13.2",
42534816 "block2",
42544817 "objc2",
42554818 "objc2-cloud-kit",
@@ -4298,7 +4861,7 @@
42984861source = "registry+https://github.com/rust-lang/crates.io-index"
42994862checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
43004863dependencies = [
4301 "bitflags 2.13.1",
4864 "bitflags 2.13.2",
43024865 "cfg-if",
43034866 "foreign-types",
43044867 "libc",
@@ -4319,9 +4882,9 @@
43194882
43204883[[package]]
43214884name = "openssl-probe"
4322version = "0.2.1"
4885version = "0.2.2"
43234886source = "registry+https://github.com/rust-lang/crates.io-index"
4324checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
4887checksum = "7e036b411754132fd6b74e94b74e9a13c5cf606fa93d1885c35860d3060820b1"
43254888
43264889[[package]]
43274890name = "openssl-sys"
@@ -4345,8 +4908,20 @@
43454908 "futures-sink",
43464909 "js-sys",
43474910 "pin-project-lite",
4348 "thiserror 2.0.20",
4911 "thiserror 2.0.21",
4912 "tracing",
4913]
4914
4915[[package]]
4916name = "opentelemetry-appender-tracing"
4917version = "0.32.0"
4918source = "registry+https://github.com/rust-lang/crates.io-index"
4919checksum = "2c0080f0dc1d7c786f467cd85a4e395fcab11ee852004f39a29a18ab7c25d837"
4920dependencies = [
4921 "opentelemetry",
43494922 "tracing",
4923 "tracing-core",
4924 "tracing-subscriber",
43504925]
43514926
43524927[[package]]
@@ -4359,7 +4934,7 @@
43594934 "bytes",
43604935 "http 1.5.0",
43614936 "opentelemetry",
4362 "reqwest 0.13.4",
4937 "reqwest 0.13.5",
43634938]
43644939
43654940[[package]]
@@ -4374,8 +4949,8 @@
43744949 "opentelemetry-proto",
43754950 "opentelemetry_sdk",
43764951 "prost 0.14.4",
4377 "reqwest 0.13.4",
4378 "thiserror 2.0.20",
4952 "reqwest 0.13.5",
4953 "thiserror 2.0.21",
43794954]
43804955
43814956[[package]]
@@ -4408,7 +4983,7 @@
44084983 "percent-encoding 2.3.2",
44094984 "portable-atomic",
44104985 "rand 0.9.5",
4411 "thiserror 2.0.20",
4986 "thiserror 2.0.21",
44124987 "tokio",
44134988 "tokio-stream",
44144989]
@@ -4455,8 +5030,8 @@
44555030checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b"
44565031dependencies = [
44575032 "android_system_properties",
4458 "log 0.4.33",
4459 "nix 0.31.3",
5033 "log 0.4.34",
5034 "nix",
44605035 "objc2",
44615036 "objc2-foundation",
44625037 "objc2-ui-kit",
@@ -4667,7 +5242,18 @@
46675242source = "registry+https://github.com/rust-lang/crates.io-index"
46685243checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
46695244dependencies = [
4670 "fixedbitset",
5245 "fixedbitset 0.4.2",
5246 "indexmap",
5247]
5248
5249[[package]]
5250name = "petgraph"
5251version = "0.8.3"
5252source = "registry+https://github.com/rust-lang/crates.io-index"
5253checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
5254dependencies = [
5255 "fixedbitset 0.5.7",
5256 "hashbrown 0.15.5",
46715257 "indexmap",
46725258]
46735259
@@ -4727,7 +5313,7 @@
47275313checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
47285314dependencies = [
47295315 "phf_shared 0.11.3",
4730 "rand 0.8.7",
5316 "rand 0.8.8",
47315317]
47325318
47335319[[package]]
@@ -4856,8 +5442,8 @@
48565442source = "registry+https://github.com/rust-lang/crates.io-index"
48575443checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
48585444dependencies = [
4859 "der 0.8.1",
4860 "spki 0.8.0",
5445 "der 0.8.2",
5446 "spki 0.8.1",
48615447]
48625448
48635449[[package]]
@@ -4886,7 +5472,7 @@
48865472 "crc32fast",
48875473 "fdeflate",
48885474 "flate2",
4889 "miniz_oxide",
5475 "miniz_oxide 0.8.9",
48905476]
48915477
48925478[[package]]
@@ -4895,11 +5481,11 @@
48955481source = "registry+https://github.com/rust-lang/crates.io-index"
48965482checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
48975483dependencies = [
4898 "bitflags 2.13.1",
5484 "bitflags 2.13.2",
48995485 "crc32fast",
49005486 "fdeflate",
49015487 "flate2",
4902 "miniz_oxide",
5488 "miniz_oxide 0.8.9",
49035489]
49045490
49055491[[package]]
@@ -4930,7 +5516,7 @@
49305516checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd"
49315517dependencies = [
49325518 "cpubits",
4933 "cpufeatures 0.3.0",
5519 "cpufeatures 0.3.1",
49345520 "universal-hash 0.6.1",
49355521]
49365522
@@ -4951,9 +5537,9 @@
49515537
49525538[[package]]
49535539name = "powerfmt"
4954version = "0.2.0"
5540version = "0.2.1"
49555541source = "registry+https://github.com/rust-lang/crates.io-index"
4956checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
5542checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958"
49575543
49585544[[package]]
49595545name = "ppv-lite86"
@@ -4989,7 +5575,7 @@
49895575source = "registry+https://github.com/rust-lang/crates.io-index"
49905576checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
49915577dependencies = [
4992 "toml_edit 0.25.13+spec-1.1.0",
5578 "toml_edit 0.25.17+spec-1.1.0",
49935579]
49945580
49955581[[package]]
@@ -5043,10 +5629,10 @@
50435629 "bytes",
50445630 "heck 0.5.0",
50455631 "itertools 0.12.1",
5046 "log 0.4.33",
5632 "log 0.4.34",
50475633 "multimap",
50485634 "once_cell",
5049 "petgraph",
5635 "petgraph 0.6.5",
50505636 "prettyplease",
50515637 "prost 0.12.6",
50525638 "prost-types 0.12.6",
@@ -5057,19 +5643,18 @@
50575643
50585644[[package]]
50595645name = "prost-build"
5060version = "0.14.1"
5646version = "0.14.4"
50615647source = "registry+https://github.com/rust-lang/crates.io-index"
5062checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1"
5648checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
50635649dependencies = [
50645650 "heck 0.5.0",
50655651 "itertools 0.14.0",
5066 "log 0.4.33",
5652 "log 0.4.34",
50675653 "multimap",
5068 "once_cell",
5069 "petgraph",
5654 "petgraph 0.8.3",
50705655 "prettyplease",
50715656 "prost 0.14.4",
5072 "prost-types 0.14.1",
5657 "prost-types 0.14.4",
50735658 "pulldown-cmark",
50745659 "pulldown-cmark-to-cmark",
50755660 "regex",
@@ -5114,43 +5699,23 @@
51145699
51155700[[package]]
51165701name = "prost-types"
5117version = "0.14.1"
5702version = "0.14.4"
51185703source = "registry+https://github.com/rust-lang/crates.io-index"
5119checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72"
5704checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a"
51205705dependencies = [
51215706 "prost 0.14.4",
51225707]
51235708
51245709[[package]]
5125name = "ptr_meta"
5126version = "0.1.4"
5127source = "registry+https://github.com/rust-lang/crates.io-index"
5128checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
5129dependencies = [
5130 "ptr_meta_derive",
5131]
5132
5133[[package]]
5134name = "ptr_meta_derive"
5135version = "0.1.4"
5136source = "registry+https://github.com/rust-lang/crates.io-index"
5137checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
5138dependencies = [
5139 "proc-macro2",
5140 "quote",
5141 "syn 1.0.109",
5142]
5143
5144[[package]]
51455710name = "pulldown-cmark"
5146version = "0.13.4"
5711version = "0.13.5"
51475712source = "registry+https://github.com/rust-lang/crates.io-index"
5148checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
5713checksum = "278e9268c93b9e498a2da46c34d90ba93a3255eb99009646398f34bc73ab36eb"
51495714dependencies = [
5150 "bitflags 2.13.1",
5715 "bitflags 2.13.2",
51515716 "memchr",
51525717 "pulldown-cmark-escape",
5153 "unicase 2.9.0",
5718 "unicase 2.10.0",
51545719]
51555720
51565721[[package]]
@@ -5161,9 +5726,9 @@
51615726
51625727[[package]]
51635728name = "pulldown-cmark-to-cmark"
5164version = "21.1.0"
5729version = "22.0.3"
51655730source = "registry+https://github.com/rust-lang/crates.io-index"
5166checksum = "8246feae3db61428fd0bb94285c690b460e4517d83152377543ca802357785f1"
5731checksum = "84bbb29c624230c4bd1047bbdb2aa47e41c860e9665ce62ba9504eebe91bf867"
51675732dependencies = [
51685733 "pulldown-cmark",
51695734]
@@ -5187,7 +5752,7 @@
51875752 "space",
51885753 "time 0.3.55",
51895754 "tracing",
5190 "utoipa",
5755 "utoipa 5.5.0",
51915756 "uuid",
51925757]
51935758
@@ -5253,9 +5818,9 @@
52535818
52545819[[package]]
52555820name = "quinn"
5256version = "0.11.11"
5821version = "0.11.12"
52575822source = "registry+https://github.com/rust-lang/crates.io-index"
5258checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
5823checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11"
52595824dependencies = [
52605825 "bytes",
52615826 "cfg_aliases",
@@ -5265,7 +5830,7 @@
52655830 "rustc-hash",
52665831 "rustls",
52675832 "socket2 0.6.5",
5268 "thiserror 2.0.20",
5833 "thiserror 2.0.21",
52695834 "tokio",
52705835 "tracing",
52715836 "web-time",
@@ -5273,22 +5838,22 @@
52735838
52745839[[package]]
52755840name = "quinn-proto"
5276version = "0.11.17"
5841version = "0.11.19"
52775842source = "registry+https://github.com/rust-lang/crates.io-index"
5278checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83"
5843checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe"
52795844dependencies = [
52805845 "aws-lc-rs",
52815846 "bytes",
52825847 "getrandom 0.4.3",
52835848 "lru-slab",
5284 "rand 0.10.2",
5849 "rand 0.10.3",
52855850 "rand_pcg",
52865851 "ring",
52875852 "rustc-hash",
52885853 "rustls",
52895854 "rustls-pki-types",
52905855 "slab",
5291 "thiserror 2.0.20",
5856 "thiserror 2.0.21",
52925857 "tinyvec",
52935858 "tracing",
52945859 "web-time",
@@ -5296,9 +5861,9 @@
52965861
52975862[[package]]
52985863name = "quinn-udp"
5299version = "0.5.15"
5864version = "0.5.16"
53005865source = "registry+https://github.com/rust-lang/crates.io-index"
5301checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
5866checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016"
53025867dependencies = [
53035868 "cfg_aliases",
53045869 "libc",
@@ -5318,6 +5883,12 @@
53185883]
53195884
53205885[[package]]
5886name = "quoted_printable"
5887version = "0.5.2"
5888source = "registry+https://github.com/rust-lang/crates.io-index"
5889checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972"
5890
5891[[package]]
53215892name = "r-efi"
53225893version = "5.3.0"
53235894source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5337,9 +5908,9 @@
53375908
53385909[[package]]
53395910name = "rand"
5340version = "0.8.7"
5911version = "0.8.8"
53415912source = "registry+https://github.com/rust-lang/crates.io-index"
5342checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
5913checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
53435914dependencies = [
53445915 "libc",
53455916 "rand_chacha 0.3.1",
@@ -5358,9 +5929,9 @@
53585929
53595930[[package]]
53605931name = "rand"
5361version = "0.10.2"
5932version = "0.10.3"
53625933source = "registry+https://github.com/rust-lang/crates.io-index"
5363checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
5934checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
53645935dependencies = [
53655936 "chacha20",
53665937 "getrandom 0.4.3",
@@ -5445,9 +6016,9 @@
54456016
54466017[[package]]
54476018name = "redis"
5448version = "1.6.0"
6019version = "1.8.0"
54496020source = "registry+https://github.com/rust-lang/crates.io-index"
5450checksum = "e37a4ca5c6ca42aa3e6df2fd32b987a65d32a4c2159a6f3fe0fd1df306a2658f"
6021checksum = "7acd809e80afa754c63f586955699d8990efe1c4b6613183ee5f027a11fbc8b5"
54516022dependencies = [
54526023 "arc-swap",
54536024 "arcstr",
@@ -5460,11 +6031,11 @@
54606031 "futures-channel",
54616032 "futures-util",
54626033 "itoa",
5463 "log 0.4.33",
6034 "log 0.4.34",
54646035 "num-bigint 0.5.1",
54656036 "percent-encoding 2.3.2",
54666037 "pin-project-lite",
5467 "rand 0.10.2",
6038 "rand 0.10.3",
54686039 "ryu",
54696040 "sha1_smol",
54706041 "socket2 0.6.5",
@@ -5480,7 +6051,7 @@
54806051source = "registry+https://github.com/rust-lang/crates.io-index"
54816052checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
54826053dependencies = [
5483 "bitflags 2.13.1",
6054 "bitflags 2.13.2",
54846055]
54856056
54866057[[package]]
@@ -5501,7 +6072,7 @@
55016072checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0"
55026073dependencies = [
55036074 "libredox",
5504 "thiserror 2.0.20",
6075 "thiserror 2.0.21",
55056076]
55066077
55076078[[package]]
@@ -5528,21 +6099,18 @@
55286099]
55296100
55306101[[package]]
6102name = "regex-lite"
6103version = "0.1.9"
6104source = "registry+https://github.com/rust-lang/crates.io-index"
6105checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
6106
6107[[package]]
55316108name = "regex-syntax"
55326109version = "0.8.11"
55336110source = "registry+https://github.com/rust-lang/crates.io-index"
55346111checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
55356112
55366113[[package]]
5537name = "rend"
5538version = "0.4.2"
5539source = "registry+https://github.com/rust-lang/crates.io-index"
5540checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
5541dependencies = [
5542 "bytecheck",
5543]
5544
5545[[package]]
55466114name = "reqwest"
55476115version = "0.12.28"
55486116source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5554,11 +6122,11 @@
55546122 "http 1.5.0",
55556123 "http-body 1.1.0",
55566124 "http-body-util",
5557 "hyper 1.11.0",
6125 "hyper 1.12.0",
55586126 "hyper-rustls",
55596127 "hyper-util",
55606128 "js-sys",
5561 "log 0.4.33",
6129 "log 0.4.34",
55626130 "percent-encoding 2.3.2",
55636131 "pin-project-lite",
55646132 "quinn",
@@ -5577,30 +6145,30 @@
55776145 "wasm-bindgen",
55786146 "wasm-bindgen-futures",
55796147 "web-sys",
5580 "webpki-roots 1.0.9",
6148 "webpki-roots 1.0.11",
55816149]
55826150
55836151[[package]]
55846152name = "reqwest"
5585version = "0.13.4"
6153version = "0.13.5"
55866154source = "registry+https://github.com/rust-lang/crates.io-index"
5587checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
6155checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029"
55886156dependencies = [
5589 "base64 0.22.1",
6157 "base64 0.23.1",
55906158 "bytes",
55916159 "encoding_rs",
55926160 "futures-channel",
55936161 "futures-core",
55946162 "futures-util",
5595 "h2 0.4.19",
6163 "h2 0.4.20",
55966164 "http 1.5.0",
55976165 "http-body 1.1.0",
55986166 "http-body-util",
5599 "hyper 1.11.0",
6167 "hyper 1.12.0",
56006168 "hyper-rustls",
56016169 "hyper-util",
56026170 "js-sys",
5603 "log 0.4.33",
6171 "log 0.4.34",
56046172 "mime 0.3.17",
56056173 "percent-encoding 2.3.2",
56066174 "pin-project-lite",
@@ -5610,15 +6178,18 @@
56106178 "rustls-platform-verifier",
56116179 "serde",
56126180 "serde_json",
6181 "serde_urlencoded",
56136182 "sync_wrapper",
56146183 "tokio",
56156184 "tokio-rustls",
6185 "tokio-util",
56166186 "tower",
56176187 "tower-http 0.6.11",
56186188 "tower-service",
56196189 "url 2.5.8",
56206190 "wasm-bindgen",
56216191 "wasm-bindgen-futures",
6192 "wasm-streams",
56226193 "web-sys",
56236194]
56246195
@@ -5647,35 +6218,6 @@
56476218]
56486219
56496220[[package]]
5650name = "rkyv"
5651version = "0.7.46"
5652source = "registry+https://github.com/rust-lang/crates.io-index"
5653checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1"
5654dependencies = [
5655 "bitvec",
5656 "bytecheck",
5657 "bytes",
5658 "hashbrown 0.12.3",
5659 "ptr_meta",
5660 "rend",
5661 "rkyv_derive",
5662 "seahash",
5663 "tinyvec",
5664 "uuid",
5665]
5666
5667[[package]]
5668name = "rkyv_derive"
5669version = "0.7.46"
5670source = "registry+https://github.com/rust-lang/crates.io-index"
5671checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5"
5672dependencies = [
5673 "proc-macro2",
5674 "quote",
5675 "syn 1.0.109",
5676]
5677
5678[[package]]
56796221name = "rocketmq-auth"
56806222version = "0.9.0"
56816223source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5696,8 +6238,8 @@
56966238 "serde_json",
56976239 "serde_yaml",
56986240 "sha1 0.11.0",
5699 "sha2 0.11.0",
5700 "thiserror 2.0.20",
6241 "sha2 0.11.1",
6242 "thiserror 2.0.21",
57016243 "tokio",
57026244 "tracing",
57036245]
@@ -5714,7 +6256,7 @@
57146256 "futures",
57156257 "num_cpus",
57166258 "parking_lot",
5717 "rand 0.10.2",
6259 "rand 0.10.3",
57186260 "rocketmq-auth",
57196261 "rocketmq-common",
57206262 "rocketmq-error",
@@ -5759,13 +6301,13 @@
57596301 "opentelemetry",
57606302 "parking_lot",
57616303 "regex",
5762 "reqwest 0.13.4",
6304 "reqwest 0.13.5",
57636305 "rocketmq-error",
57646306 "rocketmq-rust",
57656307 "serde",
57666308 "serde_json",
57676309 "tempfile",
5768 "thiserror 2.0.20",
6310 "thiserror 2.0.21",
57696311 "time 0.3.55",
57706312 "tokio",
57716313 "tracing",
@@ -5773,7 +6315,7 @@
57736315 "tracing-subscriber",
57746316 "trait-variant",
57756317 "uuid",
5776 "zstd",
6318 "zstd 0.13.3",
57776319]
57786320
57796321[[package]]
@@ -5785,7 +6327,7 @@
57856327 "anyhow",
57866328 "config 0.15.27",
57876329 "serde_json",
5788 "thiserror 2.0.20",
6330 "thiserror 2.0.21",
57896331]
57906332
57916333[[package]]
@@ -5818,7 +6360,7 @@
58186360 "futures-util",
58196361 "num_cpus",
58206362 "parking_lot",
5821 "rand 0.10.2",
6363 "rand 0.10.3",
58226364 "rcgen",
58236365 "rocketmq-common",
58246366 "rocketmq-error",
@@ -5870,7 +6412,7 @@
58706412source = "registry+https://github.com/rust-lang/crates.io-index"
58716413checksum = "97b3fe97c18ff24299ea56f802c5693eb34266fb0bc8317f4e101debc8a3ceaf"
58726414dependencies = [
5873 "bitflags 2.13.1",
6415 "bitflags 2.13.2",
58746416 "serde",
58756417 "serde_derive",
58766418 "typeid",
@@ -5929,7 +6471,7 @@
59296471source = "registry+https://github.com/rust-lang/crates.io-index"
59306472checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0"
59316473dependencies = [
5932 "sha2 0.11.0",
6474 "sha2 0.11.1",
59336475 "walkdir",
59346476]
59356477
@@ -5991,16 +6533,16 @@
59916533
59926534[[package]]
59936535name = "rust_decimal"
5994version = "1.42.1"
6536version = "1.43.0"
59956537source = "registry+https://github.com/rust-lang/crates.io-index"
5996checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a"
6538checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a"
59976539dependencies = [
59986540 "arrayvec",
59996541 "borsh",
60006542 "bytes",
60016543 "num-traits",
6002 "rand 0.8.7",
6003 "rkyv",
6544 "rand 0.8.8",
6545 "rand 0.9.5",
60046546 "serde",
60056547 "serde_json",
60066548 "wasm-bindgen",
@@ -6023,11 +6565,11 @@
60236565
60246566[[package]]
60256567name = "rustix"
6026version = "1.1.4"
6568version = "1.1.6"
60276569source = "registry+https://github.com/rust-lang/crates.io-index"
6028checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
6570checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa"
60296571dependencies = [
6030 "bitflags 2.13.1",
6572 "bitflags 2.13.2",
60316573 "errno",
60326574 "libc",
60336575 "linux-raw-sys",
@@ -6036,12 +6578,12 @@
60366578
60376579[[package]]
60386580name = "rustls"
6039version = "0.23.43"
6581version = "0.23.46"
60406582source = "registry+https://github.com/rust-lang/crates.io-index"
6041checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
6583checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf"
60426584dependencies = [
60436585 "aws-lc-rs",
6044 "log 0.4.33",
6586 "log 0.4.34",
60456587 "once_cell",
60466588 "ring",
60476589 "rustls-pki-types",
@@ -6052,9 +6594,9 @@
60526594
60536595[[package]]
60546596name = "rustls-native-certs"
6055version = "0.8.4"
6597version = "0.8.5"
60566598source = "registry+https://github.com/rust-lang/crates.io-index"
6057checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
6599checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4"
60586600dependencies = [
60596601 "openssl-probe",
60606602 "rustls-pki-types",
@@ -6083,14 +6625,14 @@
60836625
60846626[[package]]
60856627name = "rustls-platform-verifier"
6086version = "0.6.2"
6628version = "0.7.1"
60876629source = "registry+https://github.com/rust-lang/crates.io-index"
6088checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
6630checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98"
60896631dependencies = [
60906632 "core-foundation 0.10.1",
60916633 "core-foundation-sys",
6092 "jni",
6093 "log 0.4.33",
6634 "jni 0.22.4",
6635 "log 0.4.34",
60946636 "once_cell",
60956637 "rustls",
60966638 "rustls-native-certs",
@@ -6104,15 +6646,15 @@
61046646
61056647[[package]]
61066648name = "rustls-platform-verifier-android"
6107version = "0.1.1"
6649version = "0.2.0"
61086650source = "registry+https://github.com/rust-lang/crates.io-index"
6109checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
6651checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f"
61106652
61116653[[package]]
61126654name = "rustls-webpki"
6113version = "0.103.14"
6655version = "0.103.16"
61146656source = "registry+https://github.com/rust-lang/crates.io-index"
6115checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a"
6657checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281"
61166658dependencies = [
61176659 "aws-lc-rs",
61186660 "ring",
@@ -6166,9 +6708,9 @@
61666708
61676709[[package]]
61686710name = "schannel"
6169version = "0.1.29"
6711version = "0.1.30"
61706712source = "registry+https://github.com/rust-lang/crates.io-index"
6171checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
6713checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5"
61726714dependencies = [
61736715 "windows-sys 0.61.2",
61746716]
@@ -6193,9 +6735,9 @@
61936735
61946736[[package]]
61956737name = "sea-orm"
6196version = "2.0.2"
6738version = "2.0.4"
61976739source = "registry+https://github.com/rust-lang/crates.io-index"
6198checksum = "a334e83ced3ae3ee44db0f84d1fcf8d2087a1ad9bb9036f00f9f6067156ea197"
6740checksum = "40bc08e0aa1963295a30473f98d9b4c9c2b0b3a028381e0143cb2647d0a59a32"
61996741dependencies = [
62006742 "async-stream",
62016743 "async-trait",
@@ -6206,7 +6748,7 @@
62066748 "futures-util",
62076749 "inventory",
62086750 "itertools 0.14.0",
6209 "log 0.4.33",
6751 "log 0.4.34",
62106752 "mac_address",
62116753 "ouroboros",
62126754 "pgvector",
@@ -6221,7 +6763,7 @@
62216763 "sqlx",
62226764 "sqlx-core",
62236765 "strum",
6224 "thiserror 2.0.20",
6766 "thiserror 2.0.21",
62256767 "time 0.3.55",
62266768 "tracing",
62276769 "url 2.5.8",
@@ -6231,20 +6773,20 @@
62316773
62326774[[package]]
62336775name = "sea-orm-arrow"
6234version = "2.0.0-rc.4"
6776version = "2.0.0-rc.5"
62356777source = "registry+https://github.com/rust-lang/crates.io-index"
6236checksum = "4c800d9db902534d7d01728faf98e33d13c1d57bb8c57d8e4c518309172bddda"
6778checksum = "ce2edbdc3c8e00e855d7911b6c6c4048f61c28eba40a1d40456f363d7b4969a4"
62376779dependencies = [
62386780 "arrow",
62396781 "sea-query",
6240 "thiserror 2.0.20",
6782 "thiserror 2.0.21",
62416783]
62426784
62436785[[package]]
62446786name = "sea-orm-macros"
6245version = "2.0.2"
6787version = "2.0.4"
62466788source = "registry+https://github.com/rust-lang/crates.io-index"
6247checksum = "4039a86f9acc4d3b52747508b347dddc6fd725bbc429902ebeb6d26225fc2528"
6789checksum = "91fa8c81c20fc8cfe78d22d9a2721ca5ef151dc4a8a7b372de218aaba33733fa"
62486790dependencies = [
62496791 "heck 0.5.0",
62506792 "itertools 0.14.0",
@@ -6282,7 +6824,7 @@
62826824 "proc-macro2",
62836825 "quote",
62846826 "syn 2.0.119",
6285 "thiserror 2.0.20",
6827 "thiserror 2.0.21",
62866828]
62876829
62886830[[package]]
@@ -6321,12 +6863,6 @@
63216863]
63226864
63236865[[package]]
6324name = "seahash"
6325version = "4.1.0"
6326source = "registry+https://github.com/rust-lang/crates.io-index"
6327checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
6328
6329[[package]]
63306866name = "sec1"
63316867version = "0.7.3"
63326868source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6346,7 +6882,7 @@
63466882source = "registry+https://github.com/rust-lang/crates.io-index"
63476883checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
63486884dependencies = [
6349 "bitflags 2.13.1",
6885 "bitflags 2.13.2",
63506886 "core-foundation 0.10.1",
63516887 "core-foundation-sys",
63526888 "libc",
@@ -6408,14 +6944,14 @@
64086944dependencies = [
64096945 "proc-macro2",
64106946 "quote",
6411 "syn 3.0.3",
6947 "syn 3.0.9",
64126948]
64136949
64146950[[package]]
64156951name = "serde_json"
6416version = "1.0.151"
6952version = "1.0.154"
64176953source = "registry+https://github.com/rust-lang/crates.io-index"
6418checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
6954checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
64196955dependencies = [
64206956 "itoa",
64216957 "memchr",
@@ -6506,7 +7042,7 @@
65067042checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
65077043dependencies = [
65087044 "cfg-if",
6509 "cpufeatures 0.3.0",
7045 "cpufeatures 0.3.1",
65107046 "digest 0.11.3",
65117047]
65127048
@@ -6529,12 +7065,12 @@
65297065
65307066[[package]]
65317067name = "sha2"
6532version = "0.11.0"
7068version = "0.11.1"
65337069source = "registry+https://github.com/rust-lang/crates.io-index"
6534checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
7070checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19"
65357071dependencies = [
65367072 "cfg-if",
6537 "cpufeatures 0.3.0",
7073 "cpufeatures 0.3.1",
65387074 "digest 0.11.3",
65397075]
65407076
@@ -6601,6 +7137,16 @@
66017137checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
66027138
66037139[[package]]
7140name = "simd_cesu8"
7141version = "1.2.0"
7142source = "registry+https://github.com/rust-lang/crates.io-index"
7143checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
7144dependencies = [
7145 "rustc_version",
7146 "simdutf8",
7147]
7148
7149[[package]]
66047150name = "simdutf8"
66057151version = "0.1.5"
66067152source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6614,15 +7160,15 @@
66147160dependencies = [
66157161 "num-bigint 0.4.8",
66167162 "num-traits",
6617 "thiserror 2.0.20",
7163 "thiserror 2.0.21",
66187164 "time 0.3.55",
66197165]
66207166
66217167[[package]]
66227168name = "siphasher"
6623version = "1.0.3"
7169version = "1.0.4"
66247170source = "registry+https://github.com/rust-lang/crates.io-index"
6625checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
7171checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256"
66267172
66277173[[package]]
66287174name = "skrifa"
@@ -6642,9 +7188,9 @@
66427188
66437189[[package]]
66447190name = "smallvec"
6645version = "1.15.2"
7191version = "1.16.4"
66467192source = "registry+https://github.com/rust-lang/crates.io-index"
6647checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
7193checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8"
66487194dependencies = [
66497195 "serde",
66507196]
@@ -6669,7 +7215,7 @@
66697215 "serde",
66707216 "serde_json",
66717217 "url 2.5.8",
6672 "utoipa",
7218 "utoipa 5.5.0",
66737219]
66747220
66757221[[package]]
@@ -6699,20 +7245,21 @@
66997245 "async-trait",
67007246 "axum",
67017247 "config 1.0.0",
7248 "deletion",
67027249 "export",
67037250 "i18n",
67047251 "imaging",
67057252 "jwt",
67067253 "kenauth",
67077254 "models",
6708 "prost-types 0.14.1",
7255 "prost-types 0.14.4",
67097256 "sea-orm",
67107257 "serde",
67117258 "serde_json",
67127259 "storage",
67137260 "time 0.3.55",
67147261 "tracing",
6715 "utoipa",
7262 "utoipa 5.5.0",
67167263 "uuid",
67177264]
67187265
@@ -6737,12 +7284,12 @@
67377284
67387285[[package]]
67397286name = "spki"
6740version = "0.8.0"
7287version = "0.8.1"
67417288source = "registry+https://github.com/rust-lang/crates.io-index"
6742checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
7289checksum = "7ef958a98b9d5da290cfc78946e9f3e61e1e62a18db0d92cac0b83cc161491a9"
67437290dependencies = [
67447291 "base64ct",
6745 "der 0.8.1",
7292 "der 0.8.2",
67467293]
67477294
67487295[[package]]
@@ -6752,6 +7299,15 @@
67527299checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
67537300
67547301[[package]]
7302name = "sqlparser"
7303version = "0.63.0"
7304source = "registry+https://github.com/rust-lang/crates.io-index"
7305checksum = "3679862809bd1f92e563cf6fd820e28f39135303668001c10331d5027183e8b4"
7306dependencies = [
7307 "log 0.4.34",
7308]
7309
7310[[package]]
67557311name = "sqlx"
67567312version = "0.9.0"
67577313source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6785,7 +7341,7 @@
67857341 "hashbrown 0.16.1",
67867342 "hashlink",
67877343 "indexmap",
6788 "log 0.4.33",
7344 "log 0.4.34",
67897345 "memchr",
67907346 "percent-encoding 2.3.2",
67917347 "rust_decimal",
@@ -6794,14 +7350,14 @@
67947350 "serde_json",
67957351 "sha2 0.10.9",
67967352 "smallvec",
6797 "thiserror 2.0.20",
7353 "thiserror 2.0.21",
67987354 "time 0.3.55",
67997355 "tokio",
68007356 "tokio-stream",
68017357 "tracing",
68027358 "url 2.5.8",
68037359 "uuid",
6804 "webpki-roots 1.0.9",
7360 "webpki-roots 1.0.11",
68057361]
68067362
68077363[[package]]
@@ -6848,7 +7404,7 @@
68487404source = "registry+https://github.com/rust-lang/crates.io-index"
68497405checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27"
68507406dependencies = [
6851 "bitflags 2.13.1",
7407 "bitflags 2.13.2",
68527408 "byteorder",
68537409 "bytes",
68547410 "chrono",
@@ -6859,14 +7415,14 @@
68597415 "futures-core",
68607416 "futures-util",
68617417 "generic-array",
6862 "log 0.4.33",
7418 "log 0.4.34",
68637419 "percent-encoding 2.3.2",
68647420 "rust_decimal",
68657421 "serde",
68667422 "sha1 0.11.0",
6867 "sha2 0.11.0",
7423 "sha2 0.11.1",
68687424 "sqlx-core",
6869 "thiserror 2.0.20",
7425 "thiserror 2.0.21",
68707426 "time 0.3.55",
68717427 "tracing",
68727428 "uuid",
@@ -6878,9 +7434,9 @@
68787434source = "registry+https://github.com/rust-lang/crates.io-index"
68797435checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e"
68807436dependencies = [
6881 "atoi",
7437 "atoi 2.0.0",
68827438 "base64 0.22.1",
6883 "bitflags 2.13.1",
7439 "bitflags 2.13.2",
68847440 "byteorder",
68857441 "chrono",
68867442 "crc",
@@ -6893,18 +7449,18 @@
68937449 "hkdf 0.13.0",
68947450 "hmac 0.13.0",
68957451 "itoa",
6896 "log 0.4.33",
7452 "log 0.4.34",
68977453 "md-5 0.11.0",
68987454 "memchr",
6899 "rand 0.10.2",
7455 "rand 0.10.3",
69007456 "rust_decimal",
69017457 "serde",
69027458 "serde_json",
6903 "sha2 0.11.0",
7459 "sha2 0.11.1",
69047460 "smallvec",
69057461 "sqlx-core",
69067462 "stringprep",
6907 "thiserror 2.0.20",
7463 "thiserror 2.0.21",
69087464 "time 0.3.55",
69097465 "tracing",
69107466 "uuid",
@@ -6917,7 +7473,7 @@
69177473source = "registry+https://github.com/rust-lang/crates.io-index"
69187474checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c"
69197475dependencies = [
6920 "atoi",
7476 "atoi 2.0.0",
69217477 "chrono",
69227478 "flume",
69237479 "form_urlencoded",
@@ -6927,11 +7483,11 @@
69277483 "futures-intrusive",
69287484 "futures-util",
69297485 "libsqlite3-sys",
6930 "log 0.4.33",
7486 "log 0.4.34",
69317487 "percent-encoding 2.3.2",
69327488 "serde",
69337489 "sqlx-core",
6934 "thiserror 2.0.20",
7490 "thiserror 2.0.21",
69357491 "time 0.3.55",
69367492 "tracing",
69377493 "url 2.5.8",
@@ -7011,14 +7567,14 @@
70117567source = "registry+https://github.com/rust-lang/crates.io-index"
70127568checksum = "c378b1a556adcd6eeb6b56f0ee377763a088843581265f6ec821b232a571b2be"
70137569dependencies = [
7014 "aes-gcm 0.11.0",
7570 "aes-gcm 0.11.1",
70157571 "aes-keywrap",
70167572 "getrandom 0.2.17",
70177573 "getrandom 0.4.3",
70187574 "hmac-sha256",
70197575 "hmac-sha512",
70207576 "ml-dsa",
7021 "rand 0.8.7",
7577 "rand 0.8.8",
70227578 "rsa",
70237579]
70247580
@@ -7052,9 +7608,9 @@
70527608
70537609[[package]]
70547610name = "syn"
7055version = "3.0.3"
7611version = "3.0.9"
70567612source = "registry+https://github.com/rust-lang/crates.io-index"
7057checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
7613checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
70587614dependencies = [
70597615 "proc-macro2",
70607616 "quote",
@@ -7072,13 +7628,13 @@
70727628
70737629[[package]]
70747630name = "synstructure"
7075version = "0.13.2"
7631version = "0.14.0"
70767632source = "registry+https://github.com/rust-lang/crates.io-index"
7077checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
7633checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
70787634dependencies = [
70797635 "proc-macro2",
70807636 "quote",
7081 "syn 2.0.119",
7637 "syn 3.0.9",
70827638]
70837639
70847640[[package]]
@@ -7087,7 +7643,7 @@
70877643source = "registry+https://github.com/rust-lang/crates.io-index"
70887644checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
70897645dependencies = [
7090 "bitflags 2.13.1",
7646 "bitflags 2.13.2",
70917647 "core-foundation 0.9.4",
70927648 "system-configuration-sys",
70937649]
@@ -7138,11 +7694,11 @@
71387694
71397695[[package]]
71407696name = "thiserror"
7141version = "2.0.20"
7697version = "2.0.21"
71427698source = "registry+https://github.com/rust-lang/crates.io-index"
7143checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
7699checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
71447700dependencies = [
7145 "thiserror-impl 2.0.20",
7701 "thiserror-impl 2.0.21",
71467702]
71477703
71487704[[package]]
@@ -7158,13 +7714,13 @@
71587714
71597715[[package]]
71607716name = "thiserror-impl"
7161version = "2.0.20"
7717version = "2.0.21"
71627718source = "registry+https://github.com/rust-lang/crates.io-index"
7163checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
7719checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
71647720dependencies = [
71657721 "proc-macro2",
71667722 "quote",
7167 "syn 3.0.3",
7723 "syn 3.0.9",
71687724]
71697725
71707726[[package]]
@@ -7228,9 +7784,9 @@
72287784
72297785[[package]]
72307786name = "tinystr"
7231version = "0.8.4"
7787version = "0.8.5"
72327788source = "registry+https://github.com/rust-lang/crates.io-index"
7233checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
7789checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a"
72347790dependencies = [
72357791 "displaydoc",
72367792 "zerovec",
@@ -7238,24 +7794,15 @@
72387794
72397795[[package]]
72407796name = "tinyvec"
7241version = "1.12.0"
7242source = "registry+https://github.com/rust-lang/crates.io-index"
7243checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
7244dependencies = [
7245 "tinyvec_macros",
7246]
7247
7248[[package]]
7249name = "tinyvec_macros"
7250version = "0.1.1"
7797version = "1.13.3"
72517798source = "registry+https://github.com/rust-lang/crates.io-index"
7252checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
7799checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
72537800
72547801[[package]]
72557802name = "tokio"
7256version = "1.53.1"
7803version = "1.54.1"
72577804source = "registry+https://github.com/rust-lang/crates.io-index"
7258checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
7805checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
72597806dependencies = [
72607807 "bytes",
72617808 "libc",
@@ -7276,7 +7823,7 @@
72767823dependencies = [
72777824 "proc-macro2",
72787825 "quote",
7279 "syn 3.0.3",
7826 "syn 3.0.9",
72807827]
72817828
72827829[[package]]
@@ -7291,9 +7838,9 @@
72917838
72927839[[package]]
72937840name = "tokio-rustls"
7294version = "0.26.4"
7841version = "0.26.6"
72957842source = "registry+https://github.com/rust-lang/crates.io-index"
7296checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
7843checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db"
72977844dependencies = [
72987845 "rustls",
72997846 "tokio",
@@ -7318,7 +7865,7 @@
73187865checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c"
73197866dependencies = [
73207867 "futures-util",
7321 "log 0.4.33",
7868 "log 0.4.34",
73227869 "rustls",
73237870 "rustls-pki-types",
73247871 "tokio",
@@ -7329,9 +7876,9 @@
73297876
73307877[[package]]
73317878name = "tokio-util"
7332version = "0.7.19"
7879version = "0.7.20"
73337880source = "registry+https://github.com/rust-lang/crates.io-index"
7334checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
7881checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b"
73357882dependencies = [
73367883 "bytes",
73377884 "futures-core",
@@ -7406,9 +7953,9 @@
74067953
74077954[[package]]
74087955name = "toml_edit"
7409version = "0.25.13+spec-1.1.0"
7956version = "0.25.17+spec-1.1.0"
74107957source = "registry+https://github.com/rust-lang/crates.io-index"
7411checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
7958checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c"
74127959dependencies = [
74137960 "indexmap",
74147961 "toml_datetime 1.1.2+spec-1.1.0",
@@ -7439,19 +7986,19 @@
74397986
74407987[[package]]
74417988name = "tonic"
7442version = "0.14.5"
7989version = "0.14.9"
74437990source = "registry+https://github.com/rust-lang/crates.io-index"
7444checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec"
7991checksum = "e2cdfc673aabd30fe8fee92e65837b644c37c5d47f0f97ae6d5e0f1237d15e1b"
74457992dependencies = [
74467993 "async-trait",
74477994 "axum",
74487995 "base64 0.22.1",
74497996 "bytes",
7450 "h2 0.4.19",
7997 "h2 0.4.20",
74517998 "http 1.5.0",
74527999 "http-body 1.1.0",
74538000 "http-body-util",
7454 "hyper 1.11.0",
8001 "hyper 1.12.0",
74558002 "hyper-timeout",
74568003 "hyper-util",
74578004 "percent-encoding 2.3.2",
@@ -7459,7 +8006,9 @@
74598006 "socket2 0.6.5",
74608007 "sync_wrapper",
74618008 "tokio",
8009 "tokio-rustls",
74628010 "tokio-stream",
8011 "tokio-util",
74638012 "tower",
74648013 "tower-layer",
74658014 "tower-service",
@@ -7468,9 +8017,9 @@
74688017
74698018[[package]]
74708019name = "tonic-build"
7471version = "0.14.5"
8020version = "0.14.9"
74728021source = "registry+https://github.com/rust-lang/crates.io-index"
7473checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734"
8022checksum = "ac6d916d206ed7bdda1778ddff9bda43303789c258877e7bc22fb44ee5c984b4"
74748023dependencies = [
74758024 "prettyplease",
74768025 "proc-macro2",
@@ -7480,9 +8029,9 @@
74808029
74818030[[package]]
74828031name = "tonic-health"
7483version = "0.14.5"
8032version = "0.14.6"
74848033source = "registry+https://github.com/rust-lang/crates.io-index"
7485checksum = "f4ff0636fef47afb3ec02818f5bceb4377b8abb9d6a386aeade18bd6212f8eb7"
8034checksum = "fcfab99db777fba2802f0dfa861d1628d1ae916fb199d29819941f139ae85082"
74868035dependencies = [
74878036 "prost 0.14.4",
74888037 "tokio",
@@ -7493,9 +8042,9 @@
74938042
74948043[[package]]
74958044name = "tonic-prost"
7496version = "0.14.5"
8045version = "0.14.9"
74978046source = "registry+https://github.com/rust-lang/crates.io-index"
7498checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309"
8047checksum = "eba464054e2226f182a34489b8b079bf4c6e3e7ab5fd7fc30e024f24aa92a484"
74998048dependencies = [
75008049 "bytes",
75018050 "prost 0.14.4",
@@ -7504,14 +8053,14 @@
75048053
75058054[[package]]
75068055name = "tonic-prost-build"
7507version = "0.14.5"
8056version = "0.14.9"
75088057source = "registry+https://github.com/rust-lang/crates.io-index"
7509checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a"
8058checksum = "2ea216ddc448e08e7839ab09e20e60b06b6a1c32745ccb212c0ebd161c199f1b"
75108059dependencies = [
75118060 "prettyplease",
75128061 "proc-macro2",
7513 "prost-build 0.14.1",
7514 "prost-types 0.14.1",
8062 "prost-build 0.14.4",
8063 "prost-types 0.14.4",
75158064 "quote",
75168065 "syn 2.0.119",
75178066 "tempfile",
@@ -7519,6 +8068,20 @@
75198068]
75208069
75218070[[package]]
8071name = "tonic-reflection"
8072version = "0.14.6"
8073source = "registry+https://github.com/rust-lang/crates.io-index"
8074checksum = "acccd136a4bf19810a1fde9c74edc6129b42a66b44d0c1c8aaa67aeb49a146a7"
8075dependencies = [
8076 "prost 0.14.4",
8077 "prost-types 0.14.4",
8078 "tokio",
8079 "tokio-stream",
8080 "tonic",
8081 "tonic-prost",
8082]
8083
8084[[package]]
75228085name = "tower"
75238086version = "0.5.3"
75248087source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7544,7 +8107,7 @@
75448107checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
75458108dependencies = [
75468109 "async-compression",
7547 "bitflags 2.13.1",
8110 "bitflags 2.13.2",
75488111 "bytes",
75498112 "futures-core",
75508113 "futures-util",
@@ -7566,7 +8129,7 @@
75668129source = "registry+https://github.com/rust-lang/crates.io-index"
75678130checksum = "b11f75e912b0c2be01b63d8cf8057b8c3f97cf34abb3d431a3a4c8675498e233"
75688131dependencies = [
7569 "bitflags 2.13.1",
8132 "bitflags 2.13.2",
75708133 "bytes",
75718134 "http 1.5.0",
75728135 "http-body 1.1.0",
@@ -7596,7 +8159,7 @@
75968159source = "registry+https://github.com/rust-lang/crates.io-index"
75978160checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
75988161dependencies = [
7599 "log 0.4.33",
8162 "log 0.4.34",
76008163 "pin-project-lite",
76018164 "tracing-attributes",
76028165 "tracing-core",
@@ -7610,7 +8173,7 @@
76108173dependencies = [
76118174 "crossbeam-channel",
76128175 "symlink",
7613 "thiserror 2.0.20",
8176 "thiserror 2.0.21",
76148177 "time 0.3.55",
76158178 "tracing-subscriber",
76168179]
@@ -7642,7 +8205,7 @@
76428205source = "registry+https://github.com/rust-lang/crates.io-index"
76438206checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
76448207dependencies = [
7645 "log 0.4.33",
8208 "log 0.4.34",
76468209 "once_cell",
76478210 "tracing-core",
76488211]
@@ -7715,12 +8278,12 @@
77158278 "data-encoding",
77168279 "http 1.5.0",
77178280 "httparse",
7718 "log 0.4.33",
8281 "log 0.4.34",
77198282 "rand 0.9.5",
77208283 "rustls",
77218284 "rustls-pki-types",
77228285 "sha1 0.10.7",
7723 "thiserror 2.0.20",
8286 "thiserror 2.0.21",
77248287 "url 2.5.8",
77258288]
77268289
@@ -7768,9 +8331,9 @@
77688331
77698332[[package]]
77708333name = "unicase"
7771version = "2.9.0"
8334version = "2.10.0"
77728335source = "registry+https://github.com/rust-lang/crates.io-index"
7773checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
8336checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f"
77748337
77758338[[package]]
77768339name = "unicode-bidi"
@@ -7780,15 +8343,15 @@
77808343
77818344[[package]]
77828345name = "unicode-ident"
7783version = "1.0.24"
8346version = "1.0.27"
77848347source = "registry+https://github.com/rust-lang/crates.io-index"
7785checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
8348checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
77868349
77878350[[package]]
77888351name = "unicode-normalization"
7789version = "0.1.25"
8352version = "0.1.26"
77908353source = "registry+https://github.com/rust-lang/crates.io-index"
7791checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
8354checksum = "75cd3fb03d5288b3ba9a1ee103ac7e9cbb59f0b03d64f8ddfcc04dd42758a85d"
77928355dependencies = [
77938356 "tinyvec",
77948357]
@@ -7817,7 +8380,7 @@
78178380source = "registry+https://github.com/rust-lang/crates.io-index"
78188381checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
78198382dependencies = [
7820 "crypto-common 0.1.6",
8383 "crypto-common 0.1.7",
78218384 "subtle",
78228385]
78238386
@@ -7880,25 +8443,28 @@
78808443 "imaging",
78818444 "jwt",
78828445 "models",
7883 "rand 0.8.7",
8446 "rand 0.8.8",
78848447 "redis",
78858448 "sea-orm",
78868449 "serde",
78878450 "serde_json",
7888 "sha2 0.11.0",
8451 "sha2 0.11.1",
78898452 "space",
78908453 "storage",
78918454 "time 0.3.55",
78928455 "tracing",
7893 "utoipa",
8456 "utoipa 5.5.0",
78948457 "uuid",
78958458]
78968459
78978460[[package]]
78988461name = "utf8_iter"
7899version = "1.0.4"
8462version = "1.1.0"
79008463source = "registry+https://github.com/rust-lang/crates.io-index"
7901checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
8464checksum = "af3bd9d3e571c26fcce9d033b87ea5ab061b8d2ffe9b7a5675d810d68f03edf0"
8465dependencies = [
8466 "utf_types",
8467]
79028468
79038469[[package]]
79048470name = "utf8parse"
@@ -7907,6 +8473,12 @@
79078473checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
79088474
79098475[[package]]
8476name = "utf_types"
8477version = "1.0.0"
8478source = "registry+https://github.com/rust-lang/crates.io-index"
8479checksum = "3a3b425286980a367278453f3b6d28cdcb134b574e5a35824a6ff5eebad50bb6"
8480
8481[[package]]
79108482name = "utoipa"
79118483version = "5.5.0"
79128484source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7915,7 +8487,19 @@
79158487 "indexmap",
79168488 "serde",
79178489 "serde_json",
7918 "utoipa-gen",
8490 "utoipa-gen 5.5.0",
8491]
8492
8493[[package]]
8494name = "utoipa"
8495version = "6.0.0"
8496source = "registry+https://github.com/rust-lang/crates.io-index"
8497checksum = "8765fe27aeff71012a3f90fa474cf1df863a7d0dd81ed25de4e63fff2544994f"
8498dependencies = [
8499 "indexmap",
8500 "serde",
8501 "serde_json",
8502 "utoipa-gen 6.0.1",
79198503]
79208504
79218505[[package]]
@@ -7932,6 +8516,19 @@
79328516]
79338517
79348518[[package]]
8519name = "utoipa-gen"
8520version = "6.0.1"
8521source = "registry+https://github.com/rust-lang/crates.io-index"
8522checksum = "d935f1c83fdf8b88f09bbe050d0cea78ea4afee6d7b0317403228c1200b2c8ab"
8523dependencies = [
8524 "proc-macro2",
8525 "quote",
8526 "regex",
8527 "syn 3.0.9",
8528 "uuid",
8529]
8530
8531[[package]]
79358532name = "utoipa-swagger-ui"
79368533version = "9.0.2"
79378534source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7945,19 +8542,19 @@
79458542 "serde",
79468543 "serde_json",
79478544 "url 2.5.8",
7948 "utoipa",
8545 "utoipa 5.5.0",
79498546 "zip",
79508547]
79518548
79528549[[package]]
79538550name = "uuid"
7954version = "1.24.1"
8551version = "1.29.0"
79558552source = "registry+https://github.com/rust-lang/crates.io-index"
7956checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9"
8553checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514"
79578554dependencies = [
79588555 "getrandom 0.4.3",
79598556 "js-sys",
7960 "rand 0.10.2",
8557 "rand 0.10.3",
79618558 "serde_core",
79628559 "wasm-bindgen",
79638560]
@@ -7984,11 +8581,11 @@
79848581 "fearless_simd",
79858582 "guillotiere",
79868583 "hashbrown 0.17.1",
7987 "log 0.4.33",
8584 "log 0.4.34",
79888585 "peniko",
79898586 "png 0.18.1",
79908587 "smallvec",
7991 "thiserror 2.0.20",
8588 "thiserror 2.0.21",
79928589]
79938590
79948591[[package]]
@@ -8001,11 +8598,11 @@
80018598 "fearless_simd",
80028599 "guillotiere",
80038600 "hashbrown 0.17.1",
8004 "log 0.4.33",
8601 "log 0.4.34",
80058602 "peniko",
80068603 "png 0.18.1",
80078604 "smallvec",
8008 "thiserror 2.0.20",
8605 "thiserror 2.0.21",
80098606]
80108607
80118608[[package]]
@@ -8045,9 +8642,9 @@
80458642
80468643[[package]]
80478644name = "want"
8048version = "0.3.1"
8645version = "0.3.2"
80498646source = "registry+https://github.com/rust-lang/crates.io-index"
8050checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
8647checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b"
80518648dependencies = [
80528649 "try-lock",
80538650]
@@ -8084,9 +8681,9 @@
80848681
80858682[[package]]
80868683name = "wasm-bindgen"
8087version = "0.2.127"
8684version = "0.2.129"
80888685source = "registry+https://github.com/rust-lang/crates.io-index"
8089checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
8686checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
80908687dependencies = [
80918688 "cfg-if",
80928689 "once_cell",
@@ -8098,19 +8695,20 @@
80988695
80998696[[package]]
81008697name = "wasm-bindgen-futures"
8101version = "0.4.77"
8698version = "0.4.79"
81028699source = "registry+https://github.com/rust-lang/crates.io-index"
8103checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950"
8700checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e"
81048701dependencies = [
81058702 "js-sys",
8703 "tokio",
81068704 "wasm-bindgen",
81078705]
81088706
81098707[[package]]
81108708name = "wasm-bindgen-macro"
8111version = "0.2.127"
8709version = "0.2.129"
81128710source = "registry+https://github.com/rust-lang/crates.io-index"
8113checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
8711checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
81148712dependencies = [
81158713 "quote",
81168714 "wasm-bindgen-macro-support",
@@ -8118,27 +8716,40 @@
81188716
81198717[[package]]
81208718name = "wasm-bindgen-macro-support"
8121version = "0.2.127"
8719version = "0.2.129"
81228720source = "registry+https://github.com/rust-lang/crates.io-index"
8123checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
8721checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
81248722dependencies = [
81258723 "bumpalo",
81268724 "proc-macro2",
81278725 "quote",
8128 "syn 2.0.119",
8726 "syn 3.0.9",
81298727 "wasm-bindgen-shared",
81308728]
81318729
81328730[[package]]
81338731name = "wasm-bindgen-shared"
8134version = "0.2.127"
8732version = "0.2.129"
81358733source = "registry+https://github.com/rust-lang/crates.io-index"
8136checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
8734checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
81378735dependencies = [
81388736 "unicode-ident",
81398737]
81408738
81418739[[package]]
8740name = "wasm-streams"
8741version = "0.5.0"
8742source = "registry+https://github.com/rust-lang/crates.io-index"
8743checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
8744dependencies = [
8745 "futures-util",
8746 "js-sys",
8747 "wasm-bindgen",
8748 "wasm-bindgen-futures",
8749 "web-sys",
8750]
8751
8752[[package]]
81428753name = "web-push-native"
81438754version = "0.5.0"
81448755source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -8152,14 +8763,14 @@
81528763 "jwt-simple",
81538764 "p256",
81548765 "serde",
8155 "sha2 0.11.0",
8766 "sha2 0.11.1",
81568767]
81578768
81588769[[package]]
81598770name = "web-sys"
8160version = "0.3.104"
8771version = "0.3.106"
81618772source = "registry+https://github.com/rust-lang/crates.io-index"
8162checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
8773checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4"
81638774dependencies = [
81648775 "js-sys",
81658776 "wasm-bindgen",
@@ -8198,15 +8809,15 @@
81988809 "tower-http 0.7.0",
81998810 "tracing",
82008811 "user",
8201 "utoipa",
8812 "utoipa 5.5.0",
82028813 "uuid",
82038814]
82048815
82058816[[package]]
82068817name = "webpki-root-certs"
8207version = "1.0.9"
8818version = "1.0.11"
82088819source = "registry+https://github.com/rust-lang/crates.io-index"
8209checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
8820checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a"
82108821dependencies = [
82118822 "rustls-pki-types",
82128823]
@@ -8217,14 +8828,14 @@
82178828source = "registry+https://github.com/rust-lang/crates.io-index"
82188829checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
82198830dependencies = [
8220 "webpki-roots 1.0.9",
8831 "webpki-roots 1.0.11",
82218832]
82228833
82238834[[package]]
82248835name = "webpki-roots"
8225version = "1.0.9"
8836version = "1.0.11"
82268837source = "registry+https://github.com/rust-lang/crates.io-index"
8227checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
8838checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171"
82288839dependencies = [
82298840 "rustls-pki-types",
82308841]
@@ -8606,9 +9217,9 @@
86069217
86079218[[package]]
86089219name = "xxhash-rust"
8609version = "0.8.18"
9220version = "0.8.19"
86109221source = "registry+https://github.com/rust-lang/crates.io-index"
8611checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6"
9222checksum = "550a2b930b62486a393c52d5c3b84bff264b28aa437ed64694d31e93b1757af7"
86129223
86139224[[package]]
86149225name = "yaml-rust2"
@@ -8638,9 +9249,9 @@
86389249
86399250[[package]]
86409251name = "yoke"
8641version = "0.8.3"
9252version = "0.8.4"
86429253source = "registry+https://github.com/rust-lang/crates.io-index"
8643checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
9254checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3"
86449255dependencies = [
86459256 "stable_deref_trait",
86469257 "yoke-derive",
@@ -8649,30 +9260,30 @@
86499260
86509261[[package]]
86519262name = "yoke-derive"
8652version = "0.8.2"
9263version = "0.8.4"
86539264source = "registry+https://github.com/rust-lang/crates.io-index"
8654checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
9265checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71"
86559266dependencies = [
86569267 "proc-macro2",
86579268 "quote",
8658 "syn 2.0.119",
9269 "syn 3.0.9",
86599270 "synstructure",
86609271]
86619272
86629273[[package]]
86639274name = "zerocopy"
8664version = "0.8.56"
9275version = "0.8.63"
86659276source = "registry+https://github.com/rust-lang/crates.io-index"
8666checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
9277checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
86679278dependencies = [
86689279 "zerocopy-derive",
86699280]
86709281
86719282[[package]]
86729283name = "zerocopy-derive"
8673version = "0.8.56"
9284version = "0.8.63"
86749285source = "registry+https://github.com/rust-lang/crates.io-index"
8675checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
9286checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
86769287dependencies = [
86779288 "proc-macro2",
86789289 "quote",
@@ -8690,21 +9301,21 @@
86909301
86919302[[package]]
86929303name = "zerofrom-derive"
8693version = "0.1.7"
9304version = "0.1.8"
86949305source = "registry+https://github.com/rust-lang/crates.io-index"
8695checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
9306checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
86969307dependencies = [
86979308 "proc-macro2",
86989309 "quote",
8699 "syn 2.0.119",
9310 "syn 3.0.9",
87009311 "synstructure",
87019312]
87029313
87039314[[package]]
87049315name = "zeroize"
8705version = "1.9.0"
9316version = "1.9.1"
87069317source = "registry+https://github.com/rust-lang/crates.io-index"
8707checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
9318checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
87089319dependencies = [
87099320 "zeroize_derive",
87109321]
@@ -8733,9 +9344,9 @@
87339344
87349345[[package]]
87359346name = "zerovec"
8736version = "0.11.7"
9347version = "0.11.9"
87379348source = "registry+https://github.com/rust-lang/crates.io-index"
8738checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8"
9349checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40"
87399350dependencies = [
87409351 "yoke",
87419352 "zerofrom",
@@ -8744,13 +9355,13 @@
87449355
87459356[[package]]
87469357name = "zerovec-derive"
8747version = "0.11.4"
9358version = "0.11.7"
87489359source = "registry+https://github.com/rust-lang/crates.io-index"
8749checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62"
9360checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a"
87509361dependencies = [
87519362 "proc-macro2",
87529363 "quote",
8753 "syn 3.0.3",
9364 "syn 3.0.9",
87549365]
87559366
87569367[[package]]
@@ -8769,9 +9380,9 @@
87699380
87709381[[package]]
87719382name = "zlib-rs"
8772version = "0.6.7"
9383version = "0.6.8"
87739384source = "registry+https://github.com/rust-lang/crates.io-index"
8774checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
9385checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
87759386
87769387[[package]]
87779388name = "zmij"
@@ -8787,7 +9398,7 @@
87879398dependencies = [
87889399 "bumpalo",
87899400 "crc32fast",
8790 "log 0.4.33",
9401 "log 0.4.34",
87919402 "simd-adler32",
87929403]
87939404
@@ -8797,7 +9408,16 @@
87979408source = "registry+https://github.com/rust-lang/crates.io-index"
87989409checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
87999410dependencies = [
8800 "zstd-safe",
9411 "zstd-safe 7.3.0",
9412]
9413
9414[[package]]
9415name = "zstd"
9416version = "0.14.3"
9417source = "registry+https://github.com/rust-lang/crates.io-index"
9418checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70"
9419dependencies = [
9420 "zstd-safe 8.2.0",
88019421]
88029422
88039423[[package]]
@@ -8810,6 +9430,15 @@
88109430]
88119431
88129432[[package]]
9433name = "zstd-safe"
9434version = "8.2.0"
9435source = "registry+https://github.com/rust-lang/crates.io-index"
9436checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b"
9437dependencies = [
9438 "zstd-sys",
9439]
9440
9441[[package]]
88139442name = "zstd-sys"
88149443version = "2.1.2+zstd.1.5.7"
88159444source = "registry+https://github.com/rust-lang/crates.io-index"
Modified Cargo.toml View Cargo.toml
@@ -17,6 +17,7 @@
1717cache = { path = "lib/cache" }
1818chat = { path = "lib/chat" }
1919config = { path = "lib/config" }
20deletion = { path = "lib/deletion" }
2021export = { path = "lib/export" }
2122models = { path = "lib/models" }
2223push = { path = "lib/push" }
@@ -28,6 +29,7 @@
2829i18n = { path = "lib/i18n" }
2930imaging = { path = "lib/imaging" }
3031kenauth = { path = "lib/kenauth" }
32kenoauth = { path = "lib/identity" }
3133kenshell = { path = "lib/kenshell" }
3234jwt = { path = "lib/jwt" }
3335webapi = { path = "lib/webapi" }
Modified Dockerfile View Dockerfile
@@ -17,6 +17,13 @@
1717ARG RUST_IMAGE=rust:1-bookworm
1818ARG RUNTIME_IMAGE=debian:bookworm-slim
1919
20FROM node:26-bookworm-slim AS identity-web
21WORKDIR /web
22COPY lib/identity/package.json lib/identity/package-lock.json ./
23RUN npm ci
24COPY lib/identity/ ./
25RUN npm run build
26
2027# ---------------------------------------------------------------------------
2128# builder
2229# ---------------------------------------------------------------------------
@@ -51,6 +58,7 @@
5158COPY Cargo.toml Cargo.lock ./
5259COPY lib ./lib
5360COPY app ./app
61COPY --from=identity-web /web/view/dist ./lib/identity/view/dist
5462
5563RUN --mount=type=cache,id=kenapi-cargo-registry,target=/usr/local/cargo/registry \
5664 --mount=type=cache,id=kenapi-cargo-git,target=/usr/local/cargo/git \
@@ -89,14 +97,14 @@
8997COPY --from=builder /out/kenapi /usr/local/bin/
9098
9199LABEL org.opencontainers.image.title="KenApi" \
92 org.opencontainers.image.description="GitDataAI business API (kenapi: serve / migrate / openapi)" \
100 org.opencontainers.image.description="GitDataAI monolith: business API, OAuth, gRPC and email" \
93101 org.opencontainers.image.version="1.0.0"
94102
95103ENV RUST_BACKTRACE=0
96104
97105USER gitdata
98106
99EXPOSE 7051
107EXPOSE 7051 7021 7022
100108STOPSIGNAL SIGTERM
101109
102110HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
Modified justfile View justfile
@@ -92,7 +92,8 @@
9292
9393# URL 速查
9494urls:
95 @echo " apihost http://127.0.0.1:4081 (Swagger: /api/docs)"
95 @echo " kenapi http://127.0.0.1:7051 (Swagger: /api/docs)"
96 @echo " identity http://127.0.0.1:7021 (gRPC: 127.0.0.1:7022)"
9697
9798# 重新生成 openapi.json
9899openapi:
@@ -112,4 +113,13 @@
112113
113114# 清理构建产物
114115clean:
115 @cargo clean
No newline at end of file
116 @cargo clean
117# 登录前端随 kenapi 发布。
118identity-web-build:
119 cd lib/identity && npm ci && npm run build
120
121identity-web-dev:
122 cd lib/identity && npm run dev
123
124identity-openapi:
125 cargo run -p kenapi -- identity-openapi
Modified openapi.json View openapi.json

Diff too large to display.