minimost.auth

minimost.auth

Authentication routes, password utilities, and access-control decorator.

This module handles everything related to user identity:

  • Registration (/signup) — validates and stores new user credentials, creates the user’s SQLite database, and seeds it with public channel history so a new user is not greeted by an empty chat.

  • Login (/login) — verifies credentials and establishes a Flask session.

  • Logout (/logout) — marks the user as offline and clears the session.

  • :func:`login_required` — a decorator applied to every route that requires an authenticated session.

Module-level attributes

AUTH_DBstr

Absolute path to the shared auth.db SQLite file that holds all user credentials.

auth_bpflask.Blueprint

The Flask Blueprint for all authentication routes. Registered in minimost.create_app().

_USERNAME_REre.Pattern

Compiled regular expression that a username must fully match: [A-Za-z0-9_\-]{1,32}.

minimost.auth._lockout_settings() tuple[source]

Return (max_attempts, lockout_seconds) for account lockout.

Reads max_login_attempts and lockout_duration_minutes from settings.json on each call, so changes take effect without a restart. A max_login_attempts of 0 (or any non-positive value) disables lockout entirely. Missing, malformed, or invalid values fall back to the built-in defaults (5 attempts, 15 minutes).

Returns:

(max_attempts, lockout_seconds) — the number of consecutive failed attempts allowed before locking, and the lockout duration in seconds.

Return type:

tuple[int, int]

minimost.auth._lockout_message(minutes) str[source]

Return the user-facing error shown while an account is locked out.

Parameters:

minutes – Whole minutes remaining in the lockout (rounded up; a minimum of 1 is always shown).

Returns:

A human-readable lockout message.

Return type:

str

minimost.auth.hash_password(password: str) str[source]

Hash a plaintext password using PBKDF2.

Delegates to werkzeug.security.generate_password_hash(), which applies a random salt and uses PBKDF2-HMAC-SHA256 by default. The returned string is suitable for storage in auth.db and can be verified with werkzeug.security.check_password_hash().

Parameters:

password (str) – The plaintext password to hash.

Returns:

A Werkzeug-format hash string that encodes the algorithm, iterations, salt, and digest.

Return type:

str

Example:

hashed = hash_password("S3cr3t!")
assert check_password_hash(hashed, "S3cr3t!")
minimost.auth._seed_channel_history(new_user: str) None[source]

Copy all public channel message history into a newly created user’s DB.

When a new account is created, this function seeds the new user’s messages table with every public-channel message from an existing user’s database. This ensures new users can see the full conversation history from the moment they join, rather than starting with a blank slate.

Algorithm:

  1. Pick any existing user from auth.db (other than new_user).

  2. Open that user’s .db file as the source.

  3. Select all rows from messages where channel NOT LIKE 'dm:%' (i.e. public channels only — DMs are never copied).

  4. Insert those rows verbatim into the new user’s database with read = 1 so they generate no unread-message notifications.

Edge cases:

  • If no other users exist (first registration), the function returns immediately — there is no history to copy.

  • If the existing user’s .db file is missing on disk, the function also returns without error.

Parameters:

new_user (str) – The username of the account being registered.

Returns:

None

minimost.auth.login_required(fn)[source]

Decorator that enforces an authenticated Flask session.

Wraps a Flask view function so that unauthenticated requests are redirected to /login instead of executing the view. The session key "user" is set by the login() route upon successful authentication.

This decorator preserves the wrapped function’s name and docstring via functools.wraps(), which is required for Flask’s endpoint registration to work correctly when multiple routes use the decorator.

Parameters:

fn (callable) – The Flask view function to protect.

Returns:

A wrapped view function that checks for session["user"] before calling fn.

Return type:

callable

Example:

@chat_bp.route("/messages/<channel>")
@login_required
def messages(channel):
    ...
minimost.auth.login()[source]

Render the login page.

Routes: GET /login, GET /login.html

Returns:

A rendered login.html template.

Return type:

flask.Response

minimost.auth.login_post()[source]

Authenticate a user from the login form.

Reads username and password from the form, looks up the stored hash in auth.db, and verifies it with werkzeug.security.check_password_hash().

