forked from cleveragents/cleveragents-core
3c0b326c0d
Each simulation ran the full legacy lifecycle (init → tell → build → apply) with real Anthropic Claude API calls: 1. sim1-todo-cli: Click-based todo app (add/list/delete) 2. sim2-bookstore-api: FastAPI CRUD with SQLite (5 code files) 3. sim3-websocket-chat: async websocket chat server 4. sim4-hn-scraper: HN top stories scraper with JSON output 5. sim5-flask-auth: Flask + Flask-Login authentication app All 5 generated correct, production-quality Python code matching the requested task specifications.
552 lines
19 KiB
Python
552 lines
19 KiB
Python
I'll create a production-ready Flask web application with user authentication. Here's the complete implementation:
|
|
|
|
## requirements.txt
|
|
```txt
|
|
Flask>=2.3.0
|
|
Flask-Login>=0.6.0
|
|
Flask-SQLAlchemy>=3.0.0
|
|
Flask-WTF>=1.1.0
|
|
WTForms>=3.0.0
|
|
Werkzeug>=2.3.0
|
|
bcrypt>=4.0.0
|
|
email-validator>=2.0.0
|
|
```
|
|
|
|
## config.py
|
|
```python
|
|
import os
|
|
from datetime import timedelta
|
|
|
|
class Config:
|
|
"""Application configuration class"""
|
|
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key-change-in-production'
|
|
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'sqlite:///app.db'
|
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
|
|
# Session configuration
|
|
PERMANENT_SESSION_LIFETIME = timedelta(hours=24)
|
|
SESSION_COOKIE_SECURE = os.environ.get('FLASK_ENV') == 'production'
|
|
SESSION_COOKIE_HTTPONLY = True
|
|
SESSION_COOKIE_SAMESITE = 'Lax'
|
|
|
|
# WTF-Forms CSRF protection
|
|
WTF_CSRF_ENABLED = True
|
|
WTF_CSRF_TIME_LIMIT = 3600
|
|
```
|
|
|
|
## models.py
|
|
```python
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from flask_login import UserMixin
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|
from datetime import datetime
|
|
|
|
db = SQLAlchemy()
|
|
|
|
class User(UserMixin, db.Model):
|
|
"""User model for authentication"""
|
|
__tablename__ = 'users'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
username = db.Column(db.String(80), unique=True, nullable=False, index=True)
|
|
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
|
|
password_hash = db.Column(db.String(255), nullable=False)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
is_active = db.Column(db.Boolean, default=True)
|
|
|
|
def set_password(self, password):
|
|
"""Hash and set password"""
|
|
self.password_hash = generate_password_hash(password, method='pbkdf2:sha256', salt_length=16)
|
|
|
|
def check_password(self, password):
|
|
"""Check if provided password matches hash"""
|
|
return check_password_hash(self.password_hash, password)
|
|
|
|
def get_id(self):
|
|
"""Return user id as string for Flask-Login"""
|
|
return str(self.id)
|
|
|
|
def __repr__(self):
|
|
return f'<User {self.username}>'
|
|
```
|
|
|
|
## forms.py
|
|
```python
|
|
from flask_wtf import FlaskForm
|
|
from wtforms import StringField, PasswordField, SubmitField, BooleanField
|
|
from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError
|
|
from models import User
|
|
|
|
class LoginForm(FlaskForm):
|
|
"""User login form"""
|
|
username = StringField('Username', validators=[
|
|
DataRequired(message='Username is required'),
|
|
Length(min=3, max=20, message='Username must be between 3 and 20 characters')
|
|
])
|
|
password = PasswordField('Password', validators=[
|
|
DataRequired(message='Password is required')
|
|
])
|
|
remember_me = BooleanField('Remember Me')
|
|
submit = SubmitField('Sign In')
|
|
|
|
class RegistrationForm(FlaskForm):
|
|
"""User registration form"""
|
|
username = StringField('Username', validators=[
|
|
DataRequired(message='Username is required'),
|
|
Length(min=3, max=20, message='Username must be between 3 and 20 characters')
|
|
])
|
|
email = StringField('Email', validators=[
|
|
DataRequired(message='Email is required'),
|
|
Email(message='Please enter a valid email address')
|
|
])
|
|
password = PasswordField('Password', validators=[
|
|
DataRequired(message='Password is required'),
|
|
Length(min=8, message='Password must be at least 8 characters long')
|
|
])
|
|
password2 = PasswordField('Repeat Password', validators=[
|
|
DataRequired(message='Please confirm your password'),
|
|
EqualTo('password', message='Passwords must match')
|
|
])
|
|
submit = SubmitField('Register')
|
|
|
|
def validate_username(self, username):
|
|
"""Check if username already exists"""
|
|
user = User.query.filter_by(username=username.data).first()
|
|
if user:
|
|
raise ValidationError('Username already exists. Please choose a different one.')
|
|
|
|
def validate_email(self, email):
|
|
"""Check if email already exists"""
|
|
user = User.query.filter_by(email=email.data).first()
|
|
if user:
|
|
raise ValidationError('Email already registered. Please use a different email.')
|
|
```
|
|
|
|
## app.py
|
|
```python
|
|
from flask import Flask, render_template, redirect, url_for, flash, request
|
|
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
|
|
from models import db, User
|
|
from forms import LoginForm, RegistrationForm
|
|
from config import Config
|
|
import logging
|
|
|
|
def create_app():
|
|
"""Application factory pattern"""
|
|
app = Flask(__name__)
|
|
app.config.from_object(Config)
|
|
|
|
# Initialize extensions
|
|
db.init_app(app)
|
|
|
|
# Setup Flask-Login
|
|
login_manager = LoginManager()
|
|
login_manager.init_app(app)
|
|
login_manager.login_view = 'login'
|
|
login_manager.login_message = 'Please log in to access this page.'
|
|
login_manager.login_message_category = 'info'
|
|
|
|
@login_manager.user_loader
|
|
def load_user(user_id):
|
|
"""Load user for Flask-Login"""
|
|
return User.query.get(int(user_id))
|
|
|
|
# Create database tables
|
|
with app.app_context():
|
|
db.create_all()
|
|
|
|
return app
|
|
|
|
app = create_app()
|
|
|
|
@app.route('/')
|
|
def index():
|
|
"""Home page"""
|
|
return render_template('index.html')
|
|
|
|
@app.route('/login', methods=['GET', 'POST'])
|
|
def login():
|
|
"""User login"""
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for('dashboard'))
|
|
|
|
form = LoginForm()
|
|
if form.validate_on_submit():
|
|
user = User.query.filter_by(username=form.username.data).first()
|
|
|
|
if user and user.check_password(form.password.data):
|
|
login_user(user, remember=form.remember_me.data)
|
|
next_page = request.args.get('next')
|
|
flash(f'Welcome back, {user.username}!', 'success')
|
|
return redirect(next_page) if next_page else redirect(url_for('dashboard'))
|
|
else:
|
|
flash('Invalid username or password', 'danger')
|
|
|
|
return render_template('login.html', form=form)
|
|
|
|
@app.route('/register', methods=['GET', 'POST'])
|
|
def register():
|
|
"""User registration"""
|
|
if current_user.is_authenticated:
|
|
return redirect(url_for('dashboard'))
|
|
|
|
form = RegistrationForm()
|
|
if form.validate_on_submit():
|
|
try:
|
|
user = User(username=form.username.data, email=form.email.data)
|
|
user.set_password(form.password.data)
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
flash('Registration successful! Please log in.', 'success')
|
|
return redirect(url_for('login'))
|
|
except Exception as e:
|
|
db.session.rollback()
|
|
flash('Registration failed. Please try again.', 'danger')
|
|
app.logger.error(f'Registration error: {e}')
|
|
|
|
return render_template('register.html', form=form)
|
|
|
|
@app.route('/dashboard')
|
|
@login_required
|
|
def dashboard():
|
|
"""Protected user dashboard"""
|
|
return render_template('dashboard.html')
|
|
|
|
@app.route('/logout')
|
|
@login_required
|
|
def logout():
|
|
"""User logout"""
|
|
username = current_user.username
|
|
logout_user()
|
|
flash(f'Goodbye, {username}!', 'info')
|
|
return redirect(url_for('index'))
|
|
|
|
@app.errorhandler(404)
|
|
def not_found_error(error):
|
|
"""Handle 404 errors"""
|
|
return render_template('404.html'), 404
|
|
|
|
@app.errorhandler(500)
|
|
def internal_error(error):
|
|
"""Handle 500 errors"""
|
|
db.session.rollback()
|
|
return render_template('500.html'), 500
|
|
|
|
if __name__ == '__main__':
|
|
# Configure logging for production
|
|
if not app.debug:
|
|
import logging
|
|
from logging.handlers import RotatingFileHandler
|
|
|
|
file_handler = RotatingFileHandler('logs/app.log', maxBytes=10240, backupCount=10)
|
|
file_handler.setFormatter(logging.Formatter(
|
|
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
|
|
))
|
|
file_handler.setLevel(logging.INFO)
|
|
app.logger.addHandler(file_handler)
|
|
app.logger.setLevel(logging.INFO)
|
|
|
|
app.run(debug=True)
|
|
```
|
|
|
|
## templates/base.html
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{% block title %}Flask Auth App{% endblock %}</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
</head>
|
|
<body>
|
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
|
<div class="container">
|
|
<a class="navbar-brand" href="{{ url_for('index') }}">Flask Auth</a>
|
|
<div class="navbar-nav ms-auto">
|
|
{% if current_user.is_authenticated %}
|
|
<a class="nav-link" href="{{ url_for('dashboard') }}">Dashboard</a>
|
|
<a class="nav-link" href="{{ url_for('logout') }}">Logout ({{ current_user.username }})</a>
|
|
{% else %}
|
|
<a class="nav-link" href="{{ url_for('login') }}">Login</a>
|
|
<a class="nav-link" href="{{ url_for('register') }}">Register</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<main class="container mt-4">
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
{% for category, message in messages %}
|
|
<div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show">
|
|
{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
## templates/index.html
|
|
```html
|
|
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h1 class="card-title">Welcome to Flask Auth App</h1>
|
|
<p class="card-text">A secure authentication system built with Flask.</p>
|
|
|
|
{% if current_user.is_authenticated %}
|
|
<p class="text-success">Hello, {{ current_user.username }}! You are logged in.</p>
|
|
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">Go to Dashboard</a>
|
|
{% else %}
|
|
<p>Please log in to access your dashboard.</p>
|
|
<a href="{{ url_for('login') }}" class="btn btn-primary me-2">Login</a>
|
|
<a href="{{ url_for('register') }}" class="btn btn-outline-primary">Register</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
```
|
|
|
|
## templates/login.html
|
|
```html
|
|
{% extends "base.html" %}
|
|
|
|
{% block title %}Login - Flask Auth App{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="mb-0">Sign In</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST">
|
|
{{ form.hidden_tag() }}
|
|
|
|
<div class="mb-3">
|
|
{{ form.username.label(class="form-label") }}
|
|
{{ form.username(class="form-control" + (" is-invalid" if form.username.errors else "")) }}
|
|
{% if form.username.errors %}
|
|
<div class="invalid-feedback">
|
|
{% for error in form.username.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
{{ form.password.label(class="form-label") }}
|
|
{{ form.password(class="form-control" + (" is-invalid" if form.password.errors else "")) }}
|
|
{% if form.password.errors %}
|
|
<div class="invalid-feedback">
|
|
{% for error in form.password.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3 form-check">
|
|
{{ form.remember_me(class="form-check-input") }}
|
|
{{ form.remember_me.label(class="form-check-label") }}
|
|
</div>
|
|
|
|
<div class="d-grid">
|
|
{{ form.submit(class="btn btn-primary") }}
|
|
</div>
|
|
</form>
|
|
|
|
<hr>
|
|
<p class="text-center mb-0">
|
|
Don't have an account? <a href="{{ url_for('register') }}">Register here</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
```
|
|
|
|
## templates/register.html
|
|
```html
|
|
{% extends "base.html" %}
|
|
|
|
{% block title %}Register - Flask Auth App{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="mb-0">Create Account</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST">
|
|
{{ form.hidden_tag() }}
|
|
|
|
<div class="mb-3">
|
|
{{ form.username.label(class="form-label") }}
|
|
{{ form.username(class="form-control" + (" is-invalid" if form.username.errors else "")) }}
|
|
{% if form.username.errors %}
|
|
<div class="invalid-feedback">
|
|
{% for error in form.username.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
{{ form.email.label(class="form-label") }}
|
|
{{ form.email(class="form-control" + (" is-invalid" if form.email.errors else "")) }}
|
|
{% if form.email.errors %}
|
|
<div class="invalid-feedback">
|
|
{% for error in form.email.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
{{ form.password.label(class="form-label") }}
|
|
{{ form.password(class="form-control" + (" is-invalid" if form.password.errors else "")) }}
|
|
{% if form.password.errors %}
|
|
<div class="invalid-feedback">
|
|
{% for error in form.password.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
{{ form.password2.label(class="form-label") }}
|
|
{{ form.password2(class="form-control" + (" is-invalid" if form.password2.errors else "")) }}
|
|
{% if form.password2.errors %}
|
|
<div class="invalid-feedback">
|
|
{% for error in form.password2.errors %}{{ error }}{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="d-grid">
|
|
{{ form.submit(class="btn btn-success") }}
|
|
</div>
|
|
</form>
|
|
|
|
<hr>
|
|
<p class="text-center mb-0">
|
|
Already have an account? <a href="{{ url_for('login') }}">Sign in here</a>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
```
|
|
|
|
## templates/dashboard.html
|
|
```html
|
|
{% extends "base.html" %}
|
|
|
|
{% block title %}Dashboard - Flask Auth App{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h4 class="mb-0">User Dashboard</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<h5>Welcome, {{ current_user.username }}!</h5>
|
|
<p class="text-muted">This is your protected dashboard area.</p>
|
|
|
|
<div class="row mt-4">
|
|
<div class="col-md-6">
|
|
<h6>Account Information</h6>
|
|
<ul class="list-unstyled">
|
|
<li><strong>Username:</strong> {{ current_user.username }}</li>
|
|
<li><strong>Email:</strong> {{ current_user.email }}</li>
|
|
<li><strong>Member since:</strong> {{ current_user.created_at.strftime('%B %d, %Y') }}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<a href="{{ url_for('logout') }}" class="btn btn-outline-danger">Logout</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
```
|
|
|
|
## static/style.css
|
|
```css
|
|
/* Custom styles for Flask Auth App */
|
|
body {
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.card {
|
|
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
|
border: 1px solid rgba(0, 0, 0, 0.125);
|
|
}
|
|
|
|
.navbar-brand {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.alert {
|
|
border-radius: 0.375rem;
|
|
}
|
|
|
|
.form-control:focus {
|
|
border-color: #86b7fe;
|
|
outline: 0;
|
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
|
}
|
|
|
|
.btn {
|
|
border-radius: 0.375rem;
|
|
}
|
|
|
|
.card-header {
|
|
background-color: #f8f9fa;
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 768px) {
|
|
.container {
|
|
padding-left: 15px;
|
|
padding-right: 15px;
|
|
}
|
|
}
|
|
```
|
|
|
|
This implementation provides:
|
|
|
|
- **Secure Authentication**: Password hashing with PBKDF2, CSRF protection, secure sessions
|
|
- **User Management**: Registration, login, logout with proper validation
|
|
- **Route Protection**: Login required decorators for protected pages
|
|
- **Error Handling**: Comprehensive error handling and user feedback
|
|
- **Production Ready**: Proper configuration management, logging, and security headers
|
|
- **Responsive UI**: Bootstrap-based responsive design
|
|
- **Database Integration**: SQLAlchemy ORM with user model
|
|
|
|
To run the application:
|
|
1. Install dependencies: `pip install -r requirements.txt`
|
|
2. Set environment variables for production
|
|
3. Run: `python app.py` |