Пример #1
0
def send_signup_email(flow):
    logger.info(f"Sending signup email to {flow.email=}:")

    # whether we've sent an email at all yet
    email_sent_before = flow.email_sent
    if flow.email_verified:
        # we just send a link to continue, not a verification link
        signup_link = urls.signup_link(token=flow.flow_token)
    elif flow.email_token and flow.token_is_valid:
        # if the verification email was sent and still is not expired, just resend the verification email
        signup_link = urls.signup_link(token=flow.email_token)
    else:
        # otherwise send a fresh email with new token
        token = urlsafe_secure_token()
        flow.email_verified = False
        flow.email_token = token
        flow.email_token_expiry = now() + SIGNUP_EMAIL_TOKEN_VALIDITY
        signup_link = urls.signup_link(token=flow.email_token)

    flow.email_sent = True

    logger.info(f"Link is: {signup_link}")
    template = "signup_verify" if not email_sent_before else "signup_continue"
    email.enqueue_email_from_template(flow.email,
                                      template,
                                      template_args={
                                          "flow": flow,
                                          "signup_link": signup_link
                                      })
Пример #2
0
def test_purge_password_reset_tokens(db):
    user, api_token = generate_user()

    with session_scope() as session:
        password_reset_token = PasswordResetToken(token=urlsafe_secure_token(),
                                                  user=user,
                                                  expiry=now())
        session.add(password_reset_token)
        assert session.execute(
            select(func.count()).select_from(
                PasswordResetToken)).scalar_one() == 1

    queue_job(BackgroundJobType.purge_password_reset_tokens, empty_pb2.Empty())
    process_job()

    with session_scope() as session:
        assert session.execute(
            select(func.count()).select_from(
                PasswordResetToken)).scalar_one() == 0

    with session_scope() as session:
        assert (session.execute(
            select(func.count()).select_from(BackgroundJob).where(
                BackgroundJob.state ==
                BackgroundJobState.completed)).scalar_one() == 1)
        assert (session.execute(
            select(func.count()).select_from(BackgroundJob).where(
                BackgroundJob.state != BackgroundJobState.completed)).
                scalar_one() == 0)
Пример #3
0
    def ConfirmDeleteAccount(self, request, context):
        """
        Confirm account deletion using account delete token
        """
        with session_scope() as session:
            res = session.execute(
                select(User, AccountDeletionToken).join(
                    AccountDeletionToken,
                    AccountDeletionToken.user_id == User.id).where(
                        AccountDeletionToken.token == request.token).where(
                            AccountDeletionToken.is_valid)).one_or_none()

            if not res:
                context.abort(grpc.StatusCode.NOT_FOUND, errors.INVALID_TOKEN)

            user, account_deletion_token = res

            session.execute(
                delete(AccountDeletionToken).where(
                    AccountDeletionToken.user_id == user.id))

            undelete_days = 7
            user.is_deleted = True
            user.undelete_until = now() + timedelta(days=undelete_days)
            user.undelete_token = urlsafe_secure_token()

            send_account_deletion_successful_email(user, undelete_days)

        return empty_pb2.Empty()
Пример #4
0
def test_purge_account_deletion_tokens(db):
    user, api_token = generate_user()
    user2, api_token2 = generate_user()
    user3, api_token3 = generate_user()

    with session_scope() as session:
        """
        3 cases:
        1) Token is valid
        2) Token expired but account retrievable
        3) Account is irretrievable (and expired)
        """
        account_deletion_tokens = [
            AccountDeletionToken(token=urlsafe_secure_token(),
                                 user=user,
                                 expiry=now() - timedelta(hours=2)),
            AccountDeletionToken(token=urlsafe_secure_token(),
                                 user=user2,
                                 expiry=now()),
            AccountDeletionToken(token=urlsafe_secure_token(),
                                 user=user3,
                                 expiry=now() + timedelta(hours=5)),
        ]
        for token in account_deletion_tokens:
            session.add(token)
        assert session.execute(
            select(func.count()).select_from(
                AccountDeletionToken)).scalar_one() == 3

    queue_job(BackgroundJobType.purge_account_deletion_tokens,
              empty_pb2.Empty())
    process_job()

    with session_scope() as session:
        assert session.execute(
            select(func.count()).select_from(
                AccountDeletionToken)).scalar_one() == 1

    with session_scope() as session:
        assert (session.execute(
            select(func.count()).select_from(BackgroundJob).where(
                BackgroundJob.state ==
                BackgroundJobState.completed)).scalar_one() == 1)
        assert (session.execute(
            select(func.count()).select_from(BackgroundJob).where(
                BackgroundJob.state != BackgroundJobState.completed)).
                scalar_one() == 0)
