Compare commits
No commits in common. "f586d157b1926b9d42f2e9fdfca043f91ca07884" and "c46bd34e168728875df040a8208326fd7420e565" have entirely different histories.
f586d157b1
...
c46bd34e16
1169
Cargo.lock
generated
1169
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
42
Cargo.toml
42
Cargo.toml
@ -5,40 +5,34 @@ resolver = "2"
|
||||
[workspace.dependencies]
|
||||
nefarious-login = { path = "crates/nefarious-login" }
|
||||
|
||||
anyhow = { version = "1" }
|
||||
anyhow = { version = "1.0.75" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
|
||||
clap = { version = "4", features = ["derive", "env"] }
|
||||
async-trait = { version = "0.1", features = [] }
|
||||
clap = {version = "4.4.7", features = ["derive", "env"]}
|
||||
async-trait = {version = "0.1.74", features = []}
|
||||
|
||||
axum = { version = "0.7.1", features = [
|
||||
"macros",
|
||||
] }
|
||||
axum-extra = { version = "0.9.0", features = [
|
||||
"cookie",
|
||||
"cookie-private",
|
||||
"typed-header",
|
||||
] }
|
||||
axum-sessions = { version = "0.6.1", features = [] }
|
||||
async-sqlx-session = { version = "0.4.0", features = ["pg"] }
|
||||
axum = {version = "0.6.20", features = []}
|
||||
axum-extra = {version = "0.8.0", features = ["cookie", "cookie-private"]}
|
||||
axum-sessions = {version = "0.6.1", features = []}
|
||||
async-sqlx-session = {version = "0.4.0", features = ["pg"]}
|
||||
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1" }
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
serde_json = {version = "1.0.108"}
|
||||
|
||||
uuid = {version = "1.5.0", features = ["v4"]}
|
||||
sqlx = { version = "0.7", features = [
|
||||
"runtime-tokio",
|
||||
"postgres",
|
||||
uuid = {version = "1.5.0", features = []}
|
||||
sqlx = { version = "0.7.2", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"postgres",
|
||||
"migrate",
|
||||
] }
|
||||
|
||||
zitadel = { version = "3.4", features = ["axum"] }
|
||||
tower = "0.4"
|
||||
tower-http = { version = "0.4", features = ["cors", "trace"] }
|
||||
zitadel = { version = "3.4.29", features = ["axum"] }
|
||||
tower = "0.4.13"
|
||||
tower-http = { version = "0.4.4", features = ["cors", "trace"] }
|
||||
oauth2 = "4.4.2"
|
||||
openidconnect = "3.4"
|
||||
openidconnect = "3.4.0"
|
||||
|
||||
pretty_assertions = "1.4.0"
|
||||
sealed_test = "1.0.0"
|
||||
|
@ -26,4 +26,4 @@ openidconnect.workspace = true
|
||||
[dev-dependencies]
|
||||
tokio.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
sealed_test.workspace = true
|
||||
sealed_test.workspace = true
|
@ -6,22 +6,17 @@ use axum::http::{header::SET_COOKIE, HeaderMap};
|
||||
use oauth2::url::Url;
|
||||
|
||||
use crate::{
|
||||
introspection::{IdToken, IntrospectionService},
|
||||
introspection::IntrospectionService,
|
||||
login::{auth_clap::AuthEngine, config::ConfigClap, AuthClap},
|
||||
oauth::{zitadel::ZitadelConfig, OAuth},
|
||||
session::{AppSession, SessionService, User},
|
||||
session::{SessionService, User},
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
pub trait Auth {
|
||||
async fn login(&self, return_url: Option<String>) -> anyhow::Result<(HeaderMap, Url)>;
|
||||
async fn login_token(&self, user: &str, password: &str) -> anyhow::Result<IdToken>;
|
||||
async fn login_authorized(
|
||||
&self,
|
||||
code: &str,
|
||||
state: &str,
|
||||
app_session_cookie: Option<String>,
|
||||
) -> anyhow::Result<(HeaderMap, Url)>;
|
||||
async fn login(&self) -> anyhow::Result<Url>;
|
||||
async fn login_token(&self, user: &str, password: &str) -> anyhow::Result<String>;
|
||||
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>;
|
||||
}
|
||||
|
||||
@ -31,17 +26,12 @@ 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 => {
|
||||
let session = SessionService::new(config).await?;
|
||||
|
||||
Ok(Self::new_noop(session, &config.config))
|
||||
}
|
||||
AuthEngine::Noop => Ok(Self::new_noop()),
|
||||
AuthEngine::Zitadel => {
|
||||
let session = SessionService::new(config).await?;
|
||||
let oauth: OAuth = ZitadelConfig::try_from(config.zitadel.clone())?.into();
|
||||
let introspection: IntrospectionService =
|
||||
IntrospectionService::new_zitadel(config).await?;
|
||||
|
||||
Ok(Self::new_zitadel(
|
||||
oauth,
|
||||
introspection,
|
||||
@ -66,11 +56,8 @@ impl AuthService {
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn new_noop(session: SessionService, config: &ConfigClap) -> Self {
|
||||
Self(Arc::new(NoopAuthService {
|
||||
session,
|
||||
config: config.clone(),
|
||||
}))
|
||||
pub fn new_noop() -> Self {
|
||||
Self(Arc::new(NoopAuthService {}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,107 +76,18 @@ pub struct ZitadelAuthService {
|
||||
config: ConfigClap,
|
||||
}
|
||||
pub static COOKIE_NAME: &str = "SESSION";
|
||||
pub static COOKIE_APP_SESSION_NAME: &str = "APP_SESSION";
|
||||
|
||||
#[async_trait]
|
||||
impl Auth for ZitadelAuthService {
|
||||
async fn login(&self, return_url: Option<String>) -> anyhow::Result<(HeaderMap, Url)> {
|
||||
let mut headers = HeaderMap::new();
|
||||
if let Some(return_url) = return_url.clone() {
|
||||
let cookie_value = self.session.insert(AppSession { return_url }).await?;
|
||||
|
||||
let cookie = format!(
|
||||
"{}={}; SameSite=Lax; Path=/",
|
||||
COOKIE_APP_SESSION_NAME, cookie_value
|
||||
);
|
||||
headers.insert(SET_COOKIE, cookie.parse().unwrap());
|
||||
}
|
||||
|
||||
async fn login(&self) -> anyhow::Result<Url> {
|
||||
let authorize_url = self.oauth.authorize_url().await?;
|
||||
|
||||
Ok((headers, authorize_url))
|
||||
Ok(authorize_url)
|
||||
}
|
||||
async fn login_authorized(
|
||||
&self,
|
||||
code: &str,
|
||||
_state: &str,
|
||||
app_session_cookie: Option<String>,
|
||||
) -> anyhow::Result<(HeaderMap, Url)> {
|
||||
async fn login_authorized(&self, code: &str, _state: &str) -> anyhow::Result<(HeaderMap, Url)> {
|
||||
let token = self.oauth.exchange(code).await?;
|
||||
let id_token = self.introspection.get_id_token(token.as_str()).await?;
|
||||
let cookie_value = self.session.insert_user("user", id_token).await?;
|
||||
|
||||
let cookie = format!("{}={}; SameSite=Lax; Path=/", COOKIE_NAME, cookie_value);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(SET_COOKIE, cookie.parse().unwrap());
|
||||
|
||||
let mut return_url = self.config.return_url.clone();
|
||||
if let Some(cookie) = app_session_cookie {
|
||||
if let Some(session) = self.session.get(&cookie).await? {
|
||||
if session.return_url.starts_with('/') {
|
||||
let mut url = Url::parse(&return_url)?;
|
||||
url.set_path(&session.return_url);
|
||||
return_url = url.to_string();
|
||||
} else {
|
||||
return_url = session.return_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
headers,
|
||||
Url::parse(&return_url)
|
||||
.context("failed to parse login_authorized zitadel return url")?,
|
||||
))
|
||||
}
|
||||
async fn login_token(&self, _user: &str, password: &str) -> anyhow::Result<IdToken> {
|
||||
self.introspection.get_id_token(password).await
|
||||
}
|
||||
async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User> {
|
||||
match self.session.get_user(cookie).await? {
|
||||
Some(u) => Ok(u),
|
||||
None => Err(anyhow::anyhow!("failed to find user")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NoopAuthService {
|
||||
session: SessionService,
|
||||
config: ConfigClap,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Auth for NoopAuthService {
|
||||
async fn login(&self, return_url: Option<String>) -> anyhow::Result<(HeaderMap, Url)> {
|
||||
let url = Url::parse(&format!(
|
||||
"{}/auth/authorized?code=noop&state=noop",
|
||||
self.config
|
||||
.return_url
|
||||
.rsplit_once('/')
|
||||
.map(|(a, _)| a)
|
||||
.unwrap()
|
||||
))
|
||||
.unwrap();
|
||||
Ok((HeaderMap::new(), url))
|
||||
}
|
||||
async fn login_authorized(
|
||||
&self,
|
||||
_code: &str,
|
||||
_state: &str,
|
||||
_app_session_cookie: Option<String>,
|
||||
) -> anyhow::Result<(HeaderMap, Url)> {
|
||||
let cookie_value = self
|
||||
.session
|
||||
.insert_user(
|
||||
"user",
|
||||
IdToken {
|
||||
sub: uuid::Uuid::new_v4().to_string(),
|
||||
email: format!("{}@kjuulh.io", uuid::Uuid::new_v4()),
|
||||
name: uuid::Uuid::new_v4().to_string(),
|
||||
},
|
||||
)
|
||||
.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);
|
||||
|
||||
@ -202,15 +100,37 @@ impl Auth for NoopAuthService {
|
||||
.context("failed to parse login_authorized zitadel return url")?,
|
||||
))
|
||||
}
|
||||
|
||||
async fn login_token(&self, _user: &str, _password: &str) -> anyhow::Result<IdToken> {
|
||||
todo!()
|
||||
async fn login_token(&self, _user: &str, password: &str) -> anyhow::Result<String> {
|
||||
self.introspection.get_id_token(password).await
|
||||
}
|
||||
|
||||
async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User> {
|
||||
match self.session.get_user(cookie).await? {
|
||||
Some(u) => Ok(u),
|
||||
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 login_token(&self, _user: &str, _password: &str) -> anyhow::Result<String> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_user_from_session(&self, _cookie: &str) -> anyhow::Result<User> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,25 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use axum::extract::{FromRef, FromRequestParts, Query, State};
|
||||
|
||||
use axum::headers::authorization::Basic;
|
||||
use axum::headers::{Authorization, Cookie};
|
||||
use axum::http::request::Parts;
|
||||
use axum::http::StatusCode;
|
||||
|
||||
use axum::response::{ErrorResponse, IntoResponse, Redirect};
|
||||
use axum::routing::get;
|
||||
use axum::{async_trait, Json, RequestPartsExt, Router};
|
||||
use axum::{async_trait, Json, RequestPartsExt, Router, TypedHeader};
|
||||
|
||||
use axum_extra::extract::CookieJar;
|
||||
use axum_extra::headers::authorization::Basic;
|
||||
use axum_extra::headers::{Authorization, Cookie};
|
||||
use axum_extra::TypedHeader;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::auth::{AuthService, COOKIE_APP_SESSION_NAME};
|
||||
use crate::auth::AuthService;
|
||||
use crate::session::User;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ZitadelAuthParams {
|
||||
#[allow(dead_code)]
|
||||
return_url: Option<String>,
|
||||
}
|
||||
|
||||
@ -51,9 +52,9 @@ where
|
||||
pub async fn zitadel_auth(
|
||||
State(auth_service): State<AuthService>,
|
||||
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||
let (headers, url) = auth_service.login(None).await.into_response()?;
|
||||
let url = auth_service.login().await.into_response()?;
|
||||
|
||||
Ok((headers, Redirect::to(url.as_ref())))
|
||||
Ok(Redirect::to(url.as_ref()))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -66,14 +67,9 @@ pub struct AuthRequest {
|
||||
pub async fn login_authorized(
|
||||
Query(query): Query<AuthRequest>,
|
||||
State(auth_service): State<AuthService>,
|
||||
cookie_jar: CookieJar,
|
||||
) -> Result<impl IntoResponse, ErrorResponse> {
|
||||
let cookie_value = cookie_jar
|
||||
.get(COOKIE_APP_SESSION_NAME)
|
||||
.map(|c| c.value().to_string());
|
||||
|
||||
let (headers, url) = auth_service
|
||||
.login_authorized(&query.code, &query.state, cookie_value)
|
||||
.login_authorized(&query.code, &query.state)
|
||||
.await
|
||||
.into_response()?;
|
||||
|
||||
@ -126,11 +122,7 @@ where
|
||||
})?;
|
||||
|
||||
return Ok(UserFromSession {
|
||||
user: User {
|
||||
id: token.sub,
|
||||
email: token.email,
|
||||
name: token.name,
|
||||
},
|
||||
user: User { id: token },
|
||||
});
|
||||
}
|
||||
|
||||
@ -153,6 +145,8 @@ where
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(UserFromSession { user })
|
||||
Ok(UserFromSession {
|
||||
user: User { id: user.id },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ use async_trait::async_trait;
|
||||
use axum::extract::FromRef;
|
||||
use oauth2::TokenIntrospectionResponse;
|
||||
use openidconnect::IntrospectionUrl;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use zitadel::{
|
||||
axum::introspection::IntrospectionStateBuilderError,
|
||||
credentials::Application,
|
||||
@ -16,17 +15,10 @@ use zitadel::{
|
||||
|
||||
use crate::login::AuthClap;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct IdToken {
|
||||
pub sub: String,
|
||||
pub email: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Introspection {
|
||||
async fn get_user(&self) -> anyhow::Result<()>;
|
||||
async fn get_id_token(&self, token: &str) -> anyhow::Result<IdToken>;
|
||||
async fn get_id_token(&self, token: &str) -> anyhow::Result<String>;
|
||||
}
|
||||
|
||||
pub struct IntrospectionService(Arc<dyn Introspection + Send + Sync + 'static>);
|
||||
@ -69,7 +61,7 @@ impl Introspection for ZitadelIntrospection {
|
||||
async fn get_user(&self) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
async fn get_id_token(&self, token: &str) -> anyhow::Result<IdToken> {
|
||||
async fn get_id_token(&self, token: &str) -> anyhow::Result<String> {
|
||||
let config = &self.state.config;
|
||||
let res = introspect(
|
||||
&config.introspection_uri,
|
||||
@ -79,21 +71,10 @@ impl Introspection for ZitadelIntrospection {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let sub = res
|
||||
Ok(res
|
||||
.sub()
|
||||
.ok_or(anyhow::anyhow!("could not find a userid (sub) in token"))?
|
||||
.to_string();
|
||||
|
||||
let extra = res.extra_fields();
|
||||
let email = extra.email.clone().ok_or(anyhow::anyhow!(
|
||||
"could not find a email (scope email) in token"
|
||||
))?;
|
||||
|
||||
let name = extra.name.clone().ok_or(anyhow::anyhow!(
|
||||
"could not find a name (scope profile) in token"
|
||||
))?;
|
||||
|
||||
Ok(IdToken { sub, email, name })
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ pub struct AuthConfigFile {
|
||||
zitadel: Option<ZitadelClap>,
|
||||
}
|
||||
|
||||
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[group(requires_all = ["client_id", "client_secret", "redirect_url", "authority_url"])]
|
||||
pub struct ZitadelClap {
|
||||
#[arg(env = "ZITADEL_CLIENT_ID", long = "zitadel-client-id")]
|
||||
|
@ -105,15 +105,12 @@ impl OAuthClient for ZitadelOAuthClient {
|
||||
Ok(())
|
||||
}
|
||||
async fn authorize_url(&self) -> anyhow::Result<Url> {
|
||||
let req = self
|
||||
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()))
|
||||
.add_scope(Scope::new("email".to_string()))
|
||||
.add_scope(Scope::new("profile".to_string()));
|
||||
|
||||
let (auth_url, _csrf_token) = req.url();
|
||||
.url();
|
||||
|
||||
Ok(auth_url)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use async_trait::async_trait;
|
||||
use axum_sessions::async_session::{Session as AxumSession, SessionStore as AxumSessionStore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{introspection::IdToken, login::AuthClap};
|
||||
use crate::login::AuthClap;
|
||||
|
||||
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SessionClap {
|
||||
@ -19,7 +19,7 @@ pub struct SessionClap {
|
||||
pub postgresql: PostgresqlSessionClap,
|
||||
}
|
||||
|
||||
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[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>,
|
||||
@ -27,17 +27,16 @@ pub struct PostgresqlSessionClap {
|
||||
|
||||
#[async_trait]
|
||||
pub trait Session {
|
||||
async fn insert(&self, app_session: AppSession) -> anyhow::Result<String>;
|
||||
async fn insert_user(&self, id: &str, id_token: IdToken) -> anyhow::Result<String>;
|
||||
async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<User>>;
|
||||
async fn get(&self, cookie: &str) -> anyhow::Result<Option<AppSession>>;
|
||||
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::default()))),
|
||||
SessionBackend::InMemory => Ok(Self(Arc::new(InMemorySessionService {}))),
|
||||
SessionBackend::Postgresql => {
|
||||
let postgres_session = PostgresSessionStore::new(
|
||||
config
|
||||
@ -49,8 +48,6 @@ impl SessionService {
|
||||
)
|
||||
.await?;
|
||||
|
||||
postgres_session.migrate().await?;
|
||||
|
||||
Ok(Self(Arc::new(PostgresSessionService {
|
||||
store: postgres_session,
|
||||
})))
|
||||
@ -74,38 +71,16 @@ pub struct PostgresSessionService {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub email: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct AppSession {
|
||||
pub return_url: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Session for PostgresSessionService {
|
||||
async fn insert(&self, app_session: AppSession) -> anyhow::Result<String> {
|
||||
let mut session = AxumSession::new();
|
||||
session.insert("app_session", app_session)?;
|
||||
|
||||
let cookie = self
|
||||
.store
|
||||
.store_session(session)
|
||||
.await?
|
||||
.ok_or(anyhow::anyhow!("failed to store app session"))?;
|
||||
|
||||
Ok(cookie)
|
||||
}
|
||||
|
||||
async fn insert_user(&self, _id: &str, id_token: IdToken) -> anyhow::Result<String> {
|
||||
async fn insert_user(&self, _id: &str, user_id: &str) -> anyhow::Result<String> {
|
||||
let mut session = AxumSession::new();
|
||||
session.insert(
|
||||
"user",
|
||||
User {
|
||||
id: id_token.sub,
|
||||
email: id_token.email,
|
||||
name: id_token.name,
|
||||
id: user_id.to_string(),
|
||||
},
|
||||
)?;
|
||||
|
||||
@ -117,14 +92,14 @@ impl Session for PostgresSessionService {
|
||||
|
||||
Ok(cookie)
|
||||
}
|
||||
async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<User>> {
|
||||
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))
|
||||
Ok(Some(user.id))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@ -136,50 +111,17 @@ impl Session for PostgresSessionService {
|
||||
Err(anyhow::anyhow!("No session found for cookie"))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get(&self, cookie: &str) -> anyhow::Result<Option<AppSession>> {
|
||||
let Some(session) = self.store.load_session(cookie.to_string()).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(session) = session.get::<AppSession>("app_session") else {
|
||||
anyhow::bail!("failed to deserialize app_session from cookie");
|
||||
};
|
||||
|
||||
Ok(Some(session))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InMemorySessionService {
|
||||
store: std::sync::Mutex<std::collections::BTreeMap<String, User>>,
|
||||
}
|
||||
pub struct InMemorySessionService {}
|
||||
|
||||
#[async_trait]
|
||||
impl Session for InMemorySessionService {
|
||||
async fn insert(&self, app_session: AppSession) -> anyhow::Result<String> {
|
||||
async fn insert_user(&self, _id: &str, _user_id: &str) -> anyhow::Result<String> {
|
||||
todo!()
|
||||
}
|
||||
async fn insert_user(&self, _id: &str, id_token: IdToken) -> anyhow::Result<String> {
|
||||
let user = User {
|
||||
id: id_token.sub,
|
||||
email: id_token.email,
|
||||
name: id_token.name,
|
||||
};
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
self.store.lock().unwrap().insert(id.to_string(), user);
|
||||
|
||||
Ok(id.to_string())
|
||||
}
|
||||
|
||||
async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<User>> {
|
||||
let user = self.store.lock().unwrap().get(cookie).cloned();
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
async fn get(&self, cookie: &str) -> anyhow::Result<Option<AppSession>> {
|
||||
async fn get_user(&self, _cookie: &str) -> anyhow::Result<Option<String>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,3 @@ base: "git@git.front.kjuulh.io:kjuulh/cuddle-base.git"
|
||||
vars:
|
||||
service: "nefarious-login"
|
||||
registry: kasperhermansen
|
||||
|
||||
scripts:
|
||||
local_up:
|
||||
type: shell
|
||||
local_down:
|
||||
type: shell
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{net::SocketAddr, str::FromStr};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::{
|
||||
extract::{FromRef, State},
|
||||
@ -60,10 +60,13 @@ async fn main() -> anyhow::Result<()> {
|
||||
.with_state(state)
|
||||
.nest("/auth", AuthController::new_router(auth_service).await?);
|
||||
|
||||
let addr = SocketAddr::from_str(&format!("{}:{}", "127.0.0.1", "3000"))?;
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||
|
||||
axum::serve(listener, app).await?;
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3001));
|
||||
println!("listening on: {addr}");
|
||||
println!("open browser at: http://localhost:3001/auth/zitadel");
|
||||
axum::Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{net::SocketAddr, str::FromStr};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::{
|
||||
extract::{FromRef, State},
|
||||
@ -58,11 +58,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3001));
|
||||
println!("listening on: {addr}");
|
||||
println!("open browser at: http://localhost:3001/auth/zitadel");
|
||||
|
||||
let addr = SocketAddr::from_str(&format!("{}:{}", "127.0.0.1", "3000"))?;
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||
|
||||
axum::serve(listener, app).await?;
|
||||
axum::Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "custom_redirect"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nefarious-login.workspace = true
|
||||
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
axum.workspace = true
|
||||
clap.workspace = true
|
||||
|
||||
tracing-subscriber.workspace = true
|
@ -1,94 +0,0 @@
|
||||
use std::{net::SocketAddr, str::FromStr};
|
||||
|
||||
use axum::{
|
||||
extract::{FromRef, State},
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use nefarious_login::{
|
||||
auth::AuthService,
|
||||
axum::{AuthController, UserFromSession},
|
||||
login::{
|
||||
auth_clap::{AuthEngine, ZitadelClap},
|
||||
config::ConfigClap,
|
||||
AuthClap,
|
||||
},
|
||||
session::{PostgresqlSessionClap, SessionBackend},
|
||||
};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
auth: AuthService,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let auth = AuthClap {
|
||||
engine: AuthEngine::Zitadel,
|
||||
session_backend: SessionBackend::Postgresql,
|
||||
zitadel: ZitadelClap {
|
||||
authority_url: Some("https://personal-wxuujs.zitadel.cloud".into()),
|
||||
client_id: Some("237412977047895154@nefarious-test".into()),
|
||||
client_secret: Some(
|
||||
"rWwDi8gjNOyuMFKoOjNSlhjcVZ1B25wDh6HsDL27f0g2Hb0xGbvEf0WXFY2akOlL".into(),
|
||||
),
|
||||
redirect_url: Some("http://localhost:3001/auth/authorized".into()),
|
||||
},
|
||||
session: nefarious_login::session::SessionClap {
|
||||
postgresql: PostgresqlSessionClap {
|
||||
conn: Some("postgres://nefarious-test:somenotverysecurepassword@localhost:5432/nefarious-test".into()),
|
||||
},
|
||||
},
|
||||
config: ConfigClap { return_url: "http://localhost:3001/authed".into() } // this normally has /authed
|
||||
};
|
||||
|
||||
let auth_service = AuthService::new(&auth).await?;
|
||||
|
||||
let state = AppState {
|
||||
auth: auth_service.clone(),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/unauthed", get(unauthed))
|
||||
.route("/authed", get(authed))
|
||||
.route("/login", get(login))
|
||||
.with_state(state)
|
||||
.nest("/auth", AuthController::new_router(auth_service).await?);
|
||||
|
||||
let addr = SocketAddr::from_str(&format!("{}:{}", "127.0.0.1", "3000"))?;
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for AuthService {
|
||||
fn from_ref(input: &AppState) -> Self {
|
||||
input.auth.clone()
|
||||
}
|
||||
}
|
||||
|
||||
async fn login(State(auth_service): State<AuthService>) -> impl IntoResponse {
|
||||
let (headers, url) = auth_service.login(Some("/authed".into())).await.unwrap();
|
||||
|
||||
(headers, Redirect::to(url.as_ref()))
|
||||
}
|
||||
|
||||
async fn unauthed() -> String {
|
||||
"Hello Unauthorized User".into()
|
||||
}
|
||||
|
||||
#[axum::debug_handler()]
|
||||
async fn authed(
|
||||
user: UserFromSession,
|
||||
State(_auth_service): State<AuthService>,
|
||||
) -> impl IntoResponse {
|
||||
format!("Hello authorized user: {:?}", user.user.id)
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "noop"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nefarious-login.workspace = true
|
||||
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
axum.workspace = true
|
||||
clap.workspace = true
|
||||
|
||||
tracing-subscriber.workspace = true
|
@ -1,84 +0,0 @@
|
||||
use std::{net::SocketAddr, str::FromStr};
|
||||
|
||||
use axum::{
|
||||
extract::{FromRef, State},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use nefarious_login::{
|
||||
auth::AuthService,
|
||||
axum::{AuthController, UserFromSession},
|
||||
login::{
|
||||
auth_clap::{AuthEngine, ZitadelClap},
|
||||
config::ConfigClap,
|
||||
AuthClap,
|
||||
},
|
||||
session::{PostgresqlSessionClap, SessionBackend},
|
||||
};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
auth: AuthService,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let auth = AuthClap {
|
||||
engine: AuthEngine::Noop,
|
||||
session_backend: SessionBackend::InMemory,
|
||||
zitadel: ZitadelClap {
|
||||
..Default::default()
|
||||
},
|
||||
session: nefarious_login::session::SessionClap {
|
||||
postgresql: PostgresqlSessionClap {
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
config: ConfigClap {
|
||||
return_url: "http://localhost:3000/authed".into(),
|
||||
},
|
||||
};
|
||||
|
||||
let auth_service = AuthService::new(&auth).await?;
|
||||
|
||||
let state = AppState {
|
||||
auth: auth_service.clone(),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/unauthed", get(unauthed))
|
||||
.route("/authed", get(authed))
|
||||
.with_state(state)
|
||||
.nest("/auth", AuthController::new_router(auth_service).await?);
|
||||
|
||||
let addr = SocketAddr::from_str(&format!("{}:{}", "127.0.0.1", "3000"))?;
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for AuthService {
|
||||
fn from_ref(input: &AppState) -> Self {
|
||||
input.auth.clone()
|
||||
}
|
||||
}
|
||||
|
||||
async fn unauthed() -> String {
|
||||
"Hello Unauthorized User".into()
|
||||
}
|
||||
|
||||
#[axum::debug_handler()]
|
||||
async fn authed(
|
||||
user: UserFromSession,
|
||||
State(_auth_service): State<AuthService>,
|
||||
) -> impl IntoResponse {
|
||||
format!("Hello authorized user: {:?}", user.user.id)
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cuddle render_template --template-file $TMP/docker-compose.local_up.yml.tmpl --dest $TMP/docker-compose.local_up.yml
|
||||
|
||||
docker compose -f $TMP/docker-compose.local_up.yml down -v
|
@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cuddle render_template --template-file $TMP/docker-compose.local_up.yml.tmpl --dest $TMP/docker-compose.local_up.yml
|
||||
|
||||
docker compose -f $TMP/docker-compose.local_up.yml up -d --remove-orphans --build
|
||||
|
||||
sleep 3
|
@ -1,7 +0,0 @@
|
||||
target/
|
||||
.git/
|
||||
.cuddle/
|
||||
scripts/
|
||||
cuddle.yaml
|
||||
local.sh
|
||||
README.md
|
@ -1,17 +0,0 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: bitnami/postgresql:16
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRESQL_USERNAME=nefarious-test
|
||||
- POSTGRESQL_DATABASE=nefarious-test
|
||||
- POSTGRESQL_PASSWORD=somenotverysecurepassword
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
pgdata:
|
Loading…
x
Reference in New Issue
Block a user