mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-20 06:24:23 +01:00
feat(pubky): auth working everywhere except nodejs
This commit is contained in:
21
Cargo.lock
generated
21
Cargo.lock
generated
@@ -133,6 +133,7 @@ checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
@@ -205,6 +206,18 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.73"
|
||||
@@ -348,7 +361,7 @@ version = "4.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -884,6 +897,12 @@ dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
|
||||
@@ -5,7 +5,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.82"
|
||||
axum = "0.7.5"
|
||||
axum = { version = "0.7.5", features = ["macros"] }
|
||||
axum-extra = { version = "0.9.3", features = ["typed-header", "async-read-body"] }
|
||||
base32 = "0.5.1"
|
||||
bytes = "1.6.1"
|
||||
|
||||
@@ -41,10 +41,6 @@ pub fn create_app(state: AppState) -> Router {
|
||||
base(state.clone())
|
||||
// TODO: Only enable this for test environments?
|
||||
.nest("/pkarr", pkarr_router(state))
|
||||
.layer(
|
||||
CorsLayer::new()
|
||||
.allow_methods([Method::GET, Method::PUT, Method::POST, Method::DELETE])
|
||||
.allow_origin(cors::Any),
|
||||
)
|
||||
.layer(CorsLayer::very_permissive())
|
||||
.layer(TraceLayer::new_for_http())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use axum::{
|
||||
debug_handler,
|
||||
extract::{Request, State},
|
||||
http::{HeaderMap, StatusCode},
|
||||
http::{uri::Scheme, HeaderMap, StatusCode, Uri},
|
||||
response::IntoResponse,
|
||||
Router,
|
||||
};
|
||||
@@ -8,7 +9,7 @@ use axum_extra::{headers::UserAgent, TypedHeader};
|
||||
use bytes::Bytes;
|
||||
use heed::BytesEncode;
|
||||
use postcard::to_allocvec;
|
||||
use tower_cookies::{Cookie, Cookies};
|
||||
use tower_cookies::{cookie::SameSite, Cookie, Cookies};
|
||||
|
||||
use pubky_common::{
|
||||
crypto::{random_bytes, random_hash},
|
||||
@@ -26,16 +27,26 @@ use crate::{
|
||||
server::AppState,
|
||||
};
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn signup(
|
||||
State(state): State<AppState>,
|
||||
TypedHeader(user_agent): TypedHeader<UserAgent>,
|
||||
cookies: Cookies,
|
||||
pubky: Pubky,
|
||||
uri: Uri,
|
||||
body: Bytes,
|
||||
) -> Result<impl IntoResponse> {
|
||||
// TODO: Verify invitation link.
|
||||
// TODO: add errors in case of already axisting user.
|
||||
signin(State(state), TypedHeader(user_agent), cookies, pubky, body).await
|
||||
signin(
|
||||
State(state),
|
||||
TypedHeader(user_agent),
|
||||
cookies,
|
||||
pubky,
|
||||
uri,
|
||||
body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn session(
|
||||
@@ -57,6 +68,7 @@ pub async fn session(
|
||||
let session = session.to_owned();
|
||||
rtxn.commit()?;
|
||||
|
||||
// TODO: add content-type
|
||||
return Ok(session);
|
||||
};
|
||||
|
||||
@@ -95,6 +107,7 @@ pub async fn signin(
|
||||
TypedHeader(user_agent): TypedHeader<UserAgent>,
|
||||
cookies: Cookies,
|
||||
pubky: Pubky,
|
||||
uri: Uri,
|
||||
body: Bytes,
|
||||
) -> Result<impl IntoResponse> {
|
||||
let public_key = pubky.public_key();
|
||||
@@ -135,7 +148,15 @@ pub async fn signin(
|
||||
|
||||
sessions.put(&mut wtxn, &session_secret, &session.serialize())?;
|
||||
|
||||
cookies.add(Cookie::new(public_key.to_string(), session_secret));
|
||||
let mut cookie = Cookie::new(public_key.to_string(), session_secret);
|
||||
cookie.set_path("/");
|
||||
if *uri.scheme().unwrap_or(&Scheme::HTTP) == Scheme::HTTPS {
|
||||
cookie.set_secure(true);
|
||||
cookie.set_same_site(SameSite::None);
|
||||
}
|
||||
cookie.set_http_only(true);
|
||||
|
||||
cookies.add(cookie);
|
||||
|
||||
wtxn.commit()?;
|
||||
|
||||
|
||||
@@ -3,30 +3,28 @@ import test from 'tape'
|
||||
import { PubkyClient, Keypair, PublicKey } from '../index.js'
|
||||
|
||||
test('seed auth', async (t) => {
|
||||
const client = new PubkyClient()
|
||||
|
||||
let client = new PubkyClient();
|
||||
const keypair = Keypair.random()
|
||||
const publicKey = keypair.public_key()
|
||||
|
||||
let keypair = Keypair.random();
|
||||
const homeserver = PublicKey.try_from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
|
||||
await client.signup(keypair, homeserver)
|
||||
|
||||
let homeserver = PublicKey.try_from("8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo");
|
||||
await client.signup(keypair, homeserver);
|
||||
const session = await client.session(publicKey)
|
||||
t.ok(session)
|
||||
|
||||
t.ok(true);
|
||||
{
|
||||
await client.signout(publicKey)
|
||||
|
||||
// const session = await client.session()
|
||||
// t.ok(session)
|
||||
//
|
||||
// {
|
||||
// await client.logout(userId)
|
||||
//
|
||||
// const session = await client.session()
|
||||
// t.absent(session?.users?.[userId])
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// await client.login(seed)
|
||||
//
|
||||
// const session = await client.session()
|
||||
// t.ok(session?.users[userId])
|
||||
// }
|
||||
const session = await client.session(publicKey)
|
||||
t.notOk(session)
|
||||
}
|
||||
|
||||
{
|
||||
await client.signin(keypair)
|
||||
|
||||
const session = await client.session(publicKey)
|
||||
t.ok(session)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -12,9 +12,6 @@ pub enum Error {
|
||||
#[error("Generic error: {0}")]
|
||||
Generic(String),
|
||||
|
||||
#[error("Not signed in")]
|
||||
NotSignedIn,
|
||||
|
||||
// === Transparent ===
|
||||
#[error(transparent)]
|
||||
Dns(#[from] SimpleDnsError),
|
||||
@@ -29,7 +26,6 @@ pub enum Error {
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Session(#[from] pubky_common::session::Error),
|
||||
|
||||
#[error("Could not resolve endpoint for {0}")]
|
||||
@@ -37,7 +33,7 @@ pub enum Error {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
impl From<Error> for JsValue {
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
pub mod auth;
|
||||
pub mod pkarr;
|
||||
pub mod public;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use ::pkarr::{
|
||||
mainline::dht::{DhtSettings, Testnet},
|
||||
PkarrClient, PkarrClientAsync, Settings,
|
||||
PkarrClient, PublicKey, Settings, SignedPacket,
|
||||
};
|
||||
use pkarr::Keypair;
|
||||
use pubky_common::session::Session;
|
||||
use reqwest::{Method, RequestBuilder};
|
||||
use url::Url;
|
||||
|
||||
use crate::PubkyClient;
|
||||
use crate::{error::Result, PubkyClient};
|
||||
|
||||
static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
|
||||
|
||||
impl Default for PubkyClient {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PubkyClient {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
http: reqwest::Client::builder()
|
||||
.cookie_store(true)
|
||||
.user_agent(DEFAULT_USER_AGENT)
|
||||
.build()
|
||||
.unwrap(),
|
||||
@@ -25,7 +34,6 @@ impl PubkyClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn test(testnet: &Testnet) -> Self {
|
||||
Self {
|
||||
http: reqwest::Client::builder()
|
||||
@@ -45,10 +53,45 @@ impl PubkyClient {
|
||||
.as_async(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PubkyClient {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
/// Signup to a homeserver and update Pkarr accordingly.
|
||||
///
|
||||
/// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key
|
||||
/// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy"
|
||||
pub async fn signup(&self, keypair: &Keypair, homeserver: &PublicKey) -> Result<()> {
|
||||
self.inner_signup(keypair, homeserver).await
|
||||
}
|
||||
|
||||
/// Check the current sesison for a given Pubky in its homeserver.
|
||||
///
|
||||
/// Returns an [Error::NotSignedIn] if so, or [reqwest::Error] if
|
||||
/// the response has any other `>=400` status code.
|
||||
pub async fn session(&self, pubky: &PublicKey) -> Result<Option<Session>> {
|
||||
self.inner_session(pubky).await
|
||||
}
|
||||
|
||||
/// Signout from a homeserver.
|
||||
pub async fn signout(&self, pubky: &PublicKey) -> Result<()> {
|
||||
self.inner_signout(pubky).await
|
||||
}
|
||||
|
||||
/// Signin to a homeserver.
|
||||
pub async fn signin(&self, keypair: &Keypair) -> Result<()> {
|
||||
self.inner_signin(keypair).await
|
||||
}
|
||||
|
||||
pub(crate) async fn pkarr_resolve(
|
||||
&self,
|
||||
public_key: &PublicKey,
|
||||
) -> Result<Option<SignedPacket>> {
|
||||
Ok(self.pkarr.resolve(public_key).await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> {
|
||||
Ok(self.pkarr.publish(signed_packet).await?)
|
||||
}
|
||||
|
||||
pub(crate) fn request(&self, method: reqwest::Method, url: Url) -> RequestBuilder {
|
||||
self.http.request(method, url)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
use url::Url;
|
||||
|
||||
use pkarr::{
|
||||
dns::{rdata::SVCB, Packet},
|
||||
Keypair, PublicKey, SignedPacket,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
PubkyClient,
|
||||
};
|
||||
|
||||
impl PubkyClient {
|
||||
pub(crate) async fn pkarr_resolve(
|
||||
&self,
|
||||
public_key: &PublicKey,
|
||||
) -> Result<Option<SignedPacket>> {
|
||||
Ok(self.pkarr.resolve(public_key).await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn pkarr_publish(&self, signed_packet: &SignedPacket) -> Result<()> {
|
||||
Ok(self.pkarr.publish(signed_packet).await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use pkarr::{
|
||||
dns::{rdata::SVCB, Packet},
|
||||
mainline::{dht::DhtSettings, Testnet},
|
||||
Keypair, PkarrClient, Settings, SignedPacket,
|
||||
};
|
||||
use pubky_homeserver::Homeserver;
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_homeserver() {
|
||||
let testnet = Testnet::new(3);
|
||||
let server = Homeserver::start_test(&testnet).await.unwrap();
|
||||
|
||||
// Publish an intermediate controller of the homeserver
|
||||
let pkarr_client = PkarrClient::new(Settings {
|
||||
dht: DhtSettings {
|
||||
bootstrap: Some(testnet.bootstrap.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap()
|
||||
.as_async();
|
||||
|
||||
let intermediate = Keypair::random();
|
||||
|
||||
let mut packet = Packet::new_reply(0);
|
||||
|
||||
let server_tld = server.public_key().to_string();
|
||||
|
||||
let mut svcb = SVCB::new(0, server_tld.as_str().try_into().unwrap());
|
||||
|
||||
packet.answers.push(pkarr::dns::ResourceRecord::new(
|
||||
"pubky".try_into().unwrap(),
|
||||
pkarr::dns::CLASS::IN,
|
||||
60 * 60,
|
||||
pkarr::dns::rdata::RData::SVCB(svcb),
|
||||
));
|
||||
|
||||
let signed_packet = SignedPacket::from_packet(&intermediate, &packet).unwrap();
|
||||
|
||||
pkarr_client.publish(&signed_packet).await.unwrap();
|
||||
|
||||
{
|
||||
let client = PubkyClient::test(&testnet);
|
||||
|
||||
let pubky = Keypair::random();
|
||||
|
||||
client
|
||||
.publish_pubky_homeserver(&pubky, &format!("pubky.{}", &intermediate.public_key()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (public_key, url) = client
|
||||
.resolve_pubky_homeserver(&pubky.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(public_key, server.public_key());
|
||||
assert_eq!(url.host_str(), Some("localhost"));
|
||||
assert_eq!(url.port(), Some(server.port()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,12 +50,10 @@ fn normalize_path(path: &str) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::*;
|
||||
|
||||
use pkarr::{mainline::Testnet, Keypair};
|
||||
use pubky_common::session::Session;
|
||||
use pubky_homeserver::Homeserver;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -69,9 +67,10 @@ mod tests {
|
||||
|
||||
client.signup(&keypair, &server.public_key()).await.unwrap();
|
||||
|
||||
let response = client
|
||||
client
|
||||
.put(&keypair.public_key(), "/pub/foo.txt", &[0, 1, 2, 3, 4])
|
||||
.await;
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let response = client
|
||||
.get(&keypair.public_key(), "/pub/foo.txt")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use reqwest::StatusCode;
|
||||
use reqwest::{Method, StatusCode};
|
||||
|
||||
use pkarr::{Keypair, PublicKey};
|
||||
use pubky_common::{auth::AuthnSignature, session::Session};
|
||||
@@ -13,7 +13,11 @@ impl PubkyClient {
|
||||
///
|
||||
/// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key
|
||||
/// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy"
|
||||
pub async fn signup(&self, keypair: &Keypair, homeserver: &PublicKey) -> Result<()> {
|
||||
pub(crate) async fn inner_signup(
|
||||
&self,
|
||||
keypair: &Keypair,
|
||||
homeserver: &PublicKey,
|
||||
) -> Result<()> {
|
||||
let homeserver = homeserver.to_string();
|
||||
|
||||
let (audience, mut url) = self.resolve_endpoint(&homeserver).await?;
|
||||
@@ -24,7 +28,7 @@ impl PubkyClient {
|
||||
.as_bytes()
|
||||
.to_owned();
|
||||
|
||||
self.http.put(url).body(body).send().await?;
|
||||
self.request(Method::PUT, url).body(body).send().await?;
|
||||
|
||||
self.publish_pubky_homeserver(keypair, &homeserver).await?;
|
||||
|
||||
@@ -33,17 +37,17 @@ impl PubkyClient {
|
||||
|
||||
/// Check the current sesison for a given Pubky in its homeserver.
|
||||
///
|
||||
/// Returns an [Error::NotSignedIn] if so, or [reqwest::Error] if
|
||||
/// the response has any other `>=400` status code.
|
||||
pub async fn session(&self, pubky: &PublicKey) -> Result<Session> {
|
||||
let (homeserver, mut url) = self.resolve_pubky_homeserver(pubky).await?;
|
||||
/// Returns None if not signed in, or [reqwest::Error]
|
||||
/// if the response has any other `>=404` status code.
|
||||
pub(crate) async fn inner_session(&self, pubky: &PublicKey) -> Result<Option<Session>> {
|
||||
let (_, mut url) = self.resolve_pubky_homeserver(pubky).await?;
|
||||
|
||||
url.set_path(&format!("/{}/session", pubky));
|
||||
|
||||
let res = self.http.get(url).send().await?;
|
||||
let res = self.request(Method::GET, url).send().await?;
|
||||
|
||||
if res.status() == StatusCode::NOT_FOUND {
|
||||
return Err(Error::NotSignedIn);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if !res.status().is_success() {
|
||||
@@ -52,22 +56,22 @@ impl PubkyClient {
|
||||
|
||||
let bytes = res.bytes().await?;
|
||||
|
||||
Ok(Session::deserialize(&bytes)?)
|
||||
Ok(Some(Session::deserialize(&bytes)?))
|
||||
}
|
||||
|
||||
/// Signout from a homeserver.
|
||||
pub async fn signout(&self, pubky: &PublicKey) -> Result<()> {
|
||||
let (homeserver, mut url) = self.resolve_pubky_homeserver(pubky).await?;
|
||||
pub async fn inner_signout(&self, pubky: &PublicKey) -> Result<()> {
|
||||
let (_, mut url) = self.resolve_pubky_homeserver(pubky).await?;
|
||||
|
||||
url.set_path(&format!("/{}/session", pubky));
|
||||
|
||||
self.http.delete(url).send().await?;
|
||||
self.request(Method::DELETE, url).send().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Signin to a homeserver.
|
||||
pub async fn signin(&self, keypair: &Keypair) -> Result<()> {
|
||||
pub async fn inner_signin(&self, keypair: &Keypair) -> Result<()> {
|
||||
let pubky = keypair.public_key();
|
||||
|
||||
let (audience, mut url) = self.resolve_pubky_homeserver(&pubky).await?;
|
||||
@@ -78,7 +82,7 @@ impl PubkyClient {
|
||||
.as_bytes()
|
||||
.to_owned();
|
||||
|
||||
self.http.post(url).body(body).send().await?;
|
||||
self.request(Method::POST, url).body(body).send().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -86,11 +90,15 @@ impl PubkyClient {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::*;
|
||||
|
||||
use pkarr::{mainline::Testnet, Keypair};
|
||||
use pubky_common::session::Session;
|
||||
use pubky_homeserver::Homeserver;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tokio::test]
|
||||
async fn basic_authn() {
|
||||
@@ -103,27 +111,30 @@ mod tests {
|
||||
|
||||
client.signup(&keypair, &server.public_key()).await.unwrap();
|
||||
|
||||
let session = client.session(&keypair.public_key()).await.unwrap();
|
||||
let session = client
|
||||
.session(&keypair.public_key())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(session, Session { ..session.clone() });
|
||||
|
||||
client.signout(&keypair.public_key()).await.unwrap();
|
||||
|
||||
{
|
||||
let session = client.session(&keypair.public_key()).await;
|
||||
let session = client.session(&keypair.public_key()).await.unwrap();
|
||||
|
||||
assert!(session.is_err());
|
||||
|
||||
match session {
|
||||
Err(Error::NotSignedIn) => {}
|
||||
_ => panic!("expected NotSignedInt error"),
|
||||
}
|
||||
assert!(session.is_none());
|
||||
}
|
||||
|
||||
client.signin(&keypair).await.unwrap();
|
||||
|
||||
{
|
||||
let session = client.session(&keypair.public_key()).await.unwrap();
|
||||
let session = client
|
||||
.session(&keypair.public_key())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(session, Session { ..session.clone() });
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub mod auth;
|
||||
pub mod pkarr;
|
||||
@@ -77,7 +77,7 @@ impl PubkyClient {
|
||||
let response = self
|
||||
.pkarr_resolve(&public_key)
|
||||
.await
|
||||
.map_err(|e| Error::ResolveEndpoint(original_target.into()))?;
|
||||
.map_err(|_| Error::ResolveEndpoint(original_target.into()))?;
|
||||
|
||||
let mut prior = None;
|
||||
|
||||
@@ -132,3 +132,71 @@ impl PubkyClient {
|
||||
Err(Error::ResolveEndpoint(original_target.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use pkarr::{
|
||||
dns::{rdata::SVCB, Packet},
|
||||
mainline::{dht::DhtSettings, Testnet},
|
||||
Keypair, PkarrClient, Settings, SignedPacket,
|
||||
};
|
||||
use pubky_homeserver::Homeserver;
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_homeserver() {
|
||||
let testnet = Testnet::new(3);
|
||||
let server = Homeserver::start_test(&testnet).await.unwrap();
|
||||
|
||||
// Publish an intermediate controller of the homeserver
|
||||
let pkarr_client = PkarrClient::new(Settings {
|
||||
dht: DhtSettings {
|
||||
bootstrap: Some(testnet.bootstrap.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap()
|
||||
.as_async();
|
||||
|
||||
let intermediate = Keypair::random();
|
||||
|
||||
let mut packet = Packet::new_reply(0);
|
||||
|
||||
let server_tld = server.public_key().to_string();
|
||||
|
||||
let mut svcb = SVCB::new(0, server_tld.as_str().try_into().unwrap());
|
||||
|
||||
packet.answers.push(pkarr::dns::ResourceRecord::new(
|
||||
"pubky".try_into().unwrap(),
|
||||
pkarr::dns::CLASS::IN,
|
||||
60 * 60,
|
||||
pkarr::dns::rdata::RData::SVCB(svcb),
|
||||
));
|
||||
|
||||
let signed_packet = SignedPacket::from_packet(&intermediate, &packet).unwrap();
|
||||
|
||||
pkarr_client.publish(&signed_packet).await.unwrap();
|
||||
|
||||
{
|
||||
let client = PubkyClient::test(&testnet);
|
||||
|
||||
let pubky = Keypair::random();
|
||||
|
||||
client
|
||||
.publish_pubky_homeserver(&pubky, &format!("pubky.{}", &intermediate.public_key()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (public_key, url) = client
|
||||
.resolve_pubky_homeserver(&pubky.public_key())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(public_key, server.public_key());
|
||||
assert_eq!(url.host_str(), Some("localhost"));
|
||||
assert_eq!(url.port(), Some(server.port()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,66 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod auth;
|
||||
pub mod keys;
|
||||
pub mod pkarr;
|
||||
use reqwest::{Method, RequestBuilder};
|
||||
use url::Url;
|
||||
|
||||
use crate::PubkyClient;
|
||||
|
||||
mod keys;
|
||||
mod pkarr;
|
||||
mod session;
|
||||
|
||||
use keys::{Keypair, PublicKey};
|
||||
use session::Session;
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl PubkyClient {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
http: reqwest::Client::new(),
|
||||
// pkarr: pkarr::PkarrRelayClient::default(),
|
||||
http: reqwest::Client::builder().build().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Signup to a homeserver and update Pkarr accordingly.
|
||||
///
|
||||
/// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key
|
||||
/// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy"
|
||||
#[wasm_bindgen]
|
||||
pub async fn signup(&self, keypair: &Keypair, homeserver: &PublicKey) -> Result<(), JsValue> {
|
||||
self.inner_signup(keypair.as_inner(), homeserver.as_inner())
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Check the current sesison for a given Pubky in its homeserver.
|
||||
///
|
||||
/// Returns an [Error::NotSignedIn] if so, or [reqwest::Error] if
|
||||
/// the response has any other `>=400` status code.
|
||||
#[wasm_bindgen]
|
||||
pub async fn session(&self, pubky: &PublicKey) -> Result<Option<Session>, JsValue> {
|
||||
self.inner_session(pubky.as_inner())
|
||||
.await
|
||||
.map(|s| s.map(|s| Session(s).into()))
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Signout from a homeserver.
|
||||
#[wasm_bindgen]
|
||||
pub async fn signout(&self, pubky: &PublicKey) -> Result<(), JsValue> {
|
||||
self.inner_signout(pubky.as_inner())
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Signin to a homeserver.
|
||||
#[wasm_bindgen]
|
||||
pub async fn signin(&self, keypair: &Keypair) -> Result<(), JsValue> {
|
||||
self.inner_signin(keypair.as_inner())
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub(crate) fn request(&self, method: Method, url: Url) -> reqwest::RequestBuilder {
|
||||
self.http.request(method, url).fetch_credentials_include()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::RequestMode;
|
||||
|
||||
use reqwest::StatusCode;
|
||||
|
||||
use pkarr::PkarrRelayClient;
|
||||
|
||||
use pubky_common::{auth::AuthnSignature, session::Session};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
use super::{
|
||||
keys::{Keypair, PublicKey},
|
||||
PubkyClient,
|
||||
};
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl PubkyClient {
|
||||
/// Signup to a homeserver and update Pkarr accordingly.
|
||||
///
|
||||
/// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key
|
||||
/// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy"
|
||||
#[wasm_bindgen]
|
||||
pub async fn signup(&self, keypair: &Keypair, homeserver: &PublicKey) -> Result<(), JsValue> {
|
||||
let keypair = keypair.as_inner();
|
||||
let homeserver = homeserver.as_inner().to_string();
|
||||
|
||||
let (audience, mut url) = self.resolve_endpoint(&homeserver).await?;
|
||||
|
||||
url.set_path(&format!("/{}", keypair.public_key()));
|
||||
|
||||
let body = AuthnSignature::generate(keypair, &audience)
|
||||
.as_bytes()
|
||||
.to_owned();
|
||||
|
||||
self.http.put(url).body(body).send().await?;
|
||||
|
||||
self.publish_pubky_homeserver(keypair, &homeserver).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check the current sesison for a given Pubky in its homeserver.
|
||||
///
|
||||
/// Returns an [Error::NotSignedIn] if so, or [reqwest::Error] if
|
||||
/// the response has any other `>=400` status code.
|
||||
#[wasm_bindgen]
|
||||
pub async fn session(&self, pubky: &PublicKey) -> Result<Session, JsValue> {
|
||||
let (homeserver, mut url) = self.resolve_pubky_homeserver(pubky).await?;
|
||||
|
||||
url.set_path(&format!("/{}/session", pubky));
|
||||
|
||||
let res = self.http.get(url).send().await?;
|
||||
|
||||
if res.status() == StatusCode::NOT_FOUND {
|
||||
return Err(Error::NotSignedIn);
|
||||
}
|
||||
|
||||
if !res.status().is_success() {
|
||||
res.error_for_status_ref()?;
|
||||
};
|
||||
|
||||
let bytes = res.bytes().await?;
|
||||
|
||||
Ok(Session::deserialize(&bytes)?)
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ pub struct Keypair(pkarr::Keypair);
|
||||
impl Keypair {
|
||||
#[wasm_bindgen]
|
||||
/// Generate a random [Keypair]
|
||||
pub fn random(secret_key: js_sys::Uint8Array) -> Self {
|
||||
pub fn random() -> Self {
|
||||
Self(pkarr::Keypair::random())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
use reqwest::StatusCode;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub use pkarr::{
|
||||
dns::{rdata::SVCB, Packet},
|
||||
Keypair, PublicKey, SignedPacket,
|
||||
};
|
||||
pub use pkarr::{PublicKey, SignedPacket};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::error::Result;
|
||||
use crate::PubkyClient;
|
||||
|
||||
const TEST_RELAY: &str = "http://localhost:15411/pkarr";
|
||||
|
||||
// TODO: Add an in memory cache of packets
|
||||
|
||||
impl PubkyClient {
|
||||
//TODO: Allow multiple relays in parallel
|
||||
//TODO: migrate to pkarr::PkarrRelayClient
|
||||
|
||||
6
pubky/src/wasm/session.rs
Normal file
6
pubky/src/wasm/session.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use pubky_common::session;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Session(pub(crate) session::Session);
|
||||
Reference in New Issue
Block a user