Skip to content

Commit

Permalink
feat: implemented fetch secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
paulobressan committed Sep 6, 2024
1 parent ddc2b82 commit 9a1df9f
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 43 deletions.
2 changes: 1 addition & 1 deletion src/domain/project/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub trait ProjectDrivenCache: Send + Sync {
async fn update(&self, project: &ProjectUpdate) -> Result<()>;
async fn delete(&self, id: &str, deleted_at: &DateTime<Utc>) -> Result<()>;
async fn create_secret(&self, secret: &ProjectSecret) -> Result<()>;
async fn find_secret_by_project_id(&self, project_id: &str) -> Result<Vec<ProjectSecret>>;
async fn find_secrets(&self, project_id: &str) -> Result<Vec<ProjectSecret>>;
async fn find_users(
&self,
project_id: &str,
Expand Down
72 changes: 57 additions & 15 deletions src/domain/project/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ pub async fn create(
let user_id = assert_credential(&cmd.credential)?;

if cache.find_by_namespace(&cmd.namespace).await?.is_some() {
return Err(Error::CommandMalformed("invalid project namespace".into())); }
return Err(Error::CommandMalformed("invalid project namespace".into()));
}

let (name, email) = auth0.find_info(&user_id).await?;
let billing_provider_id = stripe.create_customer(&name, &email).await?;
Expand Down Expand Up @@ -115,6 +116,16 @@ pub async fn delete(
Ok(())
}

pub async fn fetch_secret(
cache: Arc<dyn ProjectDrivenCache>,
cmd: FetchSecretCmd,
) -> Result<Vec<ProjectSecret>> {
assert_credential(&cmd.credential)?;
assert_permission(cache.clone(), &cmd.credential, &cmd.project_id).await?;

cache.find_secrets(&cmd.project_id).await
}

pub async fn create_secret(
cache: Arc<dyn ProjectDrivenCache>,
event: Arc<dyn EventDrivenBridge>,
Expand All @@ -127,7 +138,7 @@ pub async fn create_secret(
return Err(Error::CommandMalformed("invalid project id".into()));
};

let secrets = cache.find_secret_by_project_id(&cmd.project_id).await?;
let secrets = cache.find_secrets(&cmd.project_id).await?;
if secrets.len() >= MAX_SECRET {
return Err(Error::SecretExceeded(format!(
"secrets exceeded the limit of {MAX_SECRET}"
Expand Down Expand Up @@ -187,7 +198,7 @@ pub async fn verify_secret(
return Err(Error::Unauthorized("invalid project secret".into()));
}

let secrets = cache.find_secret_by_project_id(&cmd.project_id).await?;
let secrets = cache.find_secrets(&cmd.project_id).await?;

let secret = secrets.into_iter().find(|project_secret| {
let argon2 = Argon2::new_with_secret(
Expand Down Expand Up @@ -428,6 +439,20 @@ impl DeleteCmd {
}
}

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

#[derive(Debug, Clone)]
pub struct CreateSecretCmd {
pub credential: Credential,
Expand Down Expand Up @@ -610,6 +635,14 @@ mod tests {
}
}
}
impl Default for FetchSecretCmd {
fn default() -> Self {
Self {
credential: Credential::Auth0("user id".into()),
project_id: Uuid::new_v4().to_string(),
}
}
}
impl Default for CreateSecretCmd {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -773,9 +806,7 @@ mod tests {
cache
.expect_find_by_id()
.return_once(|_| Ok(Some(Project::default())));
cache
.expect_find_secret_by_project_id()
.return_once(|_| Ok(Vec::new()));
cache.expect_find_secrets().return_once(|_| Ok(Vec::new()));

let mut event = MockEventDrivenBridge::new();
event.expect_dispatch().return_once(|_| Ok(()));
Expand All @@ -786,6 +817,21 @@ mod tests {
assert!(result.is_ok());
}

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

let cmd = FetchSecretCmd::default();

let result = fetch_secret(Arc::new(cache), cmd).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn it_should_create_project_secret() {
let mut cache = MockProjectDrivenCache::new();
Expand All @@ -795,9 +841,7 @@ mod tests {
cache
.expect_find_by_id()
.return_once(|_| Ok(Some(Project::default())));
cache
.expect_find_secret_by_project_id()
.return_once(|_| Ok(Vec::new()));
cache.expect_find_secrets().return_once(|_| Ok(Vec::new()));

let mut event = MockEventDrivenBridge::new();
event.expect_dispatch().return_once(|_| Ok(()));
Expand Down Expand Up @@ -859,7 +903,7 @@ mod tests {
.expect_find_by_id()
.return_once(|_| Ok(Some(Project::default())));
cache
.expect_find_secret_by_project_id()
.expect_find_secrets()
.return_once(|_| Ok(vec![ProjectSecret::default(); 3]));

let event = MockEventDrivenBridge::new();
Expand All @@ -874,7 +918,7 @@ mod tests {
async fn it_should_verify_secret() {
let mut cache = MockProjectDrivenCache::new();
cache
.expect_find_secret_by_project_id()
.expect_find_secrets()
.return_once(|_| Ok(vec![ProjectSecret::default()]));

let cmd = VerifySecretCmd::default();
Expand All @@ -886,7 +930,7 @@ mod tests {
async fn it_should_fail_verify_secret_when_invalid_key() {
let mut cache = MockProjectDrivenCache::new();
cache
.expect_find_secret_by_project_id()
.expect_find_secrets()
.return_once(|_| Ok(vec![ProjectSecret::default()]));

let cmd = VerifySecretCmd {
Expand Down Expand Up @@ -924,9 +968,7 @@ mod tests {
#[tokio::test]
async fn it_should_fail_verify_secret_when_there_arent_secrets_storaged() {
let mut cache = MockProjectDrivenCache::new();
cache
.expect_find_secret_by_project_id()
.return_once(|_| Ok(vec![]));
cache.expect_find_secrets().return_once(|_| Ok(vec![]));

let cmd = VerifySecretCmd::default();

Expand Down
7 changes: 4 additions & 3 deletions src/driven/cache/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ impl ProjectDrivenCache for SqliteProjectDrivenCache {

Ok(())
}
async fn find_secret_by_project_id(&self, project_id: &str) -> Result<Vec<ProjectSecret>> {
async fn find_secrets(&self, project_id: &str) -> Result<Vec<ProjectSecret>> {
let secrets = sqlx::query_as::<_, ProjectSecret>(
r#"
SELECT
Expand All @@ -295,7 +295,8 @@ impl ProjectDrivenCache for SqliteProjectDrivenCache {
ps.secret,
ps.created_at
FROM project_secret ps
WHERE ps.project_id = $1;
WHERE ps.project_id = $1
ORDER BY ps.created_at DESC;
"#,
)
.bind(project_id)
Expand Down Expand Up @@ -691,7 +692,7 @@ mod tests {
};
cache.create_secret(&secret).await.unwrap();

let result = cache.find_secret_by_project_id(&project.id).await;
let result = cache.find_secrets(&project.id).await;

assert!(result.is_ok());
assert!(result.unwrap().len() == 1);
Expand Down
55 changes: 31 additions & 24 deletions src/drivers/grpc/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ use dmtri::demeter::ops::v1alpha as proto;
use std::{sync::Arc, time::Duration};
use tonic::{async_trait, Status};
use tracing::error;
use uuid::Uuid;

use crate::domain::{
auth::{Auth0Driven, Credential},
event::EventDrivenBridge,
project::{
self, cache::ProjectDrivenCache, Project, ProjectEmailDriven, ProjectUser,
self, cache::ProjectDrivenCache, Project, ProjectEmailDriven, ProjectSecret, ProjectUser,
ProjectUserInvite, StripeDriven,
},
};
Expand Down Expand Up @@ -146,7 +145,26 @@ impl proto::project_service_server::ProjectService for ProjectServiceImpl {

Ok(tonic::Response::new(message))
}
async fn fetch_project_secrets(
&self,
request: tonic::Request<proto::FetchProjectSecretsRequest>,
) -> Result<tonic::Response<proto::FetchProjectSecretsResponse>, 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::FetchSecretCmd::new(credential, req.project_id);

let secrets = project::command::fetch_secret(self.cache.clone(), cmd.clone()).await?;

let records = secrets.into_iter().map(|v| v.into()).collect();
let message = proto::FetchProjectSecretsResponse { records };

Ok(tonic::Response::new(message))
}
async fn create_project_secret(
&self,
request: tonic::Request<proto::CreateProjectSecretRequest>,
Expand Down Expand Up @@ -177,28 +195,6 @@ impl proto::project_service_server::ProjectService for ProjectServiceImpl {

Ok(tonic::Response::new(message))
}
async fn fetch_project_secrets(
&self,
request: tonic::Request<proto::FetchProjectSecretsRequest>,
) -> Result<tonic::Response<proto::FetchProjectSecretsResponse>, 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 message = proto::FetchProjectSecretsResponse {
records: vec![proto::ProjectSecret {
id: Uuid::new_v4().to_string(),
name: "Secret Name".into(),
project_id: req.project_id,
..Default::default()
}],
};

Ok(tonic::Response::new(message))
}
async fn fetch_project_users(
&self,
request: tonic::Request<proto::FetchProjectUsersRequest>,
Expand Down Expand Up @@ -350,3 +346,14 @@ impl From<ProjectUser> for proto::ProjectUser {
}
}
}

impl From<ProjectSecret> for proto::ProjectSecret {
fn from(value: ProjectSecret) -> Self {
Self {
id: value.id,
project_id: value.project_id,
name: value.name,
created_at: value.created_at.to_rfc3339(),
}
}
}

0 comments on commit 9a1df9f

Please sign in to comment.