Skip to main content

better_auth_api/plugins/
helpers.rs

1//! Shared helpers for plugin implementations.
2//!
3//! Extracted to avoid duplicating common patterns across plugins (DRY).
4
5use better_auth_core::adapters::DatabaseAdapter;
6use better_auth_core::entity::AuthApiKey;
7use better_auth_core::{AuthContext, AuthError, AuthResult};
8
9/// Convert an `expiresIn` value (milliseconds from now) into an RFC 3339
10/// `expires_at` timestamp string.
11///
12/// Returns `None` when `expires_in_ms` is `None`.
13pub fn expires_in_to_at(expires_in_ms: Option<i64>) -> AuthResult<Option<String>> {
14    match expires_in_ms {
15        Some(ms) => {
16            let duration = chrono::Duration::try_milliseconds(ms)
17                .ok_or_else(|| AuthError::bad_request("expiresIn is out of range"))?;
18            let dt = chrono::Utc::now()
19                .checked_add_signed(duration)
20                .ok_or_else(|| AuthError::bad_request("expiresIn is out of range"))?;
21            Ok(Some(dt.to_rfc3339()))
22        }
23        None => Ok(None),
24    }
25}
26
27/// Fetch an API key by ID and verify that it belongs to the given user.
28///
29/// Returns `AuthError::not_found` if the key does not exist or belongs to
30/// another user.  This pattern was duplicated in `handle_get`, `handle_update`,
31/// and `handle_delete`.
32pub async fn get_owned_api_key<DB: DatabaseAdapter>(
33    ctx: &AuthContext<DB>,
34    key_id: &str,
35    user_id: &str,
36) -> AuthResult<DB::ApiKey> {
37    let api_key = ctx
38        .database
39        .get_api_key_by_id(key_id)
40        .await?
41        .ok_or_else(|| AuthError::not_found("API key not found"))?;
42
43    if api_key.user_id() != user_id {
44        return Err(AuthError::not_found("API key not found"));
45    }
46
47    Ok(api_key)
48}