mirror of
https://github.com/DarrylNixon/ghostforge
synced 2024-04-22 06:27:20 -07:00
Basic JWT endpoints
This commit is contained in:
parent
7c5919f073
commit
1fa3d8a372
13 changed files with 180 additions and 27 deletions
22
.env
22
.env
|
@ -1,3 +1,12 @@
|
|||
# DATABASE_* variables are used both to initially configure the
|
||||
# ghostforge postgresql database and to later access the data from
|
||||
# ghostforge execution.
|
||||
DATABASE_CONTAINER_NAME=ghostforge-db
|
||||
DATABASE_USER=ghost
|
||||
DATABASE_NAME=ghostforge
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_PASSWORD=
|
||||
|
||||
# GHOSTFORGE_*_WEB_PORT variables are used to determine what
|
||||
# port the web interface is served on within the container (INTERNAL)
|
||||
# and what port this will map to on the host (HOST).
|
||||
|
@ -11,8 +20,11 @@ GHOSTFORGE_INTERNAL_WEB_PORT=1337
|
|||
# Valid values are [prod, dev]
|
||||
GHOSTFORGE_ENV=prod
|
||||
|
||||
# DATABASE_* variables are used both to initially configure the
|
||||
# ghostforge postgresql database and to later access the data from
|
||||
# ghostforge execution.
|
||||
DATABASE_USER=ghost
|
||||
DATABASE_NAME=ghostforge
|
||||
# GHOSTFORGE_DATA_DIR stores persistent files for a ghostforge instance
|
||||
# and should be mapped or stored as a volume from the docker host.
|
||||
# GHOSTFORGE_DATA_DIR is created within the ghostforge container.
|
||||
GHOSTFORGE_DATA_DIR=/data
|
||||
|
||||
# JWT_SECRET is used for authentication purposes and should be
|
||||
# randomized using the method in README.md.
|
||||
GHOSTFORGE_JWT_SECRET=
|
||||
|
|
2
.flake8
2
.flake8
|
@ -1,3 +1,3 @@
|
|||
[flake8]
|
||||
max-line-length = 160
|
||||
exclude = docs/*, .git, __pycache__
|
||||
exclude = docs/*, .git, __pycache__, build
|
||||
|
|
|
@ -2,12 +2,13 @@ FROM python:3.11-alpine
|
|||
|
||||
ENV DATABASE_PASSWORD ""
|
||||
RUN if [ -z "${DATABASE_PASSWORD}" ]; then echo "ghostforge build error: Set DATABASE_PASSWORD in .env."; exit 1; fi
|
||||
RUN if [ -z "${GHOSTFORGE_JWT_SECRET}" ]; then echo "ghostforge build error: Set GHOSTFORGE_JWT_SECRET in .env."; exit 1; fi
|
||||
|
||||
WORKDIR /ghostforge
|
||||
COPY . .
|
||||
RUN rm .env
|
||||
RUN mkdir -p "${GHOSTFORGE_DATA_DIR}"
|
||||
|
||||
RUN pip install --no-cache-dir --requirement requirements.txt
|
||||
RUN pip install .
|
||||
|
||||
ENV GHOSTFORGE_INTERNAL_WEB_PORT=8080
|
||||
|
|
|
@ -23,7 +23,8 @@ You'll need `docker-compose` installed or you can convert the contents of `docke
|
|||
```bash
|
||||
git clone https://github.com/darrylnixon/ghostforge.git && \
|
||||
cd ghostforge && \
|
||||
PW=$(/usr/bin/env python3 -c "import secrets; print(secrets.token_urlsafe(24))") | sed -i .bak "s/^DATABASE_PASSWORD=.*/DATABASE_PASSWORD=$PW" .env;
|
||||
PW=$(/usr/bin/env python3 -c "import secrets; print(secrets.token_urlsafe(32))") /bin/bash -c 'sed -i "" "s/^DATABASE_PASSWORD=.*/DATABASE_PASSWORD=$PW/" .env' && \
|
||||
JWT=$(/usr/bin/env python3 -c "import secrets; print(secrets.token_urlsafe(32))") /bin/bash -c 'sed -i "" "s/^GHOSTFORGE_JWT_SECRET=.*/GHOSTFORGE_JWT_SECRET=$JWT/" .env';
|
||||
docker-compose up --detach --build;
|
||||
docker exec --interactive --tty ghostforge ghostforge_adduser;
|
||||
```
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
"""
|
||||
Initialization constants for ghostforge.
|
||||
|
||||
Constants:
|
||||
_PROJECT (str): Name for the project
|
||||
__version__ (str): The current version of the binhop package
|
||||
"""
|
||||
_PROJECT = "ghostforge"
|
||||
__version__ = "0.0.1"
|
35
ghostforge/api.py
Normal file
35
ghostforge/api.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from fastapi import Body
|
||||
from fastapi import Depends
|
||||
from fastapi import FastAPI
|
||||
|
||||
from ghostforge.auth import check_user
|
||||
from ghostforge.auth.bearer import JWTBearer
|
||||
from ghostforge.auth.handler import signJWT
|
||||
from ghostforge.models import UserLoginSchema
|
||||
|
||||
# define("port", default=os.environ.get("GHOSTFORGE_INTERNAL_WEB_PORT", 1337), help="Webserver port", type=int)
|
||||
# define("db_host", default=os.environ.get("DATABASE_CONTAINER_NAME", "localhost"), help="Host/IP of db")
|
||||
# define("db_port", default=os.environ.get("DATABASE_PORT", 5432), help="Port of db", type=int)
|
||||
# define("db_database", default=os.environ.get("DATABASE_NAME", "ghostforge"), help="Name of db")
|
||||
# define("db_user", default=os.environ.get("DATABASE_USER", "ghostforge"), help="User with access to db")
|
||||
# define("db_password", default=os.environ.get("DATABASE_PASSWORD", "secure"), help="Password for db user")
|
||||
# define("secret_key", default=os.environ.get("DATABASE_PASSWORD", "secure"), help="Password for db user")
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/", tags=["root"])
|
||||
async def read_root() -> dict:
|
||||
return {"message": "Welcome!"}
|
||||
|
||||
|
||||
@app.post("/login", tags=["user"])
|
||||
async def user_login(user: UserLoginSchema = Body(...)):
|
||||
if check_user(user):
|
||||
return signJWT(user.name)
|
||||
return {"error": "Wrong login details!"}
|
||||
|
||||
|
||||
@app.post("/auth_check", dependencies=[Depends(JWTBearer())], tags=["auth_check"])
|
||||
async def auth_check(post) -> dict:
|
||||
return {"data": "post added."}
|
11
ghostforge/auth/__init__.py
Normal file
11
ghostforge/auth/__init__.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from ghostforge.models import UserLoginSchema
|
||||
|
||||
users = [{"name": "test", "password": "pw"}]
|
||||
|
||||
|
||||
def check_user(data: UserLoginSchema):
|
||||
print(data)
|
||||
for user in users:
|
||||
if user["name"] == data.name and user["password"] == data.password:
|
||||
return True
|
||||
return False
|
35
ghostforge/auth/bearer.py
Normal file
35
ghostforge/auth/bearer.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from fastapi import HTTPException
|
||||
from fastapi import Request
|
||||
from fastapi.security import HTTPAuthorizationCredentials
|
||||
from fastapi.security import HTTPBearer
|
||||
|
||||
from ghostforge.auth.handler import decodeJWT
|
||||
|
||||
# from https://github.com/testdrivenio/fastapi-jwt/
|
||||
|
||||
|
||||
class JWTBearer(HTTPBearer):
|
||||
def __init__(self, auto_error: bool = True):
|
||||
super(JWTBearer, self).__init__(auto_error=auto_error)
|
||||
|
||||
async def __call__(self, request: Request):
|
||||
credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
|
||||
if credentials:
|
||||
if not credentials.scheme == "Bearer":
|
||||
raise HTTPException(status_code=403, detail="Invalid authentication scheme.")
|
||||
if not self.verify_jwt(credentials.credentials):
|
||||
raise HTTPException(status_code=403, detail="Invalid token or expired token.")
|
||||
return credentials.credentials
|
||||
else:
|
||||
raise HTTPException(status_code=403, detail="Invalid authorization code.")
|
||||
|
||||
def verify_jwt(self, jwtoken: str) -> bool:
|
||||
isTokenValid: bool = False
|
||||
|
||||
try:
|
||||
payload = decodeJWT(jwtoken)
|
||||
except Exception:
|
||||
payload = None
|
||||
if payload:
|
||||
isTokenValid = True
|
||||
return isTokenValid
|
28
ghostforge/auth/handler.py
Normal file
28
ghostforge/auth/handler.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import os
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
import jwt
|
||||
|
||||
# Based on handler from https://github.com/testdrivenio/fastapi-jwt/
|
||||
|
||||
JWT_SECRET = os.environ.get("GHOSTFORGE_JWT_SECRET")
|
||||
|
||||
|
||||
def token_response(token: str):
|
||||
return {"access_token": token}
|
||||
|
||||
|
||||
def signJWT(user: str) -> Dict[str, str]:
|
||||
payload = {"user_id": user, "expires": time.time() + 600}
|
||||
token = jwt.encode(payload, JWT_SECRET, algorithm="HS256")
|
||||
|
||||
return token_response(token)
|
||||
|
||||
|
||||
def decodeJWT(token: str) -> dict:
|
||||
try:
|
||||
decoded_token = jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
|
||||
return decoded_token if decoded_token["expires"] >= time.time() else None
|
||||
except Exception:
|
||||
return {}
|
|
@ -0,0 +1,9 @@
|
|||
import os
|
||||
|
||||
import uvicorn
|
||||
|
||||
|
||||
def start_api() -> None:
|
||||
uvicorn.run(
|
||||
"ghostforge.api:app", host="0.0.0.0", port=os.environ.get("GHOSTFORGE_INTERNAL_WEB_PORT", 1337), reload=True
|
||||
)
|
27
ghostforge/models.py
Normal file
27
ghostforge/models.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class UserSchema(BaseModel):
|
||||
name: str = Field(...)
|
||||
password: str = Field(...)
|
||||
created: datetime.datetime = datetime.datetime
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "Jeremy Tootsieroll",
|
||||
"password": "notarealpassword",
|
||||
"created": "2021-03-05T08:21:00.000Z",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class UserLoginSchema(BaseModel):
|
||||
name: str = Field(...)
|
||||
password: str = Field(...)
|
||||
|
||||
class Config:
|
||||
schema_extra = {"example": {"name": "Jeremy Tootsieroll", "password": "notarealpassword"}}
|
|
@ -1,5 +1,5 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=61.0"]
|
||||
requires = ["setuptools>=67.8"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
|
@ -9,22 +9,26 @@ authors = [{ name = "ghostforge", email = "git@nixon.mozmail.com" }]
|
|||
description = "A false identity information manager for privacy prudent persons"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["tornado", "queries"]
|
||||
license = "MIT"
|
||||
license = { text = "MIT" }
|
||||
dependencies = [
|
||||
"fastapi>=0.95.2",
|
||||
"uvicorn>=0.22.0",
|
||||
"loguru>=0.7.0",
|
||||
"passlib>=1.7.4",
|
||||
"pydantic>=1.10.8",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
ghostforge_serve = "ghostforge.cli:service"
|
||||
ghostforge_serve = "ghostforge.cli:start_api"
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/DarrylNixon/ghostforge"
|
||||
"Bug Tracker" = "https://github.com/DarrylNixon/ghostforge/issues"
|
||||
|
||||
[tool.bandit]
|
||||
exclude_dirs = ["/doc"]
|
||||
skips = []
|
||||
exclude_dirs = ["/doc", "/build"]
|
||||
# TODO: Stop skipping B104 (binding on 0.0.0.0), is there a nice way to get a good docker bind address?
|
||||
skips = ["B104"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
.
|
Loading…
Reference in a new issue