Quickly add security features to your Flask application.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Helper functions for URLs, tokens, validation, communication, and other common security operations that support Flask-Security's core functionality.
Functions for generating security-related URLs and handling navigation within Flask-Security.
def url_for_security(endpoint, **values):
"""
Generate URLs for Flask-Security endpoints.
Parameters:
- endpoint: Security endpoint name (e.g., 'login', 'register', 'reset_password')
- values: Additional URL parameters and query arguments
Returns:
Generated URL string for the security endpoint
"""
def get_url(endpoint_or_url, qparams=None, **values):
"""
Generate URLs with query parameters.
Parameters:
- endpoint_or_url: Flask endpoint name or absolute URL
- qparams: Dictionary of query parameters to append
- values: Additional URL generation values
Returns:
Complete URL with query parameters
"""
def transform_url(url, qparams=None, **params):
"""
Transform URLs with custom logic and parameters.
Parameters:
- url: Base URL to transform
- qparams: Query parameters to add or modify
- params: Additional transformation parameters
Returns:
Transformed URL string
"""
def get_post_login_redirect():
"""
Get redirect URL after successful login.
Returns:
URL string for post-login redirect, considering 'next' parameter
"""
def get_logout_redirect():
"""
Get redirect URL after logout.
Returns:
URL string for post-logout redirect
"""Functions for generating, validating, and managing security tokens and cryptographic operations.
def get_hmac(password):
"""
Generate HMAC for token security and password operations.
Parameters:
- password: Password or data to generate HMAC for
Returns:
HMAC digest string
"""
def check_and_get_token_status(token, serializer, max_age=None):
"""
Validate token status and return token information.
Parameters:
- token: Token string to validate
- serializer: Token serializer instance
- max_age: Maximum token age in seconds (optional)
Returns:
Tuple of (is_expired: bool, is_invalid: bool, token_data: dict)
"""
def generate_token(data, salt=None):
"""
Generate secure token from data.
Parameters:
- data: Data to encode in token
- salt: Salt for token generation (optional)
Returns:
Generated token string
"""
def verify_token(token, salt=None, max_age=None):
"""
Verify and decode token data.
Parameters:
- token: Token string to verify
- salt: Salt used for token generation (optional)
- max_age: Maximum acceptable token age in seconds
Returns:
Decoded token data if valid, None if invalid or expired
"""Functions for validating password strength, complexity, and security requirements.
def password_length_validator(password, min_length=None, max_length=None):
"""
Validate password length requirements.
Parameters:
- password: Password string to validate
- min_length: Minimum required length (default: from config)
- max_length: Maximum allowed length (default: from config)
Returns:
True if valid, raises ValidationError if invalid
"""
def password_complexity_validator(password, **requirements):
"""
Validate password complexity requirements.
Parameters:
- password: Password string to validate
- requirements: Complexity requirements dict (uppercase, lowercase, digits, symbols)
Returns:
True if valid, raises ValidationError if invalid
"""
def password_breached_validator(password, check_breach_count=True):
"""
Check password against known breach databases.
Parameters:
- password: Password string to check
- check_breach_count: Whether to check breach count threshold
Returns:
True if password is safe, raises ValidationError if breached
"""
def pwned(password, user_agent=None, request_timeout=1.0):
"""
Check if password appears in HaveIBeenPwned breach database.
Parameters:
- password: Password string to check
- user_agent: Custom User-Agent for API requests
- request_timeout: Timeout for API request in seconds
Returns:
Number of times password appears in breaches, 0 if safe
"""Functions for sending emails, SMS messages, and other forms of communication.
def send_mail(subject, recipient, template, **context):
"""
Send email utility function with template rendering.
Parameters:
- subject: Email subject line
- recipient: Recipient email address
- template: Template name for email body
- context: Template context variables
Returns:
True if email sent successfully, False otherwise
"""
class MailUtil:
"""
Email handling and template rendering utility class.
"""
def __init__(self, app=None):
"""
Initialize mail utility.
Parameters:
- app: Flask application instance (optional)
"""
def init_app(self, app):
"""
Initialize mail utility with Flask app.
Parameters:
- app: Flask application instance
"""
def send_mail(self, template, subject, recipient, sender=None, **kwargs):
"""
Send templated email.
Parameters:
- template: Email template name
- subject: Email subject
- recipient: Recipient email address
- sender: Sender email address (optional)
- kwargs: Template context variables
Returns:
True if sent successfully, False otherwise
"""
def render_template(self, template, **kwargs):
"""
Render email template with context.
Parameters:
- template: Template name to render
- kwargs: Template context variables
Returns:
Rendered template string
"""
class EmailValidateException(Exception):
"""Exception raised for email validation errors."""
passClasses and factory for SMS message sending with multiple provider support.
class SmsSenderBaseClass:
"""
Abstract base class for SMS senders.
"""
def __init__(self, **kwargs):
"""
Initialize SMS sender.
Parameters:
- kwargs: Provider-specific configuration
"""
def send_sms(self, from_number, to_number, msg):
"""
Send SMS message.
Parameters:
- from_number: Sender phone number
- to_number: Recipient phone number
- msg: Message content
Returns:
True if sent successfully, False otherwise
"""
class SmsSenderFactory:
"""
Factory for creating SMS sender instances.
"""
@staticmethod
def createSender(name, **kwargs):
"""
Create SMS sender instance by name.
Parameters:
- name: SMS service name ('Dummy', 'Twilio', etc.)
- kwargs: Service-specific configuration
Returns:
SMS sender instance
"""
class DummySmsSender(SmsSenderBaseClass):
"""
Dummy SMS sender implementation for testing and development.
"""
def send_sms(self, from_number, to_number, msg):
"""Send SMS (dummy implementation that logs message)."""
print(f"SMS to {to_number}: {msg}")
return TrueUtility functions for handling time operations and request context management.
def naive_utcnow():
"""
Get current UTC time as naive datetime.
Returns:
Datetime object representing current UTC time without timezone info
"""
def get_request_attr(name):
"""
Get attribute from current request context.
Parameters:
- name: Attribute name to retrieve from request
Returns:
Attribute value if found, None otherwise
"""
def get_request_attr_or_default(name, default=None):
"""
Get request attribute with default fallback.
Parameters:
- name: Attribute name to retrieve
- default: Default value if attribute not found
Returns:
Attribute value or default
"""Utility classes for managing specific Flask-Security features and operations.
class PasswordUtil:
"""
Password management and policy utility class.
"""
def __init__(self, app=None):
"""Initialize password utility."""
def init_app(self, app):
"""Initialize with Flask app."""
def hash_password(self, password):
"""
Hash password using configured algorithm.
Parameters:
- password: Plain text password to hash
Returns:
Hashed password string
"""
def verify_password(self, password, password_hash):
"""
Verify password against hash.
Parameters:
- password: Plain text password
- password_hash: Stored password hash
Returns:
True if password matches, False otherwise
"""
def validate_password(self, password):
"""
Validate password against configured policies.
Parameters:
- password: Password to validate
Returns:
True if valid, raises ValidationError if invalid
"""
class PhoneUtil:
"""
Phone number validation and formatting utility class.
"""
def __init__(self, app=None):
"""Initialize phone utility."""
def init_app(self, app):
"""Initialize with Flask app."""
def validate_phone_number(self, phone_number):
"""
Validate phone number format.
Parameters:
- phone_number: Phone number string to validate
Returns:
True if valid, False otherwise
"""
def normalize_phone_number(self, phone_number):
"""
Normalize phone number to standard format.
Parameters:
- phone_number: Phone number to normalize
Returns:
Normalized phone number string
"""
class UsernameUtil:
"""
Username validation and management utility class.
"""
def __init__(self, app=None):
"""Initialize username utility."""
def init_app(self, app):
"""Initialize with Flask app."""
def validate_username(self, username):
"""
Validate username format and requirements.
Parameters:
- username: Username to validate
Returns:
True if valid, raises ValidationError if invalid
"""
def normalize_username(self, username):
"""
Normalize username format.
Parameters:
- username: Username to normalize
Returns:
Normalized username string
"""Time-based One-Time Password generation and validation for two-factor authentication.
class Totp:
"""
Time-based One-Time Password generation and validation utility class.
"""
def __init__(self, secret=None, issuer=None):
"""
Initialize TOTP utility.
Parameters:
- secret: Base32-encoded secret key (optional, generates if not provided)
- issuer: Issuer name for TOTP (optional)
"""
def generate_password(self, counter=None):
"""
Generate TOTP password for current time window.
Parameters:
- counter: Time counter (optional, uses current time if not provided)
Returns:
6-digit TOTP code as string
"""
def verify(self, token, window=0):
"""
Verify TOTP token against current time.
Parameters:
- token: TOTP token string to verify
- window: Number of time windows to check (default: 0)
Returns:
True if token is valid, False otherwise
"""
def generate_qr_code(self, name, issuer=None):
"""
Generate QR code for TOTP secret.
Parameters:
- name: Account name for QR code
- issuer: Issuer name (optional)
Returns:
QR code image data as base64 string
"""
@property
def secret(self):
"""
Get TOTP secret key.
Returns:
Base32-encoded secret string
"""
@property
def provisioning_uri(self):
"""
Get provisioning URI for authenticator apps.
Returns:
otpauth:// URI string
"""from flask_security import url_for_security, get_url
@app.route('/custom-login')
def custom_login_redirect():
"""Redirect to Flask-Security login page."""
login_url = url_for_security('login', next='/dashboard')
return redirect(login_url)
@app.route('/user-profile')
@login_required
def user_profile():
"""Display user profile with navigation links."""
# Generate security URLs
change_password_url = url_for_security('change_password')
logout_url = url_for_security('logout')
# Add query parameters
reset_url = get_url('security.forgot_password',
qparams={'email': current_user.email})
return render_template('profile.html',
change_password_url=change_password_url,
logout_url=logout_url,
reset_url=reset_url)from flask_security import generate_token, verify_token
@app.route('/send-invite', methods=['POST'])
@roles_required('admin')
def send_invite():
"""Send invitation token to new user."""
email = request.form.get('email')
# Generate invitation token
token_data = {'email': email, 'action': 'invite'}
token = generate_token(token_data, salt='invite-salt')
# Send invitation email
invite_url = url_for('accept_invite', token=token, _external=True)
send_mail(
subject='You are invited to join our platform',
recipient=email,
template='invite_email',
invite_url=invite_url
)
flash(f'Invitation sent to {email}')
return redirect(url_for('admin_users'))
@app.route('/accept-invite/<token>')
def accept_invite(token):
"""Accept invitation using token."""
# Verify token (valid for 7 days)
token_data = verify_token(token, salt='invite-salt', max_age=604800)
if not token_data:
flash('Invalid or expired invitation link')
return redirect(url_for('security.register'))
# Pre-fill registration form with invited email
return redirect(url_for('security.register', email=token_data['email']))from flask_security import (
password_length_validator,
password_complexity_validator,
password_breached_validator
)
def validate_new_password(password):
"""Custom password validation with multiple checks."""
try:
# Check length requirements
password_length_validator(password, min_length=8, max_length=128)
# Check complexity requirements
password_complexity_validator(password, {
'uppercase': 1,
'lowercase': 1,
'digits': 1,
'symbols': 1
})
# Check against breach database
password_breached_validator(password)
return True
except ValidationError as e:
return False, str(e)
@app.route('/validate-password', methods=['POST'])
def validate_password_endpoint():
"""API endpoint for password validation."""
password = request.json.get('password')
is_valid, error_message = validate_new_password(password)
return jsonify({
'valid': is_valid,
'error': error_message if not is_valid else None
})from flask_security import send_mail, MailUtil
# Initialize mail utility
mail_util = MailUtil(app)
@app.route('/send-welcome-email')
@login_required
def send_welcome_email():
"""Send welcome email to new user."""
success = send_mail(
subject='Welcome to Our Platform!',
recipient=current_user.email,
template='welcome_email',
user=current_user,
login_url=url_for_security('login', _external=True)
)
if success:
flash('Welcome email sent successfully')
else:
flash('Failed to send welcome email')
return redirect(url_for('dashboard'))
@app.route('/send-custom-notification', methods=['POST'])
@roles_required('admin')
def send_custom_notification():
"""Send custom notification to users."""
subject = request.form.get('subject')
message = request.form.get('message')
recipient_emails = request.form.getlist('recipients')
sent_count = 0
for email in recipient_emails:
if mail_util.send_mail(
template='notification_email',
subject=subject,
recipient=email,
message=message,
admin_name=current_user.email
):
sent_count += 1
flash(f'Notification sent to {sent_count} users')
return redirect(url_for('admin_notifications'))from flask_security import SmsSenderFactory
# Configure SMS service
app.config['SECURITY_SMS_SERVICE'] = 'Twilio'
app.config['SECURITY_SMS_SERVICE_CONFIG'] = {
'ACCOUNT_SID': 'your-account-sid',
'AUTH_TOKEN': 'your-auth-token',
'FROM_NUMBER': '+1234567890'
}
# Create SMS sender
sms_sender = SmsSenderFactory.createSender(
app.config['SECURITY_SMS_SERVICE'],
**app.config['SECURITY_SMS_SERVICE_CONFIG']
)
@app.route('/send-sms-code', methods=['POST'])
@login_required
def send_sms_code():
"""Send SMS verification code to user."""
if not current_user.us_phone_number:
flash('Phone number not configured')
return redirect(url_for('profile'))
# Generate verification code
code = generate_random_code(6)
# Store code in session with expiration
session['sms_verification_code'] = code
session['sms_code_expires'] = (datetime.utcnow() + timedelta(minutes=5)).isoformat()
# Send SMS
message = f"Your verification code is: {code}"
success = sms_sender.send_sms(
from_number=app.config['SECURITY_SMS_SERVICE_CONFIG']['FROM_NUMBER'],
to_number=current_user.us_phone_number,
msg=message
)
if success:
flash('Verification code sent to your phone')
else:
flash('Failed to send SMS')
return redirect(url_for('verify_phone'))
def generate_random_code(length=6):
"""Generate random numeric code."""
import random
import string
return ''.join(random.choices(string.digits, k=length))from flask_security import Totp
@app.route('/setup-2fa')
@login_required
def setup_2fa():
"""Setup TOTP two-factor authentication."""
# Generate TOTP secret
totp = Totp()
# Store secret in session temporarily
session['totp_setup_secret'] = totp.secret
# Generate QR code for authenticator apps
qr_code = totp.generate_qr_code(
name=current_user.email,
issuer='My Secure App'
)
return render_template('setup_2fa.html',
secret=totp.secret,
qr_code=qr_code)
@app.route('/verify-2fa-setup', methods=['POST'])
@login_required
def verify_2fa_setup():
"""Verify TOTP setup with user-provided code."""
code = request.form.get('code')
secret = session.get('totp_setup_secret')
if not secret:
flash('Setup session expired')
return redirect(url_for('setup_2fa'))
# Verify the code
totp = Totp(secret=secret)
if totp.verify(code, window=1):
# Save TOTP secret to user
current_user.tf_totp_secret = secret
current_user.tf_primary_method = 'authenticator'
db.session.commit()
# Clear setup session
session.pop('totp_setup_secret', None)
flash('Two-factor authentication enabled successfully')
return redirect(url_for('profile'))
else:
flash('Invalid code. Please try again.')
return redirect(url_for('setup_2fa'))
@app.route('/verify-2fa', methods=['GET', 'POST'])
@login_required
def verify_2fa():
"""Verify TOTP code for authentication."""
if request.method == 'POST':
code = request.form.get('code')
if current_user.tf_totp_secret:
totp = Totp(secret=current_user.tf_totp_secret)
if totp.verify(code, window=1):
session['2fa_verified'] = True
return redirect(request.args.get('next', '/dashboard'))
else:
flash('Invalid authentication code')
return render_template('verify_2fa.html')from flask_security import PasswordUtil, PhoneUtil, UsernameUtil
# Initialize utility classes
password_util = PasswordUtil(app)
phone_util = PhoneUtil(app)
username_util = UsernameUtil(app)
@app.route('/validate-profile', methods=['POST'])
@login_required
def validate_profile():
"""Validate user profile data."""
username = request.form.get('username')
phone = request.form.get('phone')
new_password = request.form.get('new_password')
errors = []
# Validate username if provided
if username:
try:
username_util.validate_username(username)
normalized_username = username_util.normalize_username(username)
except ValidationError as e:
errors.append(f"Username: {e}")
# Validate phone number if provided
if phone:
if phone_util.validate_phone_number(phone):
normalized_phone = phone_util.normalize_phone_number(phone)
else:
errors.append("Invalid phone number format")
# Validate new password if provided
if new_password:
try:
password_util.validate_password(new_password)
except ValidationError as e:
errors.append(f"Password: {e}")
if errors:
return jsonify({'success': False, 'errors': errors})
# Update user profile
if username:
current_user.username = normalized_username
if phone:
current_user.us_phone_number = normalized_phone
if new_password:
current_user.password = password_util.hash_password(new_password)
db.session.commit()
return jsonify({'success': True, 'message': 'Profile updated successfully'})from functools import wraps
from flask_security import get_request_attr, naive_utcnow
def rate_limit(max_requests=10, window_seconds=60):
"""Rate limiting decorator using utilities."""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Get client IP using utility
client_ip = get_request_attr('remote_addr') or 'unknown'
# Simple in-memory rate limiting (use Redis in production)
current_time = naive_utcnow()
if not hasattr(app, 'rate_limit_store'):
app.rate_limit_store = {}
key = f"rate_limit:{client_ip}:{f.__name__}"
if key in app.rate_limit_store:
requests, window_start = app.rate_limit_store[key]
# Check if window expired
if (current_time - window_start).total_seconds() > window_seconds:
# Reset window
app.rate_limit_store[key] = (1, current_time)
elif requests >= max_requests:
# Rate limit exceeded
return jsonify({'error': 'Rate limit exceeded'}), 429
else:
# Increment counter
app.rate_limit_store[key] = (requests + 1, window_start)
else:
# First request in window
app.rate_limit_store[key] = (1, current_time)
return f(*args, **kwargs)
return decorated_function
return decorator
@app.route('/api/sensitive-endpoint')
@login_required
@rate_limit(max_requests=5, window_seconds=60)
def sensitive_endpoint():
"""Rate-limited API endpoint."""
return jsonify({'data': 'sensitive information'})Flask-Security utilities can be configured through various configuration variables:
# Email configuration
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = 'your-email@gmail.com'
app.config['MAIL_PASSWORD'] = 'your-password'
# Password validation configuration
app.config['SECURITY_PASSWORD_LENGTH_MIN'] = 8
app.config['SECURITY_PASSWORD_COMPLEXITY_CHECKER'] = 'zxcvbn'
# Token configuration
app.config['SECURITY_TOKEN_AUTHENTICATION_KEY'] = 'auth_token'
app.config['SECURITY_TOKEN_MAX_AGE'] = 86400 # 24 hours
# SMS configuration
app.config['SECURITY_SMS_SERVICE'] = 'Twilio'
app.config['SECURITY_SMS_SERVICE_CONFIG'] = {
'ACCOUNT_SID': 'your-sid',
'AUTH_TOKEN': 'your-token'
}