Skip to content

Commit

Permalink
Setup token generation with user_agent parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
ElieTaillard committed Apr 10, 2024
1 parent 0e0c67c commit 202d80c
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 26 deletions.
4 changes: 1 addition & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM python:3.10-slim

COPY requirements.txt /ikabotapi/
COPY . /ikabotapi/
WORKDIR /ikabotapi

# Install dependencies
Expand All @@ -12,6 +12,4 @@ RUN apt-get update && \
RUN python -m playwright install && \
python -m playwright install-deps

COPY . .

CMD ["gunicorn", "-c", "gunicorn.conf.py", "run:app"]
26 changes: 26 additions & 0 deletions apps/token/SupportedUserAgents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.3",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.3",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.3",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.4",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 OPR/108.0.0.",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.5",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.5",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 OPR/108.0.0.",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.3",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.14",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.10",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.3",
"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Geck",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.2",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.3",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0."
]
21 changes: 15 additions & 6 deletions apps/token/TokenGenerator.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import os

from fake_useragent import FakeUserAgent
Expand All @@ -10,33 +11,41 @@ class TokenGenerator:
Usage:
```
token_generator = TokenGenerator()
token_generator = TokenGenerator(supported_user_agents=["User Agent 1", "User Agent 2"])
token = token_generator.get_token()
```
"""

def __init__(self):
def __init__(self, supported_user_agents):
"""
Initialize TokenGenerator.
- Sets the path to the HTML file used for token generation.
- Sets the supported user agents.
"""
current_directory = os.path.dirname(os.path.abspath(__file__))
self.html_file_path = f"file:///{current_directory}/token.html"
self.supported_user_agents = supported_user_agents

def get_token(self):
def get_token(self, user_agent: str = None):
"""
Get a token from the HTML file.
Waits for the presence of a specific div element in the HTML file and returns its text content as the token.
This method waits for the presence of a specific div element in the HTML file and returns its text content as the token.
Args:
- user_agent (str, optional): The user agent string to use for the browser. If not provided, a random user agent will be used.
Returns:
- str: The generated token.
"""
with sync_playwright() as playwright:
random_useragent = FakeUserAgent().random
if user_agent and user_agent in self.supported_user_agents:
playwright_useragent = user_agent
else:
playwright_useragent = FakeUserAgent().random
browser = playwright.chromium.launch(headless=True)
context = browser.new_context(user_agent=random_useragent)
context = browser.new_context(user_agent=playwright_useragent)
page = context.new_page()
page.goto(self.html_file_path)
token_element = page.wait_for_selector("body > div")
Expand Down
6 changes: 5 additions & 1 deletion apps/token/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import json
import logging
import os
import threading
import time

Expand All @@ -10,7 +12,9 @@

blueprint = Blueprint("token_blueprint", __name__, url_prefix="")

token_generator = TokenGenerator()
current_directory = os.path.dirname(os.path.abspath(__file__))
supported_user_agents = json.load(open(f"{current_directory}/SupportedUserAgents.json"))
token_generator = TokenGenerator(supported_user_agents=supported_user_agents)

tokenList = []
refreshCounter = 0
Expand Down
67 changes: 65 additions & 2 deletions apps/token/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
import threading
import time

from apps.token import blueprint, getTokenFromList, lock, logger, threadqueue
from flask import jsonify, request

from apps.token import (
blueprint,
getTokenFromList,
lock,
logger,
supported_user_agents,
threadqueue,
token_generator,
)


@blueprint.route("/token", methods=["GET"])
Expand All @@ -23,5 +33,58 @@ def token_route():
time.sleep(0.01)
return token
except Exception:
logger.exception("Ran into error while generating token. Retrying...")
logger.exception("Error in token route")
return "Error"


@blueprint.route("/v1/token", methods=["GET"])
def v1_token_route():
try:
if "user_agent" not in request.args:
return (
jsonify(
{
"status": "error",
"message": "Bad Request: Missing user_agent query parameter",
}
),
400,
)

user_agent = request.args.get("user_agent")

if user_agent == "":
return (
jsonify(
{
"status": "error",
"message": "Bad Request: Empty user_agent query parameter",
}
),
400,
)

if user_agent not in supported_user_agents:
return (
jsonify(
{
"status": "error",
"message": "Bad Request: Unsupported user_agent query parameter",
}
),
400,
)

token = token_generator.get_token(user_agent)
return jsonify(token), 200
except Exception:
logger.exception("Error in v1_token route")
return (
jsonify(
{
"status": "error",
"message": "An error occurred during token generation",
}
),
500,
)
39 changes: 39 additions & 0 deletions tests/test_setup_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import logging

import pytest
from pytest_mock import MockerFixture

from apps import setup_logger


def test_setup_logger_with_webhook_url_defined(mocker: MockerFixture):
mocker.patch("settings.LOGS_WEBHOOK_URL", "https://example.com/webhook")

logger = logging.getLogger()
logger.handlers.clear()

setup_logger()

assert len(logger.handlers) == 2 # One for console and one for Discord


def test_setup_logger_with_webhook_url_not_defined(mocker: MockerFixture):
mocker.patch("settings.LOGS_WEBHOOK_URL", None)

logger = logging.getLogger()
logger.handlers.clear()

setup_logger()

assert len(logger.handlers) == 1 # Only one for console


def test_setup_logger_with_empty_webhook_url(mocker: MockerFixture):
mocker.patch("settings.LOGS_WEBHOOK_URL", "")

logger = logging.getLogger()
logger.handlers.clear()

setup_logger()

assert len(logger.handlers) == 1 # Only one for console
49 changes: 36 additions & 13 deletions tests/test_token_generator.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,52 @@
import json
import os

import pytest
from pytest_mock import MockerFixture

from apps.token.TokenGenerator import TokenGenerator
from tests.token_validator import verify_token_format


@pytest.fixture
def token_generator():
token_generator = TokenGenerator()
json_file_path = os.path.abspath(
os.path.join(
os.path.dirname(__file__), "..", "apps", "token", "SupportedUserAgents.json"
)
)
supported_user_agents = json.load(open(json_file_path))
token_generator = TokenGenerator(supported_user_agents=supported_user_agents)
yield token_generator


def test_token_not_empty(token_generator):
token = token_generator.get_token()
assert token is not None, "Token should not be None"
assert token != "", "Token should not be an empty string"
def test_get_token_returns_unique_tokens(token_generator):
tokens = [token_generator.get_token() for _ in range(5)]
assert len(set(tokens)) == len(tokens), "Tokens should be unique"


def test_tokens_are_unique(token_generator):
tokens = [token_generator.get_token() for _ in range(5)]
def test_get_token_returns_unique_tokens_with_specific_user_agent(token_generator):
tokens = [
token_generator.get_token(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
)
for _ in range(5)
]
assert len(set(tokens)) == len(tokens), "Tokens should be unique"


def test_token_characteristics(token_generator):
def test_get_token_returns_valid_token(token_generator):
token = token_generator.get_token()
assert isinstance(token, str), "Token should be a string"
assert " " not in token, "Token should not contain any whitespace"
assert any(c.isupper() for c in token), "The string must contain uppercase letters."
assert any(c.islower() for c in token), "The string must contain lowercase letters."
assert any(c.isdigit() for c in token), "The string must contain digits."
verify_token_format(token)


def test_get_token_returns_valid_token_with_specific_user_agent(token_generator):
token = token_generator.get_token(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
)
verify_token_format(token)


def test_get_token_returns_valid_token_with_empty_user_agent(token_generator):
token = token_generator.get_token(user_agent="")
verify_token_format(token)
56 changes: 55 additions & 1 deletion tests/test_token_route.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import json
import token

import pytest
from pytest_mock import MockerFixture

from apps import create_app
from tests.token_validator import verify_token_format


@pytest.fixture
Expand All @@ -14,6 +16,58 @@ def client():
yield client


def test_get_token_should_return_status_code_ok(client):
def test_token_should_return_status_code_200_even_on_error(client):
"""
Test that the /token route returns status code 200.
Note: This test reflects the behavior where the route may return status code 200 even if there is an error.
"""
response = client.get("/token")
assert response.status_code == 200


def test_v1_token_route_without_user_agent_should_return_400(client):
response = client.get("/v1/token")
assert response.status_code == 400
response_json = json.loads(response.data)
assert response_json["status"] == "error"
assert response_json["message"] == "Bad Request: Missing user_agent query parameter"


def test_v1_token_route_with_empty_user_agent_should_return_400(client):
user_agent = ""
response = client.get(f"/v1/token?user_agent={user_agent}")
assert response.status_code == 400
response_json = json.loads(response.data)
assert response_json["status"] == "error"
assert response_json["message"] == "Bad Request: Empty user_agent query parameter"


def test_v1_token_route_with_unsupported_user_agent_should_return_400(client):
user_agent = "Unsupported User Agent"
response = client.get(f"/v1/token?user_agent={user_agent}")
assert response.status_code == 400
response_json = json.loads(response.data)
assert response_json["status"] == "error"
assert (
response_json["message"]
== "Bad Request: Unsupported user_agent query parameter"
)


def test_v1_token_route_with_supported_user_agent_should_return_200(client):
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.3"
response = client.get(f"/v1/token?user_agent={user_agent}")
assert response.status_code == 200
token = json.loads(response.data)
verify_token_format(token)


def test_v1_token_route_error(client, mocker: MockerFixture):
mocker.patch(
"apps.token.TokenGenerator.get_token",
side_effect=Exception("Test Exception"),
)

user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.3"
response = client.get(f"/v1/token?user_agent={user_agent}")
assert response.status_code == 500
17 changes: 17 additions & 0 deletions tests/token_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
def verify_token_format(token):
"""
Verify the format of the token.
Args:
token (str): The token to be verified.
Raises:
AssertionError: If the token does not meet the required format criteria.
"""
assert token is not None, "Token should not be None"
assert token != "", "Token should not be an empty string"
assert isinstance(token, str), "Token should be a string"
assert " " not in token, "Token should not contain any whitespace"
assert any(c.isupper() for c in token), "The string must contain uppercase letters."
assert any(c.islower() for c in token), "The string must contain lowercase letters."
assert any(c.isdigit() for c in token), "The string must contain digits."

0 comments on commit 202d80c

Please sign in to comment.