finish /v1/verify-totp-authenticators

This commit is contained in:
c0repwn3r 2023-04-02 21:47:32 -04:00
parent d70064f998
commit 87ddb07d5c
Signed by: core
GPG Key ID: FDBF740DADDCEECF
4 changed files with 206 additions and 3 deletions

View File

@ -61,7 +61,9 @@ pub struct TrifidConfigTokens {
#[serde(default = "session_token_expiry_time")]
pub session_token_expiry_time_seconds: u64,
#[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 }
@ -71,4 +73,5 @@ fn sqlx_logging_default() -> bool { true }
fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) }
fn magic_link_expiry_time() -> u64 { 3600 } // 1 hour
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

View File

@ -66,6 +66,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
.service(routes::v1::signup::signup_request)
.service(routes::v1::auth::verify_magic_link::verify_magic_link_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?;
Ok(())

View File

@ -1,3 +1,4 @@
pub mod auth;
pub mod signup;
pub mod totp_authenticators;
pub mod totp_authenticators;
pub mod verify_totp_authenticators;

View File

@ -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 {},
})
}