This commit is contained in:
2026-01-25 21:02:45 +01:00
parent 5b868a6cde
commit 01863c1df0
10 changed files with 399 additions and 7 deletions

View File

@@ -2,7 +2,7 @@ from fastapi import FastAPI, Depends
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from .database import engine, Base, SessionLocal
from .routers import auth, users, apps, sso
from .routers import auth, users, apps, sso, requests
from . import models, auth_utils
from sqlalchemy.orm import Session
@@ -25,6 +25,7 @@ app.include_router(auth.router)
app.include_router(users.router)
app.include_router(apps.router)
app.include_router(sso.router)
app.include_router(requests.router)
import os

View File

@@ -38,3 +38,23 @@ class UserApplication(Base):
user = relationship("User", back_populates="applications")
application = relationship("Application", back_populates="users")
# Association table for AccessRequest and Application
class RequestApplication(Base):
__tablename__ = "request_applications"
id = Column(Integer, primary_key=True, index=True)
request_id = Column(Integer, ForeignKey("access_requests.id"))
application_id = Column(Integer, ForeignKey("applications.id"))
class AccessRequest(Base):
__tablename__ = "access_requests"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, index=True)
email = Column(String, index=True)
hashed_password = Column(String)
status = Column(String, default="pending") # pending, approved, rejected
created_at = Column(DateTime, default=datetime.utcnow)
requested_apps = relationship("Application", secondary="request_applications")

View File

@@ -6,11 +6,10 @@ from .. import database, models, schemas, auth_utils
router = APIRouter(
prefix="/apps",
tags=["Applications"],
dependencies=[Depends(auth_utils.get_current_admin_user)]
tags=["Applications"]
)
@router.post("/", response_model=schemas.ApplicationOut)
@router.post("/", response_model=schemas.ApplicationOut, dependencies=[Depends(auth_utils.get_current_admin_user)])
async def create_application(app: schemas.ApplicationCreate, db: Session = Depends(database.get_db)):
db_app = db.query(models.Application).filter(models.Application.name == app.name).first()
if db_app:
@@ -27,7 +26,12 @@ async def create_application(app: schemas.ApplicationCreate, db: Session = Depen
db.refresh(db_app)
return db_app
@router.get("/", response_model=List[schemas.ApplicationOut])
@router.get("/", response_model=List[schemas.ApplicationOut], dependencies=[Depends(auth_utils.get_current_admin_user)])
async def read_applications(skip: int = 0, limit: int = 100, db: Session = Depends(database.get_db)):
apps = db.query(models.Application).offset(skip).limit(limit).all()
return apps
@router.get("/public", response_model=List[schemas.ApplicationOut])
async def read_public_applications(db: Session = Depends(database.get_db)):
apps = db.query(models.Application).all()
return apps

View File

@@ -0,0 +1,98 @@
from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks
from sqlalchemy.orm import Session
from typing import List
from .. import database, models, schemas, auth_utils
from ..services import email
router = APIRouter(
prefix="/requests",
tags=["Access Requests"]
)
@router.post("/", response_model=schemas.AccessRequestOut)
async def create_request(request: schemas.AccessRequestCreate, background_tasks: BackgroundTasks, db: Session = Depends(database.get_db)):
# Check if user already exists
existing_user = db.query(models.User).filter(models.User.username == request.username).first()
if existing_user:
raise HTTPException(status_code=400, detail="Username already taken")
# Check if request already exists
existing_req = db.query(models.AccessRequest).filter(models.AccessRequest.username == request.username, models.AccessRequest.status == "pending").first()
if existing_req:
raise HTTPException(status_code=400, detail="Pending request already exists for this username")
hashed_password = auth_utils.get_password_hash(request.password)
db_request = models.AccessRequest(
username=request.username,
email=request.email,
hashed_password=hashed_password,
status="pending"
)
# Add requested apps
for app_id in request.app_ids:
app = db.query(models.Application).filter(models.Application.id == app_id).first()
if app:
db_request.requested_apps.append(app)
db.add(db_request)
db.commit()
db.refresh(db_request)
# Send email to user (Received)
background_tasks.add_task(email.send_request_received_email, request.email, request.username)
# Send email to admin (New Request)
background_tasks.add_task(email.send_admin_notification_email, request.username)
return db_request
@router.get("/", response_model=List[schemas.AccessRequestOut], dependencies=[Depends(auth_utils.get_current_admin_user)])
async def read_requests(skip: int = 0, limit: int = 100, db: Session = Depends(database.get_db)):
requests = db.query(models.AccessRequest).filter(models.AccessRequest.status == "pending").offset(skip).limit(limit).all()
return requests
@router.post("/{request_id}/approve", dependencies=[Depends(auth_utils.get_current_admin_user)])
async def approve_request(request_id: int, background_tasks: BackgroundTasks, db: Session = Depends(database.get_db)):
req = db.query(models.AccessRequest).filter(models.AccessRequest.id == request_id).first()
if not req:
raise HTTPException(status_code=404, detail="Request not found")
if req.status != "pending":
raise HTTPException(status_code=400, detail="Request already processed")
# Create User
new_user = models.User(
username=req.username,
email=req.email,
hashed_password=req.hashed_password,
is_active=True,
is_admin=False
)
db.add(new_user)
db.commit()
db.refresh(new_user)
# Assign Apps
for app in req.requested_apps:
assignment = models.UserApplication(user_id=new_user.id, application_id=app.id)
db.add(assignment)
req.status = "approved"
db.commit()
# Send email to user (Approved)
background_tasks.add_task(email.send_welcome_email, req.email, req.username, "********") # Don't send password again, they know it
return {"message": "Request approved and user created"}
@router.post("/{request_id}/reject", dependencies=[Depends(auth_utils.get_current_admin_user)])
async def reject_request(request_id: int, db: Session = Depends(database.get_db)):
req = db.query(models.AccessRequest).filter(models.AccessRequest.id == request_id).first()
if not req:
raise HTTPException(status_code=404, detail="Request not found")
req.status = "rejected"
db.commit()
return {"message": "Request rejected"}

View File

@@ -69,3 +69,21 @@ class SSOVerifyResponse(BaseModel):
authorized: bool
message: str
user: Optional[UserOut] = None
# Access Request Schemas
class AccessRequestCreate(BaseModel):
username: str
email: EmailStr
password: str
app_ids: List[int] = []
class AccessRequestOut(BaseModel):
id: int
username: str
email: EmailStr
status: str
created_at: datetime
requested_apps: List[ApplicationOut] = []
class Config:
orm_mode = True

View File

@@ -43,3 +43,25 @@ async def send_welcome_email(to_email: str, username: str, password: str):
ASF Team
"""
await send_email(to_email, subject, body)
async def send_request_received_email(to_email: str, username: str):
subject = "Access Request Received"
body = f"""
Hello {username},
We have received your request for access to the ASF SSO platform.
An administrator will review your request shortly.
Regards,
ASF Team
"""
await send_email(to_email, subject, body)
async def send_admin_notification_email(username: str):
to_email = "admin@nabd-co.com" # Default admin email
subject = "New Access Request"
body = f"""
A new access request has been submitted by {username}.
Please log in to the admin portal to review it.
"""
await send_email(to_email, subject, body)