Skip to main content

Overview

Faculty Bot implements a secure two-step email verification system that ensures only students with valid university email addresses can access the server. The system uses temporary verification codes sent via email and stores verified users in a PostgreSQL database.

Verification Flow

1

Request Verification

Users initiate verification with /verify init and provide their student email addressThe email must end with @stud.hs-kempten.de to be valid.
2

Code Generation

A unique verification code is generated and temporarily stored in memoryThe code is sent to the provided email address via the email task system.
3

Submit Code

Users enter the code using /verify code to complete verificationThe bot validates the code against the stored value.
4

Role Assignment

Upon successful verification, the user receives the verified roleUser data is permanently stored in the database.

Implementation Details

Email Validation

The system validates email addresses at multiple stages:
// Check if email ends with the required domain
if !email_used.ends_with("@stud.hs-kempten.de") {
    return Err(Error::WithMessage(lang.invalid_email().into()));
}

// Check if email is already registered
let user = sqlx::query_as::<sqlx::Postgres, structs::VerifiedUsers>(
    "SELECT * FROM verified_users WHERE user_email = $1",
)
.bind(&email_used)
.fetch_optional(pool)
.await?;

if user.is_some() {
    return Err(Error::WithMessage(lang.err_already_verified().into()));
}
Reference: src/commands/user.rs:54-71

Code Storage

Verification codes are stored in a concurrent hashmap (DashMap) for temporary access:
let code = crate::utils::generate_verification_code();
let user_id = ctx.author().id;

ctx.data().email_codes.insert(
    user_id,
    CodeEmailPair {
        code: code.clone(),
        email: email_used.clone()
    }
);
Reference: src/commands/user.rs:73-76

Database Insertion

Once the code is verified, the user is permanently added to the database:
sqlx::query("INSERT INTO verified_users (user_id, user_email) VALUES ($1, $2)")
    .bind(user_id.0 as i64)
    .bind(code_key.email.clone())
    .execute(pool)
    .await?;
Reference: src/commands/user.rs:167-172

Role Assignment

After successful verification, users automatically receive the verified role:
let verified_role = ctx.data().config.roles.verified;
let mem = ctx.author_member().await.unwrap();

mem.into_owned()
    .add_role(&ctx.serenity_context(), verified_role)
    .await?;
Reference: src/commands/user.rs:183-189

Commands

/verify init

Initiates the verification process by requesting a student email address.Parameters:
  • email - Student email ending in @stud.hs-kempten.de

/verify code

Completes verification by submitting the code received via email.Parameters:
  • code - Verification code from email

Re-verification System

For long-time members, administrators can trigger re-verification campaigns:
// Filter users who joined before cutoff date
let users_to_reverify = users.iter().filter_map(|m| {
    if m.joined_at.unwrap().date_naive() < cutoff_date && !m.user.bot {
        Some(m)
    } else {
        None
    }
}).collect::<Vec<_>>();
Reference: src/commands/administration.rs:512-518 Users receive a DM with a “Reverify” button that opens a modal to re-submit their email address.

Security Features

Duplicate Prevention

Prevents multiple accounts from using the same email address

Temporary Codes

Verification codes are stored in memory and removed after use

Domain Restriction

Only emails from @stud.hs-kempten.de are accepted

Database Persistence

Verified users are permanently stored for future reference

Error Handling

The system provides localized error messages for common issues:
  • Invalid email format
  • Email already in use
  • Invalid verification code
  • User already verified
Reference: src/commands/user.rs:48-52, 130-153

Build docs developers (and LLMs) love