Exemple #1
0
def valid_name(name):
    """
    Names must be between 2 and 50 characters.
    """
    if not name:
        raise InvalidUsageError(message="Name is missing.")
    return 2 <= len(name) <= 50
Exemple #2
0
def login():
    """
    Logs a user in by parsing a POST request containing user credentials.
    User provides email/password.

    Returns: errors if data is not valid or captcha fails.
    Returns: Access token and refresh token otherwise.
    """
    r = request.get_json(force=True, silent=True)

    if not r:
        raise InvalidUsageError(
            message="Email and password must be included in the request body."
        )

    email = r.get("email", None)
    password = r.get("password", None)
    recaptcha_token = r.get("recaptchaToken", None)

    if check_email(email):
        user = db.session.query(Users).filter_by(user_email=email).one_or_none()
    else:
        raise UnauthorizedError(message="Wrong email or password. Try again.")

    if not user or not password_valid(password) or not user.check_password(password):
        raise UnauthorizedError(message="Wrong email or password. Try again.")

    # Verify captcha with Google
    secret_key = os.environ.get("RECAPTCHA_SECRET_KEY")
    data = {"secret": secret_key, "response": recaptcha_token}
    resp = requests.post(
        "https://www.google.com/recaptcha/api/siteverify", data=data
    ).json()

    # Google will return True/False in the success field, resp must be json to properly access
    if not resp["success"]:
        raise UnauthorizedError(message="Captcha did not succeed.")

    access_token = create_access_token(identity=user, fresh=True)
    refresh_token = create_refresh_token(identity=user)

    response = make_response(
        jsonify(
            {
                "message": "successfully logged in user",
                "access_token": access_token,
                "user": {
                    "first_name": user.first_name,
                    "last_name": user.last_name,
                    "email": user.user_email,
                    "user_uuid": user.user_uuid,
                    "quiz_id": user.quiz_uuid,
                },
            }
        ),
        200,
    )
    response.set_cookie("refresh_token", refresh_token, path="/refresh", httponly=True)
    return response
Exemple #3
0
def get_general_solutions():
    """
    The front-end needs general solutions list and information to serve to user when
    they click the general solutions menu button. General solutions are ordered based
    on relevance predicted from users personal values.
    """
    quiz_uuid = request.args.get("quizId")
    user_scores = None

    if quiz_uuid:
        try:
            quiz_uuid = uuid.UUID(request.args.get("quizId"))
            user_scores = get_scores_vector(quiz_uuid)
        except:
            raise InvalidUsageError(
                message=
                "Malformed request. Quiz ID provided to get solutions is not a valid UUID."
            )

    if user_scores == "Not in db":
        raise InvalidUsageError(
            message="Malformed request. Quid ID provided is not in database.")

    if user_scores:
        user_scores = [np.array(user_scores)]
        user_liberal, user_conservative = predict_radical_political(
            user_scores)
    else:
        user_liberal, user_conservative = None, None

    try:
        recommended_general_solutions = (
            SOLUTION_PROCESSOR.get_user_general_solution_nodes(
                user_liberal, user_conservative))
        climate_general_solutions = {
            "solutions": recommended_general_solutions
        }
        return jsonify(climate_general_solutions), 200

    except:
        raise CustomError(
            message=
            "An error occurred while processing the user's general solution nodes."
        )
Exemple #4
0
def post_code():
    """

    Accepts a quizId and postCode and updates the Scores object in the database
    to include the post code.

    """

    try:
        request_body = request.json
        quiz_uuid = uuid.UUID(request_body["quizId"])
        post_code = request_body["postCode"]
    except:
        raise InvalidUsageError(
            message="Unable to post postcode. Check the request parameters."
        )

    if check_post_code(post_code):
        return store_post_code(post_code, quiz_uuid)
    else:
        raise InvalidUsageError(message="The postcode provided is not valid.")
Exemple #5
0
def subscribe():
    try:
        request_body = request.json
        email = request_body["email"]
        session_uuid = request.headers.get("X-Session-Id")
    except:
        raise InvalidUsageError(
            message=
            "Unable to post subscriber information. Check the request parameters."
        )

    if not session_uuid:
        raise InvalidUsageError(
            message="Cannot post subscriber information without a session ID.")

    if check_email(email):
        return store_subscription_data(session_uuid, email)
    else:
        raise InvalidUsageError(
            message=
            "Cannot post subscriber information. Subscriber email is invalid.")
