use std::fmt;
use std::time::SystemTime;
use serde::{Deserialize, Serialize};
use crate::client::ClientId;
use crate::scope::ScopeSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TokenType {
#[serde(rename = "Bearer")]
Bearer,
#[cfg(feature = "dpop")]
#[serde(rename = "DPoP")]
Dpop,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TokenResponse {
pub access_token: String,
pub token_type: TokenType,
pub expires_in: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<String>,
#[cfg(feature = "rar")]
#[serde(skip_serializing_if = "crate::rar::AuthorizationDetails::is_empty")]
pub authorization_details: crate::rar::AuthorizationDetails,
}
impl fmt::Debug for TokenResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn redact_opt<T>(value: &Option<T>) -> Option<&'static str> {
value.as_ref().map(|_| "[redacted]")
}
f.debug_struct("TokenResponse")
.field("access_token", &"[redacted]")
.field("token_type", &self.token_type)
.field("expires_in", &self.expires_in)
.field("refresh_token", &redact_opt(&self.refresh_token))
.field("scope", &self.scope)
.finish()
}
}
#[cfg(any(feature = "dpop", feature = "mtls"))]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Confirmation {
#[cfg(feature = "dpop")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub jkt: Option<String>,
#[cfg(feature = "mtls")]
#[serde(rename = "x5t#S256", default, skip_serializing_if = "Option::is_none")]
pub x5t_s256: Option<crate::mtls::CertificateThumbprint>,
}
#[cfg(any(feature = "dpop", feature = "mtls"))]
impl Confirmation {
#[cfg(feature = "dpop")]
pub fn jkt(jkt: impl Into<String>) -> Self {
Confirmation {
jkt: Some(jkt.into()),
#[cfg(feature = "mtls")]
x5t_s256: None,
}
}
pub fn is_empty(&self) -> bool {
#[cfg(feature = "dpop")]
if self.jkt.is_some() {
return false;
}
#[cfg(feature = "mtls")]
if self.x5t_s256.is_some() {
return false;
}
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TokenTypeHint {
AccessToken,
RefreshToken,
}
impl std::str::FromStr for TokenTypeHint {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"access_token" => Ok(TokenTypeHint::AccessToken),
"refresh_token" => Ok(TokenTypeHint::RefreshToken),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IntrospectionResponse {
pub active: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sub: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_type: Option<TokenType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exp: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iat: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub iss: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub aud: Option<Vec<String>>,
#[cfg(feature = "consent")]
#[serde(skip_serializing_if = "Option::is_none")]
pub auth_time: Option<u64>,
#[cfg(feature = "consent")]
#[serde(skip_serializing_if = "Option::is_none")]
pub acr: Option<String>,
#[cfg(feature = "rar")]
#[serde(
default,
skip_serializing_if = "crate::rar::AuthorizationDetails::is_empty"
)]
pub authorization_details: crate::rar::AuthorizationDetails,
#[cfg(any(feature = "dpop", feature = "mtls"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub cnf: Option<Confirmation>,
}
impl IntrospectionResponse {
pub fn inactive() -> Self {
IntrospectionResponse {
active: false,
scope: None,
client_id: None,
sub: None,
token_type: None,
exp: None,
iat: None,
iss: None,
aud: None,
#[cfg(feature = "consent")]
auth_time: None,
#[cfg(feature = "consent")]
acr: None,
#[cfg(feature = "rar")]
authorization_details: crate::rar::AuthorizationDetails::none(),
#[cfg(any(feature = "dpop", feature = "mtls"))]
cnf: None,
}
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssuedToken {
pub access_token: String,
pub client_id: ClientId,
pub subject: Option<String>,
pub scope: ScopeSet,
pub resource: Vec<String>,
#[cfg(feature = "rar")]
pub authorization_details: crate::rar::AuthorizationDetails,
pub issued_at: SystemTime,
pub expires_at: SystemTime,
#[cfg(feature = "dpop")]
pub jkt: Option<Box<str>>,
#[cfg(feature = "mtls")]
pub x5t_s256: Option<Box<crate::mtls::CertificateThumbprint>>,
pub family_id: Option<String>,
#[cfg(feature = "consent")]
pub authentication: Option<Box<crate::consent::Authentication>>,
}
impl fmt::Debug for IssuedToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut out = f.debug_struct("IssuedToken");
out.field("access_token", &"[redacted]")
.field("client_id", &self.client_id)
.field("subject", &self.subject)
.field("scope", &self.scope)
.field("resource", &self.resource);
#[cfg(feature = "rar")]
out.field("authorization_details", &self.authorization_details);
out.field("issued_at", &self.issued_at)
.field("expires_at", &self.expires_at)
.field("family_id", &self.family_id)
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RefreshTokenState {
Active,
Spent,
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RefreshTokenRecord {
pub refresh_token: String,
pub client_id: ClientId,
pub subject: Option<String>,
pub scope: ScopeSet,
pub resource: Vec<String>,
#[cfg(feature = "rar")]
pub authorization_details: crate::rar::AuthorizationDetails,
pub expires_at: Option<SystemTime>,
#[cfg(feature = "dpop")]
pub jkt: Option<Box<str>>,
#[cfg(feature = "mtls")]
pub x5t_s256: Option<Box<crate::mtls::CertificateThumbprint>>,
pub family_id: String,
pub state: RefreshTokenState,
#[cfg(feature = "consent")]
pub authentication: Option<Box<crate::consent::Authentication>>,
}
impl fmt::Debug for RefreshTokenRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut out = f.debug_struct("RefreshTokenRecord");
out.field("refresh_token", &"[redacted]")
.field("client_id", &self.client_id)
.field("subject", &self.subject)
.field("scope", &self.scope)
.field("resource", &self.resource);
#[cfg(feature = "rar")]
out.field("authorization_details", &self.authorization_details);
out.field("expires_at", &self.expires_at)
.field("family_id", &self.family_id)
.field("state", &self.state)
.finish()
}
}
#[cfg(test)]
#[path = "tests/token.rs"]
mod tests;