Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update auth-helper libs and improve lib usability #50

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions auth-helper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ keywords = [ "iota", "auth" ]
homepage = "https://www.iota.org"

[dependencies]
jsonwebtoken = { version = "7.2.0", default-features = false }
jsonwebtoken = { version = "8.1.0", default-features = false }
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
rand = { version = "0.8.4", default-features = false, features = [ "std" ] }
rust-argon2 = { version = "0.8.3", default-features = false }
rust-argon2 = { version = "1.0.0", default-features = false }
serde = { version = "1.0.30", default-features = false, features = [ "std", "derive" ] }
thiserror = { version = "1.0.30", default-features = false }
162 changes: 92 additions & 70 deletions auth-helper/src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

//! A module that provides JSON Web Token utilities.

use std::time::{SystemTime, UNIX_EPOCH};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

pub use jsonwebtoken::TokenData;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
pub use jsonwebtoken::{self, Validation};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData};
use serde::{Deserialize, Serialize};
use thiserror::Error;

Expand All @@ -27,52 +27,65 @@ pub enum Error {
pub struct Claims {
/// Issuer.
/// Identifies the principal that issued the JWT. The processing of this claim is generally application specific.
iss: String,
pub iss: String,
/// Subject.
/// Identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the
/// subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be
/// globally unique. The processing of this claim is generally application specific.
sub: String,
pub sub: String,
/// Audience.
/// Identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST
/// identify itself with a value in the audience claim. If the principal processing the claim does not identify
/// itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. The
/// interpretation of audience values is generally application specific.
aud: String,
pub aud: String,
/// Expiration Time.
/// Identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of
/// the "exp" claim requires that the current date/time MUST be before the expiration date/time listed in the "exp"
/// claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock
/// skew.
#[serde(skip_serializing_if = "Option::is_none")]
exp: Option<u64>,
pub exp: Option<u64>,
/// Not Before.
/// Identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the "nbf" claim
/// requires that the current date/time MUST be after or equal to the not-before date/time listed in the "nbf"
/// claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock
/// skew.
nbf: u64,
pub nbf: u64,
/// Issued At.
/// Identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT.
iat: u64,
pub iat: u64,
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
}

impl Claims {
/// Creates a new set of claims.
fn new(iss: String, sub: String, aud: String, nbf: u64) -> Self {
pub fn new(iss: impl Into<String>, sub: impl Into<String>, aud: impl Into<String>) -> Self {
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.as_ref()
.map(Duration::as_secs)
.unwrap_or_default();
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
Self {
iss,
sub,
aud,
iss: iss.into(),
sub: sub.into(),
aud: aud.into(),
exp: None,
nbf,
iat: SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Clock may have gone backwards")
.as_secs() as u64,
nbf: now,
iat: now,
}
}

/// Specifies that this token will expire, and provides an expiry time (offset from issue time).
pub fn expires_after(mut self, dur: Duration) -> Result<Self, Error> {
let dur = dur.as_secs();
let exp = self.nbf.checked_add(dur).ok_or(Error::InvalidExpiry {
issued_at: self.nbf,
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
expiry: self.nbf + dur,
})?;
self.exp = Some(exp);
Ok(self)
}

/// Returns the issuer of the JWT.
pub fn issuer(&self) -> &str {
&self.iss
Expand Down Expand Up @@ -104,58 +117,82 @@ impl Claims {
}
}

/// Builder for the [`Claims`] structure.
pub struct ClaimsBuilder {
iss: String,
sub: String,
aud: String,
exp: Option<u64>,
pub trait BuildValidation: _sealed_validation::SealedBuildValidation {
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
fn with_audience(self, aud: impl ToString) -> Self;

fn with_audiences(self, auds: &[impl ToString]) -> Self;

fn with_issuer(self, iss: impl ToString) -> Self;

fn with_issuers(self, iss: &[impl ToString]) -> Self;
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved

fn with_subject(self, sub: impl ToString) -> Self;

fn with_required_spec_claims(self, claims: &[&str]) -> Self;

fn with_leeway(self, secs: u64) -> Self;

fn validate_exp(self, validate: bool) -> Self;

fn validate_nbf(self, validate: bool) -> Self;
}

impl ClaimsBuilder {
/// Creates a new [`ClaimsBuilder`] with the given mandatory parameters.
pub fn new(iss: String, sub: String, aud: String) -> Self {
Self {
iss,
sub,
aud,
exp: None,
}
impl BuildValidation for Validation {
fn with_audience(mut self, aud: impl ToString) -> Self {
self.set_audience(&[aud]);
self
}

/// Specifies that this token will expire, and provides an expiry time (offset from issue time).
#[must_use]
pub fn with_expiry(mut self, exp: u64) -> Self {
self.exp = Some(exp);
fn with_audiences(mut self, auds: &[impl ToString]) -> Self {
self.set_audience(auds);
self
}

/// Builds and returns a [`Claims`] structure using the given builder options.
pub fn build(self) -> Result<Claims, Error> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Clock may have gone backwards")
.as_secs() as u64;
fn with_issuer(mut self, iss: impl ToString) -> Self {
self.set_issuer(&[iss]);
self
}

let mut claims = Claims::new(self.iss, self.sub, self.aud, now);
fn with_issuers(mut self, iss: &[impl ToString]) -> Self {
self.set_issuer(iss);
self
}

if let Some(exp) = self.exp {
let expiry = now.checked_add(exp).ok_or(Error::InvalidExpiry {
issued_at: now,
expiry: exp,
})?;
fn with_subject(mut self, sub: impl ToString) -> Self {
self.sub = Some(sub.to_string());
self
}

claims.exp = Some(expiry);
}
fn with_required_spec_claims(mut self, claims: &[&str]) -> Self {
self.set_required_spec_claims(claims);
self
}

Ok(claims)
fn with_leeway(mut self, secs: u64) -> Self {
self.leeway = secs;
self
}

fn validate_exp(mut self, validate: bool) -> Self {
self.validate_exp = validate;
self
}

fn validate_nbf(mut self, validate: bool) -> Self {
self.validate_nbf = validate;
self
}
}

mod _sealed_validation {
pub trait SealedBuildValidation {}
impl SealedBuildValidation for jsonwebtoken::Validation {}
}

/// Represents a JSON Web Token.
/// <https://tools.ietf.org/html/rfc7519>
#[derive(Clone, Debug)]
pub struct JsonWebToken(String);
pub struct JsonWebToken(pub String);

impl From<String> for JsonWebToken {
fn from(inner: String) -> Self {
Expand All @@ -178,26 +215,11 @@ impl JsonWebToken {
}

/// Validates a JSON Web Token.
pub fn validate(
&self,
issuer: String,
subject: String,
audience: String,
expires: bool,
secret: &[u8],
) -> Result<TokenData<Claims>, Error> {
let mut validation = Validation {
iss: Some(issuer),
sub: Some(subject),
validate_exp: expires,
..Default::default()
};
validation.set_audience(&[audience]);

pub fn validate(&self, validation: impl Into<Validation>, secret: &[u8]) -> Result<TokenData<Claims>, Error> {
Ok(decode::<Claims>(
&self.0,
&DecodingKey::from_secret(secret),
&validation,
&validation.into(),
)?)
}
}
Loading