diff --git a/crates/crates_io_database/src/models/krate.rs b/crates/crates_io_database/src/models/krate.rs index 87521cd8c2..23988ec4d2 100644 --- a/crates/crates_io_database/src/models/krate.rs +++ b/crates/crates_io_database/src/models/krate.rs @@ -35,7 +35,7 @@ pub struct Crate { pub homepage: Option, pub documentation: Option, pub repository: Option, - max_upload_size: Option, + pub max_upload_size: Option, pub max_features: Option, } diff --git a/src/controllers/trustpub/emails.rs b/src/controllers/trustpub/emails.rs new file mode 100644 index 0000000000..e6e45d885e --- /dev/null +++ b/src/controllers/trustpub/emails.rs @@ -0,0 +1,174 @@ +use crate::email::EmailMessage; +use crates_io_database::models::trustpub::GitHubConfig; +use crates_io_database::models::{Crate, User}; + +#[derive(serde::Serialize)] +pub struct ConfigCreatedEmail<'a> { + /// The GitHub login of the email recipient. + pub recipient: &'a str, + /// The user who created the trusted publishing configuration. + pub auth_user: &'a User, + /// The crate for which the trusted publishing configuration was created. + pub krate: &'a Crate, + /// The trusted publishing configuration that was created. + pub saved_config: &'a GitHubConfig, +} + +impl ConfigCreatedEmail<'_> { + pub fn render(&self) -> Result { + EmailMessage::from_template("trustpub_config_created", self) + } +} + +#[derive(serde::Serialize)] +pub struct ConfigDeletedEmail<'a> { + /// The GitHub login of the email recipient. + pub recipient: &'a str, + /// The user who deleted the trusted publishing configuration. + pub auth_user: &'a User, + /// The crate for which the trusted publishing configuration was deleted. + pub krate: &'a Crate, + /// The trusted publishing configuration that was deleted. + pub config: &'a GitHubConfig, +} + +impl ConfigDeletedEmail<'_> { + pub fn render(&self) -> Result { + EmailMessage::from_template("trustpub_config_deleted", self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::Utc; + use claims::assert_ok; + use insta::assert_snapshot; + + fn test_user() -> User { + User { + id: 1, + gh_login: "octocat".into(), + name: Some("The Octocat".into()), + gh_id: 123, + gh_avatar: None, + gh_encrypted_token: vec![], + account_lock_reason: None, + account_lock_until: None, + is_admin: false, + publish_notifications: true, + } + } + + fn test_crate() -> Crate { + Crate { + id: 1, + name: "my-crate".into(), + updated_at: Utc::now(), + created_at: Utc::now(), + description: None, + homepage: None, + documentation: None, + repository: None, + max_upload_size: None, + max_features: None, + } + } + + fn test_github_config(environment: Option<&str>) -> GitHubConfig { + GitHubConfig { + id: 1, + created_at: Utc::now(), + crate_id: 1, + repository_owner_id: 42, + repository_owner: "rust-lang".into(), + repository_name: "rust".into(), + workflow_filename: "publish.yml".into(), + environment: environment.map(String::from), + } + } + + #[test] + fn test_config_created_email() { + let email = ConfigCreatedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + saved_config: &test_github_config(None), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration added to my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_created_email_with_environment() { + let email = ConfigCreatedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + saved_config: &test_github_config(Some("production")), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration added to my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_created_email_different_recipient() { + let email = ConfigCreatedEmail { + recipient: "team-member", + auth_user: &test_user(), + krate: &test_crate(), + saved_config: &test_github_config(None), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration added to my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_deleted_email() { + let email = ConfigDeletedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + config: &test_github_config(None), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration removed from my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_deleted_email_with_environment() { + let email = ConfigDeletedEmail { + recipient: "octocat", + auth_user: &test_user(), + krate: &test_crate(), + config: &test_github_config(Some("production")), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration removed from my-crate"); + assert_snapshot!(rendered.body_text); + } + + #[test] + fn test_config_deleted_email_different_recipient() { + let email = ConfigDeletedEmail { + recipient: "team-member", + auth_user: &test_user(), + krate: &test_crate(), + config: &test_github_config(None), + }; + + let rendered = assert_ok!(email.render()); + assert_snapshot!(rendered.subject, @"crates.io: Trusted Publishing configuration removed from my-crate"); + assert_snapshot!(rendered.body_text); + } +} diff --git a/src/controllers/trustpub/github_configs/create/mod.rs b/src/controllers/trustpub/github_configs/create/mod.rs index 72870ec5c1..4ef98783ee 100644 --- a/src/controllers/trustpub/github_configs/create/mod.rs +++ b/src/controllers/trustpub/github_configs/create/mod.rs @@ -1,8 +1,8 @@ use crate::app::AppState; use crate::auth::AuthCheck; use crate::controllers::krate::load_crate; +use crate::controllers::trustpub::emails::ConfigCreatedEmail; use crate::controllers::trustpub::github_configs::json; -use crate::email::EmailMessage; use crate::util::errors::{AppResult, bad_request, forbidden, server_error}; use anyhow::Context; use axum::Json; @@ -17,7 +17,6 @@ use crates_io_trustpub::github::validation::{ use diesel::prelude::*; use diesel_async::RunQueryDsl; use http::request::Parts; -use minijinja::context; use tracing::warn; #[cfg(test)] @@ -118,7 +117,12 @@ pub async fn create_trustpub_github_config( .collect::>(); for (recipient, email_address) in &recipients { - let context = context! { recipient, auth_user, krate, saved_config }; + let context = ConfigCreatedEmail { + recipient, + auth_user, + krate: &krate, + saved_config: &saved_config, + }; if let Err(err) = send_notification_email(&state, email_address, context).await { warn!("Failed to send trusted publishing notification to {email_address}: {err}"); @@ -142,10 +146,10 @@ pub async fn create_trustpub_github_config( async fn send_notification_email( state: &AppState, email_address: &str, - context: minijinja::Value, + context: ConfigCreatedEmail<'_>, ) -> anyhow::Result<()> { - let email = EmailMessage::from_template("trustpub_config_created", context) - .context("Failed to render email template")?; + let email = context.render(); + let email = email.context("Failed to render email template")?; state .emails diff --git a/src/controllers/trustpub/github_configs/delete/mod.rs b/src/controllers/trustpub/github_configs/delete/mod.rs index 662d72ecbe..aaacf67fca 100644 --- a/src/controllers/trustpub/github_configs/delete/mod.rs +++ b/src/controllers/trustpub/github_configs/delete/mod.rs @@ -1,6 +1,6 @@ use crate::app::AppState; use crate::auth::AuthCheck; -use crate::email::EmailMessage; +use crate::controllers::trustpub::emails::ConfigDeletedEmail; use crate::util::errors::{AppResult, bad_request, not_found}; use anyhow::Context; use axum::extract::Path; @@ -12,7 +12,6 @@ use diesel::prelude::*; use diesel_async::RunQueryDsl; use http::StatusCode; use http::request::Parts; -use minijinja::context; use tracing::warn; #[cfg(test)] @@ -83,7 +82,12 @@ pub async fn delete_trustpub_github_config( .collect::>(); for (recipient, email_address) in &recipients { - let context = context! { recipient, auth_user, krate, config }; + let context = ConfigDeletedEmail { + recipient, + auth_user, + krate: &krate, + config: &config, + }; if let Err(err) = send_notification_email(&state, email_address, context).await { warn!("Failed to send trusted publishing notification to {email_address}: {err}"); @@ -96,10 +100,10 @@ pub async fn delete_trustpub_github_config( async fn send_notification_email( state: &AppState, email_address: &str, - context: minijinja::Value, + context: ConfigDeletedEmail<'_>, ) -> anyhow::Result<()> { - let email = EmailMessage::from_template("trustpub_config_deleted", context) - .context("Failed to render email template")?; + let email = context.render(); + let email = email.context("Failed to render email template")?; state .emails diff --git a/src/controllers/trustpub/mod.rs b/src/controllers/trustpub/mod.rs index fe0f81d16f..461bd02043 100644 --- a/src/controllers/trustpub/mod.rs +++ b/src/controllers/trustpub/mod.rs @@ -1,2 +1,3 @@ +pub mod emails; pub mod github_configs; pub mod tokens; diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email-2.snap new file mode 100644 index 0000000000..5ca814a332 --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email-2.snap @@ -0,0 +1,21 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You added a new "Trusted Publishing" configuration for GitHub Actions to your crate "my-crate". Trusted publishers act as trusted users and can publish new versions of the crate automatically. + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: (not set) + +If you did not make this change and you think it was made maliciously, you can remove the configuration from the crate via the "Settings" tab on the crate's page. + +If you are unable to revert the change and need to do so, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_different_recipient-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_different_recipient-2.snap new file mode 100644 index 0000000000..4231f88f3f --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_different_recipient-2.snap @@ -0,0 +1,21 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello team-member! + +crates.io user octocat added a new "Trusted Publishing" configuration for GitHub Actions to a crate that you manage ("my-crate"). Trusted publishers act as trusted users and can publish new versions of the crate automatically. + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: (not set) + +If you did not make this change and you think it was made maliciously, you can remove the configuration from the crate via the "Settings" tab on the crate's page. + +If you are unable to revert the change and need to do so, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_with_environment-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_with_environment-2.snap new file mode 100644 index 0000000000..223f897258 --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_created_email_with_environment-2.snap @@ -0,0 +1,21 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You added a new "Trusted Publishing" configuration for GitHub Actions to your crate "my-crate". Trusted publishers act as trusted users and can publish new versions of the crate automatically. + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: production + +If you did not make this change and you think it was made maliciously, you can remove the configuration from the crate via the "Settings" tab on the crate's page. + +If you are unable to revert the change and need to do so, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email-2.snap new file mode 100644 index 0000000000..488aca92de --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email-2.snap @@ -0,0 +1,19 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You removed a "Trusted Publishing" configuration for GitHub Actions from your crate "my-crate". + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: (not set) + +If you did not make this change and you think it was made maliciously, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_different_recipient-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_different_recipient-2.snap new file mode 100644 index 0000000000..5f806113d5 --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_different_recipient-2.snap @@ -0,0 +1,19 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello team-member! + +crates.io user octocat removed a "Trusted Publishing" configuration for GitHub Actions from a crate that you manage ("my-crate"). + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: (not set) + +If you did not make this change and you think it was made maliciously, you can email help@crates.io for assistance. + +-- +The crates.io Team diff --git a/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_with_environment-2.snap b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_with_environment-2.snap new file mode 100644 index 0000000000..54bebd6d24 --- /dev/null +++ b/src/controllers/trustpub/snapshots/crates_io__controllers__trustpub__emails__tests__config_deleted_email_with_environment-2.snap @@ -0,0 +1,19 @@ +--- +source: src/controllers/trustpub/emails.rs +expression: rendered.body_text +--- +Hello octocat! + +You removed a "Trusted Publishing" configuration for GitHub Actions from your crate "my-crate". + +Trusted Publishing configuration: + +- Repository owner: rust-lang +- Repository name: rust +- Workflow filename: publish.yml +- Environment: production + +If you did not make this change and you think it was made maliciously, you can email help@crates.io for assistance. + +-- +The crates.io Team