better_auth_api/plugins/admin/
permission_api.rs1use super::AdminConfig;
7use super::access::has_permission;
8use better_auth_core::wire::{SessionView, UserView};
9use better_auth_core::{
10 AuthContext, AuthError, AuthRequest, AuthSchema, HttpMethod, NativeRequest,
11};
12use std::collections::HashMap;
13
14#[derive(Debug, thiserror::Error)]
20pub enum AdminPermissionError {
21 #[error("AdminPlugin is not registered")]
23 PluginNotRegistered,
24 #[error("a permission check requires at least one action for each resource")]
26 EmptyPermissions,
27 #[error("user id or role is required")]
29 MissingSubject,
30 #[error("user not found")]
32 UserNotFound,
33 #[error("the request is not authenticated")]
37 Unauthenticated,
38 #[error("the authenticated user lacks the requested permissions")]
40 Forbidden,
41 #[error("the permission check could not complete: {0}")]
43 Internal(#[source] AuthError),
44}
45
46#[derive(Debug)]
52pub struct AuthorizedSession {
53 user: UserView,
54 session: SessionView,
55}
56
57impl AuthorizedSession {
58 pub fn user(&self) -> &UserView {
60 &self.user
61 }
62
63 pub fn session(&self) -> &SessionView {
65 &self.session
66 }
67
68 pub fn into_parts(self) -> (UserView, SessionView) {
70 (self.user, self.session)
71 }
72}
73
74pub struct AdminPermissionApi<'a, S: AuthSchema> {
79 config: &'a AdminConfig,
80 context: &'a AuthContext<S>,
81}
82
83impl<'a, S: AuthSchema> AdminPermissionApi<'a, S> {
84 pub fn from_context(context: &'a AuthContext<S>) -> Result<Self, AdminPermissionError> {
86 let config = context
87 .extensions
88 .get::<AdminConfig>()
89 .ok_or(AdminPermissionError::PluginNotRegistered)?;
90 Ok(Self { config, context })
91 }
92
93 pub fn role_has_permission(
100 &self,
101 role: &str,
102 permissions: &HashMap<String, Vec<String>>,
103 ) -> Result<bool, AdminPermissionError> {
104 require_permissions(permissions)?;
105 if role.is_empty() {
106 return Err(AdminPermissionError::MissingSubject);
107 }
108 Ok(has_permission(None, Some(role), self.config, permissions))
109 }
110
111 pub async fn user_has_permission(
119 &self,
120 user_id: &str,
121 permissions: &HashMap<String, Vec<String>>,
122 ) -> Result<bool, AdminPermissionError> {
123 require_permissions(permissions)?;
124 if user_id.is_empty() {
125 return Err(AdminPermissionError::MissingSubject);
126 }
127 let user = self
128 .context
129 .with_native_context(NativeRequest::default(), |context| async move {
130 context.database.get_user_by_id(user_id).await
131 })
132 .await
133 .map_err(AdminPermissionError::Internal)?
134 .ok_or(AdminPermissionError::UserNotFound)?;
135 Ok(has_permission(
136 user.id.as_str(),
137 user.role.as_deref(),
138 self.config,
139 permissions,
140 ))
141 }
142
143 pub async fn authorize_request(
150 &self,
151 headers: &HashMap<String, String>,
152 permissions: &HashMap<String, Vec<String>>,
153 ) -> Result<AuthorizedSession, AdminPermissionError> {
154 require_permissions(permissions)?;
155 let (user, session) = self
156 .context
157 .with_native_context(
158 NativeRequest {
159 request: None,
160 headers: Some(headers),
161 },
162 |context| async move {
163 let request = AuthRequest::new(HttpMethod::Post, "/admin/has-permission")
164 .with_optional_headers(Some(headers.clone()));
165 let mut hook_context =
166 better_auth_core::RequestHookContext::from_request(&request)?;
167 hook_context.body = better_auth_core::FieldValue::from_json(
168 serde_json::json!({ "permissions": permissions }),
169 )?;
170 better_auth_core::with_request_hook_context_value(hook_context, async {
171 context.require_authoritative_session(&request).await
172 })
173 .await
174 },
175 )
176 .await
177 .map_err(session_error)?;
178 if has_permission(
179 user.id.as_str(),
180 user.role.as_deref(),
181 self.config,
182 permissions,
183 ) {
184 Ok(AuthorizedSession { user, session })
185 } else {
186 Err(AdminPermissionError::Forbidden)
187 }
188 }
189}
190
191fn require_permissions(
192 permissions: &HashMap<String, Vec<String>>,
193) -> Result<(), AdminPermissionError> {
194 if permissions.is_empty() || permissions.values().any(Vec::is_empty) {
195 Err(AdminPermissionError::EmptyPermissions)
196 } else {
197 Ok(())
198 }
199}
200
201fn session_error(error: AuthError) -> AdminPermissionError {
202 match error {
203 AuthError::SessionNotFound | AuthError::UserNotFound => {
204 AdminPermissionError::Unauthenticated
205 }
206 error if error.status_code() == 401 => AdminPermissionError::Unauthenticated,
207 error => AdminPermissionError::Internal(error),
208 }
209}