this repo has no description
1#![allow(unused_imports)] 2mod common; 3use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 4use bspds::auth::{ 5 self, SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED, SCOPE_REFRESH, 6 TOKEN_TYPE_ACCESS, TOKEN_TYPE_REFRESH, TOKEN_TYPE_SERVICE, create_access_token, 7 create_refresh_token, create_service_token, get_did_from_token, get_jti_from_token, 8 verify_access_token, verify_refresh_token, verify_token, 9}; 10use chrono::{Duration, Utc}; 11use common::{base_url, client, create_account_and_login, get_db_connection_string}; 12use k256::SecretKey; 13use k256::ecdsa::{Signature, SigningKey, signature::Signer}; 14use rand::rngs::OsRng; 15use reqwest::StatusCode; 16use serde_json::{Value, json}; 17use sha2::{Digest, Sha256}; 18 19fn generate_user_key() -> Vec<u8> { 20 let secret_key = SecretKey::random(&mut OsRng); 21 secret_key.to_bytes().to_vec() 22} 23 24fn create_custom_jwt(header: &Value, claims: &Value, key_bytes: &[u8]) -> String { 25 let signing_key = SigningKey::from_slice(key_bytes).expect("valid key"); 26 let header_b64 = URL_SAFE_NO_PAD.encode(serde_json::to_string(header).unwrap()); 27 let claims_b64 = URL_SAFE_NO_PAD.encode(serde_json::to_string(claims).unwrap()); 28 let message = format!("{}.{}", header_b64, claims_b64); 29 let signature: Signature = signing_key.sign(message.as_bytes()); 30 let signature_b64 = URL_SAFE_NO_PAD.encode(signature.to_bytes()); 31 format!("{}.{}", message, signature_b64) 32} 33 34fn create_unsigned_jwt(header: &Value, claims: &Value) -> String { 35 let header_b64 = URL_SAFE_NO_PAD.encode(serde_json::to_string(header).unwrap()); 36 let claims_b64 = URL_SAFE_NO_PAD.encode(serde_json::to_string(claims).unwrap()); 37 format!("{}.{}.", header_b64, claims_b64) 38} 39 40#[test] 41fn test_signature_attacks() { 42 let key_bytes = generate_user_key(); 43 let did = "did:plc:test"; 44 let token = create_access_token(did, &key_bytes).expect("create token"); 45 let parts: Vec<&str> = token.split('.').collect(); 46 47 let forged_signature = URL_SAFE_NO_PAD.encode(&[0u8; 64]); 48 let forged_token = format!("{}.{}.{}", parts[0], parts[1], forged_signature); 49 let result = verify_access_token(&forged_token, &key_bytes); 50 assert!(result.is_err(), "Forged signature must be rejected"); 51 assert!(result.err().unwrap().to_string().to_lowercase().contains("signature")); 52 53 let payload_bytes = URL_SAFE_NO_PAD.decode(parts[1]).unwrap(); 54 let mut payload: Value = serde_json::from_slice(&payload_bytes).unwrap(); 55 payload["sub"] = json!("did:plc:attacker"); 56 let modified_payload = URL_SAFE_NO_PAD.encode(serde_json::to_string(&payload).unwrap()); 57 let modified_token = format!("{}.{}.{}", parts[0], modified_payload, parts[2]); 58 assert!(verify_access_token(&modified_token, &key_bytes).is_err(), "Modified payload must be rejected"); 59 60 let sig_bytes = URL_SAFE_NO_PAD.decode(parts[2]).unwrap(); 61 let truncated_sig = URL_SAFE_NO_PAD.encode(&sig_bytes[..32]); 62 let truncated_token = format!("{}.{}.{}", parts[0], parts[1], truncated_sig); 63 assert!(verify_access_token(&truncated_token, &key_bytes).is_err(), "Truncated signature must be rejected"); 64 65 let mut extended_sig = sig_bytes.clone(); 66 extended_sig.extend_from_slice(&[0u8; 32]); 67 let extended_token = format!("{}.{}.{}", parts[0], parts[1], URL_SAFE_NO_PAD.encode(&extended_sig)); 68 assert!(verify_access_token(&extended_token, &key_bytes).is_err(), "Extended signature must be rejected"); 69 70 let key_bytes_user2 = generate_user_key(); 71 assert!(verify_access_token(&token, &key_bytes_user2).is_err(), "Token signed with different key must be rejected"); 72} 73 74#[test] 75fn test_algorithm_substitution_attacks() { 76 let key_bytes = generate_user_key(); 77 let did = "did:plc:test"; 78 79 let none_header = json!({ "alg": "none", "typ": TOKEN_TYPE_ACCESS }); 80 let claims = json!({ 81 "iss": did, "sub": did, "aud": "did:web:test.pds", 82 "iat": Utc::now().timestamp(), "exp": Utc::now().timestamp() + 3600, 83 "jti": "attack-token", "scope": SCOPE_ACCESS 84 }); 85 let none_token = create_unsigned_jwt(&none_header, &claims); 86 assert!(verify_access_token(&none_token, &key_bytes).is_err(), "Algorithm 'none' must be rejected"); 87 88 let hs256_header = json!({ "alg": "HS256", "typ": TOKEN_TYPE_ACCESS }); 89 let header_b64 = URL_SAFE_NO_PAD.encode(serde_json::to_string(&hs256_header).unwrap()); 90 let claims_b64 = URL_SAFE_NO_PAD.encode(serde_json::to_string(&claims).unwrap()); 91 use hmac::{Hmac, Mac}; 92 type HmacSha256 = Hmac<Sha256>; 93 let message = format!("{}.{}", header_b64, claims_b64); 94 let mut mac = HmacSha256::new_from_slice(&key_bytes).unwrap(); 95 mac.update(message.as_bytes()); 96 let hmac_sig = mac.finalize().into_bytes(); 97 let hs256_token = format!("{}.{}", message, URL_SAFE_NO_PAD.encode(&hmac_sig)); 98 assert!(verify_access_token(&hs256_token, &key_bytes).is_err(), "HS256 substitution must be rejected"); 99 100 for (alg, sig_len) in [("RS256", 256), ("ES256", 64)] { 101 let header = json!({ "alg": alg, "typ": TOKEN_TYPE_ACCESS }); 102 let header_b64 = URL_SAFE_NO_PAD.encode(serde_json::to_string(&header).unwrap()); 103 let fake_sig = URL_SAFE_NO_PAD.encode(&vec![1u8; sig_len]); 104 let token = format!("{}.{}.{}", header_b64, claims_b64, fake_sig); 105 assert!(verify_access_token(&token, &key_bytes).is_err(), "{} substitution must be rejected", alg); 106 } 107} 108 109#[test] 110fn test_token_type_confusion() { 111 let key_bytes = generate_user_key(); 112 let did = "did:plc:test"; 113 114 let refresh_token = create_refresh_token(did, &key_bytes).expect("create refresh token"); 115 let result = verify_access_token(&refresh_token, &key_bytes); 116 assert!(result.is_err(), "Refresh token as access must be rejected"); 117 assert!(result.err().unwrap().to_string().contains("Invalid token type")); 118 119 let access_token = create_access_token(did, &key_bytes).expect("create access token"); 120 let result = verify_refresh_token(&access_token, &key_bytes); 121 assert!(result.is_err(), "Access token as refresh must be rejected"); 122 assert!(result.err().unwrap().to_string().contains("Invalid token type")); 123 124 let service_token = create_service_token(did, "did:web:target", "com.example.method", &key_bytes).unwrap(); 125 assert!(verify_access_token(&service_token, &key_bytes).is_err(), "Service token as access must be rejected"); 126} 127 128#[test] 129fn test_scope_validation() { 130 let key_bytes = generate_user_key(); 131 let did = "did:plc:test"; 132 let header = json!({ "alg": "ES256K", "typ": TOKEN_TYPE_ACCESS }); 133 134 let invalid_scope = json!({ 135 "iss": did, "sub": did, "aud": "did:web:test.pds", 136 "iat": Utc::now().timestamp(), "exp": Utc::now().timestamp() + 3600, 137 "jti": "test", "scope": "admin.all" 138 }); 139 let result = verify_access_token(&create_custom_jwt(&header, &invalid_scope, &key_bytes), &key_bytes); 140 assert!(result.is_err() && result.err().unwrap().to_string().contains("Invalid token scope")); 141 142 let empty_scope = json!({ 143 "iss": did, "sub": did, "aud": "did:web:test.pds", 144 "iat": Utc::now().timestamp(), "exp": Utc::now().timestamp() + 3600, 145 "jti": "test", "scope": "" 146 }); 147 assert!(verify_access_token(&create_custom_jwt(&header, &empty_scope, &key_bytes), &key_bytes).is_err()); 148 149 let missing_scope = json!({ 150 "iss": did, "sub": did, "aud": "did:web:test.pds", 151 "iat": Utc::now().timestamp(), "exp": Utc::now().timestamp() + 3600, 152 "jti": "test" 153 }); 154 assert!(verify_access_token(&create_custom_jwt(&header, &missing_scope, &key_bytes), &key_bytes).is_err()); 155 156 for scope in [SCOPE_ACCESS, SCOPE_APP_PASS, SCOPE_APP_PASS_PRIVILEGED] { 157 let claims = json!({ 158 "iss": did, "sub": did, "aud": "did:web:test.pds", 159 "iat": Utc::now().timestamp(), "exp": Utc::now().timestamp() + 3600, 160 "jti": "test", "scope": scope 161 }); 162 assert!(verify_access_token(&create_custom_jwt(&header, &claims, &key_bytes), &key_bytes).is_ok()); 163 } 164 165 let refresh_scope = json!({ 166 "iss": did, "sub": did, "aud": "did:web:test.pds", 167 "iat": Utc::now().timestamp(), "exp": Utc::now().timestamp() + 3600, 168 "jti": "test", "scope": SCOPE_REFRESH 169 }); 170 assert!(verify_access_token(&create_custom_jwt(&header, &refresh_scope, &key_bytes), &key_bytes).is_err()); 171} 172 173#[test] 174fn test_expiration_and_timing() { 175 let key_bytes = generate_user_key(); 176 let did = "did:plc:test"; 177 let header = json!({ "alg": "ES256K", "typ": TOKEN_TYPE_ACCESS }); 178 let now = Utc::now().timestamp(); 179 180 let expired = json!({ 181 "iss": did, "sub": did, "aud": "did:web:test.pds", 182 "iat": now - 7200, "exp": now - 3600, "jti": "test", "scope": SCOPE_ACCESS 183 }); 184 let result = verify_access_token(&create_custom_jwt(&header, &expired, &key_bytes), &key_bytes); 185 assert!(result.is_err() && result.err().unwrap().to_string().contains("expired")); 186 187 let future_iat = json!({ 188 "iss": did, "sub": did, "aud": "did:web:test.pds", 189 "iat": now + 60, "exp": now + 7200, "jti": "test", "scope": SCOPE_ACCESS 190 }); 191 assert!(verify_access_token(&create_custom_jwt(&header, &future_iat, &key_bytes), &key_bytes).is_ok()); 192 193 let just_expired = json!({ 194 "iss": did, "sub": did, "aud": "did:web:test.pds", 195 "iat": now - 10, "exp": now - 1, "jti": "test", "scope": SCOPE_ACCESS 196 }); 197 assert!(verify_access_token(&create_custom_jwt(&header, &just_expired, &key_bytes), &key_bytes).is_err()); 198 199 let far_future = json!({ 200 "iss": did, "sub": did, "aud": "did:web:test.pds", 201 "iat": now, "exp": i64::MAX, "jti": "test", "scope": SCOPE_ACCESS 202 }); 203 let _ = verify_access_token(&create_custom_jwt(&header, &far_future, &key_bytes), &key_bytes); 204 205 let negative_iat = json!({ 206 "iss": did, "sub": did, "aud": "did:web:test.pds", 207 "iat": -1000000000i64, "exp": now + 3600, "jti": "test", "scope": SCOPE_ACCESS 208 }); 209 let _ = verify_access_token(&create_custom_jwt(&header, &negative_iat, &key_bytes), &key_bytes); 210} 211 212#[test] 213fn test_malformed_tokens() { 214 let key_bytes = generate_user_key(); 215 216 for token in ["", "not-a-token", "one.two", "one.two.three.four", "....", 217 "eyJhbGciOiJFUzI1NksifQ", "eyJhbGciOiJFUzI1NksifQ.", "eyJhbGciOiJFUzI1NksifQ..", 218 ".eyJzdWIiOiJ0ZXN0In0.", "!!invalid-base64!!.eyJzdWIiOiJ0ZXN0In0.sig"] { 219 assert!(verify_access_token(token, &key_bytes).is_err(), "Malformed token must be rejected"); 220 } 221 222 let invalid_header = URL_SAFE_NO_PAD.encode("{not valid json}"); 223 let claims_b64 = URL_SAFE_NO_PAD.encode(r#"{"sub":"test"}"#); 224 let fake_sig = URL_SAFE_NO_PAD.encode(&[1u8; 64]); 225 assert!(verify_access_token(&format!("{}.{}.{}", invalid_header, claims_b64, fake_sig), &key_bytes).is_err()); 226 227 let header_b64 = URL_SAFE_NO_PAD.encode(r#"{"alg":"ES256K","typ":"at+jwt"}"#); 228 let invalid_claims = URL_SAFE_NO_PAD.encode("{not valid json}"); 229 assert!(verify_access_token(&format!("{}.{}.{}", header_b64, invalid_claims, fake_sig), &key_bytes).is_err()); 230} 231 232#[test] 233fn test_claim_validation() { 234 let key_bytes = generate_user_key(); 235 let did = "did:plc:test"; 236 let header = json!({ "alg": "ES256K", "typ": TOKEN_TYPE_ACCESS }); 237 238 let missing_exp = json!({ 239 "iss": did, "sub": did, "aud": "did:web:test", 240 "iat": Utc::now().timestamp(), "scope": SCOPE_ACCESS 241 }); 242 assert!(verify_access_token(&create_custom_jwt(&header, &missing_exp, &key_bytes), &key_bytes).is_err()); 243 244 let missing_iat = json!({ 245 "iss": did, "sub": did, "aud": "did:web:test", 246 "exp": Utc::now().timestamp() + 3600, "scope": SCOPE_ACCESS 247 }); 248 assert!(verify_access_token(&create_custom_jwt(&header, &missing_iat, &key_bytes), &key_bytes).is_err()); 249 250 let missing_sub = json!({ 251 "iss": did, "aud": "did:web:test", 252 "iat": Utc::now().timestamp(), "exp": Utc::now().timestamp() + 3600, "scope": SCOPE_ACCESS 253 }); 254 assert!(verify_access_token(&create_custom_jwt(&header, &missing_sub, &key_bytes), &key_bytes).is_err()); 255 256 let wrong_types = json!({ 257 "iss": 12345, "sub": ["did:plc:test"], "aud": {"url": "did:web:test"}, 258 "iat": "not a number", "exp": "also not a number", "jti": null, "scope": SCOPE_ACCESS 259 }); 260 assert!(verify_access_token(&create_custom_jwt(&header, &wrong_types, &key_bytes), &key_bytes).is_err()); 261 262 let unicode_injection = json!({ 263 "iss": "did:plc:test\u{0000}attacker", "sub": "did:plc:test\u{202E}rekatta", 264 "aud": "did:web:test.pds", "iat": Utc::now().timestamp(), "exp": Utc::now().timestamp() + 3600, 265 "jti": "test", "scope": SCOPE_ACCESS 266 }); 267 if let Ok(data) = verify_access_token(&create_custom_jwt(&header, &unicode_injection, &key_bytes), &key_bytes) { 268 assert!(!data.claims.sub.contains('\0')); 269 } 270} 271 272#[test] 273fn test_did_and_jti_extraction() { 274 let key_bytes = generate_user_key(); 275 let did = "did:plc:legitimate"; 276 let token = create_access_token(did, &key_bytes).expect("create token"); 277 278 assert_eq!(get_did_from_token(&token).unwrap(), did); 279 assert!(get_did_from_token("invalid").is_err()); 280 assert!(get_did_from_token("a.b").is_err()); 281 assert!(get_did_from_token("").is_err()); 282 283 let jti = get_jti_from_token(&token).unwrap(); 284 assert!(!jti.is_empty()); 285 assert!(get_jti_from_token("invalid").is_err()); 286 287 let header_b64 = URL_SAFE_NO_PAD.encode(r#"{"alg":"ES256K"}"#); 288 let claims_b64 = URL_SAFE_NO_PAD.encode(r#"{"iss":"did:plc:iss","sub":"did:plc:sub"}"#); 289 let fake_sig = URL_SAFE_NO_PAD.encode(&[0u8; 64]); 290 let unverified = format!("{}.{}.{}", header_b64, claims_b64, fake_sig); 291 assert_eq!(get_did_from_token(&unverified).unwrap(), "did:plc:sub"); 292 293 let no_jti_claims = URL_SAFE_NO_PAD.encode(r#"{"iss":"did:plc:test"}"#); 294 assert!(get_jti_from_token(&format!("{}.{}.{}", header_b64, no_jti_claims, fake_sig)).is_err()); 295} 296 297#[test] 298fn test_header_injection_and_constant_time() { 299 let key_bytes = generate_user_key(); 300 let did = "did:plc:test"; 301 302 let header = json!({ 303 "alg": "ES256K", "typ": TOKEN_TYPE_ACCESS, 304 "kid": "../../../../../../etc/passwd", "jku": "https://attacker.com/keys" 305 }); 306 let claims = json!({ 307 "iss": did, "sub": did, "aud": "did:web:test.pds", 308 "iat": Utc::now().timestamp(), "exp": Utc::now().timestamp() + 3600, 309 "jti": "test", "scope": SCOPE_ACCESS 310 }); 311 assert!(verify_access_token(&create_custom_jwt(&header, &claims, &key_bytes), &key_bytes).is_ok()); 312 313 let valid_token = create_access_token(did, &key_bytes).expect("create token"); 314 let parts: Vec<&str> = valid_token.split('.').collect(); 315 let mut almost_valid = URL_SAFE_NO_PAD.decode(parts[2]).unwrap(); 316 almost_valid[0] ^= 1; 317 let almost_valid_token = format!("{}.{}.{}", parts[0], parts[1], URL_SAFE_NO_PAD.encode(&almost_valid)); 318 let completely_invalid_token = format!("{}.{}.{}", parts[0], parts[1], URL_SAFE_NO_PAD.encode(&[0xFFu8; 64])); 319 let _ = verify_access_token(&almost_valid_token, &key_bytes); 320 let _ = verify_access_token(&completely_invalid_token, &key_bytes); 321} 322 323#[tokio::test] 324async fn test_server_rejects_invalid_tokens() { 325 let url = base_url().await; 326 let http_client = client(); 327 328 let key_bytes = generate_user_key(); 329 let forged_token = create_access_token("did:plc:fake-user", &key_bytes).unwrap(); 330 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 331 .header("Authorization", format!("Bearer {}", forged_token)) 332 .send().await.unwrap(); 333 assert_eq!(res.status(), StatusCode::UNAUTHORIZED, "Forged token must be rejected"); 334 335 let (access_jwt, _did) = create_account_and_login(&http_client).await; 336 let parts: Vec<&str> = access_jwt.split('.').collect(); 337 let payload_bytes = URL_SAFE_NO_PAD.decode(parts[1]).unwrap(); 338 let mut payload: Value = serde_json::from_slice(&payload_bytes).unwrap(); 339 340 payload["exp"] = json!(Utc::now().timestamp() - 3600); 341 let expired_token = format!("{}.{}.{}", parts[0], URL_SAFE_NO_PAD.encode(serde_json::to_string(&payload).unwrap()), parts[2]); 342 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 343 .header("Authorization", format!("Bearer {}", expired_token)) 344 .send().await.unwrap(); 345 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 346 347 let mut tampered_payload: Value = serde_json::from_slice(&payload_bytes).unwrap(); 348 tampered_payload["sub"] = json!("did:plc:attacker"); 349 tampered_payload["iss"] = json!("did:plc:attacker"); 350 let tampered_token = format!("{}.{}.{}", parts[0], URL_SAFE_NO_PAD.encode(serde_json::to_string(&tampered_payload).unwrap()), parts[2]); 351 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 352 .header("Authorization", format!("Bearer {}", tampered_token)) 353 .send().await.unwrap(); 354 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 355} 356 357#[tokio::test] 358async fn test_authorization_header_formats() { 359 let url = base_url().await; 360 let http_client = client(); 361 let (access_jwt, _did) = create_account_and_login(&http_client).await; 362 363 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 364 .header("Authorization", format!("Bearer {}", access_jwt)) 365 .send().await.unwrap(); 366 assert_eq!(res.status(), StatusCode::OK); 367 368 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 369 .header("Authorization", format!("bearer {}", access_jwt)) 370 .send().await.unwrap(); 371 assert_eq!(res.status(), StatusCode::OK); 372 373 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 374 .header("Authorization", format!("Basic {}", access_jwt)) 375 .send().await.unwrap(); 376 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 377 378 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 379 .header("Authorization", &access_jwt) 380 .send().await.unwrap(); 381 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 382 383 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 384 .header("Authorization", "Bearer ") 385 .send().await.unwrap(); 386 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 387} 388 389#[tokio::test] 390async fn test_session_lifecycle_security() { 391 let url = base_url().await; 392 let http_client = client(); 393 let (access_jwt, _did) = create_account_and_login(&http_client).await; 394 395 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 396 .header("Authorization", format!("Bearer {}", access_jwt)) 397 .send().await.unwrap(); 398 assert_eq!(res.status(), StatusCode::OK); 399 400 let logout = http_client.post(format!("{}/xrpc/com.atproto.server.deleteSession", url)) 401 .header("Authorization", format!("Bearer {}", access_jwt)) 402 .send().await.unwrap(); 403 assert_eq!(logout.status(), StatusCode::OK); 404 405 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 406 .header("Authorization", format!("Bearer {}", access_jwt)) 407 .send().await.unwrap(); 408 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 409} 410 411#[tokio::test] 412async fn test_deactivated_account_rejected() { 413 let url = base_url().await; 414 let http_client = client(); 415 let (access_jwt, _did) = create_account_and_login(&http_client).await; 416 417 let deact = http_client.post(format!("{}/xrpc/com.atproto.server.deactivateAccount", url)) 418 .header("Authorization", format!("Bearer {}", access_jwt)) 419 .json(&json!({})) 420 .send().await.unwrap(); 421 assert_eq!(deact.status(), StatusCode::OK); 422 423 let res = http_client.get(format!("{}/xrpc/com.atproto.server.getSession", url)) 424 .header("Authorization", format!("Bearer {}", access_jwt)) 425 .send().await.unwrap(); 426 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 427 let body: Value = res.json().await.unwrap(); 428 assert_eq!(body["error"], "AccountDeactivated"); 429} 430 431#[tokio::test] 432async fn test_refresh_token_replay_protection() { 433 let url = base_url().await; 434 let http_client = client(); 435 let ts = Utc::now().timestamp_millis(); 436 let handle = format!("rt-replay-jwt-{}", ts); 437 let email = format!("rt-replay-jwt-{}@example.com", ts); 438 439 let create_res = http_client.post(format!("{}/xrpc/com.atproto.server.createAccount", url)) 440 .json(&json!({ "handle": handle, "email": email, "password": "test-password-123" })) 441 .send().await.unwrap(); 442 assert_eq!(create_res.status(), StatusCode::OK); 443 let account: Value = create_res.json().await.unwrap(); 444 let did = account["did"].as_str().unwrap(); 445 446 let pool = sqlx::postgres::PgPoolOptions::new() 447 .max_connections(2) 448 .connect(&get_db_connection_string().await) 449 .await.unwrap(); 450 let code: String = sqlx::query_scalar!( 451 "SELECT code FROM channel_verifications WHERE user_id = (SELECT id FROM users WHERE did = $1) AND channel = 'email'", 452 did 453 ).fetch_one(&pool).await.unwrap(); 454 455 let confirm = http_client.post(format!("{}/xrpc/com.atproto.server.confirmSignup", url)) 456 .json(&json!({ "did": did, "verificationCode": code })) 457 .send().await.unwrap(); 458 assert_eq!(confirm.status(), StatusCode::OK); 459 let confirmed: Value = confirm.json().await.unwrap(); 460 let refresh_jwt = confirmed["refreshJwt"].as_str().unwrap().to_string(); 461 462 let first = http_client.post(format!("{}/xrpc/com.atproto.server.refreshSession", url)) 463 .header("Authorization", format!("Bearer {}", refresh_jwt)) 464 .send().await.unwrap(); 465 assert_eq!(first.status(), StatusCode::OK); 466 467 let replay = http_client.post(format!("{}/xrpc/com.atproto.server.refreshSession", url)) 468 .header("Authorization", format!("Bearer {}", refresh_jwt)) 469 .send().await.unwrap(); 470 assert_eq!(replay.status(), StatusCode::UNAUTHORIZED); 471}