🏡 index : old_projects/mc-mailer.git

author yaqubroli <walchuk2018@icloud.com> 2023-05-14 22:37:41.0 -07:00:00
committer yaqubroli <walchuk2018@icloud.com> 2023-05-14 22:37:41.0 -07:00:00
commit
b6364c80f9c90426d1f42ca225088bf7e7e7b748 [patch]
tree
aa59c06870b9e814871b8149142a0141d553edb6
parent
5298cd7128ddfaeb5404d2b0859fd7c7833963d9
download
b6364c80f9c90426d1f42ca225088bf7e7e7b748.tar.gz

added proper email function, actix endpoints etc



Diff

 Cargo.lock          | 307 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 Cargo.toml          |   7 ++++---
 src/email.rs        |  83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/endpoints.rs    |  56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main.rs         |  46 +++++++++++++++++++---------------------------
 src/verification.rs | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 src/whitelist.rs    |  23 +++++++++++++++++++++++
 7 files changed, 587 insertions(+), 92 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 3fc933e..5bb89a2 100644
--- a/Cargo.lock
+++ a/Cargo.lock
@@ -253,6 +253,131 @@
checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"

[[package]]
name = "async-channel"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
dependencies = [
 "concurrent-queue",
 "event-listener",
 "futures-core",
]

[[package]]
name = "async-executor"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
dependencies = [
 "async-lock",
 "async-task",
 "concurrent-queue",
 "fastrand",
 "futures-lite",
 "slab",
]

[[package]]
name = "async-global-executor"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
dependencies = [
 "async-channel",
 "async-executor",
 "async-io",
 "async-lock",
 "blocking",
 "futures-lite",
 "once_cell",
]

[[package]]
name = "async-io"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
dependencies = [
 "async-lock",
 "autocfg 1.1.0",
 "cfg-if",
 "concurrent-queue",
 "futures-lite",
 "log",
 "parking",
 "polling",
 "rustix",
 "slab",
 "socket2",
 "waker-fn",
]

[[package]]
name = "async-lock"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
dependencies = [
 "event-listener",
]

[[package]]
name = "async-process"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9"
dependencies = [
 "async-io",
 "async-lock",
 "autocfg 1.1.0",
 "blocking",
 "cfg-if",
 "event-listener",
 "futures-lite",
 "rustix",
 "signal-hook",
 "windows-sys 0.48.0",
]

[[package]]
name = "async-std"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
 "async-channel",
 "async-global-executor",
 "async-io",
 "async-lock",
 "crossbeam-utils",
 "futures-channel",
 "futures-core",
 "futures-io",
 "futures-lite",
 "gloo-timers",
 "kv-log-macro",
 "log",
 "memchr",
 "once_cell",
 "pin-project-lite",
 "pin-utils",
 "slab",
 "wasm-bindgen-futures",
]

[[package]]
name = "async-task"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae"

[[package]]
name = "atomic-waker"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"

[[package]]
name = "autocfg"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -305,6 +430,21 @@
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
 "generic-array",
]

[[package]]
name = "blocking"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65"
dependencies = [
 "async-channel",
 "async-lock",
 "async-task",
 "atomic-waker",
 "fastrand",
 "futures-lite",
 "log",
]

[[package]]
@@ -402,6 +542,15 @@
dependencies = [
 "termcolor",
 "unicode-width",
]

[[package]]
name = "concurrent-queue"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
dependencies = [
 "crossbeam-utils",
]

[[package]]
@@ -451,6 +600,15 @@
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
 "cfg-if",
]

[[package]]
name = "crossbeam-utils"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
 "cfg-if",
]
@@ -463,6 +621,16 @@
dependencies = [
 "generic-array",
 "typenum",
]

[[package]]
name = "ctor"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [
 "quote",
 "syn 1.0.109",
]

[[package]]
@@ -656,6 +824,12 @@
 "cc",
 "libc",
]

[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"

[[package]]
name = "fast_chemail"
@@ -720,6 +894,15 @@
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"

[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
 "futures-core",
]

[[package]]
name = "futures-core"
@@ -732,6 +915,21 @@
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"

[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
 "fastrand",
 "futures-core",
 "futures-io",
 "memchr",
 "parking",
 "pin-project-lite",
 "waker-fn",
]

[[package]]
name = "futures-sink"
@@ -779,6 +977,18 @@
 "cfg-if",
 "libc",
 "wasi 0.11.0+wasi-snapshot-preview1",
]

