added password reset
This commit is contained in:
@@ -20,6 +20,7 @@ auth_api = Blueprint('auth_api', __name__)
|
||||
UserQuery = Query()
|
||||
mail = Mail()
|
||||
TOKEN_EXPIRY_MINUTES = 60*4
|
||||
RESET_PASSWORD_TOKEN_EXPIRY_MINUTES = 10
|
||||
|
||||
|
||||
def send_verification_email(to_email, token):
|
||||
@@ -32,6 +33,16 @@ def send_verification_email(to_email, token):
|
||||
)
|
||||
mail.send(msg)
|
||||
|
||||
def send_reset_password_email(to_email, token):
|
||||
reset_url = f"{current_app.config['FRONTEND_URL']}/auth/reset-password?token={token}"
|
||||
msg = Message(
|
||||
subject="Reset your password",
|
||||
recipients=[to_email],
|
||||
body=f"Click to reset your password: {reset_url}",
|
||||
sender=current_app.config['MAIL_DEFAULT_SENDER']
|
||||
)
|
||||
mail.send(msg)
|
||||
|
||||
@auth_api.route('/signup', methods=['POST'])
|
||||
def signup():
|
||||
data = request.get_json()
|
||||
@@ -177,3 +188,75 @@ def logout():
|
||||
resp = jsonify({'message': 'Logged out'})
|
||||
resp.set_cookie('token', '', expires=0, httponly=True, secure=True, samesite='Strict')
|
||||
return resp, 200
|
||||
|
||||
@auth_api.route('/request-password-reset', methods=['POST'])
|
||||
def request_password_reset():
|
||||
data = request.get_json()
|
||||
email = data.get('email')
|
||||
# Always return success for privacy
|
||||
success_msg = 'If this email is registered, you will receive a password reset link shortly.'
|
||||
|
||||
if not email:
|
||||
return jsonify({'error': 'Missing email', 'code': MISSING_EMAIL}), 400
|
||||
|
||||
user = users_db.get(UserQuery.email == email)
|
||||
if user:
|
||||
token = secrets.token_urlsafe(32)
|
||||
now_iso = datetime.utcnow().isoformat()
|
||||
users_db.update({
|
||||
'reset_token': token,
|
||||
'reset_token_created': now_iso
|
||||
}, UserQuery.email == email)
|
||||
send_reset_password_email(email, token)
|
||||
|
||||
return jsonify({'message': success_msg}), 200
|
||||
|
||||
@auth_api.route('/validate-reset-token', methods=['GET'])
|
||||
def validate_reset_token():
|
||||
token = request.args.get('token')
|
||||
if not token:
|
||||
return jsonify({'error': 'Missing token', 'code': MISSING_TOKEN}), 400
|
||||
|
||||
user = users_db.get(UserQuery.reset_token == token)
|
||||
if not user:
|
||||
return jsonify({'error': 'Invalid token', 'code': INVALID_TOKEN}), 400
|
||||
|
||||
created_str = user.get('reset_token_created')
|
||||
if not created_str:
|
||||
return jsonify({'error': 'Token timestamp missing', 'code': TOKEN_TIMESTAMP_MISSING}), 400
|
||||
|
||||
created_dt = datetime.fromisoformat(created_str).replace(tzinfo=timezone.utc)
|
||||
if datetime.now(timezone.utc) - created_dt > timedelta(minutes=RESET_PASSWORD_TOKEN_EXPIRY_MINUTES):
|
||||
return jsonify({'error': 'Token expired', 'code': TOKEN_EXPIRED}), 400
|
||||
|
||||
return jsonify({'message': 'Token is valid'}), 200
|
||||
|
||||
@auth_api.route('/reset-password', methods=['POST'])
|
||||
def reset_password():
|
||||
data = request.get_json()
|
||||
token = data.get('token')
|
||||
new_password = data.get('password')
|
||||
|
||||
if not token or not new_password:
|
||||
return jsonify({'error': 'Missing token or password'}), 400
|
||||
|
||||
user = users_db.get(UserQuery.reset_token == token)
|
||||
if not user:
|
||||
return jsonify({'error': 'Invalid token', 'code': INVALID_TOKEN}), 400
|
||||
|
||||
created_str = user.get('reset_token_created')
|
||||
if not created_str:
|
||||
return jsonify({'error': 'Token timestamp missing', 'code': TOKEN_TIMESTAMP_MISSING}), 400
|
||||
|
||||
created_dt = datetime.fromisoformat(created_str).replace(tzinfo=timezone.utc)
|
||||
if datetime.now(timezone.utc) - created_dt > timedelta(minutes=RESET_PASSWORD_TOKEN_EXPIRY_MINUTES):
|
||||
return jsonify({'error': 'Token expired', 'code': TOKEN_EXPIRED}), 400
|
||||
|
||||
users_db.update({
|
||||
'password': new_password, # Hash in production!
|
||||
'reset_token': None,
|
||||
'reset_token_created': None
|
||||
}, UserQuery.reset_token == token)
|
||||
|
||||
return jsonify({'message': 'Password has been reset'}), 200
|
||||
|
||||
|
||||
Reference in New Issue
Block a user