finish /v1/verify-totp-authenticators
This commit is contained in:
parent
d70064f998
commit
87ddb07d5c
|
@ -61,7 +61,9 @@ pub struct TrifidConfigTokens {
|
||||||
#[serde(default = "session_token_expiry_time")]
|
#[serde(default = "session_token_expiry_time")]
|
||||||
pub session_token_expiry_time_seconds: u64,
|
pub session_token_expiry_time_seconds: u64,
|
||||||
#[serde(default = "totp_setup_timeout_time")]
|
#[serde(default = "totp_setup_timeout_time")]
|
||||||
pub totp_setup_timeout_time_seconds: u64
|
pub totp_setup_timeout_time_seconds: u64,
|
||||||
|
#[serde(default = "mfa_tokens_expiry_time")]
|
||||||
|
pub mfa_tokens_expiry_time_seconds: u64
|
||||||
}
|
}
|
||||||
|
|
||||||
fn max_connections_default() -> u32 { 100 }
|
fn max_connections_default() -> u32 { 100 }
|
||||||
|
@ -72,3 +74,4 @@ fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::
|
||||||
fn magic_link_expiry_time() -> u64 { 3600 } // 1 hour
|
fn magic_link_expiry_time() -> u64 { 3600 } // 1 hour
|
||||||
fn session_token_expiry_time() -> u64 { 15780000 } // 6 months
|
fn session_token_expiry_time() -> u64 { 15780000 } // 6 months
|
||||||
fn totp_setup_timeout_time() -> u64 { 600 } // 10 minutes
|
fn totp_setup_timeout_time() -> u64 { 600 } // 10 minutes
|
||||||
|
fn mfa_tokens_expiry_time() -> u64 { 600 } // 10 minutes
|
|
@ -66,6 +66,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
.service(routes::v1::signup::signup_request)
|
.service(routes::v1::signup::signup_request)
|
||||||
.service(routes::v1::auth::verify_magic_link::verify_magic_link_request)
|
.service(routes::v1::auth::verify_magic_link::verify_magic_link_request)
|
||||||
.service(routes::v1::totp_authenticators::totp_authenticators_request)
|
.service(routes::v1::totp_authenticators::totp_authenticators_request)
|
||||||
|
.service(routes::v1::verify_totp_authenticators::verify_totp_authenticators_request)
|
||||||
}).bind(CONFIG.server.bind)?.run().await?;
|
}).bind(CONFIG.server.bind)?.run().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod signup;
|
pub mod signup;
|
||||||
pub mod totp_authenticators;
|
pub mod totp_authenticators;
|
||||||
|
pub mod verify_totp_authenticators;
|
|
@ -0,0 +1,198 @@
|
||||||
|
use actix_web::{HttpRequest, HttpResponse, post};
|
||||||
|
use actix_web::web::{Data, Json};
|
||||||
|
use log::error;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use trifid_api_entities::entity::totp_authenticator;
|
||||||
|
use crate::AppState;
|
||||||
|
use crate::auth_tokens::{enforce_session, TokenInfo};
|
||||||
|
use crate::error::{APIError, APIErrorsResponse};
|
||||||
|
use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, IntoActiveModel, ActiveModelTrait};
|
||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
use totp_rs::{Algorithm, Secret, TOTP};
|
||||||
|
use trifid_api_entities::entity::auth_token;
|
||||||
|
use crate::config::CONFIG;
|
||||||
|
use crate::timers::expires_in_seconds;
|
||||||
|
use crate::tokens::random_token;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct VerifyTotpAuthenticatorsRequest {
|
||||||
|
#[serde(rename = "totpToken")]
|
||||||
|
pub totp_token: String,
|
||||||
|
pub code: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct VerifyTotpAuthenticatorsResponse {
|
||||||
|
pub data: VerifyTotpAuthenticatorsResponseData,
|
||||||
|
pub metadata: VerifyTotpAuthenticatorsResponseMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct VerifyTotpAuthenticatorsResponseData {
|
||||||
|
#[serde(rename = "authToken")]
|
||||||
|
pub auth_token: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct VerifyTotpAuthenticatorsResponseMetadata {}
|
||||||
|
|
||||||
|
#[post("/v1/verify-totp-authenticators")]
|
||||||
|
pub async fn verify_totp_authenticators_request(req: Json<VerifyTotpAuthenticatorsRequest>, req_data: HttpRequest, db: Data<AppState>) -> HttpResponse {
|
||||||
|
// require a user session
|
||||||
|
let session_token = match enforce_session(&req_data, &db.conn).await {
|
||||||
|
Ok(r) => {
|
||||||
|
match r {
|
||||||
|
TokenInfo::SessionToken(i) => i,
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("error enforcing session: {}", e);
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_UNAUTHORIZED".to_string(),
|
||||||
|
message: "Unauthorized".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// determine if the user has a totp authenticator
|
||||||
|
let auther = match totp_authenticator::Entity::find().filter(totp_authenticator::Column::User.eq(&session_token.user.id)).one(&db.conn).await {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
error!("database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error with the database request, please try again later.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let auther = match auther {
|
||||||
|
Some(a) => {
|
||||||
|
if a.verified {
|
||||||
|
return HttpResponse::BadRequest().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_ALREADY_HAS_TOTP".to_string(),
|
||||||
|
message: "This user already has a totp authenticator".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
a
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return HttpResponse::BadRequest().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_USER_NO_TOTP".to_string(),
|
||||||
|
message: "This user does not have a totp authenticator".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let secret = Secret::Encoded(auther.secret.clone());
|
||||||
|
let totpmachine = match TOTP::from_url(auther.url.clone()) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
error!("totp url error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_SECRET_ERROR".to_string(),
|
||||||
|
message: "There was an error parsing the totpmachine. Please try again later.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let valid = match totpmachine.check_current(&req.totp_token) {
|
||||||
|
Ok(valid) => valid,
|
||||||
|
Err(e) => {
|
||||||
|
error!("system time error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_TIME_ERROR".to_string(),
|
||||||
|
message: "There was an with the server-side time clock.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_UNAUTHORIZED".to_string(),
|
||||||
|
message: "Unauthorized".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut active_model = auther.into_active_model();
|
||||||
|
|
||||||
|
active_model.verified = Set(true);
|
||||||
|
|
||||||
|
match active_model.update(&db.conn).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error updating the totpmachine, please try again later.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let model: auth_token::Model = auth_token::Model {
|
||||||
|
id: random_token("auth"),
|
||||||
|
user: session_token.user.id,
|
||||||
|
expires_on: expires_in_seconds(CONFIG.tokens.mfa_tokens_expiry_time_seconds) as i64,
|
||||||
|
};
|
||||||
|
let token = model.id.clone();
|
||||||
|
let active_model = model.into_active_model();
|
||||||
|
match active_model.insert(&db.conn).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error issuing the authentication token.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(VerifyTotpAuthenticatorsResponse {
|
||||||
|
data: VerifyTotpAuthenticatorsResponseData { auth_token: token },
|
||||||
|
metadata: VerifyTotpAuthenticatorsResponseMetadata {},
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue