this repo has no description
1mod common;
2use reqwest::StatusCode;
3use serde_json::{Value, json};
4use sqlx::PgPool;
5
6async fn get_email_update_token(pool: &PgPool, did: &str) -> String {
7 let body_text: String = sqlx::query_scalar!(
8 "SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_update' ORDER BY created_at DESC LIMIT 1",
9 did
10 )
11 .fetch_one(pool)
12 .await
13 .expect("Verification not found");
14
15 body_text
16 .lines()
17 .skip_while(|line| !line.contains("verification code"))
18 .nth(1)
19 .map(|line| line.trim().to_string())
20 .filter(|line| !line.is_empty() && line.contains('-'))
21 .unwrap_or_else(|| {
22 body_text
23 .lines()
24 .find(|line| line.trim().starts_with("MX") && line.contains('-'))
25 .map(|s| s.trim().to_string())
26 .unwrap_or_default()
27 })
28}
29
30async fn create_verified_account(
31 client: &reqwest::Client,
32 base_url: &str,
33 handle: &str,
34 email: &str,
35) -> (String, String) {
36 let res = client
37 .post(format!(
38 "{}/xrpc/com.atproto.server.createAccount",
39 base_url
40 ))
41 .json(&json!({
42 "handle": handle,
43 "email": email,
44 "password": "Testpass123!"
45 }))
46 .send()
47 .await
48 .expect("Failed to create account");
49 assert_eq!(res.status(), StatusCode::OK);
50 let body: Value = res.json().await.expect("Invalid JSON");
51 let did = body["did"].as_str().expect("No did").to_string();
52 let jwt = common::verify_new_account(client, &did).await;
53 (jwt, did)
54}
55
56#[tokio::test]
57async fn test_request_email_update_returns_token_required() {
58 let client = common::client();
59 let base_url = common::base_url().await;
60 let handle = format!("er{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
61 let email = format!("{}@example.com", handle);
62 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
63
64 let res = client
65 .post(format!(
66 "{}/xrpc/com.atproto.server.requestEmailUpdate",
67 base_url
68 ))
69 .bearer_auth(&access_jwt)
70 .send()
71 .await
72 .expect("Failed to request email update");
73 assert_eq!(res.status(), StatusCode::OK);
74 let body: Value = res.json().await.expect("Invalid JSON");
75 assert_eq!(body["tokenRequired"], true);
76}
77
78#[tokio::test]
79async fn test_update_email_flow_success() {
80 let client = common::client();
81 let base_url = common::base_url().await;
82 let pool = common::get_test_db_pool().await;
83 let handle = format!("eu{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
84 let email = format!("{}@example.com", handle);
85 let (access_jwt, did) = create_verified_account(&client, &base_url, &handle, &email).await;
86 let new_email = format!("new_{}@example.com", handle);
87
88 let res = client
89 .post(format!(
90 "{}/xrpc/com.atproto.server.requestEmailUpdate",
91 base_url
92 ))
93 .bearer_auth(&access_jwt)
94 .send()
95 .await
96 .expect("Failed to request email update");
97 assert_eq!(res.status(), StatusCode::OK);
98 let body: Value = res.json().await.expect("Invalid JSON");
99 assert_eq!(body["tokenRequired"], true);
100
101 let code = get_email_update_token(pool, &did).await;
102
103 let res = client
104 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
105 .bearer_auth(&access_jwt)
106 .json(&json!({
107 "email": new_email,
108 "token": code
109 }))
110 .send()
111 .await
112 .expect("Failed to update email");
113 assert_eq!(res.status(), StatusCode::OK);
114
115 let user_email: Option<String> = sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did)
116 .fetch_one(pool)
117 .await
118 .expect("User not found");
119 assert_eq!(user_email, Some(new_email));
120}
121
122#[tokio::test]
123async fn test_update_email_requires_token_when_verified() {
124 let client = common::client();
125 let base_url = common::base_url().await;
126 let handle = format!("ed{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
127 let email = format!("{}@example.com", handle);
128 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
129 let new_email = format!("direct_{}@example.com", handle);
130
131 let res = client
132 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
133 .bearer_auth(&access_jwt)
134 .json(&json!({ "email": new_email }))
135 .send()
136 .await
137 .expect("Failed to update email");
138 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
139 let body: Value = res.json().await.expect("Invalid JSON");
140 assert_eq!(body["error"], "TokenRequired");
141}
142
143#[tokio::test]
144async fn test_update_email_same_email_noop() {
145 let client = common::client();
146 let base_url = common::base_url().await;
147 let handle = format!("es{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
148 let email = format!("{}@example.com", handle);
149 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
150
151 let res = client
152 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
153 .bearer_auth(&access_jwt)
154 .json(&json!({ "email": email }))
155 .send()
156 .await
157 .expect("Failed to update email");
158 assert_eq!(
159 res.status(),
160 StatusCode::OK,
161 "Updating to same email should succeed as no-op"
162 );
163}
164
165#[tokio::test]
166async fn test_update_email_invalid_token() {
167 let client = common::client();
168 let base_url = common::base_url().await;
169 let handle = format!("eb{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
170 let email = format!("{}@example.com", handle);
171 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
172 let new_email = format!("badtok_{}@example.com", handle);
173
174 let res = client
175 .post(format!(
176 "{}/xrpc/com.atproto.server.requestEmailUpdate",
177 base_url
178 ))
179 .bearer_auth(&access_jwt)
180 .send()
181 .await
182 .expect("Failed to request email update");
183 assert_eq!(res.status(), StatusCode::OK);
184
185 let res = client
186 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
187 .bearer_auth(&access_jwt)
188 .json(&json!({
189 "email": new_email,
190 "token": "wrong-token-12345"
191 }))
192 .send()
193 .await
194 .expect("Failed to attempt email update");
195 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
196 let body: Value = res.json().await.expect("Invalid JSON");
197 assert_eq!(body["error"], "InvalidToken");
198}
199
200#[tokio::test]
201async fn test_update_email_no_auth() {
202 let client = common::client();
203 let base_url = common::base_url().await;
204
205 let res = client
206 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
207 .json(&json!({ "email": "test@example.com" }))
208 .send()
209 .await
210 .expect("Failed to send request");
211 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
212 let body: Value = res.json().await.expect("Invalid JSON");
213 assert_eq!(body["error"], "AuthenticationRequired");
214}
215
216#[tokio::test]
217async fn test_update_email_invalid_format() {
218 let client = common::client();
219 let base_url = common::base_url().await;
220 let handle = format!("ef{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
221 let email = format!("{}@example.com", handle);
222 let (access_jwt, _) = create_verified_account(&client, &base_url, &handle, &email).await;
223
224 let res = client
225 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
226 .bearer_auth(&access_jwt)
227 .json(&json!({ "email": "not-an-email" }))
228 .send()
229 .await
230 .expect("Failed to send request");
231 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
232}
233
234#[tokio::test]
235async fn test_confirm_email_confirms_existing_email() {
236 let client = common::client();
237 let base_url = common::base_url().await;
238 let pool = common::get_test_db_pool().await;
239 let handle = format!("ec{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
240 let email = format!("{}@example.com", handle);
241
242 let res = client
243 .post(format!(
244 "{}/xrpc/com.atproto.server.createAccount",
245 base_url
246 ))
247 .json(&json!({
248 "handle": handle,
249 "email": email,
250 "password": "Testpass123!"
251 }))
252 .send()
253 .await
254 .expect("Failed to create account");
255 assert_eq!(res.status(), StatusCode::OK);
256 let body: Value = res.json().await.expect("Invalid JSON");
257 let did = body["did"].as_str().expect("No did").to_string();
258 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string();
259
260 let body_text: String = sqlx::query_scalar!(
261 "SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_verification' ORDER BY created_at DESC LIMIT 1",
262 did
263 )
264 .fetch_one(pool)
265 .await
266 .expect("Verification email not found");
267
268 let code = body_text
269 .lines()
270 .find(|line| line.trim().starts_with("MX") && line.contains('-'))
271 .map(|s| s.trim().to_string())
272 .unwrap_or_default();
273
274 let res = client
275 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url))
276 .bearer_auth(&access_jwt)
277 .json(&json!({
278 "email": email,
279 "token": code
280 }))
281 .send()
282 .await
283 .expect("Failed to confirm email");
284 assert_eq!(res.status(), StatusCode::OK);
285
286 let verified: bool = sqlx::query_scalar!(
287 "SELECT email_verified FROM users WHERE did = $1",
288 did
289 )
290 .fetch_one(pool)
291 .await
292 .expect("User not found");
293 assert!(verified);
294}
295
296#[tokio::test]
297async fn test_confirm_email_rejects_wrong_email() {
298 let client = common::client();
299 let base_url = common::base_url().await;
300 let pool = common::get_test_db_pool().await;
301 let handle = format!("ew{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
302 let email = format!("{}@example.com", handle);
303
304 let res = client
305 .post(format!(
306 "{}/xrpc/com.atproto.server.createAccount",
307 base_url
308 ))
309 .json(&json!({
310 "handle": handle,
311 "email": email,
312 "password": "Testpass123!"
313 }))
314 .send()
315 .await
316 .expect("Failed to create account");
317 assert_eq!(res.status(), StatusCode::OK);
318 let body: Value = res.json().await.expect("Invalid JSON");
319 let did = body["did"].as_str().expect("No did").to_string();
320 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string();
321
322 let body_text: String = sqlx::query_scalar!(
323 "SELECT body FROM comms_queue WHERE user_id = (SELECT id FROM users WHERE did = $1) AND comms_type = 'email_verification' ORDER BY created_at DESC LIMIT 1",
324 did
325 )
326 .fetch_one(pool)
327 .await
328 .expect("Verification email not found");
329
330 let code = body_text
331 .lines()
332 .find(|line| line.trim().starts_with("MX") && line.contains('-'))
333 .map(|s| s.trim().to_string())
334 .unwrap_or_default();
335
336 let res = client
337 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url))
338 .bearer_auth(&access_jwt)
339 .json(&json!({
340 "email": "different@example.com",
341 "token": code
342 }))
343 .send()
344 .await
345 .expect("Failed to confirm email");
346 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
347 let body: Value = res.json().await.expect("Invalid JSON");
348 assert_eq!(body["error"], "InvalidEmail");
349}
350
351#[tokio::test]
352async fn test_confirm_email_invalid_token() {
353 let client = common::client();
354 let base_url = common::base_url().await;
355 let handle = format!("ei{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
356 let email = format!("{}@example.com", handle);
357
358 let res = client
359 .post(format!(
360 "{}/xrpc/com.atproto.server.createAccount",
361 base_url
362 ))
363 .json(&json!({
364 "handle": handle,
365 "email": email,
366 "password": "Testpass123!"
367 }))
368 .send()
369 .await
370 .expect("Failed to create account");
371 assert_eq!(res.status(), StatusCode::OK);
372 let body: Value = res.json().await.expect("Invalid JSON");
373 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string();
374
375 let res = client
376 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url))
377 .bearer_auth(&access_jwt)
378 .json(&json!({
379 "email": email,
380 "token": "wrong-token"
381 }))
382 .send()
383 .await
384 .expect("Failed to confirm email");
385 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
386 let body: Value = res.json().await.expect("Invalid JSON");
387 assert_eq!(body["error"], "InvalidToken");
388}
389
390#[tokio::test]
391async fn test_unverified_account_can_update_email_without_token() {
392 let client = common::client();
393 let base_url = common::base_url().await;
394 let pool = common::get_test_db_pool().await;
395 let handle = format!("ev{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
396 let email = format!("{}@example.com", handle);
397
398 let res = client
399 .post(format!(
400 "{}/xrpc/com.atproto.server.createAccount",
401 base_url
402 ))
403 .json(&json!({
404 "handle": handle,
405 "email": email,
406 "password": "Testpass123!"
407 }))
408 .send()
409 .await
410 .expect("Failed to create account");
411 assert_eq!(res.status(), StatusCode::OK);
412 let body: Value = res.json().await.expect("Invalid JSON");
413 let did = body["did"].as_str().expect("No did").to_string();
414 let access_jwt = body["accessJwt"].as_str().expect("No accessJwt").to_string();
415
416 let res = client
417 .post(format!(
418 "{}/xrpc/com.atproto.server.requestEmailUpdate",
419 base_url
420 ))
421 .bearer_auth(&access_jwt)
422 .send()
423 .await
424 .expect("Failed to request email update");
425 assert_eq!(res.status(), StatusCode::OK);
426 let body: Value = res.json().await.expect("Invalid JSON");
427 assert_eq!(
428 body["tokenRequired"], false,
429 "Unverified account should not require token"
430 );
431
432 let new_email = format!("new_{}@example.com", handle);
433 let res = client
434 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
435 .bearer_auth(&access_jwt)
436 .json(&json!({ "email": new_email }))
437 .send()
438 .await
439 .expect("Failed to update email");
440 assert_eq!(
441 res.status(),
442 StatusCode::OK,
443 "Unverified account should be able to update email without token"
444 );
445
446 let user_email: Option<String> =
447 sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did)
448 .fetch_one(pool)
449 .await
450 .expect("User not found");
451 assert_eq!(user_email, Some(new_email));
452}
453
454#[tokio::test]
455async fn test_update_email_taken_by_another_user() {
456 let client = common::client();
457 let base_url = common::base_url().await;
458 let pool = common::get_test_db_pool().await;
459
460 let handle1 = format!("d1{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
461 let email1 = format!("{}@example.com", handle1);
462 let (_, _) = create_verified_account(&client, &base_url, &handle1, &email1).await;
463
464 let handle2 = format!("d2{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
465 let email2 = format!("{}@example.com", handle2);
466 let (access_jwt2, did2) = create_verified_account(&client, &base_url, &handle2, &email2).await;
467
468 let res = client
469 .post(format!(
470 "{}/xrpc/com.atproto.server.requestEmailUpdate",
471 base_url
472 ))
473 .bearer_auth(&access_jwt2)
474 .send()
475 .await
476 .expect("Failed to request email update");
477 assert_eq!(res.status(), StatusCode::OK);
478
479 let code = get_email_update_token(pool, &did2).await;
480
481 let res = client
482 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
483 .bearer_auth(&access_jwt2)
484 .json(&json!({
485 "email": email1,
486 "token": code
487 }))
488 .send()
489 .await
490 .expect("Failed to update email");
491 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
492 let body: Value = res.json().await.expect("Invalid JSON");
493 assert_eq!(body["error"], "InvalidRequest");
494 assert!(body["message"]
495 .as_str()
496 .unwrap_or("")
497 .contains("already in use"));
498}