Skip to content

Commit

Permalink
feat: implemented project secret deletion (#139)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulobressan authored Sep 26, 2024
1 parent 7dfaf01 commit c86d413
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 5 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions src/domain/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ pub struct ProjectSecretCreated {
}
into_event!(ProjectSecretCreated);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectSecretDeleted {
pub id: String,
pub deleted_by: String,
pub deleted_at: DateTime<Utc>,
}
into_event!(ProjectSecretDeleted);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectUserInviteCreated {
pub id: String,
Expand Down Expand Up @@ -156,6 +164,7 @@ pub enum Event {
ProjectUpdated(ProjectUpdated),
ProjectDeleted(ProjectDeleted),
ProjectSecretCreated(ProjectSecretCreated),
ProjectSecretDeleted(ProjectSecretDeleted),
ProjectUserInviteCreated(ProjectUserInviteCreated),
ProjectUserInviteAccepted(ProjectUserInviteAccepted),
ProjectUserDeleted(ProjectUserDeleted),
Expand All @@ -171,6 +180,7 @@ impl Event {
Event::ProjectUpdated(_) => "ProjectUpdated".into(),
Event::ProjectDeleted(_) => "ProjectDeleted".into(),
Event::ProjectSecretCreated(_) => "ProjectSecretCreated".into(),
Event::ProjectSecretDeleted(_) => "ProjectSecretDeleted".into(),
Event::ProjectUserInviteCreated(_) => "ProjectUserInviteCreated".into(),
Event::ProjectUserInviteAccepted(_) => "ProjectUserInviteAccepted".into(),
Event::ProjectUserDeleted(_) => "ProjectUserDeleted".into(),
Expand All @@ -188,6 +198,9 @@ impl Event {
"ProjectSecretCreated" => {
Ok(Self::ProjectSecretCreated(serde_json::from_slice(payload)?))
}
"ProjectSecretDeleted" => {
Ok(Self::ProjectSecretDeleted(serde_json::from_slice(payload)?))
}
"ProjectUserInviteCreated" => Ok(Self::ProjectUserInviteCreated(
serde_json::from_slice(payload)?,
)),
Expand Down Expand Up @@ -256,6 +269,15 @@ mod tests {
}
}
}
impl Default for ProjectSecretDeleted {
fn default() -> Self {
Self {
id: Uuid::new_v4().to_string(),
deleted_by: Uuid::new_v4().to_string(),
deleted_at: Utc::now(),
}
}
}
impl Default for ProjectUserInviteCreated {
fn default() -> Self {
Self {
Expand Down
22 changes: 20 additions & 2 deletions src/domain/project/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use chrono::{DateTime, Utc};
use std::sync::Arc;

use crate::domain::event::{
ProjectCreated, ProjectDeleted, ProjectSecretCreated, ProjectUpdated, ProjectUserDeleted,
ProjectUserInviteAccepted, ProjectUserInviteCreated,
ProjectCreated, ProjectDeleted, ProjectSecretCreated, ProjectSecretDeleted, ProjectUpdated,
ProjectUserDeleted, ProjectUserInviteAccepted, ProjectUserInviteCreated,
};
use crate::domain::Result;

Expand All @@ -20,6 +20,8 @@ pub trait ProjectDrivenCache: Send + Sync {
async fn delete(&self, id: &str, deleted_at: &DateTime<Utc>) -> Result<()>;
async fn create_secret(&self, secret: &ProjectSecret) -> Result<()>;
async fn find_secrets(&self, project_id: &str) -> Result<Vec<ProjectSecret>>;
async fn find_secret_by_id(&self, id: &str) -> Result<Option<ProjectSecret>>;
async fn delete_secret(&self, id: &str) -> Result<()>;
async fn find_users(
&self,
project_id: &str,
Expand Down Expand Up @@ -62,6 +64,12 @@ pub async fn create_secret(
) -> Result<()> {
cache.create_secret(&evt.into()).await
}
pub async fn delete_secret(
cache: Arc<dyn ProjectDrivenCache>,
evt: ProjectSecretDeleted,
) -> Result<()> {
cache.delete_secret(&evt.id).await
}

pub async fn create_user_invite(
cache: Arc<dyn ProjectDrivenCache>,
Expand Down Expand Up @@ -111,6 +119,16 @@ mod tests {
let result = create_secret(Arc::new(cache), evt).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn it_should_delete_project_secret_cache() {
let mut cache = MockProjectDrivenCache::new();
cache.expect_delete_secret().return_once(|_| Ok(()));

let evt = ProjectSecretDeleted::default();

let result = delete_secret(Arc::new(cache), evt).await;
assert!(result.is_ok());
}

#[tokio::test]
async fn it_should_create_user_invite_cache() {
Expand Down
68 changes: 66 additions & 2 deletions src/domain/project/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ use crate::domain::{
auth::{Auth0Driven, Credential, UserId},
error::Error,
event::{
EventDrivenBridge, ProjectCreated, ProjectDeleted, ProjectSecretCreated, ProjectUpdated,
ProjectUserDeleted, ProjectUserInviteAccepted, ProjectUserInviteCreated,
EventDrivenBridge, ProjectCreated, ProjectDeleted, ProjectSecretCreated,
ProjectSecretDeleted, ProjectUpdated, ProjectUserDeleted, ProjectUserInviteAccepted,
ProjectUserInviteCreated,
},
project::{ProjectStatus, ProjectUserInviteStatus},
utils, Result, MAX_SECRET, PAGE_SIZE_DEFAULT, PAGE_SIZE_MAX,
Expand Down Expand Up @@ -233,6 +234,30 @@ pub async fn verify_secret(

Ok(secret)
}
pub async fn delete_secret(
cache: Arc<dyn ProjectDrivenCache>,
event: Arc<dyn EventDrivenBridge>,
cmd: DeleteSecretCmd,
) -> Result<()> {
let user_id = assert_credential(&cmd.credential)?;

let Some(secret) = cache.find_secret_by_id(&cmd.id).await? else {
return Err(Error::CommandMalformed("invalid secret id".into()));
};

assert_permission(cache.clone(), &cmd.credential, &secret.project_id, None).await?;

let evt = ProjectSecretDeleted {
id: secret.id,
deleted_by: user_id,
deleted_at: Utc::now(),
};

event.dispatch(evt.into()).await?;
info!(secret = &cmd.id, "project secret deleted");

Ok(())
}

pub async fn fetch_user(
cache: Arc<dyn ProjectDrivenCache>,
Expand Down Expand Up @@ -577,6 +602,17 @@ pub struct VerifySecretCmd {
pub key: String,
}

#[derive(Debug, Clone)]
pub struct DeleteSecretCmd {
pub credential: Credential,
pub id: String,
}
impl DeleteSecretCmd {
pub fn new(credential: Credential, id: String) -> Self {
Self { credential, id }
}
}

#[derive(Debug, Clone)]
pub struct FetchUserCmd {
pub credential: Credential,
Expand Down Expand Up @@ -786,6 +822,14 @@ mod tests {
}
}
}
impl Default for DeleteSecretCmd {
fn default() -> Self {
Self {
credential: Credential::Auth0("user id".into()),
id: Uuid::new_v4().to_string(),
}
}
}
impl Default for FetchUserInviteCmd {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -1125,6 +1169,26 @@ mod tests {
assert!(result.is_err());
}

#[tokio::test]
async fn it_should_delete_secret() {
let mut cache = MockProjectDrivenCache::new();
cache
.expect_find_user_permission()
.return_once(|_, _| Ok(Some(ProjectUser::default())));

cache
.expect_find_secret_by_id()
.return_once(|_| Ok(Some(ProjectSecret::default())));

let mut event = MockEventDrivenBridge::new();
event.expect_dispatch().return_once(|_| Ok(()));

let cmd = DeleteSecretCmd::default();

let result = delete_secret(Arc::new(cache), Arc::new(event), cmd).await;
assert!(result.is_ok());
}

#[tokio::test]
async fn it_should_fetch_project_user_invites() {
let mut cache = MockProjectDrivenCache::new();
Expand Down
54 changes: 54 additions & 0 deletions src/driven/cache/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,41 @@ impl ProjectDrivenCache for SqliteProjectDrivenCache {

Ok(secrets)
}
async fn find_secret_by_id(&self, id: &str) -> Result<Option<ProjectSecret>> {
let secret = sqlx::query_as::<_, ProjectSecret>(
r#"
SELECT
ps.id,
ps.project_id,
ps.name,
ps.phc,
ps.secret,
ps.created_at
FROM project_secret ps
WHERE ps.id = $1;
"#,
)
.bind(id)
.fetch_optional(&self.sqlite.db)
.await?;

Ok(secret)
}
async fn delete_secret(&self, id: &str) -> Result<()> {
sqlx::query!(
r#"
DELETE FROM
project_secret
WHERE
id=$1;
"#,
id,
)
.execute(&self.sqlite.db)
.await?;

Ok(())
}
async fn find_user_permission(
&self,
user_id: &str,
Expand Down Expand Up @@ -737,6 +772,25 @@ mod tests {
assert!(result.unwrap().len() == 1);
}

#[tokio::test]
async fn it_should_find_secret_by_id() {
let cache = get_cache().await;

let project = Project::default();
cache.create(&project).await.unwrap();

let secret = ProjectSecret {
project_id: project.id.clone(),
..Default::default()
};
cache.create_secret(&secret).await.unwrap();

let result = cache.find_secret_by_id(&secret.id).await;

assert!(result.is_ok());
assert!(result.unwrap().is_some());
}

#[tokio::test]
async fn it_should_find_user_permission() {
let cache = get_cache().await;
Expand Down
3 changes: 3 additions & 0 deletions src/drivers/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ pub async fn subscribe(config: CacheConfig) -> Result<()> {
Event::ProjectSecretCreated(evt) => {
project::cache::create_secret(project_cache.clone(), evt.clone()).await
}
Event::ProjectSecretDeleted(evt) => {
project::cache::delete_secret(project_cache.clone(), evt.clone()).await
}
Event::ProjectUserInviteCreated(evt) => {
project::cache::create_user_invite(project_cache.clone(), evt.clone()).await
}
Expand Down
17 changes: 17 additions & 0 deletions src/drivers/grpc/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,23 @@ impl proto::project_service_server::ProjectService for ProjectServiceImpl {

Ok(tonic::Response::new(message))
}
async fn delete_project_secret(
&self,
request: tonic::Request<proto::DeleteProjectSecretRequest>,
) -> Result<tonic::Response<proto::DeleteProjectSecretResponse>, tonic::Status> {
let credential = match request.extensions().get::<Credential>() {
Some(credential) => credential.clone(),
None => return Err(Status::unauthenticated("invalid credential")),
};

let req = request.into_inner();
let cmd = project::command::DeleteSecretCmd::new(credential, req.id);
project::command::delete_secret(self.cache.clone(), self.event.clone(), cmd.clone())
.await?;
let message = proto::DeleteProjectSecretResponse {};

Ok(tonic::Response::new(message))
}
async fn fetch_project_users(
&self,
request: tonic::Request<proto::FetchProjectUsersRequest>,
Expand Down

0 comments on commit c86d413

Please sign in to comment.