Skip to content

Commit d153976

Browse files
committed
The iat and exp release!
Breaking changes - `iat` is now automatically added to the claims object - `exp` is now automatically added to the claims object if not passed in directly - `exp` defaults to 30 minutes from the time of creation Bug fixes - `exp` and `nbf` are now parsed as numbers, not string Temporary changes - 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'])`
1 parent 1a4b1a9 commit d153976

File tree

6 files changed

+164
-67
lines changed

6 files changed

+164
-67
lines changed

CHANGELOG.md

+19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
# 0.9.0
2+
> 2017-07-03
3+
4+
The `iat` and `exp` release!
5+
6+
#### Breaking changes
7+
- `iat` is now automatically added to the claims object
8+
- `exp` is now automatically added to the claims object if not passed in directly
9+
- `exp` defaults to 30 minutes from the time of creation
10+
11+
#### Bug fixes
12+
- `exp` and `nbf` are now parsed as numbers, not string
13+
14+
#### Temporary changes
15+
- Moves to my instance of `jsonwebtoken` until some PRs are merged
16+
17+
#### Roadmap to 1.0
18+
- Allow for json payload items via `-P this=json(['arbitrary', 'data'])
19+
120
# 0.8.1
221
> 2017-07-02
322

Cargo.lock

+3-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "jwt-cli"
3-
version = "0.8.1"
3+
version = "0.9.0"
44
authors = ["Mike Engel <[email protected]>"]
55

66
[[bin]]
@@ -15,3 +15,4 @@ term-painter = "0.2"
1515
serde = "1"
1616
serde_derive = "1"
1717
serde_json = "1"
18+
chrono = "0.4"

rustfmt.toml

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ control_style = "Rfc"
44
where_style = "Rfc"
55
generics_indent = "Block"
66
fn_call_style = "Block"
7+
max_width = 200

src/main.rs

+62-42
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
1+
extern crate chrono;
12
#[macro_use]
23
extern crate clap;
34
extern crate jsonwebtoken as jwt;
4-
extern crate term_painter;
55
extern crate serde;
66
#[macro_use]
77
extern crate serde_derive;
8+
#[macro_use]
89
extern crate serde_json;
10+
extern crate term_painter;
911

10-
use std::collections::BTreeMap;
12+
use chrono::{Duration, Utc};
1113
use clap::{App, Arg, ArgMatches, SubCommand};
1214
use jwt::{Algorithm, decode, encode, Header, TokenData, Validation};
13-
use jwt::errors::{self, Error, ErrorKind};
14-
use serde::de::DeserializeOwned;
15-
use serde_json::to_string_pretty;
15+
use jwt::errors::{Error, ErrorKind, Result as JWTResult};
16+
use serde_json::{to_string_pretty, to_value, Value};
17+
use std::collections::BTreeMap;
1618
use term_painter::ToStyle;
1719
use term_painter::Color::*;
1820
use term_painter::Attr::*;
1921

2022
#[derive(Debug, Serialize, Deserialize, PartialEq)]
21-
struct PayloadItem(String, String);
23+
struct PayloadItem(String, Value);
2224

2325
#[derive(Debug, Serialize, Deserialize, PartialEq)]
24-
struct Payload(BTreeMap<String, String>);
26+
struct Payload(BTreeMap<String, Value>);
2527

2628
arg_enum!{
2729
#[derive(Debug, PartialEq)]
@@ -51,28 +53,58 @@ impl PayloadItem {
5153
}
5254

5355
fn from_string_with_name(val: Option<&str>, name: &str) -> Option<PayloadItem> {
54-
if val.is_some() {
55-
Some(PayloadItem(name.to_string(), val.unwrap().to_string()))
56-
} else {
57-
None
56+
match val {
57+
Some(value) => {
58+
match to_value(value) {
59+
Ok(json_value) => Some(PayloadItem(name.to_string(), json_value)),
60+
_ => None,
61+
}
62+
}
63+
_ => None,
64+
}
65+
}
66+
67+
fn from_int_with_name(val: Option<&str>, name: &str) -> Option<PayloadItem> {
68+
match val {
69+
Some(value) => {
70+
match i64::from_str_radix(&value, 10) {
71+
Ok(int_value) => {
72+
match to_value(int_value) {
73+
Ok(json_value) => Some(PayloadItem(name.to_string(), json_value)),
74+
_ => None,
75+
}
76+
}
77+
_ => None,
78+
}
79+
}
80+
_ => None,
5881
}
5982
}
6083

6184
fn split_payload_item(p: &str) -> PayloadItem {
6285
let split: Vec<&str> = p.split('=').collect();
86+
let (name, value) = (split[0], split[1]);
6387

64-
PayloadItem(split[0].to_string(), split[1].to_string())
88+
PayloadItem(name.to_string(), to_value(value).unwrap_or(Value::Null))
6589
}
6690
}
6791

6892
impl Payload {
6993
fn from_payloads(payloads: Vec<PayloadItem>) -> Payload {
7094
let mut payload = BTreeMap::new();
95+
let iat = json!(Utc::now().timestamp());
96+
let exp = json!((Utc::now() + Duration::minutes(30)).timestamp());
7197

7298
for PayloadItem(k, v) in payloads {
7399
payload.insert(k, v);
74100
}
75101

102+
payload.insert("iat".to_string(), iat);
103+
104+
if !payload.contains_key("exp") {
105+
payload.insert("exp".to_string(), exp);
106+
}
107+
76108
Payload(payload)
77109
}
78110
}
@@ -269,26 +301,24 @@ fn create_validations(alg: Algorithm) -> Validation {
269301
}
270302
}
271303

272-
fn encode_token(matches: &ArgMatches) -> errors::Result<String> {
304+
fn encode_token(matches: &ArgMatches) -> JWTResult<String> {
273305
let algorithm = translate_algorithm(SupportedAlgorithms::from_string(
274306
matches.value_of("algorithm").unwrap(),
275307
));
276308
let kid = matches.value_of("kid");
277309
let header = create_header(&algorithm, kid);
278-
let custom_payloads: Option<Vec<Option<PayloadItem>>> =
279-
matches.values_of("payload").map(|maybe_payloads| {
280-
maybe_payloads
281-
.map(|p| PayloadItem::from_string(Some(p)))
282-
.collect()
283-
});
284-
let expires = PayloadItem::from_string_with_name(matches.value_of("expires"), "exp");
310+
let custom_payloads: Option<Vec<Option<PayloadItem>>> = matches.values_of("payload").map(|maybe_payloads| {
311+
maybe_payloads
312+
.map(|p| PayloadItem::from_string(Some(p)))
313+
.collect()
314+
});
315+
let expires = PayloadItem::from_int_with_name(matches.value_of("expires"), "exp");
285316
let issuer = PayloadItem::from_string_with_name(matches.value_of("issuer"), "iss");
286317
let subject = PayloadItem::from_string_with_name(matches.value_of("subject"), "sub");
287318
let audience = PayloadItem::from_string_with_name(matches.value_of("audience"), "aud");
288319
let principal = PayloadItem::from_string_with_name(matches.value_of("principal"), "prn");
289-
let not_before = PayloadItem::from_string_with_name(matches.value_of("not_before"), "nbf");
290-
let mut maybe_payloads: Vec<Option<PayloadItem>> =
291-
vec![expires, issuer, subject, audience, principal, not_before];
320+
let not_before = PayloadItem::from_int_with_name(matches.value_of("not_before"), "nbf");
321+
let mut maybe_payloads: Vec<Option<PayloadItem>> = vec![expires, issuer, subject, audience, principal, not_before];
292322

293323
maybe_payloads.append(&mut custom_payloads.unwrap_or(Vec::new()));
294324

@@ -303,18 +333,18 @@ fn encode_token(matches: &ArgMatches) -> errors::Result<String> {
303333
encode(&header, &claims, secret.as_ref())
304334
}
305335

306-
fn decode_token<T: DeserializeOwned>(matches: &ArgMatches) -> errors::Result<TokenData<T>> {
336+
fn decode_token(matches: &ArgMatches) -> JWTResult<TokenData<Payload>> {
307337
let algorithm = translate_algorithm(SupportedAlgorithms::from_string(
308338
matches.value_of("algorithm").unwrap(),
309339
));
310340
let secret = matches.value_of("secret").unwrap().as_bytes();
311341
let jwt = matches.value_of("jwt").unwrap().to_string();
312342
let validations = create_validations(algorithm);
313343

314-
decode::<T>(&jwt, secret.as_ref(), &validations)
344+
decode::<Payload>(&jwt, secret.as_ref(), &validations)
315345
}
316346

317-
fn print_encoded_token(token: errors::Result<String>) {
347+
fn print_encoded_token(token: JWTResult<String>) {
318348
match token {
319349
Ok(jwt) => {
320350
println!("{}", Cyan.bold().paint("Success! Here's your token\n"));
@@ -330,7 +360,7 @@ fn print_encoded_token(token: errors::Result<String>) {
330360
}
331361
}
332362

333-
fn print_decoded_token(token_data: errors::Result<TokenData<BTreeMap<String, String>>>) {
363+
fn print_decoded_token(token_data: JWTResult<TokenData<Payload>>) {
334364
match token_data {
335365
Ok(TokenData { header, claims }) => {
336366
println!("{}\n", Cyan.bold().paint("Looks like a valid JWT!"));
@@ -342,24 +372,14 @@ fn print_decoded_token(token_data: errors::Result<TokenData<BTreeMap<String, Str
342372
Err(Error(err, _)) => {
343373
match err {
344374
ErrorKind::InvalidToken => println!("The JWT provided is invalid"),
345-
ErrorKind::InvalidSignature => {
346-
println!("The JWT provided has an invalid signature")
347-
}
375+
ErrorKind::InvalidSignature => println!("The JWT provided has an invalid signature"),
348376
ErrorKind::InvalidKey => println!("The secret provided isn't a valid RSA key"),
349377
ErrorKind::ExpiredSignature => println!("The token has expired"),
350378
ErrorKind::InvalidIssuer => println!("The token issuer is invalid"),
351-
ErrorKind::InvalidAudience => {
352-
println!("The token audience doesn't match the subject")
353-
}
354-
ErrorKind::InvalidSubject => {
355-
println!("The token subject doesn't match the audience")
356-
}
357-
ErrorKind::InvalidIssuedAt => {
358-
println!("The issued at claim is in the future which isn't allowed")
359-
}
360-
ErrorKind::ImmatureSignature => {
361-
println!("The `nbf` claim is in the future which isn't allowed")
362-
}
379+
ErrorKind::InvalidAudience => println!("The token audience doesn't match the subject"),
380+
ErrorKind::InvalidSubject => println!("The token subject doesn't match the audience"),
381+
ErrorKind::InvalidIssuedAt => println!("The issued at claim is in the future which isn't allowed"),
382+
ErrorKind::ImmatureSignature => println!("The `nbf` claim is in the future which isn't allowed"),
363383
ErrorKind::InvalidAlgorithm => {
364384
println!(
365385
"The JWT provided has a different signing algorithm than the one you \

0 commit comments

Comments
 (0)