Skip to content

Commit 7212ff5

Browse files
committed
feat: add a flag to display timestamps as ISO 8601 dates
Closes #103
1 parent 5e05b90 commit 7212ff5

File tree

4 files changed

+91
-14
lines changed

4 files changed

+91
-14
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Support adding `jti` when encoding
66
- Add `no-iat` flag to disable automatic `iat` claim generation
77
- Avoid adding an `exp` claim automatically. Instead, the `--exp` flag must be present, with or without a value
8+
- Add an `--iso8601` flag to represent date-based claims as ISO 8601 date strings. Only applies to `iat`, `exp`, and `nbf`
89

910
# 3.3.0
1011

README.md

+10-11
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,26 @@
44

55
A super fast CLI tool to decode and encode JWTs built in [Rust](https://rust-lang.org).
66

7-
[![Build Status](https://travis-ci.org/mike-engel/jwt-cli.svg?branch=master)](https://travis-ci.org/mike-engel/jwt-cli)
8-
[![Build status](https://ci.appveyor.com/api/projects/status/9p1lqbo8cmhixdns/branch/master?svg=true)](https://ci.appveyor.com/project/mike-engel/jwt-cli/branch/master)
7+
![Continuous Integration](https://github.com/mike-engel/jwt-cli/workflows/Continuous%20Integration/badge.svg)
98
[![GitHub release](https://img.shields.io/github/tag/mike-engel/jwt-cli.svg)]()
109

1110
`jwt-cli` is a command line tool to help you work with JSON Web Tokens (JWTs). Like most JWT command line tools out there, you can decode almost any JWT header and claims body. Unlike any that I've found, however, `jwt-cli` allows you to encode a new JWT with nearly any piece of data you can think of. Custom header values (some), custom claim bodies (as long as it's JSON, it's game), and using any secret you need.
1211

13-
On top of all that, it's written in Rust so it's fast and extremely portable (windows, macOS, and linux supported right now).
12+
On top of all that, it's written in Rust so it's fast and portable (windows, macOS, and linux supported right now).
1413

1514
# Installation
1615

17-
Currently, installation is supported via [Homebrew](https://brew.sh) (macOS), [Cargo](https://www.rust-lang.org/tools/install) (cross-platform), and [FreshPorts](https://www.freshports.org/www/jwt-cli) (FreeBSD). If you intend to use one of these methods, [skip ahead](#homebrew).
16+
Install `jwt-cli` via [Homebrew](https://brew.sh) (macOS), [Cargo](https://www.rust-lang.org/tools/install) (cross-platform), and [FreshPorts](https://www.freshports.org/www/jwt-cli) (FreeBSD). If you intend to use one of these methods, [skip ahead](#homebrew).
1817

1918
You may also install the binary from the [release](https://github.com/mike-engel/jwt-cli/releases) page, if you're unable to use Homebrew or Cargo install methods below.
2019

2120
Only 64bit linux, macOS, and Windows targets are pre-built. Sorry if you're not on one of those! You'll need to build it from the source. See the [contributing](#contributing) section on how to install and build the project.
2221

23-
As to where you should install it, it should optimally go somewhere in your `PATH`. For Linux and macOS, a good place is generally `/usr/local/bin`. For Windows, there really isn't a good place by default :(.
22+
You should install it somewhere in your `$PATH`. For Linux and macOS, a good place is generally `/usr/local/bin`. For Windows, there isn't a good place by default :(.
2423

2524
## Homebrew
2625

27-
For those with Homebrew, you'll need to `brew tap mike-engel/jwt-cli` repo in order to install it.
26+
For those with Homebrew, you'll need to `brew tap mike-engel/jwt-cli` repo to install it.
2827

2928
```sh
3029
# Tap and install jwt-cli
@@ -43,7 +42,7 @@ If your system [supports](https://forge.rust-lang.org/platform-support.html) it,
4342
cargo install jwt-cli
4443
```
4544

46-
The binary will be installed in your Cargo bin path (`~/.cargo/bin`). Make sure this path is included in your PATH environment variable.
45+
The binary installs to your Cargo bin path (`~/.cargo/bin`). Make sure your `$PATH` environment variable includes this path.
4746

4847
## FreshPorts
4948

@@ -57,10 +56,10 @@ Big thanks to Sergey Osokin, the FreeBSD contributor who added `jwt-cli` to the
5756

5857
## GoFish
5958

60-
`jwt-cli` is also avaible on Windows, MacOSX and Linux using GoFish.
59+
`jwt-cli` is also available on Windows, macOS, and Linux using GoFish.
6160
See [gofi.sh](https://gofi.sh/index.html#install) for instructions for getting GoFish.
6261

63-
After installing GoFish, getting `jwt-cli` is just running:
62+
After installing GoFish, run `jwt-cli` with:
6463

6564
```sh
6665
gofish install jwt-cli
@@ -86,7 +85,7 @@ The `-` argument tells `jwt-cli` to read from standard input:
8685
jwt encode --secret=fake '{"hello":"world"}' | jwt decode -
8786
```
8887

89-
It's especially useful when you're dealing with a chain of shell commands that produce a JWT. Pipe the result through `jwt decode -` to decode it.
88+
It's useful when you're dealing with a chain of shell commands that produce a JWT. Pipe the result through `jwt decode -` to decode it.
9089

9190
```sh
9291
curl <auth API> | jq -r .access_token | jwt decode -
@@ -120,7 +119,7 @@ cargo build
120119
cargo build --release
121120
```
122121

123-
If it built successfully, you should be able to run the command via `cargo`.
122+
If it built without any errors, you should be able to run the command via `cargo`.
124123

125124
```sh
126125
cargo run -- help

src/main.rs

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use atty::Stream;
2-
use chrono::Utc;
2+
use chrono::{TimeZone, Utc};
33
use clap::{arg_enum, crate_authors, crate_version, App, Arg, ArgMatches, SubCommand};
44
use jsonwebtoken::errors::{ErrorKind, Result as JWTResult};
55
use jsonwebtoken::{
@@ -108,6 +108,19 @@ impl Payload {
108108

109109
Payload(payload)
110110
}
111+
112+
fn convert_timestamps(&mut self) {
113+
let timestamp_claims: Vec<String> = vec!["iat".into(), "nbf".into(), "exp".into()];
114+
115+
for (key, value) in self.0.iter_mut() {
116+
if timestamp_claims.contains(key) && value.is_number() {
117+
*value = match value.as_i64() {
118+
Some(timestamp) => Utc.timestamp(timestamp, 0).to_rfc3339().into(),
119+
None => value.clone(),
120+
}
121+
}
122+
}
123+
}
111124
}
112125

113126
impl SupportedAlgorithms {
@@ -244,6 +257,11 @@ fn config_options<'a, 'b>() -> App<'a, 'b> {
244257
.short("A")
245258
.possible_values(&SupportedAlgorithms::variants())
246259
.default_value("HS256"),
260+
).arg(
261+
Arg::with_name("iso_dates")
262+
.help("display unix timestamps as ISO 8601 dates")
263+
.takes_value(false)
264+
.long("iso8601")
247265
).arg(
248266
Arg::with_name("secret")
249267
.help("the secret to validate the JWT with. Can be prefixed with @ to read from a binary file")
@@ -476,13 +494,20 @@ fn decode_token(
476494
.to_owned();
477495

478496
let secret_validator = create_validations(algorithm);
497+
let token_data = dangerous_insecure_decode::<Payload>(&jwt).map(|mut token| {
498+
if matches.is_present("iso_dates") {
499+
token.claims.convert_timestamps();
500+
}
501+
502+
token
503+
});
479504

480505
(
481506
match secret {
482507
Some(secret_key) => decode::<Payload>(&jwt, &secret_key.unwrap(), &secret_validator),
483508
None => dangerous_insecure_decode::<Payload>(&jwt),
484509
},
485-
dangerous_insecure_decode::<Payload>(&jwt),
510+
token_data,
486511
if matches.is_present("json") {
487512
OutputFormat::JSON
488513
} else {

tests/jwt-cli.rs

+53-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ mod tests {
77
is_timestamp_or_duration, translate_algorithm, OutputFormat, Payload, PayloadItem,
88
SupportedAlgorithms,
99
};
10-
use chrono::{Duration, Utc};
10+
use chrono::{Duration, TimeZone, Utc};
1111
use jsonwebtoken::{Algorithm, Header, TokenData};
1212
use serde_json::{from_value, json};
1313

@@ -627,4 +627,56 @@ mod tests {
627627

628628
assert!(result.is_ok());
629629
}
630+
631+
#[test]
632+
fn shows_timestamps_as_iso_dates() {
633+
let exp = (Utc::now() + Duration::minutes(60)).timestamp();
634+
let nbf = Utc::now().timestamp();
635+
let encode_matcher = config_options()
636+
.get_matches_from_safe(vec![
637+
"jwt",
638+
"encode",
639+
"--exp",
640+
&exp.to_string(),
641+
"--nbf",
642+
&nbf.to_string(),
643+
"-S",
644+
"1234567890",
645+
])
646+
.unwrap();
647+
let encode_matches = encode_matcher.subcommand_matches("encode").unwrap();
648+
let encoded_token = encode_token(&encode_matches).unwrap();
649+
let decode_matcher = config_options()
650+
.get_matches_from_safe(vec![
651+
"jwt",
652+
"decode",
653+
"-S",
654+
"1234567890",
655+
"--iso8601",
656+
&encoded_token,
657+
])
658+
.unwrap();
659+
let decode_matches = decode_matcher.subcommand_matches("decode").unwrap();
660+
let (decoded_token, token_data, _) = decode_token(&decode_matches);
661+
662+
assert!(decoded_token.is_ok());
663+
664+
let TokenData { claims, header: _ } = token_data.unwrap();
665+
666+
assert!(claims.0.get("iat").is_some());
667+
assert!(claims.0.get("nbf").is_some());
668+
assert!(claims.0.get("exp").is_some());
669+
assert_eq!(
670+
claims.0.get("iat"),
671+
Some(&Utc.timestamp(nbf, 0).to_rfc3339().into())
672+
);
673+
assert_eq!(
674+
claims.0.get("nbf"),
675+
Some(&Utc.timestamp(nbf, 0).to_rfc3339().into())
676+
);
677+
assert_eq!(
678+
claims.0.get("exp"),
679+
Some(&Utc.timestamp(exp, 0).to_rfc3339().into())
680+
);
681+
}
630682
}

0 commit comments

Comments
 (0)