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
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}
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 modalerror_ping
: Any validation or process errorsform_data
: Pre-filled data if registration fails mid-way
🧠 Design Rationale
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