init commit

This commit is contained in:
2025-04-13 02:15:02 +02:00
commit 82856dff82
7 changed files with 6155 additions and 0 deletions

32
src/initializer.rs Normal file
View File

@@ -0,0 +1,32 @@
use axum::Router;
use loco_rs::prelude::*;
use crate::Keycloak;
/// KeycloakAuthInitializer is an initializer for the Keycloak authentication layer.
/// Use this if you want to add Keycloak authentication to all routes in your application.
/// It will automatically read the Keycloak settings from the application context.
/// This initializer is typically used in the `app.rs` file of your Loco application.
/// If you want to have more control over the Keycloak layer, you can use the `Keycloak` struct
/// and add the layer to the routes directly.
/// ## Example
/// ```rust
/// async fn initializers(_ctx: &AppContext) -> Result<Vec<Box<dyn Initializer>>> {
/// let keycloak_auth = loco_keycloak_auth::initializer::KeycloakAuthInitializer {};
/// Ok(vec![Box::new(keycloak_auth)])
/// }
/// ```
pub struct KeycloakAuthInitializer;
#[async_trait]
impl Initializer for KeycloakAuthInitializer {
fn name(&self) -> String {
"keycloak_auth".to_string()
}
async fn after_routes(&self, router: Router, ctx: &AppContext) -> Result<Router> {
let keycloak = Keycloak::from_context(ctx)
.map_err(|err| Error::Message(format!("Failed to create Keycloak layer: {}", err)))?;
Ok(router.layer(keycloak.layer))
}
}

60
src/lib.rs Normal file
View File

@@ -0,0 +1,60 @@
pub mod initializer;
pub mod settings;
pub use axum_keycloak_auth::decode::KeycloakToken;
use axum_keycloak_auth::{
Url,
instance::{KeycloakAuthInstance, KeycloakConfig},
layer::KeycloakAuthLayer,
};
use loco_rs::prelude::*;
use settings::{KeycloakSettings, Settings};
/// Keycloak is a struct that holds the Keycloak authentication layer.
/// ## Usage
/// ```rust
/// let keycloak = Keycloak::from_context(ctx).expect("Failed to create Keycloak layer");
/// let router = Router::new()
/// .route("/protected", get(protected_handler))
/// .layer(keycloak.layer);
/// ```
pub struct Keycloak {
pub layer: KeycloakAuthLayer<String>,
}
impl Keycloak {
pub fn from_context(ctx: &AppContext) -> Result<Self> {
build_keycloak_layer(ctx).map(|layer| Keycloak { layer })
}
}
/// This function builds the Keycloak authentication layer using the settings
/// from the application context.
pub fn build_keycloak_layer(ctx: &AppContext) -> Result<KeycloakAuthLayer<String>> {
let full_settings: Settings = serde_json::from_value(
ctx.config
.settings
.clone()
.ok_or_else(|| Error::Message("Missing `settings` in config".into()))?,
)
.map_err(|err| Error::Message(format!("Invalid settings: {}", err)))?;
let settings: KeycloakSettings = full_settings.keycloak_settings;
let instance =
KeycloakAuthInstance::new(
KeycloakConfig::builder()
.server(Url::parse(&settings.url).map_err(|err| {
Error::Message(format!("Invalid Keycloak server URL: {}", err))
})?)
.realm(settings.realm)
.build(),
);
Ok(KeycloakAuthLayer::<String>::builder()
.instance(instance)
.passthrough_mode(settings.passthrough_mode.0)
.persist_raw_claims(settings.persist_raw_claims)
.expected_audiences(settings.expected_audiences)
.build())
}

68
src/settings.rs Normal file
View File

@@ -0,0 +1,68 @@
use axum_keycloak_auth::PassthroughMode;
use serde::Deserialize;
/// Configuration settings for Keycloak authentication.
///
/// This struct should be placed under the `settings.keycloak_settings` section
/// of your Loco application's `config/config.yaml`. It provides all values
/// needed to initialize the Keycloak authentication layer.
#[derive(Clone, Deserialize)]
pub struct KeycloakSettings {
/// The full URL to your Keycloak server (e.g. `https://sso.example.com`).
pub url: String,
/// The realm name in Keycloak (e.g. `myrealm`).
pub realm: String,
/// A list of expected audiences in the token (typically contains `"account"`).
pub expected_audiences: Vec<String>,
/// The mode that determines how the authentication layer behaves.
///
/// - `PassthroughMode::Block`: Return `401 Unauthorized` on authentication failure.
/// - `PassthroughMode::Pass`: Allow unauthenticated access and set auth status as an extension.
///
/// Default: `Block`
pub passthrough_mode: PassthroughModeDef,
/// Whether to persist raw Keycloak claims as an Axum extension.
///
/// Set this to `true` if you want access to the raw token contents.
pub persist_raw_claims: bool,
}
/// Root struct to hold all custom application settings.
///
/// This is typically deserialized from the `settings:` section in Loco's
/// `config/config.yaml`.
/// ## Sample configuration
/// ```yaml
/// settings:
/// keycloak_settings:
/// url: "https://sso.example.com"
/// realm: "myrealm"
/// expected_audiences:
/// - "account"
/// passthrough_mode: "Block" # or "Pass"
/// persist_raw_claims: false
/// ```
#[derive(Clone, Deserialize)]
pub struct Settings {
pub keycloak_settings: KeycloakSettings,
}
#[derive(Debug, Clone)]
pub struct PassthroughModeDef(pub PassthroughMode);
impl<'de> Deserialize<'de> for PassthroughModeDef {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.to_lowercase().as_str() {
"block" => Ok(PassthroughModeDef(PassthroughMode::Block)),
"pass" => Ok(PassthroughModeDef(PassthroughMode::Pass)),
_ => Err(serde::de::Error::custom(format!(
"Invalid passthrough mode: {}",
s
))),
}
}
}