siopv2 0.1.0

Rust implementation for the OpenID Connect Self-Issued OpenID Provider v2 standard
Documentation
use super::id_token::{RFC7519Claims, SubJwk};
use crate::{builder_fn, IdToken, StandardClaimsValues};

#[derive(Default)]
pub struct IdTokenBuilder {
    rfc7519_claims: RFC7519Claims,
    standard_claims: StandardClaimsValues,
    auth_time: Option<i64>,
    nonce: Option<String>,
    acr: Option<String>,
    amr: Option<Vec<String>>,
    azp: Option<String>,
    sub_jwk: Option<SubJwk>,
    other: Option<serde_json::Map<String, serde_json::Value>>,
}

impl IdTokenBuilder {
    pub fn new() -> Self {
        IdTokenBuilder::default()
    }

    pub fn build(self) -> anyhow::Result<IdToken> {
        anyhow::ensure!(self.rfc7519_claims.iss.is_some(), "iss claim is required");
        anyhow::ensure!(self.rfc7519_claims.sub.is_some(), "sub claim is required");
        anyhow::ensure!(
            self.rfc7519_claims.sub.as_ref().filter(|s| s.len() <= 255).is_some(),
            "sub claim MUST NOT exceed 255 ASCII characters in length"
        );
        anyhow::ensure!(self.rfc7519_claims.aud.is_some(), "aud claim is required");
        anyhow::ensure!(self.rfc7519_claims.exp.is_some(), "exp claim is required");
        anyhow::ensure!(self.rfc7519_claims.iat.is_some(), "iat claim is required");
        anyhow::ensure!(
            self.rfc7519_claims.iss == self.rfc7519_claims.sub,
            "iss and sub must be equal"
        );

        Ok(IdToken {
            rfc7519_claims: self.rfc7519_claims,
            standard_claims: self.standard_claims,
            auth_time: self.auth_time,
            nonce: self.nonce,
            acr: self.acr,
            amr: self.amr,
            azp: self.azp,
            sub_jwk: self.sub_jwk,
            other: self.other,
        })
    }

    pub fn claims(mut self, claims: StandardClaimsValues) -> Self {
        self.standard_claims = claims;
        self
    }

    builder_fn!(rfc7519_claims, iss, String);
    builder_fn!(rfc7519_claims, sub, String);
    builder_fn!(rfc7519_claims, aud, String);
    builder_fn!(rfc7519_claims, exp, i64);
    builder_fn!(rfc7519_claims, nbf, i64);
    builder_fn!(rfc7519_claims, iat, i64);
    builder_fn!(rfc7519_claims, jti, String);
    builder_fn!(auth_time, i64);
    builder_fn!(nonce, String);
    builder_fn!(acr, String);
    builder_fn!(amr, Vec<String>);
    builder_fn!(azp, String);
    builder_fn!(sub_jwk, SubJwk);
    builder_fn!(other, serde_json::Map<String, serde_json::Value>);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_valid_build() {
        assert!(IdTokenBuilder::new()
            .iss("NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs")
            .sub("NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs")
            .aud("https://client.example.org/cb")
            .exp(1311281970)
            .iat(1311280970)
            .build()
            .is_ok());
    }

    #[test]
    fn test_invalid_build() {
        assert!(IdTokenBuilder::new().build().is_err());

        assert!(IdTokenBuilder::new()
            .iss("iss")
            .build()
            .unwrap_err()
            .to_string()
            .contains("sub claim is required"));

        assert!(IdTokenBuilder::new()
            .iss("iss")
            .sub("x".repeat(256))
            .build()
            .unwrap_err()
            .to_string()
            .contains("sub claim MUST NOT exceed 255 ASCII characters in length"));

        assert!(IdTokenBuilder::new()
            .iss("iss")
            .sub("sub")
            .aud("aud")
            .exp(0)
            .iat(0)
            .build()
            .unwrap_err()
            .to_string()
            .contains("iss and sub must be equal"));
    }
}