Skip to content
Clone

refactor: update session module references for consistency and clarity

ZhenYi committed
parent 9362a10 commit 5f84089 Browse files

300 files changed

+1130 −1763

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
@@ -10,6 +10,7 @@
1010#[derive(Subcommand)]
1111pub enum Command {
1212 Serve,
13 Mail,
1314 Migrate,
1415 IdentityOpenapi,
1516 Openapi {
Modified app/src/main.rs View app/src/main.rs
@@ -13,6 +13,7 @@
1313fn main() -> anyhow::Result<()> {
1414 match Cli::parse().command.unwrap_or(Command::Serve) {
1515 Command::Serve => serve(),
16 Command::Mail => mail(),
1617 Command::Migrate => migrate(),
1718 Command::Openapi { check } => openapi(check),
1819 Command::IdentityOpenapi => identity_openapi(),
@@ -48,25 +49,56 @@
4849 .await
4950 .context("connecting to the shared database")?,
5051 );
51 let auth_runtime = match auth_config()? {
52 let bind_addr = cfg.apihost.bind_addr().to_string();
53 let port = cfg.apihost.port();
54 let addr: SocketAddr = format!("{bind_addr}:{port}")
55 .parse()
56 .with_context(|| format!("parsing bind address {bind_addr}:{port}"))?;
57 let conn = queue::RocketMqConn::init(&cfg.rocketmq)
58 .await
59 .map_err(|err| anyhow::anyhow!("failed to start the rocketmq producer: {err}"))?;
60
61 let auth_config = auth_config()?;
62 let (auth_svc, client, mail, auth_events) = match auth_config.as_ref() {
5263 Some(auth_config) => {
5364 let db =
5465 auth::db::DbConn::new(database.primary().clone(), database.replicas().to_vec());
55 Some(
56 auth::runtime::start(&auth_config, db)
57 .await
58 .context("starting auth service")?,
66 let cache = auth::cache::Cache::connect(&app_config.redis)
67 .await
68 .context("connecting the auth cache")?;
69 let auth_events = auth::events::EventHandler::new(conn.clone());
70 let email_pool: config::email::EmailPoolConfig = auth_config
71 .section_or_default()
72 .context("loading email configuration")?;
73 let mail = auth::email::MailPublisher::new(
74 queue::Publisher::new(conn.clone()),
75 email_pool.mail_topic,
76 );
77 let pow: config::pow::PowConfig = auth_config
78 .section()
79 .context("loading pow configuration")?;
80 let service = auth::server::AuthSvc::new(
81 db,
82 cache,
83 Some(mail.clone()),
84 Some(auth_events.clone()),
85 auth_config
86 .section()
87 .context("loading auth configuration")?,
88 pow,
89 session_config.max_age.max(1) as u64,
5990 )
91 .await
92 .context("starting the auth service")?;
93 let client = std::sync::Arc::new(auth::client::KenAuthClient::local(
94 service.clone(),
95 2_000,
96 ));
97 (Some(service), Some(client), Some(mail), Some(auth_events))
6098 }
61 None => None,
99 None => (None, None, None, None),
62100 };
63 let client = auth_runtime.as_ref().map(|runtime| {
64 std::sync::Arc::new(auth::client::KenAuthClient::local(
65 runtime.service().clone(),
66 2_000,
67 ))
68 });
69 let state = export::AppState::with_identity(app_config, database, client)
101 let state = export::AppState::with_identity(app_config, database, client, mail)
70102 .await
71103 .context("building AppState")?;
72104 info!("dependencies ready");
@@ -85,12 +117,6 @@
85117 tokio::spawn(chat::worker::run_background_tasks(state.clone()));
86118 tokio::spawn(deletion::run_worker(state.clone()));
87119
88 let conn = queue::RocketMqConn::init(&cfg.rocketmq)
89 .await
90 .map_err(|err| anyhow::anyhow!("failed to start the rocketmq producer: {err}"))?;
91 let auth_events = auth_runtime
92 .as_ref()
93 .and_then(|runtime| runtime.service().events().cloned());
94120 let relay = events::outbox::OutboxRelay::start(
95121 auth_events,
96122 Some(queue::Publisher::new(conn.clone())),
@@ -111,16 +137,10 @@
111137 );
112138 }
113139
114 let verifier = webapi::verifier(&state);
115140 let doc = api_doc();
116141 let app_state = state.clone();
142 let auth_service = auth_svc.clone();
117143
118 let bind_addr = cfg.apihost.bind_addr().to_string();
119 let port = cfg.apihost.port();
120 let addr: SocketAddr = format!("{bind_addr}:{port}")
121 .parse()
122 .with_context(|| format!("parsing bind address {bind_addr}:{port}"))?;
123
124144 let server = HttpServer::new(move || {
125145 let state = app_state.clone();
126146 let mut app = App::new()
@@ -131,17 +151,19 @@
131151 .error_handler(export::http::json_error),
132152 )
133153 .app_data(web::PayloadConfig::new(webapi::MAX_REQUEST_BODY_BYTES));
134 if let Some(verifier) = verifier.clone() {
135 app = app.app_data(web::Data::new(verifier));
154 if let Some(service) = auth_service.clone() {
155 app = app.app_data(web::Data::new(service));
136156 }
137157 app.wrap(auth::session::SessionMiddleware::new(
138158 session_config.clone(),
139159 session_store.clone(),
140160 ))
161 .wrap(auth::envelope::EnvelopeContext)
141162 .wrap(webapi::build_cors(&state.config.web))
142163 .wrap(from_fn(export::middleware::trace_request))
143164 .wrap(from_fn(export::telemetry::http_telemetry))
144165 .wrap(from_fn(webapi::auth::session::revalidate::revalidate))
166 .wrap(from_fn(webapi::auth::session::csrf::csrf))
145167 .service(web::resource("/api/docs").route(web::get().to(|| async {
146168 actix_web::HttpResponse::Found()
147169 .insert_header((actix_web::http::header::LOCATION, "/api/docs/"))
@@ -149,6 +171,7 @@
149171 })))
150172 .service(SwaggerUi::new("/api/docs/{_:.*}").url("/api/docs/openapi.json", doc.clone()))
151173 .service(webapi::scope(state))
174 .default_service(web::to(auth::assets::serve))
152175 })
153176 .disable_signals()
154177 .bind(addr)
@@ -163,16 +186,30 @@
163186 handle.stop(true).await;
164187 });
165188 let api = async move { server.await.context("actix server error") };
166 let auth_cancel = cancel.clone();
167 let auth_server = async move {
168 match auth_runtime {
169 Some(runtime) => runtime.serve_until(auth_cancel).await,
170 None => {
171 auth_cancel.cancelled().await;
172 Ok(())
189
190 let grpc = if let Some(auth_config) = auth_config.as_ref() {
191 let server_config: config::server::ServerConfig = auth_config
192 .section()
193 .context("loading server configuration")?;
194 let listener = tokio::net::TcpListener::bind(server_config.grpc_addr)
195 .await
196 .with_context(|| format!("binding gRPC on {}", server_config.grpc_addr))?;
197 let grpc_cancel = cancel.clone();
198 let incoming = tokio_stream::wrappers::TcpListenerStream::new(listener);
199 Some(tokio::spawn(async move {
200 if let Err(error) = auth::server::grpc::router()
201 .serve_with_incoming_shutdown(tonic::service::Routes::default(), incoming, async move {
202 grpc_cancel.cancelled().await;
203 })
204 .await
205 {
206 warn!(%error, "gRPC server stopped");
173207 }
174 }
208 }))
209 } else {
210 None
175211 };
212
176213 let signal = async {
177214 tokio::select! {
178215 _ = export::telemetry::shutdown_signal() => cancel.cancel(),
@@ -183,13 +220,14 @@
183220 if let Err(error) = relay.shutdown().await {
184221 warn!(%error, "shutting down the event outbox relay failed");
185222 }
186 let auth_result = auth_server.await;
223 if let Some(grpc) = grpc {
224 let _ = grpc.await;
225 }
187226 for task in push_tasks {
188227 let _ = task.await;
189228 }
190229 conn.shutdown().await;
191230 api_result?;
192 auth_result?;
193231
194232 export::telemetry::shutdown_telemetry();
195233
@@ -198,6 +236,49 @@
198236}
199237
200238#[tokio::main]
239async fn mail() -> anyhow::Result<()> {
240 use tokio_util::sync::CancellationToken;
241
242 export::telemetry::init_tracing();
243
244 let figment = config::loader::figment().context("loading configuration")?;
245 let rocketmq: config::rocketmq::RocketMqConfig = figment
246 .extract_inner("rocketmq")
247 .context("loading rocketmq configuration")?;
248 let smtp: config::smtp::SmtpConfig = figment
249 .extract_inner("smtp")
250 .context("loading smtp configuration")?;
251 let email: config::email::EmailPoolConfig = figment
252 .extract_inner("email")
253 .context("loading email configuration")?;
254 rocketmq.is_valid().map_err(anyhow::Error::msg)?;
255 smtp.is_valid().map_err(anyhow::Error::msg)?;
256 email.is_valid().map_err(anyhow::Error::msg)?;
257
258 let conn = queue::RocketMqConn::init(&rocketmq)
259 .await
260 .map_err(|err| anyhow!("failed to start the rocketmq producer: {err}"))?;
261 let events = auth::events::EventHandler::new(conn.clone());
262 let consumer = auth::email::EmailConsumer::new(smtp, email, Some(events))
263 .context("building the mail consumer")?;
264
265 let cancel = CancellationToken::new();
266 let run_cancel = cancel.clone();
267 let run = consumer.run(rocketmq, run_cancel);
268 let signal = async {
269 tokio::select! {
270 _ = export::telemetry::shutdown_signal() => cancel.cancel(),
271 _ = cancel.cancelled() => {},
272 }
273 };
274 let (result, ()) = tokio::join!(run, signal);
275 conn.shutdown().await;
276 result.context("mail service failed")?;
277 export::telemetry::shutdown_telemetry();
278 Ok(())
279}
280
281#[tokio::main]
201282async fn migrate() -> anyhow::Result<()> {
202283 use config::scoped::{MigrateConfig, ScopedConfig};
203284 use models::migrate::sync_schema;
Modified app/Cargo.toml View app/Cargo.toml
@@ -15,7 +15,7 @@
1515deletion = { workspace = true }
1616events = { workspace = true }
1717export = { workspace = true }
18token = { workspace = true }
18session = { workspace = true }
1919models = { workspace = true }
2020push = { workspace = true }
2121queue = { workspace = true }
@@ -29,3 +29,5 @@
2929webapi = { workspace = true }
3030
3131utoipa-identity = { package = "utoipa", version = "6.0.0" }
32tokio-stream = { version = "0.1", features = ["net"] }
33tonic = { workspace = true }
Modified charts/core/values.yaml View charts/core/values.yaml
@@ -40,8 +40,6 @@
4040 APP_ROCKETMQ__NAME_SERVER: "rocketmq-namesrv:9876"
4141 APP_ROCKETMQ__TOPIC_PREFIX: "gitdata"
4242 # 认证中心:浏览器走同域 session,服务间走 gRPC(x-rpc-key)。
43 APP_JWT__ISSUER: "https://auth.gitdata.ai"
44 APP_JWT__SESSION_COOKIE: "gitdata_session"
4543 RUST_LOG: "info"
4644
4745# 敏感配置(渲染为 Secret)。注意 AppConfig 的 Debug 会带出密钥,
Modified charts/prod/values/prod.yaml View charts/prod/values/prod.yaml
@@ -23,7 +23,7 @@
2323 APP_PUSH__VAPID_SUBJECT: "mailto:bot@mail.gitdata.ai"
2424 APP_WEB__WEB_BASE_URL: "https://gitdata.ai"
2525 APP_WEB__CORS_ALLOW_ORIGINS: '["https://gitdata.ai"]'
26 APP_JWT__COOKIE_SECURE: "true"
26 APP_SESSION__SECURE: "true"
2727 APP_GIT__BASE_URL: "https://gitdata.ai"
2828 APP_LIVEKIT__RECORDING_ENABLED: "false"
2929 RUST_LOG: "info"
Modified config/default/auth.json View config/default/auth.json
@@ -29,8 +29,5 @@
2929 "content_md": "Placeholder privacy policy."
3030 }
3131 ],
32 "rpc_key": "REPLACE_WITH_RPC_KEY_AT_LEAST_32_CHARS",
33 "socket_origins": [
34 "https://gitdata.ai"
35 ]
32 "rpc_key": "REPLACE_WITH_RPC_KEY_AT_LEAST_32_CHARS"
3633}
Modified config/default/email.json View config/default/email.json
@@ -4,5 +4,8 @@
44 "capacity": 1024,
55 "enqueue_timeout_ms": 5000,
66 "retry_delay_ms": 2000,
7 "max_attempts": 3
7 "max_attempts": 3,
8 "mail_topic": "gitdata-mail",
9 "consumer_group": "gitdata-mail",
10 "max_reconsume": 5
811}
Modified config/default/server.json View config/default/server.json
@@ -1,4 +1,3 @@
11{
2 "http_addr": "127.0.0.1:7021",
32 "grpc_addr": "127.0.0.1:7022"
4}
3}
No newline at end of file
Modified config/default/session.json View config/default/session.json
@@ -1,9 +1,10 @@
11{
22 "cookie_name": "session",
33 "max_age": 2592000,
4 "anonymous_max_age": 300,
4 "anonymous_max_age": 3600,
55 "path": "/",
66 "secure": false,
77 "same_site": "Lax",
8 "trust_proxy": false
8 "trust_proxy": false,
9 "recheck_secs": 60
910}
Modified deploy/compose.yaml View deploy/compose.yaml
@@ -3,7 +3,7 @@
33# docker compose up -d # 用已有镜像启动
44# docker compose up -d --build # 从仓库根目录 Dockerfile 重新构建
55#
6# 端口:7051 = API;7021 = identity HTTP/登录页;7022 = 兼容外部服务的认证 gRPC。
6# 端口:7051 = API(含认证 HTTP/登录页);7022 = 认证 gRPC(当前为空路由,按需加回)。
77# config 里另留了 7052(githost HTTP)与 7053(git SSH)两个口,但当前 apihost
88# 二进制不监听它们(git 读写走 KenShell),改由 KenShell 的 7011/7012 承担。
99#
Modified lib/auth/src/client/local/mod.rs View lib/auth/src/client/local/mod.rs
@@ -1,23 +1,17 @@
1mod access_key;
21mod agreement;
32mod authority;
43mod authorization;
5mod email;
64mod identity;
75mod mfa;
86mod organization;
97mod session;
108mod ssh_key;
11mod token;
129
13pub use access_key::AccessKeyClient;
1410pub use agreement::AgreementClient;
1511pub use authority::AuthorityClient;
1612pub use authorization::AuthorizationClient;
17pub use email::EmailClient;
1813pub use identity::IdentityClient;
1914pub use mfa::MfaClient;
2015pub use organization::OrganizationClient;
2116pub use session::SessionClient;
2217pub use ssh_key::SshKeyClient;
23pub use token::TokenClient;
Modified lib/auth/src/client/client.rs View lib/auth/src/client/client.rs
@@ -1,9 +1,7 @@
1use std::collections::HashMap;
21use std::time::Duration;
32
43use crate::server::AuthSvc;
54use tonic::Request;
6use uuid::Uuid;
75
86use crate::client::error::KenAuthError;
97use crate::protos::auth as proto;
@@ -44,10 +42,6 @@
4442 crate::client::local::AuthorityClient::new(self.service.clone())
4543 }
4644
47 pub fn email(&self) -> crate::client::local::EmailClient {
48 crate::client::local::EmailClient::new(self.service.clone())
49 }
50
5145 pub fn session(&self) -> crate::client::local::SessionClient {
5246 crate::client::local::SessionClient::new(self.service.clone())
5347 }
@@ -56,18 +50,10 @@
5650 crate::client::local::OrganizationClient::new(self.service.clone())
5751 }
5852
59 pub fn token(&self) -> crate::client::local::TokenClient {
60 crate::client::local::TokenClient::new(self.service.clone())
61 }
62
6353 pub fn authorization(&self) -> crate::client::local::AuthorizationClient {
6454 crate::client::local::AuthorizationClient::new(self.service.clone())
6555 }
6656
67 pub fn access_key(&self) -> crate::client::local::AccessKeyClient {
68 crate::client::local::AccessKeyClient::new(self.service.clone())
69 }
70
7157 pub fn ssh_key(&self) -> crate::client::local::SshKeyClient {
7258 crate::client::local::SshKeyClient::new(self.service.clone())
7359 }
@@ -191,84 +177,7 @@
191177 .map(|response| response.into_inner())
192178 }
193179
194 pub async fn list_access_keys(
195 &self,
196 user_id: i64,
197 include_revoked: bool,
198 page: proto::common::v1::PageRequest,
199 ) -> Result<proto::v1::ListAccessKeysResponse, KenAuthError> {
200 let mut client = self.access_key();
201 client
202 .list_access_keys(self.read_request(proto::v1::ListAccessKeysRequest {
203 user_id,
204 include_revoked,
205 page: Some(page),
206 }))
207 .await
208 .map_err(KenAuthError::from_status)
209 .map(|response| response.into_inner())
210 }
211
212180 #[allow(clippy::too_many_arguments)]
213 pub async fn create_access_key(
214 &self,
215 user_id: i64,
216 name: &str,
217 description: &str,
218 scopes: Vec<i32>,
219 expires_in_secs: i64,
220 client_ip: &str,
221 ) -> Result<proto::v1::CreateAccessKeyResponse, KenAuthError> {
222 let mut client = self.access_key();
223 client
224 .create_access_key(self.write_request(proto::v1::CreateAccessKeyRequest {
225 user_id,
226 name: name.to_string(),
227 description: description.to_string(),
228 scopes,
229 expires_in_secs,
230 client_ip: client_ip.to_string(),
231 }))
232 .await
233 .map_err(KenAuthError::from_status)
234 .map(|response| response.into_inner())
235 }
236
237 pub async fn rotate_access_key(
238 &self,
239 user_id: i64,
240 access_key_id: &str,
241 client_ip: &str,
242 ) -> Result<proto::v1::RotateAccessKeyResponse, KenAuthError> {
243 let mut client = self.access_key();
244 client
245 .rotate_access_key(self.write_request(proto::v1::RotateAccessKeyRequest {
246 user_id,
247 access_key_id: access_key_id.to_string(),
248 client_ip: client_ip.to_string(),
249 }))
250 .await
251 .map_err(KenAuthError::from_status)
252 .map(|response| response.into_inner())
253 }
254
255 pub async fn revoke_access_key(
256 &self,
257 user_id: i64,
258 access_key_id: &str,
259 reason: &str,
260 ) -> Result<proto::v1::RevokeAccessKeyResponse, KenAuthError> {
261 let mut client = self.access_key();
262 client
263 .revoke_access_key(self.write_request(proto::v1::RevokeAccessKeyRequest {
264 user_id,
265 access_key_id: access_key_id.to_string(),
266 reason: reason.to_string(),
267 }))
268 .await
269 .map_err(KenAuthError::from_status)
270 .map(|response| response.into_inner())
271 }
272181
273182 pub async fn list_user_ssh_keys(
274183 &self,
@@ -382,42 +291,6 @@
382291 request
383292 }
384293
385 /// Enqueue one plain text / HTML mail on `kenoauth`.
386 ///
387 /// `kenoauth` owns SMTP, retries and backpressure; a `Ok` here means the
388 /// message was accepted into its queue, while delivery outcome is visible
389 /// through `GetQueueStatus` / the delivery events on that side.
390 pub async fn send_mail(
391 &self,
392 to: &str,
393 subject: &str,
394 text: &str,
395 html: &str,
396 ) -> Result<(), KenAuthError> {
397 let mut client = self.email();
398 let request = proto::internal::v1::SendEmailRequest {
399 request_id: Uuid::now_v7().to_string(),
400 to: vec![proto::internal::v1::EmailAddress {
401 address: to.to_string(),
402 name: String::new(),
403 }],
404 cc: Vec::new(),
405 bcc: Vec::new(),
406 subject: subject.to_string(),
407 text: text.to_string(),
408 html: html.to_string(),
409 reply_to: None,
410 attachments: Vec::new(),
411 metadata: HashMap::new(),
412 trace_id: String::new(),
413 };
414 client
415 .send_email(self.write_request(request))
416 .await
417 .map_err(KenAuthError::from_status)?;
418 Ok(())
419 }
420
421294 pub async fn get_session(
422295 &self,
423296 user_id: i64,
@@ -606,71 +479,6 @@
606479 .await
607480 .map_err(KenAuthError::from_status)?;
608481 Ok(())
609 }
610
611 pub async fn verify_access_key(
612 &self,
613 token: &str,
614 required_scopes: Vec<i32>,
615 ) -> Result<Option<proto::common::v1::Principal>, KenAuthError> {
616 let mut client = self.access_key();
617 let response = client
618 .verify_access_key(self.read_request(proto::v1::VerifyAccessKeyRequest {
619 access_key: token.to_string(),
620 required_scopes,
621 client_ip: String::new(),
622 }))
623 .await
624 .map_err(KenAuthError::from_status)?
625 .into_inner();
626 if !response.valid {
627 return Ok(None);
628 }
629 Ok(response.principal)
630 }
631
632 pub async fn issue_service_token(
633 &self,
634 user_id: i64,
635 audience: i32,
636 scopes: Vec<i32>,
637 ttl_seconds: u32,
638 event_id: &str,
639 ) -> Result<proto::v1::IssueServiceTokenResponse, KenAuthError> {
640 let mut client = self.token();
641 client
642 .issue_service_token(self.write_request(proto::v1::IssueServiceTokenRequest {
643 audience,
644 subject_user_id: user_id,
645 scopes,
646 ttl_seconds,
647 constraint: None,
648 event_id: event_id.to_string(),
649 }))
650 .await
651 .map_err(KenAuthError::from_status)
652 .map(|response| response.into_inner())
653 }
654
655 pub async fn introspect_token(
656 &self,
657 token: &str,
658 ) -> Result<proto::v1::IntrospectTokenResponse, KenAuthError> {
659 let mut client = self.token();
660 let credential = Some(proto::common::v1::Credential {
661 kind: Some(proto::common::v1::credential::Kind::ServiceToken(
662 token.to_string(),
663 )),
664 request_id: String::new(),
665 });
666 client
667 .introspect_token(self.read_request(proto::v1::IntrospectTokenRequest {
668 credential,
669 required_scopes: Vec::new(),
670 }))
671 .await
672 .map_err(KenAuthError::from_status)
673 .map(|response| response.into_inner())
674482 }
675483
676484 pub async fn lookup_ssh_key(
Modified lib/auth/src/server/grpc/authority.rs View lib/auth/src/server/grpc/authority.rs
@@ -1,5 +1,3 @@
1use crate::jwt::JwtError;
2use crate::jwt::authority::{AccessRecord, Authority, UserRecord};
31use crate::models::users::users;
42use crate::protos::auth::common::v1::{Code, PageResponse};
53use crate::protos::auth::internal::v1::authority_service_server::{
@@ -16,6 +14,7 @@
1614use tonic::{Request, Response, Status};
1715
1816use crate::server::AuthSvc;
17use crate::server::errors::AuthError;
1918use crate::server::grpc::{context, convert, error, store};
2019
2120pub struct AuthorityApi {
@@ -38,48 +37,45 @@
3837 }
3938}
4039
41/// 把 jwt 侧的失败翻译成 gRPC 状态。调用方(crate::jwt::authority::GrpcAuthority)会把
42/// 除 UNKNOWN/INTERNAL/UNAVAILABLE 之外的一切折叠回 `JwtError::Revoked`,所以
43/// 这里选择的具体码只影响可观测性,不影响业务分支。
44fn status(error_value: &JwtError, request_id: Option<&str>) -> Status {
40fn status(error_value: &AuthError, request_id: Option<&str>) -> Status {
4541 let code = match error_value {
46 JwtError::Revoked => Code::TokenRevoked,
47 JwtError::Missing | JwtError::Redirect(_) => Code::Unauthenticated,
48 JwtError::Malformed => Code::MalformedRequest,
49 JwtError::UnknownKey | JwtError::UnsupportedKey | JwtError::BadSignature => {
50 Code::InvalidAccessToken
42 AuthError::Unauthenticated | AuthError::SecurityVersionStale { .. } => {
43 Code::Unauthenticated
5144 }
52 JwtError::Expired => Code::TokenExpired,
53 JwtError::BadIssuer | JwtError::WrongAudience => Code::InvalidAccessToken,
54 JwtError::Config(_) | JwtError::Internal(_) => Code::Internal,
45 AuthError::Forbidden => Code::Forbidden,
46 AuthError::UserNotFound => Code::UserNotFound,
47 AuthError::SessionNotFound => Code::SessionNotFound,
48 AuthError::MalformedRequest => Code::MalformedRequest,
49 AuthError::VersionMismatch { .. } => Code::Conflict,
5550 _ => Code::Internal,
5651 };
5752 error::status_of(code, error_value.message(), request_id)
5853}
5954
60/// 账号读取的失败语义比令牌校验更窄:只有"查不到"这一种正常失败。
61fn user_status(error_value: &JwtError, request_id: Option<&str>) -> Status {
55fn user_status(error_value: &AuthError, request_id: Option<&str>) -> Status {
6256 match error_value {
63 JwtError::Revoked => error::status_of(Code::UserNotFound, "user not found", request_id),
57 AuthError::UserNotFound => {
58 error::status_of(Code::UserNotFound, "user not found", request_id)
59 }
6460 other => status(other, request_id),
6561 }
6662}
6763
68fn access(record: &AccessRecord) -> AuthorityAccess {
64fn access(user: &users::Model, mfa_verified: bool) -> AuthorityAccess {
6965 AuthorityAccess {
70 id: record.id,
71 username: record.username.clone(),
72 allow_use: record.allow_use,
73 forbid_login: record.forbid_login,
74 endtime_forbid_login_at: convert::datetime_opt(record.endtime_forbid_login_at),
75 is_admin: record.is_admin,
76 security_version: record.security_version,
77 deleted_at: convert::datetime_opt(record.deleted_at),
78 mfa_verified: record.mfa_verified,
66 id: user.id,
67 username: user.username.clone(),
68 allow_use: user.allow_use,
69 forbid_login: user.forbid_login,
70 endtime_forbid_login_at: convert::datetime_opt(user.endtime_forbid_login_at),
71 is_admin: user.is_admin,
72 security_version: user.security_version,
73 deleted_at: convert::datetime_opt(user.deleted_at),
74 mfa_verified,
7975 }
8076}
8177
82fn user(record: &UserRecord) -> AuthorityUser {
78fn user(record: &users::Model) -> AuthorityUser {
8379 AuthorityUser {
8480 id: record.id,
8581 username: record.username.clone(),
@@ -269,70 +265,25 @@
269265}
270266
271267impl AuthorityApi {
272 #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))]
268 #[tracing::instrument(skip_all, fields(request_id = %_request_id.unwrap_or_default()))]
273269 async fn redeem_socket_ticket_inner(
274270 &self,
275 request_id: Option<&str>,
276 payload: crate::protos::auth::internal::v1::RedeemSocketTicketRequest,
271 _request_id: Option<&str>,
272 _payload: crate::protos::auth::internal::v1::RedeemSocketTicketRequest,
277273 ) -> Result<Response<crate::protos::auth::internal::v1::RedeemSocketTicketResponse>, Status>
278274 {
279 let grant = self
280 .svc
281 .redeem_socket_ticket(&payload.ticket, &payload.origin, request_id)
282 .await
283 .map_err(|failure| error::status(&failure, request_id))?;
284 Ok(Response::new(
285 crate::protos::auth::internal::v1::RedeemSocketTicketResponse {
286 user_id: grant.user_id,
287 security_version: grant.version,
288 session_id: grant.session_id,
289 scopes_bounded: grant.scopes.is_some(),
290 scopes: grant.scopes.unwrap_or_default(),
291 credential_expires_at: grant.credential_expires_at.unwrap_or_default(),
292 },
293 ))
275 Err(Status::unimplemented("socket tickets are not available"))
294276 }
295277
296 #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))]
278 #[tracing::instrument(skip_all, fields(request_id = %_request_id.unwrap_or_default()))]
297279 async fn validate_inner(
298280 &self,
299 request_id: Option<&str>,
300 payload: ValidateRequest,
281 _request_id: Option<&str>,
282 _payload: ValidateRequest,
301283 ) -> Result<Response<ValidateResponse>, Status> {
302 let token = payload.token;
303 if token.is_empty() {
304 return Err(status(&JwtError::Missing, request_id));
305 }
306 let claims = std::iter::once(crate::jwt::PLATFORM_AUDIENCE)
307 .chain(
308 self.svc
309 .apps()
310 .apps()
311 .iter()
312 .map(|app| app.audience_or_id()),
313 )
314 .find_map(|audience| crate::jwt::verify_for_audience(&token, audience).ok())
315 .ok_or_else(|| status(&JwtError::WrongAudience, request_id))?;
316 if claims.token_type != "access" {
317 return Err(error::invalid_argument(
318 "token type is not access",
319 request_id,
320 ));
321 }
322 let record = self
323 .svc
324 .validate(&token, &claims)
325 .await
326 .map_err(|error_value| status(&error_value, request_id))?;
327 tracing::debug!(
328 request_id,
329 user_id = record.id,
330 session = claims.session_id.is_some(),
331 "authority validated an access token"
332 );
333 Ok(Response::new(ValidateResponse {
334 access: Some(access(&record)),
335 }))
284 Err(Status::unimplemented(
285 "access token validation is not available",
286 ))
336287 }
337288
338289 #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))]
@@ -408,7 +359,7 @@
408359 request_id,
409360 ));
410361 }
411 let record = self
362 let (user, mfa_verified) = self
412363 .svc
413364 .check_session(
414365 payload.user_id,
@@ -424,7 +375,7 @@
424375 "authority revalidated a session"
425376 );
426377 Ok(Response::new(GetSessionResponse {
427 access: Some(access(&record)),
378 access: Some(access(&user, mfa_verified)),
428379 }))
429380 }
430381
@@ -455,8 +406,7 @@
455406 let record =
456407 self.svc.update(payload.user_id, value).await.map_err(
457408 |error_value| match error_value {
458 // 结构校验已在上面完成,update 里剩下的 Malformed 只可能是版本 CAS 落空。
459 JwtError::Malformed => {
409 AuthError::VersionMismatch { .. } => {
460410 error::status_of(Code::Conflict, "account version conflict", request_id)
461411 }
462412 other => status(&other, request_id),
@@ -510,10 +460,7 @@
510460 String::new()
511461 };
512462 Ok(Response::new(ListUsersResponse {
513 users: rows
514 .iter()
515 .map(|row| user(&crate::server::authority::record(row.clone())))
516 .collect(),
463 users: rows.iter().map(user).collect(),
517464 page: Some(PageResponse { next_page_token }),
518465 }))
519466 }
Modified lib/auth/src/server/grpc/authorization.rs View lib/auth/src/server/grpc/authorization.rs
@@ -12,8 +12,8 @@
1212
1313use crate::server::AuthSvc;
1414use crate::server::errors::{AuthError, AuthResult};
15use crate::server::grpc::store::ALLOWED_APP_SCOPES;
1516use crate::server::grpc::{agreement, context, error, store};
16use crate::server::http::access_token::types::ALLOWED_APP_SCOPES;
1717
1818const MAX_BATCH_CAPABILITIES: usize = 32;
1919const ADMIN_CACHE_TTL_SECS: u64 = 300;
Modified lib/auth/src/server/grpc/cache_maintenance.rs View lib/auth/src/server/grpc/cache_maintenance.rs
@@ -203,8 +203,7 @@
203203 Ok(user) if user.deleted_at.is_none() => user,
204204 _ => return (user_id, false),
205205 };
206 let mut me = MeResponse::from(user);
207 me.avatar_url = svc.avatar_url(user_id).await.ok().flatten();
206 let me = MeResponse::from(user);
208207 match svc
209208 .cache()
210209 .set_json_ex(
Modified lib/auth/src/server/grpc/convert.rs View lib/auth/src/server/grpc/convert.rs
@@ -1,5 +1,5 @@
1use crate::models::users::{user_access_tokens, user_sessions, user_ssh_keys};
2use crate::protos::auth::common::v1::{AccessKeyInfo, SessionInfo, SshKeyInfo};
1use crate::models::users::{user_sessions, user_ssh_keys};
2use crate::protos::auth::common::v1::{SessionInfo, SshKeyInfo};
33use chrono::{DateTime, NaiveDate, TimeZone, Utc};
44use prost_types::Timestamp;
55
@@ -56,21 +56,6 @@
5656 mfa_verified_at: datetime_opt(row.mfa_verified_at),
5757 revoked_at: datetime_opt(row.revoked_at),
5858 revoke_reason: String::new(),
59 }
60}
61
62pub fn access_key_info(row: &user_access_tokens::Model) -> AccessKeyInfo {
63 AccessKeyInfo {
64 access_key_id: row.id.to_string(),
65 name: row.name.clone(),
66 description: row.description.clone().unwrap_or_default(),
67 fingerprint: row.fingerprint.clone().unwrap_or_default(),
68 scopes: store::scope_codes(&store::scopes_from_names(&row.scope)),
69 hash_key_id: row.hash_key_id.clone(),
70 created_at: datetime_opt(Some(row.created_at)),
71 expires_at: datetime_opt(row.expires_at),
72 last_used_at: datetime_opt(row.last_used_at),
73 revoked_at: datetime_opt(row.revoked_at),
7459 }
7560}
7661
Modified lib/auth/src/server/grpc/identity.rs View lib/auth/src/server/grpc/identity.rs
@@ -1,10 +1,6 @@
1use crate::models::users::{user_ssh_keys, users};
2use crate::protos::auth::common::v1::{
3 AuthMethod, Code, Credential, Principal, TokenScope, credential,
4};
1use crate::models::users::users;
52use crate::protos::auth::v1::identity_service_server::{IdentityService, IdentityServiceServer};
63use crate::protos::auth::v1::*;
7use crate::session::Session;
84use chrono::Utc;
95use rand::RngExt as _;
106use sea_orm::sea_query::{Expr, ExprTrait};
@@ -13,59 +9,13 @@
139
1410use crate::server::AuthSvc;
1511use crate::server::errors::{AuthError, AuthResult};
16use crate::server::grpc::{context, convert, error, service_token, store};
17use crate::server::http::access_token::types::AccessTokenAuthRequest;
18use crate::server::http::ssh_key::service;
19
20fn ensure_authorized(
21 principal: &Principal,
22 required_scopes: &[i32],
23 require_mfa: bool,
24 scopes_bounded: bool,
25) -> AuthResult<()> {
26 if !required_scopes.is_empty()
27 && scopes_bounded
28 && !store::scopes_cover(&principal.scopes, required_scopes)
29 {
30 let missing = required_scopes
31 .iter()
32 .copied()
33 .find(|required| *required != 0 && !principal.scopes.contains(required))
34 .and_then(|required| TokenScope::try_from(required).ok())
35 .and_then(store::scope_name)
36 .unwrap_or_default();
37 return Err(AuthError::ScopeInsufficient {
38 required: missing.to_owned(),
39 });
40 }
41 if require_mfa && !principal.mfa_verified {
42 return Err(AuthError::MfaRequired);
43 }
44 Ok(())
45}
12use crate::server::grpc::{context, error, store};
4613
4714#[derive(Clone)]
4815pub struct IdentityApi {
4916 svc: AuthSvc,
5017}
5118
52fn credential_ref(credential: Option<&Credential>) -> AuthResult<(&'static str, &str)> {
53 let kind = credential
54 .and_then(|value| value.kind.as_ref())
55 .ok_or(AuthError::MalformedRequest)?;
56 let (label, raw) = match kind {
57 credential::Kind::SessionKey(raw) => ("session", raw.as_str()),
58 credential::Kind::AccessToken(raw) => ("access_token", raw.as_str()),
59 credential::Kind::ServiceToken(raw) => ("service_token", raw.as_str()),
60 credential::Kind::SshFingerprint(raw) => ("ssh", raw.as_str()),
61 credential::Kind::SshPublicKey(raw) => ("ssh", raw.as_str()),
62 };
63 if raw.trim().is_empty() {
64 return Err(AuthError::MalformedRequest);
65 }
66 Ok((label, raw))
67}
68
6919impl IdentityApi {
7020 pub fn new(svc: AuthSvc) -> Self {
7121 Self { svc }
@@ -80,194 +30,6 @@
8030 let gate = context::Interceptor::new(self.svc.auth().rpc_key.clone());
8131 IdentityServiceServer::with_interceptor(self, gate)
8232 }
83
84 async fn ssh_user(&self, fingerprint: &str) -> AuthResult<users::Model> {
85 let record = user_ssh_keys::Entity::find()
86 .filter(user_ssh_keys::Column::Fingerprint.eq(fingerprint.trim()))
87 .one(self.svc.db().read())
88 .await?
89 .ok_or(AuthError::Unauthenticated)?;
90 if record.revoked_at.is_some()
91 || record
92 .expires_at
93 .is_some_and(|expires| expires <= Utc::now())
94 {
95 return Err(AuthError::Unauthenticated);
96 }
97 store::load_active_user(self.svc.db(), record.user_id).await
98 }
99
100 #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))]
101 async fn principal_of(
102 &self,
103 request_id: Option<&str>,
104 inner: &AuthenticateRequest,
105 ) -> AuthResult<AuthenticateResponse> {
106 // 仅作入参校验(空凭据在此拒绝);原 principal 缓存键已随写缓存一并移除。
107 let _ = credential_ref(inner.credential.as_ref())?;
108
109 let credential = inner
110 .credential
111 .as_ref()
112 .and_then(|value| value.kind.as_ref())
113 .ok_or(AuthError::MalformedRequest)?;
114 let expected_audience =
115 (!inner.expected_audience.is_empty()).then_some(inner.expected_audience.as_str());
116 let (method, user, session_id, access_key_id, granted, mfa_verified, service_id) =
117 match credential {
118 credential::Kind::SessionKey(key) => {
119 let Some(state) = store::load_session(self.svc.cache(), key).await? else {
120 return Err(AuthError::Unauthenticated);
121 };
122 let (user_id, version, session_id) = {
123 let session = Session::from_state(state);
124 (
125 session.user_id()?.ok_or(AuthError::Unauthenticated)?,
126 session
127 .security_version()?
128 .ok_or(AuthError::Unauthenticated)?,
129 session
130 .get::<String>("auth_session_id")?
131 .ok_or(AuthError::Unauthenticated)?,
132 )
133 };
134 let active = self
135 .svc
136 .check_session(user_id, version, &session_id, request_id)
137 .await
138 .map_err(|_| AuthError::Unauthenticated)?;
139 let _ = store::index_session_key(
140 self.svc.cache(),
141 user_id,
142 key,
143 std::time::Duration::from_secs(self.svc.session_max_age_secs()),
144 )
145 .await;
146 let user = store::load_active_user(self.svc.db(), user_id).await?;
147 (
148 AuthMethod::Session,
149 user,
150 session_id,
151 String::new(),
152 None,
153 active.mfa_verified,
154 String::new(),
155 )
156 }
157 credential::Kind::AccessToken(token) => {
158 if token.matches('.').count() == 2 {
159 let claims = self
160 .svc
161 .authenticate_jwt_access_token(token, expected_audience)
162 .await?;
163 let user = store::load_active_user(self.svc.db(), claims.user_id).await?;
164 let scopes = claims.scope.as_deref().map(store::required_scope_codes);
165 (
166 AuthMethod::AccessToken,
167 user,
168 claims.session_id.clone().unwrap_or_default(),
169 String::new(),
170 scopes,
171 claims.mfa_verified,
172 String::new(),
173 )
174 } else {
175 let response = self
176 .svc
177 .authenticate_access_token(
178 AccessTokenAuthRequest {
179 token: token.clone(),
180 },
181 request_id.unwrap_or_default().to_owned(),
182 )
183 .await?;
184 let user = store::load_active_user(self.svc.db(), response.user_id).await?;
185 (
186 AuthMethod::AccessToken,
187 user,
188 String::new(),
189 response.token_id.to_string(),
190 Some(store::required_scope_codes(&response.scope)),
191 false,
192 String::new(),
193 )
194 }
195 }
196 credential::Kind::ServiceToken(token) => {
197 let claims = service_token::verify(
198 &self.svc.jwt().jwks(),
199 token,
200 &service_token::ALL_AUDIENCES,
201 )?;
202 if self
203 .svc
204 .cache()
205 .exists(&format!("auth:svc-token:revoked:{}", claims.token_id))
206 .await?
207 {
208 return Err(AuthError::Unauthenticated);
209 }
210 let user = store::load_active_user(self.svc.db(), claims.user_id).await?;
211 store::ensure_security_version(&user, claims.security_version)?;
212 (
213 AuthMethod::ServiceToken,
214 user,
215 String::new(),
216 String::new(),
217 Some(claims.scopes.clone()),
218 false,
219 claims.service_id.clone(),
220 )
221 }
222 credential::Kind::SshPublicKey(raw) => {
223 let (_, fingerprint) = service::parse_public_key(raw)?;
224 let user = self.ssh_user(&fingerprint).await?;
225 (
226 AuthMethod::SshKey,
227 user,
228 String::new(),
229 String::new(),
230 None,
231 false,
232 String::new(),
233 )
234 }
235 credential::Kind::SshFingerprint(fingerprint) => {
236 let user = self.ssh_user(fingerprint).await?;
237 (
238 AuthMethod::SshKey,
239 user,
240 String::new(),
241 String::new(),
242 None,
243 false,
244 String::new(),
245 )
246 }
247 };
248
249 store::ensure_security_version(&user, inner.min_security_version)?;
250 let scope_bounded = granted.is_some();
251 let mut principal = store::principal(&user, method);
252 principal.session_id = session_id;
253 principal.access_key_id = access_key_id;
254 principal.scopes = granted.unwrap_or_default();
255 principal.scopes_bounded = scope_bounded;
256 principal.mfa_verified = mfa_verified;
257 principal.service_id = service_id;
258 principal.authenticated_at = Some(convert::now_timestamp());
259 ensure_authorized(
260 &principal,
261 &inner.required_scopes,
262 inner.require_mfa,
263 scope_bounded,
264 )?;
265 let response = AuthenticateResponse {
266 principal: Some(principal),
267 cached: false,
268 };
269 Ok(response)
270 }
27133}
27234
27335fn random_bot_id() -> i64 {
@@ -374,6 +136,19 @@
374136 .await?;
375137
376138 let security_version = store::load_user(db, user.id).await?.security_version;
139 if let Some(keep) = keep {
140 if let Err(err) = store::refresh_session_version(
141 self.svc.cache(),
142 user.id,
143 keep,
144 security_version,
145 self.svc.session_max_age_secs(),
146 )
147 .await
148 {
149 tracing::warn!(%err, user_id = user.id, "failed to refresh the kept session version");
150 }
151 }
377152 Ok(ChangePasswordResponse {
378153 changed: true,
379154 security_version,
@@ -386,28 +161,16 @@
386161impl IdentityService for IdentityApi {
387162 async fn authenticate(
388163 &self,
389 request: Request<AuthenticateRequest>,
164 _request: Request<AuthenticateRequest>,
390165 ) -> Result<Response<AuthenticateResponse>, Status> {
391 let meta = context::meta(&request).await?;
392 let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.v1.IdentityService");
393 let result = self
394 .authenticate_inner(meta.request_id(), request.into_inner())
395 .await;
396 timer.finish("Authenticate", &result);
397 result
166 Err(Status::unimplemented("authenticate is not available"))
398167 }
399168
400169 async fn verify_credential(
401170 &self,
402 request: Request<VerifyCredentialRequest>,
171 _request: Request<VerifyCredentialRequest>,
403172 ) -> Result<Response<VerifyCredentialResponse>, Status> {
404 let meta = context::meta(&request).await?;
405 let timer = crate::oltp::metrics::ActiveRpcGuard::new("auth.v1.IdentityService");
406 let result = self
407 .verify_credential_inner(meta.request_id(), request.into_inner())
408 .await;
409 timer.finish("VerifyCredential", &result);
410 result
173 Err(Status::unimplemented("verify_credential is not available"))
411174 }
412175
413176 async fn get_user_public(
@@ -478,69 +241,6 @@
478241
479242impl IdentityApi {
480243 #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))]
481 async fn authenticate_inner(
482 &self,
483 request_id: Option<&str>,
484 payload: AuthenticateRequest,
485 ) -> Result<Response<AuthenticateResponse>, Status> {
486 self.principal_of(request_id, &payload)
487 .await
488 .map(Response::new)
489 .map_err(|error_value| error::status(&error_value, request_id))
490 }
491
492 #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))]
493 async fn verify_credential_inner(
494 &self,
495 request_id: Option<&str>,
496 payload: VerifyCredentialRequest,
497 ) -> Result<Response<VerifyCredentialResponse>, Status> {
498 let auth_request = AuthenticateRequest {
499 credential: payload.credential,
500 required_scopes: payload.required_scopes,
501 min_security_version: payload.min_security_version,
502 require_mfa: payload.require_mfa,
503 expected_audience: payload.expected_audience,
504 };
505 match self.principal_of(request_id, &auth_request).await {
506 Ok(_) => Ok(Response::new(VerifyCredentialResponse {
507 valid: true,
508 reason: Code::Unspecified as i32,
509 current_security_version: 0,
510 retry_after_ms: 0,
511 })),
512 Err(reason) => {
513 // 数据库/缓存/邮件故障不是「凭据无效」:必须作为可重试错误上抛。
514 if error::is_infrastructure(&reason) {
515 return Err(error::status(&reason, request_id));
516 }
517 let code = error::code_of(&reason);
518 let credential_failure = matches!(
519 reason,
520 AuthError::MalformedRequest | AuthError::InvalidAccessToken
521 );
522 if error::tonic_code(code) == tonic::Code::InvalidArgument && !credential_failure {
523 return Err(error::status_of(
524 Code::InvalidArgument,
525 reason.message(),
526 request_id,
527 ));
528 }
529 let current_security_version = match &reason {
530 AuthError::SecurityVersionStale { current } => *current,
531 _ => 0,
532 };
533 Ok(Response::new(VerifyCredentialResponse {
534 valid: false,
535 reason: code as i32,
536 current_security_version,
537 retry_after_ms: 0,
538 }))
539 }
540 }
541 }
542
543 #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))]
544244 async fn get_user_public_inner(
545245 &self,
546246 request_id: Option<&str>,
@@ -555,9 +255,8 @@
555255 .map_err(|error_value| error::status(&error_value, request_id))?
556256 .ok_or_else(|| error::status(&AuthError::UserNotFound, request_id))?;
557257 let email = user.email.clone();
558 let avatar = self.svc.avatar_url(user.id).await.unwrap_or(None);
559258 Ok(Response::new(GetUserPublicResponse {
560 user: Some(store::user_public(&user, email, avatar)),
259 user: Some(store::user_public(&user, email, None)),
561260 }))
562261 }
563262
@@ -593,19 +292,9 @@
593292 .all(self.svc.db().read())
594293 .await
595294 .map_err(|error_value| error::status(&AuthError::from(error_value), request_id))?;
596 // 头像并发拉取:批内串行等待会把尾延迟累加到整体响应时间。
597 let avatars = futures_util::future::join_all(
598 rows.iter()
599 .map(|user| async move { self.svc.avatar_url(user.id).await.unwrap_or(None) }),
600 )
601 .await;
602295 let mut users = Vec::with_capacity(unique.len());
603 for (index, user) in rows.iter().enumerate() {
604 users.push(store::user_public(
605 user,
606 user.email.clone(),
607 avatars[index].clone(),
608 ));
296 for user in &rows {
297 users.push(store::user_public(user, user.email.clone(), None));
609298 }
610299 Ok(Response::new(BatchGetUserPublicResponse { users }))
611300 }
Modified lib/auth/src/server/grpc/mod.rs View lib/auth/src/server/grpc/mod.rs
@@ -1,4 +1,3 @@
1pub mod access_key;
21pub mod agreement;
32pub mod audit;
43pub mod authority;
@@ -6,115 +5,18 @@
65pub mod cache_maintenance;
76pub mod context;
87pub mod convert;
9pub mod email;
108pub mod error;
119pub mod identity;
12pub mod key_rotation;
1310pub mod mfa;
1411pub mod organization;
15pub mod service_token;
1612pub mod session;
1713pub mod ssh_key;
1814pub mod store;
19pub mod token;
20
21use std::net::SocketAddr;
2215
23use thiserror::Error;
24use tonic::transport::Server;
25use tonic::transport::server::Router;
26use tonic_health::{ServingStatus, server};
27use tonic_reflection::server::Builder as ReflectionBuilder;
2816use tower::layer::util::{Identity, Stack};
2917
30use crate::server::AuthSvc;
31use crate::server::grpc::access_key::AccessKeyApi;
32use crate::server::grpc::agreement::AgreementApi;
33use crate::server::grpc::audit::AuditApi;
34use crate::server::grpc::authority::AuthorityApi;
35use crate::server::grpc::authorization::AuthorizationApi;
36use crate::server::grpc::cache_maintenance::CacheMaintenanceApi;
37use crate::server::grpc::email::EmailApi;
38use crate::server::grpc::identity::IdentityApi;
39use crate::server::grpc::key_rotation::KeyRotationApi;
40use crate::server::grpc::mfa::MfaApi;
41use crate::server::grpc::organization::{OrganizationAdminApi, OrganizationApi};
42use crate::server::grpc::session::SessionApi;
43use crate::server::grpc::ssh_key::SshKeyApi;
44use crate::server::grpc::token::TokenApi;
18pub type GrpcRouter = tonic::transport::Server<Stack<crate::oltp::GrpcRequestLog, Identity>>;
4519
46pub const DEFAULT_MAX_MESSAGE_SIZE: usize = 4 * 1024 * 1024;
47pub const HEALTH_SERVICE: &str = "";
48
49pub const IMPLEMENTED_SERVICES: &[&str] = &[
50 "auth.v1.AccessKeyService",
51 "auth.v1.AgreementService",
52 "auth.v1.AuthorizationService",
53 "auth.v1.IdentityService",
54 "auth.v1.MfaService",
55 "auth.v1.OrganizationService",
56 "auth.v1.SessionService",
57 "auth.v1.SshKeyService",
58 "auth.v1.TokenService",
59 "auth.internal.v1.AuditService",
60 "auth.internal.v1.AuthorityService",
61 "auth.internal.v1.CacheMaintenanceService",
62 "auth.internal.v1.EmailService",
63 "auth.internal.v1.KeyRotationService",
64 "auth.internal.v1.OrganizationAdminService",
65];
66
67#[derive(Debug, Error)]
68pub enum GrpcError {
69 #[error("grpc transport error: {0}")]
70 Transport(#[from] tonic::transport::Error),
71 #[error("grpc reflection error: {0}")]
72 Reflection(String),
73}
74
75pub type GrpcResult<T> = Result<T, GrpcError>;
76
77pub type GrpcRouter = Router<Stack<crate::oltp::GrpcRequestLog, Identity>>;
78
79pub async fn router(svc: AuthSvc) -> GrpcResult<GrpcRouter> {
80 let (reporter, health) = server::health_reporter();
81 reporter
82 .set_service_status(HEALTH_SERVICE, ServingStatus::Serving)
83 .await;
84 for service in IMPLEMENTED_SERVICES {
85 reporter
86 .set_service_status(*service, ServingStatus::Serving)
87 .await;
88 }
89
90 let reflection = ReflectionBuilder::configure()
91 .register_encoded_file_descriptor_set(crate::protos::FILE_DESCRIPTOR_SET)
92 .build_v1()
93 .map_err(|error| GrpcError::Reflection(error.to_string()))?;
94
95 Ok(Server::builder()
96 .layer(crate::oltp::GrpcRequestLog)
97 .add_service(health)
98 .add_service(reflection)
99 .add_service(AgreementApi::new(svc.clone()).server())
100 .add_service(IdentityApi::new(svc.clone()).server())
101 .add_service(SessionApi::new(svc.clone()).server())
102 .add_service(TokenApi::new(svc.clone()).server())
103 .add_service(AccessKeyApi::new(svc.clone()).server())
104 .add_service(SshKeyApi::new(svc.clone()).server())
105 .add_service(MfaApi::new(svc.clone()).server())
106 .add_service(OrganizationApi::new(svc.clone()).server())
107 .add_service(OrganizationAdminApi::new(svc.clone()).server())
108 .add_service(AuthorizationApi::new(svc.clone()).server())
109 .add_service(KeyRotationApi::new(svc.clone()).server())
110 .add_service(AuthorityApi::new(svc.clone()).server())
111 .add_service(CacheMaintenanceApi::new(svc.clone()).server())
112 .add_service(EmailApi::new(svc.clone()).server())
113 .add_service(AuditApi::new(svc).server()))
114}
115
116pub async fn serve(addr: SocketAddr, svc: AuthSvc) -> GrpcResult<()> {
117 let router = router(svc).await?;
118 router.serve(addr).await?;
119 Ok(())
20pub fn router() -> GrpcRouter {
21 tonic::transport::Server::builder().layer(crate::oltp::GrpcRequestLog)
12022}
Modified lib/auth/src/server/grpc/session.rs View lib/auth/src/server/grpc/session.rs
@@ -195,6 +195,12 @@
195195 }));
196196 }
197197
198 if let Err(err) =
199 store::drop_session_by_id(self.svc.cache(), request.user_id, session_id).await
200 {
201 tracing::warn!(%err, %session_id, "failed to drop the revoked session state");
202 }
203
198204 if let Err(err) = store::audit(
199205 self.svc.db(),
200206 request.user_id,
Modified lib/auth/src/server/grpc/store.rs View lib/auth/src/server/grpc/store.rs
@@ -10,9 +10,23 @@
1010use crate::server::errors::{AuthError, AuthResult};
1111use crate::server::grpc::context::RequestMeta;
1212use crate::server::grpc::convert;
13use crate::server::http::access_token::types::ALLOWED_APP_SCOPES;
1413use crate::server::share::user as user_store;
1514
15pub const ALLOWED_APP_SCOPES: [&str; 12] = [
16 "user_name",
17 "ssh_key",
18 "access_key",
19 "assets",
20 "email",
21 "profile",
22 "theme",
23 "guild",
24 "event",
25 "avatar",
26 "repo_read",
27 "repo_write",
28];
29
1630pub const MAX_BATCH_USER_IDS: usize = 500;
1731
1832pub async fn find_user(
@@ -139,12 +153,16 @@
139153
140154/// 每个要求项都必须被授予,且任何一项为零值(未指定/未知)都视为不满足。
141155pub fn scopes_cover(granted: &[i32], required: &[i32]) -> bool {
142 crate::jwt::scope::valid(granted)
156 scope_valid(granted)
143157 && required.iter().all(|required| {
144 *required > 0 && crate::jwt::scope::valid(&[*required]) && granted.contains(required)
158 *required > 0 && scope_valid(&[*required]) && granted.contains(required)
145159 })
146160}
147161
162fn scope_valid(scopes: &[i32]) -> bool {
163 scopes.iter().all(|scope| *scope > 0)
164}
165
148166pub fn user_public(user: &users::Model, email: String, avatar_url: Option<String>) -> UserPublic {
149167 UserPublic {
150168 user_id: user.id,
@@ -256,17 +274,15 @@
256274 let key = crate::session::SessionKey::try_from(session_key.trim())
257275 .map_err(|_| AuthError::Unauthenticated)?;
258276 let store = crate::session::RedisStore::new(cache.clone());
259 Ok(store.load(&key).await?)
277 Ok(store.load(&key).await?.map(|(state, _)| state))
260278}
261279
262280pub fn now() -> DateTime<Utc> {
263281 Utc::now()
264282}
265283
266pub const SESSION_INDEX_PREFIX: &str = "auth:session:index:";
267
268pub fn session_index_key(user_id: i64) -> String {
269 format!("{SESSION_INDEX_PREFIX}{user_id}")
284pub fn session_index_key(cache: &crate::cache::Cache, user_id: i64) -> String {
285 cache.key(&["user:session", &user_id.to_string()])
270286}
271287
272288pub async fn index_session_key(
@@ -275,27 +291,89 @@
275291 session_key: &str,
276292 ttl: std::time::Duration,
277293) -> AuthResult<()> {
278 let index_key = session_index_key(user_id);
294 let index_key = session_index_key(cache, user_id);
279295 cache.sadd(&index_key, session_key).await?;
280296 cache.expire(&index_key, ttl).await?;
281297 Ok(())
282298}
283299
284300pub async fn session_index(cache: &crate::cache::Cache, user_id: i64) -> AuthResult<Vec<String>> {
285 Ok(cache.smembers(&session_index_key(user_id)).await?)
301 Ok(cache.smembers(&session_index_key(cache, user_id)).await?)
286302}
287303
288pub async fn drop_session_state(cache: &crate::cache::Cache, session_key: &str) -> AuthResult<()> {
304pub async fn drop_session_state(
305 cache: &crate::cache::Cache,
306 user_id: i64,
307 session_key: &str,
308) -> AuthResult<()> {
289309 use crate::session::{SessionKey, SessionStore};
290310
291311 let key =
292312 SessionKey::try_from(session_key.to_owned()).map_err(|_| AuthError::SessionNotFound)?;
293313 crate::session::RedisStore::new(cache.clone())
294 .delete(&key)
314 .delete(&key, Some(user_id))
295315 .await?;
296316 Ok(())
297317}
298318
319pub async fn refresh_session_version(
320 cache: &crate::cache::Cache,
321 user_id: i64,
322 session_id: Uuid,
323 version: i32,
324 ttl_secs: u64,
325) -> AuthResult<()> {
326 use crate::session::{RedisStore, Session, SessionKey, SessionStore};
327
328 let ttl = actix_web::cookie::time::Duration::seconds(ttl_secs.max(1) as i64);
329 let expected = session_id.to_string();
330 for key in session_index(cache, user_id).await? {
331 let Some(state) = load_session(cache, &key).await? else {
332 continue;
333 };
334 let diff = {
335 let session = Session::from_state(state);
336 if session.session_id().as_deref() != Some(expected.as_str()) {
337 continue;
338 }
339 session.set_security_version(version)?;
340 session.diff()
341 };
342 let key = SessionKey::try_from(key).map_err(|_| AuthError::SessionNotFound)?;
343 RedisStore::new(cache.clone())
344 .update(key, diff, &ttl, Some(user_id), Some(user_id))
345 .await?;
346 }
347 Ok(())
348}
349
350pub async fn drop_session_by_id(
351 cache: &crate::cache::Cache,
352 user_id: i64,
353 session_id: Uuid,
354) -> AuthResult<()> {
355 use crate::session::Session;
356
357 let expected = session_id.to_string();
358 for key in session_index(cache, user_id).await? {
359 let Some(state) = load_session(cache, &key).await? else {
360 continue;
361 };
362 let matches = Session::from_state(state).session_id().as_deref() == Some(expected.as_str());
363 if !matches {
364 continue;
365 }
366 if let Err(error) = drop_session_state(cache, user_id, &key).await {
367 tracing::warn!(user_id, %error, "session state drop failed");
368 continue;
369 }
370 if let Err(error) = cache.srem(&session_index_key(cache, user_id), &key).await {
371 tracing::warn!(user_id, %error, "session index cleanup failed");
372 }
373 }
374 Ok(())
375}
376
299377pub async fn drop_user_session_states(
300378 cache: &crate::cache::Cache,
301379 user_id: i64,
@@ -304,7 +382,7 @@
304382 use crate::session::Session;
305383 use futures_util::StreamExt;
306384
307 let index_key = session_index_key(user_id);
385 let index_key = session_index_key(cache, user_id);
308386 let dropped = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
309387 futures_util::stream::iter(session_index(cache, user_id).await?)
310388 .map(|key| {
@@ -328,7 +406,7 @@
328406 if !bound {
329407 return;
330408 }
331 if let Err(error) = drop_session_state(&cache, &key).await {
409 if let Err(error) = drop_session_state(&cache, user_id, &key).await {
332410 tracing::warn!(user_id, %error, "session state drop failed");
333411 return;
334412 }
Modified lib/auth/src/server/http/mod.rs View lib/auth/src/server/http/mod.rs
@@ -1,8 +1,4 @@
1pub mod access_token;
2pub mod app_token;
31pub mod challenge;
4pub mod cors;
5pub mod health;
62pub mod login;
73pub mod logout;
84pub mod me;
@@ -11,6 +7,4 @@
117pub mod password_reset;
128pub mod register;
139pub mod router;
14pub mod socket_ticket;
1510pub mod ssh_key;
16pub mod token;
Modified lib/auth/src/server/http/openapi.rs View lib/auth/src/server/http/openapi.rs
@@ -4,7 +4,7 @@
44#[openapi(
55 info(
66 title = "auth",
7 description = "认证与账号服务:工作量证明、口令与验证码登录、二次验证、注册、口令重置、访问令牌与 SSH 公钥。",
7 description = "认证与账号服务:工作量证明、口令与验证码登录、二次验证、注册、口令重置与 SSH 公钥。",
88 version = env!("CARGO_PKG_VERSION")
99 ),
1010 paths(
@@ -19,16 +19,9 @@
1919 crate::server::http::register::handler::register,
2020 crate::server::http::password_reset::handler::password_reset_request,
2121 crate::server::http::password_reset::handler::password_reset,
22 crate::server::http::access_token::handler::access_token_list,
23 crate::server::http::access_token::handler::access_token_create,
24 crate::server::http::access_token::handler::access_token_revoke,
25 crate::server::http::app_token::handler::app_token_issue,
26 crate::server::http::token::handler::token_issue,
2722 crate::server::http::ssh_key::handler::ssh_key_list,
2823 crate::server::http::ssh_key::handler::ssh_key_add,
2924 crate::server::http::ssh_key::handler::ssh_key_delete,
30 crate::server::http::health::health,
31 crate::server::http::health::ready,
3225 crate::server::http::organization::handler::create_organization,
3326 crate::server::http::organization::handler::list_organizations,
3427 crate::server::http::organization::handler::get_organization,
@@ -69,9 +62,6 @@
6962 crate::server::http::organization::handler::redeem_invite,
7063 ),
7164 components(schemas(
72 crate::server::http::health::HealthResponse,
73 crate::server::http::health::ReadinessResponse,
74 crate::server::http::health::ComponentCheck,
7565 crate::server::http::organization::types::OrganizationSummary,
7666 crate::server::http::organization::types::MemberSummary,
7767 crate::server::http::organization::types::RoleSummary,
Modified lib/auth/src/server/http/router.rs View lib/auth/src/server/http/router.rs
@@ -1,23 +1,10 @@
11use actix_web::web;
22
33pub fn configure(cfg: &mut web::ServiceConfig) {
4 cfg.route(
5 "/health",
6 web::get().to(crate::server::http::health::health),
7 );
8 cfg.route("/ready", web::get().to(crate::server::http::health::ready));
9 cfg.service(web::scope("/api/v1").configure(api));
4 cfg.service(web::scope("/api/v1").configure(configure_api));
105}
116
12fn api(cfg: &mut web::ServiceConfig) {
13 cfg.route(
14 "/oauth/socket-tickets",
15 web::post().to(crate::server::http::socket_ticket::issue),
16 );
17 cfg.route(
18 "/oauth/socket-tickets",
19 web::method(actix_web::http::Method::OPTIONS).to(crate::server::http::cors::preflight),
20 );
7pub fn configure_api(cfg: &mut web::ServiceConfig) {
218 cfg.route(
229 "/oauth/challenge",
2310 web::post().to(crate::server::http::challenge::handler::challenge),
@@ -61,30 +48,6 @@
6148 cfg.route(
6249 "/oauth/password/reset/confirm",
6350 web::post().to(crate::server::http::password_reset::handler::password_reset),
64 );
65 cfg.route(
66 "/oauth/access-tokens",
67 web::get().to(crate::server::http::access_token::handler::access_token_list),
68 );
69 cfg.route(
70 "/oauth/access-tokens",
71 web::post().to(crate::server::http::access_token::handler::access_token_create),
72 );
73 cfg.route(
74 "/oauth/access-tokens/{token_id}",
75 web::delete().to(crate::server::http::access_token::handler::access_token_revoke),
76 );
77 cfg.route(
78 "/oauth/access-tokens/{token_id}/rotate",
79 web::post().to(crate::server::http::access_token::handler::access_token_rotate),
80 );
81 cfg.route(
82 "/oauth/app-tokens",
83 web::post().to(crate::server::http::app_token::handler::app_token_issue),
84 );
85 cfg.route(
86 "/oauth/session/token",
87 web::post().to(crate::server::http::token::handler::token_issue),
8851 );
8952 cfg.route(
9053 "/oauth/ssh-keys",
Modified lib/auth/src/server/share/mod.rs View lib/auth/src/server/share/mod.rs
@@ -1,7 +1,6 @@
11pub mod audit;
22pub mod codes;
33pub mod crypto;
4pub mod issue;
54pub mod mfa;
65pub mod net;
76pub mod password;
Modified lib/auth/src/server/account_lifecycle.rs View lib/auth/src/server/account_lifecycle.rs
@@ -5,9 +5,7 @@
55};
66
77use crate::models::space::{OrganizationStatus, organization_members, organizations};
8use crate::models::users::{
9 user_access_tokens, user_mfa, user_mfa_backup_codes, user_sessions, user_ssh_keys, users,
10};
8use crate::models::users::{user_mfa, user_mfa_backup_codes, user_sessions, user_ssh_keys, users};
119use crate::server::AuthSvc;
1210use crate::server::errors::{AuthError, AuthResult};
1311
@@ -119,6 +117,11 @@
119117 active.version = Set(version.saturating_add(1));
120118 active.update(&txn).await?;
121119 txn.commit().await?;
120 if let Err(error) =
121 crate::server::grpc::store::drop_user_session_states(self.cache(), user_id, None).await
122 {
123 tracing::warn!(%error, %user_id, "failed to drop purged account sessions");
124 }
122125 tracing::info!(%user_id, %request_id, "account purged");
123126 Ok(())
124127 }
@@ -133,12 +136,6 @@
133136 .col_expr(user_sessions::Column::RevokedAt, Expr::value(now))
134137 .filter(user_sessions::Column::UserId.eq(user_id))
135138 .filter(user_sessions::Column::RevokedAt.is_null())
136 .exec(txn)
137 .await?;
138 user_access_tokens::Entity::update_many()
139 .col_expr(user_access_tokens::Column::RevokedAt, Expr::value(now))
140 .filter(user_access_tokens::Column::UserId.eq(user_id))
141 .filter(user_access_tokens::Column::RevokedAt.is_null())
142139 .exec(txn)
143140 .await?;
144141 user_ssh_keys::Entity::update_many()
Modified lib/auth/src/server/auth_svc.rs View lib/auth/src/server/auth_svc.rs
@@ -1,12 +1,10 @@
11use std::sync::Arc;
22
33use crate::cache::Cache;
4use crate::config::app::AppRegistry;
54use crate::config::auth::AuthConfig;
6use crate::config::jwt::JwtConfig;
75use crate::config::pow::PowConfig;
86use crate::db::DbConn;
9use crate::email::EmailService;
7use crate::email::MailPublisher;
108use crate::events::{EventHandler, Labels, label};
119use serde::Serialize;
1210
@@ -39,13 +37,10 @@
3937pub struct AuthSvc {
4038 db: DbConn,
4139 cache: Cache,
42 email: Option<EmailService>,
40 mail: Option<MailPublisher>,
4341 events: Option<EventHandler>,
4442 auth: Arc<AuthConfig>,
4543 pow: Arc<PowConfig>,
46 apps: Arc<AppRegistry>,
47 signing: Arc<crate::server::keys::SigningState>,
48 jwt: crate::jwt::JwtState,
4944 session_max_age_secs: u64,
5045}
5146
@@ -54,44 +49,21 @@
5449 pub async fn new(
5550 db: DbConn,
5651 cache: Cache,
57 email: Option<EmailService>,
52 mail: Option<MailPublisher>,
5853 events: Option<EventHandler>,
5954 auth: AuthConfig,
6055 pow: PowConfig,
61 apps: AppRegistry,
62 jwt_config: JwtConfig,
6356 session_max_age_secs: u64,
6457 ) -> AuthResult<Self> {
65 let rpc_key = auth.rpc_key.trim();
66 if !rpc_key.is_empty() && rpc_key.len() < 32 {
67 return Err(AuthError::Unavailable(
68 "auth.rpc_key must be at least 32 characters".to_owned(),
69 ));
70 }
71 if rpc_key.starts_with("REPLACE_WITH_") {
72 return Err(AuthError::Unavailable(
73 "auth.rpc_key is still set to the placeholder value".to_owned(),
74 ));
75 }
76 let signing = crate::server::keys::SigningState::from_config(&jwt_config)?;
77 let jwt_state = crate::jwt::JwtState::load(&jwt_config)
78 .await
79 .map_err(|error| AuthError::Unavailable(error.to_string()))?;
80 let svc = Self {
58 Ok(Self {
8159 db,
8260 cache,
83 email,
61 mail,
8462 events,
8563 auth: Arc::new(auth),
8664 pow: Arc::new(pow),
87 apps: Arc::new(apps),
88 signing: Arc::new(signing),
89 jwt: jwt_state,
9065 session_max_age_secs: session_max_age_secs.max(1),
91 };
92 crate::jwt::authority::install(Arc::new(svc.clone()))
93 .map_err(|error| AuthError::Unavailable(error.to_string()))?;
94 Ok(svc)
66 })
9567 }
9668
9769 pub fn db(&self) -> &DbConn {
@@ -102,8 +74,8 @@
10274 &self.cache
10375 }
10476
105 pub fn email(&self) -> Option<&EmailService> {
106 self.email.as_ref()
77 pub fn mail(&self) -> Option<&MailPublisher> {
78 self.mail.as_ref()
10779 }
10880
10981 pub fn auth(&self) -> &AuthConfig {
@@ -114,18 +86,6 @@
11486 &self.pow
11587 }
11688
117 pub fn apps(&self) -> &AppRegistry {
118 &self.apps
119 }
120
121 pub fn signing(&self) -> &crate::server::keys::SigningState {
122 &self.signing
123 }
124
125 pub fn jwt(&self) -> &crate::jwt::JwtState {
126 &self.jwt
127 }
128
12989 pub fn events(&self) -> Option<&EventHandler> {
13090 self.events.as_ref()
13191 }
@@ -259,17 +219,13 @@
259219 html: &str,
260220 request_id: Option<&str>,
261221 ) -> AuthResult<()> {
262 match self.email.as_ref() {
263 Some(service) => {
264 service
265 .enqueue_mail(to, subject, text, html, request_id)
266 .await?;
267 Ok(())
268 }
269 None => {
270 tracing::warn!(to, subject, "auth email service is not configured");
271 Ok(())
272 }
273 }
222 let Some(mail) = self.mail.as_ref() else {
223 return Err(AuthError::Unavailable(
224 "mail publisher is not configured".to_owned(),
225 ));
226 };
227 mail.send_mail(to, subject, text, html, request_id)
228 .await
229 .map_err(|error| AuthError::Internal(error.to_string()))
274230 }
275231}
Modified lib/auth/src/server/authority.rs View lib/auth/src/server/authority.rs
@@ -1,8 +1,5 @@
1use crate::jwt::authority::{AccessRecord, Authority, UserRecord};
2use crate::jwt::{AppClaims, JwtError};
31use crate::models::users::{user_sessions, users};
42use crate::session::Session;
5use async_trait::async_trait;
63use chrono::{DateTime, Duration, Utc};
74use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, Set};
85use uuid::Uuid;
@@ -10,8 +7,6 @@
107use crate::server::share::{crypto, user as store};
118use crate::server::{AuthError, AuthResult, AuthSvc};
129
13const MAX_BATCH_PROFILES: usize = store::MAX_BATCH_USER_IDS;
14
1510/// 会话凭据摘要的算法标识,落在 `user_sessions.hash_key_id`。
1611pub const SESSION_HASH_KEY_ID: &str = "logical-session-v1";
1712
@@ -20,34 +15,14 @@
2015 crypto::sha256_hex(id.to_string().as_bytes())
2116}
2217
23pub(crate) fn record(user: users::Model) -> UserRecord {
24 UserRecord {
25 id: user.id,
26 username: user.username,
27 lowername: user.lowername,
28 displayname: user.displayname,
29 birth_date: user.birth_date,
30 email: user.email,
31 public_show_email: user.public_show_email,
32 email_verified_at: user.email_verified_at,
33 allow_use: user.allow_use,
34 forbid_login: user.forbid_login,
35 forbid_login_reason: user.forbid_login_reason,
36 endtime_forbid_login_at: user.endtime_forbid_login_at,
37 is_admin: user.is_admin,
38 bot: user.bot,
39 system: user.system,
40 created_at: user.created_at,
41 updated_at: user.updated_at,
42 security_version: user.security_version,
43 version: user.version,
44 deleted_at: user.deleted_at,
45 }
46}
47
48fn invalid(error: impl std::fmt::Display) -> JwtError {
49 tracing::debug!(%error, "authority check failed");
50 JwtError::Revoked
18fn account_active(user: &users::Model) -> bool {
19 user.id > 0
20 && user.deleted_at.is_none()
21 && user.allow_use
22 && (!user.forbid_login
23 || user
24 .endtime_forbid_login_at
25 .is_none_or(|until| until > Utc::now()))
5126}
5227
5328impl AuthSvc {
@@ -93,42 +68,39 @@
9368 version: i32,
9469 session_id: &str,
9570 request_id: Option<&str>,
96 ) -> Result<AccessRecord, JwtError> {
71 ) -> AuthResult<(users::Model, bool)> {
9772 // security_version 判定一律读主库:副本延迟会让刚吊销的旧版本短暂放行。
9873 let user = users::Entity::find_by_id(user_id)
9974 .one(self.db().write())
100 .await
101 .map_err(invalid)?
102 .ok_or(JwtError::Revoked)?;
103 let user = record(user);
104 user.require_active()?;
75 .await?
76 .ok_or(AuthError::Unauthenticated)?;
77 if !account_active(&user) {
78 return Err(AuthError::Unauthenticated);
79 }
10580 if version <= 0 || version != user.security_version {
106 return Err(JwtError::Revoked);
81 return Err(AuthError::Unauthenticated);
10782 }
108 let id = Uuid::parse_str(session_id).map_err(invalid)?;
83 let id = Uuid::parse_str(session_id).map_err(|_| AuthError::Unauthenticated)?;
10984 let row = user_sessions::Entity::find_by_id(id)
11085 .one(self.db().write())
111 .await
112 .map_err(invalid)?
113 .ok_or(JwtError::Revoked)?;
86 .await?
87 .ok_or(AuthError::Unauthenticated)?;
11488 // `token_hash` 是会话凭据的摘要:只有拿着明文会话 ID 的一方能算出来。
11589 // 不校验它,单有数据库读权限的人就能直接拿行里的 ID 复会话。
11690 if !crate::server::share::crypto::constant_time_eq(
11791 row.token_hash.as_bytes(),
11892 session_token_hash(id).as_bytes(),
11993 ) {
120 return Err(JwtError::Revoked);
94 return Err(AuthError::Unauthenticated);
12195 }
12296 if row.user_id != user_id
12397 || row.security_version != version
12498 || row.revoked_at.is_some()
12599 || row.expires_at <= Utc::now()
126100 {
127 return Err(JwtError::Revoked);
101 return Err(AuthError::Unauthenticated);
128102 }
129 let mut record = AccessRecord::from(&user);
130 record.mfa_verified = row.mfa_verified_at.is_some();
131 Ok(record)
103 Ok((user, row.mfa_verified_at.is_some()))
132104 }
133105
134106 #[tracing::instrument(skip_all, fields(request_id = %request_id.unwrap_or_default()))]
@@ -144,9 +116,9 @@
144116 let id = session
145117 .get::<String>("auth_session_id")?
146118 .ok_or(AuthError::Unauthenticated)?;
147 self.check_session(user_id, version, &id, request_id)
148 .await
149 .map_err(|_| AuthError::Unauthenticated)?;
119 let _ = self
120 .check_session(user_id, version, &id, request_id)
121 .await?;
150122 Ok(user_id)
151123 }
152124
@@ -168,33 +140,15 @@
168140 .await?;
169141 Ok(row.map(|row| row.created_at))
170142 }
171}
172143
173#[async_trait]
174impl Authority for AuthSvc {
175 async fn validate(&self, _: &str, claims: &AppClaims) -> Result<AccessRecord, JwtError> {
176 self.check_session(
177 claims.user_id,
178 claims.security_version,
179 claims.session_id.as_deref().ok_or(JwtError::Revoked)?,
180 crate::envelope::context::request_id().as_deref(),
181 )
182 .await
183 }
184
185 async fn user(&self, user_id: i64) -> Result<UserRecord, JwtError> {
186 let user = users::Entity::find_by_id(user_id)
144 pub async fn user(&self, user_id: i64) -> AuthResult<users::Model> {
145 users::Entity::find_by_id(user_id)
187146 .one(self.db().read())
188 .await
189 .map_err(invalid)?
190 .ok_or(JwtError::Revoked)?;
191 Ok(record(user))
147 .await?
148 .ok_or(AuthError::UserNotFound)
192149 }
193150
194 async fn users(&self, user_ids: &[i64]) -> Result<Vec<UserRecord>, JwtError> {
195 if user_ids.len() > MAX_BATCH_PROFILES {
196 return Err(JwtError::Malformed);
197 }
151 pub async fn users(&self, user_ids: &[i64]) -> AuthResult<Vec<users::Model>> {
198152 let mut unique: Vec<i64> = Vec::with_capacity(user_ids.len());
199153 for user_id in user_ids.iter().copied().filter(|user_id| *user_id > 0) {
200154 if !unique.contains(&user_id) {
@@ -204,43 +158,27 @@
204158 if unique.is_empty() {
205159 return Ok(Vec::new());
206160 }
207 let rows = users::Entity::find()
161 Ok(users::Entity::find()
208162 .filter(users::Column::Id.is_in(unique))
209163 .all(self.db().read())
210 .await
211 .map_err(invalid)?;
212 Ok(rows.into_iter().map(record).collect())
213 }
214
215 async fn session(
216 &self,
217 user_id: i64,
218 version: i32,
219 session_id: &str,
220 ) -> Result<AccessRecord, JwtError> {
221 self.check_session(
222 user_id,
223 version,
224 session_id,
225 crate::envelope::context::request_id().as_deref(),
226 )
227 .await
164 .await?)
228165 }
229166
230 async fn update(&self, user_id: i64, patch: serde_json::Value) -> Result<UserRecord, JwtError> {
231 let user = store::load_active_user(self.db(), user_id)
232 .await
233 .map_err(invalid)?;
167 pub async fn update(&self, user_id: i64, patch: serde_json::Value) -> AuthResult<users::Model> {
168 let user = store::load_active_user(self.db(), user_id).await?;
234169 let version = patch
235170 .get("version")
236 .and_then(|v| v.as_i64())
237 .ok_or(JwtError::Malformed)?;
171 .and_then(|value| value.as_i64())
172 .ok_or(AuthError::MalformedRequest)?;
238173 if i64::from(user.version) != version {
239 return Err(JwtError::Malformed);
174 return Err(AuthError::VersionMismatch {
175 current: user.version,
176 });
240177 }
241178 let mut active = user.clone().into_active_model();
242 if let Some(name) = patch.get("username").and_then(|v| v.as_str()) {
243 let name = crate::server::share::validate::validate_username(name).map_err(invalid)?;
179 if let Some(name) = patch.get("username").and_then(|value| value.as_str()) {
180 let name = crate::server::share::validate::validate_username(name)
181 .map_err(|_| AuthError::InvalidUsername)?;
244182 active.lowername = Set(name.to_lowercase());
245183 active.username = Set(name);
246184 }
@@ -248,27 +186,33 @@
248186 active.displayname = Set(match value.as_str() {
249187 Some(value) => Some(
250188 crate::server::share::validate::validate_display_name(value)
251 .map_err(invalid)?,
189 .map_err(|_| AuthError::InvalidDisplayname)?,
252190 ),
253191 None if value.is_null() => None,
254 _ => return Err(JwtError::Malformed),
192 _ => return Err(AuthError::MalformedRequest),
255193 });
256194 }
257195 if let Some(value) = patch.get("public_show_email") {
258 active.public_show_email = Set(value.as_bool().ok_or(JwtError::Malformed)?);
196 active.public_show_email = Set(value.as_bool().ok_or(AuthError::MalformedRequest)?);
259197 }
260 active.version = Set(user.version.checked_add(1).ok_or(JwtError::Malformed)?);
198 active.version = Set(user
199 .version
200 .checked_add(1)
201 .ok_or(AuthError::VersionMismatch {
202 current: user.version,
203 })?);
261204 active.updated_at = Set(Utc::now());
262205 let updated = users::Entity::update_many()
263206 .set(active)
264207 .filter(users::Column::Id.eq(user_id))
265208 .filter(users::Column::Version.eq(user.version))
266209 .exec_with_returning(self.db().write())
267 .await
268 .map_err(invalid)?
210 .await?
269211 .pop()
270 .ok_or(JwtError::Malformed)?;
212 .ok_or(AuthError::VersionMismatch {
213 current: user.version,
214 })?;
271215 crate::server::http::me::service::invalidate_me_cache(self.cache(), user_id).await;
272 Ok(record(updated))
216 Ok(updated)
273217 }
274218}
Modified lib/auth/src/server/migrate.rs View lib/auth/src/server/migrate.rs
@@ -9,6 +9,8 @@
99 "DROP TABLE IF EXISTS oauth_refresh_tokens",
1010 "DROP TABLE IF EXISTS oauth_consents",
1111 "DROP TABLE IF EXISTS oauth_clients",
12 "DROP TABLE IF EXISTS user_access_tokens",
13 "DROP TABLE IF EXISTS service_token_revocations",
1214 ])
1315 .after([
1416 // 邮箱查询一律走 lower(email),表达式唯一索引把大小写变体挡在数据库层。
Modified lib/auth/src/server/mod.rs View lib/auth/src/server/mod.rs
@@ -3,7 +3,6 @@
33pub mod errors;
44pub mod grpc;
55pub mod http;
6pub mod keys;
76pub mod migrate;
87pub mod pow;
98pub mod share;
@@ -15,6 +14,5 @@
1514pub use auth_svc::AuthSvc;
1615
1716pub use http::{
18 access_token, app_token, challenge, health, login, logout, me, openapi, organization,
19 password_reset, register, router, socket_ticket, ssh_key, token,
17 challenge, login, logout, me, openapi, organization, password_reset, register, router, ssh_key,
2018};
Modified lib/auth/src/lib.rs View lib/auth/src/lib.rs
@@ -6,8 +6,8 @@
66
77#![recursion_limit = "512"]
88
9pub mod assets;
910pub mod client;
10pub mod runtime;
1111pub mod server;
1212
1313pub use cache;
@@ -22,4 +22,3 @@
2222pub use proto as protos;
2323pub use queue;
2424pub use session;
25pub use token as jwt;
Modified lib/auth/Cargo.toml View lib/auth/Cargo.toml
@@ -19,7 +19,6 @@
1919proto = { workspace = true }
2020queue = { workspace = true }
2121session = { workspace = true }
22token = { workspace = true }
2322tokio-stream = { version = "0.1", features = ["net"] }
2423actix-web = "4.16.0"
2524aes-gcm = "0.11.1"
@@ -29,7 +28,6 @@
2928base64 = "0.23.1"
3029bytes = "1.12.1"
3130chrono = { version = "0.4.45", features = ["serde"] }
32ed25519-dalek = "2.2.0"
3331figment = { version = "0.10", default-features = false, features = [
3432 "toml",
3533 "json",
@@ -90,9 +88,7 @@
9088tokio = { version = "1.53.3", features = ["full"] }
9189tokio-util = "0.7.20"
9290tonic = { version = "0.14.6", features = ["tls-ring"] }
93tonic-health = "0.14.6"
9491tonic-prost = "0.14.6"
95tonic-reflection = "0.14.6"
9692tower = "0.5.3"
9793tracing = "0.1.44"
9894tracing-opentelemetry = "0.33.0"
Modified lib/chat/src/handlers/attachments.rs View lib/chat/src/handlers/attachments.rs
@@ -1,9 +1,9 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, Query, Response, State};
24use export::rest::{AppResponseData, NoData};
35use imaging::layout::ImageVariant;
46use serde::Deserialize;
5use token::Session;
6use token::share::session;
77use utoipa::{IntoParams, ToSchema};
88use uuid::Uuid;
99
Modified lib/chat/src/handlers/bookmarks.rs View lib/chat/src/handlers/bookmarks.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, Query, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/dms.rs View lib/chat/src/handlers/dms.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, Query, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/drafts.rs View lib/chat/src/handlers/drafts.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, Query, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/emojis.rs View lib/chat/src/handlers/emojis.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, Response, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/messages.rs View lib/chat/src/handlers/messages.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, Query, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/presence.rs View lib/chat/src/handlers/presence.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55
66use crate::errors::ApiError;
77use crate::services::presence::{PresenceDto, SetPresenceCmd};
Modified lib/chat/src/handlers/reactions.rs View lib/chat/src/handlers/reactions.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/read_state.rs View lib/chat/src/handlers/read_state.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/reminders.rs View lib/chat/src/handlers/reminders.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/room_admin.rs View lib/chat/src/handlers/room_admin.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, Query, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/rooms.rs View lib/chat/src/handlers/rooms.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, Query, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/settings.rs View lib/chat/src/handlers/settings.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/threads.rs View lib/chat/src/handlers/threads.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, Query, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/handlers/voice.rs View lib/chat/src/handlers/voice.rs
@@ -1,10 +1,10 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{HeaderMap, Json, Path, Query, State};
24use export::rest::{AppResponseData, NoData};
35use livekit_api::access_token::TokenVerifier;
46use livekit_api::webhooks::WebhookReceiver;
57use serde::Deserialize;
6use token::Session;
7use token::share::session;
88use uuid::Uuid;
99
1010use crate::errors::ApiError;
Modified lib/chat/src/handlers/webhooks.rs View lib/chat/src/handlers/webhooks.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, Path, Query, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/chat/src/services/service.rs View lib/chat/src/services/service.rs
@@ -80,12 +80,12 @@
8080 text: &str,
8181 html: &str,
8282 ) -> Result<(), ChatError> {
83 let Some(client) = self.state.email_rpc.as_ref() else {
83 let Some(client) = self.state.mail.as_ref() else {
8484 tracing::warn!(to, "email rpc disabled; skipping chat mail");
8585 return Ok(());
8686 };
8787 client
88 .send_mail(to, subject, text, html)
88 .send_mail(to, subject, text, html, None)
8989 .await
9090 .map_err(|error| ChatError::Internal(format!("email send failed: {error}")))
9191 }
Modified lib/chat/src/share/guard.rs View lib/chat/src/share/guard.rs
@@ -1 +1 @@
1pub use token::share::guard::session_guard as chat_guard;
1pub use ::session::share::guard::session_guard as chat_guard;
Modified lib/chat/Cargo.toml View lib/chat/Cargo.toml
@@ -12,7 +12,7 @@
1212models = { workspace = true }
1313queue = { workspace = true }
1414social = { workspace = true }
15token = { workspace = true }
15session = { workspace = true }
1616space = { workspace = true }
1717storage = { workspace = true }
1818actix-web = { workspace = true }
Modified lib/config/src/app/mod.rs View lib/config/src/app/mod.rs
@@ -1,7 +1,5 @@
11mod app_config;
2mod app_registry;
32mod key;
43
54pub use app_config::AppConfig;
6pub use app_registry::{AppRegistration, AppRegistry};
75pub use key::{AppKeyAlgorithm, AppKeyConfig, SecretMaterial};
Modified lib/config/src/auth.rs View lib/config/src/auth.rs
@@ -38,7 +38,6 @@
3838#[serde(default)]
3939pub struct AuthConfig {
4040 pub rpc_key: String,
41 pub socket_origins: Vec<String>,
4241 pub password_policy: PasswordPolicy,
4342 pub verify_code_ttl_secs: u64,
4443 pub verify_code_len: usize,
@@ -55,7 +54,6 @@
5554 fn default() -> Self {
5655 Self {
5756 rpc_key: String::new(),
58 socket_origins: Vec::new(),
5957 password_policy: PasswordPolicy::default(),
6058 verify_code_ttl_secs: 600,
6159 verify_code_len: 6,
Modified lib/config/src/email.rs View lib/config/src/email.rs
@@ -22,6 +22,24 @@
2222 pub enqueue_timeout_ms: u64,
2323 pub retry_delay_ms: u64,
2424 pub max_attempts: u32,
25 #[serde(default = "default_mail_topic")]
26 pub mail_topic: String,
27 #[serde(default = "default_consumer_group")]
28 pub consumer_group: String,
29 #[serde(default = "default_max_reconsume")]
30 pub max_reconsume: u32,
31}
32
33fn default_mail_topic() -> String {
34 "gitdata-mail".to_owned()
35}
36
37fn default_consumer_group() -> String {
38 "gitdata-mail".to_owned()
39}
40
41fn default_max_reconsume() -> u32 {
42 5
2543}
2644
2745impl EmailPoolConfig {
@@ -38,6 +56,15 @@
3856 if !(1..=100).contains(&self.max_attempts) {
3957 return Err("email.max_attempts must be between 1 and 100".to_string());
4058 }
59 if self.mail_topic.trim().is_empty() {
60 return Err("email.mail_topic must not be blank".to_string());
61 }
62 if self.consumer_group.trim().is_empty() {
63 return Err("email.consumer_group must not be blank".to_string());
64 }
65 if !(1..=16).contains(&self.max_reconsume) {
66 return Err("email.max_reconsume must be between 1 and 16".to_string());
67 }
4168 Ok(())
4269 }
4370
@@ -58,6 +85,9 @@
5885 enqueue_timeout_ms: 5_000,
5986 retry_delay_ms: 2_000,
6087 max_attempts: 3,
88 mail_topic: default_mail_topic(),
89 consumer_group: default_consumer_group(),
90 max_reconsume: default_max_reconsume(),
6191 }
6292 }
6393}
Modified lib/config/src/lib.rs View lib/config/src/lib.rs
@@ -7,7 +7,6 @@
77pub mod git;
88pub mod internal;
99pub mod issues;
10pub mod jwt;
1110pub mod livekit;
1211pub mod pow;
1312pub mod push;
@@ -40,7 +39,6 @@
4039use crate::git::GitConfig;
4140use crate::internal::InternalConfig;
4241use crate::issues::IssueConfig;
43use crate::jwt::JwtConfig;
4442use crate::livekit::LiveKitConfig;
4543use crate::pow::PowConfig;
4644use crate::push::PushConfig;
@@ -69,7 +67,6 @@
6967 pub issues: IssueConfig,
7068 pub livekit: LiveKitConfig,
7169 pub git: GitConfig,
72 pub jwt: JwtConfig,
7370 pub session: SessionConfig,
7471 pub email: EmailConfig,
7572 pub apihost: ApihostConfig,
Modified lib/config/src/scoped.rs View lib/config/src/scoped.rs
@@ -9,7 +9,6 @@
99use crate::error::ConfigError;
1010use crate::git::GitConfig;
1111use crate::issues::IssueConfig;
12use crate::jwt::JwtConfig;
1312use crate::livekit::LiveKitConfig;
1413use crate::pow::PowConfig;
1514use crate::push::PushConfig;
@@ -61,7 +60,6 @@
6160 pub issues: IssueConfig,
6261 pub git: GitConfig,
6362 pub email: EmailConfig,
64 pub jwt: JwtConfig,
6563 pub session: SessionConfig,
6664 pub apihost: ApihostSection,
6765 pub livekit: LiveKitConfig,
@@ -85,7 +83,6 @@
8583 issues: self.issues,
8684 git: self.git,
8785 email: self.email,
88 jwt: self.jwt,
8986 session: self.session,
9087 apihost: self.apihost,
9188 livekit: self.livekit,
Modified lib/config/src/server.rs View lib/config/src/server.rs
@@ -5,17 +5,13 @@
55#[derive(Debug, Clone, Deserialize)]
66#[serde(default)]
77pub struct ServerConfig {
8 pub http_addr: SocketAddr,
98 pub grpc_addr: SocketAddr,
10 pub internal_grpc_addr: Option<SocketAddr>,
119}
1210
1311impl Default for ServerConfig {
1412 fn default() -> Self {
1513 Self {
16 http_addr: SocketAddr::from((Ipv4Addr::LOCALHOST, 8081)),
1714 grpc_addr: SocketAddr::from((Ipv4Addr::LOCALHOST, 8082)),
18 internal_grpc_addr: None,
1915 }
2016 }
2117}
Modified lib/config/src/session.rs View lib/config/src/session.rs
@@ -12,12 +12,18 @@
1212 pub same_site: SameSite,
1313 #[serde(default)]
1414 pub trust_proxy: bool,
15 #[serde(default = "default_recheck_secs")]
16 pub recheck_secs: u64,
1517}
1618
1719fn default_anonymous_max_age() -> i64 {
1820 300
1921}
2022
23fn default_recheck_secs() -> u64 {
24 60
25}
26
2127impl Default for SessionConfig {
2228 fn default() -> Self {
2329 Self {
@@ -29,6 +35,7 @@
2935 secure: false,
3036 same_site: SameSite::default(),
3137 trust_proxy: false,
38 recheck_secs: default_recheck_secs(),
3239 }
3340 }
3441}
Modified lib/email/src/lib.rs View lib/email/src/lib.rs
@@ -1,20 +1,14 @@
1pub mod consumer;
12pub mod error;
23pub mod events;
3pub mod i18n;
4pub mod metrics;
5pub mod queue;
6pub mod service;
7pub mod template;
8pub mod templates;
4pub mod publisher;
95pub mod transport;
106
7pub use consumer::EmailConsumer;
118pub use error::EmailError;
129pub use events::{
1310 Attachment, BatchRecipient, EmailAddress, EmailFailed, EmailSent, RenderAndSendEmailRequest,
1411 SendBatchEmailRequest, SendEmailRequest,
1512};
16pub use i18n::UiLocale;
17pub use queue::{EmailQueue, Job, JobHandler, QueueSettings};
18pub use service::{EmailService, EmailServiceBuilder};
19pub use template::{TemplateFn, TemplateRegistry};
13pub use publisher::MailPublisher;
2014pub use transport::{EmailTransport, EmailTransportBuilder, build_message};
Modified lib/email/Cargo.toml View lib/email/Cargo.toml
@@ -5,18 +5,11 @@
55
66[dependencies]
77config = { workspace = true }
8db = { workspace = true }
9envelope = { workspace = true }
108events = { workspace = true }
119queue = { workspace = true }
12async-trait = { workspace = true }
1310base64 = { workspace = true }
1411bytes = { workspace = true }
15chrono = { version = "0.4.45", features = ["serde"] }
16futures-util = "0.3.34"
17http = "1.5.0"
1812lettre = { version = "0.11.23", default-features = false, features = ["smtp-transport", "tokio1-rustls", "rustls-tls", "builder", "pool"] }
19maud = { workspace = true }
2013serde = { workspace = true, features = ["derive"] }
2114serde_json = { workspace = true }
2215thiserror = { workspace = true }
Modified lib/envelope/src/middleware.rs View lib/envelope/src/middleware.rs
@@ -6,7 +6,7 @@
66use actix_web::http::header::{HeaderMap, HeaderName, HeaderValue};
77use actix_web::{Error, HttpMessage};
88use futures_util::future::LocalBoxFuture;
9use session::{REQUEST_ID_HEADER, RequestId, validate_request_id};
9use ::session::{REQUEST_ID_HEADER, RequestId, validate_request_id};
1010use uuid::Uuid;
1111
1212use crate::context;
Modified lib/export/src/api_error.rs View lib/export/src/api_error.rs
@@ -116,7 +116,7 @@
116116/// `IntoResponses` derive, envelope constructors, `IntoResponse`, and the
117117/// `From` impls shared by every crate (boxed self, `AppResponseData<()>`,
118118/// request rejections, `serde_json::Error`, `(StatusCode, &str)` and
119/// `token::SessionError`).
119/// `session::SessionError`).
120120///
121121/// The enum must contain an `Internal` variant; `log` labels server-side
122122/// warnings as "`<log> handler failed`".
@@ -295,16 +295,18 @@
295295 }
296296 }
297297
298 impl From<::token::SessionError> for $name {
299 fn from(error: ::token::SessionError) -> Self {
298 impl From<::session::SessionError> for $name {
299 fn from(error: ::session::SessionError) -> Self {
300300 match error {
301 ::token::SessionError::Unauthorized => {
301 ::session::SessionError::Unauthorized => {
302302 Self::build(401, Self::payload(401, "unauthenticated"))
303303 }
304 ::token::SessionError::Expired => Self::build(401, Self::payload(401, "token_expired")),
305 ::token::SessionError::Verifier(message) => {
306 ::tracing::debug!(error = %message, "bearer token rejected");
307 Self::build(401, Self::payload(401, "invalid_token"))
304 ::session::SessionError::Purged => {
305 Self::build(401, Self::payload(401, "session_revoked"))
306 }
307 other => {
308 ::tracing::error!(error = %other, "session backend error");
309 Self::build(500, Self::payload(500, "internal server error"))
308310 }
309311 }
310312 }
Modified lib/export/src/http.rs View lib/export/src/http.rs
@@ -408,6 +408,11 @@
408408 self.erase()
409409 }
410410
411 pub fn setup(mut self, setup: impl FnOnce(Scope) -> Scope + 'static) -> Self {
412 self.setups.push(Box::new(setup));
413 self
414 }
415
411416 pub fn wrap(mut self, guard: Guard) -> Self {
412417 for route in &mut self.routes {
413418 route.guards.push(guard);
@@ -489,7 +494,7 @@
489494 move |request, next| {
490495 Box::pin(async move {
491496 match guard {
492 Guard::Session => token::share::guard::session_guard(request, next).await,
497 Guard::Session => session::share::guard::session_guard(request, next).await,
493498 Guard::ServiceKey => require_service_key(request, next).await,
494499 }
495500 })
Modified lib/export/src/lib.rs View lib/export/src/lib.rs
@@ -38,7 +38,7 @@
3838 pub storage: Arc<AppStorage>,
3939 pub config: AppConfig,
4040 pub kenauth: Option<Arc<auth::client::KenAuthClient>>,
41 pub email_rpc: Option<Arc<auth::client::KenAuthClient>>,
41 pub mail: Option<auth::email::MailPublisher>,
4242}
4343
4444impl AppState {
@@ -50,13 +50,14 @@
5050
5151 pub async fn with_config(config: AppConfig) -> Result<Self, ExportError> {
5252 let database = Arc::new(AppDatabase::connect(config.database.clone()).await?);
53 Self::with_identity(config, database, None).await
53 Self::with_identity(config, database, None, None).await
5454 }
5555
5656 pub async fn with_identity(
5757 config: AppConfig,
5858 database: Arc<AppDatabase>,
5959 identity: Option<Arc<auth::client::KenAuthClient>>,
60 mail: Option<auth::email::MailPublisher>,
6061 ) -> Result<Self, ExportError> {
6162 let mut redis = AppRedis::connect(config.redis.clone()).await?;
6263 // 入队点遍布各业务 crate,但只有这里有配置:把开关写进句柄,让
@@ -68,7 +69,6 @@
6869 if let Some(client) = &kenauth {
6970 identity_client::install(client.clone());
7071 }
71 let email_rpc = kenauth.clone();
7272
7373 Ok(Self {
7474 database,
@@ -76,7 +76,7 @@
7676 storage,
7777 config,
7878 kenauth,
79 email_rpc,
79 mail,
8080 })
8181 }
8282}
Modified lib/export/Cargo.toml View lib/export/Cargo.toml
@@ -12,7 +12,7 @@
1212redis = { workspace = true }
1313tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] }
1414auth = { workspace = true }
15token = { workspace = true }
15session = { workspace = true }
1616imaging = { workspace = true }
1717actix-web = { workspace = true }
1818http = "1.5.0"
Modified lib/git/src/handler/analysis/code_activity.rs View lib/git/src/handler/analysis/code_activity.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/analysis/contributors.rs View lib/git/src/handler/analysis/contributors.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/analysis/languages.rs View lib/git/src/handler/analysis/languages.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/analysis/languages_read.rs View lib/git/src/handler/analysis/languages_read.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/archives/create.rs View lib/git/src/handler/archives/create.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/archives/create_bundle.rs View lib/git/src/handler/archives/create_bundle.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/archives/download.rs View lib/git/src/handler/archives/download.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPathRev, resolve_reader};
Modified lib/git/src/handler/blobs/content.rs View lib/git/src/handler/blobs/content.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/blobs/list_lfs_pointers.rs View lib/git/src/handler/blobs/list_lfs_pointers.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/blobs/metadata.rs View lib/git/src/handler/blobs/metadata.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/branch_protection/create_policy.rs View lib/git/src/handler/branch_protection/create_policy.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_repo_admin};
Modified lib/git/src/handler/branch_protection/delete_policy.rs View lib/git/src/handler/branch_protection/delete_policy.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/branch_protection/get_policy.rs View lib/git/src/handler/branch_protection/get_policy.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/branch_protection/list_policies.rs View lib/git/src/handler/branch_protection/list_policies.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_repo_admin};
Modified lib/git/src/handler/branch_protection/update_policy.rs View lib/git/src/handler/branch_protection/update_policy.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/branches/create.rs View lib/git/src/handler/branches/create.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/branches/delete.rs View lib/git/src/handler/branches/delete.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/branches/get.rs View lib/git/src/handler/branches/get.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/branches/list.rs View lib/git/src/handler/branches/list.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/branches/rename.rs View lib/git/src/handler/branches/rename.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/branches/set_default.rs View lib/git/src/handler/branches/set_default.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/code_comments/create_code_comment.rs View lib/git/src/handler/code_comments/create_code_comment.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/code_comments/list_code_comments.rs View lib/git/src/handler/code_comments/list_code_comments.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/code_comments/modify.rs View lib/git/src/handler/code_comments/modify.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44use uuid::Uuid;
55
66use crate::errors::{ApiError, GitApiError};
Modified lib/git/src/handler/code_comments/resolve_code_comment.rs View lib/git/src/handler/code_comments/resolve_code_comment.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/commits/get.rs View lib/git/src/handler/commits/get.rs
@@ -1,7 +1,7 @@
11use crate::rpcgit::commit::types::CommitInfo;
2use ::session::Session;
23use export::http::{Path, State};
34use export::rest::AppResponseData;
4use token::Session;
55
66use crate::errors::{ApiError, GitApiError};
77use crate::handler;
Modified lib/git/src/handler/commits/is_ancestor.rs View lib/git/src/handler/commits/is_ancestor.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44use serde::Serialize;
55
66use crate::errors::{ApiError, GitApiError};
Modified lib/git/src/handler/commits/list.rs View lib/git/src/handler/commits/list.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/commits/list_commit_statuses.rs View lib/git/src/handler/commits/list_commit_statuses.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/commits/rev_parse.rs View lib/git/src/handler/commits/rev_parse.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/commits/set_commit_status.rs View lib/git/src/handler/commits/set_commit_status.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/commits/stats.rs View lib/git/src/handler/commits/stats.rs
@@ -1,9 +1,9 @@
11use std::path::PathBuf;
22
33use crate::rpcgit::commit::types::CommitStats;
4use ::session::Session;
45use export::http::{Path, Query, State};
56use export::rest::AppResponseData;
6use token::Session;
77
88use crate::errors::{ApiError, GitApiError};
99use crate::handler;
Modified lib/git/src/handler/commits/sync_commit.rs View lib/git/src/handler/commits/sync_commit.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44use serde::Serialize;
55
66use crate::errors::{ApiError, GitApiError};
Modified lib/git/src/handler/conflicts/detail.rs View lib/git/src/handler/conflicts/detail.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/conflicts/get_conflicts.rs View lib/git/src/handler/conflicts/get_conflicts.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/content/caller_signature.rs View lib/git/src/handler/content/caller_signature.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, State};
23use export::rest::AppResponseData;
3use token::Session;
44use time::OffsetDateTime;
55
66use crate::errors::{ApiError, GitApiError};
Modified lib/git/src/handler/content/commit_object.rs View lib/git/src/handler/content/commit_object.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/content/write_blob.rs View lib/git/src/handler/content/write_blob.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/content/write_tree_from_files.rs View lib/git/src/handler/content/write_tree_from_files.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/diffs/for_commit.rs View lib/git/src/handler/diffs/for_commit.rs
@@ -1,7 +1,7 @@
11use crate::rpcgit::diff::types::ChangedPath;
2use ::session::Session;
23use export::http::{Path, State};
34use export::rest::AppResponseData;
4use token::Session;
55
66use crate::errors::{ApiError, GitApiError};
77use crate::handler;
Modified lib/git/src/handler/diffs/get_diff.rs View lib/git/src/handler/diffs/get_diff.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44use serde::Deserialize;
55
66use crate::errors::{ApiError, GitApiError};
Modified lib/git/src/handler/diffs/range_diff.rs View lib/git/src/handler/diffs/range_diff.rs
@@ -1,7 +1,7 @@
11use crate::rpcgit::diff::types::RangeDiffSpec;
2use ::session::Session;
23use export::http::{Path, Query, State};
34use export::rest::AppResponseData;
4use token::Session;
55
66use crate::errors::{ApiError, GitApiError};
77use crate::handler;
Modified lib/git/src/handler/diffs/raw.rs View lib/git/src/handler/diffs/raw.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/hooks/execute_script.rs View lib/git/src/handler/hooks/execute_script.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/lfs/cleanup_unreferenced.rs View lib/git/src/handler/lfs/cleanup_unreferenced.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/lfs/list_locks.rs View lib/git/src/handler/lfs/list_locks.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, resolve_reader};
Modified lib/git/src/handler/lfs/lock.rs View lib/git/src/handler/lfs/lock.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/lfs/mod.rs View lib/git/src/handler/lfs/mod.rs
@@ -1,4 +1,4 @@
1use token::Session;
1use ::session::Session;
22
33use crate::errors::ApiError;
44use crate::handler::{require_repo_admin, require_writer};
Modified lib/git/src/handler/lfs/unlock.rs View lib/git/src/handler/lfs/unlock.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, lfs};
Modified lib/git/src/handler/lfs/unlock_by_id.rs View lib/git/src/handler/lfs/unlock_by_id.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::lfs;
Modified lib/git/src/handler/lfs/verify_locks.rs View lib/git/src/handler/lfs/verify_locks.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/maintenance/fsck.rs View lib/git/src/handler/maintenance/fsck.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/maintenance/prune.rs View lib/git/src/handler/maintenance/prune.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/packs/ingest.rs View lib/git/src/handler/packs/ingest.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Bytes, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/packs/repack.rs View lib/git/src/handler/packs/repack.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/packs/stats.rs View lib/git/src/handler/packs/stats.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, resolve_reader};
Modified lib/git/src/handler/patches/apply.rs View lib/git/src/handler/patches/apply.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/patches/generate.rs View lib/git/src/handler/patches/generate.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/patches/parse.rs View lib/git/src/handler/patches/parse.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/refs/branches.rs View lib/git/src/handler/refs/branches.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/refs/delete.rs View lib/git/src/handler/refs/delete.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/refs/filtered.rs View lib/git/src/handler/refs/filtered.rs
@@ -1,7 +1,7 @@
11use crate::rpcgit::refs::types::RefInfo;
2use ::session::Session;
23use export::http::{Path, Query, State};
34use export::rest::AppResponseData;
4use token::Session;
55
66use crate::errors::{ApiError, GitApiError};
77use crate::handler;
Modified lib/git/src/handler/refs/get.rs View lib/git/src/handler/refs/get.rs
@@ -1,7 +1,7 @@
11use crate::rpcgit::refs::types::RefInfo;
2use ::session::Session;
23use export::http::{Path, State};
34use export::rest::AppResponseData;
4use token::Session;
55
66use crate::errors::{ApiError, GitApiError};
77use crate::handler;
Modified lib/git/src/handler/refs/head.rs View lib/git/src/handler/refs/head.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/refs/is_object_in_ref.rs View lib/git/src/handler/refs/is_object_in_ref.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44use serde::Serialize;
55
66use crate::errors::{ApiError, GitApiError};
Modified lib/git/src/handler/refs/list.rs View lib/git/src/handler/refs/list.rs
@@ -1,7 +1,7 @@
11use crate::rpcgit::refs::types::RefInfo;
2use ::session::Session;
23use export::http::{Path, State};
34use export::rest::AppResponseData;
4use token::Session;
55
66use crate::errors::{ApiError, GitApiError};
77use crate::handler;
Modified lib/git/src/handler/refs/resolve.rs View lib/git/src/handler/refs/resolve.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/refs/update.rs View lib/git/src/handler/refs/update.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44use serde::{Deserialize, Serialize};
55
66use crate::errors::{ApiError, GitApiError};
Modified lib/git/src/handler/releases/create_release.rs View lib/git/src/handler/releases/create_release.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/releases/delete_release.rs View lib/git/src/handler/releases/delete_release.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{require_repo_admin, require_writer};
Modified lib/git/src/handler/releases/get_release.rs View lib/git/src/handler/releases/get_release.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/releases/list_releases.rs View lib/git/src/handler/releases/list_releases.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/releases/update_release.rs View lib/git/src/handler/releases/update_release.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{require_repo_admin, require_writer};
Modified lib/git/src/handler/remotes/add.rs View lib/git/src/handler/remotes/add.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/remotes/clone_import.rs View lib/git/src/handler/remotes/clone_import.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/remotes/fetch.rs View lib/git/src/handler/remotes/fetch.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/remotes/list.rs View lib/git/src/handler/remotes/list.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, ensure_read_access};
Modified lib/git/src/handler/remotes/push.rs View lib/git/src/handler/remotes/push.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_writer};
Modified lib/git/src/handler/remotes/remove.rs View lib/git/src/handler/remotes/remove.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/repos/archive.rs View lib/git/src/handler/repos/archive.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, ensure_caller};
Modified lib/git/src/handler/repos/create.rs View lib/git/src/handler/repos/create.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/repos/get.rs View lib/git/src/handler/repos/get.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, resolve_reader};
Modified lib/git/src/handler/repos/list.rs View lib/git/src/handler/repos/list.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::service::GitService;
Modified lib/git/src/handler/repos/update.rs View lib/git/src/handler/repos/update.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, ensure_caller};
Modified lib/git/src/handler/signatures/verify_commit.rs View lib/git/src/handler/signatures/verify_commit.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/signatures/verify_tag.rs View lib/git/src/handler/signatures/verify_tag.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/submodules/list.rs View lib/git/src/handler/submodules/list.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/submodules/resolve.rs View lib/git/src/handler/submodules/resolve.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/tag_protection/create_policy.rs View lib/git/src/handler/tag_protection/create_policy.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_repo_admin};
Modified lib/git/src/handler/tag_protection/delete_policy.rs View lib/git/src/handler/tag_protection/delete_policy.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/tag_protection/get_policy.rs View lib/git/src/handler/tag_protection/get_policy.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/tag_protection/list_policies.rs View lib/git/src/handler/tag_protection/list_policies.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler::{RepoPath, require_repo_admin};
Modified lib/git/src/handler/tag_protection/update_policy.rs View lib/git/src/handler/tag_protection/update_policy.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/tags/create.rs View lib/git/src/handler/tags/create.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/tags/delete.rs View lib/git/src/handler/tags/delete.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/tags/get.rs View lib/git/src/handler/tags/get.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/tags/list.rs View lib/git/src/handler/tags/list.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/trees/read.rs View lib/git/src/handler/trees/read.rs
@@ -1,8 +1,8 @@
11use std::path::PathBuf;
22
3use ::session::Session;
34use export::http::{Path, Query, State};
45use export::rest::AppResponseData;
5use token::Session;
66use serde::Deserialize;
77
88use crate::errors::{ApiError, GitApiError};
Modified lib/git/src/handler/blame.rs View lib/git/src/handler/blame.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/path_log.rs View lib/git/src/handler/path_log.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::{ApiError, GitApiError};
66use crate::handler;
Modified lib/git/src/handler/share.rs View lib/git/src/handler/share.rs
@@ -1,9 +1,9 @@
11use std::future::Future;
22use std::pin::Pin;
33
4use ::session::Session;
45use actix_web::dev::Payload;
56use actix_web::{FromRequest, HttpRequest};
6use token::Session;
77use serde::Deserialize;
88use utoipa::IntoParams;
99
Modified lib/git/src/handler/stars.rs View lib/git/src/handler/stars.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44use serde::Deserialize;
55use utoipa::IntoParams;
66
Modified lib/git/src/handler/topics.rs View lib/git/src/handler/topics.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44use serde::{Deserialize, Serialize};
55use utoipa::IntoParams;
66
Modified lib/git/src/handler/watches.rs View lib/git/src/handler/watches.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Json, Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44use models::common::watch_mode::WatchMode;
55use serde::Deserialize;
66use utoipa::IntoParams;
Modified lib/git/src/handler/whitelists.rs View lib/git/src/handler/whitelists.rs
@@ -4,7 +4,7 @@
44use utoipa::IntoParams;
55use uuid::Uuid;
66
7use token::Session;
7use ::session::Session;
88
99use crate::errors::{ApiError, GitApiError};
1010use crate::handler;
Modified lib/git/src/share/access.rs View lib/git/src/share/access.rs
@@ -47,31 +47,10 @@
4747}
4848
4949pub async fn verify_access_key(
50 svc: &GitService,
51 token: &str,
50 _svc: &GitService,
51 _token: &str,
5252) -> Result<Option<(i64, AccessKeyScope)>, GitError> {
53 let Some(client) = svc.state.kenauth.as_ref() else {
54 return Err(GitError::Internal(
55 "kenoauth is not configured for access key verification".into(),
56 ));
57 };
58 match client.verify_access_key(token, Vec::new()).await {
59 Ok(Some(principal)) => {
60 let write = principal.scopes.iter().any(|scope| *scope == 12);
61 Ok(Some((
62 principal.user_id,
63 if write {
64 AccessKeyScope::Write
65 } else {
66 AccessKeyScope::Read
67 },
68 )))
69 }
70 Ok(None) => Ok(None),
71 Err(error) => Err(GitError::Internal(format!(
72 "access key verification failed: {error}"
73 ))),
74 }
53 Ok(None)
7554}
7655
7756/// HTTP write-path auth: token must be valid AND the resolved `uid` must
Modified lib/git/Cargo.toml View lib/git/Cargo.toml
@@ -29,7 +29,7 @@
2929tracing = { workspace = true }
3030flate2 = { workspace = true }
3131bytes = { workspace = true }
32token = { workspace = true }
32session = { workspace = true }
3333utoipa = { workspace = true }
3434maud = { workspace = true }
3535pulldown-cmark = { workspace = true }
Modified lib/issues/src/handlers/attachments/create_attachment.rs View lib/issues/src/handlers/attachments/create_attachment.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Json, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55
66use crate::errors::ApiError;
77use crate::services::attachments::{CreateIssueAttachmentCmd, IssueUploadIntentDto};
Modified lib/issues/src/handlers/attachments/delete_attachment.rs View lib/issues/src/handlers/attachments/delete_attachment.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Path, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/issues/src/handlers/attachments/download_attachment.rs View lib/issues/src/handlers/attachments/download_attachment.rs
@@ -1,8 +1,8 @@
1use ::session::Session;
2use ::session::share::session;
13use actix_web::http::header;
24use export::http::{Path, Response, State};
35use export::rest::{AppResponseData, NoData};
4use token::Session;
5use token::share::session;
66use uuid::Uuid;
77
88use crate::errors::ApiError;
Modified lib/issues/src/handlers/attachments/finalize_attachment.rs View lib/issues/src/handlers/attachments/finalize_attachment.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Path, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55use uuid::Uuid;
66
77use crate::errors::ApiError;
Modified lib/issues/src/handlers/attachments/list_attachments.rs View lib/issues/src/handlers/attachments/list_attachments.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
2use ::session::share::session;
13use export::http::{Path, State};
24use export::rest::{AppResponseData, NoData};
3use token::Session;
4use token::share::session;
55
66use crate::errors::ApiError;
77use crate::services::attachments::IssueAttachmentDto;
Modified lib/issues/src/handlers/boards/create_board.rs View lib/issues/src/handlers/boards/create_board.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::boards::{BoardDto, CreateBoardCmd};
77use crate::services::service::IssueService;
8use ::session::share::session;
89use space::services::spaces::SpaceNamePath;
9use token::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/issues/src/handlers/boards/delete_board.rs View lib/issues/src/handlers/boards/delete_board.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::boards::BoardPath;
77use crate::services::service::IssueService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/issues/src/handlers/boards/get_board.rs View lib/issues/src/handlers/boards/get_board.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::boards::{BoardDto, BoardPath};
77use crate::services::service::IssueService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 get,
Modified lib/issues/src/handlers/boards/list_boards.rs View lib/issues/src/handlers/boards/list_boards.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::boards::{BoardPage, ListBoardQuery};
77use crate::services::service::IssueService;
8use ::session::share::session;
89use space::services::spaces::SpaceNamePath;
9use token::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/issues/src/handlers/boards/update_board.rs View lib/issues/src/handlers/boards/update_board.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::boards::{BoardDto, BoardPath, UpdateBoardCmd};
77use crate::services::service::IssueService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 patch,
Modified lib/issues/src/handlers/comments/create_comment.rs View lib/issues/src/handlers/comments/create_comment.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::comments::{CommentDto, CreateCommentCmd};
77use crate::services::issues::IssuePath;
88use crate::services::service::IssueService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/issues/src/handlers/comments/delete_comment.rs View lib/issues/src/handlers/comments/delete_comment.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::comments::CommentPath;
77use crate::services::service::IssueService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/issues/src/handlers/comments/get_comment.rs View lib/issues/src/handlers/comments/get_comment.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::comments::{CommentDto, CommentPath};
77use crate::services::service::IssueService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 get,
Modified lib/issues/src/handlers/comments/list_comments.rs View lib/issues/src/handlers/comments/list_comments.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::comments::{CommentPage, ListCommentQuery};
77use crate::services::issues::IssuePath;
88use crate::services::service::IssueService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/issues/src/handlers/comments/list_comments_tree.rs View lib/issues/src/handlers/comments/list_comments_tree.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::comments::CommentTreeNode;
77use crate::services::issues::IssuePath;
88use crate::services::service::IssueService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/issues/src/handlers/comments/update_comment.rs View lib/issues/src/handlers/comments/update_comment.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::comments::{CommentDto, CommentPath, UpdateCommentCmd};
77use crate::services::service::IssueService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 patch,
Modified lib/issues/src/handlers/content_history/list_content_history.rs View lib/issues/src/handlers/content_history/list_content_history.rs
@@ -1,13 +1,13 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::content_history::{
77 ContentHistoryPage, ContentHistoryPath, ListContentHistoryQuery,
88};
99use crate::services::service::IssueService;
10use token::share::session;
10use ::session::share::session;
1111
1212#[utoipa::path(
1313 get,
Modified lib/issues/src/handlers/dependencies/create_dependency.rs View lib/issues/src/handlers/dependencies/create_dependency.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::dependencies::{CreateDependencyCmd, DependencyDto};
77use crate::services::issues::IssuePath;
88use crate::services::service::IssueService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/issues/src/handlers/dependencies/delete_dependency.rs View lib/issues/src/handlers/dependencies/delete_dependency.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::dependencies::DependencyPath;
77use crate::services::service::IssueService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/issues/src/handlers/dependencies/list_dependencies.rs View lib/issues/src/handlers/dependencies/list_dependencies.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::dependencies::DependencyDto;
77use crate::services::issues::IssuePath;
88use crate::services::service::IssueService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/issues/src/handlers/dependencies/list_dependents.rs View lib/issues/src/handlers/dependencies/list_dependents.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::dependencies::DependencyDto;
77use crate::services::issues::IssuePath;
88use crate::services::service::IssueService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/issues/src/handlers/events/list_events.rs View lib/issues/src/handlers/events/list_events.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::events::{EventPage, EventPath, ListEventQuery};
77use crate::services::service::IssueService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 get,
Modified lib/issues/src/handlers/issue_users/list_issue_users.rs View lib/issues/src/handlers/issue_users/list_issue_users.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::issue_users::{IssueUserPage, ListIssueUserQuery};
77use crate::services::issues::IssuePath;
88use crate::services::service::IssueService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/issues/src/handlers/issue_users/mark_issue_read.rs View lib/issues/src/handlers/issue_users/mark_issue_read.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::issue_users::{IssueUserDto, UpdateIssueUserCmd};
77use crate::services::issues::IssuePath;
88use crate::services::service::IssueService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)]
1212pub struct MarkIssueReadCmd {
Modified lib/issues/src/handlers/issues/bulk_issues.rs View lib/issues/src/handlers/issues/bulk_issues.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::boards::BoardPath;
77use crate::services::issues::{BulkIssuesCmd, BulkResultDto};
88use crate::services::service::IssueService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/issues/src/services/service.rs View lib/issues/src/services/service.rs
@@ -289,12 +289,12 @@
289289 text: &str,
290290 html: &str,
291291 ) -> Result<(), IssueError> {
292 let Some(client) = self.state.email_rpc.as_ref() else {
292 let Some(client) = self.state.mail.as_ref() else {
293293 tracing::warn!(to, "email rpc disabled; skipping issue mail");
294294 return Ok(());
295295 };
296296 client
297 .send_mail(to, subject, text, html)
297 .send_mail(to, subject, text, html, None)
298298 .await
299299 .map_err(|error| IssueError::Internal(format!("email send failed: {error}")))
300300 }
Modified lib/issues/src/share/guard.rs View lib/issues/src/share/guard.rs
@@ -1 +1 @@
1pub use token::share::guard::session_guard as issue_guard;
1pub use ::session::share::guard::session_guard as issue_guard;
Modified lib/issues/Cargo.toml View lib/issues/Cargo.toml
@@ -12,7 +12,7 @@
1212i18n = { workspace = true }
1313models = { workspace = true }
1414queue = { workspace = true }
15token = { workspace = true }
15session = { workspace = true }
1616space = { workspace = true }
1717actix-web = { workspace = true }
1818http = { workspace = true }
Modified lib/models/src/users/mod.rs View lib/models/src/users/mod.rs
@@ -1,9 +1,7 @@
11pub const DATABASE: &str = "user";
22
33pub mod access_scope;
4pub mod service_token_revocations;
54pub mod user;
6pub mod user_access_tokens;
75pub mod user_agreement_acceptances;
86pub mod user_blocks;
97pub mod user_events;
Modified lib/models/src/users/users.rs View lib/models/src/users/users.rs
@@ -55,8 +55,6 @@
5555 pub deletion_requested_at: Option<DateTimeUtc>,
5656
5757 #[sea_orm(has_many)]
58 pub access_tokens: HasMany<super::user_access_tokens::Entity>,
59 #[sea_orm(has_many)]
6058 pub ssh_keys: HasMany<super::user_ssh_keys::Entity>,
6159 #[sea_orm(has_many)]
6260 pub events: HasMany<super::user_events::Entity>,
Modified lib/pulls/src/handlers/inbox.rs View lib/pulls/src/handlers/inbox.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::AppResponseData;
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::inbox::{InboxPage, InboxQuery};
Modified lib/pulls/src/services/service.rs View lib/pulls/src/services/service.rs
@@ -1,3 +1,4 @@
1use ::session::Session;
12use config::AppConfig;
23use export::AppState;
34use git::service::GitService;
@@ -7,7 +8,6 @@
78use sea_orm::{
89 ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, PaginatorTrait, QueryFilter,
910};
10use token::Session;
1111use uuid::Uuid;
1212
1313use crate::errors::PullError;
Modified lib/pulls/src/share/lookup.rs View lib/pulls/src/share/lookup.rs
@@ -1,8 +1,8 @@
1use ::session::Session;
12use git::context::facade::GitContext;
23use git::share::access;
34use models::pull_request::pull_requests;
45use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
5use token::Session;
66use uuid::Uuid;
77
88use crate::errors::PullError;
Modified lib/pulls/Cargo.toml View lib/pulls/Cargo.toml
@@ -10,7 +10,7 @@
1010issues = { workspace = true }
1111models = { workspace = true }
1212queue = { workspace = true }
13token = { workspace = true }
13session = { workspace = true }
1414space = { workspace = true }
1515actix-web = { workspace = true }
1616sea-orm = { workspace = true }
Modified lib/queue/src/lib.rs View lib/queue/src/lib.rs
@@ -1,6 +1,7 @@
11#![recursion_limit = "512"]
22
33pub mod conn;
4pub mod consumer;
45pub mod envelope;
56pub mod error;
67pub mod publisher;
@@ -8,6 +9,7 @@
89pub mod subject;
910
1011pub use conn::{DEFAULT_MAX_MESSAGE_BYTES, RocketMqConn};
12pub use consumer::{Consumer, ConsumerConfig, MessageHandler};
1113pub use envelope::{DeliveryClass, EVENT_PREFIX, Envelope, Labels, SCHEMA_VERSION};
1214pub use error::{QueueError, QueueResult};
1315pub use publisher::Publisher;
Modified lib/session/src/error.rs View lib/session/src/error.rs
@@ -1,6 +1,8 @@
11use std::error::Error;
22use std::fmt::{self, Display, Formatter};
33
4use actix_web::http::StatusCode;
5use actix_web::{HttpResponse, ResponseError};
46use cache::CacheError;
57use cache::redis::RedisError;
68
@@ -15,6 +17,7 @@
1517 source: serde_json::Error,
1618 },
1719 Purged,
20 Unauthorized,
1821 UpdateConflict {
1922 key: String,
2023 },
@@ -65,6 +68,7 @@
6568 write!(f, "failed to deserialize session field `{key}`: {source}")
6669 }
6770 Self::Purged => f.write_str("session record has been purged"),
71 Self::Unauthorized => f.write_str("authentication required"),
6872 Self::UpdateConflict { key } => {
6973 write!(
7074 f,
@@ -84,9 +88,43 @@
8488 Self::Serialize { source, .. } | Self::Deserialize { source, .. } => Some(source),
8589 Self::Store(source) => Some(source),
8690 Self::Backend(source) => Some(source),
87 Self::Purged | Self::UpdateConflict { .. } | Self::Config(_) => None,
91 Self::Purged | Self::Unauthorized | Self::UpdateConflict { .. } | Self::Config(_) => {
92 None
93 }
94 }
95 }
96}
97
98impl ResponseError for SessionError {
99 fn status_code(&self) -> StatusCode {
100 match self {
101 Self::Unauthorized | Self::Purged => StatusCode::UNAUTHORIZED,
102 _ => StatusCode::INTERNAL_SERVER_ERROR,
88103 }
89104 }
105
106 fn error_response(&self) -> HttpResponse {
107 let status = self.status_code();
108 let error_code = match self {
109 Self::Unauthorized => "unauthenticated",
110 Self::Purged => "session_revoked",
111 _ => "internal",
112 };
113 let message = if status.is_server_error() {
114 "internal server error".to_owned()
115 } else {
116 self.to_string()
117 };
118 HttpResponse::build(status).json(serde_json::json!({
119 "code": status.as_u16(),
120 "message": message,
121 "data": null,
122 "error": null,
123 "error_code": error_code,
124 "request_id": null,
125 "log_id": null,
126 }))
127 }
90128}
91129
92130impl From<CacheError> for SessionError {
Modified lib/session/src/lib.rs View lib/session/src/lib.rs
@@ -1,18 +1,32 @@
1pub mod cookie;
12pub mod error;
23pub mod extract;
4pub mod guard;
35pub mod key;
46pub mod middleware;
57pub mod redis;
68pub mod state;
79pub mod store;
810
11pub use cookie::{clear_session_cookie, read_cookie, session_cookie};
912pub use error::SessionError;
1013pub use extract::{REQUEST_ID_HEADER, REQUEST_ID_MAX_LEN, RequestId, validate_request_id};
14pub use guard::{require_uid, session_guard};
1115pub use key::{MAX_SESSION_KEY_LEN, SESSION_KEY_LEN, SessionKey};
1216pub use middleware::SessionMiddleware;
1317pub use redis::RedisStore;
1418pub use state::{
15 SESSION_CLIENT_ID, SESSION_CLIENT_IP, SESSION_SECURITY_VERSION, SESSION_USER_AGENT,
16 SESSION_USER_ID, Session, SessionStatus,
19 SESSION_AUTH_SESSION_ID, SESSION_CLIENT_ID, SESSION_CLIENT_IP, SESSION_MFA_VERIFIED,
20 SESSION_SECURITY_VERSION, SESSION_USER_AGENT, SESSION_USER_ID, Session, SessionStatus,
1721};
1822pub use store::{SessionDiff, SessionState, SessionStore, generate_session_key};
23
24pub mod share {
25 pub mod guard {
26 pub use crate::guard::session_guard;
27 }
28
29 pub mod session {
30 pub use crate::guard::require_uid;
31 }
32}
Modified lib/session/src/middleware.rs View lib/session/src/middleware.rs
@@ -96,13 +96,16 @@
9696 let storage_backend = Rc::clone(&self.storage_backend);
9797 let configuration = Rc::clone(&self.config);
9898 Box::pin(async move {
99 let session_key = req
99 let provided_key = req
100100 .cookie(&configuration.cookie_name)
101101 .and_then(|cookie| SessionKey::try_from(cookie.value().to_owned()).ok());
102 let (session_key, session_state) =
103 load_session_state(session_key, storage_backend.as_ref()).await?;
102 let (session_key, loaded) =
103 load_session_state(provided_key.clone(), storage_backend.as_ref()).await?;
104 let remaining_ttl = loaded.as_ref().map(|(_, ttl)| *ttl);
105 let session_state = loaded.map(|(state, _)| state).unwrap_or_default();
104106
105107 let session = Session::from_state(session_state);
108 let previous_user_id = session.uid();
106109 req.extensions_mut().insert(session.clone());
107110
108111 record_client(&session, req.request(), configuration.trust_proxy);
@@ -135,6 +138,7 @@
135138 .unwrap_or(session);
136139
137140 let (status, state) = session.snapshot();
141 let user_id = session.uid();
138142 let ttl = Duration::seconds(session_ttl(&configuration, &session));
139143
140144 match session_key {
@@ -142,12 +146,14 @@
142146 if has_identity(&state) {
143147 let session_key = storage_backend.save(state, &ttl).await.map_err(e500)?;
144148 set_session_cookie(res.response_mut(), &configuration, session_key, ttl)?;
149 } else if provided_key.is_some() {
150 clear_session_cookie(res.response_mut(), &configuration, ttl)?;
145151 }
146152 }
147153 Some(session_key) => match status {
148154 SessionStatus::Changed => {
149155 match storage_backend
150 .update(session_key, session.diff(), &ttl)
156 .update(session_key, session.diff(), &ttl, previous_user_id, user_id)
151157 .await
152158 {
153159 Ok(session_key) => {
@@ -164,7 +170,7 @@
164170 }
165171 SessionStatus::Renewed => {
166172 match storage_backend
167 .rotate(session_key, session.diff(), &ttl)
173 .rotate(session_key, session.diff(), &ttl, previous_user_id, user_id)
168174 .await
169175 {
170176 Ok(Some(new_session_key)) => {
@@ -183,10 +189,34 @@
183189 }
184190 }
185191 SessionStatus::Purged => {
186 storage_backend.delete(&session_key).await.map_err(e500)?;
192 storage_backend
193 .delete(&session_key, previous_user_id)
194 .await
195 .map_err(e500)?;
187196 clear_session_cookie(res.response_mut(), &configuration, ttl)?;
188197 }
189 SessionStatus::UnChanged => {}
198 SessionStatus::UnChanged => {
199 let remaining = remaining_ttl.map(|ttl| ttl.whole_seconds()).unwrap_or(0);
200 if remaining < ttl.whole_seconds() / 2 {
201 match storage_backend
202 .update_ttl(&session_key, &ttl, user_id)
203 .await
204 {
205 Ok(()) => {
206 set_session_cookie(
207 res.response_mut(),
208 &configuration,
209 session_key,
210 ttl,
211 )?;
212 }
213 Err(SessionError::Purged) => {
214 clear_session_cookie(res.response_mut(), &configuration, ttl)?;
215 }
216 Err(error) => return Err(e500(error)),
217 }
218 }
219 }
190220 },
191221 }
192222
@@ -243,13 +273,13 @@
243273async fn load_session_state<Store: SessionStore>(
244274 session_key: Option<SessionKey>,
245275 storage_backend: &Store,
246) -> Result<(Option<SessionKey>, SessionState), actix_web::Error> {
276) -> Result<(Option<SessionKey>, Option<(SessionState, Duration)>), actix_web::Error> {
247277 match session_key {
248278 Some(session_key) => match storage_backend.load(&session_key).await.map_err(e500)? {
249 Some(session_state) => Ok((Some(session_key), session_state)),
250 None => Ok((None, SessionState::new())),
279 Some(loaded) => Ok((Some(session_key), Some(loaded))),
280 None => Ok((None, None)),
251281 },
252 None => Ok((None, SessionState::new())),
282 None => Ok((None, None)),
253283 }
254284}
255285
@@ -296,55 +326,4 @@
296326 cookie.set_domain(domain.clone());
297327 }
298328 cookie
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 fn config(secure: bool, same_site: SameSite) -> SessionConfig {
306 SessionConfig {
307 cookie_name: "session".into(),
308 max_age: 3600,
309 anonymous_max_age: 300,
310 path: Some("/".into()),
311 domain: None,
312 secure,
313 same_site,
314 trust_proxy: false,
315 }
316 }
317
318 #[test]
319 fn session_cookie_is_always_httponly() {
320 let cookie = session_cookie(
321 &config(true, SameSite::Lax),
322 "key".into(),
323 Duration::days(1),
324 );
325 assert_eq!(cookie.http_only(), Some(true));
326 }
327
328 #[test]
329 fn same_site_none_forces_secure() {
330 let cookie = session_cookie(
331 &config(false, SameSite::None),
332 "key".into(),
333 Duration::days(1),
334 );
335 assert_eq!(cookie.secure(), Some(true));
336 // Lax 下 secure 跟随配置(这里为 false),强制只发生在 SameSite=None。
337 let cookie = session_cookie(
338 &config(false, SameSite::Lax),
339 "key".into(),
340 Duration::days(1),
341 );
342 assert_eq!(cookie.secure(), Some(false));
343 let cookie = session_cookie(
344 &config(true, SameSite::Lax),
345 "key".into(),
346 Duration::days(1),
347 );
348 assert_eq!(cookie.secure(), Some(true));
349 }
350329}
Modified lib/session/src/redis.rs View lib/session/src/redis.rs
@@ -1,7 +1,7 @@
11use std::sync::LazyLock;
22
33use actix_web::cookie::time::Duration;
4use cache::redis::{AsyncCommands, Script, ScriptInvocation};
4use cache::redis::{Script, ScriptInvocation};
55use cache::{Cache, CacheConnection};
66
77use config::redis::RedisConfig;
@@ -11,6 +11,7 @@
1111use crate::store::{SessionDiff, SessionState, SessionStore, generate_session_key};
1212
1313const KEY_PREFIX: &str = "session";
14const USER_BUCKET_PREFIX: &str = "user:session";
1415
1516const SAVE_ATTEMPTS: usize = 3;
1617
@@ -25,7 +26,10 @@
2526 redis.call('DEL', KEYS[1])
2627 return {}
2728end
28return redis.call('HGETALL', KEYS[1])
29local ttl = redis.call('TTL', KEYS[1])
30local values = redis.call('HGETALL', KEYS[1])
31table.insert(values, 1, tostring(ttl))
32return values
2933"#,
3034 )
3135});
@@ -36,12 +40,16 @@
3640if redis.call('EXISTS', KEYS[1]) == 1 then
3741 return 0
3842end
39for index = 2, #ARGV, 2 do
43for index = 5, #ARGV, 2 do
4044 redis.call('HSET', KEYS[1], ARGV[index], ARGV[index + 1])
4145end
4246if redis.call('EXPIRE', KEYS[1], ARGV[1]) == 0 then
4347 return -1
4448end
49if ARGV[4] == '1' then
50 redis.call('SADD', KEYS[2], ARGV[3])
51 redis.call('EXPIRE', KEYS[2], ARGV[2])
52end
4553return 1
4654"#,
4755 )
@@ -57,8 +65,8 @@
5765 end
5866 return 0
5967end
60local index = 3
61local set_count = tonumber(ARGV[2])
68local index = 7
69local set_count = tonumber(ARGV[6])
6270for _ = 1, set_count do
6371 redis.call('HSET', KEYS[1], ARGV[index], ARGV[index + 1])
6472 index = index + 2
@@ -70,6 +78,13 @@
7078 index = index + 1
7179end
7280redis.call('EXPIRE', KEYS[1], ARGV[1])
81if ARGV[4] == '1' then
82 redis.call('SREM', KEYS[2], ARGV[3])
83end
84if ARGV[5] == '1' then
85 redis.call('SADD', KEYS[3], ARGV[3])
86 redis.call('EXPIRE', KEYS[3], ARGV[2])
87end
7388return 1
7489"#,
7590 )
@@ -88,8 +103,8 @@
88103if redis.call('RENAMENX', KEYS[1], KEYS[2]) == 0 then
89104 return -1
90105end
91local index = 3
92local set_count = tonumber(ARGV[2])
106local index = 8
107local set_count = tonumber(ARGV[7])
93108for _ = 1, set_count do
94109 redis.call('HSET', KEYS[2], ARGV[index], ARGV[index + 1])
95110 index = index + 2
@@ -103,11 +118,58 @@
103118if redis.call('EXPIRE', KEYS[2], ARGV[1]) == 0 then
104119 return 2
105120end
121if ARGV[5] == '1' then
122 redis.call('SREM', KEYS[3], ARGV[3])
123end
124if ARGV[6] == '1' then
125 redis.call('SADD', KEYS[4], ARGV[4])
126 redis.call('EXPIRE', KEYS[4], ARGV[2])
127end
128return 1
129"#,
130 )
131});
132
133static UPDATE_TTL_SCRIPT: LazyLock<Script> = LazyLock::new(|| {
134 Script::new(
135 r#"
136if redis.call('TYPE', KEYS[1])['ok'] ~= 'hash' then
137 return 0
138end
139redis.call('EXPIRE', KEYS[1], ARGV[1])
140if ARGV[3] == '1' then
141 redis.call('EXPIRE', KEYS[2], ARGV[2])
142end
106143return 1
107144"#,
108145 )
109146});
110147
148static DELETE_SCRIPT: LazyLock<Script> = LazyLock::new(|| {
149 Script::new(
150 r#"
151local deleted = redis.call('DEL', KEYS[1])
152if ARGV[2] == '1' then
153 redis.call('SREM', KEYS[2], ARGV[1])
154end
155return deleted
156"#,
157 )
158});
159
160static REVOKE_USER_SCRIPT: LazyLock<Script> = LazyLock::new(|| {
161 Script::new(
162 r#"
163local members = redis.call('SMEMBERS', KEYS[1])
164for _, member in ipairs(members) do
165 redis.call('DEL', ARGV[1] .. member)
166end
167redis.call('DEL', KEYS[1])
168return #members
169"#,
170 )
171});
172
111173#[derive(Clone)]
112174pub struct RedisStore {
113175 cache: Cache,
@@ -130,6 +192,14 @@
130192 self.cache.key(&[KEY_PREFIX, session_key.as_str()])
131193 }
132194
195 fn bucket_key(&self, user_id: Option<i64>) -> String {
196 self.cache.key(&[USER_BUCKET_PREFIX, &bucket_user(user_id)])
197 }
198
199 fn session_key_prefix(&self) -> String {
200 self.cache.key(&[KEY_PREFIX, ""])
201 }
202
133203 async fn connection(&self) -> Result<CacheConnection, SessionError> {
134204 Ok(self.cache.acquire().await?)
135205 }
@@ -152,12 +222,25 @@
152222}
153223
154224impl SessionStore for RedisStore {
155 async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, SessionError> {
225 async fn load(
226 &self,
227 session_key: &SessionKey,
228 ) -> Result<Option<(SessionState, Duration)>, SessionError> {
156229 let mut connection = self.connection().await?;
157230 let mut invocation = LOAD_SCRIPT.prepare_invoke();
158231 invocation.key(self.storage_key(session_key));
159232 let entries: Vec<String> = invocation.invoke_async(&mut connection).await?;
160 Ok(state_from_entries(entries))
233 let Some((ttl, entries)) = entries.split_first() else {
234 return Ok(None);
235 };
236 let ttl = ttl.parse::<i64>().unwrap_or(0);
237 if ttl <= 0 {
238 return Ok(None);
239 }
240 let Some(state) = state_from_entries(entries.to_vec()) else {
241 return Ok(None);
242 };
243 Ok(Some((state, Duration::seconds(ttl))))
161244 }
162245
163246 async fn save(
@@ -170,11 +253,18 @@
170253 "the session state to save cannot be empty",
171254 ));
172255 }
256 let user_id = user_id_of(&session_state);
257 let bucket_ttl = ttl_seconds(ttl);
173258 for _ in 0..SAVE_ATTEMPTS {
174259 let session_key = generate_session_key();
175260 let mut invocation = SAVE_SCRIPT.prepare_invoke();
176 invocation.key(self.storage_key(&session_key));
261 invocation
262 .key(self.storage_key(&session_key))
263 .key(self.bucket_key(user_id));
177264 invocation.arg(ttl_seconds(ttl));
265 invocation.arg(bucket_ttl);
266 invocation.arg(session_key.as_str());
267 invocation.arg(if user_id.is_some() { "1" } else { "0" });
178268 for (field, value) in &session_state {
179269 invocation.arg(field).arg(value);
180270 }
@@ -200,10 +290,20 @@
200290 session_key: SessionKey,
201291 diff: SessionDiff,
202292 ttl: &Duration,
293 previous_user_id: Option<i64>,
294 user_id: Option<i64>,
203295 ) -> Result<SessionKey, SessionError> {
204296 let mut invocation = UPDATE_SCRIPT.prepare_invoke();
205 invocation.key(self.storage_key(&session_key));
206 append_diff(&mut invocation, &diff, ttl);
297 invocation
298 .key(self.storage_key(&session_key))
299 .key(self.bucket_key(previous_user_id))
300 .key(self.bucket_key(user_id));
301 invocation.arg(ttl_seconds(ttl));
302 invocation.arg(ttl_seconds(ttl));
303 invocation.arg(session_key.as_str());
304 invocation.arg(if previous_user_id.is_some() { "1" } else { "0" });
305 invocation.arg(if user_id.is_some() { "1" } else { "0" });
306 append_diff_body(&mut invocation, &diff);
207307 let mut connection = self.connection().await?;
208308 let applied: i64 = invocation.invoke_async(&mut connection).await?;
209309 if applied != 1 {
@@ -217,14 +317,24 @@
217317 session_key: SessionKey,
218318 diff: SessionDiff,
219319 ttl: &Duration,
320 previous_user_id: Option<i64>,
321 user_id: Option<i64>,
220322 ) -> Result<Option<SessionKey>, SessionError> {
221323 for _ in 0..SAVE_ATTEMPTS {
222324 let new_session_key = self.rotated_key(&session_key)?;
223325 let mut invocation = ROTATE_SCRIPT.prepare_invoke();
224326 invocation
225327 .key(self.storage_key(&session_key))
226 .key(self.storage_key(&new_session_key));
227 append_diff(&mut invocation, &diff, ttl);
328 .key(self.storage_key(&new_session_key))
329 .key(self.bucket_key(previous_user_id))
330 .key(self.bucket_key(user_id));
331 invocation.arg(ttl_seconds(ttl));
332 invocation.arg(ttl_seconds(ttl));
333 invocation.arg(session_key.as_str());
334 invocation.arg(new_session_key.as_str());
335 invocation.arg(if previous_user_id.is_some() { "1" } else { "0" });
336 invocation.arg(if user_id.is_some() { "1" } else { "0" });
337 append_diff_body(&mut invocation, &diff);
228338 let mut connection = self.connection().await?;
229339 let outcome: i64 = invocation.invoke_async(&mut connection).await?;
230340 match outcome {
@@ -248,24 +358,59 @@
248358 &self,
249359 session_key: &SessionKey,
250360 ttl: &Duration,
361 user_id: Option<i64>,
251362 ) -> Result<(), SessionError> {
363 let mut invocation = UPDATE_TTL_SCRIPT.prepare_invoke();
364 invocation
365 .key(self.storage_key(session_key))
366 .key(self.bucket_key(user_id));
367 invocation.arg(ttl_seconds(ttl));
368 invocation.arg(ttl_seconds(ttl));
369 invocation.arg(if user_id.is_some() { "1" } else { "0" });
252370 let mut connection = self.connection().await?;
253 let renewed: bool = connection
254 .expire(self.storage_key(session_key), ttl_seconds(ttl))
255 .await?;
256 if !renewed {
371 let renewed: i64 = invocation.invoke_async(&mut connection).await?;
372 if renewed != 1 {
257373 return Err(SessionError::Purged);
258374 }
259375 Ok(())
260376 }
261377
262 async fn delete(&self, session_key: &SessionKey) -> Result<(), SessionError> {
378 async fn delete(
379 &self,
380 session_key: &SessionKey,
381 user_id: Option<i64>,
382 ) -> Result<(), SessionError> {
383 let mut invocation = DELETE_SCRIPT.prepare_invoke();
384 invocation
385 .key(self.storage_key(session_key))
386 .key(self.bucket_key(user_id));
387 invocation.arg(session_key.as_str());
388 invocation.arg(if user_id.is_some() { "1" } else { "0" });
263389 let mut connection = self.connection().await?;
264 let _: usize = connection.del(self.storage_key(session_key)).await?;
390 let _: i64 = invocation.invoke_async(&mut connection).await?;
265391 Ok(())
266392 }
393
394 async fn revoke_user(&self, user_id: i64) -> Result<usize, SessionError> {
395 let mut invocation = REVOKE_USER_SCRIPT.prepare_invoke();
396 invocation.key(self.bucket_key(Some(user_id)));
397 invocation.arg(self.session_key_prefix());
398 let mut connection = self.connection().await?;
399 let revoked: usize = invocation.invoke_async(&mut connection).await?;
400 Ok(revoked)
401 }
267402}
268403
404fn bucket_user(user_id: Option<i64>) -> String {
405 user_id.map(|id| id.to_string()).unwrap_or_default()
406}
407
408fn user_id_of(state: &SessionState) -> Option<i64> {
409 state
410 .get(crate::state::SESSION_USER_ID)
411 .and_then(|value| value.parse::<i64>().ok())
412}
413
269414fn state_from_entries(entries: Vec<String>) -> Option<SessionState> {
270415 if entries.is_empty() {
271416 return None;
@@ -278,8 +423,7 @@
278423 Some(state)
279424}
280425
281fn append_diff(invocation: &mut ScriptInvocation<'_>, diff: &SessionDiff, ttl: &Duration) {
282 invocation.arg(ttl_seconds(ttl));
426fn append_diff_body(invocation: &mut ScriptInvocation<'_>, diff: &SessionDiff) {
283427 invocation.arg(diff.set.len() as i64);
284428 for (field, value) in &diff.set {
285429 invocation.arg(field).arg(value);
Modified lib/session/src/state.rs View lib/session/src/state.rs
@@ -13,6 +13,8 @@
1313pub const SESSION_CLIENT_IP: &str = "client_ip";
1414pub const SESSION_USER_AGENT: &str = "user_agent";
1515pub const SESSION_SECURITY_VERSION: &str = "security_version";
16pub const SESSION_AUTH_SESSION_ID: &str = "auth_session_id";
17pub const SESSION_MFA_VERIFIED: &str = "mfa_verified";
1618
1719#[derive(Clone, Default)]
1820pub struct Session {
@@ -160,10 +162,37 @@
160162 self.get(SESSION_USER_ID)
161163 }
162164
165 pub fn uid(&self) -> Option<i64> {
166 self.user_id().ok().flatten()
167 }
168
169 pub fn is_authenticated(&self) -> bool {
170 self.uid().is_some()
171 }
172
173 pub fn session_id(&self) -> Option<String> {
174 self.get(SESSION_AUTH_SESSION_ID).ok().flatten()
175 }
176
177 pub fn mfa_verified(&self) -> bool {
178 self.get(SESSION_MFA_VERIFIED)
179 .ok()
180 .flatten()
181 .unwrap_or(false)
182 }
183
163184 pub fn set_user_id(&self, user_id: i64) -> Result<(), SessionError> {
164185 self.set(SESSION_USER_ID, user_id)
165186 }
166187
188 pub fn set_auth_session_id(&self, session_id: impl Into<String>) -> Result<(), SessionError> {
189 self.set(SESSION_AUTH_SESSION_ID, session_id.into())
190 }
191
192 pub fn set_mfa_verified(&self, verified: bool) -> Result<(), SessionError> {
193 self.set(SESSION_MFA_VERIFIED, verified)
194 }
195
167196 pub fn clear_user_id(&self) {
168197 self.remove(SESSION_USER_ID);
169198 }
Modified lib/session/src/store.rs View lib/session/src/store.rs
@@ -24,7 +24,7 @@
2424 fn load(
2525 &self,
2626 session_key: &SessionKey,
27 ) -> impl Future<Output = Result<Option<SessionState>, SessionError>>;
27 ) -> impl Future<Output = Result<Option<(SessionState, Duration)>, SessionError>>;
2828
2929 fn save(
3030 &self,
@@ -37,6 +37,8 @@
3737 session_key: SessionKey,
3838 diff: SessionDiff,
3939 ttl: &Duration,
40 previous_user_id: Option<i64>,
41 user_id: Option<i64>,
4042 ) -> impl Future<Output = Result<SessionKey, SessionError>>;
4143
4244 fn rotate(
@@ -44,15 +46,24 @@
4446 session_key: SessionKey,
4547 diff: SessionDiff,
4648 ttl: &Duration,
49 previous_user_id: Option<i64>,
50 user_id: Option<i64>,
4751 ) -> impl Future<Output = Result<Option<SessionKey>, SessionError>>;
4852
4953 fn update_ttl(
5054 &self,
5155 session_key: &SessionKey,
5256 ttl: &Duration,
57 user_id: Option<i64>,
5358 ) -> impl Future<Output = Result<(), SessionError>>;
5459
55 fn delete(&self, session_key: &SessionKey) -> impl Future<Output = Result<(), SessionError>>;
60 fn delete(
61 &self,
62 session_key: &SessionKey,
63 user_id: Option<i64>,
64 ) -> impl Future<Output = Result<(), SessionError>>;
65
66 fn revoke_user(&self, user_id: i64) -> impl Future<Output = Result<usize, SessionError>>;
5667}
5768
5869pub fn generate_session_key() -> SessionKey {
Modified lib/space/src/handlers/accept_invite.rs View lib/space/src/handlers/accept_invite.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::invites::AcceptInviteCmd;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 post,
Modified lib/space/src/handlers/add_member.rs View lib/space/src/handlers/add_member.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::members::{AddMemberCmd, MemberDto};
77use crate::services::service::SpaceService;
88use crate::services::spaces::SpaceNamePath;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/space/src/handlers/assign_group.rs View lib/space/src/handlers/assign_group.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::get_member::MemberPath;
77use crate::services::groups::AssignGroupCmd;
88use crate::services::service::SpaceService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/space/src/handlers/ban_user.rs View lib/space/src/handlers/ban_user.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::bans::{BanDto, BanUserCmd};
77use crate::services::service::SpaceService;
88use crate::services::spaces::SpaceNamePath;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/space/src/handlers/create_dir_node.rs View lib/space/src/handlers/create_dir_node.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::dir::{CreateDirCmd, DirNodeDto};
77use crate::services::service::SpaceService;
88use crate::services::spaces::SpaceNamePath;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/space/src/handlers/create_group.rs View lib/space/src/handlers/create_group.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::groups::{CreateGroupCmd, GroupDto};
77use crate::services::service::SpaceService;
88use crate::services::spaces::SpaceNamePath;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/space/src/handlers/create_invite.rs View lib/space/src/handlers/create_invite.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::invites::{CreateInviteCmd, InviteDto};
77use crate::services::service::SpaceService;
88use crate::services::spaces::SpaceNamePath;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/space/src/handlers/create_join_rule.rs View lib/space/src/handlers/create_join_rule.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::join_rules::{CreateJoinRuleCmd, JoinRuleDto};
77use crate::services::service::SpaceService;
88use crate::services::spaces::SpaceNamePath;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/space/src/handlers/create_room.rs View lib/space/src/handlers/create_room.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::rooms::{CreateRoomCmd, RoomDto};
77use crate::services::service::SpaceService;
88use crate::services::spaces::SpaceNamePath;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/space/src/handlers/create_space.rs View lib/space/src/handlers/create_space.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::service::SpaceService;
77use crate::services::spaces::{CreateSpaceCmd, SpaceDto};
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 post,
Modified lib/space/src/handlers/delete_group.rs View lib/space/src/handlers/delete_group.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::get_group::GroupPath;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/space/src/handlers/delete_join_rule.rs View lib/space/src/handlers/delete_join_rule.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::join_rules::JoinRulePath;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/space/src/handlers/delete_room.rs View lib/space/src/handlers/delete_room.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44use uuid::Uuid;
55
66use crate::errors::ApiError;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/space/src/handlers/delete_space.rs View lib/space/src/handlers/delete_space.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::service::SpaceService;
77use crate::services::spaces::SpaceNamePath;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/space/src/handlers/delete_space_image.rs View lib/space/src/handlers/delete_space_image.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::images::SpaceImageKindPath;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/space/src/handlers/explore_spaces.rs View lib/space/src/handlers/explore_spaces.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::join_requests::{
@@ -9,7 +9,7 @@
99};
1010use crate::services::service::SpaceService;
1111use crate::services::spaces::SpaceNamePath;
12use token::share::session;
12use ::session::share::session;
1313
1414#[utoipa::path(
1515 get,
Modified lib/space/src/handlers/get_ban.rs View lib/space/src/handlers/get_ban.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::get_member::MemberPath;
77use crate::services::bans::BanDto;
88use crate::services::service::SpaceService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/space/src/handlers/get_group.rs View lib/space/src/handlers/get_group.rs
@@ -1,7 +1,7 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
34use serde::Deserialize;
4use token::Session;
55use utoipa::{IntoParams, ToSchema};
66use uuid::Uuid;
77
Modified lib/space/src/handlers/get_member.rs View lib/space/src/handlers/get_member.rs
@@ -1,13 +1,13 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
34use serde::Deserialize;
4use token::Session;
55use utoipa::{IntoParams, ToSchema};
66
77use crate::errors::ApiError;
88use crate::services::members::MemberDto;
99use crate::services::service::SpaceService;
10use token::share::session;
10use ::session::share::session;
1111
1212#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
1313pub struct MemberPath {
Modified lib/space/src/handlers/get_space.rs View lib/space/src/handlers/get_space.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::service::SpaceService;
Modified lib/space/src/handlers/get_space_dir_tree.rs View lib/space/src/handlers/get_space_dir_tree.rs
@@ -1,13 +1,13 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
34use serde::Deserialize;
4use token::Session;
55
66use crate::errors::ApiError;
77use crate::services::dir::DirNodeDto;
88use crate::services::service::SpaceService;
99use crate::services::spaces::SpaceNamePath;
10use token::share::session;
10use ::session::share::session;
1111
1212#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
1313pub struct DirTreeQuery {
Modified lib/space/src/handlers/get_space_image.rs View lib/space/src/handlers/get_space_image.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::images::{SpaceImageDto, SpaceImageKindPath};
Modified lib/space/src/handlers/get_space_image_file.rs View lib/space/src/handlers/get_space_image_file.rs
@@ -1,8 +1,8 @@
1use ::session::Session;
12use actix_web::http::header;
23use export::http::{Path, Query, Response, State};
34use imaging::layout::ImageVariant;
45use serde::Deserialize;
5use token::Session;
66use utoipa::{IntoParams, ToSchema};
77
88use crate::errors::ApiError;
Modified lib/space/src/handlers/join_group_self.rs View lib/space/src/handlers/join_group_self.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::get_group::GroupPath;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 post,
Modified lib/space/src/handlers/join_requests.rs View lib/space/src/handlers/join_requests.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::join_requests::{CreateJoinRequestCmd, JoinRequestDto, JoinRequestPath};
77use crate::services::service::SpaceService;
88use crate::services::spaces::SpaceNamePath;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/space/src/handlers/leave_group_self.rs View lib/space/src/handlers/leave_group_self.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::get_group::GroupPath;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/space/src/handlers/list_bans.rs View lib/space/src/handlers/list_bans.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::bans::BanPage;
77use crate::services::service::SpaceService;
88use crate::services::spaces::{PageQuery, SpaceNamePath};
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/space/src/handlers/list_dir_group_overwrites.rs View lib/space/src/handlers/list_dir_group_overwrites.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::dir::{DirPath, GroupOverwriteDto};
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 get,
Modified lib/space/src/handlers/list_dir_user_overwrites.rs View lib/space/src/handlers/list_dir_user_overwrites.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::dir::{DirPath, UserOverwriteDto};
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 get,
Modified lib/space/src/handlers/list_groups.rs View lib/space/src/handlers/list_groups.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::groups::GroupPage;
77use crate::services::service::SpaceService;
88use crate::services::spaces::{PageQuery, SpaceNamePath};
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/space/src/handlers/list_invites.rs View lib/space/src/handlers/list_invites.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::invites::InvitePage;
77use crate::services::service::SpaceService;
88use crate::services::spaces::{PageQuery, SpaceNamePath};
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/space/src/handlers/list_join_rules.rs View lib/space/src/handlers/list_join_rules.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::join_rules::JoinRuleDto;
77use crate::services::service::SpaceService;
88use crate::services::spaces::SpaceNamePath;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/space/src/handlers/list_members.rs View lib/space/src/handlers/list_members.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::members::MemberPage;
77use crate::services::service::SpaceService;
88use crate::services::spaces::{PageQuery, SpaceNamePath};
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/space/src/handlers/list_spaces.rs View lib/space/src/handlers/list_spaces.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::service::SpaceService;
77use crate::services::spaces::{PageQuery, SpacePage};
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 get,
Modified lib/space/src/handlers/move_dir_node.rs View lib/space/src/handlers/move_dir_node.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::dir::{DirNodeDto, DirPath, MoveDirCmd};
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 patch,
Modified lib/space/src/handlers/reject_invite.rs View lib/space/src/handlers/reject_invite.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::invites::AcceptInviteCmd;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 post,
Modified lib/space/src/handlers/remove_dir_group_overwrite.rs View lib/space/src/handlers/remove_dir_group_overwrite.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::set_dir_group_overwrite::DirGroupPath;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/space/src/handlers/remove_dir_user_overwrite.rs View lib/space/src/handlers/remove_dir_user_overwrite.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::set_dir_user_overwrite::DirUserPath;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/space/src/handlers/remove_member.rs View lib/space/src/handlers/remove_member.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::get_member::MemberPath;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/space/src/handlers/repair_dir_parents.rs View lib/space/src/handlers/repair_dir_parents.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::dir::RepairDirResp;
77use crate::services::service::SpaceService;
88use crate::services::spaces::SpaceNamePath;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 post,
Modified lib/space/src/handlers/revoke_invite.rs View lib/space/src/handlers/revoke_invite.rs
@@ -1,14 +1,14 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
34use serde::Deserialize;
4use token::Session;
55use utoipa::{IntoParams, ToSchema};
66use uuid::Uuid;
77
88use crate::errors::ApiError;
99use crate::services::invites::InviteDto;
1010use crate::services::service::SpaceService;
11use token::share::session;
11use ::session::share::session;
1212
1313#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
1414pub struct InvitePath {
Modified lib/space/src/handlers/self_member.rs View lib/space/src/handlers/self_member.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::members::{MemberDto, UpdateMemberCmd};
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 patch,
Modified lib/space/src/handlers/set_dir_group_overwrite.rs View lib/space/src/handlers/set_dir_group_overwrite.rs
@@ -1,14 +1,14 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
34use serde::Deserialize;
4use token::Session;
55use utoipa::{IntoParams, ToSchema};
66use uuid::Uuid;
77
88use crate::errors::ApiError;
99use crate::services::dir::{GroupOverwriteDto, SetOverwriteCmd};
1010use crate::services::service::SpaceService;
11use token::share::session;
11use ::session::share::session;
1212
1313#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
1414pub struct DirGroupPath {
Modified lib/space/src/handlers/set_dir_user_overwrite.rs View lib/space/src/handlers/set_dir_user_overwrite.rs
@@ -1,14 +1,14 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
34use serde::Deserialize;
4use token::Session;
55use utoipa::{IntoParams, ToSchema};
66use uuid::Uuid;
77
88use crate::errors::ApiError;
99use crate::services::dir::{SetOverwriteCmd, UserOverwriteDto};
1010use crate::services::service::SpaceService;
11use token::share::session;
11use ::session::share::session;
1212
1313#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
1414pub struct DirUserPath {
Modified lib/space/src/handlers/unassign_group.rs View lib/space/src/handlers/unassign_group.rs
@@ -1,13 +1,13 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
34use serde::Deserialize;
4use token::Session;
55use utoipa::{IntoParams, ToSchema};
66use uuid::Uuid;
77
88use crate::errors::ApiError;
99use crate::services::service::SpaceService;
10use token::share::session;
10use ::session::share::session;
1111
1212#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
1313pub struct MemberGroupPath {
Modified lib/space/src/handlers/unban_user.rs View lib/space/src/handlers/unban_user.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::get_member::MemberPath;
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/space/src/handlers/update_ban.rs View lib/space/src/handlers/update_ban.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::get_member::MemberPath;
77use crate::services::bans::{BanDto, UpdateBanCmd};
88use crate::services::service::SpaceService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 patch,
Modified lib/space/src/handlers/update_dir_node.rs View lib/space/src/handlers/update_dir_node.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::dir::{DirNodeDto, DirPath, UpdateDirCmd};
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 patch,
Modified lib/space/src/handlers/update_group.rs View lib/space/src/handlers/update_group.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::get_group::GroupPath;
77use crate::services::groups::{GroupDto, UpdateGroupCmd};
88use crate::services::service::SpaceService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 patch,
Modified lib/space/src/handlers/update_join_rule.rs View lib/space/src/handlers/update_join_rule.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::join_rules::{JoinRuleDto, JoinRulePath, UpdateJoinRuleCmd};
77use crate::services::service::SpaceService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 patch,
Modified lib/space/src/handlers/update_member.rs View lib/space/src/handlers/update_member.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::handlers::get_member::MemberPath;
77use crate::services::members::{MemberDto, UpdateMemberCmd};
88use crate::services::service::SpaceService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 patch,
Modified lib/space/src/handlers/update_space.rs View lib/space/src/handlers/update_space.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::service::SpaceService;
77use crate::services::spaces::{SpaceDto, SpaceNamePath, UpdateSpaceCmd};
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 patch,
Modified lib/space/src/handlers/upload_space_image.rs View lib/space/src/handlers/upload_space_image.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use actix_web::http::header::CONTENT_TYPE;
23use export::http::{Bytes, HeaderMap, Path, Query, State};
34use export::rest::{AppResponseData, NoData};
4use token::Session;
55
66use crate::errors::ApiError;
77use crate::services::images::{SpaceImageDto, SpaceImageKindPath, SpaceImageUploadQuery};
88use crate::services::service::SpaceService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 put,
Modified lib/space/src/services/service.rs View lib/space/src/services/service.rs
@@ -43,12 +43,12 @@
4343 text: &str,
4444 html: &str,
4545 ) -> Result<(), SpaceError> {
46 let Some(client) = self.state.email_rpc.as_ref() else {
46 let Some(client) = self.state.mail.as_ref() else {
4747 tracing::warn!(to, "email rpc disabled; skipping space mail");
4848 return Ok(());
4949 };
5050 client
51 .send_mail(to, subject, text, html)
51 .send_mail(to, subject, text, html, None)
5252 .await
5353 .map_err(|error| SpaceError::Internal(format!("email send failed: {error}")))
5454 }
Modified lib/space/src/share/guard.rs View lib/space/src/share/guard.rs
@@ -1 +1 @@
1pub use token::share::guard::session_guard as space_guard;
1pub use ::session::share::guard::session_guard as space_guard;
Modified lib/space/Cargo.toml View lib/space/Cargo.toml
@@ -11,7 +11,7 @@
1111i18n = { workspace = true }
1212imaging = { workspace = true }
1313models = { workspace = true }
14token = { workspace = true }
14session = { workspace = true }
1515auth = { workspace = true }
1616prost-types = { workspace = true }
1717storage = { workspace = true }
Modified lib/user/src/handlers/create_meme.rs View lib/user/src/handlers/create_meme.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::profile::{MemeDto, UpsertMemeCmd};
77use crate::services::service::UserService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 post,
Modified lib/user/src/handlers/delete_avatar.rs View lib/user/src/handlers/delete_avatar.rs
@@ -1,10 +1,10 @@
1use ::session::Session;
12use export::http::State;
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::service::UserService;
7use token::share::session;
7use ::session::share::session;
88
99#[utoipa::path(
1010 delete,
Modified lib/user/src/handlers/delete_meme.rs View lib/user/src/handlers/delete_meme.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44use uuid::Uuid;
55
66use crate::errors::ApiError;
77use crate::services::service::UserService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/user/src/handlers/delete_push_subscription.rs View lib/user/src/handlers/delete_push_subscription.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44use uuid::Uuid;
55
66use crate::errors::ApiError;
77use crate::services::service::UserService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/user/src/handlers/get_avatar.rs View lib/user/src/handlers/get_avatar.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::State;
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::avatar::AvatarDto;
77use crate::services::service::UserService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 get,
Modified lib/user/src/handlers/get_profile_detail.rs View lib/user/src/handlers/get_profile_detail.rs
@@ -1,6 +1,6 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::profile::ProfileDetailDto;
Modified lib/user/src/handlers/get_user_avatar.rs View lib/user/src/handlers/get_user_avatar.rs
@@ -1,8 +1,8 @@
1use ::session::Session;
2use ::session::share::session;
13use actix_web::http::header;
24use export::http::{Path, Query, Response, State};
35use imaging::layout::ImageVariant;
4use token::Session;
5use token::share::session;
66use serde::Deserialize;
77use utoipa::{IntoParams, ToSchema};
88
Modified lib/user/src/handlers/list_memes.rs View lib/user/src/handlers/list_memes.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::profile::{ListLimitQuery, MemeDto};
77use crate::services::service::UserService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 get,
Modified lib/user/src/handlers/list_push_subscriptions.rs View lib/user/src/handlers/list_push_subscriptions.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::push_subscriptions::PushSubscriptionDto;
77use crate::services::service::UserService;
88use crate::share::page::PageQuery;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/user/src/handlers/list_saved_messages.rs View lib/user/src/handlers/list_saved_messages.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::notifications::SavedMessageDto;
77use crate::services::service::UserService;
88use crate::share::page::PageQuery;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/user/src/handlers/my_reports.rs View lib/user/src/handlers/my_reports.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::notifications::UserReportDto;
77use crate::services::service::UserService;
88use crate::share::page::PageQuery;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/user/src/handlers/my_spaces.rs View lib/user/src/handlers/my_spaces.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::profile::MySpaceDto;
77use crate::services::service::UserService;
88use crate::share::page::PageQuery;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/user/src/handlers/my_suspensions.rs View lib/user/src/handlers/my_suspensions.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::service::UserService;
77use crate::services::suspensions::{IncludeRevokedQuery, SuspensionDto};
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 get,
Modified lib/user/src/handlers/recent_mentions.rs View lib/user/src/handlers/recent_mentions.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Query, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::notifications::RecentMentionDto;
77use crate::services::profile::ListLimitQuery;
88use crate::services::service::UserService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 get,
Modified lib/user/src/handlers/save_message.rs View lib/user/src/handlers/save_message.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::notifications::{SaveMessageCmd, SavedMessageDto};
77use crate::services::service::UserService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 post,
Modified lib/user/src/handlers/unsave_message.rs View lib/user/src/handlers/unsave_message.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44use uuid::Uuid;
55
66use crate::errors::ApiError;
77use crate::services::service::UserService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 delete,
Modified lib/user/src/handlers/update_meme.rs View lib/user/src/handlers/update_meme.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use export::http::{Json, Path, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44use uuid::Uuid;
55
66use crate::errors::ApiError;
77use crate::services::profile::{MemeDto, UpsertMemeCmd};
88use crate::services::service::UserService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 put,
Modified lib/user/src/handlers/upload_avatar.rs View lib/user/src/handlers/upload_avatar.rs
@@ -1,12 +1,12 @@
1use ::session::Session;
12use actix_web::http::header::CONTENT_TYPE;
23use export::http::{Bytes, HeaderMap, State};
34use export::rest::{AppResponseData, NoData};
4use token::Session;
55
66use crate::errors::ApiError;
77use crate::services::avatar::AvatarDto;
88use crate::services::service::UserService;
9use token::share::session;
9use ::session::share::session;
1010
1111#[utoipa::path(
1212 put,
Modified lib/user/src/handlers/upsert_push_subscription.rs View lib/user/src/handlers/upsert_push_subscription.rs
@@ -1,11 +1,11 @@
1use ::session::Session;
12use export::http::{Json, State};
23use export::rest::{AppResponseData, NoData};
3use token::Session;
44
55use crate::errors::ApiError;
66use crate::services::push_subscriptions::{PushSubscriptionDto, UpsertPushCmd};
77use crate::services::service::UserService;
8use token::share::session;
8use ::session::share::session;
99
1010#[utoipa::path(
1111 post,
Modified lib/user/src/share/guard.rs View lib/user/src/share/guard.rs
@@ -1 +1 @@
1pub use token::share::guard::session_guard as user_guard;
1pub use ::session::share::guard::session_guard as user_guard;
Modified lib/user/Cargo.toml View lib/user/Cargo.toml
@@ -10,7 +10,7 @@
1010models = { workspace = true }
1111i18n = { workspace = true }
1212imaging = { workspace = true }
13token = { workspace = true }
13session = { workspace = true }
1414storage = { workspace = true }
1515actix-web = { workspace = true }
1616sea-orm = { workspace = true }
Modified lib/webapi/src/auth/mod.rs View lib/webapi/src/auth/mod.rs
@@ -1,8 +1,8 @@
11pub mod errors;
22pub mod handlers;
3pub mod session;
43pub mod openapi;
54pub mod routes;
65pub mod services;
6pub mod session;
77pub mod share;
88pub mod templates;
Modified lib/webapi/src/auth/openapi.rs View lib/webapi/src/auth/openapi.rs
@@ -35,7 +35,6 @@
3535 handlers::unread_notifications_count::unread_notifications_count,
3636 handlers::update_preferences::update_preferences,
3737 handlers::update_profile::update_profile,
38 handlers::jwt_request::jwt_request,
3938 handlers::password::change_password,
4039 handlers::two_factor::totp_status,
4140 handlers::two_factor::totp_setup,
@@ -69,8 +68,6 @@
6968 ReportDto,
7069 UnreadCountResp,
7170 NotificationType,
72 handlers::jwt_request::JwtRequestQuery,
73 handlers::jwt_request::JwtRequestResp,
7471 handlers::password::ChangePasswordCmd,
7572 handlers::two_factor::TotpStatusResp,
7673 handlers::two_factor::TotpSetupResp,
Modified lib/webapi/src/auth/routes.rs View lib/webapi/src/auth/routes.rs
@@ -17,15 +17,13 @@
1717use crate::auth::handlers::update_preferences::update_preferences;
1818use crate::auth::handlers::update_profile::update_profile;
1919use crate::auth::handlers::users;
20use crate::auth::services::AuthService;
2021use crate::auth::session;
21use crate::auth::services::AuthService;
2222
2323pub fn router(_service: AuthService) -> Router<AuthService> {
2424 let public = Router::<AuthService>::new()
2525 .route("/auth/login", get(session::login::login))
2626 .route("/auth/signup", get(session::login::signup))
27 .route("/auth/callback", get(session::callback::callback))
28 .route("/auth/refresh", post(session::refresh::refresh))
2927 .route("/auth/logout", post(session::logout::logout))
3028 .route("/auth/agreements", get(list_agreements));
3129
@@ -42,10 +40,6 @@
4240 get(account_deletion_status)
4341 .post(request_account_deletion)
4442 .delete(cancel_account_deletion),
45 )
46 .route(
47 "/auth/jwt-request",
48 get(crate::auth::handlers::jwt_request::jwt_request),
4943 )
5044 .route("/me/username", put(users::update_username))
5145 .route(
Modified lib/webapi/src/cleanup.rs View lib/webapi/src/cleanup.rs
@@ -9,11 +9,11 @@
99 */
1010use std::collections::HashMap;
1111
12use ::session::Session;
13use ::session::share::session;
1214use export::http::routing::{delete, get, post};
1315use export::http::{Json, Path, Router, State};
1416use export::rest::{AppResponseData, NoData};
15use token::Session;
16use token::share::session;
1717use models::dir::{dir_overwrite_group, dir_overwrite_users, dirs};
1818use models::issue::issues as boards;
1919use models::repo::repos;
Modified lib/webapi/src/lib.rs View lib/webapi/src/lib.rs
@@ -2,8 +2,6 @@
22pub mod cleanup;
33pub mod openapi;
44
5use std::sync::Arc;
6
75use actix_cors::Cors;
86
97use actix_web::http::Method;
@@ -46,7 +44,14 @@
4644
4745 let cleanup_router = cleanup::router(space.clone(), issues.clone(), git.clone());
4846
49 let public = Router::<()>::new()
47 let mut public = Router::<()>::new();
48 if state.kenauth.is_some() {
49 public = public.merge(
50 Router::<()>::new()
51 .setup(|scope| scope.configure(::auth::server::http::router::configure_api)),
52 );
53 }
54 let public = public
5055 .merge(crate::auth::routes::router(auth.clone()).with_state(auth.clone()))
5156 .merge(chat::routes::router(chat.clone(), space.clone()).with_state(chat.clone()))
5257 .merge(issues::routes::router(issues.clone()).with_state(issues))
@@ -67,15 +72,5 @@
6772 .nest("/pages", crate::auth::routes::pages_router(auth.clone()))
6873 .merge(export::health::router(state))
6974 .into_scope(web::scope(""))
70}
71
72pub fn verifier(state: &AppState) -> Option<Arc<token::JwtVerifier>> {
73 let client = state.kenauth.as_ref()?;
74 match token::JwtVerifier::with_identity(&state.config.jwt, client.service().signing().jwks()) {
75 Ok(verifier) => Some(verifier),
76 Err(error) => {
77 tracing::warn!(%error, "the JWT verifier could not be built");
78 None
79 }
80 }
75 .default_service(web::to(::auth::assets::serve))
8176}
Modified lib/webapi/src/openapi.rs View lib/webapi/src/openapi.rs
@@ -23,14 +23,9 @@
2323- `POST /auth/logout` is idempotent: repeating it always succeeds with `200`.
2424
2525### Authentication
26- `session_cookie`: platform JWT carried in the `gitdata_session` cookie, minted
27 by the identity service from the same-domain session. `GET /auth/login`
28 redirects to the identity login page; `POST /auth/refresh` re-mints the JWT
29 from the session; `POST /auth/logout` revokes the identity session and clears
30 the cookie.
31- `basic_access_key`: HTTP Basic auth with an identity-issued access key.
32 **Scope: git repository endpoints only** (`/repos/*`, `/graphql`); it is NOT
33 accepted by chat/issues/space/user endpoints.
26- `session_cookie`: opaque session carried in the `session` cookie and stored in
27 Redis. `GET /auth/login` redirects to the same-origin login page;
28 `POST /auth/logout` revokes the session and clears the cookie.
3429
3530### URL & parameter style
3631- Paths are `kebab-case`, plural nouns, nesting expresses ownership.
Modified lib/webapi/Cargo.toml View lib/webapi/Cargo.toml
@@ -23,7 +23,7 @@
2323models = { workspace = true }
2424sea-orm = { workspace = true }
2525export = { workspace = true }
26token = { workspace = true }
26session = { workspace = true }
2727tokio = { workspace = true }
2828actix-web = { workspace = true }
2929actix-cors = { workspace = true }
Modified Cargo.lock View Cargo.lock
@@ -773,7 +773,6 @@
773773 "config 1.0.0",
774774 "crypt",
775775 "db",
776 "ed25519-dalek",
777776 "email",
778777 "envelope",
779778 "events",
@@ -808,14 +807,11 @@
808807 "sha2 0.10.9",
809808 "sqlparser",
810809 "thiserror 2.0.21",
811 "token",
812810 "tokio",
813811 "tokio-stream",
814812 "tokio-util",
815813 "tonic",
816 "tonic-health",
817814 "tonic-prost",
818 "tonic-reflection",
819815 "tower",
820816 "tracing",
821817 "tracing-opentelemetry",
@@ -1274,12 +1270,12 @@
12741270 "sea-orm",
12751271 "serde",
12761272 "serde_json",
1273 "session",
12771274 "sha2 0.11.1",
12781275 "social",
12791276 "space",
12801277 "storage",
12811278 "time 0.3.55",
1282 "token",
12831279 "tokio",
12841280 "tokio-util",
12851281 "tracing",
@@ -1785,33 +1781,6 @@
17851781]
17861782
17871783[[package]]
1788name = "curve25519-dalek"
1789version = "4.1.3"
1790source = "registry+https://github.com/rust-lang/crates.io-index"
1791checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
1792dependencies = [
1793 "cfg-if",
1794 "cpufeatures 0.2.17",
1795 "curve25519-dalek-derive",
1796 "digest 0.10.7",
1797 "fiat-crypto",
1798 "rustc_version",
1799 "subtle",
1800 "zeroize",
1801]
1802
1803[[package]]
1804name = "curve25519-dalek-derive"
1805version = "0.1.1"
1806source = "registry+https://github.com/rust-lang/crates.io-index"
1807checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
1808dependencies = [
1809 "proc-macro2",
1810 "quote",
1811 "syn 2.0.119",
1812]
1813
1814[[package]]
18151784name = "darling"
18161785version = "0.20.11"
18171786source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2157,16 +2126,6 @@
21572126]
21582127
21592128[[package]]
2160name = "ed25519"
2161version = "2.2.3"
2162source = "registry+https://github.com/rust-lang/crates.io-index"
2163checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
2164dependencies = [
2165 "pkcs8 0.10.2",
2166 "signature 2.2.0",
2167]
2168
2169[[package]]
21702129name = "ed25519-compact"
21712130version = "2.4.2"
21722131source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2177,20 +2136,6 @@
21772136]
21782137
21792138[[package]]
2180name = "ed25519-dalek"
2181version = "2.2.0"
2182source = "registry+https://github.com/rust-lang/crates.io-index"
2183checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
2184dependencies = [
2185 "curve25519-dalek",
2186 "ed25519",
2187 "serde",
2188 "sha2 0.10.9",
2189 "subtle",
2190 "zeroize",
2191]
2192
2193[[package]]
21942139name = "either"
21952140version = "1.19.0"
21962141source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2224,18 +2169,11 @@
22242169name = "email"
22252170version = "1.0.0"
22262171dependencies = [
2227 "async-trait",
22282172 "base64 0.23.1",
22292173 "bytes",
2230 "chrono",
22312174 "config 1.0.0",
2232 "db",
2233 "envelope",
22342175 "events",
2235 "futures-util",
2236 "http 1.5.0",
22372176 "lettre",
2238 "maud",
22392177 "queue",
22402178 "serde",
22412179 "serde_json",
@@ -2402,11 +2340,11 @@
24022340 "sea-orm",
24032341 "serde",
24042342 "serde_json",
2343 "session",
24052344 "sha2 0.11.1",
24062345 "storage",
24072346 "thiserror 2.0.21",
24082347 "time 0.3.55",
2409 "token",
24102348 "tokio",
24112349 "tower",
24122350 "tracing",
@@ -2451,12 +2389,6 @@
24512389]
24522390
24532391[[package]]
2454name = "fiat-crypto"
2455version = "0.2.9"
2456source = "registry+https://github.com/rust-lang/crates.io-index"
2457checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
2458
2459[[package]]
24602392name = "figment"
24612393version = "0.10.19"
24622394source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2789,11 +2721,11 @@
27892721 "sea-orm",
27902722 "serde",
27912723 "serde_json",
2724 "session",
27922725 "sha2 0.11.1",
27932726 "space",
27942727 "thiserror 2.0.21",
27952728 "time 0.3.55",
2796 "token",
27972729 "tokio",
27982730 "tokio-util",
27992731 "tonic",
@@ -3639,9 +3571,9 @@
36393571 "sea-orm",
36403572 "serde",
36413573 "serde_json",
3574 "session",
36423575 "space",
36433576 "time 0.3.55",
3644 "token",
36453577 "tracing",
36463578 "utoipa 5.5.0",
36473579 "uuid",
@@ -3787,21 +3719,6 @@
37873719
37883720[[package]]
37893721name = "jsonwebtoken"
3790version = "9.3.1"
3791source = "registry+https://github.com/rust-lang/crates.io-index"
3792checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
3793dependencies = [
3794 "base64 0.22.1",
3795 "js-sys",
3796 "pem",
3797 "ring",
3798 "serde",
3799 "serde_json",
3800 "simple_asn1",
3801]
3802
3803[[package]]
3804name = "jsonwebtoken"
38053722version = "10.4.0"
38063723source = "registry+https://github.com/rust-lang/crates.io-index"
38073724checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc"
@@ -3883,9 +3800,11 @@
38833800 "queue",
38843801 "sea-orm",
38853802 "serde_json",
3886 "token",
3803 "session",
38873804 "tokio",
3805 "tokio-stream",
38883806 "tokio-util",
3807 "tonic",
38893808 "tracing",
38903809 "tracing-subscriber",
38913810 "utoipa 5.5.0",
@@ -4171,7 +4090,7 @@
41714090checksum = "bc9c914fdcfb38e7e48acfe5dfa1a64eb01a4e3f42c37f792e48b0705a8cc7c4"
41724091dependencies = [
41734092 "hmac 0.12.1",
4174 "jsonwebtoken 10.4.0",
4093 "jsonwebtoken",
41754094 "livekit-protocol",
41764095 "serde",
41774096 "sha2 0.10.9",
@@ -5759,9 +5678,9 @@
57595678 "sea-orm",
57605679 "serde",
57615680 "serde_json",
5681 "session",
57625682 "space",
57635683 "time 0.3.55",
5764 "token",
57655684 "tracing",
57665685 "utoipa 5.5.0",
57675686 "uuid",
@@ -7168,18 +7087,6 @@
71687087checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
71697088
71707089[[package]]
7171name = "simple_asn1"
7172version = "0.6.4"
7173source = "registry+https://github.com/rust-lang/crates.io-index"
7174checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d"
7175dependencies = [
7176 "num-bigint 0.4.8",
7177 "num-traits",
7178 "thiserror 2.0.21",
7179 "time 0.3.55",
7180]
7181
7182[[package]]
71837090name = "siphasher"
71847091version = "1.0.4"
71857092source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7274,9 +7181,9 @@
72747181 "sea-orm",
72757182 "serde",
72767183 "serde_json",
7184 "session",
72777185 "storage",
72787186 "time 0.3.55",
7279 "token",
72807187 "tracing",
72817188 "utoipa 5.5.0",
72827189 "uuid",
@@ -7818,33 +7725,6 @@
78187725checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
78197726
78207727[[package]]
7821name = "token"
7822version = "1.0.0"
7823dependencies = [
7824 "actix-web",
7825 "async-trait",
7826 "base64 0.23.1",
7827 "bytes",
7828 "chrono",
7829 "config 1.0.0",
7830 "crypt",
7831 "ed25519-dalek",
7832 "envelope",
7833 "http 1.5.0",
7834 "jsonwebtoken 9.3.1",
7835 "prost-types 0.14.4",
7836 "proto",
7837 "reqwest 0.13.5",
7838 "serde",
7839 "serde_json",
7840 "sha2 0.10.9",
7841 "thiserror 2.0.21",
7842 "tokio",
7843 "tonic",
7844 "tracing",
7845]
7846
7847[[package]]
78487728name = "tokio"
78497729version = "1.54.1"
78507730source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7901,7 +7781,6 @@
79017781 "futures-core",
79027782 "pin-project-lite",
79037783 "tokio",
7904 "tokio-util",
79057784]
79067785
79077786[[package]]
@@ -8074,19 +7953,6 @@
80747953]
80757954
80767955[[package]]
8077name = "tonic-health"
8078version = "0.14.6"
8079source = "registry+https://github.com/rust-lang/crates.io-index"
8080checksum = "fcfab99db777fba2802f0dfa861d1628d1ae916fb199d29819941f139ae85082"
8081dependencies = [
8082 "prost 0.14.4",
8083 "tokio",
8084 "tokio-stream",
8085 "tonic",
8086 "tonic-prost",
8087]
8088
8089[[package]]
80907956name = "tonic-prost"
80917957version = "0.14.9"
80927958source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -8114,20 +7980,6 @@
81147980]
81157981
81167982[[package]]
8117name = "tonic-reflection"
8118version = "0.14.6"
8119source = "registry+https://github.com/rust-lang/crates.io-index"
8120checksum = "acccd136a4bf19810a1fde9c74edc6129b42a66b44d0c1c8aaa67aeb49a146a7"
8121dependencies = [
8122 "prost 0.14.4",
8123 "prost-types 0.14.4",
8124 "tokio",
8125 "tokio-stream",
8126 "tonic",
8127 "tonic-prost",
8128]
8129
8130[[package]]
81317983name = "tower"
81327984version = "0.5.3"
81337985source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -8475,11 +8327,11 @@
84758327 "sea-orm",
84768328 "serde",
84778329 "serde_json",
8330 "session",
84788331 "sha2 0.11.1",
84798332 "space",
84808333 "storage",
84818334 "time 0.3.55",
8482 "token",
84838335 "tracing",
84848336 "utoipa 5.5.0",
84858337 "uuid",
@@ -8837,10 +8689,10 @@
88378689 "sea-orm",
88388690 "serde",
88398691 "serde_json",
8692 "session",
88408693 "sha2 0.11.1",
88418694 "space",
88428695 "time 0.3.55",
8843 "token",
88448696 "tokio",
88458697 "tracing",
88468698 "url 2.5.8",
Modified Cargo.toml View Cargo.toml
@@ -36,7 +36,6 @@
3636oltp = { path = "lib/oltp" }
3737proto = { path = "lib/proto" }
3838session = { path = "lib/session" }
39token = { path = "lib/token" }
4039webapi = { path = "lib/webapi" }
4140pulls = { path = "lib/pulls" }
4241social = { path = "lib/social" }
@@ -89,7 +88,6 @@
8988livekit-protocol = "0.7.13"
9089anyhow = "1"
9190tracing-subscriber = { version = "0.3", features = ["env-filter"] }
92jsonwebtoken = "9"
9391
9492regex = "1"
9593url = "2"
Modified Dockerfile View Dockerfile
@@ -106,7 +106,7 @@
106106
107107USER gitdata
108108
109EXPOSE 7051 7021 7022
109EXPOSE 7051 7022
110110STOPSIGNAL SIGTERM
111111
112112HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
Modified vite.config.ts View vite.config.ts
@@ -6,7 +6,6 @@
66export default defineConfig(({ mode }) => {
77 const env = loadEnv(mode, process.cwd(), '')
88 return {
9 // 前端源码在 view/ 下,产物输出到 view/dist(build.rs 会把它们编进二进制)。
109 root: 'view',
1110 publicDir: 'public',
1211 build: { outDir: 'dist', emptyOutDir: true },