예제 #1
0
async def register_user(first_name: str, last_name: str, email: str,
                        password: str, config: Config):
    if not check_password(password):
        raise err.bad_request(
            "Please provide a password at least 8 characters long, containing letters and symbols"
        )

    password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
    try:
        async with config.get_database_connection() as db:
            user_persistence = users.UsersPersistence(db)
            created_user_id = await user_persistence.create_user(
                first_name, last_name, email, password_hash)
            user = await user_persistence.get_user_by_id(created_user_id)
    except IntegrityError as e:
        logger.debug("IntegrityError registering user with email %s: %s",
                     email, e)
        raise err.bad_request("Email address is already in use")
    except Exception as e:
        logger.exception("Failed to get user with email %s from db: %s", email,
                         e)
        raise err.internal_server_error()

    return {
        "token": jwt.generate_jwt(user["user_id"], jwt.Aud.AUTH,
                                  config.jwt_key())
    }
예제 #2
0
async def login(email: str, password: str, config: Config) -> Dict:
    """
    Get user from DB, check password hash matches and return a JWT token if so
    """
    try:
        async with config.get_database_connection() as db:
            user = await users.UsersPersistence(db).get_user_by_email(email)
    except Exception as e:
        logger.exception("Failed to get user with email %s from db: %s", email,
                         e)
        raise err.internal_server_error()

    auth_failed = err.unauthorized(
        "Failed to log you in with the provided credentials; please try again")
    if not user:
        logger.debug("User not found with email %s", email)
        raise auth_failed

    if not bcrypt.checkpw(password.encode(), user["password_hash"]):
        logger.debug("Incorrect password for user(id=%s)", user["user_id"])
        raise auth_failed

    return {
        "token": jwt.generate_jwt(user["user_id"], jwt.Aud.AUTH,
                                  config.jwt_key())
    }
예제 #3
0
async def get_room_by_id_minimal(room_id: int, user_id: int,
                                 config: Config) -> Dict:
    """
    Get data for a room by id, redacting secret information like the room code
    """
    try:
        async with config.get_database_connection() as db:
            room_persistence = rooms.RoomPersistence(db)
            room = await room_persistence.get_room(room_id)
            if not room or not room.get("active"):
                raise err.not_found(f"No active room with id {room_id}")
            user_in_room = await is_user_in_room(user_id,
                                                 room_id,
                                                 db,
                                                 room=room)
            return {
                "room_id": room.get("room_id"),
                "owner_id": room.get("owner_id"),
                "room_name": room.get("room_name"),
                "has_code": bool(room.get("room_code")),
                "user_in_room": user_in_room
            }
    except Exception as e:
        logger.exception("Failed to retrieve minimal data for room %s: %s",
                         room_id, e)
        raise e
예제 #4
0
async def join_room(user_id: int, room_id: int, room_code: Optional[str],
                    config: Config) -> Dict:
    """Process a request from a user to join a room"""
    try:
        async with config.get_database_connection() as db:
            room_persistence = rooms.RoomPersistence(db)
            room = await room_persistence.get_room(room_id)
            if not room or not room.get("active"):
                raise err.not_found(f"Active room with id {room_id} not found")

            if await is_user_in_room(user_id, room_id, db, room=room):
                return {"success": True, "message": "User is already in room"}

            if room.get("room_code"):
                if room["room_code"] != room_code:
                    return {"success": False, "message": "Invalid room code"}

            await room_persistence.add_user_to_room(room_id, user_id)

            return {"success": True, "message": "Successfully joined the room"}
    except HTTPException:
        raise
    except Exception as e:
        logger.exception("Failed to get rooms for user %s: %s", user_id, e)
        raise e
예제 #5
0
async def find_room(query: Dict, config: Config) -> Dict:
    """get a room's ID using either the owner's ID or the room ID
    in the case of supplying the latter, this simply verifies that a room
    with that ID exists"""

    if "owner_id" in query:
        resource_name = "owner_id"
        query_method = rooms.RoomPersistence.get_room_by_owner
    elif "room_id" in query:
        resource_name = "room_id"
        query_method = rooms.RoomPersistence.get_room
    else:
        raise err.bad_request(
            "Supply either a room_id or an owner_id to search for")

    try:
        resource_id = int(query[resource_name])
    except ValueError:
        raise err.bad_request(
            f"'{query[resource_name]}' is not a valid {resource_name}")

    try:
        async with config.get_database_connection() as db:
            room_persistence = rooms.RoomPersistence(db)
            room = await query_method(room_persistence, resource_id)
            if not room or not room.get("active"):
                raise err.not_found(
                    f"No active rooms found for {resource_name} {resource_id}")
            else:
                return {"room_id": room["room_id"]}
    except HTTPException:
        raise
    except Exception as e:
        logger.exception("Failed to find room with query %s: %s", query, e)
        raise e
