avam-client and oauth2

This commit is contained in:
2024-10-17 00:56:02 +02:00
parent bfc5cbf624
commit f93eb3c429
50 changed files with 5674 additions and 277 deletions

View File

@@ -4,17 +4,22 @@
*/
use axum_session::SessionAnySession;
use crate::inbound::http::handlers::oauth::AuthorizationCodeRequest;
use crate::inbound::http::handlers::oauth::GrantType;
use super::models::oauth::Client;
use super::models::oauth::*;
use super::models::user::*;
use super::ports::{ApiService, UserNotifier, UserRepository};
use super::ports::{ApiService, OAuthRepository, UserNotifier, UserRepository};
pub trait Repository = UserRepository;
// pub trait Repository = UserRepository + OAuthRepository;
pub trait Email = UserNotifier;
#[derive(Debug, Clone)]
pub struct Service<R, N>
where
R: Repository,
R: UserRepository + OAuthRepository,
N: Email,
{
repo: R,
@@ -23,7 +28,7 @@ where
impl<R, N> Service<R, N>
where
R: Repository,
R: UserRepository + OAuthRepository,
N: Email,
{
pub fn new(repo: R, notifier: N) -> Self {
@@ -33,12 +38,13 @@ where
impl<R, N> ApiService for Service<R, N>
where
R: UserRepository,
R: UserRepository + OAuthRepository,
N: Email,
{
async fn create_user(&self, req: CreateUserRequest) -> Result<User, CreateUserError> {
let result = self.repo.create_user(req).await;
#[allow(clippy::question_mark)]
if result.is_err() {
// something went wrong, log the error
// but keep passing on the result to the requester (http server)
@@ -56,9 +62,7 @@ where
}
async fn get_user_session(&self, session: &SessionAnySession) -> Option<User> {
let Some(user_id) = session.get("user") else {
return None;
};
let user_id = session.get("user")?;
self.repo.find_user_by_id(user_id).await.unwrap_or(None)
}
@@ -69,7 +73,7 @@ where
) -> Result<User, ActivateUserError> {
let user = match self.repo.find_user_by_activation_token(&token).await {
Ok(u) => u,
Err(e) => return Err(ActivateUserError::Unknown(e.into())),
Err(e) => return Err(ActivateUserError::Unknown(e)),
};
let Some(user) = user else {
@@ -98,7 +102,7 @@ where
Ok(u) => u,
Err(e) => {
tracing::error!("{:#?}", e);
return Err(UserLoginError::Unknown(e.into()));
return Err(UserLoginError::Unknown(e));
}
};
@@ -162,7 +166,7 @@ where
Ok(u) => u,
Err(e) => {
tracing::error!("{:#?}", e);
return Err(ResetPasswordError::Unknown(e.into()));
return Err(ResetPasswordError::Unknown(e));
}
};
@@ -190,4 +194,77 @@ where
.await
.unwrap_or(None)
}
async fn find_client_by_id(&self, id: uuid::Uuid) -> Option<Client> {
self.repo.find_client_by_id(id).await.ok().flatten()
}
async fn generate_authorization_code(
&self,
user: &User,
req: AuthorizeRequest,
) -> Result<AuthorizationResponse, anyhow::Error> {
let Some(client) = self.repo.find_client_by_id(req.client_id()).await? else {
return Err(anyhow::anyhow!("Client not found"));
};
if client.redirect_uri() != &req.redirect_uri() {
return Err(anyhow::anyhow!("Invalid redirect uri"));
}
let code = self
.repo
.create_authorization_code(
user.id(),
client.id(),
req.code_challenge(),
req.code_challenge_method().unwrap_or_default(),
)
.await?;
Ok(AuthorizationResponse::new(code, req.state()))
}
async fn create_token(
&self,
req: AuthorizationCodeRequest,
) -> Result<Option<TokenSubject>, TokenError> {
if req.grant_type() != GrantType::AuthorizationCode {
return Err(TokenError::InvalidRequest);
}
let code = req.code();
let Some(token) = self.repo.get_token_subject(code).await? else {
return Err(TokenError::InvalidRequest);
};
let code_verifier = req.code_verifier();
let code_challenge = match token.code_challenge_method() {
CodeChallengeMethod::Plain => {
use base64::prelude::*;
BASE64_URL_SAFE_NO_PAD.encode(code_verifier.to_string())
}
CodeChallengeMethod::Sha256 => {
use base64::prelude::*;
BASE64_URL_SAFE_NO_PAD.encode(sha256::digest(code_verifier.to_string()))
}
};
if token.code_challenge() != code_challenge {
return Err(TokenError::InvalidRequest);
}
let Some(client) = self.repo.find_client_by_id(token.client_id()).await? else {
return Err(TokenError::InvalidRequest); // no such client
};
if &req.redirect_uri() != client.redirect_uri() {
return Err(TokenError::InvalidRequest); // invalid redirect uri
}
let _ = self.repo.delete_token(req.code()).await;
Ok(Some(token))
}
}