BLOG-94 Create user in DB when first login through OIDC (#96)
All checks were successful
Frontend CI / build (push) Successful in 1m8s
All checks were successful
Frontend CI / build (push) Successful in 1m8s
### Description This PR introduces the functionality to persist user information in the database. When a user logs in via OIDC for the first time, a new user record is created. Subsequent logins will retrieve the existing user data from the database. This change ensures that users have a persistent identity within our system, identified by their unique combination of OIDC issuer and subject ID. #### Key Changes * **User Persistence Logic**: * In `ExchangeAuthCodeUseCase`, after successfully exchanging the authorization code, the logic now checks if the user exists in our database using their `issuer` and `source_id`. * If the user is not found (`AuthError::UserNotFound`), a new record is created in the `user` table. * The `User` entity returned by the use case now contains the internal database `id`. * **Database Integration in Auth Feature**: * Introduced a new `UserDbService` trait and its `sqlx`-based implementation, `UserDbServiceImpl`, to handle database operations for users. * The `AuthRepository` is extended to include methods for querying (`get_user_by_source_id`) and saving (`save_user`) users, delegating the calls to the new `UserDbService`. * The dependency injection container in `server/src/container.rs` has been updated to provide the `UserDbServiceImpl` to the `AuthRepositoryImpl`. * **Domain and Data Model Updates**: * The `User` domain entity now includes `id` (the database primary key) and `issuer` (from OIDC claims) to uniquely identify a user across different identity providers. * The `UserResponseDto` now exposes the internal `id` instead of the `source_id`. * **Session Management**: * The user's session now stores the database `user_id` (`i32`) instead of the entire user object. This is more efficient and secure. * Session keys have been centralized into a `constants.rs` file for better maintainability. #### Database Changes * A new database migration has been added to create the `user` table. * The table includes columns for `id`, `issuer`, `source_id`, `displayed_name`, and `email`. * A **`UNIQUE` index** has been created on `(source_id, issuer)` to guarantee that each user from a specific identity provider is stored only once. #### Refactoring * Minor refactoring in the `image` feature to change `id: Option<i32>` to `id: i32` for consistency with the new `User` entity model. ### Package Changes _No response_ ### Screenshots _No response_ ### Reference Resolves #94 ### Checklist - [x] A milestone is set - [x] The related issuse has been linked to this branch Reviewed-on: #96 Co-authored-by: SquidSpirit <squid@squidspirit.com> Co-committed-by: SquidSpirit <squid@squidspirit.com>
This commit is contained in:
parent
dd0567c937
commit
9c88b4bb55
1
backend/Cargo.lock
generated
1
backend/Cargo.lock
generated
@ -428,6 +428,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"openidconnect",
|
"openidconnect",
|
||||||
"serde",
|
"serde",
|
||||||
|
"sqlx",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -10,3 +10,4 @@ async-trait.workspace = true
|
|||||||
log.workspace = true
|
log.workspace = true
|
||||||
openidconnect.workspace = true
|
openidconnect.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
sqlx.workspace = true
|
||||||
|
@ -4,7 +4,7 @@ use crate::domain::entity::user::User;
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct UserResponseDto {
|
pub struct UserResponseDto {
|
||||||
pub source_id: String,
|
pub id: i32,
|
||||||
pub displayed_name: String,
|
pub displayed_name: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
}
|
}
|
||||||
@ -12,7 +12,7 @@ pub struct UserResponseDto {
|
|||||||
impl From<User> for UserResponseDto {
|
impl From<User> for UserResponseDto {
|
||||||
fn from(user: User) -> Self {
|
fn from(user: User) -> Self {
|
||||||
UserResponseDto {
|
UserResponseDto {
|
||||||
source_id: user.source_id,
|
id: user.id,
|
||||||
displayed_name: user.displayed_name,
|
displayed_name: user.displayed_name,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
pub mod oidc_claims_response_dto;
|
|
||||||
pub mod auth_oidc_service;
|
pub mod auth_oidc_service;
|
||||||
pub mod auth_repository_impl;
|
pub mod auth_repository_impl;
|
||||||
|
pub mod oidc_claims_response_dto;
|
||||||
|
pub mod user_db_service;
|
||||||
|
pub mod user_db_mapper;
|
||||||
|
@ -3,7 +3,9 @@ use std::sync::Arc;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
adapter::gateway::auth_oidc_service::AuthOidcService,
|
adapter::gateway::{
|
||||||
|
auth_oidc_service::AuthOidcService, user_db_service::UserDbService, user_db_mapper::UserMapper,
|
||||||
|
},
|
||||||
application::{
|
application::{
|
||||||
error::auth_error::AuthError, gateway::auth_repository::AuthRepository,
|
error::auth_error::AuthError, gateway::auth_repository::AuthRepository,
|
||||||
use_case::get_auth_url_use_case::AuthUrl,
|
use_case::get_auth_url_use_case::AuthUrl,
|
||||||
@ -12,12 +14,19 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct AuthRepositoryImpl {
|
pub struct AuthRepositoryImpl {
|
||||||
|
user_db_service: Arc<dyn UserDbService>,
|
||||||
auth_oidc_service: Arc<dyn AuthOidcService>,
|
auth_oidc_service: Arc<dyn AuthOidcService>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AuthRepositoryImpl {
|
impl AuthRepositoryImpl {
|
||||||
pub fn new(auth_oidc_service: Arc<dyn AuthOidcService>) -> Self {
|
pub fn new(
|
||||||
Self { auth_oidc_service }
|
user_db_service: Arc<dyn UserDbService>,
|
||||||
|
auth_oidc_service: Arc<dyn AuthOidcService>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
user_db_service,
|
||||||
|
auth_oidc_service,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,4 +46,21 @@ impl AuthRepository for AuthRepositoryImpl {
|
|||||||
.await
|
.await
|
||||||
.map(|dto| dto.into_entity())
|
.map(|dto| dto.into_entity())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_user_by_source_id(
|
||||||
|
&self,
|
||||||
|
issuer: &str,
|
||||||
|
source_id: &str,
|
||||||
|
) -> Result<User, AuthError> {
|
||||||
|
self.user_db_service
|
||||||
|
.get_user_by_source_id(issuer, source_id)
|
||||||
|
.await
|
||||||
|
.map(|mapper| mapper.into_entity())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_user(&self, user: User) -> Result<i32, AuthError> {
|
||||||
|
self.user_db_service
|
||||||
|
.create_user(UserMapper::from(user))
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ use crate::domain::entity::user::User;
|
|||||||
|
|
||||||
pub struct OidcClaimsResponseDto {
|
pub struct OidcClaimsResponseDto {
|
||||||
pub sub: String,
|
pub sub: String,
|
||||||
|
pub issuer: String,
|
||||||
pub preferred_username: Option<String>,
|
pub preferred_username: Option<String>,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
}
|
}
|
||||||
@ -9,6 +10,8 @@ pub struct OidcClaimsResponseDto {
|
|||||||
impl OidcClaimsResponseDto {
|
impl OidcClaimsResponseDto {
|
||||||
pub fn into_entity(self) -> User {
|
pub fn into_entity(self) -> User {
|
||||||
User {
|
User {
|
||||||
|
id: -1,
|
||||||
|
issuer: self.issuer,
|
||||||
source_id: self.sub,
|
source_id: self.sub,
|
||||||
displayed_name: self.preferred_username.unwrap_or_default(),
|
displayed_name: self.preferred_username.unwrap_or_default(),
|
||||||
email: self.email.unwrap_or_default(),
|
email: self.email.unwrap_or_default(),
|
||||||
|
33
backend/feature/auth/src/adapter/gateway/user_db_mapper.rs
Normal file
33
backend/feature/auth/src/adapter/gateway/user_db_mapper.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use crate::domain::entity::user::User;
|
||||||
|
|
||||||
|
pub struct UserMapper {
|
||||||
|
pub id: i32,
|
||||||
|
pub issuer: String,
|
||||||
|
pub source_id: String,
|
||||||
|
pub displayed_name: String,
|
||||||
|
pub email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<User> for UserMapper {
|
||||||
|
fn from(user: User) -> Self {
|
||||||
|
Self {
|
||||||
|
id: user.id,
|
||||||
|
issuer: user.issuer,
|
||||||
|
source_id: user.source_id,
|
||||||
|
displayed_name: user.displayed_name,
|
||||||
|
email: user.email,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserMapper {
|
||||||
|
pub fn into_entity(self) -> User {
|
||||||
|
User {
|
||||||
|
id: self.id,
|
||||||
|
issuer: self.issuer,
|
||||||
|
source_id: self.source_id,
|
||||||
|
displayed_name: self.displayed_name,
|
||||||
|
email: self.email,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
backend/feature/auth/src/adapter/gateway/user_db_service.rs
Normal file
14
backend/feature/auth/src/adapter/gateway/user_db_service.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{adapter::gateway::user_db_mapper::UserMapper, application::error::auth_error::AuthError};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait UserDbService: Send + Sync {
|
||||||
|
async fn get_user_by_source_id(
|
||||||
|
&self,
|
||||||
|
issuer: &str,
|
||||||
|
source_id: &str,
|
||||||
|
) -> Result<UserMapper, AuthError>;
|
||||||
|
|
||||||
|
async fn create_user(&self, user: UserMapper) -> Result<i32, AuthError>;
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum AuthError {
|
pub enum AuthError {
|
||||||
|
DatabaseError(String),
|
||||||
OidcError(String),
|
OidcError(String),
|
||||||
InvalidState,
|
InvalidState,
|
||||||
InvalidNonce,
|
InvalidNonce,
|
||||||
InvalidAuthCode,
|
InvalidAuthCode,
|
||||||
InvalidIdToken,
|
InvalidIdToken,
|
||||||
|
UserNotFound,
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,12 @@ use crate::{
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait AuthRepository: Send + Sync {
|
pub trait AuthRepository: Send + Sync {
|
||||||
fn get_auth_url(&self) -> Result<AuthUrl, AuthError>;
|
fn get_auth_url(&self) -> Result<AuthUrl, AuthError>;
|
||||||
|
|
||||||
async fn exchange_auth_code(&self, code: &str, expected_nonce: &str)
|
async fn exchange_auth_code(&self, code: &str, expected_nonce: &str)
|
||||||
-> Result<User, AuthError>;
|
-> Result<User, AuthError>;
|
||||||
|
|
||||||
|
async fn get_user_by_source_id(&self, issuer: &str, source_id: &str)
|
||||||
|
-> Result<User, AuthError>;
|
||||||
|
|
||||||
|
async fn save_user(&self, user: User) -> Result<i32, AuthError>;
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,32 @@ impl ExchangeAuthCodeUseCase for ExchangeAuthCodeUseCaseImpl {
|
|||||||
return Err(AuthError::InvalidState);
|
return Err(AuthError::InvalidState);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.auth_repository
|
let mut logged_in_user = self
|
||||||
|
.auth_repository
|
||||||
.exchange_auth_code(code, expected_nonce)
|
.exchange_auth_code(code, expected_nonce)
|
||||||
.await
|
.await?;
|
||||||
|
|
||||||
|
let saved_user_result = self
|
||||||
|
.auth_repository
|
||||||
|
.get_user_by_source_id(&logged_in_user.issuer, &logged_in_user.source_id)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match saved_user_result {
|
||||||
|
Ok(user) => {
|
||||||
|
logged_in_user.id = user.id;
|
||||||
|
}
|
||||||
|
Err(AuthError::UserNotFound) => {
|
||||||
|
let id = self
|
||||||
|
.auth_repository
|
||||||
|
.save_user(logged_in_user.clone())
|
||||||
|
.await?;
|
||||||
|
logged_in_user.id = id;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(logged_in_user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
#[derive(Clone)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
|
pub id: i32,
|
||||||
|
pub issuer: String,
|
||||||
pub source_id: String,
|
pub source_id: String,
|
||||||
pub displayed_name: String,
|
pub displayed_name: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
|
pub mod db;
|
||||||
pub mod oidc;
|
pub mod oidc;
|
||||||
pub mod web;
|
pub mod web;
|
||||||
|
2
backend/feature/auth/src/framework/db.rs
Normal file
2
backend/feature/auth/src/framework/db.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod user_db_service_impl;
|
||||||
|
pub mod user_record;
|
@ -0,0 +1,65 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
adapter::gateway::{user_db_service::UserDbService, user_db_mapper::UserMapper},
|
||||||
|
application::error::auth_error::AuthError,
|
||||||
|
framework::db::user_record::UserRecord,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UserDbServiceImpl {
|
||||||
|
db_pool: Pool<Postgres>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserDbServiceImpl {
|
||||||
|
pub fn new(db_pool: Pool<Postgres>) -> Self {
|
||||||
|
Self { db_pool }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl UserDbService for UserDbServiceImpl {
|
||||||
|
async fn get_user_by_source_id(
|
||||||
|
&self,
|
||||||
|
issuer: &str,
|
||||||
|
source_id: &str,
|
||||||
|
) -> Result<UserMapper, AuthError> {
|
||||||
|
let record = sqlx::query_as!(
|
||||||
|
UserRecord,
|
||||||
|
r#"
|
||||||
|
SELECT id, issuer, source_id, displayed_name, email
|
||||||
|
FROM "user"
|
||||||
|
WHERE issuer = $1 AND source_id = $2
|
||||||
|
"#,
|
||||||
|
issuer,
|
||||||
|
source_id
|
||||||
|
)
|
||||||
|
.fetch_optional(&self.db_pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| AuthError::DatabaseError(e.to_string()))?;
|
||||||
|
|
||||||
|
match record {
|
||||||
|
Some(record) => Ok(record.into_mapper()),
|
||||||
|
None => Err(AuthError::UserNotFound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_user(&self, user: UserMapper) -> Result<i32, AuthError> {
|
||||||
|
let id = sqlx::query_scalar!(
|
||||||
|
r#"
|
||||||
|
INSERT INTO "user" (issuer, source_id, displayed_name, email)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
RETURNING id
|
||||||
|
"#,
|
||||||
|
user.issuer,
|
||||||
|
user.source_id,
|
||||||
|
user.displayed_name,
|
||||||
|
user.email
|
||||||
|
)
|
||||||
|
.fetch_one(&self.db_pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| AuthError::DatabaseError(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
}
|
24
backend/feature/auth/src/framework/db/user_record.rs
Normal file
24
backend/feature/auth/src/framework/db/user_record.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use sqlx::FromRow;
|
||||||
|
|
||||||
|
use crate::adapter::gateway::user_db_mapper::UserMapper;
|
||||||
|
|
||||||
|
#[derive(FromRow)]
|
||||||
|
pub struct UserRecord {
|
||||||
|
pub id: i32,
|
||||||
|
pub issuer: String,
|
||||||
|
pub source_id: String,
|
||||||
|
pub displayed_name: String,
|
||||||
|
pub email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserRecord {
|
||||||
|
pub fn into_mapper(self) -> UserMapper {
|
||||||
|
UserMapper {
|
||||||
|
id: self.id,
|
||||||
|
issuer: self.issuer,
|
||||||
|
source_id: self.source_id,
|
||||||
|
displayed_name: self.displayed_name,
|
||||||
|
email: self.email,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -93,6 +93,8 @@ impl AuthOidcService for AuthOidcServiceImpl {
|
|||||||
)
|
)
|
||||||
.map_err(|_| AuthError::InvalidIdToken)?;
|
.map_err(|_| AuthError::InvalidIdToken)?;
|
||||||
|
|
||||||
|
let issuer = claims.issuer().to_string();
|
||||||
|
|
||||||
let preferred_username = claims
|
let preferred_username = claims
|
||||||
.preferred_username()
|
.preferred_username()
|
||||||
.map(|username| username.to_string());
|
.map(|username| username.to_string());
|
||||||
@ -101,6 +103,7 @@ impl AuthOidcService for AuthOidcServiceImpl {
|
|||||||
|
|
||||||
Ok(OidcClaimsResponseDto {
|
Ok(OidcClaimsResponseDto {
|
||||||
sub: claims.subject().to_string(),
|
sub: claims.subject().to_string(),
|
||||||
|
issuer: issuer,
|
||||||
preferred_username: preferred_username,
|
preferred_username: preferred_username,
|
||||||
email: email,
|
email: email,
|
||||||
})
|
})
|
||||||
|
@ -1 +1,3 @@
|
|||||||
pub mod auth_web_routes;
|
pub mod auth_web_routes;
|
||||||
|
|
||||||
|
mod constants;
|
||||||
|
@ -6,12 +6,11 @@ use crate::{
|
|||||||
auth_controller::AuthController, oidc_callback_query_dto::OidcCallbackQueryDto,
|
auth_controller::AuthController, oidc_callback_query_dto::OidcCallbackQueryDto,
|
||||||
},
|
},
|
||||||
application::error::auth_error::AuthError,
|
application::error::auth_error::AuthError,
|
||||||
|
framework::web::constants::{
|
||||||
|
SESSION_KEY_AUTH_NONCE, SESSION_KEY_AUTH_STATE, SESSION_KEY_USER_ID,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const SESSION_KEY_AUTH_STATE: &str = "auth_state";
|
|
||||||
const SESSION_KEY_AUTH_NONCE: &str = "auth_nonce";
|
|
||||||
const SESSION_KEY_USER: &str = "user";
|
|
||||||
|
|
||||||
pub fn configure_auth_routes(cfg: &mut web::ServiceConfig) {
|
pub fn configure_auth_routes(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(
|
||||||
web::scope("/auth")
|
web::scope("/auth")
|
||||||
@ -29,11 +28,11 @@ async fn oidc_login_handler(
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(auth_url) => {
|
Ok(auth_url) => {
|
||||||
if let Err(e) = session.insert(SESSION_KEY_AUTH_STATE, auth_url.state) {
|
if let Err(e) = session.insert::<String>(SESSION_KEY_AUTH_STATE, auth_url.state) {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
return HttpResponse::InternalServerError().finish();
|
return HttpResponse::InternalServerError().finish();
|
||||||
}
|
}
|
||||||
if let Err(e) = session.insert(SESSION_KEY_AUTH_NONCE, auth_url.nonce) {
|
if let Err(e) = session.insert::<String>(SESSION_KEY_AUTH_NONCE, auth_url.nonce) {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
return HttpResponse::InternalServerError().finish();
|
return HttpResponse::InternalServerError().finish();
|
||||||
}
|
}
|
||||||
@ -53,12 +52,12 @@ async fn oidc_callback_handler(
|
|||||||
query: web::Query<OidcCallbackQueryDto>,
|
query: web::Query<OidcCallbackQueryDto>,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let expected_state: String = match session.get(SESSION_KEY_AUTH_STATE) {
|
let expected_state = match session.get::<String>(SESSION_KEY_AUTH_STATE) {
|
||||||
Ok(Some(state)) => state,
|
Ok(Some(state)) => state,
|
||||||
_ => return HttpResponse::BadRequest().finish(),
|
_ => return HttpResponse::BadRequest().finish(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected_nonce: String = match session.get(SESSION_KEY_AUTH_NONCE) {
|
let expected_nonce = match session.get::<String>(SESSION_KEY_AUTH_NONCE) {
|
||||||
Ok(Some(nonce)) => nonce,
|
Ok(Some(nonce)) => nonce,
|
||||||
_ => return HttpResponse::BadRequest().finish(),
|
_ => return HttpResponse::BadRequest().finish(),
|
||||||
};
|
};
|
||||||
@ -71,7 +70,7 @@ async fn oidc_callback_handler(
|
|||||||
session.remove(SESSION_KEY_AUTH_NONCE);
|
session.remove(SESSION_KEY_AUTH_NONCE);
|
||||||
match result {
|
match result {
|
||||||
Ok(user) => {
|
Ok(user) => {
|
||||||
if let Err(e) = session.insert(SESSION_KEY_USER, user) {
|
if let Err(e) = session.insert::<i32>(SESSION_KEY_USER_ID, user.id) {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
return HttpResponse::InternalServerError().finish();
|
return HttpResponse::InternalServerError().finish();
|
||||||
}
|
}
|
||||||
@ -95,7 +94,7 @@ async fn oidc_callback_handler(
|
|||||||
async fn logout_handler(session: Session) -> impl Responder {
|
async fn logout_handler(session: Session) -> impl Responder {
|
||||||
session.remove(SESSION_KEY_AUTH_STATE);
|
session.remove(SESSION_KEY_AUTH_STATE);
|
||||||
session.remove(SESSION_KEY_AUTH_NONCE);
|
session.remove(SESSION_KEY_AUTH_NONCE);
|
||||||
session.remove(SESSION_KEY_USER);
|
session.remove(SESSION_KEY_USER_ID);
|
||||||
HttpResponse::Found()
|
HttpResponse::Found()
|
||||||
.append_header((header::LOCATION, "/"))
|
.append_header((header::LOCATION, "/"))
|
||||||
.finish()
|
.finish()
|
||||||
|
3
backend/feature/auth/src/framework/web/constants.rs
Normal file
3
backend/feature/auth/src/framework/web/constants.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub const SESSION_KEY_AUTH_STATE: &str = "auth_state";
|
||||||
|
pub const SESSION_KEY_AUTH_NONCE: &str = "auth_nonce";
|
||||||
|
pub const SESSION_KEY_USER_ID: &str = "user_id";
|
@ -8,7 +8,7 @@ pub struct ImageRequestDto {
|
|||||||
impl ImageRequestDto {
|
impl ImageRequestDto {
|
||||||
pub fn into_entity(self) -> Image {
|
pub fn into_entity(self) -> Image {
|
||||||
Image {
|
Image {
|
||||||
id: None,
|
id: -1,
|
||||||
mime_type: self.mime_type,
|
mime_type: self.mime_type,
|
||||||
data: self.data,
|
data: self.data,
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::domain::entity::image::Image;
|
use crate::domain::entity::image::Image;
|
||||||
|
|
||||||
pub struct ImageDbMapper {
|
pub struct ImageDbMapper {
|
||||||
pub id: Option<i32>,
|
pub id: i32,
|
||||||
pub mime_type: String,
|
pub mime_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
pub struct Image {
|
pub struct Image {
|
||||||
pub id: Option<i32>,
|
pub id: i32,
|
||||||
pub mime_type: String,
|
pub mime_type: String,
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ impl ImageDbService for ImageDbServiceImpl {
|
|||||||
match image_record {
|
match image_record {
|
||||||
Ok(record) => match record {
|
Ok(record) => match record {
|
||||||
Some(record) => Ok(ImageDbMapper {
|
Some(record) => Ok(ImageDbMapper {
|
||||||
id: Some(record.id),
|
id: record.id,
|
||||||
mime_type: record.mime_type,
|
mime_type: record.mime_type,
|
||||||
}),
|
}),
|
||||||
None => Err(ImageError::NotFound),
|
None => Err(ImageError::NotFound),
|
||||||
|
@ -6,7 +6,25 @@ CREATE TABLE "image" (
|
|||||||
"updated_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
"updated_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "user" (
|
||||||
|
"id" SERIAL PRIMARY KEY NOT NULL,
|
||||||
|
"issuer" VARCHAR(100) NOT NULL,
|
||||||
|
"source_id" VARCHAR(100) NOT NULL,
|
||||||
|
"displayed_name" VARCHAR(100) NOT NULL,
|
||||||
|
"email" VARCHAR(100) NOT NULL,
|
||||||
|
"created_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "user_source_id_issuer_key" ON "user" ("source_id", "issuer");
|
||||||
|
CREATE INDEX "user_email_key" ON "user" HASH ("email");
|
||||||
|
|
||||||
CREATE TRIGGER "update_image_updated_time"
|
CREATE TRIGGER "update_image_updated_time"
|
||||||
BEFORE UPDATE ON "image"
|
BEFORE UPDATE ON "image"
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION update_updated_time_column();
|
EXECUTE FUNCTION update_updated_time_column();
|
||||||
|
|
||||||
|
CREATE TRIGGER "update_user_updated_time"
|
||||||
|
BEFORE UPDATE ON "user"
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_updated_time_column();
|
||||||
|
@ -9,7 +9,10 @@ use auth::{
|
|||||||
exchange_auth_code_use_case::ExchangeAuthCodeUseCaseImpl,
|
exchange_auth_code_use_case::ExchangeAuthCodeUseCaseImpl,
|
||||||
get_auth_url_use_case::LoginUseCaseImpl,
|
get_auth_url_use_case::LoginUseCaseImpl,
|
||||||
},
|
},
|
||||||
framework::oidc::auth_oidc_service_impl::AuthOidcServiceImpl,
|
framework::{
|
||||||
|
db::user_db_service_impl::UserDbServiceImpl,
|
||||||
|
oidc::auth_oidc_service_impl::AuthOidcServiceImpl,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use image::{
|
use image::{
|
||||||
adapter::{
|
adapter::{
|
||||||
@ -60,7 +63,8 @@ impl Container {
|
|||||||
oidc_configuration.redirect_url.clone(),
|
oidc_configuration.redirect_url.clone(),
|
||||||
http_client,
|
http_client,
|
||||||
));
|
));
|
||||||
let auth_repository = Arc::new(AuthRepositoryImpl::new(auth_oidc_service));
|
let user_db_service = Arc::new(UserDbServiceImpl::new(db_pool.clone()));
|
||||||
|
let auth_repository = Arc::new(AuthRepositoryImpl::new(user_db_service, auth_oidc_service));
|
||||||
let get_auth_url_use_case = Arc::new(LoginUseCaseImpl::new(auth_repository.clone()));
|
let get_auth_url_use_case = Arc::new(LoginUseCaseImpl::new(auth_repository.clone()));
|
||||||
let exchange_auth_code_use_case =
|
let exchange_auth_code_use_case =
|
||||||
Arc::new(ExchangeAuthCodeUseCaseImpl::new(auth_repository.clone()));
|
Arc::new(ExchangeAuthCodeUseCaseImpl::new(auth_repository.clone()));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user