-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# OAuth2 OAuth integration with [fastapi-oauth2](https://github.com/pysnippet/fastapi-oauth2) ## Features - OAuth2 provider using GitHub - Uses cookies to store JWT user token - Login page - Login with GitHub: working - Login with Google: not implemented yet - Integrate with starlette-admin `AuthProvider` routes - Add OAuth dependency for API routes ## Cleanup Cleaned up old routes and old code Closes #1
- Loading branch information
Showing
21 changed files
with
1,213 additions
and
346 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"inputs": [ | ||
{ | ||
"id": "numWorkers", | ||
"type": "promptString", | ||
"default": "4", | ||
"description": "Number of Gunicorn workers to run" | ||
} | ||
], | ||
"configurations": [ | ||
{ | ||
"name": "Organ", | ||
"type": "debugpy", | ||
"request": "launch", | ||
"python": "${workspaceFolder}/.venv/bin/python", | ||
"program": "${workspaceFolder}/.venv/bin/uvicorn", | ||
"args": ["--host", "0.0.0.0", "--port", "9000", "--reload", "organ:app"] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
from ._version import __version__ | ||
from .main import main | ||
from .app import app | ||
|
||
__all__ = ['main', '__version__'] | ||
__all__ = ['app', '__version__'] |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import logfire | ||
from fastapi import Depends, FastAPI | ||
from fastapi.staticfiles import StaticFiles | ||
from fastapi_oauth2.middleware import OAuth2Middleware | ||
from fastapi_oauth2.router import router as oauth2_router | ||
from sqlmodel import SQLModel | ||
from starlette.middleware.sessions import SessionMiddleware | ||
from starlette.routing import RedirectResponse, Route | ||
from starlette_admin.contrib.sqlmodel import Admin | ||
|
||
from organ._version import __version__ | ||
from organ.auth import OAuthProvider | ||
from organ.config import ORGAN_SECRET | ||
from organ.crud import orgs | ||
from organ.db import engine | ||
from organ.models import Organization, User | ||
from organ.oauth import is_user_authenticated, oauth_config, on_auth | ||
from organ.views import OrganizationView, UserView | ||
|
||
|
||
def init_db(): | ||
logfire.info(f'Organ version: {__version__}') | ||
SQLModel.metadata.create_all(engine) | ||
|
||
|
||
def redirect_to_admin(request): | ||
return RedirectResponse(url="/admin") | ||
|
||
|
||
app = FastAPI( | ||
on_startup=[init_db], | ||
routes=[ | ||
Route("/", redirect_to_admin), | ||
], | ||
) | ||
logfire.configure(pydantic_plugin=logfire.PydanticPlugin(record='all')) | ||
logfire.instrument_fastapi(app) | ||
|
||
|
||
app.include_router(oauth2_router, tags=["auth"]) | ||
app.add_middleware(OAuth2Middleware, config=oauth_config, callback=on_auth) | ||
app.add_middleware(SessionMiddleware, secret_key=ORGAN_SECRET) | ||
app.include_router(orgs, dependencies=[Depends(is_user_authenticated)]) | ||
|
||
# Add static files | ||
app.mount("/static", StaticFiles(directory="static"), name="static") | ||
|
||
admin = Admin( | ||
engine, | ||
title='Organ', | ||
templates_dir='templates', | ||
auth_provider=OAuthProvider( | ||
logout_path="/oauth2/logout", | ||
), | ||
logo_url='/static/GBH_Archives.png', | ||
) | ||
|
||
# Add views | ||
admin.add_view(UserView(User, icon="fa fa-users")) | ||
admin.add_view(OrganizationView(Organization, icon="fa fa-box")) | ||
|
||
admin.mount_to(app) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,78 +1,25 @@ | ||
from typing import Optional | ||
|
||
from sqlmodel import Session, SQLModel, select | ||
from starlette.datastructures import URL | ||
from starlette.requests import Request | ||
from starlette.responses import Response | ||
from starlette.responses import RedirectResponse, Response | ||
from starlette_admin import BaseAdmin | ||
from starlette_admin.auth import AdminUser, AuthProvider | ||
from starlette_admin.exceptions import FormValidationError, LoginFailed | ||
from organ.db import engine, get_user | ||
from organ.models import User | ||
|
||
# users = { | ||
# "admin": { | ||
# "name": "Admin", | ||
# "avatar": "avatars/01.png", | ||
# "roles": ["admin"], | ||
# }, | ||
# "demo": { | ||
# "name": "John Doe", | ||
# "avatar": None, | ||
# "roles": ["demo"], | ||
# }, | ||
# } | ||
|
||
class OAuthProvider(AuthProvider): | ||
async def is_authenticated(self, request: Request) -> bool: | ||
if request.get('user'): | ||
return True | ||
return False | ||
|
||
class CustomAuthProvider(AuthProvider): | ||
""" | ||
This is for demo purpose, it's not a better | ||
way to save and validate user credentials | ||
""" | ||
def get_admin_user(self, request: Request) -> Optional[AdminUser]: | ||
user = request.user | ||
return AdminUser( | ||
username=user['name'], | ||
photo_url=user['avatar_url'], | ||
) | ||
|
||
async def login( | ||
self, | ||
username: str, | ||
password: str, | ||
remember_me: bool, | ||
request: Request, | ||
response: Response, | ||
) -> Response: | ||
if len(username) < 3: | ||
"""Form data validation""" | ||
raise FormValidationError( | ||
{"username": "Please ensure that your username has at least 3 characters"} | ||
) | ||
|
||
# load user from db | ||
user = get_user(username) | ||
if user and password == user.password: | ||
"""Save `username` in session""" | ||
request.session.update({"username": username}) | ||
return response | ||
|
||
raise LoginFailed("Invalid username or password.") | ||
|
||
async def is_authenticated(self, request) -> bool: | ||
print(request.method == "GET") | ||
if request.method == "GET" and str(request.url).startswith("/admin/api/organization"): | ||
# allow unauthenticated read access | ||
return True | ||
|
||
user = get_user(request.session.get("username", None)) | ||
if user: | ||
""" | ||
Save current `user` object in the request state. Can be used later | ||
to restrict access to connected user. | ||
""" | ||
request.state.user = user.username | ||
return True | ||
|
||
return False | ||
|
||
def get_admin_user(self, request: Request) -> Optional[AdminUser]: | ||
username = request.state.user # Retrieve current user | ||
|
||
return AdminUser(username=username) | ||
|
||
async def logout(self, request: Request, response: Response) -> Response: | ||
request.session.clear() | ||
return response | ||
async def render_logout(self, request: Request, admin: BaseAdmin) -> Response: | ||
"""Override the default logout to implement custom logic""" | ||
return RedirectResponse(url=URL('/oauth2/logout')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,16 @@ | ||
from os import environ | ||
|
||
from dotenv import load_dotenv | ||
|
||
load_dotenv() | ||
|
||
ENVIRONMENT = environ.get('ENVIRONMENT', 'development') | ||
DB_URL = environ.get('DB_URL', 'postgresql://postgres:postgres@localhost:5432/organ') | ||
|
||
ORGAN_SECRET = environ.get('ORGAN_SECRET', 1234567890) | ||
|
||
# TEMPLATES_DIR = environ.get('TEMPLATES_DIR', 'templates') | ||
# STATIC_DIR = environ.get('STATIC_DIR', 'static') | ||
SECRET_KEY = environ.get('SECRET_KEY', 'secret') | ||
AUTH0_DOMAIN = environ.get('AUTH0_DOMAIN') | ||
AUTH0_CLIENT_ID = environ.get('AUTH0_CLIENT_ID') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.