Пример #5
0
def new_login_token(session, user, hours=2):
    """
    Make a login token that's valid for `hours` hours

    Returns token and expiry text
    """
    token = urlsafe_secure_token()
    login_token = LoginToken(token=token, user=user, expiry=datetime.datetime.utcnow() + datetime.timedelta(hours=hours))
    session.add(login_token)
    session.commit()
    return login_token, f"{hours} hours"
Пример #6
0
def new_signup_token(session, email, hours=2):
    """
    Make a signup token that's valid for `hours` hours

    Returns token and expiry text
    """
    token = urlsafe_secure_token()
    signup_token = SignupToken(token=token, email=email, expiry=datetime.datetime.utcnow() + datetime.timedelta(hours=hours))
    session.add(signup_token)
    session.commit()
    return signup_token, f"{hours} hours"
Пример #7
0
def new_password_reset_token(session, user, hours=2):
    """
    Make a password reset token that's valid for `hours` hours

    Returns token and expiry text
    """
    token = urlsafe_secure_token()
    password_reset_token = PasswordResetToken(token=token, user=user, expiry=now() + timedelta(hours=hours))
    session.add(password_reset_token)
    session.commit()
    return password_reset_token, f"{hours} hours"
Пример #8
0
def set_email_change_token(session, user, hours=2):
    """
    Make a new email change token that's valid for `hours` hours for this user

    Note: does not call session.commit()

    Returns token and expiry text
    """
    token = urlsafe_secure_token()
    user.new_email_token = token
    user.new_email_token_created = now()
    user.new_email_token_expiry = now() + datetime.timedelta(hours=hours)
    return token, f"{hours} hours"
Пример #9
0
    def _create_session(self, session, user):
        """
        Creates a session for the given user and returns the bearer token.

        You need to give an active DB session as nested sessions don't really work here due to the active User object.
        """
        token = urlsafe_secure_token()

        user_session = UserSession(
            user=user,
            token=token
        )

        session.add(user_session)
        session.commit()

        logging.debug(f"Handing out {token=} to {user=}")
        return token
Пример #10
0
def send_account_deletion_confirmation_email(user):
    logger.info(f"Sending account deletion confirmation email to {user=}.")
    logger.info(f"Email for {user.username=} sent to {user.email}.")
    token = AccountDeletionToken(token=urlsafe_secure_token(),
                                 user=user,
                                 expiry=now() + timedelta(hours=2))
    deletion_link = urls.delete_account_link(
        account_deletion_token=token.token)
    email.enqueue_email_from_template(
        user.email,
        "account_deletion_confirmation",
        template_args={
            "user": user,
            "deletion_link": deletion_link
        },
    )

    return token
Пример #11
0
def send_login_email(session, user):
    login_token = LoginToken(token=urlsafe_secure_token(),
                             user=user,
                             expiry=now() + timedelta(hours=2))
    session.add(login_token)

    logger.info(f"Sending login email to {user=}:")
    logger.info(f"Email for {user.username=} to {user.email=}")
    logger.info(f"Token: {login_token=} ({login_token.created=}")
    login_link = urls.login_link(login_token=login_token.token)
    logger.info(f"Link is: {login_link}")
    email.enqueue_email_from_template(user.email,
                                      "login",
                                      template_args={
                                          "user": user,
                                          "login_link": login_link
                                      })

    return login_token
