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