+26
-265
src/oauth.rs
+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