Files
Owlbot/owlbot/web/sessions.py
T
LogalDeveloper 5443aa86ce
CI / Formatting (push) Successful in 5s
CI / Linting (push) Successful in 5s
CI / Tests (Python 3.12) (push) Successful in 2m48s
CI / Tests (Python 3.13) (push) Successful in 2m49s
CI / Tests (Python 3.14) (push) Successful in 2m43s
CI / Type Checking (push) Successful in 9s
CI / Spelling (push) Successful in 5s
Added browser sessions and protected route support.
2026-05-04 21:01:57 -04:00

141 lines
4.4 KiB
Python

# Copyright 2026 Logan Fick
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Web helpers for Owlbot browser session flows."""
from __future__ import annotations
from http import HTTPStatus
from pathlib import Path
import jinja2
from aiohttp import web
from owlbot import __version__
from owlbot.sessions import (
SESSION_COOKIE_NAME,
BrowserSession,
ConnectTokenRedemptionError,
SessionManager,
)
_TEMPLATE_DIR = Path(__file__).resolve().parent.parent / "templates"
_TEMPLATE_ENV = jinja2.Environment(
loader=jinja2.FileSystemLoader(_TEMPLATE_DIR),
autoescape=True,
)
_TEMPLATE_ENV.globals["owlbot_version"] = __version__
def _render_template(template_name: str, **context: object) -> str:
template = _TEMPLATE_ENV.get_template(template_name)
return template.render(**context)
def connect_guidance_response(
*,
status: int,
command_prefix: str = "!",
title: str = "Connect to Owlbot",
message: str | None = None,
command_message: str | None = None,
) -> web.Response:
"""Return a simple connect guidance page for protected routes."""
body_message = message or "Run this command in chat, then try again."
return web.Response(
status=status,
text=_render_template(
"connect_error.html",
title=title,
body_message=body_message,
command_message=command_message,
command_prefix=command_prefix,
),
content_type="text/html",
)
def browser_session_status_response(
session: BrowserSession | None,
*,
command_prefix: str,
) -> web.Response:
"""Return the browser session status page."""
return web.Response(
text=_render_template(
"session_status.html",
session=session,
command_prefix=command_prefix,
),
content_type="text/html",
)
def register_browser_session_routes(
app: web.Application,
*,
session_manager: SessionManager,
command_prefix: str,
cookie_secure: bool = False,
) -> None:
"""Register the shared browser-connection endpoints on the app."""
async def redeem(request: web.Request) -> web.Response:
token = request.match_info["token"]
replacing_session_id = request.cookies.get(SESSION_COOKIE_NAME)
connect_token = session_manager.get_connect_token(token)
if connect_token is None:
return connect_guidance_response(
status=HTTPStatus.FORBIDDEN,
command_prefix=command_prefix,
title="Invalid or expired link",
message="This connection link is invalid or has expired.",
)
try:
session = session_manager.redeem_connect_token(
token,
replacing_session_id=replacing_session_id,
)
except ConnectTokenRedemptionError:
return connect_guidance_response(
status=HTTPStatus.FORBIDDEN,
command_prefix=command_prefix,
title="Invalid or expired link",
message="This connection link is invalid or has expired.",
)
response = web.HTTPFound(location=connect_token.destination_path)
response.set_cookie(
SESSION_COOKIE_NAME,
session.session_id,
httponly=True,
max_age=int((session.expires_at - session.created_at).total_seconds()),
samesite="Lax",
secure=cookie_secure,
path="/owlbot",
)
raise response
async def status(request: web.Request) -> web.Response:
session_id = request.cookies.get(SESSION_COOKIE_NAME)
session = session_manager.get_session(session_id)
return browser_session_status_response(
session,
command_prefix=command_prefix,
)
app.router.add_get("/owlbot/connect", status)
app.router.add_get("/owlbot/connect/{token}", redeem)