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
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ Subject: crates.io: Successfully published [email protected]
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable


Hello foo!

A new version of the package foo (1.0.0) was published by your account (https://crates.io/users/foo) at [0000-00-00T00:00:00Z].

If you have questions or security concerns, you can contact us at [email protected]. If you would like to stop receiving these security notifications, you can disable them in your account settings.

--
The crates.io Team
----------------------------------------

To: [email protected]
Expand All @@ -21,8 +25,12 @@ Subject: crates.io: Deleted "foo" crate
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable


Hi foo,

Your "foo" crate has been deleted, per your request.

If you did not initiate this deletion, your account may have been compromised. Please contact us at [email protected].

--
The crates.io Team
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ Subject: crates.io: Successfully published [email protected]
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable


Hello foo!

A new version of the package foo (1.0.0) was published by your account (https://crates.io/users/foo) at [0000-00-00T00:00:00Z].

If you have questions or security concerns, you can contact us at [email protected]. If you would like to stop receiving these security notifications, you can disable them in your account settings.

--
The crates.io Team
----------------------------------------

To: [email protected]
Expand All @@ -21,8 +25,12 @@ Subject: crates.io: Deleted "foo" crate
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable


Hi foo,

Your "foo" crate has been deleted, per your request.

If you did not initiate this deletion, your account may have been compromised. Please contact us at [email protected].

--
The crates.io Team
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ Subject: crates.io: Successfully published [email protected]
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable


Hello foo!

A new version of the package foo (1.0.0) was published by your account (https://crates.io/users/foo) at [0000-00-00T00:00:00Z].

If you have questions or security concerns, you can contact us at help@crates.io. If you would like to stop receiving these security notifications, you can disable them in your account settings.

--
The crates.io Team
----------------------------------------

To: foo@example.com
Expand All @@ -21,8 +25,12 @@ Subject: crates.io: Deleted "foo" crate
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable


Hi foo,

Your "foo" crate has been deleted, per your request.

If you did not initiate this deletion, your account may have been compromised. Please contact us at help@crates.io.

--
The crates.io Team
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ Subject: crates.io: Trusted Publishing configuration added to foo
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable


Hello foo!

crates.io user foo added a new "Trusted Publishing" configuration for GitHub Actions to a crate that you manage (foo). Trusted publishers act as trusted users and can publish new versions of the crate automatically.
crates.io user foo added a new "Trusted Publishing" configuration for GitHub Actions to a crate that you manage ("foo"). Trusted publishers act as trusted users and can publish new versions of the crate automatically.

Trusted Publishing configuration:

Expand All @@ -21,4 +22,7 @@ Trusted Publishing configuration:

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 to communicate with the crates.io support team.
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ Subject: crates.io: Trusted Publishing configuration removed from foo
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable


Hello foo!

crates.io user foo removed a "Trusted Publishing" configuration for GitHub Actions from a crate that you manage (foo).
crates.io user foo removed a "Trusted Publishing" configuration for GitHub Actions from a crate that you manage ("foo").

Trusted Publishing configuration:

Expand All @@ -19,4 +20,7 @@ Trusted Publishing configuration:
- 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] to communicate with the crates.io support team.
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
Expand Up @@ -6,9 +6,16 @@ To: [email protected]
From: crates.io <[email protected]>
Subject: crates.io: Please confirm your email address
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Content-Transfer-Encoding: quoted-printable

Hello foo! Welcome to crates.io. Please click the
link below to verify your email address. Thank you!

Hello foo!

Welcome to crates.io. Please click the link below to verify your email address:

https://crates.io/confirm/[confirm-token]

Thank you!

--
The crates.io Team
51 changes: 48 additions & 3 deletions src/email.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,40 @@ use std::sync::LazyLock;
static EMAIL_ENV: LazyLock<Environment<'static>> = LazyLock::new(|| {
let mut env = Environment::new();

// Load templates from each email directory
// Load templates from the templates directory
let entries = std::fs::read_dir("src/email/templates");
let entries = entries.expect("Failed to read email templates directory");

for entry in entries {
let entry = entry.expect("Failed to read directory entry");

let path = entry.path();
let file_type = entry.file_type().expect("Failed to get file type");

// Handle base template files
if file_type.is_file() && path.extension().and_then(|s| s.to_str()) == Some("j2") {
let template_name = entry.file_name();
let template_name = template_name.to_str();
let template_name = template_name.expect("Invalid UTF-8 in template filename");

let template_contents = std::fs::read_to_string(&path)
.unwrap_or_else(|error| panic!("Failed to read template {template_name}: {error}"));

env.add_template_owned(template_name.to_string(), template_contents)
.expect("Failed to add template");
}

if !file_type.is_dir() {
continue;
}

// Handle email template directories
let dir_name = entry.file_name();
let email_name = dir_name.to_str();
let email_name = email_name.expect("Invalid UTF-8 in email template directory name");

// Load subject.txt.j2 file
let subject_path = entry.path().join("subject.txt.j2");
let subject_path = path.join("subject.txt.j2");
let subject_contents = std::fs::read_to_string(&subject_path).unwrap_or_else(|error| {
panic!("Failed to read subject template for {email_name}: {error}")
});
Expand All @@ -42,7 +58,7 @@ static EMAIL_ENV: LazyLock<Environment<'static>> = LazyLock::new(|| {
.expect("Failed to add subject template");

// Load body.txt.j2 file
let body_path = entry.path().join("body.txt.j2");
let body_path = path.join("body.txt.j2");
let body_contents = std::fs::read_to_string(&body_path).unwrap_or_else(|error| {
panic!("Failed to read body template for {email_name}: {error}")
});
Expand Down Expand Up @@ -234,6 +250,35 @@ pub struct StoredEmail {
mod tests {
use super::*;
use claims::{assert_err, assert_ok};
use minijinja::context;

#[test]
fn test_user_confirm_template_inheritance() {
// Test that the `user_confirm` template inherits properly from the base template
let result = render_template(
"user_confirm/body.txt.j2",
context! {
domain => "crates.io",
user_name => "testuser",
token => "abc123"
},
);
assert_ok!(&result);

let content = result.unwrap();
insta::assert_snapshot!(content, @r"
Hello testuser!

Welcome to crates.io. Please click the link below to verify your email address:

https://crates.io/confirm/abc123

Thank you!

--
The crates.io Team
");
}

#[tokio::test]
async fn sending_to_invalid_email_fails() {
Expand Down
6 changes: 5 additions & 1 deletion src/email/templates/admin_account/body.txt.j2
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{% extends "base.txt.j2" %}

{% block content %}
{% if added_admins -%}
Granted admin access:

Expand All @@ -10,4 +13,5 @@ Revoked admin access:
{% for admin in removed_admins -%}
- {{ admin }}
{% endfor -%}
{% endif %}
{% endif %}
{% endblock %}
3 changes: 3 additions & 0 deletions src/email/templates/base.txt.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% block content %}{% endblock %}
--
The crates.io Team
8 changes: 6 additions & 2 deletions src/email/templates/config_created/body.txt.j2
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{% extends "base.txt.j2" %}

{% block content %}
Hello {{ recipient }}!

crates.io user {{ user }} added a new "Trusted Publishing" configuration for GitHub Actions to a crate that you manage ({{ krate }}). Trusted publishers act as trusted users and can publish new versions of the crate automatically.
crates.io user {{ user }} added a new "Trusted Publishing" configuration for GitHub Actions to a crate that you manage ("{{ krate }}"). Trusted publishers act as trusted users and can publish new versions of the crate automatically.

Trusted Publishing configuration:

Expand All @@ -11,4 +14,5 @@ Trusted Publishing configuration:

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] to communicate with the crates.io support team.
If you are unable to revert the change and need to do so, you can email [email protected] for assistance.
{% endblock %}
8 changes: 6 additions & 2 deletions src/email/templates/config_deleted/body.txt.j2
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{% extends "base.txt.j2" %}

{% block content %}
Hello {{ recipient }}!

crates.io user {{ user }} removed a "Trusted Publishing" configuration for GitHub Actions from a crate that you manage ({{ krate }}).
crates.io user {{ user }} removed a "Trusted Publishing" configuration for GitHub Actions from a crate that you manage ("{{ krate }}").

Trusted Publishing configuration:

Expand All @@ -9,4 +12,5 @@ Trusted Publishing configuration:
- Workflow filename: {{ workflow_filename }}
- Environment: {{ environment or "(not set)" }}

If you did not make this change and you think it was made maliciously, you can email [email protected] to communicate with the crates.io support team.
If you did not make this change and you think it was made maliciously, you can email [email protected] for assistance.
{% endblock %}
6 changes: 5 additions & 1 deletion src/email/templates/crate_deletion/body.txt.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{% extends "base.txt.j2" %}

{% block content %}
Hi {{ user }},

Your "{{ krate }}" crate has been deleted, per your request.

If you did not initiate this deletion, your account may have been compromised. Please contact us at [email protected].
If you did not initiate this deletion, your account may have been compromised. Please contact us at [email protected].
{% endblock %}
9 changes: 5 additions & 4 deletions src/email/templates/expiry_notification/body.txt.j2
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
{% extends "base.txt.j2" %}

{% block content %}
Hi {{ name }},

We noticed your token "{{ token_name }}" will expire on {{ expiry_date }}.
We noticed that your token "{{ token_name }}" will expire on {{ expiry_date }}.

If this token is still needed, visit https://crates.io/settings/tokens/new?from={{ token_id }} to generate a new one.

Thanks,
The crates.io team
{% endblock %}
6 changes: 5 additions & 1 deletion src/email/templates/new_token/body.txt.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{% extends "base.txt.j2" %}

{% block content %}
Hello {{ user_name }}!

A new API token with the name "{{ token_name }}" was recently added to your {{ domain }} account.

If this wasn't you, you should revoke the token immediately: https://{{ domain }}/settings/tokens
If you did not create this token, you should revoke it immediately: https://{{ domain }}/settings/tokens.
{% endblock %}
9 changes: 7 additions & 2 deletions src/email/templates/owner_invite/body.txt.j2
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{% extends "base.txt.j2" %}

{% block content %}
{{ inviter }} has invited you to become an owner of the crate {{ crate_name }}!

Visit https://{{ domain }}/accept-invite/{{ token }} to accept this invitation,
or go to https://{{ domain }}/me/pending-invites to manage all of your crate ownership invitations.
Visit https://{{ domain }}/accept-invite/{{ token }} to accept this invitation.

You can also go to https://{{ domain }}/me/pending-invites to manage all of your crate ownership invitations.
{% endblock %}
6 changes: 5 additions & 1 deletion src/email/templates/possible_typosquat/body.txt.j2
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{% extends "base.txt.j2" %}

{% block content %}
New crate {{ crate_name }} may be typosquatting one or more other crates.

Visit https://{{ domain }}/crates/{{ crate_name }} to see the offending crate.
Expand All @@ -6,4 +9,5 @@ Specific squat checks that triggered:

{% for squat in squats -%}
- {{ squat.display }} (https://{{ domain }}/crates/{{ squat.package }})
{% endfor %}
{% endfor %}
{% endblock %}
6 changes: 5 additions & 1 deletion src/email/templates/publish_notification/body.txt.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{% extends "base.txt.j2" %}

{% block content %}
Hello {{ recipient }}!

A new version of the package {{ krate }} ({{ version }}) was published{{ publisher_info }} at {{ publish_time }}.

If you have questions or security concerns, you can contact us at [email protected]. If you would like to stop receiving these security notifications, you can disable them in your account settings.
If you have questions or security concerns, you can contact us at [email protected]. If you would like to stop receiving these security notifications, you can disable them in your account settings.
{% endblock %}
6 changes: 5 additions & 1 deletion src/email/templates/token_exposed/body.txt.j2
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{% extends "base.txt.j2" %}

{% block content %}
{{ reporter }} has notified us that your crates.io API token {{ token_name }} has been exposed publicly. We have revoked this token as a precaution.

Please review your account at https://{{ domain }} to confirm that no unexpected changes have been made to your settings or crates.
Expand All @@ -7,5 +10,6 @@ Source type: {{ source }}
{% if url -%}
URL where the token was found: {{ url }}
{%- else -%}
We were not informed of the URL where the token was found.
We were not provided with the URL where the token was found.
{%- endif %}
{% endblock %}
Loading