Exemplo n.º 1
0
    def Authenticate(self, request, context):
        """
        Authenticates a classic password based login request.

        request.user can be any of id/username/email
        """
        logging.debug(f"Logging in with {request.user=}, password=*******")
        with session_scope(self._Session) as session:
            user = get_user_by_field(session, request.user)
            if user:
                logging.debug(f"Found user")
                if not user.hashed_password:
                    logging.debug(f"User doesn't have a password!")
                    context.abort(grpc.StatusCode.FAILED_PRECONDITION, "User does not have a password")
                if verify_password(user.hashed_password, request.password):
                    logging.debug(f"Right password")
                    # correct password
                    token = self._create_session(session, user)
                    return auth_pb2.AuthRes(token=token)
                else:
                    logging.debug(f"Wrong password")
                    # wrong password
                    context.abort(grpc.StatusCode.UNAUTHENTICATED, "Invalid username or password")
            else: # user not found
                logging.debug(f"Didn't find user")
                # do about as much work as if the user was found, reduces timing based username enumeration attacks
                hash_password(request.password)
                context.abort(grpc.StatusCode.UNAUTHENTICATED, "Invalid username or password")
Exemplo n.º 2
0
    def Authenticate(self, request, context):
        """
        Authenticates a classic password based login request.

        request.user can be any of id/username/email
        """
        logger.debug(f"Logging in with {request.user=}, password=*******")
        with session_scope() as session:
            user = get_user_by_field(session, request.user)
            if user:
                logger.debug(f"Found user")
                if not user.hashed_password:
                    logger.debug(f"User doesn't have a password!")
                    context.abort(grpc.StatusCode.FAILED_PRECONDITION, errors.NO_PASSWORD)
                if verify_password(user.hashed_password, request.password):
                    logger.debug(f"Right password")
                    # correct password
                    token, expiry = self._create_session(context, session, user, request.remember_device)
                    context.send_initial_metadata(
                        [
                            ("set-cookie", create_session_cookie(token, expiry)),
                        ]
                    )
                    return auth_pb2.AuthRes(jailed=user.is_jailed)
                else:
                    logger.debug(f"Wrong password")
                    # wrong password
                    context.abort(grpc.StatusCode.NOT_FOUND, errors.INVALID_USERNAME_OR_PASSWORD)
            else:  # user not found
                logger.debug(f"Didn't find user")
                # do about as much work as if the user was found, reduces timing based username enumeration attacks
                hash_password(request.password)
                context.abort(grpc.StatusCode.NOT_FOUND, errors.INVALID_USERNAME_OR_PASSWORD)
Exemplo n.º 3
0
    def CompleteTokenLogin(self, request, context):
        """
        Second step of email-based login.

        Validates the given LoginToken (sent in email), creates a new session and returns bearer token.

        Or fails with grpc.NOT_FOUND if LoginToken is invalid.
        """
        with session_scope() as session:
            res = (
                session.query(LoginToken, User)
                .join(User, User.id == LoginToken.user_id)
                .filter(LoginToken.token == request.login_token)
                .filter(LoginToken.is_valid)
                .one_or_none()
            )
            if res:
                login_token, user = res

                # delete the login token so it can't be reused
                session.delete(login_token)
                session.commit()

                # create a session
                token, expiry = self._create_session(context, session, user, False)
                context.send_initial_metadata(
                    [
                        ("set-cookie", create_session_cookie(token, expiry)),
                    ]
                )
                return auth_pb2.AuthRes(jailed=user.is_jailed)
            else:
                context.abort(grpc.StatusCode.NOT_FOUND, errors.INVALID_TOKEN)
Exemplo n.º 4
0
    def CompleteTokenLogin(self, request, context):
        """
        Second step of email-based login.

        Validates the given LoginToken (sent in email), creates a new session and returns bearer token.

        Or fails with grpc.UNAUTHENTICATED if LoginToken is invalid.
        """
        with session_scope(self._Session) as session:
            res = (
                session.query(LoginToken, User)
                .join(User, User.id == LoginToken.user_id)
                .filter(LoginToken.token == request.login_token)
                .filter(LoginToken.is_valid)
                .one_or_none()
            )
            if res:
                login_token, user = res
                # this is the bearer token
                token = self._create_session(context, session, user=user)
                # delete the login token so it can't be reused
                session.delete(login_token)
                session.commit()
                return auth_pb2.AuthRes(token=token, jailed=user.is_jailed)
            else:
                context.abort(grpc.StatusCode.UNAUTHENTICATED, errors.INVALID_TOKEN)