Пример #12
0
def send_password_reset_email(session, user):
    password_reset_token = PasswordResetToken(token=urlsafe_secure_token(),
                                              user=user,
                                              expiry=now() +
                                              timedelta(hours=2))
    session.add(password_reset_token)

    logger.info(f"Sending password reset email to {user=}:")
    password_reset_link = urls.password_reset_link(
        password_reset_token=password_reset_token.token)
    logger.info(f"Link is: {password_reset_link}")
    email.enqueue_email_from_template(user.email,
                                      "password_reset",
                                      template_args={
                                          "user":
                                          user,
                                          "password_reset_link":
                                          password_reset_link
                                      })

    return password_reset_token
Пример #13
0
    def _create_session(self, context, session, user):
        """
        Creates a session for the given user and returns the bearer token.

        You need to give an active DB session as nested sessions don't
        really work here due to the active User object.

        Will abort the API calling context if the user is banned from logging in.
        """
        if user.is_banned:
            context.abort(grpc.StatusCode.FAILED_PRECONDITION, errors.ACCOUNT_SUSPENDED)

        token = urlsafe_secure_token()

        user_session = UserSession(user=user, token=token)

        session.add(user_session)
        session.commit()

        logger.debug(f"Handing out {token=} to {user=}")
        return token
Пример #14
0
def test_email_changed_confirmation_sent_to_new_email(db):
    confirmation_token = urlsafe_secure_token()
    user, user_token = generate_user()
    user.new_email = f"{random_hex(12)}@couchers.org.invalid"
    user.new_email_token = confirmation_token
    with patch("couchers.email.queue_email") as mock:
        send_email_changed_confirmation_to_new_email(user)

    assert mock.call_count == 1
    (sender_name, sender_email, recipient, subject, plain, html), _ = mock.call_args
    assert "new email" in subject
    assert recipient == user.new_email
    assert user.name in plain
    assert user.name in html
    assert user.email in plain
    assert user.email in html
    assert "via a similar email sent to your old email address" in plain
    assert "via a similar email sent to your old email address" in html
    assert f"{config['BASE_URL']}/confirm-email?token={confirmation_token}" in plain
    assert f"{config['BASE_URL']}/confirm-email?token={confirmation_token}" in html
    assert "*****@*****.**" in plain
    assert "*****@*****.**" in html
Пример #15
0
    def ChangeEmail(self, request, context):
        """
        Change the user's email address.

        If the user has a password, a notification is sent to the old email, and a confirmation is sent to the new one.

        Otherwise they need to confirm twice, via an email sent to each of their old and new emails.

        In all confirmation emails, the user must click on the confirmation link.
        """
        with session_scope() as session:
            user = session.execute(
                select(User).where(User.id == context.user_id)).scalar_one()

            # check password first
            _check_password(user, "password", request, context)

            # not a valid email
            if not is_valid_email(request.new_email):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.INVALID_EMAIL)

            # email already in use (possibly by this user)
            if session.execute(
                    select(User).where(
                        User.email == request.new_email)).scalar_one_or_none():
                context.abort(grpc.StatusCode.INVALID_ARGUMENT,
                              errors.INVALID_EMAIL)

            user.new_email = request.new_email
            user.new_email_token = urlsafe_secure_token()
            user.new_email_token_created = now()
            user.new_email_token_expiry = now() + timedelta(hours=2)
            user.need_to_confirm_via_new_email = True

            if user.has_password:
                user.old_email_token = None
                user.old_email_token_created = None
                user.old_email_token_expiry = None
                user.need_to_confirm_via_old_email = False
                send_email_changed_notification_email(user)
                send_email_changed_confirmation_to_new_email(user)

                notify(
                    user_id=user.id,
                    topic="email_address",
                    key="",
                    action="change",
                    icon="wrench",
                    title=f"Your email was changed",
                    link=urls.account_settings_link(),
                )
            else:
                user.old_email_token = urlsafe_secure_token()
                user.old_email_token_created = now()
                user.old_email_token_expiry = now() + timedelta(hours=2)
                user.need_to_confirm_via_old_email = True
                send_email_changed_confirmation_to_old_email(user)
                send_email_changed_confirmation_to_new_email(user)

        # session autocommit
        return empty_pb2.Empty()