diff --git a/examples/authz/3rd-party-app/src/pubky-auth-widget.js b/examples/authz/3rd-party-app/src/pubky-auth-widget.js index 09e73ef..4535840 100644 --- a/examples/authz/3rd-party-app/src/pubky-auth-widget.js +++ b/examples/authz/3rd-party-app/src/pubky-auth-widget.js @@ -33,9 +33,16 @@ export class PubkyAuthWidget extends LitElement { canvasRef = createRef(); constructor() { - // TODO: show error if the PubkyClient is not available! + if (!window.pubky) { + throw new Error("window.pubky is unavailable, make sure to import `@synonymdev/pubky` before this web component.") + } + super() + this.open = false; + + this.secret = window.pubky.randomBytes(32) + this.channelId = base64url(window.pubky.hash(this.secret)) } connectedCallback() { @@ -51,10 +58,10 @@ export class PubkyAuthWidget extends LitElement { ) : DEFAULT_HTTP_RELAY - const channel = Math.random().toString(32).slice(2); - callbackUrl.pathname = callbackUrl.pathname + "/" + channel + callbackUrl.pathname = callbackUrl.pathname + "/" + this.channelId - this.authUrl = `pubkyauth:///?cb=${callbackUrl.toString()}&caps=${this.caps}`; + this.authUrl = `pubkyauth://${callbackUrl.hostname + callbackUrl.pathname}?capabilities=${this.caps}&secret=${base64url(this.secret)}`; + console.log({ url: this.authUrl }); fetch(callbackUrl) .catch(error => console.error("PubkyAuthWidget: Failed to subscribe to http relay channel", error)) @@ -278,3 +285,16 @@ export class PubkyAuthWidget extends LitElement { } window.customElements.define('pubky-auth-widget', PubkyAuthWidget) + +function base64url(input) { + // Convert Uint8Array to a binary string + let binaryString = ''; + for (let i = 0; i < input.length; i++) { + binaryString += String.fromCharCode(input[i]); + } + + return btoa(binaryString) + .replace(/\+/g, '-') // Replace + with - + .replace(/\//g, '_') // Replace / with _ + .replace(/=+$/, '') // Remove padding (i.e., =) +} diff --git a/pubky-common/src/crypto.rs b/pubky-common/src/crypto.rs index a7adea5..82240d1 100644 --- a/pubky-common/src/crypto.rs +++ b/pubky-common/src/crypto.rs @@ -19,14 +19,15 @@ pub fn random_hash() -> Hash { Hash::from_bytes(rng.gen()) } -pub fn random_bytes() -> [u8; N] { +pub fn random_bytes(size: usize) -> Vec { let mut rng = rand::thread_rng(); - let mut arr = [0u8; N]; + let mut arr = vec![0u8; size]; #[allow(clippy::needless_range_loop)] - for i in 0..N { + for i in 0..size { arr[i] = rng.gen(); } + arr } diff --git a/pubky-homeserver/src/routes/auth.rs b/pubky-homeserver/src/routes/auth.rs index 9bfe5f8..a832e17 100644 --- a/pubky-homeserver/src/routes/auth.rs +++ b/pubky-homeserver/src/routes/auth.rs @@ -111,7 +111,7 @@ pub async fn signin( )?; } - let session_secret = base32::encode(base32::Alphabet::Crockford, &random_bytes::<16>()); + let session_secret = base32::encode(base32::Alphabet::Crockford, &random_bytes(16)); state.db.tables.sessions.put( &mut wtxn, diff --git a/pubky/pkg/README.md b/pubky/pkg/README.md index 3fcbcd1..6e33389 100644 --- a/pubky/pkg/README.md +++ b/pubky/pkg/README.md @@ -67,22 +67,6 @@ await client.delete(url); let client = new PubkyClient() ``` -#### createRecoveryFile -```js -let recoveryFile = PubkyClient.createRecoveryFile(keypair, passphrase) -``` -- keypair: An instance of [Keypair](#keypair). -- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/). -- Returns: A recovery file with a spec line and an encrypted secret key. - -#### createRecoveryFile -```js -let keypair = PubkyClient.decryptRecoveryfile(recoveryFile, passphrase) -``` -- recoveryFile: An instance of Uint8Array containing the recovery file blob. -- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/). -- Returns: An instance of [Keypair](#keypair). - #### signup ```js await client.signup(keypair, homeserver) @@ -172,6 +156,40 @@ let pubky = publicKey.z32(); ``` Returns: The z-base-32 encoded string representation of the PublicKey. +### Helper functions + +#### createRecoveryFile +```js +let recoveryFile = createRecoveryFile(keypair, passphrase) +``` +- keypair: An instance of [Keypair](#keypair). +- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/). +- Returns: A recovery file with a spec line and an encrypted secret key. + +#### createRecoveryFile +```js +let keypair = decryptRecoveryfile(recoveryFile, passphrase) +``` +- recoveryFile: An instance of Uint8Array containing the recovery file blob. +- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/). +- Returns: An instance of [Keypair](#keypair). + +#### randomBytes +```js +let bytes = randomBytes(size) +``` +Generates random bytes + +- size: The number of random bytes to be generated + +### hash +```js +let hash = hash(input) +``` +Creates a Blake3 hash of the input. + +- input: A Uint8Array input to be hashed. + ## Test and Development For test and development, you can run a local homeserver in a test network. diff --git a/pubky/src/wasm.rs b/pubky/src/wasm.rs index 047f2ac..8cce6f1 100644 --- a/pubky/src/wasm.rs +++ b/pubky/src/wasm.rs @@ -8,6 +8,7 @@ use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; use crate::PubkyClient; +mod crypto; mod http; mod keys; mod pkarr; diff --git a/pubky/src/wasm/crypto.rs b/pubky/src/wasm/crypto.rs new file mode 100644 index 0000000..8d7307a --- /dev/null +++ b/pubky/src/wasm/crypto.rs @@ -0,0 +1,17 @@ +use js_sys::Uint8Array; +use wasm_bindgen::prelude::wasm_bindgen; + +/// Generate random bytes. +#[wasm_bindgen(js_name = "randomBytes")] +pub fn random_bytes(size: usize) -> Uint8Array { + pubky_common::crypto::random_bytes(size).as_slice().into() +} + +/// Pubky hash function (blake3) +#[wasm_bindgen] +pub fn hash(input: &[u8]) -> Uint8Array { + pubky_common::crypto::hash(input) + .as_bytes() + .as_slice() + .into() +}