4.63.0
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m54s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 23m12s
Build-Release-Image / Merge-Images (push) Successful in 46s
Build-Release-Image / Create-Release (push) Successful in 9s
Build-Release-Image / Notify (push) Successful in 3s

This commit is contained in:
MrMeeb 2025-01-20 12:00:06 +00:00
parent 33f0eb6c41
commit 664cd32f81
17 changed files with 1083 additions and 184 deletions

View File

@ -1,6 +1,12 @@
name: Test and lint name: SimpleLogin actions
on: [push, pull_request] on:
push:
branches:
- master
tags:
- v*
pull_request:
jobs: jobs:
lint: lint:
@ -9,35 +15,30 @@ jobs:
- name: Check out repo - name: Check out repo
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install poetry - name: "Install rye"
run: pipx install poetry id: setup-rye
uses: eifinger/setup-rye@v4
- uses: actions/setup-python@v4
with: with:
python-version: '3.10' version: '0.43.0'
cache: 'poetry' checksum: 'ca702c3d93fd6ec76a1a0efaaa605e10736ee79a0674d241aad1bc0fe26f7d80'
enable-cache: true
- name: Install OS dependencies - name: Install OS dependencies
if: ${{ matrix.python-version }} == '3.10'
run: | run: |
sudo apt update sudo apt update
sudo apt install -y libre2-dev libpq-dev sudo apt install -y libre2-dev libpq-dev
- name: Install dependencies - name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' if: steps.setup-rye.outputs.cache-hit != 'true'
run: poetry install --no-interaction run: rye sync --no-lock
- name: Check formatting & linting - name: Check formatting & linting
run: | run: |
poetry run pre-commit run --all-files rye run pre-commit run --all-files
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: ["3.10"]
# service containers to run with `postgres-job` # service containers to run with `postgres-job`
services: services:
@ -69,24 +70,23 @@ jobs:
- name: Check out repo - name: Check out repo
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install poetry - name: Install rye
run: pipx install poetry id: setup-rye
uses: eifinger/setup-rye@v4
- uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} version: '0.43.0'
cache: 'poetry' checksum: 'ca702c3d93fd6ec76a1a0efaaa605e10736ee79a0674d241aad1bc0fe26f7d80'
enable-cache: true
cache-prefix: 'rye-cache'
- name: Install OS dependencies - name: Install OS dependencies
if: ${{ matrix.python-version }} == '3.10'
run: | run: |
sudo apt update sudo apt update
sudo apt install -y libre2-dev libpq-dev sudo apt install -y libre2-dev libpq-dev
- name: Install dependencies - name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' if: steps.setup-rye.outputs.cache-hit != 'true'
run: poetry install --no-interaction run: rye sync --no-lock
- name: Start Redis v6 - name: Start Redis v6
uses: superchargejs/redis-github-action@1.1.0 uses: superchargejs/redis-github-action@1.1.0
@ -95,7 +95,8 @@ jobs:
- name: Run db migration - name: Run db migration
run: | 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 - name: Prepare version file
run: | run: |
@ -104,7 +105,7 @@ jobs:
- name: Test with pytest - name: Test with pytest
run: | run: |
poetry run pytest rye run pytest
env: env:
GITHUB_ACTIONS_TEST: true GITHUB_ACTIONS_TEST: true

1
app/.python-version Normal file
View File

@ -0,0 +1 @@
3.10.16

View File

