mod middleware;
use crate::auth::{openid, pat, AuthError, UserInformation};
use ::openid::{Claims, CustomClaims};
use chrono::{DateTime, LocalResult, TimeZone, Utc};
pub use middleware::AuthenticatedUntil;
use tracing::instrument;
pub enum Credentials {
OpenIDToken(String),
AccessToken(UsernameAndToken),
Anonymous,
}
pub struct UsernameAndToken {
pub username: String,
pub access_token: Option<String>,
}
#[derive(Clone, Debug)]
pub enum AuthN {
Disabled,
Enabled {
openid: Option<openid::Authenticator>,
token: Option<pat::Authenticator>,
},
}
impl From<(Option<openid::Authenticator>, Option<pat::Authenticator>)> for AuthN {
fn from(auth: (Option<openid::Authenticator>, Option<pat::Authenticator>)) -> Self {
let (openid, token) = auth;
if openid.is_none() {
AuthN::Disabled
} else {
AuthN::Enabled { openid, token }
}
}
}
impl AuthN {
#[instrument(skip_all, err)]
async fn authenticate(
&self,
credentials: Credentials,
) -> Result<(UserInformation, Option<DateTime<Utc>>), AuthError> {
match self {
Self::Disabled => {
Ok((UserInformation::Anonymous, None))
}
Self::Enabled { openid, token } => match credentials {
Credentials::AccessToken(creds) => {
if let Some(token) = token {
if creds.access_token.is_none() {
log::debug!("Cannot authenticate : empty access token.");
return Err(AuthError::InvalidRequest(String::from(
"No access token provided.",
)));
}
let auth_response = token
.authenticate(pat::Request {
user_id: creds.username.clone(),
access_token: creds.access_token.clone().unwrap_or_default(),
})
.await
.map_err(|e| AuthError::Internal(e.to_string()))?;
match auth_response.outcome {
pat::Outcome::Known(details) => {
Ok((UserInformation::Authenticated(details), None))
}
pat::Outcome::Unknown => {
log::debug!("Unknown access token");
Err(AuthError::Forbidden)
}
}
} else {
log::debug!("Access token authentication disabled");
Err(AuthError::InvalidRequest(
"Access token authentication disabled".to_string(),
))
}
}
Credentials::OpenIDToken(token) => {
if let Some(openid) = openid {
match openid.validate_token(&token).await {
Ok(token) => Ok((
UserInformation::Authenticated(token.clone().into()),
Some(to_expiration(token.standard_claims().exp())?),
)),
Err(err) => {
log::debug!("Authentication error: {err}");
Err(AuthError::Forbidden)
}
}
} else {
log::debug!("Open ID authentication disabled");
Err(AuthError::InvalidRequest(
"Open ID authentication disabled".to_string(),
))
}
}
Credentials::Anonymous => Ok((UserInformation::Anonymous, None)),
},
}
}
}
fn to_expiration(exp: i64) -> Result<DateTime<Utc>, AuthError> {
match Utc.timestamp_opt(exp, 0) {
LocalResult::None => Err(AuthError::Internal(
"Unable to convert timestamp".to_string(),
)),
LocalResult::Single(exp) => Ok(exp),
LocalResult::Ambiguous(min, _) => Ok(min),
}
}