From 63b59432a3e5d01c16527fa8bb2fd3bdd8153382 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 30 Jul 2023 02:50:13 +0100 Subject: [PATCH] feat: simplified the signup --- README.md | 26 +-- src/bin/new_users.rs | 9 +- src/bin/update_groups.rs | 2 +- src/lib.rs | 55 ++---- src/main.rs | 10 +- src/methods/account_new.rs | 338 ++++++++++------------------------ src/methods/account_update.rs | 9 +- 7 files changed, 136 insertions(+), 313 deletions(-) diff --git a/README.md b/README.md index 36dfe48..4392de7 100644 --- a/README.md +++ b/README.md @@ -34,38 +34,22 @@ Changing ``userPassword`` requires the existing password in teh apssword field a ```json { + "auth" : "Authcode from the email", "user" : "username the user wants", - "mail" : "An email account that matches what is on wolves", - "name_first": "Firstname", - "name_second": "Surname" + "pass" : "password teh user wants" } ``` -Username taken: +Username taken: ```json {"result": "error", "error": "username not available"} ``` -Email used: +Invalid Auth: ```json -{"result": "error", "error": "email in use"} +{"result": "error", "error": "Invalid auth"} ``` -### POST /ldap/new/verify - -```json -{ - "auth_code": "Auth code that got send in the email", - "password": "Password the user wants to use" -} -``` - -Auth code non existent: -```json -{"result": "error"} -``` - - ## Responses Generic responses which is used unless otherwise specified above. diff --git a/src/bin/new_users.rs b/src/bin/new_users.rs index 05725f4..2c10ac3 100644 --- a/src/bin/new_users.rs +++ b/src/bin/new_users.rs @@ -4,7 +4,7 @@ use lettre::{ Message, SmtpTransport, Transport, }; use maud::html; -use skynet_ldap_backend::{db_init, get_config, read_csv, Accounts, AccountsNew, Config, Record, get_now_iso, random_string}; +use skynet_ldap_backend::{db_init, get_config, get_now_iso, random_string, read_csv, Accounts, AccountsNew, Config, Record}; use sqlx::{Pool, Sqlite}; #[async_std::main] @@ -18,7 +18,7 @@ async fn main() { if record.email.trim().ends_with("@skynet.ie") { continue; } - + // check if the email is already in the db if !check(&db, &record.email).await { continue; @@ -164,8 +164,8 @@ fn send_mail(config: &Config, record: &Record, auth: &str) -> Result, record: &Record, auth: &str) -> Result, sqlx::Error> { sqlx::query_as::<_, AccountsNew>( " - INSERT OR REPLACE INTO accounts_new (mail, auth_code, date_iso, date_expiry, name_first, name_surname) - VALUES (?1, ?2, ?3, ?4, ?5, ?6) + INSERT OR REPLACE INTO accounts_new (mail, auth_code, date_iso, date_expiry, name_first, name_surname, id_student) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) ", ) .bind(record.email.to_owned()) @@ -174,6 +174,7 @@ async fn save_to_db(db: &Pool, record: &Record, auth: &str) -> Result Result, Error> { .connect_with(SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?.create_if_missing(true)) .await?; - sqlx::query( - "CREATE TABLE IF NOT EXISTS accounts_pending ( - user text primary key, - mail text not null, - cn text not null, - sn text not null, - action text not null, - auth_code text not null, - expiry integer not null - )", - ) - .execute(&pool) - .await?; - sqlx::query( "CREATE TABLE IF NOT EXISTS accounts_new ( mail text primary key, @@ -74,7 +50,8 @@ pub async fn db_init(config: &Config) -> Result, Error> { date_iso text not null, date_expiry text not null, name_first text not null, - name_surname integer not null + name_surname integer not null, + id_student text not null )", ) .execute(&pool) @@ -100,6 +77,10 @@ pub async fn db_init(config: &Config) -> Result, Error> { .await?; sqlx::query("CREATE INDEX IF NOT EXISTS index_uid_number ON accounts (uid)").execute(&pool).await?; + sqlx::query("CREATE INDEX IF NOT EXISTS index_mail ON accounts (mail)").execute(&pool).await?; + sqlx::query("CREATE INDEX IF NOT EXISTS index_student_id ON accounts (student_id)") + .execute(&pool) + .await?; update_accounts(&pool, config).await; diff --git a/src/main.rs b/src/main.rs index 71aceef..5b96b59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ -use skynet_ldap_backend::methods::account_new::{post_new_account, post_new_account_confirmation}; -use skynet_ldap_backend::methods::account_update::post_update_ldap; -use skynet_ldap_backend::{db_init, get_config, State}; +use skynet_ldap_backend::{ + db_init, get_config, + methods::{account_new::post_new_account, account_update::post_update_ldap}, + State, +}; #[async_std::main] async fn main() -> tide::Result<()> { @@ -19,9 +21,7 @@ async fn main() -> tide::Result<()> { let mut app = tide::with_state(state); app.at("/ldap/update").post(post_update_ldap); - app.at("/ldap/new").post(post_new_account); - app.at("/ldap/new/verify").post(post_new_account_confirmation); app.listen(host_port).await?; Ok(()) diff --git a/src/methods/account_new.rs b/src/methods/account_new.rs index aa63564..4f2ccdf 100644 --- a/src/methods/account_new.rs +++ b/src/methods/account_new.rs @@ -1,42 +1,40 @@ -use crate::{Accounts, AccountsPending, State, get_now_iso, AccountsNew, random_string}; -use ldap3::exop::PasswordModify; -use ldap3::result::ExopResult; -use ldap3::{LdapConn, Scope}; +use crate::{get_now_iso, random_string, Accounts, AccountsNew, Config, State}; +use ldap3::{exop::PasswordModify, LdapConn, Scope}; use sqlx::{Error, Pool, Sqlite}; use std::collections::HashSet; -use tide::prelude::{json, Deserialize}; -use tide::Request; +use tide::{ + prelude::{json, Deserialize}, + Request, +}; #[derive(Debug, Deserialize)] pub struct LdapNewUser { + auth: String, user: String, - // email that is used on wolves - mail: String, - name_first: String, - name_second: String, + pass: String, } /// Handles initial detail entering page +/// Verify users have access to said email +/// Get users to set username and password. pub async fn post_new_account(mut req: Request) -> tide::Result { - // check if username exists - // search ldap and local - // send back that that username is in use - - // check local if email exists (periodic sync) - // if not then request info on individual user - // if there is no email matching still send 200 back - // if there is then send email with link to the account - - // save user details in the db - let LdapNewUser { + auth, user, - mail, - name_first, - name_second, + pass, } = req.body_json().await?; let config = &req.state().config; + let db = &req.state().db; + + // ensure there are no old requests + db_pending_clear_expired(db).await?; + + let user_db = if let Some(x) = db_get_user(db, &auth).await { + x + } else { + return Ok(json!({"result": "error", "error": "Invalid auth"}).into()); + }; // easier to give each request its own connection let mut ldap = LdapConn::new(&config.ldap_host)?; @@ -53,101 +51,24 @@ pub async fn post_new_account(mut req: Request) -> tide::Result { } } - let filter_email = format!("(mail={})", mail); - if let Ok(x) = ldap.search("ou=users,dc=skynet,dc=ie", Scope::OneLevel, &filter_email, vec!["*"]) { - if let Ok((rs, _res)) = x.success() { - if !rs.is_empty() { - return Ok(json!({"result": "error", "error": "email in use"}).into()); - } - } - } - - // done with ldap + // done with anon ldap ldap.unbind()?; - // setup the pool, going to need it for the rest of it - let pool = &req.state().db; + ldap_create_account(config, db, user_db, &user, &pass).await?; - db_pending_clear_expired(pool).await?; - - // now check local - if let Ok(results) = sqlx::query_as::<_, AccountsPending>( - r#" - SELECT * - FROM accounts_pending - WHERE user == ? - "#, - ) - .bind(&user) - .fetch_all(pool) - .await - { - if !results.is_empty() { - return Ok(json!({"result": "error", "error": "username not available"}).into()); - } - } - - if let Ok(results) = sqlx::query_as::<_, AccountsPending>( - r#" - SELECT * - FROM accounts_pending - WHERE mail == ? - "#, - ) - .bind(&mail) - .fetch_all(pool) - .await - { - if !results.is_empty() { - return Ok(json!({"result": "error", "error": "email in use"}).into()); - } - } - - // frontend now tells user to check their email - - /* - TODO: - now check with wolves to see if the email is already activated - use email as primary match - then search up to see if teh wolves ID has a match - if not generate tuhe user and send email - */ - - let cn = format!("{} {}", name_first, name_second); - let auth_code = create_random_string(50); - // 1 hour expiry - let expiry = get_now() + (60 * 60); - - sqlx::query_as::<_, AccountsPending>( - r#" - INSERT OR REPLACE INTO accounts_pending (user, mail, cn, sn, action, auth_code, expiry) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) - "#, - ) - .bind(&user) - .bind(&mail) - .bind(&cn) - .bind(&name_second) - .bind("account_new") - .bind(&auth_code) - .bind(expiry) - .fetch_optional(pool) - .await - .ok(); - - // TODO: Send email with auth_code + // account now created, delete from the new table + account_verification_clear_pending(db, &auth).await?; Ok(json!({"result": "success"}).into()) } // clear the db of expired ones before checking for username and validating inputs -async fn db_pending_clear_expired(pool: &Pool) -> Result, Error> { - let now = get_now(); - sqlx::query_as::<_, AccountsPending>( +async fn db_pending_clear_expired(pool: &Pool) -> Result, Error> { + sqlx::query_as::<_, AccountsNew>( r#" DELETE - FROM accounts_pending - WHERE expiry < ? + FROM accounts_new + WHERE date_expiry < ? "#, ) .bind(get_now_iso(true)) @@ -155,57 +76,83 @@ async fn db_pending_clear_expired(pool: &Pool) -> Result, auth: &str) -> Option { + if let Ok(res) = sqlx::query_as::<_, AccountsNew>( + r#" + SELECT * + FROM accounts_new + WHERE auth_code == ? + "#, + ) + .bind(auth) + .fetch_all(pool) + .await + { + if !res.is_empty() { + return Some(res[0].to_owned()); + } + } -#[derive(Debug, Deserialize)] -pub struct LdapUserVerify { - auth_code: String, - password: String, + None } -/// Handles the verification that a user has access to the email -pub async fn post_new_account_confirmation(mut req: Request) -> tide::Result { - let user_verify: LdapUserVerify = req.body_json().await?; - - let State { - db, - config, - .. - } = &req.state(); - - // setup ldap connection +async fn ldap_create_account(config: &Config, db: &Pool, user: AccountsNew, username: &str, pass: &str) -> Result<(), ldap3::LdapError> { let mut ldap = LdapConn::new(&config.ldap_host)?; ldap.simple_bind(&config.ldap_admin, &config.ldap_admin_pw)?.success()?; - // make sure to clear out the expired ones first - db_pending_clear_expired(db).await?; - - // search db for auth_code - let results = account_verification_find_pending(db, "account_new", &user_verify.auth_code).await; - - if results.is_empty() { - return Ok(json!({"result": "error"}).into()); - } - - let user_details = &results[0]; + let dn = format!("uid={},ou=users,dc=skynet,dc=ie", username); + let cn = format!("{} {}", &user.name_first, &user.name_surname); + let home_directory = format!("/home/{}", username); + let password_tmp = random_string(50); + let labeled_uri = format!("ldap:///ou=groups,dc=skynet,dc=ie??sub?(&(objectclass=posixgroup)(memberuid={}))", username); + let sk_mail = format!("{}@skynet.ie", username); + let sk_created = get_sk_created(); let uid_number = get_max_uid_number(db).await; - // create teh new user account in ldap - account_verification_new_account(&mut ldap, user_details, uid_number).await?; + // create user + ldap.add( + &dn, + vec![ + ("objectClass", HashSet::from(["top", "person", "posixaccount", "ldapPublicKey", "inetOrgPerson", "skPerson"])), + // top + ("ou", HashSet::from(["users"])), + // person + ("uid", HashSet::from([username])), + ("cn", HashSet::from([cn.as_str()])), + // posixaccount + ("uidNumber", HashSet::from([uid_number.to_string().as_str()])), + ("gidNumber", HashSet::from(["1001"])), + ("homedirectory", HashSet::from([home_directory.as_str()])), + ("userpassword", HashSet::from([password_tmp.as_str()])), + // inetOrgPerson + ("mail", HashSet::from([user.mail.as_str()])), + ("sn", HashSet::from([user.name_surname.as_str()])), + // skPerson + ("labeledURI", HashSet::from([labeled_uri.as_str()])), + ("skMail", HashSet::from([sk_mail.as_str()])), + ("skID", HashSet::from([user.id_student.as_str()])), + ("skCreated", HashSet::from([sk_created.as_str()])), + // 1 = secure, automatic since its a new account + ("skSecure", HashSet::from(["1"])), + // quotas + ("quotaEmail", HashSet::from(["10737418240"])), + ("quotaDisk", HashSet::from(["10737418240"])), + ], + )? + .success()?; // now to properly set teh password - account_verification_reset_password_admin(&mut ldap, &user_details.user, &user_verify.password)?; + let tmp = PasswordModify { + user_id: Some(&dn), + old_pass: None, + new_pass: Some(pass), + }; + + ldap.extended(tmp).unwrap(); - // done with ldap ldap.unbind()?; - // delete from tmp db - account_verification_clear_pending(db, &user_verify.auth_code).await?; - - // add new users to teh local database - account_verification_add_local(db, &user_details.user, uid_number).await?; - - // frontend tells user that initial password ahs been sent to tehm - Ok(json!({"result": "success"}).into()) + Ok(()) } fn get_sk_created() -> String { @@ -235,87 +182,10 @@ async fn get_max_uid_number(db: &Pool) -> i64 { 9999 } -async fn account_verification_find_pending(db: &Pool, action: &str, auth_code: &str) -> Vec { - sqlx::query_as::<_, AccountsPending>( +async fn account_verification_clear_pending(db: &Pool, auth_code: &str) -> Result, Error> { + sqlx::query_as::<_, AccountsNew>( r#" - SELECT * - FROM accounts_pending - WHERE auth_code == ? AND action == ? - "#, - ) - .bind(auth_code) - .bind(action) - .fetch_all(db) - .await - .unwrap_or(vec![]) -} - -async fn account_verification_new_account(ldap: &mut LdapConn, user_details: &AccountsPending, uid_number: i64) -> Result<(), ldap3::LdapError> { - let AccountsPending { - user, - mail, - cn, - sn, - .. - } = user_details; - - let dn = format!("uid={},ou=users,dc=skynet,dc=ie", user); - let home_directory = format!("/home/{}", user); - let password_tmp = random_string(50); - let labeled_uri = format!("ldap:///ou=groups,dc=skynet,dc=ie??sub?(&(objectclass=posixgroup)(memberuid={}))", user); - let sk_mail = format!("{}@skynet.ie", user); - let sk_created = get_sk_created(); - - // create user - ldap.add( - &dn, - vec![ - ("objectClass", HashSet::from(["top", "person", "posixaccount", "ldapPublicKey", "inetOrgPerson", "skPerson"])), - // top - ("ou", HashSet::from(["users"])), - // person - ("uid", HashSet::from([user.as_str()])), - ("cn", HashSet::from([cn.as_str()])), - // posixaccount - ("uidNumber", HashSet::from([uid_number.to_string().as_str()])), - ("gidNumber", HashSet::from(["1001"])), - ("homedirectory", HashSet::from([home_directory.as_str()])), - ("userpassword", HashSet::from([password_tmp.as_str()])), - // inetOrgPerson - ("mail", HashSet::from([mail.as_str()])), - ("sn", HashSet::from([sn.as_str()])), - // skPerson - ("labeledURI", HashSet::from([labeled_uri.as_str()])), - ("skMail", HashSet::from([sk_mail.as_str()])), - // need to get this from wolves - //("skID", HashSet::from(["12345678"])), - ("skCreated", HashSet::from([sk_created.as_str()])), - // 1 = secure, automatic since its a new account - ("skSecure", HashSet::from(["1"])), - ], - )? - .success()?; - - Ok(()) -} - -fn account_verification_reset_password_admin(ldap: &mut LdapConn, user: &str, pass: &str) -> Result { - let dn = format!("uid={},ou=users,dc=skynet,dc=ie", user); - - // now to properly set teh password - let tmp = PasswordModify { - user_id: Some(&dn), - old_pass: None, - new_pass: Some(pass), - }; - - ldap.extended(tmp) -} - -async fn account_verification_clear_pending(db: &Pool, auth_code: &str) -> Result, Error> { - sqlx::query_as::<_, AccountsPending>( - r#" - DELETE FROM accounts_pending + DELETE FROM accounts_new WHERE auth_code == ? "#, ) @@ -323,17 +193,3 @@ async fn account_verification_clear_pending(db: &Pool, auth_code: &str) .fetch_all(db) .await } - -async fn account_verification_add_local(db: &Pool, user: &str, uid_number: i64) -> Result, Error> { - sqlx::query_as::<_, Accounts>( - " - INSERT OR REPLACE INTO accounts (user, uid_number, enabled) - VALUES (?1, ?2, ?3) - ", - ) - .bind(user) - .bind(uid_number) - .bind(false) - .fetch_optional(db) - .await -} diff --git a/src/methods/account_update.rs b/src/methods/account_update.rs index 6ec2bac..628f5ff 100644 --- a/src/methods/account_update.rs +++ b/src/methods/account_update.rs @@ -1,9 +1,10 @@ use crate::State; -use ldap3::exop::PasswordModify; -use ldap3::{LdapConn, Mod, Scope, SearchEntry}; +use ldap3::{exop::PasswordModify, LdapConn, Mod, Scope, SearchEntry}; use std::collections::HashSet; -use tide::prelude::{json, Deserialize}; -use tide::Request; +use tide::{ + prelude::{json, Deserialize}, + Request, +}; #[derive(Debug, Deserialize)] pub struct LdapUpdate {