use crate::{
database::{DatabaseProvider, close_pool_on_drop},
password::random_password,
root::migrate_root::{MigrateRootError, migrate_root},
};
use docbox_core::database::{
DbErr, DbPool, DbResult, ROOT_DATABASE_NAME, ROOT_DATABASE_ROLE_NAME,
create::{create_database, create_restricted_role, create_restricted_role_aws_iam},
models::tenant::Tenant,
sqlx::types::Uuid,
utils::DatabaseErrorExt,
};
use docbox_core::secrets::{SecretManager, SecretManagerError};
use serde_json::json;
use thiserror::Error;
const TEMP_SETUP_DATABASE: &str = "postgres";
#[derive(Debug, Error)]
pub enum InitializeError {
#[error("error connecting to 'postgres' database: {0}")]
ConnectPostgres(DbErr),
#[error("error creating root database: {0}")]
CreateRootDatabase(DbErr),
#[error("error connecting to root database: {0}")]
ConnectRootDatabase(DbErr),
#[error("error migrating root database: {0}")]
MigrateRoot(MigrateRootError),
#[error("error creating root database role: {0}")]
CreateRootRole(DbErr),
#[error("error serializing root secret: {0}")]
SerializeSecret(serde_json::Error),
#[error("failed to create root secret: {0}")]
CreateRootSecret(SecretManagerError),
#[error("error creating tenants table: {0}")]
CreateTenantsTable(DbErr),
}
#[tracing::instrument(skip(db_provider))]
pub async fn is_initialized(db_provider: &impl DatabaseProvider) -> DbResult<bool> {
let db = match db_provider.connect(ROOT_DATABASE_NAME).await {
Ok(value) => value,
Err(error) => {
if error.is_database_does_not_exist() {
return Ok(false);
}
return Err(error);
}
};
tracing::debug!("root is initialized");
let _guard = close_pool_on_drop(&db);
if let Err(error) = Tenant::find_by_id(&db, Uuid::nil(), "__DO_NOT_USE").await {
if error.is_table_does_not_exist() {
return Ok(false);
}
return Err(error);
}
tracing::debug!("tenant table is setup");
Ok(true)
}
#[tracing::instrument(skip(db_provider, secrets))]
pub async fn initialize(
db_provider: &impl DatabaseProvider,
secrets: &SecretManager,
root_secret_name: &str,
) -> Result<(), InitializeError> {
let db_docbox = initialize_root_database(db_provider).await?;
let _guard = close_pool_on_drop(&db_docbox);
let root_password = random_password(30);
initialize_root_role(&db_docbox, ROOT_DATABASE_ROLE_NAME, &root_password).await?;
tracing::info!("created root user");
initialize_root_secret(
secrets,
root_secret_name,
ROOT_DATABASE_ROLE_NAME,
&root_password,
)
.await?;
tracing::info!("created database secret");
migrate_root(db_provider, None)
.await
.map_err(InitializeError::MigrateRoot)?;
Ok(())
}
#[tracing::instrument(skip(db_provider))]
pub async fn initialize_iam(db_provider: &impl DatabaseProvider) -> Result<(), InitializeError> {
let db_docbox = initialize_root_database(db_provider).await?;
let _guard = close_pool_on_drop(&db_docbox);
initialize_root_role_aws_iam(&db_docbox, ROOT_DATABASE_ROLE_NAME).await?;
tracing::info!("created root user");
migrate_root(db_provider, None)
.await
.map_err(InitializeError::MigrateRoot)?;
Ok(())
}
#[tracing::instrument(skip(db_provider))]
pub async fn initialize_root_database(
db_provider: &impl DatabaseProvider,
) -> Result<DbPool, InitializeError> {
let db_root = db_provider
.connect(TEMP_SETUP_DATABASE)
.await
.map_err(InitializeError::ConnectPostgres)?;
let _guard = close_pool_on_drop(&db_root);
if let Err(err) = create_database(&db_root, ROOT_DATABASE_NAME).await
&& !err.is_database_exists()
{
return Err(InitializeError::CreateRootDatabase(err));
}
let db_docbox = db_provider
.connect(ROOT_DATABASE_NAME)
.await
.map_err(InitializeError::ConnectRootDatabase)?;
Ok(db_docbox)
}
#[tracing::instrument(skip(db, root_role_password))]
pub async fn initialize_root_role(
db: &DbPool,
root_role_name: &str,
root_role_password: &str,
) -> Result<(), InitializeError> {
create_restricted_role(db, ROOT_DATABASE_NAME, root_role_name, root_role_password)
.await
.map_err(InitializeError::CreateRootRole)?;
Ok(())
}
#[tracing::instrument(skip(db))]
pub async fn initialize_root_role_aws_iam(
db: &DbPool,
root_role_name: &str,
) -> Result<(), InitializeError> {
create_restricted_role_aws_iam(db, ROOT_DATABASE_NAME, root_role_name)
.await
.map_err(InitializeError::CreateRootRole)?;
Ok(())
}
#[tracing::instrument(skip(secrets, root_role_password))]
pub async fn initialize_root_secret(
secrets: &SecretManager,
root_secret_name: &str,
root_role_name: &str,
root_role_password: &str,
) -> Result<(), InitializeError> {
let secret_value = serde_json::to_string(&json!({
"username": root_role_name,
"password": root_role_password
}))
.map_err(InitializeError::SerializeSecret)?;
secrets
.set_secret(root_secret_name, &secret_value)
.await
.map_err(InitializeError::CreateRootSecret)?;
Ok(())
}