Skip to main content

better_auth_api/plugins/api_key/
callbacks.rs

1use async_trait::async_trait;
2use better_auth_core::{
3    AuthConfig, AuthContext, AuthError, AuthRequest, AuthResponse, AuthResult, AuthSchema,
4    RuntimeExtensions,
5    plugin::MetadataMap,
6    store::{ApiKeyStore, MemberStore, OrganizationStore},
7};
8use serde_json::Value;
9use std::collections::HashMap;
10
11/// Resource permissions assigned to an API key.
12pub type ApiKeyPermissions = HashMap<String, Vec<String>>;
13
14/// Endpoint data available to application API key callbacks.
15#[derive(Clone, Copy)]
16pub struct ApiKeyEndpoint<'a> {
17    /// HTTP request, absent for a requestless server API call.
18    pub request: Option<&'a AuthRequest>,
19    /// Endpoint path; absent in the server-only verification hook matcher.
20    pub path: Option<&'a str>,
21    /// Parsed endpoint input, including route defaults.
22    pub body: &'a Value,
23    /// Global authentication configuration.
24    pub auth_config: &'a AuthConfig,
25    /// Application runtime state registered with the authentication instance.
26    pub extensions: &'a RuntimeExtensions,
27    /// Metadata registered by enabled plugins.
28    pub metadata: &'a MetadataMap,
29    /// Database API key adapter, independent of the selected key storage mode.
30    pub api_keys: &'a dyn ApiKeyStore,
31    /// Organization adapter for organization-owned credentials.
32    pub organizations: &'a dyn OrganizationStore,
33    /// Organization memberships available to application permission policies.
34    pub members: &'a dyn MemberStore,
35}
36
37impl<'a> ApiKeyEndpoint<'a> {
38    pub(super) fn new<S: AuthSchema>(
39        ctx: &'a AuthContext<S>,
40        request: Option<&'a AuthRequest>,
41        path: Option<&'a str>,
42        body: &'a Value,
43    ) -> Self {
44        Self {
45            request,
46            path,
47            body,
48            auth_config: &ctx.config,
49            extensions: &ctx.extensions,
50            metadata: &ctx.metadata,
51            api_keys: ctx.database.as_ref(),
52            organizations: ctx.database.as_ref(),
53            members: ctx.database.as_ref(),
54        }
55    }
56}
57
58/// Application credential generation, before hashing and starting-character storage.
59#[async_trait]
60pub trait ApiKeyGenerator: Send + Sync {
61    async fn generate(&self, length: usize, prefix: Option<&str>) -> AuthResult<String>;
62}
63
64/// Synchronous key extraction used by the session hook matcher and handler.
65pub trait ApiKeyGetter: Send + Sync {
66    fn get(&self, ctx: ApiKeyEndpoint<'_>) -> AuthResult<Option<String>>;
67}
68
69/// Application validation before credential state and permission checks.
70#[async_trait]
71pub trait ApiKeyValidator: Send + Sync {
72    async fn validate(&self, key: &str, ctx: ApiKeyEndpoint<'_>) -> AuthResult<bool>;
73}
74
75/// Dynamic creation permissions. Explicit permissions still cause this callback to run.
76#[async_trait]
77pub trait ApiKeyDefaultPermissions: Send + Sync {
78    async fn permissions(
79        &self,
80        reference_id: &str,
81        ctx: ApiKeyEndpoint<'_>,
82    ) -> AuthResult<ApiKeyPermissions>;
83}
84
85// The HTTP router hides ordinary callback errors; requestless APIs preserve them.
86pub(super) fn callback_error(error: AuthError, request: Option<&AuthRequest>) -> AuthError {
87    if request.is_some()
88        && error.status_code() == 500
89        && !matches!(error, AuthError::Response(_) | AuthError::Upstream { .. })
90    {
91        better_auth_core::observability::logger::current().error(
92            "API key callback failed",
93            &[better_auth_core::observability::LogArgument::Error(&error)],
94        );
95        AuthResponse::new(500).into()
96    } else {
97        error
98    }
99}