Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sample code for Dashboard API #2

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
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
141 changes: 141 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,144 @@ node_modules
.idea
package-lock.json
mongo_data
.vscode/

#Python default gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/
18 changes: 18 additions & 0 deletions dashboard_backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
We want to run fastapi with `systemd` so it will be handled automatically. Here's a sample service file


Note I'm using hard coded paths from my local user account, you will want to change this

```[Unit]
Description=Gunicorn manager for Uvicorn serving maia_dash_api
After=network.target

[Service]
User=reidmcy
Group=www-data
WorkingDirectory=/home/reidmcy/team-project-4-csslab-uoft/dashboard_backend
ExecStart=/home/reidmcy/miniconda/bin/gunicorn --timeout 1000 --env GUNICORN_DEPLOY=PROD --log-file logs/activity.log --access-logfile logs/main_access.log --workers 6 --worker-class uvicorn.workers.UvicornWorker --bind unix:services/maia_dash_api.sock -m 007 main:app

[Install]
WantedBy=multi-user.target
```
4 changes: 4 additions & 0 deletions dashboard_backend/maia_dash_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .auth import *
from .database import *

__version__ = '0.0.1'
3 changes: 3 additions & 0 deletions dashboard_backend/maia_dash_api/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .oauth import auth_via_lichess, login_via_lichess
from .auth_router import auth_router, jwt_contr
from .auth_schemas import *
74 changes: 74 additions & 0 deletions dashboard_backend/maia_dash_api/auth/auth_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import datetime

from ..database import get_dash_db

default_username = "dash-guest"


async def get_user_info(user_id):
client = await get_dash_db()
return await client.users.find_one({"_id": user_id})


async def log_auth_event(client, event_dict):
event_dict.update({"event_date": datetime.datetime.now()})
await client.auth_events.insert_one(event_dict)

async def set_lichess_username(user_id, user_name):
client = await get_dash_db()
await client.users.update_one(
{"_id": user_id},
{"$set": {"lichess_username": user_name}},
)
return await client.users.find_one({"_id": user_id})

async def register_user_db(user_id):
client = await get_db()
current_dt = datetime.datetime.now()
result = await client.users.insert_one(
{
"_id": user_id,
"user_id": user_id,
"provided_username": default_username,
"lichess_username": None,
"creation_date": current_dt,
}
)
return result


async def log_user_login(user_id, how, screen_width=None, screen_height=None):
client = await get_dash_db()
await log_auth_event(
client,
{
"user_id": user_id,
"how": how,
"screen_width": screen_width,
"screen_height": screen_height,
},
)


async def log_player_data(user_dict):
client = await get_dash_db()
await client["user_data"].insert_one(user_dict)


async def set_lichess_username(user_id, username):
client = await get_dash_db()
update_result = client.users.update_one(
{"_id": user_id},
{"$set": {"lichess_username": username}},
)
await log_auth_event(
client,
{
"user_id": user_id,
"event_type": "set_username",
"lichess_name": True,
"username": username,
},
)
await update_result
return user_id
54 changes: 54 additions & 0 deletions dashboard_backend/maia_dash_api/auth/auth_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import fastapi
import fastapi.responses

from .jwt_management import JWTBearer, signJWT
from .auth_schemas import JWT_Response, gen_user_name
from .auth_db import (
get_user_info,
register_user_db,
log_user_login,
default_username,
)

auth_router = fastapi.APIRouter(prefix="/auth",tags=['Auth'])

jwt_contr = JWTBearer()


@auth_router.get("/register", response_model=JWT_Response)
async def register_user(background_tasks: fastapi.BackgroundTasks):
user_id = gen_user_name()
background_tasks.add_task(register_user_db, user_id)
return {
"user_id": user_id,
"jwt": signJWT(user_id),
"provided_username": default_username,
"lichess_username": None,
}


@auth_router.post("/login_id")
async def login_id(
background_tasks: fastapi.BackgroundTasks,
user_id: str,
screen_width: int,
screen_height: int,
):
user_dict = await get_user_info(user_id)
if user_dict is None:
raise fastapi.HTTPException(
status_code=403,
detail=f"Unreconized user ID: '{user_id}'.",
)
background_tasks.add_task(log_user_login, "user_id", screen_width, screen_height)
return {
"user_id": user_id,
"provided_username": user_dict["provided_username"],
"lichess_username": user_dict["lichess_username"],
"jwt": signJWT(user_id),
}


@auth_router.get("/add_get_user_info")
async def add_get_user_info(token: dict = fastapi.Depends(jwt_contr)):
return await get_user_info(token["user_id"])
31 changes: 31 additions & 0 deletions dashboard_backend/maia_dash_api/auth/auth_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import random
import typing

import pydantic

import jwt #pyjwt

from .jwt_management import jwt_duration


def gen_user_name():
return f"dash-guest-{random.randint(100000,1000000)}"


class JWT_Token(pydantic.BaseModel):
access_token: str = jwt.encode(
{
"user_id": "example",
"expires": 0,
},
"NOT THE REAL SECRET",
algorithm="HS256",
)


class JWT_Response(pydantic.BaseModel):
user_id: str = gen_user_name()
jwt: JWT_Token
jwt_duration: int = jwt_duration
provided_username: typing.Optional[str] = None
lichess_username: typing.Optional[str] = None
63 changes: 63 additions & 0 deletions dashboard_backend/maia_dash_api/auth/jwt_management.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import time
import typing

import jwt #pyjwt

import fastapi
import fastapi.security

SECRET = "JWT_SECRET_TODO_MAKE_BETTER"

ALGO = "HS256"
jwt_duration = 120 #seconds


def signJWT(user_id: str) -> typing.Dict[str, str]:
payload = {"user_id": user_id, "expires": time.time() + jwt_duration}
token = jwt.encode(payload, SECRET, algorithm=ALGO)
return {"access_token": token}


def decodeJWT(token: str) -> dict:
if token.startswith("access_token: "):
token = token.replace("access_token: ", "")
try:
decoded_token = jwt.decode(token, SECRET, algorithms=[ALGO])
except jwt.DecodeError:
return None
if decoded_token["expires"] >= time.time():
return decoded_token
else:
return None


async def register(user_id):
return signJWT(user_id)


class JWTBearer(fastapi.security.HTTPBearer):
def __init__(self, auto_error: bool = True):
super().__init__(auto_error=auto_error)

async def __call__(self, request: fastapi.Request):
credentials: fastapi.security.HTTPAuthorizationCredentials = (
await super().__call__(request)
)
if credentials:
if not credentials.scheme == "Bearer":
raise fastapi.HTTPException(
status_code=403,
detail="Invalid authentication scheme.",
)
jwt_tkn = decodeJWT(credentials.credentials)
if jwt_tkn:
return jwt_tkn
raise fastapi.HTTPException(
status_code=403,
detail="Invalid token or expired token.",
)
else:
raise fastapi.HTTPException(
status_code=403,
detail="Invalid authorization code.",
)
Loading