Skip to main content

mcpmem_core/
auth.rs

1//! Typed machine-ingress policy. HTTP parsing remains an adapter concern.
2use 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}