Skip to main content

systemprompt_users/repository/user/
service_account.rs

1//! Service-account rows: the non-human identity a workflow acts as.
2//!
3//! A service account is created active with the plain `user` role and no
4//! interactive credential. Its credentials are API keys and in-process JWTs;
5//! every interactive login path refuses it by `account_kind`.
6//!
7//! Copyright (c) systemprompt.io — Business Source License 1.1.
8//! See <https://systemprompt.io> for licensing details.
9
10use chrono::Utc;
11use systemprompt_identifiers::UserId;
12
13use crate::error::{Result, UserError};
14use crate::models::{AccountKind, User, UserRole, UserRow, UserStatus, normalise_email};
15use crate::repository::UserRepository;
16
17#[derive(Debug, Clone)]
18pub struct NewServiceAccount {
19    pub name: String,
20    pub email: String,
21    pub created_by: UserId,
22}
23
24impl UserRepository {
25    pub async fn insert_service_account(&self, account: NewServiceAccount) -> Result<User> {
26        let id = UserId::generate();
27        let email = normalise_email(&account.email);
28        let user = sqlx::query_as!(
29            UserRow,
30            r#"
31            INSERT INTO users (
32                id, name, email, display_name, status, email_verified, roles, is_bot,
33                account_kind, created_at, updated_at
34            )
35            VALUES ($1, $2, $3, $2, $4, true, ARRAY[$5]::TEXT[], false, $6, $7, $7)
36            RETURNING id, name, email, full_name, display_name, status, email_verified,
37                      roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
38            "#,
39            id.as_str(),
40            account.name,
41            email,
42            UserStatus::Active.as_str(),
43            UserRole::User.as_str(),
44            AccountKind::Service.as_str(),
45            Utc::now()
46        )
47        .fetch_one(&*self.write_pool)
48        .await
49        .map_err(UserError::from)
50        .and_then(User::try_from)?;
51        tracing::info!(
52            user_id = %user.id,
53            created_by = %account.created_by,
54            "service account created"
55        );
56        Ok(user)
57    }
58
59    pub async fn list_service_accounts(&self) -> Result<Vec<User>> {
60        let rows = sqlx::query_as!(
61            UserRow,
62            r#"
63            SELECT id, name, email, full_name, display_name, status, email_verified,
64                   roles, avatar_url, is_bot, is_scanner, created_at, updated_at, account_kind
65            FROM users
66            WHERE account_kind = $1 AND status != $2
67            ORDER BY created_at
68            "#,
69            AccountKind::Service.as_str(),
70            UserStatus::Deleted.as_str()
71        )
72        .fetch_all(&*self.pool)
73        .await?;
74        rows.into_iter().map(User::try_from).collect()
75    }
76}