uni_plugin/traits/connector.rs
1//! Authentication and authorization plugins.
2
3/// Authentication credentials presented to an `AuthProvider`.
4#[derive(Clone, Debug)]
5pub enum Credentials {
6 /// Username + password pair.
7 Basic {
8 /// Username.
9 username: String,
10 /// Password.
11 password: String,
12 },
13 /// Bearer token.
14 Bearer(String),
15 /// mTLS client cert (DER-encoded).
16 MtlsCert(Vec<u8>),
17}
18
19/// Successfully-authenticated identity.
20#[derive(Clone, Debug)]
21pub struct Principal {
22 /// Identity string (subject id, username, etc.).
23 pub id: String,
24 /// Group memberships.
25 pub groups: Vec<String>,
26 /// Capabilities held by this principal.
27 ///
28 /// Populated by the host's authentication/authorization layer at
29 /// principal-construction time — typically the
30 /// [`AuthProvider`] / [`AuthzPolicy`] pair resolves group
31 /// memberships to a [`crate::CapabilitySet`]. Procedure invocation
32 /// paths (e.g.
33 /// `uni.plugin.declareProcedure` for `WRITE` mode) gate on
34 /// `principal.capabilities.contains_variant(&Capability::...)`.
35 pub capabilities: crate::CapabilitySet,
36}
37
38impl Principal {
39 /// Construct an anonymous principal with no capabilities — the
40 /// safe default for unauthenticated paths.
41 #[must_use]
42 pub fn anonymous() -> Self {
43 Self {
44 id: "anonymous".to_owned(),
45 groups: Vec::new(),
46 capabilities: crate::CapabilitySet::new(),
47 }
48 }
49}
50
51/// Authentication failure cause.
52#[derive(Clone, Debug, thiserror::Error)]
53#[error("authentication failure: {0}")]
54pub struct AuthError(pub String);
55
56/// Authentication provider — `AuthProvider::authenticate(creds) -> Principal`.
57pub trait AuthProvider: Send + Sync {
58 /// Authentication scheme name (`"basic"`, `"bearer"`, `"mtls"`).
59 fn scheme(&self) -> &str;
60
61 /// Authenticate the given credentials.
62 ///
63 /// # Errors
64 ///
65 /// Returns [`AuthError`] if the credentials are invalid.
66 fn authenticate(&self, credentials: &Credentials) -> Result<Principal, AuthError>;
67}
68
69/// Authorization action under check.
70#[derive(Clone, Debug)]
71pub struct Action {
72 /// Action verb (`"read"`, `"write"`, `"delete"`, …).
73 pub verb: String,
74}
75
76/// Authorization resource under check.
77///
78/// `path` is the raw query text (retained for backward compatibility); the
79/// structured fields are extracted from the parsed query so a policy can gate on
80/// vertex labels, relationship types, touched properties, and operations rather
81/// than string-matching the Cypher. Empty structured fields mean "none found" or
82/// "not extracted" — a policy should treat empties conservatively.
83#[derive(Clone, Debug, Default)]
84pub struct Resource {
85 /// Raw query text / resource identifier.
86 pub path: String,
87 /// Vertex labels the query references.
88 pub labels: Vec<String>,
89 /// Relationship types the query references.
90 pub rel_types: Vec<String>,
91 /// Property keys the query reads or writes.
92 pub properties: Vec<String>,
93 /// Operations the query performs (`"read"`, `"write"`, `"delete"`, …).
94 pub operations: Vec<String>,
95}
96
97/// Authorization decision.
98#[derive(Clone, Debug)]
99pub enum Decision {
100 /// Permit the action.
101 Allow,
102 /// Deny with reason.
103 Deny {
104 /// Human-readable reason.
105 reason: String,
106 },
107}
108
109/// Authorization failure (policy errored out, not "denied").
110#[derive(Clone, Debug, thiserror::Error)]
111#[error("authorization policy failure: {0}")]
112pub struct AuthzError(pub String);
113
114/// Authorization policy plugin.
115pub trait AuthzPolicy: Send + Sync {
116 /// Check whether `principal` may perform `action` on `resource`.
117 ///
118 /// # Errors
119 ///
120 /// Returns [`AuthzError`] if the policy fails to evaluate (e.g.,
121 /// external policy server unreachable).
122 fn check(
123 &self,
124 principal: &Principal,
125 action: &Action,
126 resource: &Resource,
127 ) -> Result<Decision, AuthzError>;
128}