use anyhow::{Context, Result};
use chrono::Utc;
use sea_orm::*;
use uuid::Uuid;
use crate::entities::{api_tokens, users};
/// Generate a new API token. Format: `dms_<32 url-safe chars>` so they're
/// distinguishable from session UUIDs at a glance.
pub fn generate_api_token() -> String {
let body = Uuid::new_v4().simple().to_string();
format!("dms_{body}")
}
pub async fn create_api_token(
db: &DatabaseConnection,
user_id: Uuid,
label: Option<String>,
expires_in_days: Option<i64>,
) -> Result<api_tokens::Model> {
let token = generate_api_token();
let expires_at = expires_in_days.map(|d| (Utc::now() + chrono::Duration::days(d)).into());
let active = api_tokens::ActiveModel {
id: Set(Uuid::now_v7()),
user_id: Set(user_id),
token: Set(token),
label: Set(label),
last_used_at: Set(None),
expires_at: Set(expires_at),
created_at: Set(Utc::now().into()),
};
active.insert(db).await.context("insert api_token")
}
/// Look up the user owning a token. Returns Ok(None) for unknown / expired
/// tokens. Side-effect: bumps last_used_at when the token is valid.
pub async fn validate_api_token(
db: &DatabaseConnection,
token: &str,
) -> Result<Option<users::Model>> {
let row = api_tokens::Entity::find()
.filter(api_tokens::Column::Token.eq(token))
.one(db)
.await
.context("query api_token")?;
let Some(row) = row else { return Ok(None); };
if let Some(exp) = row.expires_at
&& exp < Utc::now()
{
return Ok(None);
}
// Bump last_used_at — best effort, don't fail validation on update error.
let mut update: api_tokens::ActiveModel = row.clone().into();
update.last_used_at = Set(Some(Utc::now().into()));
let _ = update.update(db).await;
let user = users::Entity::find_by_id(row.user_id)
.filter(users::Column::IsActive.eq(true))
.one(db)
.await
.context("query user for api_token")?;
Ok(user)
}
pub async fn delete_api_token(
db: &DatabaseConnection,
user_id: Uuid,
token_id: Uuid,
) -> Result<u64> {
let result = api_tokens::Entity::delete_many()
.filter(api_tokens::Column::Id.eq(token_id))
.filter(api_tokens::Column::UserId.eq(user_id))
.exec(db)
.await
.context("delete api_token")?;
Ok(result.rows_affected)
}
pub async fn list_api_tokens(
db: &DatabaseConnection,
user_id: Uuid,
) -> Result<Vec<api_tokens::Model>> {
api_tokens::Entity::find()
.filter(api_tokens::Column::UserId.eq(user_id))
.order_by_desc(api_tokens::Column::CreatedAt)
.all(db)
.await
.context("list api_tokens")
}
⭐
Log in to mark this repository.
2.7 KiB · text
History
c20d4f7