use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::enums::OAuthScope;
use crate::errors::error_code;
use crate::ids::{AgentId, OAuthClientId};
use crate::secrets::Secret;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum OAuthErrorCode {
InvalidRequest,
InvalidClient,
InvalidGrant,
UnsupportedGrantType,
ServerError,
InvalidClientMetadata,
InvalidRedirectUri,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct OAuthError {
pub error: OAuthErrorCode,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error_description: Option<String>,
}
impl OAuthError {
pub fn new(error: OAuthErrorCode, description: impl Into<String>) -> Self {
Self {
error,
error_description: Some(description.into()),
}
}
pub fn code(error: OAuthErrorCode) -> Self {
Self {
error,
error_description: None,
}
}
}
error_code!(
Bearer = "Bearer"
);
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct TokenResponse {
#[serde(serialize_with = "expose", deserialize_with = "secret")]
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
pub access_token: Secret,
pub token_type: Bearer,
pub expires_in: u64,
#[serde(serialize_with = "expose", deserialize_with = "secret")]
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
pub refresh_token: Secret,
#[serde(serialize_with = "join_scopes", deserialize_with = "split_scopes")]
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
pub scope: Vec<OAuthScope>,
}
fn expose<S: Serializer>(secret: &Secret, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(secret.expose())
}
fn secret<'de, D: Deserializer<'de>>(d: D) -> Result<Secret, D::Error> {
String::deserialize(d).map(Secret::new)
}
fn join_scopes<S: Serializer>(
scopes: &[OAuthScope],
s: S,
) -> Result<S::Ok, S::Error> {
let words: Vec<String> = scopes.iter().map(ToString::to_string).collect();
s.serialize_str(&words.join(" "))
}
fn split_scopes<'de, D: Deserializer<'de>>(
d: D,
) -> Result<Vec<OAuthScope>, D::Error> {
String::deserialize(d)?
.split_whitespace()
.map(|w| w.parse().map_err(serde::de::Error::custom))
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct OAuthServerMetadata {
pub issuer: String,
pub authorization_endpoint: String,
pub token_endpoint: String,
pub registration_endpoint: String,
pub response_types_supported: Vec<String>,
pub grant_types_supported: Vec<String>,
pub code_challenge_methods_supported: Vec<String>,
pub token_endpoint_auth_methods_supported: Vec<String>,
pub scopes_supported: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ClientRegistrationRequest {
pub redirect_uris: Vec<String>,
#[serde(default)]
pub client_name: Option<String>,
#[serde(default = "default_auth_method")]
pub token_endpoint_auth_method: String,
}
fn default_auth_method() -> String {
"none".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ClientRegistrationResponse {
pub client_id: OAuthClientId,
pub client_id_issued_at: i64,
pub redirect_uris: Vec<String>,
pub client_name: String,
pub token_endpoint_auth_method: String,
pub grant_types: Vec<String>,
pub response_types: Vec<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
serialize_with = "expose_opt",
deserialize_with = "secret_opt"
)]
#[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
pub client_secret: Option<Secret>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub client_secret_expires_at: Option<i64>,
}
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct TokenRequest {
pub grant_type: String,
#[serde(default, deserialize_with = "secret_opt")]
#[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
pub code: Option<Secret>,
#[serde(default)]
pub redirect_uri: Option<String>,
#[serde(default, deserialize_with = "secret_opt")]
#[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
pub client_secret: Option<Secret>,
#[serde(default, deserialize_with = "secret_opt")]
#[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
pub code_verifier: Option<Secret>,
#[serde(default, deserialize_with = "secret_opt")]
#[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
pub refresh_token: Option<Secret>,
#[serde(default)]
pub client_id: Option<OAuthClientId>,
}
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ApproveRequest {
pub client_id: OAuthClientId,
pub redirect_uri: String,
pub operator_email: String,
#[serde(deserialize_with = "secret")]
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
pub operator_password: Secret,
pub agent_id: AgentId,
pub scope: Option<String>,
pub state: Option<String>,
pub code_challenge: String,
pub code_challenge_method: String,
}
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct RevokeTokenRequest {
pub operator_email: String,
#[serde(deserialize_with = "secret")]
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
pub operator_password: Secret,
#[serde(deserialize_with = "secret")]
#[cfg_attr(feature = "schemars", schemars(with = "String"))]
pub token: Secret,
}
fn expose_opt<S: Serializer>(
secret: &Option<Secret>,
s: S,
) -> Result<S::Ok, S::Error> {
match secret {
Some(secret) => s.serialize_some(secret.expose()),
None => s.serialize_none(),
}
}
fn secret_opt<'de, D: Deserializer<'de>>(
d: D,
) -> Result<Option<Secret>, D::Error> {
Ok(Option::<String>::deserialize(d)?.map(Secret::new))
}
#[cfg(test)]
mod tests;