diff --git a/trifid-api/src/auth.rs b/trifid-api/src/auth.rs index ad8ef17..8b61e68 100644 --- a/trifid-api/src/auth.rs +++ b/trifid-api/src/auth.rs @@ -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 } #[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)) diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 18e86c1..6d80fdc 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -81,7 +81,8 @@ async fn main() -> Result<(), Box> { 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, diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs index 3044952..6b8e56f 100644 --- a/trifid-api/src/routes/v1/mod.rs +++ b/trifid-api/src/routes/v1/mod.rs @@ -1,4 +1,5 @@ pub mod auth; pub mod signup; -pub mod totp_authenticators; \ No newline at end of file +pub mod totp_authenticators; +pub mod verify_totp_authenticator; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/verify_totp_authenticator.rs b/trifid-api/src/routes/v1/verify_totp_authenticator.rs new file mode 100644 index 0000000..3bc5ebb --- /dev/null +++ b/trifid-api/src/routes/v1/verify_totp_authenticator.rs @@ -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 = "")] +pub async fn verify_totp_authenticator_request(req: Json, db: &State, user: PartialUserInfo) -> Result<(ContentType, Json), (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 {}, + }))) +} \ No newline at end of file diff --git a/trifid-api/src/tokens.rs b/trifid-api/src/tokens.rs index 14cc243..6021278 100644 --- a/trifid-api/src/tokens.rs +++ b/trifid-api/src/tokens.rs @@ -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> { - 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();