Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
334 changes: 295 additions & 39 deletions bluesky_httpserver/_authentication.py

Large diffs are not rendered by default.

61 changes: 56 additions & 5 deletions bluesky_httpserver/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
from fastapi.openapi.utils import get_openapi

from .authentication import ExternalAuthenticator, InternalAuthenticator
from .console_output import CollectPublishedConsoleOutput, ConsoleOutputStream, SystemInfoStream
from .authenticators import ProxiedOIDCAuthenticator
from .console_output import (
CollectPublishedConsoleOutput,
ConsoleOutputStream,
SystemInfoStream,
)
from .core import PatchedStreamingResponse
from .database.core import purge_expired
from .resources import SERVER_RESOURCES as SR
Expand Down Expand Up @@ -217,6 +222,24 @@ def build_app(authentication=None, api_access=None, resource_access=None, server
authentication_router.post(f"/provider/{provider}/token")(
build_device_code_token_route(authenticator, provider)
)
# Warn if the operator forgot to configure a redirect target
# for successful browser-based logins. Without it the user
# will get a page of raw JSON instead of being sent to the UI.
if not getattr(authenticator, "redirect_on_success", None):
logger.warning(
"External authenticator %r has no 'redirect_on_success' "
"configured. Browser-based login will return raw JSON "
"tokens instead of redirecting to a UI landing page. "
"Set 'redirect_on_success' in the authenticator "
"configuration to a UI callback URL to silence this "
"warning.",
provider,
)
# Expose the OIDC provider name on app.state so that
# get_current_principal / websocket handshakes can resolve
# externally-minted JWTs to a Principal in the auth DB.
if isinstance(authenticator, ProxiedOIDCAuthenticator):
app.state.provider = provider
else:
raise ValueError(f"unknown authenticator type {type(authenticator)}")
for custom_router in getattr(authenticator, "include_routers", []):
Expand Down Expand Up @@ -263,8 +286,10 @@ async def startup_event():
from .database.core import ( # make_admin_by_identity,
REQUIRED_REVISION,
UninitializedDatabase,
DatabaseUpgradeNeeded,
check_database,
initialize_database,
upgrade,
)

connect_args = {}
Expand All @@ -282,6 +307,12 @@ async def startup_event():
)
initialize_database(engine)
logger.info("Database initialized.")
except DatabaseUpgradeNeeded:
logger.info(
f"Database at {redacted_url} is out of date. Upgrading to {REQUIRED_REVISION}..."
)
upgrade(engine, REQUIRED_REVISION)
logger.info("Database upgraded.")
else:
logger.info(f"Connected to existing database at {redacted_url}.")
# SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Expand Down Expand Up @@ -416,10 +447,30 @@ async def purge_expired_sessions_and_api_keys():

@app.on_event("shutdown")
async def shutdown_event():
await SR.RM.close()
await SR.console_output_loader.stop()
await SR.console_output_stream.stop()
await SR.system_info_stream.stop()
"""Safely shutdown and perform the cleanup robustly

This change ensures that the application shuts down and cleans up resources even if there is
a problem, without silencing the errors.
"""
for task in getattr(app.state, "tasks", []):
task.cancel()
for closer_name in (
"console_output_loader",
"console_output_stream",
"system_info_stream",
):
closer = getattr(SR, closer_name, None)
if closer is not None:
try:
await closer.stop()
except Exception:
logger.exception("Error stopping %s", closer_name)
rm = getattr(SR, "RM", None)
if rm is not None:
try:
await rm.close()
except Exception:
logger.exception("Error closing REManagerAPI connection")

@lru_cache(1)
def override_get_authenticators():
Expand Down
4 changes: 4 additions & 0 deletions bluesky_httpserver/authentication/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .._authentication import (
authenticate_websocket_first_message,
base_authentication_router,
build_auth_code_route,
build_authorize_route,
Expand All @@ -9,6 +10,7 @@
build_handle_credentials_route,
get_current_principal,
get_current_principal_websocket,
get_session_state,
oauth2_scheme,
)
from .authenticator_base import (
Expand All @@ -21,8 +23,10 @@
"ExternalAuthenticator",
"InternalAuthenticator",
"UserSessionState",
"authenticate_websocket_first_message",
"get_current_principal",
"get_current_principal_websocket",
"get_session_state",
"base_authentication_router",
"build_auth_code_route",
"build_authorize_route",
Expand Down
Loading
Loading