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}")))?)
}