Skip to main content

systemprompt_cli/commands/admin/session/
login_helpers.rs

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