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]

No-op retained for backwards compatibility.

Messages now live in a single shared database, so a newly registered user can already see the full public-channel history the instant their account exists — there is nothing to copy. This stub remains only so existing callers and imports keep working.

Parameters:

new_user – Ignored.

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

Returns:

A rendered login.html template.

Return type:

flask.Response

minimost.auth.about()[source]

Render the “What is MiniMost?” marketing page.

Routes: GET /about

A public, login-free page that explains what MiniMost is and pitches visitors on creating an account. Linked from the login page.

Returns:

A rendered about.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

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() and clears any manual presence override, 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.change_password_post()[source]

Change the logged-in user’s password from the account modal.

Reads current_password, new_password, and confirm_password from the submitted form. The current password is verified against the stored hash before the new password is applied; the new password is held to the same strength rules as signup and reset (see _validate_password()).

Unlike the HTML form routes, this endpoint is called via fetch from the chat UI and responds with JSON so the modal can show inline feedback without a page reload. CSRF is still enforced (auth blueprint) so the request must include the csrf_token form field.

Route: POST /change-password

Returns:

{"ok": True} on success, or {"error": <message>} with a 400 status when the current password is wrong or the new password fails validation.

Return type:

tuple[dict, int] or dict

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()