예제 #6
0
async def deactivate_room(user_id: int, room_id: int, config: Config) -> Dict:
    """Process a request from an owner to deactivate an owned room"""
    try:
        async with config.get_database_connection() as db:
            room_persistence = rooms.RoomPersistence(db)
            room = await room_persistence.get_room(room_id)
            if not room or not room.get("active"):
                raise err.not_found(f"Active room with id {room_id} not found")

            if room["owner_id"] != user_id:
                raise err.forbidden(
                    "User is not permitted to deactivate this room")

            await room_persistence.deactivate_room(room_id)

            return {
                "success": True,
                "message": "Successfully deactivated the room"
            }
    except HTTPException:
        raise
    except Exception as e:
        logger.exception("Failed to deactivate rooms for user %s: %s", user_id,
                         e)
        raise e
예제 #7
0
async def search(user_id: int, room_id: int, query: str,
                 config: Config) -> Dict:
    if not query:
        raise err.bad_request("No query string supplied to search for")

    try:
        async with config.get_database_connection() as db:
            room = await get_room_for_user_assertive(room_id, user_id, db)
            token_result = await spotify.get_valid_token_for_user(
                room["owner_id"], config, requested_by=user_id)
            token = _handle_token_result(token_result)

        search_results = await config.get_spotify_api().search(query, token)
        return {
            "results":
            extract_relevant_data_from_search_results(search_results)
        }
    except HTTPException:
        raise
    except ClientResponseError as e:
        logger.warning(
            "Failed to enqueue track with Spotify for user %s in room %s: %s",
            user_id, room_id, e)
        if e.status == 403:
            raise err.forbidden(
                "Unable to enqueue track: user is not a premium subscriber")
        if e.status == 404:
            raise err.not_found("The requested track could not be found")
        raise e
    except Exception as e:
        logger.exception(
            "Failed to play track for user(id=%s) in room(id=%s): %s", user_id,
            room_id, e)
        raise
예제 #8
0
async def enqueue_song(user_id: int, room_id: int, track_uri: str,
                       config: Config) -> Dict:
    try:
        async with config.get_database_connection() as db:
            room = await get_room_for_user_assertive(room_id, user_id, db)
            token_result = await spotify.get_valid_token_for_user(
                room["owner_id"], config, requested_by=user_id)
            token = _handle_token_result(token_result)

        await config.get_spotify_api().enqueue_song(track_uri, token)

        return {"success": True}

    except HTTPException:
        raise
    except ClientResponseError as e:
        logger.warning(
            "Failed to enqueue track with Spotify for user %s in room %s: %s",
            user_id, room_id, e)
        if e.status == 403:
            raise err.forbidden(
                "Unable to enqueue track: user is not a premium subscriber")
        if e.status == 404:
            raise err.not_found("The requested track could not be found")
        raise e
    except Exception as e:
        logger.exception(
            "Failed to play track for user(id=%s) in room(id=%s): %s", user_id,
            room_id, e)
        raise
예제 #9
0
async def spotify_auth(user_id: int, config: Config):
    """
    Initiate authentication with the spotify API
    """
    token = jwt.generate_jwt(user_id, jwt.Aud.API, config.jwt_key())
    query_parameters = {
        "response_type": "code",
        "client_id": config.spotify_client_id(),
        "scope": REQUIRED_SCOPES,
        "redirect_uri": config.spotify_redirect(),
        "state": token
    }
    query_string = urlencode(query_parameters)
    redirect = f"https://accounts.spotify.com/authorize?{query_string}"

    logger.info("Redirecting user to %s", redirect)
    return {"url": redirect}
예제 #10
0
async def get_owned_room_for_user(user_id: int, config: Config) -> Dict:
    try:
        async with config.get_database_connection() as db:
            room_persistence = rooms.RoomPersistence(db)
            room = await room_persistence.get_room_by_owner(user_id)
            return room
    except Exception as e:
        logger.exception("Failed to create room for user %s: %s", user_id, e)
        raise e
예제 #11
0
async def get_room_by_id(room_id: int, user_id: int, config: Config) -> Dict:
    """
    Delegates to the get_room_for_user_assertive method
    """
    try:
        async with config.get_database_connection() as db:
            return await get_room_for_user_assertive(room_id, user_id, db)
    except Exception as e:
        logger.exception("Failed to retrieve room %s for user %s: %s", room_id,
                         user_id, e)
        raise e
예제 #12
0
async def get_joined_rooms_for_user(user_id: int, config: Config) -> Dict:
    """Get rooms which the user is a member of"""
    try:
        async with config.get_database_connection() as db:
            room_persistence = rooms.RoomPersistence(db)
            joined_rooms = await room_persistence.get_joined_rooms_by_user(
                user_id)
            return {"rooms": joined_rooms}
    except Exception as e:
        logger.exception("Failed to get rooms for user %s: %s", user_id, e)
        raise e
