An easy-to-host PDS on the ATProtocol, MacOS. Grandma-approved.

fix: address code review feedback - remove AC references and add JWT assertions

- Issue 1: Remove all AC reference suffixes from assertion message strings in:
- crates/relay/src/routes/oauth_token.rs (11 instances)
- crates/relay/src/db/oauth.rs (5 instances)

- Issue 2: Add iss and aud JWT claim assertions in happy-path tests:
- authorization_code_happy_path_returns_200_with_tokens: assert iss and aud match public_url
- refresh_token_happy_path_returns_200_with_new_tokens: decode access token and assert iss and aud

+26 -16
+5 -5
crates/relay/src/db/oauth.rs
··· 630 630 .unwrap(); 631 631 assert!( 632 632 second.is_none(), 633 - "consumed code must not be found again (AC1.6)" 633 + "consumed code must not be found again" 634 634 ); 635 635 } 636 636 ··· 676 676 .unwrap(); 677 677 assert!( 678 678 result.is_none(), 679 - "expired auth code must return None (AC1.5)" 679 + "expired auth code must return None" 680 680 ); 681 681 } 682 682 ··· 713 713 assert_eq!(id, "refresh-token-hash-01"); 714 714 assert_eq!( 715 715 scope, "com.atproto.refresh", 716 - "scope must be com.atproto.refresh (AC1.3)" 716 + "scope must be com.atproto.refresh" 717 717 ); 718 718 assert_eq!(jkt.as_deref(), Some("jkt-thumbprint")); 719 719 } ··· 759 759 .unwrap(); 760 760 assert!( 761 761 second.is_none(), 762 - "consumed token must not be found again (AC4.2)" 762 + "consumed token must not be found again" 763 763 ); 764 764 } 765 765 ··· 793 793 .unwrap(); 794 794 assert!( 795 795 result.is_none(), 796 - "expired refresh token must return None (AC4.3)" 796 + "expired refresh token must return None" 797 797 ); 798 798 } 799 799
+21 -11
crates/relay/src/routes/oauth_token.rs
··· 995 995 let header: serde_json::Value = serde_json::from_str(&header_json).unwrap(); 996 996 assert_eq!( 997 997 header["typ"], "at+jwt", 998 - "access token typ must be at+jwt (AC1.2)" 998 + "access token typ must be at+jwt" 999 999 ); 1000 1000 assert_eq!( 1001 1001 header["alg"], "ES256", 1002 - "access token alg must be ES256 (AC6.3)" 1002 + "access token alg must be ES256" 1003 1003 ); 1004 1004 1005 1005 let payload_b64 = at.split('.').nth(1).unwrap(); ··· 1009 1009 let expected_jkt = dpop_thumbprint(&key); 1010 1010 assert_eq!( 1011 1011 cnf_jkt, expected_jkt, 1012 - "cnf.jkt must match DPoP key thumbprint (AC2.2)" 1012 + "cnf.jkt must match DPoP key thumbprint" 1013 1013 ); 1014 + assert_eq!(payload["iss"], "https://test.example.com", "iss must be public_url"); 1015 + assert_eq!(payload["aud"], "https://test.example.com", "aud must be public_url"); 1014 1016 } 1015 1017 1016 1018 #[tokio::test] ··· 1049 1051 let json = json_body(resp).await; 1050 1052 assert_eq!( 1051 1053 json["error"], "invalid_grant", 1052 - "wrong code_verifier must return invalid_grant (AC1.4)" 1054 + "wrong code_verifier must return invalid_grant" 1053 1055 ); 1054 1056 } 1055 1057 ··· 1106 1108 let json2 = json_body(resp2).await; 1107 1109 assert_eq!( 1108 1110 json2["error"], "invalid_grant", 1109 - "second use must return invalid_grant (AC1.6)" 1111 + "second use must return invalid_grant" 1110 1112 ); 1111 1113 } 1112 1114 ··· 1145 1147 let json = json_body(resp).await; 1146 1148 assert_eq!( 1147 1149 json["error"], "invalid_grant", 1148 - "client_id mismatch must return invalid_grant (AC1.7)" 1150 + "client_id mismatch must return invalid_grant" 1149 1151 ); 1150 1152 } 1151 1153 ··· 1184 1186 let json = json_body(resp).await; 1185 1187 assert_eq!( 1186 1188 json["error"], "invalid_grant", 1187 - "redirect_uri mismatch must return invalid_grant (AC1.8)" 1189 + "redirect_uri mismatch must return invalid_grant" 1188 1190 ); 1189 1191 } 1190 1192 ··· 1319 1321 plaintext.as_str(), 1320 1322 "rotated refresh token must differ from original" 1321 1323 ); 1324 + 1325 + // Verify access token has correct iss and aud. 1326 + let at = json["access_token"].as_str().unwrap(); 1327 + let payload_b64 = at.split('.').nth(1).unwrap(); 1328 + let payload_json = String::from_utf8(URL_SAFE_NO_PAD.decode(payload_b64).unwrap()).unwrap(); 1329 + let payload: serde_json::Value = serde_json::from_str(&payload_json).unwrap(); 1330 + assert_eq!(payload["iss"], "https://test.example.com", "iss must be public_url"); 1331 + assert_eq!(payload["aud"], "https://test.example.com", "aud must be public_url"); 1322 1332 } 1323 1333 1324 1334 #[tokio::test] ··· 1375 1385 let json = json_body(resp2).await; 1376 1386 assert_eq!( 1377 1387 json["error"], "invalid_grant", 1378 - "second use of consumed token must return invalid_grant (AC4.2)" 1388 + "second use of consumed token must return invalid_grant" 1379 1389 ); 1380 1390 } 1381 1391 ··· 1410 1420 let json = json_body(resp).await; 1411 1421 assert_eq!( 1412 1422 json["error"], "invalid_grant", 1413 - "expired refresh token must return invalid_grant (AC4.3)" 1423 + "expired refresh token must return invalid_grant" 1414 1424 ); 1415 1425 } 1416 1426 ··· 1449 1459 let json = json_body(resp).await; 1450 1460 assert_eq!( 1451 1461 json["error"], "invalid_grant", 1452 - "DPoP key mismatch must return invalid_grant (AC4.4)" 1462 + "DPoP key mismatch must return invalid_grant" 1453 1463 ); 1454 1464 } 1455 1465 ··· 1485 1495 let json = json_body(resp).await; 1486 1496 assert_eq!( 1487 1497 json["error"], "invalid_grant", 1488 - "client_id mismatch must return invalid_grant (AC4.5)" 1498 + "client_id mismatch must return invalid_grant" 1489 1499 ); 1490 1500 } 1491 1501 }