use askama::Template;
use axum::{
Router,
extract::{Path, State},
http::{HeaderMap, Uri},
response::{Html, Redirect},
routing::{get, post},
};
use ocre::{
Ctx, Result, Session,
mail::{self, Email},
render,
};
use crate::{
auth::{CurrentUser, origin},
auth_api::throttle,
models::{
auth_token::{self, EMAIL_CONFIRMATION, valid_minutes},
user::{self, User},
},
};
pub fn routes() -> Router<Ctx> {
Router::new()
.route("/confirmations", post(create))
.route("/confirmations/{token}", get(show).post(update))
}
#[derive(Template)]
#[template(path = "auth/confirmation_show.html")]
struct ShowView {
token: String,
}
pub async fn send_confirmation(ctx: &Ctx, uri: &Uri, user: &User) -> Result<()> {
let token = auth_token::issue(ctx, user.id, EMAIL_CONFIRMATION).await?;
let link = format!("{}/confirmations/{token}", origin(uri));
let hours = valid_minutes(EMAIL_CONFIRMATION) / 60;
let text = format!(
"Confirm your email address by opening this link within {hours} hours:\n\n{link}\n\nIf you did not sign up, ignore this email.\n"
);
let html = format!(
"<p><a href=\"{link}\">Confirm your email address</a> (valid {hours} hours).</p><p>If you did not sign up, ignore this email.</p>"
);
mail::send(ctx, Email::new(&user.email, "Confirm your email address", text).html(html)).await
}
async fn create(
State(ctx): State<Ctx>,
CurrentUser(user): CurrentUser,
session: Session,
headers: HeaderMap,
uri: Uri,
) -> Result<Redirect> {
throttle(&ctx, &headers, "confirmation").await?;
if user.confirmed() {
session.flash("notice", "Your email address is already confirmed.")?;
} else {
send_confirmation(&ctx, &uri, &user).await?;
session.flash("notice", "A new confirmation link is on its way.")?;
}
Ok(Redirect::to("/account"))
}
async fn show(Path(token): Path<String>) -> Result<Html<String>> {
render(&ShowView { token })
}
async fn update(State(ctx): State<Ctx>, session: Session, Path(token): Path<String>) -> Result<Redirect> {
match auth_token::consume(&ctx, EMAIL_CONFIRMATION, &token).await? {
Some(user_id) => {
user::confirm(&ctx, user_id).await?;
session.flash("notice", "Thanks, your email address is confirmed.")?;
}
None => session.flash("alert", "That confirmation link is invalid or has expired.")?,
}
Ok(Redirect::to("/account"))
}