better_auth_api/plugins/admin/
access.rs1use std::collections::HashMap;
4
5use super::AdminConfig;
6
7#[derive(Debug, Clone, Default, PartialEq, Eq)]
9pub struct RolePermissions {
10 pub permissions: HashMap<String, Vec<String>>,
12}
13
14impl RolePermissions {
15 pub fn new() -> Self {
17 Self::default()
18 }
19
20 pub fn allow<I, S>(mut self, resource: impl Into<String>, actions: I) -> Self
22 where
23 I: IntoIterator<Item = S>,
24 S: Into<String>,
25 {
26 let _ = self.permissions.insert(
27 resource.into(),
28 actions.into_iter().map(Into::into).collect(),
29 );
30 self
31 }
32
33 fn allows(&self, requested: &HashMap<String, Vec<String>>) -> bool {
34 if requested.is_empty() {
35 return false;
36 }
37
38 requested.iter().all(|(resource, actions)| {
39 self.permissions.get(resource).is_some_and(|allowed| {
40 actions
41 .iter()
42 .all(|action| allowed.iter().any(|item| item == action))
43 })
44 })
45 }
46}
47
48pub(super) fn default_roles() -> HashMap<String, RolePermissions> {
49 HashMap::from([
50 (
51 "admin".to_string(),
52 RolePermissions::new()
53 .allow(
54 "user",
55 [
56 "create",
57 "list",
58 "set-role",
59 "ban",
60 "impersonate",
61 "delete",
62 "set-password",
63 "get",
64 "update",
65 ],
66 )
67 .allow("session", ["list", "revoke", "delete"]),
68 ),
69 ("user".to_string(), RolePermissions::new()),
70 ])
71}
72
73fn configured_roles(config: &AdminConfig) -> HashMap<String, RolePermissions> {
74 if config.roles.is_empty() {
75 default_roles()
76 } else {
77 config.roles.clone()
78 }
79}
80
81fn role_names<'a>(role: Option<&'a str>, default_role: &'a str) -> Vec<&'a str> {
82 role.unwrap_or(default_role)
83 .split(',')
84 .map(str::trim)
85 .filter(|role| !role.is_empty())
86 .collect()
87}
88
89pub(super) fn is_admin_user_id(user_id: Option<&str>, config: &AdminConfig) -> bool {
90 user_id.is_some_and(|user_id| {
91 config
92 .admin_user_ids
93 .as_ref()
94 .is_some_and(|ids| ids.iter().any(|id| id == user_id))
95 })
96}
97
98pub(super) fn has_permission(
99 user_id: Option<&str>,
100 role: Option<&str>,
101 config: &AdminConfig,
102 requested: &HashMap<String, Vec<String>>,
103) -> bool {
104 if is_admin_user_id(user_id, config) {
105 return true;
106 }
107
108 let roles = configured_roles(config);
109 role_names(role, &config.default_role)
110 .into_iter()
111 .filter_map(|role| roles.get(role))
112 .any(|role| role.allows(requested))
113}
114
115pub(super) fn is_admin_role(role: Option<&str>, config: &AdminConfig) -> bool {
116 role_names(role, &config.default_role)
117 .into_iter()
118 .any(|role| config.admin_roles.iter().any(|admin| admin == role))
119}