Skip to main content

systemprompt_cli/session/
api.rs

1//! Local session and JWT minting for CLI commands.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use anyhow::{Context, Result};
7use chrono::Duration;
8use std::sync::Arc;
9use systemprompt_analytics::AnalyticsService;
10use systemprompt_database::DbPool;
11use systemprompt_identifiers::{SessionId, SessionSource, UserId};
12use systemprompt_oauth::services::SessionCreationService;
13use systemprompt_traits::{AnalyticsProvider, SessionAnalytics, UserProvider};
14use systemprompt_users::UserService;
15
16/// Lifetime of a CLI session row and of the admin token that names it. The two
17/// must agree, or the operator sees a mid-session 401.
18pub const DEFAULT_CLI_SESSION_HOURS: i64 = 24;
19
20// Why: the public `POST /oauth/session` endpoint must not accept a
21// caller-supplied `user_id` — doing so allows arbitrary admin-JWT issuance
22// against any known user UUID on a public route. The CLI is colocated with
23// the database and holds the JWT signing secret, so it mints session rows
24// (and the JWTs above) locally instead of round-tripping through the public
25// HTTP endpoint. It goes through `SessionCreationService` rather than the
26// repository so every `user_sessions` row in the deployment is written by one
27// code path, whatever minted it.
28pub async fn create_local_session_row(
29    db_pool: &DbPool,
30    user: &UserId,
31    ttl: Duration,
32) -> Result<SessionId> {
33    let analytics: Arc<dyn AnalyticsProvider> = Arc::new(
34        AnalyticsService::new(db_pool, None, None)
35            .context("Failed to construct analytics service")?,
36    );
37    let users: Arc<dyn UserProvider> =
38        Arc::new(UserService::new(db_pool).context("Failed to construct user service")?);
39
40    SessionCreationService::new(analytics, users)
41        .create_authenticated_session_with_ttl(
42            user,
43            &SessionAnalytics::default(),
44            SessionSource::Cli,
45            ttl,
46        )
47        .await
48        .context("Failed to insert CLI session row")
49}