Skip to content

Commit

Permalink
Extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnGrubba committed Jul 29, 2024
1 parent e5142af commit a9dea09
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
**config.json
**.pyc

**.env**
**.env**
**extensions/
2 changes: 1 addition & 1 deletion docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ services:
- ./config:/src/app/config
- ./src:/src/app
command: |
bash -c 'uvicorn api.main:app --reload --host 0.0.0.0 --port 80'
bash -c 'uvicorn api.main:app --reload --log-level debug --host 0.0.0.0 --port 80'
depends_on:
- db
- redis
Expand Down
77 changes: 77 additions & 0 deletions docs/advanced/extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
Since the Goal of EZAuth is to be as extensive as possible, it is possible to create your own extensions to add functionality to EZAuth.

## Preparation

To allow Extensions, you need to create a new folder called `extensions/` in the `src/` directory, if it doesn't already exist. This folder is not tracked by git, so you can safely add your own extensions without the risk of losing them when updating the repository.

```sh
mkdir -p src/extensions
```

!!! Tip "EZAuth Developer Mode"
To also enjoy a greater developement experience, you can start EZAuth in [Development Mode](https://github.com/JohnGrubba/ezauth?tab=readme-ov-file#developement). This will allow you to see changes in your extensions without restarting the server.
It will also show you more detailed error messages, which can be helpful when developing extensions.



## Creating an Extension

!!! Danger "Extensions can break **EVERYTHING (including all your userdata)**"
Since Extensions are a really advanced feature, they are not recommended to be developed by beginners. However, if you are an experienced developer, you can create your own extensions.

1. New Folder for the Extension

Inside the `extensions/` directory, create a new folder for your extension. The name of the folder should be the name of your extension. We force a folder, to make it easier for you to structure your extension and keep an overview of all the installed extensions. We use the name `my_extension` for this example.

```sh
mkdir src/extensions/my_extension
```

2. Make the Extension Loadable

To make EZAuth recognize your extension as a valid one, add a `__init__.py` file to your newly created extension folder. This file **must** be there and export a `router` which is a FastAPI Router.

Depending on the structure of your Extension, you can either write the whole extension into the `__init__.py` file or import only the router from another file.

!!! Tip "Imports in Extension Files"
Be careful when doing imports in extensions, as by just importing other files using `.myextension` won't work (importlib can't find the module). You have to use the full import path to the file starting with `extensions.my_extension.` -> E.g. `extensions.my_extension.myextension`

```python title="src/extensions/my_extension/myextension.py"
from fastapi import APIRouter
router = APIRouter(
prefix="/test",
tags=["Test Extension"]
)
@router.get("")
async def test():
"""
# Test Endpoint
"""
pass
```

```python title="src/extensions/my_extension/__init__.py"
from extensions.my_extension.myextension import router
```

3. Provide some information about the Extension

To make it easier for users to understand what your extension does, you can provide a `README.md` file in your extension folder. This file can contain information about the extension, how to use it, and what it does.

```md title="src/extensions/my_extension/README.md"
# My Extension
This is a test extension for EZAuth.
```

This is especially useful if you want to share your extension with others.


## Downloading Extensions

If you want to use an extension that someone else has created, you can download it from a repository and place it in the `extensions/` directory. The extension should be structured as described above.

Stay tuned for a repository of extensions that you can use to enhance your EZAuth experience.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ nav:
- Further Customization: advanced/further_custom.md
- OAuth: advanced/oauth.md
- SSL / HTTPS: advanced/ssl.md
- Extensions: advanced/extensions.md
theme:
name: material
logo: "ezauth_logo.png"
Expand Down
39 changes: 39 additions & 0 deletions src/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
from api.oauth_providers import router as oauthRouter
from api.sessions import router as sessionsRouter
import logging
import os
import importlib
import importlib.util
from tools import SecurityConfig

logging.basicConfig(format="%(message)s", level=logging.INFO, force=True)
logger = logging.getLogger("uvicorn")

app = FastAPI(
title="EZAuth API",
Expand Down Expand Up @@ -50,3 +54,38 @@ async def up():
app.include_router(twofactorRouter)
app.include_router(oauthRouter)
app.include_router(internalRouter)


def load_extensions():
# Extension Loading
extensions_dir = "/src/app/extensions/"
if not os.path.exists(extensions_dir):
return
modules = []
for item in os.listdir(extensions_dir):
item_path = os.path.join(extensions_dir, item)
init_file = os.path.join(item_path, "__init__.py")
if os.path.isdir(item_path) and os.path.isfile(init_file):
spec = importlib.util.spec_from_file_location(item, init_file)
module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(module)
except Exception as e:
logger.error(f"Failed to load extension {item}: {e}")
if logger.level == logging.INFO:
raise e
continue
modules.append([spec, module])

for spec, module in modules:
app.include_router(module.router)

logger.info(
"\u001b[32m-> Loaded Extensions: "
+ ", ".join([module.__name__ for spec, module in modules])
+ "\u001b[0m"
)


load_extensions()
logger.info("\u001b[32m--- API Startup Done ---\u001b[0m")

0 comments on commit a9dea09

Please sign in to comment.