05/09 Update: security enhanced
This commit is contained in:
+22
-25
@@ -2,7 +2,7 @@ import re
|
||||
import time
|
||||
|
||||
from flask import Blueprint, request, jsonify, g, session
|
||||
from app import db, limiter
|
||||
from app import db, limiter, client_ip
|
||||
from app.models.user import User
|
||||
from app.models.audit_log import AuditLog
|
||||
from app.services.auth_service import (
|
||||
@@ -26,11 +26,6 @@ auth_bp = Blueprint('auth', __name__)
|
||||
EMAIL_RE = re.compile(r'^[^@\s]+@[^@\s]+\.[^@\s]+$')
|
||||
|
||||
|
||||
def _client_ip():
|
||||
"""Return the best-effort client IP from the request context."""
|
||||
return request.headers.get('X-Forwarded-For', request.remote_addr or '').split(',')[0].strip()
|
||||
|
||||
|
||||
@auth_bp.route('/register', methods=['POST'])
|
||||
@limiter.limit('10 per minute')
|
||||
def register():
|
||||
@@ -41,6 +36,8 @@ def register():
|
||||
|
||||
if not email or not EMAIL_RE.match(email):
|
||||
return jsonify({'error': 'Invalid email address'}), 400
|
||||
if len(email) > 254:
|
||||
return jsonify({'error': 'Email address is too long'}), 400
|
||||
if not auth_hash:
|
||||
return jsonify({'error': 'auth_hash is required'}), 400
|
||||
if not enc_key_salt:
|
||||
@@ -60,7 +57,7 @@ def register():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail=f'New account registered: {email}',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
@@ -103,7 +100,7 @@ def login():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail=f'Login blocked — account locked for {remaining} more minute(s)',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
@@ -129,7 +126,7 @@ def login():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail=f'Account locked for {LOCKOUT_MINUTES} minutes after {user.failed_login_count} failed attempts',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
else:
|
||||
AuditLog.log(
|
||||
@@ -138,7 +135,7 @@ def login():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail=f'Failed login attempt — invalid password ({user.failed_login_count}/{MAX_FAILED_LOGINS})',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
except OperationalError:
|
||||
@@ -149,7 +146,7 @@ def login():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail='Failed login attempt — invalid password',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
return jsonify({'error': 'Invalid email or password'}), 401
|
||||
@@ -169,7 +166,7 @@ def login():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail=f'Successful login{" (MFA pending)" if user.totp_enabled else ""}',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
@@ -298,7 +295,7 @@ def mfa_enable():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail='TOTP two-factor authentication enabled; backup codes generated',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
@@ -349,7 +346,7 @@ def mfa_disable():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail='TOTP two-factor authentication disabled; backup codes cleared',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
@@ -397,7 +394,7 @@ def mfa_verify():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail=f'MFA backup code used; {len(remaining)} code(s) remaining',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
|
||||
if not verified:
|
||||
@@ -412,7 +409,7 @@ def mfa_verify():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail='MFA verification successful — session tokens issued',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
@@ -469,7 +466,7 @@ def mfa_backup_codes_regenerate():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail='MFA backup codes regenerated — previous codes invalidated',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
@@ -572,7 +569,7 @@ def change_password():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail='Password change rejected — current password incorrect',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
return jsonify({'error': 'Current password is incorrect'}), 401
|
||||
@@ -619,7 +616,7 @@ def change_password():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail=f'Master password changed; {len(existing)} vault item(s) re-encrypted; recovery code cleared',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
@@ -653,7 +650,7 @@ def delete_account():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail='Account deletion rejected — password incorrect',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
return jsonify({'error': 'Incorrect password'}), 401
|
||||
@@ -668,7 +665,7 @@ def delete_account():
|
||||
resource_type='user',
|
||||
resource_id=user_id,
|
||||
detail=f'Account permanently deleted: {user_email}',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
@@ -712,7 +709,7 @@ def recovery_setup():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail='Account recovery code configured',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
@@ -775,7 +772,7 @@ def recover_account():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail='Recovery attempt failed — incorrect recovery proof',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
return jsonify({'error': 'Invalid recovery code'}), 401
|
||||
@@ -824,7 +821,7 @@ def recover_account():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail=f'Account recovered; {len(existing)} vault item(s) re-encrypted; recovery code consumed',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
@@ -926,7 +923,7 @@ def recovery_items():
|
||||
resource_type='user',
|
||||
resource_id=user.id,
|
||||
detail='Recovery items request denied — incorrect recovery proof',
|
||||
ip_address=_client_ip(),
|
||||
ip_address=client_ip(),
|
||||
)
|
||||
db.session.commit()
|
||||
return jsonify({'error': 'Invalid recovery proof'}), 401
|
||||
|
||||
Reference in New Issue
Block a user