diff options
Diffstat (limited to 'src/routes/jwt.rs')
-rw-r--r-- | src/routes/jwt.rs | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/src/routes/jwt.rs b/src/routes/jwt.rs new file mode 100644 index 0000000..6a229a3 --- /dev/null +++ b/src/routes/jwt.rs @@ -0,0 +1,114 @@ +use std::sync::Arc; + +use axum::{ + extract::{Request, State}, + response::IntoResponse, +}; +use axum_extra::{ + extract::{cookie::Cookie, CookieJar}, + headers::{authorization::Bearer, Authorization}, + routing::TypedPath, + TypedHeader, +}; +use jsonwebtoken::{DecodingKey, Validation}; +use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; +use uuid::Uuid; + +use crate::{error::AuthError, state::AppState, Error}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Claims { + pub sub: Uuid, + pub iat: i64, + pub exp: i64, + pub jti: Uuid, +} + +impl Claims { + const MAX_AGE: i64 = 3600; + + pub fn new(sub: Uuid) -> Self { + let iat = OffsetDateTime::now_utc().unix_timestamp(); + let exp = iat + Self::MAX_AGE; + let jti = uuid::Uuid::new_v4(); + Self { sub, iat, exp, jti } + } + + pub fn encode(&self, secret: &[u8]) -> Result<String, jsonwebtoken::errors::Error> { + jsonwebtoken::encode( + &jsonwebtoken::Header::default(), + self, + &jsonwebtoken::EncodingKey::from_secret(secret), + ) + } +} + +impl From<Uuid> for Claims { + fn from(value: Uuid) -> Self { + Self::new(value) + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +struct Session { + jti: Uuid, + uuid: Uuid, +} + +#[derive(Debug, Deserialize, TypedPath)] +#[typed_path("/api/auth/refresh")] +pub struct Refresh; + +impl Refresh { + #[tracing::instrument] + pub async fn post( + self, + State(state): State<Arc<AppState>>, + TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>, + cookie_jar: CookieJar, + ) -> Result<impl IntoResponse, Error> { + let Claims { sub, .. } = jsonwebtoken::decode::<Claims>( + bearer.token(), + &DecodingKey::from_secret(state.jwt_secret.as_ref()), + &Validation::default(), + )? + .claims; + + let claims = Claims::from(sub); + + let token = jsonwebtoken::encode( + &jsonwebtoken::Header::default(), + &claims, + &jsonwebtoken::EncodingKey::from_secret(state.jwt_secret.as_ref()), + )?; + + let cookie = Cookie::build(("token", token)) + .expires(OffsetDateTime::from_unix_timestamp(claims.exp)?) + .secure(true) + .http_only(true); + + Ok(cookie_jar.add(cookie)) + } +} + +pub async fn authenticate( + State(state): State<Arc<AppState>>, + cookie_jar: CookieJar, + mut req: Request, +) -> Result<Request, AuthError> { + let token = cookie_jar + .get("token") + .ok_or(AuthError::JwtNotFound)? + .to_string(); + + let claims = jsonwebtoken::decode::<Claims>( + &token, + &DecodingKey::from_secret(state.jwt_secret.as_ref()), + &Validation::default(), + )? + .claims; + + req.extensions_mut().insert(claims); + Ok(req) +} |