use std::collections::{BTreeMap, BTreeSet};
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, RwLock};
use rustauth_core::db::{Session, User};
use rustauth_core::error::RustAuthError;
use rustauth_core::options::RateLimitRule;
use serde_json::{Map, Value};
use thiserror::Error;
use crate::models::SchemaClient;
type ClientReferenceFuture =
Pin<Box<dyn Future<Output = Result<Option<String>, RustAuthError>> + Send>>;
type ClientPrivilegesFuture = Pin<Box<dyn Future<Output = Result<bool, RustAuthError>> + Send>>;
type JsonObjectFuture =
Pin<Box<dyn Future<Output = Result<Map<String, Value>, RustAuthError>> + Send>>;
type OptionalStringFuture =
Pin<Box<dyn Future<Output = Result<Option<String>, RustAuthError>> + Send>>;
type RequestUriFuture =
Pin<Box<dyn Future<Output = Result<Option<Vec<(String, String)>>, RustAuthError>> + Send>>;
type StringGeneratorFuture = Pin<Box<dyn Future<Output = Result<String, RustAuthError>> + Send>>;
type BoolResolverFuture = Pin<Box<dyn Future<Output = Result<bool, RustAuthError>> + Send>>;
type RefreshTokenEncodeFuture = Pin<Box<dyn Future<Output = Result<String, RustAuthError>> + Send>>;
type RefreshTokenDecodeFuture =
Pin<Box<dyn Future<Output = Result<RefreshTokenFormatDecodeOutput, RustAuthError>> + Send>>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClientReferenceInput {
pub user: Option<User>,
pub session: Option<Session>,
}
#[derive(Clone)]
pub struct ClientReferenceResolver {
resolver: Arc<dyn Fn(ClientReferenceInput) -> ClientReferenceFuture + Send + Sync>,
}
impl ClientReferenceResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(ClientReferenceInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Option<String>, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(
&self,
input: ClientReferenceInput,
) -> Result<Option<String>, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for ClientReferenceResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("ClientReferenceResolver(..)")
}
}
impl PartialEq for ClientReferenceResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for ClientReferenceResolver {}
#[derive(Clone, Default)]
pub struct TrustedClientCache {
clients: Arc<RwLock<BTreeMap<String, SchemaClient>>>,
}
impl TrustedClientCache {
pub fn get(&self, client_id: &str) -> Result<Option<SchemaClient>, RustAuthError> {
let clients = self
.clients
.read()
.map_err(|_| RustAuthError::Api("trusted client cache lock poisoned".to_owned()))?;
Ok(clients.get(client_id).cloned())
}
pub fn insert(&self, client: SchemaClient) -> Result<(), RustAuthError> {
let mut clients = self
.clients
.write()
.map_err(|_| RustAuthError::Api("trusted client cache lock poisoned".to_owned()))?;
clients.insert(client.client_id.clone(), client);
Ok(())
}
}
impl std::fmt::Debug for TrustedClientCache {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("TrustedClientCache(..)")
}
}
impl PartialEq for TrustedClientCache {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for TrustedClientCache {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ClientPrivilegeAction {
Create,
Read,
Update,
Delete,
List,
Rotate,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClientPrivilegesInput {
pub action: ClientPrivilegeAction,
pub user: Option<User>,
pub session: Option<Session>,
}
#[derive(Clone)]
pub struct ClientPrivilegesResolver {
resolver: Arc<dyn Fn(ClientPrivilegesInput) -> ClientPrivilegesFuture + Send + Sync>,
}
impl ClientPrivilegesResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(ClientPrivilegesInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<bool, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(&self, input: ClientPrivilegesInput) -> Result<bool, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for ClientPrivilegesResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("ClientPrivilegesResolver(..)")
}
}
impl PartialEq for ClientPrivilegesResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for ClientPrivilegesResolver {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClientSecretHashInput {
pub secret: String,
}
#[derive(Clone)]
pub struct ClientSecretHashResolver {
resolver: Arc<dyn Fn(ClientSecretHashInput) -> StringGeneratorFuture + Send + Sync>,
}
impl ClientSecretHashResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(ClientSecretHashInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<String, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(&self, input: ClientSecretHashInput) -> Result<String, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for ClientSecretHashResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("ClientSecretHashResolver(..)")
}
}
impl PartialEq for ClientSecretHashResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for ClientSecretHashResolver {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClientSecretVerifyInput {
pub secret: String,
pub stored_hash: String,
}
#[derive(Clone)]
pub struct ClientSecretVerifyResolver {
resolver: Arc<dyn Fn(ClientSecretVerifyInput) -> BoolResolverFuture + Send + Sync>,
}
impl ClientSecretVerifyResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(ClientSecretVerifyInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<bool, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(&self, input: ClientSecretVerifyInput) -> Result<bool, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for ClientSecretVerifyResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("ClientSecretVerifyResolver(..)")
}
}
impl PartialEq for ClientSecretVerifyResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for ClientSecretVerifyResolver {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenHashInput {
pub token: String,
pub token_type: String,
}
#[derive(Clone)]
pub struct TokenHashResolver {
resolver: Arc<dyn Fn(TokenHashInput) -> StringGeneratorFuture + Send + Sync>,
}
impl TokenHashResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(TokenHashInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<String, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(&self, input: TokenHashInput) -> Result<String, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for TokenHashResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("TokenHashResolver(..)")
}
}
impl PartialEq for TokenHashResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for TokenHashResolver {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PromptRedirectInput {
pub user: User,
pub session: Session,
pub scopes: Vec<String>,
}
#[derive(Clone)]
pub struct PromptRedirectResolver {
resolver: Arc<dyn Fn(PromptRedirectInput) -> OptionalStringFuture + Send + Sync>,
}
impl PromptRedirectResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(PromptRedirectInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Option<String>, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(
&self,
input: PromptRedirectInput,
) -> Result<Option<String>, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for PromptRedirectResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("PromptRedirectResolver(..)")
}
}
impl PartialEq for PromptRedirectResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for PromptRedirectResolver {}
#[derive(Clone)]
pub struct PromptShouldRedirectResolver {
resolver: Arc<dyn Fn(PromptRedirectInput) -> BoolResolverFuture + Send + Sync>,
}
impl PromptShouldRedirectResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(PromptRedirectInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<bool, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(&self, input: PromptRedirectInput) -> Result<bool, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for PromptShouldRedirectResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("PromptShouldRedirectResolver(..)")
}
}
impl PartialEq for PromptShouldRedirectResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for PromptShouldRedirectResolver {}
#[derive(Debug, Clone, PartialEq)]
pub struct CustomIdTokenClaimsInput {
pub user: User,
pub scopes: Vec<String>,
pub metadata: Option<Value>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CustomAccessTokenClaimsInput {
pub user: Option<User>,
pub reference_id: Option<String>,
pub scopes: Vec<String>,
pub resource: Vec<String>,
pub metadata: Option<Value>,
}
#[derive(Clone)]
pub struct CustomAccessTokenClaimsResolver {
resolver: Arc<dyn Fn(CustomAccessTokenClaimsInput) -> JsonObjectFuture + Send + Sync>,
}
impl CustomAccessTokenClaimsResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(CustomAccessTokenClaimsInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Map<String, Value>, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(
&self,
input: CustomAccessTokenClaimsInput,
) -> Result<Map<String, Value>, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for CustomAccessTokenClaimsResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("CustomAccessTokenClaimsResolver(..)")
}
}
impl PartialEq for CustomAccessTokenClaimsResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for CustomAccessTokenClaimsResolver {}
#[derive(Clone)]
pub struct CustomIdTokenClaimsResolver {
resolver: Arc<dyn Fn(CustomIdTokenClaimsInput) -> JsonObjectFuture + Send + Sync>,
}
impl CustomIdTokenClaimsResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(CustomIdTokenClaimsInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Map<String, Value>, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(
&self,
input: CustomIdTokenClaimsInput,
) -> Result<Map<String, Value>, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for CustomIdTokenClaimsResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("CustomIdTokenClaimsResolver(..)")
}
}
impl PartialEq for CustomIdTokenClaimsResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for CustomIdTokenClaimsResolver {}
#[derive(Debug, Clone, PartialEq)]
pub struct CustomTokenResponseFieldsInput {
pub grant_type: GrantType,
pub user: Option<User>,
pub scopes: Vec<String>,
pub metadata: Option<Value>,
}
#[derive(Clone)]
pub struct CustomTokenResponseFieldsResolver {
resolver: Arc<dyn Fn(CustomTokenResponseFieldsInput) -> JsonObjectFuture + Send + Sync>,
}
impl CustomTokenResponseFieldsResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(CustomTokenResponseFieldsInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Map<String, Value>, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(
&self,
input: CustomTokenResponseFieldsInput,
) -> Result<Map<String, Value>, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for CustomTokenResponseFieldsResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("CustomTokenResponseFieldsResolver(..)")
}
}
impl PartialEq for CustomTokenResponseFieldsResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for CustomTokenResponseFieldsResolver {}
#[derive(Debug, Clone, PartialEq)]
pub struct CustomUserInfoClaimsInput {
pub user: User,
pub scopes: Vec<String>,
pub jwt: Value,
}
#[derive(Clone)]
pub struct CustomUserInfoClaimsResolver {
resolver: Arc<dyn Fn(CustomUserInfoClaimsInput) -> JsonObjectFuture + Send + Sync>,
}
impl CustomUserInfoClaimsResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(CustomUserInfoClaimsInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Map<String, Value>, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(
&self,
input: CustomUserInfoClaimsInput,
) -> Result<Map<String, Value>, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for CustomUserInfoClaimsResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("CustomUserInfoClaimsResolver(..)")
}
}
impl PartialEq for CustomUserInfoClaimsResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for CustomUserInfoClaimsResolver {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RequestUriResolverInput {
pub request_uri: String,
pub client_id: Option<String>,
}
#[derive(Clone)]
pub struct RequestUriResolver {
resolver: Arc<dyn Fn(RequestUriResolverInput) -> RequestUriFuture + Send + Sync>,
}
impl RequestUriResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn(RequestUriResolverInput) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Option<Vec<(String, String)>>, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move |input| Box::pin(resolver(input))),
}
}
pub async fn resolve(
&self,
input: RequestUriResolverInput,
) -> Result<Option<Vec<(String, String)>>, RustAuthError> {
(self.resolver)(input).await
}
}
impl std::fmt::Debug for RequestUriResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("RequestUriResolver(..)")
}
}
impl PartialEq for RequestUriResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for RequestUriResolver {}
#[derive(Clone)]
pub struct StringGeneratorResolver {
resolver: Arc<dyn Fn() -> StringGeneratorFuture + Send + Sync>,
}
impl StringGeneratorResolver {
pub fn new<F, Fut>(resolver: F) -> Self
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<String, RustAuthError>> + Send + 'static,
{
Self {
resolver: Arc::new(move || Box::pin(resolver())),
}
}
pub async fn generate(&self) -> Result<String, RustAuthError> {
(self.resolver)().await
}
}
impl std::fmt::Debug for StringGeneratorResolver {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("StringGeneratorResolver(..)")
}
}
impl PartialEq for StringGeneratorResolver {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for StringGeneratorResolver {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RefreshTokenFormatEncodeInput {
pub token: String,
pub session_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RefreshTokenFormatDecodeOutput {
pub session_id: Option<String>,
pub token: String,
}
#[derive(Clone)]
pub struct RefreshTokenFormatter {
encoder: Arc<dyn Fn(RefreshTokenFormatEncodeInput) -> RefreshTokenEncodeFuture + Send + Sync>,
decoder: Arc<dyn Fn(String) -> RefreshTokenDecodeFuture + Send + Sync>,
}
impl RefreshTokenFormatter {
pub fn new<Encode, EncodeFuture, Decode, DecodeFuture>(encoder: Encode, decoder: Decode) -> Self
where
Encode: Fn(RefreshTokenFormatEncodeInput) -> EncodeFuture + Send + Sync + 'static,
EncodeFuture: Future<Output = Result<String, RustAuthError>> + Send + 'static,
Decode: Fn(String) -> DecodeFuture + Send + Sync + 'static,
DecodeFuture:
Future<Output = Result<RefreshTokenFormatDecodeOutput, RustAuthError>> + Send + 'static,
{
Self {
encoder: Arc::new(move |input| Box::pin(encoder(input))),
decoder: Arc::new(move |token| Box::pin(decoder(token))),
}
}
pub async fn encode(
&self,
input: RefreshTokenFormatEncodeInput,
) -> Result<String, RustAuthError> {
(self.encoder)(input).await
}
pub async fn decode(
&self,
token: String,
) -> Result<RefreshTokenFormatDecodeOutput, RustAuthError> {
(self.decoder)(token).await
}
}
impl std::fmt::Debug for RefreshTokenFormatter {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("RefreshTokenFormatter(..)")
}
}
impl PartialEq for RefreshTokenFormatter {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for RefreshTokenFormatter {}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct OAuthTokenPrefixes {
pub opaque_access_token: Option<String>,
pub refresh_token: Option<String>,
pub client_secret: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GrantType {
AuthorizationCode,
ClientCredentials,
RefreshToken,
}
impl GrantType {
pub fn as_str(self) -> &'static str {
match self {
Self::AuthorizationCode => "authorization_code",
Self::ClientCredentials => "client_credentials",
Self::RefreshToken => "refresh_token",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TokenEndpointAuthMethod {
None,
ClientSecretBasic,
ClientSecretPost,
}
impl TokenEndpointAuthMethod {
pub fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::ClientSecretBasic => "client_secret_basic",
Self::ClientSecretPost => "client_secret_post",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecretStorage {
Auto,
Hashed,
Encrypted,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OAuthProviderRateLimit {
Default,
Disabled,
Custom(RateLimitRule),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OAuthProviderRateLimits {
pub token: OAuthProviderRateLimit,
pub authorize: OAuthProviderRateLimit,
pub introspect: OAuthProviderRateLimit,
pub revoke: OAuthProviderRateLimit,
pub register: OAuthProviderRateLimit,
pub userinfo: OAuthProviderRateLimit,
}
impl Default for OAuthProviderRateLimits {
fn default() -> Self {
Self {
token: OAuthProviderRateLimit::Default,
authorize: OAuthProviderRateLimit::Default,
introspect: OAuthProviderRateLimit::Default,
revoke: OAuthProviderRateLimit::Default,
register: OAuthProviderRateLimit::Default,
userinfo: OAuthProviderRateLimit::Default,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize)]
pub struct McpMetadataOverrides {
#[serde(default, skip_serializing_if = "Map::is_empty")]
pub authorization_server: Map<String, Value>,
#[serde(default, skip_serializing_if = "Map::is_empty")]
pub protected_resource: Map<String, Value>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct McpOptions {
pub resource: Option<String>,
pub metadata: McpMetadataOverrides,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub struct ResolvedMcpOptions {
pub resource: Option<String>,
pub metadata: McpMetadataOverrides,
}
#[derive(Clone)]
pub struct OAuthProviderOptions {
pub scopes: Vec<String>,
pub client_registration_default_scopes: Vec<String>,
pub client_registration_allowed_scopes: Vec<String>,
pub grant_types: Vec<GrantType>,
pub login_page: String,
pub consent_page: String,
pub signup_page: Option<String>,
pub select_account_page: Option<String>,
pub post_login_page: Option<String>,
pub signup_redirect: Option<PromptRedirectResolver>,
pub select_account_redirect: Option<PromptRedirectResolver>,
pub post_login_redirect: Option<PromptRedirectResolver>,
pub signup_should_redirect: Option<PromptShouldRedirectResolver>,
pub select_account_should_redirect: Option<PromptShouldRedirectResolver>,
pub post_login_should_redirect: Option<PromptShouldRedirectResolver>,
pub consent_reference_id: Option<ClientReferenceResolver>,
pub code_expires_in: u64,
pub access_token_expires_in: u64,
pub m2m_access_token_expires_in: u64,
pub id_token_expires_in: u64,
pub refresh_token_expires_in: u64,
pub client_credential_grant_default_scopes: Vec<String>,
pub scope_expirations: BTreeMap<String, u64>,
pub client_registration_client_secret_expiration: Option<u64>,
pub allow_unauthenticated_client_registration: bool,
pub allow_dynamic_client_registration: bool,
pub allow_public_client_prelogin: bool,
pub cached_trusted_clients: BTreeSet<String>,
pub client_reference: Option<ClientReferenceResolver>,
pub client_privileges: Option<ClientPrivilegesResolver>,
pub custom_access_token_claims: Option<CustomAccessTokenClaimsResolver>,
pub custom_id_token_claims: Option<CustomIdTokenClaimsResolver>,
pub custom_token_response_fields: Option<CustomTokenResponseFieldsResolver>,
pub custom_userinfo_claims: Option<CustomUserInfoClaimsResolver>,
pub request_uri_resolver: Option<RequestUriResolver>,
pub prefixes: OAuthTokenPrefixes,
pub generate_client_id: Option<StringGeneratorResolver>,
pub generate_client_secret: Option<StringGeneratorResolver>,
pub generate_opaque_access_token: Option<StringGeneratorResolver>,
pub generate_refresh_token: Option<StringGeneratorResolver>,
pub format_refresh_token: Option<RefreshTokenFormatter>,
pub disable_jwt_plugin: bool,
pub store_client_secret: SecretStorage,
pub store_tokens: SecretStorage,
pub hash_client_secret: Option<ClientSecretHashResolver>,
pub verify_client_secret_hash: Option<ClientSecretVerifyResolver>,
pub hash_token: Option<TokenHashResolver>,
pub pairwise_secret: Option<String>,
pub advertised_scopes_supported: Vec<String>,
pub advertised_claims_supported: Vec<String>,
pub advertised_jwks_uri: Option<String>,
pub advertised_id_token_signing_algorithms: Vec<String>,
pub jwks_path: String,
pub valid_audiences: Vec<String>,
pub rate_limits: OAuthProviderRateLimits,
pub mcp: Option<McpOptions>,
}
impl Default for OAuthProviderOptions {
fn default() -> Self {
Self {
scopes: Vec::new(),
client_registration_default_scopes: Vec::new(),
client_registration_allowed_scopes: Vec::new(),
grant_types: Vec::new(),
login_page: String::new(),
consent_page: String::new(),
signup_page: None,
select_account_page: None,
post_login_page: None,
signup_redirect: None,
select_account_redirect: None,
post_login_redirect: None,
signup_should_redirect: None,
select_account_should_redirect: None,
post_login_should_redirect: None,
consent_reference_id: None,
code_expires_in: 600,
access_token_expires_in: 3600,
m2m_access_token_expires_in: 3600,
id_token_expires_in: 36000,
refresh_token_expires_in: 2_592_000,
client_credential_grant_default_scopes: Vec::new(),
scope_expirations: BTreeMap::new(),
client_registration_client_secret_expiration: None,
allow_unauthenticated_client_registration: false,
allow_dynamic_client_registration: false,
allow_public_client_prelogin: false,
cached_trusted_clients: BTreeSet::new(),
client_reference: None,
client_privileges: None,
custom_access_token_claims: None,
custom_id_token_claims: None,
custom_token_response_fields: None,
custom_userinfo_claims: None,
request_uri_resolver: None,
prefixes: OAuthTokenPrefixes::default(),
generate_client_id: None,
generate_client_secret: None,
generate_opaque_access_token: None,
generate_refresh_token: None,
format_refresh_token: None,
disable_jwt_plugin: false,
store_client_secret: SecretStorage::Auto,
store_tokens: SecretStorage::Hashed,
hash_client_secret: None,
verify_client_secret_hash: None,
hash_token: None,
pairwise_secret: None,
advertised_scopes_supported: Vec::new(),
advertised_claims_supported: Vec::new(),
advertised_jwks_uri: None,
advertised_id_token_signing_algorithms: Vec::new(),
jwks_path: "/jwks".to_owned(),
valid_audiences: Vec::new(),
rate_limits: OAuthProviderRateLimits::default(),
mcp: None,
}
}
}
impl OAuthProviderOptions {
#[must_use]
pub fn with_external_jwt(mut self) -> Self {
self.disable_jwt_plugin = true;
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedOAuthProviderOptions {
pub scopes: Vec<String>,
pub claims: Vec<String>,
pub client_registration_allowed_scopes: Vec<String>,
pub grant_types: Vec<GrantType>,
pub login_page: String,
pub consent_page: String,
pub signup_page: Option<String>,
pub select_account_page: Option<String>,
pub post_login_page: Option<String>,
pub signup_redirect: Option<PromptRedirectResolver>,
pub select_account_redirect: Option<PromptRedirectResolver>,
pub post_login_redirect: Option<PromptRedirectResolver>,
pub signup_should_redirect: Option<PromptShouldRedirectResolver>,
pub select_account_should_redirect: Option<PromptShouldRedirectResolver>,
pub post_login_should_redirect: Option<PromptShouldRedirectResolver>,
pub consent_reference_id: Option<ClientReferenceResolver>,
pub code_expires_in: u64,
pub access_token_expires_in: u64,
pub m2m_access_token_expires_in: u64,
pub id_token_expires_in: u64,
pub refresh_token_expires_in: u64,
pub client_credential_grant_default_scopes: Vec<String>,
pub scope_expirations: BTreeMap<String, u64>,
pub client_registration_default_scopes: Vec<String>,
pub client_registration_client_secret_expiration: Option<u64>,
pub allow_unauthenticated_client_registration: bool,
pub allow_dynamic_client_registration: bool,
pub allow_public_client_prelogin: bool,
pub cached_trusted_clients: BTreeSet<String>,
pub trusted_client_cache: TrustedClientCache,
pub client_reference: Option<ClientReferenceResolver>,
pub client_privileges: Option<ClientPrivilegesResolver>,
pub custom_access_token_claims: Option<CustomAccessTokenClaimsResolver>,
pub custom_id_token_claims: Option<CustomIdTokenClaimsResolver>,
pub custom_token_response_fields: Option<CustomTokenResponseFieldsResolver>,
pub custom_userinfo_claims: Option<CustomUserInfoClaimsResolver>,
pub request_uri_resolver: Option<RequestUriResolver>,
pub prefixes: OAuthTokenPrefixes,
pub generate_client_id: Option<StringGeneratorResolver>,
pub generate_client_secret: Option<StringGeneratorResolver>,
pub generate_opaque_access_token: Option<StringGeneratorResolver>,
pub generate_refresh_token: Option<StringGeneratorResolver>,
pub format_refresh_token: Option<RefreshTokenFormatter>,
pub disable_jwt_plugin: bool,
pub store_client_secret: SecretStorage,
pub store_tokens: SecretStorage,
pub hash_client_secret: Option<ClientSecretHashResolver>,
pub verify_client_secret_hash: Option<ClientSecretVerifyResolver>,
pub hash_token: Option<TokenHashResolver>,
pub pairwise_secret: Option<String>,
pub advertised_scopes_supported: Vec<String>,
pub advertised_claims_supported: Vec<String>,
pub advertised_jwks_uri: Option<String>,
pub advertised_id_token_signing_algorithms: Vec<String>,
pub jwks_path: String,
pub valid_audiences: Vec<String>,
pub rate_limits: OAuthProviderRateLimits,
pub mcp: Option<ResolvedMcpOptions>,
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum OAuthProviderConfigError {
#[error("login_page is required")]
MissingLoginPage,
#[error("consent_page is required")]
MissingConsentPage,
#[error("clientRegistrationAllowedScope {0} not found in scopes")]
UnknownClientRegistrationScope(String),
#[error("clientCredentialGrantDefaultScopes {0} not found in scopes")]
UnknownClientCredentialGrantScope(String),
#[error("advertisedMetadata.scopes_supported {0} not found in scopes")]
UnknownAdvertisedScope(String),
#[error(
"pairwiseSecret must be at least 32 characters long for adequate HMAC-SHA256 security"
)]
PairwiseSecretTooShort,
#[error("refresh_token grant requires authorization_code grant")]
RefreshTokenRequiresAuthorizationCode,
#[error("unable to store hashed secrets because id tokens will be signed with secret")]
HashedClientSecretsRequireJwtPlugin,
#[error("encryption method not recommended, please use 'hashed' or the 'hash' function")]
EncryptedClientSecretsWithJwtPlugin,
#[error("mcp.resource must be a valid absolute URL when set")]
InvalidMcpResource,
}