[[package]]
name = "gloo-timers"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
dependencies = [
 "futures-channel",
 "futures-core",
 "js-sys",
 "wasm-bindgen",
]

[[package]]
@@ -941,6 +1151,15 @@
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
 "wasm-bindgen",
]

[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
 "log",
]

[[package]]
@@ -1058,6 +1277,7 @@
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
 "cfg-if",
 "value-bag",
]

[[package]]
@@ -1072,11 +1292,14 @@
dependencies = [
 "actix-rt",
 "actix-web",
 "async-process",
 "async-std",
 "base64 0.21.0",
 "chrono",
 "lettre 0.10.4",
 "lettre_email",
 "openssl",
 "rand 0.8.5",
 "serde",
 "serde_qs",
]
@@ -1226,6 +1449,12 @@
 "pkg-config",
 "vcpkg",
]

[[package]]
name = "parking"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"

[[package]]
name = "parking_lot"
@@ -1279,6 +1508,22 @@
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"

[[package]]
name = "polling"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
 "autocfg 1.1.0",
 "bitflags",
 "cfg-if",
 "concurrent-queue",
 "libc",
 "log",
 "pin-project-lite",
 "windows-sys 0.48.0",
]

[[package]]
name = "ppv-lite86"
@@ -1593,6 +1838,20 @@
version = "1.0.162"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.162"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
dependencies = [
 "proc-macro2",
 "quote",
 "syn 2.0.15",
]

[[package]]
name = "serde_json"
@@ -1637,6 +1896,16 @@
 "cfg-if",
 "cpufeatures",
 "digest",
]

[[package]]
name = "signal-hook"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
dependencies = [
 "libc",
 "signal-hook-registry",
]

[[package]]
@@ -1893,6 +2162,16 @@
checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
dependencies = [
 "rand 0.6.5",
]

[[package]]
name = "value-bag"
version = "1.0.0-alpha.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55"
dependencies = [
 "ctor",
 "version_check 0.9.4",
]

[[package]]
@@ -1912,6 +2191,12 @@
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"

[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"

[[package]]
name = "wasi"
@@ -1948,6 +2233,18 @@
 "quote",
 "syn 1.0.109",
 "wasm-bindgen-shared",
]

[[package]]
name = "wasm-bindgen-futures"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
dependencies = [
 "cfg-if",
 "js-sys",
 "wasm-bindgen",
 "web-sys",
]

[[package]]
@@ -1978,6 +2275,16 @@
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"

[[package]]
name = "web-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
dependencies = [
 "js-sys",
 "wasm-bindgen",
]

[[package]]
name = "winapi"
diff --git a/Cargo.toml b/Cargo.toml
index e51c526..7fedd5f 100644
--- a/Cargo.toml
+++ a/Cargo.toml
@@ -1,17 +1,18 @@
[package]
name = "mc-mailer"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-rt = "2.8.0"
actix-web = "4.3.1"
async-process = "1.7.0"
async-std = "1.12.0"
base64 = "0.21.0"
chrono = "0.4.24"
lettre = "0.10.4"
lettre_email = "0.9.4"
openssl = "0.10.52"
serde = "1.0.162"
rand = "0.8.5"
serde = { version = "1.0.162", features = ["derive"] }
serde_qs = "0.12.0"
diff --git a/src/email.rs b/src/email.rs
new file mode 100644
index 0000000..1929f64 100644
--- /dev/null
+++ a/src/email.rs
@@ -1,0 +1,83 @@
use actix_web::Responder;
use lettre::message::Mailbox;
use lettre::*;
use lettre::address::Address;
use lettre::transport::smtp::authentication::Credentials;
use lettre_email::EmailBuilder;
use crate::verification::*;
use crate::secrets::*;

