Skip to content

Commit

Permalink
feat: add support for authentik (#2)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Martin Jurk <[email protected]>
Co-authored-by: Martin Jurk <[email protected]>
Co-authored-by: Martin Jurk <[email protected]>
Co-authored-by: janskiba <[email protected]>
Co-authored-by: Martin Jurk <[email protected]>
Co-authored-by: Jan <[email protected]>
  • Loading branch information
7 people authored Feb 4, 2025
1 parent 2c0a0ad commit f0bc039
Show file tree
Hide file tree
Showing 24 changed files with 1,852 additions and 504 deletions.
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[env]
RUST_LOG = "debug"
24 changes: 16 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@ members = ["local", "central", "shared"]
resolver = "2"

[workspace.dependencies]
beam-lib = { git = "https://github.com/samply/beam", features = ["http-util"], branch = "develop" }
clap = { version = "4.4", features = ["derive", "env"]}
beam-lib = { git = "https://github.com/samply/beam", features = [
"http-util",
], branch = "develop" }
clap = { version = "4.4", features = ["derive", "env"] }
once_cell = "1"
tokio = { version = "1", default-features = false, features = ["macros", "rt-multi-thread"] }
serde = { version = "1", features = ["derive"]}
tokio = { version = "1", default-features = false, features = [
"macros",
"rt-multi-thread",
] }
serde = { version = "1", features = ["derive"] }
futures = "0.3"
shared = { path = "./shared" }
tracing = "0.1"
tracing-subscriber = "0.3.0"
anyhow = "1"

[profile.release]
#opt-level = "z" # Optimize for size.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = "abort" # Abort on panic
strip = true # Automatically strip symbols from the binary.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = "abort" # Abort on panic
strip = true # Automatically strip symbols from the binary.

[profile.bloat]
inherits = "release"
Expand Down
7 changes: 5 additions & 2 deletions central/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ license = "Apache-2.0"
[dependencies]
beam-lib = { workspace = true }
clap = { workspace = true }
tokio = { workspace = true, features= ["signal"] }
tokio = { workspace = true, features = ["signal"] }
once_cell = { workspace = true }
shared = { workspace = true }
serde = { workspace = true }
futures = { workspace = true }
serde_json = "1"
rand = "0.9"
reqwest = { version = "0.12", default_features = false, features = ["default-tls"] }
anyhow = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
urlencoding = "2.1.3"
chrono = "0.4.39"
chrono = "0.4"
139 changes: 139 additions & 0 deletions central/src/auth/authentik/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use beam_lib::reqwest::{self, Response, StatusCode};
use reqwest::{Client, Url};
use serde_json::{json, Value};
use shared::OIDCConfig;
use std::i64;
use tracing::{debug, info};

use crate::CLIENT;

use super::{
get_uuid,
provider::{compare_provider, get_provider, get_provider_id, RedirectURIS},
AuthentikConfig,
};

pub fn generate_app_values(provider: i64, client_id: &str) -> Value {
json!({
"name": client_id,
"slug": client_id,
"provider": provider,
"group": client_id.split('-').next().expect("group name does not contain - ")
})
}

pub async fn generate_application(
provider: i64,
client_id: &str,
conf: &AuthentikConfig,
token: &str,
) -> reqwest::Result<Response> {
let app_value = generate_app_values(provider, client_id);
debug!("{:#?}", app_value);
CLIENT
.post(
conf.authentik_url
.join("api/v3/core/applications/")
.expect("Error parsing app url"),
)
.bearer_auth(token)
.json(&app_value)
.send()
.await
}

pub async fn check_app_result(
token: &str,
client_id: &str,
provider_pk: i64,
conf: &AuthentikConfig,
) -> anyhow::Result<bool> {
let res = generate_application(provider_pk, client_id, conf, token).await?;
match res.status() {
StatusCode::CREATED => {
info!("Application for {client_id} created.");
Ok(true)
}
StatusCode::BAD_REQUEST => {
let conflicting_client = get_application(client_id, token, conf).await?;
if app_configs_match(
&conflicting_client,
&generate_app_values(provider_pk, client_id),
) {
info!("Application {client_id} exists.");
Ok(true)
} else {
info!("Application for {client_id} is updated.");
Ok(CLIENT
.put(
conf.authentik_url.join("api/v3/core/applicaions/")?.join(
conflicting_client
.get("slug")
.and_then(Value::as_str)
.expect("No valid client"),
)?,
)
.bearer_auth(token)
.json(&generate_app_values(provider_pk, client_id))
.send()
.await?
.status()
.is_success())
}
}
s => anyhow::bail!("Unexpected statuscode {s} while creating authentik client. {res:?}"),
}
}

pub async fn get_application(
client_id: &str,
token: &str,
conf: &AuthentikConfig,
) -> reqwest::Result<serde_json::Value> {
CLIENT
.get(
conf.authentik_url
.join(&format!("api/v3/core/applications/{client_id}/"))
.expect("Error parsing app url"),
)
.bearer_auth(token)
.send()
.await?
.json()
.await
}

// used only from validate in config
pub async fn compare_app_provider(
token: &str,
name: &str,
oidc_client_config: &OIDCConfig,
secret: &str,
conf: &AuthentikConfig,
) -> anyhow::Result<bool> {
let client_id = format!(
"{name}-{}",
if oidc_client_config.is_public {
"public"
} else {
"private"
}
);

let provider_pk = get_provider_id(&client_id, token, conf).await;
match provider_pk {
Some(pr_id) => {
let app_res = get_application(&client_id, token, conf).await?;
if app_configs_match(&app_res, &generate_app_values(pr_id, &client_id)) {
compare_provider(token, &client_id, oidc_client_config, conf, secret).await
} else {
Ok(false)
}
}
None => Ok(false),
}
}

pub fn app_configs_match(a: &Value, b: &Value) -> bool {
a.get("name") == b.get("name") && a["group"] == b["group"] && a["provider"] == b["provider"]
}
44 changes: 44 additions & 0 deletions central/src/auth/authentik/group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use beam_lib::reqwest::StatusCode;
use serde_json::json;
use tracing::info;

use crate::CLIENT;

use super::AuthentikConfig;

pub async fn create_groups(name: &str, token: &str, conf: &AuthentikConfig) -> anyhow::Result<()> {
let capitalize = |s: &str| {
let mut chrs = s.chars();
chrs.next()
.map(char::to_uppercase)
.map(Iterator::collect)
.unwrap_or(String::new())
+ chrs.as_str()
};
let name = capitalize(name);
for group in &conf.authentik_groups_per_bh {
post_group(&group.replace('#', &name), token, conf).await?;
}
Ok(())
}

pub async fn post_group(name: &str, token: &str, conf: &AuthentikConfig) -> anyhow::Result<()> {
let res = CLIENT
.post(conf.authentik_url.join("api/v3/core/groups/")?)
.bearer_auth(token)
.json(&json!({
"name": name
}))
.send()
.await?;
match res.status() {
StatusCode::CREATED => info!("Created group {name}"),
StatusCode::OK => info!("Created group {name}"),
StatusCode::BAD_REQUEST => info!("Group {name} already existed"),
s => anyhow::bail!(
"Unexpected statuscode {s} while creating group {name}: {:#?}",
res.json::<serde_json::Value>().await.unwrap_or_default()
),
}
Ok(())
}
Loading

0 comments on commit f0bc039

Please sign in to comment.