create totp authenticator
/ build (push) Successful in 46s Details
/ build_x64 (push) Successful in 2m7s Details
/ build_arm64 (push) Successful in 2m34s Details
/ build_win64 (push) Successful in 2m40s Details

This commit is contained in:
core 2023-11-21 11:26:25 -05:00
parent 68627b0c92
commit 4180bdd162
Signed by: core
GPG Key ID: FDBF740DADDCEECF
5 changed files with 120 additions and 19 deletions

37
Cargo.lock generated
View File

@ -354,6 +354,12 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base32"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.13.1" version = "0.13.1"
@ -650,6 +656,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "constant_time_eq"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.4.0" version = "0.4.0"
@ -2902,6 +2914,22 @@ dependencies = [
"winnow", "winnow",
] ]
[[package]]
name = "totp-rs"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3504f96adf86d28e7eb16fa236a7951ec72c15ee100d1b5318e225944bc8cb"
dependencies = [
"base32",
"constant_time_eq 0.2.6",
"hmac",
"rand",
"sha1",
"sha2",
"url",
"urlencoding",
]
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.2" version = "0.3.2"
@ -2957,6 +2985,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"toml 0.8.5", "toml 0.8.5",
"totp-rs",
] ]
[[package]] [[package]]
@ -3037,6 +3066,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.1"
@ -3417,7 +3452,7 @@ dependencies = [
"aes", "aes",
"byteorder", "byteorder",
"bzip2", "bzip2",
"constant_time_eq", "constant_time_eq 0.1.5",
"crc32fast", "crc32fast",
"crossbeam-utils", "crossbeam-utils",
"flate2", "flate2",

View File

@ -25,3 +25,4 @@ diesel_migrations = "2"
bb8 = "0.8" bb8 = "0.8"
rand = "0.8" rand = "0.8"
mail-send = "0.4" mail-send = "0.4"
totp-rs = { version = "5.4", features = ["gen_secret", "otpauth"] }

View File

@ -61,36 +61,61 @@ macro_rules! auth {
#[macro_export] #[macro_export]
macro_rules! enforce { macro_rules! enforce {
(sess $i:expr) => { (sess $i:expr) => {{
if $i.session_token.is_none() { if $i.session_token.is_none() {
$crate::err!( $crate::err!(
actix_web::http::StatusCode::UNAUTHORIZED, actix_web::http::StatusCode::UNAUTHORIZED,
$crate::make_err!("ERR_UNAUTHORIZED", "unauthorized") $crate::make_err!("ERR_UNAUTHORIZED", "unauthorized")
) )
} }
}; $i.session_token.unwrap()
(auth $i:expr) => { }};
(auth $i:expr) => {{
if $i.auth_token.is_none() { if $i.auth_token.is_none() {
$crate::err!( $crate::err!(
actix_web::http::StatusCode::UNAUTHORIZED, actix_web::http::StatusCode::UNAUTHORIZED,
$crate::make_err!("ERR_UNAUTHORIZED", "unauthorized") $crate::make_err!(
"ERR_2FA_REQUIRED",
"must provide a valid 2FA token to access this endpoint"
)
) )
} }
}; $i.auth_token.unwrap()
(sess auth $i:expr) => { }};
if $i.session_token.is_none() || $i.auth_token.is_none() { (sess auth $i:expr) => {{
if $i.session_token.is_none() {
$crate::err!( $crate::err!(
actix_web::http::StatusCode::UNAUTHORIZED, actix_web::http::StatusCode::UNAUTHORIZED,
$crate::make_err!("ERR_UNAUTHORIZED", "unauthorized") $crate::make_err!("ERR_UNAUTHORIZED", "unauthorized")
) )
} }
}; if $i.auth_token.is_none() {
(auth sess $i:expr) => { $crate::err!(
if $i.session_token.is_none() || $i.auth_token.is_none() { actix_web::http::StatusCode::UNAUTHORIZED,
$crate::make_err!(
"ERR_2FA_REQUIRED",
"must provide a valid 2FA token to access this endpoint"
)
)
}
($i.session_token.unwrap(), $i.auth_token.unwrap())
}};
(auth sess $i:expr) => {{
if $i.session_token.is_none() {
$crate::err!( $crate::err!(
actix_web::http::StatusCode::UNAUTHORIZED, actix_web::http::StatusCode::UNAUTHORIZED,
$crate::make_err!("ERR_UNAUTHORIZED", "unauthorized") $crate::make_err!("ERR_UNAUTHORIZED", "unauthorized")
) )
} }
}; if $i.auth_token.is_none() {
$crate::err!(
actix_web::http::StatusCode::UNAUTHORIZED,
$crate::make_err!(
"ERR_2FA_REQUIRED",
"must provide a valid 2FA token to access this endpoint"
)
)
}
($i.session_token.unwrap(), $i.auth_token.unwrap())
}};
} }

View File

@ -133,6 +133,7 @@ async fn main() {
.service(routes::v1::signup::signup_req) .service(routes::v1::signup::signup_req)
.service(routes::v1::auth::verify_magic_link::verify_link_req) .service(routes::v1::auth::verify_magic_link::verify_link_req)
.service(routes::v1::auth::magic_link::login_req) .service(routes::v1::auth::magic_link::login_req)
.service(routes::v1::totp_authenticators::create_totp_auth_req)
.wrap(Logger::default()) .wrap(Logger::default())
.wrap(actix_cors::Cors::permissive()) .wrap(actix_cors::Cors::permissive())
.app_data(app_state.clone()) .app_data(app_state.clone())

View File

@ -1,5 +1,6 @@
use crate::models::TotpAuthenticator;
use crate::response::JsonAPIResponse; use crate::response::JsonAPIResponse;
use crate::{auth, enforce, AppState}; use crate::{auth, enforce, randid, AppState};
use actix_web::web::{Data, Json}; use actix_web::web::{Data, Json};
use actix_web::{post, HttpRequest}; use actix_web::{post, HttpRequest};
use diesel::ExpressionMethods; use diesel::ExpressionMethods;
@ -7,6 +8,10 @@ use diesel::QueryDsl;
use diesel::SelectableHelper; use diesel::SelectableHelper;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use totp_rs::{Algorithm, Secret, TOTP};
use crate::schema::totp_authenticators;
use crate::schema::users;
use crate::models::User;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct TotpAuthenticatorReq {} pub struct TotpAuthenticatorReq {}
@ -28,16 +33,50 @@ pub struct TotpAuthResp {
pub metadata: TotpAuthRespMeta, pub metadata: TotpAuthRespMeta,
} }
#[post("/v1/auth/totp-authenticators")] #[post("/v1/totp-authenticators")]
pub async fn totp_auth_req( pub async fn create_totp_auth_req(
req: Json<TotpAuthenticatorReq>, _req: Json<TotpAuthenticatorReq>,
state: Data<AppState>, state: Data<AppState>,
req_info: HttpRequest, req_info: HttpRequest,
) -> JsonAPIResponse<TotpAuthResp> { ) -> JsonAPIResponse<TotpAuthResp> {
let mut conn = handle_error!(state.pool.get().await); let mut conn = handle_error!(state.pool.get().await);
let auth_info = auth!(req_info, conn); let auth_info = auth!(req_info, conn);
enforce!(sess auth_info); let session_token = enforce!(sess auth_info);
todo!() let users_vec = handle_error!(
users::dsl::users
.filter(users::dsl::id.eq(&session_token.user_id))
.select(User::as_select())
.load(&mut conn)
.await
);
let user = handle_error!(users_vec.get(0).ok_or("impossible relation"));
let secret = Secret::generate_secret();
let totp = handle_error!(TOTP::new(Algorithm::SHA1, 6, 1, 30, handle_error!(secret.to_bytes()), Some("Trifid".to_string()), user.email.clone()));
let new_totp_authenticator = TotpAuthenticator {
id: randid!(id "totp"),
user_id: session_token.user_id,
secret: secret.to_encoded().to_string(),
verified: false
};
handle_error!(
diesel::insert_into(totp_authenticators::table)
.values(&new_totp_authenticator)
.execute(&mut conn)
.await
);
ok!(TotpAuthResp {
data: TotpAuthRespData {
totp_token: new_totp_authenticator.id.clone(),
secret: new_totp_authenticator.secret.clone(),
url: totp.get_url()
},
metadata: TotpAuthRespMeta {}
})
} }