Skip to content

Commit

Permalink
create actual magic token
Browse files Browse the repository at this point in the history
  • Loading branch information
glendc committed Oct 22, 2023
1 parent bb9c05e commit a85e5cc
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 11 deletions.
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ repository = "https://github.com/plabayo/bucket"
askama = { version = "0.12", features = ["with-axum"] }
askama_axum = "0.3"
axum = "0.6"
base64 = "0.21"
chrono = "0.4"
orion = "0.17"
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
6 changes: 4 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use std::sync::Arc;

use shuttle_secrets::SecretStore;

mod router;
mod services;

#[shuttle_runtime::main]
async fn axum(#[shuttle_secrets::Secrets] secret_store: SecretStore) -> shuttle_axum::ShuttleAxum {
let auth = services::Auth::new(
let auth = Arc::new(services::Auth::new(
secret_store.get("AUTH_PRIVATE_KEY").unwrap(),
secret_store.get("AUTHORIZED_EMAILS").unwrap(),
secret_store.get("SENDGRID_API_KEY").unwrap(),
);
));

let state = router::State { auth };
let router = router::new(state);
Expand Down
2 changes: 1 addition & 1 deletion src/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod shared;

#[derive(Debug, Clone)]
pub struct State {
pub auth: crate::services::Auth,
pub auth: Arc<crate::services::Auth>,
}

fn new_root(state: State) -> Router {
Expand Down
103 changes: 95 additions & 8 deletions src/services/auth.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
use std::sync::Arc;

use axum::http::StatusCode;
use base64::{engine::general_purpose, Engine as _};
use orion::aead::SecretKey;

#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Auth {
email_validator: Arc<EmailValidator>,
email_validator: EmailValidator,
sendgrid_api_key: String,
secret_key: SecretKey,
}

// TODO implement using
// - orion for encryption

impl Auth {
pub fn new(_private_key: String, raw_auth_emails: String, sendgrid_api_key: String) -> Self {
pub fn new(private_key: String, raw_auth_emails: String, sendgrid_api_key: String) -> Self {
let secret_key =
SecretKey::from_slice(private_key.as_bytes()).expect("invalid private key");
Self {
email_validator: Arc::new(EmailValidator::new(raw_auth_emails)),
email_validator: EmailValidator::new(raw_auth_emails),
sendgrid_api_key,
secret_key,
}
}

Expand All @@ -27,9 +31,26 @@ impl Auth {
));
}

// TODO create actual magic here...
let magic = "hello";
// create magic
let magic = AuthTokenMagic::new(email.to_string())
.map_err(|e| {
tracing::error!("failed making auth token magic: {:?}", e);
(
"failed making auth token magic".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
)
})?
.to_string();
let cipher_text = orion::aead::seal(&self.secret_key, magic.as_bytes()).map_err(|e| {
tracing::error!("failed encrypting magic: {:?}", e);
(
"failed encrypting magic".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
)
})?;
let magic = general_purpose::STANDARD_NO_PAD.encode(&cipher_text);

// send magic
let client = reqwest::Client::new();
let result = client
.post("https://api.sendgrid.com/v3/mail/send")
Expand Down Expand Up @@ -106,6 +127,72 @@ impl Auth {
}
}

struct AuthTokenMagic {
email: String,
token: Vec<u8>,
expires_at: u64,
}

impl AuthTokenMagic {
pub fn new(email: String) -> Result<Self, String> {
let mut token = [0u8; 16];
orion::util::secure_rand_bytes(&mut token).map_err(|e| e.to_string())?;
let expires_at = chrono::Utc::now()
.checked_add_signed(chrono::Duration::hours(24))
.ok_or("failed to calculate expires_at")?
.timestamp() as u64;
Ok(Self {
email,
token: token.to_vec(),
expires_at,
})
}
}

impl std::fmt::Display for AuthTokenMagic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = serde_json::json!({
"email": self.email,
"token": general_purpose::STANDARD_NO_PAD.encode(&self.token),
"expires_at": self.expires_at,
})
.to_string();
write!(f, "{value}")
}
}

impl TryFrom<&str> for AuthTokenMagic {
type Error = String;

fn try_from(raw: &str) -> Result<Self, Self::Error> {
let value: serde_json::Value = serde_json::from_str(raw).map_err(|e| e.to_string())?;
let email = value
.get("email")
.ok_or("missing email")?
.as_str()
.ok_or("invalid email")?
.to_owned();
let token = value
.get("token")
.ok_or("missing token")?
.as_str()
.ok_or("invalid token")?;
let token = general_purpose::STANDARD_NO_PAD
.decode(token.as_bytes())
.map_err(|e| e.to_string())?;
let expires_at = value
.get("expires_at")
.ok_or("missing expires_at")?
.as_u64()
.ok_or("invalid expires_at")?;
Ok(Self {
email,
token,
expires_at,
})
}
}

#[derive(Debug)]
struct EmailValidator {
filters: Vec<String>,
Expand Down

0 comments on commit a85e5cc

Please sign in to comment.