Implement oidc login

This commit is contained in:
2025-05-30 10:57:59 +02:00
parent b491dff4b1
commit 391e310b91
39 changed files with 938 additions and 308 deletions

View File

@ -13,7 +13,7 @@ from sshecret_admin.services import AdminBackend
from starlette.datastructures import URL
from sshecret_admin.auth import (
User,
IdentityClaims,
authenticate_user_async,
create_access_token,
create_refresh_token,
@ -34,7 +34,16 @@ class LoginError(BaseModel):
message: str
async def audit_login_failure(admin: AdminBackend, username: str, request: Request) -> None:
class OidcLogin(BaseModel):
"""Small container to hold OIDC info for the login box."""
enabled: bool = False
provider_name: str | None = None
async def audit_login_failure(
admin: AdminBackend, username: str, request: Request
) -> None:
"""Write login failure to audit log."""
origin: str | None = None
if request.client:
@ -65,7 +74,16 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
return RedirectResponse("/dashboard")
login_error: LoginError | None = None
if error_title and error_message:
LOG.info("Got an error here: %s %s", error_title, error_message)
login_error = LoginError(title=error_title, message=error_message)
else:
LOG.info("Got no errors")
oidc_login = OidcLogin()
if dependencies.settings.oidc:
oidc_login.enabled = True
oidc_login.provider_name = dependencies.settings.oidc.name
return templates.TemplateResponse(
request,
"login.html",
@ -73,6 +91,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
"page_title": "Login",
"page_description": "Login page.",
"login_error": login_error,
"oidc": oidc_login,
},
)
@ -100,7 +119,9 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
},
)
user = await authenticate_user_async(session, form_data.username, form_data.password)
user = await authenticate_user_async(
session, form_data.username, form_data.password
)
login_failed = RedirectException(
to=URL("/login").include_query_params(
error_title="Login Error", error_message="Invalid username or password"
@ -143,16 +164,22 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
@app.get("/refresh")
async def get_refresh_token(
response: Response,
user: Annotated[User, Depends(dependencies.get_user_from_refresh_token)],
refresh_claims: Annotated[
IdentityClaims, Depends(dependencies.get_refresh_claims)
],
next: Annotated[str, Query()],
):
"""Refresh tokens.
We might as well refresh the long-lived one here.
"""
token_data: dict[str, str] = {"sub": user.username}
access_token = create_access_token(dependencies.settings, data=token_data)
refresh_token = create_refresh_token(dependencies.settings, data=token_data)
token_data: dict[str, str] = {"sub": refresh_claims.sub}
access_token = create_access_token(
dependencies.settings, data=token_data, provider=refresh_claims.provider
)
refresh_token = create_refresh_token(
dependencies.settings, data=token_data, provider=refresh_claims.provider
)
response = RedirectResponse(url=next, status_code=status.HTTP_302_FOUND)
response.set_cookie(
"access_token",
@ -176,8 +203,12 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
):
"""Log out user."""
response = RedirectResponse(url="/login", status_code=status.HTTP_302_FOUND)
response.delete_cookie("refresh_token", httponly=True, secure=False, samesite="strict")
response.delete_cookie("access_token", httponly=True, secure=False, samesite="strict")
response.delete_cookie(
"refresh_token", httponly=True, secure=False, samesite="strict"
)
response.delete_cookie(
"access_token", httponly=True, secure=False, samesite="strict"
)
return response
return app