use wasm_bindgen::prelude::*;
#[cfg(feature = "console_error_panic_hook")]
pub fn set_panic_hook() {
console_error_panic_hook::set_once();
}
#[wasm_bindgen(js_name = "ashInit")]
pub fn ash_init() {
#[cfg(feature = "console_error_panic_hook")]
set_panic_hook();
}
#[wasm_bindgen(js_name = "ashCanonicalizeJson")]
pub fn ash_canonicalize_json(input: &str) -> Result<String, JsValue> {
ash_core::canonicalize_json(input).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = "ashCanonicalizeUrlencoded")]
pub fn ash_canonicalize_urlencoded(input: &str) -> Result<String, JsValue> {
ash_core::canonicalize_urlencoded(input).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = "ashBuildProof")]
pub fn ash_build_proof(
mode: &str,
binding: &str,
context_id: &str,
nonce: Option<String>,
canonical_payload: &str,
) -> Result<String, JsValue> {
let ash_mode: ash_core::AshMode = mode
.parse()
.map_err(|e: ash_core::AshError| JsValue::from_str(&e.to_string()))?;
ash_core::build_proof(
ash_mode,
binding,
context_id,
nonce.as_deref(),
canonical_payload,
)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = "ashVerifyProof")]
pub fn ash_verify_proof(expected: &str, actual: &str) -> bool {
ash_core::timing_safe_equal(expected.as_bytes(), actual.as_bytes())
}
#[wasm_bindgen(js_name = "ashCanonicalizeQuery")]
pub fn ash_canonicalize_query(query: &str) -> Result<String, JsValue> {
ash_core::canonicalize_query(query).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = "ashNormalizeBinding")]
pub fn ash_normalize_binding(method: &str, path: &str, query: &str) -> Result<String, JsValue> {
ash_core::normalize_binding(method, path, query).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = "ashNormalizeBindingFromUrl")]
pub fn ash_normalize_binding_from_url(method: &str, full_path: &str) -> Result<String, JsValue> {
ash_core::normalize_binding_from_url(method, full_path)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = "ashTimingSafeEqual")]
pub fn ash_timing_safe_equal(a: &str, b: &str) -> bool {
ash_core::timing_safe_equal(a.as_bytes(), b.as_bytes())
}
#[wasm_bindgen(js_name = "ashVersion")]
pub fn ash_version() -> String {
"ASHv2.1".to_string()
}
#[wasm_bindgen(js_name = "ashLibraryVersion")]
pub fn ash_library_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
#[wasm_bindgen(js_name = "canonicalizeJson")]
pub fn canonicalize_json(input: &str) -> Result<String, JsValue> {
ash_canonicalize_json(input)
}
#[wasm_bindgen(js_name = "canonicalizeUrlencoded")]
pub fn canonicalize_urlencoded(input: &str) -> Result<String, JsValue> {
ash_canonicalize_urlencoded(input)
}
#[wasm_bindgen(js_name = "buildProof")]
pub fn build_proof(
mode: &str,
binding: &str,
context_id: &str,
nonce: Option<String>,
canonical_payload: &str,
) -> Result<String, JsValue> {
ash_build_proof(mode, binding, context_id, nonce, canonical_payload)
}
#[wasm_bindgen(js_name = "verifyProof")]
pub fn verify_proof(expected: &str, actual: &str) -> bool {
ash_verify_proof(expected, actual)
}
#[wasm_bindgen(js_name = "normalizeBinding")]
pub fn normalize_binding(method: &str, path: &str, query: &str) -> Result<String, JsValue> {
ash_normalize_binding(method, path, query)
}
#[wasm_bindgen(js_name = "canonicalizeQuery")]
pub fn canonicalize_query(query: &str) -> Result<String, JsValue> {
ash_canonicalize_query(query)
}
#[wasm_bindgen(js_name = "normalizeBindingFromUrl")]
pub fn normalize_binding_from_url(method: &str, full_path: &str) -> Result<String, JsValue> {
ash_normalize_binding_from_url(method, full_path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_canonicalize_json() {
let result = ash_canonicalize_json(r#"{"z":1,"a":2}"#).unwrap();
assert_eq!(result, r#"{"a":2,"z":1}"#);
}
#[test]
fn test_canonicalize_urlencoded() {
let result = ash_canonicalize_urlencoded("z=1&a=2").unwrap();
assert_eq!(result, "a=2&z=1");
}
#[test]
fn test_build_and_verify_proof() {
let proof1 =
ash_build_proof("balanced", "POST /api/test", "ctx123", None, r#"{"a":1}"#).unwrap();
let proof2 =
ash_build_proof("balanced", "POST /api/test", "ctx123", None, r#"{"a":1}"#).unwrap();
assert!(ash_verify_proof(&proof1, &proof2));
}
#[test]
fn test_normalize_binding() {
let result = ash_normalize_binding("post", "/api//test/", "").unwrap();
assert_eq!(result, "POST|/api/test|");
}
#[test]
fn test_normalize_binding_with_query() {
let result = ash_normalize_binding("GET", "/api/users", "page=1&sort=name").unwrap();
assert_eq!(result, "GET|/api/users|page=1&sort=name");
}
#[test]
fn test_normalize_binding_from_url() {
let result = ash_normalize_binding_from_url("GET", "/api/search?z=3&a=1").unwrap();
assert_eq!(result, "GET|/api/search|a=1&z=3");
}
#[test]
fn test_canonicalize_query() {
let result = ash_canonicalize_query("z=3&a=1&b=2").unwrap();
assert_eq!(result, "a=1&b=2&z=3");
}
#[test]
fn test_version() {
assert_eq!(ash_version(), "ASHv2.1");
}
}
#[wasm_bindgen(js_name = "ashGenerateNonce")]
pub fn ash_generate_nonce(bytes: Option<usize>) -> String {
ash_core::generate_nonce(bytes.unwrap_or(32))
}
#[wasm_bindgen(js_name = "ashGenerateContextId")]
pub fn ash_generate_context_id() -> String {
ash_core::generate_context_id()
}
#[wasm_bindgen(js_name = "ashDeriveClientSecret")]
pub fn ash_derive_client_secret(nonce: &str, context_id: &str, binding: &str) -> String {
ash_core::derive_client_secret(nonce, context_id, binding)
}
#[wasm_bindgen(js_name = "ashBuildProofV21")]
pub fn ash_build_proof_v21(
client_secret: &str,
timestamp: &str,
binding: &str,
body_hash: &str,
) -> String {
ash_core::build_proof_v21(client_secret, timestamp, binding, body_hash)
}
#[wasm_bindgen(js_name = "ashVerifyProofV21")]
pub fn ash_verify_proof_v21(
nonce: &str,
context_id: &str,
binding: &str,
timestamp: &str,
body_hash: &str,
client_proof: &str,
) -> bool {
ash_core::verify_proof_v21(
nonce,
context_id,
binding,
timestamp,
body_hash,
client_proof,
)
}
#[wasm_bindgen(js_name = "ashHashBody")]
pub fn ash_hash_body(canonical_body: &str) -> String {
ash_core::hash_body(canonical_body)
}
#[wasm_bindgen(js_name = "ashBuildProofScoped")]
pub fn ash_build_proof_scoped(
client_secret: &str,
timestamp: &str,
binding: &str,
payload: &str,
scope: &str,
) -> Result<JsValue, JsValue> {
let scope_vec: Vec<&str> = if scope.is_empty() {
vec![]
} else {
scope.split(',').collect()
};
let (proof, scope_hash) =
ash_core::build_proof_v21_scoped(client_secret, timestamp, binding, payload, &scope_vec)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let result = serde_json::json!({
"proof": proof,
"scopeHash": scope_hash
});
Ok(JsValue::from_str(&result.to_string()))
}
#[allow(clippy::too_many_arguments)]
#[wasm_bindgen(js_name = "ashVerifyProofScoped")]
pub fn ash_verify_proof_scoped(
nonce: &str,
context_id: &str,
binding: &str,
timestamp: &str,
payload: &str,
scope: &str,
scope_hash: &str,
client_proof: &str,
) -> Result<bool, JsValue> {
let scope_vec: Vec<&str> = if scope.is_empty() {
vec![]
} else {
scope.split(',').collect()
};
ash_core::verify_proof_v21_scoped(
nonce,
context_id,
binding,
timestamp,
payload,
&scope_vec,
scope_hash,
client_proof,
)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = "ashHashScopedBody")]
pub fn ash_hash_scoped_body(payload: &str, scope: &str) -> Result<String, JsValue> {
let scope_vec: Vec<&str> = if scope.is_empty() {
vec![]
} else {
scope.split(',').collect()
};
ash_core::hash_scoped_body(payload, &scope_vec).map_err(|e| JsValue::from_str(&e.to_string()))
}
#[wasm_bindgen(js_name = "ashHashProof")]
pub fn ash_hash_proof(proof: &str) -> String {
ash_core::hash_proof(proof)
}
#[wasm_bindgen(js_name = "ashBuildProofUnified")]
pub fn ash_build_proof_unified(
client_secret: &str,
timestamp: &str,
binding: &str,
payload: &str,
scope: &str,
previous_proof: Option<String>,
) -> Result<JsValue, JsValue> {
let scope_vec: Vec<&str> = if scope.is_empty() {
vec![]
} else {
scope.split(',').collect()
};
let prev_proof = previous_proof.as_deref().filter(|s| !s.is_empty());
let result = ash_core::build_proof_v21_unified(
client_secret,
timestamp,
binding,
payload,
&scope_vec,
prev_proof,
)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let json_result = serde_json::json!({
"proof": result.proof,
"scopeHash": result.scope_hash,
"chainHash": result.chain_hash
});
Ok(JsValue::from_str(&json_result.to_string()))
}
#[allow(clippy::too_many_arguments)]
#[wasm_bindgen(js_name = "ashVerifyProofUnified")]
pub fn ash_verify_proof_unified(
nonce: &str,
context_id: &str,
binding: &str,
timestamp: &str,
payload: &str,
client_proof: &str,
scope: &str,
scope_hash: &str,
previous_proof: Option<String>,
chain_hash: &str,
) -> Result<bool, JsValue> {
let scope_vec: Vec<&str> = if scope.is_empty() {
vec![]
} else {
scope.split(',').collect()
};
let prev_proof = previous_proof.as_deref().filter(|s| !s.is_empty());
ash_core::verify_proof_v21_unified(
nonce,
context_id,
binding,
timestamp,
payload,
client_proof,
&scope_vec,
scope_hash,
prev_proof,
chain_hash,
)
.map_err(|e| JsValue::from_str(&e.to_string()))
}