use ocre::{Ctx, Error, Result, Validator, params};
use serde::{Deserialize, Serialize};
const COLUMNS: &str = "id, user_id, name, last_used_at, created_at";
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ApiKey {
pub id: i64,
pub user_id: i64,
pub name: String,
pub last_used_at: Option<String>,
pub created_at: String,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct NewApiKey {
pub name: String,
}
impl NewApiKey {
pub fn validate(&self) -> Validator {
let mut v = Validator::new();
v.required("name", &self.name).max_length("name", &self.name, 100);
v
}
}
pub async fn create(ctx: &Ctx, user_id: i64, new: NewApiKey) -> Result<(ApiKey, String)> {
new.validate().finish()?;
let key = ocre::token::generate();
let sql = format!("INSERT INTO api_keys (user_id, name, digest) VALUES (?1, ?2, ?3) RETURNING {COLUMNS}");
let row = ctx
.db()?
.first(&sql, params![user_id, new.name.trim(), ocre::token::digest(&key)])
.await?
.ok_or_else(|| Error::internal("INSERT ... RETURNING returned no row"))?;
Ok((row, key))
}
pub async fn for_user(ctx: &Ctx, user_id: i64) -> Result<Vec<ApiKey>> {
let sql = format!("SELECT {COLUMNS} FROM api_keys WHERE user_id = ?1 ORDER BY id DESC");
ctx.db()?.all(&sql, params![user_id]).await
}
pub async fn revoke(ctx: &Ctx, user_id: i64, id: i64) -> Result<bool> {
Ok(ctx.db()?.execute("DELETE FROM api_keys WHERE id = ?1 AND user_id = ?2", params![id, user_id]).await? > 0)
}
pub async fn authenticate(ctx: &Ctx, key: &str) -> Result<Option<i64>> {
#[derive(Deserialize)]
struct Row {
id: i64,
user_id: i64,
#[serde(deserialize_with = "ocre::bool_from_sql")]
stale: bool,
}
let db = ctx.db()?;
let row: Option<Row> = db
.first(
"SELECT id, user_id, (last_used_at IS NULL OR last_used_at < datetime('now', '-1 hour')) AS stale \
FROM api_keys WHERE digest = ?1",
params![ocre::token::digest(key)],
)
.await?;
let Some(row) = row else { return Ok(None) };
if row.stale {
db.execute("UPDATE api_keys SET last_used_at = datetime('now') WHERE id = ?1", params![row.id]).await?;
}
Ok(Some(row.user_id))
}