feat: with noop
Some checks are pending
ci/woodpecker/pr/test Pipeline is pending

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2023-11-12 22:42:55 +01:00
parent 835cd32fb1
commit d4a162876a
Signed by: kjuulh
GPG Key ID: 57B6E1465221F912
7 changed files with 193 additions and 19 deletions

15
Cargo.lock generated
View File

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

View File

@ -27,9 +27,9 @@ 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",
"migrate", "migrate",
] } ] }

View File

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

View File

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

View File

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