fully working totp authentication
This commit is contained in:
parent
6619662d0d
commit
fffcf5ee8f
|
@ -7,7 +7,10 @@ pub struct PartialUserInfo {
|
|||
pub user_id: i32,
|
||||
pub created_at: i64,
|
||||
pub email: String,
|
||||
pub has_totp: bool
|
||||
pub has_totp: bool,
|
||||
|
||||
pub session_id: String,
|
||||
pub auth_id: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -86,6 +89,8 @@ impl<'r> FromRequest<'r> for PartialUserInfo {
|
|||
created_at: user.created_on as i64,
|
||||
email: user.email,
|
||||
has_totp: at.is_some(),
|
||||
session_id: st,
|
||||
auth_id: at,
|
||||
})
|
||||
} else {
|
||||
Outcome::Failure((Status::Unauthorized, AuthenticationError::MissingToken))
|
||||
|
|
|
@ -81,7 +81,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||
crate::routes::v1::auth::magic_link::magiclink_request,
|
||||
crate::routes::v1::signup::signup_request,
|
||||
crate::routes::v1::auth::verify_magic_link::verify_magic_link,
|
||||
crate::routes::v1::totp_authenticators::totp_authenticators_request
|
||||
crate::routes::v1::totp_authenticators::totp_authenticators_request,
|
||||
crate::routes::v1::verify_totp_authenticator::verify_totp_authenticator_request
|
||||
])
|
||||
.register("/", catchers![
|
||||
crate::routes::handler_400,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub mod auth;
|
||||
pub mod signup;
|
||||
|
||||
pub mod totp_authenticators;
|
||||
pub mod totp_authenticators;
|
||||
pub mod verify_totp_authenticator;
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
{"totpToken":"totp-mH9eLzA9Q5WB-sg3Fq8CfkP13eTh3DxF25kVK2VEDOk","code":"266242"}
|
||||
{"errors":[{"code":"ERR_INVALID_TOTP_TOKEN","message":"TOTP token does not exist (maybe it expired?)","path":"totpToken"}]}
|
||||
|
||||
|
||||
{"totpToken":"totp-gaUDaxPrrIBc8GEQ6z0vPisT8k0MEP1fgI8FA2ztLMw","code":"175543"}
|
||||
{"data":{"authToken":"auth-O7mugxdYta-RKtLMqDW4j8XCJ85EfZKKezeZZXBYtFQ"},"metadata":{}}
|
||||
*/
|
||||
|
||||
use std::error::Error;
|
||||
use rocket::http::{ContentType, Status};
|
||||
use crate::auth::PartialUserInfo;
|
||||
use rocket::post;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sqlx::PgPool;
|
||||
use totp_rs::TOTP;
|
||||
use crate::tokens::{generate_auth_token, use_totp_token, verify_totp_token};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyTotpAuthenticatorRequest {
|
||||
#[serde(rename = "totpToken")]
|
||||
pub totp_token: String,
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyTotpAuthenticatorResponseMetadata {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyTotpAuthenticatorResponseData {
|
||||
#[serde(rename = "authToken")]
|
||||
pub auth_token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyTotpAuthenticatorResponse {
|
||||
pub data: VerifyTotpAuthenticatorResponseData,
|
||||
pub metadata: VerifyTotpAuthenticatorResponseMetadata,
|
||||
}
|
||||
|
||||
#[post("/v1/verify-totp-authenticator", data = "<req>")]
|
||||
pub async fn verify_totp_authenticator_request(req: Json<VerifyTotpAuthenticatorRequest>, db: &State<PgPool>, user: PartialUserInfo) -> Result<(ContentType, Json<VerifyTotpAuthenticatorResponse>), (Status, String)> {
|
||||
let totpmachine = match verify_totp_token(req.0.totp_token.clone(), user.email.clone(), db.inner()).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNAUTHORIZED", "this token is invalid", e)))
|
||||
};
|
||||
if !totpmachine.check_current(&req.0.code).unwrap() {
|
||||
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\",\"path\":\"totpToken\"}}]}}", "ERR_INVALID_TOTP_TOKEN", "TOTP token does not exist (maybe it expired?)")))
|
||||
}
|
||||
match use_totp_token(req.0.totp_token, user.email, db.inner()).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e)))
|
||||
}
|
||||
Ok((ContentType::JSON, Json(VerifyTotpAuthenticatorResponse {
|
||||
data: VerifyTotpAuthenticatorResponseData { auth_token: match generate_auth_token(user.user_id as i64, user.session_id, db.inner()).await {
|
||||
Ok(at) => at,
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e)))
|
||||
} },
|
||||
metadata: VerifyTotpAuthenticatorResponseMetadata {},
|
||||
})))
|
||||
}
|
|
@ -64,7 +64,7 @@ pub async fn create_totp_token(email: String, db: &PgPool, config: &TFConfig) ->
|
|||
}
|
||||
|
||||
pub async fn verify_totp_token(otpid: String, email: String, db: &PgPool) -> Result<TOTP, Box<dyn Error>> {
|
||||
let totprow = sqlx::query!("SELECT * FROM totp_create_tokens WHERE id = $1 AND expires_on < $2", otpid, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32).fetch_one(db).await?;
|
||||
let totprow = sqlx::query!("SELECT * FROM totp_create_tokens WHERE id = $1 AND expires_on > $2", otpid, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32).fetch_one(db).await?;
|
||||
let secret = Secret::Encoded(totprow.totp_secret);
|
||||
let totpmachine = TOTP::new(TOTP_ALGORITHM, TOTP_DIGITS, TOTP_SKEW, TOTP_STEP, secret.to_bytes().unwrap(), Some(TOTP_ISSUER.to_string()), email).unwrap();
|
||||
|
||||
|
|
Loading…
Reference in New Issue