Skip to content

Commit

Permalink
Merge pull request #4 from JohnGrubba/dev
Browse files Browse the repository at this point in the history
Internal Columns and Docker Optimizations
  • Loading branch information
JohnGrubba authored Jul 2, 2024
2 parents 82225ab + 0e6cb53 commit 95563a1
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 20 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@
- ⚠️ Make sure to always have a backup of your user data.

> [!NOTE]
> You can find the Documentation <a href="https://johngrubba.github.io/ezauth/" target="_blank">here</a>
> You can find the Documentation <a href="https://johngrubba.github.io/ezauth/" target="_blank">here</a>
## Developement

To enable a efficient development process, you can start the Service with hot reloading enabled. This will automatically restart the service when a file is changed.

```sh
docker compose -f .\docker-compose.dev.yml up -d --build
```
8 changes: 7 additions & 1 deletion config/configtemplate.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
"auto_cookie_name": "session"
},
"internal": {
"internal_api_key": "CHANGE_ME_NOW"
"internal_api_key": "CHANGE_ME_NOW",
"internal_columns": [
"_id"
]
},
"account_features": {
"enable_change_password": true
}
}
Empty file.
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ services:
- ./config:/src/app/config
db:
image: mongo
ports:
- '27017:27017'
expose:
- 27017
restart: always
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
Expand Down
8 changes: 7 additions & 1 deletion docs/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,10 @@ Make sure that all parameters are set correctly before starting the service.

| Parameter | Description |
|------------|-------------|
| `internal.internal_api_key` | **Datatype:** String <br> **Default:** `CHANGE_ME_NOW` <br> This is **sensitive** information and must **never** be exposed anywhere. |
| `internal.internal_api_key` | **Datatype:** String <br> **Default:** `CHANGE_ME_NOW` <br> This is **sensitive** information and must **never** be exposed anywhere. |
| `internal.internal_columns` | **Datatype:** List <br> **Default:** `["_id"]` <br> Columns that should only be revealed via the internal API. This example will never reveal `_id` to public endpoints, but just to the `/internal` endpoints as well as E-Mails. |

### Account Features Configuration
| Parameter | Description |
|------------|-------------|
| `account_features.enable_change_password` | **Datatype:** Boolean <br> **Default:** `true` <br> Enable or disable the password reset feature. |
9 changes: 9 additions & 0 deletions docs/information.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ To guarantee the security of your user data, EZAuth uses the following security
- **2FA**: Two-factor authentication can be enabled for users.
- **OAuth2**: OAuth2 can be enabled for users.

## Documentation
You are reading the informative documentation for EZAuth.
If you need a documentation of all the endpoints, you can start the service and navigate to the `/docs` endpoint to find the API Documentation.
Because we utilize FastAPI, the documentation is done automatically and displayed via Swagger UI.
This also makes the API OpenAPI compliant.

!!! Info "API Documentation"
Navigate to [`http://localhost:3250/docs`](http://localhost:3250/docs) to see the API Documentation.

## Code Examples

!!! Info "Official Libraries"
Expand Down
8 changes: 4 additions & 4 deletions src/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
FROM python:3.12
FROM python:3.12-slim

WORKDIR /src/app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

WORKDIR /src/app

COPY ./requirements.txt /src/app/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /src/app/requirements.txt

COPY . /src/app

CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "80", "--log-level", "info"]
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "80", "--log-level", "critical"]
23 changes: 23 additions & 0 deletions src/api/dependencies/authenticated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from fastapi import HTTPException, Cookie
from tools import SessionConfig, InternalConfig
from tools import users_collection, sessions_collection
import logging


async def get_pub_user(
session_token: str = Cookie(default=None, alias=SessionConfig.auto_cookie_name)
):
if not session_token:
logging.debug("No session token")
raise HTTPException(status_code=401)
session = sessions_collection.find_one({"session_token": session_token})
if not session:
logging.debug("No session found")
raise HTTPException(status_code=401)
user = users_collection.find_one(
{"_id": session["user_id"]}, InternalConfig.internal_columns
)
if not user:
logging.debug("No user for session found")
raise HTTPException(status_code=401)
return user
15 changes: 12 additions & 3 deletions src/api/internal.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from fastapi import APIRouter, Header, HTTPException, Depends, BackgroundTasks
from typing import Annotated
from tools import broadcast_emails, InternalConfig
from api.model import BroadCastEmailRequest
from threading import Lock

email_task_running = Lock()


