Add Forget Password feature.

This commit is contained in:
2025-07-13 12:57:52 +02:00
parent 1500b2cf88
commit 2969fb41c9
9 changed files with 1063 additions and 56 deletions

226
CLAUDE.md Normal file
View File

@@ -0,0 +1,226 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
TimeTrack is a comprehensive web-based time tracking application built with Flask that provides enterprise-level time management capabilities for teams and organizations. It features multi-tenancy, role-based access control, project/team management, billing/invoicing, and secure authentication with 2FA.
## Tech Stack
- **Backend**: Flask 2.0.1 with SQLAlchemy ORM
- **Database**: PostgreSQL (production) / SQLite (development)
- **Migrations**: Flask-Migrate (Alembic-based)
- **Frontend**: Server-side rendered Jinja2 templates with vanilla JavaScript
- **Authentication**: Session-based with TOTP 2FA support and password reset via email
- **Export**: Pandas for CSV/Excel, ReportLab for PDF generation
- **Mobile**: Progressive Web App (PWA) support with optimized mobile UI
## Development Setup
### Local Development
```bash
# Using virtual environment
source .venv/bin/activate # or pipenv shell
# Set environment variables (PostgreSQL example)
export DATABASE_URL="postgresql://timetrack:timetrack123@localhost:5432/timetrack"
# Run the application
python app.py
```
### Docker Development
```bash
# Standard docker-compose (uses PostgreSQL)
docker-compose up
# Debug mode with hot-reload
docker-compose -f docker-compose.debug.yml up
```
## Database Operations
### Flask-Migrate Commands
```bash
# Create a new migration
python create_migration.py "Description of changes"
# Apply pending migrations
python apply_migration.py
# Check current migration state
python check_migration_state.py
# Clean migration state (CAUTION: destructive)
python clean_migration_state.py
# For Docker environments
docker exec timetrack-timetrack-1 python create_migration.py "Description"
docker exec timetrack-timetrack-1 python apply_migration.py
```
### Standard Flask-Migrate Commands
```bash
# Create migration
flask db migrate -m "Description"
# Apply migrations
flask db upgrade
# Rollback one revision
flask db downgrade
# Show current revision
flask db current
# Show migration history
flask db history
```
## Key Architecture Patterns
### 1. Blueprint-Based Modular Architecture
Routes are organized into blueprints by feature domain:
- `/routes/auth.py` - Authentication and authorization decorators
- `/routes/projects.py` - Project management
- `/routes/invoice.py` - Billing and invoicing
- `/routes/tax_configuration.py` - Tax management
- `/routes/teams.py` - Team management
- `/routes/export.py` - Data export functionality
### 2. Model Organization
Models are split by domain in `/models/`:
- `user.py` - User, Role, UserPreferences
- `company.py` - Company, CompanySettings, CompanyWorkConfig
- `project.py` - Project, ProjectCategory
- `team.py` - Team
- `time_entry.py` - TimeEntry
- `invoice.py` - Invoice, InvoiceLineItem, InvoiceStatus
- `tax_configuration.py` - TaxConfiguration
- `enums.py` - BillingType, AccountType, etc.
### 3. Multi-Tenancy Pattern
All data is scoped by company_id with automatic filtering:
```python
# Common pattern in routes
projects = Project.query.filter_by(company_id=g.user.company_id).all()
```
### 4. Role-Based Access Control
Decorators enforce permissions:
```python
@role_required(Role.SUPERVISOR) # Supervisor and above
@admin_required # Admin and System Admin only
@company_required # Ensures user has company context
```
### 5. Billing Architecture
- Projects support multiple billing types: NON_BILLABLE, HOURLY, DAILY_RATE, FIXED_RATE
- Invoices support net/gross pricing with country-specific tax configurations
- TimeEntry calculates billing based on project settings (8-hour day for daily rates)
## Common Development Tasks
### Adding a New Feature
1. Create model in appropriate file under `/models/`
2. Create blueprint in `/routes/` with proper decorators
3. Register blueprint in `app.py`
4. Create templates in `/templates/`
5. Run migration: `python create_migration.py "Add feature X"`
### Testing Database Changes
```bash
# Check what will be migrated
python check_migration_state.py
# Test in isolated environment
docker-compose -f docker-compose.debug.yml up
# Apply to development database
DATABASE_URL="postgresql://..." python apply_migration.py
```
### Working with Billing Features
When modifying billing/invoice features:
1. Check `models/enums.py` for BillingType values
2. Update TimeEntry.calculate_billing_amount() for new billing logic
3. Update Invoice.calculate_totals() for tax calculations
4. Ensure templates handle all billing types (hourly/daily rate display)
## Important Implementation Details
### Session Management
- Sessions are permanent with 7-day lifetime
- User context loaded in `app.before_request()` via `g.user`
- Company context available via `g.company`
### Time Calculations
- Time entries use UTC internally, converted for display
- Rounding rules configured per company/user
- Break time calculations handled in TimeEntry model
### Export System
- Pandas handles CSV/Excel generation
- ReportLab for PDF invoices
- Export routes handle large datasets with streaming
### Email Configuration
- Flask-Mail configured via environment variables
- MAIL_SERVER, MAIL_PORT, MAIL_USERNAME required
- Used for user invitations and password resets
### Authentication Features
- Password reset functionality with secure tokens (1-hour expiry)
- Two-factor authentication (2FA) using TOTP
- Session-based authentication with "Remember Me" option
- Email verification for new accounts (configurable)
### Mobile UI Features
- Progressive Web App (PWA) manifest for installability
- Mobile-optimized navigation with hamburger menu and bottom nav
- Touch-friendly form inputs and buttons (44px minimum touch targets)
- Responsive tables with card view on small screens
- Pull-to-refresh functionality
- Mobile gestures support (swipe, pinch-to-zoom)
- Date/time pickers that respect user preferences
## Environment Variables
Required for production:
- `DATABASE_URL` - PostgreSQL connection string
- `SECRET_KEY` - Flask session secret
- `MAIL_SERVER`, `MAIL_PORT`, `MAIL_USERNAME`, `MAIL_PASSWORD` - Email config
Optional:
- `FLASK_ENV` - Set to 'development' for debug mode
- `FORCE_HTTPS` - Set to 'true' for HTTPS enforcement
- `TRUST_PROXY_HEADERS` - Set to 'true' when behind reverse proxy
## Troubleshooting
### Migration Issues
1. Run `python check_migration_state.py` to verify database state
2. Check `/migrations/versions/` for migration files
3. If stuck, use `clean_migration_state.py` (careful - destructive)
### Import Errors
- Ensure all model imports in routes use: `from models import ModelName`
- Blueprint registration order matters in `app.py`
### Database Connection
- PostgreSQL requires running container or local instance
- Default fallback to SQLite at `/data/timetrack.db`
- Check `docker-compose.yml` for PostgreSQL credentials

