use std::borrow::Cow;
use salvo_core::async_trait;
use salvo_core::http::header::{AUTHORIZATION, HeaderName};
use salvo_core::http::{Method, Request};
use super::STANDARD_METHODS;
#[async_trait]
pub trait JwtTokenFinder: Send + Sync {
async fn find_token(&self, req: &mut Request) -> Option<String>;
}
#[derive(Eq, PartialEq, Clone, Debug)]
#[non_exhaustive]
pub struct HeaderFinder {
pub allowed_methods: Option<Vec<Method>>,
pub header_names: Vec<HeaderName>,
}
impl Default for HeaderFinder {
fn default() -> Self {
Self::new()
}
}
impl HeaderFinder {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
allowed_methods: None,
header_names: vec![AUTHORIZATION],
}
}
#[inline]
pub fn header_names_mut(&mut self) -> &mut Vec<HeaderName> {
&mut self.header_names
}
#[inline]
#[must_use]
pub fn header_names(mut self, header_names: impl Into<Vec<HeaderName>>) -> Self {
self.header_names = header_names.into();
self
}
#[inline]
pub fn allowed_methods_mut(&mut self) -> &mut Vec<Method> {
self.allowed_methods
.get_or_insert_with(|| STANDARD_METHODS.to_vec())
}
#[inline]
#[must_use]
pub fn allowed_methods(mut self, methods: Vec<Method>) -> Self {
self.allowed_methods = Some(methods);
self
}
#[deprecated(since = "0.93.0", note = "use `allowed_methods_mut` instead")]
#[inline]
pub fn cared_methods_mut(&mut self) -> &mut Vec<Method> {
self.allowed_methods_mut()
}
#[deprecated(since = "0.93.0", note = "use `allowed_methods` instead")]
#[inline]
#[must_use]
pub fn cared_methods(self, methods: Vec<Method>) -> Self {
self.allowed_methods(methods)
}
}
#[async_trait]
impl JwtTokenFinder for HeaderFinder {
#[inline]
async fn find_token(&self, req: &mut Request) -> Option<String> {
if self
.allowed_methods
.as_ref()
.is_none_or(|methods| methods.contains(req.method()))
{
for header_name in &self.header_names {
if let Some(Ok(auth)) = req.headers().get(header_name).map(|auth| auth.to_str())
&& let Some((scheme, token)) = auth.split_once(' ')
&& scheme.eq_ignore_ascii_case("Bearer")
{
let token = token.trim_start();
if !token.is_empty() {
return Some(token.to_owned());
}
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use salvo_core::http::header::AUTHORIZATION;
use super::*;
#[tokio::test]
async fn header_finder_rejects_prefixed_bearer_scheme() {
let finder = HeaderFinder::new();
let mut req = Request::new();
req.headers_mut()
.insert(AUTHORIZATION, "BearerX token".parse().unwrap());
assert_eq!(finder.find_token(&mut req).await, None);
}
#[tokio::test]
async fn header_finder_accepts_case_insensitive_bearer_scheme() {
let finder = HeaderFinder::new();
let mut req = Request::new();
req.headers_mut()
.insert(AUTHORIZATION, "bearer token".parse().unwrap());
assert_eq!(finder.find_token(&mut req).await.as_deref(), Some("token"));
}
}
#[derive(Eq, PartialEq, Clone, Default, Debug)]
#[non_exhaustive]
pub struct FormFinder {
pub allowed_methods: Option<Vec<Method>>,
pub field_name: Cow<'static, str>,
}
impl FormFinder {
#[inline]
pub fn new<T: Into<Cow<'static, str>>>(field_name: T) -> Self {
Self {
field_name: field_name.into(),
allowed_methods: None,
}
}
#[inline]
pub fn allowed_methods_mut(&mut self) -> &mut Vec<Method> {
self.allowed_methods
.get_or_insert_with(|| STANDARD_METHODS.to_vec())
}
#[inline]
#[must_use]
pub fn allowed_methods(mut self, methods: Vec<Method>) -> Self {
self.allowed_methods = Some(methods);
self
}
#[deprecated(since = "0.93.0", note = "use `allowed_methods_mut` instead")]
#[inline]
pub fn cared_methods_mut(&mut self) -> &mut Vec<Method> {
self.allowed_methods_mut()
}
#[deprecated(since = "0.93.0", note = "use `allowed_methods` instead")]
#[inline]
#[must_use]
pub fn cared_methods(self, methods: Vec<Method>) -> Self {
self.allowed_methods(methods)
}
}
#[async_trait]
impl JwtTokenFinder for FormFinder {
#[inline]
async fn find_token(&self, req: &mut Request) -> Option<String> {
if self
.allowed_methods
.as_ref()
.is_none_or(|methods| methods.contains(req.method()))
{
req.form(&self.field_name).await
} else {
None
}
}
}
#[derive(Eq, PartialEq, Clone, Default, Debug)]
#[non_exhaustive]
pub struct QueryFinder {
pub allowed_methods: Option<Vec<Method>>,
pub query_name: Cow<'static, str>,
}
impl QueryFinder {
#[inline]
pub fn new<T: Into<Cow<'static, str>>>(query_name: T) -> Self {
Self {
query_name: query_name.into(),
allowed_methods: None,
}
}
#[inline]
pub fn allowed_methods_mut(&mut self) -> &mut Vec<Method> {
self.allowed_methods
.get_or_insert_with(|| STANDARD_METHODS.to_vec())
}
#[inline]
#[must_use]
pub fn allowed_methods(mut self, methods: Vec<Method>) -> Self {
self.allowed_methods = Some(methods);
self
}
#[deprecated(since = "0.93.0", note = "use `allowed_methods_mut` instead")]
#[inline]
pub fn cared_methods_mut(&mut self) -> &mut Vec<Method> {
self.allowed_methods_mut()
}
#[deprecated(since = "0.93.0", note = "use `allowed_methods` instead")]
#[inline]
#[must_use]
pub fn cared_methods(self, methods: Vec<Method>) -> Self {
self.allowed_methods(methods)
}
}
#[async_trait]
impl JwtTokenFinder for QueryFinder {
#[inline]
async fn find_token(&self, req: &mut Request) -> Option<String> {
if self
.allowed_methods
.as_ref()
.is_none_or(|methods| methods.contains(req.method()))
{
req.query(&self.query_name)
} else {
None
}
}
}
#[derive(Eq, PartialEq, Clone, Default, Debug)]
#[non_exhaustive]
pub struct CookieFinder {
pub allowed_methods: Option<Vec<Method>>,
pub cookie_name: Cow<'static, str>,
}
impl CookieFinder {
#[inline]
pub fn new<T: Into<Cow<'static, str>>>(cookie_name: T) -> Self {
Self {
cookie_name: cookie_name.into(),
allowed_methods: None,
}
}
#[inline]
pub fn allowed_methods_mut(&mut self) -> &mut Vec<Method> {
self.allowed_methods
.get_or_insert_with(|| STANDARD_METHODS.to_vec())
}
#[inline]
#[must_use]
pub fn allowed_methods(mut self, methods: Vec<Method>) -> Self {
self.allowed_methods = Some(methods);
self
}
#[deprecated(since = "0.93.0", note = "use `allowed_methods_mut` instead")]
#[inline]
pub fn cared_methods_mut(&mut self) -> &mut Vec<Method> {
self.allowed_methods_mut()
}
#[deprecated(since = "0.93.0", note = "use `allowed_methods` instead")]
#[inline]
#[must_use]
pub fn cared_methods(self, methods: Vec<Method>) -> Self {
self.allowed_methods(methods)
}
}
#[async_trait]
impl JwtTokenFinder for CookieFinder {
#[inline]
async fn find_token(&self, req: &mut Request) -> Option<String> {
if self
.allowed_methods
.as_ref()
.is_none_or(|methods| methods.contains(req.method()))
{
req.cookie(&self.cookie_name).map(|c| c.value().to_owned())
} else {
None
}
}
}