def get_feed():
    """
    The front-end needs to request personalized climate change effects that are most
    relevant to a user to display in the user's feed.
    PARAMETER (as GET)
    ------------------
    session-id : uuid4 as string
    """
    N_FEED_CARDS = 21
    try:
        quiz_uuid = uuid.UUID(request.args.get("quizId"))
    except:
        raise InvalidUsageError(
            message=
            "Malformed request. Quiz ID provided to get feed is not a valid UUID."
        )

    session_uuid = request.headers.get("X-Session-Id")

    if not session_uuid:
        raise InvalidUsageError(
            message="Cannot get feed without a session ID.")

    try:
        session_uuid = uuid.UUID(session_uuid)
    except:
        raise InvalidUsageError(
            message="Session ID used to get feed is not a valid UUID.")

    valid_session_uuid = Sessions.query.get(session_uuid)

    if valid_session_uuid:
        feed_entries = get_feed_results(quiz_uuid, N_FEED_CARDS, session_uuid)
    else:
        raise InvalidUsageError(
            message="Session ID used to get feed is not in the db.")

    return feed_entries
Exemple #7
0
def password_valid(password):
    """
    Passwords must contain at least one digit or special character.
    Passwords must be between 8 and 128 characters.
    Passwords cannot contain spaces.
    """
    if not password:
        raise InvalidUsageError(
            message="Email and password must be included in the request body."
        )

    conds = [
        lambda s: any(x.isdigit() or not x.isalnum() for x in s),
        lambda s: all(not x.isspace() for x in s),
        lambda s: 8 <= len(s) <= 128,
    ]
    return all(cond(password) for cond in conds)
def store_subscription_data(session_uuid, email):

    email_in_db = Signup.query.filter_by(signup_email=email).first()

    try:
        valid_uuid = uuid.UUID(session_uuid)
    except:
        raise InvalidUsageError(
            message="Session ID used to sign up is not a valid UUID."
        )

    valid_session_uuid = Sessions.query.get(session_uuid)

    if email_in_db:
        raise AlreadyExistsError(message="Subscriber email address")
    elif not valid_session_uuid:
        raise DatabaseError(
            message="Cannot save subscription information. Session ID not in the database."
        )
    else:
        try:
            new_subscription = Signup()
            new_subscription.signup_email = email
            new_subscription.session_uuid = session_uuid
            now = datetime.datetime.now(timezone.utc)
            new_subscription.signup_timestamp = now

            db.session.add(new_subscription)
            db.session.commit()

            response = {
                "message": "Successfully added email",
                "email": email,
                "sessionId": session_uuid,
                "datetime": now,
            }

            return response, 201
        except:
            raise DatabaseError(
                message="An error occurred while saving the subscription information to the database."
            )
def check_email(email):
    """
    Checks an email format against the RFC 5322 specification.
    """
    if not email:
        raise InvalidUsageError(
            message="Email and password must be included in the request body"
        )

    if not isinstance(email, str):
        raise UnauthorizedError(message="Wrong email or password. Try again.")

    # RFC 5322 Specification as Regex
    regex = """(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"
    (?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])
    *\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:
    (?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1
    [0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a
    \x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"""

    if re.search(regex, email):
        return True
    return False
Exemple #10
0
def get_personal_values():
    """
    Users want to know their personal values based on their Schwartz questionnaire
    results. This returns the top 3 personal values with descriptions plus all scores for a user given a quiz ID.
    """
    try:
        quiz_uuid = uuid.UUID(request.args.get("quizId"))

    except:
        raise InvalidUsageError(
            message=
            "Malformed request. Quiz ID provided to get personal values is not a valid UUID."
        )

    scores = Scores.query.filter_by(quiz_uuid=quiz_uuid).first()

    if scores:

        personal_values_categories = [
            "security",
            "conformity",
            "benevolence",
            "tradition",
            "universalism",
            "self_direction",
            "stimulation",
            "hedonism",
            "achievement",
            "power",
        ]

        scores = scores.__dict__

        # All scores and accoiated values for response
        all_scores = [{
            "personalValue": key,
            "score": scores[key]
        } for key in personal_values_categories]

        normalized_scores = normalize_scores(all_scores)

        # Top 3 personal values
        top_scores = sorted(all_scores,
                            key=lambda value: value["score"],
                            reverse=True)[:3]

        # Fetch descriptions
        try:
            file = os.path.join(os.getcwd(), "app/personal_values/static",
                                "value_descriptions.json")
            with open(file) as f:
                value_descriptions = load(f)
        except FileNotFoundError:
            return jsonify({"error": "Value descriptions file not found"}), 404

        # Add desciptions for top 3 values to retrun
        values_and_descriptions = [
            value_descriptions[score["personalValue"]] for score in top_scores
        ]

        # Build and return response
        response = {
            "personalValues": values_and_descriptions,
            "valueScores": normalized_scores,
        }
        return jsonify(response), 200

    else:
        raise DatabaseError(
            message="Cannot get personal values. Quiz ID is not in database.")
