unc_jsonrpc_client/
auth.rs

1//! Helpers for client authentication.
2//!
3//! Some RPC nodes will require authentication before requests can be sent to them.
4//!
5//! This module provides the [`ApiKey`] and [`Authorization`] types that can be used to authenticate
6//! requests.
7//!
8//! ## Example
9//!
10//! ### API Key (`x-api-key` Header)
11//!
12//! ```
13//! use unc_jsonrpc_client::{JsonRpcClient, auth, methods};
14//! use unc_primitives::types::{BlockReference, Finality};
15//!
16//! # #[tokio::main]
17//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
18//! let client = JsonRpcClient::connect("https://rpc.testnet.unc.org");
19//!
20//! let client = client.header(auth::ApiKey::new("399ba741-e939-4ffa-8c3c-306ec36fa8de")?);
21//!
22//! let request = methods::block::RpcBlockRequest {
23//!     block_reference: BlockReference::Finality(Finality::Final),
24//! };
25//!
26//! let response = client.call(request).await?;
27//!
28//! assert!(matches!(response, methods::block::RpcBlockResponse { .. }));
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! ### `Authorization` Header
34//!
35//! ```
36//! use unc_jsonrpc_client::{JsonRpcClient, auth, methods};
37//! use unc_primitives::types::{BlockReference, Finality};
38//!
39//! # #[tokio::main]
40//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
41//! let client = JsonRpcClient::connect("https://rpc.testnet.unc.org")
42//!     .header(auth::Authorization::bearer(
43//!         "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
44//!     )?);
45//!
46//! let request = methods::block::RpcBlockRequest {
47//!     block_reference: BlockReference::Finality(Finality::Final),
48//! };
49//!
50//! let response = client.call(request).await?;
51//!
52//! assert!(matches!(response, methods::block::RpcBlockResponse { .. }));
53//! # Ok(())
54//! # }
55//! ```
56
57use std::ops::{Index, RangeFrom};
58use std::str;
59
60use super::header::{HeaderValue, InvalidHeaderValue, ToStrError};
61
62/// unc JSON RPC API key.
63#[derive(Eq, Hash, Clone, Debug, PartialEq)]
64pub struct ApiKey(HeaderValue);
65
66impl ApiKey {
67    pub const HEADER_NAME: &'static str = "x-api-key";
68
69    /// Creates a new API key.
70    ///
71    /// See the [`auth`](self) module documentation for more information.
72    pub fn new<K: AsRef<[u8]>>(api_key: K) -> Result<Self, InvalidHeaderValue> {
73        HeaderValue::from_bytes(api_key.as_ref()).map(|mut api_key| {
74            ApiKey({
75                api_key.set_sensitive(true);
76                api_key
77            })
78        })
79    }
80
81    /// Returns a string slice if the API Key only contains visible ASCII chars.
82    pub fn to_str(&self) -> Result<&str, ToStrError> {
83        self.0.to_str()
84    }
85
86    /// Returns the API key as a byte slice.
87    pub fn as_bytes(&self) -> &[u8] {
88        self.0.as_bytes()
89    }
90}
91
92impl crate::header::HeaderEntry for ApiKey {
93    type HeaderName = &'static str;
94    type HeaderValue = HeaderValue;
95
96    fn header_name(&self) -> &Self::HeaderName {
97        &Self::HEADER_NAME
98    }
99
100    fn header_pair(self) -> (Self::HeaderName, Self::HeaderValue) {
101        (Self::HEADER_NAME, self.0)
102    }
103}
104
105/// HTTP authorization scheme.
106#[derive(Eq, Hash, Copy, Clone, Debug, PartialEq)]
107#[non_exhaustive]
108pub enum AuthorizationScheme {
109    Bearer,
110}
111
112/// unc JSON RPC authorization header.
113#[derive(Eq, Hash, Clone, Debug, PartialEq)]
114pub struct Authorization(AuthorizationScheme, HeaderValue);
115
116impl Authorization {
117    pub const HEADER_NAME: &'static str = "Authorization";
118
119    /// Creates a new authorization token with the bearer scheme.
120    ///
121    /// This does not perform any token-specific validation on the token.
122    ///
123    /// See the [`auth`](self) module documentation for more information.
124    pub fn bearer<T: AsRef<str>>(token: T) -> Result<Self, InvalidHeaderValue> {
125        HeaderValue::from_bytes(&[b"Bearer ", token.as_ref().as_bytes()].concat()).map(
126            |mut token| {
127                Authorization(AuthorizationScheme::Bearer, {
128                    token.set_sensitive(true);
129                    token
130                })
131            },
132        )
133    }
134
135    /// Returns the scheme of the authorization header.
136    pub fn scheme(&self) -> AuthorizationScheme {
137        self.0
138    }
139
140    /// Returns the token as a string slice.
141    pub fn as_str(&self) -> &str {
142        unsafe { str::from_utf8_unchecked(self.as_bytes()) }
143    }
144
145    /// Returns the token as a byte slice.
146    pub fn as_bytes(&self) -> &[u8] {
147        self.strip_scheme(self.1.as_bytes())
148    }
149
150    fn strip_scheme<'a, T: Index<RangeFrom<usize>> + ?Sized>(&self, token: &'a T) -> &'a T::Output {
151        &token[match self.0 {
152            AuthorizationScheme::Bearer => 7,
153        }..]
154    }
155}
156
157impl crate::header::HeaderEntry for Authorization {
158    type HeaderName = &'static str;
159    type HeaderValue = HeaderValue;
160
161    fn header_name(&self) -> &Self::HeaderName {
162        &Self::HEADER_NAME
163    }
164
165    fn header_pair(self) -> (Self::HeaderName, Self::HeaderValue) {
166        (Self::HEADER_NAME, self.1)
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn sensitive_debug() {
176        let api_key = ApiKey::new("this is a very secret secret").expect("valid API key");
177
178        assert_eq!(format!("{:?}", api_key), "ApiKey(Sensitive)");
179
180        assert_eq!(
181            api_key.to_str().expect("valid utf8 secret"),
182            "this is a very secret secret"
183        );
184
185        assert_eq!(api_key.as_bytes(), b"this is a very secret secret");
186    }
187
188    #[test]
189    fn bearer_token() {
190        let token = Authorization::bearer("this is a very secret token").expect("valid token");
191
192        assert_eq!(format!("{:?}", token), "Authorization(Bearer, Sensitive)");
193
194        assert_eq!(token.scheme(), AuthorizationScheme::Bearer);
195
196        assert_eq!(token.as_str(), "this is a very secret token");
197
198        assert_eq!(token.as_bytes(), b"this is a very secret token");
199    }
200}