Skip to content

Commit

Permalink
handle bearer token
Browse files Browse the repository at this point in the history
  • Loading branch information
LesnyRumcajs committed Apr 23, 2024
1 parent 5610f2a commit c6b52ed
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
Forest-specific RPC methods to `Forest`; `Filecoin.NetInfo` and
`Filecoin.StateFetchRoot` to `Forest.NetInfo` and `Forest.StateFetchRoot`.

- [#4262](https://github.com/ChainSafe/forest/pull/4262) Added `Bearer` prefix
to the `Authorization` header in the Forest RPC API. This is a
partially-breaking change - new Forest RPC clients will not work with old
Forest nodes. This change is necessary to align with the Lotus RPC API.

### Added

- [#4246](https://github.com/ChainSafe/forest/pull/4246) Add support for the
Expand Down
86 changes: 85 additions & 1 deletion src/rpc/auth_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,10 @@ async fn check_permissions(
) -> anyhow::Result<(), ErrorCode> {
let claims = match auth_header {
Some(token) => {
let token = token.to_str().map_err(|_| ErrorCode::ParseError)?;
let token = token
.to_str()
.map_err(|_| ErrorCode::ParseError)?
.trim_start_matches("Bearer ");

debug!("JWT from HTTP Header: {}", token);

Expand All @@ -275,3 +278,84 @@ async fn check_permissions(
None => Err(ErrorCode::MethodNotFound),
}
}

#[cfg(test)]
mod tests {
use self::chain::ChainHead;
use super::*;
use chrono::Duration;

#[tokio::test]
async fn check_permissions_no_header() {
let keystore = Arc::new(RwLock::new(
KeyStore::new(crate::KeyStoreConfig::Memory).unwrap(),
));

let res = check_permissions(keystore.clone(), None, ChainHead::NAME).await;
assert!(res.is_ok());

let res = check_permissions(keystore.clone(), None, "Cthulhu.InvokeElderGods").await;
assert_eq!(res.unwrap_err(), ErrorCode::MethodNotFound);

let res = check_permissions(keystore.clone(), None, wallet::WalletNew::NAME).await;
assert_eq!(res.unwrap_err(), ErrorCode::InvalidRequest);
}

#[tokio::test]
async fn check_permissions_invalid_header() {
let keystore = Arc::new(RwLock::new(
KeyStore::new(crate::KeyStoreConfig::Memory).unwrap(),
));

let auth_header = HeaderValue::from_static("Bearer Azathoth");
let res = check_permissions(keystore.clone(), Some(auth_header), ChainHead::NAME).await;
assert_eq!(res.unwrap_err(), ErrorCode::InvalidRequest);

let auth_header = HeaderValue::from_static("Cthulhu");
let res = check_permissions(keystore.clone(), Some(auth_header), ChainHead::NAME).await;
assert_eq!(res.unwrap_err(), ErrorCode::InvalidRequest);
}

#[tokio::test]
async fn check_permissions_valid_header() {
use crate::auth::*;
let keystore = Arc::new(RwLock::new(
KeyStore::new(crate::KeyStoreConfig::Memory).unwrap(),
));

// generate a key and store it in the keystore
let key_info = generate_priv_key();
keystore
.write()
.await
.put(JWT_IDENTIFIER, key_info.clone())
.unwrap();
let token_exp = Duration::hours(1);
let token = create_token(
ADMIN.iter().map(ToString::to_string).collect(),
key_info.private_key(),
token_exp,
)
.unwrap();

// Should work with the `Bearer` prefix
let auth_header = HeaderValue::from_str(&format!("Bearer {token}")).unwrap();
let res =
check_permissions(keystore.clone(), Some(auth_header.clone()), ChainHead::NAME).await;
assert!(res.is_ok());

let res = check_permissions(
keystore.clone(),
Some(auth_header.clone()),
wallet::WalletNew::NAME,
)
.await;
assert!(res.is_ok());

// Should work without the `Bearer` prefix
let auth_header = HeaderValue::from_str(&token).unwrap();
let res =
check_permissions(keystore.clone(), Some(auth_header), wallet::WalletNew::NAME).await;
assert!(res.is_ok());
}
}
9 changes: 4 additions & 5 deletions src/rpc/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,13 @@ impl UrlClient {
async fn new(url: Url, token: impl Into<Option<String>>) -> Result<Self, ClientError> {
let timeout = Duration::MAX; // we handle timeouts ourselves.
let headers = match token.into() {
Some(it) => HeaderMap::from_iter([(
Some(token) => HeaderMap::from_iter([(
header::AUTHORIZATION,
match HeaderValue::try_from(it) {
Ok(it) => it,
match HeaderValue::try_from(format!("Bearer {token}")) {
Ok(token) => token,
Err(e) => {
return Err(ClientError::Custom(format!(
"Invalid authorization token: {}",
e
"Invalid authorization token: {e}",
)))
}
},
Expand Down

0 comments on commit c6b52ed

Please sign in to comment.