oauth.rs (5314B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 use std::{borrow::Cow, time::SystemTime}; 18 19 use base64::{Engine as _, prelude::BASE64_STANDARD}; 20 use hmac::{Hmac, Mac}; 21 use percent_encoding::NON_ALPHANUMERIC; 22 use rand_core::RngCore; 23 use reqwest::{Request, header::HeaderValue}; 24 use serde::{Deserialize, Serialize}; 25 use sha1::Sha1; 26 27 #[derive(Debug, Clone, Serialize, Deserialize)] 28 pub struct Token { 29 #[serde(rename = "oauth_token")] 30 pub key: String, 31 #[serde(rename = "oauth_token_secret")] 32 pub secret: String, 33 } 34 35 #[derive(Debug, Deserialize)] 36 pub struct TokenAuth { 37 pub oauth_token: String, 38 pub oauth_verifier: String, 39 } 40 41 /** Generate a secure OAuth nonce */ 42 fn oauth_nonce() -> String { 43 // Generate 8 secure random bytes 44 let mut buf = [0u8; 8]; 45 rand_core::OsRng.fill_bytes(&mut buf); 46 // Encode as base64 string 47 BASE64_STANDARD.encode(buf) 48 } 49 50 /** Generate an OAuth timestamp */ 51 fn oauth_timestamp() -> u64 { 52 let start = SystemTime::now(); 53 let since_the_epoch = start 54 .duration_since(std::time::UNIX_EPOCH) 55 .expect("Time went backwards"); 56 57 since_the_epoch.as_secs() 58 } 59 60 /** Generate a valid OAuth Authorization header */ 61 fn oauth_header( 62 method: &reqwest::Method, 63 url: &reqwest::Url, 64 consumer: &Token, 65 access: Option<&Token>, 66 verifier: Option<&str>, 67 ) -> String { 68 // Per request value 69 let oauth_nonce = oauth_nonce(); 70 let oauth_timestamp = oauth_timestamp().to_string(); 71 72 // Base string 73 let base_string = { 74 let oauth_data = { 75 let mut oauth_query: Vec<(&str, &str)> = vec![ 76 ("oauth_consumer_key", &consumer.key), 77 ("oauth_nonce", &oauth_nonce), 78 ("oauth_signature_method", "HMAC-SHA1"), 79 ("oauth_timestamp", &oauth_timestamp), 80 ]; 81 if let Some(token) = &access { 82 oauth_query.push(("oauth_token", &token.key)); 83 } 84 if let Some(verifier) = &verifier { 85 oauth_query.push(("oauth_verifier", verifier)); 86 } 87 oauth_query.push(("oauth_version", "1.0")); 88 let mut all_query: Vec<_> = oauth_query 89 .into_iter() 90 .map(|(a, b)| (Cow::Borrowed(a), Cow::Borrowed(b))) 91 .chain(url.query_pairs()) 92 .collect(); 93 all_query.sort_unstable(); 94 95 let mut tmp: form_urlencoded::Serializer<'_, String> = 96 form_urlencoded::Serializer::new(String::new()); 97 for (k, v) in all_query { 98 tmp.append_pair(&k, &v); 99 } 100 tmp.finish() 101 }; 102 let mut stripped = url.clone(); 103 stripped.set_query(None); 104 form_urlencoded::Serializer::new(String::new()) 105 .append_key_only(method.as_str()) 106 .append_key_only(stripped.as_str()) 107 .append_key_only(&oauth_data) 108 .finish() 109 }; 110 111 // Signature 112 let key = { 113 let mut buf = consumer.secret.clone(); 114 buf.push('&'); 115 if let Some(token) = access { 116 buf.push_str(&token.secret); 117 } 118 buf 119 }; 120 let signature = Hmac::<Sha1>::new_from_slice(key.as_bytes()) 121 .expect("HMAC can take key of any size") 122 .chain_update(base_string.as_bytes()) 123 .finalize() 124 .into_bytes(); 125 let signature_encoded = BASE64_STANDARD.encode(signature); 126 127 // Authorization header 128 { 129 let mut buf = "OAuth ".to_string(); 130 let mut append = |key: &str, value: &str| { 131 buf.push_str(key); 132 buf.push_str("=\""); 133 for part in percent_encoding::percent_encode(value.as_bytes(), NON_ALPHANUMERIC) { 134 buf.push_str(part); 135 } 136 buf.push_str("\","); 137 }; 138 append("oauth_consumer_key", &consumer.key); 139 append("oauth_nonce", &oauth_nonce); 140 append("oauth_signature_method", "HMAC-SHA1"); 141 append("oauth_timestamp", &oauth_timestamp); 142 if let Some(token) = &access { 143 append("oauth_token", &token.key); 144 } 145 if let Some(verifier) = &verifier { 146 append("oauth_verifier", verifier); 147 } 148 append("oauth_version", "1.0"); 149 append("oauth_signature", &signature_encoded); 150 buf 151 } 152 } 153 154 /** Perform OAuth on an HTTP request */ 155 pub fn oauth(req: &mut Request, consumer: &Token, access: Option<&Token>, verifier: Option<&str>) { 156 let header = oauth_header(req.method(), req.url(), consumer, access, verifier); 157 req.headers_mut() 158 .append("Authorization", HeaderValue::from_str(&header).unwrap()); 159 }