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> =
116 sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did)
117 .fetch_one(pool)
118 .await
119 .expect("User not found");
120 assert_eq!(user_email, Some(new_email));
121}
122
123#[tokio::test]
124async fn test_update_email_requires_token_when_verified() {
125 let client = common::client();
126 let base_url = common::base_url().await;
127 let handle = format!("ed{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
128 let email = format!("{}@example.com", handle);
129 let (access_jwt, _) = create_verified_account(&client, base_url, &handle, &email).await;
130 let new_email = format!("direct_{}@example.com", handle);
131
132 let res = client
133 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
134 .bearer_auth(&access_jwt)
135 .json(&json!({ "email": new_email }))
136 .send()
137 .await
138 .expect("Failed to update email");
139 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
140 let body: Value = res.json().await.expect("Invalid JSON");
141 assert_eq!(body["error"], "TokenRequired");
142}
143
144#[tokio::test]
145async fn test_update_email_same_email_noop() {
146 let client = common::client();
147 let base_url = common::base_url().await;
148 let handle = format!("es{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
149 let email = format!("{}@example.com", handle);
150 let (access_jwt, _) = create_verified_account(&client, base_url, &handle, &email).await;
151
152 let res = client
153 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
154 .bearer_auth(&access_jwt)
155 .json(&json!({ "email": email }))
156 .send()
157 .await
158 .expect("Failed to update email");
159 assert_eq!(
160 res.status(),
161 StatusCode::OK,
162 "Updating to same email should succeed as no-op"
163 );
164}
165
166#[tokio::test]
167async fn test_update_email_invalid_token() {
168 let client = common::client();
169 let base_url = common::base_url().await;
170 let handle = format!("eb{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
171 let email = format!("{}@example.com", handle);
172 let (access_jwt, _) = create_verified_account(&client, base_url, &handle, &email).await;
173 let new_email = format!("badtok_{}@example.com", handle);
174
175 let res = client
176 .post(format!(
177 "{}/xrpc/com.atproto.server.requestEmailUpdate",
178 base_url
179 ))
180 .bearer_auth(&access_jwt)
181 .send()
182 .await
183 .expect("Failed to request email update");
184 assert_eq!(res.status(), StatusCode::OK);
185
186 let res = client
187 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
188 .bearer_auth(&access_jwt)
189 .json(&json!({
190 "email": new_email,
191 "token": "wrong-token-12345"
192 }))
193 .send()
194 .await
195 .expect("Failed to attempt email update");
196 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
197 let body: Value = res.json().await.expect("Invalid JSON");
198 assert_eq!(body["error"], "InvalidToken");
199}
200
201#[tokio::test]
202async fn test_update_email_no_auth() {
203 let client = common::client();
204 let base_url = common::base_url().await;
205
206 let res = client
207 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
208 .json(&json!({ "email": "test@example.com" }))
209 .send()
210 .await
211 .expect("Failed to send request");
212 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
213 let body: Value = res.json().await.expect("Invalid JSON");
214 assert_eq!(body["error"], "AuthenticationRequired");
215}
216
217#[tokio::test]
218async fn test_update_email_invalid_format() {
219 let client = common::client();
220 let base_url = common::base_url().await;
221 let handle = format!("ef{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
222 let email = format!("{}@example.com", handle);
223 let (access_jwt, _) = create_verified_account(&client, base_url, &handle, &email).await;
224
225 let res = client
226 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
227 .bearer_auth(&access_jwt)
228 .json(&json!({ "email": "not-an-email" }))
229 .send()
230 .await
231 .expect("Failed to send request");
232 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
233}
234
235#[tokio::test]
236async fn test_confirm_email_confirms_existing_email() {
237 let client = common::client();
238 let base_url = common::base_url().await;
239 let pool = common::get_test_db_pool().await;
240 let handle = format!("ec{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
241 let email = format!("{}@example.com", handle);
242
243 let res = client
244 .post(format!(
245 "{}/xrpc/com.atproto.server.createAccount",
246 base_url
247 ))
248 .json(&json!({
249 "handle": handle,
250 "email": email,
251 "password": "Testpass123!"
252 }))
253 .send()
254 .await
255 .expect("Failed to create account");
256 assert_eq!(res.status(), StatusCode::OK);
257 let body: Value = res.json().await.expect("Invalid JSON");
258 let did = body["did"].as_str().expect("No did").to_string();
259 let access_jwt = body["accessJwt"]
260 .as_str()
261 .expect("No accessJwt")
262 .to_string();
263
264 let body_text: String = sqlx::query_scalar!(
265 "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",
266 did
267 )
268 .fetch_one(pool)
269 .await
270 .expect("Verification email not found");
271
272 let code = body_text
273 .lines()
274 .find(|line| line.trim().starts_with("MX") && line.contains('-'))
275 .map(|s| s.trim().to_string())
276 .unwrap_or_default();
277
278 let res = client
279 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url))
280 .bearer_auth(&access_jwt)
281 .json(&json!({
282 "email": email,
283 "token": code
284 }))
285 .send()
286 .await
287 .expect("Failed to confirm email");
288 assert_eq!(res.status(), StatusCode::OK);
289
290 let verified: bool =
291 sqlx::query_scalar!("SELECT email_verified FROM users WHERE did = $1", did)
292 .fetch_one(pool)
293 .await
294 .expect("User not found");
295 assert!(verified);
296}
297
298#[tokio::test]
299async fn test_confirm_email_rejects_wrong_email() {
300 let client = common::client();
301 let base_url = common::base_url().await;
302 let pool = common::get_test_db_pool().await;
303 let handle = format!("ew{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
304 let email = format!("{}@example.com", handle);
305
306 let res = client
307 .post(format!(
308 "{}/xrpc/com.atproto.server.createAccount",
309 base_url
310 ))
311 .json(&json!({
312 "handle": handle,
313 "email": email,
314 "password": "Testpass123!"
315 }))
316 .send()
317 .await
318 .expect("Failed to create account");
319 assert_eq!(res.status(), StatusCode::OK);
320 let body: Value = res.json().await.expect("Invalid JSON");
321 let did = body["did"].as_str().expect("No did").to_string();
322 let access_jwt = body["accessJwt"]
323 .as_str()
324 .expect("No accessJwt")
325 .to_string();
326
327 let body_text: String = sqlx::query_scalar!(
328 "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",
329 did
330 )
331 .fetch_one(pool)
332 .await
333 .expect("Verification email not found");
334
335 let code = body_text
336 .lines()
337 .find(|line| line.trim().starts_with("MX") && line.contains('-'))
338 .map(|s| s.trim().to_string())
339 .unwrap_or_default();
340
341 let res = client
342 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url))
343 .bearer_auth(&access_jwt)
344 .json(&json!({
345 "email": "different@example.com",
346 "token": code
347 }))
348 .send()
349 .await
350 .expect("Failed to confirm email");
351 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
352 let body: Value = res.json().await.expect("Invalid JSON");
353 assert_eq!(body["error"], "InvalidEmail");
354}
355
356#[tokio::test]
357async fn test_confirm_email_invalid_token() {
358 let client = common::client();
359 let base_url = common::base_url().await;
360 let handle = format!("ei{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
361 let email = format!("{}@example.com", handle);
362
363 let res = client
364 .post(format!(
365 "{}/xrpc/com.atproto.server.createAccount",
366 base_url
367 ))
368 .json(&json!({
369 "handle": handle,
370 "email": email,
371 "password": "Testpass123!"
372 }))
373 .send()
374 .await
375 .expect("Failed to create account");
376 assert_eq!(res.status(), StatusCode::OK);
377 let body: Value = res.json().await.expect("Invalid JSON");
378 let access_jwt = body["accessJwt"]
379 .as_str()
380 .expect("No accessJwt")
381 .to_string();
382
383 let res = client
384 .post(format!("{}/xrpc/com.atproto.server.confirmEmail", base_url))
385 .bearer_auth(&access_jwt)
386 .json(&json!({
387 "email": email,
388 "token": "wrong-token"
389 }))
390 .send()
391 .await
392 .expect("Failed to confirm email");
393 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
394 let body: Value = res.json().await.expect("Invalid JSON");
395 assert_eq!(body["error"], "InvalidToken");
396}
397
398#[tokio::test]
399async fn test_unverified_account_can_update_email_without_token() {
400 let client = common::client();
401 let base_url = common::base_url().await;
402 let pool = common::get_test_db_pool().await;
403 let handle = format!("ev{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
404 let email = format!("{}@example.com", handle);
405
406 let res = client
407 .post(format!(
408 "{}/xrpc/com.atproto.server.createAccount",
409 base_url
410 ))
411 .json(&json!({
412 "handle": handle,
413 "email": email,
414 "password": "Testpass123!"
415 }))
416 .send()
417 .await
418 .expect("Failed to create account");
419 assert_eq!(res.status(), StatusCode::OK);
420 let body: Value = res.json().await.expect("Invalid JSON");
421 let did = body["did"].as_str().expect("No did").to_string();
422 let access_jwt = body["accessJwt"]
423 .as_str()
424 .expect("No accessJwt")
425 .to_string();
426
427 let res = client
428 .post(format!(
429 "{}/xrpc/com.atproto.server.requestEmailUpdate",
430 base_url
431 ))
432 .bearer_auth(&access_jwt)
433 .send()
434 .await
435 .expect("Failed to request email update");
436 assert_eq!(res.status(), StatusCode::OK);
437 let body: Value = res.json().await.expect("Invalid JSON");
438 assert_eq!(
439 body["tokenRequired"], false,
440 "Unverified account should not require token"
441 );
442
443 let new_email = format!("new_{}@example.com", handle);
444 let res = client
445 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
446 .bearer_auth(&access_jwt)
447 .json(&json!({ "email": new_email }))
448 .send()
449 .await
450 .expect("Failed to update email");
451 assert_eq!(
452 res.status(),
453 StatusCode::OK,
454 "Unverified account should be able to update email without token"
455 );
456
457 let user_email: Option<String> =
458 sqlx::query_scalar!("SELECT email FROM users WHERE did = $1", did)
459 .fetch_one(pool)
460 .await
461 .expect("User not found");
462 assert_eq!(user_email, Some(new_email));
463}
464
465#[tokio::test]
466async fn test_update_email_taken_by_another_user() {
467 let client = common::client();
468 let base_url = common::base_url().await;
469 let pool = common::get_test_db_pool().await;
470
471 let handle1 = format!("d1{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
472 let email1 = format!("{}@example.com", handle1);
473 let (_, _) = create_verified_account(&client, base_url, &handle1, &email1).await;
474
475 let handle2 = format!("d2{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
476 let email2 = format!("{}@example.com", handle2);
477 let (access_jwt2, did2) = create_verified_account(&client, base_url, &handle2, &email2).await;
478
479 let res = client
480 .post(format!(
481 "{}/xrpc/com.atproto.server.requestEmailUpdate",
482 base_url
483 ))
484 .bearer_auth(&access_jwt2)
485 .send()
486 .await
487 .expect("Failed to request email update");
488 assert_eq!(res.status(), StatusCode::OK);
489
490 let code = get_email_update_token(pool, &did2).await;
491
492 let res = client
493 .post(format!("{}/xrpc/com.atproto.server.updateEmail", base_url))
494 .bearer_auth(&access_jwt2)
495 .json(&json!({
496 "email": email1,
497 "token": code
498 }))
499 .send()
500 .await
501 .expect("Failed to update email");
502 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
503 let body: Value = res.json().await.expect("Invalid JSON");
504 assert_eq!(body["error"], "InvalidRequest");
505 assert!(
506 body["message"]
507 .as_str()
508 .unwrap_or("")
509 .contains("already in use")
510 );
511}