@ -20,7 +20,7 @@ SimpleLogin backend consists of 2 main components:
## Install dependencies ## Install dependencies
The project requires: The project requires:
- Python 3.10 and poetry to manage dependencies - Python 3.10 and rye to manage dependencies
- Node v10 for front-end. - Node v10 for front-end.
- Postgres 13+ - 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. Feel free to use `virtualenv` or similar tools to isolate development environment.
```bash ```bash
poetry sync rye sync
``` ```
On Mac, sometimes you might need to install some other packages via `brew`: 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 We use pre-commit to run all our linting and static analysis checks. Please run
```bash ```bash
poetry run pre-commit install rye run pre-commit install
``` ```
To install it in your development environment. 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 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 The code is also checked with `flake8`, make sure to run `flake8` before creating the pull request by
```bash ```bash
poetry run flake8 rye run flake8
``` ```
For HTML templates, we use `djlint`. Before creating a pull request, please run For HTML templates, we use `djlint`. Before creating a pull request, please run
```bash ```bash
poetry run djlint --check templates rye run djlint --check templates
``` ```
If some files aren't properly formatted, you can format all files with If some files aren't properly formatted, you can format all files with
```bash ```bash
poetry run djlint --reformat . rye run djlint --reformat .
``` ```
## Test sending email ## 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 # we haven't managed to make python 3.12 work
brew install python3.10 brew install python3.10
# make sure to update the PATH so python, pip point to Python3 # Install rye using the official installation script, found on https://rye.astral.sh/guide/installation/
# for us it can be done by adding "export PATH=/opt/homebrew/opt/python@3.10/libexec/bin:$PATH" to .zprofile
# Although pipx is the recommended way to install poetry, # Install the dependencies
# install pipx via brew will automatically install python 3.12 rye sync
# 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
# activate the virtualenv and you should be good to go! # activate the virtualenv and you should be good to go!
source .venv/bin/activate source .venv/bin/activate
``` ```

View File

@ -4,43 +4,45 @@ WORKDIR /code
COPY ./static/package*.json /code/static/ COPY ./static/package*.json /code/static/
RUN cd /code/static && npm ci RUN cd /code/static && npm ci
# Main image FROM --platform=linux/amd64 ubuntu:22.04
FROM python:3.10
ARG RYE_VERSION="0.43.0"
ARG RYE_HASH="ca702c3d93fd6ec76a1a0efaaa605e10736ee79a0674d241aad1bc0fe26f7d80"
# Keeps Python from generating .pyc files in the container # Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONDONTWRITEBYTECODE=1
# Turns off buffering for easier container logging # 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 WORKDIR /code
# Copy poetry files # Copy dependency files
COPY poetry.lock pyproject.toml ./ COPY pyproject.toml requirements.lock requirements-dev.lock .python-version ./
# Install and setup poetry # Install deps
RUN pip install -U pip \ RUN apt-get update \
&& 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 \
&& apt install -y curl netcat-traditional gcc python3-dev gnupg git libre2-dev cmake ninja-build\ && curl -sSL "https://github.com/astral-sh/rye/releases/download/${RYE_VERSION}/rye-x86_64-linux.gz" > rye.gz \
&& curl -sSL https://install.python-poetry.org | python3 - \ && echo "${RYE_HASH} rye.gz" | sha256sum -c - \
# Remove curl and netcat from the image && gunzip rye.gz \
&& apt-get purge -y curl netcat-traditional \ && chmod +x rye \
# Run poetry && mv rye /usr/bin/rye \
&& poetry config virtualenvs.create false \ && rye toolchain fetch `cat .python-version` \
&& poetry install --no-interaction --no-ansi --no-root \ && rye sync --no-lock --no-dev \
# Clear apt cache \ && apt-get autoremove -y \
&& apt-get purge -y libre2-dev cmake ninja-build\ && apt-get purge -y curl netcat-traditional build-essential pkg-config cmake ninja-build python3-dev clang\
&& apt-get autoremove -y \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Copy code
COPY . .
# copy npm packages # copy npm packages
COPY --from=npm /code /code COPY --from=npm /code /code
# copy everything else into /code ENV PATH="/code/.venv/bin:$PATH"
COPY . .
EXPOSE 7777 EXPOSE 7777
#gunicorn wsgi:app -b 0.0.0.0:7777 -w 2 --timeout 15 --log-level DEBUG #gunicorn wsgi:app -b 0.0.0.0:7777 -w 2 --timeout 15 --log-level DEBUG

View File

