Skip to content

Commit 2987c04

Browse files
committed
Add cookie file for auth
1 parent 138e4eb commit 2987c04

File tree

7 files changed

+139
-93
lines changed

7 files changed

+139
-93
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ clap = { version = "4.5.6", features = ["derive", "env"] }
2727
log = "0.4.21"
2828
serde = { version = "1.0.200", features = ["derive"] }
2929
hex = "0.4.3"
30+
rand = "0.8"
3031
jsonrpsee = { version = "0.22.5", features = ["server", "http-client", "macros"] }
3132
directories = "5.0.1"
3233
env_logger = "0.11.3"

client/src/auth.rs

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1+
use base64::Engine;
2+
use hyper::{Body, HeaderMap, Request, Response, StatusCode};
13
use std::{
24
error::Error,
35
future::Future,
46
pin::Pin,
57
sync::Arc,
68
task::{Context, Poll},
79
};
8-
use base64::Engine;
9-
use hyper::{Body, Request, Response, StatusCode, HeaderMap};
1010
use tower::{Layer, Service};
1111

1212
#[derive(Debug, Clone)]
1313
pub(crate) struct BasicAuthLayer {
14-
token: Option<String>,
14+
token: String,
1515
}
1616

1717
impl BasicAuthLayer {
18-
pub fn new(token: Option<String>) -> Self {
18+
pub fn new(token: String) -> Self {
1919
Self { token }
2020
}
2121
}
@@ -31,37 +31,23 @@ impl<S> Layer<S> for BasicAuthLayer {
3131
#[derive(Debug, Clone)]
3232
pub(crate) struct BasicAuth<S> {
3333
inner: S,
34-
token: Option<Arc<str>>,
34+
token: Arc<str>,
3535
}
3636

3737
impl<S> BasicAuth<S> {
38-
pub fn new(inner: S, token: Option<String>) -> Self {
38+
pub fn new(inner: S, token: String) -> Self {
3939
Self {
4040
inner,
41-
token: token.map(|t| Arc::from(t.as_str())),
41+
token: Arc::from(token.as_str()),
4242
}
4343
}
4444

4545
fn check_auth(&self, headers: &HeaderMap) -> bool {
46-
let Some(expected_token) = &self.token else {
47-
return true;
48-
};
49-
50-
let auth_header = match headers.get("authorization") {
51-
Some(header) => header,
52-
None => return false,
53-
};
54-
55-
let auth_str = match auth_header.to_str() {
56-
Ok(s) => s,
57-
Err(_) => return false,
58-
};
59-
60-
if let Some(token_part) = auth_str.strip_prefix("Basic ") {
61-
token_part == expected_token.as_ref()
62-
} else {
63-
false
64-
}
46+
headers
47+
.get("authorization")
48+
.and_then(|h| h.to_str().ok())
49+
.and_then(|s| s.strip_prefix("Basic "))
50+
.map_or(false, |token| token == self.token.as_ref())
6551
}
6652

6753
fn unauthorized_response() -> Response<Body> {
@@ -82,7 +68,8 @@ where
8268
{
8369
type Response = S::Response;
8470
type Error = Box<dyn Error + Send + Sync + 'static>;
85-
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
71+
type Future =
72+
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
8673

8774
#[inline]
8875
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
@@ -96,13 +83,19 @@ where
9683
}
9784

9885
let fut = self.inner.call(req);
99-
let res_fut = async move {
100-
fut.await.map_err(|err| err.into())
101-
};
86+
let res_fut = async move { fut.await.map_err(|err| err.into()) };
10287
Box::pin(res_fut)
10388
}
10489
}
10590

106-
pub fn basic_auth_token(user: &str, password: &str) -> String {
107-
base64::prelude::BASE64_STANDARD.encode(format!("{user}:{password}"))
108-
}
91+
pub fn auth_cookie(user: &str, password: &str) -> String {
92+
format!("{user}:{password}")
93+
}
94+
95+
pub fn auth_token_from_cookie(cookie: &str) -> String {
96+
base64::prelude::BASE64_STANDARD.encode(cookie)
97+
}
98+
99+
pub fn auth_token_from_creds(user: &str, password: &str) -> String {
100+
base64::prelude::BASE64_STANDARD.encode(auth_cookie(user, password))
101+
}

client/src/bin/space-cli.rs

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@ use jsonrpsee::{
2020
};
2121
use serde::{Deserialize, Serialize};
2222
use spaces_client::{
23-
auth::basic_auth_token,
24-
config::{default_spaces_rpc_port, ExtendedNetwork},
23+
auth::{auth_token_from_cookie, auth_token_from_creds},
24+
config::{default_cookie_path, default_spaces_rpc_port, ExtendedNetwork},
2525
deserialize_base64,
2626
format::{
2727
print_error_rpc_response, print_list_bidouts, print_list_spaces_response,
28-
print_list_transactions, print_list_unspent, print_server_info,
29-
print_list_wallets, print_wallet_balance_response, print_wallet_info, print_wallet_response,
30-
Format,
28+
print_list_transactions, print_list_unspent, print_list_wallets, print_server_info,
29+
print_wallet_balance_response, print_wallet_info, print_wallet_response, Format,
3130
},
3231
rpc::{
3332
BidParams, ExecuteParams, OpenParams, RegisterParams, RpcClient, RpcWalletRequest,
@@ -55,6 +54,9 @@ pub struct Args {
5554
/// Spaced RPC URL [default: based on specified chain]
5655
#[arg(long)]
5756
spaced_rpc_url: Option<String>,
57+
/// Spaced RPC cookie file path
58+
#[arg(long, env = "SPACED_RPC_COOKIE")]
59+
rpc_cookie: Option<PathBuf>,
5860
/// Spaced RPC user
5961
#[arg(long, requires = "rpc_password", env = "SPACED_RPC_USER")]
6062
rpc_user: Option<String>,
@@ -394,16 +396,35 @@ impl SpaceCli {
394396
args.spaced_rpc_url = Some(default_spaced_rpc_url(&args.chain));
395397
}
396398

397-
let client = HttpClientBuilder::default();
398-
let client = if args.rpc_user.is_some() {
399-
let token = basic_auth_token(args.rpc_user.as_ref().unwrap(), args.rpc_password.as_ref().unwrap());
400-
let mut headers = hyper::http::HeaderMap::new();
401-
headers.insert("Authorization", hyper::http::HeaderValue::from_str(&format!("Basic {token}")).unwrap());
402-
client.set_headers(headers)
399+
let auth_token = if args.rpc_user.is_some() {
400+
auth_token_from_creds(
401+
args.rpc_user.as_ref().unwrap(),
402+
args.rpc_password.as_ref().unwrap(),
403+
)
403404
} else {
404-
client
405+
let cookie_path = match &args.rpc_cookie {
406+
Some(path) => path,
407+
None => &default_cookie_path(&args.chain),
408+
};
409+
let cookie = fs::read_to_string(cookie_path).map_err(|e| {
410+
anyhow!(
411+
"Failed to read cookie file '{}': {}",
412+
cookie_path.display(),
413+
e
414+
)
415+
})?;
416+
auth_token_from_cookie(&cookie)
417+
};
418+
let client = {
419+
let mut headers = hyper::http::HeaderMap::new();
420+
headers.insert(
421+
"Authorization",
422+
hyper::http::HeaderValue::from_str(&format!("Basic {auth_token}")).unwrap(),
423+
);
424+
HttpClientBuilder::default()
425+
.set_headers(headers)
426+
.build(args.spaced_rpc_url.clone().unwrap())?
405427
};
406-
let client = client.build(args.spaced_rpc_url.clone().unwrap())?;
407428

408429
Ok((
409430
Self {

client/src/config.rs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,23 @@ use std::{
55
path::PathBuf,
66
};
77

8-
use clap::{
9-
ArgGroup, Parser, ValueEnum,
8+
use rand::{
9+
distributions::Alphanumeric,
10+
{thread_rng, Rng},
1011
};
12+
13+
use clap::{ArgGroup, Parser, ValueEnum};
1114
use directories::ProjectDirs;
1215
use jsonrpsee::core::Serialize;
1316
use log::error;
1417
use serde::Deserialize;
1518
use spaces_protocol::bitcoin::Network;
1619

1720
use crate::{
18-
auth::basic_auth_token,
21+
auth::{auth_token_from_cookie, auth_token_from_creds},
1922
source::{BitcoinRpc, BitcoinRpcAuth},
20-
store::{LiveStore, Store},
2123
spaces::Spaced,
24+
store::{LiveStore, Store},
2225
};
2326

2427
const RPC_OPTIONS: &str = "RPC Server Options";
@@ -140,9 +143,21 @@ impl Args {
140143
.collect();
141144

142145
let auth_token = if args.rpc_user.is_some() {
143-
Some(basic_auth_token(args.rpc_user.as_ref().unwrap(), args.rpc_password.as_ref().unwrap()))
146+
auth_token_from_creds(
147+
args.rpc_user.as_ref().unwrap(),
148+
args.rpc_password.as_ref().unwrap(),
149+
)
144150
} else {
145-
None
151+
let cookie = format!(
152+
"__cookie__:{}",
153+
thread_rng()
154+
.sample_iter(&Alphanumeric)
155+
.take(64)
156+
.map(char::from)
157+
.collect::<String>()
158+
);
159+
fs::write(data_dir.join(".cookie"), &cookie)?;
160+
auth_token_from_cookie(&cookie)
146161
};
147162

148163
let bitcoin_rpc_auth = if let Some(cookie) = args.bitcoin_rpc_cookie {
@@ -228,6 +243,13 @@ fn get_default_node_dirs() -> ProjectDirs {
228243
})
229244
}
230245

246+
pub fn default_cookie_path(network: &ExtendedNetwork) -> PathBuf {
247+
get_default_node_dirs()
248+
.data_dir()
249+
.join(network.to_string())
250+
.join(".cookie")
251+
}
252+
231253
// from clap utilities
232254
pub fn safe_exit(code: i32) -> ! {
233255
use std::io::Write;

0 commit comments

Comments
 (0)