use ocre::{Ctx, Result, Statement, params};
use serde::Deserialize;
pub const PASSWORD_RESET: &str = "password_reset";
pub const MAGIC_LINK: &str = "magic_link";
pub const EMAIL_CONFIRMATION: &str = "email_confirmation";
pub fn valid_minutes(purpose: &str) -> i64 {
if purpose == EMAIL_CONFIRMATION { 24 * 60 } else { 15 }
}
#[derive(Deserialize)]
struct Row {
user_id: i64,
}
pub async fn issue(ctx: &Ctx, user_id: i64, purpose: &str) -> Result<String> {
let token = ocre::token::generate();
ctx.db()?
.batch(vec![
Statement::new("DELETE FROM auth_tokens WHERE user_id = ?1 AND purpose = ?2", params![user_id, purpose]),
Statement::new(
"INSERT INTO auth_tokens (user_id, purpose, digest, expires_at) VALUES (?1, ?2, ?3, datetime('now', ?4))",
params![user_id, purpose, ocre::token::digest(&token), format!("+{} minutes", valid_minutes(purpose))],
),
])
.await?;
Ok(token)
}
pub async fn peek(ctx: &Ctx, purpose: &str, token: &str) -> Result<Option<i64>> {
let row: Option<Row> = ctx
.db()?
.first(
"SELECT user_id FROM auth_tokens WHERE digest = ?1 AND purpose = ?2 AND expires_at > datetime('now')",
params![ocre::token::digest(token), purpose],
)
.await?;
Ok(row.map(|row| row.user_id))
}
pub async fn consume(ctx: &Ctx, purpose: &str, token: &str) -> Result<Option<i64>> {
let row: Option<Row> = ctx
.db()?
.first(
"DELETE FROM auth_tokens WHERE digest = ?1 AND purpose = ?2 AND expires_at > datetime('now') RETURNING user_id",
params![ocre::token::digest(token), purpose],
)
.await?;
Ok(row.map(|row| row.user_id))
}