@@ -3,6 +3,7 @@ use crate::models::{ApiToken, User};
3
3
use crate :: schema:: api_tokens;
4
4
use crate :: util:: read_fill;
5
5
use crate :: util:: token:: SecureToken ;
6
+ use anyhow:: { anyhow, Context } ;
6
7
use base64;
7
8
use once_cell:: sync:: Lazy ;
8
9
use ring:: signature;
@@ -154,50 +155,64 @@ fn alert_revoke_token(
154
155
155
156
let hashed_token = SecureToken :: hash ( & alert. token ) ;
156
157
157
- // not using ApiToken::find_by_api_token in order to preserve last_used_at
158
- // the token field has a uniqueness constraint so get_result() should be safe to use
159
- let token = diesel:: update ( api_tokens:: table)
158
+ // Not using `ApiToken::find_by_api_token()` in order to preserve `last_used_at`
159
+ let token = api_tokens:: table
160
160
. filter ( api_tokens:: token. eq ( hashed_token) )
161
- . filter ( api_tokens:: revoked. eq ( false ) )
162
- . set ( api_tokens:: revoked. eq ( true ) )
163
161
. get_result :: < ApiToken > ( & * conn)
164
162
. optional ( ) ?;
165
163
166
164
let Some ( token) = token else {
165
+ debug ! ( "Unknown API token received (false positive)" ) ;
167
166
return Ok ( GitHubSecretAlertFeedbackLabel :: FalsePositive ) ;
168
167
} ;
169
168
170
- // send email notification to the token owner
171
- let user = User :: find ( & conn, token. user_id ) ?;
169
+ if token. revoked {
170
+ debug ! (
171
+ token_id = %token. id, user_id = %token. user_id,
172
+ "Already revoked API token received (true positive)" ,
173
+ ) ;
174
+ return Ok ( GitHubSecretAlertFeedbackLabel :: TruePositive ) ;
175
+ }
176
+
177
+ diesel:: update ( & token)
178
+ . set ( api_tokens:: revoked. eq ( true ) )
179
+ . execute ( & * conn) ?;
180
+
172
181
warn ! (
173
- gh_login = %user . gh_login , user_id = %user . id , token_id = % token. id ,
174
- "Revoked API token" ,
182
+ token_id = %token . id , user_id = %token. user_id ,
183
+ "Active API token received and revoked (true positive) " ,
175
184
) ;
176
185
177
- if let Some ( email) = user. email ( & conn) ? {
178
- let result = req. app ( ) . emails . send_token_exposed_notification (
179
- & email,
180
- & alert. url ,
181
- "GitHub" ,
182
- & alert. source ,
183
- & token. name ,
184
- ) ;
185
- if let Err ( error) = result {
186
- warn ! (
187
- gh_login = %user. gh_login, user_id = %user. id, ?error,
188
- "Failed to send email notification" ,
189
- ) ;
190
- }
191
- } else {
186
+ if let Err ( error) = send_notification_email ( & token, alert, req) {
192
187
warn ! (
193
- gh_login = %user . gh_login , user_id = %user . id , error = "No address found" ,
188
+ token_id = %token . id , user_id = %token . user_id , ? error,
194
189
"Failed to send email notification" ,
195
- ) ;
196
- } ;
190
+ )
191
+ }
197
192
198
193
Ok ( GitHubSecretAlertFeedbackLabel :: TruePositive )
199
194
}
200
195
196
+ fn send_notification_email (
197
+ token : & ApiToken ,
198
+ alert : & GitHubSecretAlert ,
199
+ req : & dyn RequestExt ,
200
+ ) -> anyhow:: Result < ( ) > {
201
+ let conn = req. db_read ( ) ?;
202
+
203
+ let user = User :: find ( & conn, token. user_id ) . context ( "Failed to find user" ) ?;
204
+ let Some ( email) = user. email ( & conn) ? else {
205
+ return Err ( anyhow ! ( "No address found" ) ) ;
206
+ } ;
207
+
208
+ req. app ( )
209
+ . emails
210
+ . send_token_exposed_notification ( & email, & alert. url , "GitHub" , & alert. source , & token. name )
211
+ . map_err ( |error| anyhow ! ( "{error}" ) ) ?;
212
+
213
+ Ok ( ( ) )
214
+ }
215
+
201
216
#[ derive( Deserialize , Serialize ) ]
202
217
pub struct GitHubSecretAlertFeedback {
203
218
pub token_raw : String ,
0 commit comments