1use std::{fmt::Display, time::Duration};
29
30use base64ct::{Base64UrlUnpadded, Encoding};
31use http::{header, Uri};
32use jwt_simple::{
33 algorithms::{ECDSAP256KeyPairLike, ECDSAP256PublicKeyLike, ES256KeyPair, ES256PublicKey},
34 claims::Claims,
35};
36
37use super::{AddHeaders, WebPushBuilder};
38
39#[derive(Clone, Debug)]
41pub struct VapidAuthorization<'a> {
42 vapid_kp: &'a ES256KeyPair,
43 contact: &'a str,
44}
45
46impl<'a> VapidAuthorization<'a> {
47 pub(crate) fn new(vapid_kp: &'a ES256KeyPair, contact: &'a str) -> Self {
48 Self { vapid_kp, contact }
49 }
50}
51
52impl<'a> AddHeaders for VapidAuthorization<'a> {
53 type Error = jwt_simple::Error;
54
55 fn add_headers(
56 this: &WebPushBuilder<Self>,
57 builder: http::request::Builder,
58 ) -> Result<http::request::Builder, Self::Error> {
59 let vapid = VapidSignature::sign(
60 &this.endpoint,
61 this.valid_duration,
62 this.http_auth.contact.to_string(),
63 this.http_auth.vapid_kp,
64 )?;
65 Ok(builder.header(header::AUTHORIZATION, vapid))
66 }
67}
68
69#[derive(Clone, Debug)]
71pub struct VapidSignature {
72 token: String,
73 public_key: ES256PublicKey,
74}
75
76impl VapidSignature {
77 pub fn sign<T: ToString>(
80 endpoint: &Uri,
81 valid_duration: Duration,
82 contact: T,
83 key: &ES256KeyPair,
84 ) -> Result<VapidSignature, jwt_simple::Error> {
85 let claims = Claims::create(valid_duration.into())
86 .with_audience(format!(
87 "{}://{}",
88 endpoint
89 .scheme_str()
90 .ok_or(jwt_simple::Error::msg("missing scheme in endpoint"))?,
91 endpoint
92 .host()
93 .ok_or(jwt_simple::Error::msg("missing host in endpoint"))?
94 ))
95 .with_subject(contact);
96
97 Ok(VapidSignature {
98 token: key.sign(claims)?,
99 public_key: key.public_key(),
100 })
101 }
102}
103
104impl Display for VapidSignature {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 let encoded_public =
107 Base64UrlUnpadded::encode_string(&self.public_key.public_key().to_bytes_uncompressed());
108 write!(f, "vapid t={}, k={}", self.token, encoded_public)
109 }
110}
111
112impl From<VapidSignature> for http::HeaderValue {
113 fn from(signature: VapidSignature) -> Self {
114 Self::try_from(signature.to_string()).expect("given string is always a valid header value")
115 }
116}