-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/add login #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…ML templates for better presentation, and add JavaScript for date selection.
…t, and enhance accessibility with consistent casing for text elements.
…dalone.py, and test_time_formatting.py to clean up the repository.
Implement weekly report generation and improve templates
- Added Flask-Login for user session management. - Created User model with password hashing and role management. - Developed user service for creating, updating, and deleting users. - Implemented authentication routes for login, logout, and profile management. - Added admin routes for user management, including adding, editing, and deleting users. - Enhanced templates for user management with forms and user lists. - Updated header template to include user authentication status and admin links. - Created default admin user if no users exist. - Added client-side confirmation for user deletion.
- Updated create_app function to accept configuration overrides for testing. - Modified Config class to use an in-memory database during tests. - Renamed 'is_active' to 'user_active' in User model for clarity and added compatibility property for Flask-Login. - Adjusted user creation and update logic to reflect the new 'user_active' field. - Added comprehensive tests for UserService, covering user creation, retrieval, updating, and deletion scenarios.
…entication and data handling
…y; add PostgreSQL support and deployment guide
… and adjust versioning
…vice functionality - Implement tests for authentication routes including profile and password change functionalities. - Create tests for home routes to validate time entry display and CRUD operations. - Develop extensive tests for user service functions, covering user creation, retrieval, updating, and deletion with exception handling. - Ensure all tests validate expected outcomes and handle edge cases effectively.
…n bump process in build_image.py
…d Render deployment; consolidate information in README.md
…atabase and user creation scripts for PostgreSQL
… 'is_active'; enhance profile update functionality with email validation
…er_active'; adjust related tests and versioning
…; include version tagging and requirements update
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds comprehensive user authentication and authorization functionality to the TimeTracker application, along with PostgreSQL support for production deployment. The changes implement Flask-Login for session management, user administration features, and enhanced weekly reporting capabilities.
Key changes:
- Added user authentication system with login/logout, profile management, and password changes
- Implemented role-based access control with admin privileges and user management
- Added PostgreSQL database support while maintaining SQLite for development
- Enhanced weekly report generation with detailed statistics and improved UI
Reviewed Changes
Copilot reviewed 39 out of 42 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock, requirements.txt, pyproject.toml | Added flask-login and psycopg2-binary dependencies for auth and PostgreSQL support |
| tests/test_user_service.py | New comprehensive test suite for user service operations (586 lines) |
| tests/test_auth.py | New tests for authentication routes (213 lines) |
| tests/test_home.py, test_reports.py | Updated tests to include authentication requirements |
| tests/conftest.py | Enhanced test fixtures to skip default admin creation and support authentication |
| app/models.py | Added User model with password hashing and Flask-Login integration; changed time_out from int to boolean |
| app/service/user_service.py | New service module for user CRUD operations with custom exceptions |
| app/service/weekly_report_service.py | New service for generating comprehensive weekly reports with statistics |
| app/routes/auth.py | New authentication blueprint with login, logout, profile, and password change routes |
| app/routes/admin.py | Enhanced admin routes with user management (list, add, edit, delete) |
| app/routes/home.py, reports.py | Added @login_required decorators and refactored to use new services |
| app/templates/auth/* | New templates for login, profile, and password change pages |
| app/templates/admin/* | New templates for user management UI |
| app/templates/reports/* | Enhanced weekly report templates with detailed statistics and improved UX |
| app/templates/header.html | Added user menu with authentication state display |
| app/config.py | Updated to support DATABASE_URL (Render.com) and PostgreSQL configuration |
| app/app.py | Integrated Flask-Login, added user loader, and default admin creation |
| scripts/create_db.py | New standalone script for PostgreSQL database setup |
| docs/* | Added comprehensive documentation for deployment and PostgreSQL setup |
| render.yaml | New Render.com deployment configuration |
| build_image.py, build_current.py | Updated build scripts with proper version management |
|
|
||
| DB_NAME = 'timetracker_db' | ||
| DB_USER = 'timetracker_user' | ||
| DB_PASS = 'your_password' |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security issue: The database password is hardcoded as 'your_password' in plaintext. This script creates a PostgreSQL user with this weak, predictable password.
Recommendation: Either:
- Prompt the user for the database password during script execution (similar to how the superuser password is prompted on line 30), or
- Read it from an environment variable, or
- Add a comment/documentation warning that this must be changed before use
| finally: | ||
| if conn: | ||
| conn.close() |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uninitialized variable conn may cause UnboundLocalError: If the psycopg2.connect() call on line 33 raises an exception, the conn variable will never be assigned. The finally block on line 68 then attempts to check if conn:, which will raise UnboundLocalError: local variable 'conn' referenced before assignment.
Fix: Initialize conn = None before the try block.
| def admin_required(f: Callable[..., Any]) -> Callable[..., Any]: | ||
| """Decorator to require admin privileges.""" | ||
|
|
||
| def decorated_function(*args, **kwargs): | ||
| if not current_user.is_authenticated or not current_user.is_admin: | ||
| flash('Admin privileges required.', 'error') | ||
| return redirect(url_for('home.index')) | ||
| return f(*args, **kwargs) | ||
|
|
||
| decorated_function.__name__ = f.__name__ | ||
| return decorated_function |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The decorator admin_required should use @wraps(f) from functools to preserve the original function's metadata (name, docstring, etc.). Without this, Flask's routing system may have issues with multiple routes using this decorator, and debugging/introspection will be harder.
| @patch.dict( | ||
| os.environ, | ||
| { | ||
| 'DEFAULT_ADMIN_USERNAME': 'customadmin', | ||
| 'DEFAULT_ADMIN_EMAIL': 'custom@admin.com', | ||
| 'DEFAULT_ADMIN_PASSWORD': 'custompass123', | ||
| }, | ||
| ) | ||
| def test_create_default_admin_with_custom_env_vars(self, app): | ||
| """Test creating default admin with custom environment variables.""" | ||
| with app.app_context(): | ||
| # Act | ||
| admin_user = create_default_admin() | ||
|
|
||
| # Assert | ||
| assert admin_user is not None | ||
| assert admin_user.username == 'customadmin' | ||
| assert admin_user.email == 'custom@admin.com' | ||
| assert admin_user.check_password('custompass123') | ||
| assert admin_user.is_admin is True | ||
|
|
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test function is incorrectly indented inside another test function's scope. Lines 565-585 define test_create_default_admin_with_custom_env_vars but it's indented under test_create_default_admin_when_users_exist (which ends at line 563). This will cause the nested function to never be executed as a test.
The @patch.dict decorator on line 565 should be at the class level, and the function should be dedented to be a proper class method.
| # /// script | ||
| # requires-python = ">=3.11" | ||
| # dependencies = [ | ||
| # "getpass", |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getpass module is a standard library module and should not be listed in the dependencies. The PEP 723 inline script metadata at line 6 incorrectly lists "getpass" as a dependency, which will cause an error when uv tries to install it.
| is_admin: Optional[bool] = None, | ||
| is_active: Optional[bool] = None, | ||
| ) -> User: | ||
| """Update user detail""" |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing docstring: The update_user function is missing a proper docstring. While there's a brief one-liner on line 98, it should document the parameters, return value, and exceptions raised (especially the custom UpdateUserError), following the pattern used in other functions like delete_user.
| """Update user detail""" | |
| """ | |
| Update the details of an existing user. | |
| Args: | |
| user_id (int): The ID of the user to update. | |
| username (Optional[str], optional): New username. Must be unique and at least 3 characters. | |
| email (Optional[str], optional): New email address. Must be unique and valid. | |
| password (Optional[str], optional): New password. Must be at least 6 characters. | |
| is_admin (Optional[bool], optional): Whether the user is an admin. | |
| is_active (Optional[bool], optional): Whether the user is active. | |
| Returns: | |
| User: The updated User object. | |
| Raises: | |
| UpdateUserError: If the user is not found, if username/email conflicts occur, | |
| if validation fails, or if a database error occurs. | |
| """ |
| user.is_admin = is_admin | ||
|
|
||
| if is_active is not None: | ||
| user.user_active = is_active |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistency between property name and database column: The model defines a column named user_active (line 22), and the is_active property (lines 53-56) reads from user_active. However, in the update_user function, line 131 assigns to user.user_active, while line 329 in the test expects user.is_active to be updated. This creates a mismatch.
The __init__ method (line 39) also assigns to self.user_active when the parameter is named is_active, which is correct for the database column but inconsistent with the property name used elsewhere.
Consider standardizing on either:
- Use
user_activeconsistently as the attribute name, OR - Add a setter for the
is_activeproperty to handle assignments properly
| user.user_active = is_active | |
| user.is_active = is_active |
| updated_user = update_user(user.id, is_active=False) | ||
|
|
||
| # Assert | ||
| assert updated_user is not None | ||
| assert updated_user.user_active is False | ||
|
|
||
| def test_update_user_multiple_fields_success(self, app): | ||
| """Test updating multiple user fields at once.""" | ||
| with app.app_context(): | ||
| # Arrange | ||
| user = create_user('oldname', 'old@example.com', 'oldpassword', is_admin=False, is_active=True) | ||
|
|
||
| # Act | ||
| updated_user = update_user( | ||
| user.id, | ||
| username='newname', | ||
| email='new@example.com', | ||
| password='newpassword123', | ||
| is_admin=True, | ||
| is_active=False, | ||
| ) | ||
|
|
||
| # Assert | ||
| assert updated_user is not None | ||
| assert updated_user.username == 'newname' | ||
| assert updated_user.email == 'new@example.com' | ||
| assert updated_user.check_password('newpassword123') | ||
| assert updated_user.is_admin is True | ||
| assert updated_user.is_active is False |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test expects updated_user.is_active to be False, but the update_user function assigns to user.user_active. Since is_active is a read-only property (no setter defined) that returns bool(self.user_active), this test should pass. However, for clarity and to prevent future issues, consider adding a setter for the is_active property in the User model, or consistently use user_active throughout the codebase.
| with app.app_context(): | ||
| # Create two users | ||
| user1 = create_user('user1', 'user1@example.com', 'password123') | ||
| _user2 = create_user('user2', 'user2@example.com', 'password123') |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable _user2 is not used.
Login now working nicely, cleaned up tests and added versioning to docker image generation.