Page MenuHomePhabricator

D12754.diff
No OneTemporary

D12754.diff

diff --git a/services/tunnelbroker/src/notifs/fcm/error.rs b/services/tunnelbroker/src/notifs/fcm/error.rs
--- a/services/tunnelbroker/src/notifs/fcm/error.rs
+++ b/services/tunnelbroker/src/notifs/fcm/error.rs
@@ -6,6 +6,7 @@
ReqwestError(reqwest::Error),
InvalidHeaderValue(reqwest::header::InvalidHeaderValue),
SerdeJson(serde_json::Error),
+ FCMTokenNotInitialized,
}
impl From<jsonwebtoken::errors::Error> for Error {
diff --git a/services/tunnelbroker/src/notifs/fcm/mod.rs b/services/tunnelbroker/src/notifs/fcm/mod.rs
--- a/services/tunnelbroker/src/notifs/fcm/mod.rs
+++ b/services/tunnelbroker/src/notifs/fcm/mod.rs
@@ -1,20 +1,31 @@
use crate::notifs::fcm::config::FCMConfig;
+use crate::notifs::fcm::token::FCMToken;
+use std::time::Duration;
pub mod config;
mod error;
+mod token;
#[derive(Clone)]
pub struct FCMClient {
http_client: reqwest::Client,
config: FCMConfig,
+ token: FCMToken,
}
impl FCMClient {
pub fn new(config: &FCMConfig) -> Result<Self, error::Error> {
let http_client = reqwest::Client::builder().build()?;
+
+ // Token must be a short-lived token (60 minutes) and in a reasonable
+ // timeframe.
+ let token_ttl = Duration::from_secs(60 * 55);
+ let token = FCMToken::new(&config.clone(), token_ttl)?;
+
Ok(FCMClient {
http_client,
config: config.clone(),
+ token,
})
}
}
diff --git a/services/tunnelbroker/src/notifs/fcm/token.rs b/services/tunnelbroker/src/notifs/fcm/token.rs
new file mode 100644
--- /dev/null
+++ b/services/tunnelbroker/src/notifs/fcm/token.rs
@@ -0,0 +1,68 @@
+use crate::notifs::fcm::config::FCMConfig;
+use crate::notifs::fcm::error::Error;
+use crate::notifs::fcm::error::Error::FCMTokenNotInitialized;
+use jsonwebtoken::{Algorithm, EncodingKey, Header};
+use serde::Deserialize;
+use serde_json::json;
+use std::sync::Arc;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+use tokio::sync::RwLock;
+use tracing::debug;
+
+#[derive(Debug, Clone, Deserialize)]
+struct FCMAccessToken {
+ access_token: String,
+ token_type: String,
+ expiration_time: u64,
+}
+
+#[derive(Debug, Clone)]
+pub struct FCMToken {
+ token: Arc<RwLock<Option<FCMAccessToken>>>,
+ config: FCMConfig,
+ validity_duration: Duration,
+}
+
+impl FCMToken {
+ pub fn new(config: &FCMConfig, token_ttl: Duration) -> Result<Self, Error> {
+ Ok(FCMToken {
+ token: Arc::new(RwLock::new(None)),
+ config: config.clone(),
+ validity_duration: token_ttl,
+ })
+ }
+
+ pub async fn get_auth_bearer(&self) -> Result<String, Error> {
+ let bearer = self.token.read().await;
+ match &*bearer {
+ Some(token) => Ok(format!("{} {}", token.token_type, token.access_token)),
+ None => Err(FCMTokenNotInitialized),
+ }
+ }
+
+ fn get_jwt_token(&self, created_at: u64) -> Result<String, Error> {
+ let exp = created_at + self.validity_duration.as_secs();
+ let payload = json!({
+ // The email address of the service account.
+ "iss": self.config.client_email,
+ // A descriptor of the intended target of the assertion.
+ "aud": self.config.token_uri,
+ // The time the assertion was issued.
+ "iat": created_at,
+ // The expiration time of the assertion.
+ // This value has a maximum of 1 hour after the issued time.
+ "exp": exp,
+ // A space-delimited list of the permissions that the application
+ // requests.
+ "scope": "https://www.googleapis.com/auth/firebase.messaging",
+ });
+
+ debug!("Encoding JWT token for FCM, created at: {}", created_at);
+
+ let header = Header::new(Algorithm::RS256);
+ let encoding_key =
+ EncodingKey::from_rsa_pem(self.config.private_key.as_bytes()).unwrap();
+ let token = jsonwebtoken::encode(&header, &payload, &encoding_key)?;
+ Ok(token)
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Fri, Nov 29, 2:47 AM (19 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2595520
Default Alt Text
D12754.diff (3 KB)

Event Timeline