On success:

  • Sets session["user"] to the authenticated username.

  • Calls minimost.common.init_user_db() to ensure the user’s database exists (relevant after a manual users/ directory wipe).

  • Redirects to / (the main chat interface).

On failure:

  • Waits 3 seconds before responding to slow down brute-force attempts.

  • Re-renders login.html with a generic "Invalid credentials" error (username and password failures are intentionally indistinguishable).

Account lockout: consecutive failed attempts against an existing account are counted in users.failed_attempts. Once they reach max_login_attempts (from settings.json) the account is locked for lockout_duration_minutes by setting users.lockout_until; further logins are rejected — without checking the password — until that time passes. A successful login clears the counter. Setting max_login_attempts to 0 disables the feature. See _lockout_settings().

Routes: POST /login, POST /login.html

Returns:

A rendered login.html template on failure, or a redirect to / on success.

Return type:

flask.Response

minimost.auth.logout()[source]

Log the current user out and redirect to the login page.

Sets the user’s presence state to "offline" in presence.db via minimost.presence.update_presence(), then clears the Flask session and redirects to /login.

Route: GET /logout

Returns:

A redirect response to /login.

Return type:

flask.Response

minimost.auth._validate_password(password: str)[source]

Return an error string if password fails the strength rules, else None.

Shared by signup and password reset so the rules stay in one place.

Parameters:

password – The submitted password.

Returns:

A human-readable error message, or None if all rules pass.

Return type:

str or None

minimost.auth._validate_signup(username: str, password: str, confirm: str)[source]

Validate signup form fields and return an error string, or None on success.

Parameters:
  • username – The submitted username.

  • password – The submitted password.

  • confirm – The password confirmation field.

Returns:

A human-readable error message, or None if all rules pass.

Return type:

str or None

minimost.auth.forgot_password()[source]

Render the forgot-password information page.

Route: GET /forgot-password

Returns:

A rendered forgot_password.html template.

Return type:

flask.Response

minimost.auth.signup()[source]

Render the registration page.

Route: GET /signup

Returns:

A rendered signup.html template.

Return type:

flask.Response

minimost.auth.signup_post()[source]

Create a new user account from the registration form.

Validates the submitted username, password, and confirm_password fields, creates the account, and logs the user in.

Validation rules (enforced server-side):

  • username must fully match [A-Za-z0-9_\-]{1,32}.

  • password must be at least 8 characters long.

  • password must contain at least one digit (\d).

  • password must contain at least one uppercase ASCII letter.

  • password must contain at least one special character from the set !@#$%^&*()_+-=[]{};\':|,./<>?`~.

  • password and confirm_password must match.

On success:

  1. Inserts (username, hashed_password) into auth.db.

  2. Calls minimost.common.init_user_db() to create the user’s DB.

  3. Calls _seed_channel_history() to populate public channel history.

  4. Calls minimost.chat.post_welcome_message() to greet the new user in the first public channel under the MiniMost identity.

  5. Sets session["user"] and redirects to /.

On failure:

  • Returns signup.html with a descriptive error variable.

  • If the username is already taken (IntegrityError), the error message says so.

Route: POST /signup

Returns:

A rendered signup.html template on validation failure, or a redirect to / on successful registration.

Return type:

flask.Response

minimost.auth._validate_password_reset(password: str, confirm: str)[source]

Return an error string if the new password fails validation, else None.

minimost.auth.reset_password_form(token)[source]

Render the password reset form if the token is valid and unexpired.

Route: GET /reset-password/<token>

Returns:

Rendered reset_password.html with the form or an error.

Return type:

flask.Response

minimost.auth.reset_password_post(token)[source]

Process a password reset form submission.

Validates the token is still active, applies password rules, updates the stored hash in auth.db, and marks the token as used.

Route: POST /reset-password/<token>

Returns:

Rendered reset_password.html (success or error).

Return type:

flask.Response

Route Summary

Method

Path

Handler

GET, POST

/login, /login.html

minimost.auth.login()

GET

/logout

minimost.auth.logout()

GET, POST

/signup

minimost.auth.signup()