1use crate::auth::{JWT_IDENTIFIER, verify_token};
5use crate::key_management::KeyStore;
6use crate::prelude::*;
7use crate::rpc::error::implementation_defined_errors::INSUFFICIENT_PERMISSIONS;
8use crate::rpc::{CANCEL_METHOD_NAME, Permission, RpcMethod as _, chain};
9use ahash::HashMap;
10use futures::future::Either;
11use http::header::HeaderValue;
12use jsonrpsee::MethodResponse;
13use jsonrpsee::core::middleware::{Batch, BatchEntry, BatchEntryErr, Notification};
14use jsonrpsee::server::middleware::rpc::RpcServiceT;
15use jsonrpsee::types::Id;
16use jsonrpsee::types::{ErrorObject, ErrorObjectOwned, error::ErrorCode};
17use parking_lot::RwLock;
18use std::sync::LazyLock;
19use tower::Layer;
20use tracing::debug;
21
22static METHOD_NAME2REQUIRED_PERMISSION: LazyLock<HashMap<&str, Permission>> = LazyLock::new(|| {
23 let mut access = HashMap::new();
24
25 macro_rules! insert {
26 ($ty:ty) => {
27 access.insert(<$ty>::NAME, <$ty>::PERMISSION);
28
29 if let Some(alias) = <$ty>::NAME_ALIAS {
30 access.insert(alias, <$ty>::PERMISSION);
31 }
32 };
33 }
34 super::for_each_rpc_method!(insert);
35
36 access.insert(chain::CHAIN_NOTIFY, Permission::Read);
37 access.insert(CANCEL_METHOD_NAME, Permission::Read);
38
39 access
40});
41
42fn permission_str(permission: Permission) -> &'static str {
45 match permission {
46 Permission::Admin => "admin",
47 Permission::Sign => "sign",
48 Permission::Write => "write",
49 Permission::Read => "read",
50 }
51}
52
53fn is_allowed(required_by_method: Permission, claimed_by_user: &[String]) -> bool {
54 let needle = permission_str(required_by_method);
55 claimed_by_user.iter().any(|haystack| haystack == needle)
56}
57
58#[derive(Clone)]
59pub struct AuthLayer {
60 claims: Arc<[String]>,
65}
66
67impl AuthLayer {
68 pub fn new(claims: Arc<[String]>) -> Self {
69 Self { claims }
70 }
71}
72
73impl<S> Layer<S> for AuthLayer {
74 type Service = Auth<S>;
75
76 fn layer(&self, service: S) -> Self::Service {
77 Auth {
78 claims: self.claims.clone(),
79 service,
80 }
81 }
82}
83
84#[derive(Clone)]
85pub struct Auth<S> {
86 claims: Arc<[String]>,
87 service: S,
88}
89
90impl<S> Auth<S> {
91 fn authorize(&self, method_name: &str) -> Result<(), ErrorObjectOwned> {
98 match METHOD_NAME2REQUIRED_PERMISSION.get(&method_name) {
99 None => Err(ErrorObject::from(ErrorCode::MethodNotFound)),
100 Some(&required) if is_allowed(required, &self.claims) => Ok(()),
101 Some(&required) => {
102 tracing::warn!("insufficient permissions to invoke method {method_name}");
103 Err(insufficient_permissions(method_name, required))
104 }
105 }
106 }
107}
108
109impl<S> RpcServiceT for Auth<S>
110where
111 S: RpcServiceT<
112 MethodResponse = MethodResponse,
113 NotificationResponse = MethodResponse,
114 BatchResponse = MethodResponse,
115 > + Send
116 + Sync
117 + Clone
118 + 'static,
119{
120 type MethodResponse = S::MethodResponse;
121 type NotificationResponse = S::NotificationResponse;
122 type BatchResponse = S::BatchResponse;
123
124 fn call<'a>(
125 &self,
126 req: jsonrpsee::types::Request<'a>,
127 ) -> impl Future<Output = Self::MethodResponse> + Send + 'a {
128 match self.authorize(req.method_name()) {
129 Ok(()) => Either::Left(self.service.call(req)),
130 Err(e) => Either::Right(async move { MethodResponse::error(req.id(), e) }),
131 }
132 }
133
134 fn notification<'a>(
135 &self,
136 n: Notification<'a>,
137 ) -> impl Future<Output = Self::NotificationResponse> + Send + 'a {
138 match self.authorize(n.method_name()) {
139 Ok(()) => Either::Left(self.service.notification(n)),
140 Err(e) => Either::Right(async move { MethodResponse::error(Id::Null, e) }),
141 }
142 }
143
144 fn batch<'a>(&self, batch: Batch<'a>) -> impl Future<Output = Self::BatchResponse> + Send + 'a {
145 let entries = batch
146 .into_iter()
147 .filter_map(|entry| match entry {
148 Ok(BatchEntry::Call(req)) => Some(match self.authorize(req.method_name()) {
149 Ok(()) => Ok(BatchEntry::Call(req)),
150 Err(e) => Err(BatchEntryErr::new(req.id(), e)),
151 }),
152 Ok(BatchEntry::Notification(n)) => match self.authorize(n.method_name()) {
153 Ok(_) => Some(Ok(BatchEntry::Notification(n))),
154 Err(_) => None,
155 },
156 Err(err) => Some(Err(err)),
157 })
158 .collect_vec();
159 self.service.batch(Batch::from(entries))
160 }
161}
162
163fn insufficient_permissions(method: &str, required: Permission) -> ErrorObjectOwned {
169 ErrorObject::owned(
170 INSUFFICIENT_PERMISSIONS,
171 format!(
172 "missing permission to invoke '{method}' (need '{}')",
173 permission_str(required)
174 ),
175 None::<()>,
176 )
177}
178
179fn auth_verify(token: &str, keystore: &RwLock<KeyStore>) -> anyhow::Result<Vec<String>> {
181 let key_info = keystore.read().get(JWT_IDENTIFIER)?;
182 Ok(verify_token(token, key_info.private_key())?)
183}
184
185pub(super) fn resolve_claims(
194 keystore: &RwLock<KeyStore>,
195 auth_header: Option<&HeaderValue>,
196) -> Result<Arc<[String]>, &'static str> {
197 let claims: Vec<String> = match auth_header {
198 Some(header) => {
199 let token = header
200 .to_str()
201 .map_err(|_| "malformed authorization header")?
202 .strip_prefix("Bearer ")
203 .ok_or("malformed authorization header")?;
204
205 auth_verify(token, keystore).map_err(|_| "invalid authorization token")?
206 }
207 None => vec!["read".to_owned()],
209 };
210 debug!("Decoded JWT permissions: {}", claims.join(","));
211 Ok(claims.into())
212}
213
214#[cfg(test)]
215mod tests {
216 use self::chain::ChainHead;
217 use super::*;
218 use crate::rpc::wallet;
219 use chrono::Duration;
220
221 fn empty_keystore() -> Arc<RwLock<KeyStore>> {
222 Arc::new(RwLock::new(
223 KeyStore::new(crate::KeyStoreConfig::Memory).unwrap(),
224 ))
225 }
226
227 fn keystore_with_token(perms: &[&str]) -> (Arc<RwLock<KeyStore>>, String) {
230 use crate::auth::*;
231 let keystore = empty_keystore();
232 let key_info = generate_priv_key();
233 keystore
234 .write()
235 .put(JWT_IDENTIFIER, key_info.clone())
236 .unwrap();
237 let token = create_token(
238 perms.iter().map(ToString::to_string).collect(),
239 key_info.private_key(),
240 Duration::hours(1),
241 )
242 .unwrap();
243 (keystore, token)
244 }
245
246 fn auth_with(claims: &[&str]) -> Auth<()> {
247 Auth {
248 claims: claims.iter().map(ToString::to_string).collect(),
249 service: (),
250 }
251 }
252
253 #[test]
256 fn resolve_claims_no_header_defaults_to_read() {
257 let claims = resolve_claims(&empty_keystore(), None).unwrap();
258 assert_eq!(&*claims, &["read".to_owned()]);
259 }
260
261 #[test]
262 fn resolve_claims_rejects_malformed_header() {
263 let keystore = empty_keystore();
264
265 let header = HeaderValue::from_bytes(&[0xff, 0xfe]).unwrap();
267 assert_eq!(
268 resolve_claims(&keystore, Some(&header)).unwrap_err(),
269 "malformed authorization header"
270 );
271
272 let header = HeaderValue::from_static("Cthulhu");
274 assert_eq!(
275 resolve_claims(&keystore, Some(&header)).unwrap_err(),
276 "malformed authorization header"
277 );
278 }
279
280 #[test]
281 fn resolve_claims_rejects_invalid_token() {
282 let header = HeaderValue::from_static("Bearer Azathoth");
283 assert_eq!(
284 resolve_claims(&empty_keystore(), Some(&header)).unwrap_err(),
285 "invalid authorization token"
286 );
287 }
288
289 #[test]
290 fn resolve_claims_accepts_valid_token() {
291 let (keystore, token) = keystore_with_token(crate::auth::ADMIN);
292
293 let header = HeaderValue::from_str(&format!("Bearer {token}")).unwrap();
294 let claims = resolve_claims(&keystore, Some(&header)).unwrap();
295 assert!(claims.iter().any(|c| c == "admin"));
296
297 let header = HeaderValue::from_str(&token).unwrap();
300 assert_eq!(
301 resolve_claims(&keystore, Some(&header)).unwrap_err(),
302 "malformed authorization header"
303 );
304
305 let header = HeaderValue::from_str(&format!("Bearer Bearer {token}")).unwrap();
308 assert_eq!(
309 resolve_claims(&keystore, Some(&header)).unwrap_err(),
310 "invalid authorization token"
311 );
312 }
313
314 #[test]
317 fn authorize_allows_methods_within_permissions() {
318 assert!(auth_with(&["read"]).authorize(ChainHead::NAME).is_ok());
319 }
320
321 #[test]
322 fn authorize_denies_insufficient_permissions_with_jsonrpc_error() {
323 let err = auth_with(&["read"])
324 .authorize(wallet::WalletNew::NAME)
325 .unwrap_err();
326 assert_eq!(err.code(), INSUFFICIENT_PERMISSIONS);
328 assert_eq!(
329 err.message(),
330 format!(
331 "missing permission to invoke '{}' (need 'write')",
332 wallet::WalletNew::NAME
333 )
334 );
335 }
336
337 #[test]
338 fn authorize_unknown_method_is_method_not_found() {
339 let err = auth_with(&["read"])
340 .authorize("Cthulhu.InvokeElderGods")
341 .unwrap_err();
342 assert_eq!(err.code(), ErrorCode::MethodNotFound.code());
343 }
344
345 #[test]
348 fn layer_propagates_resolved_claims() {
349 let (keystore, token) = keystore_with_token(crate::auth::ADMIN);
350 let header = HeaderValue::from_str(&format!("Bearer {token}")).unwrap();
351 let claims = resolve_claims(&keystore, Some(&header)).unwrap();
352
353 let auth = AuthLayer::new(claims).layer(());
354 assert!(auth.authorize(wallet::WalletNew::NAME).is_ok());
355 }
356}