cloudiful_redactor/session/
permit.rs1use std::collections::HashSet;
2
3use anyhow::{Result, anyhow};
4
5use crate::{RedactionSession, RestorePermit};
6
7use super::crypto::{open_json, seal_json};
8
9pub(super) const PERMIT_VERSION: u32 = 1;
10const PERMIT_AAD: &[u8] = b"redactor:restore-permit:v1";
11
12pub fn create_restore_permit(session: &RedactionSession) -> RestorePermit {
13 RestorePermit {
14 version: PERMIT_VERSION,
15 permit_id: crate::replace::random_id(),
16 scope_id: session.scope_id.clone(),
17 external_id: session.external_id.clone(),
18 issued_tokens: session.issued_tokens.clone(),
19 }
20}
21
22pub fn encrypt_restore_permit(permit: &RestorePermit, passphrase: &str) -> Result<String> {
23 seal_json(permit, passphrase, PERMIT_AAD)
24}
25
26pub fn decrypt_restore_permit(data: &str, passphrase: &str) -> Result<RestorePermit> {
27 let permit: RestorePermit = open_json(data, passphrase, PERMIT_AAD)
28 .map_err(|error| anyhow!("failed to decrypt restore permit: {error}"))?;
29 if permit.version != PERMIT_VERSION {
30 return Err(anyhow!(
31 "unsupported restore permit version {}",
32 permit.version
33 ));
34 }
35 Ok(permit)
36}
37
38pub fn authorized_tokens<'a>(
39 session: &'a RedactionSession,
40 permits: &[RestorePermit],
41) -> Result<HashSet<&'a str>> {
42 let known = session
43 .entries
44 .iter()
45 .map(|entry| entry.token.as_str())
46 .collect::<HashSet<_>>();
47 let mut authorized = HashSet::new();
48 for permit in permits {
49 if permit.version != PERMIT_VERSION {
50 return Err(anyhow!(
51 "unsupported restore permit version {}",
52 permit.version
53 ));
54 }
55 if permit.scope_id != session.scope_id || permit.external_id != session.external_id {
56 return Err(anyhow!("restore permit does not match session context"));
57 }
58 for token in &permit.issued_tokens {
59 let Some(known_token) = known.get(token.as_str()) else {
60 return Err(anyhow!("restore permit authorizes unknown token `{token}`"));
61 };
62 authorized.insert(*known_token);
63 }
64 }
65 Ok(authorized)
66}