timegraph-wasm 0.1.3

Timegraph WASM
Documentation
use js_sys::JsString;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use timegraph_identity::{bytes_decode, bytes_encode, ApiKey, Cert, Identity, Role, Signer};
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn version() -> JsString {
    "1.0.0".into()
}

#[derive(Serialize, Deserialize)]
pub struct SskIdentity {
    pub ns: u64,
    pub key: String,
    pub secret: Option<String>,
    pub expiration: u64,
    pub user_id: Option<u64>,
}

impl SskIdentity {
    fn identity(&self) -> Result<Identity, JsString> {
        let pk = Signer::pubkey_from_address(&self.key)
            .and_then(|x| bytes_encode(&x.to_bytes()))
            .map_err(|e| JsString::from(format!("bad key: {e}")))?;
        Ok(match self.user_id {
            Some(user_id) => Identity::User {
                subgraph_id: self.ns,
                user_id,
                key: pk,
                expiration: self.expiration,
                origins: "*".to_string(),
                signature: "".to_string(),
            },
            None => Identity::ApiKey {
                subgraph_id: self.ns,
                key: pk,
                expiration: self.expiration,
                origins: "*".to_string(),
                signature: "".to_string(),
            },
        })
    }
}

#[derive(Serialize, Deserialize)]
pub struct Ssk {
    pub ns: u64,
    pub key: String,
    pub expiration: u64,
    pub user_id: Option<u64>,
}

#[wasm_bindgen]
pub fn new_ssk(identity: JsValue) -> Result<JsString, JsString> {
    let data: SskIdentity = serde_wasm_bindgen::from_value(identity)
        .map_err(|e| JsString::from(format!("bad identity: {e}")))?;
    let mut key = data.identity()?;
    key.sign(
        &data
            .secret
            .ok_or(JsString::from("secret must be specified"))?,
    )
    .map_err(|e| JsString::from(format!("failed to sign session key: {e}")))?;
    key.to_ssk()
        .map(JsString::from)
        .map_err(|e| JsString::from(format!("failed to stringify session key: {e}")))
}

#[wasm_bindgen]
pub fn encode_ssk(identity: JsValue) -> Result<JsString, JsString> {
    let data: SskIdentity = serde_wasm_bindgen::from_value(identity)
        .map_err(|e| JsString::from(format!("bad identity: {e}")))?;
    let key = data.identity()?;
    key.stringify_as_ssk()
        .map(|(x, _)| JsString::from(x))
        .map_err(|e| JsString::from(format!("failed to encode SSK data: {e}")))
}

#[wasm_bindgen]
pub fn build_ssk(ssk_data: String, signature: JsValue) -> Result<JsString, JsString> {
    let signature: Vec<u8> = serde_wasm_bindgen::from_value(signature)
        .map_err(|e| JsString::from(format!("failed to decode signature: {e}")))?;
    let signature_part =
        bytes_encode(&signature).map_err(|e| JsString::from(format!("bad signature: {e}")))?;
    let ssk = ssk_data + &signature_part;
    Identity::from_ssk(&ssk)
        .map_err(|e| JsString::from(format!("failed to unpack session key: {e}")))?;
    Ok(ssk.into())
}

#[wasm_bindgen]
pub fn verify_ssk(js_ssk: String) -> Result<JsValue, JsString> {
    let identity = Identity::from_ssk(&js_ssk)
        .map_err(|e| JsString::from(format!("failed to unpack session key: {e}")))?;
    Ok(match identity {
        Identity::ApiKey {
            subgraph_id,
            key,
            expiration,
            ..
        } => serde_wasm_bindgen::to_value(&Ssk {
            ns: subgraph_id,
            key,
            expiration,
            user_id: None,
        }),
        Identity::User {
            subgraph_id,
            key,
            expiration,
            user_id,
            ..
        } => serde_wasm_bindgen::to_value(&Ssk {
            ns: subgraph_id,
            key,
            expiration,
            user_id: Some(user_id),
        }),
    }
    .map_err(|e| JsString::from(format!("failed to represent session key: {e}")))?)
}

#[wasm_bindgen]
pub fn get_pubkey_of(sk: String) -> Result<JsString, JsString> {
    bytes_decode(&sk)
        .and_then(|x| Signer::from_bytes(&x))
        .and_then(|x| x.stringify_public_key())
        .map(JsString::from)
        .map_err(|e| JsString::from(format!("failed to decode private key: {e}")))
}

#[derive(Serialize, Deserialize)]
pub struct JsApiKey {
    pub key: String,
    pub secret: String,
    pub cert: String,
}

#[wasm_bindgen]
pub fn new_apikey(sk: String, role: String) -> Result<JsValue, JsString> {
    let owner = bytes_decode(&sk)
        .and_then(|x| Signer::from_bytes(&x))
        .map_err(|e| JsString::from(format!("failed to decode private key: {e}")))?;
    let ak =
        ApiKey::new().map_err(|e| JsString::from(format!("failed to generate key pair: {e}")))?;
    let r = Role::from_str(&role)
        .map_err(|e| JsString::from(format!("failed to recognize role: {e}")))?;
    let cert = ak
        .certify(r, &owner)
        .map_err(|e| JsString::from(format!("failed to certify apikey: {e}")))?;
    make_apikey(&ak, &cert)
}

