def GetBlockedUsers(self, request, context): with session_scope() as session: blocked_users = (session.execute( select(User).join(UserBlock, UserBlock.blocked_user_id == User.id).where( User.is_visible).where( UserBlock.blocking_user_id == context.user_id)).scalars().all()) return blocking_pb2.GetBlockedUsersRes(blocked_usernames=[ blocked_user.username for blocked_user in blocked_users ], )
def get_parent_node_at_location(session, shape): """ Finds the smallest node containing the shape. Shape can be any PostGIS geo object, e.g. output from create_coordinate """ # Fin the lowest Node (in the Node tree) that contains the shape. By construction of nodes, the area of a sub-node # must always be less than its parent Node, so no need to actually traverse the tree! return (session.execute( select(Node).where(func.ST_Contains(Node.geom, shape)).order_by( func.ST_Area(Node.geom))).scalars().first())
def test_relationships_userblock_dot_user(db): user1, token1 = generate_user() user2, token2 = generate_user() make_user_block(user1, user2) with session_scope() as session: block = session.execute( select(UserBlock).where((UserBlock.blocking_user_id == user1.id) & (UserBlock.blocked_user_id == user2.id)) ).scalar_one_or_none() assert block.blocking_user.username == user1.username assert block.blocked_user.username == user2.username
def GetHostRequestMessages(self, request, context): with session_scope() as session: host_request = session.execute( select(HostRequest).where( HostRequest.conversation_id == request.host_request_id)).scalar_one_or_none() if not host_request: context.abort(grpc.StatusCode.NOT_FOUND, errors.HOST_REQUEST_NOT_FOUND) if host_request.surfer_user_id != context.user_id and host_request.host_user_id != context.user_id: context.abort(grpc.StatusCode.NOT_FOUND, errors.HOST_REQUEST_NOT_FOUND) pagination = request.number if request.number > 0 else DEFAULT_PAGINATION_LENGTH pagination = min(pagination, MAX_PAGE_SIZE) messages = (session.execute( select(Message).where( Message.conversation_id == host_request.conversation_id).where( or_(Message.id < request.last_message_id, request.last_message_id == 0)).order_by( Message.id.desc()).limit(pagination + 1)).scalars().all()) no_more = len(messages) <= pagination last_message_id = min( map(lambda m: m.id if m else 1, messages[:pagination])) if len(messages) > 0 else 0 return requests_pb2.GetHostRequestMessagesRes( last_message_id=last_message_id, no_more=no_more, messages=[ message_to_pb(message) for message in messages[:pagination] ], )
def test_media_upload(db): user, token = generate_user() media_bearer_token = random_hex(32) with api_session(token) as api: res = api.InitiateMediaUpload(empty_pb2.Empty()) params = parse_qs(urlparse(res.upload_url).query) data = b64decode(params["data"][0]) response = media_pb2.UploadRequest.FromString(data) key = response.key filename = random_hex(32) req = media_pb2.UploadConfirmationReq(key=key, filename=filename) with session_scope() as session: # make sure it exists assert (session.execute( select(InitiatedUpload).where( InitiatedUpload.key == key)).scalar_one_or_none() is not None) with media_session(media_bearer_token) as media: res = media.UploadConfirmation(req) with session_scope() as session: # make sure it exists assert (session.execute( select(Upload).where(Upload.key == key).where( Upload.filename == filename).where( Upload.creator_user_id == user.id)).scalar_one() is not None) with session_scope() as session: # make sure it was deleted assert not session.execute( select(InitiatedUpload)).scalar_one_or_none()
def test_banned_user_without_password(db): _quick_signup() with session_scope() as session: user = session.execute(select(User)).scalar_one() user.hashed_password = None with auth_api_session() as (auth_api, metadata_interceptor): reply = auth_api.Login(auth_pb2.LoginReq(user="******")) assert reply.next_step == auth_pb2.LoginRes.LoginStep.SENT_LOGIN_EMAIL with session_scope() as session: login_token = session.execute(select(LoginToken)).scalar_one().token with session_scope() as session: session.execute(select(User)).scalar_one().is_banned = True with auth_api_session() as (auth_api, metadata_interceptor): with pytest.raises(grpc.RpcError) as e: auth_api.CompleteTokenLogin( auth_pb2.CompleteTokenLoginReq(login_token=login_token)) assert e.value.details() == "Your account is suspended."
def AcceptTOS(self, request, context): with session_scope() as session: user = session.execute( select(User).where(User.id == context.user_id)).scalar_one() if not request.accept: context.abort(grpc.StatusCode.FAILED_PRECONDITION, errors.CANT_UNACCEPT_TOS) user.accepted_tos = TOS_VERSION session.commit() return self._get_jail_info(user)
def AcceptCommunityGuidelines(self, request, context): with session_scope() as session: user = session.execute( select(User).where(User.id == context.user_id)).scalar_one() if not request.accept: context.abort(grpc.StatusCode.FAILED_PRECONDITION, errors.CANT_UNACCEPT_COMMUNITY_GUIDELINES) user.accepted_community_guidelines = GUIDELINES_VERSION session.commit() return self._get_jail_info(user)
def ListEventOccurrences(self, request, context): with session_scope() as session: page_size = min(MAX_PAGINATION_LENGTH, request.page_size or MAX_PAGINATION_LENGTH) # the page token is a unix timestamp of where we left off page_token = dt_from_millis(int( request.page_token)) if request.page_token else now() occurrence = session.execute( select(EventOccurrence).where(EventOccurrence.id == request. event_id)).scalar_one_or_none() if not occurrence: context.abort(grpc.StatusCode.NOT_FOUND, errors.EVENT_NOT_FOUND) occurrences = select(EventOccurrence).where( EventOccurrence.event_id == Event.id) if not request.past: occurrences = occurrences.where( EventOccurrence.end_time > page_token - timedelta(seconds=1)).order_by( EventOccurrence.start_time.asc()) else: occurrences = occurrences.where( EventOccurrence.end_time < page_token + timedelta(seconds=1)).order_by( EventOccurrence.start_time.desc()) occurrences = occurrences.limit(page_size + 1) occurrences = session.execute(occurrences).scalars().all() return events_pb2.ListEventOccurrencesRes( events=[ event_to_pb(session, occurrence, context) for occurrence in occurrences[:page_size] ], next_page_token=str(millis_from_dt(occurrences[-1].end_time)) if len(occurrences) > page_size else None, )
def UnblockUser(self, request, context): with session_scope() as session: blockee = session.execute( select(User).where(User.is_visible).where( User.username == request.username)).scalar_one_or_none() if not blockee: context.abort(grpc.StatusCode.NOT_FOUND, errors.USER_NOT_FOUND) user_block = session.execute( select(UserBlock).where( UserBlock.blocking_user_id == context.user_id).where( UserBlock.blocked_user_id == blockee.id)).scalar_one_or_none() if not user_block: context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.USER_NOT_BLOCKED) session.delete(user_block) session.commit() return empty_pb2.Empty()
def _search_pages(session, search_statement, title_only, next_rank, page_size, context, include_places, include_guides): rank, snippet, execute_search_statement = _gen_search_elements( search_statement, title_only, next_rank, page_size, [PageVersion.title], [PageVersion.address], [], [PageVersion.content], ) if not include_places and not include_guides: return [] latest_pages = (select(func.max(PageVersion.id).label("id")).join( Page, Page.id == PageVersion.page_id).where( or_( (Page.type == PageType.place) if include_places else False, (Page.type == PageType.guide) if include_guides else False, )).group_by(PageVersion.page_id).subquery()) pages = execute_search_statement( session, select(Page, rank, snippet).join(PageVersion, PageVersion.page_id == Page.id).join( latest_pages, latest_pages.c.id == PageVersion.id), ) return [ search_pb2.Result( rank=rank, place=page_to_pb(page, context) if page.type == PageType.place else None, guide=page_to_pb(page, context) if page.type == PageType.guide else None, snippet=snippet, ) for page, rank, snippet in pages ]
def handle_notification(notification_id): with session_scope() as session: notification = session.execute( select(Notification).where( Notification.id == notification_id)).scalar_one() # ignore this notification if the user hasn't enabled new notifications user = session.execute( select(User).where(User.id == notification.user_id)).scalar_one() if not user.new_notifications_enabled: logger.info( f"Skipping notification for {user} due to new notifications disabled" ) return topic, action = notification.topic_action.unpack() delivery_types = get_notification_preference(session, notification.user.id, notification.topic_action) for delivery_type in delivery_types: logger.info(f"Should notify by {delivery_type}") if delivery_type == NotificationDeliveryType.email: # for emails we don't deliver straight up, wait until the email background worker gets around to it and handles deduplication session.add( NotificationDelivery( notification_id=notification.id, delivered=None, delivery_type=NotificationDeliveryType.email, )) elif delivery_type == NotificationDeliveryType.push: # for push notifications, we send them straight away session.add( NotificationDelivery( notification_id=notification.id, delivered=func.now(), delivery_type=NotificationDeliveryType.push, )) # todo logger.info("Supposed to send push notification")
def test_full_delete_account_with_recovery(db): user, token = generate_user() user_id = user.id with account_session(token) as account: with pytest.raises(grpc.RpcError) as e: account.DeleteAccount(account_pb2.DeleteAccountReq()) assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION assert e.value.details() == errors.MUST_CONFIRM_ACCOUNT_DELETE # Check the right email is sent with patch("couchers.email.queue_email") as mock: account.DeleteAccount(account_pb2.DeleteAccountReq(confirm=True)) mock.assert_called_once() (_, _, _, subject, _, _), _ = mock.call_args assert subject == "[TEST] Confirm your Couchers.org account deletion" with session_scope() as session: token_o = session.execute(select(AccountDeletionToken)).scalar_one() token = token_o.token user = session.execute( select(User).where(User.id == user_id)).scalar_one() assert token_o.user == user assert not user.is_deleted assert not user.undelete_token assert not user.undelete_until with auth_api_session() as (auth_api, metadata_interceptor): auth_api.ConfirmDeleteAccount( auth_pb2.ConfirmDeleteAccountReq(token=token, )) with session_scope() as session: assert not session.execute( select(AccountDeletionToken)).scalar_one_or_none() with session_scope() as session: user = session.execute( select(User).where(User.id == user_id)).scalar_one() assert user.is_deleted assert user.undelete_token assert user.undelete_until > now() undelete_token = user.undelete_token with auth_api_session() as (auth_api, metadata_interceptor): auth_api.RecoverAccount( auth_pb2.RecoverAccountReq(token=undelete_token, )) with session_scope() as session: assert not session.execute( select(AccountDeletionToken)).scalar_one_or_none() user = session.execute( select(User).where(User.id == user_id)).scalar_one() assert not user.is_deleted assert not user.undelete_token assert not user.undelete_until
def CreateDiscussion(self, request, context): if not request.title: context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.MISSING_DISCUSSION_TITLE) if not request.content: context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.MISSING_DISCUSSION_CONTENT) if not request.owner_community_id and not request.owner_group_id: context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.GROUP_OR_COMMUNITY_NOT_FOUND) with session_scope() as session: if request.WhichOneof("owner") == "owner_group_id": cluster = session.execute( select(Cluster).where(~Cluster.is_official_cluster).where( Cluster.id == request.owner_group_id)).scalar_one_or_none() elif request.WhichOneof("owner") == "owner_community_id": cluster = session.execute( select(Cluster).where( Cluster.parent_node_id == request.owner_community_id).where( Cluster.is_official_cluster)).scalar_one_or_none() if not cluster: context.abort(grpc.StatusCode.NOT_FOUND, errors.GROUP_OR_COMMUNITY_NOT_FOUND) discussion = Discussion( title=request.title, content=request.content, creator_user_id=context.user_id, owner_cluster=cluster, thread=Thread(), ) session.add(discussion) session.commit() return discussion_to_pb(discussion, context)
def test_process_send_onboarding_emails(db): # needs to get first onboarding email user1, token1 = generate_user(onboarding_emails_sent=0, last_onboarding_email_sent=None) process_send_onboarding_emails(empty_pb2.Empty()) with session_scope() as session: assert (session.execute( select(func.count()).select_from(BackgroundJob).where( BackgroundJob.job_type == BackgroundJobType.send_email)).scalar_one() == 1) # needs to get second onboarding email, but not yet user2, token2 = generate_user(onboarding_emails_sent=1, last_onboarding_email_sent=now() - timedelta(days=6)) process_send_onboarding_emails(empty_pb2.Empty()) with session_scope() as session: assert (session.execute( select(func.count()).select_from(BackgroundJob).where( BackgroundJob.job_type == BackgroundJobType.send_email)).scalar_one() == 1) # needs to get second onboarding email user3, token3 = generate_user(onboarding_emails_sent=1, last_onboarding_email_sent=now() - timedelta(days=8)) process_send_onboarding_emails(empty_pb2.Empty()) with session_scope() as session: assert (session.execute( select(func.count()).select_from(BackgroundJob).where( BackgroundJob.job_type == BackgroundJobType.send_email)).scalar_one() == 2)
def TransferEvent(self, request, context): with session_scope() as session: res = session.execute( select(Event, EventOccurrence).where( EventOccurrence.id == request.event_id).where( EventOccurrence.event_id == Event.id)).one_or_none() if not res: context.abort(grpc.StatusCode.NOT_FOUND, errors.EVENT_NOT_FOUND) event, occurrence = res if not _can_edit_event(session, event, context.user_id): context.abort(grpc.StatusCode.PERMISSION_DENIED, errors.EVENT_TRANSFER_PERMISSION_DENIED) if request.WhichOneof("new_owner") == "new_owner_group_id": cluster = session.execute( select(Cluster).where(~Cluster.is_official_cluster).where( Cluster.id == request.new_owner_group_id)).scalar_one_or_none() elif request.WhichOneof("new_owner") == "new_owner_community_id": cluster = session.execute( select(Cluster).where( Cluster.parent_node_id == request.new_owner_community_id).where( Cluster.is_official_cluster)).scalar_one_or_none() if not cluster: context.abort(grpc.StatusCode.NOT_FOUND, errors.GROUP_OR_COMMUNITY_NOT_FOUND) event.owner_user = None event.owner_cluster = cluster session.commit() return event_to_pb(session, occurrence, context)
def test_ChangeEmail_tokens_two_hour_window(db): def two_hours_one_minute_in_future(): return now() + timedelta(hours=2, minutes=1) def one_minute_ago(): return now() - timedelta(minutes=1) new_email = f"{random_hex()}@couchers.org.invalid" user, token = generate_user(hashed_password=None) with account_session(token) as account: account.ChangeEmail(account_pb2.ChangeEmailReq(new_email=new_email, )) with session_scope() as session: user = session.execute( select(User).where(User.id == user.id)).scalar_one() old_email_token = user.old_email_token new_email_token = user.new_email_token with patch("couchers.servicers.auth.now", one_minute_ago): with auth_api_session() as (auth_api, metadata_interceptor): with pytest.raises(grpc.RpcError) as e: auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq( change_email_token=old_email_token, )) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert e.value.details() == errors.INVALID_TOKEN with pytest.raises(grpc.RpcError) as e: auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq( change_email_token=new_email_token, )) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert e.value.details() == errors.INVALID_TOKEN with patch("couchers.servicers.auth.now", two_hours_one_minute_in_future): with auth_api_session() as (auth_api, metadata_interceptor): with pytest.raises(grpc.RpcError) as e: auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq( change_email_token=old_email_token, )) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert e.value.details() == errors.INVALID_TOKEN with pytest.raises(grpc.RpcError) as e: auth_api.ConfirmChangeEmail( auth_pb2.ConfirmChangeEmailReq( change_email_token=new_email_token, )) assert e.value.code() == grpc.StatusCode.NOT_FOUND assert e.value.details() == errors.INVALID_TOKEN
def test_select_dot_where_users_visible(db): user1, token1 = generate_user() user2, token2 = generate_user(delete_user=True) user3, token3 = generate_user() user4, token4 = generate_user() make_user_block(user1, user3) make_user_block(user4, user1) context = _FakeContext(user1.id) with session_scope() as session: assert session.execute( select(func.count()).select_from(User).where_users_visible( context)).scalar_one() == 1
def ListNearbyUsers(self, request, context): with session_scope() as session: page_size = min(MAX_PAGINATION_LENGTH, request.page_size or MAX_PAGINATION_LENGTH) next_nearby_id = int(request.page_token) if request.page_token else 0 node = session.execute(select(Node).where(Node.id == request.community_id)).scalar_one_or_none() if not node: context.abort(grpc.StatusCode.NOT_FOUND, errors.COMMUNITY_NOT_FOUND) nearbys = ( session.execute( select(User) .where_users_visible(context) .where(func.ST_Contains(node.geom, User.geom)) .where(User.id >= next_nearby_id) .order_by(User.id) .limit(page_size + 1) ) .scalars() .all() ) return communities_pb2.ListNearbyUsersRes( nearby_user_ids=[nearby.id for nearby in nearbys[:page_size]], next_page_token=str(nearbys[-1].id) if len(nearbys) > page_size else None, )
def enforce_community_memberships_for_user(session, user): """ Adds a given user to all the communities they belong in based on their location. """ nodes = session.execute( select(Node).where(func.ST_Contains(Node.geom, user.geom))).scalars().all() for node in nodes: node.official_cluster.cluster_subscriptions.append( ClusterSubscription( user=user, role=ClusterRole.member, )) session.commit()
def test_ChangePassword_add_no_passwords(db, fast_passwords): # user does not have an old password and called with empty body user, token = generate_user(hashed_password=None) with account_session(token) as account: with pytest.raises(grpc.RpcError) as e: account.ChangePassword(account_pb2.ChangePasswordReq()) assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT assert e.value.details() == errors.MISSING_BOTH_PASSWORDS with session_scope() as session: updated_user = session.execute( select(User).where(User.id == user.id)).scalar_one() assert updated_user.hashed_password == None
def get_node_parents_recursively(session, node_id): """ Gets the upwards hierarchy of parents, ordered by level, for a given node Returns SQLAlchemy rows of (node_id, parent_node_id, level, cluster) """ parents = (select(Node.id, Node.parent_node_id, literal(0).label("level")).where(Node.id == node_id).cte( "parents", recursive=True)) subquery = select( parents.union( select(Node.id, Node.parent_node_id, (parents.c.level + 1).label("level")).join( parents, Node.id == parents.c.parent_node_id))).subquery() return session.execute( select(subquery, Cluster).join(Cluster, Cluster.parent_node_id == subquery.c.id).where( Cluster.is_official_cluster).order_by( subquery.c.level.desc())).all()
def SetEventAttendance(self, request, context): with session_scope() as session: occurrence = session.execute( select(EventOccurrence).where(EventOccurrence.id == request. event_id)).scalar_one_or_none() if not occurrence: context.abort(grpc.StatusCode.NOT_FOUND, errors.EVENT_NOT_FOUND) current_attendance = session.execute( select(EventOccurrenceAttendee).where( EventOccurrenceAttendee.user_id == context.user_id).where( EventOccurrenceAttendee.occurrence_id == occurrence.id)).scalar_one_or_none() if request.attendance_state == events_pb2.ATTENDANCE_STATE_NOT_GOING: if current_attendance: session.delete(current_attendance) # if unset/not going, nothing to do! else: if current_attendance: current_attendance.attendee_status = attendancestate2sql[ request.attendance_state] else: # create new attendance = EventOccurrenceAttendee( user_id=context.user_id, occurrence_id=occurrence.id, attendee_status=attendancestate2sql[ request.attendance_state], ) session.add(attendance) session.flush() return event_to_pb(session, occurrence, context)
def ListFriendRequests(self, request, context): # both sent and received with session_scope() as session: sent_requests = (session.execute( select(FriendRelationship).where_users_column_visible( context, FriendRelationship.to_user_id).where( FriendRelationship.from_user_id == context.user_id). where(FriendRelationship.status == FriendStatus.pending)).scalars().all()) received_requests = (session.execute( select(FriendRelationship).where_users_column_visible( context, FriendRelationship.from_user_id).where( FriendRelationship.to_user_id == context.user_id). where(FriendRelationship.status == FriendStatus.pending)).scalars().all()) return api_pb2.ListFriendRequestsRes( sent=[ api_pb2.FriendRequest( friend_request_id=friend_request.id, state=api_pb2.FriendRequest.FriendRequestStatus. PENDING, user_id=friend_request.to_user.id, sent=True, ) for friend_request in sent_requests ], received=[ api_pb2.FriendRequest( friend_request_id=friend_request.id, state=api_pb2.FriendRequest.FriendRequestStatus. PENDING, user_id=friend_request.from_user.id, sent=False, ) for friend_request in received_requests ], )
def LeaveGroupChat(self, request, context): with session_scope() as session: subscription = session.execute( select(GroupChatSubscription) .where(GroupChatSubscription.group_chat_id == request.group_chat_id) .where(GroupChatSubscription.user_id == context.user_id) .where(GroupChatSubscription.left == None) ).scalar_one_or_none() if not subscription: context.abort(grpc.StatusCode.NOT_FOUND, errors.CHAT_NOT_FOUND) if subscription.role == GroupChatRole.admin: other_admins_count = session.execute( select(func.count()) .select_from(GroupChatSubscription) .where(GroupChatSubscription.group_chat_id == request.group_chat_id) .where(GroupChatSubscription.user_id != context.user_id) .where(GroupChatSubscription.role == GroupChatRole.admin) .where(GroupChatSubscription.left == None) ).scalar_one() participants_count = session.execute( select(func.count()) .select_from(GroupChatSubscription) .where(GroupChatSubscription.group_chat_id == request.group_chat_id) .where(GroupChatSubscription.user_id != context.user_id) .where(GroupChatSubscription.role == GroupChatRole.participant) .where(GroupChatSubscription.left == None) ).scalar_one() if not (other_admins_count > 0 or participants_count == 0): context.abort(grpc.StatusCode.FAILED_PRECONDITION, errors.LAST_ADMIN_CANT_LEAVE) _add_message_to_subscription(session, subscription, message_type=MessageType.user_left) subscription.left = func.now() return empty_pb2.Empty()
def ReportBug(self, request, context): if not config["BUG_TOOL_ENABLED"]: context.abort(grpc.StatusCode.UNAVAILABLE, "Bug tool disabled") repo = config["BUG_TOOL_GITHUB_REPO"] auth = (config["BUG_TOOL_GITHUB_USERNAME"], config["BUG_TOOL_GITHUB_TOKEN"]) if context.user_id: with session_scope() as session: username = session.execute( select(User.username).where( User.id == context.user_id)).scalar_one() user_details = f"{username} ({context.user_id})" else: user_details = "<not logged in>" issue_title = request.subject issue_body = (f"Subject: {request.subject}\n" f"Description:\n" f"{request.description}\n" f"\n" f"Results:\n" f"{request.results}\n" f"\n" f"Backend version: {self._version()}\n" f"Frontend version: {request.frontend_version}\n" f"User Agent: {request.user_agent}\n" f"Page: {request.page}\n" f"User: {user_details}") issue_labels = ["bug tool"] json_body = { "title": issue_title, "body": issue_body, "labels": issue_labels } r = requests.post(f"https://api.github.com/repos/{repo}/issues", auth=auth, json=json_body) if not r.status_code == 201: context.abort(grpc.StatusCode.INTERNAL, "Request failed") issue_number = r.json()["number"] return bugs_pb2.ReportBugRes( bug_id=f"#{issue_number}", bug_url=f"https://github.com/{repo}/issues/{issue_number}")
def ChangePhone(self, request, context): phone = request.phone # early quick validation if phone and not is_e164_format(phone): context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.INVALID_PHONE) with session_scope() as session: user = session.execute( select(User).where(User.id == context.user_id)).scalar_one() if not phone: user.phone = None user.phone_verification_verified = None user.phone_verification_token = None user.phone_verification_attempts = 0 return empty_pb2.Empty() if not is_known_operator(phone): context.abort(grpc.StatusCode.UNIMPLEMENTED, errors.UNRECOGNIZED_PHONE_NUMBER) if now( ) - user.phone_verification_sent < PHONE_REVERIFICATION_INTERVAL: context.abort(grpc.StatusCode.RESOURCE_EXHAUSTED, errors.REVERIFICATION_TOO_EARLY) token = sms.generate_random_code() result = sms.send_sms(phone, sms.format_message(token)) if result == "success": user.phone = phone user.phone_verification_verified = None user.phone_verification_token = token user.phone_verification_sent = now() user.phone_verification_attempts = 0 notify( user_id=user.id, topic="phone_number", key="", action="change", icon="wrench", title=f"Your phone number was changed", link=urls.account_settings_link(), ) return empty_pb2.Empty() context.abort(grpc.StatusCode.UNIMPLEMENTED, result)
def test_UnblockUser(db): user1, token1 = generate_user() user2, token2 = generate_user() make_user_block(user1, user2) with blocking_session(token1) as user_blocks: user_blocks.UnblockUser(blocking_pb2.UnblockUserReq(username=user2.username)) with session_scope() as session: blocked_users = session.execute(select(UserBlock).where(UserBlock.blocking_user_id == user1.id)).scalars().all() assert len(blocked_users) == 0 with blocking_session(token1) as user_blocks: with pytest.raises(grpc.RpcError) as e: user_blocks.UnblockUser(blocking_pb2.UnblockUserReq(username=user2.username)) assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT assert e.value.details() == errors.USER_NOT_BLOCKED # Test re-blocking user_blocks.BlockUser(blocking_pb2.BlockUserReq(username=user2.username)) with session_scope() as session: blocked_users = session.execute(select(UserBlock).where(UserBlock.blocking_user_id == user1.id)).scalars().all() assert len(blocked_users) == 1
def test_banned_user(db): _quick_signup() with auth_api_session() as (auth_api, metadata_interceptor): reply = auth_api.Login(auth_pb2.LoginReq(user="******")) assert reply.next_step == auth_pb2.LoginRes.LoginStep.NEED_PASSWORD with session_scope() as session: session.execute(select(User)).scalar_one().is_banned = True with auth_api_session() as (auth_api, metadata_interceptor): with pytest.raises(grpc.RpcError) as e: auth_api.Authenticate( auth_pb2.AuthReq(user="******", password="******")) assert e.value.details() == "Your account is suspended."
def test_login_tokens_invalidate_after_use(db): _quick_signup() with session_scope() as session: user = session.execute(select(User)).scalar_one() user.hashed_password = None with auth_api_session() as (auth_api, metadata_interceptor): reply = auth_api.Login(auth_pb2.LoginReq(user="******")) assert reply.next_step == auth_pb2.LoginRes.LoginStep.SENT_LOGIN_EMAIL with session_scope() as session: login_token = session.execute(select(LoginToken)).scalar_one().token with auth_api_session() as (auth_api, metadata_interceptor): auth_api.CompleteTokenLogin( auth_pb2.CompleteTokenLoginReq(login_token=login_token)) session_token = get_session_cookie_token(metadata_interceptor) with auth_api_session() as (auth_api, metadata_interceptor), pytest.raises( grpc.RpcError): # check we can't login again auth_api.CompleteTokenLogin( auth_pb2.CompleteTokenLoginReq(login_token=login_token))