···127 .await
128 .is_err()
129 {
130- tracing::warn!("Failed to set delegated DID on authorization request");
000000131 }
132133 let grant = match state
···127 .await
128 .is_err()
129 {
130+ return Json(DelegationAuthResponse {
131+ success: false,
132+ needs_totp: None,
133+ redirect_uri: None,
134+ error: Some("Failed to update authorization request".to_string()),
135+ })
136+ .into_response();
137 }
138139 let grant = match state
+138
crates/tranquil-pds/tests/oauth_security.rs
···1250 "Error should be InsufficientScope"
1251 );
1252}
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
···1250 "Error should be InsufficientScope"
1251 );
1252}
1253+1254+#[tokio::test]
1255+async fn test_delegation_oauth_token_sub_is_delegated_account() {
1256+ let url = base_url().await;
1257+ let http_client = client();
1258+ let suffix = &uuid::Uuid::new_v4().simple().to_string()[..8];
1259+1260+ let (controller_jwt, controller_did) = create_account_and_login(&http_client).await;
1261+1262+ let delegated_handle = format!("dlgsub{}", suffix);
1263+ let delegated_res = http_client
1264+ .post(format!("{}/xrpc/_delegation.createDelegatedAccount", url))
1265+ .bearer_auth(&controller_jwt)
1266+ .json(&json!({
1267+ "handle": delegated_handle,
1268+ "controllerScopes": "atproto"
1269+ }))
1270+ .send()
1271+ .await
1272+ .unwrap();
1273+ assert_eq!(
1274+ delegated_res.status(),
1275+ StatusCode::OK,
1276+ "Should create delegated account"
1277+ );
1278+ let delegated_account: Value = delegated_res.json().await.unwrap();
1279+ let delegated_did = delegated_account["did"].as_str().unwrap();
1280+1281+ assert_ne!(
1282+ delegated_did, controller_did,
1283+ "Delegated DID should be different from controller DID"
1284+ );
1285+1286+ let redirect_uri = "https://example.com/deleg-sub-callback";
1287+ let mock_client = setup_mock_client_metadata(redirect_uri).await;
1288+ let client_id = mock_client.uri();
1289+ let (code_verifier, code_challenge) = generate_pkce();
1290+1291+ let par_body: Value = http_client
1292+ .post(format!("{}/oauth/par", url))
1293+ .form(&[
1294+ ("response_type", "code"),
1295+ ("client_id", &client_id),
1296+ ("redirect_uri", redirect_uri),
1297+ ("code_challenge", &code_challenge),
1298+ ("code_challenge_method", "S256"),
1299+ ("scope", "atproto"),
1300+ ("login_hint", delegated_did),
1301+ ])
1302+ .send()
1303+ .await
1304+ .unwrap()
1305+ .json()
1306+ .await
1307+ .unwrap();
1308+ let request_uri = par_body["request_uri"].as_str().unwrap();
1309+1310+ let auth_res = http_client
1311+ .post(format!("{}/oauth/delegation/auth", url))
1312+ .header("Content-Type", "application/json")
1313+ .json(&json!({
1314+ "request_uri": request_uri,
1315+ "delegated_did": delegated_did,
1316+ "controller_did": controller_did,
1317+ "password": "Testpass123!",
1318+ "remember_device": false
1319+ }))
1320+ .send()
1321+ .await
1322+ .unwrap();
1323+ assert_eq!(
1324+ auth_res.status(),
1325+ StatusCode::OK,
1326+ "Delegation auth should succeed"
1327+ );
1328+ let auth_body: Value = auth_res.json().await.unwrap();
1329+ assert!(
1330+ auth_body["success"].as_bool().unwrap_or(false),
1331+ "Delegation auth should report success: {:?}",
1332+ auth_body
1333+ );
1334+1335+ let consent_res = http_client
1336+ .post(format!("{}/oauth/authorize/consent", url))
1337+ .header("Content-Type", "application/json")
1338+ .json(&json!({
1339+ "request_uri": request_uri,
1340+ "approved_scopes": ["atproto"],
1341+ "remember": false
1342+ }))
1343+ .send()
1344+ .await
1345+ .unwrap();
1346+ assert_eq!(
1347+ consent_res.status(),
1348+ StatusCode::OK,
1349+ "Consent should succeed"
1350+ );
1351+ let consent_body: Value = consent_res.json().await.unwrap();
1352+ let redirect_location = consent_body["redirect_uri"]
1353+ .as_str()
1354+ .expect("Expected redirect_uri");
1355+1356+ let code = redirect_location
1357+ .split("code=")
1358+ .nth(1)
1359+ .unwrap()
1360+ .split('&')
1361+ .next()
1362+ .unwrap();
1363+1364+ let token_res = http_client
1365+ .post(format!("{}/oauth/token", url))
1366+ .form(&[
1367+ ("grant_type", "authorization_code"),
1368+ ("code", code),
1369+ ("redirect_uri", redirect_uri),
1370+ ("code_verifier", &code_verifier),
1371+ ("client_id", &client_id),
1372+ ])
1373+ .send()
1374+ .await
1375+ .unwrap();
1376+ assert_eq!(token_res.status(), StatusCode::OK, "Token exchange should succeed");
1377+ let tokens: Value = token_res.json().await.unwrap();
1378+1379+ let sub = tokens["sub"].as_str().expect("Token response should have sub claim");
1380+1381+ assert_eq!(
1382+ sub, delegated_did,
1383+ "Token sub claim should be the DELEGATED account's DID, not the controller's. Got {} but expected {}",
1384+ sub, delegated_did
1385+ );
1386+ assert_ne!(
1387+ sub, controller_did,
1388+ "Token sub claim should NOT be the controller's DID"
1389+ );
1390+}