summaryrefslogtreecommitdiffstats
path: root/src/routes/jwt.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes/jwt.rs')
-rw-r--r--src/routes/jwt.rs114
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)
+}