Initial commit

This commit is contained in:
Vincent Prouillet 2015-10-31 15:37:15 +00:00
commit 7585a7f0f9
8 changed files with 264 additions and 0 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
[*.md]
trim_trailing_whitespace = false

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
Cargo.lock

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "jwt"
version = "0.1.0"
authors = ["Vincent Prouillet <vincent@wearewizards.io>"]
license = "MIT"
readme = "README.md"
description = "Create and parse JWT."
keywords = ["jwt", "web", "api", "token"]
[dependencies]
rustc-serialize = "0.3"
clippy = {version = "0.0.22", optional = true}
[features]
default = []
dev = ["clippy"]

6
README.md Normal file
View File

@ -0,0 +1,6 @@
# JWT
```
JWT::encode(payload, secret, algo) -> Result<String>
JWT::decode(token, secret, algo) -> Result<Claims>
```

101
src/claims.rs Normal file
View File

@ -0,0 +1,101 @@
use std::collections::BTreeMap;
use rustc_serialize::json::{self, Json, ToJson};
use rustc_serialize::base64::{self, ToBase64, FromBase64};
use errors::Error;
macro_rules! add_registered_claims {
($map: expr, $key: expr, $reg: expr) => {
if let Some(val) = $reg {
$map.insert($key, val.to_json());
}
}
}
#[derive(Debug, Default, RustcEncodable, RustcDecodable)]
struct RegisteredClaims {
iss: Option<String>,
sub: Option<String>,
aud: Option<String>,
exp: Option<u64>,
nbf: Option<u64>,
iat: Option<u64>,
jti: Option<String>,
}
#[derive(Debug)]
pub struct Claims {
registered: RegisteredClaims,
private: BTreeMap<String, Json>,
}
impl Claims {
pub fn new() -> Claims {
Claims {
registered: Default::default(),
private: BTreeMap::new(),
}
}
fn to_base64(self) -> Result<String, Error> {
let mut map: BTreeMap<String, Json> = BTreeMap::new();
// just encoding the struct would give null values in the resulting json
// like {"iss": null}, which we don't want
add_registered_claims!(map, "iss".to_owned(), self.registered.iss);
add_registered_claims!(map, "sub".to_owned(), self.registered.sub);
add_registered_claims!(map, "aud".to_owned(), self.registered.aud);
add_registered_claims!(map, "exp".to_owned(), self.registered.exp);
add_registered_claims!(map, "nbf".to_owned(), self.registered.nbf);
add_registered_claims!(map, "iat".to_owned(), self.registered.iat);
add_registered_claims!(map, "jti".to_owned(), self.registered.jti);
map.extend(self.private);
let encoded = try!(json::encode(&map));
Ok(encoded.as_bytes().to_base64(base64::STANDARD))
}
fn from_base64(encoded String) -> Result<Claims, Error> {
}
pub fn add<T: ToJson>(&mut self, key: String, value: T) {
self.private.insert(key, value.to_json());
}
}
#[cfg(test)]
mod tests {
use claims::Claims;
use rustc_serialize::json::{ToJson};
#[test]
fn to_base64_no_null_values() {
let mut claims = Claims::new();
claims.registered.iss = Some("JWT".to_owned());
let result = claims.to_base64().unwrap();
let expected = "eyJpc3MiOiJKV1QifQ==";
assert_eq!(result, expected);
}
#[test]
fn to_base64_custom_claims() {
let mut claims = Claims::new();
claims.add::<String>("group".to_owned(), "zombie".to_owned());
let result = claims.to_base64().unwrap();
let expected = "eyJncm91cCI6InpvbWJpZSJ9";
assert_eq!(result, expected);
}
#[test]
fn to_base64_registered_and_customs() {
let mut claims = Claims::new();
claims.registered.iss = Some("JWT".to_owned());
claims.add::<String>("group".to_owned(), "zombie".to_owned());
let result = claims.to_base64().unwrap();
let expected = "eyJncm91cCI6InpvbWJpZSIsImlzcyI6IkpXVCJ9";
assert_eq!(result, expected);
}
}

23
src/errors.rs Normal file
View File

@ -0,0 +1,23 @@
use std::string;
use rustc_serialize::{json, base64};
#[derive(Debug)]
pub enum Error {
EncodeJSON(json::EncoderError),
DecodeBase64(base64::FromBase64Error),
DecodeJSON(json::DecoderError),
Utf8(string::FromUtf8Error),
}
macro_rules! impl_from_error {
($f: ty, $e: expr) => {
impl From<$f> for Error {
fn from(f: $f) -> Error { $e(f) }
}
}
}
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);

58
src/header.rs Normal file
View File

@ -0,0 +1,58 @@
use rustc_serialize::json;
use rustc_serialize::base64::{self, ToBase64, FromBase64};
use errors::Error;
#[derive(Debug, PartialEq, RustcEncodable, RustcDecodable)]
pub struct Header {
typ: Option<String>,
alg: String,
}
impl Header {
pub fn new(algorithm: String) -> Header {
Header {
typ: Some("JWT".to_owned()),
alg: algorithm,
}
}
pub fn to_base64(&self) -> Result<String, Error> {
let encoded = try!(json::encode(&self));
Ok(encoded.as_bytes().to_base64(base64::STANDARD))
}
pub fn from_base64(encoded: String) -> Result<Header, Error> {
let decoded = try!(encoded.as_bytes().from_base64());
let s = try!(String::from_utf8(decoded));
Ok(try!(json::decode(&s)))
}
}
#[cfg(test)]
mod tests {
use header::Header;
#[test]
fn to_base64() {
let expected = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9".to_owned();
let result = Header::new("HS256".to_owned()).to_base64();
assert_eq!(expected, result.unwrap());
}
#[test]
fn from_base64() {
let encoded = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9".to_owned();
let header = Header::from_base64(encoded).unwrap();
assert_eq!(header.typ.unwrap(), "JWT");
assert_eq!(header.alg, "HS256");
}
#[test]
fn round_trip() {
let header = Header::new("HS256".to_owned());
assert_eq!(Header::from_base64(header.to_base64().unwrap()).unwrap(), header);
}
}

46
src/lib.rs Normal file
View File

@ -0,0 +1,46 @@
//! Create and parses JWT (JSON Web Tokens)
//!
// #![deny(
// missing_docs,
// missing_debug_implementations, missing_copy_implementations,
// trivial_casts, trivial_numeric_casts,
// unsafe_code,
// unstable_features,
// unused_import_braces, unused_qualifications
// )]
#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]
extern crate rustc_serialize;
pub mod errors;
pub mod header;
pub mod claims;
#[derive(Debug)]
pub enum Algorithm {
HS256
}
impl ToString for Algorithm {
fn to_string(&self) -> String {
match *self {
Algorithm::HS256 => "HS256".to_owned(),
}
}
}
// pub fn encode(secret: String, algorithm: Algorithm) -> String {
// }
// pub fn decode(token: String, secret: String, algorithm: Algorithm) -> Result<int> {
// }
#[cfg(test)]
mod tests {
}