web_push/vapid/
signer.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
use std::collections::BTreeMap;

use http::uri::Uri;
use jwt_simple::prelude::*;
use serde_json::Value;

use crate::{error::WebPushError, vapid::VapidKey};

/// A struct representing a VAPID signature. Should be generated using the
/// [VapidSignatureBuilder](struct.VapidSignatureBuilder.html).
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct VapidSignature {
    /// The signed JWT, base64 encoded
    pub auth_t: String,
    /// The public key bytes
    pub auth_k: Vec<u8>,
}

/// JWT claims object. Custom claims are implemented as a map.
pub type Claims = JWTClaims<BTreeMap<String /*Use String as lifetimes bug out when serializing a tuple*/, Value>>;

pub struct VapidSigner {}

impl VapidSigner {
    /// Create a signature with a given key. Sets the default audience from the
    /// endpoint host and sets the expiry in twelve hours. Values can be
    /// overwritten by adding the `aud` and `exp` claims.
    pub fn sign(key: VapidKey, endpoint: &Uri, mut claims: Claims) -> Result<VapidSignature, WebPushError> {
        if !claims.custom.contains_key("aud") {
            //Add audience if not provided.
            let audience = format!("{}://{}", endpoint.scheme_str().unwrap(), endpoint.host().unwrap());
            claims = claims.with_audience(audience);
        } else {
            //Use provided claims if given. This is here to avoid breaking changes.
            let aud = claims.custom.get("aud").unwrap().clone();
            claims = claims.with_audience(aud);
            claims.custom.remove("aud");
        }

        //Override the exp claim if provided in custom. Must then remove from custom to avoid printing
        //Twice, as this is just for backwards compatibility.
        if claims.custom.contains_key("exp") {
            let exp = claims.custom.get("exp").unwrap().clone();
            claims.expires_at = Some(Duration::from_secs(exp.as_u64().ok_or(WebPushError::InvalidClaims)?));
            claims.custom.remove("exp");
        }

        // Add sub if not provided as some browsers (like firefox) require it even though the API doesn't say its needed >:[
        if !claims.custom.contains_key("sub") {
            claims = claims.with_subject("mailto:example@example.com".to_string());
        }

        log::trace!("Using jwt: {:?}", claims);

        let auth_k = key.public_key();

        //Generate JWT signature
        let auth_t = key.0.sign(claims).map_err(|_| WebPushError::InvalidClaims)?;

        Ok(VapidSignature { auth_t, auth_k })
    }
}

#[cfg(test)]
mod tests {}