fn make_apikey(ak: &ApiKey, cert: &Cert) -> Result<JsValue, JsString> {
    Ok(serde_wasm_bindgen::to_value(&JsApiKey {
        key: ak
            .key()
            .map_err(|e| JsString::from(format!("failed to stringify apikey: {e}")))?,
        secret: ak
            .secret()
            .map_err(|e| JsString::from(format!("failed to stringify apikey: {e}")))?,
        cert: cert
            .stringify()
            .map_err(|e| JsString::from(format!("failed to stringify cert: {e}")))?,
    })
    .map_err(|e| JsString::from(format!("failed to represent apikey: {e}")))?)
}

#[derive(Serialize, Deserialize)]
pub struct JsCert {
    pub owner: String,
    pub delegate: String,
    pub role: String,
}

#[wasm_bindgen]
pub fn pubkey_as_address(key: String) -> Result<JsString, JsString> {
    let pubkey = Signer::pubkey_from_bytes(&key.as_bytes())
        .map_err(|e| JsString::from(format!("failed to decode key: {e}")))?;
    Signer::pubkey_to_address(&pubkey)
        .map(JsString::from)
        .map_err(|e| {
            JsString::from(format!(
                "failed to construct address for the public key: {e}"
            ))
        })
}

#[wasm_bindgen]
pub fn address_as_pubkey(address: String) -> Result<JsString, JsString> {
    let pubkey = Signer::pubkey_from_address(&address)
        .map_err(|e| JsString::from(format!("failed to decode wallet: {e}")))?;
    bytes_encode(&pubkey.to_bytes())
        .map(JsString::from)
        .map_err(|e| JsString::from(format!("failed to encode public key: {e}")))
}

#[wasm_bindgen]
pub fn verify_cert(cert: String) -> Result<JsValue, JsString> {
    let cert = Cert::verify_str(&cert)
        .map_err(|e| JsString::from(format!("failed to verify cert: {e}")))?;
    Ok(serde_wasm_bindgen::to_value(&JsCert {
        owner: bytes_encode(&cert.owner.to_bytes())
            .map_err(|e| JsString::from(format!("failed to encode owner: {e}")))?,
        delegate: bytes_encode(&cert.delegate.to_bytes())
            .map_err(|e| JsString::from(format!("failed to encode delegate: {e}")))?,
        role: cert.role.to_string(),
    })
    .map_err(|e| JsString::from(format!("failed to represent cert: {e}")))?)
}

#[wasm_bindgen]
pub fn new_cert(owner: String, role: String) -> Result<JsValue, JsString> {
    let owner = Signer::pubkey_from_address(&owner)
        .map_err(|e| JsString::from(format!("failed to decode private key: {e}")))?;
    let ak =
        ApiKey::new().map_err(|e| JsString::from(format!("failed to generate key pair: {e}")))?;
    let r = Role::from_str(&role)
        .map_err(|e| JsString::from(format!("failed to recognize role: {e}")))?;
    let cert = ak
        .build(r, &owner)
        .map_err(|e| JsString::from(format!("failed to build apikey certificate: {e}")))?;
    let secret = ak
        .secret()
        .map_err(|e| JsString::from(format!("failed to stringify secret: {e}")))?;
    Ok(serde_wasm_bindgen::to_value(&(&cert, secret))
        .map_err(|e| JsString::from(format!("failed to serialize the cert and secret: {e}")))?)
}

#[wasm_bindgen]
pub fn build_apikey(
    secret: String,
    cert: JsValue,
    signature: JsValue,
) -> Result<JsValue, JsString> {
    let signature: Vec<u8> = serde_wasm_bindgen::from_value(signature)
        .map_err(|e| JsString::from(format!("failed to decode signature: {e}")))?;
    let cert: Vec<u8> = serde_wasm_bindgen::from_value(cert)
        .map_err(|e| JsString::from(format!("failed to decode cert: {e}")))?;
    let cert = Cert::complete(&cert, &signature)
        .map_err(|e| JsString::from(format!("failed to build apikey certificate: {e}")))?;
    cert.verify()
        .map_err(|e| JsString::from(format!("invalid certificate found: {e}")))?;
    let ak = &JsApiKey {
        key: cert
            .stringify_delegate()
            .map_err(|e| JsString::from(format!("failed to stringify apikey: {e}")))?,
        secret: secret,
        cert: cert
            .stringify()
            .map_err(|e| JsString::from(format!("failed to stringify cert: {e}")))?,
    };
    Ok(serde_wasm_bindgen::to_value(ak)
        .map_err(|e| JsString::from(format!("failed to represent apikey: {e}")))?)
}