use std::net::IpAddr;
use serde_json::Value;
use super::events::{LoggedIn, Registered, announce};
use super::module::{Registration, after_login};
use super::{User, intended, login};
use crate::db::Model;
use crate::{AppState, Error, Result, Session};
pub fn registration_open(state: &AppState) -> bool {
state
.auth
.as_ref()
.is_some_and(|settings| settings.registration)
}
pub async fn register_verified(
state: &AppState,
name: &str,
email: &str,
fields: &[(&str, &str)],
) -> Result<User> {
let Some(settings) = state.auth.clone().filter(|s| s.registration) else {
return Err(Error::Forbidden);
};
let email = super::user::normalize_email(email);
let name = match name.trim() {
"" => email.split('@').next().unwrap_or_default().to_owned(),
name => name.to_owned(),
};
let user = User {
name: name.clone(),
email: email.clone(),
password: String::new(),
email_verified_at: Some(crate::db::now()),
..User::default()
};
let id = User::create(&state.db, user).await?.id;
if let Some(hook) = &settings.on_registered {
let mut map = serde_json::Map::new();
map.insert("name".into(), Value::String(name));
map.insert("email".into(), Value::String(email.clone()));
for (key, value) in fields {
map.insert((*key).to_owned(), Value::String((*value).to_owned()));
}
let user = User::find_or_404(&state.db, id).await?;
if let Err(err) = hook(state.clone(), user, Registration { fields: map }).await {
crate::db::sql("DELETE FROM users WHERE id = ?")
.bind(id)
.execute(&state.db)
.await?;
return Err(err);
}
}
let user = User::find_or_404(&state.db, id).await?;
let event = Registered {
user_id: user.id,
email: user.email.clone(),
};
announce(state, event).await;
Ok(user)
}
pub async fn sign_in(
state: &AppState,
session: &Session,
user: &User,
remember: bool,
ip: Option<IpAddr>,
) -> Result<String> {
let to = match &state.auth {
Some(settings) => after_login(state, settings, session),
None => intended(
session,
state.url("home", &[]).unwrap_or_else(|_| "/".into()),
),
};
if let Some(second) = &state.second_factor
&& (second.required)(user.clone(), state.clone()).await?
{
super::second_factor::begin(session, user, &user.email, remember, to)?;
return state.url(&second.challenge, &[]);
}
super::second_factor::clear(session);
let lifetime = remember.then_some(state.config.remember_lifetime);
login(session, user, lifetime)?;
super::account::mark_confirmed(session)?;
let event = LoggedIn {
user_id: user.id,
ip: ip.map(|ip| ip.to_string()),
};
announce(state, event).await;
Ok(to)
}
pub fn confirm_identity(session: &Session) -> Result<String> {
super::account::mark_confirmed(session)?;
Ok(super::account::confirmed_destination(session))
}