Skip to content
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 crates/crates_io_database/src/models/krate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub struct Crate {
pub homepage: Option<String>,
pub documentation: Option<String>,
pub repository: Option<String>,
max_upload_size: Option<i32>,
pub max_upload_size: Option<i32>,
pub max_features: Option<i16>,
}

Expand Down
174 changes: 174 additions & 0 deletions src/controllers/trustpub/emails.rs
Original file line number Diff line number Diff line change
@@ -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, minijinja::Error> {
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, minijinja::Error> {
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);
}
}
16 changes: 10 additions & 6 deletions src/controllers/trustpub/github_configs/create/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)]
Expand Down Expand Up @@ -118,7 +117,12 @@ pub async fn create_trustpub_github_config(
.collect::<Vec<_>>();

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}");
Expand All @@ -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
Expand Down
16 changes: 10 additions & 6 deletions src/controllers/trustpub/github_configs/delete/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)]
Expand Down Expand Up @@ -83,7 +82,12 @@ pub async fn delete_trustpub_github_config(
.collect::<Vec<_>>();

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}");
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/controllers/trustpub/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod emails;
pub mod github_configs;
pub mod tokens;
Original file line number Diff line number Diff line change
@@ -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 [email protected] for assistance.

--
The crates.io Team
Original file line number Diff line number Diff line change
@@ -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 [email protected] for assistance.

--
The crates.io Team
Original file line number Diff line number Diff line change
@@ -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 [email protected] for assistance.

--
The crates.io Team
Original file line number Diff line number Diff line change
@@ -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 [email protected] for assistance.

--
The crates.io Team
Original file line number Diff line number Diff line change
@@ -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 [email protected] for assistance.

--
The crates.io Team
Loading