docbox_management/root/
initialize.rs1use docbox_database::{
2 DbErr, DbPool, DbResult, ROOT_DATABASE_NAME,
3 create::{create_database, create_restricted_role, create_tenants_table},
4 models::tenant::Tenant,
5 sqlx::types::Uuid,
6};
7use docbox_secrets::AppSecretManager;
8use serde_json::json;
9use thiserror::Error;
10
11use crate::{database::DatabaseProvider, password::random_password};
12
13pub async fn is_initialized(db_provider: &impl DatabaseProvider) -> DbResult<bool> {
15 let db = match db_provider.connect(ROOT_DATABASE_NAME).await {
17 Ok(value) => value,
18 Err(error) => {
19 if !error.as_database_error().is_some_and(|error| {
20 error.code().is_some_and(|code| {
21 code.to_string().eq("3D000" )
22 })
23 }) {
24 return Err(error);
25 }
26
27 return Ok(false);
29 }
30 };
31
32 if let Err(error) = Tenant::find_by_id(&db, Uuid::nil(), "__DO_NOT_USE").await {
34 if !error.as_database_error().is_some_and(|error| {
35 error.code().is_some_and(|code| {
36 code.to_string().eq("42P01" )
37 })
38 }) {
39 return Err(error);
40 }
41
42 return Ok(false);
44 }
45
46 Ok(true)
47}
48
49#[derive(Debug, Error)]
50pub enum InitializeError {
51 #[error("error connecting to 'postgres' database: {0}")]
52 ConnectPostgres(DbErr),
53
54 #[error("error creating root database: {0}")]
55 CreateRootDatabase(DbErr),
56
57 #[error("error connecting to root database: {0}")]
58 ConnectRootDatabase(DbErr),
59
60 #[error("error creating root database role: {0}")]
61 CreateRootRole(DbErr),
62
63 #[error("error serializing root secret: {0}")]
64 SerializeSecret(serde_json::Error),
65
66 #[error("failed to create root secret: {0}")]
67 CreateRootSecret(anyhow::Error),
68
69 #[error("error creating tenants table: {0}")]
70 CreateTenantsTable(DbErr),
71}
72
73const TEMP_SETUP_DATABASE: &str = "postgres";
75
76pub async fn initialize(
78 db_provider: &impl DatabaseProvider,
79 secrets: &AppSecretManager,
80 root_secret_name: &str,
81) -> Result<(), InitializeError> {
82 let db_docbox = initialize_root_database(db_provider).await?;
83
84 let root_role_name = "docbox_config_api";
85 let root_password = random_password(30);
86
87 initialize_root_role(&db_docbox, root_role_name, &root_password).await?;
89 tracing::info!("created root user");
90
91 initialize_root_secret(secrets, root_secret_name, root_role_name, &root_password).await?;
93 tracing::info!("created database secret");
94
95 create_tenants_table(&db_docbox)
97 .await
98 .map_err(InitializeError::CreateTenantsTable)?;
99
100 Ok(())
101}
102
103pub async fn initialize_root_database(
105 db_provider: &impl DatabaseProvider,
106) -> Result<DbPool, InitializeError> {
107 let db_root = db_provider
109 .connect(TEMP_SETUP_DATABASE)
110 .await
111 .map_err(InitializeError::ConnectPostgres)?;
112
113 if let Err(err) = create_database(&db_root, ROOT_DATABASE_NAME).await {
115 if !err
116 .as_database_error()
117 .is_some_and(|err| err.code().is_some_and(|code| code.to_string().eq("42P04")))
118 {
119 return Err(InitializeError::CreateRootDatabase(err));
120 }
121 }
122
123 let db_docbox = db_provider
125 .connect(ROOT_DATABASE_NAME)
126 .await
127 .map_err(InitializeError::ConnectRootDatabase)?;
128
129 Ok(db_docbox)
130}
131
132pub async fn initialize_root_role(
135 db: &DbPool,
136 root_role_name: &str,
137 root_role_password: &str,
138) -> Result<(), InitializeError> {
139 create_restricted_role(db, ROOT_DATABASE_NAME, root_role_name, root_role_password)
141 .await
142 .map_err(InitializeError::CreateRootRole)?;
143
144 Ok(())
145}
146
147pub async fn initialize_root_secret(
149 secrets: &AppSecretManager,
150 root_secret_name: &str,
151 root_role_name: &str,
152 root_role_password: &str,
153) -> Result<(), InitializeError> {
154 let secret_value = serde_json::to_string(&json!({
155 "username": root_role_name,
156 "password": root_role_password
157 }))
158 .map_err(InitializeError::SerializeSecret)?;
159
160 secrets
161 .set_secret(root_secret_name, &secret_value)
162 .await
163 .map_err(InitializeError::CreateRootSecret)?;
164
165 Ok(())
166}