@ -791,7 +791,7 @@ class EmailSearchResult:
self.query: str self.query: str
@staticmethod @staticmethod
def from_email(email: str) -> EmailSearchResult: def from_request_email(email: str) -> EmailSearchResult:
output = EmailSearchResult() output = EmailSearchResult()
output.query = email output.query = email
alias = Alias.get_by(email=email) alias = Alias.get_by(email=email)
@ -803,7 +803,11 @@ class EmailSearchResult:
.all() .all()
) )
output.no_match = False 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: if user:
output.user = user output.user = user
output.user_audit_log = ( output.user_audit_log = (
@ -912,7 +916,7 @@ class EmailSearchAdmin(BaseView):
email = request.args.get("email") email = request.args.get("email")
if email is not None and len(email) > 0: if email is not None and len(email) > 0:
email = email.strip() email = email.strip()
search = EmailSearchResult.from_email(email) search = EmailSearchResult.from_request_email(email)
return self.render( return self.render(
"admin/email_search.html", "admin/email_search.html",

View File

@ -343,7 +343,7 @@ class Fido(Base, ModelMixin):
class User(Base, ModelMixin, UserMixin, PasswordOracle): class User(Base, ModelMixin, UserMixin, PasswordOracle):
__tablename__ = "users" __tablename__ = "users"
FLAG_DISABLE_CREATE_CONTACTS = 1 << 0 FLAG_FREE_DISABLE_CREATE_CONTACTS = 1 << 0
FLAG_CREATED_FROM_PARTNER = 1 << 1 FLAG_CREATED_FROM_PARTNER = 1 << 1
FLAG_FREE_OLD_ALIAS_LIMIT = 1 << 2 FLAG_FREE_OLD_ALIAS_LIMIT = 1 << 2
FLAG_CREATED_ALIAS_FROM_PARTNER = 1 << 3 FLAG_CREATED_ALIAS_FROM_PARTNER = 1 << 3
@ -550,7 +550,7 @@ class User(Base, ModelMixin, UserMixin, PasswordOracle):
# bitwise flags. Allow for future expansion # bitwise flags. Allow for future expansion
flags = sa.Column( flags = sa.Column(
sa.BigInteger, sa.BigInteger,
default=FLAG_DISABLE_CREATE_CONTACTS, default=FLAG_FREE_DISABLE_CREATE_CONTACTS,
server_default="0", server_default="0",
nullable=False, nullable=False,
) )
@ -640,7 +640,7 @@ class User(Base, ModelMixin, UserMixin, PasswordOracle):
# If the user is created from partner, do not notify # If the user is created from partner, do not notify
# nor give a trial # nor give a trial
if from_partner: if from_partner:
user.flags = User.FLAG_CREATED_FROM_PARTNER user.flags = user.flags | User.FLAG_CREATED_FROM_PARTNER
user.notification = False user.notification = False
user.trial_end = None user.trial_end = None
Job.create( Job.create(
@ -1189,7 +1189,7 @@ class User(Base, ModelMixin, UserMixin, PasswordOracle):
def can_create_contacts(self) -> bool: def can_create_contacts(self) -> bool:
if self.is_premium(): if self.is_premium():
return True return True
if self.flags & User.FLAG_DISABLE_CREATE_CONTACTS == 0: if self.flags & User.FLAG_FREE_DISABLE_CREATE_CONTACTS == 0:
return True return True
return not config.DISABLE_CREATE_CONTACTS_FOR_FREE_USERS return not config.DISABLE_CREATE_CONTACTS_FOR_FREE_USERS

View File

@ -600,6 +600,17 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str
else: else:
reply_to_contact = get_or_create_reply_to_contact(reply_to, alias, msg) 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: if not alias.enabled or contact.block_forward:
LOG.d("%s is disabled, do not forward", alias) LOG.d("%s is disabled, do not forward", alias)
EmailLog.create( EmailLog.create(

View File

@ -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] [tool.black]
target-version = ['py310'] target-version = ['py310']
exclude = ''' exclude = '''
( (
/( /(
\.eggs # exclude a few common directories in the \.eggs # exclude a few common directories in the
| \.git # root of the project | \.git # root of the project
| \.hg | \.hg
| \.mypy_cache | \.mypy_cache
| \.tox | \.tox
| \.venv | \.venv
| _build | _build
| buck-out | buck-out
| build | build
| dist | dist
| migrations # migrations/ is generated by alembic | migrations # migrations/ is generated by alembic
| app/events/generated | app/events/generated
)/ )/
) )
''' '''
@ -27,7 +107,6 @@ exclude = [".venv", "migrations", "app/events/generated"]
indent = 2 indent = 2
profile = "jinja" profile = "jinja"
blank_line_after_tag = "if,for,include,load,extends,block,endcall" blank_line_after_tag = "if,for,include,load,extends,block,endcall"
# H006: Images should have a height attribute # H006: Images should have a height attribute
# H013: Images should have an alt attribute # H013: Images should have an alt attribute
# H016: Missing title tag in html. | False positive on template # 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 # 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" ignore = "H006,H013,H016,H017,H019,H021,H025,H030,H031,T003,J004,J018,T001"
[tool.poetry] [tool.uv]
name = "SimpleLogin" dev-dependencies = [
version = "0.1.0" "pytest ~= 7.0.0",
description = "open-source email alias solution" "pytest-cov ~= 3.0.0",
authors = ["SimpleLogin <dev@simplelogin.io>"] "pre-commit ~= 2.17.0",
license = "MIT" "black ~= 22.1.0",
repository = "https://github.com/simple-login/app" "djlint ~= 1.3.0",
keywords = ["email", "alias", "privacy", "oauth2", "openid"] "pylint ~= 2.14.4",
packages = [ "ruff ~= 0.1.5",
{ include = "app/" },
{ include = "migrations/" },
] ]
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] [build-system]
requires = ["poetry>=0.12"] requires = ["hatchling"]
build-backend = "poetry.masonry.api" 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"]

469
app/requirements-dev.lock Normal file
View File

@ -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

392
app/requirements.lock Normal file
View File

@ -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

View File

@ -12,10 +12,10 @@ docker run -p 25432:5432 --name ${container_name} -e POSTGRES_PASSWORD=postgres
sleep 3 sleep 3
# upgrade the DB to the latest stage and # 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. # 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 # remove the db
docker rm -f ${container_name} docker rm -f ${container_name}

View File

@ -3,5 +3,5 @@
export DB_URI=postgresql://myuser:mypassword@localhost:15432/simplelogin export DB_URI=postgresql://myuser:mypassword@localhost:15432/simplelogin
echo 'drop schema public cascade; create schema public;' | psql $DB_URI echo 'drop schema public cascade; create schema public;' | psql $DB_URI
poetry run alembic upgrade head rye run alembic upgrade head
poetry run flask dummy-data rye run flask dummy-data

View File

@ -3,4 +3,4 @@
export DB_URI=postgresql://myuser:mypassword@localhost:15432/test export DB_URI=postgresql://myuser:mypassword@localhost:15432/test
echo 'drop schema public cascade; create schema public;' | psql $DB_URI echo 'drop schema public cascade; create schema public;' | psql $DB_URI
poetry run alembic upgrade head rye run alembic upgrade head

View File

@ -10,10 +10,10 @@ docker run -d --name sl-test-db -e POSTGRES_PASSWORD=test -e POSTGRES_USER=test
sleep 3 sleep 3
# migrate the DB to the latest version # 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 # run test
poetry run pytest -c pytest.ci.ini rye run pytest -c pytest.ci.ini
# Delete the test DB # Delete the test DB
docker rm -f sl-test-db docker rm -f sl-test-db

View File

@ -549,7 +549,7 @@ def test_create_contact_route_free_users(flask_client):
assert r.status_code == 201 assert r.status_code == 201
# End trial and disallow for new free users. Config should allow it # 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() Session.commit()
r = flask_client.post( r = flask_client.post(
url_for("api.create_contact_route", alias_id=alias.id), url_for("api.create_contact_route", alias_id=alias.id),

View File

@ -135,7 +135,7 @@ def test_create_contact_free_user():
assert result.contact is not None assert result.contact is not None
assert not result.contact.automatic_created assert not result.contact.automatic_created
# Free users with the flag should be able to still create automatic emails # 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() Session.flush()
result = create_contact(random_email(), alias, automatic_created=True) result = create_contact(random_email(), alias, automatic_created=True)
assert result.error is None assert result.error is None

View File

@ -2,6 +2,7 @@ import random
from email.message import EmailMessage from email.message import EmailMessage
from typing import List from typing import List
import arrow
import pytest import pytest
from aiosmtpd.smtp import Envelope from aiosmtpd.smtp import Envelope
@ -387,3 +388,15 @@ def test_preserve_headers(flask_client):
msg = sent_mails[0].msg msg = sent_mails[0].msg
for header in headers_to_keep: for header in headers_to_keep:
assert msg[header] == header + "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