Skip to main content

better_auth_api/plugins/admin/
access.rs

1//! Permission helpers for the admin plugin.
2
3use std::collections::HashMap;
4
5use super::AdminConfig;
6
7/// Role-based permission grants for the admin plugin.
8#[derive(Debug, Clone, Default, PartialEq, Eq)]
9pub struct RolePermissions {
10    /// Resource name -> allowed actions.
11    pub permissions: HashMap<String, Vec<String>>,
12}
13
14impl RolePermissions {
15    /// Create an empty role definition.
16    pub fn new() -> Self {
17        Self::default()
18    }
19
20    /// Allow a set of actions for one resource.
21    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}