2020-05-04 21:34:20 +02:00
|
|
|
use crate::{AuthError, Forbidden, HeaderName};
|
2020-01-22 16:53:02 +00:00
|
|
|
use cookie::CookieJar;
|
2020-09-15 15:10:41 +02:00
|
|
|
use futures_util::{
|
|
|
|
future,
|
|
|
|
future::{FutureExt, TryFutureExt}
|
|
|
|
};
|
2020-01-22 16:53:02 +00:00
|
|
|
use gotham::{
|
2020-09-17 12:25:58 +02:00
|
|
|
anyhow,
|
2020-01-22 16:53:02 +00:00
|
|
|
handler::HandlerFuture,
|
2020-09-15 15:10:41 +02:00
|
|
|
hyper::header::{HeaderMap, AUTHORIZATION},
|
2020-11-23 23:17:28 +01:00
|
|
|
middleware::{cookie::CookieParser, Middleware, NewMiddleware},
|
2020-01-22 16:53:02 +00:00
|
|
|
state::{FromState, State}
|
|
|
|
};
|
2020-09-15 15:10:41 +02:00
|
|
|
use jsonwebtoken::{errors::ErrorKind, DecodingKey};
|
2020-01-22 16:53:02 +00:00
|
|
|
use serde::de::DeserializeOwned;
|
2020-09-15 15:10:41 +02:00
|
|
|
use std::{marker::PhantomData, panic::RefUnwindSafe, pin::Pin};
|
2020-01-22 16:53:02 +00:00
|
|
|
|
|
|
|
pub use jsonwebtoken::Validation as AuthValidation;
|
|
|
|
|
|
|
|
/// The authentication status returned by the auth middleware for each request.
|
|
|
|
#[derive(Debug, StateData)]
|
2020-09-15 15:10:41 +02:00
|
|
|
pub enum AuthStatus<T: Send + 'static> {
|
2020-01-22 16:53:02 +00:00
|
|
|
/// The auth status is unknown.
|
|
|
|
Unknown,
|
|
|
|
/// The request has been performed without any kind of authentication.
|
|
|
|
Unauthenticated,
|
|
|
|
/// The request has been performed with an invalid authentication.
|
|
|
|
Invalid,
|
|
|
|
/// The request has been performed with an expired authentication.
|
|
|
|
Expired,
|
|
|
|
/// The request has been performed with a valid authentication.
|
|
|
|
Authenticated(T)
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Clone for AuthStatus<T>
|
|
|
|
where
|
2020-09-15 15:10:41 +02:00
|
|
|
T: Clone + Send + 'static
|
2020-01-22 16:53:02 +00:00
|
|
|
{
|
2020-09-15 15:10:41 +02:00
|
|
|
fn clone(&self) -> Self {
|
2020-01-22 16:53:02 +00:00
|
|
|
match self {
|
|
|
|
Self::Unknown => Self::Unknown,
|
|
|
|
Self::Unauthenticated => Self::Unauthenticated,
|
|
|
|
Self::Invalid => Self::Invalid,
|
|
|
|
Self::Expired => Self::Expired,
|
|
|
|
Self::Authenticated(data) => Self::Authenticated(data.clone())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-15 15:10:41 +02:00
|
|
|
impl<T> Copy for AuthStatus<T> where T: Copy + Send + 'static {}
|
2020-05-03 18:21:50 +02:00
|
|
|
|
2020-09-15 15:10:41 +02:00
|
|
|
impl<T: Send + 'static> AuthStatus<T> {
|
|
|
|
pub fn ok(self) -> Result<T, AuthError> {
|
2020-05-04 21:34:20 +02:00
|
|
|
match self {
|
|
|
|
Self::Authenticated(data) => Ok(data),
|
|
|
|
_ => Err(Forbidden)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
/// The source of the authentication token in the request.
|
2020-05-03 18:21:50 +02:00
|
|
|
#[derive(Clone, Debug, StateData)]
|
2020-09-15 15:10:41 +02:00
|
|
|
pub enum AuthSource {
|
2020-01-22 16:53:02 +00:00
|
|
|
/// Take the token from a cookie with the given name.
|
|
|
|
Cookie(String),
|
|
|
|
/// Take the token from a header with the given name.
|
|
|
|
Header(HeaderName),
|
|
|
|
/// Take the token from the HTTP Authorization header. This is different from `Header("Authorization")`
|
|
|
|
/// as it will follow the `scheme param` format from the HTTP specification. The `scheme` will
|
|
|
|
/// be discarded, so its value doesn't matter.
|
|
|
|
AuthorizationHeader
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
This trait will help the auth middleware to determine the validity of an authentication token.
|
|
|
|
|
|
|
|
A very basic implementation could look like this:
|
2020-11-22 23:55:52 +01:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
```
|
2020-04-19 22:27:34 +02:00
|
|
|
# use gotham_restful::{AuthHandler, State};
|
2020-01-22 16:53:02 +00:00
|
|
|
#
|
|
|
|
const SECRET : &'static [u8; 32] = b"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc";
|
|
|
|
|
|
|
|
struct CustomAuthHandler;
|
|
|
|
impl<T> AuthHandler<T> for CustomAuthHandler {
|
|
|
|
fn jwt_secret<F : FnOnce() -> Option<T>>(&self, _state : &mut State, _decode_data : F) -> Option<Vec<u8>> {
|
|
|
|
Some(SECRET.to_vec())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
*/
|
2020-09-15 15:10:41 +02:00
|
|
|
pub trait AuthHandler<Data> {
|
2020-01-22 16:53:02 +00:00
|
|
|
/// Return the SHA256-HMAC secret used to verify the JWT token.
|
2020-09-15 15:10:41 +02:00
|
|
|
fn jwt_secret<F: FnOnce() -> Option<Data>>(&self, state: &mut State, decode_data: F) -> Option<Vec<u8>>;
|
2020-01-22 16:53:02 +00:00
|
|
|
}
|
|
|
|
|
2020-11-22 23:55:52 +01:00
|
|
|
/// An [AuthHandler] returning always the same secret. See [AuthMiddleware] for a usage example.
|
2020-01-22 16:53:02 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2020-09-15 15:10:41 +02:00
|
|
|
pub struct StaticAuthHandler {
|
|
|
|
secret: Vec<u8>
|
2020-01-22 16:53:02 +00:00
|
|
|
}
|
|
|
|
|
2020-09-15 15:10:41 +02:00
|
|
|
impl StaticAuthHandler {
|
|
|
|
pub fn from_vec(secret: Vec<u8>) -> Self {
|
2020-01-22 16:53:02 +00:00
|
|
|
Self { secret }
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
|
|
|
pub fn from_array(secret: &[u8]) -> Self {
|
2020-01-22 16:53:02 +00:00
|
|
|
Self::from_vec(secret.to_vec())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-15 15:10:41 +02:00
|
|
|
impl<T> AuthHandler<T> for StaticAuthHandler {
|
|
|
|
fn jwt_secret<F: FnOnce() -> Option<T>>(&self, _state: &mut State, _decode_data: F) -> Option<Vec<u8>> {
|
2020-01-22 16:53:02 +00:00
|
|
|
Some(self.secret.clone())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
This is the auth middleware. To use it, first make sure you have the `auth` feature enabled. Then
|
|
|
|
simply add it to your pipeline and request it inside your handler:
|
|
|
|
|
|
|
|
```rust,no_run
|
|
|
|
# #[macro_use] extern crate gotham_restful_derive;
|
|
|
|
# use gotham::{router::builder::*, pipeline::{new_pipeline, single::single_pipeline}, state::State};
|
|
|
|
# use gotham_restful::*;
|
|
|
|
# use serde::{Deserialize, Serialize};
|
|
|
|
#
|
|
|
|
#[derive(Resource)]
|
2020-05-04 21:34:20 +02:00
|
|
|
#[resource(read_all)]
|
2020-01-22 16:53:02 +00:00
|
|
|
struct AuthResource;
|
|
|
|
|
2020-01-25 14:04:26 +01:00
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
2020-01-22 16:53:02 +00:00
|
|
|
struct AuthData {
|
|
|
|
sub: String,
|
|
|
|
exp: u64
|
|
|
|
}
|
|
|
|
|
2020-05-04 21:34:20 +02:00
|
|
|
#[read_all(AuthResource)]
|
2020-01-22 16:53:02 +00:00
|
|
|
fn read_all(auth : &AuthStatus<AuthData>) -> Success<String> {
|
|
|
|
format!("{:?}", auth).into()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let auth : AuthMiddleware<AuthData, _> = AuthMiddleware::new(
|
|
|
|
AuthSource::AuthorizationHeader,
|
|
|
|
AuthValidation::default(),
|
|
|
|
StaticAuthHandler::from_array(b"zlBsA2QXnkmpe0QTh8uCvtAEa4j33YAc")
|
|
|
|
);
|
|
|
|
let (chain, pipelines) = single_pipeline(new_pipeline().add(auth).build());
|
|
|
|
gotham::start("127.0.0.1:8080", build_router(chain, pipelines, |route| {
|
2020-04-05 22:18:31 +02:00
|
|
|
route.resource::<AuthResource>("auth");
|
2020-01-22 16:53:02 +00:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
```
|
|
|
|
*/
|
2020-05-03 18:21:50 +02:00
|
|
|
#[derive(Debug)]
|
2020-09-15 15:10:41 +02:00
|
|
|
pub struct AuthMiddleware<Data, Handler> {
|
|
|
|
source: AuthSource,
|
|
|
|
validation: AuthValidation,
|
|
|
|
handler: Handler,
|
|
|
|
_data: PhantomData<Data>
|
2020-01-22 16:53:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<Data, Handler> Clone for AuthMiddleware<Data, Handler>
|
2020-09-15 15:10:41 +02:00
|
|
|
where
|
|
|
|
Handler: Clone
|
2020-01-22 16:53:02 +00:00
|
|
|
{
|
2020-09-15 15:10:41 +02:00
|
|
|
fn clone(&self) -> Self {
|
2020-01-22 16:53:02 +00:00
|
|
|
Self {
|
|
|
|
source: self.source.clone(),
|
|
|
|
validation: self.validation.clone(),
|
|
|
|
handler: self.handler.clone(),
|
|
|
|
_data: self._data
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<Data, Handler> AuthMiddleware<Data, Handler>
|
|
|
|
where
|
2020-09-15 15:10:41 +02:00
|
|
|
Data: DeserializeOwned + Send,
|
|
|
|
Handler: AuthHandler<Data> + Default
|
2020-01-22 16:53:02 +00:00
|
|
|
{
|
2020-09-15 15:10:41 +02:00
|
|
|
pub fn from_source(source: AuthSource) -> Self {
|
2020-01-22 16:53:02 +00:00
|
|
|
Self {
|
|
|
|
source,
|
|
|
|
validation: Default::default(),
|
|
|
|
handler: Default::default(),
|
|
|
|
_data: Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<Data, Handler> AuthMiddleware<Data, Handler>
|
|
|
|
where
|
2020-09-15 15:10:41 +02:00
|
|
|
Data: DeserializeOwned + Send,
|
|
|
|
Handler: AuthHandler<Data>
|
2020-01-22 16:53:02 +00:00
|
|
|
{
|
2020-09-15 15:10:41 +02:00
|
|
|
pub fn new(source: AuthSource, validation: AuthValidation, handler: Handler) -> Self {
|
2020-01-22 16:53:02 +00:00
|
|
|
Self {
|
|
|
|
source,
|
|
|
|
validation,
|
|
|
|
handler,
|
|
|
|
_data: Default::default()
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
|
|
|
fn auth_status(&self, state: &mut State) -> AuthStatus<Data> {
|
2020-01-22 16:53:02 +00:00
|
|
|
// extract the provided token, if any
|
|
|
|
let token = match &self.source {
|
2020-09-15 15:10:41 +02:00
|
|
|
AuthSource::Cookie(name) => CookieJar::try_borrow_from(&state)
|
2020-11-23 23:17:28 +01:00
|
|
|
.map(|jar| jar.get(&name).map(|cookie| cookie.value().to_owned()))
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
CookieParser::from_state(&state)
|
|
|
|
.get(&name)
|
|
|
|
.map(|cookie| cookie.value().to_owned())
|
|
|
|
}),
|
2020-09-15 15:10:41 +02:00
|
|
|
AuthSource::Header(name) => HeaderMap::try_borrow_from(&state)
|
|
|
|
.and_then(|map| map.get(name))
|
|
|
|
.and_then(|header| header.to_str().ok())
|
|
|
|
.map(|value| value.to_owned()),
|
|
|
|
AuthSource::AuthorizationHeader => HeaderMap::try_borrow_from(&state)
|
|
|
|
.and_then(|map| map.get(AUTHORIZATION))
|
|
|
|
.and_then(|header| header.to_str().ok())
|
|
|
|
.and_then(|value| value.split_whitespace().nth(1))
|
|
|
|
.map(|value| value.to_owned())
|
2020-01-22 16:53:02 +00:00
|
|
|
};
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
// unauthed if no token
|
|
|
|
let token = match token {
|
|
|
|
Some(token) => token,
|
|
|
|
None => return AuthStatus::Unauthenticated
|
|
|
|
};
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
// get the secret from the handler, possibly decoding claims ourselves
|
|
|
|
let secret = self.handler.jwt_secret(state, || {
|
2020-04-25 17:01:16 +02:00
|
|
|
let b64 = token.split('.').nth(1)?;
|
2020-01-22 16:53:02 +00:00
|
|
|
let raw = base64::decode_config(b64, base64::URL_SAFE_NO_PAD).ok()?;
|
|
|
|
serde_json::from_slice(&raw).ok()?
|
|
|
|
});
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
// unknown if no secret
|
|
|
|
let secret = match secret {
|
|
|
|
Some(secret) => secret,
|
|
|
|
None => return AuthStatus::Unknown
|
|
|
|
};
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
// validate the token
|
2020-09-15 15:10:41 +02:00
|
|
|
let data: Data = match jsonwebtoken::decode(&token, &DecodingKey::from_secret(&secret), &self.validation) {
|
2020-01-22 16:53:02 +00:00
|
|
|
Ok(data) => data.claims,
|
|
|
|
Err(e) => match dbg!(e.into_kind()) {
|
|
|
|
ErrorKind::ExpiredSignature => return AuthStatus::Expired,
|
|
|
|
_ => return AuthStatus::Invalid
|
|
|
|
}
|
|
|
|
};
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
// we found a valid token
|
2020-04-25 17:01:16 +02:00
|
|
|
AuthStatus::Authenticated(data)
|
2020-01-22 16:53:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<Data, Handler> Middleware for AuthMiddleware<Data, Handler>
|
|
|
|
where
|
2020-09-15 15:10:41 +02:00
|
|
|
Data: DeserializeOwned + Send + 'static,
|
|
|
|
Handler: AuthHandler<Data>
|
2020-01-22 16:53:02 +00:00
|
|
|
{
|
2020-09-15 15:10:41 +02:00
|
|
|
fn call<Chain>(self, mut state: State, chain: Chain) -> Pin<Box<HandlerFuture>>
|
2020-01-22 16:53:02 +00:00
|
|
|
where
|
2020-09-15 15:10:41 +02:00
|
|
|
Chain: FnOnce(State) -> Pin<Box<HandlerFuture>>
|
2020-01-22 16:53:02 +00:00
|
|
|
{
|
|
|
|
// put the source in our state, required for e.g. openapi
|
|
|
|
state.put(self.source.clone());
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
// put the status in our state
|
|
|
|
let status = self.auth_status(&mut state);
|
|
|
|
state.put(status);
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
// call the rest of the chain
|
2020-04-14 17:44:07 +02:00
|
|
|
chain(state).and_then(|(state, res)| future::ok((state, res))).boxed()
|
2020-01-22 16:53:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<Data, Handler> NewMiddleware for AuthMiddleware<Data, Handler>
|
|
|
|
where
|
2020-09-15 15:10:41 +02:00
|
|
|
Self: Clone + Middleware + Sync + RefUnwindSafe
|
2020-01-22 16:53:02 +00:00
|
|
|
{
|
|
|
|
type Instance = Self;
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-09-17 12:25:58 +02:00
|
|
|
fn new_middleware(&self) -> anyhow::Result<Self> {
|
2020-09-15 15:10:41 +02:00
|
|
|
let c: Self = self.clone();
|
2020-01-22 16:53:02 +00:00
|
|
|
Ok(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
2020-09-15 15:10:41 +02:00
|
|
|
mod test {
|
2020-01-22 16:53:02 +00:00
|
|
|
use super::*;
|
|
|
|
use cookie::Cookie;
|
2020-11-23 23:17:28 +01:00
|
|
|
use gotham::hyper::header::COOKIE;
|
2020-01-22 16:53:02 +00:00
|
|
|
use std::fmt::Debug;
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
// 256-bit random string
|
2020-09-15 15:10:41 +02:00
|
|
|
const JWT_SECRET: &'static [u8; 32] = b"Lyzsfnta0cdxyF0T9y6VGxp3jpgoMUuW";
|
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
// some known tokens
|
|
|
|
const VALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9.8h8Ax-nnykqEQ62t7CxmM3ja6NzUQ4L0MLOOzddjLKk";
|
|
|
|
const EXPIRED_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjE1Nzc4MzcxMDB9.eV1snaGLYrJ7qUoMk74OvBY3WUU9M0Je5HTU2xtX1v0";
|
2020-01-25 15:59:37 +01:00
|
|
|
const INVALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9";
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
#[derive(Debug, Deserialize, PartialEq)]
|
2020-09-15 15:10:41 +02:00
|
|
|
struct TestData {
|
|
|
|
iss: String,
|
|
|
|
sub: String,
|
|
|
|
iat: u64,
|
|
|
|
exp: u64
|
2020-01-22 16:53:02 +00:00
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
|
|
|
impl Default for TestData {
|
|
|
|
fn default() -> Self {
|
2020-01-22 16:53:02 +00:00
|
|
|
Self {
|
|
|
|
iss: "msrd0".to_owned(),
|
|
|
|
sub: "gotham-restful".to_owned(),
|
|
|
|
iat: 1577836800,
|
|
|
|
exp: 4102444800
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
#[derive(Default)]
|
2020-01-25 15:59:37 +01:00
|
|
|
struct NoneAuthHandler;
|
2020-09-15 15:10:41 +02:00
|
|
|
impl<T> AuthHandler<T> for NoneAuthHandler {
|
|
|
|
fn jwt_secret<F: FnOnce() -> Option<T>>(&self, _state: &mut State, _decode_data: F) -> Option<Vec<u8>> {
|
2020-01-25 15:59:37 +01:00
|
|
|
None
|
2020-01-22 16:53:02 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-25 15:59:37 +01:00
|
|
|
#[test]
|
2020-09-15 15:10:41 +02:00
|
|
|
fn test_auth_middleware_none_secret() {
|
2020-01-25 15:59:37 +01:00
|
|
|
let middleware = <AuthMiddleware<TestData, NoneAuthHandler>>::from_source(AuthSource::AuthorizationHeader);
|
|
|
|
State::with_new(|mut state| {
|
|
|
|
let mut headers = HeaderMap::new();
|
|
|
|
headers.insert(AUTHORIZATION, format!("Bearer {}", VALID_TOKEN).parse().unwrap());
|
|
|
|
state.put(headers);
|
|
|
|
middleware.auth_status(&mut state);
|
|
|
|
});
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
#[derive(Default)]
|
|
|
|
struct TestAssertingHandler;
|
|
|
|
impl<T> AuthHandler<T> for TestAssertingHandler
|
2020-09-15 15:10:41 +02:00
|
|
|
where
|
|
|
|
T: Debug + Default + PartialEq
|
2020-01-22 16:53:02 +00:00
|
|
|
{
|
2020-09-15 15:10:41 +02:00
|
|
|
fn jwt_secret<F: FnOnce() -> Option<T>>(&self, _state: &mut State, decode_data: F) -> Option<Vec<u8>> {
|
2020-01-22 16:53:02 +00:00
|
|
|
assert_eq!(decode_data(), Some(T::default()));
|
|
|
|
Some(JWT_SECRET.to_vec())
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
#[test]
|
2020-09-15 15:10:41 +02:00
|
|
|
fn test_auth_middleware_decode_data() {
|
2020-01-22 16:53:02 +00:00
|
|
|
let middleware = <AuthMiddleware<TestData, TestAssertingHandler>>::from_source(AuthSource::AuthorizationHeader);
|
|
|
|
State::with_new(|mut state| {
|
|
|
|
let mut headers = HeaderMap::new();
|
|
|
|
headers.insert(AUTHORIZATION, format!("Bearer {}", VALID_TOKEN).parse().unwrap());
|
|
|
|
state.put(headers);
|
|
|
|
middleware.auth_status(&mut state);
|
|
|
|
});
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
|
|
|
fn new_middleware<T>(source: AuthSource) -> AuthMiddleware<T, StaticAuthHandler>
|
|
|
|
where
|
|
|
|
T: DeserializeOwned + Send
|
2020-01-22 16:53:02 +00:00
|
|
|
{
|
2020-01-25 15:59:37 +01:00
|
|
|
AuthMiddleware::new(source, Default::default(), StaticAuthHandler::from_array(JWT_SECRET))
|
2020-01-22 16:53:02 +00:00
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
#[test]
|
2020-09-15 15:10:41 +02:00
|
|
|
fn test_auth_middleware_no_token() {
|
2020-01-22 16:53:02 +00:00
|
|
|
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
|
|
|
State::with_new(|mut state| {
|
|
|
|
let status = middleware.auth_status(&mut state);
|
|
|
|
match status {
|
|
|
|
AuthStatus::Unauthenticated => {},
|
|
|
|
_ => panic!("Expected AuthStatus::Unauthenticated, got {:?}", status)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
#[test]
|
2020-09-15 15:10:41 +02:00
|
|
|
fn test_auth_middleware_expired_token() {
|
2020-01-22 16:53:02 +00:00
|
|
|
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
|
|
|
State::with_new(|mut state| {
|
|
|
|
let mut headers = HeaderMap::new();
|
|
|
|
headers.insert(AUTHORIZATION, format!("Bearer {}", EXPIRED_TOKEN).parse().unwrap());
|
|
|
|
state.put(headers);
|
|
|
|
let status = middleware.auth_status(&mut state);
|
|
|
|
match status {
|
|
|
|
AuthStatus::Expired => {},
|
|
|
|
_ => panic!("Expected AuthStatus::Expired, got {:?}", status)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-25 15:59:37 +01:00
|
|
|
#[test]
|
2020-09-15 15:10:41 +02:00
|
|
|
fn test_auth_middleware_invalid_token() {
|
2020-01-25 15:59:37 +01:00
|
|
|
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
|
|
|
State::with_new(|mut state| {
|
|
|
|
let mut headers = HeaderMap::new();
|
|
|
|
headers.insert(AUTHORIZATION, format!("Bearer {}", INVALID_TOKEN).parse().unwrap());
|
|
|
|
state.put(headers);
|
|
|
|
let status = middleware.auth_status(&mut state);
|
|
|
|
match status {
|
|
|
|
AuthStatus::Invalid => {},
|
|
|
|
_ => panic!("Expected AuthStatus::Invalid, got {:?}", status)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
#[test]
|
2020-09-15 15:10:41 +02:00
|
|
|
fn test_auth_middleware_auth_header_token() {
|
2020-01-22 16:53:02 +00:00
|
|
|
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
|
|
|
State::with_new(|mut state| {
|
|
|
|
let mut headers = HeaderMap::new();
|
|
|
|
headers.insert(AUTHORIZATION, format!("Bearer {}", VALID_TOKEN).parse().unwrap());
|
|
|
|
state.put(headers);
|
|
|
|
let status = middleware.auth_status(&mut state);
|
|
|
|
match status {
|
|
|
|
AuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),
|
|
|
|
_ => panic!("Expected AuthStatus::Authenticated, got {:?}", status)
|
|
|
|
};
|
|
|
|
})
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
#[test]
|
2020-09-15 15:10:41 +02:00
|
|
|
fn test_auth_middleware_header_token() {
|
2020-01-22 16:53:02 +00:00
|
|
|
let header_name = "x-znoiprwmvfexju";
|
|
|
|
let middleware = new_middleware::<TestData>(AuthSource::Header(HeaderName::from_static(header_name)));
|
|
|
|
State::with_new(|mut state| {
|
|
|
|
let mut headers = HeaderMap::new();
|
|
|
|
headers.insert(header_name, VALID_TOKEN.parse().unwrap());
|
|
|
|
state.put(headers);
|
|
|
|
let status = middleware.auth_status(&mut state);
|
|
|
|
match status {
|
|
|
|
AuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),
|
|
|
|
_ => panic!("Expected AuthStatus::Authenticated, got {:?}", status)
|
|
|
|
};
|
|
|
|
})
|
|
|
|
}
|
2020-09-15 15:10:41 +02:00
|
|
|
|
2020-01-22 16:53:02 +00:00
|
|
|
#[test]
|
2020-09-15 15:10:41 +02:00
|
|
|
fn test_auth_middleware_cookie_token() {
|
2020-01-22 16:53:02 +00:00
|
|
|
let cookie_name = "znoiprwmvfexju";
|
|
|
|
let middleware = new_middleware::<TestData>(AuthSource::Cookie(cookie_name.to_owned()));
|
|
|
|
State::with_new(|mut state| {
|
|
|
|
let mut jar = CookieJar::new();
|
|
|
|
jar.add_original(Cookie::new(cookie_name, VALID_TOKEN));
|
|
|
|
state.put(jar);
|
|
|
|
let status = middleware.auth_status(&mut state);
|
|
|
|
match status {
|
|
|
|
AuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),
|
|
|
|
_ => panic!("Expected AuthStatus::Authenticated, got {:?}", status)
|
|
|
|
};
|
|
|
|
})
|
|
|
|
}
|
2020-11-23 23:17:28 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_auth_middleware_cookie_no_jar() {
|
|
|
|
let cookie_name = "znoiprwmvfexju";
|
|
|
|
let middleware = new_middleware::<TestData>(AuthSource::Cookie(cookie_name.to_owned()));
|
|
|
|
State::with_new(|mut state| {
|
|
|
|
let mut headers = HeaderMap::new();
|
|
|
|
headers.insert(COOKIE, format!("{}={}", cookie_name, VALID_TOKEN).parse().unwrap());
|
|
|
|
state.put(headers);
|
|
|
|
let status = middleware.auth_status(&mut state);
|
|
|
|
match status {
|
|
|
|
AuthStatus::Authenticated(data) => assert_eq!(data, TestData::default()),
|
|
|
|
_ => panic!("Expected AuthStatus::Authenticated, got {:?}", status)
|
|
|
|
};
|
|
|
|
})
|
|
|
|
}
|
2020-01-22 16:53:02 +00:00
|
|
|
}
|