auth.rs (3144B)
1 use actix_web::{ 2 FromRequest, HttpRequest, dev::Payload, error::ResponseError, http::header::AUTHORIZATION, 3 }; 4 use rand::RngExt; 5 use rand::distr::Alphanumeric; 6 use serde_json::Value; 7 use shared::ErrorResponse; 8 use std::env; 9 use std::future::{Ready, ready}; 10 11 pub struct AuthenticatedDevice { 12 #[allow(dead_code)] 13 pub device_id: String, 14 } 15 16 #[derive(Debug)] 17 pub enum AuthError { 18 MissingHeader, 19 InvalidFormat, 20 Unauthorized, 21 } 22 23 impl std::fmt::Display for AuthError { 24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 match self { 26 AuthError::MissingHeader => write!(f, "Missing Authorization Header"), 27 AuthError::InvalidFormat => write!(f, "Invalid Bearer Format"), 28 AuthError::Unauthorized => write!(f, "Authorization failed"), 29 } 30 } 31 } 32 33 impl std::error::Error for AuthError {} 34 35 impl ResponseError for AuthError { 36 fn error_response(&self) -> actix_web::HttpResponse { 37 actix_web::HttpResponse::Forbidden().json(ErrorResponse { 38 errcode: "M_FORBIDDEN".to_string(), 39 error: self.to_string(), 40 }) 41 } 42 } 43 44 impl FromRequest for AuthenticatedDevice { 45 type Error = AuthError; 46 type Future = Ready<Result<Self, Self::Error>>; 47 48 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { 49 let auth_header = match req.headers().get(AUTHORIZATION) { 50 Some(h) => h, 51 None => return ready(Err(AuthError::MissingHeader)), 52 }; 53 54 let auth_str = match auth_header.to_str() { 55 Ok(s) => s, 56 Err(_) => return ready(Err(AuthError::InvalidFormat)), 57 }; 58 59 if !auth_str.starts_with("Bearer ") { 60 return ready(Err(AuthError::InvalidFormat)); 61 } 62 63 let token = auth_str.trim_start_matches("Bearer "); 64 65 let state = match req.app_data::<actix_web::web::Data<crate::AppState>>() { 66 Some(s) => s, 67 None => return ready(Err(AuthError::Unauthorized)), 68 }; 69 70 let tokens = state.tokens.lock().unwrap(); 71 72 if let Some(device_id) = tokens.get(token) { 73 ready(Ok(AuthenticatedDevice { 74 device_id: device_id.clone(), 75 })) 76 } else { 77 ready(Err(AuthError::Unauthorized)) 78 } 79 } 80 } 81 82 pub fn validate_device(device_id: &str, secret: &str) -> bool { 83 let _ = dotenv::dotenv(); 84 let devices_raw = env::var("ALLOWED_DEVICES").unwrap_or_else(|_| "{}".to_string()); 85 86 if let Ok(Value::Object(map)) = serde_json::from_str(&devices_raw) { 87 if let Some(Value::String(stored_secret)) = map.get(device_id) { 88 return stored_secret == secret; 89 } 90 } 91 false 92 } 93 94 pub fn generate_secure_token() -> String { 95 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; 96 97 const LENGTH: usize = 32; 98 99 let token: String = rand::rng() 100 .sample_iter(&Alphanumeric) 101 .take(LENGTH) 102 .map(char::from) 103 .collect(); 104 URL_SAFE_NO_PAD.encode(token) 105 }