pub mod password;
use chrono::Utc;
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode};
use rand::Rng;
use serde::{Deserialize, Serialize};
use tonic::{Request, Status, metadata::MetadataMap};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub exp: usize,
pub iat: usize,
}
pub fn generate_jwt_secret() -> String {
let mut rng = rand::thread_rng();
(0..32)
.map(|_| format!("{:02x}", rng.r#gen::<u8>()))
.collect()
}
pub fn create_token(username: &str, secret: &str) -> Result<String, jsonwebtoken::errors::Error> {
let now = Utc::now().timestamp() as usize;
let claims = Claims {
sub: username.to_string(),
iat: now,
exp: now + 3600, };
encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(secret.as_bytes()),
)
}
pub fn verify_token(token: &str, secret: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
decode::<Claims>(
token,
&DecodingKey::from_secret(secret.as_bytes()),
&Validation::default(),
)
.map(|data| data.claims)
}
pub fn parse_cookie<'a>(cookie_header: &'a str, name: &str) -> Option<&'a str> {
cookie_header.split(';').find_map(|pair| {
let (k, v) = pair.trim().split_once('=')?;
if k == name { Some(v) } else { None }
})
}
fn extract_jwt(metadata: &MetadataMap) -> Option<String> {
let cookie = metadata
.get("cookie")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
parse_cookie(cookie, "jwt").map(|s| s.to_string())
}
#[allow(clippy::result_large_err)]
pub fn check_auth<T>(req: &Request<T>, secret: &str) -> Result<(), Status> {
let token = extract_jwt(req.metadata())
.ok_or_else(|| Status::unauthenticated("authentication required"))?;
verify_token(&token, secret)
.map(|_| ())
.map_err(|_| Status::unauthenticated("invalid or expired token"))
}