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