def request_password_reset(): data = request.json if not data: return abort(make_response(jsonify(message="Missing payload"), 400)) email = data.get("email", "").lstrip().rstrip().lower() if not email or not is_email(email): return abort( make_response(jsonify(message="Missing or bad email"), 400)) existing = users.get_by_email(email) # we are not leaking used or unused e-mails, that's why this responds 202 if not existing or existing["activated"] is None: return "", 202 local = local_users.get_local_user(existing["uuid"]) if not local: return abort( make_response(jsonify(message="User does not have a password"), 400)) pwd_reset_key = guid.uuid4() pwd_reset_key_expires = datetime.now().astimezone() + timedelta(hours=1) local_users.update_local_user( user_uuid=existing["uuid"], pwd_reset_key_hash=hashlib.sha256( str(pwd_reset_key).encode("utf-8")).hexdigest(), pwd_reset_key_expires=pwd_reset_key_expires, ) send_mail.delay( [email], "PASSWORD_RESET_LINK", { "pwd_reset_key": pwd_reset_key, "pwd_reset_key_expires": int(pwd_reset_key_expires.timestamp()), "email": email, }, ) return "", 202
def reset_password(): data = request.json if not data: return abort(make_response(jsonify(message="Missing payload"), 400)) email = data.get("email", "").lstrip().rstrip().lower() pwd_reset_key = data.get("pwd_reset_key", None) password = data.get("password", None) password_confirmation = data.get("password_confirmation", None) try: validate_email(email, check_deliverability=False) except EmailNotValidError as e: return abort( make_response(jsonify(message=f"Email not valid: {e}"), 400)) if not password: return abort(make_response(jsonify(message="Missing password"), 400)) if not pwd_reset_key: return abort(make_response(jsonify(message="Missing reset key"), 400)) if password != password_confirmation: return abort(make_response(jsonify(message="Password mismatch"), 400)) existing = users.get_by_email(email) if not existing: return abort(make_response(jsonify(message="Cannot reset"), 400)) local = local_users.get_local_user(existing["uuid"]) if not local: return abort( make_response(jsonify(message="User does not have a password"), 400)) if (hashlib.sha256(str(pwd_reset_key).encode("utf-8")).hexdigest() != local["pwd_reset_key_hash"]): return abort(make_response(jsonify(message="Cannot reset"), 400)) if local["pwd_reset_key_expires"] < datetime.now().astimezone(): return abort(make_response(jsonify(message="Reset key expired"), 400)) pwd_hash = bcrypt.hashpw(password.encode("utf8"), bcrypt.gensalt()) local_users.update_local_user( user_uuid=existing["uuid"], pwd_hash=pwd_hash.decode("utf8"), pwd_reset_key_hash=None, pwd_reset_key_expires=None, force_pwd_change=False, ) send_mail.delay( [email], "PASSWORD_RESET_CONFIRMATION", { "email": email, }, ) return "", 204
def change_password(): data = request.json if not data: return abort(make_response("", 400)) password = data.get("password", None) new_password = data.get("new_password", None) new_password_confirmation = data.get("new_password_confirmation", None) if (not password or not new_password or not new_password_confirmation or new_password != new_password_confirmation): return abort(make_response("", 400)) user = get_current_user() if not user: return abort(make_response("", 401)) local = local_users.get_local_user(user["uuid"]) if not local: return abort(make_response("", 401)) if not bcrypt.checkpw(password.encode("utf8"), local["pwd_hash"].encode("utf8")): return abort(make_response("", 401)) pwd_hash = bcrypt.hashpw(new_password.encode("utf8"), bcrypt.gensalt()) local_users.update_local_user(pwd_hash=pwd_hash.decode("utf8"), force_pwd_change=False, user_uuid=user["uuid"]) userdata = dict(user) response = jsonify({ "identity": userdata.get("uuid", None), "role": userdata.get("role", "user"), "username": userdata.get("username"), "force_pwd_change": userdata.get("force_pwd_change", False), "fresh": True, "expires_on": datetime.now() + ACCESS_TOKEN_EXPIRES_AFTER, }) access_token = create_access_token( identity=userdata, fresh=True, expires_delta=ACCESS_TOKEN_EXPIRES_AFTER) set_access_cookies(response, access_token) return response, 200
def authenticate_base(include_refresh_token): data = request.json if not data: return abort(make_response(jsonify(message="Missing payload"), 400)) username = data.get("username", None) password = data.get("password", None) if not username or not password: raise BadRequest("Missing username or password in request body.") user = users.get_by_email(username.lstrip().rstrip().lower()) # fallback to legacy username login if not user: user = users.get_by_username(username) if not user: raise Unauthorized("Invalid credentials.") if user["suspended"]: raise Unauthorized("Account suspended") local = local_users.get_local_user(user["uuid"]) if not local: raise Unauthorized("Invalid credentials.") if not bcrypt.checkpw(password.encode("utf8"), local["pwd_hash"].encode("utf8")): raise Unauthorized("Invalid credentials.") # drop reset pwd key if any if local["pwd_reset_key_hash"]: local_users.update_local_user(uuid=user["uuid"], pwd_reset_key_hash=None, pwd_reset_key_expires=None) userdata = dict(user) userdata.update(local) response = jsonify(get_user_identity(userdata, True)) access_token = create_access_token( identity=userdata, fresh=True, expires_delta=ACCESS_TOKEN_EXPIRES_AFTER) set_access_cookies(response, access_token) if include_refresh_token: refresh_token = create_refresh_token( identity=userdata, expires_delta=REFRESH_TOKEN_EXPIRES_AFTER) set_refresh_cookies(response, refresh_token, 7 * 24 * 60 * 60) return response, 200
def change_password(): data = request.json if not data: return abort(make_response(jsonify(message="Missing payload"), 400)) password = data.get("password", None) new_password = data.get("new_password", None) new_password_confirmation = data.get("new_password_confirmation", None) if (not password or not new_password or not new_password_confirmation or new_password != new_password_confirmation): return abort(make_response(jsonify(message="Password issues"), 400)) user = get_current_user() if not user: return abort(make_response(jsonify(message="Unauthorized"), 401)) local = local_users.get_local_user(user["uuid"]) if not local: return abort(make_response(jsonify(message="Unauthorized"), 401)) if not bcrypt.checkpw(password.encode("utf8"), local["pwd_hash"].encode("utf8")): return abort(make_response(jsonify(message="Unauthorized"), 401)) pwd_hash = bcrypt.hashpw(new_password.encode("utf8"), bcrypt.gensalt()) local_users.update_local_user(pwd_hash=pwd_hash.decode("utf8"), force_pwd_change=False, user_uuid=user["uuid"]) userdata = dict(user) response = jsonify(get_user_identity(userdata, True)) access_token = create_access_token( identity=userdata, fresh=True, expires_delta=ACCESS_TOKEN_EXPIRES_AFTER) set_access_cookies(response, access_token) return response, 200