2026-05-15 15:08:20 +02:00
|
|
|
use geth_types::{AuthOpId, Capability, GroupId, PrincipalId, ResourceId, SecretId, UnixMillis};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
|
|
pub const AUTH_SIGNATURE_NAMESPACE: &str = "geth.auth-op.v1@geth.local";
|
|
|
|
|
pub const RESOURCE_GRANT_SIGNATURE_NAMESPACE: &str = "geth.resource-grant.v1@geth.local";
|
|
|
|
|
pub const REVOCATION_SIGNATURE_NAMESPACE: &str = "geth.revocation.v1@geth.local";
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
pub struct AuthOp {
|
|
|
|
|
pub id: AuthOpId,
|
|
|
|
|
pub resource: ResourceId,
|
|
|
|
|
pub created_at: UnixMillis,
|
|
|
|
|
pub kind: AuthOpKind,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
#[serde(tag = "kind", rename_all = "kebab-case")]
|
|
|
|
|
pub enum AuthOpKind {
|
|
|
|
|
ResourceCreate,
|
|
|
|
|
ResourceAuthoritySet {
|
|
|
|
|
authority: ResourceId,
|
|
|
|
|
},
|
|
|
|
|
GrantCreate {
|
|
|
|
|
grant_id: String,
|
|
|
|
|
principal: PrincipalId,
|
|
|
|
|
capabilities: Vec<Capability>,
|
|
|
|
|
},
|
|
|
|
|
GrantRevoke {
|
|
|
|
|
grant_id: String,
|
|
|
|
|
},
|
|
|
|
|
BearerAccessCreate {
|
|
|
|
|
secret: SecretId,
|
|
|
|
|
capabilities: Vec<Capability>,
|
|
|
|
|
expires_at: Option<UnixMillis>,
|
|
|
|
|
},
|
|
|
|
|
BearerAccessRevoke {
|
|
|
|
|
secret: SecretId,
|
|
|
|
|
},
|
|
|
|
|
GroupCreate {
|
|
|
|
|
group: GroupId,
|
|
|
|
|
},
|
|
|
|
|
GroupAddMember {
|
|
|
|
|
group: GroupId,
|
|
|
|
|
principal: PrincipalId,
|
|
|
|
|
},
|
|
|
|
|
GroupRemoveMember {
|
|
|
|
|
group: GroupId,
|
|
|
|
|
principal: PrincipalId,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
pub struct AuthExplanation {
|
|
|
|
|
pub subject: String,
|
|
|
|
|
pub resource: String,
|
|
|
|
|
pub capability: String,
|
|
|
|
|
pub allowed: bool,
|
|
|
|
|
pub reason: String,
|
|
|
|
|
pub evaluated_ops: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AuthExplanation {
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn stub(subject: String, resource: String, capability: String) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
subject,
|
|
|
|
|
resource,
|
|
|
|
|
capability,
|
|
|
|
|
allowed: false,
|
|
|
|
|
reason: "authorization logs are scaffolded; no grant reducer is active yet".to_owned(),
|
|
|
|
|
evaluated_ops: 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-16 14:24:21 +02:00
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn discovered_candidate(subject: String, resource: String, capability: String) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
subject,
|
|
|
|
|
resource,
|
|
|
|
|
capability,
|
|
|
|
|
allowed: false,
|
|
|
|
|
reason: "subject is a discovered peer candidate only; discovery does not grant trust or authorization".to_owned(),
|
|
|
|
|
evaluated_ops: 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-15 15:08:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn auth_structs_roundtrip() {
|
|
|
|
|
let op = AuthOp {
|
|
|
|
|
id: "op:auth:1".into(),
|
|
|
|
|
resource: "resource:notes".into(),
|
|
|
|
|
created_at: UnixMillis(10),
|
|
|
|
|
kind: AuthOpKind::GrantCreate {
|
|
|
|
|
grant_id: "grant:1".to_owned(),
|
|
|
|
|
principal: "node:laptop".into(),
|
|
|
|
|
capabilities: vec!["kv.read".into(), "kv.write_prefix:apps/foo/".into()],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
let json = serde_json::to_string(&op).expect("json");
|
|
|
|
|
let decoded: AuthOp = serde_json::from_str(&json).expect("decode");
|
|
|
|
|
assert_eq!(decoded, op);
|
|
|
|
|
}
|
|
|
|
|
}
|