Skip to content

Commit

Permalink
feat: add a flag to display timestamps as ISO 8601 dates
Browse files Browse the repository at this point in the history
Closes #103
  • Loading branch information
mike-engel committed Feb 16, 2021
1 parent 5e05b90 commit 7212ff5
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Support adding `jti` when encoding
- Add `no-iat` flag to disable automatic `iat` claim generation
- Avoid adding an `exp` claim automatically. Instead, the `--exp` flag must be present, with or without a value
- Add an `--iso8601` flag to represent date-based claims as ISO 8601 date strings. Only applies to `iat`, `exp`, and `nbf`

# 3.3.0

Expand Down
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,26 @@

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

[![Build Status](https://travis-ci.org/mike-engel/jwt-cli.svg?branch=master)](https://travis-ci.org/mike-engel/jwt-cli)
[![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)
![Continuous Integration](https://github.com/mike-engel/jwt-cli/workflows/Continuous%20Integration/badge.svg)
[![GitHub release](https://img.shields.io/github/tag/mike-engel/jwt-cli.svg)]()

`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.

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

# Installation

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).
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).

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.

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.

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 :(.
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 :(.

## Homebrew

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

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

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

## FreshPorts

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

## GoFish

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

After installing GoFish, getting `jwt-cli` is just running:
After installing GoFish, run `jwt-cli` with:

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

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.
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.

```sh
curl <auth API> | jq -r .access_token | jwt decode -
Expand Down Expand Up @@ -120,7 +119,7 @@ cargo build
cargo build --release
```

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

```sh
cargo run -- help
Expand Down
29 changes: 27 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use atty::Stream;
use chrono::Utc;
use chrono::{TimeZone, Utc};
use clap::{arg_enum, crate_authors, crate_version, App, Arg, ArgMatches, SubCommand};
use jsonwebtoken::errors::{ErrorKind, Result as JWTResult};
use jsonwebtoken::{
Expand Down Expand Up @@ -108,6 +108,19 @@ impl Payload {

Payload(payload)
}

fn convert_timestamps(&mut self) {
let timestamp_claims: Vec<String> = vec!["iat".into(), "nbf".into(), "exp".into()];

for (key, value) in self.0.iter_mut() {
if timestamp_claims.contains(key) && value.is_number() {
*value = match value.as_i64() {
Some(timestamp) => Utc.timestamp(timestamp, 0).to_rfc3339().into(),
None => value.clone(),
}
}
}
}
}

impl SupportedAlgorithms {
Expand Down Expand Up @@ -244,6 +257,11 @@ fn config_options<'a, 'b>() -> App<'a, 'b> {
.short("A")
.possible_values(&SupportedAlgorithms::variants())
.default_value("HS256"),
).arg(
Arg::with_name("iso_dates")
.help("display unix timestamps as ISO 8601 dates")
.takes_value(false)
.long("iso8601")
).arg(
Arg::with_name("secret")
.help("the secret to validate the JWT with. Can be prefixed with @ to read from a binary file")
Expand Down Expand Up @@ -476,13 +494,20 @@ fn decode_token(
.to_owned();

let secret_validator = create_validations(algorithm);
let token_data = dangerous_insecure_decode::<Payload>(&jwt).map(|mut token| {
if matches.is_present("iso_dates") {
token.claims.convert_timestamps();
}

token
});

(
match secret {
Some(secret_key) => decode::<Payload>(&jwt, &secret_key.unwrap(), &secret_validator),
None => dangerous_insecure_decode::<Payload>(&jwt),
},
dangerous_insecure_decode::<Payload>(&jwt),
token_data,
if matches.is_present("json") {
OutputFormat::JSON
} else {
Expand Down
54 changes: 53 additions & 1 deletion tests/jwt-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod tests {
is_timestamp_or_duration, translate_algorithm, OutputFormat, Payload, PayloadItem,
SupportedAlgorithms,
};
use chrono::{Duration, Utc};
use chrono::{Duration, TimeZone, Utc};
use jsonwebtoken::{Algorithm, Header, TokenData};
use serde_json::{from_value, json};

Expand Down Expand Up @@ -627,4 +627,56 @@ mod tests {

assert!(result.is_ok());
}

#[test]
fn shows_timestamps_as_iso_dates() {
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",
"--exp",
&exp.to_string(),
"--nbf",
&nbf.to_string(),
"-S",
"1234567890",
])
.unwrap();
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",
"--iso8601",
&encoded_token,
])
.unwrap();
let decode_matches = decode_matcher.subcommand_matches("decode").unwrap();
let (decoded_token, token_data, _) = decode_token(&decode_matches);

assert!(decoded_token.is_ok());

let TokenData { claims, header: _ } = token_data.unwrap();

assert!(claims.0.get("iat").is_some());
assert!(claims.0.get("nbf").is_some());
assert!(claims.0.get("exp").is_some());
assert_eq!(
claims.0.get("iat"),
Some(&Utc.timestamp(nbf, 0).to_rfc3339().into())
);
assert_eq!(
claims.0.get("nbf"),
Some(&Utc.timestamp(nbf, 0).to_rfc3339().into())
);
assert_eq!(
claims.0.get("exp"),
Some(&Utc.timestamp(exp, 0).to_rfc3339().into())
);
}
}

0 comments on commit 7212ff5

Please sign in to comment.