Skip to content

Commit c25bd84

Browse files
add code based mfa setup (#141)
* Update proto * register mfa endpoints * sort use * update register finish * nest routes for better backwards compat * Update proto
1 parent 45cbaf6 commit c25bd84

File tree

5 files changed

+106
-2
lines changed

5 files changed

+106
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
.direnv/
55
.envrc
66
/node_modules
7+
.env

proto

src/handlers/enrollment.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use axum::{extract::State, routing::post, Json, Router};
22
use axum_extra::extract::{cookie::Cookie, PrivateCookieJar};
33
use time::OffsetDateTime;
44

5+
use super::register_mfa::router as register_mfa_router;
6+
57
use crate::{
68
error::ApiError,
79
handlers::{get_core_response, mobile_client::register_mobile_auth},
@@ -14,6 +16,7 @@ use crate::{
1416

1517
pub(crate) fn router() -> Router<AppState> {
1618
Router::new()
19+
.nest("/register-mfa", register_mfa_router())
1720
.route("/start", post(start_enrollment_process))
1821
.route("/activate_user", post(activate_user))
1922
.route("/create_device", post(create_device))
@@ -82,7 +85,7 @@ async fn activate_user(
8285
.grpc_server
8386
.send(core_request::Payload::ActivateUser(req), device_info)?;
8487
let payload = get_core_response(rx).await?;
85-
debug!("Receving payload from the core service. Trying to remove private cookie...");
88+
debug!("Receiving payload from the core service. Trying to remove private cookie...");
8689
if let core_response::Payload::Empty(()) = payload {
8790
info!("Activated user - phone number {phone:?}");
8891
if let Some(cookie) = private_cookies.get(ENROLLMENT_COOKIE_NAME) {

src/handlers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub(crate) mod enrollment;
1414
pub(crate) mod mobile_client;
1515
pub(crate) mod password_reset;
1616
pub(crate) mod polling;
17+
pub(crate) mod register_mfa;
1718

1819
// Timeout for awaiting response from Defguard Core.
1920
const CORE_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5);

src/handlers/register_mfa.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use serde::Deserialize;
2+
3+
use axum::{extract::State, response::IntoResponse, routing::post, Json, Router};
4+
use axum_extra::extract::PrivateCookieJar;
5+
6+
use crate::{
7+
error::ApiError,
8+
handlers::get_core_response,
9+
http::{AppState, ENROLLMENT_COOKIE_NAME},
10+
proto::{
11+
core_request, core_response, CodeMfaSetupFinishRequest, CodeMfaSetupFinishResponse,
12+
CodeMfaSetupStartRequest, CodeMfaSetupStartResponse, DeviceInfo, MfaMethod,
13+
},
14+
};
15+
16+
pub(crate) fn router() -> Router<AppState> {
17+
Router::new()
18+
.route("/code/start", post(register_code_mfa_start))
19+
.route("/code/finish", post(register_code_mfa_finish))
20+
}
21+
22+
#[derive(Debug, Clone, Deserialize)]
23+
struct RegisterMfaCodeStartRequest {
24+
pub method: MfaMethod,
25+
}
26+
27+
#[instrument(level = "debug", skip(state, req))]
28+
async fn register_code_mfa_start(
29+
State(state): State<AppState>,
30+
device_info: DeviceInfo,
31+
cookie_jar: PrivateCookieJar,
32+
Json(req): Json<RegisterMfaCodeStartRequest>,
33+
) -> Result<Json<CodeMfaSetupStartResponse>, impl IntoResponse> {
34+
debug!("Register code MFA started");
35+
let token = cookie_jar
36+
.get(ENROLLMENT_COOKIE_NAME)
37+
.ok_or_else(|| ApiError::Unauthorized(String::new()))?
38+
.value()
39+
.to_string();
40+
41+
if req.method != MfaMethod::Email && req.method != MfaMethod::Totp {
42+
error!("Requested method not supported");
43+
return Err(ApiError::BadRequest("Method not supported.".to_string()));
44+
}
45+
46+
let rx = state.grpc_server.send(
47+
core_request::Payload::CodeMfaSetupStart(CodeMfaSetupStartRequest {
48+
token,
49+
method: req.method.into(),
50+
}),
51+
device_info,
52+
)?;
53+
let payload = get_core_response(rx).await?;
54+
match payload {
55+
core_response::Payload::CodeMfaSetupStartResponse(response) => Ok(Json(response)),
56+
_ => Err(ApiError::InvalidResponseType),
57+
}
58+
}
59+
60+
#[derive(Debug, Clone, Deserialize)]
61+
struct RegisterMfaCodeFinishRequest {
62+
pub code: String,
63+
pub method: MfaMethod,
64+
}
65+
66+
#[instrument(level = "debug", skip(state, req))]
67+
async fn register_code_mfa_finish(
68+
State(state): State<AppState>,
69+
device_info: DeviceInfo,
70+
cookie_jar: PrivateCookieJar,
71+
Json(req): Json<RegisterMfaCodeFinishRequest>,
72+
) -> Result<Json<CodeMfaSetupFinishResponse>, impl IntoResponse> {
73+
let token = cookie_jar
74+
.get(ENROLLMENT_COOKIE_NAME)
75+
.ok_or_else(|| ApiError::Unauthorized(String::new()))?
76+
.value()
77+
.to_string();
78+
79+
let code = req.code;
80+
let method = req.method;
81+
82+
if method != MfaMethod::Totp && method != MfaMethod::Email {
83+
return Err(ApiError::BadRequest("Method not supported".to_string()));
84+
}
85+
86+
let rx = state.grpc_server.send(
87+
core_request::Payload::CodeMfaSetupFinish(CodeMfaSetupFinishRequest {
88+
token,
89+
code,
90+
method: method as i32,
91+
}),
92+
device_info,
93+
)?;
94+
let payload = get_core_response(rx).await?;
95+
match payload {
96+
core_response::Payload::CodeMfaSetupFinishResponse(response) => Ok(Json(response)),
97+
_ => Err(ApiError::InvalidResponseType),
98+
}
99+
}

0 commit comments

Comments
 (0)