This commit is contained in:
Matthieu Bessat 2024-12-09 09:38:39 +01:00
parent 1b21ee1573
commit ca84a0f99f
10 changed files with 233 additions and 40 deletions

View file

@ -4,9 +4,9 @@ use fully_pub::fully_pub;
use log::error;
use serde::{Deserialize, Serialize};
use kernel::models::authorization::Authorization;
use kernel::{models::authorization::Authorization, repositories::users::get_user_by_id};
use crate::{
services::{app_session::AppClientSession, session::create_token}, token_claims::AppUserTokenClaims, AppState
services::{app_session::AppClientSession, session::create_token}, token_claims::{AppUserTokenClaims, OAuth2AccessTokenClaims, OIDCIdTokenClaims}, AppState
};
const AUTHORIZATION_CODE_TTL_SECONDS: i64 = 120;
@ -22,6 +22,7 @@ struct AccessTokenRequestParams {
#[derive(Serialize, Deserialize)]
#[fully_pub]
struct AccessTokenResponse {
id_token: String,
access_token: String,
token_type: String,
expires_in: u64
@ -60,6 +61,7 @@ pub async fn get_access_token(
).into_response();
}
};
// 2.2. Validate that the authorization code is not expired
let is_code_valid = authorization.last_used_at
.map_or(false, |ts| {
@ -72,19 +74,35 @@ pub async fn get_access_token(
).into_response();
}
// 3. Generate JWT for oauth2 client user session
let jwt = create_token(
// 2.3. Fetch user resource owner
let user = get_user_by_id(&app_state.db, &authorization.user_id)
.await
.expect("Expected to get user from authorization.");
// 3.1. Generate JWT for OAuth2 client user session
let access_token_jwt = create_token(
&app_state.secrets,
AppUserTokenClaims::new(
OAuth2AccessTokenClaims::new(
&app_state.config,
&app_client_session.client_id,
&authorization.user_id,
&user,
authorization.scopes.to_vec()
)
);
// 3.2. Generate id_token for OIDC client
let id_token_jwt = create_token(
&app_state.secrets,
OIDCIdTokenClaims::new(
&app_state.config,
&app_client_session.client_id,
&user
)
);
// 4. return JWT
let access_token_res = AccessTokenResponse {
access_token: jwt,
token_type: "jwt".to_string(),
id_token: id_token_jwt,
access_token: access_token_jwt,
token_type: "Bearer".to_string(),
expires_in: 3600
};
Json(access_token_res).into_response()

View file

@ -6,6 +6,8 @@ use strum::IntoEnumIterator;
use crate::AppState;
/// Manifest used by OpenID Connect clients
/// @See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
#[derive(Serialize)]
#[fully_pub]
struct WellKnownOpenIdConfiguration {
@ -15,7 +17,8 @@ struct WellKnownOpenIdConfiguration {
userinfo_endpoint: String,
scopes_supported: Vec<String>,
response_types_supported: Vec<String>,
token_endpoint_auth_methods_supported: Vec<String>
token_endpoint_auth_methods_supported: Vec<String>,
id_token_signing_alg_values_supported: Vec<String>
}
pub async fn get_well_known_openid_configuration(
@ -30,5 +33,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()],
// jwks_uri:
// subject_types_supported
})
}

View file

@ -56,6 +56,7 @@ pub async fn authorize_form(
query_params: Query<AuthorizationParams>
) -> impl IntoResponse {
let Query(authorization_params) = query_params;
dbg!(&authorization_params);
// 1. Verify the app details
let app = match app_state.config.applications

View file

@ -12,9 +12,16 @@ pub fn verify_redirect_uri(app: &Application, input_redirect_uri: &str) -> bool
pub fn parse_scope(scope_str: &str) -> Result<Vec<AuthorizationScope>> {
let mut scopes: Vec<AuthorizationScope> = vec![];
for part in scope_str.split(' ') {
scopes.push(
AuthorizationScope::from_str(part).context("Cannot parse space-delimited scope.")?
)
if part == "openid" {
scopes.push(AuthorizationScope::UserReadBasic);
scopes.push(AuthorizationScope::UserReadRoles);
continue;
}
if part == "profile" || part == "email" {
continue;
}
scopes.push(AuthorizationScope::from_str(part).context("Cannot parse space-delimited scope.")?);
}
dbg!(&scopes);
Ok(scopes)
}

View file

@ -1,6 +1,6 @@
use fully_pub::fully_pub;
use jsonwebtoken::get_current_timestamp;
use kernel::models::authorization::AuthorizationScope;
use kernel::models::{authorization::AuthorizationScope, config::Config, user::User};
use serde::{Deserialize, Serialize};
use time::Duration;
@ -26,27 +26,67 @@ impl UserTokenClaims {
}
}
/// Access token for OAuth2 defined in RFC 9068
/// @See https://datatracker.ietf.org/doc/html/rfc9068
#[derive(Debug, Serialize, Deserialize, Clone)]
#[fully_pub]
struct AppUserTokenClaims {
/// combined subject
client_id: String,
user_id: String,
scopes: Vec<AuthorizationScope>,
/// token expiration
struct OAuth2AccessTokenClaims {
/// Token issuer (URI to the issuer)
iss: String,
/// Audiance (client_id)
aud: String,
/// End-user id assigned by the issuer (user_id)
sub: String,
/// Token expiration
exp: u64,
/// token issuer
iss: String
/// List of OAuth 2 scopes asked by the client
scopes: Vec<AuthorizationScope>
}
impl AppUserTokenClaims {
pub fn new(client_id: &str, user_id: &str, scopes: Vec<AuthorizationScope>) -> Self {
AppUserTokenClaims {
client_id: client_id.into(),
user_id: user_id.into(),
scopes,
impl OAuth2AccessTokenClaims {
pub fn new(config: &Config, client_id: &str, user: &User, scopes: Vec<AuthorizationScope>) -> Self {
OAuth2AccessTokenClaims {
iss: config.instance.base_uri.clone(),
aud: client_id.into(),
sub: user.id.clone(),
exp: get_current_timestamp() + 86_000,
iss: "Minauth".into()
scopes,
}
}
}
/// @See https://openid.net/specs/openid-connect-core-1_0.html#IDToken
/// @See https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
#[derive(Debug, Serialize, Deserialize, Clone)]
#[fully_pub]
struct OIDCIdTokenClaims {
/// Token expiration
exp: u64,
/// Token issuer (URI to the issuer)
iss: String,
/// Audiance (client_id)
aud: String,
/// End-user id assigned by the issuer (user_id)
sub: String,
/// additional claims
name: Option<String>,
email: Option<String>,
preferred_username: Option<String>,
roles: Vec<String>
}
impl OIDCIdTokenClaims {
pub fn new(config: &Config, client_id: &str, user: &User) -> Self {
OIDCIdTokenClaims {
iss: config.instance.base_uri.clone(),
aud: client_id.into(),
sub: user.id.clone(),
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()
}
}
}