From 961cf6da26c944e4b6e3a5b8cd009efd7c219fc0 Mon Sep 17 00:00:00 2001 From: ablogo Date: Sat, 22 Aug 2026 12:50:46 +0900 Subject: [PATCH] put all enviroment variables in one place --- .env | 3 +- Dockerfile | 2 +- src/__init__.py | 2 +- src/dependency_injection/containers.py | 29 ++++++----- src/main.py | 7 +-- src/middlewares/auth_roles_jwt.py | 2 +- src/models/sign_up_model.py | 2 - src/routers/auth_router.py | 12 ++--- src/routers/oauth2_router.py | 2 +- src/routers/products_router.py | 3 +- src/routers/users_router.py | 2 +- src/services/jwt_service.py | 13 +++-- src/services/oauth_google_service.py | 14 +++--- src/services/user_service.py | 7 +-- src/settings_validator.py | 67 ++++++++++++++++++++++++++ 15 files changed, 113 insertions(+), 54 deletions(-) create mode 100644 src/settings_validator.py diff --git a/.env b/.env index be25dfe..9c57567 100644 --- a/.env +++ b/.env @@ -20,4 +20,5 @@ GOOGLE_OAUTH_CLIENT= GOOGLE_OAUTH_SECRET= GOOGLE_OAUTH_REDIRECT_RESPONSE=https://127.0.0.1:8000/auth/google-response GOOGLE_OAUTH_JS_ORIGINS="http://127.0.0.1:8000,http://localhost:8081" -GOOGLE_OAUTH_SCOPES="https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/userinfo.profile,openid" \ No newline at end of file +GOOGLE_OAUTH_SCOPES="https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/userinfo.profile,openid" +TZ=UTC \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1b1f4f0..3e411e8 100755 --- a/Dockerfile +++ b/Dockerfile @@ -9,4 +9,4 @@ RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt COPY ./src /app/src -CMD [ "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "80" ] \ No newline at end of file +CMD [ "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "7010" ] \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py index 11e188a..fcd51d6 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,2 +1,2 @@ "Authentication microservice, the goal of this project is to learn Python and FastApi" -__version__= "0.1.0" \ No newline at end of file +__version__= "1.0.0" \ No newline at end of file diff --git a/src/dependency_injection/containers.py b/src/dependency_injection/containers.py index 419c71c..e421805 100644 --- a/src/dependency_injection/containers.py +++ b/src/dependency_injection/containers.py @@ -1,13 +1,15 @@ from dependency_injector import containers, providers -import os -from dotenv import load_dotenv from log2mongo import log2mongo +from src.settings_validator import Settings from src.services import mongodb_service from src.services.crypto_service import CryptoService from src.services.totp_service import TOTP -load_dotenv() +#settings = Settings() + +#def get_settings(): +# return settings class Container(containers.DeclarativeContainer): @@ -25,19 +27,20 @@ class Container(containers.DeclarativeContainer): "src.services.totp_service", ]) - #config = providers.Configuration(ini_files=["config.ini"]) + #get_settings = providers.Callable(get_settings) + config = providers.Configuration(default={ "d": Settings().__dict__ }) logging = providers.Singleton( log2mongo, - os.environ["LOG_DB_URL"], #config.log.db_url, - os.environ["LOG_DATABASE_NAME"], #config.log.db_database, - level = os.environ["LOG_LEVEL"], #config.log.level + config.d.LOG_DB_URL, + config.d.LOG_DATABASE_NAME, + level = config.d.LOG_LEVEL, ) database_client = providers.Singleton( mongodb_service.MongoAsyncService, - os.environ["DB_URL"], #config.database.url, - os.environ["DB_NAME"], #config.database.name + config.d.DB_URL, + config.d.DB_NAME, ) crypto_service = providers.Singleton( @@ -47,9 +50,9 @@ class Container(containers.DeclarativeContainer): totp = providers.Singleton( TOTP, - os.environ["TOTP_SECRET"], - os.environ["TOTP_DIGEST"], - int(os.environ["TOTP_TIME_STEP"]), - int(os.environ["TOTP_RETURN_DIGITS"]), + config.d.TOTP_SECRET, + config.d.TOTP_DIGEST, + config.d.TOTP_TIME_STEP, + config.d.TOTP_RETURN_DIGITS, logging ) \ No newline at end of file diff --git a/src/main.py b/src/main.py index bff948e..a0bdbb8 100644 --- a/src/main.py +++ b/src/main.py @@ -2,8 +2,6 @@ from fastapi import FastAPI from contextlib import asynccontextmanager from fastapi.middleware.cors import CORSMiddleware -from dotenv import load_dotenv -import os from src.routers import auth_router, products_router, users_router, oauth2_router from src.routers.admin import users_router as admin_user_router, security_router @@ -12,8 +10,8 @@ from src.dependency_injection.containers import Container from src.dependencies import close_db -load_dotenv() -origins = os.environ["CORS_ALLOWED_HOSTS"].split(',') if os.environ["CORS_ALLOWED_HOSTS"] else [] +container = Container() +origins = container.config.d.CORS_ALLOWED_HOSTS().split(',') if container.config.d.CORS_ALLOWED_HOSTS() else [] @asynccontextmanager async def lifespan(app: FastAPI): @@ -29,7 +27,6 @@ async def shutdown(): print("Website is shutting down!") app = FastAPI(lifespan=lifespan) -container = Container() app.add_middleware( CORSMiddleware, allow_origins = origins, diff --git a/src/middlewares/auth_roles_jwt.py b/src/middlewares/auth_roles_jwt.py index c8badb9..710b9b8 100644 --- a/src/middlewares/auth_roles_jwt.py +++ b/src/middlewares/auth_roles_jwt.py @@ -1,5 +1,5 @@ from typing import Dict -from fastapi import HTTPException, Request +from fastapi import Request from fastapi.security import OAuth2PasswordBearer from src.services.jwt_service import verify_token_and_roles diff --git a/src/models/sign_up_model.py b/src/models/sign_up_model.py index b1d5922..63cc3ff 100644 --- a/src/models/sign_up_model.py +++ b/src/models/sign_up_model.py @@ -1,5 +1,3 @@ -from datetime import datetime -from typing import Optional from pydantic import BaseModel class SignUp(BaseModel): diff --git a/src/routers/auth_router.py b/src/routers/auth_router.py index 02e953e..c2d15ae 100644 --- a/src/routers/auth_router.py +++ b/src/routers/auth_router.py @@ -1,7 +1,6 @@ from typing import Annotated -from fastapi import APIRouter, Depends, HTTPException, Request, Response, status +from fastapi import APIRouter, Depends, Request, Response, status from fastapi.security import OAuth2PasswordRequestForm -from fastapi.responses import JSONResponse from dependency_injector.wiring import Provide, inject from log2mongo import log2mongo @@ -28,8 +27,9 @@ async def sign_up(model: SignUp, db: db_dependency, log: log_dependency, request client_host = request.client.host # type: ignore log.logger.info(f"{ model.email } sign-up from ip: { client_host }") content = "" - status_code = 0 - media_type = "" + status_code = status.HTTP_400_BAD_REQUEST + media_type = "application/json" + user = await create_user(db.database, User( name = model.name, email = model.email, @@ -40,10 +40,8 @@ async def sign_up(model: SignUp, db: db_dependency, log: log_dependency, request if user: content = user.model_dump_json() status_code = status.HTTP_200_OK - media_type = "application/json" else: content = "Unable to create user" - status_code = status.HTTP_400_BAD_REQUEST media_type = "text/plain" except Exception as e: @@ -62,7 +60,7 @@ async def sign_in(form_data: Annotated[OAuth2PasswordRequestForm, Depends()], db media_type = "text/plain" result, token = await login(form_data.username, form_data.password, db.database) if token: - content = token.model_dump() + content = token.model_dump_json() status_code = status.HTTP_200_OK media_type = "application/json" elif result and token is None: diff --git a/src/routers/oauth2_router.py b/src/routers/oauth2_router.py index 5c011b7..2566adf 100644 --- a/src/routers/oauth2_router.py +++ b/src/routers/oauth2_router.py @@ -1,5 +1,5 @@ from typing import Annotated -from fastapi import APIRouter, Depends, HTTPException, Request, Response, status +from fastapi import APIRouter, Depends, Request, Response, status from fastapi.responses import JSONResponse, RedirectResponse from dependency_injector.wiring import Provide, inject from log2mongo import log2mongo diff --git a/src/routers/products_router.py b/src/routers/products_router.py index 0251c9a..f2ed7e9 100644 --- a/src/routers/products_router.py +++ b/src/routers/products_router.py @@ -4,7 +4,6 @@ from pymongo.asynchronous.database import AsyncDatabase from src.services.jwt_service import verify_token -from src.services.mongodb_service import MongoService from src.custom_json import MJSONEncoder from src.models.product_model import Product from src.middlewares.auth_jwt import JWTCustom @@ -46,7 +45,7 @@ async def list_items(token: Annotated[str, Depends(oauth2_scheme)], db: db_depen p = [(Product(**x)).dict() for x in products] return p -# Route to add an item +# Route to update an item @router.put("/products") async def update_item(product: Product, db: db_dependency): diff --git a/src/routers/users_router.py b/src/routers/users_router.py index e076182..3d3936a 100644 --- a/src/routers/users_router.py +++ b/src/routers/users_router.py @@ -1,6 +1,6 @@ from typing import Annotated from fastapi import APIRouter, Depends, Response, UploadFile, status -from fastapi.responses import JSONResponse, StreamingResponse +from fastapi.responses import StreamingResponse from dependency_injector.wiring import Provide, inject import io diff --git a/src/services/jwt_service.py b/src/services/jwt_service.py index dd0080f..9dbe3a5 100644 --- a/src/services/jwt_service.py +++ b/src/services/jwt_service.py @@ -3,18 +3,17 @@ from dependency_injector.wiring import Provide, inject from fastapi import HTTPException, Request from log2mongo import log2mongo -from dotenv import load_dotenv -import os, jwt +import jwt from src.services.crypto_service import CryptoService from src.dependency_injection.containers import Container crypto_service: CryptoService = Provide[Container.crypto_service] log_service: log2mongo = Provide[Container.logging] -load_dotenv() +config = Container.config.d() @inject -async def create_token(data: dict, expire_time: timedelta = timedelta(minutes=int(str(os.environ["JWT_EXPIRE_MINUTES"]))), crypto = crypto_service, log = log_service): +async def create_token(data: dict, expire_time: timedelta = timedelta(minutes=int(config["JWT_EXPIRE_MINUTES"])), crypto = crypto_service, log = log_service): try: for item in data: if isinstance(data[item], list): @@ -26,7 +25,7 @@ async def create_token(data: dict, expire_time: timedelta = timedelta(minutes=in expire = datetime.now(timezone.utc) + expire_time data.update({ "exp": expire }) - encode_jwt = jwt.encode(data, str(os.environ["JWT_SECRET_KEY"]), algorithm= os.environ["JWT_ALGORITHM"]) + encode_jwt = jwt.encode(data, str(config["JWT_SECRET_KEY"]), algorithm= config["JWT_ALGORITHM"]) return encode_jwt except Exception as e: log.logger.error(e) @@ -74,7 +73,7 @@ async def verify_token_from_requests(request: Request): @inject async def verify(request_token: str, log = log_service): try: - payload = jwt.decode(request_token, str(os.environ["JWT_SECRET_KEY"]), os.environ["JWT_ALGORITHM"]) + payload = jwt.decode(request_token, str(config["JWT_SECRET_KEY"]), config["JWT_ALGORITHM"]) return payload except jwt.ExpiredSignatureError as e: log.logger.error(e) @@ -89,7 +88,7 @@ async def verify(request_token: str, log = log_service): @inject async def get_email(token: str, crypto = crypto_service, log = log_service): try: - payload = jwt.decode(token, str(os.environ["JWT_SECRET_KEY"]), os.environ["JWT_ALGORITHM"]) + payload = jwt.decode(token, str(config["JWT_SECRET_KEY"]), config["JWT_ALGORITHM"]) return await crypto.decrypt_text(payload.get("sub")) except Exception as e: log.logger.error(e) diff --git a/src/services/oauth_google_service.py b/src/services/oauth_google_service.py index 00508e0..5feb125 100644 --- a/src/services/oauth_google_service.py +++ b/src/services/oauth_google_service.py @@ -1,4 +1,3 @@ -import os import google.oauth2.credentials from log2mongo import log2mongo import google_auth_oauthlib.flow @@ -7,15 +6,16 @@ from src.dependency_injection.containers import Container log_service: log2mongo = Provide[Container.logging] +config = Container.config.d() @inject async def get_auth_url(log = log_service): try: flow = google_auth_oauthlib.flow.Flow.from_client_config( - {"web":{"client_id":os.environ["GOOGLE_OAUTH_CLIENT"],"project_id":os.environ["GOOGLE_OAUTH_ID"],"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":os.environ["GOOGLE_OAUTH_SECRET"],"javascript_origins":os.environ["GOOGLE_OAUTH_JS_ORIGINS"].split(',') if os.environ["GOOGLE_OAUTH_JS_ORIGINS"] else []}}, - scopes = os.environ["GOOGLE_OAUTH_SCOPES"].split(',') if os.environ["GOOGLE_OAUTH_SCOPES"] else [] + {"web":{"client_id":config["GOOGLE_OAUTH_CLIENT"],"project_id":config["GOOGLE_OAUTH_ID"],"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":config["GOOGLE_OAUTH_SECRET"],"javascript_origins":config["GOOGLE_OAUTH_JS_ORIGINS"].split(',') if config["GOOGLE_OAUTH_JS_ORIGINS"] else []}}, + scopes = config["GOOGLE_OAUTH_SCOPES"].split(',') if config["GOOGLE_OAUTH_SCOPES"] else [] ) - flow.redirect_uri = os.environ["GOOGLE_OAUTH_REDIRECT_RESPONSE"] + flow.redirect_uri = config["GOOGLE_OAUTH_REDIRECT_RESPONSE"] auth_url , state = flow.authorization_url( acces_type = 'offline', @@ -33,10 +33,10 @@ async def get_auth_response(url: str, log = log_service): try: credentials = None flow = google_auth_oauthlib.flow.Flow.from_client_config( - {"web":{"client_id":os.environ["GOOGLE_OAUTH_CLIENT"],"project_id":os.environ["GOOGLE_OAUTH_ID"],"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":os.environ["GOOGLE_OAUTH_SECRET"],"javascript_origins":os.environ["GOOGLE_OAUTH_JS_ORIGINS"].split(',') if os.environ["GOOGLE_OAUTH_JS_ORIGINS"] else []}}, - scopes = os.environ["GOOGLE_OAUTH_SCOPES"].split(',') if os.environ["GOOGLE_OAUTH_SCOPES"] else [] + {"web":{"client_id":config["GOOGLE_OAUTH_CLIENT"],"project_id":config["GOOGLE_OAUTH_ID"],"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":config["GOOGLE_OAUTH_SECRET"],"javascript_origins":config["GOOGLE_OAUTH_JS_ORIGINS"].split(',') if config["GOOGLE_OAUTH_JS_ORIGINS"] else []}}, + scopes = config["GOOGLE_OAUTH_SCOPES"].split(',') if config["GOOGLE_OAUTH_SCOPES"] else [] ) - flow.redirect_uri = os.environ["GOOGLE_OAUTH_REDIRECT_RESPONSE"] + flow.redirect_uri = config["GOOGLE_OAUTH_REDIRECT_RESPONSE"] auth_reponse = flow.fetch_token(authorization_response = url) credentials = flow.credentials diff --git a/src/services/user_service.py b/src/services/user_service.py index b1a3629..3b20a74 100644 --- a/src/services/user_service.py +++ b/src/services/user_service.py @@ -3,8 +3,6 @@ from pymongo.asynchronous.database import AsyncDatabase from bson import ObjectId, Binary from log2mongo import log2mongo -from dotenv import load_dotenv -import os from src.services.crypto_service import CryptoService from src.services.jwt_service import get_email @@ -15,9 +13,8 @@ crypto_service: CryptoService = Provide[Container.crypto_service] log_service: log2mongo = Provide[Container.logging] -load_dotenv() -users_collection = str(os.environ["DB_USERS_COLLECTION"]) -users_pics_collection = str(os.environ["DB_USERS_PICTURES_COLLECTION"]) +users_collection = Container.config.d.DB_USERS_COLLECTION() +users_pics_collection = Container.config.d.DB_USERS_PICTURES_COLLECTION() @inject async def get_user(email: str, db: AsyncDatabase, log = log_service) -> User | None: diff --git a/src/settings_validator.py b/src/settings_validator.py new file mode 100644 index 0000000..b4c7c1c --- /dev/null +++ b/src/settings_validator.py @@ -0,0 +1,67 @@ +import os, sys +from dotenv import load_dotenv + +load_dotenv() + +class Settings(): + DB_URL: str + DB_NAME: str = "auth-service" + DB_USERS_COLLECTION: str = "users" + DB_USERS_PICTURES_COLLECTION: str = "users.pictures" + DB_USERS_CONTACTS_COLLECTION: str = "users.contacts" + DB_USERS_MESSAGES_COLLECTION: str = "users.messages" + LOG_DB_URL: str + LOG_DATABASE_NAME: str = "auth-service-logs" + LOG_LEVEL: str = "DEBUG" + JWT_SECRET_KEY: str + JWT_ALGORITHM: str = "HS256" + JWT_EXPIRE_MINUTES: int = 240 + CORS_ALLOWED_HOSTS: str = "http://localhost:8081,http://localhost:8002" + TOTP_SECRET: str + TOTP_DIGEST: str = "sha1" + TOTP_RETURN_DIGITS: int = 8 + TOTP_TIME_STEP: int = 30 + GOOGLE_OAUTH_ID: str + GOOGLE_OAUTH_CLIENT: str + GOOGLE_OAUTH_SECRET: str + GOOGLE_OAUTH_REDIRECT_RESPONSE: str = "https://127.0.0.1:8000/auth/google-response" + GOOGLE_OAUTH_JS_ORIGINS: str = "http://127.0.0.1:8000,http://localhost:8081" + GOOGLE_OAUTH_SCOPES: str = "https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/userinfo.profile,openid" + CORS_ALLOWED_HOSTS: str + TZ: str = "UTC" + ERROR: bool = False + ERROR_MESSAGE: str + + def __init__(self) -> None: + try: + print("init 1") + self.DB_URL = os.environ["DB_URL"] + self.DB_NAME = os.environ["DB_NAME"] + self.DB_USERS_COLLECTION = os.environ["DB_USERS_COLLECTION"] + self.DB_USERS_PICTURES_COLLECTION = os.environ["DB_USERS_PICTURES_COLLECTION"] + self.DB_USERS_CONTACTS_COLLECTION = os.environ["DB_USERS_CONTACTS_COLLECTION"] + self.DB_USERS_MESSAGES_COLLECTION = os.environ["DB_USERS_MESSAGES_COLLECTION"] + self.LOG_DB_URL = os.environ["LOG_DB_URL"] + self.LOG_DATABASE_NAME = os.environ["LOG_DATABASE_NAME"] + self.LOG_LEVEL = os.environ["LOG_LEVEL"] + self.JWT_SECRET_KEY = os.environ["JWT_SECRET_KEY"] + self.JWT_ALGORITHM = os.environ["JWT_ALGORITHM"] + self.JWT_EXPIRE_MINUTES = int(os.environ["JWT_EXPIRE_MINUTES"]) + self.CORS_ALLOWED_HOSTS = os.environ["CORS_ALLOWED_HOSTS"] + self.TOTP_SECRET = os.environ["TOTP_SECRET"] + self.TOTP_DIGEST = os.environ["TOTP_DIGEST"] + self.TOTP_RETURN_DIGITS = int(os.environ["TOTP_RETURN_DIGITS"]) + self.TOTP_TIME_STEP = int(os.environ["TOTP_TIME_STEP"]) + self.GOOGLE_OAUTH_ID = os.environ["GOOGLE_OAUTH_ID"] + self.GOOGLE_OAUTH_CLIENT = os.environ["GOOGLE_OAUTH_CLIENT"] + self.GOOGLE_OAUTH_SECRET = os.environ["GOOGLE_OAUTH_SECRET"] + self.GOOGLE_OAUTH_REDIRECT_RESPONSE = os.environ["GOOGLE_OAUTH_REDIRECT_RESPONSE"] + self.GOOGLE_OAUTH_JS_ORIGINS = os.environ["GOOGLE_OAUTH_JS_ORIGINS"] + self.GOOGLE_OAUTH_SCOPES = os.environ["GOOGLE_OAUTH_SCOPES"] + self.CORS_ALLOWED_HOSTS = os.environ["CORS_ALLOWED_HOSTS"] + self.TZ = os.environ["TZ"] + except Exception as e: + self.ERROR = True + self.ERROR_MESSAGE = "Missing or incorrect configuration value: " + e.__str__() + print(self.ERROR_MESSAGE) + sys.exit(78) \ No newline at end of file