pub async fn send_verification_email(request: VerificationRequest) -> Result<(), String> {

    // TODO: Move creds and mailer outside of the function; maybe just make them static?

    let creds = Credentials::new(
        format!("{}@{}", EMAIL_USERNAME, EMAIL_DOMAIN),
        EMAIL_PASSWORD.to_string()
    );

    let mailer = SmtpTransport::starttls_relay("mail.cock.li")
        .unwrap()
        .credentials(creds)
        .build();

    let email = Message::builder()
        .from(
            Mailbox::new(
                Some(VERIFY_EMAIL_NICKNAME.to_string()),
                Address::new(EMAIL_USERNAME.to_string(), EMAIL_DOMAIN.to_string()).unwrap()
            )
        )
        .to(
            Mailbox::new(
                Some(request.minecraft_username.clone()),
                Address::new(request.email.clone(), "st-andrews.ac.uk").unwrap()
            )
        )
        .subject(VERIFY_EMAIL_SUBJECT)
        .body(
            VERIFY_EMAIL_BODY_1.to_string() + &request.as_code() + VERIFY_EMAIL_BODY_2
        )
        .unwrap();
    println!("Sending email...");
    match mailer.send(&email) {
        Ok(_) => Ok(()),
        Err(e) => Err(format!("Failed to send email: {}", e))
    }
}

pub async fn send_written_reason_email(request: WrittenReasonRequest) -> Result<(), String> {
    let creds = Credentials::new(
        format!("{}@{}", EMAIL_USERNAME, EMAIL_DOMAIN),
        EMAIL_PASSWORD.to_string()
    );

    let mailer = SmtpTransport::starttls_relay("mail.cock.li")
        .unwrap()
        .credentials(creds)
        .build();

    let email = Message::builder()
        .from(
            Mailbox::new(
                Some(VERIFY_EMAIL_NICKNAME.to_string()),
                Address::new(EMAIL_USERNAME.to_string(), EMAIL_DOMAIN.to_string()).unwrap()
            )
        )
        .to(
            Mailbox::new(
                Some(request.minecraft_username.clone()),
                Address::new("walchuk2018", "icloud.com").unwrap()
            )
        )
        .subject("Request from ".to_string() + &request.minecraft_username)
        .body(
            format!("{:?}", request)
        )
        .unwrap();
    println!("Sending email...");
    match mailer.send(&email) {
        Ok(_) => Ok(()),
        Err(e) => Err(format!("Failed to send email: {}", e))
    }
}
diff --git a/src/endpoints.rs b/src/endpoints.rs
new file mode 100644
index 0000000..08300d2 100644
--- /dev/null
+++ a/src/endpoints.rs
@@ -1,0 +1,56 @@
use chrono::Utc;
use crate::secrets::*;
use crate::verification::*;
use crate::email::*;
use actix_web::{web, Responder, HttpResponse};

pub async fn index() -> impl Responder {
    // generate a nonce verification request, and send it as an email
    let request_proto = VerificationRequestProto {
        email: String::from("jpw24"),
        minecraft_username: String::from("jacobroly")
    };
    let request = request_proto.hydrate();
    let response = HttpResponse::Ok()
        .content_type("text/html; charset=utf-8")
        .body(
            HTML_ON_EMAIL_SENT_1.to_string() + &request.minecraft_username + HTML_ON_EMAIL_SENT_2 + &request.email + HTML_ON_EMAIL_SENT_3
        );
    // actix_rt::spawn(send_verification_email(request));
    println!("{}", request.as_code());
    return response;
}

pub async fn send_sta(form: web::Form<VerificationRequestProto>) -> impl Responder {
    let request = form.into_inner().hydrate();
    let response = HttpResponse::Ok()
        .content_type("text/html; charset=utf-8")
        .body(
            HTML_ON_EMAIL_SENT_1.to_string() + &request.minecraft_username + HTML_ON_EMAIL_SENT_2 + &request.email + HTML_ON_EMAIL_SENT_3
        );
    actix_rt::spawn(send_verification_email(request));
    return response;
}

pub async fn send_written(form: web::Form<WrittenReasonRequest>) -> impl Responder {
    let response = HttpResponse::Ok()
        .content_type("text/html; charset=utf-8")
        .body(HTML_ON_WRITTEN_REQUEST_SENT.to_string());
    actix_rt::spawn(send_written_reason_email(form.into_inner()));
    return response;
}

