Move to serde + base64
Remove Part trait Move tests to tests directory Reorganise code
This commit is contained in:
parent
e60cb57f0f
commit
e3a4294e85
|
@ -12,3 +12,8 @@ keywords = ["jwt", "web", "api", "token", "json"]
|
|||
[dependencies]
|
||||
rustc-serialize = "^0.3"
|
||||
ring = "^0.7"
|
||||
error-chain = "0.9"
|
||||
serde_json = "0.9"
|
||||
serde_derive = "0.9"
|
||||
serde = "0.9"
|
||||
base64 = "0.4"
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#![feature(test)]
|
||||
extern crate test;
|
||||
extern crate jsonwebtoken as jwt;
|
||||
extern crate rustc_serialize;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use jwt::{encode, decode, Algorithm, Header};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, RustcEncodable, RustcDecodable)]
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
sub: String,
|
||||
company: String
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
extern crate jsonwebtoken as jwt;
|
||||
extern crate rustc_serialize;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
use jwt::{encode, decode, Header, Algorithm};
|
||||
use jwt::errors::{Error};
|
||||
use jwt::errors::{ErrorKind};
|
||||
|
||||
|
||||
#[derive(Debug, RustcEncodable, RustcDecodable)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
sub: String,
|
||||
company: String
|
||||
|
@ -38,8 +38,8 @@ fn main() {
|
|||
|
||||
let token_data = match decode::<Claims>(&token, key.as_ref(), Algorithm::HS256) {
|
||||
Ok(c) => c,
|
||||
Err(err) => match err {
|
||||
Error::InvalidToken => panic!(), // Example on how to handle a specific error
|
||||
Err(err) => match *err.kind() {
|
||||
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
|
||||
_ => panic!()
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
extern crate jsonwebtoken as jwt;
|
||||
extern crate rustc_serialize;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use jwt::{encode, decode, Header, Algorithm};
|
||||
use jwt::errors::{Error};
|
||||
use jwt::errors::{ErrorKind};
|
||||
|
||||
|
||||
#[derive(Debug, RustcEncodable, RustcDecodable)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
sub: String,
|
||||
company: String
|
||||
|
@ -29,8 +30,8 @@ fn main() {
|
|||
|
||||
let token_data = match decode::<Claims>(&token, key.as_ref(), Algorithm::HS512) {
|
||||
Ok(c) => c,
|
||||
Err(err) => match err {
|
||||
Error::InvalidToken => panic!(), // Example on how to handle a specific error
|
||||
Err(err) => match *err.kind() {
|
||||
ErrorKind::InvalidToken => panic!(), // Example on how to handle a specific error
|
||||
_ => panic!()
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
use base64;
|
||||
use ring::{digest, hmac};
|
||||
use ring::constant_time::verify_slices_are_equal;
|
||||
use serde::de::Deserialize;
|
||||
use serde::ser::Serialize;
|
||||
use serde_json;
|
||||
|
||||
|
||||
use errors::{Result, ErrorKind};
|
||||
use header::Header;
|
||||
|
||||
|
||||
/// The algorithms supported for signing/verifying
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum Algorithm {
|
||||
HS256,
|
||||
HS384,
|
||||
HS512
|
||||
}
|
||||
|
||||
/// Serializes and encodes to base64
|
||||
fn to_jwt_part<T: Serialize>(input: &T) -> Result<String> {
|
||||
let encoded = serde_json::to_string(input)?;
|
||||
Ok(base64::encode_config(encoded.as_bytes(), base64::URL_SAFE_NO_PAD))
|
||||
}
|
||||
|
||||
/// Decodes from base64 and deserializes
|
||||
fn from_jwt_part<B: AsRef<str>, T: Deserialize>(encoded: B) -> Result<T> {
|
||||
let decoded = base64::decode_config(encoded.as_ref(), base64::URL_SAFE_NO_PAD)?;
|
||||
let s = String::from_utf8(decoded)?;
|
||||
|
||||
Ok(serde_json::from_str(&s)?)
|
||||
}
|
||||
|
||||
|
||||
/// The return type of a successful call to decode(...)
|
||||
#[derive(Debug)]
|
||||
pub struct TokenData<T: Deserialize> {
|
||||
pub header: Header,
|
||||
pub claims: T
|
||||
}
|
||||
|
||||
/// Take the payload of a JWT and sign it using the algorithm given.
|
||||
/// Returns the base64 url safe encoded of the hmac result
|
||||
pub fn sign(data: &str, secret: &[u8], algorithm: Algorithm) -> String {
|
||||
let digest = match algorithm {
|
||||
Algorithm::HS256 => &digest::SHA256,
|
||||
Algorithm::HS384 => &digest::SHA384,
|
||||
Algorithm::HS512 => &digest::SHA512,
|
||||
};
|
||||
let key = hmac::SigningKey::new(digest, secret);
|
||||
base64::encode_config(
|
||||
hmac::sign(&key, data.as_bytes()).as_ref(),
|
||||
base64::URL_SAFE_NO_PAD
|
||||
)
|
||||
}
|
||||
|
||||
/// Compares the signature given with a re-computed signature
|
||||
pub fn verify(signature: &str, data: &str, secret: &[u8], algorithm: Algorithm) -> bool {
|
||||
verify_slices_are_equal(signature.as_ref(), sign(data, secret, algorithm).as_ref()).is_ok()
|
||||
}
|
||||
|
||||
/// Encode the claims passed and sign the payload using the algorithm from the header and the secret
|
||||
pub fn encode<T: Serialize>(header: Header, claims: &T, secret: &[u8]) -> Result<String> {
|
||||
let encoded_header = to_jwt_part(&header)?;
|
||||
let encoded_claims = to_jwt_part(&claims)?;
|
||||
let payload = [encoded_header.as_ref(), encoded_claims.as_ref()].join(".");
|
||||
let signature = sign(&*payload, secret.as_ref(), header.alg);
|
||||
|
||||
Ok([payload, signature].join("."))
|
||||
}
|
||||
|
||||
/// Used in decode: takes the result of a rsplit and ensure we only get 2 parts
|
||||
/// Errors if we don't
|
||||
macro_rules! expect_two {
|
||||
($iter:expr) => {{
|
||||
let mut i = $iter;
|
||||
match (i.next(), i.next(), i.next()) {
|
||||
(Some(first), Some(second), None) => (first, second),
|
||||
_ => return Err(ErrorKind::InvalidToken.into())
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
/// Decode a token into a Claims struct
|
||||
/// If the token or its signature is invalid, it will return an error
|
||||
pub fn decode<T: Deserialize>(token: &str, secret: &[u8], algorithm: Algorithm) -> Result<TokenData<T>> {
|
||||
let (signature, payload) = expect_two!(token.rsplitn(2, '.'));
|
||||
|
||||
if !verify(signature, payload, secret, algorithm) {
|
||||
return Err(ErrorKind::InvalidSignature.into());
|
||||
}
|
||||
|
||||
let (claims, header) = expect_two!(payload.rsplitn(2, '.'));
|
||||
|
||||
let header: Header = from_jwt_part(header)?;
|
||||
if header.alg != algorithm {
|
||||
return Err(ErrorKind::WrongAlgorithmHeader.into());
|
||||
}
|
||||
let decoded_claims: T = from_jwt_part(claims)?;
|
||||
|
||||
Ok(TokenData { header: header, claims: decoded_claims })
|
||||
}
|
|
@ -1,68 +1,25 @@
|
|||
use std::{string, fmt, error};
|
||||
use rustc_serialize::{json, base64};
|
||||
use base64;
|
||||
use serde_json;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// All the errors we can encounter while signing/verifying tokens
|
||||
/// and a couple of custom one for when the token we are trying
|
||||
/// to verify is invalid
|
||||
pub enum Error {
|
||||
EncodeJSON(json::EncoderError),
|
||||
DecodeBase64(base64::FromBase64Error),
|
||||
DecodeJSON(json::DecoderError),
|
||||
Utf8(string::FromUtf8Error),
|
||||
|
||||
InvalidToken,
|
||||
InvalidSignature,
|
||||
WrongAlgorithmHeader
|
||||
}
|
||||
|
||||
macro_rules! impl_from_error {
|
||||
($f: ty, $e: expr) => {
|
||||
impl From<$f> for Error {
|
||||
fn from(f: $f) -> Error { $e(f) }
|
||||
error_chain! {
|
||||
errors {
|
||||
InvalidToken {
|
||||
description("invalid token")
|
||||
display("Invalid token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_from_error!(json::EncoderError, Error::EncodeJSON);
|
||||
impl_from_error!(base64::FromBase64Error, Error::DecodeBase64);
|
||||
impl_from_error!(json::DecoderError, Error::DecodeJSON);
|
||||
impl_from_error!(string::FromUtf8Error, Error::Utf8);
|
||||
|
||||
impl error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
Error::EncodeJSON(ref err) => err.description(),
|
||||
Error::DecodeBase64(ref err) => err.description(),
|
||||
Error::DecodeJSON(ref err) => err.description(),
|
||||
Error::Utf8(ref err) => err.description(),
|
||||
Error::InvalidToken => "Invalid Token",
|
||||
Error::InvalidSignature => "Invalid Signature",
|
||||
Error::WrongAlgorithmHeader => "Wrong Algorithm Header",
|
||||
InvalidSignature {
|
||||
description("invalid signature")
|
||||
display("Invalid signature")
|
||||
}
|
||||
WrongAlgorithmHeader {
|
||||
description("Wrong Algorithm Header")
|
||||
display("Wrong Algorithm Header")
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
Some(match *self {
|
||||
Error::EncodeJSON(ref err) => err as &error::Error,
|
||||
Error::DecodeBase64(ref err) => err as &error::Error,
|
||||
Error::DecodeJSON(ref err) => err as &error::Error,
|
||||
Error::Utf8(ref err) => err as &error::Error,
|
||||
ref e => e as &error::Error,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Error::EncodeJSON(ref err) => fmt::Display::fmt(err, f),
|
||||
Error::DecodeBase64(ref err) => fmt::Display::fmt(err, f),
|
||||
Error::DecodeJSON(ref err) => fmt::Display::fmt(err, f),
|
||||
Error::Utf8(ref err) => fmt::Display::fmt(err, f),
|
||||
Error::InvalidToken => write!(f, "{}", error::Error::description(self)),
|
||||
Error::InvalidSignature => write!(f, "{}", error::Error::description(self)),
|
||||
Error::WrongAlgorithmHeader => write!(f, "{}", error::Error::description(self)),
|
||||
}
|
||||
foreign_links {
|
||||
Base64(base64::Base64Error);
|
||||
Json(serde_json::Error);
|
||||
Utf8(::std::string::FromUtf8Error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
use crypto::Algorithm;
|
||||
|
||||
|
||||
/// A basic JWT header, the alg defaults to HS256 and typ is automatically
|
||||
/// set to `JWT`. All the other fields are optional
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Header {
|
||||
typ: String,
|
||||
pub alg: Algorithm,
|
||||
pub jku: Option<String>,
|
||||
pub kid: Option<String>,
|
||||
pub x5u: Option<String>,
|
||||
pub x5t: Option<String>
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub fn new(algorithm: Algorithm) -> Header {
|
||||
Header {
|
||||
typ: "JWT".to_string(),
|
||||
alg: algorithm,
|
||||
jku: None,
|
||||
kid: None,
|
||||
x5u: None,
|
||||
x5t: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Header {
|
||||
fn default() -> Header {
|
||||
Header::new(Algorithm::HS256)
|
||||
}
|
||||
}
|
295
src/lib.rs
295
src/lib.rs
|
@ -1,292 +1,19 @@
|
|||
//! Create and parses JWT (JSON Web Tokens)
|
||||
//!
|
||||
|
||||
#![cfg_attr(feature = "dev", allow(unstable_features))]
|
||||
#![cfg_attr(feature = "dev", feature(plugin))]
|
||||
#![cfg_attr(feature = "dev", plugin(clippy))]
|
||||
|
||||
extern crate rustc_serialize;
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate serde;
|
||||
extern crate base64;
|
||||
extern crate ring;
|
||||
|
||||
use ring::{digest, hmac};
|
||||
use ring::constant_time::verify_slices_are_equal;
|
||||
|
||||
use rustc_serialize::{json, Encodable, Decodable};
|
||||
use rustc_serialize::base64::{self, ToBase64, FromBase64};
|
||||
use rustc_serialize::json::{ToJson, Json};
|
||||
|
||||
pub mod errors;
|
||||
use errors::Error;
|
||||
use std::collections::BTreeMap;
|
||||
mod header;
|
||||
mod crypto;
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, RustcDecodable, RustcEncodable)]
|
||||
/// The algorithms supported for signing/verifying
|
||||
pub enum Algorithm {
|
||||
HS256,
|
||||
HS384,
|
||||
HS512
|
||||
}
|
||||
pub use header::{Header};
|
||||
pub use crypto::{Algorithm, sign, verify, encode, decode};
|
||||
|
||||
impl ToJson for Algorithm {
|
||||
fn to_json(&self) -> Json {
|
||||
match *self {
|
||||
Algorithm::HS256 => Json::String("HS256".to_string()),
|
||||
Algorithm::HS384 => Json::String("HS384".to_string()),
|
||||
Algorithm::HS512 => Json::String("HS512".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A part of the JWT: header and claims specifically
|
||||
/// Allows converting from/to struct with base64
|
||||
pub trait Part {
|
||||
type Encoded: AsRef<str>;
|
||||
|
||||
fn from_base64<B: AsRef<[u8]>>(encoded: B) -> Result<Self, Error> where Self: Sized;
|
||||
fn to_base64(&self) -> Result<Self::Encoded, Error>;
|
||||
}
|
||||
|
||||
impl<T> Part for T where T: Encodable + Decodable {
|
||||
type Encoded = String;
|
||||
|
||||
fn to_base64(&self) -> Result<Self::Encoded, Error> {
|
||||
let encoded = try!(json::encode(&self));
|
||||
Ok(encoded.as_bytes().to_base64(base64::URL_SAFE))
|
||||
}
|
||||
|
||||
fn from_base64<B: AsRef<[u8]>>(encoded: B) -> Result<T, Error> {
|
||||
let decoded = try!(encoded.as_ref().from_base64());
|
||||
let s = try!(String::from_utf8(decoded));
|
||||
Ok(try!(json::decode(&s)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, RustcDecodable)]
|
||||
/// A basic JWT header part, the alg defaults to HS256 and typ is automatically
|
||||
/// set to `JWT`. All the other fields are optional
|
||||
pub struct Header {
|
||||
typ: String,
|
||||
pub alg: Algorithm,
|
||||
pub jku: Option<String>,
|
||||
pub kid: Option<String>,
|
||||
pub x5u: Option<String>,
|
||||
pub x5t: Option<String>
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub fn new(algorithm: Algorithm) -> Header {
|
||||
Header {
|
||||
typ: "JWT".to_string(),
|
||||
alg: algorithm,
|
||||
jku: None,
|
||||
kid: None,
|
||||
x5u: None,
|
||||
x5t: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Header {
|
||||
fn default() -> Header {
|
||||
Header::new(Algorithm::HS256)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for Header {
|
||||
fn encode<S: rustc_serialize::Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
|
||||
self.to_json().encode(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson for Header {
|
||||
fn to_json(&self) -> Json {
|
||||
let mut d = BTreeMap::new();
|
||||
d.insert("typ".to_string(), self.typ.to_json());
|
||||
d.insert("alg".to_string(), self.alg.to_json());
|
||||
|
||||
// Define a macro to reduce boilerplate.
|
||||
macro_rules! optional {
|
||||
($field_name:ident) => (
|
||||
if let Some(ref value) = self.$field_name {
|
||||
d.insert(stringify!($field_name).to_string(), value.to_json());
|
||||
}
|
||||
)
|
||||
}
|
||||
optional!(jku);
|
||||
optional!(kid);
|
||||
optional!(x5u);
|
||||
optional!(x5t);
|
||||
Json::Object(d)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// The return type of a successful call to decode(...)
|
||||
pub struct TokenData<T: Part> {
|
||||
pub header: Header,
|
||||
pub claims: T
|
||||
}
|
||||
|
||||
/// Take the payload of a JWT and sign it using the algorithm given.
|
||||
/// Returns the base64 url safe encoded of the hmac result
|
||||
pub fn sign(data: &str, secret: &[u8], algorithm: Algorithm) -> String {
|
||||
let digest = match algorithm {
|
||||
Algorithm::HS256 => &digest::SHA256,
|
||||
Algorithm::HS384 => &digest::SHA384,
|
||||
Algorithm::HS512 => &digest::SHA512,
|
||||
};
|
||||
let key = hmac::SigningKey::new(digest, secret);
|
||||
hmac::sign(&key, data.as_bytes()).as_ref().to_base64(base64::URL_SAFE)
|
||||
}
|
||||
|
||||
/// Compares the signature given with a re-computed signature
|
||||
pub fn verify(signature: &str, data: &str, secret: &[u8], algorithm: Algorithm) -> bool {
|
||||
verify_slices_are_equal(signature.as_ref(), sign(data, secret, algorithm).as_ref()).is_ok()
|
||||
}
|
||||
|
||||
/// Encode the claims passed and sign the payload using the algorithm from the header and the secret
|
||||
pub fn encode<T: Part>(header: Header, claims: &T, secret: &[u8]) -> Result<String, Error> {
|
||||
let encoded_header = try!(header.to_base64());
|
||||
let encoded_claims = try!(claims.to_base64());
|
||||
// seems to be a tiny bit faster than format!("{}.{}", x, y)
|
||||
let payload = [encoded_header.as_ref(), encoded_claims.as_ref()].join(".");
|
||||
let signature = sign(&*payload, secret.as_ref(), header.alg);
|
||||
|
||||
Ok([payload, signature].join("."))
|
||||
}
|
||||
|
||||
/// Used in decode: takes the result of a rsplit and ensure we only get 2 parts
|
||||
/// Errors if we don't
|
||||
macro_rules! expect_two {
|
||||
($iter:expr) => {{
|
||||
let mut i = $iter; // evaluate the expr
|
||||
match (i.next(), i.next(), i.next()) {
|
||||
(Some(first), Some(second), None) => (first, second),
|
||||
_ => return Err(Error::InvalidToken)
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
/// Decode a token into a Claims struct
|
||||
/// If the token or its signature is invalid, it will return an error
|
||||
pub fn decode<T: Part>(token: &str, secret: &[u8], algorithm: Algorithm) -> Result<TokenData<T>, Error> {
|
||||
let (signature, payload) = expect_two!(token.rsplitn(2, '.'));
|
||||
|
||||
let is_valid = verify(
|
||||
signature,
|
||||
payload,
|
||||
secret,
|
||||
algorithm
|
||||
);
|
||||
|
||||
if !is_valid {
|
||||
return Err(Error::InvalidSignature);
|
||||
}
|
||||
|
||||
let (claims, header) = expect_two!(payload.rsplitn(2, '.'));
|
||||
|
||||
let header = try!(Header::from_base64(header));
|
||||
if header.alg != algorithm {
|
||||
return Err(Error::WrongAlgorithmHeader);
|
||||
}
|
||||
let decoded_claims = try!(T::from_base64(claims));
|
||||
|
||||
Ok(TokenData { header: header, claims: decoded_claims})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{encode, decode, Algorithm, Header, sign, verify};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, RustcEncodable, RustcDecodable)]
|
||||
struct Claims {
|
||||
sub: String,
|
||||
company: String
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_hs256() {
|
||||
let result = sign("hello world", b"secret", Algorithm::HS256);
|
||||
let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_hs256() {
|
||||
let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
|
||||
let valid = verify(sig, "hello world", b"secret", Algorithm::HS256);
|
||||
assert!(valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_with_custom_header() {
|
||||
// TODO: test decode value
|
||||
let my_claims = Claims {
|
||||
sub: "b@b.com".to_string(),
|
||||
company: "ACME".to_string()
|
||||
};
|
||||
let mut header = Header::default();
|
||||
header.kid = Some("kid".to_string());
|
||||
let token = encode(header, &my_claims, "secret".as_ref()).unwrap();
|
||||
let token_data = decode::<Claims>(&token, "secret".as_ref(), Algorithm::HS256).unwrap();
|
||||
assert_eq!(my_claims, token_data.claims);
|
||||
assert_eq!("kid", token_data.header.kid.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_claim() {
|
||||
let my_claims = Claims {
|
||||
sub: "b@b.com".to_string(),
|
||||
company: "ACME".to_string()
|
||||
};
|
||||
let token = encode(Header::default(), &my_claims, "secret".as_ref()).unwrap();
|
||||
let token_data = decode::<Claims>(&token, "secret".as_ref(), Algorithm::HS256).unwrap();
|
||||
assert_eq!(my_claims, token_data.claims);
|
||||
assert!(token_data.header.kid.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_token() {
|
||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidToken")]
|
||||
fn decode_token_missing_parts() {
|
||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidSignature")]
|
||||
fn decode_token_invalid_signature() {
|
||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "WrongAlgorithmHeader")]
|
||||
fn decode_token_wrong_algorithm() {
|
||||
let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.pKscJVk7-aHxfmQKlaZxh5uhuKhGMAa-1F5IX5mfUwI";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_token_with_bytes_secret() {
|
||||
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29tcGFueSI6Ikdvb2dvbCJ9.27QxgG96vpX4akKNpD1YdRGHE3_u2X35wR3EHA2eCrs";
|
||||
let claims = decode::<Claims>(token, b"\x01\x02\x03", Algorithm::HS256);
|
||||
assert!(claims.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_token_with_shuffled_header_fields() {
|
||||
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.SEIZ4Jg46VGhquuwPYDLY5qHF8AkQczF14aXM3a2c28";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
||||
assert!(claims.is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
extern crate jsonwebtoken;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use jsonwebtoken::{encode, decode, Algorithm, Header, sign, verify};
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
sub: String,
|
||||
company: String
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_hs256() {
|
||||
let result = sign("hello world", b"secret", Algorithm::HS256);
|
||||
let expected = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_hs256() {
|
||||
let sig = "c0zGLzKEFWj0VxWuufTXiRMk5tlI5MbGDAYhzaxIYjo";
|
||||
let valid = verify(sig, "hello world", b"secret", Algorithm::HS256);
|
||||
assert!(valid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_with_custom_header() {
|
||||
let my_claims = Claims {
|
||||
sub: "b@b.com".to_string(),
|
||||
company: "ACME".to_string()
|
||||
};
|
||||
let mut header = Header::default();
|
||||
header.kid = Some("kid".to_string());
|
||||
let token = encode(header, &my_claims, "secret".as_ref()).unwrap();
|
||||
let token_data = decode::<Claims>(&token, "secret".as_ref(), Algorithm::HS256).unwrap();
|
||||
assert_eq!(my_claims, token_data.claims);
|
||||
assert_eq!("kid", token_data.header.kid.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip_claim() {
|
||||
let my_claims = Claims {
|
||||
sub: "b@b.com".to_string(),
|
||||
company: "ACME".to_string()
|
||||
};
|
||||
let token = encode(Header::default(), &my_claims, "secret".as_ref()).unwrap();
|
||||
let token_data = decode::<Claims>(&token, "secret".as_ref(), Algorithm::HS256).unwrap();
|
||||
assert_eq!(my_claims, token_data.claims);
|
||||
assert!(token_data.header.kid.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_token() {
|
||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.I1BvFoHe94AFf09O6tDbcSB8-jp8w6xZqmyHIwPeSdY";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidToken")]
|
||||
fn decode_token_missing_parts() {
|
||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidSignature")]
|
||||
fn decode_token_invalid_signature() {
|
||||
let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.wrong";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "WrongAlgorithmHeader")]
|
||||
fn decode_token_wrong_algorithm() {
|
||||
let token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiQGIuY29tIiwiY29tcGFueSI6IkFDTUUifQ.pKscJVk7-aHxfmQKlaZxh5uhuKhGMAa-1F5IX5mfUwI";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
||||
claims.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_token_with_bytes_secret() {
|
||||
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiY29tcGFueSI6Ikdvb2dvbCJ9.27QxgG96vpX4akKNpD1YdRGHE3_u2X35wR3EHA2eCrs";
|
||||
let claims = decode::<Claims>(token, b"\x01\x02\x03", Algorithm::HS256);
|
||||
assert!(claims.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_token_with_shuffled_header_fields() {
|
||||
let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjb21wYW55IjoiMTIzNDU2Nzg5MCIsInN1YiI6IkpvaG4gRG9lIn0.SEIZ4Jg46VGhquuwPYDLY5qHF8AkQczF14aXM3a2c28";
|
||||
let claims = decode::<Claims>(token, "secret".as_ref(), Algorithm::HS256);
|
||||
assert!(claims.is_ok());
|
||||
}
|
Loading…
Reference in New Issue