Merge branch 'ci'

Added CI and moved examples to `examples` folder.
This commit is contained in:
Paul Zinselmeyer 2024-03-08 17:23:11 +01:00
commit 2741a3ee28
Signed by: pfzetto
GPG key ID: B471A1AF06C895FD
7 changed files with 158 additions and 49 deletions

34
.github/workflows/ci.yml vendored Normal file
View file

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

2
.gitignore vendored
View file

@ -1,2 +1,2 @@
/target
target
Cargo.lock

View file

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

View file

@ -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::<EmptyAdditionalClaims>::new());
let oidc_auth_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
e.into_response()
}))
.layer(
OidcAuthLayer::<EmptyAdditionalClaims>::discover_client(
Uri::from_static("https://example.com"),
"<issuer>".to_string(),
"<client_id>".to_string(),
"<client_secret>".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.

13
examples/basic/Cargo.toml Normal file
View file

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

View file

@ -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::<EmptyAdditionalClaims>::new());
let oidc_auth_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
e.into_response()
}))
.layer(
OidcAuthLayer::<EmptyAdditionalClaims>::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<EmptyAdditionalClaims>) -> impl IntoResponse {
format!("Hello {}", claims.subject().as_str())
}
async fn maybe_authenticated(
claims: Option<OidcClaims<EmptyAdditionalClaims>>,
) -> 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()
}
}

View file

@ -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<AC: AdditionalClaims> Deref for OidcClaims<AC> {
type Target = IdTokenClaims<AC, CoreGenderClaim>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<AC> AsRef<IdTokenClaims<AC, CoreGenderClaim>> for OidcClaims<AC>
where
AC: AdditionalClaims,
{
fn as_ref(&self) -> &IdTokenClaims<AC, CoreGenderClaim> {
&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<str> for OidcAccessToken {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}