impl/rust/read_access_token.rs
4,462 bytes · the Rust implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::auth_bearer_token::parse_bearer_token; ← from auth.bearer-token ^1.0.0 · built alongside by fune
use super::auth_jwt_decode_jwt::check_jwt_secret;
use super::auth_jwt_verify_jwt::verify_jwt;
use super::time_unix_to_iso::unix_to_iso; ← from time.unix-to-iso ^1.0.0 · built alongside by fune
/// The latest second time.unix-to-iso can write.
const MAX_EXP: i64 = 253402300799;
/// A failed check: every field but the reason is `None`.
pub fn access_denied(error: &str, message: &str) -> AccessCheck {
AccessCheck {
ok: false,
subject: None,
token_version: None,
jti: None,
expires_at: None,
claims: None,
error: Some(error.to_string()),
message: Some(message.to_string()),
}
}
/// A JSON number with no fraction; `1.0` is whole, as it is in JavaScript.
fn whole(value: &Value) -> Option<i64> {
match value {
Value::Int(i) => Some(*i),
Value::Float(f) if f.is_finite() && f.fract() == 0.0 && f.abs() < 9.007_199_254_740_992e15 => Some(*f as i64),
_ => None,
}
}
/// Who is making this request? The Bearer token from the Authorization
/// header, verified, and required to carry the claims issue_access_token
/// writes. A missing or bad token is an answer (401), never a panic.
///
/// # Panics
/// Panics on a secret under 32 bytes or a negative leeway.
pub fn read_access_token(authorization: Option<&str>, secret: &[i64], now: i64, leeway_seconds: i64) -> AccessCheck {
check_jwt_secret(secret);
if leeway_seconds < 0 {
panic!("leewaySeconds must be a whole number, 0 or more");
}
let token = match parse_bearer_token(authorization) {
Some(token) => token,
None => return access_denied("missing_token", "the request has no Bearer token"),
};
let verified = verify_jwt(&token, secret, now, leeway_seconds);
let claims = match (verified.valid, verified.claims) {
(true, Some(claims)) => claims,
_ => {
return access_denied(
verified.error.as_deref().unwrap_or("malformed_token"),
verified.message.as_deref().unwrap_or("the token is not a well-formed JWT"),
)
}
};
let sub = match claims.get("sub") {
Value::Str(s) if !s.is_empty() => Some(s.clone()),
_ => None,
};
let jti = match claims.get("jti") {
Value::Str(s) if !s.is_empty() => Some(s.clone()),
_ => None,
};
let ver = whole(claims.get("ver")).filter(|v| *v >= 0);
let exp = whole(claims.get("exp")).filter(|e| (0..=MAX_EXP).contains(e));
match (sub, jti, ver, exp) {
(Some(subject), Some(jti), Some(ver), Some(exp)) => AccessCheck {
ok: true,
subject: Some(subject),
token_version: Some(ver),
jti: Some(jti),
expires_at: Some(unix_to_iso(exp)),
claims: Some(claims),
error: None,
message: None,
},
_ => access_denied("invalid_claims", "the token lacks the sub, jti, ver or exp this API issues"),
}
}
pub fn access_check_to_value(check: &AccessCheck) -> Value {
let text = |v: &Option<String>| v.as_deref().map(Value::str).unwrap_or(Value::Null);
Value::obj(vec![
("ok", Value::Bool(check.ok)),
("subject", text(&check.subject)),
("tokenVersion", check.token_version.map(Value::Int).unwrap_or(Value::Null)),
("jti", text(&check.jti)),
("expiresAt", text(&check.expires_at)),
("claims", check.claims.clone().unwrap_or(Value::Null)),
("error", text(&check.error)),
("message", text(&check.message)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let header = match &args[0] {
Value::Str(s) => Some(s.as_str()),
_ => None,
};
let secret: Vec<i64> = match &args[1] {
Value::Arr(items) => items
.iter()
.map(|item| match item {
Value::Int(i) => *i,
_ => panic!("secret must be a list of integers from 0 to 255"),
})
.collect(),
_ => panic!("secret must be a list of integers from 0 to 255"),
};
let now = match &args[2] {
Value::Int(i) => *i,
_ => panic!("now must be a whole number of Unix seconds"),
};
let leeway = match &args[3] {
Value::Int(i) => *i,
_ => panic!("leewaySeconds must be a whole number, 0 or more"),
};
access_check_to_value(&read_access_token(header, &secret, now, leeway))
}