83 lines
2.1 KiB
Rust
83 lines
2.1 KiB
Rust
use std::{env, sync::Arc};
|
|
|
|
use crate::error::Result;
|
|
use jsonwebtoken::{
|
|
Algorithm, DecodingKey, Validation, decode, decode_header, errors::ErrorKind, jwk::JwkSet,
|
|
};
|
|
use serde::Deserialize;
|
|
|
|
#[derive(Clone)]
|
|
pub struct User {
|
|
pub id: String,
|
|
pub name: String,
|
|
}
|
|
|
|
pub struct JWT {
|
|
reqwest_client: reqwest::Client,
|
|
}
|
|
|
|
pub trait AuthImpl {
|
|
async fn for_protected(&self, token: &str) -> Result<User>;
|
|
}
|
|
|
|
pub enum Auth {
|
|
JWT(JWT),
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct Claims {
|
|
sub: String,
|
|
name: String,
|
|
}
|
|
|
|
impl JWT {
|
|
pub fn new(reqwest_client: reqwest::Client) -> Self {
|
|
Self { reqwest_client }
|
|
}
|
|
}
|
|
|
|
impl AuthImpl for JWT {
|
|
async fn for_protected(&self, token: &str) -> Result<User> {
|
|
let frontend_url =
|
|
env::var("FRONTEND_BASE_URL").unwrap_or_else(|_| "http://localhost:5173".to_string());
|
|
|
|
let jwks = get_jwks(
|
|
&self.reqwest_client,
|
|
&format!("{frontend_url}/api/auth/jwks"),
|
|
)
|
|
.await?;
|
|
let header = decode_header(token)?;
|
|
let kid = header.kid.ok_or(crate::error::Error::Unauthorized)?;
|
|
|
|
let jwk = jwks.find(&kid).ok_or(crate::error::Error::Unauthorized)?;
|
|
let decoding_key = DecodingKey::from_jwk(jwk)?;
|
|
|
|
let mut validation = Validation::new(Algorithm::EdDSA);
|
|
validation.set_issuer(&[&frontend_url]);
|
|
validation.set_audience(&[&frontend_url]);
|
|
|
|
let token_data =
|
|
decode::<Claims>(token, &decoding_key, &validation).map_err(|e| match e.kind() {
|
|
ErrorKind::ExpiredSignature => crate::error::Error::TokenExpired,
|
|
_ => crate::error::Error::Unauthorized,
|
|
})?;
|
|
|
|
Ok(User {
|
|
id: token_data.claims.sub,
|
|
name: token_data.claims.name,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl AuthImpl for Auth {
|
|
async fn for_protected(&self, token: &str) -> Result<User> {
|
|
match self {
|
|
Auth::JWT(jwt) => jwt.for_protected(token).await,
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn get_jwks(client: &reqwest::Client, jwks_url: &str) -> Result<JwkSet> {
|
|
Ok(client.get(jwks_url).send().await?.json::<JwkSet>().await?)
|
|
}
|