AppFlowy-Cloud/libs/authentication/src/jwt.rs

177 lines
4.6 KiB
Rust

use actix_http::Payload;
use actix_web::{web::Data, FromRequest, HttpRequest};
use gotrue_entity::gotrue_jwt::GoTrueJWTClaims;
use secrecy::{ExposeSecret, Secret};
use serde::{Deserialize, Serialize};
use sqlx::types::{uuid, Uuid};
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use std::str::FromStr;
use tracing::instrument;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserUuid(uuid::Uuid);
impl UserUuid {
pub fn from_auth(auth: Authorization) -> Result<Self, actix_web::Error> {
Ok(Self(auth.uuid()?))
}
}
impl Deref for UserUuid {
type Target = uuid::Uuid;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug)]
pub struct UserToken(pub String);
impl UserToken {
pub fn from_auth(auth: Authorization) -> Result<Self, actix_web::Error> {
Ok(Self(auth.token))
}
}
impl Display for UserToken {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl FromRequest for UserUuid {
type Error = actix_web::Error;
type Future = std::future::Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let auth = get_auth_from_request(req);
match auth {
Ok(auth) => match UserUuid::from_auth(auth) {
Ok(uuid) => std::future::ready(Ok(uuid)),
Err(e) => std::future::ready(Err(e)),
},
Err(e) => std::future::ready(Err(e)),
}
}
}
// For cases where the handler itself will handle the request differently
// based on whether the user is authenticated or not
pub struct OptionalUserUuid(Option<UserUuid>);
impl FromRequest for OptionalUserUuid {
type Error = actix_web::Error;
type Future = std::future::Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let auth = get_auth_from_request(req);
match auth {
Ok(auth) => match UserUuid::from_auth(auth) {
Ok(uuid) => std::future::ready(Ok(OptionalUserUuid(Some(uuid)))),
Err(_) => std::future::ready(Ok(OptionalUserUuid(None))),
},
Err(_) => std::future::ready(Ok(OptionalUserUuid(None))),
}
}
}
impl OptionalUserUuid {
pub fn as_uuid(&self) -> Option<uuid::Uuid> {
self.0.as_deref().map(|uuid| uuid.to_owned())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Authorization {
pub token: String,
pub claims: GoTrueJWTClaims,
}
impl Authorization {
pub fn uuid(&self) -> Result<uuid::Uuid, actix_web::Error> {
self
.claims
.sub
.as_deref()
.map(Uuid::from_str)
.ok_or(actix_web::error::ErrorUnauthorized(
"Invalid Authorization header, missing sub(uuid)",
))?
.map_err(|e| {
actix_web::error::ErrorUnauthorized(format!(
"Invalid Authorization header, invalid sub(uuid): {}",
e
))
})
}
}
impl FromRequest for Authorization {
type Error = actix_web::Error;
type Future = std::future::Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let auth = get_auth_from_request(req);
match auth {
Ok(auth) => std::future::ready(Ok(auth)),
Err(e) => std::future::ready(Err(e)),
}
}
}
fn get_auth_from_request(req: &HttpRequest) -> Result<Authorization, actix_web::Error> {
let jwt_secret_data =
req
.app_data::<Data<Secret<String>>>()
.ok_or(actix_web::error::ErrorInternalServerError(
"jwt secret not found",
))?;
let bearer = req
.headers()
.get("Authorization")
.ok_or(actix_web::error::ErrorUnauthorized(
"No Authorization header",
))?;
let bearer_str = bearer
.to_str()
.map_err(actix_web::error::ErrorUnauthorized)?;
let (_, token) = bearer_str
.split_once("Bearer ")
.ok_or(actix_web::error::ErrorUnauthorized(
"Invalid Authorization header, missing Bearer",
))?;
authorization_from_token(token, jwt_secret_data)
}
#[instrument(level = "trace", skip_all, err)]
pub fn authorization_from_token(
token: &str,
jwt_secret: &Data<Secret<String>>,
) -> Result<Authorization, actix_web::Error> {
let claims = gotrue_jwt_claims_from_token(token, jwt_secret)?;
Ok(Authorization {
token: token.to_string(),
claims,
})
}
#[instrument(level = "trace", skip_all, err)]
fn gotrue_jwt_claims_from_token(
token: &str,
jwt_secret: &Data<Secret<String>>,
) -> Result<GoTrueJWTClaims, actix_web::Error> {
let claims =
GoTrueJWTClaims::decode(token, jwt_secret.expose_secret().as_bytes()).map_err(|err| {
actix_web::error::ErrorUnauthorized(format!("fail to decode token, error:{}", err))
})?;
Ok(claims)
}