Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update jsonwebtoken to 8.0 #166

Merged
merged 8 commits into from Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org).

## [Unreleased]
### Updated
- `jsonwebtoken` crate to 8.0

### Changed
- When logging errors, the `Debug` implementation is now used (`errorlog` feature only)
- `AuthStatus::Invalid` now contains the jsonwebtoken error (`auth` feature only)
- `AuthStatus::Expired` has been removed (`auth` feature only)
- `AuthError` is now a struct and will include an error message in the payload

## [0.6.4] - 2022-01-31
### Updated
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -32,7 +32,7 @@ base64 = { version = "0.13", optional = true }
cookie = { version = "0.15", optional = true }
either = { version = "1.6", optional = true }
gotham_middleware_diesel = { version = "0.4.0", optional = true }
jsonwebtoken = { version = "7.2", optional = true }
jsonwebtoken = { version = "8.0", default-features = false, optional = true }
lazy-regex = { version = "2.2.1", optional = true }
openapi_type = { version = "0.3.1", optional = true }
parking_lot = { version = "0.12", optional = true }
Expand Down
6 changes: 4 additions & 2 deletions derive/src/resource_error.rs
Expand Up @@ -236,7 +236,7 @@ impl ErrorVariant {
fn were(&self) -> Option<TokenStream> {
self.from_ty
.as_ref()
.map(|(_, ty)| quote!( #ty : ::std::error::Error ))
.map(|(_, ty)| quote!(#ty : ::gotham_restful::IntoResponseError))
}
}

Expand Down Expand Up @@ -304,7 +304,9 @@ pub fn expand_resource_error(input: DeriveInput) -> Result<TokenStream> {
let ty = &field.ty;
quote!( #ty : Default )
})
.chain(iter::once(quote!( #from_ty : ::std::error::Error )));
.chain(iter::once(
quote!(#from_ty : ::gotham_restful::IntoResponseError)
));
let fields_let = var
.fields
.iter()
Expand Down
51 changes: 29 additions & 22 deletions src/auth.rs
@@ -1,4 +1,4 @@
use crate::{AuthError, Forbidden};
use crate::AuthError;

use cookie::CookieJar;
use futures_util::{
Expand All @@ -13,7 +13,7 @@ use gotham::{
prelude::*,
state::State
};
use jsonwebtoken::{errors::ErrorKind, DecodingKey};
use jsonwebtoken::DecodingKey;
use serde::de::DeserializeOwned;
use std::{marker::PhantomData, panic::RefUnwindSafe, pin::Pin};

Expand All @@ -22,15 +22,20 @@ pub type AuthValidation = jsonwebtoken::Validation;
/// The authentication status returned by the auth middleware for each request.
#[derive(Debug, StateData)]
pub enum AuthStatus<T: Send + 'static> {
/// The auth status is unknown.
/// The auth status is unknown. This is likely because no secret was provided
/// that could be used to verify the token of the client.
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.

/// The request has been performed with an invalid authentication. This
/// includes expired tokens. Further details can be obtained from the
/// included error.
Invalid(jsonwebtoken::errors::Error),

/// The request has been performed with a valid authentication. The claims
/// that were decoded from the token are attached.
Authenticated(T)
}

Expand All @@ -39,23 +44,23 @@ where
T: Clone + Send + 'static
{
fn clone(&self) -> Self {
// TODO why is this manually implemented?
match self {
Self::Unknown => Self::Unknown,
Self::Unauthenticated => Self::Unauthenticated,
Self::Invalid => Self::Invalid,
Self::Expired => Self::Expired,
Self::Invalid(err) => Self::Invalid(err.clone()),
Self::Authenticated(data) => Self::Authenticated(data.clone())
}
}
}

impl<T> Copy for AuthStatus<T> where T: Copy + Send + 'static {}

impl<T: Send + 'static> AuthStatus<T> {
pub fn ok(self) -> Result<T, AuthError> {
match self {
Self::Authenticated(data) => Ok(data),
_ => Err(Forbidden)
Self::Unknown => Err(AuthError::new("The authentication could not be determined")),
Self::Unauthenticated => Err(AuthError::new("Missing token")),
Self::Invalid(err) => Err(AuthError::new(format!("Invalid token: {err}"))),
Self::Authenticated(data) => Ok(data)
}
}
}
Expand Down Expand Up @@ -262,10 +267,7 @@ where
&self.validation
) {
Ok(data) => data.claims,
Err(e) => match e.into_kind() {
ErrorKind::ExpiredSignature => return AuthStatus::Expired,
_ => return AuthStatus::Invalid
}
Err(e) => return AuthStatus::Invalid(e)
};

// we found a valid token
Expand Down Expand Up @@ -313,6 +315,7 @@ mod test {
use super::*;
use cookie::Cookie;
use gotham::hyper::header::COOKIE;
use jsonwebtoken::errors::ErrorKind;
use std::fmt::Debug;

// 256-bit random string
Expand Down Expand Up @@ -437,8 +440,10 @@ mod test {
state.put(headers);
let status = middleware.auth_status(&mut state);
match status {
AuthStatus::Expired => {},
_ => panic!("Expected AuthStatus::Expired, got {status:?}")
AuthStatus::Invalid(err) if *err.kind() == ErrorKind::ExpiredSignature => {},
_ => panic!(
"Expected AuthStatus::Invalid(..) with ErrorKind::ExpiredSignature, got {status:?}"
)
};
});
}
Expand All @@ -455,8 +460,10 @@ mod test {
state.put(headers);
let status = middleware.auth_status(&mut state);
match status {
AuthStatus::Invalid => {},
_ => panic!("Expected AuthStatus::Invalid, got {status:?}")
AuthStatus::Invalid(err) if *err.kind() == ErrorKind::InvalidToken => {},
_ => panic!(
"Expected AuthStatus::Invalid(..) with ErrorKind::InvalidToken, got {status:?}"
)
};
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Expand Up @@ -524,8 +524,8 @@ pub use endpoint::{Endpoint, NoopExtractor};

mod response;
pub use response::{
AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponse,
IntoResponseError, NoContent, Raw, Redirect, Response, Success
AuthError, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponse, IntoResponseError,
NoContent, Raw, Redirect, Response, Success
};
#[cfg(feature = "openapi")]
pub use response::{IntoResponseWithSchema, ResponseSchema};
Expand Down
78 changes: 45 additions & 33 deletions src/response/auth_result.rs
@@ -1,14 +1,42 @@
use crate::{IntoResponseError, Response};
use gotham::{hyper::StatusCode, mime::TEXT_PLAIN_UTF_8};
use gotham_restful_derive::ResourceError;
#[cfg(feature = "openapi")]
use openapi_type::{OpenapiSchema, OpenapiType};

/**
This is an error type that always yields a _403 Forbidden_ response. This type is best used in
combination with [AuthSuccess] or [AuthResult].
*/
#[derive(Debug, Clone, Copy, ResourceError)]
pub enum AuthError {
#[status(FORBIDDEN)]
#[display("Forbidden")]
Forbidden
/// This is an error type that always yields a _403 Forbidden_ response. This type
/// is best used in combination with [`AuthSuccess`] or [`AuthResult`].
#[derive(Clone, Debug)]
pub struct AuthError(String);

impl AuthError {
pub fn new<T: Into<String>>(msg: T) -> Self {
Self(msg.into())
}
}

impl IntoResponseError for AuthError {
// TODO why does this need to be serde_json::Error ?!?
type Err = serde_json::Error;

fn into_response_error(self) -> Result<Response, Self::Err> {
Ok(Response::new(
StatusCode::FORBIDDEN,
self.0,
Some(TEXT_PLAIN_UTF_8)
))
}

#[cfg(feature = "openapi")]
fn status_codes() -> Vec<StatusCode> {
vec![StatusCode::FORBIDDEN]
}

#[cfg(feature = "openapi")]
fn schema(code: StatusCode) -> OpenapiSchema {
assert_eq!(code, StatusCode::FORBIDDEN);
<super::Raw<String> as OpenapiType>::schema()
}
}

/**
Expand All @@ -35,10 +63,7 @@ Use can look something like this (assuming the `auth` feature is enabled):
#
#[read_all]
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthSuccess<NoContent> {
let auth_data = match auth {
AuthStatus::Authenticated(data) => data,
_ => return Err(Forbidden)
};
let auth_data = auth.ok()?;
// do something
Ok(NoContent::default())
}
Expand All @@ -47,28 +72,18 @@ fn read_all(auth : AuthStatus<MyAuthData>) -> AuthSuccess<NoContent> {
*/
pub type AuthSuccess<T> = Result<T, AuthError>;

/**
This is an error type that either yields a _403 Forbidden_ respone if produced from an authentication
error, or delegates to another error type. This type is best used with [AuthResult].
*/
#[derive(Debug, ResourceError)]
/// This is an error type that either yields a _403 Forbidden_ response if produced
/// from an authentication error, or delegates to another error type. This type is
/// best used with [`AuthResult`].
#[derive(Debug, Clone, ResourceError)]
pub enum AuthErrorOrOther<E> {
#[status(FORBIDDEN)]
#[display("Forbidden")]
Forbidden,
Forbidden(#[from] AuthError),

#[status(INTERNAL_SERVER_ERROR)]
#[display("{0}")]
Other(E)
}

impl<E> From<AuthError> for AuthErrorOrOther<E> {
fn from(err: AuthError) -> Self {
match err {
AuthError::Forbidden => Self::Forbidden
}
}
}

mod private {
use gotham::handler::HandlerError;
pub trait Sealed {}
Expand Down Expand Up @@ -110,10 +125,7 @@ Use can look something like this (assuming the `auth` feature is enabled):
#
#[read_all]
fn read_all(auth : AuthStatus<MyAuthData>) -> AuthResult<NoContent, io::Error> {
let auth_data = match auth {
AuthStatus::Authenticated(data) => data,
_ => Err(Forbidden)?
};
let auth_data = auth.ok()?;
// do something
Ok(NoContent::default().into())
}
Expand Down
6 changes: 3 additions & 3 deletions src/response/mod.rs
Expand Up @@ -245,15 +245,15 @@ fn errorlog<E>(_e: E) {}

fn handle_error<E>(e: E) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
where
E: Display + IntoResponseError
E: Debug + IntoResponseError
{
let msg = e.to_string();
let msg = format!("{e:?}");
let res = e.into_response_error();
match &res {
Ok(res) if res.status.is_server_error() => errorlog(msg),
Err(err) => {
errorlog(msg);
errorlog(&err);
errorlog(format!("{err:?}"));
},
_ => {}
};
Expand Down
6 changes: 3 additions & 3 deletions src/response/no_content.rs
Expand Up @@ -11,7 +11,7 @@ use gotham::{
};
#[cfg(feature = "openapi")]
use openapi_type::{OpenapiSchema, OpenapiType};
use std::{fmt::Display, future::Future, pin::Pin};
use std::{fmt::Debug, future::Future, pin::Pin};

/**
This is the return type of a resource that doesn't actually return something. It will result
Expand Down Expand Up @@ -87,7 +87,7 @@ impl ResponseSchema for NoContent {

impl<E> IntoResponse for Result<NoContent, E>
where
E: Display + IntoResponseError<Err = serde_json::Error>
E: Debug + IntoResponseError<Err = serde_json::Error>
{
type Err = serde_json::Error;

Expand All @@ -108,7 +108,7 @@ where
#[cfg(feature = "openapi")]
impl<E> ResponseSchema for Result<NoContent, E>
where
E: Display + IntoResponseError<Err = serde_json::Error>
E: Debug + IntoResponseError<Err = serde_json::Error>
{
fn status_codes() -> Vec<StatusCode> {
let mut status_codes = E::status_codes();
Expand Down
11 changes: 5 additions & 6 deletions src/response/raw.rs
Expand Up @@ -2,9 +2,6 @@ use super::{handle_error, IntoResponse, IntoResponseError};
use crate::{types::ResourceType, FromBody, RequestBody, Response};
#[cfg(feature = "openapi")]
use crate::{IntoResponseWithSchema, ResponseSchema};
#[cfg(feature = "openapi")]
use openapi_type::{OpenapiSchema, OpenapiType};

use futures_core::future::Future;
use futures_util::{future, future::FutureExt};
use gotham::{
Expand All @@ -16,8 +13,10 @@ use gotham::{
};
#[cfg(feature = "openapi")]
use openapi_type::openapi::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty};
#[cfg(feature = "openapi")]
use openapi_type::{OpenapiSchema, OpenapiType};
use serde_json::error::Error as SerdeJsonError;
use std::{convert::Infallible, fmt::Display, pin::Pin};
use std::{convert::Infallible, fmt::Debug, pin::Pin};

/**
This type can be used both as a raw request body, as well as as a raw response. However, all types
Expand Down Expand Up @@ -130,7 +129,7 @@ where
impl<T, E> IntoResponse for Result<Raw<T>, E>
where
Raw<T>: IntoResponse,
E: Display + IntoResponseError<Err = <Raw<T> as IntoResponse>::Err>
E: Debug + IntoResponseError<Err = <Raw<T> as IntoResponse>::Err>
{
type Err = E::Err;

Expand All @@ -146,7 +145,7 @@ where
impl<T, E> ResponseSchema for Result<Raw<T>, E>
where
Raw<T>: IntoResponseWithSchema,
E: Display + IntoResponseError<Err = <Raw<T> as IntoResponse>::Err>
E: Debug + IntoResponseError<Err = <Raw<T> as IntoResponse>::Err>
{
fn status_codes() -> Vec<StatusCode> {
let mut status_codes = E::status_codes();
Expand Down
9 changes: 3 additions & 6 deletions src/response/redirect.rs
Expand Up @@ -9,10 +9,7 @@ use gotham::hyper::{
};
#[cfg(feature = "openapi")]
use openapi_type::OpenapiSchema;
use std::{
error::Error as StdError,
fmt::{Debug, Display}
};
use std::{error::Error as StdError, fmt::Debug};
use thiserror::Error;

/**
Expand Down Expand Up @@ -81,7 +78,7 @@ pub enum RedirectError<E: StdError + 'static> {
#[allow(ambiguous_associated_items)] // an enum variant is not a type. never.
impl<E> IntoResponse for Result<Redirect, E>
where
E: Display + IntoResponseError,
E: Debug + IntoResponseError,
<E as IntoResponseError>::Err: StdError + Sync
{
type Err = RedirectError<<E as IntoResponseError>::Err>;
Expand All @@ -97,7 +94,7 @@ where
#[cfg(feature = "openapi")]
impl<E> ResponseSchema for Result<Redirect, E>
where
E: Display + IntoResponseError,
E: Debug + IntoResponseError,
<E as IntoResponseError>::Err: StdError + Sync
{
fn status_codes() -> Vec<StatusCode> {
Expand Down