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")
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)
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)
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)
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)
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)
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.")
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)