First commit
This commit is contained in:
commit
937d9ab266
|
@ -0,0 +1,6 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
.ycm_extra_conf.py
|
||||
.vimspector.json
|
||||
tls
|
||||
.cargo
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "foxtrot_mfa"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rocket = { version = "0.5.0-rc.2", features = ["mtls"] }
|
||||
sqlx = { version = "0.6", features = [ "runtime-async-std-native-tls", "mysql" ] }
|
||||
argon2 = "0.4.1"
|
||||
rand = { version = "0.8", features = ["std"] }
|
||||
openssl = "0.10.42"
|
||||
totp-rs = "3.1.0"
|
||||
data-encoding = "1.1.0"
|
|
@ -0,0 +1,23 @@
|
|||
# Foxtrot MFA
|
||||
This application is an example implementation of 3 methods of authentication, all of which must be satisfied in order to be granted access.
|
||||
These methods are:
|
||||
* Username & password
|
||||
* Time based one time password
|
||||
* Mutual TLS certificate
|
||||
|
||||
## Setup
|
||||
|
||||
Create SQL database:
|
||||
`sudo mysql -p < migrations/20221017163745_users.sql`
|
||||
|
||||
Create TLS directory:
|
||||
`mkdir tls`
|
||||
|
||||
Generate TLS certs:
|
||||
`openssl req -x509 -newkey rsa:4096 -keyout tls/key.pem -out tls/cert.pem -sha256 -days 365 -nodes`
|
||||
|
||||
Generate CA:
|
||||
`openssl genrsa -out tls/ca.pem 4096
|
||||
openssl req -x509 -new -sha512 -nodes -key tls/ca.pem -days 365 -out tls/ca.crt`
|
||||
|
||||
And fill in the options as required.
|
|
@ -0,0 +1,7 @@
|
|||
[default.tls]
|
||||
key = "tls/key.pem"
|
||||
certs = "tls/cert.pem"
|
||||
|
||||
[default.tls.mutual]
|
||||
ca_certs = "tls/ca_crt.pem"
|
||||
mandatory = false
|
|
@ -0,0 +1,28 @@
|
|||
drop database if exists foxtrot_mfa;
|
||||
|
||||
create database foxtrot_mfa;
|
||||
|
||||
grant all privileges on foxtrot_mfa.* to foxtrot@localhost;
|
||||
|
||||
use foxtrot_mfa;
|
||||
|
||||
create table users
|
||||
(
|
||||
id uuid DEFAULT uuid() PRIMARY KEY,
|
||||
username varchar(255),
|
||||
password varchar(255),
|
||||
otpSecret varchar(255),
|
||||
creationTime timestamp
|
||||
);
|
||||
|
||||
create table certs
|
||||
(
|
||||
id uuid DEFAULT uuid() PRIMARY KEY,
|
||||
userID uuid,
|
||||
FOREIGN KEY (userID) REFERENCES users(id),
|
||||
pkcs12 BLOB,
|
||||
creationTime timestamp
|
||||
);
|
||||
|
||||
|
||||
insert into users ( id, username, password) values ( uuid(), "test", "test");
|
|
@ -0,0 +1,181 @@
|
|||
use rocket::form::Form; // for creating form struct
|
||||
use rocket::State;
|
||||
use rocket::fs::NamedFile;
|
||||
use sqlx::mysql::MySqlPool;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use rocket::mtls::Certificate;
|
||||
use rocket::response::content::RawHtml;
|
||||
use rocket::http::ContentType;
|
||||
|
||||
#[derive(FromForm)]
|
||||
pub struct RegisterForm {
|
||||
username: String,
|
||||
password: String,
|
||||
confirmpassword: String,
|
||||
}
|
||||
|
||||
#[derive(FromForm)]
|
||||
pub struct LoginForm {
|
||||
username: String,
|
||||
password: String,
|
||||
otp: String,
|
||||
}
|
||||
|
||||
pub struct Pool(pub MySqlPool);
|
||||
|
||||
/* This function takes the user's submitted registration form and adds them to the users table of
|
||||
* the database if the user's password and confirmation password are equal. If there are any
|
||||
* errors during the database operation, the error is safely handled. */
|
||||
#[post("/user/create", data = "<register_form>")]
|
||||
pub async fn user_create(register_form: Form<RegisterForm>, pool: &State<Pool>) -> RawHtml<String>
|
||||
{
|
||||
if !register_form.password.eq(®ister_form.confirmpassword) // if the password and the
|
||||
// confirmation password aren't
|
||||
// the same
|
||||
{
|
||||
let response = format!("Password and confirmation password are not the same.");
|
||||
RawHtml(response) // return error to user
|
||||
}
|
||||
else
|
||||
{
|
||||
let hash_result = super::database::hash_pass(®ister_form.password).await; // hash password
|
||||
let hash = match hash_result { // if the function returned a hash, then save it, if it
|
||||
// returned an error then show the error to the user
|
||||
Ok(hash) => hash,
|
||||
Err(e) => {
|
||||
return RawHtml(format!("Error hashing password, {}", e)) // return error to the user
|
||||
}
|
||||
};
|
||||
|
||||
// add user
|
||||
let uuid_result = super::database::add_user(®ister_form.username, &hash, &pool.0).await;
|
||||
|
||||
let uuid = match uuid_result { // if the function returned an id, then save it, if it returned an error
|
||||
// then show the error to the user
|
||||
Ok(uuid) => uuid,
|
||||
Err(e) => return RawHtml(format!("Error adding user, {}", e)) // return error to the user
|
||||
};
|
||||
|
||||
let otp_result = super::database::create_otp(&uuid, &pool.0).await;
|
||||
|
||||
let otp = match otp_result {
|
||||
Ok(otp) => otp,
|
||||
Err(e) => return RawHtml(format!("Error generating OTP, {}", e))
|
||||
};
|
||||
|
||||
let response = format!("<html><body><p>Your OTP secret is: {}</p>\n<p>Please click <a href='/user/{}/cert.p12'>here</a> to download your p12 file.</p></body</html>", otp, uuid);
|
||||
|
||||
RawHtml(response)
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is absolutely bloody awful right now but I have no idea how to generate a
|
||||
* reference to a file without actually writing to the hard disk :( */
|
||||
#[get("/user/<uuid>/cert.p12")]
|
||||
pub async fn user_cert(uuid: String, pool: &State<Pool>) -> (ContentType, NamedFile)
|
||||
{
|
||||
let cert_result = super::database::create_cert(&uuid, &pool.0).await;
|
||||
|
||||
let cert = match cert_result {
|
||||
Ok(cert) => cert,
|
||||
Err(e) => panic!("Error creating cert, {}", e)
|
||||
};
|
||||
|
||||
let cert_file_result = File::create("/tmp/browser.p12");
|
||||
|
||||
let mut cert_file = match cert_file_result {
|
||||
Ok(cert_file) => cert_file,
|
||||
Err(e) => panic!("Error creating cert file, {}", e)
|
||||
};
|
||||
|
||||
let cert_der = match cert.to_der() {
|
||||
Ok(cert_der) => cert_der,
|
||||
Err(e) => panic!("Error converting cert to der, {}", e)
|
||||
};
|
||||
|
||||
let write_result = cert_file.write_all(&cert_der[..]);
|
||||
|
||||
match write_result {
|
||||
Ok(_w) => _w,
|
||||
Err(e) => panic!("Error writing to cert file, {}", e)
|
||||
};
|
||||
|
||||
let named_cert_result = NamedFile::open("/tmp/browser.p12").await;
|
||||
|
||||
let named_cert = match named_cert_result {
|
||||
Ok(named_cert) => named_cert,
|
||||
Err(e) => panic!("Error converting cert to der, {}", e)
|
||||
};
|
||||
|
||||
let content_type = ContentType::new("application", "x-pkcs12");
|
||||
(content_type, named_cert)
|
||||
}
|
||||
|
||||
#[post("/user/login", data = "<login_form>")]
|
||||
pub async fn user_login(_cert: Certificate<'_>, login_form: Form<LoginForm>, pool: &State<Pool>) -> String
|
||||
{
|
||||
let hash_result = super::database::retrieve_hash(&login_form.username, &pool.0).await;
|
||||
|
||||
let hash_result_value = match hash_result {
|
||||
Ok(hash_result_value) => hash_result_value,
|
||||
Err(e) => return format!("Error, username doesn't exist: {}", e)
|
||||
};
|
||||
|
||||
let comparison_result = super::database::verify_pass(&login_form.password, &hash_result_value).await;
|
||||
|
||||
let comparison_result_value = match comparison_result {
|
||||
Ok(comparison_result_value) => comparison_result_value,
|
||||
Err(e) => return format!("Error when comparing hash and password: {}", e)
|
||||
};
|
||||
|
||||
let otp_result = super::database::verify_otp(&login_form.username, &login_form.otp, &pool.0).await;
|
||||
|
||||
let otp_result_value = match otp_result {
|
||||
Ok(result_value) => result_value,
|
||||
Err(e) => return format!("Error when verifying otp: {}", e)
|
||||
};
|
||||
|
||||
if otp_result_value && comparison_result_value
|
||||
{
|
||||
return format!("You've successfully authenticated!");
|
||||
}
|
||||
else
|
||||
{
|
||||
return format!("Incorrect login.");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod main_tests {
|
||||
use super::super::rocket;
|
||||
use rocket::local::asynchronous::Client;
|
||||
use rocket::http::Status;
|
||||
use rocket::http::ContentType;
|
||||
|
||||
/* This unit test checks if the /register route outputs that the password and confirmation
|
||||
* password are not equal when provided with two different inputs for those fields. */
|
||||
#[rocket::async_test]
|
||||
async fn register_test() {
|
||||
let client = Client::tracked(super::super::rocket().await).await.expect("Valid instance");
|
||||
let response = client
|
||||
.post("/register")
|
||||
.body("username=test&password=1&confirmpassword=2")
|
||||
.header(ContentType::Form)
|
||||
.dispatch()
|
||||
.await;
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.into_string().await.unwrap(), "Password and confirmation password are not equal.");
|
||||
}
|
||||
|
||||
/* This unit test checks if the connection to the database works properly */
|
||||
#[rocket::async_test]
|
||||
async fn pool_test() {
|
||||
let pool_result = super::super::database::connect().await;
|
||||
match pool_result {
|
||||
Ok(_p) => assert!(true),
|
||||
Err(_e) => assert!(false)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
use openssl::asn1::Asn1Time;
|
||||
use openssl::bn::{BigNum, MsbOption};
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::pkey::{PKey, Private};
|
||||
use openssl::rsa::Rsa;
|
||||
use openssl::x509::extension::{
|
||||
AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName,
|
||||
SubjectKeyIdentifier, ExtendedKeyUsage
|
||||
};
|
||||
use openssl::x509::{X509NameBuilder, X509Req, X509ReqBuilder, X509};
|
||||
use openssl::pkcs12::Pkcs12;
|
||||
|
||||
async fn create_req(key_pair: &PKey<Private>) -> Result<X509Req, ErrorStack>
|
||||
{
|
||||
let mut req_builder = X509ReqBuilder::new()?;
|
||||
req_builder.set_pubkey(key_pair)?;
|
||||
|
||||
let mut x509_name = X509NameBuilder::new()?;
|
||||
x509_name.append_entry_by_text("C", "GB")?;
|
||||
x509_name.append_entry_by_text("ST", "Cornwall")?;
|
||||
x509_name.append_entry_by_text("O", "Foxtrot MFA Corp.")?;
|
||||
x509_name.append_entry_by_text("CN", "foxtrot.mfa")?;
|
||||
let x509_name = x509_name.build();
|
||||
req_builder.set_subject_name(&x509_name)?;
|
||||
|
||||
req_builder.sign(key_pair, MessageDigest::sha256())?;
|
||||
let req = req_builder.build();
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
pub async fn create_ca_signed_cert(ca_cert: &X509, ca_key_pair: &PKey<Private>) -> Result<(X509, PKey<Private>), ErrorStack>
|
||||
{
|
||||
let rsa = Rsa::generate(4096)?;
|
||||
let key_pair = PKey::from_rsa(rsa)?;
|
||||
|
||||
let req = create_req(&key_pair).await?;
|
||||
|
||||
let mut cert_builder = X509::builder()?;
|
||||
cert_builder.set_version(2)?;
|
||||
let serial_number = {
|
||||
let mut serial = BigNum::new()?;
|
||||
serial.rand(159, MsbOption::MAYBE_ZERO, false)?;
|
||||
serial.to_asn1_integer()?
|
||||
};
|
||||
cert_builder.set_serial_number(&serial_number)?;
|
||||
cert_builder.set_subject_name(req.subject_name())?;
|
||||
cert_builder.set_issuer_name(ca_cert.subject_name())?;
|
||||
cert_builder.set_pubkey(&key_pair)?;
|
||||
let not_before = Asn1Time::days_from_now(0)?;
|
||||
cert_builder.set_not_before(¬_before)?;
|
||||
let not_after = Asn1Time::days_from_now(365)?;
|
||||
cert_builder.set_not_after(¬_after)?;
|
||||
|
||||
cert_builder.append_extension(BasicConstraints::new().build()?)?;
|
||||
|
||||
cert_builder.append_extension(
|
||||
KeyUsage::new()
|
||||
.critical()
|
||||
.non_repudiation()
|
||||
.digital_signature()
|
||||
.key_encipherment()
|
||||
.build()?,
|
||||
)?;
|
||||
|
||||
cert_builder.append_extension(
|
||||
ExtendedKeyUsage::new()
|
||||
.client_auth()
|
||||
.build()?,
|
||||
)?;
|
||||
|
||||
let subject_key_identifier =
|
||||
SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
|
||||
cert_builder.append_extension(subject_key_identifier)?;
|
||||
|
||||
let auth_key_identifier = AuthorityKeyIdentifier::new()
|
||||
.keyid(false)
|
||||
.issuer(false)
|
||||
.build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
|
||||
cert_builder.append_extension(auth_key_identifier)?;
|
||||
|
||||
let subject_alt_name = SubjectAlternativeName::new()
|
||||
.dns("foxtrot.mfa")
|
||||
.build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
|
||||
cert_builder.append_extension(subject_alt_name)?;
|
||||
|
||||
cert_builder.sign(ca_key_pair, MessageDigest::sha256())?;
|
||||
let cert = cert_builder.build();
|
||||
|
||||
Ok((cert, key_pair))
|
||||
}
|
||||
|
||||
pub async fn create_pkcs12(cert: &X509, key: &PKey<Private>) -> Result<Pkcs12, ErrorStack>
|
||||
{
|
||||
let pkcs12_builder = Pkcs12::builder();
|
||||
let pkcs12_keystore = pkcs12_builder.build("", "browser", key, cert)?;
|
||||
Ok(pkcs12_keystore)
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
use sqlx::mysql::MySqlPool; // for connecting to db
|
||||
use argon2::Argon2;
|
||||
use std::{env, fs};
|
||||
use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
|
||||
use rand::rngs::OsRng;
|
||||
use rand::Rng;
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::pkey::PKey;
|
||||
use openssl::x509::X509;
|
||||
use totp_rs::{Algorithm, TOTP, Secret};
|
||||
use openssl::pkcs12::Pkcs12;
|
||||
use data_encoding::base32;
|
||||
|
||||
pub async fn connect() -> Result<MySqlPool, sqlx::Error>
|
||||
{
|
||||
let url = env!("DATABASE_URL");
|
||||
|
||||
let pool = MySqlPool::connect(url).await?;
|
||||
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
pub async fn hash_pass(password: &String) -> Result<String, argon2::password_hash::Error> {
|
||||
let argon2 = Argon2::default(); // init new instance of argon2 hashing
|
||||
let password_u8 = password.as_bytes(); // get value of password as bytes
|
||||
let salt = SaltString::generate(&mut OsRng); // generate salt
|
||||
let hash = argon2.hash_password(password_u8, &salt)?.to_string(); // generate hash of password using salt
|
||||
|
||||
Ok(hash) // return hash
|
||||
}
|
||||
|
||||
pub async fn verify_pass(password: &String, hash: &String) -> Result<bool, argon2::password_hash::Error> {
|
||||
let argon2 = Argon2::default();
|
||||
let password_u8 = password.as_bytes();
|
||||
let hash_parsed = PasswordHash::new(hash)?;
|
||||
let result = argon2.verify_password(password_u8, &hash_parsed).is_ok();
|
||||
if result
|
||||
{
|
||||
return Ok(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn retrieve_hash(username: &String, pool: &MySqlPool) -> Result<String, sqlx::Error>
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
select password from users
|
||||
where username = ?
|
||||
"#,
|
||||
username)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
let hash: String = result.password.unwrap();
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
pub async fn add_user(username: &String, hash: &String, pool: &MySqlPool) -> Result<String, sqlx::Error>
|
||||
{
|
||||
sqlx::query!(
|
||||
r#"
|
||||
insert into users ( username, password )
|
||||
values ( ?, ? )
|
||||
"#,
|
||||
username,
|
||||
hash
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
let user_id_result = sqlx::query!(
|
||||
r#"
|
||||
select id from users
|
||||
where username=?
|
||||
"#,
|
||||
username
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
Ok(user_id_result.id)
|
||||
}
|
||||
|
||||
pub async fn create_otp(uuid: &String, pool: &MySqlPool) -> Result<String, sqlx::Error>
|
||||
{
|
||||
let mut secret = String::from("");
|
||||
for _ in 1..22
|
||||
{
|
||||
let mut rng = rand::thread_rng();
|
||||
let letter: char = rng.gen_range(b'a'..b'z') as char;
|
||||
secret.push(letter);
|
||||
}
|
||||
|
||||
let base32_secret = base32::encode(secret.as_bytes());
|
||||
let trimmed_secret = base32_secret.replace("=", ""); // trim the equals from the base32 encoded
|
||||
// string
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
update users
|
||||
set otpSecret = ?
|
||||
where id = ?
|
||||
"#,
|
||||
trimmed_secret, // store base32 encoded secret
|
||||
uuid
|
||||
)
|
||||
.execute(pool)
|
||||
.await?
|
||||
.rows_affected();
|
||||
|
||||
return Ok(trimmed_secret);
|
||||
}
|
||||
|
||||
pub async fn verify_otp(username: &String, otp_code: &String, pool: &MySqlPool) -> Result<bool, sqlx::Error>
|
||||
{
|
||||
let otp_result = sqlx::query!(
|
||||
r#"
|
||||
select otpSecret from users
|
||||
where username = ?
|
||||
"#,
|
||||
username
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
let otp_secret: String = otp_result.otpSecret.unwrap();
|
||||
|
||||
let totp = TOTP::new(
|
||||
Algorithm::SHA1,
|
||||
6,
|
||||
1,
|
||||
30,
|
||||
Secret::Encoded(otp_secret.to_string()).to_bytes().unwrap(),
|
||||
).unwrap();
|
||||
let expected_code = totp.generate_current().unwrap();
|
||||
|
||||
if otp_code.eq(&expected_code)
|
||||
{
|
||||
return Ok(true)
|
||||
}
|
||||
else
|
||||
{
|
||||
return Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_cert(uuid: &String, pool: &MySqlPool) -> Result<Pkcs12, ErrorStack>
|
||||
{
|
||||
let ca_crt_pem_result = fs::read_to_string("tls/ca_crt.pem");
|
||||
|
||||
let ca_crt_pem = match ca_crt_pem_result {
|
||||
Ok(ca_crt_pem) => ca_crt_pem,
|
||||
Err(e) => panic!("{}", e)
|
||||
};
|
||||
|
||||
let ca_key_pem_result = fs::read_to_string("tls/ca_key.pem");
|
||||
|
||||
let ca_key_pem = match ca_key_pem_result {
|
||||
Ok(ca_key_pem) => ca_key_pem,
|
||||
Err(e) => panic!("{}", e)
|
||||
};
|
||||
|
||||
let ca_crt_pem_obj = X509::from_pem(ca_crt_pem.as_bytes())?;
|
||||
|
||||
let ca_key_pem_obj = PKey::private_key_from_pem(ca_key_pem.as_bytes())?;
|
||||
|
||||
let cert_and_key = super::cert::create_ca_signed_cert(&ca_crt_pem_obj, &ca_key_pem_obj).await?;
|
||||
|
||||
let cert = cert_and_key.0;
|
||||
let key = cert_and_key.1;
|
||||
|
||||
let pkcs12_keystore = super::cert::create_pkcs12(&cert, &key).await?;
|
||||
|
||||
let pkcs12_der = match pkcs12_keystore.to_der() {
|
||||
Ok(pkcs12_der) => pkcs12_der,
|
||||
Err(e) => panic!("{}", e)
|
||||
};
|
||||
|
||||
let cert_id_result = sqlx::query!(
|
||||
r#"
|
||||
insert into certs ( userID, pkcs12 )
|
||||
values ( ?, ? )
|
||||
"#,
|
||||
uuid,
|
||||
&pkcs12_der[..]
|
||||
)
|
||||
.execute(pool)
|
||||
.await;
|
||||
|
||||
let _o = match cert_id_result {
|
||||
Ok(_o) => _o,
|
||||
Err(e) => panic!("{}", e)
|
||||
};
|
||||
|
||||
Ok(pkcs12_keystore)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod database_tests {
|
||||
use sqlx::mysql::MySqlPool;
|
||||
|
||||
/* This unit test checks if the add user functionality successfully inserts the record
|
||||
* into the database */
|
||||
#[sqlx::test]
|
||||
async fn add_user_test(pool: MySqlPool) {
|
||||
use super::*;
|
||||
let username = String::from("test");
|
||||
let password = String::from("test");
|
||||
let id_result = add_user(&username, &password, &pool).await; // add test user to database
|
||||
match id_result { // panic if add user returns an error
|
||||
Ok(_p) => assert!(true),
|
||||
Err(_e) => assert!(false)
|
||||
};
|
||||
}
|
||||
|
||||
/* This unit test checks if the connection to the database works properly */
|
||||
#[rocket::async_test]
|
||||
async fn pool_test() {
|
||||
use super::*;
|
||||
let pool_result = connect().await; // connect to the database
|
||||
match pool_result { // panic if connect returns an error
|
||||
Ok(_p) => assert!(true),
|
||||
Err(_e) => assert!(false)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::fs::{FileServer, relative}; // for serving static dir
|
||||
mod database;
|
||||
mod api;
|
||||
mod cert;
|
||||
|
||||
#[launch]
|
||||
pub async fn rocket() -> _ {
|
||||
let pool_result = database::connect().await;
|
||||
|
||||
let pool = match pool_result {
|
||||
Ok(pool) => pool,
|
||||
Err(e) => panic!("Error with database connection, {}", e)
|
||||
};
|
||||
|
||||
rocket::build()
|
||||
.manage(api::Pool(pool))
|
||||
.mount("/", FileServer::from(relative!("static")))
|
||||
.mount("/", routes![api::user_create])
|
||||
.mount("/", routes![api::user_login])
|
||||
.mount("/", routes![api::user_cert])
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE HTML>
|
||||
<head>
|
||||
<title>Foxtrot MFA system</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Welcome</h1>
|
||||
<a href="register.html">
|
||||
<button>Register</button>
|
||||
</a>
|
||||
<a href="login.html">
|
||||
<button>Login</button>
|
||||
</a>
|
||||
</body>
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE HTML>
|
||||
<head>
|
||||
<title>Foxtrot MFA system</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<form action="user/login" method="post">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username"/><br>
|
||||
<label for="username">Password:</label>
|
||||
<input type="password" id="password" name="password"/><br>
|
||||
<label for="username">OTP:</label>
|
||||
<input type="password" id="otp" name="otp"/><br>
|
||||
<input type="submit" value="Submit"/>
|
||||
</form>
|
||||
</body>
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE HTML>
|
||||
<head>
|
||||
<title>Foxtrot MFA system</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<form action="user/create" method="post">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username"/><br>
|
||||
<label for="username">Password:</label>
|
||||
<input type="password" id="password" name="password"/><br>
|
||||
<label for="username">Confirm password:</label>
|
||||
<input type="password" id="confirmpassword" name="confirmpassword"/><br>
|
||||
<input type="submit" value="Submit"/>
|
||||
</form>
|
||||
</body>
|
Loading…
Reference in New Issue