Migrated to anyhow and chrono from failure and time crates.
This commit is contained in:
parent
30ff924e9a
commit
4776132e51
8 changed files with 76 additions and 90 deletions
14
Cargo.toml
14
Cargo.toml
|
@ -9,17 +9,17 @@ license = "MIT"
|
||||||
name = "medallion"
|
name = "medallion"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "http://github.com/cmdln/medallion"
|
repository = "http://github.com/cmdln/medallion"
|
||||||
version = "2.3.1"
|
version = "2.4.0"
|
||||||
|
edition = "2018"
|
||||||
[badges]
|
[badges]
|
||||||
[badges.travis-ci]
|
[badges.travis-ci]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
repository = "https://travis-ci.org/cmdln/medallion"
|
repository = "https://travis-ci.org/cmdln/medallion"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "~0.10.0"
|
base64 = "~0.12.2"
|
||||||
failure = "~0.1.3"
|
|
||||||
openssl = "~0.10.15"
|
openssl = "~0.10.15"
|
||||||
serde = "^1.0.80"
|
serde = { version = "^1.0.114", features = [ "derive" ] }
|
||||||
serde_derive = "^1.0.80"
|
serde_json = "^1.0.55"
|
||||||
serde_json = "^1.0.33"
|
anyhow = "^1.0.31"
|
||||||
time = "~0.1.40"
|
chrono = "~0.4.11"
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
// need this for custom derivation
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate medallion;
|
|
||||||
|
|
||||||
use medallion::{Header, Payload, Token};
|
use medallion::{Header, Payload, Token};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
|
#[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
|
||||||
struct Custom {
|
struct Custom {
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
// need this for custom derivation
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate medallion;
|
|
||||||
|
|
||||||
use medallion::{DefaultPayload, DefaultToken, Header};
|
use medallion::{DefaultPayload, DefaultToken, Header};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
|
#[derive(Default, Serialize, Deserialize, PartialEq, Debug)]
|
||||||
struct Custom {
|
struct Custom {
|
||||||
|
|
18
src/crypt.rs
18
src/crypt.rs
|
@ -1,5 +1,6 @@
|
||||||
|
use super::Result;
|
||||||
|
use crate::header::Algorithm;
|
||||||
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
||||||
use header::Algorithm;
|
|
||||||
use openssl::{
|
use openssl::{
|
||||||
hash::MessageDigest,
|
hash::MessageDigest,
|
||||||
memcmp,
|
memcmp,
|
||||||
|
@ -8,8 +9,6 @@ use openssl::{
|
||||||
sign::{Signer, Verifier},
|
sign::{Signer, Verifier},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Result;
|
|
||||||
|
|
||||||
pub fn sign(data: &str, key: &[u8], algorithm: &Algorithm) -> Result<String> {
|
pub fn sign(data: &str, key: &[u8], algorithm: &Algorithm) -> Result<String> {
|
||||||
match *algorithm {
|
match *algorithm {
|
||||||
Algorithm::HS256 => sign_hmac(data, key, MessageDigest::sha256()),
|
Algorithm::HS256 => sign_hmac(data, key, MessageDigest::sha256()),
|
||||||
|
@ -76,8 +75,7 @@ fn verify_rsa(signature: &str, data: &str, key: &[u8], digest: MessageDigest) ->
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::{sign, verify};
|
use super::{sign, verify};
|
||||||
use header::Algorithm;
|
use crate::header::Algorithm;
|
||||||
use openssl;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn sign_data_hmac() {
|
pub fn sign_data_hmac() {
|
||||||
|
@ -104,16 +102,16 @@ pub mod tests {
|
||||||
&*data,
|
&*data,
|
||||||
&keypair.private_key_to_pem().unwrap(),
|
&keypair.private_key_to_pem().unwrap(),
|
||||||
&Algorithm::RS256,
|
&Algorithm::RS256,
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert!(verify(
|
||||||
verify(
|
|
||||||
&sig,
|
&sig,
|
||||||
&*data,
|
&*data,
|
||||||
&keypair.public_key_to_pem().unwrap(),
|
&keypair.public_key_to_pem().unwrap(),
|
||||||
&Algorithm::RS256
|
&Algorithm::RS256
|
||||||
).unwrap()
|
)
|
||||||
);
|
.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
use failure::Error;
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
|
@ -1,8 +1,8 @@
|
||||||
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
|
||||||
use serde_json::{self, Value};
|
|
||||||
|
|
||||||
use super::Result;
|
use super::Result;
|
||||||
|
use anyhow::format_err;
|
||||||
|
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
use serde_json::{self, Value};
|
||||||
|
|
||||||
/// An extensible Header that provides only algorithm field and allows for additional fields to be
|
/// An extensible Header that provides only algorithm field and allows for additional fields to be
|
||||||
/// passed in via a struct that can be serialized and deserialized. Unlike the Claims struct, there
|
/// passed in via a struct that can be serialized and deserialized. Unlike the Claims struct, there
|
||||||
|
@ -79,6 +79,7 @@ impl<T> Default for Header<T> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Algorithm, Header};
|
use super::{Algorithm, Header};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||||
struct CustomHeaders {
|
struct CustomHeaders {
|
||||||
|
|
60
src/lib.rs
60
src/lib.rs
|
@ -6,26 +6,15 @@
|
||||||
///!
|
///!
|
||||||
///! Tries to support the standard uses for JWTs while providing reasonable ways to extend,
|
///! Tries to support the standard uses for JWTs while providing reasonable ways to extend,
|
||||||
///! primarily by adding custom headers and claims to tokens.
|
///! primarily by adding custom headers and claims to tokens.
|
||||||
extern crate base64;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate failure;
|
|
||||||
extern crate openssl;
|
|
||||||
extern crate serde;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate serde_json;
|
|
||||||
extern crate time;
|
|
||||||
|
|
||||||
pub use header::{Algorithm, Header};
|
pub use header::{Algorithm, Header};
|
||||||
pub use payload::{DefaultPayload, Payload};
|
pub use payload::{DefaultPayload, Payload};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
mod crypt;
|
mod crypt;
|
||||||
mod error;
|
|
||||||
mod header;
|
mod header;
|
||||||
mod payload;
|
mod payload;
|
||||||
|
|
||||||
pub use error::Result;
|
pub use anyhow::Result;
|
||||||
|
|
||||||
/// A convenient type that binds the same type parameter for the custom claims, an empty tuple, as
|
/// A convenient type that binds the same type parameter for the custom claims, an empty tuple, as
|
||||||
/// `DefaultPayload` so that the two aliases may be used together to reduce boilerplate when no
|
/// `DefaultPayload` so that the two aliases may be used together to reduce boilerplate when no
|
||||||
|
@ -104,10 +93,10 @@ where
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Algorithm::{HS256, RS512};
|
use super::Algorithm::{HS256, RS512};
|
||||||
use openssl;
|
use crate::{DefaultPayload, DefaultToken, Header, Payload, Token};
|
||||||
use std::default::Default;
|
use anyhow::Result;
|
||||||
use time::{self, Duration, Tm};
|
use chrono::{prelude::*, Duration};
|
||||||
use {DefaultPayload, DefaultToken, Header, Payload, Token};
|
use std::convert::TryInto;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn raw_data() {
|
pub fn raw_data() {
|
||||||
|
@ -122,11 +111,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn roundtrip_hmac() {
|
pub fn roundtrip_hmac() {
|
||||||
let now = time::now();
|
let now = Utc::now();
|
||||||
let header: Header<()> = Header::default();
|
let header: Header<()> = Header::default();
|
||||||
let payload = DefaultPayload {
|
let payload = DefaultPayload {
|
||||||
nbf: Some(now.to_timespec().sec as u64),
|
nbf: Some(now.timestamp().try_into().unwrap()),
|
||||||
exp: Some((now + Duration::minutes(5)).to_timespec().sec as u64),
|
exp: Some((now + Duration::minutes(5)).timestamp().try_into().unwrap()),
|
||||||
..DefaultPayload::default()
|
..DefaultPayload::default()
|
||||||
};
|
};
|
||||||
let token = Token::new(header, payload);
|
let token = Token::new(header, payload);
|
||||||
|
@ -139,27 +128,29 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn roundtrip_expired() {
|
pub fn roundtrip_expired() -> Result<()> {
|
||||||
let now = time::now();
|
let now = Utc::now();
|
||||||
let token = create_for_range(now, now + Duration::minutes(-5));
|
let token = create_for_range(now, now + Duration::minutes(-5))?;
|
||||||
let key = b"secret";
|
let key = b"secret";
|
||||||
let raw = token.sign(key).unwrap();
|
let raw = token.sign(key)?;
|
||||||
let same = Token::parse(&*raw).unwrap();
|
let same = Token::parse(&*raw).unwrap();
|
||||||
|
|
||||||
assert_eq!(token, same);
|
assert_eq!(token, same);
|
||||||
assert_eq!(false, same.verify(key).unwrap());
|
assert_eq!(false, same.verify(key).unwrap());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn roundtrip_not_yet_valid() {
|
pub fn roundtrip_not_yet_valid() -> Result<()> {
|
||||||
let now = time::now();
|
let now = Utc::now();
|
||||||
let token = create_for_range(now + Duration::minutes(5), now + Duration::minutes(10));
|
let token = create_for_range(now + Duration::minutes(5), now + Duration::minutes(10))?;
|
||||||
let key = b"secret";
|
let key = b"secret";
|
||||||
let raw = token.sign(key).unwrap();
|
let raw = token.sign(key)?;
|
||||||
let same = Token::parse(&*raw).unwrap();
|
let same = Token::parse(&*raw).unwrap();
|
||||||
|
|
||||||
assert_eq!(token, same);
|
assert_eq!(token, same);
|
||||||
assert_eq!(false, same.verify(key).unwrap());
|
assert_eq!(false, same.verify(key).unwrap());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -179,19 +170,18 @@ mod tests {
|
||||||
let same = Token::parse(&*raw).unwrap();
|
let same = Token::parse(&*raw).unwrap();
|
||||||
|
|
||||||
assert_eq!(token, same);
|
assert_eq!(token, same);
|
||||||
assert!(
|
assert!(same
|
||||||
same.verify(&rsa_keypair.public_key_to_pem().unwrap())
|
.verify(&rsa_keypair.public_key_to_pem().unwrap())
|
||||||
.unwrap()
|
.unwrap());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_for_range(nbf: Tm, exp: Tm) -> Token {
|
fn create_for_range(nbf: DateTime<Utc>, exp: DateTime<Utc>) -> Result<Token> {
|
||||||
let header: Header = Header::default();
|
let header: Header = Header::default();
|
||||||
let payload = Payload {
|
let payload = Payload {
|
||||||
nbf: Some(nbf.to_timespec().sec as u64),
|
nbf: Some(nbf.timestamp().try_into()?),
|
||||||
exp: Some(exp.to_timespec().sec as u64),
|
exp: Some(exp.timestamp().try_into()?),
|
||||||
..Payload::default()
|
..Payload::default()
|
||||||
};
|
};
|
||||||
Token::new(header, payload)
|
Ok(Token::new(header, payload))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use super::Result;
|
use super::Result;
|
||||||
|
use anyhow::format_err;
|
||||||
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
||||||
use serde::de::DeserializeOwned;
|
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||||
use serde::Serialize;
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use serde_json;
|
|
||||||
use serde_json::value::Value;
|
use serde_json::value::Value;
|
||||||
use time::{self, Timespec};
|
|
||||||
|
|
||||||
/// A default claim set, including the standard, or registered, claims and the ability to specify
|
/// A default claim set, including the standard, or registered, claims and the ability to specify
|
||||||
/// your own as custom claims.
|
/// your own as custom claims.
|
||||||
|
@ -73,7 +72,7 @@ impl<T: Serialize + DeserializeOwned> Payload<T> {
|
||||||
None => {
|
None => {
|
||||||
let s = serde_json::to_string(&claims_map)?;
|
let s = serde_json::to_string(&claims_map)?;
|
||||||
let enc = encode_config((&*s).as_bytes(), URL_SAFE_NO_PAD);
|
let enc = encode_config((&*s).as_bytes(), URL_SAFE_NO_PAD);
|
||||||
return Ok(enc);
|
Ok(enc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -82,13 +81,25 @@ impl<T: Serialize + DeserializeOwned> Payload<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify(&self) -> bool {
|
pub fn verify(&self) -> bool {
|
||||||
let now = time::now().to_timespec();
|
let now = Utc::now();
|
||||||
let nbf_verified = match self.nbf {
|
let nbf_verified = match self.nbf {
|
||||||
Some(nbf_sec) => Timespec::new(nbf_sec as i64, 0) < now,
|
Some(nbf_sec) => {
|
||||||
|
let nbf = DateTime::<Utc>::from_utc(
|
||||||
|
NaiveDateTime::from_timestamp(nbf_sec as i64, 0),
|
||||||
|
Utc,
|
||||||
|
);
|
||||||
|
nbf < now
|
||||||
|
}
|
||||||
None => true,
|
None => true,
|
||||||
};
|
};
|
||||||
let exp_verified = match self.exp {
|
let exp_verified = match self.exp {
|
||||||
Some(exp_sec) => now < Timespec::new(exp_sec as i64, 0),
|
Some(exp_sec) => {
|
||||||
|
let exp = DateTime::<Utc>::from_utc(
|
||||||
|
NaiveDateTime::from_timestamp(exp_sec as i64, 0),
|
||||||
|
Utc,
|
||||||
|
);
|
||||||
|
now < exp
|
||||||
|
}
|
||||||
None => true,
|
None => true,
|
||||||
};
|
};
|
||||||
nbf_verified && exp_verified
|
nbf_verified && exp_verified
|
||||||
|
@ -98,8 +109,9 @@ impl<T: Serialize + DeserializeOwned> Payload<T> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{DefaultPayload, Payload};
|
use super::{DefaultPayload, Payload};
|
||||||
|
use chrono::{prelude::*, Duration};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use time::{self, Duration};
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
struct CustomClaims {
|
struct CustomClaims {
|
||||||
|
@ -199,7 +211,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_with_nbf(offset: i64) -> DefaultPayload {
|
fn create_with_nbf(offset: i64) -> DefaultPayload {
|
||||||
let nbf = (time::now() - Duration::minutes(offset)).to_timespec().sec;
|
let nbf = (Utc::now() - Duration::minutes(offset)).timestamp();
|
||||||
DefaultPayload {
|
DefaultPayload {
|
||||||
nbf: Some(nbf as u64),
|
nbf: Some(nbf as u64),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -207,7 +219,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_with_exp(offset: i64) -> DefaultPayload {
|
fn create_with_exp(offset: i64) -> DefaultPayload {
|
||||||
let exp = (time::now() + Duration::minutes(offset)).to_timespec().sec;
|
let exp = (Utc::now() + Duration::minutes(offset)).timestamp();
|
||||||
DefaultPayload {
|
DefaultPayload {
|
||||||
exp: Some(exp as u64),
|
exp: Some(exp as u64),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -215,12 +227,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_with_nbf_exp(nbf_offset: i64, exp_offset: i64) -> DefaultPayload {
|
fn create_with_nbf_exp(nbf_offset: i64, exp_offset: i64) -> DefaultPayload {
|
||||||
let nbf = (time::now() - Duration::minutes(nbf_offset))
|
let nbf = (Utc::now() - Duration::minutes(nbf_offset)).timestamp();
|
||||||
.to_timespec()
|
let exp = (Utc::now() + Duration::minutes(exp_offset)).timestamp();
|
||||||
.sec;
|
|
||||||
let exp = (time::now() + Duration::minutes(exp_offset))
|
|
||||||
.to_timespec()
|
|
||||||
.sec;
|
|
||||||
DefaultPayload {
|
DefaultPayload {
|
||||||
nbf: Some(nbf as u64),
|
nbf: Some(nbf as u64),
|
||||||
exp: Some(exp as u64),
|
exp: Some(exp as u64),
|
||||||
|
|
Loading…
Reference in a new issue