Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
835cd32fb1
commit
d4a162876a
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -2233,6 +2233,18 @@ dependencies = [
|
|||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "noop"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"axum 0.6.16",
|
||||||
|
"clap 4.4.7",
|
||||||
|
"nefarious-login",
|
||||||
|
"tokio",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
@ -4089,6 +4101,9 @@ name = "uuid"
|
|||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
|
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
|
@ -27,7 +27,7 @@ async-sqlx-session = { version = "0.4.0", features = ["pg"] }
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = { version = "1" }
|
serde_json = { version = "1" }
|
||||||
|
|
||||||
uuid = { version = "1.5", features = [] }
|
uuid = {version = "1.5.0", features = ["v4"]}
|
||||||
sqlx = { version = "0.7", features = [
|
sqlx = { version = "0.7", features = [
|
||||||
"runtime-tokio",
|
"runtime-tokio",
|
||||||
"postgres",
|
"postgres",
|
||||||
|
@ -26,12 +26,17 @@ pub struct AuthService(Arc<dyn Auth + Send + Sync + 'static>);
|
|||||||
impl AuthService {
|
impl AuthService {
|
||||||
pub async fn new(config: &AuthClap) -> anyhow::Result<Self> {
|
pub async fn new(config: &AuthClap) -> anyhow::Result<Self> {
|
||||||
match config.engine {
|
match config.engine {
|
||||||
AuthEngine::Noop => Ok(Self::new_noop()),
|
AuthEngine::Noop => {
|
||||||
|
let session = SessionService::new(config).await?;
|
||||||
|
|
||||||
|
Ok(Self::new_noop(session, &config.config))
|
||||||
|
}
|
||||||
AuthEngine::Zitadel => {
|
AuthEngine::Zitadel => {
|
||||||
let session = SessionService::new(config).await?;
|
let session = SessionService::new(config).await?;
|
||||||
let oauth: OAuth = ZitadelConfig::try_from(config.zitadel.clone())?.into();
|
let oauth: OAuth = ZitadelConfig::try_from(config.zitadel.clone())?.into();
|
||||||
let introspection: IntrospectionService =
|
let introspection: IntrospectionService =
|
||||||
IntrospectionService::new_zitadel(config).await?;
|
IntrospectionService::new_zitadel(config).await?;
|
||||||
|
|
||||||
Ok(Self::new_zitadel(
|
Ok(Self::new_zitadel(
|
||||||
oauth,
|
oauth,
|
||||||
introspection,
|
introspection,
|
||||||
@ -56,8 +61,11 @@ impl AuthService {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_noop() -> Self {
|
pub fn new_noop(session: SessionService, config: &ConfigClap) -> Self {
|
||||||
Self(Arc::new(NoopAuthService {}))
|
Self(Arc::new(NoopAuthService {
|
||||||
|
session,
|
||||||
|
config: config.clone(),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,26 +119,62 @@ impl Auth for ZitadelAuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NoopAuthService {}
|
pub struct NoopAuthService {
|
||||||
|
session: SessionService,
|
||||||
|
config: ConfigClap,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Auth for NoopAuthService {
|
impl Auth for NoopAuthService {
|
||||||
async fn login(&self) -> anyhow::Result<Url> {
|
async fn login(&self) -> anyhow::Result<Url> {
|
||||||
todo!()
|
let url = Url::parse(&format!(
|
||||||
|
"{}/auth/authorized?code=noop&state=noop",
|
||||||
|
self.config
|
||||||
|
.return_url
|
||||||
|
.rsplit_once('/')
|
||||||
|
.map(|(a, b)| a)
|
||||||
|
.unwrap()
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
Ok(url)
|
||||||
}
|
}
|
||||||
async fn login_authorized(
|
async fn login_authorized(
|
||||||
&self,
|
&self,
|
||||||
_code: &str,
|
_code: &str,
|
||||||
_state: &str,
|
_state: &str,
|
||||||
) -> anyhow::Result<(HeaderMap, Url)> {
|
) -> anyhow::Result<(HeaderMap, Url)> {
|
||||||
todo!()
|
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 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(&self.config.return_url)
|
||||||
|
.context("failed to parse login_authorized zitadel return url")?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn login_token(&self, _user: &str, _password: &str) -> anyhow::Result<IdToken> {
|
async fn login_token(&self, _user: &str, _password: &str) -> anyhow::Result<IdToken> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_user_from_session(&self, _cookie: &str) -> anyhow::Result<User> {
|
async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User> {
|
||||||
todo!()
|
match self.session.get_user(cookie).await? {
|
||||||
|
Some(u) => Ok(u),
|
||||||
|
None => Err(anyhow::anyhow!("failed to find user")),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ pub struct AuthConfigFile {
|
|||||||
zitadel: Option<ZitadelClap>,
|
zitadel: Option<ZitadelClap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
#[group(requires_all = ["client_id", "client_secret", "redirect_url", "authority_url"])]
|
#[group(requires_all = ["client_id", "client_secret", "redirect_url", "authority_url"])]
|
||||||
pub struct ZitadelClap {
|
pub struct ZitadelClap {
|
||||||
#[arg(env = "ZITADEL_CLIENT_ID", long = "zitadel-client-id")]
|
#[arg(env = "ZITADEL_CLIENT_ID", long = "zitadel-client-id")]
|
||||||
|
@ -19,7 +19,7 @@ pub struct SessionClap {
|
|||||||
pub postgresql: PostgresqlSessionClap,
|
pub postgresql: PostgresqlSessionClap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
pub struct PostgresqlSessionClap {
|
pub struct PostgresqlSessionClap {
|
||||||
#[arg(env = "SESSION_POSTGRES_CONN", long = "session-postgres-conn")]
|
#[arg(env = "SESSION_POSTGRES_CONN", long = "session-postgres-conn")]
|
||||||
pub conn: Option<String>,
|
pub conn: Option<String>,
|
||||||
@ -36,7 +36,7 @@ pub struct SessionService(Arc<dyn Session + Send + Sync + 'static>);
|
|||||||
impl SessionService {
|
impl SessionService {
|
||||||
pub async fn new(config: &AuthClap) -> anyhow::Result<Self> {
|
pub async fn new(config: &AuthClap) -> anyhow::Result<Self> {
|
||||||
match config.session_backend {
|
match config.session_backend {
|
||||||
SessionBackend::InMemory => Ok(Self(Arc::new(InMemorySessionService {}))),
|
SessionBackend::InMemory => Ok(Self(Arc::new(InMemorySessionService::default()))),
|
||||||
SessionBackend::Postgresql => {
|
SessionBackend::Postgresql => {
|
||||||
let postgres_session = PostgresSessionStore::new(
|
let postgres_session = PostgresSessionStore::new(
|
||||||
config
|
config
|
||||||
@ -119,15 +119,30 @@ impl Session for PostgresSessionService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct InMemorySessionService {}
|
#[derive(Default)]
|
||||||
|
pub struct InMemorySessionService {
|
||||||
|
store: std::sync::Mutex<std::collections::BTreeMap<String, User>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Session for InMemorySessionService {
|
impl Session for InMemorySessionService {
|
||||||
async fn insert_user(&self, _id: &str, _id_token: IdToken) -> anyhow::Result<String> {
|
async fn insert_user(&self, _id: &str, id_token: IdToken) -> anyhow::Result<String> {
|
||||||
todo!()
|
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>> {
|
async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<User>> {
|
||||||
todo!()
|
let user = self.store.lock().unwrap().get(cookie).cloned();
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
examples/noop/Cargo.toml
Normal file
16
examples/noop/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[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
|
84
examples/noop/src/main.rs
Normal file
84
examples/noop/src/main.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user