use std::collections::HashMap;
use chrono::{DateTime, Utc};
use securitydept_oidc_client::UserInfoExchangeResult;
use securitydept_utils::principal::AuthenticatedPrincipal;
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;
use url::form_urlencoded;
use super::{
metadata_redemption::{MetadataRedemptionId, PendingAuthStateMetadataRedemptionPayload},
refresh_material::SealedRefreshMaterial,
};
use crate::models::{
AuthStateMetadataDelta, AuthStateMetadataSnapshot, AuthTokenDelta, AuthTokenSnapshot,
CurrentAuthStateMetadataSnapshotPartial,
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct BackendOidcModeAuthorizeQuery {
#[serde(default)]
pub post_auth_redirect_uri: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BackendOidcModeRefreshPayload {
#[serde(rename = "refresh_token")]
pub refresh_material: SealedRefreshMaterial,
#[serde(
rename = "post_auth_redirect_uri",
default,
skip_serializing_if = "Option::is_none"
)]
pub post_auth_redirect_uri: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id_token: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub current_metadata_snapshot: Option<CurrentAuthStateMetadataSnapshotPartial>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder)]
pub struct BackendOidcModeCallbackReturns {
#[builder(setter(into))]
pub access_token: String,
#[builder(default, setter(into))]
pub id_token: String,
#[serde(rename = "refresh_token", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(strip_option))]
pub refresh_material: Option<SealedRefreshMaterial>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default, setter(strip_option))]
pub access_token_expires_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default, setter(strip_option))]
pub metadata_redemption_id: Option<MetadataRedemptionId>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default, setter(strip_option))]
pub metadata: Option<AuthStateMetadataSnapshot>,
}
impl BackendOidcModeCallbackReturns {
pub fn from_snapshot(
snapshot: &AuthTokenSnapshot,
metadata_redemption_id: Option<MetadataRedemptionId>,
) -> Self {
Self {
access_token: snapshot.access_token.clone(),
id_token: snapshot.id_token.clone(),
refresh_material: snapshot.refresh_material.clone(),
access_token_expires_at: snapshot.access_token_expires_at,
metadata_redemption_id,
metadata: None,
}
}
pub fn from_snapshot_with_inline_metadata(
snapshot: &AuthTokenSnapshot,
metadata: AuthStateMetadataSnapshot,
) -> Self {
Self {
access_token: snapshot.access_token.clone(),
id_token: snapshot.id_token.clone(),
refresh_material: snapshot.refresh_material.clone(),
access_token_expires_at: snapshot.access_token_expires_at,
metadata_redemption_id: None,
metadata: Some(metadata),
}
}
pub fn to_fragment_query_string(&self) -> String {
let mut s = form_urlencoded::Serializer::new(String::new());
s.append_pair("access_token", &self.access_token);
s.append_pair("id_token", &self.id_token);
if let Some(ref rm) = self.refresh_material {
s.append_pair("refresh_token", rm.expose());
}
if let Some(ref expires) = self.access_token_expires_at {
s.append_pair("expires_at", &expires.to_rfc3339());
}
if let Some(ref mrid) = self.metadata_redemption_id {
s.append_pair("metadata_redemption_id", mrid.expose());
}
s.finish()
}
pub fn to_response_body(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or(serde_json::Value::Null)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TypedBuilder)]
pub struct BackendOidcModeRefreshReturns {
#[builder(setter(into))]
pub access_token: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default, setter(strip_option, into))]
pub id_token: Option<String>,
#[serde(rename = "refresh_token", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(strip_option))]
pub refresh_material: Option<SealedRefreshMaterial>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default, setter(strip_option))]
pub access_token_expires_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default, setter(strip_option))]
pub metadata_redemption_id: Option<MetadataRedemptionId>,
#[serde(skip_serializing_if = "Option::is_none")]
#[builder(default, setter(strip_option))]
pub metadata: Option<AuthStateMetadataDelta>,
}
impl BackendOidcModeRefreshReturns {
pub fn from_delta(
delta: &AuthTokenDelta,
metadata_redemption_id: Option<MetadataRedemptionId>,
) -> Self {
Self {
access_token: delta.access_token.clone(),
id_token: delta.id_token.clone(),
refresh_material: delta.refresh_material.clone(),
access_token_expires_at: delta.access_token_expires_at,
metadata_redemption_id,
metadata: None,
}
}
pub fn from_delta_with_inline_metadata(
delta: &AuthTokenDelta,
metadata: AuthStateMetadataDelta,
) -> Self {
Self {
access_token: delta.access_token.clone(),
id_token: delta.id_token.clone(),
refresh_material: delta.refresh_material.clone(),
access_token_expires_at: delta.access_token_expires_at,
metadata_redemption_id: None,
metadata: Some(metadata),
}
}
pub fn to_fragment_query_string(&self) -> String {
let mut s = form_urlencoded::Serializer::new(String::new());
s.append_pair("access_token", &self.access_token);
if let Some(ref id_token) = self.id_token {
s.append_pair("id_token", id_token);
}
if let Some(ref rm) = self.refresh_material {
s.append_pair("refresh_token", rm.expose());
}
if let Some(ref expires) = self.access_token_expires_at {
s.append_pair("expires_at", &expires.to_rfc3339());
}
if let Some(ref mrid) = self.metadata_redemption_id {
s.append_pair("metadata_redemption_id", mrid.expose());
}
s.finish()
}
pub fn to_response_body(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or(serde_json::Value::Null)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BackendOidcModeMetadataRedemptionRequest {
pub metadata_redemption_id: MetadataRedemptionId,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BackendOidcModeMetadataRedemptionResponse {
pub metadata: PendingAuthStateMetadataRedemptionPayload,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BackendOidcModeUserInfoRequest {
pub id_token: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BackendOidcModeUserInfoResponse {
pub subject: String,
pub display_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub picture: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub issuer: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub claims: Option<HashMap<String, serde_json::Value>>,
}
impl From<AuthenticatedPrincipal> for BackendOidcModeUserInfoResponse {
fn from(principal: AuthenticatedPrincipal) -> Self {
Self {
subject: principal.subject,
display_name: principal.display_name,
picture: principal.picture,
issuer: principal.issuer,
claims: (!principal.claims.is_empty()).then_some(principal.claims),
}
}
}
impl From<UserInfoExchangeResult> for BackendOidcModeUserInfoResponse {
fn from(result: UserInfoExchangeResult) -> Self {
Self {
subject: result.subject,
display_name: result.display_name,
picture: result.picture,
issuer: result.issuer,
claims: result.claims,
}
}
}