diff --git a/.env b/.env index 8c4c540..e69de29 100644 --- a/.env +++ b/.env @@ -1 +0,0 @@ -APP_JWT_SECRET=bc1996ea-5464-424a-9a38-5604f2bc865a diff --git a/.swp b/.swp new file mode 100644 index 0000000..6f58751 Binary files /dev/null and b/.swp differ diff --git a/Cargo.lock b/Cargo.lock index fca0ec9..9b04e92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,6 +349,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -966,11 +972,13 @@ dependencies = [ "chrono", "env_logger", "fully_pub", + "jsonwebkey-convert", "jsonwebtoken", "kernel", "log", "minijinja", "minijinja-embed", + "pem 3.0.4", "serde", "serde_json", "serde_urlencoded", @@ -1238,6 +1246,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebkey-convert" +version = "0.3.0" +dependencies = [ + "base64 0.13.1", + "lazy_static", + "num-bigint", + "pem 0.8.3", + "serde", + "serde_json", + "simple_asn1 0.5.4", + "thiserror 1.0.69", +] + [[package]] name = "jsonwebtoken" version = "9.3.0" @@ -1246,11 +1268,11 @@ checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" dependencies = [ "base64 0.21.7", "js-sys", - "pem", + "pem 3.0.4", "ring", "serde", "serde_json", - "simple_asn1", + "simple_asn1 0.6.2", ] [[package]] @@ -1561,6 +1583,17 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64 0.13.1", + "once_cell", + "regex", +] + [[package]] name = "pem" version = "3.0.4" @@ -1921,6 +1954,18 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simple_asn1" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb4ea60fb301dc81dfc113df680571045d375ab7345d171c5dc7d7e13107a80" +dependencies = [ + "chrono", + "num-bigint", + "num-traits", + "thiserror 1.0.69", +] + [[package]] name = "simple_asn1" version = "0.6.2" diff --git a/TODO.md b/TODO.md index 6d8df20..5771e72 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,8 @@ # TODO +- [ ] better OIDC support +- [ ] better support of `profile` `openid` `email` `roles` scopes + - [ ] i18n strings in the http website. - [ ] Instance customization support @@ -50,3 +53,5 @@ - [X] basic docker setup - [ ] make `docker stop` working (handle SIGTERM/SIGINT) - [ ] implement docker secrets. https://docs.docker.com/engine/swarm/secrets/ + +- [ ] Find a minimal OpenID client implementation like Listmonk but a little bit more mature diff --git a/config.toml b/config.toml index ca1c4be..12f31b4 100644 --- a/config.toml +++ b/config.toml @@ -1,3 +1,5 @@ +signing_key = "tmp/secrets/signing.key" + [instance] base_uri = "https://auth.fictive.org" name = "Fictive's auth" diff --git a/lib/http_server/Cargo.toml b/lib/http_server/Cargo.toml index 260d58c..7fc709b 100644 --- a/lib/http_server/Cargo.toml +++ b/lib/http_server/Cargo.toml @@ -43,6 +43,15 @@ argh = { workspace = true } sqlx = { workspace = true } uuid = { workspace = true } url = { workspace = true } +pem = "3.0.4" + +# For now, we test if it's viable, and later we will fork it to fix the build (cf. issue +# https://github.com/informationsea/jsonwebkey-rs#1 ) +[dependencies.jsonwebkey-convert] +path = "/home/mbess/workspace/foss/rust_libs/jsonwebkey-rs/jsonwebkey-convert" +features = ["simple_asn1", "pem"] + +pem = "3.0.4" [build-dependencies] minijinja-embed = "2.3.1" diff --git a/lib/http_server/src/controllers/api/oauth2/access_token.rs b/lib/http_server/src/controllers/api/oauth2/access_token.rs index fcd7299..396ea96 100644 --- a/lib/http_server/src/controllers/api/oauth2/access_token.rs +++ b/lib/http_server/src/controllers/api/oauth2/access_token.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use kernel::{models::authorization::Authorization, repositories::users::get_user_by_id}; use crate::{ - services::{app_session::AppClientSession, session::create_token}, token_claims::{AppUserTokenClaims, OAuth2AccessTokenClaims, OIDCIdTokenClaims}, AppState + services::{app_session::AppClientSession, session::create_token}, token_claims::{OAuth2AccessTokenClaims, OIDCIdTokenClaims}, AppState }; const AUTHORIZATION_CODE_TTL_SECONDS: i64 = 120; @@ -81,22 +81,25 @@ pub async fn get_access_token( // 3.1. Generate JWT for OAuth2 client user session let access_token_jwt = create_token( + &app_state.config, &app_state.secrets, OAuth2AccessTokenClaims::new( &app_state.config, - &app_client_session.client_id, &user, authorization.scopes.to_vec() ) ); // 3.2. Generate id_token for OIDC client + let id_token_claims = OIDCIdTokenClaims::new( + &app_state.config, + &app_client_session.client_id, + user.clone(), + authorization.nonce.clone() + ); let id_token_jwt = create_token( + &app_state.config, &app_state.secrets, - OIDCIdTokenClaims::new( - &app_state.config, - &app_client_session.client_id, - &user - ) + id_token_claims ); // 4. return JWT let access_token_res = AccessTokenResponse { diff --git a/lib/http_server/src/controllers/api/openid/keys.rs b/lib/http_server/src/controllers/api/openid/keys.rs new file mode 100644 index 0000000..8943483 --- /dev/null +++ b/lib/http_server/src/controllers/api/openid/keys.rs @@ -0,0 +1,45 @@ +use jsonwebkey_convert::RSAPublicKey; +use jsonwebkey_convert::der::FromPem; + +use axum::{extract::State, response::IntoResponse, Json}; +use fully_pub::fully_pub; +use serde::Serialize; + +use crate::AppState; + +// /// JSON Web Key +// /// @See https://www.rfc-editor.org/rfc/rfc7517.html +// #[derive(Serialize)] +// #[fully_pub] +// struct RsaJWK { +// #[serde(rename = "use")] +// utilisation: String, +// alg: String, +// kid: String, +// #[serde(rename = "modulus")] +// modulus: String, +// exp: String +// } + +/// JSON Web Key set +/// @See https://www.rfc-editor.org/rfc/rfc7517.html +#[derive(Serialize)] +#[fully_pub] +struct JWKs { + keys: Vec +} + +pub async fn get_signing_public_keys( + State(app_state): State, +) -> impl IntoResponse { + let pem_data = app_state.secrets.signing_keypair.0; + + // extract modulus and exp number from ASN.1 encoded PCKS 1 package + let rsa_jwk = RSAPublicKey::from_pem(pem_data) + .expect("Expected to decode PEM public key"); + dbg!(&rsa_jwk); + + Json(JWKs { + keys: vec![rsa_jwk] + }).into_response() +} diff --git a/lib/http_server/src/controllers/api/openid/mod.rs b/lib/http_server/src/controllers/api/openid/mod.rs index 1ab9853..063a9c9 100644 --- a/lib/http_server/src/controllers/api/openid/mod.rs +++ b/lib/http_server/src/controllers/api/openid/mod.rs @@ -1 +1,2 @@ pub mod well_known; +pub mod keys; diff --git a/lib/http_server/src/controllers/api/openid/well_known.rs b/lib/http_server/src/controllers/api/openid/well_known.rs index 2e3f8fa..6b2d0e8 100644 --- a/lib/http_server/src/controllers/api/openid/well_known.rs +++ b/lib/http_server/src/controllers/api/openid/well_known.rs @@ -18,7 +18,8 @@ struct WellKnownOpenIdConfiguration { scopes_supported: Vec, response_types_supported: Vec, token_endpoint_auth_methods_supported: Vec, - id_token_signing_alg_values_supported: Vec + id_token_signing_alg_values_supported: Vec, + jwks_uri: String } pub async fn get_well_known_openid_configuration( @@ -33,7 +34,8 @@ pub async fn get_well_known_openid_configuration( scopes_supported: AuthorizationScope::iter().map(|v| v.to_string()).collect(), response_types_supported: vec!["code".into()], token_endpoint_auth_methods_supported: vec!["client_secret_basic".into()], - id_token_signing_alg_values_supported: vec!["HS256".into()], + id_token_signing_alg_values_supported: vec!["RS256".into()], + jwks_uri: format!("{}/.well-known/jwks", base_url) // jwks_uri: // subject_types_supported }) diff --git a/lib/http_server/src/controllers/api/read_user.rs b/lib/http_server/src/controllers/api/read_user.rs index 4b9e7c1..465a307 100644 --- a/lib/http_server/src/controllers/api/read_user.rs +++ b/lib/http_server/src/controllers/api/read_user.rs @@ -2,7 +2,7 @@ use axum::{extract::State, response::IntoResponse, Extension, Json}; use fully_pub::fully_pub; use serde::Serialize; -use crate::{token_claims::AppUserTokenClaims, AppState}; +use crate::{token_claims::OAuth2AccessTokenClaims, AppState}; use kernel::models::user::User; #[derive(Serialize)] @@ -18,11 +18,11 @@ struct ReadUserBasicExtract { pub async fn read_user_basic( State(app_state): State, - Extension(token_claims): Extension, + Extension(token_claims): Extension, ) -> impl IntoResponse { // 1. This handler require app user authentification (JWT) let user_res = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1") - .bind(&token_claims.user_id) + .bind(&token_claims.sub) .fetch_one(&app_state.db.0) .await .expect("To get user from claim"); diff --git a/lib/http_server/src/controllers/ui/authorize.rs b/lib/http_server/src/controllers/ui/authorize.rs index 64ca470..923d8a3 100644 --- a/lib/http_server/src/controllers/ui/authorize.rs +++ b/lib/http_server/src/controllers/ui/authorize.rs @@ -7,9 +7,7 @@ use serde::{Deserialize, Serialize}; use url::Url; use uuid::Uuid; -use kernel::{ - models::{authorization::Authorization, config::AppAuthorizeFlow} -}; +use kernel::models::{authorization::Authorization, config::AppAuthorizeFlow}; use utils::get_random_alphanumerical; use crate::{ renderer::TemplateRenderer, services::oauth2::{parse_scope, verify_redirect_uri}, token_claims::UserTokenClaims, AppState @@ -25,6 +23,7 @@ struct AuthorizationParams { redirect_uri: String, /// An opaque value used by the client to maintain state between the request and callback state: String, + nonce: Option } fn redirect_to_client( @@ -34,7 +33,7 @@ fn redirect_to_client( let target_url = format!("{}?code={}&state={}", authorization_params.redirect_uri, authorization_code, - authorization_params.state, + authorization_params.state ); debug!("Redirecting to {}", target_url); @@ -117,9 +116,10 @@ pub async fn authorize_form( // Create new auth code let authorization_code = get_random_alphanumerical(32); // Update last used timestamp for this authorization - let _result = sqlx::query("UPDATE authorizations SET code = $2, last_used_at = $3 WHERE id = $1") + let _result = sqlx::query("UPDATE authorizations SET code = $2, nonce = $3, last_used_at = $4 WHERE id = $1") .bind(existing_authorization.id) .bind(authorization_code.clone()) + .bind(authorization_params.nonce.clone()) .bind(Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true)) .execute(&app_state.db.0) .await.unwrap(); @@ -173,6 +173,7 @@ pub async fn perform_authorize( Extension(token_claims): Extension, Form(authorize_form): Form ) -> impl IntoResponse { + dbg!(&authorize_form); // 1. Get the app details let app = match app_state.config.applications .iter() @@ -204,6 +205,7 @@ pub async fn perform_authorize( client_id: app.client_id.clone(), scopes: sqlx::types::Json(scopes), code: authorization_code.clone(), + nonce: authorize_form.nonce.clone(), last_used_at: Some(Utc::now()), created_at: Utc::now(), }; @@ -211,14 +213,15 @@ pub async fn perform_authorize( // 3. Save authorization in DB with state let res = sqlx::query(" INSERT INTO authorizations - (id, user_id, client_id, scopes, code, last_used_at, created_at) - VALUES ($1, $2, $3, $4, $5, $6, $7) + (id, user_id, client_id, scopes, code, nonce, last_used_at, created_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ") .bind(authorization.id.clone()) .bind(authorization.user_id) .bind(authorization.client_id) .bind(authorization.scopes) .bind(authorization.code) + .bind(authorization.nonce) .bind(authorization.last_used_at.map(|x| x.to_rfc3339_opts(SecondsFormat::Millis, true))) .bind(authorization.created_at.to_rfc3339_opts(SecondsFormat::Millis, true)) .execute(&app_state.db.0) diff --git a/lib/http_server/src/controllers/ui/login.rs b/lib/http_server/src/controllers/ui/login.rs index 59cce05..68061d4 100644 --- a/lib/http_server/src/controllers/ui/login.rs +++ b/lib/http_server/src/controllers/ui/login.rs @@ -91,8 +91,8 @@ pub async fn perform_login( .await.unwrap(); let jwt_max_age = Duration::days(15); - let claims = UserTokenClaims::new(&user.id, jwt_max_age); - let jwt = create_token(&app_state.secrets, claims); + let claims = UserTokenClaims::new(&app_state.config, &user.id, jwt_max_age); + let jwt = create_token(&app_state.config, &app_state.secrets, claims); // TODO: handle keep_session boolean from form and specify cookie max age only if this setting // is true diff --git a/lib/http_server/src/middlewares/app_auth.rs b/lib/http_server/src/middlewares/app_auth.rs index 3709f8b..c5c4e56 100644 --- a/lib/http_server/src/middlewares/app_auth.rs +++ b/lib/http_server/src/middlewares/app_auth.rs @@ -9,7 +9,7 @@ use utils::parse_basic_auth; use crate::{ services::{app_session::AppClientSession, session::verify_token}, - token_claims::AppUserTokenClaims, + token_claims::OAuth2AccessTokenClaims, AppState }; @@ -102,14 +102,16 @@ pub async fn enforce_jwt_auth_middleware( ); } }; - let token_claims: AppUserTokenClaims = match verify_token(&app_state.secrets, jwt) { - Ok(val) => val, - Err(_e) => { - return Err( - (StatusCode::UNAUTHORIZED, Html("Unauthorized: The provided JWT is invalid.")) - ); - } - }; + let token_claims: OAuth2AccessTokenClaims = + match verify_token(&app_state.config, &app_state.secrets, jwt) { + Ok(val) => val, + Err(_e) => { + dbg!(_e); + return Err( + (StatusCode::UNAUTHORIZED, Html("Unauthorized: The provided JWT is invalid.")) + ); + } + }; req.extensions_mut().insert(token_claims); Ok(next.run(req).await) } diff --git a/lib/http_server/src/middlewares/user_auth.rs b/lib/http_server/src/middlewares/user_auth.rs index 7582eb7..921bdce 100644 --- a/lib/http_server/src/middlewares/user_auth.rs +++ b/lib/http_server/src/middlewares/user_auth.rs @@ -28,18 +28,20 @@ pub async fn auth_middleware( return Ok(next.run(req).await) } }; - let token_claims: UserTokenClaims = match verify_token(&app_state.secrets, jwt) { - Ok(val) => val, - Err(_e) => { - // UserWebGUI: delete invalid JWT cookie - return Err( - ( - cookies.remove(WEB_GUI_JWT_COOKIE_NAME), - Redirect::to(&original_uri.to_string()) - ) - ); - } - }; + let token_claims: UserTokenClaims = + match verify_token(&app_state.config, &app_state.secrets, jwt) { + Ok(val) => val, + Err(_e) => { + dbg!(&_e); + // UserWebGUI: delete invalid JWT cookie + return Err( + ( + cookies.remove(WEB_GUI_JWT_COOKIE_NAME), + Redirect::to(&original_uri.to_string()) + ) + ); + } + }; req.extensions_mut().insert(token_claims); Ok(next.run(req).await) } diff --git a/lib/http_server/src/router.rs b/lib/http_server/src/router.rs index 7901928..fcbd89a 100644 --- a/lib/http_server/src/router.rs +++ b/lib/http_server/src/router.rs @@ -51,7 +51,8 @@ pub fn build_router(server_config: &ServerConfig, app_state: AppState) -> Router .route("/api/user-assets/:asset_id", get(api::public_assets::get_user_asset)); let well_known_routes = Router::new() - .route("/.well-known/openid-configuration", get(api::openid::well_known::get_well_known_openid_configuration)); + .route("/.well-known/openid-configuration", get(api::openid::well_known::get_well_known_openid_configuration)) + .route("/.well-known/jwks", get(api::openid::keys::get_signing_public_keys)); Router::new() .merge(public_routes) diff --git a/lib/http_server/src/services/session.rs b/lib/http_server/src/services/session.rs index 018e094..dd97828 100644 --- a/lib/http_server/src/services/session.rs +++ b/lib/http_server/src/services/session.rs @@ -1,24 +1,37 @@ use anyhow::Result; use serde::{de::DeserializeOwned, Serialize}; use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey}; -use kernel::context::AppSecrets; +use kernel::{context::AppSecrets, models::config::Config}; -pub fn create_token(secrets: &AppSecrets, claims: T) -> String { +pub fn create_token( + _config: &Config, + secrets: &AppSecrets, + claims: T +) -> String { let token = encode( - &Header::default(), + &Header::new(Algorithm::RS256), &claims, - &EncodingKey::from_secret(secrets.jwt_secret.as_bytes()) + &EncodingKey::from_rsa_pem(&secrets.signing_keypair.1) + .expect("To build encoding key from signing key.") ).expect("Create token"); token } -pub fn verify_token(secrets: &AppSecrets, jwt: &str) -> Result { +pub fn verify_token( + config: &Config, + secrets: &AppSecrets, + jwt: &str +) -> Result { + let mut validation = Validation::new(Algorithm::RS256); + validation.set_issuer(&[config.instance.base_uri.clone()]); + validation.set_audience(&[config.instance.base_uri.clone()]); let token_data = decode::( jwt, - &DecodingKey::from_secret(secrets.jwt_secret.as_bytes()), - &Validation::new(Algorithm::HS256) + &DecodingKey::from_rsa_pem(&secrets.signing_keypair.0) + .expect("To build decoding key from signing key."), + &validation )?; Ok(token_data.claims) diff --git a/lib/http_server/src/token_claims.rs b/lib/http_server/src/token_claims.rs index 4339eb4..726c4e5 100644 --- a/lib/http_server/src/token_claims.rs +++ b/lib/http_server/src/token_claims.rs @@ -12,16 +12,19 @@ struct UserTokenClaims { /// token expiration exp: u64, /// token issuer - iss: String + iss: String, // TODO: add roles + /// token audience + aud: String } impl UserTokenClaims { - pub fn new(user_id: &str, max_age: Duration) -> Self { + pub fn new(config: &Config, user_id: &str, max_age: Duration) -> Self { UserTokenClaims { sub: user_id.into(), exp: get_current_timestamp() + max_age.whole_seconds() as u64, - iss: "Minauthator".into() + iss: config.instance.base_uri.clone(), + aud: config.instance.base_uri.clone(), } } } @@ -33,7 +36,7 @@ impl UserTokenClaims { struct OAuth2AccessTokenClaims { /// Token issuer (URI to the issuer) iss: String, - /// Audiance (client_id) + /// Audiance (In this case, the audiance is equal to the issuer) aud: String, /// End-user id assigned by the issuer (user_id) sub: String, @@ -44,10 +47,10 @@ struct OAuth2AccessTokenClaims { } impl OAuth2AccessTokenClaims { - pub fn new(config: &Config, client_id: &str, user: &User, scopes: Vec) -> Self { + pub fn new(config: &Config, user: &User, scopes: Vec) -> Self { OAuth2AccessTokenClaims { iss: config.instance.base_uri.clone(), - aud: client_id.into(), + aud: config.instance.base_uri.clone(), sub: user.id.clone(), exp: get_current_timestamp() + 86_000, scopes, @@ -73,20 +76,27 @@ struct OIDCIdTokenClaims { name: Option, email: Option, preferred_username: Option, - roles: Vec + roles: Vec, + nonce: Option } impl OIDCIdTokenClaims { - pub fn new(config: &Config, client_id: &str, user: &User) -> Self { + pub fn new( + config: &Config, + client_id: &str, + user: User, + nonce: Option + ) -> Self { OIDCIdTokenClaims { iss: config.instance.base_uri.clone(), aud: client_id.into(), - sub: user.id.clone(), + sub: user.id, exp: get_current_timestamp() + 86_000, - email: user.email.clone(), - name: user.full_name.clone(), - preferred_username: Some(user.handle.clone()), - roles: user.roles.0.clone() + email: user.email, + name: user.full_name, + preferred_username: Some(user.handle), + roles: user.roles.0, + nonce } } } diff --git a/lib/kernel/src/consts.rs b/lib/kernel/src/consts.rs index 31fca20..9bdfaf2 100644 --- a/lib/kernel/src/consts.rs +++ b/lib/kernel/src/consts.rs @@ -1,4 +1,5 @@ pub const DEFAULT_DB_PATH: &str = "/var/lib/minauthator/minauthator.db"; pub const DEFAULT_ASSETS_PATH: &str = "/usr/local/lib/minauthator/assets"; pub const DEFAULT_CONFIG_PATH: &str = "/etc/minauthator/config.toml"; +pub const DEFAULT_SIGNING_KEY_PATH: &str = "/etc/minauthator/secrets/jwt.key.pem"; diff --git a/lib/kernel/src/context.rs b/lib/kernel/src/context.rs index 0c6e3b1..cb90e0d 100644 --- a/lib/kernel/src/context.rs +++ b/lib/kernel/src/context.rs @@ -1,11 +1,13 @@ -use std::{env, fs}; +use std::{env, fs, path::Path}; use anyhow::{Result, Context, anyhow}; use fully_pub::fully_pub; use log::info; -use sqlx::{Pool, Sqlite}; use crate::{ - consts::{DEFAULT_CONFIG_PATH, DEFAULT_DB_PATH}, database::prepare_database, models::config::Config, repositories::storage::Storage + consts::{DEFAULT_CONFIG_PATH, DEFAULT_DB_PATH, DEFAULT_SIGNING_KEY_PATH}, + database::prepare_database, + models::config::Config, + repositories::storage::Storage }; /// get server config @@ -26,9 +28,18 @@ struct StartKernelConfig { #[derive(Debug, Clone)] #[fully_pub] struct AppSecrets { - jwt_secret: String + /// RSA keypair (public, private) used to signed the JWT issued by minauthator in PEM conainer format + signing_keypair: (Vec, Vec) } +#[derive(Debug, Clone)] +#[fully_pub] +struct ComputedConfig { + signing_public_key: Vec, + signing_private_key: Vec +} + + #[derive(Debug, Clone)] #[fully_pub] struct KernelContext { @@ -37,6 +48,19 @@ struct KernelContext { storage: Storage } +fn get_signing_keypair(key_path: &str) -> Result<(Vec, Vec)> { + let key_path = Path::new(key_path); + let pub_key_path = key_path.with_extension("pub"); + let private_key: Vec = fs::read_to_string(&key_path) + .context(format!("Failed to read private key from path {:?}.", key_path))? + .as_bytes().to_vec(); + let public_key: Vec = fs::read_to_string(&pub_key_path) + .context(format!("Failed to read public key from path {:?}.", pub_key_path))? + .as_bytes().to_vec(); + + Ok((public_key, private_key)) +} + pub async fn get_kernel_context(start_config: StartKernelConfig) -> Result { env_logger::init(); let _ = dotenvy::dotenv(); @@ -50,10 +74,13 @@ pub async fn get_kernel_context(start_config: StartKernelConfig) -> Result>, + nonce: Option, + /// defined in https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2 code: String, last_used_at: Option>, diff --git a/lib/kernel/src/models/config.rs b/lib/kernel/src/models/config.rs index 57a275a..bf2d311 100644 --- a/lib/kernel/src/models/config.rs +++ b/lib/kernel/src/models/config.rs @@ -66,11 +66,6 @@ struct Role { struct Config { instance: InstanceConfig, applications: Vec, - roles: Vec -} - -#[derive(Debug, Clone)] -#[fully_pub] -struct AppSecrets { - jwt_secret: String + roles: Vec, + signing_key: Option } diff --git a/lib/kernel/src/models/user.rs b/lib/kernel/src/models/user.rs index 9634fa0..355fcdd 100644 --- a/lib/kernel/src/models/user.rs +++ b/lib/kernel/src/models/user.rs @@ -14,7 +14,7 @@ enum UserStatus { Active } -#[derive(sqlx::FromRow, Deserialize, Serialize, Debug)] +#[derive(sqlx::FromRow, Deserialize, Serialize, Debug, Clone)] #[fully_pub] struct User { /// uuid diff --git a/migrations/all.sql b/migrations/all.sql index 2f71c3c..adf3327 100644 --- a/migrations/all.sql +++ b/migrations/all.sql @@ -33,6 +33,7 @@ CREATE TABLE authorizations ( client_id TEXT NOT NULL, scopes TEXT, -- json array of app scope (permissions) code TEXT, + nonce TEXT, -- code used to associate client session to id_token last_used_at DATETIME, created_at DATETIME NOT NULL diff --git a/tests/hurl_integration/oidc_core/config.toml b/tests/hurl_integration/oidc_core/config.toml index ad3f58e..df607aa 100644 --- a/tests/hurl_integration/oidc_core/config.toml +++ b/tests/hurl_integration/oidc_core/config.toml @@ -1,3 +1,5 @@ +signing_key = "tmp/secrets/signing.key" + [instance] base_uri = "http://localhost:8086" name = "Example org" diff --git a/tests/hurl_integration/oidc_core/main.hurl b/tests/hurl_integration/oidc_core/main.hurl index 4d2c981..36e70db 100644 --- a/tests/hurl_integration/oidc_core/main.hurl +++ b/tests/hurl_integration/oidc_core/main.hurl @@ -1,6 +1,6 @@ POST {{ base_url }}/login [FormParams] -login: root +login: john.doe password: root HTTP 303 [Captures] diff --git a/tests/hurl_integration/scenario_1/config.toml b/tests/hurl_integration/scenario_1/config.toml index ad3f58e..df607aa 100644 --- a/tests/hurl_integration/scenario_1/config.toml +++ b/tests/hurl_integration/scenario_1/config.toml @@ -1,3 +1,5 @@ +signing_key = "tmp/secrets/signing.key" + [instance] base_uri = "http://localhost:8086" name = "Example org" diff --git a/tests/hurl_integration/scenario_1/main.hurl b/tests/hurl_integration/scenario_1/main.hurl index 1e68473..6213536 100644 --- a/tests/hurl_integration/scenario_1/main.hurl +++ b/tests/hurl_integration/scenario_1/main.hurl @@ -27,7 +27,7 @@ handle: root email: root@johndoe.net full_name: John Doe website: https://johndoe.net -picture: file,john_doe_profile_pic.jpg; image/jpeg +avatar: file,john_doe_profile_pic.jpg; image/jpeg HTTP 200 GET {{ base_url }}/me/authorizations diff --git a/tests/hurl_integration/user_invitation_scenario/config.toml b/tests/hurl_integration/user_invitation_scenario/config.toml index f85dc48..722fd33 100644 --- a/tests/hurl_integration/user_invitation_scenario/config.toml +++ b/tests/hurl_integration/user_invitation_scenario/config.toml @@ -1,3 +1,4 @@ +signing_key = "tmp/secrets/signing.key" applications = [] roles = []