systemprompt_cli/commands/admin/session/
login_helpers.rs1use std::path::Path;
7use std::sync::Arc;
8
9use anyhow::{Context, Result};
10
11use systemprompt_cloud::{
12 CliSession, CredentialsBootstrap, SessionIdentity, SessionKey, SessionStore,
13};
14use systemprompt_database::DbPool;
15use systemprompt_identifiers::{ContextId, SessionId, UserId};
16use systemprompt_logging::CliService;
17use systemprompt_users::{User, UserRepository, UserRole, UserService};
18
19use super::login::{LoginArgs, LoginOutput};
20use crate::shared::CommandOutput;
21
22pub(super) async fn try_use_existing_session(
23 sessions_dir: &Path,
24 session_key: &SessionKey,
25 args: &LoginArgs,
26 db_pool: &DbPool,
27) -> Result<Option<CommandOutput>> {
28 let mut store = SessionStore::load_or_create(sessions_dir)?;
29
30 let Some(session) = store.get_valid_session(session_key) else {
31 if !args.token_only {
32 CliService::info("No valid session found, creating new session...");
33 }
34 return Ok(None);
35 };
36
37 let session_id = session.session_id.clone();
38 let user_id = session.user_id.clone();
39 let user_email = session.user_email.to_string();
40 let session_token = session.session_token.clone();
41
42 let user_service = UserService::new(Arc::new(UserRepository::new(db_pool)?));
43 let exists = user_service
44 .session_exists(&session_id)
45 .await
46 .unwrap_or(false);
47
48 if !exists {
49 if !args.token_only {
50 CliService::info(
51 "Cached session is stale (not found in database), creating new session...",
52 );
53 }
54 store.remove_session(session_key);
55 store.save(sessions_dir)?;
56 return Ok(None);
57 }
58
59 let output = LoginOutput {
60 status: "existing".to_owned(),
61 user_id,
62 email: user_email,
63 session_id,
64 expires_in_hours: 24,
65 };
66
67 if args.token_only {
68 CliService::output(session_token.as_str());
69 return Ok(Some(
70 CommandOutput::card_value("Admin Session", &output).with_skip_render(),
71 ));
72 }
73
74 CliService::success("Using existing valid session");
75 Ok(Some(CommandOutput::card_value("Admin Session", &output)))
76}
77
78pub async fn fetch_admin_user(
79 db_pool: &DbPool,
80 admin_name: &str,
81 is_cloud_profile: bool,
82 email_override: Option<&str>,
83) -> Result<User> {
84 let user_service = UserService::new(Arc::new(UserRepository::new(db_pool)?));
85
86 if let Some(user) = user_service
87 .find_by_name(admin_name)
88 .await
89 .context("Failed to fetch admin user")?
90 {
91 if !user.is_admin() {
92 anyhow::bail!(
93 "User '{}' exists but is not an admin. Contact your administrator.",
94 admin_name
95 );
96 }
97 return Ok(user);
98 }
99
100 if !is_cloud_profile {
101 anyhow::bail!(
102 "Local admin user '{}' not found. Run 'systemprompt admin bootstrap --email \
103 <email>' to create it.",
104 admin_name
105 );
106 }
107
108 let email = match email_override {
109 Some(e) => e.to_owned(),
110 None => cloud_credentials_email().await?,
111 };
112
113 CliService::info(&format!(
114 "Admin user '{}' not found, creating it for cloud profile...",
115 admin_name
116 ));
117
118 let user = user_service
119 .create(admin_name, &email, None, None)
120 .await
121 .context("Failed to create user")?;
122
123 let user = user_service
124 .assign_roles(&user.id, &[UserRole::Admin.as_str().to_owned()])
125 .await
126 .context("Failed to assign admin role")?;
127
128 CliService::success(&format!("Created admin user: {admin_name} <{email}>"));
129 Ok(user)
130}
131
132async fn cloud_credentials_email() -> Result<String> {
133 CredentialsBootstrap::try_init()
134 .await
135 .context("Failed to initialize credentials")?;
136 let creds = CredentialsBootstrap::require().map_err(|_e| {
137 anyhow::anyhow!(
138 "No credentials found. Run 'systemprompt cloud auth login' first to authenticate."
139 )
140 })?;
141 Ok(creds.user_email.as_str().to_owned())
142}
143
144pub(super) struct SessionStoreParams<'a> {
145 pub sessions_dir: &'a Path,
146 pub session_key: &'a SessionKey,
147 pub profile_path: &'a str,
148 pub session_token: systemprompt_identifiers::SessionToken,
149 pub session_id: SessionId,
150 pub context_id: ContextId,
151 pub user_id: UserId,
152 pub user_email: &'a str,
153 pub user_type: systemprompt_models::auth::UserType,
154}
155
156pub(super) fn save_session_to_store(params: SessionStoreParams<'_>) -> Result<()> {
157 let SessionStoreParams {
158 sessions_dir,
159 session_key,
160 profile_path,
161 session_token,
162 session_id,
163 context_id,
164 user_id,
165 user_email,
166 user_type,
167 } = params;
168 let mut store = SessionStore::load_or_create(sessions_dir)?;
169
170 let profile_dir = Path::new(profile_path).parent();
171 let profile_name_str = profile_dir
172 .and_then(|d| d.file_name())
173 .and_then(|n| n.to_str())
174 .context("Invalid profile path")?;
175
176 let profile_name = systemprompt_identifiers::ProfileName::try_new(profile_name_str)
177 .map_err(|e| anyhow::anyhow!("Invalid profile name: {}", e))?;
178
179 let email = systemprompt_identifiers::Email::try_new(user_email)
180 .map_err(|e| anyhow::anyhow!("Invalid email: {}", e))?;
181
182 let cli_session = CliSession::builder(
183 profile_name,
184 session_token,
185 session_id,
186 context_id,
187 SessionIdentity::new(user_id, email, user_type),
188 )
189 .with_session_key(session_key)
190 .with_profile_path(profile_path)
191 .build();
192
193 store.upsert_session(session_key, cli_session);
194 store.set_active_with_profile(session_key, profile_name_str);
195 store.save(sessions_dir)?;
196
197 tracing::debug!(sessions_dir = %sessions_dir.display(), "session saved to index.json");
198 Ok(())
199}