1use crate::errors::{MCSError, Result};
3use crate::events::request_fingerprint;
4use crate::mutation::MutationContext;
5use std::collections::BTreeSet;
6use uuid::Uuid;
7
8#[derive(Clone, Debug, Eq, PartialEq)]
9pub enum PrincipalKind {
10 Machine,
11 Human,
12}
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct Principal {
15 pub id: String,
16 pub kind: PrincipalKind,
17 pub scopes: BTreeSet<String>,
18 pub allowed_origins: BTreeSet<String>,
19}
20pub trait CredentialAuthenticator: Send + Sync {
21 fn authenticate(&self, credential: &str) -> Result<Principal>;
22}
23
24pub fn machine_mutation_context(
25 principal: &Principal,
26 origin: &str,
27 idempotency_key: String,
28 correlation_id: Uuid,
29 causation_id: Option<Uuid>,
30 hop_count: u8,
31 raw_body: &[u8],
32) -> Result<(MutationContext, String)> {
33 if principal.id.trim().is_empty()
34 || principal.id.len() > 256
35 || principal.id.chars().any(char::is_control)
36 || principal.kind != PrincipalKind::Machine
37 || !principal.scopes.contains("memory:write")
38 || !principal.allowed_origins.contains(origin)
39 {
40 return Err(MCSError::InvalidParams(
41 "machine principal is not authorized for this origin".into(),
42 ));
43 }
44 let context = MutationContext {
45 actor: principal.id.clone(),
46 origin: origin.to_owned(),
47 correlation_id,
48 causation_id,
49 hop_count,
50 idempotency_key: Some(idempotency_key),
51 }
52 .validate()?;
53 Ok((
54 context,
55 request_fingerprint("POST", "/api/v1/mutations", raw_body),
56 ))
57}