use std::time::SystemTime; use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation}; use serde::{Deserialize, Serialize}; use tower_cookies::Cookie; use crate::{ database::{ db::DBError, keys::Keys, users::{self, UserSelect, Users}, }, sec, }; #[derive(Clone)] pub struct Auth { secret: String, users: Users, } const KEY_JWT_SECRET: &str = "JWT_SECRET"; const SECS_JWT_EXPIRE: u64 = 60 * 60; // 1hr impl Auth { pub async fn new(db: Keys, users: Users) -> Self { Self { secret: match db.get_key(KEY_JWT_SECRET).await { Ok(secret) => secret, Err(_) => { // Create new secret and store to db // If that fails, crash the application let secret = sec::new_id(); db.set_key(KEY_JWT_SECRET, &secret).await.unwrap(); secret } }, users, } } pub async fn login(&self, username: String, password: String) -> Result { let user = self.users.user(UserSelect::Username(username)).await?; if !sec::compare(&password, &user.password_hash) { return Err(AuthError::InvalidCredentials); } Ok(jsonwebtoken::encode( &Header::default(), &Claims::from(user), &EncodingKey::from_secret(self.secret.as_ref()), )?) } pub fn get_claims(&self, token: String) -> Result { Ok(jsonwebtoken::decode::( token.as_str(), &DecodingKey::from_secret(self.secret.as_ref()), &Validation::new(Algorithm::HS256), )? .claims) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Claims { pub sub: String, pub username: String, pub exp: u64, pub iat: u64, } impl Claims { pub fn expired(&self) -> bool { SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs() >= self.exp } } impl From for Claims { fn from(u: users::User) -> Self { let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(); Claims { sub: u.id, username: u.username, exp: now + SECS_JWT_EXPIRE, iat: now, } } } #[derive(Debug, Clone, PartialEq)] pub enum AuthError { InvalidCredentials, Expired, ServerError(String), } impl AuthError { pub fn expired(&self) -> bool { *self == Self::Expired } } impl From for AuthError { fn from(_: DBError) -> Self { Self::InvalidCredentials } } impl From for AuthError { fn from(e: jsonwebtoken::errors::Error) -> Self { match e.kind() { jsonwebtoken::errors::ErrorKind::ExpiredSignature => Self::Expired, kind => Self::ServerError(e.to_string()), } } }