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_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
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_password = random_password(30);
93
94 initialize_root_role(&db_docbox, ROOT_DATABASE_ROLE_NAME, &root_password).await?;
96 tracing::info!("created root user");
97
98 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_root(db_provider, None)
110 .await
111 .map_err(InitializeError::MigrateRoot)?;
112
113 Ok(())
114}
115
116#[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 initialize_root_role_aws_iam(&db_docbox, ROOT_DATABASE_ROLE_NAME).await?;
124 tracing::info!("created root user");
125
126 migrate_root(db_provider, None)
128 .await
129 .map_err(InitializeError::MigrateRoot)?;
130
131 Ok(())
132}
133
134#[tracing::instrument(skip(db_provider))]
136pub async fn initialize_root_database(
137 db_provider: &impl DatabaseProvider,
138) -> Result<DbPool, InitializeError> {
139 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 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 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#[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 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#[tracing::instrument(skip(db))]
182pub async fn initialize_root_role_aws_iam(
183 db: &DbPool,
184 root_role_name: &str,
185) -> Result<(), InitializeError> {
186 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#[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}