flaron-sdk 0.99.0

Official Rust SDK for writing Flaron edge flares - WebAssembly modules that run on the Flaron CDN edge runtime.
Documentation
//! Read domain-scoped secrets allowlisted for this flare.
//!
//! Secrets are stored per-domain on the edge node and gated by an explicit
//! allowlist on each flare. A flare can only read secrets whose names appear
//! in its `allowed_secrets` config field; any other lookup returns `None`
//! (and the host logs a warning).
//!
//! Use this for API keys you need to construct an outbound [`crate::beam`]
//! request, signing material the host's [`crate::crypto`] helpers don't
//! cover, or anything else where the secret value must touch flare code.
//!
//! ## Don't read secrets to compute HMACs / sign JWTs
//!
//! The [`crate::crypto`] HMAC and JWT helpers reference secrets by name on
//! the host side without ever exposing the bytes to Wasm. Prefer those over
//! reading the secret with [`get`] and computing the signature in-flare -
//! it's faster, safer, and harder to leak.

use crate::{ffi, mem};

/// Read a secret by name.
///
/// Returns `None` when:
/// * the secret name is not in this flare's `allowed_secrets` allowlist,
/// * the secret does not exist for this domain, or
/// * the secrets store is not configured on this edge.
pub fn get(name: &str) -> Option<String> {
    let (name_ptr, name_len) = mem::host_arg_str(name);
    let result = unsafe { ffi::secret_get(name_ptr, name_len) };
    // SAFETY: host writes the secret value (UTF-8) into the bump arena.
    unsafe { mem::read_packed_string(result) }
}

/// Convenience: read a secret as raw bytes.
pub fn get_bytes(name: &str) -> Option<Vec<u8>> {
    let (name_ptr, name_len) = mem::host_arg_str(name);
    let result = unsafe { ffi::secret_get(name_ptr, name_len) };
    // SAFETY: host writes the secret bytes into the bump arena.
    unsafe { mem::read_packed_bytes(result) }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ffi::test_host;

    #[test]
    fn get_returns_allowed_secret() {
        test_host::reset();
        test_host::with_mock(|m| {
            m.secrets.insert("api-key".into(), b"sk-12345".to_vec());
        });
        assert_eq!(get("api-key").as_deref(), Some("sk-12345"));
    }

    #[test]
    fn get_none_for_disallowed_or_missing() {
        test_host::reset();
        // No secrets configured - host returns 0 (not in allowlist or missing).
        assert!(get("not-allowed").is_none());
    }

    #[test]
    fn get_records_lookup_name() {
        test_host::reset();
        let _ = get("my-secret");
        assert_eq!(
            test_host::read_mock(|m| m.last_secret_get.clone()),
            Some("my-secret".into())
        );
    }

    #[test]
    fn get_bytes_returns_raw() {
        test_host::reset();
        test_host::with_mock(|m| {
            m.secrets
                .insert("binary".into(), vec![0xde, 0xad, 0xbe, 0xef]);
        });
        assert_eq!(get_bytes("binary"), Some(vec![0xde, 0xad, 0xbe, 0xef]));
    }

    #[test]
    fn get_bytes_none_when_denied() {
        test_host::reset();
        assert!(get_bytes("missing").is_none());
    }
}