mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-26 22:01:59 -06:00
Implement Engine HTTP interfaces for health, enrollment, and tokens
This commit is contained in:
@@ -4,6 +4,8 @@ from __future__ import annotations
|
||||
|
||||
from flask import Flask
|
||||
|
||||
from Data.Engine.services.container import EngineServiceContainer
|
||||
|
||||
from . import admin, agents, enrollment, health, tokens
|
||||
|
||||
_REGISTRARS = (
|
||||
@@ -15,14 +17,14 @@ _REGISTRARS = (
|
||||
)
|
||||
|
||||
|
||||
def register_http_interfaces(app: Flask) -> None:
|
||||
def register_http_interfaces(app: Flask, services: EngineServiceContainer) -> None:
|
||||
"""Attach HTTP blueprints to *app*.
|
||||
|
||||
The implementation is intentionally minimal for the initial scaffolding.
|
||||
"""
|
||||
|
||||
for registrar in _REGISTRARS:
|
||||
registrar(app)
|
||||
registrar(app, services)
|
||||
|
||||
|
||||
__all__ = ["register_http_interfaces"]
|
||||
|
||||
@@ -4,11 +4,13 @@ from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, Flask
|
||||
|
||||
from Data.Engine.services.container import EngineServiceContainer
|
||||
|
||||
|
||||
blueprint = Blueprint("engine_admin", __name__, url_prefix="/api/admin")
|
||||
|
||||
|
||||
def register(app: Flask) -> None:
|
||||
def register(app: Flask, _services: EngineServiceContainer) -> None:
|
||||
"""Attach administrative routes to *app*.
|
||||
|
||||
Concrete endpoints will be migrated in subsequent phases.
|
||||
|
||||
@@ -4,11 +4,13 @@ from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, Flask
|
||||
|
||||
from Data.Engine.services.container import EngineServiceContainer
|
||||
|
||||
|
||||
blueprint = Blueprint("engine_agents", __name__, url_prefix="/api/agents")
|
||||
|
||||
|
||||
def register(app: Flask) -> None:
|
||||
def register(app: Flask, _services: EngineServiceContainer) -> None:
|
||||
"""Attach agent management routes to *app*.
|
||||
|
||||
Implementation will be populated as services migrate from the legacy server.
|
||||
|
||||
@@ -2,20 +2,110 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, Flask
|
||||
from flask import Blueprint, Flask, current_app, jsonify, request
|
||||
|
||||
from Data.Engine.builders.device_enrollment import EnrollmentRequestBuilder
|
||||
from Data.Engine.services import EnrollmentValidationError
|
||||
from Data.Engine.services.container import EngineServiceContainer
|
||||
|
||||
AGENT_CONTEXT_HEADER = "X-Borealis-Agent-Context"
|
||||
|
||||
|
||||
blueprint = Blueprint("engine_enrollment", __name__, url_prefix="/api/enrollment")
|
||||
blueprint = Blueprint("engine_enrollment", __name__)
|
||||
|
||||
|
||||
def register(app: Flask) -> None:
|
||||
"""Attach enrollment routes to *app*.
|
||||
|
||||
Implementation will be ported during later migration phases.
|
||||
"""
|
||||
def register(app: Flask, _services: EngineServiceContainer) -> None:
|
||||
"""Attach enrollment routes to *app*."""
|
||||
|
||||
if "engine_enrollment" not in app.blueprints:
|
||||
app.register_blueprint(blueprint)
|
||||
|
||||
|
||||
__all__ = ["register", "blueprint"]
|
||||
@blueprint.route("/api/agent/enroll/request", methods=["POST"])
|
||||
def enrollment_request() -> object:
|
||||
services: EngineServiceContainer = current_app.extensions["engine_services"]
|
||||
payload = request.get_json(force=True, silent=True)
|
||||
builder = EnrollmentRequestBuilder().with_payload(payload).with_service_context(
|
||||
request.headers.get(AGENT_CONTEXT_HEADER)
|
||||
)
|
||||
try:
|
||||
normalized = builder.build()
|
||||
result = services.enrollment_service.request_enrollment(
|
||||
normalized,
|
||||
remote_addr=_remote_addr(),
|
||||
)
|
||||
except EnrollmentValidationError as exc:
|
||||
response = jsonify(exc.to_response())
|
||||
response.status_code = exc.http_status
|
||||
if exc.retry_after is not None:
|
||||
response.headers["Retry-After"] = f"{int(exc.retry_after)}"
|
||||
return response
|
||||
|
||||
response_payload = {
|
||||
"status": result.status,
|
||||
"approval_reference": result.approval_reference,
|
||||
"server_nonce": result.server_nonce,
|
||||
"poll_after_ms": result.poll_after_ms,
|
||||
"server_certificate": result.server_certificate,
|
||||
"signing_key": result.signing_key,
|
||||
}
|
||||
response = jsonify(response_payload)
|
||||
response.status_code = result.http_status
|
||||
if result.retry_after is not None:
|
||||
response.headers["Retry-After"] = f"{int(result.retry_after)}"
|
||||
return response
|
||||
|
||||
|
||||
@blueprint.route("/api/agent/enroll/poll", methods=["POST"])
|
||||
def enrollment_poll() -> object:
|
||||
services: EngineServiceContainer = current_app.extensions["engine_services"]
|
||||
payload = request.get_json(force=True, silent=True) or {}
|
||||
approval_reference = str(payload.get("approval_reference") or "").strip()
|
||||
client_nonce = str(payload.get("client_nonce") or "").strip()
|
||||
proof_sig = str(payload.get("proof_sig") or "").strip()
|
||||
|
||||
try:
|
||||
result = services.enrollment_service.poll_enrollment(
|
||||
approval_reference=approval_reference,
|
||||
client_nonce_b64=client_nonce,
|
||||
proof_signature_b64=proof_sig,
|
||||
)
|
||||
except EnrollmentValidationError as exc:
|
||||
return jsonify(exc.to_response()), exc.http_status
|
||||
|
||||
body = {"status": result.status}
|
||||
if result.poll_after_ms is not None:
|
||||
body["poll_after_ms"] = result.poll_after_ms
|
||||
if result.reason:
|
||||
body["reason"] = result.reason
|
||||
if result.detail:
|
||||
body["detail"] = result.detail
|
||||
if result.tokens:
|
||||
body.update(
|
||||
{
|
||||
"guid": result.tokens.guid.value,
|
||||
"access_token": result.tokens.access_token,
|
||||
"refresh_token": result.tokens.refresh_token,
|
||||
"token_type": result.tokens.token_type,
|
||||
"expires_in": result.tokens.expires_in,
|
||||
"server_certificate": result.server_certificate or "",
|
||||
"signing_key": result.signing_key or "",
|
||||
}
|
||||
)
|
||||
else:
|
||||
if result.server_certificate:
|
||||
body["server_certificate"] = result.server_certificate
|
||||
if result.signing_key:
|
||||
body["signing_key"] = result.signing_key
|
||||
|
||||
return jsonify(body), result.http_status
|
||||
|
||||
|
||||
def _remote_addr() -> str:
|
||||
forwarded = request.headers.get("X-Forwarded-For")
|
||||
if forwarded:
|
||||
return forwarded.split(",")[0].strip()
|
||||
return (request.remote_addr or "unknown").strip()
|
||||
|
||||
|
||||
__all__ = ["register", "blueprint", "enrollment_request", "enrollment_poll"]
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
"""Health check HTTP interface placeholders for the Engine."""
|
||||
"""Health check HTTP interface for the Engine."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, Flask
|
||||
from flask import Blueprint, Flask, jsonify
|
||||
|
||||
from Data.Engine.services.container import EngineServiceContainer
|
||||
|
||||
blueprint = Blueprint("engine_health", __name__)
|
||||
|
||||
|
||||
def register(app: Flask) -> None:
|
||||
"""Attach health-related routes to *app*.
|
||||
|
||||
Routes will be populated in later migration phases.
|
||||
"""
|
||||
def register(app: Flask, _services: EngineServiceContainer) -> None:
|
||||
"""Attach health-related routes to *app*."""
|
||||
|
||||
if "engine_health" not in app.blueprints:
|
||||
app.register_blueprint(blueprint)
|
||||
|
||||
|
||||
@blueprint.route("/health", methods=["GET"])
|
||||
def health() -> object:
|
||||
"""Return a basic liveness response."""
|
||||
|
||||
return jsonify({"status": "ok"})
|
||||
|
||||
|
||||
__all__ = ["register", "blueprint"]
|
||||
|
||||
@@ -1,21 +1,52 @@
|
||||
"""Token management HTTP interface placeholders for the Engine."""
|
||||
"""Token management HTTP interface for the Engine."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, Flask
|
||||
from flask import Blueprint, Flask, current_app, jsonify, request
|
||||
|
||||
from Data.Engine.builders.device_auth import RefreshTokenRequestBuilder
|
||||
from Data.Engine.domain.device_auth import DeviceAuthFailure
|
||||
from Data.Engine.services.container import EngineServiceContainer
|
||||
from Data.Engine.services import TokenRefreshError
|
||||
|
||||
blueprint = Blueprint("engine_tokens", __name__)
|
||||
|
||||
|
||||
blueprint = Blueprint("engine_tokens", __name__, url_prefix="/api/tokens")
|
||||
|
||||
|
||||
def register(app: Flask) -> None:
|
||||
"""Attach token management routes to *app*.
|
||||
|
||||
Implementation will be introduced as authentication services are migrated.
|
||||
"""
|
||||
def register(app: Flask, _services: EngineServiceContainer) -> None:
|
||||
"""Attach token management routes to *app*."""
|
||||
|
||||
if "engine_tokens" not in app.blueprints:
|
||||
app.register_blueprint(blueprint)
|
||||
|
||||
|
||||
__all__ = ["register", "blueprint"]
|
||||
@blueprint.route("/api/agent/token/refresh", methods=["POST"])
|
||||
def refresh_token() -> object:
|
||||
services: EngineServiceContainer = current_app.extensions["engine_services"]
|
||||
builder = (
|
||||
RefreshTokenRequestBuilder()
|
||||
.with_payload(request.get_json(force=True, silent=True))
|
||||
.with_http_method(request.method)
|
||||
.with_htu(request.url)
|
||||
.with_dpop_proof(request.headers.get("DPoP"))
|
||||
)
|
||||
try:
|
||||
refresh_request = builder.build()
|
||||
except DeviceAuthFailure as exc:
|
||||
payload = exc.to_dict()
|
||||
return jsonify(payload), exc.http_status
|
||||
|
||||
try:
|
||||
response = services.token_service.refresh_access_token(refresh_request)
|
||||
except TokenRefreshError as exc:
|
||||
return jsonify(exc.to_dict()), exc.http_status
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"access_token": response.access_token,
|
||||
"expires_in": response.expires_in,
|
||||
"token_type": response.token_type,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["register", "blueprint", "refresh_token"]
|
||||
|
||||
Reference in New Issue
Block a user