#![warn(missing_docs)]
use chrono::serde::ts_seconds_option;
use chrono::{DateTime, Utc};
use std::borrow::Cow;
use std::error::Error;
use std::fmt::Error as FormatterError;
use std::fmt::{Debug, Display, Formatter};
use std::future::Future;
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::Duration;
use http::header::{HeaderMap, HeaderValue, ACCEPT, AUTHORIZATION, CONTENT_TYPE};
use http::status::StatusCode;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use url::{form_urlencoded, Url};
pub mod basic;
#[cfg(all(feature = "curl", not(target_arch = "wasm32")))]
pub mod curl;
#[cfg(all(feature = "curl", target_arch = "wasm32"))]
compile_error!("wasm32 is not supported with the `curl` feature. Use the `reqwest` backend or a custom backend for wasm32 support");
pub mod devicecode;
use devicecode::{
DeviceAccessTokenPollResult, DeviceAuthorizationResponse, DeviceCodeErrorResponse,
DeviceCodeErrorResponseType, ExtraDeviceAuthorizationFields,
};
pub mod revocation;
pub mod helpers;
#[cfg(feature = "reqwest")]
pub mod reqwest;
#[cfg(test)]
mod tests;
mod types;
#[cfg(feature = "ureq")]
pub mod ureq;
pub use http;
pub use url;
pub use types::{
AccessToken, AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken,
DeviceAuthorizationUrl, DeviceCode, EndUserVerificationUrl, IntrospectionUrl,
PkceCodeChallenge, PkceCodeChallengeMethod, PkceCodeVerifier, RedirectUrl, RefreshToken,
ResourceOwnerPassword, ResourceOwnerUsername, ResponseType, RevocationUrl, Scope, TokenUrl,
UserCode,
};
pub use revocation::{RevocableToken, RevocationErrorResponseType, StandardRevocableToken};
const CONTENT_TYPE_JSON: &str = "application/json";
const CONTENT_TYPE_FORMENCODED: &str = "application/x-www-form-urlencoded";
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum ConfigurationError {
#[error("No {0} endpoint URL specified")]
MissingUrl(&'static str),
#[error("Scheme for {0} endpoint URL must be HTTPS")]
InsecureUrl(&'static str),
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum AuthType {
RequestBody,
BasicAuth,
}
#[derive(Clone, Debug)]
pub struct Client<TE, TR, TT, TIR, RT, TRE>
where
TE: ErrorResponse,
TR: TokenResponse<TT>,
TT: TokenType,
TIR: TokenIntrospectionResponse<TT>,
RT: RevocableToken,
TRE: ErrorResponse,
{
client_id: ClientId,
client_secret: Option<ClientSecret>,
auth_url: AuthUrl,
auth_type: AuthType,
token_url: Option<TokenUrl>,
redirect_url: Option<RedirectUrl>,
introspection_url: Option<IntrospectionUrl>,
revocation_url: Option<RevocationUrl>,
device_authorization_url: Option<DeviceAuthorizationUrl>,
phantom: PhantomData<(TE, TR, TT, TIR, RT, TRE)>,
}
impl<TE, TR, TT, TIR, RT, TRE> Client<TE, TR, TT, TIR, RT, TRE>
where
TE: ErrorResponse + 'static,
TR: TokenResponse<TT>,
TT: TokenType,
TIR: TokenIntrospectionResponse<TT>,
RT: RevocableToken,
TRE: ErrorResponse + 'static,
{
pub fn new(
client_id: ClientId,
client_secret: Option<ClientSecret>,
auth_url: AuthUrl,
token_url: Option<TokenUrl>,
) -> Self {
Client {
client_id,
client_secret,
auth_url,
auth_type: AuthType::BasicAuth,
token_url,
redirect_url: None,
introspection_url: None,
revocation_url: None,
device_authorization_url: None,
phantom: PhantomData,
}
}
pub fn set_auth_type(mut self, auth_type: AuthType) -> Self {
self.auth_type = auth_type;
self
}
pub fn set_redirect_uri(mut self, redirect_url: RedirectUrl) -> Self {
self.redirect_url = Some(redirect_url);
self
}
pub fn set_introspection_uri(mut self, introspection_url: IntrospectionUrl) -> Self {
self.introspection_url = Some(introspection_url);
self
}
pub fn set_revocation_uri(mut self, revocation_url: RevocationUrl) -> Self {
self.revocation_url = Some(revocation_url);
self
}
pub fn set_device_authorization_url(
mut self,
device_authorization_url: DeviceAuthorizationUrl,
) -> Self {
self.device_authorization_url = Some(device_authorization_url);
self
}
pub fn authorize_url<S>(&self, state_fn: S) -> AuthorizationRequest
where
S: FnOnce() -> CsrfToken,
{
AuthorizationRequest {
auth_url: &self.auth_url,
client_id: &self.client_id,
extra_params: Vec::new(),
pkce_challenge: None,
redirect_url: self.redirect_url.as_ref().map(Cow::Borrowed),
response_type: "code".into(),
scopes: Vec::new(),
state: state_fn(),
}
}
pub fn exchange_code(&self, code: AuthorizationCode) -> CodeTokenRequest<TE, TR, TT> {
CodeTokenRequest {
auth_type: &self.auth_type,
client_id: &self.client_id,
client_secret: self.client_secret.as_ref(),
code,
extra_params: Vec::new(),
pkce_verifier: None,
token_url: self.token_url.as_ref(),
redirect_url: self.redirect_url.as_ref().map(Cow::Borrowed),
_phantom: PhantomData,
}
}
pub fn exchange_password<'a, 'b>(
&'a self,
username: &'b ResourceOwnerUsername,
password: &'b ResourceOwnerPassword,
) -> PasswordTokenRequest<'b, TE, TR, TT>
where
'a: 'b,
{
PasswordTokenRequest::<'b> {
auth_type: &self.auth_type,
client_id: &self.client_id,
client_secret: self.client_secret.as_ref(),
username,
password,
extra_params: Vec::new(),
scopes: Vec::new(),
token_url: self.token_url.as_ref(),
_phantom: PhantomData,
}
}
pub fn exchange_client_credentials(&self) -> ClientCredentialsTokenRequest<TE, TR, TT> {
ClientCredentialsTokenRequest {
auth_type: &self.auth_type,
client_id: &self.client_id,
client_secret: self.client_secret.as_ref(),
extra_params: Vec::new(),
scopes: Vec::new(),
token_url: self.token_url.as_ref(),
_phantom: PhantomData,
}
}
pub fn exchange_refresh_token<'a, 'b>(
&'a self,
refresh_token: &'b RefreshToken,
) -> RefreshTokenRequest<'b, TE, TR, TT>
where
'a: 'b,
{
RefreshTokenRequest {
auth_type: &self.auth_type,
client_id: &self.client_id,
client_secret: self.client_secret.as_ref(),
extra_params: Vec::new(),
refresh_token,
scopes: Vec::new(),
token_url: self.token_url.as_ref(),
_phantom: PhantomData,
}
}
pub fn exchange_device_code(
&self,
) -> Result<DeviceAuthorizationRequest<TE>, ConfigurationError> {
Ok(DeviceAuthorizationRequest {
auth_type: &self.auth_type,
client_id: &self.client_id,
client_secret: self.client_secret.as_ref(),
extra_params: Vec::new(),
scopes: Vec::new(),
device_authorization_url: self
.device_authorization_url
.as_ref()
.ok_or(ConfigurationError::MissingUrl("device authorization_url"))?,
_phantom: PhantomData,
})
}
pub fn exchange_device_access_token<'a, 'b, 'c, EF>(
&'a self,
auth_response: &'b DeviceAuthorizationResponse<EF>,
) -> DeviceAccessTokenRequest<'b, 'c, TR, TT, EF>
where
'a: 'b,
EF: ExtraDeviceAuthorizationFields,
{
DeviceAccessTokenRequest {
auth_type: &self.auth_type,
client_id: &self.client_id,
client_secret: self.client_secret.as_ref(),
extra_params: Vec::new(),
token_url: self.token_url.as_ref(),
dev_auth_resp: auth_response,
time_fn: Arc::new(Utc::now),
_phantom: PhantomData,
}
}
pub fn introspect<'a>(
&'a self,
token: &'a AccessToken,
) -> Result<IntrospectionRequest<'a, TE, TIR, TT>, ConfigurationError> {
Ok(IntrospectionRequest {
auth_type: &self.auth_type,
client_id: &self.client_id,
client_secret: self.client_secret.as_ref(),
extra_params: Vec::new(),
introspection_url: self
.introspection_url
.as_ref()
.ok_or(ConfigurationError::MissingUrl("introspection"))?,
token,
token_type_hint: None,
_phantom: PhantomData,
})
}
pub fn revoke_token(
&self,
token: RT,
) -> Result<RevocationRequest<RT, TRE>, ConfigurationError> {
let revocation_url = match self.revocation_url.as_ref() {
Some(url) if url.url().scheme() == "https" => Ok(url),
Some(_) => Err(ConfigurationError::InsecureUrl("revocation")),
None => Err(ConfigurationError::MissingUrl("revocation")),
}?;
Ok(RevocationRequest {
auth_type: &self.auth_type,
client_id: &self.client_id,
client_secret: self.client_secret.as_ref(),
extra_params: Vec::new(),
revocation_url,
token,
_phantom: PhantomData,
})
}
pub fn client_id(&self) -> &ClientId {
&self.client_id
}
pub fn auth_url(&self) -> &AuthUrl {
&self.auth_url
}
pub fn auth_type(&self) -> &AuthType {
&self.auth_type
}
pub fn token_url(&self) -> Option<&TokenUrl> {
self.token_url.as_ref()
}
pub fn redirect_url(&self) -> Option<&RedirectUrl> {
self.redirect_url.as_ref()
}
pub fn introspection_url(&self) -> Option<&IntrospectionUrl> {
self.introspection_url.as_ref()
}
pub fn revocation_url(&self) -> Option<&RevocationUrl> {
self.revocation_url.as_ref()
}
pub fn device_authorization_url(&self) -> Option<&DeviceAuthorizationUrl> {
self.device_authorization_url.as_ref()
}
}
#[derive(Debug)]
pub struct AuthorizationRequest<'a> {
auth_url: &'a AuthUrl,
client_id: &'a ClientId,
extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
pkce_challenge: Option<PkceCodeChallenge>,
redirect_url: Option<Cow<'a, RedirectUrl>>,
response_type: Cow<'a, str>,
scopes: Vec<Cow<'a, Scope>>,
state: CsrfToken,
}
impl<'a> AuthorizationRequest<'a> {
pub fn add_scope(mut self, scope: Scope) -> Self {
self.scopes.push(Cow::Owned(scope));
self
}
pub fn add_scopes<I>(mut self, scopes: I) -> Self
where
I: IntoIterator<Item = Scope>,
{
self.scopes.extend(scopes.into_iter().map(Cow::Owned));
self
}
pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.extra_params.push((name.into(), value.into()));
self
}
pub fn use_implicit_flow(mut self) -> Self {
self.response_type = "token".into();
self
}
pub fn set_response_type(mut self, response_type: &ResponseType) -> Self {
self.response_type = (&**response_type).to_owned().into();
self
}
pub fn set_pkce_challenge(mut self, pkce_code_challenge: PkceCodeChallenge) -> Self {
self.pkce_challenge = Some(pkce_code_challenge);
self
}
pub fn set_redirect_uri(mut self, redirect_url: Cow<'a, RedirectUrl>) -> Self {
self.redirect_url = Some(redirect_url);
self
}
pub fn url(self) -> (Url, CsrfToken) {
let scopes = self
.scopes
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(" ");
let url = {
let mut pairs: Vec<(&str, &str)> = vec![
("response_type", self.response_type.as_ref()),
("client_id", &self.client_id),
("state", self.state.secret()),
];
if let Some(ref pkce_challenge) = self.pkce_challenge {
pairs.push(("code_challenge", &pkce_challenge.as_str()));
pairs.push(("code_challenge_method", &pkce_challenge.method().as_str()));
}
if let Some(ref redirect_url) = self.redirect_url {
pairs.push(("redirect_uri", redirect_url.as_str()));
}
if !scopes.is_empty() {
pairs.push(("scope", &scopes));
}
let mut url: Url = self.auth_url.url().to_owned();
url.query_pairs_mut()
.extend_pairs(pairs.iter().map(|&(k, v)| (k, &v[..])));
url.query_pairs_mut()
.extend_pairs(self.extra_params.iter().cloned());
url
};
(url, self.state)
}
}
#[derive(Clone, Debug)]
pub struct HttpRequest {
pub url: Url,
pub method: http::method::Method,
pub headers: HeaderMap,
pub body: Vec<u8>,
}
#[derive(Clone, Debug)]
pub struct HttpResponse {
pub status_code: http::status::StatusCode,
pub headers: HeaderMap,
pub body: Vec<u8>,
}
#[derive(Debug)]
pub struct CodeTokenRequest<'a, TE, TR, TT>
where
TE: ErrorResponse,
TR: TokenResponse<TT>,
TT: TokenType,
{
auth_type: &'a AuthType,
client_id: &'a ClientId,
client_secret: Option<&'a ClientSecret>,
code: AuthorizationCode,
extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
pkce_verifier: Option<PkceCodeVerifier>,
token_url: Option<&'a TokenUrl>,
redirect_url: Option<Cow<'a, RedirectUrl>>,
_phantom: PhantomData<(TE, TR, TT)>,
}
impl<'a, TE, TR, TT> CodeTokenRequest<'a, TE, TR, TT>
where
TE: ErrorResponse + 'static,
TR: TokenResponse<TT>,
TT: TokenType,
{
pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.extra_params.push((name.into(), value.into()));
self
}
pub fn set_pkce_verifier(mut self, pkce_verifier: PkceCodeVerifier) -> Self {
self.pkce_verifier = Some(pkce_verifier);
self
}
pub fn set_redirect_uri(mut self, redirect_url: Cow<'a, RedirectUrl>) -> Self {
self.redirect_url = Some(redirect_url);
self
}
fn prepare_request<RE>(self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
where
RE: Error + 'static,
{
let mut params = vec![
("grant_type", "authorization_code"),
("code", self.code.secret()),
];
if let Some(ref pkce_verifier) = self.pkce_verifier {
params.push(("code_verifier", pkce_verifier.secret()));
}
Ok(endpoint_request(
self.auth_type,
self.client_id,
self.client_secret,
&self.extra_params,
self.redirect_url,
None,
self.token_url
.ok_or_else(|| RequestTokenError::Other("no token_url provided".to_string()))?
.url(),
params,
))
}
pub fn request<F, RE>(self, http_client: F) -> Result<TR, RequestTokenError<RE, TE>>
where
F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
RE: Error + 'static,
{
http_client(self.prepare_request()?)
.map_err(RequestTokenError::Request)
.and_then(endpoint_response)
}
pub async fn request_async<C, F, RE>(
self,
http_client: C,
) -> Result<TR, RequestTokenError<RE, TE>>
where
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
RE: Error + 'static,
{
let http_request = self.prepare_request()?;
let http_response = http_client(http_request)
.await
.map_err(RequestTokenError::Request)?;
endpoint_response(http_response)
}
}
#[derive(Debug)]
pub struct RefreshTokenRequest<'a, TE, TR, TT>
where
TE: ErrorResponse,
TR: TokenResponse<TT>,
TT: TokenType,
{
auth_type: &'a AuthType,
client_id: &'a ClientId,
client_secret: Option<&'a ClientSecret>,
extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
refresh_token: &'a RefreshToken,
scopes: Vec<Cow<'a, Scope>>,
token_url: Option<&'a TokenUrl>,
_phantom: PhantomData<(TE, TR, TT)>,
}
impl<'a, TE, TR, TT> RefreshTokenRequest<'a, TE, TR, TT>
where
TE: ErrorResponse + 'static,
TR: TokenResponse<TT>,
TT: TokenType,
{
pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.extra_params.push((name.into(), value.into()));
self
}
pub fn add_scope(mut self, scope: Scope) -> Self {
self.scopes.push(Cow::Owned(scope));
self
}
pub fn add_scopes<I>(mut self, scopes: I) -> Self
where
I: IntoIterator<Item = Scope>,
{
self.scopes.extend(scopes.into_iter().map(Cow::Owned));
self
}
pub fn request<F, RE>(self, http_client: F) -> Result<TR, RequestTokenError<RE, TE>>
where
F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
RE: Error + 'static,
{
http_client(self.prepare_request()?)
.map_err(RequestTokenError::Request)
.and_then(endpoint_response)
}
pub async fn request_async<C, F, RE>(
self,
http_client: C,
) -> Result<TR, RequestTokenError<RE, TE>>
where
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
RE: Error + 'static,
{
let http_request = self.prepare_request()?;
let http_response = http_client(http_request)
.await
.map_err(RequestTokenError::Request)?;
endpoint_response(http_response)
}
fn prepare_request<RE>(&self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
where
RE: Error + 'static,
{
Ok(endpoint_request(
self.auth_type,
self.client_id,
self.client_secret,
&self.extra_params,
None,
Some(&self.scopes),
self.token_url
.ok_or_else(|| RequestTokenError::Other("no token_url provided".to_string()))?
.url(),
vec![
("grant_type", "refresh_token"),
("refresh_token", self.refresh_token.secret()),
],
))
}
}
#[derive(Debug)]
pub struct PasswordTokenRequest<'a, TE, TR, TT>
where
TE: ErrorResponse,
TR: TokenResponse<TT>,
TT: TokenType,
{
auth_type: &'a AuthType,
client_id: &'a ClientId,
client_secret: Option<&'a ClientSecret>,
extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
username: &'a ResourceOwnerUsername,
password: &'a ResourceOwnerPassword,
scopes: Vec<Cow<'a, Scope>>,
token_url: Option<&'a TokenUrl>,
_phantom: PhantomData<(TE, TR, TT)>,
}
impl<'a, TE, TR, TT> PasswordTokenRequest<'a, TE, TR, TT>
where
TE: ErrorResponse + 'static,
TR: TokenResponse<TT>,
TT: TokenType,
{
pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.extra_params.push((name.into(), value.into()));
self
}
pub fn add_scope(mut self, scope: Scope) -> Self {
self.scopes.push(Cow::Owned(scope));
self
}
pub fn add_scopes<I>(mut self, scopes: I) -> Self
where
I: IntoIterator<Item = Scope>,
{
self.scopes.extend(scopes.into_iter().map(Cow::Owned));
self
}
pub fn request<F, RE>(self, http_client: F) -> Result<TR, RequestTokenError<RE, TE>>
where
F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
RE: Error + 'static,
{
http_client(self.prepare_request()?)
.map_err(RequestTokenError::Request)
.and_then(endpoint_response)
}
pub async fn request_async<C, F, RE>(
self,
http_client: C,
) -> Result<TR, RequestTokenError<RE, TE>>
where
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
RE: Error + 'static,
{
let http_request = self.prepare_request()?;
let http_response = http_client(http_request)
.await
.map_err(RequestTokenError::Request)?;
endpoint_response(http_response)
}
fn prepare_request<RE>(&self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
where
RE: Error + 'static,
{
Ok(endpoint_request(
self.auth_type,
self.client_id,
self.client_secret,
&self.extra_params,
None,
Some(&self.scopes),
self.token_url
.ok_or_else(|| RequestTokenError::Other("no token_url provided".to_string()))?
.url(),
vec![
("grant_type", "password"),
("username", self.username),
("password", self.password.secret()),
],
))
}
}
#[derive(Debug)]
pub struct ClientCredentialsTokenRequest<'a, TE, TR, TT>
where
TE: ErrorResponse,
TR: TokenResponse<TT>,
TT: TokenType,
{
auth_type: &'a AuthType,
client_id: &'a ClientId,
client_secret: Option<&'a ClientSecret>,
extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
scopes: Vec<Cow<'a, Scope>>,
token_url: Option<&'a TokenUrl>,
_phantom: PhantomData<(TE, TR, TT)>,
}
impl<'a, TE, TR, TT> ClientCredentialsTokenRequest<'a, TE, TR, TT>
where
TE: ErrorResponse + 'static,
TR: TokenResponse<TT>,
TT: TokenType,
{
pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.extra_params.push((name.into(), value.into()));
self
}
pub fn add_scope(mut self, scope: Scope) -> Self {
self.scopes.push(Cow::Owned(scope));
self
}
pub fn add_scopes<I>(mut self, scopes: I) -> Self
where
I: IntoIterator<Item = Scope>,
{
self.scopes.extend(scopes.into_iter().map(Cow::Owned));
self
}
pub fn request<F, RE>(self, http_client: F) -> Result<TR, RequestTokenError<RE, TE>>
where
F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
RE: Error + 'static,
{
http_client(self.prepare_request()?)
.map_err(RequestTokenError::Request)
.and_then(endpoint_response)
}
pub async fn request_async<C, F, RE>(
self,
http_client: C,
) -> Result<TR, RequestTokenError<RE, TE>>
where
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
RE: Error + 'static,
{
let http_request = self.prepare_request()?;
let http_response = http_client(http_request)
.await
.map_err(RequestTokenError::Request)?;
endpoint_response(http_response)
}
fn prepare_request<RE>(&self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
where
RE: Error + 'static,
{
Ok(endpoint_request(
self.auth_type,
self.client_id,
self.client_secret,
&self.extra_params,
None,
Some(&self.scopes),
self.token_url
.ok_or_else(|| RequestTokenError::Other("no token_url provided".to_string()))?
.url(),
vec![("grant_type", "client_credentials")],
))
}
}
#[derive(Debug)]
pub struct IntrospectionRequest<'a, TE, TIR, TT>
where
TE: ErrorResponse,
TIR: TokenIntrospectionResponse<TT>,
TT: TokenType,
{
token: &'a AccessToken,
token_type_hint: Option<Cow<'a, str>>,
auth_type: &'a AuthType,
client_id: &'a ClientId,
client_secret: Option<&'a ClientSecret>,
extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
introspection_url: &'a IntrospectionUrl,
_phantom: PhantomData<(TE, TIR, TT)>,
}
impl<'a, TE, TIR, TT> IntrospectionRequest<'a, TE, TIR, TT>
where
TE: ErrorResponse + 'static,
TIR: TokenIntrospectionResponse<TT>,
TT: TokenType,
{
pub fn set_token_type_hint<V>(mut self, value: V) -> Self
where
V: Into<Cow<'a, str>>,
{
self.token_type_hint = Some(value.into());
self
}
pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.extra_params.push((name.into(), value.into()));
self
}
fn prepare_request<RE>(self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
where
RE: Error + 'static,
{
let mut params: Vec<(&str, &str)> = vec![("token", self.token.secret())];
if let Some(ref token_type_hint) = self.token_type_hint {
params.push(("token_type_hint", token_type_hint));
}
Ok(endpoint_request(
self.auth_type,
self.client_id,
self.client_secret,
&self.extra_params,
None,
None,
self.introspection_url.url(),
params,
))
}
pub fn request<F, RE>(self, http_client: F) -> Result<TIR, RequestTokenError<RE, TE>>
where
F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
RE: Error + 'static,
{
http_client(self.prepare_request()?)
.map_err(RequestTokenError::Request)
.and_then(endpoint_response)
}
pub async fn request_async<C, F, RE>(
self,
http_client: C,
) -> Result<TIR, RequestTokenError<RE, TE>>
where
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
RE: Error + 'static,
{
let http_request = self.prepare_request()?;
let http_response = http_client(http_request)
.await
.map_err(RequestTokenError::Request)?;
endpoint_response(http_response)
}
}
#[derive(Debug)]
pub struct RevocationRequest<'a, RT, TE>
where
RT: RevocableToken,
TE: ErrorResponse,
{
token: RT,
auth_type: &'a AuthType,
client_id: &'a ClientId,
client_secret: Option<&'a ClientSecret>,
extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
revocation_url: &'a RevocationUrl,
_phantom: PhantomData<(RT, TE)>,
}
impl<'a, RT, TE> RevocationRequest<'a, RT, TE>
where
RT: RevocableToken,
TE: ErrorResponse + 'static,
{
pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.extra_params.push((name.into(), value.into()));
self
}
fn prepare_request<RE>(self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
where
RE: Error + 'static,
{
let mut params: Vec<(&str, &str)> = vec![("token", self.token.secret())];
if let Some(type_hint) = self.token.type_hint() {
params.push(("token_type_hint", type_hint));
}
Ok(endpoint_request(
self.auth_type,
self.client_id,
self.client_secret,
&self.extra_params,
None,
None,
self.revocation_url.url(),
params,
))
}
pub fn request<F, RE>(self, http_client: F) -> Result<(), RequestTokenError<RE, TE>>
where
F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
RE: Error + 'static,
{
http_client(self.prepare_request()?)
.map_err(RequestTokenError::Request)
.and_then(endpoint_response_status_only)
}
pub async fn request_async<C, F, RE>(
self,
http_client: C,
) -> Result<(), RequestTokenError<RE, TE>>
where
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
RE: Error + 'static,
{
let http_request = self.prepare_request()?;
let http_response = http_client(http_request)
.await
.map_err(RequestTokenError::Request)?;
endpoint_response_status_only(http_response)
}
}
#[allow(clippy::too_many_arguments)]
fn endpoint_request<'a>(
auth_type: &'a AuthType,
client_id: &'a ClientId,
client_secret: Option<&'a ClientSecret>,
extra_params: &'a [(Cow<'a, str>, Cow<'a, str>)],
redirect_url: Option<Cow<'a, RedirectUrl>>,
scopes: Option<&'a Vec<Cow<'a, Scope>>>,
url: &'a Url,
params: Vec<(&'a str, &'a str)>,
) -> HttpRequest {
let mut headers = HeaderMap::new();
headers.append(ACCEPT, HeaderValue::from_static(CONTENT_TYPE_JSON));
headers.append(
CONTENT_TYPE,
HeaderValue::from_static(CONTENT_TYPE_FORMENCODED),
);
let scopes_opt = scopes.and_then(|scopes| {
if !scopes.is_empty() {
Some(
scopes
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(" "),
)
} else {
None
}
});
let mut params: Vec<(&str, &str)> = params;
if let Some(ref scopes) = scopes_opt {
params.push(("scope", scopes));
}
match (auth_type, client_secret) {
(AuthType::BasicAuth, Some(secret)) => {
let urlencoded_id: String =
form_urlencoded::byte_serialize(&client_id.as_bytes()).collect();
let urlencoded_secret: String =
form_urlencoded::byte_serialize(secret.secret().as_bytes()).collect();
let b64_credential =
base64::encode(&format!("{}:{}", &urlencoded_id, urlencoded_secret));
headers.append(
AUTHORIZATION,
HeaderValue::from_str(&format!("Basic {}", &b64_credential)).unwrap(),
);
}
(AuthType::RequestBody, _) | (AuthType::BasicAuth, None) => {
params.push(("client_id", client_id));
if let Some(ref client_secret) = client_secret {
params.push(("client_secret", client_secret.secret()));
}
}
}
if let Some(ref redirect_url) = redirect_url {
params.push(("redirect_uri", redirect_url.as_str()));
}
params.extend_from_slice(
extra_params
.iter()
.map(|&(ref k, ref v)| (k.as_ref(), v.as_ref()))
.collect::<Vec<_>>()
.as_slice(),
);
let body = url::form_urlencoded::Serializer::new(String::new())
.extend_pairs(params)
.finish()
.into_bytes();
HttpRequest {
url: url.to_owned(),
method: http::method::Method::POST,
headers,
body,
}
}
fn endpoint_response<RE, TE, DO>(
http_response: HttpResponse,
) -> Result<DO, RequestTokenError<RE, TE>>
where
RE: Error + 'static,
TE: ErrorResponse,
DO: DeserializeOwned,
{
check_response_status(&http_response)?;
check_response_body(&http_response)?;
let response_body = http_response.body.as_slice();
serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_slice(response_body))
.map_err(|e| RequestTokenError::Parse(e, response_body.to_vec()))
}
fn endpoint_response_status_only<RE, TE>(
http_response: HttpResponse,
) -> Result<(), RequestTokenError<RE, TE>>
where
RE: Error + 'static,
TE: ErrorResponse,
{
check_response_status(&http_response)
}
fn check_response_status<RE, TE>(
http_response: &HttpResponse,
) -> Result<(), RequestTokenError<RE, TE>>
where
RE: Error + 'static,
TE: ErrorResponse,
{
if http_response.status_code != StatusCode::OK {
let reason = http_response.body.as_slice();
if reason.is_empty() {
return Err(RequestTokenError::Other(
"Server returned empty error response".to_string(),
));
} else {
let error = match serde_path_to_error::deserialize::<_, TE>(
&mut serde_json::Deserializer::from_slice(reason),
) {
Ok(error) => RequestTokenError::ServerResponse(error),
Err(error) => RequestTokenError::Parse(error, reason.to_vec()),
};
return Err(error);
}
}
Ok(())
}
fn check_response_body<RE, TE>(
http_response: &HttpResponse,
) -> Result<(), RequestTokenError<RE, TE>>
where
RE: Error + 'static,
TE: ErrorResponse,
{
http_response
.headers
.get(CONTENT_TYPE)
.map_or(Ok(()), |content_type|
if content_type.to_str().ok().filter(|ct| ct.to_lowercase().starts_with(CONTENT_TYPE_JSON)).is_none() {
Err(
RequestTokenError::Other(
format!(
"Unexpected response Content-Type: {:?}, should be `{}`",
content_type,
CONTENT_TYPE_JSON
)
)
)
} else {
Ok(())
}
)?;
if http_response.body.is_empty() {
return Err(RequestTokenError::Other(
"Server returned empty response body".to_string(),
));
}
Ok(())
}
#[derive(Debug)]
pub struct DeviceAuthorizationRequest<'a, TE>
where
TE: ErrorResponse,
{
auth_type: &'a AuthType,
client_id: &'a ClientId,
client_secret: Option<&'a ClientSecret>,
extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
scopes: Vec<Cow<'a, Scope>>,
device_authorization_url: &'a DeviceAuthorizationUrl,
_phantom: PhantomData<TE>,
}
impl<'a, TE> DeviceAuthorizationRequest<'a, TE>
where
TE: ErrorResponse + 'static,
{
pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.extra_params.push((name.into(), value.into()));
self
}
pub fn add_scope(mut self, scope: Scope) -> Self {
self.scopes.push(Cow::Owned(scope));
self
}
pub fn add_scopes<I>(mut self, scopes: I) -> Self
where
I: IntoIterator<Item = Scope>,
{
self.scopes.extend(scopes.into_iter().map(Cow::Owned));
self
}
fn prepare_request<RE>(self) -> Result<HttpRequest, RequestTokenError<RE, TE>>
where
RE: Error + 'static,
{
Ok(endpoint_request(
self.auth_type,
self.client_id,
self.client_secret,
&self.extra_params,
None,
Some(&self.scopes),
self.device_authorization_url.url(),
vec![],
))
}
pub fn request<F, RE, EF>(
self,
http_client: F,
) -> Result<DeviceAuthorizationResponse<EF>, RequestTokenError<RE, TE>>
where
F: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
RE: Error + 'static,
EF: ExtraDeviceAuthorizationFields,
{
http_client(self.prepare_request()?)
.map_err(RequestTokenError::Request)
.and_then(endpoint_response)
}
pub async fn request_async<C, F, RE, EF>(
self,
http_client: C,
) -> Result<DeviceAuthorizationResponse<EF>, RequestTokenError<RE, TE>>
where
C: FnOnce(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
RE: Error + 'static,
EF: ExtraDeviceAuthorizationFields,
{
let http_request = self.prepare_request()?;
let http_response = http_client(http_request)
.await
.map_err(RequestTokenError::Request)?;
endpoint_response(http_response)
}
}
#[derive(Clone)]
pub struct DeviceAccessTokenRequest<'a, 'b, TR, TT, EF>
where
TR: TokenResponse<TT>,
TT: TokenType,
EF: ExtraDeviceAuthorizationFields,
{
auth_type: &'a AuthType,
client_id: &'a ClientId,
client_secret: Option<&'a ClientSecret>,
extra_params: Vec<(Cow<'a, str>, Cow<'a, str>)>,
token_url: Option<&'a TokenUrl>,
dev_auth_resp: &'a DeviceAuthorizationResponse<EF>,
time_fn: Arc<dyn Fn() -> DateTime<Utc> + 'b + Send + Sync>,
_phantom: PhantomData<(TR, TT, EF)>,
}
impl<'a, 'b, TR, TT, EF> DeviceAccessTokenRequest<'a, 'b, TR, TT, EF>
where
TR: TokenResponse<TT>,
TT: TokenType,
EF: ExtraDeviceAuthorizationFields,
{
pub fn add_extra_param<N, V>(mut self, name: N, value: V) -> Self
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
self.extra_params.push((name.into(), value.into()));
self
}
pub fn set_time_fn<T>(mut self, time_fn: T) -> Self
where
T: Fn() -> DateTime<Utc> + 'b + Send + Sync,
{
self.time_fn = Arc::new(time_fn);
self
}
pub fn request<F, S, RE>(
self,
http_client: F,
sleep_fn: S,
timeout: Option<Duration>,
) -> Result<TR, RequestTokenError<RE, DeviceCodeErrorResponse>>
where
F: Fn(HttpRequest) -> Result<HttpResponse, RE>,
S: Fn(Duration),
RE: Error + 'static,
{
let timeout_dt = self.compute_timeout(timeout)?;
let mut interval = self.dev_auth_resp.interval();
loop {
let now = (*self.time_fn)();
if now > timeout_dt {
break Err(RequestTokenError::ServerResponse(
DeviceCodeErrorResponse::new(
DeviceCodeErrorResponseType::ExpiredToken,
Some(String::from("This device code has expired.")),
None,
),
));
}
match self.process_response(http_client(self.prepare_request()?), interval) {
DeviceAccessTokenPollResult::ContinueWithNewPollInterval(new_interval) => {
interval = new_interval
}
DeviceAccessTokenPollResult::Done(res, _) => break res,
}
sleep_fn(interval);
}
}
pub async fn request_async<C, F, S, SF, RE>(
self,
http_client: C,
sleep_fn: S,
timeout: Option<Duration>,
) -> Result<TR, RequestTokenError<RE, DeviceCodeErrorResponse>>
where
C: Fn(HttpRequest) -> F,
F: Future<Output = Result<HttpResponse, RE>>,
S: Fn(Duration) -> SF,
SF: Future<Output = ()>,
RE: Error + 'static,
{
let timeout_dt = self.compute_timeout(timeout)?;
let mut interval = self.dev_auth_resp.interval();
loop {
let now = (*self.time_fn)();
if now > timeout_dt {
break Err(RequestTokenError::ServerResponse(
DeviceCodeErrorResponse::new(
DeviceCodeErrorResponseType::ExpiredToken,
Some(String::from("This device code has expired.")),
None,
),
));
}
match self.process_response(http_client(self.prepare_request()?).await, interval) {
DeviceAccessTokenPollResult::ContinueWithNewPollInterval(new_interval) => {
interval = new_interval
}
DeviceAccessTokenPollResult::Done(res, _) => break res,
}
sleep_fn(interval).await;
}
}
fn prepare_request<RE>(
&self,
) -> Result<HttpRequest, RequestTokenError<RE, DeviceCodeErrorResponse>>
where
RE: Error + 'static,
{
Ok(endpoint_request(
self.auth_type,
self.client_id,
self.client_secret,
&self.extra_params,
None,
None,
self.token_url
.ok_or_else(|| RequestTokenError::Other("no token_url provided".to_string()))?
.url(),
vec![
("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
("device_code", self.dev_auth_resp.device_code().secret()),
],
))
}
fn process_response<RE>(
&self,
res: Result<HttpResponse, RE>,
current_interval: Duration,
) -> DeviceAccessTokenPollResult<TR, RE, DeviceCodeErrorResponse, TT>
where
RE: Error + 'static,
{
let http_response = match res {
Ok(inner) => inner,
Err(_) => {
let new_interval = current_interval.checked_mul(2).unwrap_or(current_interval);
return DeviceAccessTokenPollResult::ContinueWithNewPollInterval(new_interval);
}
};
let res = endpoint_response::<RE, DeviceCodeErrorResponse, TR>(http_response);
match res {
Err(RequestTokenError::ServerResponse(dcer)) => {
match dcer.error() {
DeviceCodeErrorResponseType::AuthorizationPending => {
DeviceAccessTokenPollResult::ContinueWithNewPollInterval(current_interval)
}
DeviceCodeErrorResponseType::SlowDown => {
DeviceAccessTokenPollResult::ContinueWithNewPollInterval(
current_interval + Duration::from_secs(5),
)
}
_ => DeviceAccessTokenPollResult::Done(
Err(RequestTokenError::ServerResponse(dcer)),
PhantomData,
),
}
}
res => DeviceAccessTokenPollResult::Done(res, PhantomData),
}
}
fn compute_timeout<RE>(
&self,
timeout: Option<Duration>,
) -> Result<DateTime<Utc>, RequestTokenError<RE, DeviceCodeErrorResponse>>
where
RE: Error + 'static,
{
let timeout_dur = timeout.unwrap_or_else(|| self.dev_auth_resp.expires_in());
let chrono_timeout = chrono::Duration::from_std(timeout_dur)
.map_err(|_| RequestTokenError::Other("Failed to convert duration".to_string()))?;
let timeout_dt = (*self.time_fn)()
.checked_add_signed(chrono_timeout)
.ok_or_else(|| RequestTokenError::Other("Failed to calculate timeout".to_string()))?;
Ok(timeout_dt)
}
}
pub trait TokenType: Clone + DeserializeOwned + Debug + PartialEq + Serialize {}
pub trait ExtraTokenFields: DeserializeOwned + Debug + Serialize {}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct EmptyExtraTokenFields {}
impl ExtraTokenFields for EmptyExtraTokenFields {}
pub trait TokenResponse<TT>: Debug + DeserializeOwned + Serialize
where
TT: TokenType,
{
fn access_token(&self) -> &AccessToken;
fn token_type(&self) -> &TT;
fn expires_in(&self) -> Option<Duration>;
fn refresh_token(&self) -> Option<&RefreshToken>;
fn scopes(&self) -> Option<&Vec<Scope>>;
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StandardTokenResponse<EF, TT>
where
EF: ExtraTokenFields,
TT: TokenType,
{
access_token: AccessToken,
#[serde(bound = "TT: TokenType")]
#[serde(deserialize_with = "helpers::deserialize_untagged_enum_case_insensitive")]
token_type: TT,
#[serde(skip_serializing_if = "Option::is_none")]
expires_in: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
refresh_token: Option<RefreshToken>,
#[serde(rename = "scope")]
#[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
#[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
scopes: Option<Vec<Scope>>,
#[serde(bound = "EF: ExtraTokenFields")]
#[serde(flatten)]
extra_fields: EF,
}
impl<EF, TT> StandardTokenResponse<EF, TT>
where
EF: ExtraTokenFields,
TT: TokenType,
{
pub fn new(access_token: AccessToken, token_type: TT, extra_fields: EF) -> Self {
Self {
access_token,
token_type,
expires_in: None,
refresh_token: None,
scopes: None,
extra_fields,
}
}
pub fn set_access_token(&mut self, access_token: AccessToken) {
self.access_token = access_token;
}
pub fn set_token_type(&mut self, token_type: TT) {
self.token_type = token_type;
}
pub fn set_expires_in(&mut self, expires_in: Option<&Duration>) {
self.expires_in = expires_in.map(Duration::as_secs);
}
pub fn set_refresh_token(&mut self, refresh_token: Option<RefreshToken>) {
self.refresh_token = refresh_token;
}
pub fn set_scopes(&mut self, scopes: Option<Vec<Scope>>) {
self.scopes = scopes;
}
pub fn extra_fields(&self) -> &EF {
&self.extra_fields
}
pub fn set_extra_fields(&mut self, extra_fields: EF) {
self.extra_fields = extra_fields;
}
}
impl<EF, TT> TokenResponse<TT> for StandardTokenResponse<EF, TT>
where
EF: ExtraTokenFields,
TT: TokenType,
{
fn access_token(&self) -> &AccessToken {
&self.access_token
}
fn token_type(&self) -> &TT {
&self.token_type
}
fn expires_in(&self) -> Option<Duration> {
self.expires_in.map(Duration::from_secs)
}
fn refresh_token(&self) -> Option<&RefreshToken> {
self.refresh_token.as_ref()
}
fn scopes(&self) -> Option<&Vec<Scope>> {
self.scopes.as_ref()
}
}
pub trait TokenIntrospectionResponse<TT>: Debug + DeserializeOwned + Serialize
where
TT: TokenType,
{
fn active(&self) -> bool;
fn scopes(&self) -> Option<&Vec<Scope>>;
fn client_id(&self) -> Option<&ClientId>;
fn username(&self) -> Option<&str>;
fn token_type(&self) -> Option<&TT>;
fn exp(&self) -> Option<DateTime<Utc>>;
fn iat(&self) -> Option<DateTime<Utc>>;
fn nbf(&self) -> Option<DateTime<Utc>>;
fn sub(&self) -> Option<&str>;
fn aud(&self) -> Option<&Vec<String>>;
fn iss(&self) -> Option<&str>;
fn jti(&self) -> Option<&str>;
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StandardTokenIntrospectionResponse<EF, TT>
where
EF: ExtraTokenFields,
TT: TokenType + 'static,
{
active: bool,
#[serde(rename = "scope")]
#[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
#[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
scopes: Option<Vec<Scope>>,
#[serde(skip_serializing_if = "Option::is_none")]
client_id: Option<ClientId>,
#[serde(skip_serializing_if = "Option::is_none")]
username: Option<String>,
#[serde(
bound = "TT: TokenType",
skip_serializing_if = "Option::is_none",
deserialize_with = "helpers::deserialize_untagged_enum_case_insensitive",
default = "none_field"
)]
token_type: Option<TT>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "ts_seconds_option")]
#[serde(default)]
exp: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "ts_seconds_option")]
#[serde(default)]
iat: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "ts_seconds_option")]
#[serde(default)]
nbf: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
sub: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
#[serde(deserialize_with = "helpers::deserialize_optional_string_or_vec_string")]
aud: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
iss: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
jti: Option<String>,
#[serde(bound = "EF: ExtraTokenFields")]
#[serde(flatten)]
extra_fields: EF,
}
fn none_field<T>() -> Option<T> {
None
}
impl<EF, TT> StandardTokenIntrospectionResponse<EF, TT>
where
EF: ExtraTokenFields,
TT: TokenType,
{
pub fn new(active: bool, extra_fields: EF) -> Self {
Self {
active,
scopes: None,
client_id: None,
username: None,
token_type: None,
exp: None,
iat: None,
nbf: None,
sub: None,
aud: None,
iss: None,
jti: None,
extra_fields,
}
}
pub fn set_active(&mut self, active: bool) {
self.active = active;
}
pub fn set_scopes(&mut self, scopes: Option<Vec<Scope>>) {
self.scopes = scopes;
}
pub fn set_client_id(&mut self, client_id: Option<ClientId>) {
self.client_id = client_id;
}
pub fn set_username(&mut self, username: Option<String>) {
self.username = username;
}
pub fn set_token_type(&mut self, token_type: Option<TT>) {
self.token_type = token_type;
}
pub fn set_exp(&mut self, exp: Option<DateTime<Utc>>) {
self.exp = exp;
}
pub fn set_iat(&mut self, iat: Option<DateTime<Utc>>) {
self.iat = iat;
}
pub fn set_nbf(&mut self, nbf: Option<DateTime<Utc>>) {
self.nbf = nbf;
}
pub fn set_sub(&mut self, sub: Option<String>) {
self.sub = sub;
}
pub fn set_aud(&mut self, aud: Option<Vec<String>>) {
self.aud = aud;
}
pub fn set_iss(&mut self, iss: Option<String>) {
self.iss = iss;
}
pub fn set_jti(&mut self, jti: Option<String>) {
self.jti = jti;
}
pub fn extra_fields(&self) -> &EF {
&self.extra_fields
}
pub fn set_extra_fields(&mut self, extra_fields: EF) {
self.extra_fields = extra_fields;
}
}
impl<EF, TT> TokenIntrospectionResponse<TT> for StandardTokenIntrospectionResponse<EF, TT>
where
EF: ExtraTokenFields,
TT: TokenType,
{
fn active(&self) -> bool {
self.active
}
fn scopes(&self) -> Option<&Vec<Scope>> {
self.scopes.as_ref()
}
fn client_id(&self) -> Option<&ClientId> {
self.client_id.as_ref()
}
fn username(&self) -> Option<&str> {
self.username.as_deref()
}
fn token_type(&self) -> Option<&TT> {
self.token_type.as_ref()
}
fn exp(&self) -> Option<DateTime<Utc>> {
self.exp
}
fn iat(&self) -> Option<DateTime<Utc>> {
self.iat
}
fn nbf(&self) -> Option<DateTime<Utc>> {
self.nbf
}
fn sub(&self) -> Option<&str> {
self.sub.as_deref()
}
fn aud(&self) -> Option<&Vec<String>> {
self.aud.as_ref()
}
fn iss(&self) -> Option<&str> {
self.iss.as_deref()
}
fn jti(&self) -> Option<&str> {
self.jti.as_deref()
}
}
pub trait ErrorResponse: Debug + DeserializeOwned + Serialize {}
pub trait ErrorResponseType: Debug + DeserializeOwned + Serialize {}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct StandardErrorResponse<T: ErrorResponseType> {
#[serde(bound = "T: ErrorResponseType")]
error: T,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
error_description: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
error_uri: Option<String>,
}
impl<T: ErrorResponseType> StandardErrorResponse<T> {
pub fn new(error: T, error_description: Option<String>, error_uri: Option<String>) -> Self {
Self {
error,
error_description,
error_uri,
}
}
pub fn error(&self) -> &T {
&self.error
}
pub fn error_description(&self) -> Option<&String> {
self.error_description.as_ref()
}
pub fn error_uri(&self) -> Option<&String> {
self.error_uri.as_ref()
}
}
impl<T> ErrorResponse for StandardErrorResponse<T> where T: ErrorResponseType + 'static {}
impl<TE> Display for StandardErrorResponse<TE>
where
TE: ErrorResponseType + Display,
{
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
let mut formatted = self.error().to_string();
if let Some(error_description) = self.error_description() {
formatted.push_str(": ");
formatted.push_str(error_description);
}
if let Some(error_uri) = self.error_uri() {
formatted.push_str(" / See ");
formatted.push_str(error_uri);
}
write!(f, "{}", formatted)
}
}
#[derive(Debug, thiserror::Error)]
pub enum RequestTokenError<RE, T>
where
RE: Error + 'static,
T: ErrorResponse + 'static,
{
#[error("Server returned error response")]
ServerResponse(T),
#[error("Request failed")]
Request(#[source] RE),
#[error("Failed to parse server response")]
Parse(
#[source] serde_path_to_error::Error<serde_json::error::Error>,
Vec<u8>,
),
#[error("Other error: {}", _0)]
Other(String),
}