Skip to main content

docbox_management/root/
initialize.rs

1use crate::{
2    database::{DatabaseProvider, close_pool_on_drop},
3    password::random_password,
4    root::migrate_root::{MigrateRootError, migrate_root},
5};
6use docbox_core::database::{
7    DbErr, DbPool, DbResult, ROOT_DATABASE_NAME, ROOT_DATABASE_ROLE_NAME,
8    create::{create_database, create_restricted_role, create_restricted_role_aws_iam},
9    models::tenant::Tenant,
10    sqlx::types::Uuid,
11    utils::DatabaseErrorExt,
12};
13use docbox_core::secrets::{SecretManager, SecretManagerError};
14use serde_json::json;
15use thiserror::Error;
16
17/// Temporary database to connect to while setting up the other databases
18const TEMP_SETUP_DATABASE: &str = "postgres";
19
20#[derive(Debug, Error)]
21pub enum InitializeError {
22    #[error("error connecting to 'postgres' database: {0}")]
23    ConnectPostgres(DbErr),
24
25    #[error("error creating root database: {0}")]
26    CreateRootDatabase(DbErr),
27
28    #[error("error connecting to root database: {0}")]
29    ConnectRootDatabase(DbErr),
30
31    #[error("error migrating root database: {0}")]
32    MigrateRoot(MigrateRootError),
33
34    #[error("error creating root database role: {0}")]
35    CreateRootRole(DbErr),
36
37    #[error("error serializing root secret: {0}")]
38    SerializeSecret(serde_json::Error),
39
40    #[error("failed to create root secret: {0}")]
41    CreateRootSecret(SecretManagerError),
42
43    #[error("error creating tenants table: {0}")]
44    CreateTenantsTable(DbErr),
45}
46
47/// Check if the root database is initialized
48#[tracing::instrument(skip(db_provider))]
49pub async fn is_initialized(db_provider: &impl DatabaseProvider) -> DbResult<bool> {
50    // First check if the root database exists
51    let db = match db_provider.connect(ROOT_DATABASE_NAME).await {
52        Ok(value) => value,
53        Err(error) => {
54            if error.is_database_does_not_exist() {
55                // Database is not setup, server is not initialized
56                return Ok(false);
57            }
58
59            return Err(error);
60        }
61    };
62
63    tracing::debug!("root is initialized");
64
65    let _guard = close_pool_on_drop(&db);
66
67    // Then query the table for a non-existent tenant to make sure its setup correctly
68    if let Err(error) = Tenant::find_by_id(&db, Uuid::nil(), "__DO_NOT_USE").await {
69        if error.is_table_does_not_exist() {
70            // Database is not setup, server is not initialized
71            return Ok(false);
72        }
73
74        return Err(error);
75    }
76
77    tracing::debug!("tenant table is setup");
78
79    Ok(true)
80}
81
82/// Initializes the root database of docbox using a secret based authentication
83#[tracing::instrument(skip(db_provider, secrets))]
84pub async fn initialize(
85    db_provider: &impl DatabaseProvider,
86    secrets: &SecretManager,
87    root_secret_name: &str,
88) -> Result<(), InitializeError> {
89    let db_docbox = initialize_root_database(db_provider).await?;
90    let _guard = close_pool_on_drop(&db_docbox);
91
92    let root_password = random_password(30);
93
94    // Setup the restricted root db role
95    initialize_root_role(&db_docbox, ROOT_DATABASE_ROLE_NAME, &root_password).await?;
96    tracing::info!("created root user");
97
98    // Setup the secret to store the role credentials
99    initialize_root_secret(
100        secrets,
101        root_secret_name,
102        ROOT_DATABASE_ROLE_NAME,
103        &root_password,
104    )
105    .await?;
106    tracing::info!("created database secret");
107
108    // Migrate the root database
109    migrate_root(db_provider, None)
110        .await
111        .map_err(InitializeError::MigrateRoot)?;
112
113    Ok(())
114}
115
116/// Initializes the root database of docbox using IAM based authentication
117#[tracing::instrument(skip(db_provider))]
118pub async fn initialize_iam(db_provider: &impl DatabaseProvider) -> Result<(), InitializeError> {
119    let db_docbox = initialize_root_database(db_provider).await?;
120    let _guard = close_pool_on_drop(&db_docbox);
121
122    // Setup the restricted root db role
123    initialize_root_role_aws_iam(&db_docbox, ROOT_DATABASE_ROLE_NAME).await?;
124    tracing::info!("created root user");
125
126    // Migrate the root database
127    migrate_root(db_provider, None)
128        .await
129        .map_err(InitializeError::MigrateRoot)?;
130
131    Ok(())
132}
133
134/// Initializes the root database used by docbox
135#[tracing::instrument(skip(db_provider))]
136pub async fn initialize_root_database(
137    db_provider: &impl DatabaseProvider,
138) -> Result<DbPool, InitializeError> {
139    // Connect to the root postgres database
140    let db_root = db_provider
141        .connect(TEMP_SETUP_DATABASE)
142        .await
143        .map_err(InitializeError::ConnectPostgres)?;
144
145    let _guard = close_pool_on_drop(&db_root);
146
147    // Create the tenant database
148    if let Err(err) = create_database(&db_root, ROOT_DATABASE_NAME).await
149        && !err.is_database_exists()
150    {
151        return Err(InitializeError::CreateRootDatabase(err));
152    }
153
154    // Connect to the docbox database
155    let db_docbox = db_provider
156        .connect(ROOT_DATABASE_NAME)
157        .await
158        .map_err(InitializeError::ConnectRootDatabase)?;
159
160    Ok(db_docbox)
161}
162
163/// Initializes a root role that the docbox API will use when accessing
164/// the tenants table
165#[tracing::instrument(skip(db, root_role_password))]
166pub async fn initialize_root_role(
167    db: &DbPool,
168    root_role_name: &str,
169    root_role_password: &str,
170) -> Result<(), InitializeError> {
171    // Setup the restricted root db role
172    create_restricted_role(db, ROOT_DATABASE_NAME, root_role_name, root_role_password)
173        .await
174        .map_err(InitializeError::CreateRootRole)?;
175
176    Ok(())
177}
178
179/// Initializes a root IAM accessible role that the docbox API will use when accessing
180/// the tenants table
181#[tracing::instrument(skip(db))]
182pub async fn initialize_root_role_aws_iam(
183    db: &DbPool,
184    root_role_name: &str,
185) -> Result<(), InitializeError> {
186    // Setup the restricted root db role
187    create_restricted_role_aws_iam(db, ROOT_DATABASE_NAME, root_role_name)
188        .await
189        .map_err(InitializeError::CreateRootRole)?;
190
191    Ok(())
192}
193
194/// Initializes and stores the secret for the root database access
195#[tracing::instrument(skip(secrets, root_role_password))]
196pub async fn initialize_root_secret(
197    secrets: &SecretManager,
198    root_secret_name: &str,
199    root_role_name: &str,
200    root_role_password: &str,
201) -> Result<(), InitializeError> {
202    let secret_value = serde_json::to_string(&json!({
203        "username": root_role_name,
204        "password": root_role_password
205    }))
206    .map_err(InitializeError::SerializeSecret)?;
207
208    secrets
209        .set_secret(root_secret_name, &secret_value)
210        .await
211        .map_err(InitializeError::CreateRootSecret)?;
212
213    Ok(())
214}