mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-04 14:54:29 +01:00
fix(pubky): fix authz test with in process http relay
This commit is contained in:
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -1575,7 +1575,7 @@ checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||
[[package]]
|
||||
name = "mainline"
|
||||
version = "4.1.0"
|
||||
source = "git+https://github.com/pubky/mainline?branch=v5#2948896ac074cd5bf9728966dcd076de35e7e763"
|
||||
source = "git+https://github.com/pubky/mainline?branch=v5#c5e02270223bfa49a791a627a54348e0494762e5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"crc",
|
||||
@@ -1921,7 +1921,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
[[package]]
|
||||
name = "pkarr"
|
||||
version = "3.0.0"
|
||||
source = "git+https://github.com/Pubky/pkarr?branch=v3#c3510a82c5a3a496349d8dc030edd96f750f1471"
|
||||
source = "git+https://github.com/Pubky/pkarr?branch=v3#6821166474cd567a1e11f39e335b42c0029d4063"
|
||||
dependencies = [
|
||||
"base32",
|
||||
"byteorder",
|
||||
@@ -1958,7 +1958,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pkarr-relay"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Pubky/pkarr?branch=v3#c3510a82c5a3a496349d8dc030edd96f750f1471"
|
||||
source = "git+https://github.com/Pubky/pkarr?branch=v3#6821166474cd567a1e11f39e335b42c0029d4063"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -2063,11 +2063,14 @@ name = "pubky"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"axum-server",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"cookie_store",
|
||||
"futures-lite",
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"pkarr",
|
||||
"pubky-common",
|
||||
|
||||
@@ -41,6 +41,9 @@ web-sys = "0.3.76"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.94"
|
||||
axum = "0.7.9"
|
||||
axum-server = "0.7.1"
|
||||
futures-util = "0.3.31"
|
||||
pubky-homeserver = { path = "../pubky-homeserver" }
|
||||
tokio = "1.42.0"
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use pkarr::Keypair;
|
||||
use pubky_common::session::Session;
|
||||
use reqwest::IntoUrl;
|
||||
use tokio::sync::oneshot;
|
||||
use url::Url;
|
||||
|
||||
@@ -40,30 +41,21 @@ impl Client {
|
||||
/// Return `pubkyauth://` url and wait for the incoming [AuthToken]
|
||||
/// verifying that AuthToken, and if capabilities were requested, signing in to
|
||||
/// the Pubky's homeserver and returning the [Session] information.
|
||||
pub fn auth_request(
|
||||
pub fn auth_request<T: IntoUrl>(
|
||||
&self,
|
||||
relay: impl TryInto<Url>,
|
||||
relay: T,
|
||||
capabilities: &Capabilities,
|
||||
) -> Result<(Url, tokio::sync::oneshot::Receiver<PublicKey>)> {
|
||||
let mut relay: Url = relay
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::anyhow!("Invalid relay Url"))?;
|
||||
) -> Result<(Url, tokio::sync::oneshot::Receiver<Result<PublicKey>>)> {
|
||||
let mut relay: Url = relay.into_url()?;
|
||||
|
||||
let (pubkyauth_url, client_secret) = self.create_auth_request(&mut relay, capabilities)?;
|
||||
|
||||
let (tx, rx) = oneshot::channel::<PublicKey>();
|
||||
let (tx, rx) = oneshot::channel::<Result<PublicKey>>();
|
||||
|
||||
let this = self.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let to_send = this
|
||||
.subscribe_to_auth_response(relay, &client_secret)
|
||||
.await?;
|
||||
|
||||
tx.send(to_send)
|
||||
.map_err(|_| anyhow::anyhow!("Failed to send the session after signing in with token, since the receiver is dropped"))?;
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
tx.send(this.subscribe_to_auth_response(relay, &client_secret).await)
|
||||
});
|
||||
|
||||
Ok((pubkyauth_url, rx))
|
||||
@@ -71,17 +63,11 @@ impl Client {
|
||||
|
||||
/// Sign an [pubky_common::auth::AuthToken], encrypt it and send it to the
|
||||
/// source of the pubkyauth request url.
|
||||
pub async fn send_auth_token<T: TryInto<Url>>(
|
||||
pub async fn send_auth_token<T: IntoUrl>(
|
||||
&self,
|
||||
keypair: &Keypair,
|
||||
pubkyauth_url: T,
|
||||
) -> Result<()> {
|
||||
let url: Url = pubkyauth_url
|
||||
.try_into()
|
||||
.map_err(|_| anyhow::anyhow!("Invalid Url"))?;
|
||||
|
||||
self.inner_send_auth_token(keypair, url).await?;
|
||||
|
||||
Ok(())
|
||||
self.inner_send_auth_token(keypair, pubkyauth_url).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use base64::{alphabet::URL_SAFE, engine::general_purpose::NO_PAD, Engine};
|
||||
use reqwest::{Method, StatusCode};
|
||||
use reqwest::{IntoUrl, Method, StatusCode};
|
||||
use url::Url;
|
||||
|
||||
use pkarr::{Keypair, PublicKey};
|
||||
@@ -90,11 +90,18 @@ impl Client {
|
||||
self.signin_with_authtoken(&token).await
|
||||
}
|
||||
|
||||
pub(crate) async fn inner_send_auth_token(
|
||||
pub(crate) async fn inner_send_auth_token<T: IntoUrl>(
|
||||
&self,
|
||||
keypair: &Keypair,
|
||||
pubkyauth_url: Url,
|
||||
pubkyauth_url: T,
|
||||
) -> Result<()> {
|
||||
let pubkyauth_url = Url::parse(
|
||||
pubkyauth_url
|
||||
.as_str()
|
||||
.replace("pubkyauth_url", "http")
|
||||
.as_str(),
|
||||
)?;
|
||||
|
||||
let query_params: HashMap<String, String> =
|
||||
pubkyauth_url.query_pairs().into_owned().collect();
|
||||
|
||||
@@ -130,14 +137,14 @@ impl Client {
|
||||
|
||||
let engine = base64::engine::GeneralPurpose::new(&URL_SAFE, NO_PAD);
|
||||
|
||||
let mut callback = relay.clone();
|
||||
let mut path_segments = callback.path_segments_mut().unwrap();
|
||||
let mut callback_url = relay.clone();
|
||||
let mut path_segments = callback_url.path_segments_mut().unwrap();
|
||||
path_segments.pop_if_empty();
|
||||
let channel_id = engine.encode(hash(&client_secret).as_bytes());
|
||||
path_segments.push(&channel_id);
|
||||
drop(path_segments);
|
||||
|
||||
self.inner_request(Method::POST, callback)
|
||||
self.inner_request(Method::POST, callback_url)
|
||||
.await
|
||||
.body(encrypted_token)
|
||||
.send()
|
||||
@@ -196,7 +203,8 @@ impl Client {
|
||||
// TODO: use a clearnet client.
|
||||
let response = reqwest::get(relay).await?;
|
||||
let encrypted_token = response.bytes().await?;
|
||||
let token_bytes = decrypt(&encrypted_token, client_secret)?;
|
||||
let token_bytes = decrypt(&encrypted_token, client_secret)
|
||||
.map_err(|e| anyhow::anyhow!("Got invalid token: {e}"))?;
|
||||
let token = AuthToken::verify(&token_bytes)?;
|
||||
|
||||
if !token.capabilities().is_empty() {
|
||||
@@ -210,6 +218,8 @@ impl Client {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use crate::*;
|
||||
|
||||
use pkarr::{mainline::Testnet, Keypair};
|
||||
@@ -258,11 +268,107 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
async fn http_relay_server() -> Option<SocketAddr> {
|
||||
use axum::{
|
||||
body::{Body, Bytes},
|
||||
extract::{Path, State},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use axum_server::Handle;
|
||||
use futures_util::stream::StreamExt;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::SocketAddr,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tokio::sync::Notify;
|
||||
|
||||
// Shared state to store GET requests and their notifications
|
||||
type SharedState = Arc<Mutex<HashMap<String, (Vec<u8>, Arc<Notify>)>>>;
|
||||
let shared_state: SharedState = Arc::new(Mutex::new(HashMap::new()));
|
||||
|
||||
// Handler for the GET endpoint
|
||||
async fn subscribe(
|
||||
Path(id): Path<String>,
|
||||
State(state): State<SharedState>,
|
||||
) -> impl IntoResponse {
|
||||
// Create a notification for this ID
|
||||
let notify = Arc::new(Notify::new());
|
||||
|
||||
{
|
||||
let mut map = state.lock().unwrap();
|
||||
|
||||
// Store the notification and return it when POST arrives
|
||||
map.entry(id.clone())
|
||||
.or_insert_with(|| (vec![], notify.clone()));
|
||||
}
|
||||
|
||||
notify.notified().await;
|
||||
|
||||
// Respond with the data stored for this ID
|
||||
let map = state.lock().unwrap();
|
||||
if let Some((data, _)) = map.get(&id) {
|
||||
Bytes::from(data.clone()).into_response()
|
||||
} else {
|
||||
(axum::http::StatusCode::NOT_FOUND, "Not Found").into_response()
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for the POST endpoint
|
||||
async fn publish(
|
||||
Path(id): Path<String>,
|
||||
State(state): State<SharedState>,
|
||||
body: Body,
|
||||
) -> impl IntoResponse {
|
||||
// Aggregate the body into bytes
|
||||
let mut stream = body.into_data_stream();
|
||||
let mut bytes = vec![];
|
||||
while let Some(next) = stream.next().await {
|
||||
let chunk = next.map_err(|e| e.to_string()).unwrap();
|
||||
bytes.extend_from_slice(&chunk);
|
||||
}
|
||||
|
||||
// Notify any waiting GET request for this ID
|
||||
let mut map = state.lock().unwrap();
|
||||
if let Some((storage, notify)) = map.get_mut(&id) {
|
||||
*storage = bytes;
|
||||
notify.notify_one();
|
||||
Ok(())
|
||||
} else {
|
||||
Err((
|
||||
axum::http::StatusCode::NOT_FOUND,
|
||||
"No waiting GET request for this ID",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/:id", get(subscribe).post(publish))
|
||||
.with_state(shared_state);
|
||||
|
||||
let handle = Handle::new();
|
||||
|
||||
let cloned = handle.clone();
|
||||
tokio::spawn(async {
|
||||
axum_server::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
|
||||
.handle(cloned)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
handle.listening().await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn authz() {
|
||||
let testnet = Testnet::new(10).unwrap();
|
||||
let server = Homeserver::start_test(&testnet).await.unwrap();
|
||||
|
||||
let http_relay_url = http_relay_server().await.unwrap();
|
||||
|
||||
let keypair = Keypair::random();
|
||||
let pubky = keypair.public_key();
|
||||
|
||||
@@ -270,8 +376,12 @@ mod tests {
|
||||
let capabilities: Capabilities =
|
||||
"/pub/pubky.app/:rw,/pub/foo.bar/file:r".try_into().unwrap();
|
||||
let client = Client::test(&testnet);
|
||||
|
||||
let (pubkyauth_url, pubkyauth_response) = client
|
||||
.auth_request("https://demo.httprelay.io/link", &capabilities)
|
||||
.auth_request(
|
||||
&format!("http://{}", http_relay_url.to_string()),
|
||||
&capabilities,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Authenticator side
|
||||
@@ -286,7 +396,10 @@ mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let public_key = pubkyauth_response.await.unwrap();
|
||||
let public_key = pubkyauth_response
|
||||
.await
|
||||
.expect("sender to not be dropped")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(&public_key, &pubky);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user