diff --git a/CHANGELOG.md b/CHANGELOG.md index ee93051..bb23634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 1.0.0 +> 2017-07-03 + +The 1.0 release! + +This is the initial non-beta, non-alpha release of jwt-cli! + +#### New features +- Everything is parsed by serde now. You can pass strings, numbers, arrays, objects, whatever. If serde can parse it, it's valid! + +#### Things left to do +- Add jwt-cli to package managers! + # 0.9.1 > 2017-07-03 @@ -7,7 +20,7 @@ The forkless release! - Swaps out my fork of `jsonwebtoken` for the master branch of keats' `jsonwebtoken` #### Roadmap to 1.0 -- Allow for json payload items via `-P this=json(['arbitrary', 'data']) +- Allow for json payload items via `-P this=json(['arbitrary', 'data'])` # 0.9.0 > 2017-07-03 @@ -26,7 +39,7 @@ The `iat` and `exp` release! - Moves to my instance of `jsonwebtoken` until some PRs are merged #### Roadmap to 1.0 -- Allow for json payload items via `-P this=json(['arbitrary', 'data']) +- Allow for json payload items via `-P this=json(['arbitrary', 'data'])` # 0.8.1 > 2017-07-02 @@ -46,7 +59,7 @@ Dependency updates #### Roadmap to 1.0 - Automatically set `iat` and `exp` - Default `exp` to 30 minutes from now -- Allow for json payload items via `-P this=json(['arbitrary', 'data']) +- Allow for json payload items via `-P this=json(['arbitrary', 'data'])` # 0.7.0 > 2017-03-13 diff --git a/Cargo.lock b/Cargo.lock index 2cb003a..30ebb9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [root] name = "jwt-cli" -version = "0.9.0" +version = "0.9.1" dependencies = [ "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.25.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/README.md b/README.md index 2abea5e..d0eff29 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,10 @@ cargo build cargo build --release ``` -If it built successfully, you should be able to run the command from the `target` folder. +If it built successfully, you should be able to run the command via `cargo`. ```sh -# on macOS/linux -./target/debug/jwt help - -# on windows -target\debug\jwt.exe help +cargo run -- help ``` # [Code of conduct](code_of_conduct.md) diff --git a/src/main.rs b/src/main.rs index 4937b8a..e1fdd62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use chrono::{Duration, Utc}; use clap::{App, Arg, ArgMatches, SubCommand}; use jwt::{Algorithm, decode, encode, Header, TokenData, Validation}; use jwt::errors::{Error, ErrorKind, Result as JWTResult}; -use serde_json::{to_string_pretty, to_value, Value}; +use serde_json::{from_str, to_string_pretty, Value}; use std::collections::BTreeMap; use term_painter::ToStyle; use term_painter::Color::*; @@ -55,26 +55,14 @@ impl PayloadItem { fn from_string_with_name(val: Option<&str>, name: &str) -> Option { match val { Some(value) => { - match to_value(value) { + match from_str(value) { Ok(json_value) => Some(PayloadItem(name.to_string(), json_value)), - _ => None, - } - } - _ => None, - } - } - - fn from_int_with_name(val: Option<&str>, name: &str) -> Option { - match val { - Some(value) => { - match i64::from_str_radix(&value, 10) { - Ok(int_value) => { - match to_value(int_value) { + Err(_) => { + match from_str(format!("\"{}\"", value).as_str()) { Ok(json_value) => Some(PayloadItem(name.to_string(), json_value)), - _ => None, + Err(_) => None, } } - _ => None, } } _ => None, @@ -84,8 +72,9 @@ impl PayloadItem { fn split_payload_item(p: &str) -> PayloadItem { let split: Vec<&str> = p.split('=').collect(); let (name, value) = (split[0], split[1]); + let payload_item = PayloadItem::from_string_with_name(Some(value), name); - PayloadItem(name.to_string(), to_value(value).unwrap_or(Value::Null)) + payload_item.unwrap() } } @@ -253,7 +242,7 @@ fn is_num(val: String) -> Result<(), String> { match parse_result { Ok(_) => Ok(()), - Err(_) => Err(String::from("expires must be an integer")), + Err(_) => Err(String::from("exp and nbf must be integers")), } } @@ -312,12 +301,12 @@ fn encode_token(matches: &ArgMatches) -> JWTResult { .map(|p| PayloadItem::from_string(Some(p))) .collect() }); - let expires = PayloadItem::from_int_with_name(matches.value_of("expires"), "exp"); + let expires = PayloadItem::from_string_with_name(matches.value_of("expires"), "exp"); let issuer = PayloadItem::from_string_with_name(matches.value_of("issuer"), "iss"); let subject = PayloadItem::from_string_with_name(matches.value_of("subject"), "sub"); let audience = PayloadItem::from_string_with_name(matches.value_of("audience"), "aud"); let principal = PayloadItem::from_string_with_name(matches.value_of("principal"), "prn"); - let not_before = PayloadItem::from_int_with_name(matches.value_of("not_before"), "nbf"); + let not_before = PayloadItem::from_string_with_name(matches.value_of("not_before"), "nbf"); let mut maybe_payloads: Vec> = vec![expires, issuer, subject, audience, principal, not_before]; maybe_payloads.append(&mut custom_payloads.unwrap_or(Vec::new())); diff --git a/tests/jwt-cli.rs b/tests/jwt-cli.rs index 5c6d0b8..31b7def 100644 --- a/tests/jwt-cli.rs +++ b/tests/jwt-cli.rs @@ -172,7 +172,9 @@ mod tests { #[test] fn encodes_a_token() { - let matches = config_options() + let exp = (Utc::now() + Duration::minutes(60)).timestamp(); + let nbf = Utc::now().timestamp(); + let encode_matcher = config_options() .get_matches_from_safe(vec![ "jwt", "encode", @@ -183,25 +185,51 @@ mod tests { "-a", "yolo", "-e", - "0987654321", + &exp.to_string(), "-i", "yolo-service", "-k", "1234", "-n", - "001293", + &nbf.to_string(), "-P", "this=that", + "-P", + "number=10", + "-P", + "array=[1, 2, 3]", + "-P", + "object={\"foo\": \"bar\"}", "-p", "yolo-principal", "-s", "yolo-subject", ]) .unwrap(); - let encode_matches = matches.subcommand_matches("encode").unwrap(); - let result = encode_token(&encode_matches); + let encode_matches = encode_matcher.subcommand_matches("encode").unwrap(); + let encoded_token = encode_token(&encode_matches).unwrap(); + let decode_matcher = config_options() + .get_matches_from_safe(vec!["jwt", "decode", "-S", "1234567890", &encoded_token]) + .unwrap(); + let decode_matches = decode_matcher.subcommand_matches("decode").unwrap(); + let decoded_token = decode_token(&decode_matches); - assert!(result.is_ok()); + assert!(decoded_token.is_ok()); + + let TokenData { claims, header } = decoded_token.unwrap(); + + assert_eq!(header.alg, Algorithm::HS256); + assert_eq!(header.kid, Some("1234".to_string())); + assert_eq!(claims.0["aud"], "yolo"); + assert_eq!(claims.0["iss"], "yolo-service"); + assert_eq!(claims.0["prn"], "yolo-principal"); + assert_eq!(claims.0["sub"], "yolo-subject"); + assert_eq!(claims.0["nbf"], nbf); + assert_eq!(claims.0["exp"], exp); + assert_eq!(claims.0["this"], "that"); + assert_eq!(claims.0["number"], 10); + assert_eq!(claims.0["array"].to_string(), "[1,2,3]"); + assert_eq!(claims.0["object"]["foo"], "bar"); } #[test]