#![warn(missing_docs)]
use crate::auth::{error::AuthError, state::Identity, CredentialsProvider, UserMapper};
pub use crate::auth::{ErasedOAuthFlow, Session, SessionConfig, SessionStore};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
pub use chrono;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FlowContext {
pub state: String,
pub params: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FlowResult {
Complete(Identity),
Redirect(String),
Pending,
}
#[async_trait]
pub trait Flow: Send + Sync {
fn id(&self) -> &str;
async fn execute(&self, ctx: FlowContext) -> Result<FlowResult, AuthError>;
}
use std::collections::HashMap;
pub use crate::engine::{Configured, Engine, EngineBuilder, Missing};
pub mod client_credentials_flow;
pub mod device_flow;
pub mod oauth2;
pub use client_credentials_flow::ClientCredentialsFlow;
pub use device_flow::{DeviceAuthorizationResponse, DeviceFlow};
pub use oauth2::OAuth2Flow;
#[non_exhaustive]
pub struct CredentialsFlow<P: CredentialsProvider, M: UserMapper = ()> {
provider: P,
mapper: Option<M>,
}
impl<P: CredentialsProvider> CredentialsFlow<P, ()> {
pub fn new(provider: P) -> Self {
Self {
provider,
mapper: None,
}
}
}
impl<P: CredentialsProvider, M: UserMapper> CredentialsFlow<P, M> {
pub fn with_mapper(provider: P, mapper: M) -> Self {
Self {
provider,
mapper: Some(mapper),
}
}
pub async fn authenticate(
&self,
creds: P::Credentials,
) -> Result<(Identity, Option<M::LocalUser>), AuthError> {
let identity = self.provider.authenticate(creds).await?;
let local_user = if let Some(mapper) = &self.mapper {
Some(mapper.map_user(&identity).await?)
} else {
None
};
Ok((identity, local_user))
}
}
#[cfg(test)]
mod tests {
use super::*;
use async_trait::async_trait;
#[derive(Debug, PartialEq, Clone)]
struct DummyCreds(String);
struct DummyProvider;
#[async_trait]
impl CredentialsProvider for DummyProvider {
type Credentials = DummyCreds;
async fn authenticate(&self, creds: Self::Credentials) -> Result<Identity, AuthError> {
if creds.0 == "valid" {
Ok(Identity {
provider_id: "dummy".to_string(),
external_id: "user_123".to_string(),
email: None,
username: None,
attributes: std::collections::HashMap::new(),
})
} else {
Err(AuthError::InvalidCredentials)
}
}
}
#[derive(Debug, PartialEq)]
struct DummyUser(String);
struct DummyMapper;
#[async_trait]
impl UserMapper for DummyMapper {
type LocalUser = DummyUser;
async fn map_user(&self, identity: &Identity) -> Result<Self::LocalUser, AuthError> {
Ok(DummyUser(identity.external_id.clone()))
}
}
#[tokio::test]
async fn test_credentials_flow() {
let flow = CredentialsFlow::new(DummyProvider);
let res = flow
.authenticate(DummyCreds("valid".to_string()))
.await
.unwrap();
assert_eq!(res.0.external_id, "user_123");
assert!(res.1.is_none());
let err = flow.authenticate(DummyCreds("invalid".to_string())).await;
assert!(err.is_err());
}
#[tokio::test]
async fn test_credentials_flow_with_mapper() {
let flow = CredentialsFlow::with_mapper(DummyProvider, DummyMapper);
let res = flow
.authenticate(DummyCreds("valid".to_string()))
.await
.unwrap();
assert_eq!(res.0.external_id, "user_123");
assert_eq!(res.1.unwrap(), DummyUser("user_123".to_string()));
}
}