1use std::sync::Arc;
2
3use linkrs_api::api_core::SyncApi;
4#[cfg(feature = "vector")]
5use linkrs_api::api_core::VectorApi;
6use linkrs_metrics::{MetricType, StatsManager};
7use linkrs_query::query_manager::QueryManager;
8
9use crate::auth::Authenticator;
10use crate::permission::GOD_SPACE_ID;
11use crate::query::executor::streaming::transaction_scope::CancelReason;
12use crate::session::{SessionError, SessionResult};
13use crate::storage::{
14 StorageClient, StorageOperationContextOps, StorageSchemaContextOps, StorageSyncContextOps,
15};
16
17use super::GraphService;
18
19#[derive(Debug, Clone)]
21pub struct UserDetail {
22 pub username: String,
23 pub role: Option<String>,
24 pub status: String,
25 pub last_active: Option<String>,
26}
27
28fn format_last_active(millis: Option<i64>) -> Option<String> {
29 millis.and_then(|ts| chrono::DateTime::from_timestamp_millis(ts).map(|dt| dt.to_rfc3339()))
30}
31
32impl<
33 S: StorageClient
34 + StorageSchemaContextOps
35 + StorageSyncContextOps
36 + StorageOperationContextOps
37 + Clone
38 + 'static,
39 > GraphService<S>
40{
41 pub async fn authenticate(
42 &self,
43 username: &str,
44 password: &str,
45 ) -> Result<Arc<crate::session::ClientSession>, String> {
46 if username.is_empty() || password.is_empty() {
47 self.stats_manager
48 .add_value(MetricType::NumAuthFailedSessions);
49 return Err("User name or password cannot be empty".to_string());
50 }
51
52 if self.session_manager.is_out_of_connections().await {
53 self.stats_manager
54 .add_value(MetricType::NumAuthFailedSessions);
55 return Err("More than the maximum number of connections limit".to_string());
56 }
57
58 let auth = self.authenticator.clone();
59 let owned_user = username.to_string();
60 let owned_pass = password.to_string();
61 let auth_result =
62 tokio::task::spawn_blocking(move || auth.authenticate(&owned_user, &owned_pass))
63 .await
64 .map_err(|e| format!("authentication failure: blocking join failed: {}", e))?;
65
66 match auth_result {
67 Ok(_) => {
68 let _ = self.storage.update_last_login(username);
69 let session = self
70 .session_manager
71 .create_session(username.to_string(), "127.0.0.1".to_string())
72 .await
73 .map_err(|e| format!("Creating a session failed: {}", e))?;
74
75 Ok(session)
76 }
77 Err(e) => {
78 self.stats_manager
79 .add_value(MetricType::NumAuthFailedSessions);
80 Err(format!("authentication failure: {}", e))
81 }
82 }
83 }
84
85 pub fn security_config(&self) -> &crate::config::SecurityConfig {
87 &self.security_config
88 }
89
90 pub fn validate_new_password(&self, username: &str, password: &str) -> Result<(), String> {
95 if password.is_empty() {
96 return Err("password cannot be empty".to_string());
97 }
98 self.security_config
99 .password_policy
100 .validate_password(username, password)
101 .map_err(|reason| format!("password does not meet the password policy: {}", reason))?;
102 self.reject_reused_password(username, password)?;
103 Ok(())
104 }
105
106 pub fn password_expired(&self, username: &str) -> bool {
108 let max_age_days = self.security_config.password_policy.max_age_days;
109 if max_age_days == 0 {
110 return false;
111 }
112 match self.storage.get_user(username) {
113 Some(user) => {
114 let now = chrono::Utc::now().timestamp_millis();
115 let age_millis = now.saturating_sub(user.password_changed_at);
116 age_millis > (max_age_days as i64).saturating_mul(86_400_000)
117 }
118 None => false,
119 }
120 }
121
122 fn reject_reused_password(&self, username: &str, password: &str) -> Result<(), String> {
124 if self.security_config.password_policy.history_size == 0 {
125 return Ok(());
126 }
127 let Some(user) = self.storage.get_user(username) else {
128 return Ok(());
129 };
130 if user.reuses_password(password) {
131 return Err("password must not reuse a recent password".to_string());
132 }
133 Ok(())
134 }
135
136 pub fn session_idle_timeout(&self) -> std::time::Duration {
138 self.session_manager.idle_timeout()
139 }
140
141 pub fn must_change_password(&self, username: &str) -> bool {
147 if self.is_auth_disabled() {
148 return false;
149 }
150 if self.password_expired(username) {
151 return true;
152 }
153 if !self.authenticator.config().force_change_default_password {
154 return false;
155 }
156 if username != self.authenticator.config().default_username {
157 return false;
158 }
159 match self.storage.get_user(username) {
160 Some(user) => user.password_changed_at <= user.created_at,
161 None => false,
162 }
163 }
164
165 pub fn is_user_locked(&self, username: &str) -> bool {
167 self.storage
168 .get_user(username)
169 .map(|user| user.is_locked)
170 .unwrap_or(false)
171 }
172
173 pub fn shutdown(&self) {
175 let cancelled = self.query_registry.cancel_all(CancelReason::Shutdown);
176 if !cancelled.is_empty() {
177 log::info!(
178 "Cancelled {} active query(s) during shutdown",
179 cancelled.len()
180 );
181 }
182 self.shared_scheduler.shutdown_shared();
183 log::info!("Shared query scheduler shut down");
184 }
185
186 pub async fn signout(&self, session_id: i64) {
187 if let Some(session) = self.session_manager.find_session(session_id) {
188 if let Some(space_name) = session.space_name() {
189 self.stats_manager
190 .dec_space_metric(&space_name, MetricType::NumActiveQueries);
191 }
192 }
193 self.session_manager.remove_session(session_id).await;
194 }
195
196 pub fn get_session_manager(&self) -> &Arc<crate::session::GraphSessionManager> {
197 &self.session_manager
198 }
199
200 pub fn get_permission_manager(&self) -> &Arc<crate::permission::PermissionManager> {
201 &self.permission_manager
202 }
203
204 pub fn permission_checker(&self) -> crate::permission::PermissionChecker {
205 crate::permission::PermissionChecker::new(
206 (*self.permission_manager).clone(),
207 self.authenticator.config().clone(),
208 )
209 }
210
211 pub fn is_auth_disabled(&self) -> bool {
219 !self.authenticator.config().enable_authorize || self.bootstrap_config.single_user_mode
220 }
221
222 pub fn auth_config(&self) -> &crate::config::AuthConfig {
224 self.authenticator.config()
225 }
226
227 pub fn bootstrap_config(&self) -> &crate::config::BootstrapConfig {
229 &self.bootstrap_config
230 }
231
232 pub fn get_storage_space_id(&self, space_name: &str) -> Option<i64> {
233 self.storage
234 .get_space_id(space_name)
235 .map(|id| id as i64)
236 .ok()
237 }
238
239 pub fn get_stats_manager(&self) -> &Arc<StatsManager> {
240 &self.stats_manager
241 }
242
243 #[cfg(feature = "vector")]
244 pub fn vector_api(&self) -> Option<&Arc<VectorApi>> {
245 self.vector_api.as_ref()
246 }
247
248 pub fn sync_api(&self) -> Option<&Arc<SyncApi>> {
249 self.sync_api.as_ref()
250 }
251
252 pub fn query_manager(&self) -> Arc<QueryManager> {
254 Arc::clone(&self.query_manager)
255 }
256
257 pub async fn list_sessions(&self) -> Vec<crate::session::SessionInfo> {
259 self.session_manager.list_sessions().await
260 }
261
262 pub async fn get_session_info(&self, session_id: i64) -> Option<crate::session::SessionInfo> {
264 self.session_manager.get_session_info(session_id).await
265 }
266
267 pub async fn kill_session(&self, session_id: i64, current_user: &str) -> SessionResult<()> {
269 let is_admin = self.permission_manager.is_admin(current_user);
270
271 self.session_manager
272 .kill_session(session_id, current_user, is_admin)
273 .await
274 }
275
276 pub fn kill_query(&self, session_id: i64, query_id: u32) -> SessionResult<()> {
278 let session = self
279 .session_manager
280 .find_session(session_id)
281 .ok_or(SessionError::session_not_found(session_id))?;
282
283 match session.kill_query(query_id) {
284 Ok(()) => {
285 self.stats_manager.dec_value(MetricType::NumActiveQueries);
286 Ok(())
287 }
288 Err(e) => Err(SessionError::manager_error(e.to_string())),
289 }
290 }
291
292 fn require_admin(&self, caller: &str) -> Result<(), String> {
293 if self.permission_manager.is_admin(caller) {
294 Ok(())
295 } else {
296 Err("admin permission required".to_string())
297 }
298 }
299
300 fn enabled_admin_count_excluding(&self, exclude: &str) -> usize {
301 self.storage
302 .list_users()
303 .into_iter()
304 .filter(|name| name != exclude)
305 .filter(|name| self.permission_manager.is_admin(name))
306 .filter(|name| {
307 self.storage
308 .get_user(name)
309 .map(|user| !user.is_locked)
310 .unwrap_or(false)
311 })
312 .count()
313 }
314
315 pub fn list_users_detailed(&self) -> Vec<UserDetail> {
317 let mut details: Vec<UserDetail> = self
318 .storage
319 .list_users()
320 .into_iter()
321 .map(|username| {
322 let stored = self.storage.get_user(&username);
323 let role = self
324 .permission_manager
325 .highest_role(&username)
326 .map(|role| role.to_string());
327 let status = match stored.as_ref() {
328 Some(user) if user.is_locked => "disabled".to_string(),
329 _ => "enabled".to_string(),
330 };
331 let last_active = stored.and_then(|user| format_last_active(user.last_login_at));
332 UserDetail {
333 username,
334 role,
335 status,
336 last_active,
337 }
338 })
339 .collect();
340 details.sort_by(|a, b| a.username.cmp(&b.username));
341 details
342 }
343
344 pub fn admin_create_user(
346 &self,
347 caller: &str,
348 username: &str,
349 password: &str,
350 ) -> Result<(), String> {
351 self.require_admin(caller)?;
352 if username.is_empty() || password.is_empty() {
353 return Err("username and password cannot be empty".to_string());
354 }
355 if self.storage.user_exists(username) {
356 return Err(format!("user {} already exists", username));
357 }
358 self.validate_new_password(username, password)?;
359 let info = linkrs_core::types::UserInfo::new(username.to_string(), password.to_string())
360 .map_err(|e| e.to_string())?;
361 let mut handle = (*self.storage).clone();
362 handle.create_user(&info).map_err(|e| e.to_string())?;
363 Ok(())
364 }
365
366 pub async fn admin_reset_password(
371 &self,
372 caller: &str,
373 caller_session_id: Option<i64>,
374 username: &str,
375 new_password: &str,
376 ) -> Result<(), String> {
377 self.require_admin(caller)?;
378 if new_password.is_empty() {
379 return Err("password cannot be empty".to_string());
380 }
381 if !self.storage.user_exists(username) {
382 return Err(format!("user {} not found", username));
383 }
384 self.validate_new_password(username, new_password)?;
385 let mut alter = linkrs_core::types::UserAlterInfo::new(username.to_string());
386 alter.new_password = Some(new_password.to_string());
387 alter.history_limit = self.security_config.password_policy.history_size;
388 let mut handle = (*self.storage).clone();
389 handle.alter_user(&alter).map_err(|e| e.to_string())?;
390 self.session_manager
391 .remove_sessions_by_username(username, caller_session_id)
392 .await;
393 Ok(())
394 }
395
396 pub async fn admin_set_user_enabled(
398 &self,
399 caller: &str,
400 caller_session_id: Option<i64>,
401 username: &str,
402 enabled: bool,
403 ) -> Result<(), String> {
404 self.require_admin(caller)?;
405 if username == caller && !enabled {
406 return Err("cannot disable self".to_string());
407 }
408 let stored = self
409 .storage
410 .get_user(username)
411 .ok_or_else(|| format!("user {} not found", username))?;
412 if stored.is_locked == !enabled {
413 return Ok(());
414 }
415 if !enabled
416 && self.permission_manager.is_admin(username)
417 && self.enabled_admin_count_excluding(username) == 0
418 {
419 return Err("cannot disable the last available admin".to_string());
420 }
421 let mut alter = linkrs_core::types::UserAlterInfo::new(username.to_string());
422 alter.is_locked = Some(!enabled);
423 let mut handle = (*self.storage).clone();
424 handle.alter_user(&alter).map_err(|e| e.to_string())?;
425 if !enabled {
426 self.session_manager
427 .remove_sessions_by_username(username, caller_session_id)
428 .await;
429 }
430 Ok(())
431 }
432
433 pub async fn admin_drop_user(&self, caller: &str, username: &str) -> Result<(), String> {
435 self.require_admin(caller)?;
436 if username == caller {
437 return Err("cannot drop self".to_string());
438 }
439 if !self.storage.user_exists(username) {
440 return Err(format!("user {} not found", username));
441 }
442 if self.permission_manager.is_admin(username)
443 && self.enabled_admin_count_excluding(username) == 0
444 {
445 return Err("cannot drop the last available admin".to_string());
446 }
447 let mut handle = (*self.storage).clone();
448 handle.drop_user(username).map_err(|e| e.to_string())?;
449 self.permission_manager.remove_user(username);
450 self.session_manager
451 .remove_sessions_by_username(username, None)
452 .await;
453 Ok(())
454 }
455
456 fn resolve_space_id(&self, space: &str) -> Result<i64, String> {
457 let trimmed = space.trim();
458 if trimmed.is_empty()
459 || trimmed.eq_ignore_ascii_case("global")
460 || trimmed == GOD_SPACE_ID.to_string()
461 {
462 return Ok(GOD_SPACE_ID);
463 }
464 self.storage
465 .get_space_id(trimmed)
466 .map(|id| id as i64)
467 .map_err(|e| e.to_string())
468 }
469
470 pub fn admin_grant_role(
472 &self,
473 caller: &str,
474 username: &str,
475 space: &str,
476 role_name: &str,
477 ) -> Result<(), String> {
478 if caller == username {
479 return Err("cannot modify own role".to_string());
480 }
481 if !self.storage.user_exists(username) {
482 return Err(format!("user {} not found", username));
483 }
484 let role: linkrs_core::RoleType = role_name.parse().map_err(|e: String| e)?;
485 let space_id = self.resolve_space_id(space)?;
486 let operator_role = self
487 .permission_manager
488 .get_role(caller, space_id)
489 .or_else(|| self.permission_manager.get_role(caller, GOD_SPACE_ID))
490 .ok_or_else(|| "permission denied: only Admin or God can manage roles".to_string())?;
491 if !matches!(
492 operator_role,
493 linkrs_core::RoleType::God | linkrs_core::RoleType::Admin | linkrs_core::RoleType::Dba
494 ) {
495 return Err("permission denied: only Admin or God can manage roles".to_string());
496 }
497 if !operator_role.can_grant(role) {
498 return Err(format!("permission denied: cannot grant role {}", role));
499 }
500 let mut handle = (*self.storage).clone();
501 handle
502 .grant_role(username, space_id, role)
503 .map_err(|e| e.to_string())?;
504 self.permission_manager
505 .grant_role(username, space_id, role)
506 .map_err(|e| e.to_string())?;
507 Ok(())
508 }
509
510 pub fn admin_revoke_role(
512 &self,
513 caller: &str,
514 username: &str,
515 space: &str,
516 ) -> Result<(), String> {
517 if caller == username {
518 return Err("cannot modify own role".to_string());
519 }
520 if !self.storage.user_exists(username) {
521 return Err(format!("user {} not found", username));
522 }
523 let space_id = self.resolve_space_id(space)?;
524 let operator_role = self
525 .permission_manager
526 .get_role(caller, space_id)
527 .or_else(|| self.permission_manager.get_role(caller, GOD_SPACE_ID))
528 .ok_or_else(|| "permission denied: only Admin or God can manage roles".to_string())?;
529 if !matches!(
530 operator_role,
531 linkrs_core::RoleType::God | linkrs_core::RoleType::Admin | linkrs_core::RoleType::Dba
532 ) {
533 return Err("permission denied: only Admin or God can manage roles".to_string());
534 }
535 let mut handle = (*self.storage).clone();
536 handle
537 .revoke_role(username, space_id)
538 .map_err(|e| e.to_string())?;
539 self.permission_manager
540 .revoke_role(username, space_id)
541 .map_err(|e| e.to_string())?;
542 Ok(())
543 }
544
545 pub fn sync_role_from_storage(&self, username: &str) {
550 let entries = self.storage.list_user_roles(username);
551 if entries.is_empty() {
552 self.permission_manager.remove_user(username);
553 return;
554 }
555 for (space_id, role) in entries {
556 let _ = self.permission_manager.grant_role(username, space_id, role);
557 }
558 }
559
560 pub(crate) async fn serve_show_sessions(
566 &self,
567 ) -> Result<linkrs_api::api_core::QueryResult, super::error::GraphServiceError> {
568 use linkrs_core::{NullType, Value};
569 let mut sessions = self.list_sessions().await;
570 sessions.sort_by_key(|info| info.session_id);
571 let rows = sessions
572 .iter()
573 .map(|info| {
574 vec![
575 Value::BigInt(info.session_id),
576 Value::string(info.user_name.clone()),
577 info.space_name
578 .clone()
579 .map(Value::string)
580 .unwrap_or(Value::Null(NullType::Null)),
581 Value::BigInt(epoch_secs(info.create_time)),
582 Value::BigInt(epoch_secs(info.last_access_time)),
583 Value::BigInt(info.active_queries as i64),
584 info.graph_addr
585 .clone()
586 .map(Value::string)
587 .unwrap_or(Value::Null(NullType::Null)),
588 ]
589 })
590 .collect();
591 Ok(Self::inspection_result(
592 rows,
593 vec![
594 "session_id".to_string(),
595 "user_name".to_string(),
596 "space_name".to_string(),
597 "created_at".to_string(),
598 "last_active".to_string(),
599 "active_queries".to_string(),
600 "graph_addr".to_string(),
601 ],
602 ))
603 }
604
605 pub(crate) fn serve_show_queries(
612 &self,
613 ) -> Result<linkrs_api::api_core::QueryResult, super::error::GraphServiceError> {
614 use linkrs_core::{NullType, Value};
615 let mut running = self.query_manager.get_running_queries();
616 running.sort_by_key(|info| info.query_id);
617 let rows = running
618 .iter()
619 .map(|info| {
620 vec![
621 Value::BigInt(info.query_id),
622 Value::BigInt(info.session_id),
623 Value::string(info.user_name.clone()),
624 info.space_name
625 .clone()
626 .map(Value::string)
627 .unwrap_or(Value::Null(NullType::Null)),
628 Value::string(query_status_name(info.status)),
629 Value::BigInt(live_duration_ms(info)),
630 Value::string(info.query_text.clone()),
631 ]
632 })
633 .collect();
634 Ok(Self::inspection_result(
635 rows,
636 vec![
637 "query_id".to_string(),
638 "session_id".to_string(),
639 "user_name".to_string(),
640 "space_name".to_string(),
641 "status".to_string(),
642 "duration_ms".to_string(),
643 "query".to_string(),
644 ],
645 ))
646 }
647
648 pub(crate) fn serve_kill_query(
655 &self,
656 target_session_id: i64,
657 query_id: i64,
658 ) -> Result<linkrs_api::api_core::QueryResult, super::error::GraphServiceError> {
659 use linkrs_core::Value;
660 let target = self
661 .session_manager
662 .find_session(target_session_id)
663 .ok_or_else(|| {
664 super::error::GraphServiceError::new(format!(
665 "SessionId[{}] does not exist",
666 target_session_id
667 ))
668 })?;
669 let info = self.query_manager.get_query(query_id).ok_or_else(|| {
670 super::error::GraphServiceError::new(format!(
671 "ExecutionPlanId[{}] does not exist",
672 query_id
673 ))
674 })?;
675 if info.session_id != target_session_id {
676 return Err(super::error::GraphServiceError::new(format!(
677 "ExecutionPlanId[{}] does not exist in Session[{}]",
678 query_id, target_session_id
679 )));
680 }
681 if info.status != linkrs_query::query_manager::QueryStatus::Running {
682 return Err(super::error::GraphServiceError::new(format!(
683 "ExecutionPlanId[{}] is not running (status: {})",
684 query_id,
685 query_status_name(info.status)
686 )));
687 }
688 self.query_manager
689 .kill_query(query_id)
690 .map_err(|e| super::error::GraphServiceError::new(e.to_string()))?;
691 if let Ok(streaming_id) = u32::try_from(query_id) {
692 let _ = target.kill_query(streaming_id);
693 }
694 Ok(Self::inspection_result(
695 vec![vec![
696 Value::BigInt(target_session_id),
697 Value::BigInt(query_id),
698 Value::string("killed".to_string()),
699 ]],
700 vec![
701 "session_id".to_string(),
702 "query_id".to_string(),
703 "status".to_string(),
704 ],
705 ))
706 }
707
708 pub(crate) fn assign_tracked_query_id(
711 &self,
712 session: &Arc<crate::session::ClientSession>,
713 username: &str,
714 stmt: &str,
715 ) -> Option<i64> {
716 let id = self
717 .next_query_id
718 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
719 let tracked: i64 = id as i64;
720 self.query_manager
721 .register_query_with_id(
722 tracked,
723 session.id(),
724 username.to_string(),
725 session.space().map(|space| space.name),
726 stmt.to_string(),
727 )
728 .ok()?;
729 Some(tracked)
730 }
731
732 pub(crate) fn finish_tracked_query(&self, query_id: Option<i64>, success: bool) {
734 if let Some(tracked) = query_id {
735 if success {
736 let _ = self.query_manager.finish_query(tracked);
737 } else {
738 let _ = self.query_manager.fail_query(tracked);
739 }
740 self.query_manager.cleanup_finished_queries(1000);
741 }
742 }
743
744 pub(crate) async fn execute_session_inspection(
750 &self,
751 session_id: i64,
752 stmt: &str,
753 parsed: crate::query::parser::ast::Stmt,
754 ) -> Result<linkrs_api::api_core::QueryResult, super::error::GraphServiceError> {
755 let session = self
756 .session_manager
757 .find_session(session_id)
758 .ok_or_else(|| {
759 super::error::GraphServiceError::new(format!("Invalid session ID: {}", session_id))
760 })?;
761 let space_id = session.space().map(|space| space.id as i64).unwrap_or(0);
762 let context = super::context::QueryExecutionContext {
763 stmt,
764 parsed_ast: None,
765 space_id,
766 parameters: None,
767 session_variables: None,
768 };
769 let username = session.user();
770 self.check_query_permission(&username, &context)?;
771 match parsed {
772 crate::query::parser::ast::Stmt::ShowSessions(_) => self.serve_show_sessions().await,
773 crate::query::parser::ast::Stmt::ShowQueries(_) => self.serve_show_queries(),
774 crate::query::parser::ast::Stmt::KillQuery(kill) => {
775 self.serve_kill_query(kill.session_id, kill.plan_id)
776 }
777 _ => Err(super::error::GraphServiceError::new(
778 "Statement is not a session inspection statement".to_string(),
779 )),
780 }
781 }
782
783 fn inspection_result(
784 rows: Vec<Vec<linkrs_core::Value>>,
785 columns: Vec<String>,
786 ) -> linkrs_api::api_core::QueryResult {
787 let returned = rows.len() as u64;
788 linkrs_api::api_core::QueryResult::new(
789 linkrs_query::executor::base::ExecutionResult::DataSet {
790 data: linkrs_core::DataSet::from_rows(rows, columns),
791 },
792 linkrs_api::api_core::types::ExecutionMetadata {
793 rows_returned: returned,
794 ..Default::default()
795 },
796 )
797 }
798}
799
800fn epoch_secs(time: std::time::SystemTime) -> i64 {
801 time.duration_since(std::time::UNIX_EPOCH)
802 .map(|elapsed| elapsed.as_secs() as i64)
803 .unwrap_or(0)
804}
805
806fn query_status_name(status: linkrs_query::query_manager::QueryStatus) -> String {
807 match status {
808 linkrs_query::query_manager::QueryStatus::Running => "running".to_string(),
809 linkrs_query::query_manager::QueryStatus::Finished => "finished".to_string(),
810 linkrs_query::query_manager::QueryStatus::Failed => "failed".to_string(),
811 linkrs_query::query_manager::QueryStatus::Killed => "killed".to_string(),
812 }
813}
814
815fn live_duration_ms(info: &linkrs_query::query_manager::QueryInfo) -> i64 {
816 if let Some(elapsed) = info.duration_ms {
817 return elapsed;
818 }
819 std::time::SystemTime::now()
820 .duration_since(info.start_time)
821 .map(|elapsed| elapsed.as_millis() as i64)
822 .unwrap_or(0)
823}