120 lines
3.8 KiB
Rust
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."),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
}
|