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")]
|
||||
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
|
|
@ -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(())
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod auth;
|
||||
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