Exemplo n.º 5
0
    def CompleteSignup(self, request, context):
        """
        Completes user sign up by creating the user in question, then logs them in.

        TODO: nice error handling for dupe username/email?
        """
        with session_scope(self._Session) as session:
            signup_token = (
                session.query(SignupToken)
                .filter(SignupToken.token == request.signup_token)
                .filter(SignupToken.is_valid)
                .one_or_none()
            )
            if not signup_token:
                context.abort(grpc.StatusCode.NOT_FOUND, errors.INVALID_TOKEN)

            # should be in YYYY-MM-DD format
            try:
                birthdate = datetime.fromisoformat(request.birthdate)
            except ValueError:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_BIRTHDATE)

            # check email again
            if not is_valid_email(signup_token.email):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_EMAIL)

            # check username validity
            if not is_valid_username(request.username):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_USERNAME)

            # check name validity
            if not is_valid_name(request.name):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_NAME)

            if not request.hosting_status:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.HOSTING_STATUS_REQUIRED)

            if not self._username_available(request.username):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.USERNAME_NOT_AVAILABLE)

            user = User(
                email=signup_token.email,
                username=request.username,
                name=request.name,
                city=request.city,
                gender=request.gender,
                birthdate=birthdate,
                hosting_status=hostingstatus2sql[request.hosting_status],
            )

            # happens in same transaction
            session.delete(signup_token)

            # enforces email/username uniqueness
            session.add(user)
            session.commit()

            token = self._create_session(context, session, user)

            return auth_pb2.AuthRes(token=token, jailed=user.is_jailed)
Exemplo n.º 6
0
    def CompleteSignup(self, request, context):
        """
        Completes user sign up by creating the user in question, then logs them in.

        TODO: nice error handling for dupe username/email?
        """
        with session_scope(self._Session) as session:
            signup_token = session.query(SignupToken) \
                .filter(SignupToken.token == request.signup_token) \
                .filter(SignupToken.created <= func.now()) \
                .filter(SignupToken.expiry >= func.now()) \
                .one_or_none()
            if not signup_token:
                context.abort(grpc.StatusCode.NOT_FOUND, "Invalid token.")

            # should be in YYYY/MM/DD format, will raise exception if can't parse
            birthdate = datetime.fromisoformat(request.birthdate)

            # check email again
            if not is_valid_email(signup_token.email):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, "Invalid email")

            # check username validity
            if not is_valid_username(request.username):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, "Invalid username")

            user = User(
                email=signup_token.email,
                username=request.username,
                name=request.name,
                city=request.city,
                gender=request.gender,
                birthdate=birthdate
            )

            # happens in same transaction
            session.delete(signup_token)

            # enforces email/username uniqueness
            session.add(user)
            session.commit()

            token = self._create_session(session, user)

            return auth_pb2.AuthRes(token=token)
Exemplo n.º 7
0
    def CompleteTokenLogin(self, request, context):
        """
        Second step of email-based login.

        Validates the given LoginToken (sent in email), creates a new session and returns bearer token.

        Or fails with grpc.UNAUTHENTICATED if LoginToken is invalid.
        """
        with session_scope(self._Session) as session:
            login_token = session.query(LoginToken) \
                .filter(LoginToken.token == request.login_token) \
                .filter(LoginToken.created <= func.now()) \
                .filter(LoginToken.expiry >= func.now()) \
                .one_or_none()
            if login_token:
                # this is the bearer token
                token = self._create_session(session, user=login_token.user)
                return auth_pb2.AuthRes(token=token)
            else:
                context.abort(grpc.StatusCode.UNAUTHENTICATED, "Invalid token.")
Exemplo n.º 8
0
    def CompleteSignup(self, request, context):
        """
        Completes user sign up by creating the user in question, then logs them in.

        TODO: nice error handling for dupe username/email?
        """
        with session_scope() as session:
            signup_token = (
                session.query(SignupToken)
                .filter(SignupToken.token == request.signup_token)
                .filter(SignupToken.is_valid)
                .one_or_none()
            )
            if not signup_token:
                context.abort(grpc.StatusCode.NOT_FOUND, errors.INVALID_TOKEN)

            # check birthdate validity (YYYY-MM-DD format and in the past)
            try:
                birthdate = datetime.fromisoformat(request.birthdate)
            except ValueError:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_BIRTHDATE)
            if pytz.UTC.localize(birthdate) >= now():
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_BIRTHDATE)

            # check email again
            if not is_valid_email(signup_token.email):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_EMAIL)

            # check username validity
            if not is_valid_username(request.username):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_USERNAME)

            # check name validity
            if not is_valid_name(request.name):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_NAME)

            if not request.hosting_status:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.HOSTING_STATUS_REQUIRED)

            if not self._username_available(request.username):
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.USERNAME_NOT_AVAILABLE)

            if request.lat == 0 and request.lng == 0:
                context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_COORDINATE)

            user = User(
                email=signup_token.email,
                username=request.username,
                name=request.name,
                gender=request.gender,
                birthdate=birthdate,
                hosting_status=hostingstatus2sql[request.hosting_status],
                city=request.city,
                geom=create_coordinate(request.lat, request.lng),
                geom_radius=request.radius,
                accepted_tos=1 if request.accept_tos else 0,
            )

            # happens in same transaction
            session.delete(signup_token)

            # enforces email/username uniqueness
            session.add(user)
            session.commit()

            token, expiry = self._create_session(context, session, user, False)
            context.send_initial_metadata(
                [
                    ("set-cookie", create_session_cookie(token, expiry)),
                ]
            )
            return auth_pb2.AuthRes(jailed=user.is_jailed)