def user_scores():
    """
    User scores are used to determine which solutions are best to serve
    the user. Users also want to be able to see their score results after
    submitting the survey.

    This route checks for a POST request from the front-end
    containing a JSON object with the users scores.

    The user can answer 10 or 20 questions. If they answer 20, the scores
    are averaged between the 10 additional and 10 original questions to get
    10 corresponding value scores.

    Then to get a centered score for each value, each score value is subtracted
    from the overall average of all 10 or 20 questions.

    A quiz ID is saved with the scores in the database.

    Returns: SessionID (UUID4)
    """

    parameter = request.json

    if not parameter:
        raise InvalidUsageError(
            message="Cannot post scores. No user response provided."
        )

    responses_to_add = 10

    questions = parameter["questionResponses"]

    if len(questions["SetOne"]) != responses_to_add:
        raise InvalidUsageError(
            message="Cannot post scores. Invalid number of questions provided."
        )

    process_scores = ProcessScores(questions)
    process_scores.calculate_scores("SetOne")

    if "SetTwo" in questions:
        process_scores.calculate_scores("SetTwo")
    process_scores.center_scores()
    value_scores = process_scores.get_value_scores()

    quiz_uuid = uuid.uuid4()
    value_scores["quiz_uuid"] = quiz_uuid

    user_uuid = None
    if current_user:
        user_uuid = current_user.user_uuid

    session_uuid = request.headers.get("X-Session-Id")

    if not session_uuid:
        raise InvalidUsageError(message="Cannot post scores without a session ID.")

    try:
        session_uuid = uuid.UUID(session_uuid)
    except:
        raise InvalidUsageError(
            message="Session ID used to post scores is not a valid UUID."
        )

    valid_session_uuid = Sessions.query.get(session_uuid)

    if valid_session_uuid:
        process_scores.persist_scores(user_uuid, session_uuid)
    else:
        raise InvalidUsageError(
            message="Session ID used to save scores is not in the db."
        )

    response = {"quizId": quiz_uuid}
    return jsonify(response), 201
Exemple #12
0
def register():
    """
    Registration endpoint

    Takes a first name, last name, email, and password, validates this data and saves the user into the database.
    The user should automatically be logged in upon successful registration.
    The same email cannot be used for more than one account.
    Users will have to take the quiz before registering, meaning the quiz_uuid is linked to scores.

    Returns: Errors if any data is invalid
    Returns: Access Token and Refresh Token otherwise
    """

    r = request.get_json(force=True, silent=True)

    if not r:
        raise InvalidUsageError(
            message="Email and password must be included in the request body."
        )

    first_name = r.get("firstName", None)
    last_name = r.get("lastName", None)
    email = r.get("email", None)
    password = r.get("password", None)
    quiz_uuid = r.get("quizId", None)

    if not valid_name(first_name):
        raise InvalidUsageError(
            message="First name must be between 2 and 50 characters."
        )

    if not valid_name(last_name):
        raise InvalidUsageError(
            message="Last name must be between 2 and 50 characters."
        )

    if not quiz_uuid:
        raise InvalidUsageError(message="Quiz UUID must be included to register.")

    try:
        quiz_uuid = uuid.UUID(quiz_uuid)

    except:
        raise InvalidUsageError(message="Quiz UUID is improperly formatted.")

    if not scores_in_db(quiz_uuid):
        raise DatabaseError(message="Quiz ID is not in the db.")

    if not check_email(email):
        raise InvalidUsageError(message=f"The email {email} is invalid.")

    if not password_valid(password):
        raise InvalidUsageError(
            message="Password does not fit the requirements. "
            "Password must be between 8-128 characters, contain at least one number or special character, and cannot contain any spaces."
        )

    user = Users.find_by_email(email)
    if user:
        raise UnauthorizedError(message="Email already registered")
    else:
        user = add_user_to_db(first_name, last_name, email, password, quiz_uuid)

    access_token = create_access_token(identity=user, fresh=True)
    refresh_token = create_refresh_token(identity=user)
    response = make_response(
        jsonify(
            {
                "message": "Successfully created user",
                "access_token": access_token,
                "user": {
                    "first_name": user.first_name,
                    "last_name": user.last_name,
                    "email": user.user_email,
                    "user_uuid": user.user_uuid,
                    "quiz_id": user.quiz_uuid,
                },
            }
        ),
        201,
    )
    response.set_cookie("refresh_token", refresh_token, path="/refresh", httponly=True)
    return response