feat(auth): add authentication integration
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
48d09c8ae3
commit
e6084a7f4e
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -896,24 +896,17 @@ name = "como_auth"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-graphql",
|
|
||||||
"async-graphql-axum",
|
|
||||||
"async-sqlx-session",
|
"async-sqlx-session",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
"axum-sessions",
|
"axum-sessions",
|
||||||
"clap",
|
"clap",
|
||||||
"como_core",
|
|
||||||
"como_domain",
|
|
||||||
"como_gql",
|
|
||||||
"como_infrastructure",
|
|
||||||
"oauth2",
|
"oauth2",
|
||||||
"openidconnect",
|
"openidconnect",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"sealed_test",
|
"sealed_test",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
|
||||||
"sqlx 0.6.3",
|
"sqlx 0.6.3",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
@ -985,6 +978,7 @@ dependencies = [
|
|||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"como_auth",
|
||||||
"como_core",
|
"como_core",
|
||||||
"como_domain",
|
"como_domain",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::router::AppState;
|
use crate::router::AppState;
|
||||||
use crate::zitadel::{IntrospectionConfig, IntrospectionState};
|
|
||||||
|
|
||||||
use async_sqlx_session::PostgresSessionStore;
|
use async_sqlx_session::PostgresSessionStore;
|
||||||
use axum::extract::{FromRef, FromRequestParts, Query, State};
|
use axum::extract::{FromRef, FromRequestParts, Query, State};
|
||||||
@ -10,9 +10,9 @@ use axum::headers::{Authorization, Cookie};
|
|||||||
use axum::http::request::Parts;
|
use axum::http::request::Parts;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::http::{header::SET_COOKIE, HeaderMap};
|
use axum::http::{header::SET_COOKIE, HeaderMap};
|
||||||
use axum::response::{IntoResponse, Redirect};
|
use axum::response::{ErrorResponse, IntoResponse, Redirect};
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum::{async_trait, RequestPartsExt, Router, TypedHeader};
|
use axum::{async_trait, Json, RequestPartsExt, Router, TypedHeader};
|
||||||
use axum_sessions::async_session::{Session, SessionStore};
|
use axum_sessions::async_session::{Session, SessionStore};
|
||||||
use como_domain::users::User;
|
use como_domain::users::User;
|
||||||
use como_infrastructure::register::ServiceRegister;
|
use como_infrastructure::register::ServiceRegister;
|
||||||
@ -20,6 +20,7 @@ use oauth2::basic::BasicClient;
|
|||||||
use oauth2::{reqwest::async_http_client, AuthorizationCode, CsrfToken, Scope, TokenResponse};
|
use oauth2::{reqwest::async_http_client, AuthorizationCode, CsrfToken, Scope, TokenResponse};
|
||||||
use oauth2::{RedirectUrl, TokenIntrospectionResponse};
|
use oauth2::{RedirectUrl, TokenIntrospectionResponse};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use serde_json::json;
|
||||||
use zitadel::oidc::introspection::introspect;
|
use zitadel::oidc::introspection::introspect;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -27,17 +28,39 @@ pub struct ZitadelAuthParams {
|
|||||||
return_url: Option<String>,
|
return_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn zitadel_auth(State(client): State<BasicClient>) -> impl IntoResponse {
|
trait AnyhowExtensions<T, E>
|
||||||
let (auth_url, _csrf_token) = client
|
where
|
||||||
.authorize_url(CsrfToken::new_random)
|
E: Display,
|
||||||
.add_scope(Scope::new("identify".to_string()))
|
{
|
||||||
.add_scope(Scope::new("openid".to_string()))
|
fn into_response(self) -> Result<T, ErrorResponse>;
|
||||||
.url();
|
}
|
||||||
|
impl<T, E> AnyhowExtensions<T, E> for anyhow::Result<T, E>
|
||||||
Redirect::to(auth_url.as_ref())
|
where
|
||||||
|
E: Display,
|
||||||
|
{
|
||||||
|
fn into_response(self) -> Result<T, ErrorResponse> {
|
||||||
|
match self {
|
||||||
|
Ok(o) => Ok(o),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("failed with anyhow error: {}", e);
|
||||||
|
Err(ErrorResponse::from((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({
|
||||||
|
"status": "something",
|
||||||
|
})),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static COOKIE_NAME: &str = "SESSION";
|
pub async fn zitadel_auth(
|
||||||
|
State(services): State<ServiceRegister>,
|
||||||
|
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||||
|
let url = services.auth_service.login().await.into_response()?;
|
||||||
|
|
||||||
|
Ok(Redirect::to(&url.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -48,44 +71,15 @@ pub struct AuthRequest {
|
|||||||
|
|
||||||
pub async fn login_authorized(
|
pub async fn login_authorized(
|
||||||
Query(query): Query<AuthRequest>,
|
Query(query): Query<AuthRequest>,
|
||||||
State(store): State<PostgresSessionStore>,
|
State(services): State<ServiceRegister>,
|
||||||
State(introspection_state): State<IntrospectionState>,
|
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||||
) -> impl IntoResponse {
|
let (headers, url) = services
|
||||||
let token = oauth_client
|
.auth_service
|
||||||
.exchange_code(AuthorizationCode::new(query.code.clone()))
|
.login_authorized(&query.code, &query.state)
|
||||||
.request_async(async_http_client)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.into_response()?;
|
||||||
|
|
||||||
let config = IntrospectionConfig::from_ref(&introspection_state);
|
Ok((headers, Redirect::to(url.as_str())))
|
||||||
|
|
||||||
let res = introspect(
|
|
||||||
&config.introspection_uri,
|
|
||||||
&config.authority,
|
|
||||||
&config.authentication,
|
|
||||||
token.access_token().secret(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut session = Session::new();
|
|
||||||
session
|
|
||||||
.insert(
|
|
||||||
"user",
|
|
||||||
User {
|
|
||||||
id: res.sub().unwrap().into(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let cookie = store.store_session(session).await.unwrap().unwrap();
|
|
||||||
|
|
||||||
let cookie = format!("{}={}; SameSite=Lax; Path=/", COOKIE_NAME, cookie);
|
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
|
||||||
headers.insert(SET_COOKIE, cookie.parse().unwrap());
|
|
||||||
|
|
||||||
(headers, Redirect::to("http://localhost:3000/dash/home"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AuthController;
|
pub struct AuthController;
|
||||||
@ -106,79 +100,61 @@ pub struct UserFromSession {
|
|||||||
pub user: User,
|
pub user: User,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static COOKIE_NAME: &str = "SESSION";
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<S> FromRequestParts<S> for UserFromSession
|
impl<S> FromRequestParts<S> for UserFromSession
|
||||||
where
|
where
|
||||||
PostgresSessionStore: FromRef<S>,
|
ServiceRegister: FromRef<S>,
|
||||||
IntrospectionState: FromRef<S>,
|
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
{
|
{
|
||||||
type Rejection = (StatusCode, &'static str);
|
type Rejection = (StatusCode, &'static str);
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||||
let store = PostgresSessionStore::from_ref(state);
|
let services = ServiceRegister::from_ref(state);
|
||||||
|
|
||||||
let cookie: Option<TypedHeader<Cookie>> = parts.extract().await.unwrap();
|
let cookie: Option<TypedHeader<Cookie>> = parts.extract().await.unwrap();
|
||||||
|
|
||||||
let session_cookie = cookie.as_ref().and_then(|cookie| cookie.get(COOKIE_NAME));
|
let session_cookie = cookie.as_ref().and_then(|cookie| cookie.get(COOKIE_NAME));
|
||||||
if let None = session_cookie {
|
if let None = session_cookie {
|
||||||
let introspection_state = IntrospectionState::from_ref(state);
|
// let introspection_state = IntrospectionState::from_ref(state);
|
||||||
|
|
||||||
let basic: Option<TypedHeader<Authorization<Basic>>> = parts.extract().await.unwrap();
|
// let basic: Option<TypedHeader<Authorization<Basic>>> = parts.extract().await.unwrap();
|
||||||
|
|
||||||
if let Some(basic) = basic {
|
// if let Some(basic) = basic {
|
||||||
let config = IntrospectionConfig::from_ref(&introspection_state);
|
// let config = IntrospectionConfig::from_ref(&introspection_state);
|
||||||
|
|
||||||
let res = introspect(
|
// let res = introspect(
|
||||||
&config.introspection_uri,
|
// &config.introspection_uri,
|
||||||
&config.authority,
|
// &config.authority,
|
||||||
&config.authentication,
|
// &config.authentication,
|
||||||
basic.password(),
|
// basic.password(),
|
||||||
)
|
// )
|
||||||
.await
|
// .await
|
||||||
.unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
return Ok(UserFromSession {
|
// return Ok(UserFromSession {
|
||||||
user: User {
|
// user: User {
|
||||||
id: res.sub().unwrap().into(),
|
// id: res.sub().unwrap().into(),
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
todo!()
|
||||||
|
|
||||||
return Err((StatusCode::UNAUTHORIZED, "No session was found"));
|
//return Err(anyhow::anyhow!("No session was found")).into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
let session_cookie = session_cookie.unwrap();
|
let session_cookie = session_cookie.unwrap();
|
||||||
|
|
||||||
tracing::debug!(
|
|
||||||
"UserFromSession: got session cookie from user agent, {}={}",
|
|
||||||
COOKIE_NAME,
|
|
||||||
session_cookie
|
|
||||||
);
|
|
||||||
// continue to decode the session cookie
|
// continue to decode the session cookie
|
||||||
let user =
|
let user = services
|
||||||
if let Some(session) = store.load_session(session_cookie.to_owned()).await.unwrap() {
|
.auth_service
|
||||||
if let Some(user) = session.get::<User>("user") {
|
.get_user_from_session(session_cookie)
|
||||||
tracing::debug!(
|
.await
|
||||||
"UserFromSession: session decoded success, user_id={:?}",
|
.into_response()
|
||||||
user.id
|
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "failed with error"))?;
|
||||||
);
|
|
||||||
user
|
|
||||||
} else {
|
|
||||||
return Err((
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
"No `user_id` found in session",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::debug!(
|
|
||||||
"UserIdFromSession: err session not exists in store, {}={}",
|
|
||||||
COOKIE_NAME,
|
|
||||||
session_cookie
|
|
||||||
);
|
|
||||||
return Err((StatusCode::BAD_REQUEST, "No session found for cookie"));
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(UserFromSession { user })
|
Ok(UserFromSession {
|
||||||
|
user: User { id: user.id },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use async_sqlx_session::PostgresSessionStore;
|
|
||||||
use axum::extract::FromRef;
|
use axum::extract::FromRef;
|
||||||
use axum::http::{HeaderValue, Method};
|
use axum::http::{HeaderValue, Method};
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use como_infrastructure::register::ServiceRegister;
|
use como_infrastructure::register::ServiceRegister;
|
||||||
use oauth2::basic::BasicClient;
|
|
||||||
use tower::ServiceBuilder;
|
use tower::ServiceBuilder;
|
||||||
use tower_http::{cors::CorsLayer, trace::TraceLayer};
|
use tower_http::{cors::CorsLayer, trace::TraceLayer};
|
||||||
|
|
||||||
use crate::controllers::auth::AuthController;
|
use crate::controllers::auth::AuthController;
|
||||||
use crate::controllers::graphql::GraphQLController;
|
use crate::controllers::graphql::GraphQLController;
|
||||||
use crate::zitadel::{IntrospectionState, IntrospectionStateBuilder};
|
|
||||||
|
|
||||||
pub struct Api;
|
pub struct Api;
|
||||||
|
|
||||||
@ -22,18 +19,8 @@ impl Api {
|
|||||||
cors_origin: &str,
|
cors_origin: &str,
|
||||||
service_register: ServiceRegister,
|
service_register: ServiceRegister,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let client_id = env::var("CLIENT_ID").expect("Missing CLIENT_ID!");
|
|
||||||
let client_secret = env::var("CLIENT_SECRET").expect("Missing CLIENT_SECRET!");
|
|
||||||
let zitadel_url = env::var("ZITADEL_URL").expect("missing ZITADEL_URL");
|
|
||||||
|
|
||||||
let is = IntrospectionStateBuilder::new(&zitadel_url)
|
|
||||||
.with_basic_auth(&client_id, &client_secret)
|
|
||||||
.build()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let app_state = AppState {
|
let app_state = AppState {
|
||||||
store: service_register.session_store.clone(),
|
service_register: service_register.clone(),
|
||||||
introspection_state: is,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let router = Router::new()
|
let router = Router::new()
|
||||||
@ -76,18 +63,11 @@ impl Api {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
introspection_state: IntrospectionState,
|
service_register: ServiceRegister,
|
||||||
store: PostgresSessionStore,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromRef<AppState> for PostgresSessionStore {
|
impl FromRef<AppState> for ServiceRegister {
|
||||||
fn from_ref(state: &AppState) -> Self {
|
|
||||||
state.store.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromRef<AppState> for IntrospectionState {
|
|
||||||
fn from_ref(input: &AppState) -> Self {
|
fn from_ref(input: &AppState) -> Self {
|
||||||
input.introspection_state.clone()
|
input.service_register.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,90 +1 @@
|
|||||||
pub mod client;
|
|
||||||
|
|
||||||
use axum::extract::FromRef;
|
|
||||||
use openidconnect::IntrospectionUrl;
|
|
||||||
use zitadel::{
|
|
||||||
axum::introspection::IntrospectionStateBuilderError,
|
|
||||||
credentials::Application,
|
|
||||||
oidc::{discovery::discover, introspection::AuthorityAuthentication},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct IntrospectionState {
|
|
||||||
pub(crate) config: IntrospectionConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration that must be inject into the axum application state. Used by the
|
|
||||||
/// [IntrospectionStateBuilder](super::IntrospectionStateBuilder). This struct is also used to create the [IntrospectionState](IntrospectionState)
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct IntrospectionConfig {
|
|
||||||
pub(crate) authority: String,
|
|
||||||
pub(crate) authentication: AuthorityAuthentication,
|
|
||||||
pub(crate) introspection_uri: IntrospectionUrl,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromRef<IntrospectionState> for IntrospectionConfig {
|
|
||||||
fn from_ref(input: &IntrospectionState) -> Self {
|
|
||||||
input.config.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct IntrospectionStateBuilder {
|
|
||||||
authority: String,
|
|
||||||
authentication: Option<AuthorityAuthentication>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builder for [IntrospectionConfig]
|
|
||||||
impl IntrospectionStateBuilder {
|
|
||||||
pub fn new(authority: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
authority: authority.to_string(),
|
|
||||||
authentication: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_basic_auth(
|
|
||||||
&mut self,
|
|
||||||
client_id: &str,
|
|
||||||
client_secret: &str,
|
|
||||||
) -> &mut IntrospectionStateBuilder {
|
|
||||||
self.authentication = Some(AuthorityAuthentication::Basic {
|
|
||||||
client_id: client_id.to_string(),
|
|
||||||
client_secret: client_secret.to_string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_jwt_profile(&mut self, application: Application) -> &mut IntrospectionStateBuilder {
|
|
||||||
self.authentication = Some(AuthorityAuthentication::JWTProfile { application });
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn build(&mut self) -> Result<IntrospectionState, IntrospectionStateBuilderError> {
|
|
||||||
if self.authentication.is_none() {
|
|
||||||
return Err(IntrospectionStateBuilderError::NoAuthSchema);
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata = discover(&self.authority)
|
|
||||||
.await
|
|
||||||
.map_err(|source| IntrospectionStateBuilderError::Discovery { source })?;
|
|
||||||
|
|
||||||
let introspection_uri = metadata
|
|
||||||
.additional_metadata()
|
|
||||||
.introspection_endpoint
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
if introspection_uri.is_none() {
|
|
||||||
return Err(IntrospectionStateBuilderError::NoIntrospectionUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(IntrospectionState {
|
|
||||||
config: IntrospectionConfig {
|
|
||||||
authority: self.authority.clone(),
|
|
||||||
introspection_uri: introspection_uri.unwrap(),
|
|
||||||
authentication: self.authentication.as_ref().unwrap().clone(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -6,21 +6,12 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
como_gql.workspace = true
|
|
||||||
como_core.workspace = true
|
|
||||||
como_domain.workspace = true
|
|
||||||
como_infrastructure.workspace = true
|
|
||||||
|
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
async-graphql.workspace = true
|
|
||||||
async-graphql-axum.workspace = true
|
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
axum-extra.workspace = true
|
axum-extra.workspace = true
|
||||||
axum-sessions.workspace = true
|
axum-sessions.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
|
||||||
tokio.workspace = true
|
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
sqlx.workspace = true
|
sqlx.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
@ -34,5 +25,6 @@ oauth2 = "4.4.0"
|
|||||||
openidconnect = "3.0.0"
|
openidconnect = "3.0.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
tokio.workspace = true
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
sealed_test.workspace = true
|
sealed_test.workspace = true
|
||||||
|
107
como_auth/src/auth.rs
Normal file
107
como_auth/src/auth.rs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use axum::http::{header::SET_COOKIE, HeaderMap};
|
||||||
|
use oauth2::url::Url;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
introspection::IntrospectionService,
|
||||||
|
oauth::OAuth,
|
||||||
|
session::{SessionService, User},
|
||||||
|
AuthClap, AuthEngine,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Auth {
|
||||||
|
async fn login(&self) -> anyhow::Result<Url>;
|
||||||
|
async fn login_authorized(&self, code: &str, state: &str) -> anyhow::Result<(HeaderMap, Url)>;
|
||||||
|
async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AuthService(Arc<dyn Auth + Send + Sync + 'static>);
|
||||||
|
|
||||||
|
impl AuthService {
|
||||||
|
pub async fn new(config: &AuthClap) -> anyhow::Result<Self> {
|
||||||
|
match config.engine {
|
||||||
|
AuthEngine::Noop => Ok(Self::new_noop()),
|
||||||
|
AuthEngine::Zitadel => Ok(Self::new_zitadel()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_zitadel() -> Self {
|
||||||
|
todo!()
|
||||||
|
//Self(Arc::new(ZitadelAuthService {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_noop() -> Self {
|
||||||
|
Self(Arc::new(NoopAuthService {}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for AuthService {
|
||||||
|
type Target = Arc<dyn Auth + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ZitadelAuthService {
|
||||||
|
oauth: OAuth,
|
||||||
|
introspection: IntrospectionService,
|
||||||
|
session: SessionService,
|
||||||
|
}
|
||||||
|
pub static COOKIE_NAME: &str = "SESSION";
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Auth for ZitadelAuthService {
|
||||||
|
async fn login(&self) -> anyhow::Result<Url> {
|
||||||
|
let authorize_url = self.oauth.authorize_url().await?;
|
||||||
|
|
||||||
|
Ok(authorize_url)
|
||||||
|
}
|
||||||
|
async fn login_authorized(&self, code: &str, _state: &str) -> anyhow::Result<(HeaderMap, Url)> {
|
||||||
|
let token = self.oauth.exchange(code).await?;
|
||||||
|
let user_id = self.introspection.get_id_token(token.as_str()).await?;
|
||||||
|
let cookie_value = self.session.insert_user("user", user_id.as_str()).await?;
|
||||||
|
|
||||||
|
let cookie = format!("{}={}; SameSite=Lax; Path=/", COOKIE_NAME, cookie_value);
|
||||||
|
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(SET_COOKIE, cookie.parse().unwrap());
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
headers,
|
||||||
|
Url::parse("http://localhost:3000/dash/home")
|
||||||
|
.context("failed to parse login_authorized zitadel return url")?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User> {
|
||||||
|
match self.session.get_user(cookie).await? {
|
||||||
|
Some(u) => Ok(User { id: u }),
|
||||||
|
None => Err(anyhow::anyhow!("failed to find user")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NoopAuthService {}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Auth for NoopAuthService {
|
||||||
|
async fn login(&self) -> anyhow::Result<Url> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
async fn login_authorized(
|
||||||
|
&self,
|
||||||
|
_code: &str,
|
||||||
|
_state: &str,
|
||||||
|
) -> anyhow::Result<(HeaderMap, Url)> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,16 @@
|
|||||||
use std::sync::Arc;
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use axum::extract::FromRef;
|
use axum::extract::FromRef;
|
||||||
|
use oauth2::TokenIntrospectionResponse;
|
||||||
use openidconnect::IntrospectionUrl;
|
use openidconnect::IntrospectionUrl;
|
||||||
use zitadel::{
|
use zitadel::{
|
||||||
axum::introspection::IntrospectionStateBuilderError,
|
axum::introspection::IntrospectionStateBuilderError,
|
||||||
credentials::Application,
|
credentials::Application,
|
||||||
oidc::{discovery::discover, introspection::AuthorityAuthentication},
|
oidc::{
|
||||||
|
discovery::discover,
|
||||||
|
introspection::{introspect, AuthorityAuthentication},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::AuthClap;
|
use crate::AuthClap;
|
||||||
@ -14,10 +18,10 @@ use crate::AuthClap;
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Introspection {
|
pub trait Introspection {
|
||||||
async fn get_user(&self) -> anyhow::Result<()>;
|
async fn get_user(&self) -> anyhow::Result<()>;
|
||||||
|
async fn get_id_token(&self, token: &str) -> anyhow::Result<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IntrospectionService(Arc<dyn Introspection + Send + Sync + 'static>);
|
pub struct IntrospectionService(Arc<dyn Introspection + Send + Sync + 'static>);
|
||||||
|
|
||||||
impl IntrospectionService {
|
impl IntrospectionService {
|
||||||
pub async fn new_zitadel(config: &AuthClap) -> anyhow::Result<Self> {
|
pub async fn new_zitadel(config: &AuthClap) -> anyhow::Result<Self> {
|
||||||
let res = IntrospectionStateBuilder::new(&config.zitadel.authority_url.clone().unwrap())
|
let res = IntrospectionStateBuilder::new(&config.zitadel.authority_url.clone().unwrap())
|
||||||
@ -34,6 +38,14 @@ impl IntrospectionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for IntrospectionService {
|
||||||
|
type Target = Arc<dyn Introspection + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ZitadelIntrospection {
|
pub struct ZitadelIntrospection {
|
||||||
state: IntrospectionState,
|
state: IntrospectionState,
|
||||||
}
|
}
|
||||||
@ -49,6 +61,21 @@ impl Introspection for ZitadelIntrospection {
|
|||||||
async fn get_user(&self) -> anyhow::Result<()> {
|
async fn get_user(&self) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
async fn get_id_token(&self, token: &str) -> anyhow::Result<String> {
|
||||||
|
let config = &self.state.config;
|
||||||
|
let res = introspect(
|
||||||
|
&config.introspection_uri,
|
||||||
|
&config.authority,
|
||||||
|
&config.authentication,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(res
|
||||||
|
.sub()
|
||||||
|
.ok_or(anyhow::anyhow!("could not find a userid (sub) in token"))?
|
||||||
|
.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
use oauth::{OAuth, ZitadelConfig};
|
use oauth::{OAuth, ZitadelConfig};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
mod auth;
|
||||||
mod introspection;
|
mod introspection;
|
||||||
mod oauth;
|
mod oauth;
|
||||||
|
mod session;
|
||||||
|
|
||||||
|
pub use auth::{Auth, AuthService};
|
||||||
|
use session::SessionClap;
|
||||||
|
|
||||||
#[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug)]
|
#[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum AuthEngine {
|
pub enum AuthEngine {
|
||||||
@ -10,6 +15,12 @@ pub enum AuthEngine {
|
|||||||
Zitadel,
|
Zitadel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(clap::ValueEnum, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub enum SessionBackend {
|
||||||
|
InMemory,
|
||||||
|
Postgresql,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(clap::Args, Clone, PartialEq, Eq, Debug)]
|
#[derive(clap::Args, Clone, PartialEq, Eq, Debug)]
|
||||||
pub struct AuthClap {
|
pub struct AuthClap {
|
||||||
#[arg(
|
#[arg(
|
||||||
@ -22,8 +33,21 @@ pub struct AuthClap {
|
|||||||
]
|
]
|
||||||
pub engine: AuthEngine,
|
pub engine: AuthEngine,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
env = "SESSION_BACKEND",
|
||||||
|
long = "session-backend",
|
||||||
|
requires_ifs = [
|
||||||
|
( "postgresql", "PostgresqlSessionClap" )
|
||||||
|
],
|
||||||
|
default_value = "in-memory" )
|
||||||
|
]
|
||||||
|
pub session_backend: SessionBackend,
|
||||||
|
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub zitadel: ZitadelClap,
|
pub zitadel: ZitadelClap,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub session: SessionClap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
@ -105,7 +129,10 @@ impl AuthClap {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{AuthClap, AuthEngine, ZitadelClap};
|
use crate::{
|
||||||
|
session::{PostgresqlSessionClap, SessionClap},
|
||||||
|
AuthClap, AuthEngine, SessionBackend, ZitadelClap,
|
||||||
|
};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
@ -141,6 +168,10 @@ mod test {
|
|||||||
token_url: None,
|
token_url: None,
|
||||||
authority_url: None,
|
authority_url: None,
|
||||||
},
|
},
|
||||||
|
session_backend: SessionBackend::InMemory,
|
||||||
|
session: SessionClap {
|
||||||
|
postgresql: PostgresqlSessionClap { conn: None }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -163,6 +194,10 @@ mod test {
|
|||||||
token_url: None,
|
token_url: None,
|
||||||
authority_url: None,
|
authority_url: None,
|
||||||
},
|
},
|
||||||
|
session_backend: crate::SessionBackend::InMemory,
|
||||||
|
session: crate::SessionClap {
|
||||||
|
postgresql: PostgresqlSessionClap { conn: None }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -195,6 +230,10 @@ mod test {
|
|||||||
token_url: Some("https://something".into()),
|
token_url: Some("https://something".into()),
|
||||||
authority_url: Some("https://something".into()),
|
authority_url: Some("https://something".into()),
|
||||||
},
|
},
|
||||||
|
session_backend: crate::SessionBackend::InMemory,
|
||||||
|
session: crate::SessionClap {
|
||||||
|
postgresql: PostgresqlSessionClap { conn: None }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use oauth2::reqwest::async_http_client;
|
||||||
|
use oauth2::url::Url;
|
||||||
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
||||||
|
use oauth2::{AuthorizationCode, CsrfToken, Scope, TokenResponse};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -8,6 +11,8 @@ use crate::ZitadelClap;
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait OAuthClient {
|
pub trait OAuthClient {
|
||||||
async fn get_token(&self) -> anyhow::Result<()>;
|
async fn get_token(&self) -> anyhow::Result<()>;
|
||||||
|
async fn authorize_url(&self) -> anyhow::Result<Url>;
|
||||||
|
async fn exchange(&self, code: &str) -> anyhow::Result<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OAuth(Arc<dyn OAuthClient + Send + Sync + 'static>);
|
pub struct OAuth(Arc<dyn OAuthClient + Send + Sync + 'static>);
|
||||||
@ -43,6 +48,13 @@ impl OAuthClient for NoopOAuthClient {
|
|||||||
async fn get_token(&self) -> anyhow::Result<()> {
|
async fn get_token(&self) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
async fn authorize_url(&self) -> anyhow::Result<Url> {
|
||||||
|
Ok(Url::parse("http://localhost:3000/auth/zitadel").unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn exchange(&self, code: &str) -> anyhow::Result<String> {
|
||||||
|
Ok(String::new())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Zitadel
|
// -- Zitadel
|
||||||
@ -138,6 +150,26 @@ impl OAuthClient for ZitadelOAuthClient {
|
|||||||
async fn get_token(&self) -> anyhow::Result<()> {
|
async fn get_token(&self) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
async fn authorize_url(&self) -> anyhow::Result<Url> {
|
||||||
|
let (auth_url, _csrf_token) = self
|
||||||
|
.client
|
||||||
|
.authorize_url(CsrfToken::new_random)
|
||||||
|
.add_scope(Scope::new("identify".to_string()))
|
||||||
|
.add_scope(Scope::new("openid".to_string()))
|
||||||
|
.url();
|
||||||
|
|
||||||
|
Ok(auth_url)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn exchange(&self, code: &str) -> anyhow::Result<String> {
|
||||||
|
let token = self
|
||||||
|
.client
|
||||||
|
.exchange_code(AuthorizationCode::new(code.to_string()))
|
||||||
|
.request_async(async_http_client)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(token.access_token().secret().clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
108
como_auth/src/session.rs
Normal file
108
como_auth/src/session.rs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use async_sqlx_session::PostgresSessionStore;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use axum_sessions::async_session::{Session as AxumSession, SessionStore as AxumSessionStore};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{AuthClap, SessionBackend};
|
||||||
|
|
||||||
|
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SessionClap {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub postgresql: PostgresqlSessionClap,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct PostgresqlSessionClap {
|
||||||
|
#[arg(env = "SESSION_POSTGRES_CONN", long = "session-postgres-conn")]
|
||||||
|
pub conn: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Session {
|
||||||
|
async fn insert_user(&self, id: &str, user_id: &str) -> anyhow::Result<String>;
|
||||||
|
async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<String>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SessionService(Arc<dyn Session + Send + Sync + 'static>);
|
||||||
|
impl SessionService {
|
||||||
|
pub async fn new(config: &AuthClap) -> anyhow::Result<Self> {
|
||||||
|
match config.session_backend {
|
||||||
|
SessionBackend::InMemory => Ok(Self(Arc::new(InMemorySessionService {}))),
|
||||||
|
SessionBackend::Postgresql => {
|
||||||
|
Ok(Self(Arc::new(PostgresSessionService { store: todo!() })))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for SessionService {
|
||||||
|
type Target = Arc<dyn Session + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PostgresSessionService {
|
||||||
|
store: PostgresSessionStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Session for PostgresSessionService {
|
||||||
|
async fn insert_user(&self, id: &str, user_id: &str) -> anyhow::Result<String> {
|
||||||
|
let mut session = AxumSession::new();
|
||||||
|
session.insert(
|
||||||
|
"user",
|
||||||
|
User {
|
||||||
|
id: user_id.to_string(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let cookie = self
|
||||||
|
.store
|
||||||
|
.store_session(session)
|
||||||
|
.await?
|
||||||
|
.ok_or(anyhow::anyhow!("failed to store session"))?;
|
||||||
|
|
||||||
|
Ok(cookie)
|
||||||
|
}
|
||||||
|
async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<String>> {
|
||||||
|
if let Some(session) = self.store.load_session(cookie.to_string()).await.unwrap() {
|
||||||
|
if let Some(user) = session.get::<User>("user") {
|
||||||
|
tracing::debug!(
|
||||||
|
"UserFromSession: session decoded success, user_id={:?}",
|
||||||
|
user.id
|
||||||
|
);
|
||||||
|
Ok(Some(user.id))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::debug!(
|
||||||
|
"UserIdFromSession: err session not exists in store, {}",
|
||||||
|
cookie
|
||||||
|
);
|
||||||
|
Err(anyhow::anyhow!("No session found for cookie"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InMemorySessionService {}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Session for InMemorySessionService {
|
||||||
|
async fn insert_user(&self, id: &str, user_id: &str) -> anyhow::Result<String> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<String>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
como_core.workspace = true
|
como_core.workspace = true
|
||||||
como_domain.workspace = true
|
como_domain.workspace = true
|
||||||
|
como_auth.workspace = true
|
||||||
|
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
|
use como_auth::AuthClap;
|
||||||
|
|
||||||
#[derive(clap::Parser)]
|
#[derive(clap::Parser)]
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
@ -18,6 +19,9 @@ pub struct AppConfig {
|
|||||||
pub seed: bool,
|
pub seed: bool,
|
||||||
#[clap(long, env)]
|
#[clap(long, env)]
|
||||||
pub cors_origin: String,
|
pub cors_origin: String,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub auth: AuthClap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, ValueEnum)]
|
#[derive(Clone, Debug, ValueEnum)]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_sqlx_session::PostgresSessionStore;
|
use async_sqlx_session::PostgresSessionStore;
|
||||||
|
use como_auth::AuthService;
|
||||||
use como_core::{items::DynItemService, projects::DynProjectService, users::DynUserService};
|
use como_core::{items::DynItemService, projects::DynProjectService, users::DynUserService};
|
||||||
use tracing::log::info;
|
use tracing::log::info;
|
||||||
|
|
||||||
@ -20,12 +21,15 @@ pub struct ServiceRegister {
|
|||||||
pub project_service: DynProjectService,
|
pub project_service: DynProjectService,
|
||||||
pub user_service: DynUserService,
|
pub user_service: DynUserService,
|
||||||
pub session_store: PostgresSessionStore,
|
pub session_store: PostgresSessionStore,
|
||||||
|
pub auth_service: AuthService,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceRegister {
|
impl ServiceRegister {
|
||||||
pub async fn new(pool: ConnectionPool, config: Arc<AppConfig>) -> anyhow::Result<Self> {
|
pub async fn new(pool: ConnectionPool, config: Arc<AppConfig>) -> anyhow::Result<Self> {
|
||||||
info!("creating services");
|
info!("creating services");
|
||||||
|
|
||||||
|
let auth = AuthService::new(&config.auth).await?;
|
||||||
|
|
||||||
let s = match config.database_type {
|
let s = match config.database_type {
|
||||||
DatabaseType::Postgres => {
|
DatabaseType::Postgres => {
|
||||||
let item_service =
|
let item_service =
|
||||||
@ -42,6 +46,7 @@ impl ServiceRegister {
|
|||||||
user_service,
|
user_service,
|
||||||
project_service,
|
project_service,
|
||||||
session_store: store,
|
session_store: store,
|
||||||
|
auth_service: auth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DatabaseType::InMemory => {
|
DatabaseType::InMemory => {
|
||||||
@ -57,6 +62,7 @@ impl ServiceRegister {
|
|||||||
user_service,
|
user_service,
|
||||||
project_service,
|
project_service,
|
||||||
session_store: store,
|
session_store: store,
|
||||||
|
auth_service: auth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user