Authentication module

This module handles user authentication. It includes essential security and usability features, including IP trust checks, session attempt limits, password hashing, and role-based access.

🔐 Login Module

🔧 Workflow

  1. GET Request (/login)

    • Renders the login form.

    • Displays a modal if the request is from an untrusted IP.

    show_modal = not is_ip_trusted(ip_address) and not request.session.get("pingid_ok", False)
    return {"show_modal": show_modal}
  2. POST Request (/login)

    • Validates IP, ping code, credentials, and email domain.

    • Limits login attempts to 5.

    • Verifies password using Argon2.

    • All POST forms are protected against CSRF.

    if psw_hasher.verify(user.password, password):
        headers = remember(request, str(user.id))
        request.session['role'] = user.permission
        ...
        return HTTPFound(location=request.route_url('home'), headers=headers)

🔒 Security Measures

  • Trusted Network Gate Users from unrecognized IPs must submit a ping code:

    if not is_ip_trusted(ip_address) and not request.session.get("pingid_ok", False):
        if not is_valid_ping_code(ping_code):
            return {"show_modal": True, "error_ping": "Incorrect code."}
  • Password Protection Uses argon2.PasswordHasher() for hashing and verifying.

  • Session-based Brute Force Protection Tracks failed attempts in the session and logs them if necessary:

    if request.session["current_attempt"] >= MAX_ATTEMPTS:
        request.dbsession.add(ActivityLog(...))
        return {"error_ping": "Too many failed attempts."}

📝 User Registration

User registration is an admin-only view that is used to register new users, making it easier than having to enter the database and adding them manually.

🔐 Access Control

Admin-Only Route

Registration is protected with a permission requirement. Only authenticated users with "admin" permission can access it:

@view_config(route_name='register', ..., permission="admin")

This is enforced for both GET (form display) and POST (form submission) methods.


🔧 Registration Workflow

Step 1: Accessing the Form

A GET request renders the registration form. If the user is on an untrusted IP, a modal appears prompting for a ping code.

show_modal = not is_ip_trusted(ip_address) and not request.session.get("pingid_ok", False)

Step 2: Submitting the Form

Upon form submission (POST):

  • The IP and ping code are verified.

  • Inputs (email, password, etc.) are validated.

  • If all is valid, a new user is added and automatically logged in.

  • This and every other POST forms have protection against CSRF attacks.


🔐 Security & Validation Details

✅ IP Trust with Ping Code

Untrusted network access triggers a ping code prompt to prevent rogue signups, just as in login


✅ Password Strength Enforcement

To promote security, passwords must meet strong validation criteria:

def validate_password(password):
    if len(password) < 8: ...
    if not search(r'[A-Z]', password): ...
    if not search(r'\d', password): ...
    if not search(r'\W', password): ...

Rejected passwords return a list of human-readable errors:

return {
    "error_ping": "<br>".join(password_errors),
    ...
}

✅ Email Domain Restriction

To limit registration to internal users only, emails must end in @kochcc.com:

if not email.endswith("@kochcc.com"):
    return {"error_ping": "Invalid email domain."}

✅ Password Confirmation

Passwords must be confirmed. If they don’t match, registration is halted:

if password != confirm_password:
    return {"error_ping": "Passwords do not match."}

✅ Unique Username Generator

The system generates a unique username using parts of the user's name and a random number:

def generate_unique_username(...):
    username = f"{firstname[:3]}_{lastname[:3]}{randint(1, 1000)}"

It checks the database for uniqueness and retries until success. This ensures all usernames are unique.


✅ Duplicate Email Check

Before creating a user, the system verifies that the email hasn’t already been registered:

existing = dbsession.query(User).filter(User.email == email).first()
if existing:
    return {"error_ping": "Email already registered."}

✅ Secure Storage with Argon2

Passwords are securely hashed using Argon2 before saving to the database:

hashed_password = psw_hasher.hash(password)
new_user = User(..., password=hashed_password)

🤝 Post-Registration Login

Once a user is registered:

  • They are automatically logged in.

  • Their session stores their role and an expiration timestamp.

  • Ping code trust and form data are cleared from the session.

headers = remember(request, str(new_user.id))
request.session['expires_at'] = (datetime.now() + timedelta(minutes=30)).isoformat()

📂 Template Integration

  • Template: /templates/register.jinja2

  • Passed Context:

    • show_modal: Whether to show the ping code modal

    • error_ping: Any validation or process errors

    • form_data: Pre-filled data if registration fails mid-way


🧠 Design Rationale

Feature
Purpose

Admin-Only Access

Prevents arbitrary user creation

Strong Password Policy

Reduces risk of credential compromise

Domain Filtering

Limits registration to official emails

IP Trust Mechanism

Adds friction against unknown device signups

Username Auto-Generation

Simplifies onboarding, ensures unique identifiers

Auto-login After Sign-Up

Improves UX by eliminating a second login step

Last updated