use rusqlite::{TransactionBehavior, params};
use super::{
AuthError, AuthResult, AuthStore, User, clean_name, ensure_org_tx, external, normalize_email,
};
use crate::org::OrgId;
impl AuthStore {
pub fn setup_needed(&self) -> AuthResult<bool> {
let n: i64 = self
.db()
.query_row("SELECT COUNT(*) FROM users", [], |r| r.get(0))?;
Ok(n == 0)
}
pub fn create_first_admin(&self, email: &str, name: &str, password: &str) -> AuthResult<User> {
self.first_admin(email, name, Some(password), None)
}
pub fn claim_first_admin(
&self,
email: &str,
name: &str,
password: Option<&str>,
link: &external::ExternalIdentity,
) -> AuthResult<User> {
self.first_admin(email, name, password, Some(link))
}
fn first_admin(
&self,
email: &str,
name: &str,
password: Option<&str>,
link: Option<&external::ExternalIdentity>,
) -> AuthResult<User> {
let email = normalize_email(email)?;
let name = clean_name(name)?;
let hash = password.map(|p| self.hash_pw(p)).transpose()?;
let now = self.now();
let mut db = self.db();
let tx = db.transaction_with_behavior(TransactionBehavior::Immediate)?;
let n: i64 = tx.query_row("SELECT COUNT(*) FROM users", [], |r| r.get(0))?;
if n > 0 {
return Err(AuthError::Conflict("setup is already done".into()));
}
tx.execute(
"INSERT INTO users (email, name, password_hash, platform_admin, created_at)
VALUES (?1, ?2, ?3, 1, ?4)",
params![email, name, hash, now],
)?;
let id = tx.last_insert_rowid();
if let Some(ext) = link {
let verified = ext.email_verified
&& ext
.email
.as_deref()
.is_some_and(|e| e.eq_ignore_ascii_case(&email));
let shown = ext.email.as_deref().and_then(|e| normalize_email(e).ok());
external::insert_identity(&tx, id, ext, shown.as_deref(), verified, now)?;
}
let org = OrgId::default_org();
ensure_org_tx(&tx, &org, now)?;
tx.execute(
"INSERT INTO memberships (user_id, org, role, created_at) VALUES (?1, ?2, 'owner', ?3)",
params![id, org.as_str(), now],
)?;
tx.commit()?;
drop(db);
self.user(id)
}
}