@@ -5,9 +5,12 @@ use diesel_async::AsyncPgConnection;
5
5
use typomania:: Package ;
6
6
7
7
use crate :: Emails ;
8
- use crate :: email:: Email ;
8
+ use crate :: email:: EmailMessage ;
9
9
use crate :: typosquat:: { Cache , Crate } ;
10
10
use crate :: worker:: Environment ;
11
+ use anyhow:: Context ;
12
+ use minijinja:: context;
13
+ use tracing:: { error, info} ;
11
14
12
15
/// A job to check the name of a newly published crate against the most popular crates to see if
13
16
/// the new crate might be typosquatting an existing, popular crate.
@@ -55,14 +58,25 @@ async fn check(
55
58
// hopefully care to check into things more closely.
56
59
info ! ( ?squats, "Found potential typosquatting" ) ;
57
60
58
- let email = PossibleTyposquatEmail {
59
- domain : & emails. domain ,
60
- crate_name : name,
61
- squats : & squats,
61
+ let squats_data: Vec < _ > = squats
62
+ . iter ( )
63
+ . map ( |squat| {
64
+ context ! {
65
+ display => squat. to_string( ) ,
66
+ package => squat. package( )
67
+ }
68
+ } )
69
+ . collect ( ) ;
70
+
71
+ let email_context = context ! {
72
+ domain => emails. domain,
73
+ crate_name => name,
74
+ squats => squats_data
62
75
} ;
63
76
64
77
for recipient in cache. iter_emails ( ) {
65
- if let Err ( error) = emails. send ( recipient, email. clone ( ) ) . await {
78
+ if let Err ( error) = send_notification_email ( emails, recipient, & email_context) . await
79
+ {
66
80
error ! (
67
81
?error,
68
82
?recipient,
@@ -76,45 +90,18 @@ async fn check(
76
90
Ok ( ( ) )
77
91
}
78
92
79
- #[ derive( Debug , Clone ) ]
80
- struct PossibleTyposquatEmail < ' a > {
81
- domain : & ' a str ,
82
- crate_name : & ' a str ,
83
- squats : & ' a [ typomania:: checks:: Squat ] ,
84
- }
85
-
86
- impl Email for PossibleTyposquatEmail < ' _ > {
87
- fn subject ( & self ) -> String {
88
- format ! (
89
- "crates.io: Possible typosquatting in new crate \" {}\" " ,
90
- self . crate_name
91
- )
92
- }
93
-
94
- fn body ( & self ) -> String {
95
- let squats = self
96
- . squats
97
- . iter ( )
98
- . map ( |squat| {
99
- let domain = self . domain ;
100
- let crate_name = squat. package ( ) ;
101
- format ! ( "- {squat} (https://{domain}/crates/{crate_name})\n " )
102
- } )
103
- . collect :: < Vec < _ > > ( )
104
- . join ( "" ) ;
105
-
106
- format ! (
107
- "New crate {crate_name} may be typosquatting one or more other crates.
108
-
109
- Visit https://{domain}/crates/{crate_name} to see the offending crate.
110
-
111
- Specific squat checks that triggered:
93
+ async fn send_notification_email (
94
+ emails : & Emails ,
95
+ recipient : & str ,
96
+ context : & minijinja:: Value ,
97
+ ) -> anyhow:: Result < ( ) > {
98
+ let email = EmailMessage :: from_template ( "possible_typosquat" , context)
99
+ . context ( "Failed to render email template" ) ?;
112
100
113
- {squats}" ,
114
- domain = self . domain,
115
- crate_name = self . crate_name,
116
- )
117
- }
101
+ emails
102
+ . send ( recipient, email)
103
+ . await
104
+ . context ( "Failed to send email" )
118
105
}
119
106
120
107
#[ cfg( test) ]
0 commit comments