feat: Add OIDC_CLIENT_SECRET and other changes for v2 (#4254)

Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
This commit is contained in:
Carter
2024-10-05 16:12:11 -05:00
committed by GitHub
parent 4f1abcf4a3
commit 5ed0ec029b
31 changed files with 530 additions and 349 deletions

View File

@@ -6,7 +6,7 @@ from mealie.core.settings.static import APP_VERSION
from mealie.db.db_setup import generate_session
from mealie.db.models.users.users import User
from mealie.repos.all_repositories import get_repositories
from mealie.schema.admin.about import AppInfo, AppStartupInfo, AppTheme, OIDCInfo
from mealie.schema.admin.about import AppInfo, AppStartupInfo, AppTheme
router = APIRouter(prefix="/about")
@@ -69,16 +69,3 @@ def get_app_theme(resp: Response):
resp.headers["Cache-Control"] = "public, max-age=604800"
return AppTheme(**settings.theme.model_dump())
@router.get("/oidc", response_model=OIDCInfo)
def get_oidc_info(resp: Response):
"""Get's the current OIDC configuration needed for the frontend"""
settings = get_app_settings()
resp.headers["Cache-Control"] = "public, max-age=604800"
return OIDCInfo(
configuration_url=settings.OIDC_CONFIGURATION_URL,
client_id=settings.OIDC_CLIENT_ID,
groups_claim=settings.OIDC_GROUPS_CLAIM if settings.OIDC_USER_GROUP or settings.OIDC_ADMIN_GROUP else None,
)

View File

@@ -1,13 +1,18 @@
from datetime import timedelta
from authlib.integrations.starlette_client import OAuth
from fastapi import APIRouter, Depends, Request, Response, status
from fastapi.exceptions import HTTPException
from fastapi.responses import RedirectResponse
from pydantic import BaseModel
from sqlalchemy.orm.session import Session
from starlette.datastructures import URLPath
from mealie.core import root_logger, security
from mealie.core.config import get_app_settings
from mealie.core.dependencies import get_current_user
from mealie.core.exceptions import UserLockedOut
from mealie.core.security.providers.openid_provider import OpenIDProvider
from mealie.core.security.security import get_auth_provider
from mealie.db.db_setup import generate_session
from mealie.routes._base.routers import UserAPIRouter
@@ -20,6 +25,20 @@ logger = root_logger.get_logger("auth")
remember_me_duration = timedelta(days=14)
settings = get_app_settings()
if settings.OIDC_READY:
oauth = OAuth()
groups_claim = settings.OIDC_GROUPS_CLAIM if settings.OIDC_REQUIRES_GROUP_CLAIM else ""
scope = f"openid email profile {groups_claim}"
oauth.register(
"oidc",
client_id=settings.OIDC_CLIENT_ID,
client_secret=settings.OIDC_CLIENT_SECRET,
server_metadata_url=settings.OIDC_CONFIGURATION_URL,
client_kwargs={"scope": scope.rstrip()},
code_challenge_method="S256",
)
class MealieAuthToken(BaseModel):
access_token: str
@@ -31,7 +50,7 @@ class MealieAuthToken(BaseModel):
@public_router.post("/token")
async def get_token(
def get_token(
request: Request,
response: Response,
data: CredentialsRequestForm = Depends(),
@@ -46,8 +65,8 @@ async def get_token(
ip = request.client.host if request.client else "unknown"
try:
auth_provider = get_auth_provider(session, request, data)
auth = await auth_provider.authenticate()
auth_provider = get_auth_provider(session, data)
auth = auth_provider.authenticate()
except UserLockedOut as e:
logger.error(f"User is locked out from {ip}")
raise HTTPException(status_code=status.HTTP_423_LOCKED, detail="User is locked out") from e
@@ -61,7 +80,61 @@ async def get_token(
expires_in = duration.total_seconds() if duration else None
response.set_cookie(
key="mealie.access_token", value=access_token, httponly=True, max_age=expires_in, expires=expires_in
key="mealie.access_token",
value=access_token,
httponly=True,
max_age=expires_in,
secure=settings.PRODUCTION,
)
return MealieAuthToken.respond(access_token)
@public_router.get("/oauth")
async def oauth_login(request: Request):
if not oauth:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Could not initialize OAuth client",
)
client = oauth.create_client("oidc")
redirect_url = None
if not settings.PRODUCTION:
# in development, we want to redirect to the frontend
redirect_url = "http://localhost:3000/login"
else:
redirect_url = URLPath("/login").make_absolute_url(request.base_url)
response: RedirectResponse = await client.authorize_redirect(request, redirect_url)
return response
@public_router.get("/oauth/callback")
async def oauth_callback(request: Request, response: Response, session: Session = Depends(generate_session)):
if not oauth:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Could not initialize OAuth client",
)
client = oauth.create_client("oidc")
token = await client.authorize_access_token(request)
auth_provider = OpenIDProvider(session, token["userinfo"])
auth = auth_provider.authenticate()
if not auth:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
)
access_token, duration = auth
expires_in = duration.total_seconds() if duration else None
response.set_cookie(
key="mealie.access_token",
value=access_token,
httponly=True,
max_age=expires_in,
secure=settings.PRODUCTION,
)
return MealieAuthToken.respond(access_token)