302
README.md
View File

@@ -1,6 +1,6 @@
# TimeTrack
TimeTrack is a comprehensive web-based time tracking application built with Flask that provides enterprise-level time management capabilities for teams and organizations. The application features role-based access control, project management, team collaboration, and secure authentication.
TimeTrack is a comprehensive web-based time tracking application built with Flask that provides enterprise-level time management capabilities for teams and organizations. The application features multi-tenancy support, role-based access control, project management, team collaboration, billing/invoicing, and secure authentication with 2FA and password reset functionality. It includes a Progressive Web App (PWA) interface with mobile-optimized features.
## Features
@@ -13,59 +13,99 @@ TimeTrack is a comprehensive web-based time tracking application built with Flas
### User Management & Security
- **Two-Factor Authentication (2FA)**: TOTP-based authentication with QR codes
- **Role-Based Access Control**: Admin, Supervisor, Team Leader, and Team Member roles
- **User Administration**: Create, edit, block/unblock users with verification system
- **Profile Management**: Update email, password, and personal settings
- **Password Reset**: Secure email-based password recovery (1-hour token expiry)
- **Role-Based Access Control**: System Admin, Admin, Supervisor, Team Leader, and Team Member roles
- **Multi-Tenancy**: Complete data isolation between companies
- **User Administration**: Create, edit, block/unblock users with email verification
- **Profile Management**: Update email, password, avatar, and personal settings
- **Company Invitations**: Invite users to join your company via email
### Team & Project Management
- **Team Management**: Create teams, assign members, and track team hours
- **Project Management**: Create projects with codes, assign to teams, set active/inactive status
- **Project Management**: Create projects with codes, categories, billing types (hourly/daily/fixed)
- **Sprint Management**: Agile sprint planning and tracking
- **Task Management**: Create and manage tasks with subtasks and dependencies
- **Notes System**: Create, organize, and share notes with folder structure
- **Team Hours Tracking**: View team member working hours with date filtering
- **Project Assignment**: Flexible project-team assignments and access control
### Export & Reporting
- **Multiple Export Formats**: CSV and Excel export capabilities
- **Multiple Export Formats**: CSV, Excel, and PDF export capabilities
- **Individual Time Export**: Personal time entries with date range selection
- **Team Hours Export**: Export team member hours with filtering options
- **Invoice Generation**: Create and export invoices with tax configurations
- **Analytics Dashboard**: Visual analytics with charts and statistics
- **Quick Export Options**: Today, week, month, or all-time data exports
### Administrative Features
- **Company Management**: Multi-company support with separate settings
- **Admin Dashboard**: System overview with user, team, and activity statistics
- **System Settings**: Configure registration settings and global preferences
- **System Settings**: Configure registration, email verification, tracking scripts
- **Work Configuration**: Set work hours, break rules, and rounding preferences
- **Tax Configuration**: Country-specific tax rates for invoicing
- **Branding**: Custom logos, colors, and application naming
- **User Administration**: Complete user lifecycle management
- **Team & Project Administration**: Full CRUD operations for teams and projects
- **System Admin Tools**: Multi-company oversight and system health monitoring
## Tech Stack
- **Backend**: Flask 2.0.1 with SQLAlchemy ORM
- **Database**: SQLite with comprehensive relational schema
- **Authentication**: Flask session management with 2FA support
- **Frontend**: Responsive HTML, CSS, JavaScript with real-time updates
- **Security**: TOTP-based two-factor authentication, role-based access control
- **Export**: CSV and Excel export capabilities
- **Dependencies**: See Pipfile for complete dependency list
- **Database**: PostgreSQL (production)
- **Migrations**: Flask-Migrate (Alembic-based) with custom helpers
- **Authentication**: Session-based with 2FA and password reset via Flask-Mail
- **Frontend**: Server-side Jinja2 templates with vanilla JavaScript
- **Mobile**: Progressive Web App (PWA) with mobile-optimized UI
- **Export**: Pandas for CSV/Excel, ReportLab for PDF generation
- **Containerization**: Docker and Docker Compose for easy deployment
- **Dependencies**: See requirements.txt for complete dependency list
## Installation
### Prerequisites
- Python 3.12
- pip or pipenv
- Python 3.9+
- PostgreSQL 15+ (for production)
- Docker & Docker Compose (recommended)
### Setup with pipenv (recommended)
### Quick Start with Docker (Recommended)
```bash
# Clone the repository
git clone https://github.com/nullmedium/TimeTrack.git
cd TimeTrack
# Install dependencies using pipenv
pipenv install
# Copy and configure environment variables
cp .env.example .env
# Edit .env with your settings
# Activate the virtual environment
pipenv shell
# Start the application
docker-compose up -d
# Run the application (migrations run automatically on first startup)
# Access at http://localhost:5000
```
### Local Development Setup
```bash
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Set environment variables
export DATABASE_URL="postgresql://user:pass@localhost/timetrack"
export SECRET_KEY="your-secret-key"
export MAIL_SERVER="smtp.example.com"
# ... other environment variables
# Run migrations
python create_migration.py "Initial setup"
python apply_migration.py
# Run the application
python app.py
```
@@ -81,22 +121,60 @@ python app.py
### Database Migrations
**Automatic Migration System**: All database migrations now run automatically when the application starts. No manual migration scripts need to be run.
**Flask-Migrate System**: The application uses Flask-Migrate (Alembic-based) for database version control.
The integrated migration system handles:
- Database schema creation for new installations
- Automatic schema updates for existing databases
- User table enhancements (verification, roles, teams, 2FA)
- Project and team management table creation
- Sample data initialization
- Data integrity maintenance during upgrades
```bash
# Create a new migration
python create_migration.py "Description of changes"
# Apply migrations
python apply_migration.py
# Check migration status
python check_migration_state.py
# For Docker environments
docker exec timetrack-timetrack-1 python create_migration.py "Description"
docker exec timetrack-timetrack-1 python apply_migration.py
```
The migration system handles:
- Automatic schema updates on container startup
- PostgreSQL-specific features (enums, constraints)
- Safe rollback capabilities
- Multi-tenancy data isolation
- Preservation of existing data during schema changes
### Configuration
The application can be configured through:
- **Admin Dashboard**: System-wide settings and user management
- **User Profiles**: Individual work hour and break preferences
- **Environment Variables**: Database and Flask configuration
#### Environment Variables
```bash
# Database
DATABASE_URL=postgresql://user:password@host:5432/dbname
# Flask
SECRET_KEY=your-secret-key-here
FLASK_ENV=development # or production
# Email (required for invitations and password reset)
MAIL_SERVER=smtp.example.com
MAIL_PORT=587
MAIL_USE_TLS=true
MAIL_USERNAME=your-email@example.com
MAIL_PASSWORD=your-password
# Optional
FORCE_HTTPS=true # Force HTTPS in production
TRUST_PROXY_HEADERS=true # When behind reverse proxy
```
#### Application Settings
- **Company Settings**: Work hours, break requirements, time rounding
- **User Preferences**: Date/time formats, timezone, UI preferences
- **System Settings**: Registration, email verification, tracking scripts
- **Branding**: Custom logos, colors, and naming
## Usage
@@ -123,36 +201,152 @@ The application can be configured through:
## Security Features
- **Two-Factor Authentication**: TOTP-based 2FA with QR code setup
- **Role-Based Access Control**: Four distinct user roles with appropriate permissions
- **Session Management**: Secure login/logout with "remember me" functionality
- **Data Validation**: Comprehensive input validation and error handling
- **Account Verification**: Email verification system for new accounts
- **Password Reset**: Secure email-based recovery with time-limited tokens
- **Role-Based Access Control**: Five distinct user roles with granular permissions
- **Multi-Tenancy**: Complete data isolation between companies
- **Session Management**: Secure sessions with configurable lifetime
- **Email Verification**: Optional email verification for new accounts
- **Password Strength**: Enforced password complexity requirements
- **Security Headers**: HSTS, CSP, X-Frame-Options, and more
- **Input Validation**: Comprehensive server-side validation
- **CSRF Protection**: Built-in Flask CSRF protection
## Features for Mobile Users
- **Progressive Web App**: Install TimeTrack as an app on mobile devices
- **Mobile Navigation**: Bottom navigation bar and hamburger menu
- **Touch Optimization**: 44px minimum touch targets throughout
- **Responsive Tables**: Automatic card view on small screens
- **Mobile Gestures**: Swipe navigation and pull-to-refresh
- **Date/Time Pickers**: Mobile-friendly pickers respecting user preferences
- **Offline Support**: Basic offline functionality with service workers
- **Performance**: Lazy loading and optimized for mobile networks
## Project Structure
```
TimeTrack/
├── app.py # Main Flask application
├── models/ # Database models organized by domain
│ ├── user.py # User, Role, UserPreferences
│ ├── company.py # Company, CompanySettings
│ ├── project.py # Project, ProjectCategory
│ ├── time_entry.py # TimeEntry model
│ └── ... # Other domain models
├── routes/ # Blueprint-based route handlers
│ ├── auth.py # Authentication routes
│ ├── projects.py # Project management
│ ├── teams.py # Team management
│ └── ... # Other route modules
├── templates/ # Jinja2 templates
├── static/ # CSS, JS, images
│ ├── css/ # Stylesheets including mobile
│ ├── js/ # JavaScript including PWA support
│ └── manifest.json # PWA manifest
├── migrations/ # Flask-Migrate/Alembic migrations
├── docker-compose.yml # Docker configuration
└── requirements.txt # Python dependencies
```
## API Endpoints
The application provides various endpoints for different user roles:
- `/admin/*`: Administrative functions (Admin only)
- `/supervisor/*`: Supervisor functions (Supervisor+ roles)
- `/team/*`: Team management (Team Leader+ roles)
- `/export/*`: Data export functionality
- `/auth/*`: Authentication and profile management
Key application routes organized by function:
- `/` - Home dashboard
- `/login`, `/logout`, `/register` - Authentication
- `/forgot_password`, `/reset_password/<token>` - Password recovery
- `/time-tracking` - Main time tracking interface
- `/projects/*` - Project management
- `/teams/*` - Team management
- `/tasks/*` - Task and sprint management
- `/notes/*` - Notes system
- `/invoices/*` - Billing and invoicing
- `/export/*` - Data export functionality
- `/admin/*` - Administrative functions
- `/system-admin/*` - System administration (multi-company)
## File Structure
## Deployment
- `app.py`: Main Flask application with integrated migration system
- `models.py`: Database models and relationships
- `templates/`: HTML templates for all pages
- `static/`: CSS and JavaScript files
- `migrate_*.py`: Legacy migration scripts (no longer needed)
### Production Deployment with Docker
```bash
# Clone and configure
git clone https://github.com/nullmedium/TimeTrack.git
cd TimeTrack
cp .env.example .env
# Edit .env with production values
# Build and start
docker-compose -f docker-compose.yml up -d
# View logs
docker-compose logs -f
# Backup database
docker exec timetrack-db-1 pg_dump -U timetrack timetrack > backup.sql
```
### Reverse Proxy Configuration (Nginx)
```nginx
server {
listen 80;
server_name timetrack.example.com;
location / {
proxy_pass http://localhost:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
## Troubleshooting
### Common Issues
1. **Migration Errors**
- Check current state: `python check_migration_state.py`
- View migration history: `flask db history`
- Fix revision conflicts in alembic_version table
2. **Docker Issues**
- Container not starting: `docker logs timetrack-timetrack-1`
- Database connection: Ensure PostgreSQL is healthy
- Permission errors: Check volume permissions
3. **Email Not Sending**
- Verify MAIL_* environment variables
- Check firewall rules for SMTP port
- Enable "less secure apps" if using Gmail
4. **2FA Issues**
- Ensure system time is synchronized
- QR code not scanning: Check for firewall blocking images
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test thoroughly
5. Submit a pull request
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
### Development Guidelines
- Follow PEP 8 for Python code
- Write tests for new features
- Update documentation as needed
- Ensure migrations are reversible
- Test on both PostgreSQL and SQLite
## License
This project is licensed under the MIT License.
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Acknowledgments
- Flask community for the excellent framework
- Contributors and testers
- Open source projects that made this possible

100
app.py
View File

@@ -909,6 +909,106 @@ def verify_email(token):
return redirect(url_for('login'))
@app.route('/forgot_password', methods=['GET', 'POST'])
def forgot_password():
"""Handle forgot password requests"""
if request.method == 'POST':
username_or_email = request.form.get('username_or_email', '').strip()
if not username_or_email:
flash('Please enter your username or email address.', 'error')
return render_template('forgot_password.html', title='Forgot Password')
# Try to find user by username or email
user = User.query.filter(
db.or_(
User.username == username_or_email,
User.email == username_or_email
)
).first()
if user and user.email:
# Generate reset token
token = user.generate_password_reset_token()
# Send reset email
reset_url = url_for('reset_password', token=token, _external=True)
msg = Message(
f'Password Reset Request - {g.branding.app_name if g.branding else "TimeTrack"}',
recipients=[user.email]
)
msg.body = f'''Hello {user.username},
You have requested to reset your password for {g.branding.app_name if g.branding else "TimeTrack"}.
To reset your password, please click on the link below:
{reset_url}
This link will expire in 1 hour.
If you did not request a password reset, please ignore this email.
Best regards,
The {g.branding.app_name if g.branding else "TimeTrack"} Team
'''
try:
mail.send(msg)
logger.info(f"Password reset email sent to user {user.username}")
except Exception as e:
logger.error(f"Failed to send password reset email: {str(e)}")
flash('Failed to send reset email. Please contact support.', 'error')
return render_template('forgot_password.html', title='Forgot Password')
# Always show success message to prevent user enumeration
flash('If an account exists with that username or email address, we have sent a password reset link.', 'success')
return redirect(url_for('login'))
return render_template('forgot_password.html', title='Forgot Password')
@app.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
"""Handle password reset with token"""
# Find user by reset token
user = User.query.filter_by(password_reset_token=token).first()
if not user or not user.verify_password_reset_token(token):
flash('Invalid or expired reset link.', 'error')
return redirect(url_for('login'))
if request.method == 'POST':
password = request.form.get('password')
confirm_password = request.form.get('confirm_password')
# Validate input
error = None
if not password:
error = 'Password is required'
elif password != confirm_password:
error = 'Passwords do not match'
# Validate password strength
if not error:
validator = PasswordValidator()
is_valid, password_errors = validator.validate(password)
if not is_valid:
error = password_errors[0]
if error:
flash(error, 'error')
return render_template('reset_password.html', token=token, title='Reset Password')
# Update password
user.set_password(password)
user.clear_password_reset_token()
db.session.commit()
logger.info(f"Password reset successful for user {user.username}")
flash('Your password has been reset successfully. Please log in with your new password.', 'success')
return redirect(url_for('login'))
return render_template('reset_password.html', token=token, title='Reset Password')
@app.route('/dashboard')
@role_required(Role.TEAM_MEMBER)
@company_required

View File

@@ -0,0 +1,29 @@
"""Add password reset fields to user model
Revision ID: 85d490db548b
Revises: c72667903a91
Create Date: 2025-07-13 12:24:14.261548
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '85d490db548b'
down_revision = 'c72667903a91'
branch_labels = None
depends_on = None
def upgrade():
# Add password reset fields to user table
op.add_column('user', sa.Column('password_reset_token', sa.String(length=100), nullable=True))
op.add_column('user', sa.Column('password_reset_expiry', sa.DateTime(), nullable=True))
op.create_unique_constraint('uq_user_password_reset_token', 'user', ['password_reset_token'])
def downgrade():
# Remove password reset fields from user table
op.drop_constraint('uq_user_password_reset_token', 'user', type_='unique')
op.drop_column('user', 'password_reset_expiry')
op.drop_column('user', 'password_reset_token')

View File

@@ -49,6 +49,10 @@ class User(db.Model):
# Avatar field
avatar_url = db.Column(db.String(255), nullable=True) # URL to user's avatar image
# Password reset fields
password_reset_token = db.Column(db.String(100), unique=True, nullable=True)
password_reset_expiry = db.Column(db.DateTime, nullable=True)
# Relationships
time_entries = db.relationship('TimeEntry', backref='user', lazy=True)
work_config = db.relationship('WorkConfig', backref='user', lazy=True, uselist=False)
@@ -140,6 +144,28 @@ class User(db.Model):
return self.username[:2].upper()
return "??"
def generate_password_reset_token(self):
"""Generate a password reset token"""
token = secrets.token_urlsafe(32)
self.password_reset_token = token
self.password_reset_expiry = datetime.utcnow() + timedelta(hours=1)
db.session.commit()
return token
def verify_password_reset_token(self, token):
"""Verify if the password reset token is valid"""
if not self.password_reset_token or self.password_reset_token != token:
return False
if not self.password_reset_expiry or datetime.utcnow() > self.password_reset_expiry:
return False
return True
def clear_password_reset_token(self):
"""Clear the password reset token after use"""
self.password_reset_token = None
self.password_reset_expiry = None
db.session.commit()
def __repr__(self):
return f'<User {self.username}>'

View File

@@ -3,13 +3,13 @@ Security headers middleware for Flask.
Add this to ensure secure form submission and prevent security warnings.
"""
from flask import request
from flask import request, current_app
def add_security_headers(response):
"""Add security headers to all responses."""
# Force HTTPS for all resources
if request.is_secure or not request.app.debug:
if request.is_secure or not current_app.debug:
# Strict Transport Security - force HTTPS for 1 year
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'

View File

@@ -0,0 +1,135 @@
{% extends "layout.html" %}
{% block content %}
<div class="content">
<div class="auth-container">
<div class="auth-card">
<h2 class="auth-title">Forgot Password</h2>
<p class="auth-subtitle">
Enter your username or email address and we'll send you a link to reset your password.
</p>
<form method="POST" class="auth-form">
<div class="form-group">
<label for="username_or_email">Username or Email</label>
<input type="text"
id="username_or_email"
name="username_or_email"
class="form-control"
placeholder="Enter your username or email"
required
autofocus>
</div>
<button type="submit" class="btn btn-primary btn-block">Send Reset Link</button>
<div class="auth-links">
<a href="{{ url_for('login') }}">Back to Login</a>
</div>
</form>
</div>
</div>
</div>
<style>
.auth-container {
min-height: calc(100vh - 200px);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.auth-card {
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
padding: 2.5rem;
width: 100%;
max-width: 400px;
}
.auth-title {
text-align: center;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
.auth-subtitle {
text-align: center;
color: #666;
margin-bottom: 2rem;
line-height: 1.5;
}
.auth-form .form-group {
margin-bottom: 1.5rem;
}
.auth-form label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #333;
}
.auth-form .form-control {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 1rem;
transition: border-color 0.2s;
}
.auth-form .form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.btn-block {
width: 100%;
padding: 0.875rem;
font-size: 1rem;
font-weight: 500;
margin-bottom: 1rem;
}
.auth-links {
text-align: center;
margin-top: 1.5rem;
}
.auth-links a {
color: var(--primary-color);
text-decoration: none;
font-size: 0.875rem;
}
.auth-links a:hover {
text-decoration: underline;
}
/* Mobile optimization */
@media (max-width: 768px) {
.auth-container {
padding: 1rem;
min-height: calc(100vh - 140px);
}
.auth-card {
padding: 1.5rem;
}
.auth-title {
font-size: 1.5rem;
}
.auth-subtitle {
font-size: 0.875rem;
}
}
</style>
{% endblock %}

View File

@@ -47,6 +47,12 @@
<button type="submit" class="btn btn-primary">Sign In</button>
</div>
<div class="auth-links" style="margin-bottom: 1rem;">
<p>
<a href="{{ url_for('forgot_password') }}">Forgot your password?</a>
</p>
</div>
<div class="social-divider">
<span>New to {{ g.branding.app_name if g.branding else 'TimeTrack' }}?</span>
</div>

View File

@@ -0,0 +1,291 @@
{% extends "layout.html" %}
{% block content %}
<div class="content">
<div class="auth-container">
<div class="auth-card">
<h2 class="auth-title">Reset Password</h2>
<p class="auth-subtitle">
Enter your new password below.
</p>
<form method="POST" class="auth-form">
<div class="form-group">
<label for="password">New Password</label>
<input type="password"
id="password"
name="password"
class="form-control"
placeholder="Enter new password"
required
autofocus>
<div id="password-strength" class="password-strength-meter"></div>
</div>
<div class="form-group">
<label for="confirm_password">Confirm Password</label>
<input type="password"
id="confirm_password"
name="confirm_password"
class="form-control"
placeholder="Confirm new password"
required>
</div>
<!-- Password requirements -->
<div class="password-requirements">
<p class="requirements-title">Password must contain:</p>
<ul id="password-requirements-list">
<li id="req-length">At least 8 characters</li>
<li id="req-uppercase">One uppercase letter</li>
<li id="req-lowercase">One lowercase letter</li>
<li id="req-number">One number</li>
<li id="req-special">One special character</li>
</ul>
</div>
<button type="submit" class="btn btn-primary btn-block">Reset Password</button>
<div class="auth-links">
<a href="{{ url_for('login') }}">Back to Login</a>
</div>
</form>
</div>
</div>
</div>
<style>
.auth-container {
min-height: calc(100vh - 200px);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.auth-card {
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
padding: 2.5rem;
width: 100%;
max-width: 400px;
}
.auth-title {
text-align: center;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
.auth-subtitle {
text-align: center;
color: #666;
margin-bottom: 2rem;
line-height: 1.5;
}
.auth-form .form-group {
margin-bottom: 1.5rem;
}
.auth-form label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #333;
}
.auth-form .form-control {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 1rem;
transition: border-color 0.2s;
}
.auth-form .form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.password-strength-meter {
margin-top: 0.5rem;
height: 4px;
background: #e0e0e0;
border-radius: 2px;
overflow: hidden;
transition: all 0.3s ease;
}
.password-strength-meter.weak {
background: #ff4444;
}
.password-strength-meter.fair {
background: #ffaa00;
}
.password-strength-meter.good {
background: #00aa00;
}
.password-strength-meter.strong {
background: #008800;
}
.password-requirements {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 1rem;
margin-bottom: 1.5rem;
font-size: 0.875rem;
}
.requirements-title {
margin: 0 0 0.5rem 0;
font-weight: 500;
color: #495057;
}
#password-requirements-list {
margin: 0;
padding-left: 1.25rem;
color: #6c757d;
}
#password-requirements-list li {
margin-bottom: 0.25rem;
position: relative;
}
#password-requirements-list li.valid {
color: #28a745;
}
#password-requirements-list li.valid::before {
content: "✓ ";
position: absolute;
left: -1.25rem;
font-weight: bold;
}
.btn-block {
width: 100%;
padding: 0.875rem;
font-size: 1rem;
font-weight: 500;
margin-bottom: 1rem;
}
.auth-links {
text-align: center;
margin-top: 1.5rem;
}
.auth-links a {
color: var(--primary-color);
text-decoration: none;
font-size: 0.875rem;
}
.auth-links a:hover {
text-decoration: underline;
}
/* Mobile optimization */
@media (max-width: 768px) {
.auth-container {
padding: 1rem;
min-height: calc(100vh - 140px);
}
.auth-card {
padding: 1.5rem;
}
.auth-title {
font-size: 1.5rem;
}
.auth-subtitle {
font-size: 0.875rem;
}
.password-requirements {
font-size: 0.8125rem;
}
}
</style>
<script>
// Password strength validation
document.addEventListener('DOMContentLoaded', function() {
const passwordInput = document.getElementById('password');
const confirmInput = document.getElementById('confirm_password');
const strengthMeter = document.getElementById('password-strength');
// Requirement elements
const reqLength = document.getElementById('req-length');
const reqUppercase = document.getElementById('req-uppercase');
const reqLowercase = document.getElementById('req-lowercase');
const reqNumber = document.getElementById('req-number');
const reqSpecial = document.getElementById('req-special');
passwordInput.addEventListener('input', function() {
const password = this.value;
// Check each requirement
const hasLength = password.length >= 8;
const hasUppercase = /[A-Z]/.test(password);
const hasLowercase = /[a-z]/.test(password);
const hasNumber = /[0-9]/.test(password);
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
// Update requirement indicators
reqLength.classList.toggle('valid', hasLength);
reqUppercase.classList.toggle('valid', hasUppercase);
reqLowercase.classList.toggle('valid', hasLowercase);
reqNumber.classList.toggle('valid', hasNumber);
reqSpecial.classList.toggle('valid', hasSpecial);
// Calculate strength
let strength = 0;
if (hasLength) strength++;
if (hasUppercase) strength++;
if (hasLowercase) strength++;
if (hasNumber) strength++;
if (hasSpecial) strength++;
// Update strength meter
strengthMeter.className = 'password-strength-meter';
if (password.length === 0) {
strengthMeter.className = 'password-strength-meter';
} else if (strength <= 2) {
strengthMeter.className = 'password-strength-meter weak';
} else if (strength === 3) {
strengthMeter.className = 'password-strength-meter fair';
} else if (strength === 4) {
strengthMeter.className = 'password-strength-meter good';
} else {
strengthMeter.className = 'password-strength-meter strong';
}
});
// Real-time password match validation
confirmInput.addEventListener('input', function() {
if (passwordInput.value && this.value) {
if (passwordInput.value !== this.value) {
this.setCustomValidity('Passwords do not match');
} else {
this.setCustomValidity('');
}
}
});
});
</script>
{% endblock %}