From 41f5e7ba3cb9c5153ce8c7bb6a68a0ac5daddfe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Thu, 21 Aug 2025 12:06:11 +0200 Subject: [PATCH 1/6] Update proto --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index dbb08b7..070511d 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit dbb08b75e1b009c48a73bfc07cb25bea1838243a +Subproject commit 070511d2f5496485f83e074cbf836c2bbf258c9b From a80fb3f04afbf96e55f9e8ed799173ee6337dc12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Thu, 21 Aug 2025 13:50:00 +0200 Subject: [PATCH 2/6] register mfa endpoints --- proto | 2 +- src/handlers/mod.rs | 1 + src/handlers/register_mfa.rs | 91 ++++++++++++++++++++++++++++++++++++ src/http.rs | 3 +- 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/handlers/register_mfa.rs diff --git a/proto b/proto index 070511d..ae1a7a6 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 070511d2f5496485f83e074cbf836c2bbf258c9b +Subproject commit ae1a7a6e44071f0cf6c78682a4300621a1b7d566 diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index cb74e02..bb49ab7 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -14,6 +14,7 @@ pub(crate) mod enrollment; pub(crate) mod mobile_client; pub(crate) mod password_reset; pub(crate) mod polling; +pub(crate) mod register_mfa; // Timeout for awaiting response from Defguard Core. const CORE_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5); diff --git a/src/handlers/register_mfa.rs b/src/handlers/register_mfa.rs new file mode 100644 index 0000000..310dea4 --- /dev/null +++ b/src/handlers/register_mfa.rs @@ -0,0 +1,91 @@ +use axum::Router; +use serde::Deserialize; + +use axum::{extract::State, response::IntoResponse, routing::post, Json}; +use axum_extra::extract::PrivateCookieJar; + +use crate::http::ENROLLMENT_COOKIE_NAME; +use crate::proto::MfaMethod; +use crate::{ + error::ApiError, + handlers::get_core_response, + http::AppState, + proto::{ + core_request, core_response, CodeMfaSetupFinishRequest, CodeMfaSetupFinishResponse, + CodeMfaSetupStartRequest, CodeMfaSetupStartResponse, DeviceInfo, + }, +}; + +pub(crate) fn router() -> Router { + Router::new() + .route("/start", post(register_code_mfa_start)) + .route("/finish", post(register_code_mfa_finish)) +} + +#[derive(Debug, Clone, Deserialize)] +struct RegisterMfaCodeStartRequest { + pub method: MfaMethod, +} + +#[instrument(level = "debug", skip(state, req))] +async fn register_code_mfa_start( + State(state): State, + device_info: DeviceInfo, + cookie_jar: PrivateCookieJar, + Json(req): Json, +) -> Result, impl IntoResponse> { + let token = cookie_jar + .get(ENROLLMENT_COOKIE_NAME) + .ok_or_else(|| ApiError::Unauthorized(String::new()))? + .value() + .to_string(); + + if req.method != MfaMethod::Email && req.method != MfaMethod::Totp { + return Err(ApiError::BadRequest("Method not supported.".to_string())); + } + + let rx = state.grpc_server.send( + core_request::Payload::CodeMfaSetupStartRequest(CodeMfaSetupStartRequest { + token, + method: req.method.into(), + }), + device_info, + )?; + let payload = get_core_response(rx).await?; + match payload { + core_response::Payload::CodeMfaSetupStartResponse(response) => Ok(Json(response)), + _ => Err(ApiError::InvalidResponseType), + } +} + +#[derive(Debug, Clone, Deserialize)] +struct RegisterMfaCodeFinishRequest { + pub code: String, +} + +#[instrument(level = "debug", skip(state, req))] +async fn register_code_mfa_finish( + State(state): State, + device_info: DeviceInfo, + cookie_jar: PrivateCookieJar, + Json(req): Json, +) -> Result, impl IntoResponse> { + let token = cookie_jar + .get(ENROLLMENT_COOKIE_NAME) + .ok_or_else(|| ApiError::Unauthorized(String::new()))? + .value() + .to_string(); + + let rx = state.grpc_server.send( + core_request::Payload::CodeMfaSetupFinishRequest(CodeMfaSetupFinishRequest { + token, + code: req.code, + }), + device_info, + )?; + let payload = get_core_response(rx).await?; + match payload { + core_response::Payload::CodeMfaSetupFinishResponse(response) => Ok(Json(response)), + _ => Err(ApiError::InvalidResponseType), + } +} diff --git a/src/http.rs b/src/http.rs index 5662c08..c1e5b53 100644 --- a/src/http.rs +++ b/src/http.rs @@ -32,7 +32,7 @@ use crate::{ enterprise::handlers::openid_login::{self, FlowType}, error::ApiError, grpc::ProxyServer, - handlers::{desktop_client_mfa, enrollment, password_reset, polling}, + handlers::{desktop_client_mfa, enrollment, password_reset, polling, register_mfa}, proto::proxy_server, }; @@ -222,6 +222,7 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { .nest("/password-reset", password_reset::router()) .nest("/client-mfa", desktop_client_mfa::router()) .nest("/openid", openid_login::router()) + .nest("/register-mfa", register_mfa::router()) .route("/poll", post(polling::info)) .route("/health", get(healthcheck)) .route("/health-grpc", get(healthcheckgrpc)) From 28ef3d658f1baa3dcdb0a0ff47d0b3669146c5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Thu, 21 Aug 2025 13:55:04 +0200 Subject: [PATCH 3/6] sort use --- src/handlers/register_mfa.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/handlers/register_mfa.rs b/src/handlers/register_mfa.rs index 310dea4..432f3b4 100644 --- a/src/handlers/register_mfa.rs +++ b/src/handlers/register_mfa.rs @@ -1,18 +1,15 @@ -use axum::Router; use serde::Deserialize; -use axum::{extract::State, response::IntoResponse, routing::post, Json}; +use axum::{extract::State, response::IntoResponse, routing::post, Json, Router}; use axum_extra::extract::PrivateCookieJar; -use crate::http::ENROLLMENT_COOKIE_NAME; -use crate::proto::MfaMethod; use crate::{ error::ApiError, handlers::get_core_response, - http::AppState, + http::{AppState, ENROLLMENT_COOKIE_NAME}, proto::{ core_request, core_response, CodeMfaSetupFinishRequest, CodeMfaSetupFinishResponse, - CodeMfaSetupStartRequest, CodeMfaSetupStartResponse, DeviceInfo, + CodeMfaSetupStartRequest, CodeMfaSetupStartResponse, DeviceInfo, MfaMethod, }, }; From b532973c778314b6f95a99f8a9f39bb22936c89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Mon, 25 Aug 2025 08:28:22 +0200 Subject: [PATCH 4/6] update register finish --- .gitignore | 1 + proto | 2 +- src/handlers/enrollment.rs | 5 ++++- src/handlers/register_mfa.rs | 13 ++++++++++--- src/http.rs | 3 +-- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 54bf0c2..f2379c7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ .direnv/ .envrc /node_modules +.env diff --git a/proto b/proto index ae1a7a6..710bc6f 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit ae1a7a6e44071f0cf6c78682a4300621a1b7d566 +Subproject commit 710bc6fa0d55e012ee4d8059d420ab1901b0ba9c diff --git a/src/handlers/enrollment.rs b/src/handlers/enrollment.rs index eed4c98..7d8b99b 100644 --- a/src/handlers/enrollment.rs +++ b/src/handlers/enrollment.rs @@ -2,6 +2,8 @@ use axum::{extract::State, routing::post, Json, Router}; use axum_extra::extract::{cookie::Cookie, PrivateCookieJar}; use time::OffsetDateTime; +use super::register_mfa::router as register_mfa_router; + use crate::{ error::ApiError, handlers::{get_core_response, mobile_client::register_mobile_auth}, @@ -14,6 +16,7 @@ use crate::{ pub(crate) fn router() -> Router { Router::new() + .nest("/register-mfa", register_mfa_router()) .route("/start", post(start_enrollment_process)) .route("/activate_user", post(activate_user)) .route("/create_device", post(create_device)) @@ -82,7 +85,7 @@ async fn activate_user( .grpc_server .send(core_request::Payload::ActivateUser(req), device_info)?; let payload = get_core_response(rx).await?; - debug!("Receving payload from the core service. Trying to remove private cookie..."); + debug!("Receiving payload from the core service. Trying to remove private cookie..."); if let core_response::Payload::Empty(()) = payload { info!("Activated user - phone number {phone:?}"); if let Some(cookie) = private_cookies.get(ENROLLMENT_COOKIE_NAME) { diff --git a/src/handlers/register_mfa.rs b/src/handlers/register_mfa.rs index 432f3b4..4c294f8 100644 --- a/src/handlers/register_mfa.rs +++ b/src/handlers/register_mfa.rs @@ -31,6 +31,7 @@ async fn register_code_mfa_start( cookie_jar: PrivateCookieJar, Json(req): Json, ) -> Result, impl IntoResponse> { + debug!("Register code MFA started"); let token = cookie_jar .get(ENROLLMENT_COOKIE_NAME) .ok_or_else(|| ApiError::Unauthorized(String::new()))? @@ -38,11 +39,12 @@ async fn register_code_mfa_start( .to_string(); if req.method != MfaMethod::Email && req.method != MfaMethod::Totp { + error!("Requested method not supported"); return Err(ApiError::BadRequest("Method not supported.".to_string())); } let rx = state.grpc_server.send( - core_request::Payload::CodeMfaSetupStartRequest(CodeMfaSetupStartRequest { + core_request::Payload::CodeMfaSetupStart(CodeMfaSetupStartRequest { token, method: req.method.into(), }), @@ -58,6 +60,7 @@ async fn register_code_mfa_start( #[derive(Debug, Clone, Deserialize)] struct RegisterMfaCodeFinishRequest { pub code: String, + pub method: MfaMethod, } #[instrument(level = "debug", skip(state, req))] @@ -73,10 +76,14 @@ async fn register_code_mfa_finish( .value() .to_string(); + let code = req.code; + let method = req.method; + let rx = state.grpc_server.send( - core_request::Payload::CodeMfaSetupFinishRequest(CodeMfaSetupFinishRequest { + core_request::Payload::CodeMfaSetupFinish(CodeMfaSetupFinishRequest { token, - code: req.code, + code, + method: method as i32, }), device_info, )?; diff --git a/src/http.rs b/src/http.rs index c1e5b53..5662c08 100644 --- a/src/http.rs +++ b/src/http.rs @@ -32,7 +32,7 @@ use crate::{ enterprise::handlers::openid_login::{self, FlowType}, error::ApiError, grpc::ProxyServer, - handlers::{desktop_client_mfa, enrollment, password_reset, polling, register_mfa}, + handlers::{desktop_client_mfa, enrollment, password_reset, polling}, proto::proxy_server, }; @@ -222,7 +222,6 @@ pub async fn run_server(config: Config) -> anyhow::Result<()> { .nest("/password-reset", password_reset::router()) .nest("/client-mfa", desktop_client_mfa::router()) .nest("/openid", openid_login::router()) - .nest("/register-mfa", register_mfa::router()) .route("/poll", post(polling::info)) .route("/health", get(healthcheck)) .route("/health-grpc", get(healthcheckgrpc)) From 5ccb236d9524232068253a239f485416c8014cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Tue, 26 Aug 2025 09:55:14 +0200 Subject: [PATCH 5/6] nest routes for better backwards compat --- src/handlers/register_mfa.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/handlers/register_mfa.rs b/src/handlers/register_mfa.rs index 4c294f8..caef630 100644 --- a/src/handlers/register_mfa.rs +++ b/src/handlers/register_mfa.rs @@ -15,8 +15,8 @@ use crate::{ pub(crate) fn router() -> Router { Router::new() - .route("/start", post(register_code_mfa_start)) - .route("/finish", post(register_code_mfa_finish)) + .route("/code/start", post(register_code_mfa_start)) + .route("/code/finish", post(register_code_mfa_finish)) } #[derive(Debug, Clone, Deserialize)] @@ -79,6 +79,10 @@ async fn register_code_mfa_finish( let code = req.code; let method = req.method; + if method != MfaMethod::Totp && method != MfaMethod::Email { + return Err(ApiError::BadRequest("Method not supported".to_string())); + } + let rx = state.grpc_server.send( core_request::Payload::CodeMfaSetupFinish(CodeMfaSetupFinishRequest { token, From bdb2277d74b232bbe8b053028b960721dcb72699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= Date: Tue, 26 Aug 2025 10:04:19 +0200 Subject: [PATCH 6/6] Update proto --- proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proto b/proto index 710bc6f..4688d4d 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 710bc6fa0d55e012ee4d8059d420ab1901b0ba9c +Subproject commit 4688d4d587246e09d800c03963721b437f3a3eca