oidc authentication

This commit is contained in:
2025-08-09 00:31:35 +02:00
parent 2c9b44d215
commit 5f4201428a
34 changed files with 1766 additions and 84 deletions

View File

@@ -0,0 +1,4 @@
pub mod models;
pub mod ports;
pub mod requests;
pub mod service;

View File

@@ -0,0 +1,69 @@
use crate::domain::warren::models::user::{UserEmail, UserName};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UserInfo {
sub: String,
name: UserName,
email: UserEmail,
preferred_username: Option<UserName>,
picture: Option<String>,
locale: Option<String>,
updated_at: Option<i64>,
warren_admin: Option<bool>,
}
impl UserInfo {
pub fn new(
sub: String,
name: UserName,
email: UserEmail,
preferred_username: Option<UserName>,
picture: Option<String>,
locale: Option<String>,
updated_at: Option<i64>,
warren_admin: Option<bool>,
) -> Self {
Self {
sub,
name,
email,
preferred_username,
picture,
locale,
updated_at,
warren_admin,
}
}
pub fn sub(&self) -> &String {
&self.sub
}
pub fn name(&self) -> &UserName {
&self.name
}
pub fn email(&self) -> &UserEmail {
&self.email
}
pub fn preferred_username(&self) -> Option<&UserName> {
self.preferred_username.as_ref()
}
pub fn picture(&self) -> Option<&String> {
self.picture.as_ref()
}
pub fn locale(&self) -> Option<&String> {
self.locale.as_ref()
}
pub fn updated_at(&self) -> Option<i64> {
self.updated_at
}
pub fn warren_admin(&self) -> Option<bool> {
self.warren_admin
}
}

View File

@@ -0,0 +1,39 @@
use super::requests::{
GetRedirectError, GetRedirectRequest, GetRedirectResponse, GetUserInfoError,
GetUserInfoRequest, GetUserInfoResponse,
};
pub trait OidcService: Clone + Send + Sync + 'static {
fn get_redirect(
&self,
request: GetRedirectRequest,
) -> impl Future<Output = Result<GetRedirectResponse, GetRedirectError>> + Send;
fn get_user_info(
&self,
request: GetUserInfoRequest,
) -> impl Future<Output = Result<GetUserInfoResponse, GetUserInfoError>> + Send;
}
pub trait OidcRepository: Clone + Send + Sync + 'static {
fn get_redirect(
&self,
request: GetRedirectRequest,
) -> impl Future<Output = Result<GetRedirectResponse, GetRedirectError>> + Send;
fn get_user_info(
&self,
request: GetUserInfoRequest,
) -> impl Future<Output = Result<GetUserInfoResponse, GetUserInfoError>> + Send;
}
pub trait OidcMetrics: Clone + Send + Sync + 'static {
fn record_get_redirect_success(&self) -> impl Future<Output = ()> + Send;
fn record_get_redirect_failure(&self) -> impl Future<Output = ()> + Send;
fn record_get_user_info_success(&self) -> impl Future<Output = ()> + Send;
fn record_get_user_info_failure(&self) -> impl Future<Output = ()> + Send;
}
pub trait OidcNotifier: Clone + Send + Sync + 'static {
fn get_redirect(&self, response: &GetRedirectResponse) -> impl Future<Output = ()> + Send;
fn get_user_info(&self, response: &GetUserInfoResponse) -> impl Future<Output = ()> + Send;
}

View File

@@ -0,0 +1,74 @@
use thiserror::Error;
use super::models::UserInfo;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetRedirectRequest {}
impl GetRedirectRequest {
pub fn new() -> Self {
Self {}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetRedirectResponse {
url: String,
}
impl GetRedirectResponse {
pub fn new(url: String) -> Self {
Self { url }
}
pub fn url(&self) -> &String {
&self.url
}
}
#[derive(Debug, Error)]
pub enum GetRedirectError {
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetUserInfoRequest {
code: String,
state: Option<String>,
}
impl GetUserInfoRequest {
pub fn new(code: String, state: Option<String>) -> Self {
Self { code, state }
}
pub fn code(&self) -> &String {
&self.code
}
pub fn state(&self) -> Option<&String> {
self.state.as_ref()
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GetUserInfoResponse {
info: UserInfo,
}
impl GetUserInfoResponse {
pub fn new(info: UserInfo) -> Self {
Self { info }
}
pub fn info(&self) -> &UserInfo {
&self.info
}
}
#[derive(Debug, Error)]
pub enum GetUserInfoError {
#[error(transparent)]
Unknown(#[from] anyhow::Error),
}

View File

@@ -0,0 +1,73 @@
use super::{
ports::{OidcMetrics, OidcNotifier, OidcRepository, OidcService},
requests::{
GetRedirectError, GetRedirectRequest, GetRedirectResponse, GetUserInfoError,
GetUserInfoRequest, GetUserInfoResponse,
},
};
#[derive(Debug, Clone)]
pub struct Service<R, M, N>
where
R: OidcRepository,
M: OidcMetrics,
N: OidcNotifier,
{
repository: R,
metrics: M,
notifier: N,
}
impl<R, M, N> Service<R, M, N>
where
R: OidcRepository,
M: OidcMetrics,
N: OidcNotifier,
{
pub fn new(repository: R, metrics: M, notifier: N) -> Self {
Self {
repository,
metrics,
notifier,
}
}
}
impl<R, M, N> OidcService for Service<R, M, N>
where
R: OidcRepository,
M: OidcMetrics,
N: OidcNotifier,
{
async fn get_redirect(
&self,
request: GetRedirectRequest,
) -> Result<GetRedirectResponse, GetRedirectError> {
let result = self.repository.get_redirect(request).await;
if let Ok(response) = result.as_ref() {
self.metrics.record_get_redirect_success().await;
self.notifier.get_redirect(response).await;
} else {
self.metrics.record_get_redirect_failure().await;
}
result
}
async fn get_user_info(
&self,
request: GetUserInfoRequest,
) -> Result<GetUserInfoResponse, GetUserInfoError> {
let result = self.repository.get_user_info(request).await;
if let Ok(response) = result.as_ref() {
self.metrics.record_get_user_info_success().await;
self.notifier.get_user_info(response).await;
} else {
self.metrics.record_get_user_info_failure().await;
}
result
}
}