Skip to content

Commit

Permalink
Feature: New Flat format which omits unprotected headers
Browse files Browse the repository at this point in the history
  • Loading branch information
alexrudy committed Nov 23, 2023
1 parent 6d3bc15 commit 813e65d
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ pub use claims::{Claims, RegisteredClaims};
pub use fmt::JWTFormat;
pub use jose::{Header, RegisteredHeader};
pub use token::Token;
pub use token::{Compact, Flat};
pub use token::{Compact, Flat, FlatUnprotected};
108 changes: 103 additions & 5 deletions src/token/formats.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt::Write;

use serde::{Deserialize, Serialize};
use serde::{ser, Deserialize, Serialize};

use super::{HasSignature, MaybeSigned};
use super::{Payload, Token};
Expand All @@ -20,13 +20,14 @@ impl Compact {
}
}

/// A token format that serializes the token as a single JSON object.
/// A token format that serializes the token as a single JSON object,
/// with the unprotected header as a top-level field.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Flat<U> {
pub struct FlatUnprotected<U> {
unprotected: U,
}

impl<U> Flat<U> {
impl<U> FlatUnprotected<U> {
/// Creates a new `Flat` token format with the given unprotected header.
pub fn new(unprotected: U) -> Self {
Self { unprotected }
Expand All @@ -43,6 +44,10 @@ impl<U> Flat<U> {
}
}

/// A token format that serializes the token as a single JSON object.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Flat;

/// Error returned when a token cannot be formatted as a string.
///
/// This error can occur when serializing the header or payload, or
Expand Down Expand Up @@ -103,7 +108,7 @@ struct FlatToken<'t, P, U> {
signature: String,
}

impl<U> TokenFormat for Flat<U>
impl<U> TokenFormat for FlatUnprotected<U>
where
U: Serialize,
{
Expand Down Expand Up @@ -134,3 +139,96 @@ where
Ok(())
}
}

impl<P, S, U> Serialize for Token<P, S, FlatUnprotected<U>>
where
S: HasSignature,
<S as MaybeSigned>::Header: Serialize,
<S as MaybeSigned>::HeaderState: Serialize,
U: Serialize,
P: Serialize,
{
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
where
Ser: ser::Serializer,
{
let header = Base64JSON(self.state.header())
.serialized_value()
.map_err(|err| ser::Error::custom(err))?;

Check failure on line 157 in src/token/formats.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant closure

error: redundant closure --> src/token/formats.rs:157:22 | 157 | .map_err(|err| ser::Error::custom(err))?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `ser::Error::custom` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure = note: `-D clippy::redundant-closure` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]`
let signature = Base64Data(self.state.signature())
.serialized_value()
.map_err(|err| ser::Error::custom(err))?;

Check failure on line 160 in src/token/formats.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant closure

error: redundant closure --> src/token/formats.rs:160:22 | 160 | .map_err(|err| ser::Error::custom(err))?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `ser::Error::custom` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure

let flat = FlatToken {
payload: &self.payload,
protected: header,
unprotected: &self.fmt.unprotected,
signature,
};

flat.serialize(serializer)
}
}

#[derive(Debug, Serialize)]
struct FlatSimpleToken<'t, P> {
payload: &'t Payload<P>,
protected: String,
signature: String,
}

impl TokenFormat for Flat {
fn render<S>(
&self,
writer: &mut impl Write,
token: &Token<impl Serialize, S, Self>,
) -> Result<(), TokenFormattingError>
where
Self: Sized,
S: HasSignature,
<S as MaybeSigned>::Header: Serialize,
<S as MaybeSigned>::HeaderState: Serialize,
{
let header = Base64JSON(&token.state.header()).serialized_value()?;
let signature = Base64Data(token.state.signature()).serialized_value()?;

let flat = FlatSimpleToken {
payload: &token.payload,
protected: header,
signature,
};

let data = serde_json::to_string(&flat)?;
write!(writer, "{}", data)?;

Ok(())
}
}

impl<P, S> Serialize for Token<P, S, Flat>
where
S: HasSignature,
<S as MaybeSigned>::Header: Serialize,
<S as MaybeSigned>::HeaderState: Serialize,
P: Serialize,
{
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
where
Ser: ser::Serializer,
{
let header = Base64JSON(self.state.header())
.serialized_value()
.map_err(|err| ser::Error::custom(err))?;

Check failure on line 221 in src/token/formats.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant closure

error: redundant closure --> src/token/formats.rs:221:22 | 221 | .map_err(|err| ser::Error::custom(err))?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `ser::Error::custom` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure
let signature = Base64Data(self.state.signature())
.serialized_value()
.map_err(|err| ser::Error::custom(err))?;

Check failure on line 224 in src/token/formats.rs

View workflow job for this annotation

GitHub Actions / clippy

redundant closure

error: redundant closure --> src/token/formats.rs:224:22 | 224 | .map_err(|err| ser::Error::custom(err))?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `ser::Error::custom` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure

let flat = FlatSimpleToken {
payload: &self.payload,
protected: header,
signature,
};

flat.serialize(serializer)
}
}
11 changes: 4 additions & 7 deletions src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::{
mod formats;
mod state;

pub use self::formats::{Compact, Flat, TokenFormat, TokenFormattingError};
pub use self::formats::{Compact, Flat, FlatUnprotected, TokenFormat, TokenFormattingError};
pub use self::state::{HasSignature, MaybeSigned, Signed, Unsigned, Unverified, Verified};

/// A JWT Playload. Most payloads are JSON objects, which are serialized, and then converted
Expand Down Expand Up @@ -223,19 +223,16 @@ impl<P, H> Token<P, Unsigned<H>, Compact> {
}
}

impl<P, U, H> Token<P, Unsigned<H>, Flat<U>>
where
U: Serialize,
{
impl<P, H> Token<P, Unsigned<H>, Flat> {
/// Create a new token with the given header and payload, in the flat format.
///
/// See also [`Token::new`] and [`Token::compact`] to create a token in a specific format.
///
/// The flat format is the format with a JSON object containing the header, payload, and
/// signature, all in the same object. It can also include additional JSON data as "unprotected"\
/// headers, which are not signed and cannot be verified.
pub fn flat(header: H, unprotected: U, payload: P) -> Token<P, Unsigned<H>, Flat<U>> {
Token::new(header, payload, Flat::new(unprotected))
pub fn flat(header: H, payload: P) -> Token<P, Unsigned<H>, Flat> {
Token::new(header, payload, Flat)
}
}

Expand Down

0 comments on commit 813e65d

Please sign in to comment.