biscuit_wasm/
lib.rs

1use biscuit_auth as biscuit;
2use wasm_bindgen::prelude::*;
3
4#[global_allocator]
5static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
6
7/// a Biscuit token
8///
9/// it can produce an attenuated or sealed token, or be used
10/// in an authorizer along with Datalog policies
11#[wasm_bindgen]
12pub struct Biscuit(biscuit::Biscuit);
13
14#[wasm_bindgen]
15impl Biscuit {
16    /// Creates a BiscuitBuilder
17    ///
18    /// the builder can then create a new token with a root key
19    pub fn builder() -> BiscuitBuilder {
20        BiscuitBuilder::new()
21    }
22
23    /// Creates a BlockBuilder to prepare for attenuation
24    ///
25    /// the bulder can then be given to the token's append method to create an attenuated token
26    pub fn create_block(&self) -> BlockBuilder {
27        BlockBuilder(self.0.create_block())
28    }
29
30    /// Creates an attenuated token by adding the block generated by the BlockBuilder
31    pub fn append(&self, block: BlockBuilder) -> Result<Biscuit, JsValue> {
32        Ok(Biscuit(
33            self.0
34                .append(block.0)
35                .map_err(|e| JsValue::from_serde(&e).unwrap())?,
36        ))
37    }
38
39    /// Creates an authorizer from the token
40    pub fn authorizer(&self) -> Result<Authorizer, JsValue> {
41        Ok(Authorizer {
42            token: Some(self.0.clone()),
43            ..Authorizer::default()
44        })
45    }
46
47    /// Seals the token
48    ///
49    /// A sealed token cannot be attenuated
50    pub fn seal(&self) -> Result<Biscuit, JsValue> {
51        Ok(Biscuit(
52            self.0
53                .seal()
54                .map_err(|e| JsValue::from_serde(&e).unwrap())?,
55        ))
56    }
57
58    /// Deserializes a token from raw data
59    ///
60    /// This will check the signature using the root key
61    pub fn from_bytes(data: &[u8], root: &PublicKey) -> Result<Biscuit, JsValue> {
62        Ok(Biscuit(
63            biscuit::Biscuit::from(data, |_| root.0)
64                .map_err(|e| JsValue::from_serde(&e).unwrap())?,
65        ))
66    }
67
68    /// Deserializes a token from URL safe base 64 data
69    ///
70    /// This will check the signature using the root key
71    pub fn from_base64(data: &str, root: &PublicKey) -> Result<Biscuit, JsValue> {
72        Ok(Biscuit(
73            biscuit::Biscuit::from_base64(data, |_| root.0)
74                .map_err(|e| JsValue::from_serde(&e).unwrap())?,
75        ))
76    }
77
78    /// Serializes to raw data
79    pub fn to_bytes(&self) -> Result<Box<[u8]>, JsValue> {
80        Ok(self
81            .0
82            .to_vec()
83            .map_err(|e| JsValue::from_serde(&e).unwrap())?
84            .into_boxed_slice())
85    }
86
87    /// Serializes to URL safe base 64 data
88    pub fn to_base64(&self) -> Result<String, JsValue> {
89        Ok(self
90            .0
91            .to_base64()
92            .map_err(|e| JsValue::from_serde(&e).unwrap())?)
93    }
94
95    /// Returns the list of revocation identifiers, encoded as URL safe base 64
96    pub fn revocation_identifiers(&self) -> Box<[JsValue]> {
97        let ids: Vec<_> = self
98            .0
99            .revocation_identifiers()
100            .into_iter()
101            .map(|id| base64::encode_config(id, base64::URL_SAFE).into())
102            .collect();
103        ids.into_boxed_slice()
104    }
105
106    /// Returns the number of blocks in the token
107    pub fn block_count(&self) -> usize {
108        self.0.block_count()
109    }
110
111    /// Prints a block's content as Datalog code
112    pub fn block_source(&self, index: usize) -> Option<String> {
113        self.0.print_block_source(index)
114    }
115}
116
117/// The Authorizer verifies a request according to its policies and the provided token
118#[wasm_bindgen]
119#[derive(Default)]
120pub struct Authorizer {
121    token: Option<biscuit::Biscuit>,
122    facts: Vec<biscuit::builder::Fact>,
123    rules: Vec<biscuit::builder::Rule>,
124    checks: Vec<biscuit::builder::Check>,
125    policies: Vec<biscuit::builder::Policy>,
126}
127
128#[wasm_bindgen]
129impl Authorizer {
130    #[wasm_bindgen(constructor)]
131    pub fn new() -> Authorizer {
132        Authorizer::default()
133    }
134
135    pub fn add_token(&mut self, token: Biscuit) {
136        self.token = Some(token.0);
137    }
138
139    /// Adds a Datalog fact
140    pub fn add_fact(&mut self, fact: Fact) -> Result<(), JsValue> {
141        self.facts.push(fact.0);
142        Ok(())
143    }
144
145    /// Adds a Datalog rule
146    pub fn add_rule(&mut self, rule: Rule) -> Result<(), JsValue> {
147        self.rules.push(rule.0);
148        Ok(())
149    }
150
151    /// Adds a check
152    ///
153    /// All checks, from authorizer and token, must be validated to authorize the request
154    pub fn add_check(&mut self, check: Check) -> Result<(), JsValue> {
155        self.checks.push(check.0);
156        Ok(())
157    }
158
159    /// Adds a policy
160    ///
161    /// The authorizer will test all policies in order of addition and stop at the first one that
162    /// matches. If it is a "deny" policy, the request fails, while with an "allow" policy, it will
163    /// succeed
164    pub fn add_policy(&mut self, policy: Policy) -> Result<(), JsValue> {
165        self.policies.push(policy.0);
166        Ok(())
167    }
168
169    /// Adds facts, rules, checks and policies as one code block
170    pub fn add_code(&mut self, source: &str) -> Result<(), JsValue> {
171        let source_result = biscuit::parser::parse_source(source).map_err(|e| {
172            let e: biscuit::error::Token = e.into();
173            JsValue::from_serde(&e).unwrap()
174        })?;
175
176        for (_, fact) in source_result.facts.into_iter() {
177            self.facts.push(fact);
178        }
179
180        for (_, rule) in source_result.rules.into_iter() {
181            self.rules.push(rule);
182        }
183
184        for (_, check) in source_result.checks.into_iter() {
185            self.checks.push(check);
186        }
187
188        for (_, policy) in source_result.policies.into_iter() {
189            self.policies.push(policy);
190        }
191
192        Ok(())
193    }
194
195    /// Runs the authorization checks and policies
196    ///
197    /// Returns the index of the matching allow policy, or an error containing the matching deny
198    /// policy or a list of the failing checks
199    pub fn authorize(&self) -> Result<usize, JsValue> {
200        let mut authorizer = match &self.token {
201            Some(token) => token
202                .authorizer()
203                .map_err(|e| JsValue::from_serde(&e).unwrap())?,
204            None => biscuit::Authorizer::new().map_err(|e| JsValue::from_serde(&e).unwrap())?,
205        };
206
207        for fact in self.facts.iter() {
208            authorizer
209                .add_fact(fact.clone())
210                .map_err(|e| JsValue::from_serde(&e).unwrap())?;
211        }
212        for rule in self.rules.iter() {
213            authorizer
214                .add_rule(rule.clone())
215                .map_err(|e| JsValue::from_serde(&e).unwrap())?;
216        }
217        for check in self.checks.iter() {
218            authorizer
219                .add_check(check.clone())
220                .map_err(|e| JsValue::from_serde(&e).unwrap())?;
221        }
222        for policy in self.policies.iter() {
223            authorizer
224                .add_policy(policy.clone())
225                .map_err(|e| JsValue::from_serde(&e).unwrap())?;
226        }
227
228        Ok(authorizer
229            .authorize()
230            .map_err(|e| JsValue::from_serde(&e).unwrap())?)
231    }
232}
233
234/// Creates a token
235#[wasm_bindgen]
236pub struct BiscuitBuilder {
237    facts: Vec<biscuit::builder::Fact>,
238    rules: Vec<biscuit::builder::Rule>,
239    checks: Vec<biscuit::builder::Check>,
240}
241
242#[wasm_bindgen]
243impl BiscuitBuilder {
244    fn new() -> BiscuitBuilder {
245        BiscuitBuilder {
246            facts: Vec::new(),
247            rules: Vec::new(),
248            checks: Vec::new(),
249        }
250    }
251
252    pub fn build(self, root: &PrivateKey) -> Result<Biscuit, JsValue> {
253        let keypair = biscuit_auth::KeyPair::from(root.0.clone());
254        let mut builder = biscuit_auth::Biscuit::builder(&keypair);
255        for fact in self.facts.into_iter() {
256            builder
257                .add_authority_fact(fact)
258                .map_err(|e| JsValue::from_serde(&e).unwrap())?;
259        }
260        for rule in self.rules.into_iter() {
261            builder
262                .add_authority_rule(rule)
263                .map_err(|e| JsValue::from_serde(&e).unwrap())?;
264        }
265        for check in self.checks.into_iter() {
266            builder
267                .add_authority_check(check)
268                .map_err(|e| JsValue::from_serde(&e).unwrap())?;
269        }
270
271        Ok(Biscuit(
272            builder
273                .build()
274                .map_err(|e| JsValue::from_serde(&e).unwrap())?,
275        ))
276    }
277
278    /// Adds a Datalog fact
279    pub fn add_authority_fact(&mut self, fact: Fact) -> Result<(), JsValue> {
280        self.facts.push(fact.0);
281        Ok(())
282    }
283
284    /// Adds a Datalog rule
285    pub fn add_authority_rule(&mut self, rule: Rule) -> Result<(), JsValue> {
286        self.rules.push(rule.0);
287        Ok(())
288    }
289
290    /// Adds a check
291    ///
292    /// All checks, from authorizer and token, must be validated to authorize the request
293    pub fn add_authority_check(&mut self, check: Check) -> Result<(), JsValue> {
294        self.checks.push(check.0);
295        Ok(())
296    }
297}
298
299/// Creates a block to attenuate a token
300#[wasm_bindgen]
301pub struct BlockBuilder(biscuit::builder::BlockBuilder);
302
303#[wasm_bindgen]
304impl BlockBuilder {
305    /// Adds a Datalog fact
306    pub fn add_fact(&mut self, fact: Fact) -> Result<(), JsValue> {
307        Ok(self
308            .0
309            .add_fact(fact.0)
310            .map_err(|e| JsValue::from_serde(&e).unwrap())?)
311    }
312
313    /// Adds a Datalog rule
314    pub fn add_rule(&mut self, rule: Rule) -> Result<(), JsValue> {
315        Ok(self
316            .0
317            .add_rule(rule.0)
318            .map_err(|e| JsValue::from_serde(&e).unwrap())?)
319    }
320
321    /// Adds a check
322    ///
323    /// All checks, from authorizer and token, must be validated to authorize the request
324    pub fn add_check(&mut self, check: Check) -> Result<(), JsValue> {
325        Ok(self
326            .0
327            .add_check(check.0)
328            .map_err(|e| JsValue::from_serde(&e).unwrap())?)
329    }
330
331    /// Adds facts, rules, checks and policies as one code block
332    pub fn add_code(&mut self, source: &str) -> Result<(), JsValue> {
333        self.0
334            .add_code(source)
335            .map_err(|e| JsValue::from_serde(&e).unwrap())
336    }
337}
338
339#[wasm_bindgen]
340pub struct Fact(biscuit::builder::Fact);
341
342#[wasm_bindgen]
343impl Fact {
344    pub fn from_str(source: &str) -> Result<Fact, JsValue> {
345        source
346            .try_into()
347            .map(Fact)
348            .map_err(|e| JsValue::from_serde(&e).unwrap())
349    }
350
351    pub fn set(&mut self, name: &str, value: JsValue) -> Result<(), JsValue> {
352        let value = js_to_term(value)?;
353
354        self.0
355            .set(name, value)
356            .map_err(|e| JsValue::from_serde(&e).unwrap())
357    }
358}
359
360#[wasm_bindgen]
361pub struct Rule(biscuit::builder::Rule);
362
363#[wasm_bindgen]
364impl Rule {
365    pub fn from_str(source: &str) -> Result<Rule, JsValue> {
366        source
367            .try_into()
368            .map(Rule)
369            .map_err(|e| JsValue::from_serde(&e).unwrap())
370    }
371
372    pub fn set(&mut self, name: &str, value: JsValue) -> Result<(), JsValue> {
373        let value = js_to_term(value)?;
374
375        self.0
376            .set(name, value)
377            .map_err(|e| JsValue::from_serde(&e).unwrap())
378    }
379}
380
381#[wasm_bindgen]
382pub struct Check(biscuit::builder::Check);
383
384#[wasm_bindgen]
385impl Check {
386    pub fn from_str(source: &str) -> Result<Check, JsValue> {
387        source
388            .try_into()
389            .map(Check)
390            .map_err(|e| JsValue::from_serde(&e).unwrap())
391    }
392
393    pub fn set(&mut self, name: &str, value: JsValue) -> Result<(), JsValue> {
394        let value = js_to_term(value)?;
395
396        self.0
397            .set(name, value)
398            .map_err(|e| JsValue::from_serde(&e).unwrap())
399    }
400}
401
402#[wasm_bindgen]
403pub struct Policy(biscuit::builder::Policy);
404
405#[wasm_bindgen]
406impl Policy {
407    pub fn from_str(source: &str) -> Result<Policy, JsValue> {
408        source
409            .try_into()
410            .map(Policy)
411            .map_err(|e| JsValue::from_serde(&e).unwrap())
412    }
413
414    pub fn set(&mut self, name: &str, value: JsValue) -> Result<(), JsValue> {
415        let value = js_to_term(value)?;
416
417        self.0
418            .set(name, value)
419            .map_err(|e| JsValue::from_serde(&e).unwrap())
420    }
421}
422
423fn js_to_term(value: JsValue) -> Result<biscuit::builder::Term, JsValue> {
424    if let Some(b) = value.as_bool() {
425        Ok(biscuit::builder::Term::Bool(b))
426    } else if let Some(f) = value.as_f64() {
427        Ok(biscuit::builder::Term::Integer(f as i64))
428    } else if let Some(s) = value.as_string() {
429        Ok(biscuit::builder::Term::Str(s))
430    } else {
431        Err(JsValue::from_serde("unexpected value").unwrap())
432    }
433}
434
435/// A pair of public and private key
436#[wasm_bindgen]
437pub struct KeyPair(biscuit::KeyPair);
438
439#[wasm_bindgen]
440impl KeyPair {
441    #[wasm_bindgen(constructor)]
442    pub fn new() -> KeyPair {
443        KeyPair(biscuit::KeyPair::new())
444    }
445
446    pub fn from(key: PrivateKey) -> Self {
447        KeyPair(biscuit::KeyPair::from(key.0))
448    }
449
450    pub fn public(&self) -> PublicKey {
451        PublicKey(self.0.public())
452    }
453
454    pub fn private(&self) -> PrivateKey {
455        PrivateKey(self.0.private())
456    }
457}
458
459/// Public key
460#[wasm_bindgen]
461pub struct PublicKey(biscuit::PublicKey);
462
463#[wasm_bindgen]
464impl PublicKey {
465    /// Serializes a public key to raw bytes
466    pub fn to_bytes(&self, out: &mut [u8]) -> Result<(), JsValue> {
467        if out.len() != 32 {
468            return Err(JsValue::from_serde(&biscuit::error::Token::Format(
469                biscuit::error::Format::InvalidKeySize(out.len()),
470            ))
471            .unwrap());
472        }
473
474        out.copy_from_slice(&self.0.to_bytes());
475        Ok(())
476    }
477
478    /// Serializes a public key to a hexadecimal string
479    pub fn to_hex(&self) -> String {
480        hex::encode(&self.0.to_bytes())
481    }
482
483    /// Deserializes a public key from raw bytes
484    pub fn from_bytes(data: &[u8]) -> Result<PublicKey, JsValue> {
485        let key = biscuit_auth::PublicKey::from_bytes(data)
486            .map_err(|e| JsValue::from_serde(&e).unwrap())?;
487        Ok(PublicKey(key))
488    }
489
490    /// Deserializes a public key from a hexadecimal string
491    pub fn from_hex(data: &str) -> Result<PublicKey, JsValue> {
492        let data = hex::decode(data).map_err(|e| {
493            JsValue::from_serde(&biscuit::error::Token::Format(
494                biscuit::error::Format::InvalidKey(format!(
495                    "could not deserialize hex encoded key: {}",
496                    e
497                )),
498            ))
499            .unwrap()
500        })?;
501        let key = biscuit_auth::PublicKey::from_bytes(&data)
502            .map_err(|e| JsValue::from_serde(&e).unwrap())?;
503        Ok(PublicKey(key))
504    }
505}
506#[wasm_bindgen]
507pub struct PrivateKey(biscuit::PrivateKey);
508
509#[wasm_bindgen]
510impl PrivateKey {
511    /// Serializes a private key to raw bytes
512    pub fn to_bytes(&self, out: &mut [u8]) -> Result<(), JsValue> {
513        if out.len() != 32 {
514            return Err(JsValue::from_serde(&biscuit::error::Token::Format(
515                biscuit::error::Format::InvalidKeySize(out.len()),
516            ))
517            .unwrap());
518        }
519
520        out.copy_from_slice(&self.0.to_bytes());
521        Ok(())
522    }
523
524    /// Serializes a private key to a hexadecimal string
525    pub fn to_hex(&self) -> String {
526        hex::encode(&self.0.to_bytes())
527    }
528
529    /// Deserializes a private key from raw bytes
530    pub fn from_bytes(data: &[u8]) -> Result<PrivateKey, JsValue> {
531        let key = biscuit_auth::PrivateKey::from_bytes(data)
532            .map_err(|e| JsValue::from_serde(&e).unwrap())?;
533        Ok(PrivateKey(key))
534    }
535
536    /// Deserializes a private key from a hexadecimal string
537    pub fn from_hex(data: &str) -> Result<PrivateKey, JsValue> {
538        let data = hex::decode(data).map_err(|e| {
539            JsValue::from_serde(&biscuit::error::Token::Format(
540                biscuit::error::Format::InvalidKey(format!(
541                    "could not deserialize hex encoded key: {}",
542                    e
543                )),
544            ))
545            .unwrap()
546        })?;
547        let key = biscuit_auth::PrivateKey::from_bytes(&data)
548            .map_err(|e| JsValue::from_serde(&e).unwrap())?;
549        Ok(PrivateKey(key))
550    }
551}
552
553#[wasm_bindgen]
554extern "C" {
555    // Use `js_namespace` here to bind `console.log(..)` instead of just
556    // `log(..)`
557    #[wasm_bindgen(js_namespace = console)]
558    fn log(s: &str);
559}
560
561#[wasm_bindgen(start)]
562pub fn init() {
563    wasm_logger::init(wasm_logger::Config::default());
564    std::panic::set_hook(Box::new(console_error_panic_hook::hook));
565
566    log("biscuit-wasm loading")
567}