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

More specific error responses when authorization fails #46

Merged
merged 1 commit into from
Jun 30, 2023

Conversation

kim
Copy link
Contributor

@kim kim commented Jun 30, 2023

Description of Changes

API

  • This is a breaking change to the module API
  • This is a breaking change to the ClientAPI

If the API is breaking, please state below what will break

@kim kim requested review from Centril and jdetter June 30, 2023 22:05
@@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
use std::{collections::HashSet, time::SystemTime};

pub use jsonwebtoken::errors::Error as JwtError;
pub use jsonwebtoken::errors::ErrorKind as JwtErrorKind;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sidenote: when I worked on the Rust compiler, I noticed that these sort of re-exports tend to make it harder to split crates and can be detrimental for refactoring. I would in the future recommend more direct imports instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mm, good point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be true for a compiler, but I don’t agree it is in general. Re-exporting can help ensure consistent dependency resolution, for example.

In this particular case, it is a reasonable API choice (consumers should work against the core crate, not a particular implementation of JWT), and it is a reasonable choice to enable improvement: the chosen library is of somewhat questionable quality, and it is arguably easier to replace it in only one place.

Admittedly I blinked when I arrived here, but decided to leave it as is for these reasons.

Copy link
Contributor

@Centril Centril left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-functional nits and suggestions

Comment on lines +91 to +93
pub struct AuthorizationRejection {
reason: AuthorizationRejectionReason,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub struct AuthorizationRejection {
reason: AuthorizationRejectionReason,
}
/// A response by the API signifying that an authorization was rejected with the `reason` for this.
pub struct AuthorizationRejection {
/// The reason the authorization was rejected.
reason: AuthorizationRejectionReason,
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These docstrings do not add any information over just the types. So what is the value of doing this, even for non public items?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They do add a bit of context on the type which can be useful when hovering over the type. Also, I think this is a good habit to get into to document too much rather than too little.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we will have to agree to disagree

impl IntoResponse for AuthorizationRejection {
fn into_response(self) -> axum::response::Response {
(StatusCode::BAD_REQUEST, "Authorization is invalid - malformed token.").into_response()
// Most likely, the server key was rotated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Most likely, the server key was rotated
// Most likely, the server key was rotated.

Nit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to you feel about authoring a style guide?

);
// JWT is hard bruh
const INVALID: (StatusCode, &str) = (StatusCode::BAD_REQUEST, "Authorization is invalid: malformed token");
// Sensible fallback if no auth header is present
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Sensible fallback if no auth header is present
// Sensible fallback if no auth header is present.

nit

AuthorizationRejectionReason::Jwt(JwtErrorKind::InvalidSignature) => ROTATED.into_response(),
AuthorizationRejectionReason::Header(rejection) => match rejection.reason() {
TypedHeaderRejectionReason::Missing => REQUIRED.into_response(),
_ => rejection.into_response(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_ => rejection.into_response(),
TypedHeaderRejectionReason::Error(_) => rejection.into_response(),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general a good suggestion, here I don’t think. I should’ve used matches!.

StatusCode::UNAUTHORIZED,
"Authorization failed: token not signed by this instance",
);
// JWT is hard bruh
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be helpful to expand this comment for someone who has no context on JWT.

TypedHeaderRejectionReason::Missing => Ok(Self { auth: None }),
_ => Err(AuthorizationRejection),
_ => Err(AuthorizationRejection {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_ => Err(AuthorizationRejection {
TypedHeaderRejectionReason::Error(_) => Err(AuthorizationRejection {

let auth = SpacetimeAuth {
creds,
identity: claims.hex_identity,
};
Ok(Self { auth: Some(auth) })
}
Err(e) => match e.reason() {
// Leave it to handlers to decide on unauthorized requests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Leave it to handlers to decide on unauthorized requests
// Leave it to handlers to decide on unauthorized requests.

Copy link
Collaborator

@jdetter jdetter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid token error:

Error: invalid HTTP header (authorization)

Token valid, but not correct instance:

Error: Authorization failed: token not signed by this instance

Awesome, thank you @kim!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants