eidetica/auth/validation/
permissions.rs1use crate::{
7 Error, Result,
8 auth::{
9 crypto::PublicKey,
10 errors::AuthError,
11 settings::AuthSettings,
12 types::{Operation, Permission, ResolvedAuth, SigKey},
13 validation::AuthValidator,
14 },
15};
16
17pub async fn resolve_identity_permission(
31 pubkey: &PublicKey,
32 identity: &SigKey,
33 auth_settings: &AuthSettings,
34 instance: Option<&crate::Instance>,
35) -> Result<Permission> {
36 match identity {
37 SigKey::Direct { hint } if hint.is_global() => {
38 if let Some(embedded_pubkey) = &hint.pubkey
39 && *embedded_pubkey != *pubkey
40 {
41 return Err(Error::Auth(Box::new(AuthError::SigningKeyMismatch {
42 reason: format!(
43 "pubkey '{pubkey}' but global identity claims '{embedded_pubkey}'"
44 ),
45 })));
46 }
47 let global = auth_settings.get_global_key().map_err(|_| {
48 Error::Auth(Box::new(AuthError::InvalidAuthConfiguration {
49 reason: "Global '*' permission not configured".to_string(),
50 }))
51 })?;
52 if !global.is_active() {
53 return Err(Error::Auth(Box::new(AuthError::InvalidAuthConfiguration {
54 reason: "Global '*' permission is not active".to_string(),
55 })));
56 }
57 Ok(*global.permissions())
58 }
59 SigKey::Direct { hint } => {
60 if let Some(claimed_pubkey) = &hint.pubkey
62 && *claimed_pubkey != *pubkey
63 {
64 return Err(Error::Auth(Box::new(AuthError::SigningKeyMismatch {
65 reason: format!("pubkey '{pubkey}' but identity claims '{claimed_pubkey}'"),
66 })));
67 }
68 if hint.pubkey.is_none() && hint.name.is_none() {
69 return Err(Error::Auth(Box::new(AuthError::InvalidAuthConfiguration {
70 reason: "identity has empty hint".to_string(),
71 })));
72 }
73 if let Ok(candidates) = auth_settings.resolve_hint(hint)
81 && let Some(permission) = select_effective_permission(&candidates, pubkey)
82 {
83 return Ok(permission);
84 }
85 if let Ok(global) = auth_settings.get_global_key()
86 && global.is_active()
87 {
88 return Ok(*global.permissions());
89 }
90 Err(Error::Auth(Box::new(AuthError::KeyNotFound {
91 key_name: hint.name.clone().unwrap_or_else(|| pubkey.to_string()),
92 })))
93 }
94 SigKey::Delegation { .. } => {
95 let mut validator = AuthValidator::new();
96 let resolved_auths = validator
97 .resolve_sig_key(identity, auth_settings, instance)
98 .await
99 .map_err(|e| {
100 Error::Auth(Box::new(AuthError::InvalidAuthConfiguration {
101 reason: format!("Delegation resolution failed: {e}"),
102 }))
103 })?;
104
105 select_effective_permission(&resolved_auths, pubkey).ok_or_else(|| {
106 Error::Auth(Box::new(AuthError::SigningKeyMismatch {
107 reason: format!("no active resolved delegation key matches pubkey '{pubkey}'"),
108 }))
109 })
110 }
111 }
112}
113
114pub(crate) fn select_effective_permission(
122 candidates: &[ResolvedAuth],
123 pubkey: &PublicKey,
124) -> Option<Permission> {
125 candidates
126 .iter()
127 .filter(|ra| ra.public_key == *pubkey && ra.grants_access())
128 .map(|ra| ra.effective_permission)
129 .max()
130}
131
132pub fn check_permissions(resolved: &ResolvedAuth, operation: &Operation) -> Result<bool> {
134 match operation {
135 Operation::WriteData => {
136 Ok(resolved.effective_permission.can_write()
137 || resolved.effective_permission.can_admin())
138 }
139 Operation::WriteSettings => Ok(resolved.effective_permission.can_admin()),
140 }
141}