vbytes-lan-attendence/api/src/auth.rs
2025-09-20 15:18:41 +02:00

120 lines
3.8 KiB
Rust

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<String, ApiError> {
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<Self, Self::Error> {
let state_outcome = request.guard::<&State<AppState>>().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::<Claims>(
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."),
))
}
}
}
}