Add basic FastAPI auth and React login gate
CI / basic-check (push) Has been cancelled

This commit is contained in:
Perplexity Bot
2026-08-04 20:21:34 +00:00
parent 998c5246dd
commit 8aae681706
9 changed files with 139 additions and 456 deletions
+43
View File
@@ -0,0 +1,43 @@
from datetime import datetime, timedelta, timezone
from typing import Optional
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
SECRET_KEY = 'change-me-in-production'
ALGORITHM = 'HS256'
ACCESS_TOKEN_EXPIRE_MINUTES = 120
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/auth/token')
class Token(BaseModel): access_token: str; token_type: str = 'bearer'
class TokenData(BaseModel): username: Optional[str] = None
class User(BaseModel): username: str; disabled: bool = False; role: str = 'viewer'
class UserInDB(User): hashed_password: str
fake_users_db = {'admin': {'username': 'admin', 'hashed_password': pwd_context.hash('admin123'), 'disabled': False, 'role': 'admin'}, 'viewer': {'username': 'viewer', 'hashed_password': pwd_context.hash('viewer123'), 'disabled': False, 'role': 'viewer'}}
def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password)
def get_user(username: str) -> Optional[UserInDB]:
user = fake_users_db.get(username)
return UserInDB(**user) if user else None
def authenticate_user(username: str, password: str) -> Optional[UserInDB]:
user = get_user(username)
return user if user and verify_password(password, user.hashed_password) else None
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
to_encode = data.copy()
expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
to_encode.update({'exp': expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
credentials_exception = HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Could not validate credentials', headers={'WWW-Authenticate': 'Bearer'})
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get('sub')
if username is None: raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user(username)
if user is None: raise credentials_exception
return User(username=user.username, disabled=user.disabled, role=user.role)
def get_current_active_user(current_user: User = Depends(get_current_user)) -> User:
if current_user.disabled: raise HTTPException(status_code=400, detail='Inactive user')
return current_user
+6 -23
View File
@@ -1,26 +1,9 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.routes.auth import router as auth_router
from app.routers.discoveries import router as discoveries_router
from app.routers.health import router as health_router
from app.routers.inventory import router as inventory_router
from app.routers.jobs import router as jobs_router
from app.routers.objects import router as objects_router
from app.routers.relations import router as relations_router
app = FastAPI(title='NetMapper API')
app.include_router(auth_router)
app = FastAPI(title='NetMapper Backend', version='0.1.0')
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)
app.include_router(health_router)
app.include_router(objects_router)
app.include_router(jobs_router)
app.include_router(discoveries_router)
app.include_router(inventory_router)
app.include_router(relations_router)
@app.get('/health')
async def health():
return {'status': 'ok'}
View File
+14
View File
@@ -0,0 +1,14 @@
from datetime import timedelta
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from app.auth import ACCESS_TOKEN_EXPIRE_MINUTES, Token, authenticate_user, create_access_token, get_current_active_user
router = APIRouter(prefix='/auth', tags=['auth'])
@router.post('/token', response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(form_data.username, form_data.password)
if not user: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Incorrect username or password')
access_token = create_access_token({'sub': user.username, 'role': user.role}, timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
return Token(access_token=access_token)
@router.get('/me')
async def me(current_user=Depends(get_current_active_user)):
return {'username': current_user.username, 'role': current_user.role, 'disabled': current_user.disabled}
+5 -6
View File
@@ -1,6 +1,5 @@
fastapi==0.115.0
uvicorn[standard]==0.30.6
pydantic==2.9.2
sqlalchemy==2.0.35
psycopg[binary]==3.2.3
redis==5.1.1
fastapi
uvicorn[standard]
python-jose[cryptography]
passlib[bcrypt]
python-multipart