diff --git a/.env.example b/.env.example index 518e1b5..a71d8c3 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,12 @@ -# Base URL of the OIDC provider (e.g. Keycloak realm URL) -OIDC_PROVIDER_URL=http://localhost:8080 +# Base URL of the OIDC provider. +# For local development, `docker-compose up -d` starts oidc-provider-mock +# at http://localhost:9400 (accepts any client ID/secret). +# For Keycloak use e.g. https://keycloak.example.com/realms/myrealm +OIDC_PROVIDER_URL=http://localhost:9400 # OAuth2 client credentials (required) -OIDC_CLIENT_ID=your-client-id -OIDC_CLIENT_SECRET=your-client-secret +OIDC_CLIENT_ID=dev-client +OIDC_CLIENT_SECRET=dev-secret # Full callback URL — must match the redirect URI configured at the provider OIDC_REDIRECT_URI=http://localhost:3000/auth/callback diff --git a/Cargo.lock b/Cargo.lock index 57a040d..910f76e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,6 +216,7 @@ dependencies = [ "axum", "axum-oidc-client", "base64", + "dotenvy", "serde", "serde_json", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 82c1ae9..79fb1ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" base64 = "0.22" anyhow = "1" +dotenvy = "0.15" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a05065d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +services: + oidc: + image: ghcr.io/geigerzaehler/oidc-provider-mock:latest + ports: + - "9400:9400" + command: + - --user-claims + - '{"sub": "alice", "preferred_username": "alice", "email": "alice@example.com", "name": "Alice"}' + - --user-claims + - '{"sub": "bob", "preferred_username": "bob", "email": "bob@example.com", "name": "Bob"}' diff --git a/src/main.rs b/src/main.rs index 8d0fffa..f16dac9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,20 @@ use std::env; use std::sync::Arc; -use axum::{http::StatusCode, response::Html, routing::get, Json, Router}; +use axum::{ + extract::State, + http::StatusCode, + response::{Html, IntoResponse, Redirect, Response}, + routing::get, + Json, Router, +}; use axum_oidc_client::authentication::AuthenticationLayer; use axum_oidc_client::authentication::builder::OAuthConfigurationBuilder; use axum_oidc_client::authentication::logout::handle_default_logout::DefaultLogoutHandler; use axum_oidc_client::auth_cache::AuthCache; use axum_oidc_client::auth_session::AuthSession; use axum_oidc_client::cache::{TwoTierAuthCache, config::TwoTierCacheConfig}; +use axum_oidc_client::extractors::OptionalAuthSession; use axum_oidc_client::sql_cache::{SqlAuthCache, SqlCacheConfig}; use axum_oidc_client::authentication::CodeChallengeMethod; use base64::Engine; @@ -78,12 +85,22 @@ async fn hello(session: AuthSession) -> Result, StatusCode> Ok(Json(HelloResponse { preferred_username })) } -async fn index(_session: AuthSession) -> Html<&'static str> { - Html(INDEX_HTML) +#[derive(Clone)] +struct AppState { + login_path: String, +} + +async fn index(session: OptionalAuthSession, State(state): State) -> Response { + match session.0 { + Some(_) => Html(INDEX_HTML).into_response(), + None => Redirect::to(&state.login_path).into_response(), + } } #[tokio::main] async fn main() -> anyhow::Result<()> { + dotenvy::dotenv().ok(); + let provider_url = env_required("OIDC_PROVIDER_URL"); let client_id = env_required("OIDC_CLIENT_ID"); let client_secret = env_required("OIDC_CLIENT_SECRET"); @@ -131,7 +148,10 @@ async fn main() -> anyhow::Result<()> { Arc::new(config), cache, logout_handler, - )); + )) + .with_state(AppState { + login_path: auth_base_path, + }); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; println!("Listening on http://0.0.0.0:3000");