예제 #13
0
async def get_valid_token_for_user(
        user_id: int,
        config: Config,
        requested_by=None) -> Union[str, GetTokenError]:
    try:
        async with config.get_database_connection() as db:
            token_persistence = spotify_token.SpotifyTokenPersistence(db)
            stored_token = await token_persistence.get_token_by_user(user_id)

            if not stored_token:
                # the user never auth'd
                return GetTokenError.NOT_AUTHED

            if not stored_token.get("access_token") and not stored_token.get(
                    "refresh_token"):
                # we're not in a position to use or refresh the token and need the user to re-auth
                return GetTokenError.EXPIRED

            if not is_token_expired(stored_token):
                return stored_token["access_token"]
            elif not stored_token.get("refresh_token"):
                # user session has expired and is not refreshable
                if requested_by is not None and requested_by == user_id:
                    # the owner is making the request, so ask them to re-auth
                    await spotify_auth(user_id, config)
                return GetTokenError.EXPIRED

            # we have a refresh token to use
            response = await config.get_spotify_api().refresh_tokens(
                stored_token["refresh_token"])
            created_at = datetime.utcnow()

            if not "access_token" in response:
                return GetTokenError.NOT_AUTHED

            await token_persistence.upsert_token(
                user_id, stored_token["spotify_user_id"],
                response["access_token"],
                response.get("refresh_token", stored_token["refresh_token"]),
                created_at, response["expires_in"])
            return response["access_token"]
    except aiohttp.client_exceptions.ClientResponseError as e:
        logger.exception(
            "Something went wrong when refreshing tokens for user %s: %s",
            user_id, e)
        return GetTokenError.API_ERROR
    except DatabaseError as e:
        logger.exception(
            "Something went wrong retrieving or storing tokens for user %s: %s",
            user_id, e)
        return GetTokenError.DB_ERROR
예제 #14
0
async def handle_spotify_callback(query_params: Dict, config: Config):
    if query_params.get("error"):
        raise err.failed_dependency(
            "Please allow access to your Spotify account to proceed")
    token = query_params.get("state")
    if not token:
        raise err.bad_request(
            "'state' query parameter required but not provided")
    try:
        claims = jwt.get_claims_from_jwt(token, config.jwt_key(), jwt.Aud.API)
        user_id = int(claims["sub"])
    except Exception as e:
        logger.debug("Failed to decode state as JWT: %s", e)
        raise err.bad_request("Invalid state from Spotify callback")

    code = query_params.get("code")
    if not code:
        raise err.failed_dependency(
            "Spotify authorization failed; no valid code returned")

    request = {
        "grant_type": "authorization_code",
        "code": code,
        "redirect_uri": config.spotify_redirect(),
        "client_id": config.spotify_client_id(),
        "client_secret": config.spotify_secret()
    }

    try:
        session = config.get_session()
        tokens_response = await config.get_spotify_api().request_tokens(request
                                                                        )

        access_token = tokens_response["access_token"]
        refresh_token = tokens_response["refresh_token"]
        created_at = datetime.utcnow()
        expires_in = tokens_response["expires_in"]

        spotify_user_data = await config.get_spotify_api().spotify_user_data(
            access_token)
        spotify_user_id = spotify_user_data["id"]
    except Exception as e:
        logger.exception("Failed to authorize user(id=%s): %s", user_id, e)
        raise err.internal_server_error(
            "Something went wrong logging in with Spotify")

    try:
        async with config.get_database_connection() as db:
            await spotify_token.SpotifyTokenPersistence(db).upsert_token(
                user_id, spotify_user_id, access_token, refresh_token,
                created_at, expires_in)
    except Exception as e:
        logger.exception("Failed to upsert Spotify token for user(id=%s): %s",
                         user_id, e)
        raise err.internal_server_error()

    return {"success": True}
예제 #15
0
async def create_room(user_id: int, room_code: Optional[str], room_name: str,
                      config: Config) -> Dict:
    try:
        # only users that have auth'd with Spotify may create rooms
        token_result = await spotify.get_valid_token_for_user(user_id, config)
        token = _handle_token_result(token_result)

        async with config.get_database_connection() as db:
            room_persistence = rooms.RoomPersistence(db)
            room_id = await room_persistence.create_room(
                user_id, room_code, room_name)
            logger.debug("Created room(id=%s) for user(id=%s)", room_id,
                         user_id)
            created_room = await room_persistence.get_room(room_id)
            return created_room
    except Exception as e:
        logger.exception("Failed to create room for user %s: %s", user_id, e)
        raise e
예제 #16
0
async def me(user_id: int, config: Config) -> Dict:
    try:
        async with config.get_database_connection() as db:
            get_user = users.UsersPersistence(db).get_user_by_id(user_id)
            get_joined_rooms = rooms.RoomPersistence(
                db).get_joined_rooms_by_user(user_id)
            get_token = spotify.get_valid_token_for_user(user_id, config)
            (user, joined_rooms,
             token) = await asyncio.gather(get_user,
                                           get_joined_rooms,
                                           get_token,
                                           return_exceptions=False)
    except Exception as e:
        logger.exception("Failed to get user(id=%s) from db: %s", user_id, e)
        raise err.internal_server_error()
    del user["password_hash"]
    user["rooms"] = joined_rooms
    user["authed_with_spotify"] = isinstance(token, str)
    return user