From 664cd32f81cd2d9c3a27a45a611d4f52cd07d517 Mon Sep 17 00:00:00 2001 From: MrMeeb Date: Mon, 20 Jan 2025 12:00:06 +0000 Subject: [PATCH] 4.63.0 --- app/.github/workflows/main.yml | 57 ++-- app/.python-version | 1 + app/CONTRIBUTING.md | 27 +- app/Dockerfile | 48 ++-- app/app/admin_model.py | 10 +- app/app/models.py | 8 +- app/email_handler.py | 11 + app/pyproject.toml | 213 ++++++++------- app/requirements-dev.lock | 469 ++++++++++++++++++++++++++++++++ app/requirements.lock | 392 ++++++++++++++++++++++++++ app/scripts/new-migration.sh | 4 +- app/scripts/reset_local_db.sh | 4 +- app/scripts/reset_test_db.sh | 2 +- app/scripts/run-test.sh | 4 +- app/tests/api/test_alias.py | 2 +- app/tests/test_contact_utils.py | 2 +- app/tests/test_email_handler.py | 13 + 17 files changed, 1083 insertions(+), 184 deletions(-) create mode 100644 app/.python-version create mode 100644 app/requirements-dev.lock create mode 100644 app/requirements.lock diff --git a/app/.github/workflows/main.yml b/app/.github/workflows/main.yml index b14160a..48211ba 100644 --- a/app/.github/workflows/main.yml +++ b/app/.github/workflows/main.yml @@ -1,6 +1,12 @@ -name: Test and lint +name: SimpleLogin actions -on: [push, pull_request] +on: + push: + branches: + - master + tags: + - v* + pull_request: jobs: lint: @@ -9,35 +15,30 @@ jobs: - name: Check out repo uses: actions/checkout@v3 - - name: Install poetry - run: pipx install poetry - - - uses: actions/setup-python@v4 + - name: "Install rye" + id: setup-rye + uses: eifinger/setup-rye@v4 with: - python-version: '3.10' - cache: 'poetry' + version: '0.43.0' + checksum: 'ca702c3d93fd6ec76a1a0efaaa605e10736ee79a0674d241aad1bc0fe26f7d80' + enable-cache: true - name: Install OS dependencies - if: ${{ matrix.python-version }} == '3.10' run: | sudo apt update sudo apt install -y libre2-dev libpq-dev - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - run: poetry install --no-interaction + if: steps.setup-rye.outputs.cache-hit != 'true' + run: rye sync --no-lock - name: Check formatting & linting run: | - poetry run pre-commit run --all-files + rye run pre-commit run --all-files test: runs-on: ubuntu-latest - strategy: - max-parallel: 4 - matrix: - python-version: ["3.10"] # service containers to run with `postgres-job` services: @@ -69,24 +70,23 @@ jobs: - name: Check out repo uses: actions/checkout@v3 - - name: Install poetry - run: pipx install poetry - - - uses: actions/setup-python@v4 + - name: Install rye + id: setup-rye + uses: eifinger/setup-rye@v4 with: - python-version: ${{ matrix.python-version }} - cache: 'poetry' + version: '0.43.0' + checksum: 'ca702c3d93fd6ec76a1a0efaaa605e10736ee79a0674d241aad1bc0fe26f7d80' + enable-cache: true + cache-prefix: 'rye-cache' - name: Install OS dependencies - if: ${{ matrix.python-version }} == '3.10' run: | sudo apt update sudo apt install -y libre2-dev libpq-dev - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - run: poetry install --no-interaction - + if: steps.setup-rye.outputs.cache-hit != 'true' + run: rye sync --no-lock - name: Start Redis v6 uses: superchargejs/redis-github-action@1.1.0 @@ -95,7 +95,8 @@ jobs: - name: Run db migration run: | - CONFIG=tests/test.env poetry run alembic upgrade head + rye install alembic + CONFIG=tests/test.env rye run alembic upgrade head - name: Prepare version file run: | @@ -104,7 +105,7 @@ jobs: - name: Test with pytest run: | - poetry run pytest + rye run pytest env: GITHUB_ACTIONS_TEST: true diff --git a/app/.python-version b/app/.python-version new file mode 100644 index 0000000..251b350 --- /dev/null +++ b/app/.python-version @@ -0,0 +1 @@ +3.10.16 diff --git a/app/CONTRIBUTING.md b/app/CONTRIBUTING.md index 008ea11..819822d 100644 --- a/app/CONTRIBUTING.md +++ b/app/CONTRIBUTING.md @@ -20,7 +20,7 @@ SimpleLogin backend consists of 2 main components: ## Install dependencies The project requires: -- Python 3.10 and poetry to manage dependencies +- Python 3.10 and rye to manage dependencies - Node v10 for front-end. - Postgres 13+ @@ -28,7 +28,7 @@ First, install all dependencies by running the following command. Feel free to use `virtualenv` or similar tools to isolate development environment. ```bash -poetry sync +rye sync ``` On Mac, sometimes you might need to install some other packages via `brew`: @@ -55,7 +55,7 @@ brew install -s re2 pybind11 We use pre-commit to run all our linting and static analysis checks. Please run ```bash -poetry run pre-commit install +rye run pre-commit install ``` To install it in your development environment. @@ -160,25 +160,25 @@ Here are the small sum-ups of the directory structures and their roles: The code is formatted using [ruff](https://github.com/astral-sh/ruff), to format the code, simply run ``` -poetry run ruff format . +rye run ruff format . ``` The code is also checked with `flake8`, make sure to run `flake8` before creating the pull request by ```bash -poetry run flake8 +rye run flake8 ``` For HTML templates, we use `djlint`. Before creating a pull request, please run ```bash -poetry run djlint --check templates +rye run djlint --check templates ``` If some files aren't properly formatted, you can format all files with ```bash -poetry run djlint --reformat . +rye run djlint --reformat . ``` ## Test sending email @@ -236,18 +236,11 @@ There are several ways to setup Python and manage the project dependencies on Ma # we haven't managed to make python 3.12 work brew install python3.10 -# make sure to update the PATH so python, pip point to Python3 -# for us it can be done by adding "export PATH=/opt/homebrew/opt/python@3.10/libexec/bin:$PATH" to .zprofile +# Install rye using the official installation script, found on https://rye.astral.sh/guide/installation/ -# Although pipx is the recommended way to install poetry, -# install pipx via brew will automatically install python 3.12 -# and poetry will then use python 3.12 -# so we recommend using poetry this way instead -curl -sSL https://install.python-poetry.org | python3 - - -poetry install +# Install the dependencies +rye sync # activate the virtualenv and you should be good to go! source .venv/bin/activate - ``` \ No newline at end of file diff --git a/app/Dockerfile b/app/Dockerfile index 851b7d2..21822ae 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -4,43 +4,45 @@ WORKDIR /code COPY ./static/package*.json /code/static/ RUN cd /code/static && npm ci -# Main image -FROM python:3.10 +FROM --platform=linux/amd64 ubuntu:22.04 + +ARG RYE_VERSION="0.43.0" +ARG RYE_HASH="ca702c3d93fd6ec76a1a0efaaa605e10736ee79a0674d241aad1bc0fe26f7d80" # Keeps Python from generating .pyc files in the container -ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONDONTWRITEBYTECODE=1 # Turns off buffering for easier container logging -ENV PYTHONUNBUFFERED 1 +ENV PYTHONUNBUFFERED=1 -# Add poetry to PATH -ENV PATH="${PATH}:/root/.local/bin" WORKDIR /code -# Copy poetry files -COPY poetry.lock pyproject.toml ./ +# Copy dependency files +COPY pyproject.toml requirements.lock requirements-dev.lock .python-version ./ -# Install and setup poetry -RUN pip install -U pip \ - && apt-get update \ - && apt install -y curl netcat-traditional gcc python3-dev gnupg git libre2-dev cmake ninja-build\ - && curl -sSL https://install.python-poetry.org | python3 - \ - # Remove curl and netcat from the image - && apt-get purge -y curl netcat-traditional \ - # Run poetry - && poetry config virtualenvs.create false \ - && poetry install --no-interaction --no-ansi --no-root \ - # Clear apt cache \ - && apt-get purge -y libre2-dev cmake ninja-build\ +# Install deps +RUN apt-get update \ + && apt-get install -y curl netcat-traditional gcc python3-dev gnupg git libre2-dev build-essential pkg-config cmake ninja-build bash clang \ + && curl -sSL "https://github.com/astral-sh/rye/releases/download/${RYE_VERSION}/rye-x86_64-linux.gz" > rye.gz \ + && echo "${RYE_HASH} rye.gz" | sha256sum -c - \ + && gunzip rye.gz \ + && chmod +x rye \ + && mv rye /usr/bin/rye \ + && rye toolchain fetch `cat .python-version` \ + && rye sync --no-lock --no-dev \ + && apt-get autoremove -y \ + && apt-get purge -y curl netcat-traditional build-essential pkg-config cmake ninja-build python3-dev clang\ + && apt-get autoremove -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +# Copy code +COPY . . + # copy npm packages COPY --from=npm /code /code -# copy everything else into /code -COPY . . - +ENV PATH="/code/.venv/bin:$PATH" EXPOSE 7777 #gunicorn wsgi:app -b 0.0.0.0:7777 -w 2 --timeout 15 --log-level DEBUG diff --git a/app/app/admin_model.py b/app/app/admin_model.py index 2123e36..f49654b 100644 --- a/app/app/admin_model.py +++ b/app/app/admin_model.py @@ -791,7 +791,7 @@ class EmailSearchResult: self.query: str @staticmethod - def from_email(email: str) -> EmailSearchResult: + def from_request_email(email: str) -> EmailSearchResult: output = EmailSearchResult() output.query = email alias = Alias.get_by(email=email) @@ -803,7 +803,11 @@ class EmailSearchResult: .all() ) output.no_match = False - user = User.get_by(email=email) + try: + user_id = int(email) + user = User.get(user_id) + except ValueError: + user = User.get_by(email=email) if user: output.user = user output.user_audit_log = ( @@ -912,7 +916,7 @@ class EmailSearchAdmin(BaseView): email = request.args.get("email") if email is not None and len(email) > 0: email = email.strip() - search = EmailSearchResult.from_email(email) + search = EmailSearchResult.from_request_email(email) return self.render( "admin/email_search.html", diff --git a/app/app/models.py b/app/app/models.py index e5f6453..32d017e 100644 --- a/app/app/models.py +++ b/app/app/models.py @@ -343,7 +343,7 @@ class Fido(Base, ModelMixin): class User(Base, ModelMixin, UserMixin, PasswordOracle): __tablename__ = "users" - FLAG_DISABLE_CREATE_CONTACTS = 1 << 0 + FLAG_FREE_DISABLE_CREATE_CONTACTS = 1 << 0 FLAG_CREATED_FROM_PARTNER = 1 << 1 FLAG_FREE_OLD_ALIAS_LIMIT = 1 << 2 FLAG_CREATED_ALIAS_FROM_PARTNER = 1 << 3 @@ -550,7 +550,7 @@ class User(Base, ModelMixin, UserMixin, PasswordOracle): # bitwise flags. Allow for future expansion flags = sa.Column( sa.BigInteger, - default=FLAG_DISABLE_CREATE_CONTACTS, + default=FLAG_FREE_DISABLE_CREATE_CONTACTS, server_default="0", nullable=False, ) @@ -640,7 +640,7 @@ class User(Base, ModelMixin, UserMixin, PasswordOracle): # If the user is created from partner, do not notify # nor give a trial if from_partner: - user.flags = User.FLAG_CREATED_FROM_PARTNER + user.flags = user.flags | User.FLAG_CREATED_FROM_PARTNER user.notification = False user.trial_end = None Job.create( @@ -1189,7 +1189,7 @@ class User(Base, ModelMixin, UserMixin, PasswordOracle): def can_create_contacts(self) -> bool: if self.is_premium(): return True - if self.flags & User.FLAG_DISABLE_CREATE_CONTACTS == 0: + if self.flags & User.FLAG_FREE_DISABLE_CREATE_CONTACTS == 0: return True return not config.DISABLE_CREATE_CONTACTS_FOR_FREE_USERS diff --git a/app/email_handler.py b/app/email_handler.py index 9eb88ba..da273f9 100644 --- a/app/email_handler.py +++ b/app/email_handler.py @@ -600,6 +600,17 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str else: reply_to_contact = get_or_create_reply_to_contact(reply_to, alias, msg) + if alias.user.delete_on is not None: + LOG.d(f"user {user} is pending to be deleted. Do not forward") + EmailLog.create( + contact_id=contact.id, + user_id=contact.user_id, + blocked=True, + alias_id=contact.alias_id, + commit=True, + ) + return [(True, status.E502)] + if not alias.enabled or contact.block_forward: LOG.d("%s is disabled, do not forward", alias) EmailLog.create( diff --git a/app/pyproject.toml b/app/pyproject.toml index a09e019..3e9409b 100644 --- a/app/pyproject.toml +++ b/app/pyproject.toml @@ -1,20 +1,100 @@ +[project] +name = "SimpleLogin" +version = "0.1.0" +description = "SimpleLogin partner API" +authors = [ {name="SimpleLogin", email="dev@simplelogin.io"}] +license = "MIT" +repository = "https://github.com/simple-login/app" +keywords = ["email", "alias", "privacy", "oauth2", "openid"] +packages = [ + { include = "app/" }, + { include = "migrations/" }, +] +include = ["templates/*", "templates/**/*", "local_data/*.txt"] + +requires-python = "~=3.10" + +dependencies = [ + "flask ~= 1.1.2", + "flask_login ~= 0.5.0", + "wtforms ~= 2.3.3", + "unidecode ~= 1.1.1", + "gunicorn ~= 20.0.4", + "bcrypt ~= 3.2.0", + "python-dotenv ~= 0.14.0", + "ipython ~= 7.31.1", + "sqlalchemy_utils ~= 0.36.8", + "psycopg2-binary ~= 2.9.3", + "sentry_sdk ~= 2.20.0", + "blinker ~= 1.4", + "arrow ~= 0.16.0", + "Flask-WTF ~= 0.14.3", + "boto3 ~= 1.35.37", + "Flask-Migrate ~= 2.5.3", + "flask_admin ~= 1.5.6", + "flask-cors ~= 3.0.9", + "watchtower ~= 0.8.0", + "sqlalchemy-utils == 0.36.8", + "jwcrypto ~= 0.8", + "yacron>=0.19.0", + "flask-debugtoolbar ~= 0.11.0", + "requests_oauthlib ~= 1.3.0", + "pyopenssl ~= 19.1.0", + "aiosmtpd ~= 1.2", + "dnspython == 2.6.1", + "coloredlogs ~= 14.0", + "pycryptodome ~= 3.9.8", + "phpserialize ~= 1.3", + "dkimpy ~= 1.0.5", + "pyotp ~= 2.4.0", + "flask_profiler ~= 1.8.1", + "facebook-sdk ~= 3.1.0", + "google-api-python-client ~= 1.12.3", + "google-auth-httplib2 ~= 0.0.4", + "python-gnupg ~= 0.4.6", + "webauthn ~= 0.4.7", + "pyspf ~= 2.0.14", + "Flask-Limiter == 1.4", + "memory_profiler ~= 0.57.0", + "gevent ~= 24.11.1", + "email-validator ~= 1.1.3", + "PGPy == 0.5.4", + "coinbase-commerce ~= 1.0.1", + "requests ~= 2.25.1", + "newrelic ~= 8.8.0", + "flanker ~= 0.9.11", + "pyre2 ~= 0.3.6", + "tldextract ~= 3.1.2", + "flask-debugtoolbar-sqlalchemy ~= 0.2.0", + "twilio ~= 7.3.2", + "Deprecated ~= 1.2.13", + "MarkupSafe~=1.1.1", + "cryptography ~= 37.0.1", + "SQLAlchemy ~= 1.3.24", + "redis ~= 4.5.3", + "newrelic-telemetry-sdk ~= 0.5.0", + "aiospamc == 0.10", + "itsdangerous ~= 1.1.0", + "werkzeug ~= 1.0.1", +] + [tool.black] target-version = ['py310'] exclude = ''' ( - /( - \.eggs # exclude a few common directories in the - | \.git # root of the project - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - | migrations # migrations/ is generated by alembic - | app/events/generated + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + | migrations # migrations/ is generated by alembic + | app/events/generated )/ ) ''' @@ -27,7 +107,6 @@ exclude = [".venv", "migrations", "app/events/generated"] indent = 2 profile = "jinja" blank_line_after_tag = "if,for,include,load,extends,block,endcall" - # H006: Images should have a height attribute # H013: Images should have an alt attribute # H016: Missing title tag in html. | False positive on template @@ -43,92 +122,26 @@ blank_line_after_tag = "if,for,include,load,extends,block,endcall" # T001: Variables should be wrapped in a single whitespace. | Messes up with comments ignore = "H006,H013,H016,H017,H019,H021,H025,H030,H031,T003,J004,J018,T001" -[tool.poetry] -name = "SimpleLogin" -version = "0.1.0" -description = "open-source email alias solution" -authors = ["SimpleLogin "] -license = "MIT" -repository = "https://github.com/simple-login/app" -keywords = ["email", "alias", "privacy", "oauth2", "openid"] -packages = [ - { include = "app/" }, - { include = "migrations/" }, +[tool.uv] +dev-dependencies = [ + "pytest ~= 7.0.0", + "pytest-cov ~= 3.0.0", + "pre-commit ~= 2.17.0", + "black ~= 22.1.0", + "djlint ~= 1.3.0", + "pylint ~= 2.14.4", + "ruff ~= 0.1.5", ] -include = ["templates/*", "templates/**/*", "local_data/*.txt"] - -[tool.poetry.dependencies] -python = "^3.10" -flask = "^1.1.2" -flask_login = "^0.5.0" -wtforms = "^2.3.3" -unidecode = "^1.1.1" -gunicorn = "^20.0.4" -bcrypt = "^3.2.0" -python-dotenv = "^0.14.0" -ipython = "^7.31.1" -sqlalchemy_utils = "^0.36.8" -psycopg2-binary = "^2.9.3" -sentry_sdk = "^2.16.0" -blinker = "^1.4" -arrow = "^0.16.0" -Flask-WTF = "^0.14.3" -boto3 = "^1.15.9" -Flask-Migrate = "^2.5.3" -flask_admin = "^1.5.6" -flask-cors = "^3.0.9" -watchtower = "^0.8.0" -sqlalchemy-utils = "^0.36.8" -jwcrypto = "^0.8" -yacron = "^0.11.1" -flask-debugtoolbar = "^0.11.0" -requests_oauthlib = "^1.3.0" -pyopenssl = "^19.1.0" -aiosmtpd = "^1.2" -dnspython = "^2.0.0" -coloredlogs = "^14.0" -pycryptodome = "^3.9.8" -phpserialize = "^1.3" -dkimpy = "^1.0.5" -pyotp = "^2.4.0" -flask_profiler = "^1.8.1" -facebook-sdk = "^3.1.0" -google-api-python-client = "^1.12.3" -google-auth-httplib2 = "^0.0.4" -python-gnupg = "^0.4.6" -webauthn = "^0.4.7" -pyspf = "^2.0.14" -Flask-Limiter = "^1.4" -memory_profiler = "^0.57.0" -gevent = "22.10.2" -email_validator = "^1.1.1" -PGPy = "0.5.4" -coinbase-commerce = "^1.0.1" -requests = "^2.25.1" -newrelic = "8.8.0" -flanker = "^0.9.11" -pyre2 = "^0.3.6" -tldextract = "^3.1.2" -flask-debugtoolbar-sqlalchemy = "^0.2.0" -twilio = "^7.3.2" -Deprecated = "^1.2.13" -cryptography = "37.0.1" -SQLAlchemy = "1.3.24" -redis = "^4.5.3" -newrelic-telemetry-sdk = "^0.5.0" -aiospamc = "0.10" - -[tool.poetry.dev-dependencies] -pytest = "^7.0.0" -pytest-cov = "^3.0.0" -black = "^22.1.0" -djlint = "^1.3.0" -pylint = "^2.14.4" - -[tool.poetry.group.dev.dependencies] -ruff = "^0.1.5" -pre-commit = "^3.8.0" [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.sdist] +include = ["app", "local_data", "migrations", "templates"] + +[tool.hatch.build.targets.wheel] +packages = ["app", "local_data", "migrations", "templates"] diff --git a/app/requirements-dev.lock b/app/requirements-dev.lock new file mode 100644 index 0000000..1e5c91f --- /dev/null +++ b/app/requirements-dev.lock @@ -0,0 +1,469 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false +# generate-hashes: false +# universal: false + +-e file:. +aiohappyeyeballs==2.4.4 + # via aiohttp +aiohttp==3.11.11 + # via yacron +aiosignal==1.3.2 + # via aiohttp +aiosmtpd==1.4.6 + # via simplelogin +aiosmtplib==3.0.2 + # via yacron +aiospamc==0.10.0 + # via simplelogin +alembic==1.14.0 + # via flask-migrate +appnope==0.1.4 + # via ipython +arrow==0.16.0 + # via simplelogin +astroid==2.11.7 + # via pylint +async-timeout==5.0.1 + # via aiohttp + # via redis +atpublic==5.0 + # via aiosmtpd +attrs==24.3.0 + # via aiohttp + # via aiosmtpd + # via flanker + # via pytest +backcall==0.2.0 + # via ipython +bcrypt==3.2.2 + # via simplelogin +black==22.1.0 +blinker==1.9.0 + # via flask-debugtoolbar + # via simplelogin +boto3==1.35.99 + # via simplelogin + # via watchtower +botocore==1.35.99 + # via boto3 + # via s3transfer +cachetools==5.5.0 + # via google-auth +cbor2==5.6.5 + # via webauthn +certifi==2024.12.14 + # via aiospamc + # via requests + # via sentry-sdk +cffi==1.17.1 + # via bcrypt + # via cryptography +cfgv==3.4.0 + # via pre-commit +chardet==4.0.0 + # via flanker + # via requests +click==8.1.8 + # via black + # via djlint + # via flask + # via typer +coinbase-commerce==1.0.1 + # via simplelogin +colorama==0.4.6 + # via djlint +coloredlogs==14.3 + # via simplelogin +coverage==7.6.10 + # via pytest-cov +crontab==0.22.8 + # via yacron +cryptography==37.0.4 + # via flanker + # via jwcrypto + # via pgpy + # via pyopenssl + # via simplelogin + # via webauthn +decorator==5.1.1 + # via ipython +deprecated==1.2.15 + # via jwcrypto + # via limits + # via simplelogin +dill==0.3.9 + # via pylint +distlib==0.3.9 + # via virtualenv +djlint==1.3.0 +dkimpy==1.0.6 + # via simplelogin +dnspython==2.6.1 + # via dkimpy + # via email-validator + # via simplelogin +email-validator==1.1.3 + # via simplelogin +facebook-sdk==3.1.0 + # via simplelogin +filelock==3.16.1 + # via tldextract + # via virtualenv +flanker==0.9.11 + # via simplelogin +flask==1.1.2 + # via flask-admin + # via flask-cors + # via flask-debugtoolbar + # via flask-httpauth + # via flask-limiter + # via flask-login + # via flask-migrate + # via flask-profiler + # via flask-sqlalchemy + # via flask-wtf + # via simplelogin +flask-admin==1.5.8 + # via simplelogin +flask-cors==3.0.10 + # via simplelogin +flask-debugtoolbar==0.11.0 + # via flask-debugtoolbar-sqlalchemy + # via simplelogin +flask-debugtoolbar-sqlalchemy==0.2.0 + # via simplelogin +flask-httpauth==4.8.0 + # via flask-profiler +flask-limiter==1.4 + # via simplelogin +flask-login==0.5.0 + # via simplelogin +flask-migrate==2.5.3 + # via simplelogin +flask-profiler==1.8.1 + # via simplelogin +flask-sqlalchemy==2.5.1 + # via flask-migrate +flask-wtf==0.14.3 + # via simplelogin +frozenlist==1.5.0 + # via aiohttp + # via aiosignal +future==1.0.0 + # via webauthn +gevent==24.11.1 + # via simplelogin +google-api-core==2.24.0 + # via google-api-python-client +google-api-python-client==1.12.11 + # via simplelogin +google-auth==2.37.0 + # via google-api-core + # via google-api-python-client + # via google-auth-httplib2 +google-auth-httplib2==0.0.4 + # via google-api-python-client + # via simplelogin +googleapis-common-protos==1.66.0 + # via google-api-core +greenlet==3.1.1 + # via gevent +gunicorn==20.0.4 + # via simplelogin +html-tag-names==0.1.2 + # via djlint +html-void-elements==0.1.0 + # via djlint +httplib2==0.22.0 + # via google-api-python-client + # via google-auth-httplib2 +humanfriendly==10.0 + # via coloredlogs +identify==2.6.5 + # via pre-commit +idna==2.10 + # via email-validator + # via flanker + # via requests + # via tldextract + # via yarl +importlib-metadata==4.13.0 + # via djlint +iniconfig==2.0.0 + # via pytest +ipython==7.31.1 + # via simplelogin +isort==5.13.2 + # via pylint +itsdangerous==1.1.0 + # via flask + # via flask-debugtoolbar + # via flask-wtf + # via simplelogin +jedi==0.19.2 + # via ipython +jinja2==2.11.3 + # via flask + # via yacron +jmespath==1.0.1 + # via boto3 + # via botocore +jwcrypto==0.9.1 + # via simplelogin +lazy-object-proxy==1.10.0 + # via astroid +limits==4.0.0 + # via flask-limiter +loguru==0.7.3 + # via aiospamc +mako==1.3.8 + # via alembic +markupsafe==1.1.1 + # via jinja2 + # via mako + # via simplelogin + # via wtforms +matplotlib-inline==0.1.7 + # via ipython +mccabe==0.7.0 + # via pylint +memory-profiler==0.57.0 + # via simplelogin +multidict==6.1.0 + # via aiohttp + # via yarl +mypy-extensions==1.0.0 + # via black +newrelic==8.8.1 + # via simplelogin +newrelic-telemetry-sdk==0.5.1 + # via simplelogin +nodeenv==1.9.1 + # via pre-commit +oauthlib==3.2.2 + # via requests-oauthlib +packaging==24.2 + # via limits + # via pytest +parso==0.8.4 + # via jedi +pathspec==0.9.0 + # via black + # via djlint +pexpect==4.9.0 + # via ipython +pgpy==0.5.4 + # via simplelogin +phpserialize==1.3 + # via simplelogin +pickleshare==0.7.5 + # via ipython +platformdirs==4.3.6 + # via black + # via pylint + # via virtualenv +pluggy==1.5.0 + # via pytest +ply==3.11 + # via flanker +pre-commit==2.17.0 +prompt-toolkit==3.0.48 + # via ipython +propcache==0.2.1 + # via aiohttp + # via yarl +proto-plus==1.25.0 + # via google-api-core +protobuf==5.29.3 + # via google-api-core + # via googleapis-common-protos + # via proto-plus +psutil==6.1.1 + # via memory-profiler +psycopg2-binary==2.9.10 + # via simplelogin +ptyprocess==0.7.0 + # via pexpect +py==1.11.0 + # via pytest +pyasn1==0.6.1 + # via pgpy + # via pyasn1-modules + # via rsa +pyasn1-modules==0.4.1 + # via google-auth +pycparser==2.22 + # via cffi +pycryptodome==3.9.9 + # via simplelogin +pygments==2.19.1 + # via flask-debugtoolbar-sqlalchemy + # via ipython +pyjwt==2.10.1 + # via twilio +pylint==2.14.5 +pyopenssl==19.1.0 + # via simplelogin + # via webauthn +pyotp==2.4.1 + # via simplelogin +pyparsing==3.2.1 + # via httplib2 +pyre2==0.3.6 + # via simplelogin +pyspf==2.0.14 + # via simplelogin +pytest==7.0.1 + # via pytest-cov +pytest-cov==3.0.0 +python-dateutil==2.9.0.post0 + # via arrow + # via botocore + # via strictyaml +python-dotenv==0.14.0 + # via simplelogin +python-gnupg==0.4.9 + # via simplelogin +pytz==2024.2 + # via twilio + # via yacron +pyyaml==6.0.2 + # via djlint + # via pre-commit +redis==4.5.5 + # via simplelogin +regex==2022.10.31 + # via djlint + # via flanker +requests==2.25.1 + # via coinbase-commerce + # via facebook-sdk + # via google-api-core + # via requests-file + # via requests-oauthlib + # via simplelogin + # via tldextract + # via twilio +requests-file==2.1.0 + # via tldextract +requests-oauthlib==1.3.1 + # via simplelogin +rsa==4.9 + # via google-auth +ruamel-yaml==0.17.4 + # via yacron +ruff==0.1.15 +s3transfer==0.10.4 + # via boto3 +sentry-sdk==2.20.0 + # via simplelogin + # via yacron +setuptools==75.8.0 + # via astroid + # via gunicorn + # via ipython + # via zope-event + # via zope-interface +simplejson==3.19.3 + # via flask-profiler +six==1.17.0 + # via coinbase-commerce + # via flanker + # via flask-cors + # via flask-limiter + # via google-api-python-client + # via google-auth-httplib2 + # via jwcrypto + # via pgpy + # via pyopenssl + # via python-dateutil + # via sqlalchemy-utils + # via webauthn +sqlalchemy==1.3.24 + # via alembic + # via flask-debugtoolbar-sqlalchemy + # via flask-sqlalchemy + # via simplelogin + # via sqlalchemy-utils +sqlalchemy-utils==0.36.8 + # via simplelogin +sqlparse==0.5.3 + # via flask-debugtoolbar-sqlalchemy +strictyaml==1.7.3 + # via yacron +tld==0.13 + # via flanker +tldextract==3.1.2 + # via simplelogin +toml==0.10.2 + # via pre-commit +tomli==2.2.1 + # via black + # via coverage + # via djlint + # via pylint + # via pytest +tomlkit==0.13.2 + # via pylint +tqdm==4.67.1 + # via djlint +traitlets==5.14.3 + # via ipython + # via matplotlib-inline +twilio==7.3.2 + # via simplelogin +typer==0.9.4 + # via aiospamc +typing-extensions==4.12.2 + # via aiospamc + # via alembic + # via limits + # via multidict + # via typer +unidecode==1.1.2 + # via simplelogin +uritemplate==3.0.1 + # via google-api-python-client +urllib3==1.26.20 + # via botocore + # via newrelic-telemetry-sdk + # via requests + # via sentry-sdk +virtualenv==20.29.0 + # via pre-commit +watchtower==0.8.0 + # via simplelogin +wcwidth==0.2.13 + # via prompt-toolkit +webauthn==0.4.7 + # via simplelogin +webob==1.8.9 + # via flanker +werkzeug==1.0.1 + # via flask + # via flask-debugtoolbar + # via simplelogin +wrapt==1.17.2 + # via astroid + # via deprecated +wtforms==2.3.3 + # via flask-admin + # via flask-wtf + # via simplelogin +yacron==0.19.0 + # via simplelogin +yarl==1.18.3 + # via aiohttp +zipp==3.21.0 + # via importlib-metadata +zope-event==5.0 + # via gevent +zope-interface==7.2 + # via gevent diff --git a/app/requirements.lock b/app/requirements.lock new file mode 100644 index 0000000..449c9be --- /dev/null +++ b/app/requirements.lock @@ -0,0 +1,392 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false +# generate-hashes: false +# universal: false + +-e file:. +aiohttp==3.8.4 + # via google-auth + # via yacron +aiosignal==1.2.0 + # via aiohttp +aiosmtpd==1.4.2 + # via simplelogin +aiosmtplib==1.1.4 + # via yacron +aiospamc==0.10.0 + # via simplelogin +alembic==1.4.3 + # via flask-migrate +appnope==0.1.0 + # via ipython +arrow==0.16.0 + # via simplelogin +async-timeout==4.0.2 + # via aiohttp + # via redis +atpublic==2.0 + # via aiosmtpd +attrs==20.2.0 + # via aiohttp + # via aiosmtpd + # via flanker +backcall==0.2.0 + # via ipython +bcrypt==3.2.0 + # via simplelogin +blinker==1.4 + # via flask-debugtoolbar + # via simplelogin +boto3==1.35.99 + # via simplelogin + # via watchtower +botocore==1.35.99 + # via boto3 + # via s3transfer +cachetools==4.1.1 + # via google-auth +cbor2==5.2.0 + # via webauthn +certifi==2019.11.28 + # via aiospamc + # via requests + # via sentry-sdk +cffi==1.14.4 + # via bcrypt + # via cryptography +chardet==3.0.4 + # via flanker + # via requests +charset-normalizer==3.4.1 + # via aiohttp +click==8.0.3 + # via flask + # via typer +coinbase-commerce==1.0.1 + # via simplelogin +coloredlogs==14.0 + # via simplelogin +crontab==0.22.8 + # via yacron +cryptography==37.0.1 + # via flanker + # via jwcrypto + # via pgpy + # via pyopenssl + # via simplelogin + # via webauthn +decorator==4.4.2 + # via ipython +deprecated==1.2.13 + # via simplelogin +dkimpy==1.0.5 + # via simplelogin +dnspython==2.6.1 + # via dkimpy + # via email-validator + # via simplelogin +email-validator==1.1.3 + # via simplelogin +facebook-sdk==3.1.0 + # via simplelogin +filelock==3.15.4 + # via tldextract +flanker==0.9.11 + # via simplelogin +flask==1.1.2 + # via flask-admin + # via flask-cors + # via flask-debugtoolbar + # via flask-httpauth + # via flask-limiter + # via flask-login + # via flask-migrate + # via flask-profiler + # via flask-sqlalchemy + # via flask-wtf + # via simplelogin +flask-admin==1.5.7 + # via simplelogin +flask-cors==3.0.9 + # via simplelogin +flask-debugtoolbar==0.11.0 + # via flask-debugtoolbar-sqlalchemy + # via simplelogin +flask-debugtoolbar-sqlalchemy==0.2.0 + # via simplelogin +flask-httpauth==4.1.0 + # via flask-profiler +flask-limiter==1.4 + # via simplelogin +flask-login==0.5.0 + # via simplelogin +flask-migrate==2.5.3 + # via simplelogin +flask-profiler==1.8.1 + # via simplelogin +flask-sqlalchemy==2.5.1 + # via flask-migrate +flask-wtf==0.14.3 + # via simplelogin +frozenlist==1.3.3 + # via aiohttp + # via aiosignal +future==0.18.3 + # via webauthn +gevent==24.11.1 + # via simplelogin +google-api-core==1.22.2 + # via google-api-python-client +google-api-python-client==1.12.3 + # via simplelogin +google-auth==1.22.0 + # via google-api-core + # via google-api-python-client + # via google-auth-httplib2 +google-auth-httplib2==0.0.4 + # via google-api-python-client + # via simplelogin +googleapis-common-protos==1.52.0 + # via google-api-core +greenlet==3.1.1 + # via gevent +gunicorn==20.0.4 + # via simplelogin +httplib2==0.22.0 + # via google-api-python-client + # via google-auth-httplib2 +humanfriendly==8.2 + # via coloredlogs +idna==2.10 + # via email-validator + # via flanker + # via requests + # via tldextract + # via yarl +ipython==7.31.1 + # via simplelogin +ipython-genutils==0.2.0 + # via traitlets +itsdangerous==1.1.0 + # via flask + # via flask-debugtoolbar + # via flask-wtf + # via simplelogin +jedi==0.17.2 + # via ipython +jinja2==2.11.3 + # via flask + # via yacron +jmespath==0.10.0 + # via boto3 + # via botocore +jwcrypto==0.8 + # via simplelogin +limits==1.5.1 + # via flask-limiter +loguru==0.7.2 + # via aiospamc +mako==1.2.4 + # via alembic +markupsafe==1.1.1 + # via jinja2 + # via mako + # via simplelogin + # via wtforms +matplotlib-inline==0.1.3 + # via ipython +memory-profiler==0.57.0 + # via simplelogin +multidict==4.7.6 + # via aiohttp + # via yarl +newrelic==8.8.0 + # via simplelogin +newrelic-telemetry-sdk==0.5.0 + # via simplelogin +oauthlib==3.1.0 + # via requests-oauthlib +parso==0.7.1 + # via jedi +pexpect==4.8.0 + # via ipython +pgpy==0.5.4 + # via simplelogin +phpserialize==1.3 + # via simplelogin +pickleshare==0.7.5 + # via ipython +ply==3.11 + # via flanker +prompt-toolkit==3.0.7 + # via ipython +protobuf==5.27.1 + # via google-api-core + # via googleapis-common-protos +psutil==5.7.2 + # via memory-profiler +psycopg2-binary==2.9.3 + # via simplelogin +ptyprocess==0.6.0 + # via pexpect +pyasn1==0.4.8 + # via pgpy + # via pyasn1-modules + # via rsa +pyasn1-modules==0.2.8 + # via google-auth +pycparser==2.20 + # via cffi +pycryptodome==3.9.8 + # via simplelogin +pygments==2.7.4 + # via flask-debugtoolbar-sqlalchemy + # via ipython +pyjwt==2.4.0 + # via twilio +pyopenssl==19.1.0 + # via simplelogin + # via webauthn +pyotp==2.4.0 + # via simplelogin +pyparsing==2.4.7 + # via httplib2 +pyre2==0.3.6 + # via simplelogin +pyspf==2.0.14 + # via simplelogin +python-dateutil==2.8.1 + # via alembic + # via arrow + # via botocore + # via strictyaml +python-dotenv==0.14.0 + # via simplelogin +python-editor==1.0.4 + # via alembic +python-gnupg==0.4.6 + # via simplelogin +pytz==2020.1 + # via google-api-core + # via twilio + # via yacron +redis==4.5.5 + # via simplelogin +regex==2023.12.25 + # via flanker +requests==2.25.1 + # via coinbase-commerce + # via facebook-sdk + # via google-api-core + # via requests-file + # via requests-oauthlib + # via simplelogin + # via tldextract + # via twilio +requests-file==1.5.1 + # via tldextract +requests-oauthlib==1.3.0 + # via simplelogin +rsa==4.6 + # via google-auth +ruamel-yaml==0.17.4 + # via strictyaml + # via yacron +s3transfer==0.10.4 + # via boto3 +sentry-sdk==2.20.0 + # via simplelogin + # via yacron +setuptools==67.6.0 + # via google-api-core + # via google-auth + # via gunicorn + # via ipython + # via zope-event + # via zope-interface +simplejson==3.17.2 + # via flask-profiler +six==1.15.0 + # via bcrypt + # via coinbase-commerce + # via flanker + # via flask-cors + # via flask-limiter + # via google-api-core + # via google-api-python-client + # via google-auth + # via google-auth-httplib2 + # via limits + # via pgpy + # via pyopenssl + # via python-dateutil + # via requests-file + # via sqlalchemy-utils + # via webauthn +sqlalchemy==1.3.24 + # via alembic + # via flask-debugtoolbar-sqlalchemy + # via flask-sqlalchemy + # via simplelogin + # via sqlalchemy-utils +sqlalchemy-utils==0.36.8 + # via simplelogin +sqlparse==0.4.4 + # via flask-debugtoolbar-sqlalchemy +strictyaml==1.1.0 + # via yacron +tld==0.12.6 + # via flanker +tldextract==3.1.2 + # via simplelogin +traitlets==5.0.4 + # via ipython + # via matplotlib-inline +twilio==7.3.2 + # via simplelogin +typer==0.9.0 + # via aiospamc +typing-extensions==4.8.0 + # via aiospamc + # via typer +unidecode==1.1.1 + # via simplelogin +uritemplate==3.0.1 + # via google-api-python-client +urllib3==1.26.20 + # via botocore + # via newrelic-telemetry-sdk + # via requests + # via sentry-sdk +watchtower==0.8.0 + # via simplelogin +wcwidth==0.2.5 + # via prompt-toolkit +webauthn==0.4.7 + # via simplelogin +webob==1.8.7 + # via flanker +werkzeug==1.0.1 + # via flask + # via flask-debugtoolbar + # via simplelogin +wrapt==1.15.0 + # via deprecated +wtforms==2.3.3 + # via flask-admin + # via flask-wtf + # via simplelogin +yacron==0.19.0 + # via simplelogin +yarl==1.9.2 + # via aiohttp +zope-event==5.0 + # via gevent +zope-interface==7.2 + # via gevent diff --git a/app/scripts/new-migration.sh b/app/scripts/new-migration.sh index da11a75..5456857 100755 --- a/app/scripts/new-migration.sh +++ b/app/scripts/new-migration.sh @@ -12,10 +12,10 @@ docker run -p 25432:5432 --name ${container_name} -e POSTGRES_PASSWORD=postgres sleep 3 # upgrade the DB to the latest stage and -env DB_URI=postgresql://postgres:postgres@127.0.0.1:25432/sl poetry run alembic upgrade head +env DB_URI=postgresql://postgres:postgres@127.0.0.1:25432/sl rye run alembic upgrade head # generate the migration script. -env DB_URI=postgresql://postgres:postgres@127.0.0.1:25432/sl poetry run alembic revision --autogenerate $@ +env DB_URI=postgresql://postgres:postgres@127.0.0.1:25432/sl rye run alembic revision --autogenerate $@ # remove the db docker rm -f ${container_name} diff --git a/app/scripts/reset_local_db.sh b/app/scripts/reset_local_db.sh index 422c2a8..42dae95 100755 --- a/app/scripts/reset_local_db.sh +++ b/app/scripts/reset_local_db.sh @@ -3,5 +3,5 @@ export DB_URI=postgresql://myuser:mypassword@localhost:15432/simplelogin echo 'drop schema public cascade; create schema public;' | psql $DB_URI -poetry run alembic upgrade head -poetry run flask dummy-data +rye run alembic upgrade head +rye run flask dummy-data diff --git a/app/scripts/reset_test_db.sh b/app/scripts/reset_test_db.sh index 2546601..234cef3 100755 --- a/app/scripts/reset_test_db.sh +++ b/app/scripts/reset_test_db.sh @@ -3,4 +3,4 @@ export DB_URI=postgresql://myuser:mypassword@localhost:15432/test echo 'drop schema public cascade; create schema public;' | psql $DB_URI -poetry run alembic upgrade head +rye run alembic upgrade head diff --git a/app/scripts/run-test.sh b/app/scripts/run-test.sh index b403b76..531a9aa 100755 --- a/app/scripts/run-test.sh +++ b/app/scripts/run-test.sh @@ -10,10 +10,10 @@ docker run -d --name sl-test-db -e POSTGRES_PASSWORD=test -e POSTGRES_USER=test sleep 3 # migrate the DB to the latest version -CONFIG=tests/test.env poetry run alembic upgrade head +CONFIG=tests/test.env rye run alembic upgrade head # run test -poetry run pytest -c pytest.ci.ini +rye run pytest -c pytest.ci.ini # Delete the test DB docker rm -f sl-test-db diff --git a/app/tests/api/test_alias.py b/app/tests/api/test_alias.py index dd39e13..010b396 100644 --- a/app/tests/api/test_alias.py +++ b/app/tests/api/test_alias.py @@ -549,7 +549,7 @@ def test_create_contact_route_free_users(flask_client): assert r.status_code == 201 # End trial and disallow for new free users. Config should allow it - user.flags = User.FLAG_DISABLE_CREATE_CONTACTS + user.flags = User.FLAG_FREE_DISABLE_CREATE_CONTACTS Session.commit() r = flask_client.post( url_for("api.create_contact_route", alias_id=alias.id), diff --git a/app/tests/test_contact_utils.py b/app/tests/test_contact_utils.py index 0b4b985..d0fa574 100644 --- a/app/tests/test_contact_utils.py +++ b/app/tests/test_contact_utils.py @@ -135,7 +135,7 @@ def test_create_contact_free_user(): assert result.contact is not None assert not result.contact.automatic_created # Free users with the flag should be able to still create automatic emails - user.flags = User.FLAG_DISABLE_CREATE_CONTACTS + user.flags = User.FLAG_FREE_DISABLE_CREATE_CONTACTS Session.flush() result = create_contact(random_email(), alias, automatic_created=True) assert result.error is None diff --git a/app/tests/test_email_handler.py b/app/tests/test_email_handler.py index 1c9c650..e77a971 100644 --- a/app/tests/test_email_handler.py +++ b/app/tests/test_email_handler.py @@ -2,6 +2,7 @@ import random from email.message import EmailMessage from typing import List +import arrow import pytest from aiosmtpd.smtp import Envelope @@ -387,3 +388,15 @@ def test_preserve_headers(flask_client): msg = sent_mails[0].msg for header in headers_to_keep: assert msg[header] == header + "keep" + + +def test_not_send_to_pending_to_delete_users(flask_client): + user = create_new_user() + alias = Alias.create_new_random(user) + user.delete_on = arrow.utcnow() + envelope = Envelope() + envelope.mail_from = "somewhere@lo.cal" + envelope.rcpt_tos = [alias.email] + msg = EmailMessage() + result = email_handler.handle(envelope, msg) + assert result == status.E504