305 lines
9.9 KiB
Rust
305 lines
9.9 KiB
Rust
use super::*;
|
|
|
|
impl PostgresRegistry {
|
|
pub async fn list_secrets(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
) -> Result<Vec<SecretRecord>, RegistryError> {
|
|
let rows = sqlx::query!(
|
|
"select
|
|
id,
|
|
workspace_id,
|
|
name,
|
|
kind,
|
|
status,
|
|
current_version,
|
|
created_at as \"created_at!: time::OffsetDateTime\",
|
|
updated_at as \"updated_at!: time::OffsetDateTime\",
|
|
last_used_at as \"last_used_at: time::OffsetDateTime\"
|
|
from secrets
|
|
where workspace_id = $1
|
|
order by name asc",
|
|
workspace_id.as_str(),
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
rows.into_iter()
|
|
.map(|row| {
|
|
Ok(SecretRecord {
|
|
secret: Secret {
|
|
id: SecretId::new(row.id),
|
|
workspace_id: WorkspaceId::new(row.workspace_id),
|
|
name: row.name,
|
|
kind: deserialize_enum_text(&row.kind, "kind")?,
|
|
status: deserialize_enum_text(&row.status, "status")?,
|
|
current_version: from_db_version(row.current_version, "current_version")?,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at,
|
|
last_used_at: row.last_used_at,
|
|
},
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub async fn get_secret(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
secret_id: &SecretId,
|
|
) -> Result<Option<SecretRecord>, RegistryError> {
|
|
let row = sqlx::query!(
|
|
"select
|
|
id,
|
|
workspace_id,
|
|
name,
|
|
kind,
|
|
status,
|
|
current_version,
|
|
created_at as \"created_at!: time::OffsetDateTime\",
|
|
updated_at as \"updated_at!: time::OffsetDateTime\",
|
|
last_used_at as \"last_used_at: time::OffsetDateTime\"
|
|
from secrets
|
|
where workspace_id = $1 and id = $2",
|
|
workspace_id.as_str(),
|
|
secret_id.as_str(),
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
row.map(|row| {
|
|
Ok(SecretRecord {
|
|
secret: Secret {
|
|
id: SecretId::new(row.id),
|
|
workspace_id: WorkspaceId::new(row.workspace_id),
|
|
name: row.name,
|
|
kind: deserialize_enum_text(&row.kind, "kind")?,
|
|
status: deserialize_enum_text(&row.status, "status")?,
|
|
current_version: from_db_version(row.current_version, "current_version")?,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at,
|
|
last_used_at: row.last_used_at,
|
|
},
|
|
})
|
|
})
|
|
.transpose()
|
|
}
|
|
|
|
pub async fn get_current_secret_version(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
secret_id: &SecretId,
|
|
) -> Result<Option<SecretVersionRecord>, RegistryError> {
|
|
let row = sqlx::query!(
|
|
"select
|
|
sv.secret_id,
|
|
sv.version,
|
|
sv.ciphertext,
|
|
sv.key_version,
|
|
sv.created_at as \"created_at!: time::OffsetDateTime\",
|
|
sv.created_by
|
|
from secrets s
|
|
join secret_versions sv
|
|
on sv.secret_id = s.id and sv.version = s.current_version
|
|
where s.workspace_id = $1 and s.id = $2",
|
|
workspace_id.as_str(),
|
|
secret_id.as_str(),
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
row.map(|row| {
|
|
Ok(SecretVersionRecord {
|
|
secret_version: SecretVersion {
|
|
secret_id: SecretId::new(row.secret_id),
|
|
version: from_db_version(row.version, "version")?,
|
|
ciphertext: row.ciphertext,
|
|
key_version: row.key_version,
|
|
created_at: row.created_at,
|
|
created_by: row.created_by.map(UserId::new),
|
|
},
|
|
})
|
|
})
|
|
.transpose()
|
|
}
|
|
|
|
pub async fn create_secret(
|
|
&self,
|
|
request: CreateSecretRequest<'_>,
|
|
) -> Result<(), RegistryError> {
|
|
let mut tx = self.pool.begin().await?;
|
|
let result = sqlx::query(
|
|
"insert into secrets (
|
|
id,
|
|
workspace_id,
|
|
name,
|
|
kind,
|
|
status,
|
|
current_version,
|
|
last_used_at,
|
|
created_at,
|
|
updated_at
|
|
) values (
|
|
$1, $2, $3, $4, $5, $6, $7::timestamptz, $8::timestamptz, $9::timestamptz
|
|
)",
|
|
)
|
|
.bind(request.secret.id.as_str())
|
|
.bind(request.secret.workspace_id.as_str())
|
|
.bind(&request.secret.name)
|
|
.bind(serialize_enum_text(&request.secret.kind, "kind")?)
|
|
.bind(serialize_enum_text(&request.secret.status, "status")?)
|
|
.bind(to_db_version(request.secret.current_version))
|
|
.bind(request.secret.last_used_at)
|
|
.bind(request.secret.created_at)
|
|
.bind(request.secret.updated_at)
|
|
.execute(&mut *tx)
|
|
.await;
|
|
|
|
match result {
|
|
Ok(_) => {
|
|
sqlx::query(
|
|
"insert into secret_versions (
|
|
secret_id,
|
|
version,
|
|
ciphertext,
|
|
key_version,
|
|
created_at,
|
|
created_by
|
|
) values (
|
|
$1, $2, $3, $4, $5::timestamptz, $6
|
|
)",
|
|
)
|
|
.bind(request.secret.id.as_str())
|
|
.bind(to_db_version(request.secret.current_version))
|
|
.bind(request.ciphertext)
|
|
.bind(request.key_version)
|
|
.bind(request.secret.created_at)
|
|
.bind(request.created_by.map(|value| value.as_str()))
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
tx.commit().await?;
|
|
Ok(())
|
|
}
|
|
Err(sqlx::Error::Database(error))
|
|
if error.constraint() == Some("secrets_workspace_name_idx") =>
|
|
{
|
|
Err(RegistryError::SecretNameAlreadyExists {
|
|
workspace_id: request.secret.workspace_id.as_str().to_owned(),
|
|
name: request.secret.name.clone(),
|
|
})
|
|
}
|
|
Err(error) => Err(RegistryError::Storage(error)),
|
|
}
|
|
}
|
|
|
|
pub async fn rotate_secret(
|
|
&self,
|
|
request: RotateSecretRequest<'_>,
|
|
) -> Result<SecretVersionRecord, RegistryError> {
|
|
let existing = self
|
|
.get_secret(request.workspace_id, request.secret_id)
|
|
.await?
|
|
.ok_or_else(|| RegistryError::SecretNotFound {
|
|
secret_id: request.secret_id.as_str().to_owned(),
|
|
})?;
|
|
let next_version = existing.secret.current_version + 1;
|
|
|
|
let mut tx = self.pool.begin().await?;
|
|
sqlx::query(
|
|
"insert into secret_versions (
|
|
secret_id,
|
|
version,
|
|
ciphertext,
|
|
key_version,
|
|
created_at,
|
|
created_by
|
|
) values (
|
|
$1, $2, $3, $4, $5::timestamptz, $6
|
|
)",
|
|
)
|
|
.bind(request.secret_id.as_str())
|
|
.bind(to_db_version(next_version))
|
|
.bind(request.ciphertext)
|
|
.bind(request.key_version)
|
|
.bind(request.created_at)
|
|
.bind(request.created_by.map(|value| value.as_str()))
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query(
|
|
"update secrets
|
|
set current_version = $3,
|
|
updated_at = $4::timestamptz
|
|
where workspace_id = $1 and id = $2",
|
|
)
|
|
.bind(request.workspace_id.as_str())
|
|
.bind(request.secret_id.as_str())
|
|
.bind(to_db_version(next_version))
|
|
.bind(request.updated_at)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
tx.commit().await?;
|
|
|
|
Ok(SecretVersionRecord {
|
|
secret_version: SecretVersion {
|
|
secret_id: request.secret_id.clone(),
|
|
version: next_version,
|
|
ciphertext: request.ciphertext.to_owned(),
|
|
key_version: request.key_version.to_owned(),
|
|
created_at: *request.created_at,
|
|
created_by: request.created_by.cloned(),
|
|
},
|
|
})
|
|
}
|
|
|
|
pub async fn delete_secret(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
secret_id: &SecretId,
|
|
) -> Result<(), RegistryError> {
|
|
let result = sqlx::query(
|
|
"delete from secrets
|
|
where workspace_id = $1 and id = $2",
|
|
)
|
|
.bind(workspace_id.as_str())
|
|
.bind(secret_id.as_str())
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
if result.rows_affected() == 0 {
|
|
return Err(RegistryError::SecretNotFound {
|
|
secret_id: secret_id.as_str().to_owned(),
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn touch_secret(
|
|
&self,
|
|
workspace_id: &WorkspaceId,
|
|
secret_id: &SecretId,
|
|
used_at: &OffsetDateTime,
|
|
) -> Result<(), RegistryError> {
|
|
let result = sqlx::query(
|
|
"update secrets
|
|
set last_used_at = $3::timestamptz
|
|
where workspace_id = $1 and id = $2",
|
|
)
|
|
.bind(workspace_id.as_str())
|
|
.bind(secret_id.as_str())
|
|
.bind(used_at)
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
if result.rows_affected() == 0 {
|
|
return Err(RegistryError::SecretNotFound {
|
|
secret_id: secret_id.as_str().to_owned(),
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|