Alternative ATProto PDS implementation

prototype oauth

Changed files
+26 -265
src
+26 -265
src/oauth.rs
··· 359 359 .context("failed to compute expiration time")? 360 360 .timestamp(); 361 361 362 - // _ = sqlx::query!( 363 - // r#" 364 - // INSERT INTO oauth_par_requests ( 365 - // request_uri, client_id, response_type, code_challenge, code_challenge_method, 366 - // state, login_hint, scope, redirect_uri, response_mode, display, 367 - // created_at, expires_at 368 - // ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 369 - // "#, 370 - // request_uri, 371 - // client_id, 372 - // response_type, 373 - // code_challenge, 374 - // code_challenge_method, 375 - // state, 376 - // login_hint, 377 - // scope, 378 - // redirect_uri, 379 - // response_mode, 380 - // display, 381 - // created_at, 382 - // expires_at 383 - // ) 384 - // .execute(&db) 385 - // .await 386 - // .context("failed to store PAR request")?; 387 362 use crate::schema::pds::oauth_par_requests::dsl as ParRequestSchema; 388 363 let client_id = client_id.to_owned(); 389 364 let request_uri_cloned = request_uri.to_owned(); ··· 449 424 let timestamp = chrono::Utc::now().timestamp(); 450 425 451 426 // Retrieve the PAR request from the database 452 - // let par_request = sqlx::query!( 453 - // r#" 454 - // SELECT * FROM oauth_par_requests 455 - // WHERE request_uri = ? AND client_id = ? AND expires_at > ? 456 - // "#, 457 - // request_uri, 458 - // client_id, 459 - // timestamp 460 - // ) 461 - // .fetch_optional(&db) 462 - // .await 463 - // .context("failed to query PAR request")? 464 - // .context("PAR request not found or expired")?; 465 427 use crate::schema::pds::oauth_par_requests::dsl as ParRequestSchema; 466 428 467 429 let request_uri_clone = request_uri.to_owned(); ··· 575 537 let timestamp = chrono::Utc::now().timestamp(); 576 538 577 539 // Retrieve the PAR request 578 - // let par_request = sqlx::query!( 579 - // r#" 580 - // SELECT * FROM oauth_par_requests 581 - // WHERE request_uri = ? AND client_id = ? AND expires_at > ? 582 - // "#, 583 - // request_uri, 584 - // client_id, 585 - // timestamp 586 - // ) 587 - // .fetch_optional(&db) 588 - // .await 589 - // .context("failed to query PAR request")? 590 - // .context("PAR request not found or expired")?; 591 540 use crate::schema::pds::oauth_par_requests::dsl as ParRequestSchema; 592 - // diesel::table! { 593 - // pds.oauth_par_requests (request_uri) { 594 - // request_uri -> Varchar, 595 - // client_id -> Varchar, 596 - // response_type -> Varchar, 597 - // code_challenge -> Varchar, 598 - // code_challenge_method -> Varchar, 599 - // state -> Nullable<Varchar>, 600 - // login_hint -> Nullable<Varchar>, 601 - // scope -> Nullable<Varchar>, 602 - // redirect_uri -> Nullable<Varchar>, 603 - // response_mode -> Nullable<Varchar>, 604 - // display -> Nullable<Varchar>, 605 - // created_at -> Int8, 606 - // expires_at -> Int8, 607 - // } 608 - // } 609 541 #[derive(Queryable, Selectable)] 610 542 #[diesel(table_name = crate::schema::pds::oauth_par_requests)] 611 543 #[diesel(check_for_backend(sqlite::Sqlite))] ··· 720 652 .context("failed to compute expiration time")? 721 653 .timestamp(); 722 654 723 - // _ = sqlx::query!( 724 - // r#" 725 - // INSERT INTO oauth_authorization_codes ( 726 - // code, client_id, subject, code_challenge, code_challenge_method, 727 - // redirect_uri, scope, created_at, expires_at, used 728 - // ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 729 - // "#, 730 - // code, 731 - // client_id, 732 - // account.did, 733 - // par_request.code_challenge, 734 - // par_request.code_challenge_method, 735 - // redirect_uri, 736 - // par_request.scope, 737 - // created_at, 738 - // expires_at, 739 - // false 740 - // ) 741 - // .execute(&db) 742 - // .await 743 - // .context("failed to store authorization code")?; 744 655 use crate::schema::pds::oauth_authorization_codes::dsl as AuthCodeSchema; 745 656 let code_cloned = code.to_owned(); 746 657 let client_id = client_id.to_owned(); ··· 971 882 } 972 883 973 884 // 11. Check for replay attacks via JTI tracking 974 - // let jti_used = 975 - // sqlx::query_scalar!(r#"SELECT COUNT(*) FROM oauth_used_jtis WHERE jti = ?"#, jti) 976 - // .fetch_one(db) 977 - // .await 978 - // .context("failed to check JTI")?; 979 885 use crate::schema::pds::oauth_used_jtis::dsl as JtiSchema; 980 886 let jti_clone = jti.to_owned(); 981 887 let jti_used = db ··· 1002 908 } 1003 909 1004 910 // 12. Store the JTI to prevent replay attacks 1005 - // _ = sqlx::query!( 1006 - // r#" 1007 - // INSERT INTO oauth_used_jtis (jti, issuer, created_at, expires_at) 1008 - // VALUES (?, ?, ?, ?) 1009 - // "#, 1010 - // jti, 1011 - // thumbprint, // Use thumbprint as issuer identifier 1012 - // now, 1013 - // exp 1014 - // ) 1015 - // .execute(db) 1016 - // .await 1017 - // .context("failed to store JTI")?; 1018 911 let jti_cloned = jti.to_owned(); 1019 912 let issuer = thumbprint.to_owned(); 1020 913 let created_at = now; ··· 1039 932 1040 933 // 13. Cleanup expired JTIs periodically (1% chance on each request) 1041 934 if thread_rng().gen_range(0_i32..100_i32) == 0_i32 { 1042 - // _ = sqlx::query!(r#"DELETE FROM oauth_used_jtis WHERE expires_at < ?"#, now) 1043 - // .execute(db) 1044 - // .await 1045 - // .context("failed to clean up expired JTIs")?; 1046 935 let now_clone = now.to_owned(); 1047 936 _ = db 1048 937 .get() ··· 1124 1013 == "private_key_jwt"; 1125 1014 1126 1015 // Verify DPoP proof 1127 - let dpop_thumbprint = verify_dpop_proof( 1016 + let dpop_thumbprint_res = verify_dpop_proof( 1128 1017 dpop_token, 1129 1018 "POST", 1130 1019 &format!("https://{}/oauth/token", config.host_name), ··· 1170 1059 // } 1171 1060 } else { 1172 1061 // Rule 2: For public clients, check if this DPoP key has been used before 1173 - // let is_key_reused = sqlx::query_scalar!( 1174 - // r#"SELECT COUNT(*) FROM oauth_refresh_tokens WHERE dpop_thumbprint = ? AND client_id = ?"#, 1175 - // dpop_thumbprint, 1176 - // client_id 1177 - // ) 1178 - // .fetch_one(&db) 1179 - // .await 1180 - // .context("failed to check key usage history")? > 0; 1181 1062 use crate::schema::pds::oauth_refresh_tokens::dsl as RefreshTokenSchema; 1063 + let dpop_thumbprint_clone = dpop_thumbprint_res.to_owned(); 1064 + let client_id_clone = client_id.to_owned(); 1182 1065 let is_key_reused = db 1183 1066 .get() 1184 1067 .await 1185 1068 .expect("Failed to get database connection") 1186 1069 .interact(move |conn| { 1187 1070 RefreshTokenSchema::oauth_refresh_tokens 1188 - .filter(RefreshTokenSchema::dpop_thumbprint.eq(dpop_thumbprint)) 1189 - .filter(RefreshTokenSchema::client_id.eq(client_id)) 1071 + .filter(RefreshTokenSchema::dpop_thumbprint.eq(dpop_thumbprint_clone)) 1072 + .filter(RefreshTokenSchema::client_id.eq(client_id_clone)) 1190 1073 .count() 1191 1074 .get_result::<i64>(conn) 1192 1075 .optional() ··· 1219 1102 let timestamp = chrono::Utc::now().timestamp(); 1220 1103 1221 1104 // Retrieve and validate the authorization code 1222 - // let auth_code = sqlx::query!( 1223 - // r#" 1224 - // SELECT * FROM oauth_authorization_codes 1225 - // WHERE code = ? AND client_id = ? AND redirect_uri = ? AND expires_at > ? AND used = FALSE 1226 - // "#, 1227 - // code, 1228 - // client_id, 1229 - // redirect_uri, 1230 - // timestamp 1231 - // ) 1232 - // .fetch_optional(&db) 1233 - // .await 1234 - // .context("failed to query authorization code")? 1235 - // .context("authorization code not found, expired, or already used")?; 1236 1105 use crate::schema::pds::oauth_authorization_codes::dsl as AuthCodeSchema; 1237 - // diesel::table! { 1238 - // pds.oauth_authorization_codes (code) { 1239 - // code -> Varchar, 1240 - // client_id -> Varchar, 1241 - // subject -> Varchar, 1242 - // code_challenge -> Varchar, 1243 - // code_challenge_method -> Varchar, 1244 - // redirect_uri -> Varchar, 1245 - // scope -> Nullable<Varchar>, 1246 - // created_at -> Int8, 1247 - // expires_at -> Int8, 1248 - // used -> Bool, 1249 - // } 1250 - // } 1251 - #[derive(Queryable, Selectable)] 1106 + #[derive(Queryable, Selectable, Serialize)] 1252 1107 #[diesel(table_name = crate::schema::pds::oauth_authorization_codes)] 1253 1108 #[diesel(check_for_backend(sqlite::Sqlite))] 1254 1109 struct AuthCode { ··· 1263 1118 expires_at: i64, 1264 1119 used: bool, 1265 1120 } 1121 + let code_clone = code.to_owned(); 1122 + let client_id_clone = client_id.to_owned(); 1123 + let redirect_uri_clone = redirect_uri.to_owned(); 1266 1124 let auth_code = db 1267 1125 .get() 1268 1126 .await 1269 1127 .expect("Failed to get database connection") 1270 1128 .interact(move |conn| { 1271 1129 AuthCodeSchema::oauth_authorization_codes 1272 - .filter(AuthCodeSchema::code.eq(code)) 1273 - .filter(AuthCodeSchema::client_id.eq(client_id)) 1274 - .filter(AuthCodeSchema::redirect_uri.eq(redirect_uri)) 1130 + .filter(AuthCodeSchema::code.eq(code_clone)) 1131 + .filter(AuthCodeSchema::client_id.eq(client_id_clone)) 1132 + .filter(AuthCodeSchema::redirect_uri.eq(redirect_uri_clone)) 1275 1133 .filter(AuthCodeSchema::expires_at.gt(timestamp)) 1276 1134 .filter(AuthCodeSchema::used.eq(false)) 1277 1135 .first::<AuthCode>(conn) ··· 1290 1148 )?; 1291 1149 1292 1150 // Mark the code as used 1293 - // _ = sqlx::query!( 1294 - // r#"UPDATE oauth_authorization_codes SET used = TRUE WHERE code = ?"#, 1295 - // code 1296 - // ) 1297 - // .execute(&db) 1298 - // .await 1299 - // .context("failed to mark code as used")?; 1300 1151 let code_cloned = code.to_owned(); 1301 1152 _ = db 1302 1153 .get() ··· 1334 1185 "exp": access_token_expires_at, 1335 1186 "iat": now, 1336 1187 "cnf": { 1337 - "jkt": dpop_thumbprint // Rule 1: Bind to DPoP key 1188 + "jkt": dpop_thumbprint_res // Rule 1: Bind to DPoP key 1338 1189 }, 1339 1190 "scope": auth_code.scope 1340 1191 }); ··· 1350 1201 "exp": refresh_token_expires_at, 1351 1202 "iat": now, 1352 1203 "cnf": { 1353 - "jkt": dpop_thumbprint // Rule 1: Bind to DPoP key 1204 + "jkt": dpop_thumbprint_res // Rule 1: Bind to DPoP key 1354 1205 }, 1355 1206 "scope": auth_code.scope 1356 1207 }); ··· 1359 1210 .context("failed to sign refresh token")?; 1360 1211 1361 1212 // Store the refresh token with DPoP binding 1362 - // _ = sqlx::query!( 1363 - // r#" 1364 - // INSERT INTO oauth_refresh_tokens ( 1365 - // token, client_id, subject, dpop_thumbprint, scope, created_at, expires_at, revoked 1366 - // ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) 1367 - // "#, 1368 - // refresh_token, 1369 - // client_id, 1370 - // auth_code.subject, 1371 - // dpop_thumbprint, 1372 - // auth_code.scope, 1373 - // now, 1374 - // refresh_token_expires_at, 1375 - // false 1376 - // ) 1377 - // .execute(&db) 1378 - // .await 1379 - // .context("failed to store refresh token")?; 1380 1213 use crate::schema::pds::oauth_refresh_tokens::dsl as RefreshTokenSchema; 1381 1214 let refresh_token_cloned = refresh_token.to_owned(); 1382 1215 let client_id_cloned = client_id.to_owned(); 1383 1216 let subject = auth_code.subject.to_owned(); 1384 - let dpop_thumbprint_cloned = dpop_thumbprint.to_owned(); 1217 + let dpop_thumbprint_cloned = dpop_thumbprint_res.to_owned(); 1385 1218 let scope = auth_code.scope.to_owned(); 1386 1219 let created_at = now; 1387 1220 let expires_at = refresh_token_expires_at; ··· 1427 1260 1428 1261 // Rules 7 & 8: Verify refresh token and DPoP consistency 1429 1262 // Retrieve the refresh token 1430 - // let token_data = sqlx::query!( 1431 - // r#" 1432 - // SELECT * FROM oauth_refresh_tokens 1433 - // WHERE token = ? AND client_id = ? AND expires_at > ? AND revoked = FALSE AND dpop_thumbprint = ? 1434 - // "#, 1435 - // refresh_token, 1436 - // client_id, 1437 - // timestamp, 1438 - // dpop_thumbprint // Rule 8: Must use same DPoP key 1439 - // ) 1440 - // .fetch_optional(&db) 1441 - // .await 1442 - // .context("failed to query refresh token")? 1443 - // .context("refresh token not found, expired, revoked, or invalid for this DPoP key")?; 1444 1263 use crate::schema::pds::oauth_refresh_tokens::dsl as RefreshTokenSchema; 1445 - // diesel::table! { 1446 - // pds.oauth_refresh_tokens (token) { 1447 - // token -> Varchar, 1448 - // client_id -> Varchar, 1449 - // subject -> Varchar, 1450 - // dpop_thumbprint -> Varchar, 1451 - // scope -> Nullable<Varchar>, 1452 - // created_at -> Int8, 1453 - // expires_at -> Int8, 1454 - // revoked -> Bool, 1455 - // } 1456 - // } 1457 - #[derive(Queryable, Selectable)] 1264 + #[derive(Queryable, Selectable, Serialize)] 1458 1265 #[diesel(table_name = crate::schema::pds::oauth_refresh_tokens)] 1459 1266 #[diesel(check_for_backend(sqlite::Sqlite))] 1460 1267 struct TokenData { ··· 1467 1274 expires_at: i64, 1468 1275 revoked: bool, 1469 1276 } 1277 + let dpop_thumbprint_clone = dpop_thumbprint_res.to_owned(); 1278 + let refresh_token_clone = refresh_token.to_owned(); 1279 + let client_id_clone = client_id.to_owned(); 1470 1280 let token_data = db 1471 1281 .get() 1472 1282 .await 1473 1283 .expect("Failed to get database connection") 1474 1284 .interact(move |conn| { 1475 1285 RefreshTokenSchema::oauth_refresh_tokens 1476 - .filter(RefreshTokenSchema::token.eq(refresh_token)) 1477 - .filter(RefreshTokenSchema::client_id.eq(client_id)) 1286 + .filter(RefreshTokenSchema::token.eq(refresh_token_clone)) 1287 + .filter(RefreshTokenSchema::client_id.eq(client_id_clone)) 1478 1288 .filter(RefreshTokenSchema::expires_at.gt(timestamp)) 1479 1289 .filter(RefreshTokenSchema::revoked.eq(false)) 1480 - .filter(RefreshTokenSchema::dpop_thumbprint.eq(dpop_thumbprint)) 1290 + .filter(RefreshTokenSchema::dpop_thumbprint.eq(dpop_thumbprint_clone)) 1481 1291 .first::<TokenData>(conn) 1482 1292 .optional() 1483 1293 }) ··· 1491 1301 let client_still_advertises_key = true; // Implement actual check against client jwks 1492 1302 if !client_still_advertises_key { 1493 1303 // Revoke all tokens bound to this key 1494 - // _ = sqlx::query!( 1495 - // r#"UPDATE oauth_refresh_tokens SET revoked = TRUE 1496 - // WHERE client_id = ? AND dpop_thumbprint = ?"#, 1497 - // client_id, 1498 - // dpop_thumbprint 1499 - // ) 1500 - // .execute(&db) 1501 - // .await 1502 - // .context("failed to revoke tokens")?; 1503 1304 let client_id_cloned = client_id.to_owned(); 1504 - let dpop_thumbprint_cloned = dpop_thumbprint.to_owned(); 1305 + let dpop_thumbprint_cloned = dpop_thumbprint_res.to_owned(); 1505 1306 _ = db 1506 1307 .get() 1507 1308 .await ··· 1527 1328 } 1528 1329 1529 1330 // Rotate the refresh token 1530 - // _ = sqlx::query!( 1531 - // r#"UPDATE oauth_refresh_tokens SET revoked = TRUE WHERE token = ?"#, 1532 - // refresh_token 1533 - // ) 1534 - // .execute(&db) 1535 - // .await 1536 - // .context("failed to revoke old refresh token")?; 1537 1331 let refresh_token_cloned = refresh_token.to_owned(); 1538 1332 _ = db 1539 1333 .get() ··· 1566 1360 "exp": access_token_expires_at, 1567 1361 "iat": now, 1568 1362 "cnf": { 1569 - "jkt": dpop_thumbprint 1363 + "jkt": dpop_thumbprint_res 1570 1364 }, 1571 1365 "scope": token_data.scope 1572 1366 }); ··· 1582 1376 "exp": refresh_token_expires_at, 1583 1377 "iat": now, 1584 1378 "cnf": { 1585 - "jkt": dpop_thumbprint 1379 + "jkt": dpop_thumbprint_res 1586 1380 }, 1587 1381 "scope": token_data.scope 1588 1382 }); ··· 1591 1385 .context("failed to sign refresh token")?; 1592 1386 1593 1387 // Store the new refresh token 1594 - // _ = sqlx::query!( 1595 - // r#" 1596 - // INSERT INTO oauth_refresh_tokens ( 1597 - // token, client_id, subject, dpop_thumbprint, scope, created_at, expires_at, revoked 1598 - // ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) 1599 - // "#, 1600 - // new_refresh_token, 1601 - // client_id, 1602 - // token_data.subject, 1603 - // dpop_thumbprint, 1604 - // token_data.scope, 1605 - // now, 1606 - // refresh_token_expires_at, 1607 - // false 1608 - // ) 1609 - // .execute(&db) 1610 - // .await 1611 - // .context("failed to store refresh token")?; 1612 1388 let new_refresh_token_cloned = new_refresh_token.to_owned(); 1613 1389 let client_id_cloned = client_id.to_owned(); 1614 1390 let subject = token_data.subject.to_owned(); 1615 - let dpop_thumbprint_cloned = dpop_thumbprint.to_owned(); 1391 + let dpop_thumbprint_cloned = dpop_thumbprint_res.to_owned(); 1616 1392 let scope = token_data.scope.to_owned(); 1617 1393 let created_at = now; 1618 1394 let expires_at = refresh_token_expires_at; ··· 1732 1508 } 1733 1509 1734 1510 // Revoke the token 1735 - // _ = sqlx::query!( 1736 - // r#"UPDATE oauth_refresh_tokens SET revoked = TRUE WHERE token = ?"#, 1737 - // token 1738 - // ) 1739 - // .execute(&db) 1740 - // .await 1741 - // .context("failed to revoke token")?; 1742 1511 use crate::schema::pds::oauth_refresh_tokens::dsl as RefreshTokenSchema; 1743 1512 let token_cloned = token.to_owned(); 1744 1513 _ = db ··· 1807 1576 1808 1577 // For refresh tokens, check if it's been revoked 1809 1578 if is_refresh_token { 1810 - // let is_revoked = sqlx::query_scalar!( 1811 - // r#"SELECT revoked FROM oauth_refresh_tokens WHERE token = ?"#, 1812 - // token 1813 - // ) 1814 - // .fetch_optional(&db) 1815 - // .await 1816 - // .context("failed to query token")? 1817 - // .unwrap_or(true); 1818 1579 use crate::schema::pds::oauth_refresh_tokens::dsl as RefreshTokenSchema; 1819 1580 let token_cloned = token.to_owned(); 1820 1581 let is_revoked = db