BLOG-85 Implement OIDC authentication #93

Merged
squid merged 8 commits from BLOG-85_oidc_login into main 2025-07-30 03:46:50 +08:00
32 changed files with 1670 additions and 35 deletions
Showing only changes of commit 32b3d47715 - Show all commits

1048
backend/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,13 +8,19 @@ edition = "2024"
[workspace.dependencies] [workspace.dependencies]
actix-multipart = "0.7.2" actix-multipart = "0.7.2"
actix-session = { version = "0.10.1", features = ["redis-session"] }
actix-web = "4.10.2" actix-web = "4.10.2"
async-trait = "0.1.88" async-trait = "0.1.88"
chrono = "0.4.41" chrono = "0.4.41"
dotenv = "0.15.0" dotenv = "0.15.0"
env_logger = "0.11.8" env_logger = "0.11.8"
futures = "0.3.31" futures = "0.3.31"
hex = "0.4.3"
log = "0.4.27" log = "0.4.27"
openidconnect = { version = "4.0.1", features = [
"reqwest",
"reqwest-blocking",
] }
percent-encoding = "2.3.1" percent-encoding = "2.3.1"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
sqlx = { version = "0.8.5", features = [ sqlx = { version = "0.8.5", features = [

View File

@ -4,9 +4,9 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dependencies] [dependencies]
actix-session.workspace = true
actix-web.workspace = true actix-web.workspace = true
async-trait.workspace = true async-trait.workspace = true
chrono.workspace = true
log.workspace = true log.workspace = true
openidconnect.workspace = true
serde.workspace = true serde.workspace = true
sqlx.workspace = true

View File

@ -0,0 +1,3 @@
pub mod auth_controller;
pub mod oidc_callback_query_dto;
pub mod user_response_dto;

View File

@ -0,0 +1,66 @@
use std::sync::Arc;
use async_trait::async_trait;
use crate::{
adapter::delivery::{
oidc_callback_query_dto::OidcCallbackQueryDto, user_response_dto::UserResponseDto,
},
application::{
error::auth_error::AuthError,
use_case::{
exchange_auth_code_use_case::ExchangeAuthCodeUseCase,
get_auth_url_use_case::{AuthUrl, GetAuthUrlUseCase},
},
},
};
#[async_trait]
pub trait AuthController: Send + Sync {
fn oidc_login(&self) -> Result<AuthUrl, AuthError>;
async fn oidc_callback(
&self,
query: OidcCallbackQueryDto,
expected_state: &str,
expected_nonce: &str,
) -> Result<UserResponseDto, AuthError>;
}
pub struct AuthControllerImpl {
get_auth_url_use_case: Arc<dyn GetAuthUrlUseCase>,
exchange_auth_code_use_case: Arc<dyn ExchangeAuthCodeUseCase>,
}
impl AuthControllerImpl {
pub fn new(
get_auth_url_use_case: Arc<dyn GetAuthUrlUseCase>,
exchange_auth_code_use_case: Arc<dyn ExchangeAuthCodeUseCase>,
) -> Self {
Self {
get_auth_url_use_case,
exchange_auth_code_use_case,
}
}
}
#[async_trait]
impl AuthController for AuthControllerImpl {
fn oidc_login(&self) -> Result<AuthUrl, AuthError> {
self.get_auth_url_use_case.execute()
}
async fn oidc_callback(
&self,
query: OidcCallbackQueryDto,
expected_state: &str,
expected_nonce: &str,
) -> Result<UserResponseDto, AuthError> {
let result = self
.exchange_auth_code_use_case
.execute(&query.code, &query.state, expected_state, expected_nonce)
.await;
result.map(|user| UserResponseDto::from(user))
}
}

View File

@ -0,0 +1,7 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct OidcCallbackQueryDto {
pub code: String,
pub state: String,
}

View File

@ -0,0 +1,20 @@
use serde::Serialize;
use crate::domain::entity::user::User;
#[derive(Serialize)]
pub struct UserResponseDto {
pub source_id: String,
pub displayed_name: String,
pub email: String,
}
impl From<User> for UserResponseDto {
fn from(user: User) -> Self {
UserResponseDto {
source_id: user.source_id,
displayed_name: user.displayed_name,
email: user.email,
}
}
}

View File

@ -0,0 +1,3 @@
pub mod oidc_claims_response_dto;
pub mod auth_oidc_service;
pub mod auth_repository_impl;

View File

@ -0,0 +1,16 @@
use async_trait::async_trait;
use crate::{
adapter::gateway::oidc_claims_response_dto::OidcClaimsResponseDto,
application::{error::auth_error::AuthError, use_case::get_auth_url_use_case::AuthUrl},
};
#[async_trait]
pub trait AuthOidcService: Send + Sync {
fn get_auth_url(&self) -> Result<AuthUrl, AuthError>;
async fn exchange_auth_code(
&self,
code: &str,
expected_nonce: &str,
) -> Result<OidcClaimsResponseDto, AuthError>;
}

View File

@ -0,0 +1,40 @@
use std::sync::Arc;
use async_trait::async_trait;
use crate::{
adapter::gateway::auth_oidc_service::AuthOidcService,
application::{
error::auth_error::AuthError, gateway::auth_repository::AuthRepository,
use_case::get_auth_url_use_case::AuthUrl,
},
domain::entity::user::User,
};
pub struct AuthRepositoryImpl {
auth_oidc_service: Arc<dyn AuthOidcService>,
}
impl AuthRepositoryImpl {
pub fn new(auth_oidc_service: Arc<dyn AuthOidcService>) -> Self {
Self { auth_oidc_service }
}
}
#[async_trait]
impl AuthRepository for AuthRepositoryImpl {
fn get_auth_url(&self) -> Result<AuthUrl, AuthError> {
self.auth_oidc_service.get_auth_url()
}
async fn exchange_auth_code(
&self,
code: &str,
expected_nonce: &str,
) -> Result<User, AuthError> {
self.auth_oidc_service
.exchange_auth_code(code, expected_nonce)
.await
.map(|dto| dto.into_entity())
}
}

View File

@ -0,0 +1,17 @@
use crate::domain::entity::user::User;
pub struct OidcClaimsResponseDto {
pub sub: String,
pub preferred_username: Option<String>,
pub email: Option<String>,
}
impl OidcClaimsResponseDto {
pub fn into_entity(self) -> User {
User {
source_id: self.sub,
displayed_name: self.preferred_username.unwrap_or_default(),
email: self.email.unwrap_or_default(),
}
}
}

View File

@ -0,0 +1 @@
pub mod auth_error;

View File

@ -0,0 +1,7 @@
#[derive(Debug, PartialEq)]
pub enum AuthError {
OidcError(String),
InvalidNonce,
InvalidAuthCode,
InvalidIdToken,
}

View File

@ -0,0 +1 @@
pub mod auth_repository;

View File

@ -0,0 +1,13 @@
use async_trait::async_trait;
use crate::{
application::{error::auth_error::AuthError, use_case::get_auth_url_use_case::AuthUrl},
domain::entity::user::User,
};
#[async_trait]
pub trait AuthRepository: Send + Sync {
fn get_auth_url(&self) -> Result<AuthUrl, AuthError>;
async fn exchange_auth_code(&self, code: &str, expected_nonce: &str)
-> Result<User, AuthError>;
}

View File

@ -0,0 +1,2 @@
pub mod exchange_auth_code_use_case;
pub mod get_auth_url_use_case;

View File

@ -0,0 +1,48 @@
use std::sync::Arc;
use async_trait::async_trait;
use crate::{
application::{error::auth_error::AuthError, gateway::auth_repository::AuthRepository},
domain::entity::user::User,
};
#[async_trait]
pub trait ExchangeAuthCodeUseCase: Send + Sync {
async fn execute(
&self,
code: &str,
received_state: &str,
expected_state: &str,
expected_nonce: &str,
) -> Result<User, AuthError>;
}
pub struct ExchangeAuthCodeUseCaseImpl {
auth_repository: Arc<dyn AuthRepository>,
}
impl ExchangeAuthCodeUseCaseImpl {
pub fn new(auth_repository: Arc<dyn AuthRepository>) -> Self {
Self { auth_repository }
}
}
#[async_trait]
impl ExchangeAuthCodeUseCase for ExchangeAuthCodeUseCaseImpl {
async fn execute(
&self,
code: &str,
received_state: &str,
expected_state: &str,
expected_nonce: &str,
) -> Result<User, AuthError> {
if received_state != expected_state {
return Err(AuthError::InvalidNonce);
}
self.auth_repository
.exchange_auth_code(code, expected_nonce)
.await
}
}

View File

@ -0,0 +1,29 @@
use std::sync::Arc;
use crate::application::{error::auth_error::AuthError, gateway::auth_repository::AuthRepository};
pub trait GetAuthUrlUseCase: Send + Sync {
fn execute(&self) -> Result<AuthUrl, AuthError>;
}
pub struct LoginUseCaseImpl {
auth_repository: Arc<dyn AuthRepository>,
}
impl LoginUseCaseImpl {
pub fn new(auth_repository: Arc<dyn AuthRepository>) -> Self {
Self { auth_repository }
}
}
impl GetAuthUrlUseCase for LoginUseCaseImpl {
fn execute(&self) -> Result<AuthUrl, AuthError> {
self.auth_repository.get_auth_url()
}
}
pub struct AuthUrl {
pub url: String,
pub state: String,
pub nonce: String,
}

View File

@ -0,0 +1 @@
pub mod user;

View File

@ -0,0 +1,5 @@
pub struct User {
pub source_id: String,
pub displayed_name: String,
pub email: String,
}

View File

@ -1,2 +1,2 @@
pub mod db; pub mod oidc;
pub mod web; pub mod web;

View File

@ -0,0 +1 @@
pub mod auth_oidc_service_impl;

View File

@ -0,0 +1,108 @@
use async_trait::async_trait;
use openidconnect::{
AuthorizationCode, ClientId, ClientSecret, CsrfToken, EndpointMaybeSet, EndpointNotSet,
EndpointSet, Nonce, RedirectUrl, TokenResponse as _,
core::{CoreAuthenticationFlow, CoreClient, CoreProviderMetadata},
};
use crate::{
adapter::gateway::{
auth_oidc_service::AuthOidcService, oidc_claims_response_dto::OidcClaimsResponseDto,
},
application::{error::auth_error::AuthError, use_case::get_auth_url_use_case::AuthUrl},
};
type CompleteClient<
HasAuthUrl = EndpointSet,
HasDeviceAuthUrl = EndpointNotSet,
HasIntrospectionUrl = EndpointNotSet,
HasRevocationUrl = EndpointNotSet,
HasTokenUrl = EndpointMaybeSet,
HasUserInfoUrl = EndpointMaybeSet,
> = CoreClient<
HasAuthUrl,
HasDeviceAuthUrl,
HasIntrospectionUrl,
HasRevocationUrl,
HasTokenUrl,
HasUserInfoUrl,
>;
pub struct AuthOidcServiceImpl {
oidc_client: CompleteClient,
http_client: openidconnect::reqwest::Client,
}
impl AuthOidcServiceImpl {
pub fn new(
provider_metadata: CoreProviderMetadata,
client_id: &str,
client_secret: &str,
redirect_url: RedirectUrl,
http_client: openidconnect::reqwest::Client,
) -> Self {
Self {
oidc_client: CoreClient::from_provider_metadata(
provider_metadata,
ClientId::new(client_id.to_string()),
Some(ClientSecret::new(client_secret.to_string())),
)
.set_redirect_uri(redirect_url),
http_client,
}
}
}
#[async_trait]
impl AuthOidcService for AuthOidcServiceImpl {
fn get_auth_url(&self) -> Result<AuthUrl, AuthError> {
let (url, state, nonce) = self
.oidc_client
.authorize_url(
CoreAuthenticationFlow::AuthorizationCode,
CsrfToken::new_random,
Nonce::new_random,
)
.url();
Ok(AuthUrl {
url: url.to_string(),
state: state.secret().into(),
nonce: nonce.secret().into(),
})
}
async fn exchange_auth_code(
&self,
code: &str,
expected_nonce: &str,
) -> Result<OidcClaimsResponseDto, AuthError> {
let token_response = self
.oidc_client
.exchange_code(AuthorizationCode::new(code.to_string()))
.map_err(|e| AuthError::OidcError(e.to_string()))?
.request_async(&self.http_client)
.await
.map_err(|_| AuthError::InvalidAuthCode)?;
let id_token = token_response.id_token().ok_or(AuthError::InvalidIdToken)?;
let claims = id_token
.claims(
&self.oidc_client.id_token_verifier(),
&Nonce::new(expected_nonce.to_string()),
)
.map_err(|_| AuthError::InvalidIdToken)?;
let preferred_username = claims
.preferred_username()
.map(|username| username.to_string());
let email = claims.email().map(|email| email.to_string());
Ok(OidcClaimsResponseDto {
sub: claims.subject().to_string(),
preferred_username: preferred_username,
email: email,
})
}
}

View File

@ -0,0 +1 @@
pub mod auth_web_routes;

View File

@ -0,0 +1,92 @@
use actix_session::Session;
use actix_web::{HttpResponse, Responder, http::header, web};
use crate::{
adapter::delivery::{
auth_controller::AuthController, oidc_callback_query_dto::OidcCallbackQueryDto,
},
application::error::auth_error::AuthError,
};
pub fn configure_auth_routes(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/auth")
.route("/login", web::get().to(oidc_login_handler))
.route("/callback", web::get().to(oidc_callback_handler))
.route("/logout", web::get().to(logout_handler)),
);
}
async fn oidc_login_handler(
auth_controller: web::Data<dyn AuthController>,
session: Session,
) -> impl Responder {
let result = auth_controller.oidc_login();
match result {
Ok(auth_url) => {
if let Err(e) = session.insert("auth_state", auth_url.state) {
log::error!("{e:?}");
return HttpResponse::InternalServerError().finish();
}
if let Err(e) = session.insert("auth_nonce", auth_url.nonce) {
log::error!("{e:?}");
return HttpResponse::InternalServerError().finish();
}
HttpResponse::Found()
.append_header((header::LOCATION, auth_url.url))
.finish()
}
Err(e) => {
log::error!("{e:?}");
HttpResponse::InternalServerError().finish()
}
}
}
async fn oidc_callback_handler(
auth_controller: web::Data<dyn AuthController>,
query: web::Query<OidcCallbackQueryDto>,
session: Session,
) -> impl Responder {
let expected_state: String = match session.get("auth_state") {
Ok(Some(state)) => state,
_ => return HttpResponse::BadRequest().finish(),
};
let expected_nonce: String = match session.get("auth_nonce") {
Ok(Some(nonce)) => nonce,
_ => return HttpResponse::BadRequest().finish(),
};
let result = auth_controller
.oidc_callback(query.into_inner(), &expected_state, &expected_nonce)
.await;
session.remove("auth_state");
session.remove("auth_nonce");
match result {
Ok(user) => {
if let Err(e) = session.insert("user", user) {
log::error!("{e:?}");
return HttpResponse::InternalServerError().finish();
}
HttpResponse::Found()
.append_header((header::LOCATION, "/"))
.finish()
}
Err(e) => match e {
AuthError::InvalidAuthCode | AuthError::InvalidIdToken | AuthError::InvalidNonce => {
HttpResponse::BadRequest().finish()
}
_ => {
log::error!("{e:?}");
HttpResponse::InternalServerError().finish()
}
},
}
}
async fn logout_handler() -> impl Responder {
HttpResponse::Ok().finish()
}

View File

@ -4,11 +4,15 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dependencies] [dependencies]
actix-session.workspace = true
actix-web.workspace = true actix-web.workspace = true
dotenv.workspace = true dotenv.workspace = true
env_logger.workspace = true env_logger.workspace = true
hex.workspace = true
openidconnect.workspace = true
percent-encoding.workspace = true percent-encoding.workspace = true
sqlx.workspace = true sqlx.workspace = true
auth.workspace = true
image.workspace = true image.workspace = true
post.workspace = true post.workspace = true

View File

@ -0,0 +1,18 @@
use openidconnect::reqwest;
use crate::configuration::oidc::OidcConfiguration;
pub mod oidc;
#[derive(Clone)]
pub struct Configuration {
pub oidc_configuration: OidcConfiguration,
}
impl Configuration {
pub async fn new(http_client: reqwest::Client) -> Self {
Self {
oidc_configuration: OidcConfiguration::new(http_client).await,
}
}
}

View File

@ -0,0 +1,35 @@
use std::env;
use openidconnect::{IssuerUrl, RedirectUrl, core::CoreProviderMetadata, reqwest};
#[derive(Clone)]
pub struct OidcConfiguration {
pub provider_metadata: CoreProviderMetadata,
pub client_id: String,
pub client_secret: String,
pub redirect_url: RedirectUrl,
}
impl OidcConfiguration {
pub async fn new(http_client: reqwest::Client) -> Self {
let issuer_url = env::var("OIDC_ISSUER_URL").expect("OIDC_ISSUER_URL must be set");
let client_id = env::var("OIDC_CLIENT_ID").expect("OIDC_CLIENT_ID must be set");
let client_secret = env::var("OIDC_CLIENT_SECRET").expect("OIDC_CLIENT_SECRET must be set");
let redirect_url_str = env::var("OIDC_REDIRECT_URL")
.unwrap_or_else(|_| "http://127.0.0.1:8080/auth/callback".to_string());
let provider_metadata = CoreProviderMetadata::discover_async(
IssuerUrl::new(issuer_url).expect("Invalid issuer URL"),
&http_client,
)
.await
.expect("Failed to discover OIDC provider metadata");
Self {
provider_metadata,
client_id,
client_secret,
redirect_url: RedirectUrl::new(redirect_url_str).expect("Invalid redirect URI"),
}
}
}

View File

@ -1,5 +1,16 @@
use std::sync::Arc; use std::sync::Arc;
use auth::{
adapter::{
delivery::auth_controller::{AuthController, AuthControllerImpl},
gateway::auth_repository_impl::AuthRepositoryImpl,
},
application::use_case::{
exchange_auth_code_use_case::ExchangeAuthCodeUseCaseImpl,
get_auth_url_use_case::LoginUseCaseImpl,
},
framework::oidc::auth_oidc_service_impl::AuthOidcServiceImpl,
};
use image::{ use image::{
adapter::{ adapter::{
delivery::image_controller::{ImageController, ImageControllerImpl}, delivery::image_controller::{ImageController, ImageControllerImpl},
@ -13,6 +24,7 @@ use image::{
storage::image_storage_impl::ImageStorageImpl, storage::image_storage_impl::ImageStorageImpl,
}, },
}; };
use openidconnect::reqwest;
use post::{ use post::{
adapter::{ adapter::{
delivery::post_controller::{PostController, PostControllerImpl}, delivery::post_controller::{PostController, PostControllerImpl},
@ -26,13 +38,38 @@ use post::{
}; };
use sqlx::{Pool, Postgres}; use sqlx::{Pool, Postgres};
use crate::configuration::Configuration;
pub struct Container { pub struct Container {
pub post_controller: Arc<dyn PostController>, pub auth_controller: Arc<dyn AuthController>,
pub image_controller: Arc<dyn ImageController>, pub image_controller: Arc<dyn ImageController>,
pub post_controller: Arc<dyn PostController>,
} }
impl Container { impl Container {
pub fn new(db_pool: Pool<Postgres>, storage_path: &str) -> Self { pub fn new(
db_pool: Pool<Postgres>,
storage_path: &str,
http_client: reqwest::Client,
configuration: Configuration,
) -> Self {
let oidc_configuration = &configuration.oidc_configuration;
let auth_oidc_service = Arc::new(AuthOidcServiceImpl::new(
oidc_configuration.provider_metadata.clone(),
&oidc_configuration.client_id,
&oidc_configuration.client_secret,
oidc_configuration.redirect_url.clone(),
http_client,
));
let auth_repository = Arc::new(AuthRepositoryImpl::new(auth_oidc_service));
let get_auth_url_use_case = Arc::new(LoginUseCaseImpl::new(auth_repository.clone()));
let exchange_auth_code_use_case =
Arc::new(ExchangeAuthCodeUseCaseImpl::new(auth_repository.clone()));
let auth_controller = Arc::new(AuthControllerImpl::new(
get_auth_url_use_case,
exchange_auth_code_use_case,
));
let post_db_service = Arc::new(PostDbServiceImpl::new(db_pool.clone())); let post_db_service = Arc::new(PostDbServiceImpl::new(db_pool.clone()));
let post_repository = Arc::new(PostRepositoryImpl::new(post_db_service.clone())); let post_repository = Arc::new(PostRepositoryImpl::new(post_db_service.clone()));
let get_all_post_info_use_case = let get_all_post_info_use_case =
@ -57,8 +94,9 @@ impl Container {
)); ));
Self { Self {
post_controller, auth_controller,
image_controller, image_controller,
post_controller,
} }
} }
} }

View File

@ -1 +1,2 @@
pub mod configuration;
pub mod container; pub mod container;

View File

@ -1,12 +1,18 @@
use actix_session::{
SessionMiddleware, config::SessionMiddlewareBuilder, storage::RedisSessionStore,
};
use actix_web::{ use actix_web::{
App, Error, HttpServer, App, Error, HttpServer,
body::MessageBody, body::MessageBody,
cookie,
dev::{ServiceFactory, ServiceRequest, ServiceResponse}, dev::{ServiceFactory, ServiceRequest, ServiceResponse},
web, web,
}; };
use auth::framework::web::auth_web_routes::configure_auth_routes;
use image::framework::web::image_web_routes::configure_image_routes; use image::framework::web::image_web_routes::configure_image_routes;
use openidconnect::reqwest;
use post::framework::web::post_web_routes::configure_post_routes; use post::framework::web::post_web_routes::configure_post_routes;
use server::container::Container; use server::{configuration::Configuration, container::Container};
use sqlx::{Pool, Postgres, postgres::PgPoolOptions}; use sqlx::{Pool, Postgres, postgres::PgPoolOptions};
use std::env; use std::env;
@ -17,6 +23,13 @@ async fn main() -> std::io::Result<()> {
let db_pool = init_database().await; let db_pool = init_database().await;
let storage_path = env::var("STORAGE_PATH").unwrap_or_else(|_| "static".to_string()); let storage_path = env::var("STORAGE_PATH").unwrap_or_else(|_| "static".to_string());
let http_client = reqwest::ClientBuilder::new()
.redirect(reqwest::redirect::Policy::none())
.build()
.expect("Failed to create HTTP client");
let session_store = init_session_store().await;
let session_key = init_session_key().await;
let host = env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string()); let host = env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string());
let port = env::var("PORT") let port = env::var("PORT")
@ -24,7 +37,17 @@ async fn main() -> std::io::Result<()> {
.parse::<u16>() .parse::<u16>()
.unwrap(); .unwrap();
HttpServer::new(move || create_app(db_pool.clone(), storage_path.clone())) let configuration = Configuration::new(http_client.clone()).await;
HttpServer::new(move || {
create_app(
db_pool.clone(),
storage_path.clone(),
http_client.clone(),
SessionMiddleware::builder(session_store.clone(), session_key.clone()),
configuration.clone(),
)
})
.bind((host, port))? .bind((host, port))?
.run() .run()
.await .await
@ -59,9 +82,29 @@ async fn init_database() -> Pool<Postgres> {
db_pool db_pool
} }
async fn init_session_store() -> RedisSessionStore {
let redis_url = env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.1:6379".to_string());
RedisSessionStore::new(redis_url)
.await
.expect("Failed to create Redis session store")
}
async fn init_session_key() -> cookie::Key {
let session_key_hex = env::var("SESSION_KEY").expect("SESSION_KEY must be set");
let session_key_bytes =
hex::decode(session_key_hex).expect("Invalid SESSION_KEY format, must be hex encoded");
if session_key_bytes.len() != 64 {
panic!("SESSION_KEY must be 64 bytes long");
}
cookie::Key::from(&session_key_bytes)
}
fn create_app( fn create_app(
db_pool: Pool<Postgres>, db_pool: Pool<Postgres>,
storage_path: String, storage_path: String,
http_client: reqwest::Client,
session_middleware_builder: SessionMiddlewareBuilder<RedisSessionStore>,
configuration: Configuration,
) -> App< ) -> App<
impl ServiceFactory< impl ServiceFactory<
ServiceRequest, ServiceRequest,
@ -71,11 +114,14 @@ fn create_app(
Error = Error, Error = Error,
>, >,
> { > {
let container = Container::new(db_pool, &storage_path); let container = Container::new(db_pool, &storage_path, http_client, configuration);
App::new() App::new()
.app_data(web::Data::from(container.post_controller)) .wrap(session_middleware_builder.build())
.app_data(web::Data::from(container.auth_controller))
.app_data(web::Data::from(container.image_controller)) .app_data(web::Data::from(container.image_controller))
.configure(configure_post_routes) .app_data(web::Data::from(container.post_controller))
.configure(configure_auth_routes)
.configure(configure_image_routes) .configure(configure_image_routes)
.configure(configure_post_routes)
} }