use async_trait::async_trait;
use better_auth_core::{
AuthConfig, AuthContext, AuthError, AuthRequest, AuthResponse, AuthResult, AuthSchema,
RuntimeExtensions,
plugin::MetadataMap,
store::{ApiKeyStore, MemberStore, OrganizationStore},
};
use serde_json::Value;
use std::collections::HashMap;
pub type ApiKeyPermissions = HashMap<String, Vec<String>>;
#[derive(Clone, Copy)]
pub struct ApiKeyEndpoint<'a> {
pub request: Option<&'a AuthRequest>,
pub path: Option<&'a str>,
pub body: &'a Value,
pub auth_config: &'a AuthConfig,
pub extensions: &'a RuntimeExtensions,
pub metadata: &'a MetadataMap,
pub api_keys: &'a dyn ApiKeyStore,
pub organizations: &'a dyn OrganizationStore,
pub members: &'a dyn MemberStore,
}
impl<'a> ApiKeyEndpoint<'a> {
pub(super) fn new<S: AuthSchema>(
ctx: &'a AuthContext<S>,
request: Option<&'a AuthRequest>,
path: Option<&'a str>,
body: &'a Value,
) -> Self {
Self {
request,
path,
body,
auth_config: &ctx.config,
extensions: &ctx.extensions,
metadata: &ctx.metadata,
api_keys: ctx.database.as_ref(),
organizations: ctx.database.as_ref(),
members: ctx.database.as_ref(),
}
}
}
#[async_trait]
pub trait ApiKeyGenerator: Send + Sync {
async fn generate(&self, length: usize, prefix: Option<&str>) -> AuthResult<String>;
}
pub trait ApiKeyGetter: Send + Sync {
fn get(&self, ctx: ApiKeyEndpoint<'_>) -> AuthResult<Option<String>>;
}
#[async_trait]
pub trait ApiKeyValidator: Send + Sync {
async fn validate(&self, key: &str, ctx: ApiKeyEndpoint<'_>) -> AuthResult<bool>;
}
#[async_trait]
pub trait ApiKeyDefaultPermissions: Send + Sync {
async fn permissions(
&self,
reference_id: &str,
ctx: ApiKeyEndpoint<'_>,
) -> AuthResult<ApiKeyPermissions>;
}
pub(super) fn callback_error(error: AuthError, request: Option<&AuthRequest>) -> AuthError {
if request.is_some()
&& error.status_code() == 500
&& !matches!(error, AuthError::Response(_) | AuthError::Upstream { .. })
{
better_auth_core::observability::logger::current().error(
"API key callback failed",
&[better_auth_core::observability::LogArgument::Error(&error)],
);
AuthResponse::new(500).into()
} else {
error
}
}