pub async fn verify(path: web::Path<String>) -> impl Responder {
    let code = path.into_inner();
    let receipt = VerificationReceipt::from_code(code);
    if (receipt.valid) {
        return HttpResponse::Ok()
            .content_type("text/html; charset=utf-8")
            .body(HTML_ON_EMAIL_VERIFIED_1.to_string() + &receipt.minecraft_username + HTML_ON_EMAIL_VERIFIED_2);
    } else {
        return HttpResponse::Ok()
            .content_type("text/html; charset=utf-8")
            .body(HTML_ON_EMAIL_VERIFICATION_FAILED.to_string());
    }
}

diff --git a/src/main.rs b/src/main.rs
index f188803..e835538 100644
--- a/src/main.rs
+++ a/src/main.rs
@@ -1,30 +1,20 @@
use chrono::Utc;
use lettre_email::EmailBuilder;
use secrets::secrets::EMAIL_USERNAME;
use crate::verification::verification::*;
use crate::secrets::secrets::SPECIAL_SALT_CODE;
mod endpoints;
mod secrets;
mod verification;
mod email;

pub mod verification;
pub mod secrets;
use actix_web::{web, App, HttpServer};

async fn send_verification_email(verificationRequest: VerificationRequest, ) -> impl Responder {
    let email = EmailBuilder::new()
        .to(format("{}@st-andrews.ac.uk", verificationRequest.email))
        .from(EMAIL_USERNAME)
        .subject("Minecraft Verification")
        .text(format!("Here's your whitelist verification link: https://mc.7800.io/verify/{}", verificationRequest.as_code()))
        .build();
}

fn main() {
    let verification_request = VerificationRequest {
        minecraft_username: String::from("jacobroly"),
        date: Utc::now(),
        seed: 45343,
    };
    println!("Generated verification request: {:?}", verification_request);
    let verification_code = verification_request.as_code();
    println!("Generated verification code: {}", verification_code);
    let verification_receipt = VerificationReceipt::from_code(verification_code);
    println!("Generated verification receipt: {:?}", verification_receipt);
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(endpoints::index))
            .route("/send-sta", web::post().to(endpoints::send_sta))
            .route("/send-written", web::post().to(endpoints::send_written))
            .route("/verify/{code}", web::get().to(endpoints::verify))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}
diff --git a/src/verification.rs b/src/verification.rs
index 114871e..e272649 100644
--- a/src/verification.rs
+++ a/src/verification.rs
@@ -1,66 +1,101 @@
pub mod verification {
    use openssl::hash::*;
    use base64::engine::Engine as _;
    use base64::engine::general_purpose::URL_SAFE_NO_PAD as BASE64;
    use chrono::prelude::*;
    use crate::SPECIAL_SALT_CODE;

    #[derive(Debug)]
    pub struct VerificationRequest {
        pub(crate) minecraft_username: String,
        pub(crate) date: DateTime<Utc>,
        pub(crate) seed: u32,
    }
    
    #[derive(Debug)]
    pub struct VerificationReceipt {
        valid: bool,
        minecraft_username: String,
    }
    
    pub trait VerificationCodeGenerator {
        fn as_code(&self) -> String;
    }
    
    pub trait VerificationCodeValidator {
        fn from_code(code: String) -> VerificationReceipt;

use openssl::{hash::*};
use rand::{thread_rng, Rng};
use base64::engine::Engine as _;
use base64::engine::general_purpose::URL_SAFE_NO_PAD as BASE64;
use chrono::prelude::*;
use serde::{Serialize, Deserialize};
use crate::secrets::SPECIAL_SALT_CODE;

#[derive(Debug, Serialize, Deserialize)]
pub struct VerificationRequestProto {
    pub(crate) email: String,
    pub(crate) minecraft_username: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct WrittenReasonRequest {
    pub(crate) email: String,
    pub(crate) minecraft_username: String,
    pub(crate) reason: String,
}

#[derive(Debug)]
pub struct VerificationRequest {
    pub(crate) email: String,
    pub(crate) minecraft_username: String,
    pub(crate) date: DateTime<Utc>,
    pub(crate) seed: u32,
}

#[derive(Debug)]
pub struct VerificationReceipt {
    pub(crate) valid: bool,
    pub(crate) minecraft_username: String,
}

pub trait VerificationCodeGenerator {
    fn as_code(&self) -> String;
}

pub trait VerificationCodeValidator {
    fn from_code(code: String) -> VerificationReceipt;
}

pub trait VerificationCodeHydrator {
    fn hydrate(&self) -> VerificationRequest;
}

impl VerificationCodeGenerator for VerificationRequest {
    fn as_code(&self) -> String {
        println!("generating verification code from request: {:?}", self.clone());
        return format!(
                "{}{}{}",
                BASE64.encode(
                    &self.seed.to_be_bytes()
                ),
                BASE64.encode(
                    hash(MessageDigest::sha256(),
                    format!(
                        "{}{}{}{}",
                        self.seed,
                        self.date.format("%Y%m%d").to_string(),
                        SPECIAL_SALT_CODE,
                        self.minecraft_username
                    ).as_bytes()).unwrap().as_ref()
                ),
                BASE64.encode(
                    &self.minecraft_username.as_bytes()
                )
        );
    }
}

