Compare commits

..

No commits in common. "f586d157b1926b9d42f2e9fdfca043f91ca07884" and "c46bd34e168728875df040a8208326fd7420e565" have entirely different histories.

20 changed files with 480 additions and 1327 deletions

1169
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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!()
}
}

View File

@ -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 },
})
}
}

View File

@ -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())
}
}

View File

@ -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")]

View File

@ -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)
}

View File

@ -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!()
}
}

View File

@ -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

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -1,7 +0,0 @@
target/
.git/
.cuddle/
scripts/
cuddle.yaml
local.sh
README.md

View File

@ -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: