Compare commits

..

1 Commits

Author SHA1 Message Date
bb7682d4b9 Update Rust crate jwt-simple to v0.12.11
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-12-10 00:19:13 +00:00
6 changed files with 548 additions and 541 deletions

965
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -18,13 +18,13 @@ serde_json = "1.0.128"
serde_yaml = "0.9.34"
env_logger = "0.11.3"
serde = { version = "1.0.210", features = ["derive"] }
bcrypt = "0.17.0"
bcrypt = "0.16.0"
uuid = { version = "1.8.0", features = ["v4"] }
mime_guess = "2.0.4"
askama = "0.12.1"
futures-util = "0.3.30"
urlencoding = "2.1.3"
rand = "0.9.0"
rand = "0.8.5"
base64 = "0.22.1"
jwt-simple = { version = "0.12.10", default-features = false, features = ["pure-rust"] }
digest = "0.10.7"

View File

@ -16,7 +16,7 @@ use crate::constants::*;
use crate::controllers::base_controller::{build_fatal_error_page, redirect_user};
use crate::data::action_logger::{Action, ActionLogger};
use crate::data::app_config::AppConfig;
use crate::data::client::{AdditionalClaims, ClientID, ClientManager};
use crate::data::client::{AdditionalClaims, AuthenticationFlow, ClientID, ClientManager};
use crate::data::code_challenge::CodeChallenge;
use crate::data::current_user::CurrentUser;
use crate::data::id_token::IdToken;
@ -50,39 +50,37 @@ pub async fn get_configuration(req: HttpRequest) -> impl Responder {
host
);
HttpResponse::Ok()
.insert_header(("access-control-allow-origin", "*"))
.json(OpenIDConfig {
issuer: AppConfig::get().website_origin.clone(),
authorization_endpoint: AppConfig::get().full_url(AUTHORIZE_URI),
token_endpoint: curr_origin.clone() + TOKEN_URI,
userinfo_endpoint: Some(curr_origin.clone() + USERINFO_URI),
jwks_uri: curr_origin + CERT_URI,
scopes_supported: Some(vec![
"openid".to_string(),
"profile".to_string(),
"email".to_string(),
]),
response_types_supported: vec![
"code".to_string(),
"id_token".to_string(),
"token id_token".to_string(),
],
subject_types_supported: vec!["public".to_string()],
id_token_signing_alg_values_supported: vec!["RS256".to_string()],
token_endpoint_auth_methods_supported: Some(vec![
"client_secret_post".to_string(),
"client_secret_basic".to_string(),
]),
claims_supported: Some(vec![
"sub".to_string(),
"name".to_string(),
"given_name".to_string(),
"family_name".to_string(),
"email".to_string(),
]),
code_challenge_methods_supported: Some(vec!["plain".to_string(), "S256".to_string()]),
})
HttpResponse::Ok().json(OpenIDConfig {
issuer: AppConfig::get().website_origin.clone(),
authorization_endpoint: AppConfig::get().full_url(AUTHORIZE_URI),
token_endpoint: curr_origin.clone() + TOKEN_URI,
userinfo_endpoint: Some(curr_origin.clone() + USERINFO_URI),
jwks_uri: curr_origin + CERT_URI,
scopes_supported: Some(vec![
"openid".to_string(),
"profile".to_string(),
"email".to_string(),
]),
response_types_supported: vec![
"code".to_string(),
"id_token".to_string(),
"token id_token".to_string(),
],
subject_types_supported: vec!["public".to_string()],
id_token_signing_alg_values_supported: vec!["RS256".to_string()],
token_endpoint_auth_methods_supported: Some(vec![
"client_secret_post".to_string(),
"client_secret_basic".to_string(),
]),
claims_supported: Some(vec![
"sub".to_string(),
"name".to_string(),
"given_name".to_string(),
"family_name".to_string(),
"email".to_string(),
]),
code_challenge_methods_supported: Some(vec!["plain".to_string(), "S256".to_string()]),
})
}
#[derive(serde::Deserialize, Debug)]
@ -220,8 +218,8 @@ pub async fn authorize(
));
}
match (client.has_secret(), query.response_type.as_str()) {
(_, "code") => {
match (client.auth_flow(), query.response_type.as_str()) {
(AuthenticationFlow::AuthorizationCode, "code") => {
// Save all authentication information in memory
let session = Session {
session_id: SessionID(rand_str(OPEN_ID_SESSION_LEN)),
@ -263,8 +261,7 @@ pub async fn authorize(
.finish())
}
// id_token is available only if user has no secret configured
(false, "id_token") => {
(AuthenticationFlow::Implicit, "id_token") => {
let id_token = IdToken {
issuer: AppConfig::get().website_origin.to_string(),
subject_identifier: user.uid.0.clone(),
@ -296,11 +293,11 @@ pub async fn authorize(
.finish())
}
(secret, code) => {
(flow, code) => {
log::warn!(
"For client {:?}, configured with secret {:?}, made request with code {}",
"For client {:?}, configured with flow {:?}, made request with code {}",
client.id,
secret,
flow,
code
);
Ok(error_redirect(
@ -369,7 +366,9 @@ pub async fn token(
let (client_id, client_secret) =
match (&query.client_id, &query.client_secret, authorization_header) {
// post authentication
(Some(client_id), client_secret, None) => (client_id.clone(), client_secret.clone()),
(Some(client_id), Some(client_secret), None) => {
(client_id.clone(), client_secret.to_string())
}
// Basic authentication
(_, None, Some(v)) => {
@ -400,8 +399,8 @@ pub async fn token(
.to_string();
match decode.split_once(':') {
None => (ClientID(decode), None),
Some((id, secret)) => (ClientID(id.to_string()), Some(secret.to_string())),
None => (ClientID(decode), "".to_string()),
Some((id, secret)) => (ClientID(id.to_string()), secret.to_string()),
}
}
@ -419,7 +418,7 @@ pub async fn token(
.ok_or_else(|| ErrorUnauthorized("Client not found"))?;
// Retrieving token requires the client to have a defined secret
if client.secret != client_secret {
if client.secret != Some(client_secret) {
return Ok(error_response(
&query,
"invalid_request",
@ -609,9 +608,8 @@ pub async fn token(
};
Ok(HttpResponse::Ok()
.insert_header(("Cache-Control", "no-store"))
.insert_header(("Pragma", "no-cache"))
.insert_header(("access-control-allow-origin", "*"))
.append_header(("Cache-Control", "no-store"))
.append_header(("Pragam", "no-cache"))
.json(token_response))
}

View File

@ -7,6 +7,12 @@ use std::collections::HashMap;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
pub struct ClientID(pub String);
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum AuthenticationFlow {
AuthorizationCode,
Implicit,
}
pub type AdditionalClaims = HashMap<String, Value>;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@ -55,9 +61,12 @@ impl PartialEq for Client {
impl Eq for Client {}
impl Client {
/// Check if the client has a secret defined
pub fn has_secret(&self) -> bool {
self.secret.is_some()
/// Get the client authentication flow
pub fn auth_flow(&self) -> AuthenticationFlow {
match self.secret {
None => AuthenticationFlow::Implicit,
Some(_) => AuthenticationFlow::AuthorizationCode,
}
}
/// Process a single claim value

View File

@ -21,7 +21,7 @@ pub struct TotpKey {
impl TotpKey {
/// Generate a new TOTP key
pub fn new_random() -> Self {
let random_bytes = rand::rng().random::<[u8; 20]>();
let random_bytes = rand::thread_rng().gen::<[u8; 20]>();
Self {
encoded: base32::encode(BASE32_ALPHABET, &random_bytes),
}

View File

@ -1,9 +1,14 @@
use lazy_regex::regex_find;
use rand::distr::{Alphanumeric, SampleString};
use rand::distributions::Alphanumeric;
use rand::Rng;
/// Generate a random string of a given size
pub fn rand_str(len: usize) -> String {
Alphanumeric.sample_string(&mut rand::rng(), len)
rand::thread_rng()
.sample_iter(&Alphanumeric)
.map(char::from)
.take(len)
.collect()
}
/// Parse environment variables