From 0b34935b5a72302b8a5876de6705396278675208 Mon Sep 17 00:00:00 2001 From: Paul Zinselmeyer Date: Fri, 8 Mar 2024 17:17:00 +0100 Subject: [PATCH] Dependency Update, Added examples/basic, CI Updated the dependencies to the newest version. Added `AsRef` and `Deref` traits to the extractors. Moved the example from `README.md` to `examples/basic`. Added a CI Pipeline. --- .github/workflows/ci.yml | 34 ++++++++++++++++++ .gitignore | 2 +- Cargo.toml | 6 ++-- README.md | 47 ++----------------------- examples/basic/Cargo.toml | 13 +++++++ examples/basic/src/main.rs | 72 ++++++++++++++++++++++++++++++++++++++ src/extractor.rs | 33 +++++++++++++++++ 7 files changed, 158 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 examples/basic/Cargo.toml create mode 100644 examples/basic/src/main.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5a1c7a1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: Cargo Build & Test + +on: + push: + pull_request: + schedule: + - cron: '0 0 1,7,14,21 * *' + +env: + CARGO_TERM_COLOR: always + +jobs: + build_and_test: + name: axum-oidc - latest + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - stable + - nightly + steps: + - uses: actions/checkout@v3 + - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - run: cargo build --verbose + - run: cargo test --verbose + + build_examples: + name: axum-oidc - examples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: rustup update stable && rustup default stable + - run: cargo build --verbose + working-directory: ./examples/basic diff --git a/.gitignore b/.gitignore index 96ef6c0..a9d37c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -/target +target Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index c7b81d6..86da21a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,10 @@ axum-core = "0.4" axum = { version = "0.7", default-features = false, features = [ "query" ] } tower-service = "0.3.2" tower-layer = "0.3" -tower-sessions = { version = "0.9", default-features = false, features = [ "axum-core" ] } -http = "1.0" +tower-sessions = { version = "0.11", default-features = false, features = [ "axum-core" ] } +http = "1.1" async-trait = "0.1" -openidconnect = "3.4" +openidconnect = "3.5" serde = "1.0" futures-util = "0.3" reqwest = { version = "0.11", default-features = false } diff --git a/README.md b/README.md index 9a3c51d..11a3fe7 100644 --- a/README.md +++ b/README.md @@ -15,51 +15,8 @@ The `OidcAccessToken`-extractor can be used to get the OpenId Connect Access Tok Your OIDC-Client must be allowed to redirect to **every** subpath of your application base url. -```rust -#[tokio::main] -async fn main() { - - let session_store = MemoryStore::default(); - let session_service = ServiceBuilder::new() - .layer(HandleErrorLayer::new(|_: BoxError| async { - StatusCode::BAD_REQUEST - })) - .layer(SessionManagerLayer::new(session_store).with_same_site(SameSite::Lax)); - - let oidc_login_service = ServiceBuilder::new() - .layer(HandleErrorLayer::new(|e: MiddlewareError| async { - e.into_response() - })) - .layer(OidcLoginLayer::::new()); - - let oidc_auth_service = ServiceBuilder::new() - .layer(HandleErrorLayer::new(|e: MiddlewareError| async { - e.into_response() - })) - .layer( - OidcAuthLayer::::discover_client( - Uri::from_static("https://example.com"), - "".to_string(), - "".to_string(), - "".to_owned(), - vec![], - ).await.unwrap(), - ); - - let app = Router::new() - .route("/", get(|| async { "Hello, authenticated World!" })) - .layer(oidc_login_service) - .layer(oidc_auth_service) - .layer(session_service); - - let listener = TcpListener::bind("[::]:8080").await.unwrap(); - axum::serve(listener, app.into_make_service()).await.unwrap(); -} -``` - -# Example Projects -Here is a place for projects that are using this library. -- [zettoIT ARS - AudienceResponseSystem](https://git2.zettoit.eu/zettoit/ars) (by me) +# Examples +Take a look at the `examples` folder for examples. # Contributing I'm happy about any contribution in any form. diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml new file mode 100644 index 0000000..d57537a --- /dev/null +++ b/examples/basic/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "basic" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1.36.0", features = ["net", "macros"] } +axum = "0.7.4" +axum-oidc = { path = "./../.." } +tower = "0.4.13" +tower-sessions = "0.11.0" diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs new file mode 100644 index 0000000..e8cd78a --- /dev/null +++ b/examples/basic/src/main.rs @@ -0,0 +1,72 @@ +use axum::{ + error_handling::HandleErrorLayer, http::Uri, response::IntoResponse, routing::get, Router, +}; +use axum_oidc::{ + error::MiddlewareError, EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcLoginLayer, +}; +use tokio::net::TcpListener; +use tower::ServiceBuilder; +use tower_sessions::{ + cookie::{time::Duration, SameSite}, + Expiry, MemoryStore, SessionManagerLayer, +}; + +#[tokio::main] +async fn main() { + let session_store = MemoryStore::default(); + let session_layer = SessionManagerLayer::new(session_store) + .with_secure(false) + .with_same_site(SameSite::Lax) + .with_expiry(Expiry::OnInactivity(Duration::seconds(120))); + + let oidc_login_service = ServiceBuilder::new() + .layer(HandleErrorLayer::new(|e: MiddlewareError| async { + e.into_response() + })) + .layer(OidcLoginLayer::::new()); + + let oidc_auth_service = ServiceBuilder::new() + .layer(HandleErrorLayer::new(|e: MiddlewareError| async { + e.into_response() + })) + .layer( + OidcAuthLayer::::discover_client( + Uri::from_static("http://localhost:8080"), + "https://auth.zettoit.eu/realms/zettoit".to_string(), + "oxicloud".to_string(), + Some("IvBcDOfp9WBfGNmwIbiv67bxCwuQUGbl".to_owned()), + vec![], + ) + .await + .unwrap(), + ); + + let app = Router::new() + .route("/foo", get(authenticated)) + .layer(oidc_login_service) + .route("/bar", get(maybe_authenticated)) + .layer(oidc_auth_service) + .layer(session_layer); + + let listener = TcpListener::bind("[::]:8080").await.unwrap(); + axum::serve(listener, app.into_make_service()) + .await + .unwrap(); +} + +async fn authenticated(claims: OidcClaims) -> impl IntoResponse { + format!("Hello {}", claims.subject().as_str()) +} + +async fn maybe_authenticated( + claims: Option>, +) -> impl IntoResponse { + if let Some(claims) = claims { + format!( + "Hello {}! You are already logged in from another Handler.", + claims.subject().as_str() + ) + } else { + "Hello anon!".to_string() + } +} diff --git a/src/extractor.rs b/src/extractor.rs index 5e33b2a..5c18d78 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use crate::{error::ExtractorError, AdditionalClaims}; use async_trait::async_trait; use axum_core::extract::FromRequestParts; @@ -27,6 +29,23 @@ where } } +impl Deref for OidcClaims { + type Target = IdTokenClaims; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef> for OidcClaims +where + AC: AdditionalClaims, +{ + fn as_ref(&self) -> &IdTokenClaims { + &self.0 + } +} + /// Extractor for the OpenID Connect Access Token. /// /// This Extractor will only return the Access Token when the cached session is valid and [crate::middleware::OidcAuthMiddleware] is loaded. @@ -48,3 +67,17 @@ where .ok_or(ExtractorError::Unauthorized) } } + +impl Deref for OidcAccessToken { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef for OidcAccessToken { + fn as_ref(&self) -> &str { + self.0.as_str() + } +}