facebook_signed_request/
lib.rs1use base64::{engine::general_purpose, Engine as _};
2use hmac::{Hmac, Mac as _};
3use serde::de::DeserializeOwned;
4use sha2::Sha256;
5
6type HmacSha256 = Hmac<Sha256>;
7
8#[cfg(feature = "with-data-deletion-callback")]
9pub mod data_deletion_callback;
10#[cfg(feature = "with-fb-login-deauth-callback")]
11pub mod fb_login_deauth_callback;
12#[cfg(feature = "with-ig-basic-display-data-deletion-request")]
13pub mod ig_basic_display_data_deletion_request;
14#[cfg(feature = "with-ig-basic-display-deauth-callback")]
15pub mod ig_basic_display_deauth_callback;
16
17pub const NORMALLY_ALGORITHM: &str = "HMAC-SHA256";
18
19pub trait Payload: DeserializeOwned {
20 fn algorithm(&self) -> Option<&str> {
21 None
22 }
23}
24
25pub fn parse<T: Payload>(signed_request: &str, app_secret: &str) -> Result<T, ParseError> {
27 let mut signed_request_split = signed_request.split('.');
28 let encoded_sig = signed_request_split
29 .next()
30 .ok_or(ParseError::EncodedSignatureMissing)?;
31 let payload = signed_request_split
32 .next()
33 .ok_or(ParseError::PayloadMissing)?;
34 if signed_request_split.next().is_some() {
35 return Err(ParseError::SignedRequestInvalid);
36 }
37
38 let sig = general_purpose::URL_SAFE_NO_PAD
39 .decode(encoded_sig)
40 .map_err(ParseError::EncodedSignatureBase64DecodeFailed)?;
41 let data = general_purpose::URL_SAFE_NO_PAD
42 .decode(payload)
43 .map_err(ParseError::EncodedSignatureBase64DecodeFailed)?;
44
45 let data: T = serde_json::from_slice(&data).map_err(ParseError::PayloadJsonDecodeFailed)?;
46
47 let algorithm = data.algorithm().unwrap_or(NORMALLY_ALGORITHM);
48
49 let expected_sig = match algorithm {
50 NORMALLY_ALGORITHM => hmac_sha256_payload(payload.as_bytes(), app_secret)
51 .map_err(|_| ParseError::SignatureCalculateFailed)?,
52 _ => return Err(ParseError::AlgorithmUnknown(algorithm.to_owned())),
53 };
54
55 if sig != expected_sig {
56 return Err(ParseError::SignatureMismatch);
57 }
58
59 Ok(data)
60}
61
62#[derive(thiserror::Error, Debug)]
63pub enum ParseError {
64 #[error("EncodedSignatureMissing")]
65 EncodedSignatureMissing,
66 #[error("PayloadMissing")]
67 PayloadMissing,
68 #[error("SignedRequestInvalid")]
69 SignedRequestInvalid,
70 #[error("EncodedSignatureBase64DecodeFailed {0}")]
71 EncodedSignatureBase64DecodeFailed(base64::DecodeError),
72 #[error("PayloadBase64DecodeFailed {0}")]
73 PayloadBase64DecodeFailed(base64::DecodeError),
74 #[error("PayloadJsonDecodeFailed {0}")]
75 PayloadJsonDecodeFailed(serde_json::Error),
76 #[error("AlgorithmUnknown {0}")]
77 AlgorithmUnknown(String),
78 #[error("SignatureCalculateFailed")]
79 SignatureCalculateFailed,
80 #[error("SignatureMismatch")]
81 SignatureMismatch,
82}
83
84fn hmac_sha256_payload(payload_bytes: &[u8], app_secret: &str) -> Result<Vec<u8>, String> {
87 let mut hmac =
88 HmacSha256::new_from_slice(app_secret.as_bytes()).map_err(|err| err.to_string())?;
89 hmac.update(payload_bytes);
90 let hmac_result = hmac.finalize().into_bytes();
91 let sig = hmac_result.to_vec();
92
93 Ok(sig)
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 use serde::Deserialize;
101
102 #[test]
103 fn test_parse() {
104 #[derive(Deserialize)]
105 struct MyPayload {
106 user_id: String,
107 algorithm: String,
108 issued_at: u64,
109 }
110 impl Payload for MyPayload {
111 fn algorithm(&self) -> Option<&str> {
112 Some(&self.algorithm)
113 }
114 }
115
116 let signed_request = "Mf_s6nTb38UYqioBmPqu0Ewm9souPZB9I2fIGwV729U.eyJ1c2VyX2lkIjoiMCIsImFsZ29yaXRobSI6IkhNQUMtU0hBMjU2IiwiaXNzdWVkX2F0IjoxNjI0MjQ0MTU2fQ";
119
120 match parse::<MyPayload>(signed_request, "key") {
121 Ok(payload) => {
122 assert_eq!(payload.user_id, "0");
123 assert_eq!(payload.algorithm, "HMAC-SHA256");
124 assert_eq!(payload.issued_at, 1624244156);
125 }
126 Err(err) => panic!("{}", err),
127 }
128 }
129
130 #[test]
131 fn test_hmac_sha256_payload() {
132 assert_eq!(
133 hex::encode(hmac_sha256_payload(b"value", "key").unwrap()),
134 "90fbfcf15e74a36b89dbdb2a721d9aecffdfdddc5c83e27f7592594f71932481"
135 );
136 }
137}