From 4227be5a8000eff73d23c32a2a33b1902e74e177 Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Tue, 2 Dec 2025 16:08:40 +1100 Subject: [PATCH] Add login history to settings --- db.py | 28 ++++- routes/auth.py | 7 ++ routes/settings.py | 18 +++ templates/dashboard/settings/api_keys.html | 5 + .../dashboard/settings/database_schema.html | 5 + templates/dashboard/settings/export.html | 5 + .../dashboard/settings/login_history.html | 115 ++++++++++++++++++ 7 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 templates/dashboard/settings/login_history.html diff --git a/db.py b/db.py index 77d8115..1fffbbf 100644 --- a/db.py +++ b/db.py @@ -547,4 +547,30 @@ ORDER BY invocation_time DESC""", [http_function_id]) return (True, f"Imported shared environment '{env_data['name']}'", result['id']) except Exception as e: - return (False, f"Error importing environment '{env_data.get('name', 'unknown')}': {str(e)}", None) \ No newline at end of file + return (False, f"Error importing environment '{env_data.get('name', 'unknown')}': {str(e)}", None) + + def record_login(self, user_id, ip_address, user_agent, success=True, failure_reason=None): + """Record a login attempt""" + try: + self.execute( + """INSERT INTO login_history + (user_id, ip_address, user_agent, success, failure_reason) + VALUES (%s, %s, %s, %s, %s)""", + (user_id, ip_address, user_agent, success, failure_reason), + commit=True + ) + return True + except Exception as e: + print(f"Error recording login: {e}") + return False + + def get_login_history(self, user_id, limit=50): + """Get login history for a user""" + return self.execute( + """SELECT id, login_time, ip_address, user_agent, success, failure_reason + FROM login_history + WHERE user_id = %s + ORDER BY login_time DESC + LIMIT %s""", + (user_id, limit) + ) diff --git a/routes/auth.py b/routes/auth.py index 27064f2..f77504b 100644 --- a/routes/auth.py +++ b/routes/auth.py @@ -41,13 +41,20 @@ def login(): user_data = db.get_user_by_username(username) if not user_data: + # Record failed login attempt + db.record_login(None, request.remote_addr, str(request.user_agent), False, "User not found") return render_template("login.html", error="User does not exist") if not check_password_hash(user_data['password_hash'], password): + # Record failed login attempt + db.record_login(user_data['id'], request.remote_addr, str(request.user_agent), False, "Invalid password") return render_template("login.html", error="Invalid username or password") user = User(id=str(user_data['id']), username=user_data['username'], password_hash=user_data['password_hash'], created_at=user_data['created_at'], theme_preference=user_data.get('theme_preference', 'light')) + # Record successful login + db.record_login(user.id, request.remote_addr, str(request.user_agent), True) + login_user(user) next = request.args.get('next') diff --git a/routes/settings.py b/routes/settings.py index d7d7a07..414aac0 100644 --- a/routes/settings.py +++ b/routes/settings.py @@ -133,6 +133,23 @@ def database_schema(): ) return render_template("dashboard/settings/database_schema.html", schema_info=schema_info) +@settings.route("/login-history", methods=["GET"]) +@login_required +def login_history(): + """Display login history for the current user""" + user_id = current_user.id + history = db.get_login_history(user_id, limit=50) + + if htmx: + return render_block( + environment, + "dashboard/settings/login_history.html", + "page", + history=history + ) + return render_template("dashboard/settings/login_history.html", history=history) + + def get_database_schema(): """Fetch database schema information for ERD generation""" # Get all tables @@ -413,3 +430,4 @@ def import_data(): except Exception as e: return {"error": f"Import failed: {str(e)}"}, 500 + diff --git a/templates/dashboard/settings/api_keys.html b/templates/dashboard/settings/api_keys.html index 612db4e..1e18e04 100644 --- a/templates/dashboard/settings/api_keys.html +++ b/templates/dashboard/settings/api_keys.html @@ -18,6 +18,11 @@ class="border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 py-4 px-1 text-sm font-medium cursor-pointer"> Database Schema + + Login History + diff --git a/templates/dashboard/settings/database_schema.html b/templates/dashboard/settings/database_schema.html index ac28d70..e78ac16 100644 --- a/templates/dashboard/settings/database_schema.html +++ b/templates/dashboard/settings/database_schema.html @@ -18,6 +18,11 @@ class="border-b-2 border-blue-500 text-blue-600 dark:text-blue-400 py-4 px-1 text-sm font-medium cursor-pointer"> Database Schema + + Login History + diff --git a/templates/dashboard/settings/export.html b/templates/dashboard/settings/export.html index a940121..8d426b1 100644 --- a/templates/dashboard/settings/export.html +++ b/templates/dashboard/settings/export.html @@ -18,6 +18,11 @@ class="border-b-2 border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 py-4 px-1 text-sm font-medium cursor-pointer"> Database Schema + + Login History + diff --git a/templates/dashboard/settings/login_history.html b/templates/dashboard/settings/login_history.html new file mode 100644 index 0000000..e000373 --- /dev/null +++ b/templates/dashboard/settings/login_history.html @@ -0,0 +1,115 @@ +{% extends 'dashboard.html' %} + +{% block page %} +
+ + + +
+

Login History

+

View your recent login activity and security events

+
+ +
+ {% if history %} +
+ + + + + + + + + + + {% for entry in history %} + + + + + + + {% endfor %} + +
+ Date & Time + + IP Address + + Browser / Device + + Status +
+
{{ entry.login_time.strftime('%b %d, %Y') }}
+
+ {{ entry.login_time.strftime('%I:%M %p') }} +
+
+ {{ entry.ip_address or 'N/A' }} + +
+ {{ entry.user_agent or 'Unknown' }} +
+
+ {% if entry.success %} + + Success + + {% else %} + + Failed + + {% endif %} +
+
+ {% else %} +
+ + + +

No login history

+

+ Your login activity will appear here +

+
+ {% endif %} +
+ + {% if history %} +
+

Showing last {{ history|length }} login{% if history|length != 1 %}s{% endif %}. Login history is kept for + security purposes.

+
+ {% endif %} +
+{% endblock %} \ No newline at end of file