authgent-server 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- authgent_server/__init__.py +3 -0
- authgent_server/app.py +155 -0
- authgent_server/cli.py +1103 -0
- authgent_server/config.py +128 -0
- authgent_server/crypto.py +33 -0
- authgent_server/db.py +81 -0
- authgent_server/dependencies.py +210 -0
- authgent_server/endpoints/__init__.py +32 -0
- authgent_server/endpoints/agents.py +88 -0
- authgent_server/endpoints/audit.py +95 -0
- authgent_server/endpoints/authorize.py +204 -0
- authgent_server/endpoints/device.py +204 -0
- authgent_server/endpoints/health.py +51 -0
- authgent_server/endpoints/introspect.py +49 -0
- authgent_server/endpoints/register.py +22 -0
- authgent_server/endpoints/revoke.py +33 -0
- authgent_server/endpoints/stepup.py +97 -0
- authgent_server/endpoints/token.py +120 -0
- authgent_server/endpoints/token_check.py +151 -0
- authgent_server/endpoints/token_inspect.py +134 -0
- authgent_server/endpoints/wellknown.py +107 -0
- authgent_server/errors.py +138 -0
- authgent_server/logging.py +107 -0
- authgent_server/middleware/__init__.py +1 -0
- authgent_server/middleware/cors.py +23 -0
- authgent_server/middleware/error_handler.py +77 -0
- authgent_server/middleware/rate_limit.py +76 -0
- authgent_server/middleware/request_id.py +38 -0
- authgent_server/models/__init__.py +33 -0
- authgent_server/models/agent.py +39 -0
- authgent_server/models/audit_log.py +22 -0
- authgent_server/models/authorization_code.py +26 -0
- authgent_server/models/base.py +29 -0
- authgent_server/models/consent.py +29 -0
- authgent_server/models/delegation_receipt.py +19 -0
- authgent_server/models/device_code.py +25 -0
- authgent_server/models/oauth_client.py +35 -0
- authgent_server/models/refresh_token.py +25 -0
- authgent_server/models/signing_key.py +20 -0
- authgent_server/models/stepup_request.py +24 -0
- authgent_server/models/token_blocklist.py +17 -0
- authgent_server/models/user.py +19 -0
- authgent_server/providers/__init__.py +1 -0
- authgent_server/providers/attestation.py +15 -0
- authgent_server/providers/events.py +60 -0
- authgent_server/providers/hitl.py +167 -0
- authgent_server/providers/keys.py +15 -0
- authgent_server/providers/policy.py +17 -0
- authgent_server/providers/protocols.py +116 -0
- authgent_server/schemas/__init__.py +1 -0
- authgent_server/schemas/agent.py +65 -0
- authgent_server/schemas/client.py +73 -0
- authgent_server/schemas/common.py +42 -0
- authgent_server/schemas/token.py +36 -0
- authgent_server/services/__init__.py +1 -0
- authgent_server/services/agent_service.py +166 -0
- authgent_server/services/audit_service.py +47 -0
- authgent_server/services/client_service.py +162 -0
- authgent_server/services/consent_service.py +60 -0
- authgent_server/services/delegation_service.py +152 -0
- authgent_server/services/dpop_service.py +156 -0
- authgent_server/services/external_oidc.py +220 -0
- authgent_server/services/jwks_service.py +204 -0
- authgent_server/services/stepup_service.py +90 -0
- authgent_server/services/token_service.py +647 -0
- authgent_server/templates/consent.html +88 -0
- authgent_server/utils.py +22 -0
- authgent_server-0.1.0.dist-info/METADATA +258 -0
- authgent_server-0.1.0.dist-info/RECORD +71 -0
- authgent_server-0.1.0.dist-info/WHEEL +4 -0
- authgent_server-0.1.0.dist-info/entry_points.txt +2 -0
authgent_server/app.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""FastAPI app factory with lifespan, middleware, and cleanup tasks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import AsyncGenerator
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
|
+
|
|
10
|
+
import structlog
|
|
11
|
+
from fastapi import FastAPI
|
|
12
|
+
from sqlalchemy import text
|
|
13
|
+
|
|
14
|
+
from authgent_server.config import Settings, get_settings
|
|
15
|
+
from authgent_server.db import get_engine, get_session_factory
|
|
16
|
+
from authgent_server.endpoints import api_router
|
|
17
|
+
from authgent_server.errors import AuthgentError
|
|
18
|
+
from authgent_server.middleware.cors import setup_cors
|
|
19
|
+
from authgent_server.middleware.error_handler import (
|
|
20
|
+
authgent_error_handler,
|
|
21
|
+
unhandled_exception_handler,
|
|
22
|
+
)
|
|
23
|
+
from authgent_server.middleware.rate_limit import RateLimitMiddleware
|
|
24
|
+
from authgent_server.middleware.request_id import RequestIdMiddleware
|
|
25
|
+
from authgent_server.models.base import Base
|
|
26
|
+
from authgent_server.services.jwks_service import JWKSService
|
|
27
|
+
|
|
28
|
+
logger = structlog.get_logger()
|
|
29
|
+
|
|
30
|
+
# Cleanup queries — explicit per-table, no string interpolation
|
|
31
|
+
_CLEANUP_QUERIES = {
|
|
32
|
+
"token_blocklist": text("DELETE FROM token_blocklist WHERE expires_at < :now"),
|
|
33
|
+
"authorization_codes": text("DELETE FROM authorization_codes WHERE expires_at < :now"),
|
|
34
|
+
"device_codes": text("DELETE FROM device_codes WHERE expires_at < :now"),
|
|
35
|
+
"refresh_tokens": text("DELETE FROM refresh_tokens WHERE expires_at < :now"),
|
|
36
|
+
"stepup_requests": text(
|
|
37
|
+
"UPDATE stepup_requests SET status = 'expired' "
|
|
38
|
+
"WHERE expires_at < :now AND status = 'pending'"
|
|
39
|
+
),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Cleanup intervals in seconds
|
|
43
|
+
_CLEANUP_INTERVALS = {
|
|
44
|
+
"token_blocklist": 3600,
|
|
45
|
+
"authorization_codes": 900,
|
|
46
|
+
"device_codes": 900,
|
|
47
|
+
"refresh_tokens": 3600,
|
|
48
|
+
"stepup_requests": 60,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def _cleanup_loop(
|
|
53
|
+
table: str,
|
|
54
|
+
interval: int,
|
|
55
|
+
shutdown: asyncio.Event,
|
|
56
|
+
session_factory: object,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Background cleanup task for expired records."""
|
|
59
|
+
query = _CLEANUP_QUERIES[table]
|
|
60
|
+
while not shutdown.is_set():
|
|
61
|
+
try:
|
|
62
|
+
async with session_factory() as session: # type: ignore[operator]
|
|
63
|
+
await session.execute(query, {"now": datetime.now(UTC)})
|
|
64
|
+
await session.commit()
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.warning("cleanup_failed", table=table, error=str(e))
|
|
67
|
+
try:
|
|
68
|
+
await asyncio.wait_for(shutdown.wait(), timeout=interval)
|
|
69
|
+
break
|
|
70
|
+
except TimeoutError:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _configure_logging(debug: bool = False) -> None:
|
|
75
|
+
"""Configure structlog with JSON output and secret redaction."""
|
|
76
|
+
from authgent_server.logging import configure_logging
|
|
77
|
+
|
|
78
|
+
configure_logging(debug=debug, json_output=not debug)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@asynccontextmanager
|
|
82
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: # type: ignore[type-arg]
|
|
83
|
+
"""Server lifecycle — startup and shutdown."""
|
|
84
|
+
settings = get_settings()
|
|
85
|
+
_configure_logging(settings.debug)
|
|
86
|
+
|
|
87
|
+
engine = get_engine(settings)
|
|
88
|
+
session_factory = get_session_factory(settings)
|
|
89
|
+
|
|
90
|
+
# Create tables (for dev/SQLite — production uses Alembic)
|
|
91
|
+
async with engine.begin() as conn:
|
|
92
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
93
|
+
|
|
94
|
+
# Ensure signing key exists
|
|
95
|
+
jwks = JWKSService(settings)
|
|
96
|
+
async with session_factory() as session:
|
|
97
|
+
await jwks.get_active_key(session)
|
|
98
|
+
|
|
99
|
+
# Start background cleanup tasks
|
|
100
|
+
shutdown_event = asyncio.Event()
|
|
101
|
+
cleanup_tasks = [
|
|
102
|
+
asyncio.create_task(_cleanup_loop(table, interval, shutdown_event, session_factory))
|
|
103
|
+
for table, interval in _CLEANUP_INTERVALS.items()
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
logger.info(
|
|
107
|
+
"server_started",
|
|
108
|
+
host=settings.host,
|
|
109
|
+
port=settings.port,
|
|
110
|
+
database=settings.database_url.split("://")[0],
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
yield
|
|
114
|
+
|
|
115
|
+
# Shutdown
|
|
116
|
+
logger.info("server_shutting_down")
|
|
117
|
+
shutdown_event.set()
|
|
118
|
+
done, pending = await asyncio.wait(cleanup_tasks, timeout=5.0)
|
|
119
|
+
for task in pending:
|
|
120
|
+
task.cancel()
|
|
121
|
+
await engine.dispose()
|
|
122
|
+
logger.info("server_stopped")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def create_app(settings: Settings | None = None) -> FastAPI:
|
|
126
|
+
"""App factory — creates and configures the FastAPI application."""
|
|
127
|
+
if settings is None:
|
|
128
|
+
settings = get_settings()
|
|
129
|
+
|
|
130
|
+
app = FastAPI(
|
|
131
|
+
title="authgent",
|
|
132
|
+
description="The open-source identity provider for AI agents",
|
|
133
|
+
version="0.1.0",
|
|
134
|
+
lifespan=lifespan,
|
|
135
|
+
docs_url="/docs",
|
|
136
|
+
redoc_url="/redoc",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Error handlers
|
|
140
|
+
app.add_exception_handler(AuthgentError, authgent_error_handler) # type: ignore[arg-type]
|
|
141
|
+
app.add_exception_handler(Exception, unhandled_exception_handler) # type: ignore[arg-type]
|
|
142
|
+
|
|
143
|
+
# Middleware (order matters — outermost first)
|
|
144
|
+
app.add_middleware(RequestIdMiddleware)
|
|
145
|
+
setup_cors(app, settings)
|
|
146
|
+
app.add_middleware(
|
|
147
|
+
RateLimitMiddleware,
|
|
148
|
+
rate=settings.token_rate_limit,
|
|
149
|
+
paths=["/token"],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Routes
|
|
153
|
+
app.include_router(api_router)
|
|
154
|
+
|
|
155
|
+
return app
|