headless_lms_server/domain/oauth/
dpop.rs

1use actix_web::HttpRequest;
2use async_trait::async_trait;
3use dpop_verifier::{
4    DpopError, DpopVerifier,
5    actix_helpers::{dpop_header_str, expected_htu_from_actix},
6    replay::{ReplayContext, ReplayStore},
7};
8use headless_lms_models::library::oauth::Digest as TokenDigest;
9use headless_lms_models::oauth_dpop_proofs::OAuthDpopProof;
10use secrecy::{ExposeSecret, SecretBox};
11use sqlx::PgConnection;
12
13pub struct SqlxReplayStore<'c> {
14    pub conn: &'c mut PgConnection,
15}
16
17#[async_trait]
18impl<'c> ReplayStore for SqlxReplayStore<'c> {
19    async fn insert_once(
20        &mut self,
21        jti_hash: [u8; 32],
22        ctx: ReplayContext<'_>,
23    ) -> Result<bool, DpopError> {
24        let digest = TokenDigest::from(jti_hash);
25        let first_time = OAuthDpopProof::insert_once(
26            self.conn,
27            digest,
28            ctx.client_id,
29            ctx.jkt,       // jkt: Option<&str>
30            ctx.htm,       // htm: Option<&str>
31            ctx.htu,       // htu: Option<&str>
32            Some(ctx.iat), // iat: Option<i64>
33        )
34        .await
35        .map_err(|e| DpopError::Store(e.into()))?;
36
37        Ok(first_time)
38    }
39}
40
41pub async fn verify_dpop_from_actix(
42    conn: &mut PgConnection,
43    req: &HttpRequest,
44    method: &str, // "POST", "GET", ...
45    dpop_nonce_key: &SecretBox<String>,
46    access_token: Option<&str>, // Some(at) at resource endpoints; None at /token
47) -> Result<String, DpopError> {
48    let hdr = dpop_header_str(req)?;
49
50    let htu = expected_htu_from_actix(req, true);
51
52    let mut store = SqlxReplayStore { conn };
53    let verifier = DpopVerifier::new()
54        .with_max_age_seconds(300)
55        .with_future_skew_seconds(5)
56        .with_nonce_mode(dpop_verifier::NonceMode::Hmac(
57            dpop_verifier::HmacConfig::new(
58                dpop_nonce_key.expose_secret().as_bytes(),
59                300,
60                true,
61                true,
62                true,
63            ),
64        ));
65    let verified = verifier
66        .verify(&mut store, hdr, &htu, method, access_token)
67        .await?;
68
69    Ok(verified.jkt)
70}