brycx / pasetors

PASETOrs: PASETO tokens in pure Rust
MIT License
83 stars 8 forks source link

Implementation issues #82

Closed Sirneij closed 1 year ago

Sirneij commented 1 year ago

I am currently using an implementation of paseto but it is currently not being maintained again. This crate, implementation, was recommended but I have issues implementing what I want. In the previous crate, I have these two functions:

...
pub fn issue_confirmation_token(user_id: uuid::Uuid) -> String {
    let settings = crate::settings::get_settings().expect("Cannot load settings.");
    let current_date_time = chrono::Utc::now();
    let dt = current_date_time + chrono::Duration::minutes(settings.secret.token_expiration);
    paseto::tokens::PasetoBuilder::new()
        .set_encryption_key(&Vec::from(settings.secret.secret_key.as_bytes()))
        .set_expiration(&dt)
        .set_not_before(&chrono::Utc::now())
        .set_claim("user_id", serde_json::json!(user_id))
        .build()
        .expect("Failed to construct paseto token w/ builder!")
}

pub async fn verify_confirmation_token(
    token: String,
) -> Result<crate::types::ConfirmationToken, String> {
    let settings = crate::settings::get_settings().expect("Cannot load settings.");
    let token = paseto::tokens::validate_local_token(
        &token,
        None,
        &settings.secret.secret_key.as_bytes(),
        &paseto::tokens::TimeBackend::Chrono,
    )
    .map_err(|e| format!("Paseto: {}", e))?;

    serde_json::from_value::<crate::types::ConfirmationToken>(token)
        .map_err(|e| format!("Serde_json: {}", e))
}
...

How can I achieve these with this crate. The examples in the docs ain't helping much and I had spent ample time trying to get this to work but all to no avail.

brycx commented 1 year ago

Something in the lines of this should work (note this uses V4), though I haven't compiled it so let me know if it causes any issues you can't correct. If you're bound to using V2 tokens, then claims can be serialized to/from bytes and then be passed directly to encrypt() as &[u8]. ClaimsValidationRules also has validate_claims() such that you can manually perform the validation.

Perhaps worth to know that you shouldn't use V2 for new systems and prefer V4 instead.

use pasetors::claims::{Claims, ClaimsValidationRules};
use pasetors::keys::{Generate, SymmetricKey};
use pasetors::{local, Local, version4::V4};
use pasetors::token::UntrustedToken;
use core::convert::TryFrom;

pub fn issue_confirmation_token(user_id: uuid::Uuid) -> String {
    let settings = crate::settings::get_settings().expect("Cannot load settings.");
    let current_date_time = chrono::Utc::now();
    let dt = current_date_time + chrono::Duration::minutes(settings.secret.token_expiration);

    let mut claims = Claims::new().unwrap();
    // Set custom expiration, default is 1 hour
    claims.expiration(dt.as_str()).unwrap();
    claims.add_additional("user_id", serde_json::json!(user_id)).unwrap();

    let sk = SymmetricKey::<V4>::from(settings.secret.secret_key.as_bytes()).unwrap();
    let token = local::encrypt(&sk, &claims, None, Some(b"implicit assertion")).unwrap();

    return token;
}

pub async fn verify_confirmation_token(token: String) -> Result<crate::types::ConfirmationToken, String> {
    let settings = crate::settings::get_settings().expect("Cannot load settings.");

    let validation_rules = ClaimsValidationRules::new();
    let untrusted_token = UntrustedToken::<Local, V4>::try_from(&token)?;
    let trusted_token = local::decrypt(&sk, &untrusted_token, &validation_rules, None, Some(b"implicit assertion"))?;
    let claims = trusted_token.payload_claims().unwrap();

    let uid = claims.get_claim("user_id")
}
Sirneij commented 1 year ago

I was able to figure it out using your submission and some modifications. Here it is:

use core::convert::TryFrom;
use pasetors::claims::{Claims, ClaimsValidationRules};
use pasetors::keys::SymmetricKey;
use pasetors::token::UntrustedToken;
use pasetors::{local, version4::V4, Local};

pub fn issue_confirmation_token_pasetors(user_id: uuid::Uuid) -> String {
    let settings = crate::settings::get_settings().expect("Cannot load settings.");
    let current_date_time = chrono::Utc::now();
    let dt = current_date_time + chrono::Duration::minutes(settings.secret.token_expiration);

    let mut claims = Claims::new().unwrap();
    // Set custom expiration, default is 1 hour
    claims.expiration(&dt.to_rfc3339()).unwrap();
    claims
        .add_additional("user_id", serde_json::json!(user_id))
        .unwrap();

    let sk = SymmetricKey::<V4>::from(settings.secret.secret_key.as_bytes()).unwrap();
    let token = local::encrypt(&sk, &claims, None, Some(b"implicit assertion")).unwrap();

    return token;
}

#[tracing::instrument(name = "Verify pasetors token", skip(token))]
pub async fn verify_confirmation_token_pasetor(
    token: String,
) -> Result<crate::types::ConfirmationToken, String> {
    let settings = crate::settings::get_settings().expect("Cannot load settings.");
    let sk = SymmetricKey::<V4>::from(settings.secret.secret_key.as_bytes()).unwrap();

    let validation_rules = ClaimsValidationRules::new();
    let untrusted_token = UntrustedToken::<Local, V4>::try_from(&token).unwrap();
    let trusted_token = local::decrypt(
        &sk,
        &untrusted_token,
        &validation_rules,
        None,
        Some(b"implicit assertion"),
    )
    .map_err(|e| format!("Pasetor: {}", e))?;
    let claims = trusted_token.payload_claims().unwrap();

    let uid = serde_json::to_value(claims.get_claim("user_id").unwrap()).unwrap();

    match serde_json::from_value::<String>(uid) {
        Ok(uuid_string) => match uuid::Uuid::parse_str(&uuid_string) {
            Ok(user_uuid) => Ok(crate::types::ConfirmationToken { user_id: user_uuid }),
            Err(e) => Err(format!("{}", e)),
        },

        Err(e) => Err(format!("{}", e)),
    }
}

Kindly check it and suggest any improvement in case there is.

brycx commented 1 year ago

The implicit assertion is not a required argument. Since you don't specify anything specific, you might as well set it None, like the footer.

That's the only thing that sticks out. Of course, the DateTime string (used for expiration) must be valid in terms of ISO 8601 as per the PASETO spec, but I think RFC 3339 is. I'm not certain though.

Sirneij commented 1 year ago

All right. Thank you.