Skip to main content

systemprompt_cli/session/creation/
mod.rs

1//! CLI session creation for local and cloud-tenant profiles.
2//!
3//! Resolves the user, mints a token and records session context. Local sessions
4//! resolve the configured administrator by username without provisioning a
5//! user; cloud and tenant sessions resolve identities from credential email
6//! addresses.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11pub mod helpers;
12
13use anyhow::{Context, Result};
14use systemprompt_cloud::{CliSession, CloudCredentials, SessionKey};
15use systemprompt_logging::CliService;
16use systemprompt_models::Profile;
17
18use super::api::create_local_session_row;
19use super::resolution::ProfileContext;
20use crate::CliConfig;
21use helpers::{
22    SessionComponents, build_cli_session, connect_database, create_cli_context,
23    generate_admin_token, load_secrets, resolve_admin_with_fallback,
24    resolve_credentialed_user_email, resolve_local_admin, resolve_tenant_admin_with_fallback,
25};
26
27pub(super) async fn create_local_session(
28    profile: &Profile,
29    profile_ctx: &ProfileContext<'_>,
30    session_key: &SessionKey,
31    config: &CliConfig,
32    session_email_hint: Option<&str>,
33) -> Result<CliSession> {
34    profile
35        .validate()
36        .with_context(|| format!("Failed to validate profile: {}", profile_ctx.name))?;
37
38    let secrets = load_secrets().context("Failed to load secrets")?;
39
40    if config.is_interactive() {
41        CliService::info("Creating local CLI session...");
42        CliService::key_value("Profile", profile_ctx.name);
43    }
44
45    let db_pool = connect_database(&secrets).await?;
46
47    let admin_user = if profile.is_local_trial() && session_email_hint.is_none() {
48        resolve_local_admin(&db_pool, &profile.system_admin.username).await?
49    } else {
50        let user_email = resolve_credentialed_user_email(session_email_hint).await?;
51        resolve_admin_with_fallback(&db_pool, user_email.as_str(), session_email_hint, "local")
52            .await?
53    };
54
55    if config.is_interactive() {
56        CliService::key_value("User", &admin_user.email);
57    }
58
59    let session_id = create_local_session_row(
60        &db_pool,
61        &admin_user.id,
62        chrono::Duration::hours(crate::session::api::DEFAULT_CLI_SESSION_HOURS),
63    )
64    .await
65    .context("Failed to create local CLI session row in the database")?;
66
67    let context_id =
68        create_cli_context(db_pool, &admin_user, &session_id, profile_ctx.name).await?;
69    let session_token = generate_admin_token(&profile.security.issuer, &admin_user, &session_id)?;
70
71    if config.is_interactive() {
72        CliService::success("Local session created");
73        CliService::key_value("Session ID", session_id.as_str());
74        CliService::key_value("Context ID", context_id.as_str());
75    }
76
77    build_cli_session(
78        profile_ctx,
79        session_key,
80        SessionComponents {
81            session_token,
82            session_id,
83            context_id,
84        },
85        &admin_user,
86        &profile.security.issuer,
87    )
88}
89
90pub(super) struct TenantSessionParams<'a> {
91    pub creds: &'a CloudCredentials,
92    pub profile: &'a Profile,
93    pub profile_ctx: &'a ProfileContext<'a>,
94    pub session_key: &'a SessionKey,
95    pub config: &'a CliConfig,
96    pub session_email_hint: Option<&'a str>,
97}
98
99pub(super) async fn create_session_for_tenant(
100    params: TenantSessionParams<'_>,
101) -> Result<CliSession> {
102    let TenantSessionParams {
103        creds,
104        profile,
105        profile_ctx,
106        session_key,
107        config,
108        session_email_hint,
109    } = params;
110    profile
111        .validate()
112        .with_context(|| format!("Failed to validate profile: {}", profile_ctx.name))?;
113
114    let user_email = session_email_hint.unwrap_or(creds.user_email.as_str());
115    let secrets = load_secrets().context("Failed to load secrets")?;
116
117    if config.is_interactive() {
118        CliService::info("Creating CLI session...");
119        CliService::key_value("Profile", profile_ctx.name);
120        CliService::key_value("User", user_email);
121    }
122
123    let db_pool = connect_database(&secrets).await?;
124    let admin_user =
125        resolve_tenant_admin_with_fallback(&db_pool, creds, user_email, session_email_hint).await?;
126
127    let session_id = create_local_session_row(
128        &db_pool,
129        &admin_user.id,
130        chrono::Duration::hours(crate::session::api::DEFAULT_CLI_SESSION_HOURS),
131    )
132    .await
133    .context("Failed to create local tenant CLI session row in the database")?;
134
135    let context_id =
136        create_cli_context(db_pool, &admin_user, &session_id, profile_ctx.name).await?;
137    let session_token = generate_admin_token(&profile.security.issuer, &admin_user, &session_id)?;
138
139    if config.is_interactive() {
140        CliService::success("Session created");
141        CliService::key_value("Session ID", session_id.as_str());
142        CliService::key_value("Context ID", context_id.as_str());
143    }
144
145    build_cli_session(
146        profile_ctx,
147        session_key,
148        SessionComponents {
149            session_token,
150            session_id,
151            context_id,
152        },
153        &admin_user,
154        &profile.security.issuer,
155    )
156}