docbox_management/root/
initialize.rs1use crate::{
2 database::{DatabaseProvider, close_pool_on_drop},
3 password::random_password,
4 root::migrate_root::{MigrateRootError, migrate_root},
5};
6use docbox_database::{
7 DbErr, DbPool, DbResult, ROOT_DATABASE_NAME,
8 create::{create_database, create_restricted_role},
9 models::tenant::Tenant,
10 sqlx::types::Uuid,
11 utils::DatabaseErrorExt,
12};
13use docbox_secrets::{SecretManager, SecretManagerError};
14use serde_json::json;
15use thiserror::Error;
16
17const 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#[tracing::instrument(skip(db_provider))]
49pub async fn is_initialized(db_provider: &impl DatabaseProvider) -> DbResult<bool> {
50 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 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 if let Err(error) = Tenant::find_by_id(&db, Uuid::nil(), "__DO_NOT_USE").await {
69 if error.is_table_does_not_exist() {
70 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#[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_role_name = "docbox_config_api";
93 let root_password = random_password(30);
94
95 initialize_root_role(&db_docbox, root_role_name, &root_password).await?;
97 tracing::info!("created root user");
98
99 initialize_root_secret(secrets, root_secret_name, root_role_name, &root_password).await?;
101 tracing::info!("created database secret");
102
103 migrate_root(db_provider, None)
105 .await
106 .map_err(InitializeError::MigrateRoot)?;
107
108 Ok(())
109}
110
111#[tracing::instrument(skip(db_provider))]
113pub async fn initialize_root_database(
114 db_provider: &impl DatabaseProvider,
115) -> Result<DbPool, InitializeError> {
116 let db_root = db_provider
118 .connect(TEMP_SETUP_DATABASE)
119 .await
120 .map_err(InitializeError::ConnectPostgres)?;
121
122 let _guard = close_pool_on_drop(&db_root);
123
124 if let Err(err) = create_database(&db_root, ROOT_DATABASE_NAME).await
126 && !err
127 .as_database_error()
128 .is_some_and(|err| err.code().is_some_and(|code| code.to_string().eq("42P04")))
129 {
130 return Err(InitializeError::CreateRootDatabase(err));
131 }
132
133 let db_docbox = db_provider
135 .connect(ROOT_DATABASE_NAME)
136 .await
137 .map_err(InitializeError::ConnectRootDatabase)?;
138
139 Ok(db_docbox)
140}
141
142#[tracing::instrument(skip(db, root_role_password))]
145pub async fn initialize_root_role(
146 db: &DbPool,
147 root_role_name: &str,
148 root_role_password: &str,
149) -> Result<(), InitializeError> {
150 create_restricted_role(db, ROOT_DATABASE_NAME, root_role_name, root_role_password)
152 .await
153 .map_err(InitializeError::CreateRootRole)?;
154
155 Ok(())
156}
157
158#[tracing::instrument(skip(secrets, root_role_password))]
160pub async fn initialize_root_secret(
161 secrets: &SecretManager,
162 root_secret_name: &str,
163 root_role_name: &str,
164 root_role_password: &str,
165) -> Result<(), InitializeError> {
166 let secret_value = serde_json::to_string(&json!({
167 "username": root_role_name,
168 "password": root_role_password
169 }))
170 .map_err(InitializeError::SerializeSecret)?;
171
172 secrets
173 .set_secret(root_secret_name, &secret_value)
174 .await
175 .map_err(InitializeError::CreateRootSecret)?;
176
177 Ok(())
178}