From 8b037fbeb5e8c306dda23064e0b0d6a1a27625c8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:23:52 +0100 Subject: [PATCH 01/23] chore: Configure Renovate (#20) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Conrad --- .github/workflows/ci.yml | 2 +- renovate.json | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 renovate.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f44b72b..fcfca2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: tag_and_publish: runs-on: ubuntu-latest - if: github.ref_name == 'master' || github.ref_name == 'dev' + if: (github.ref_name == 'master' || github.ref_name == 'dev') && github.event_name == 'push' needs: test permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..88b0152 --- /dev/null +++ b/renovate.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":semanticCommitTypeAll(feat)" + ] +} From 90c9d2dc09fb1de5650b83018e0e644b478695e8 Mon Sep 17 00:00:00 2001 From: Conrad Date: Sun, 24 Nov 2024 17:57:49 +0100 Subject: [PATCH 02/23] breaking: default version no longer uses postgres --- README.md | 1 - requirements.build.txt | 2 -- requirements.pg.txt | 5 +++++ requirements.txt | 7 ------- setup.py | 8 +++++++- 5 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 requirements.pg.txt diff --git a/README.md b/README.md index 0b1ac62..2dd4772 100644 --- a/README.md +++ b/README.md @@ -58,5 +58,4 @@ print(LanguageEnum.EN.value) # Output: English ## TODO -- Add async support for database connection - Add version without postgresql dependency diff --git a/requirements.build.txt b/requirements.build.txt index 9353eba..270aabd 100644 --- a/requirements.build.txt +++ b/requirements.build.txt @@ -23,5 +23,3 @@ twine>=5.0.0 urllib3>=2.2.1 wheel>=0.43.0 zipp>=3.18.1 - --r requirements.txt diff --git a/requirements.pg.txt b/requirements.pg.txt new file mode 100644 index 0000000..e205e3e --- /dev/null +++ b/requirements.pg.txt @@ -0,0 +1,5 @@ +psycopg>=3.2.1 # PostgreSQL +psycopg-binary>=3.2.1 # PostgreSQL +psycopg-pool>=3.2.2 # PostgreSQL +asyncpg>=0.30.0 # SQLAlchemy +greenlet>=3.1.1 # Async diff --git a/requirements.txt b/requirements.txt index a03bdfe..4183694 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,13 +14,6 @@ sqlalchemy>=2.0.31 # SQLAlchemy python-dotenv>=1.0.1 # Environment variables -psycopg>=3.2.1 # PostgreSQL -psycopg-binary>=3.2.1 # PostgreSQL -psycopg-pool>=3.2.2 # PostgreSQL - h11>=0.14.0 # Testing httpcore>=1.0.5 # Testing httpx>=0.27.0 # Testing - -asyncpg>=0.30.0 #SQLAlchemy -greenlet>=3.1.1 #Async diff --git a/setup.py b/setup.py index 6cd69b9..8dd4e9c 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,12 @@ with open("requirements.txt") as f: requirements = f.read().splitlines() +with open("requirements.build.txt") as f: + build_requirements = f.read().splitlines() + +with open("requirements.pg.txt") as f: + pg_requirements = f.read().splitlines() + def get_latest_git_tag() -> str: try: @@ -33,6 +39,7 @@ def get_latest_git_tag() -> str: license="MIT", python_requires=">=3.12", install_requires=requirements, + extras_require={"build": build_requirements, "postgres": pg_requirements}, keywords=[ "creyPY", "Python", @@ -40,7 +47,6 @@ def get_latest_git_tag() -> str: "shortcuts", "snippets", "utils", - "personal library", ], platforms="any", ) From 481bfcfffd841d663491da2b403ebdc4d429d900 Mon Sep 17 00:00:00 2001 From: Conrad Date: Sun, 24 Nov 2024 17:57:53 +0100 Subject: [PATCH 03/23] feat: unified configs for pg sessions --- creyPY/fastapi/db/async_session.py | 14 +++----------- creyPY/fastapi/db/common.py | 13 +++++++++++++ creyPY/fastapi/db/session.py | 11 +---------- 3 files changed, 17 insertions(+), 21 deletions(-) create mode 100644 creyPY/fastapi/db/common.py diff --git a/creyPY/fastapi/db/async_session.py b/creyPY/fastapi/db/async_session.py index 07a0e61..8b8e557 100644 --- a/creyPY/fastapi/db/async_session.py +++ b/creyPY/fastapi/db/async_session.py @@ -1,22 +1,14 @@ import os from typing import AsyncGenerator + from dotenv import load_dotenv from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.orm import sessionmaker - -load_dotenv() - -host = os.getenv("POSTGRES_HOST", "localhost") -user = os.getenv("POSTGRES_USER", "postgres") -password = os.getenv("POSTGRES_PASSWORD", "root") -port = os.getenv("POSTGRES_PORT", "5432") -name = os.getenv("POSTGRES_DB", "fastapi") - -SQLALCHEMY_DATABASE_URL = f"postgresql+psycopg://{user}:{password}@{host}:{port}/" - +from .common import SQLALCHEMY_DATABASE_URL, name async_engine = create_async_engine(SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True) + AsyncSessionLocal = sessionmaker( bind=async_engine, class_=AsyncSession, diff --git a/creyPY/fastapi/db/common.py b/creyPY/fastapi/db/common.py new file mode 100644 index 0000000..67c43dd --- /dev/null +++ b/creyPY/fastapi/db/common.py @@ -0,0 +1,13 @@ +import os + +from dotenv import load_dotenv + +load_dotenv() + +host = os.getenv("POSTGRES_HOST", "localhost") +user = os.getenv("POSTGRES_USER", "postgres") +password = os.getenv("POSTGRES_PASSWORD", "root") +port = os.getenv("POSTGRES_PORT", "5432") +name = os.getenv("POSTGRES_DB", "fastapi") + +SQLALCHEMY_DATABASE_URL = f"postgresql+psycopg://{user}:{password}@{host}:{port}/" diff --git a/creyPY/fastapi/db/session.py b/creyPY/fastapi/db/session.py index 213e56b..b6be878 100644 --- a/creyPY/fastapi/db/session.py +++ b/creyPY/fastapi/db/session.py @@ -6,16 +6,7 @@ from sqlalchemy.orm import sessionmaker from sqlalchemy.orm.session import Session -load_dotenv() - -host = os.getenv("POSTGRES_HOST", "localhost") -user = os.getenv("POSTGRES_USER", "postgres") -password = os.getenv("POSTGRES_PASSWORD", "root") -port = os.getenv("POSTGRES_PORT", "5432") -name = os.getenv("POSTGRES_DB", "fastapi") - -SQLALCHEMY_DATABASE_URL = f"postgresql+psycopg://{user}:{password}@{host}:{port}/" - +from .common import SQLALCHEMY_DATABASE_URL, name engine = create_engine(SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) From 7dbce117c868fb6651ac9eb58e1cf434884e4644 Mon Sep 17 00:00:00 2001 From: Conrad Date: Sun, 24 Nov 2024 18:01:45 +0100 Subject: [PATCH 04/23] feat: added common database helper --- creyPY/fastapi/db/__init__.py | 3 ++- creyPY/fastapi/db/helpers.py | 8 ++++++++ requirements.txt | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 creyPY/fastapi/db/helpers.py diff --git a/creyPY/fastapi/db/__init__.py b/creyPY/fastapi/db/__init__.py index 395efc9..94b32a2 100644 --- a/creyPY/fastapi/db/__init__.py +++ b/creyPY/fastapi/db/__init__.py @@ -1,2 +1,3 @@ +from .async_session import * # noqa +from .helpers import * # noqa from .session import * # noqa -from .async_session import * # noqa diff --git a/creyPY/fastapi/db/helpers.py b/creyPY/fastapi/db/helpers.py new file mode 100644 index 0000000..864ec4b --- /dev/null +++ b/creyPY/fastapi/db/helpers.py @@ -0,0 +1,8 @@ +from sqlalchemy_utils import create_database, database_exists + + +def create_if_not_exists(db_name: str): + from .common import SQLALCHEMY_DATABASE_URL + + if not database_exists(SQLALCHEMY_DATABASE_URL + db_name): + create_database(SQLALCHEMY_DATABASE_URL + db_name) diff --git a/requirements.txt b/requirements.txt index 4183694..3401b77 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ starlette>=0.37.2 # FastAPI fastapi-pagination>=0.12.26 # Pagination sqlalchemy>=2.0.31 # SQLAlchemy +sqlalchemy-utils==0.41.2 # For managing databases python-dotenv>=1.0.1 # Environment variables From 5efed5399b09d5be1fe296ff2cbc483007d2e61e Mon Sep 17 00:00:00 2001 From: Conrad Date: Sun, 24 Nov 2024 18:02:00 +0100 Subject: [PATCH 05/23] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 2dd4772..335a9ae 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,3 @@ from creyPY.const import LanguageEnum print(LanguageEnum.EN) # Output: LanguageEnum.EN print(LanguageEnum.EN.value) # Output: English ``` - -## TODO - -- Add version without postgresql dependency From 33bdeb12a0fb14be6b47ee857cc6842b70f2dc6c Mon Sep 17 00:00:00 2001 From: creyD Date: Sun, 24 Nov 2024 17:03:03 +0000 Subject: [PATCH 06/23] Adjusted files for isort & autopep --- creyPY/fastapi/db/async_session.py | 2 -- creyPY/fastapi/db/session.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/creyPY/fastapi/db/async_session.py b/creyPY/fastapi/db/async_session.py index 8b8e557..56ed9ae 100644 --- a/creyPY/fastapi/db/async_session.py +++ b/creyPY/fastapi/db/async_session.py @@ -1,7 +1,5 @@ -import os from typing import AsyncGenerator -from dotenv import load_dotenv from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.orm import sessionmaker diff --git a/creyPY/fastapi/db/session.py b/creyPY/fastapi/db/session.py index b6be878..b400468 100644 --- a/creyPY/fastapi/db/session.py +++ b/creyPY/fastapi/db/session.py @@ -1,7 +1,5 @@ -import os from typing import Generator -from dotenv import load_dotenv from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.orm.session import Session From 244426948672d150886c341c63f3af5c3ad2702e Mon Sep 17 00:00:00 2001 From: Conrad Date: Sun, 24 Nov 2024 18:13:58 +0100 Subject: [PATCH 07/23] feat: added auth0 common module --- creyPY/helpers.py | 16 ++++ creyPY/services/auth0/__init__.py | 3 + creyPY/services/auth0/common.py | 13 +++ creyPY/services/auth0/exceptions.py | 12 +++ creyPY/services/auth0/manage.py | 20 +++++ creyPY/services/auth0/utils.py | 131 ++++++++++++++++++++++++++++ requirements.auth0.txt | 7 ++ setup.py | 9 +- 8 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 creyPY/helpers.py create mode 100644 creyPY/services/auth0/__init__.py create mode 100644 creyPY/services/auth0/common.py create mode 100644 creyPY/services/auth0/exceptions.py create mode 100644 creyPY/services/auth0/manage.py create mode 100644 creyPY/services/auth0/utils.py create mode 100644 requirements.auth0.txt diff --git a/creyPY/helpers.py b/creyPY/helpers.py new file mode 100644 index 0000000..bfb334b --- /dev/null +++ b/creyPY/helpers.py @@ -0,0 +1,16 @@ +import random +import string + + +def create_random_password(length: int = 12) -> str: + all_characters = string.ascii_letters + string.digits + string.punctuation + + password = [ + random.choice(string.ascii_lowercase), + random.choice(string.ascii_uppercase), + random.choice(string.digits), + random.choice(string.punctuation), + ] + password += random.choices(all_characters, k=length - 4) + random.shuffle(password) + return "".join(password) diff --git a/creyPY/services/auth0/__init__.py b/creyPY/services/auth0/__init__.py new file mode 100644 index 0000000..fb934e5 --- /dev/null +++ b/creyPY/services/auth0/__init__.py @@ -0,0 +1,3 @@ +from .exceptions import * # noqa +from .manage import * # noqa +from .utils import * # noqa diff --git a/creyPY/services/auth0/common.py b/creyPY/services/auth0/common.py new file mode 100644 index 0000000..c15174d --- /dev/null +++ b/creyPY/services/auth0/common.py @@ -0,0 +1,13 @@ +import os + +from dotenv import load_dotenv + +load_dotenv() + +AUTH0_DOMAIN = os.getenv("AUTH0_DOMAIN") +AUTH0_CLIENT_ID = os.getenv("AUTH0_CLIENT_ID") +AUTH0_CLIENT_SECRET = os.getenv("AUTH0_CLIENT_SECRET") +AUTH0_ALGORIGHM = os.getenv("AUTH0_ALGORIGHM", "RS256") + +AUTH0_AUDIENCE = os.getenv("AUTH0_AUDIENCE") +AUTH0_ISSUER = os.getenv("AUTH0_ISSUER") diff --git a/creyPY/services/auth0/exceptions.py b/creyPY/services/auth0/exceptions.py new file mode 100644 index 0000000..535b674 --- /dev/null +++ b/creyPY/services/auth0/exceptions.py @@ -0,0 +1,12 @@ +from fastapi import HTTPException, status + + +class UnauthorizedException(HTTPException): + def __init__(self, detail: str, **kwargs): + """Returns HTTP 403""" + super().__init__(status.HTTP_403_FORBIDDEN, detail=detail) + + +class UnauthenticatedException(HTTPException): + def __init__(self): + super().__init__(status_code=status.HTTP_401_UNAUTHORIZED, detail="Requires authentication") diff --git a/creyPY/services/auth0/manage.py b/creyPY/services/auth0/manage.py new file mode 100644 index 0000000..d809859 --- /dev/null +++ b/creyPY/services/auth0/manage.py @@ -0,0 +1,20 @@ +import requests +from cachetools import TTLCache, cached + +from .common import AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, AUTH0_DOMAIN + +cache = TTLCache(maxsize=100, ttl=600) + + +@cached(cache) +def get_management_token() -> str: + re = requests.post( + f"https://{AUTH0_DOMAIN}/oauth/token", + json={ + "client_id": AUTH0_CLIENT_ID, + "client_secret": AUTH0_CLIENT_SECRET, + "audience": f"https://{AUTH0_DOMAIN}/api/v2/", # This should be the management audience + "grant_type": "client_credentials", + }, + ).json() + return re["access_token"] diff --git a/creyPY/services/auth0/utils.py b/creyPY/services/auth0/utils.py new file mode 100644 index 0000000..3cb209e --- /dev/null +++ b/creyPY/services/auth0/utils.py @@ -0,0 +1,131 @@ +from typing import Optional + +import jwt +import requests +from fastapi import HTTPException, Request, Security +from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer + +from creyPY.helpers import create_random_password + +from .common import ( + AUTH0_ALGORIGHM, + AUTH0_AUDIENCE, + AUTH0_CLIENT_ID, + AUTH0_DOMAIN, + AUTH0_ISSUER, +) +from .exceptions import UnauthenticatedException, UnauthorizedException +from .manage import get_management_token + +JWKS_CLIENT = jwt.PyJWKClient(f"https://{AUTH0_DOMAIN}/.well-known/jwks.json") + + +async def verify( + request: Request, + token: Optional[HTTPAuthorizationCredentials] = Security(HTTPBearer(auto_error=False)), +) -> str: + if token is None: + raise UnauthenticatedException + + # This gets the 'kid' from the passed token + try: + signing_key = JWKS_CLIENT.get_signing_key_from_jwt(token.credentials).key + except jwt.exceptions.PyJWKClientError as error: + raise UnauthorizedException(str(error)) + except jwt.exceptions.DecodeError as error: + raise UnauthorizedException(str(error)) + + try: + payload = jwt.decode( + token.credentials, + signing_key, + algorithms=[AUTH0_ALGORIGHM], + audience=AUTH0_AUDIENCE, + issuer=AUTH0_ISSUER, + ) + except Exception as error: + raise UnauthorizedException(str(error)) + + return payload["sub"] + + +### GENERIC AUTH0 CALLS ### +def get_user(sub) -> dict: + re = requests.get( + f"https://{AUTH0_DOMAIN}/api/v2/users/{sub}", + headers={"Authorization": f"Bearer {get_management_token()}"}, + ) + if re.status_code != 200: + raise HTTPException(re.status_code, re.json()) + return re.json() + + +def patch_user(input_obj: dict, sub) -> dict: + re = requests.patch( + f"https://{AUTH0_DOMAIN}/api/v2/users/{sub}", + headers={"Authorization": f"Bearer {get_management_token()}"}, + json=input_obj, + ) + if re.status_code != 200: + raise HTTPException(re.status_code, re.json()) + return re.json() + + +### USER METADATA CALLS ### +def get_user_metadata(sub) -> dict: + try: + return get_user(sub).get("user_metadata", {}) + except: + return {} + + +def patch_user_metadata(input_obj: dict, sub) -> dict: + return patch_user({"user_metadata": input_obj}, sub) + + +def clear_user_metadata(sub) -> dict: + return patch_user({"user_metadata": {}}, sub) + + +def request_verification_mail(sub: str) -> None: + re = requests.post( + f"https://{AUTH0_DOMAIN}/api/v2/jobs/verification-email", + headers={"Authorization": f"Bearer {get_management_token()}"}, + json={"user_id": sub}, + ) + if re.status_code != 201: + raise HTTPException(re.status_code, re.json()) + return re.json() + + +def create_user_invite(email: str) -> dict: + re = requests.post( + f"https://{AUTH0_DOMAIN}/api/v2/users", + headers={"Authorization": f"Bearer {get_management_token()}"}, + json={ + "email": email, + "connection": "Username-Password-Authentication", + "password": create_random_password(), + "verify_email": False, + "app_metadata": {"invitedToMyApp": True}, + }, + ) + if re.status_code != 201: + raise HTTPException(re.status_code, re.json()) + return re.json() + + +def password_change_mail(email: str) -> bool: + re = requests.post( + f"https://{AUTH0_DOMAIN}/dbconnections/change_password", + headers={"Authorization": f"Bearer {get_management_token()}"}, + json={ + "client_id": AUTH0_CLIENT_ID, + "email": email, + "connection": "Username-Password-Authentication", + }, + ) + + if re.status_code != 200: + raise HTTPException(re.status_code, re.json()) + return True diff --git a/requirements.auth0.txt b/requirements.auth0.txt new file mode 100644 index 0000000..2175c1a --- /dev/null +++ b/requirements.auth0.txt @@ -0,0 +1,7 @@ +cachetools==5.5.0 # for caching +charset-normalizer==3.4.0 # Auth0 API interactions +requests==2.32.3 # Auth0 API interactions +pyjwt==2.10.0 # Auth0 API interactions +cffi==1.17.1 # Auth0 API interactions +cryptography==43.0.3 # Auth0 API interactions +pycparser==2.22 # Auth0 API interactions diff --git a/setup.py b/setup.py index 8dd4e9c..f9539c9 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,9 @@ with open("requirements.pg.txt") as f: pg_requirements = f.read().splitlines() +with open("requirements.auth0.txt") as f: + auth0_requirements = f.read().splitlines() + def get_latest_git_tag() -> str: try: @@ -39,7 +42,11 @@ def get_latest_git_tag() -> str: license="MIT", python_requires=">=3.12", install_requires=requirements, - extras_require={"build": build_requirements, "postgres": pg_requirements}, + extras_require={ + "build": build_requirements, + "postgres": pg_requirements, + "auth0": auth0_requirements, + }, keywords=[ "creyPY", "Python", From 0418c75e19206db63333fe4263304aeff2109b37 Mon Sep 17 00:00:00 2001 From: Conrad Date: Sun, 24 Nov 2024 18:16:03 +0100 Subject: [PATCH 08/23] feat: added all install option for dependencies --- .github/workflows/ci.yml | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcfca2d..fbd401b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,7 +95,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements.build.txt + pip install ".[all]" python setup.py sdist bdist_wheel - name: Build and publish diff --git a/setup.py b/setup.py index f9539c9..f15a71c 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ def get_latest_git_tag() -> str: "build": build_requirements, "postgres": pg_requirements, "auth0": auth0_requirements, + "all": build_requirements + pg_requirements + auth0_requirements, }, keywords=[ "creyPY", From 0be70deb00f1ee8ac394f29e93306e4b8085e8da Mon Sep 17 00:00:00 2001 From: Conrad Date: Sun, 24 Nov 2024 18:18:13 +0100 Subject: [PATCH 09/23] fix: fixed pipeline --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbd401b..c30e133 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: with: python-version: '3.12' - run: python -m pip install --upgrade pip - - run: python -m pip install -r requirements.txt + - run: python -m pip install ".[all]" - run: python test.py tag_and_publish: @@ -95,7 +95,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install ".[all]" + pip install ".[build]" python setup.py sdist bdist_wheel - name: Build and publish From 85fe263da4b260c260fe32a5ca2201ea8ce54a5c Mon Sep 17 00:00:00 2001 From: Conrad Date: Sun, 24 Nov 2024 18:21:43 +0100 Subject: [PATCH 10/23] fix: pipeline fix --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c30e133..d13b734 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: with: python-version: '3.12' - run: python -m pip install --upgrade pip - - run: python -m pip install ".[all]" + - run: python -m pip install .[all] - run: python test.py tag_and_publish: @@ -95,7 +95,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install ".[build]" + pip install requirements.build.txt python setup.py sdist bdist_wheel - name: Build and publish From 3251afdb90469bd24ddc15cf3676dd8f1db79506 Mon Sep 17 00:00:00 2001 From: Conrad Date: Sun, 24 Nov 2024 18:25:59 +0100 Subject: [PATCH 11/23] fix: fixed pipeline --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d13b734..705cabe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,10 @@ jobs: with: python-version: '3.12' - run: python -m pip install --upgrade pip - - run: python -m pip install .[all] + - run: | + python -m pip install requirements.txt + python -m pip install requirements.pg.txt + python -m pip install requirements.auth0.txt - run: python test.py tag_and_publish: From dfb0588d1c10a138ec43c7689241bad3867cb2c2 Mon Sep 17 00:00:00 2001 From: Conrad Date: Sun, 24 Nov 2024 18:27:45 +0100 Subject: [PATCH 12/23] fix: fixed pipeline --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 705cabe..ed6657a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,9 +41,9 @@ jobs: python-version: '3.12' - run: python -m pip install --upgrade pip - run: | - python -m pip install requirements.txt - python -m pip install requirements.pg.txt - python -m pip install requirements.auth0.txt + python -m pip install -r requirements.txt + python -m pip install -r requirements.pg.txt + python -m pip install -r requirements.auth0.txt - run: python test.py tag_and_publish: @@ -98,7 +98,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install requirements.build.txt + pip install -r requirements.build.txt python setup.py sdist bdist_wheel - name: Build and publish From 5daf6eb8c5f5fb23ad478f0b392b3bc7c2c7bad9 Mon Sep 17 00:00:00 2001 From: Conrad Date: Mon, 25 Nov 2024 12:55:35 +0100 Subject: [PATCH 13/23] fix: fixed missing import --- creyPY/services/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 creyPY/services/__init__.py diff --git a/creyPY/services/__init__.py b/creyPY/services/__init__.py new file mode 100644 index 0000000..a2ec6fe --- /dev/null +++ b/creyPY/services/__init__.py @@ -0,0 +1 @@ +from .auth0 import * # noqa From 364e07daa11c77a0dccb96d9fda7faecf90ce147 Mon Sep 17 00:00:00 2001 From: Conrad Date: Mon, 25 Nov 2024 13:14:07 +0100 Subject: [PATCH 14/23] fix: fixed random issue (codacy) --- creyPY/helpers.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/creyPY/helpers.py b/creyPY/helpers.py index bfb334b..7773e98 100644 --- a/creyPY/helpers.py +++ b/creyPY/helpers.py @@ -1,4 +1,4 @@ -import random +import secrets import string @@ -6,11 +6,11 @@ def create_random_password(length: int = 12) -> str: all_characters = string.ascii_letters + string.digits + string.punctuation password = [ - random.choice(string.ascii_lowercase), - random.choice(string.ascii_uppercase), - random.choice(string.digits), - random.choice(string.punctuation), + secrets.choice(string.ascii_lowercase), + secrets.choice(string.ascii_uppercase), + secrets.choice(string.digits), + secrets.choice(string.punctuation), ] - password += random.choices(all_characters, k=length - 4) - random.shuffle(password) + password += [secrets.choice(all_characters) for _ in range(length - 4)] + secrets.SystemRandom().shuffle(password) return "".join(password) From 5daddf260efddf163ddb9d4dc4ee86e46031d6b7 Mon Sep 17 00:00:00 2001 From: Conrad Date: Mon, 25 Nov 2024 13:20:17 +0100 Subject: [PATCH 15/23] fix: added timeouts to the requests to fix Bandit issue --- creyPY/services/auth0/manage.py | 5 +++-- creyPY/services/auth0/utils.py | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/creyPY/services/auth0/manage.py b/creyPY/services/auth0/manage.py index d809859..28682fb 100644 --- a/creyPY/services/auth0/manage.py +++ b/creyPY/services/auth0/manage.py @@ -8,7 +8,7 @@ @cached(cache) def get_management_token() -> str: - re = requests.post( + response = requests.post( f"https://{AUTH0_DOMAIN}/oauth/token", json={ "client_id": AUTH0_CLIENT_ID, @@ -16,5 +16,6 @@ def get_management_token() -> str: "audience": f"https://{AUTH0_DOMAIN}/api/v2/", # This should be the management audience "grant_type": "client_credentials", }, + timeout=5, # Add a timeout parameter to avoid hanging requests ).json() - return re["access_token"] + return response["access_token"] diff --git a/creyPY/services/auth0/utils.py b/creyPY/services/auth0/utils.py index 3cb209e..917ff4b 100644 --- a/creyPY/services/auth0/utils.py +++ b/creyPY/services/auth0/utils.py @@ -54,6 +54,7 @@ def get_user(sub) -> dict: re = requests.get( f"https://{AUTH0_DOMAIN}/api/v2/users/{sub}", headers={"Authorization": f"Bearer {get_management_token()}"}, + timeout=5, ) if re.status_code != 200: raise HTTPException(re.status_code, re.json()) @@ -65,6 +66,7 @@ def patch_user(input_obj: dict, sub) -> dict: f"https://{AUTH0_DOMAIN}/api/v2/users/{sub}", headers={"Authorization": f"Bearer {get_management_token()}"}, json=input_obj, + timeout=5, ) if re.status_code != 200: raise HTTPException(re.status_code, re.json()) @@ -92,6 +94,7 @@ def request_verification_mail(sub: str) -> None: f"https://{AUTH0_DOMAIN}/api/v2/jobs/verification-email", headers={"Authorization": f"Bearer {get_management_token()}"}, json={"user_id": sub}, + timeout=5, ) if re.status_code != 201: raise HTTPException(re.status_code, re.json()) @@ -109,6 +112,7 @@ def create_user_invite(email: str) -> dict: "verify_email": False, "app_metadata": {"invitedToMyApp": True}, }, + timeout=5, ) if re.status_code != 201: raise HTTPException(re.status_code, re.json()) @@ -124,6 +128,7 @@ def password_change_mail(email: str) -> bool: "email": email, "connection": "Username-Password-Authentication", }, + timeout=5, ) if re.status_code != 200: From 2176b1a37d8306ebafceb00fe4eb201d40079b7b Mon Sep 17 00:00:00 2001 From: Conrad Date: Wed, 4 Dec 2024 20:05:19 +0100 Subject: [PATCH 16/23] fix: bumped security risks and enabled newer packages installed --- requirements.auth0.txt | 14 +++++++------- requirements.txt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/requirements.auth0.txt b/requirements.auth0.txt index 2175c1a..7de9f5b 100644 --- a/requirements.auth0.txt +++ b/requirements.auth0.txt @@ -1,7 +1,7 @@ -cachetools==5.5.0 # for caching -charset-normalizer==3.4.0 # Auth0 API interactions -requests==2.32.3 # Auth0 API interactions -pyjwt==2.10.0 # Auth0 API interactions -cffi==1.17.1 # Auth0 API interactions -cryptography==43.0.3 # Auth0 API interactions -pycparser==2.22 # Auth0 API interactions +cachetools>=5.5.0 # for caching +charset-normalizer>=3.4.0 # Auth0 API interactions +requests>=2.32.3 # Auth0 API interactions +pyjwt>=2.10.1 # Auth0 API interactions +cffi>=1.17.1 # Auth0 API interactions +cryptography>=43.0.3 # Auth0 API interactions +pycparser>=2.22 # Auth0 API interactions diff --git a/requirements.txt b/requirements.txt index 3401b77..383fca6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ starlette>=0.37.2 # FastAPI fastapi-pagination>=0.12.26 # Pagination sqlalchemy>=2.0.31 # SQLAlchemy -sqlalchemy-utils==0.41.2 # For managing databases +sqlalchemy-utils>=0.41.2 # For managing databases python-dotenv>=1.0.1 # Environment variables From 2a22471de9627eb45ff9486d7c2fb3a06a050757 Mon Sep 17 00:00:00 2001 From: vikynoah Date: Mon, 9 Dec 2024 16:28:44 +0100 Subject: [PATCH 17/23] fix: httpx fix as per latest version (#25) --- creyPY/fastapi/testing_async.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/creyPY/fastapi/testing_async.py b/creyPY/fastapi/testing_async.py index 91c835e..538ae31 100644 --- a/creyPY/fastapi/testing_async.py +++ b/creyPY/fastapi/testing_async.py @@ -1,10 +1,10 @@ import json -from httpx import AsyncClient +from httpx import AsyncClient, ASGITransport class AsyncGenericClient: def __init__(self, app): - self.c = AsyncClient(app=app, base_url="http://testserver", follow_redirects=True) + self.c = AsyncClient(transport=ASGITransport(app=app), base_url="http://testserver", follow_redirects=True) self.default_headers = {} async def get(self, url: str, r_code: int = 200, parse_json=True): From 2e444539154f57bedcbb3ae3c18b114aae0b819f Mon Sep 17 00:00:00 2001 From: creyD Date: Mon, 9 Dec 2024 15:29:15 +0000 Subject: [PATCH 18/23] Adjusted files for isort & autopep --- creyPY/fastapi/testing_async.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/creyPY/fastapi/testing_async.py b/creyPY/fastapi/testing_async.py index 538ae31..1e1fb5d 100644 --- a/creyPY/fastapi/testing_async.py +++ b/creyPY/fastapi/testing_async.py @@ -4,7 +4,9 @@ class AsyncGenericClient: def __init__(self, app): - self.c = AsyncClient(transport=ASGITransport(app=app), base_url="http://testserver", follow_redirects=True) + self.c = AsyncClient( + transport=ASGITransport(app=app), base_url="http://testserver", follow_redirects=True + ) self.default_headers = {} async def get(self, url: str, r_code: int = 200, parse_json=True): From 93c7f6f6cb54166e6b2340f44480ec95eb74acbf Mon Sep 17 00:00:00 2001 From: vikynoah Date: Wed, 11 Dec 2024 17:14:59 +0100 Subject: [PATCH 19/23] fix: Async Testing (#26) * fix: httpx fix as per latest version * fix: Fix Async Testing client --- creyPY/fastapi/testing_async.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/creyPY/fastapi/testing_async.py b/creyPY/fastapi/testing_async.py index 1e1fb5d..d4a744d 100644 --- a/creyPY/fastapi/testing_async.py +++ b/creyPY/fastapi/testing_async.py @@ -1,13 +1,11 @@ import json -from httpx import AsyncClient, ASGITransport +from httpx import ASGITransport, AsyncClient class AsyncGenericClient: - def __init__(self, app): - self.c = AsyncClient( - transport=ASGITransport(app=app), base_url="http://testserver", follow_redirects=True - ) - self.default_headers = {} + def __init__(self, app, headers = {}): + self.c = AsyncClient(transport=ASGITransport(app=app), base_url="http://testserver", follow_redirects=True) + self.default_headers = headers async def get(self, url: str, r_code: int = 200, parse_json=True): re = await self.c.get(url, headers=self.default_headers) From f8b781b3e7242bdb924ad62d698c92a7c6240547 Mon Sep 17 00:00:00 2001 From: creyD Date: Wed, 11 Dec 2024 16:15:33 +0000 Subject: [PATCH 20/23] Adjusted files for isort & autopep --- creyPY/fastapi/testing_async.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/creyPY/fastapi/testing_async.py b/creyPY/fastapi/testing_async.py index d4a744d..b61a6fd 100644 --- a/creyPY/fastapi/testing_async.py +++ b/creyPY/fastapi/testing_async.py @@ -3,8 +3,10 @@ class AsyncGenericClient: - def __init__(self, app, headers = {}): - self.c = AsyncClient(transport=ASGITransport(app=app), base_url="http://testserver", follow_redirects=True) + def __init__(self, app, headers={}): + self.c = AsyncClient( + transport=ASGITransport(app=app), base_url="http://testserver", follow_redirects=True + ) self.default_headers = headers async def get(self, url: str, r_code: int = 200, parse_json=True): From d75fede3d1b7dfbd90f588f9470d0de95529a4a0 Mon Sep 17 00:00:00 2001 From: vikynoah Date: Thu, 2 Jan 2025 23:20:17 +0100 Subject: [PATCH 21/23] fix: Force postgresql SSL mode (#27) * fix: Force postgresql SSL mode * changes --- creyPY/fastapi/db/async_session.py | 5 ++++- creyPY/fastapi/db/session.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/creyPY/fastapi/db/async_session.py b/creyPY/fastapi/db/async_session.py index 56ed9ae..55d2c5a 100644 --- a/creyPY/fastapi/db/async_session.py +++ b/creyPY/fastapi/db/async_session.py @@ -5,7 +5,10 @@ from .common import SQLALCHEMY_DATABASE_URL, name -async_engine = create_async_engine(SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True) +async_engine = create_async_engine(SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True, + connect_args={ + 'sslmode': 'require' + }) AsyncSessionLocal = sessionmaker( bind=async_engine, diff --git a/creyPY/fastapi/db/session.py b/creyPY/fastapi/db/session.py index b400468..7c746ed 100644 --- a/creyPY/fastapi/db/session.py +++ b/creyPY/fastapi/db/session.py @@ -6,7 +6,10 @@ from .common import SQLALCHEMY_DATABASE_URL, name -engine = create_engine(SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True) +engine = create_engine(SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True, + connect_args={ + 'sslmode': 'require' + }) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) From 32bf08945603e3dc0727aa3ed8c9496dd9b60278 Mon Sep 17 00:00:00 2001 From: creyD Date: Thu, 2 Jan 2025 22:20:49 +0000 Subject: [PATCH 22/23] Adjusted files for isort & autopep --- creyPY/fastapi/db/async_session.py | 7 +++---- creyPY/fastapi/db/session.py | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/creyPY/fastapi/db/async_session.py b/creyPY/fastapi/db/async_session.py index 55d2c5a..f0186ca 100644 --- a/creyPY/fastapi/db/async_session.py +++ b/creyPY/fastapi/db/async_session.py @@ -5,10 +5,9 @@ from .common import SQLALCHEMY_DATABASE_URL, name -async_engine = create_async_engine(SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True, - connect_args={ - 'sslmode': 'require' - }) +async_engine = create_async_engine( + SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True, connect_args={"sslmode": "require"} +) AsyncSessionLocal = sessionmaker( bind=async_engine, diff --git a/creyPY/fastapi/db/session.py b/creyPY/fastapi/db/session.py index 7c746ed..5ae5c6d 100644 --- a/creyPY/fastapi/db/session.py +++ b/creyPY/fastapi/db/session.py @@ -6,10 +6,9 @@ from .common import SQLALCHEMY_DATABASE_URL, name -engine = create_engine(SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True, - connect_args={ - 'sslmode': 'require' - }) +engine = create_engine( + SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True, connect_args={"sslmode": "require"} +) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) From 573f59349f2396cb9f9681f48bb254fa65b71f7b Mon Sep 17 00:00:00 2001 From: vikynoah Date: Wed, 8 Jan 2025 19:37:10 +0100 Subject: [PATCH 23/23] fix: changes to post method in testing_async (#28) --- creyPY/fastapi/testing_async.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/creyPY/fastapi/testing_async.py b/creyPY/fastapi/testing_async.py index b61a6fd..836e3f1 100644 --- a/creyPY/fastapi/testing_async.py +++ b/creyPY/fastapi/testing_async.py @@ -35,7 +35,8 @@ async def post( ) if re.status_code != r_code: print(re.content) - assert r_code == re.status_code + if not raw_response: + assert r_code == re.status_code return re.json() if not raw_response else re async def post_file(