Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented fetch secrets #117

Merged
merged 1 commit into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(),
}
}
}
Loading