1use better_auth_core::adapters::DatabaseAdapter;
2use better_auth_core::entity::AuthUser;
3use better_auth_core::{AuthContext, AuthError, AuthResult};
4use better_auth_core::{AuthRequest, AuthResponse};
5
6use better_auth_core::utils::cookie_utils::create_session_cookie;
7
8#[cfg(feature = "axum")]
9use super::StatusResponse;
10
11pub(super) mod handlers;
12pub(super) mod types;
13
14#[cfg(test)]
15mod tests;
16
17use handlers::*;
18use types::*;
19
20pub struct AdminPlugin {
32 config: AdminConfig,
33}
34
35#[derive(Debug, Clone, better_auth_core::PluginConfig)]
37#[plugin(name = "AdminPlugin")]
38pub struct AdminConfig {
39 #[config(default = "admin".to_string())]
41 pub admin_role: String,
42 #[config(default = "user".to_string())]
44 pub default_user_role: String,
45 #[config(default = false)]
47 pub allow_ban_admin: bool,
48 #[config(default = 100)]
50 pub default_page_limit: usize,
51 #[config(default = 500)]
53 pub max_page_limit: usize,
54}
55
56better_auth_core::impl_auth_plugin! {
61 AdminPlugin, "admin";
62 routes {
63 post "/admin/set-role" => handle_set_role, "admin_set_role";
64 post "/admin/create-user" => handle_create_user, "admin_create_user";
65 get "/admin/list-users" => handle_list_users, "admin_list_users";
66 post "/admin/list-user-sessions" => handle_list_user_sessions, "admin_list_user_sessions";
67 post "/admin/ban-user" => handle_ban_user, "admin_ban_user";
68 post "/admin/unban-user" => handle_unban_user, "admin_unban_user";
69 post "/admin/impersonate-user" => handle_impersonate_user, "admin_impersonate_user";
70 post "/admin/stop-impersonating" => handle_stop_impersonating, "admin_stop_impersonating";
71 post "/admin/revoke-user-session" => handle_revoke_user_session, "admin_revoke_user_session";
72 post "/admin/revoke-user-sessions" => handle_revoke_user_sessions, "admin_revoke_user_sessions";
73 post "/admin/remove-user" => handle_remove_user, "admin_remove_user";
74 post "/admin/set-user-password" => handle_set_user_password, "admin_set_user_password";
75 post "/admin/has-permission" => handle_has_permission, "admin_has_permission";
76 }
77}
78
79impl AdminPlugin {
84 async fn require_admin<DB: DatabaseAdapter>(
86 &self,
87 req: &AuthRequest,
88 ctx: &AuthContext<DB>,
89 ) -> AuthResult<(DB::User, DB::Session)> {
90 let (user, session) = ctx.require_session(req).await?;
91
92 let user_role = user.role().unwrap_or("user");
93 if user_role != self.config.admin_role {
94 return Err(AuthError::forbidden(
95 "You do not have permission to access this resource",
96 ));
97 }
98
99 Ok((user, session))
100 }
101
102 async fn handle_set_role<DB: DatabaseAdapter>(
103 &self,
104 req: &AuthRequest,
105 ctx: &AuthContext<DB>,
106 ) -> AuthResult<AuthResponse> {
107 let (_admin_user, _admin_session) = self.require_admin(req, ctx).await?;
108 let body: SetRoleRequest = match better_auth_core::validate_request_body(req) {
109 Ok(v) => v,
110 Err(resp) => return Ok(resp),
111 };
112 let response = set_role_core(&body, ctx).await?;
113 AuthResponse::json(200, &response).map_err(AuthError::from)
114 }
115
116 async fn handle_create_user<DB: DatabaseAdapter>(
117 &self,
118 req: &AuthRequest,
119 ctx: &AuthContext<DB>,
120 ) -> AuthResult<AuthResponse> {
121 let (_admin_user, _admin_session) = self.require_admin(req, ctx).await?;
122 let body: CreateUserRequest = match better_auth_core::validate_request_body(req) {
123 Ok(v) => v,
124 Err(resp) => return Ok(resp),
125 };
126 let response = create_user_core(&body, &self.config, ctx).await?;
127 AuthResponse::json(200, &response).map_err(AuthError::from)
128 }
129
130 async fn handle_list_users<DB: DatabaseAdapter>(
131 &self,
132 req: &AuthRequest,
133 ctx: &AuthContext<DB>,
134 ) -> AuthResult<AuthResponse> {
135 let (_admin_user, _admin_session) = self.require_admin(req, ctx).await?;
136 let query = ListUsersQueryParams {
137 limit: req.query.get("limit").and_then(|v| v.parse().ok()),
138 offset: req.query.get("offset").and_then(|v| v.parse().ok()),
139 search_field: req.query.get("searchField").cloned(),
140 search_value: req.query.get("searchValue").cloned(),
141 search_operator: req.query.get("searchOperator").cloned(),
142 sort_by: req.query.get("sortBy").cloned(),
143 sort_direction: req.query.get("sortDirection").cloned(),
144 filter_field: req.query.get("filterField").cloned(),
145 filter_value: req.query.get("filterValue").cloned(),
146 filter_operator: req.query.get("filterOperator").cloned(),
147 };
148 let response = list_users_core(&query, &self.config, ctx).await?;
149 AuthResponse::json(200, &response).map_err(AuthError::from)
150 }
151
152 async fn handle_list_user_sessions<DB: DatabaseAdapter>(
153 &self,
154 req: &AuthRequest,
155 ctx: &AuthContext<DB>,
156 ) -> AuthResult<AuthResponse> {
157 let (_admin_user, _admin_session) = self.require_admin(req, ctx).await?;
158 let body: UserIdRequest = match better_auth_core::validate_request_body(req) {
159 Ok(v) => v,
160 Err(resp) => return Ok(resp),
161 };
162 let response = list_user_sessions_core(&body, ctx).await?;
163 AuthResponse::json(200, &response).map_err(AuthError::from)
164 }
165
166 async fn handle_ban_user<DB: DatabaseAdapter>(
167 &self,
168 req: &AuthRequest,
169 ctx: &AuthContext<DB>,
170 ) -> AuthResult<AuthResponse> {
171 let (admin_user, _admin_session) = self.require_admin(req, ctx).await?;
172 let body: BanUserRequest = match better_auth_core::validate_request_body(req) {
173 Ok(v) => v,
174 Err(resp) => return Ok(resp),
175 };
176 let response = ban_user_core(&body, admin_user.id(), &self.config, ctx).await?;
177 AuthResponse::json(200, &response).map_err(AuthError::from)
178 }
179
180 async fn handle_unban_user<DB: DatabaseAdapter>(
181 &self,
182 req: &AuthRequest,
183 ctx: &AuthContext<DB>,
184 ) -> AuthResult<AuthResponse> {
185 let (_admin_user, _admin_session) = self.require_admin(req, ctx).await?;
186 let body: UserIdRequest = match better_auth_core::validate_request_body(req) {
187 Ok(v) => v,
188 Err(resp) => return Ok(resp),
189 };
190 let response = unban_user_core(&body, ctx).await?;
191 AuthResponse::json(200, &response).map_err(AuthError::from)
192 }
193
194 async fn handle_impersonate_user<DB: DatabaseAdapter>(
195 &self,
196 req: &AuthRequest,
197 ctx: &AuthContext<DB>,
198 ) -> AuthResult<AuthResponse> {
199 let (admin_user, _admin_session) = self.require_admin(req, ctx).await?;
200 let body: UserIdRequest = match better_auth_core::validate_request_body(req) {
201 Ok(v) => v,
202 Err(resp) => return Ok(resp),
203 };
204 let (response, token) = impersonate_user_core(
205 &body,
206 admin_user.id(),
207 req.headers.get("x-forwarded-for").map(|s| s.as_str()),
208 req.headers.get("user-agent").map(|s| s.as_str()),
209 ctx,
210 )
211 .await?;
212 let cookie_header = create_session_cookie(&token, &ctx.config);
213 Ok(AuthResponse::json(200, &response)?.with_header("Set-Cookie", cookie_header))
214 }
215
216 async fn handle_stop_impersonating<DB: DatabaseAdapter>(
217 &self,
218 req: &AuthRequest,
219 ctx: &AuthContext<DB>,
220 ) -> AuthResult<AuthResponse> {
221 let session_manager = ctx.session_manager();
222 let token = session_manager
223 .extract_session_token(req)
224 .ok_or(AuthError::Unauthenticated)?;
225 let session = session_manager
226 .get_session(&token)
227 .await?
228 .ok_or(AuthError::Unauthenticated)?;
229 let (response, new_token) = stop_impersonating_core(
230 &session,
231 &token,
232 req.headers.get("x-forwarded-for").map(|s| s.as_str()),
233 req.headers.get("user-agent").map(|s| s.as_str()),
234 ctx,
235 )
236 .await?;
237 let cookie_header = create_session_cookie(&new_token, &ctx.config);
238 Ok(AuthResponse::json(200, &response)?.with_header("Set-Cookie", cookie_header))
239 }
240
241 async fn handle_revoke_user_session<DB: DatabaseAdapter>(
242 &self,
243 req: &AuthRequest,
244 ctx: &AuthContext<DB>,
245 ) -> AuthResult<AuthResponse> {
246 let (_admin_user, _admin_session) = self.require_admin(req, ctx).await?;
247 let body: RevokeSessionRequest = match better_auth_core::validate_request_body(req) {
248 Ok(v) => v,
249 Err(resp) => return Ok(resp),
250 };
251 let response = revoke_user_session_core(&body, ctx).await?;
252 AuthResponse::json(200, &response).map_err(AuthError::from)
253 }
254
255 async fn handle_revoke_user_sessions<DB: DatabaseAdapter>(
256 &self,
257 req: &AuthRequest,
258 ctx: &AuthContext<DB>,
259 ) -> AuthResult<AuthResponse> {
260 let (_admin_user, _admin_session) = self.require_admin(req, ctx).await?;
261 let body: UserIdRequest = match better_auth_core::validate_request_body(req) {
262 Ok(v) => v,
263 Err(resp) => return Ok(resp),
264 };
265 let response = revoke_user_sessions_core(&body, ctx).await?;
266 AuthResponse::json(200, &response).map_err(AuthError::from)
267 }
268
269 async fn handle_remove_user<DB: DatabaseAdapter>(
270 &self,
271 req: &AuthRequest,
272 ctx: &AuthContext<DB>,
273 ) -> AuthResult<AuthResponse> {
274 let (admin_user, _admin_session) = self.require_admin(req, ctx).await?;
275 let body: UserIdRequest = match better_auth_core::validate_request_body(req) {
276 Ok(v) => v,
277 Err(resp) => return Ok(resp),
278 };
279 let response = remove_user_core(&body, admin_user.id(), ctx).await?;
280 AuthResponse::json(200, &response).map_err(AuthError::from)
281 }
282
283 async fn handle_set_user_password<DB: DatabaseAdapter>(
284 &self,
285 req: &AuthRequest,
286 ctx: &AuthContext<DB>,
287 ) -> AuthResult<AuthResponse> {
288 let (_admin_user, _admin_session) = self.require_admin(req, ctx).await?;
289 let body: SetUserPasswordRequest = match better_auth_core::validate_request_body(req) {
290 Ok(v) => v,
291 Err(resp) => return Ok(resp),
292 };
293 let response = set_user_password_core(&body, ctx).await?;
294 AuthResponse::json(200, &response).map_err(AuthError::from)
295 }
296
297 async fn handle_has_permission<DB: DatabaseAdapter>(
298 &self,
299 req: &AuthRequest,
300 ctx: &AuthContext<DB>,
301 ) -> AuthResult<AuthResponse> {
302 let (user, _session) = ctx.require_session(req).await?;
303 let body: HasPermissionRequest = match better_auth_core::validate_request_body(req) {
304 Ok(v) => v,
305 Err(resp) => return Ok(resp),
306 };
307 let response = has_permission_core::<DB>(&body, &user, &self.config).await?;
308 AuthResponse::json(200, &response).map_err(AuthError::from)
309 }
310}
311
312#[cfg(feature = "axum")]
317mod axum_impl {
318 use super::*;
319 use std::sync::Arc;
320
321 use axum::Json;
322 use axum::extract::{Extension, Query, State};
323 use axum::http::header;
324 use better_auth_core::entity::AuthSession;
325 use better_auth_core::{AdminRole, AdminSession, AuthState, CurrentSession, ValidatedJson};
326
327 #[derive(Clone)]
328 struct PluginState {
329 config: AdminConfig,
330 }
331
332 async fn handle_set_role<DB: DatabaseAdapter>(
333 State(state): State<AuthState<DB>>,
334 Extension(ps): Extension<Arc<PluginState>>,
335 AdminSession { .. }: AdminSession<DB>,
336 ValidatedJson(body): ValidatedJson<SetRoleRequest>,
337 ) -> Result<Json<UserResponse<DB::User>>, AuthError> {
338 let ctx = state.to_context();
339 let response = set_role_core(&body, &ctx).await?;
340 Ok(Json(response))
341 }
342
343 async fn handle_create_user<DB: DatabaseAdapter>(
344 State(state): State<AuthState<DB>>,
345 Extension(ps): Extension<Arc<PluginState>>,
346 AdminSession { .. }: AdminSession<DB>,
347 ValidatedJson(body): ValidatedJson<CreateUserRequest>,
348 ) -> Result<Json<UserResponse<DB::User>>, AuthError> {
349 let ctx = state.to_context();
350 let response = create_user_core(&body, &ps.config, &ctx).await?;
351 Ok(Json(response))
352 }
353
354 async fn handle_list_users<DB: DatabaseAdapter>(
355 State(state): State<AuthState<DB>>,
356 Extension(ps): Extension<Arc<PluginState>>,
357 AdminSession { .. }: AdminSession<DB>,
358 Query(query): Query<ListUsersQueryParams>,
359 ) -> Result<Json<ListUsersResponse<DB::User>>, AuthError> {
360 let ctx = state.to_context();
361 let response = list_users_core(&query, &ps.config, &ctx).await?;
362 Ok(Json(response))
363 }
364
365 async fn handle_list_user_sessions<DB: DatabaseAdapter>(
366 State(state): State<AuthState<DB>>,
367 AdminSession { .. }: AdminSession<DB>,
368 ValidatedJson(body): ValidatedJson<UserIdRequest>,
369 ) -> Result<Json<ListSessionsResponse<DB::Session>>, AuthError> {
370 let ctx = state.to_context();
371 let response = list_user_sessions_core(&body, &ctx).await?;
372 Ok(Json(response))
373 }
374
375 async fn handle_ban_user<DB: DatabaseAdapter>(
376 State(state): State<AuthState<DB>>,
377 Extension(ps): Extension<Arc<PluginState>>,
378 AdminSession { user, .. }: AdminSession<DB>,
379 ValidatedJson(body): ValidatedJson<BanUserRequest>,
380 ) -> Result<Json<UserResponse<DB::User>>, AuthError> {
381 let ctx = state.to_context();
382 let response = ban_user_core(&body, user.id(), &ps.config, &ctx).await?;
383 Ok(Json(response))
384 }
385
386 async fn handle_unban_user<DB: DatabaseAdapter>(
387 State(state): State<AuthState<DB>>,
388 AdminSession { .. }: AdminSession<DB>,
389 ValidatedJson(body): ValidatedJson<UserIdRequest>,
390 ) -> Result<Json<UserResponse<DB::User>>, AuthError> {
391 let ctx = state.to_context();
392 let response = unban_user_core(&body, &ctx).await?;
393 Ok(Json(response))
394 }
395
396 async fn handle_impersonate_user<DB: DatabaseAdapter>(
397 State(state): State<AuthState<DB>>,
398 AdminSession { user, .. }: AdminSession<DB>,
399 ValidatedJson(body): ValidatedJson<UserIdRequest>,
400 ) -> Result<
401 (
402 [(header::HeaderName, String); 1],
403 Json<SessionUserResponse<DB::Session, DB::User>>,
404 ),
405 AuthError,
406 > {
407 let ctx = state.to_context();
408 let (response, token) = impersonate_user_core(&body, user.id(), None, None, &ctx).await?;
409 let cookie = state.session_cookie(&token);
410 Ok(([(header::SET_COOKIE, cookie)], Json(response)))
411 }
412
413 async fn handle_stop_impersonating<DB: DatabaseAdapter>(
414 State(state): State<AuthState<DB>>,
415 CurrentSession { session, .. }: CurrentSession<DB>,
416 ) -> Result<
417 (
418 [(header::HeaderName, String); 1],
419 Json<SessionUserResponse<DB::Session, DB::User>>,
420 ),
421 AuthError,
422 > {
423 let ctx = state.to_context();
424 let token = session.token().to_string();
425 let (response, new_token) =
426 stop_impersonating_core(&session, &token, None, None, &ctx).await?;
427 let cookie = state.session_cookie(&new_token);
428 Ok(([(header::SET_COOKIE, cookie)], Json(response)))
429 }
430
431 async fn handle_revoke_user_session<DB: DatabaseAdapter>(
432 State(state): State<AuthState<DB>>,
433 AdminSession { .. }: AdminSession<DB>,
434 ValidatedJson(body): ValidatedJson<RevokeSessionRequest>,
435 ) -> Result<Json<SuccessResponse>, AuthError> {
436 let ctx = state.to_context();
437 let response = revoke_user_session_core(&body, &ctx).await?;
438 Ok(Json(response))
439 }
440
441 async fn handle_revoke_user_sessions<DB: DatabaseAdapter>(
442 State(state): State<AuthState<DB>>,
443 AdminSession { .. }: AdminSession<DB>,
444 ValidatedJson(body): ValidatedJson<UserIdRequest>,
445 ) -> Result<Json<SuccessResponse>, AuthError> {
446 let ctx = state.to_context();
447 let response = revoke_user_sessions_core(&body, &ctx).await?;
448 Ok(Json(response))
449 }
450
451 async fn handle_remove_user<DB: DatabaseAdapter>(
452 State(state): State<AuthState<DB>>,
453 AdminSession { user, .. }: AdminSession<DB>,
454 ValidatedJson(body): ValidatedJson<UserIdRequest>,
455 ) -> Result<Json<SuccessResponse>, AuthError> {
456 let ctx = state.to_context();
457 let response = remove_user_core(&body, user.id(), &ctx).await?;
458 Ok(Json(response))
459 }
460
461 async fn handle_set_user_password<DB: DatabaseAdapter>(
462 State(state): State<AuthState<DB>>,
463 AdminSession { .. }: AdminSession<DB>,
464 ValidatedJson(body): ValidatedJson<SetUserPasswordRequest>,
465 ) -> Result<Json<StatusResponse>, AuthError> {
466 let ctx = state.to_context();
467 let response = set_user_password_core(&body, &ctx).await?;
468 Ok(Json(response))
469 }
470
471 async fn handle_has_permission<DB: DatabaseAdapter>(
472 State(state): State<AuthState<DB>>,
473 Extension(ps): Extension<Arc<PluginState>>,
474 CurrentSession { user, .. }: CurrentSession<DB>,
475 ValidatedJson(body): ValidatedJson<HasPermissionRequest>,
476 ) -> Result<Json<PermissionResponse>, AuthError> {
477 let response = has_permission_core::<DB>(&body, &user, &ps.config).await?;
478 Ok(Json(response))
479 }
480
481 impl<DB: DatabaseAdapter> better_auth_core::AxumPlugin<DB> for AdminPlugin {
482 fn name(&self) -> &'static str {
483 "admin"
484 }
485
486 fn router(&self) -> axum::Router<AuthState<DB>> {
487 use axum::routing::{get, post};
488
489 let plugin_state = Arc::new(PluginState {
490 config: self.config.clone(),
491 });
492 axum::Router::new()
493 .route("/admin/set-role", post(handle_set_role::<DB>))
494 .route("/admin/create-user", post(handle_create_user::<DB>))
495 .route("/admin/list-users", get(handle_list_users::<DB>))
496 .route(
497 "/admin/list-user-sessions",
498 post(handle_list_user_sessions::<DB>),
499 )
500 .route("/admin/ban-user", post(handle_ban_user::<DB>))
501 .route("/admin/unban-user", post(handle_unban_user::<DB>))
502 .route(
503 "/admin/impersonate-user",
504 post(handle_impersonate_user::<DB>),
505 )
506 .route(
507 "/admin/stop-impersonating",
508 post(handle_stop_impersonating::<DB>),
509 )
510 .route(
511 "/admin/revoke-user-session",
512 post(handle_revoke_user_session::<DB>),
513 )
514 .route(
515 "/admin/revoke-user-sessions",
516 post(handle_revoke_user_sessions::<DB>),
517 )
518 .route("/admin/remove-user", post(handle_remove_user::<DB>))
519 .route(
520 "/admin/set-user-password",
521 post(handle_set_user_password::<DB>),
522 )
523 .route("/admin/has-permission", post(handle_has_permission::<DB>))
524 .layer(Extension(plugin_state))
525 .layer(Extension(AdminRole(self.config.admin_role.clone())))
526 }
527 }
528}