    impl VerificationCodeGenerator for VerificationRequest {
        fn as_code(&self) -> String {
            return format!(
                    "{}{}{}",
                    BASE64.encode(
                        &self.seed.to_be_bytes()
                    ),
                    BASE64.encode(
                        hash(MessageDigest::sha256(),
                        format!(
                            "{}{}{}{}",
                            self.seed,
                            self.date.timestamp(),
                            SPECIAL_SALT_CODE,
                            self.minecraft_username
                        ).as_bytes()).unwrap().as_ref()
                    ),
                    BASE64.encode(
                        &self.minecraft_username.as_bytes()
                    )
            );
        }
impl VerificationCodeValidator for VerificationReceipt {
    fn from_code(code: String) -> VerificationReceipt {
        println!("code: {}", code);
        println!("reconstructed username: {}", String::from_utf8(BASE64.decode(&code[49..]).unwrap()).unwrap());
        println!("reconstructed seed: {}", u32::from_be_bytes(BASE64.decode(&code[0..6]).unwrap().try_into().unwrap()));
        let username = String::from_utf8(BASE64.decode(&code[49..]).unwrap()).unwrap();
        return VerificationReceipt {
            minecraft_username: username.clone(),
            valid: code == VerificationRequest {
                email: String::from(""),
                minecraft_username: username,
                date: Utc::now(),
                seed: u32::from_be_bytes(BASE64.decode(&code[0..6]).unwrap().try_into().unwrap()),
            }.as_code()
        };
    }
    
    impl VerificationCodeValidator for VerificationReceipt {
        fn from_code(code: String) -> VerificationReceipt {
            let username = String::from_utf8(BASE64.decode(&code[49..]).unwrap()).unwrap();
            return VerificationReceipt {
                minecraft_username: username.clone(),
                valid: code == VerificationRequest {
                    minecraft_username: username,
                    date: Utc::now(),
                    seed: u32::from_be_bytes(BASE64.decode(&code[0..6]).unwrap().try_into().unwrap()),
                }.as_code()
            };
        }
}

impl VerificationCodeHydrator for VerificationRequestProto {
    fn hydrate(&self) -> VerificationRequest {
        return VerificationRequest {
            email: self.email.clone(),
            minecraft_username: self.minecraft_username.clone(),
            date: Utc::now(),
            seed: rand::thread_rng().gen::<u32>(),
        };
    }
}
diff --git a/src/whitelist.rs b/src/whitelist.rs
index e69de29..7bad0be 100644
--- a/src/whitelist.rs
+++ a/src/whitelist.rs
@@ -1,0 +1,23 @@
use std::io::Error;
use std::process::Stdio;
use async_process::Command;
use async_std::prelude::*;
use actix_rt::System;
use crate::secrets::WHITELIST_SCRIPT_PATH;

async fn add_to_whitelist(args: &str) -> Result<String, Error> {
    let mut child = Command::new("sh")
        .arg(WHITELIST_SCRIPT_PATH)
        .arg(args)
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .spawn()?;

    let output = child.output().await?;

    let success = output.status.success();
    let output_text = String::from_utf8_lossy(&output.stdout).to_string();
    let error_text = String::from_utf8_lossy(&output.stderr).to_string();

    Ok(format!("=== STDOUT ===\n{}\n\n== STDERR ==\n{}", output_text, error_text))
}