async def check_internal_key(internal_api_key: Annotated[str, Header()]):
async def check_internal_key(internal_api_key: str = Header(default=None)):
if not internal_api_key:
raise HTTPException(status_code=401)
if internal_api_key != InternalConfig.internal_api_key:
Expand All @@ -18,7 +17,6 @@ async def check_internal_key(internal_api_key: Annotated[str, Header()]):
prefix="/internal",
tags=["Internal API"],
dependencies=[Depends(check_internal_key)],
responses={401: {"description": "Unauthorized"}},
)


Expand Down Expand Up @@ -53,3 +51,14 @@ async def broadcast_email(
broadcast_request.mongodb_search_condition,
)
return {"status": "E-Mail Task Started"}


@router.get("/profile")
async def profile():
"""
# Get Profile Information
## Description
This endpoint is used to get the whole profile information of the user. (Including Internal Information)
"""
return {"status": "ok"}
2 changes: 2 additions & 0 deletions src/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from api.signup import router as signupRouter
from api.login import router as loginRouter
from api.internal import router as internalRouter
from api.profile import router as profileRouter
import logging

logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.INFO)
Expand Down Expand Up @@ -37,3 +38,4 @@ async def root():
app.include_router(signupRouter)
app.include_router(loginRouter)
app.include_router(internalRouter)
app.include_router(profileRouter)
20 changes: 20 additions & 0 deletions src/api/profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from fastapi import APIRouter, Depends
from api.dependencies.authenticated import get_pub_user


router = APIRouter(
prefix="/profile",
tags=["Profile"],
dependencies=[Depends(get_pub_user)],
)


@router.get("/")
async def profile(user: dict = Depends(get_pub_user)):
"""
# Get Profile Information
## Description
This endpoint is used to get the public profile information of the user.
"""
return user
10 changes: 8 additions & 2 deletions src/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
from .db import users_collection, sessions_collection, insecure_cols
from .conf import SignupConfig, EmailConfig, SessionConfig, InternalConfig
from .db import users_collection, sessions_collection
from .conf import (
SignupConfig,
EmailConfig,
SessionConfig,
InternalConfig,
AccountFeaturesConfig,
)
from .mail import send_email, broadcast_emails
13 changes: 13 additions & 0 deletions src/tools/conf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import json
from collections import ChainMap

config = json.load(open("/src/app/config/config.json", "rb"))

# Columns that should never leave EZAuth (maybe get more in the future)
insecure_cols = {"password": 0}
# Columns that can leave EZAuth but should only be used internally can be defined in config


class SignupConfig:
enable_conf_email: bool = config["signup"]["enable_conf_email"]
Expand All @@ -27,3 +32,11 @@ class SessionConfig:

class InternalConfig:
internal_api_key: str = config["internal"]["internal_api_key"]
internal_columns: dict = dict(
ChainMap(*[{col: 0} for col in config["internal"]["internal_columns"]])
)
internal_columns.update(insecure_cols)


class AccountFeaturesConfig:
enable_change_password: bool = config["account_features"]["enable_change_password"]
10 changes: 6 additions & 4 deletions src/tools/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@
users_collection = db.get_collection("users")
sessions_collection = db.get_collection("sessions")

# Find Users by email and username fast (id is already indexed)
users_collection.create_index("email", unique=True)
users_collection.create_index("username", unique=True)
# Find Sessions by session_token fast
sessions_collection.create_index("session_token", unique=True)

sessions_collection.drop_index("createdAt_1")
try:
sessions_collection.drop_index("createdAt_1")
except:
pass
# Set TTL For Sessions
sessions_collection.create_index(
"createdAt", expireAfterSeconds=SessionConfig.session_expiry_seconds
)

# Columns that should never leave EZAuth (maybe get more in the future)
insecure_cols = {"password": 0}
6 changes: 4 additions & 2 deletions src/tools/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from . import EmailConfig
import logging
from threading import Lock
from tools import users_collection, insecure_cols
from tools import users_collection, InternalConfig


def load_template(template_name: str, **kwargs) -> str:
Expand Down Expand Up @@ -39,7 +39,9 @@ def broadcast_emails(
):
# Iter over all users and send them an email
try:
cursor = users_collection.find(mongodb_search_condition, insecure_cols)
cursor = users_collection.find(
mongodb_search_condition, InternalConfig.internal_columns
)
for user in cursor:
try:
send_email(template_name, user["email"], **user)
Expand Down

0 comments on commit 95563a1

Please sign in to comment.