Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -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"
GOOGLE_OAUTH_SCOPES="https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/userinfo.profile,openid"
TZ=UTC
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
CMD [ "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "7010" ]
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
"Authentication microservice, the goal of this project is to learn Python and FastApi"
__version__= "0.1.0"
__version__= "1.0.0"
29 changes: 16 additions & 13 deletions src/dependency_injection/containers.py
Original file line number Diff line number Diff line change
@@ -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):

Expand All @@ -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(
Expand All @@ -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
)
7 changes: 2 additions & 5 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/middlewares/auth_roles_jwt.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 0 additions & 2 deletions src/models/sign_up_model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel

class SignUp(BaseModel):
Expand Down
12 changes: 5 additions & 7 deletions src/routers/auth_router.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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,
Expand All @@ -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:
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/routers/oauth2_router.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 1 addition & 2 deletions src/routers/products_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):

Expand Down
2 changes: 1 addition & 1 deletion src/routers/users_router.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down
13 changes: 6 additions & 7 deletions src/services/jwt_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
14 changes: 7 additions & 7 deletions src/services/oauth_google_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
import google.oauth2.credentials
from log2mongo import log2mongo
import google_auth_oauthlib.flow
Expand All @@ -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',
Expand All @@ -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
Expand Down
7 changes: 2 additions & 5 deletions src/services/user_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
67 changes: 67 additions & 0 deletions src/settings_validator.py
Original file line number Diff line number Diff line change
@@ -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)