use crate::error::ApiError; use crate::AppState; use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; use rocket::http::CookieJar; use rocket::request::{FromRequest, Outcome}; use rocket::{async_trait, http::Status, Request, State}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct Claims { pub sub: i32, pub username: String, pub exp: usize, pub iat: usize, pub iss: String, } #[derive(Debug, Clone)] pub struct AuthUser { pub user_id: i32, pub username: String, } pub fn generate_token(state: &AppState, user_id: i32, username: &str) -> Result { let now = chrono::Utc::now(); let exp = now .checked_add_signed(chrono::Duration::seconds(state.jwt_ttl_seconds)) .ok_or_else(|| ApiError::internal("Kunde inte skapa utgångstid för token."))?; let claims = Claims { sub: user_id, username: username.to_string(), exp: exp.timestamp() as usize, iat: now.timestamp() as usize, iss: state.jwt_issuer.clone(), }; let header = Header::new(Algorithm::HS256); encode( &header, &claims, &EncodingKey::from_secret(state.jwt_secret.as_bytes()), ) .map_err(|e| { eprintln!("Failed to encode JWT: {e:?}"); ApiError::internal("Misslyckades att skapa token.") }) } #[async_trait] impl<'r> FromRequest<'r> for AuthUser { type Error = ApiError; async fn from_request(request: &'r Request<'_>) -> Outcome { let state_outcome = request.guard::<&State>().await; let state = match state_outcome { Outcome::Success(state) => state, Outcome::Error((status, _)) => { return Outcome::Error(( status, ApiError::internal("Kunde inte läsa applikationsstatus."), )); } Outcome::Forward(_) => { return Outcome::Error(( Status::InternalServerError, ApiError::internal("Kunde inte läsa applikationsstatus."), )); } }; let jar_outcome = request.guard::<&CookieJar>().await; let jar = match jar_outcome { Outcome::Success(jar) => jar, Outcome::Error((status, _)) => { return Outcome::Error((status, ApiError::internal("Kunde inte läsa cookies."))); } Outcome::Forward(_) => { return Outcome::Error(( Status::Unauthorized, ApiError::unauthorized("Ingen åtkomsttoken hittades."), )); } }; let cookie = match jar.get(&state.cookie_name) { Some(cookie) => cookie, None => { return Outcome::Error(( Status::Unauthorized, ApiError::unauthorized("Inloggning krävs."), )); } }; let token = cookie.value(); let mut validation = Validation::new(Algorithm::HS256); validation.validate_exp = true; validation.set_issuer(&[state.jwt_issuer.clone()]); match decode::( token, &DecodingKey::from_secret(state.jwt_secret.as_bytes()), &validation, ) { Ok(data) => Outcome::Success(AuthUser { user_id: data.claims.sub, username: data.claims.username, }), Err(err) => { eprintln!("JWT decode error: {err:?}"); Outcome::Error(( Status::Unauthorized, ApiError::unauthorized("Ogiltig eller utgången session."), )) } } } }