feat(standards): integrate auth-server via pinned spec (docs://spec) + auth recipe and template slice

- serve the pinned auth-server OpenAPI contract as the standards MCP resource docs://spec (no auth-server MCP bundle)
- add Auth Integration recipe (14) and wire it into the registry, prompts, index, and nav
- un-defer Auth Server in the library registry (spec/recipe pointers)
- ship the opt-in template auth slice: interfaces/auth/ (placeholder + mergeable bearerAuth security.yaml) and AUTH_SERVER/JWT_SERVER env
- rebuild bundle (13 modules, 44 components); template stays authless by default
This commit is contained in:
2026-10-14 16:18:14 +05:30
parent 6c0d859a1e
commit 18d52bdb7c
37 changed files with 2094 additions and 24 deletions

View File

@@ -187,6 +187,8 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [
"description": "Thin HTTP handlers bound to operationIds."}, "description": "Thin HTTP handlers bound to operationIds."},
{"kind": "openapi-routes", "path": "interfaces/openapi.py", "placeholder": "", {"kind": "openapi-routes", "path": "interfaces/openapi.py", "placeholder": "",
"description": "Route registry: flat re-export of every handler namespace."}, "description": "Route registry: flat re-export of every handler namespace."},
{"kind": "auth", "path": "interfaces/auth/security.yaml", "placeholder": "",
"description": "Mergeable bearerAuth security scheme + global security for openapi.yaml."},
{"kind": "test-model", "path": "tests/unit/test_item_models.py", "placeholder": "resource", {"kind": "test-model", "path": "tests/unit/test_item_models.py", "placeholder": "resource",
"description": "Unit test for the resource model."}, "description": "Unit test for the resource model."},
{"kind": "test-routes", "path": "tests/integration/test_item_routes.py", "placeholder": "resource", {"kind": "test-routes", "path": "tests/integration/test_item_routes.py", "placeholder": "resource",

View File

@@ -7,6 +7,7 @@ template code — before writing any infrastructure.
Resources: Resources:
docs://index, docs://nav, docs://modules/{module} (from the base server) docs://index, docs://nav, docs://modules/{module} (from the base server)
docs://spec (the pinned auth-server OpenAPI contract)
registry://libraries (the structured catalog) registry://libraries (the structured catalog)
registry://components (the template component index) registry://components (the template component index)
@@ -46,7 +47,8 @@ _INTAKE_TEXT = (
"2. Axis 1 — resources: each resource, its fields, and relationships\n" "2. Axis 1 — resources: each resource, its fields, and relationships\n"
" (or paste `openapi.yaml`).\n" " (or paste `openapi.yaml`).\n"
"3. Axis 2 — pipelines: multi-step inputs? families + formats (or `none`).\n" "3. Axis 2 — pipelines: multi-step inputs? families + formats (or `none`).\n"
"4. Auth needed now? (yes/no; auth is deferred — `py-jwt` only).\n" "4. Auth needed now? (yes/no). If yes, the auth-server service provides it —\n"
" read `docs://spec` + `docs://modules/auth_integration` (no auth code to write).\n"
"5. Integrations: Mongo / ingestion formats / email / LLM provider.\n\n" "5. Integrations: Mongo / ingestion formats / email / LLM provider.\n\n"
"Then: `build(app_name=..., resources=..., pipelines=..., auth=...)`.\n" "Then: `build(app_name=..., resources=..., pipelines=..., auth=...)`.\n"
) )
@@ -79,7 +81,10 @@ def _build_text(app_name: str, resources: str, pipelines: str, auth: str) -> str
"4. Scaffold with `get_app_template()` (whole tree) or pull single " "4. Scaffold with `get_app_template()` (whole tree) or pull single "
"files with `list_app_components()` and " "files with `list_app_components()` and "
"`get_app_component(kind, resource=..., family=...)`.\n" "`get_app_component(kind, resource=..., family=...)`.\n"
"5. Author only Tier C; keep Tier B as shipped.\n" "5. If auth is needed, read `docs://spec` (the auth-server contract) and "
"`docs://modules/auth_integration`; declare `securitySchemes.bearerAuth` "
"and `security` in your spec — do not write auth code.\n"
"6. Author only Tier C; keep Tier B as shipped.\n"
) )
@@ -114,6 +119,13 @@ class StandardsMCPServer(MCPServer):
return {"error": "not_found", "path": str(path), "components": []} return {"error": "not_found", "path": str(path), "components": []}
return json.loads(path.read_bytes()) return json.loads(path.read_bytes())
def _spec(self) -> dict[str, Any]:
"""Load the pinned OpenAPI contract (auth-server)."""
path = Path(self.mcp_root) / "specs" / "auth-server.json"
if not path.exists():
return {"error": "not_found", "path": str(path)}
return json.loads(path.read_bytes())
def _find_component(self, kind: str) -> dict[str, Any] | None: def _find_component(self, kind: str) -> dict[str, Any] | None:
for entry in self._components().get("components", []): for entry in self._components().get("components", []):
if entry.get("kind") == kind: if entry.get("kind") == kind:
@@ -183,6 +195,15 @@ class StandardsMCPServer(MCPServer):
def components() -> dict[str, Any]: def components() -> dict[str, Any]:
return self._components() return self._components()
@self.app.resource("docs://spec")
def spec() -> dict[str, Any]:
"""Return the pinned auth-server OpenAPI contract.
Use this (instead of a live fetch) to learn the auth-server client
contract when wiring authentication into an app.
"""
return self._spec()
@self.app.tool() @self.app.tool()
def find_library(capability: str) -> list[dict[str, Any]]: def find_library(capability: str) -> list[dict[str, Any]]:
"""Find internal libraries matching a capability keyword. """Find internal libraries matching a capability keyword.
@@ -211,6 +232,8 @@ class StandardsMCPServer(MCPServer):
str(lib.get("import", "")), str(lib.get("import", "")),
str(lib.get("purpose", "")), str(lib.get("purpose", "")),
str(lib.get("use_instead_of", "")), str(lib.get("use_instead_of", "")),
str(lib.get("spec", "")),
str(lib.get("recipe", "")),
" ".join(lib.get("capabilities", [])), " ".join(lib.get("capabilities", [])),
] ]
).lower() ).lower()

View File

@@ -571,6 +571,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link"> <a href="../13_library_registry/" class="md-nav__link">

View File

@@ -562,6 +562,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link"> <a href="../13_library_registry/" class="md-nav__link">

View File

@@ -553,6 +553,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link"> <a href="../13_library_registry/" class="md-nav__link">

View File

@@ -580,6 +580,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link"> <a href="../13_library_registry/" class="md-nav__link">

View File

@@ -589,6 +589,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link"> <a href="../13_library_registry/" class="md-nav__link">

View File

@@ -580,6 +580,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link"> <a href="../13_library_registry/" class="md-nav__link">

View File

@@ -553,6 +553,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link"> <a href="../13_library_registry/" class="md-nav__link">

View File

@@ -562,6 +562,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link"> <a href="../13_library_registry/" class="md-nav__link">

View File

@@ -580,6 +580,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link"> <a href="../13_library_registry/" class="md-nav__link">

View File

@@ -14,7 +14,7 @@
<link rel="prev" href="../09_infra_adaptors/"> <link rel="prev" href="../09_infra_adaptors/">
<link rel="next" href="../13_library_registry/"> <link rel="next" href="../14_auth_integration/">
<link rel="icon" href="../assets/images/favicon.png"> <link rel="icon" href="../assets/images/favicon.png">
@@ -562,6 +562,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link"> <a href="../13_library_registry/" class="md-nav__link">

View File

@@ -11,7 +11,7 @@
<link rel="prev" href="../12_genericity_matrix/"> <link rel="prev" href="../14_auth_integration/">
@@ -462,6 +462,28 @@
<li class="md-nav__item">
<a href="../14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
@@ -734,9 +756,9 @@ can read a library's full API by connecting to its MCP and reading
<tr> <tr>
<td>Auth Server</td> <td>Auth Server</td>
<td><em>(service)</em></td> <td><em>(service)</em></td>
<td>Central identity service (register/login/me/introspect)</td> <td>Central identity service (register/login/me/logout/introspect)</td>
<td>building auth endpoints yourself</td> <td>building auth endpoints yourself</td>
<td><em>(deferred)</em></td> <td>spec: <code>docs://spec</code></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -786,15 +808,20 @@ can read a library's full API by connecting to its MCP and reading
<h2 id="discovery">Discovery</h2> <h2 id="discovery">Discovery</h2>
<p>A single <strong>standards MCP</strong> exposes <code>find_library(capability)</code>, which returns the <p>A single <strong>standards MCP</strong> exposes <code>find_library(capability)</code>, which returns the
matching library, its import name, its "use instead of" guidance, and its MCP matching library, its import name, its "use instead of" guidance, and its MCP
endpoint. Query it before writing any infrastructure.</p> endpoint (or, for the auth-server service, a spec pointer). It also serves the
pinned auth-server OpenAPI contract at <code>docs://spec</code>. Query it before writing
any infrastructure.</p>
<h2 id="notes">Notes</h2> <h2 id="notes">Notes</h2>
<ul> <ul>
<li><strong>Hexa</strong> is a full library with its own MCP server on <code>:8008</code>: read <li><strong>Hexa</strong> is a full library with its own MCP server on <code>:8008</code>: read
<code>docs://index</code>, <code>docs://nav</code>, and <code>docs://modules/{module}</code> (e.g. <code>docs://index</code>, <code>docs://nav</code>, and <code>docs://modules/{module}</code> (e.g.
<code>docs://modules/hexa.container</code>) to discover the DI surface — <code>build(cls, *, <code>docs://modules/hexa.container</code>) to discover the DI surface — <code>build(cls, *,
instances, config)</code> and the <code>PortNode</code> model.</li> instances, config)</code> and the <code>PortNode</code> model.</li>
<li><strong>Auth Server</strong> is deferred: it has no <code>pyproject.toml</code>/importable package and <li><strong>Auth Server</strong> is a <strong>service</strong>, not an importable library: it has no MCP
no MCP bundle. Do not reach for it yet; <code>py-jwt</code> covers token primitives.</li> bundle by design. Read its client contract from the pinned OpenAPI document
via <code>docs://spec</code> and wire it by declaring a <code>bearerAuth</code> security scheme in
the app's own spec (see <strong>Auth Integration</strong>); <code>py-jwt</code> covers the
low-level token path.</li>
<li>Library MCP servers expose <strong>documentation resources</strong> (not executable tools): <li>Library MCP servers expose <strong>documentation resources</strong> (not executable tools):
they tell an agent the available API surface so it does not invent code.</li> they tell an agent the available API surface so it does not invent code.</li>
</ul> </ul>

View File

@@ -0,0 +1,971 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="The rules and library registry of the standardized application blueprint.">
<link rel="prev" href="../12_genericity_matrix/">
<link rel="next" href="../13_library_registry/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
<title>Auth Integration - Aetos Standards — Reference</title>
<link rel="stylesheet" href="../assets/stylesheets/main.84d31ad4.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Inter";--md-code-font:"JetBrains Mono"}</style>
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="cyan">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#auth-integration" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="Aetos Standards — Reference" class="md-header__button md-logo" aria-label="Aetos Standards — Reference" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Aetos Standards — Reference
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Auth Integration
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="deep-purple" data-md-color-accent="cyan" aria-hidden="true" type="radio" name="__palette" id="__palette_0">
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="Aetos Standards — Reference" class="md-nav__button md-logo" aria-label="Aetos Standards — Reference" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Aetos Standards — Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../01_build_recipe/" class="md-nav__link">
<span class="md-ellipsis">
Build Recipe
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../02_layering_rules/" class="md-nav__link">
<span class="md-ellipsis">
Layering Rules
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../03_composition_root/" class="md-nav__link">
<span class="md-ellipsis">
Composition Root
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../04_spec_first_routing/" class="md-nav__link">
<span class="md-ellipsis">
Spec-First Routing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../05_crud_template/" class="md-nav__link">
<span class="md-ellipsis">
CRUD Template
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../06_pipeline_scaffold/" class="md-nav__link">
<span class="md-ellipsis">
Pipeline Scaffold
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../07_worker_and_progress/" class="md-nav__link">
<span class="md-ellipsis">
Worker & Progress
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../08_human_in_loop/" class="md-nav__link">
<span class="md-ellipsis">
Human-in-the-Loop
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../09_infra_adaptors/" class="md-nav__link">
<span class="md-ellipsis">
Infra Adapters
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../12_genericity_matrix/" class="md-nav__link">
<span class="md-ellipsis">
Genericity Matrix
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Auth Integration
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#read-the-contract" class="md-nav__link">
<span class="md-ellipsis">
Read the contract
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#wire-it-up-declare-dont-code" class="md-nav__link">
<span class="md-ellipsis">
Wire it up: declare, don't code
</span>
</a>
<nav class="md-nav" aria-label="Wire it up: declare, don&#39;t code">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#public-operations" class="md-nav__link">
<span class="md-ellipsis">
Public operations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#environment" class="md-nav__link">
<span class="md-ellipsis">
Environment
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#path-1-jwtlib-client-no-generator" class="md-nav__link">
<span class="md-ellipsis">
Path 1: jwtlib client (no generator)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#golden-rules" class="md-nav__link">
<span class="md-ellipsis">
Golden rules
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#notes" class="md-nav__link">
<span class="md-ellipsis">
Notes
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../13_library_registry/" class="md-nav__link">
<span class="md-ellipsis">
Library Registry
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#summary" class="md-nav__link">
<span class="md-ellipsis">
Summary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#read-the-contract" class="md-nav__link">
<span class="md-ellipsis">
Read the contract
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#wire-it-up-declare-dont-code" class="md-nav__link">
<span class="md-ellipsis">
Wire it up: declare, don't code
</span>
</a>
<nav class="md-nav" aria-label="Wire it up: declare, don&#39;t code">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#public-operations" class="md-nav__link">
<span class="md-ellipsis">
Public operations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#environment" class="md-nav__link">
<span class="md-ellipsis">
Environment
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#path-1-jwtlib-client-no-generator" class="md-nav__link">
<span class="md-ellipsis">
Path 1: jwtlib client (no generator)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#golden-rules" class="md-nav__link">
<span class="md-ellipsis">
Golden rules
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#notes" class="md-nav__link">
<span class="md-ellipsis">
Notes
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="auth-integration">Auth Integration</h1>
<h2 id="summary">Summary</h2>
<p>Authentication is a <strong>service</strong>, not a library. Every Aetos application shares a
single identity service — the <strong>auth-server</strong> — and consumes it through its HTTP
contract. An app never implements registration, login, password hashing, or
token issuance itself.</p>
<p>The agent does <strong>not</strong> need an MCP bundle for the auth-server. It reads the
pinned OpenAPI contract (below) and declares a bearer security scheme in its own
spec; <code>openapi_first</code> then generates the verifying dependency. <strong>No auth code is
written.</strong></p>
<hr />
<h2 id="read-the-contract">Read the contract</h2>
<p>The auth-server OpenAPI document is pinned in the standards bundle and served as
an MCP resource:</p>
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-0-1">1</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1"></a>docs://spec
</span></code></pre></div></td></tr></table></div>
<p>It is the authoritative client contract (OpenAPI 3.1). Read it to learn the
endpoints and the request/response schemas:</p>
<table>
<thead>
<tr>
<th>Endpoint</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>POST /register</code></td>
<td>Create a user (201, returns <code>PublicUser</code>)</td>
</tr>
<tr>
<td><code>POST /login</code></td>
<td>Authenticate and issue a JWT (<code>LoginResponse</code>)</td>
</tr>
<tr>
<td><code>GET /me</code></td>
<td>Current user's <code>PublicUser</code> (bearer required)</td>
</tr>
<tr>
<td><code>POST /logout</code></td>
<td>Stateless logout (<code>LogoutResponse</code>)</td>
</tr>
<tr>
<td><code>POST /introspect</code></td>
<td>Internal token introspection (<code>active</code>, <code>user</code>)</td>
</tr>
</tbody>
</table>
<p>The pinned snapshot is versioned (currently <code>0.0.5</code>). If the caller has a live
auth-server and needs the <em>current</em> contract, they can pass the full endpoint
(<code>{AUTH_SERVER}/openapi.json</code>) and read it directly instead of the pin.</p>
<hr />
<h2 id="wire-it-up-declare-dont-code">Wire it up: declare, don't code</h2>
<p>Authentication is added by editing the app's <strong>own</strong> <code>openapi.yaml</code> — nothing
else. Declare a global bearer requirement and point the scheme at the auth
server (values come from the environment):</p>
<div class="language-yaml highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1"> 1</a></span>
<span class="normal"><a href="#__codelineno-1-2"> 2</a></span>
<span class="normal"><a href="#__codelineno-1-3"> 3</a></span>
<span class="normal"><a href="#__codelineno-1-4"> 4</a></span>
<span class="normal"><a href="#__codelineno-1-5"> 5</a></span>
<span class="normal"><a href="#__codelineno-1-6"> 6</a></span>
<span class="normal"><a href="#__codelineno-1-7"> 7</a></span>
<span class="normal"><a href="#__codelineno-1-8"> 8</a></span>
<span class="normal"><a href="#__codelineno-1-9"> 9</a></span>
<span class="normal"><a href="#__codelineno-1-10">10</a></span>
<span class="normal"><a href="#__codelineno-1-11">11</a></span>
<span class="normal"><a href="#__codelineno-1-12">12</a></span>
<span class="normal"><a href="#__codelineno-1-13">13</a></span>
<span class="normal"><a href="#__codelineno-1-14">14</a></span>
<span class="normal"><a href="#__codelineno-1-15">15</a></span>
<span class="normal"><a href="#__codelineno-1-16">16</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a><span class="nt">security</span><span class="p">:</span>
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a><span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">bearerAuth</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[]</span>
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3"></a><span class="nt">paths</span><span class="p">:</span>
</span><span id="__span-1-4"><a id="__codelineno-1-4" name="__codelineno-1-4"></a><span class="w"> </span><span class="c1"># ...</span>
</span><span id="__span-1-5"><a id="__codelineno-1-5" name="__codelineno-1-5"></a><span class="nt">components</span><span class="p">:</span>
</span><span id="__span-1-6"><a id="__codelineno-1-6" name="__codelineno-1-6"></a><span class="w"> </span><span class="nt">securitySchemes</span><span class="p">:</span>
</span><span id="__span-1-7"><a id="__codelineno-1-7" name="__codelineno-1-7"></a><span class="w"> </span><span class="nt">bearerAuth</span><span class="p">:</span>
</span><span id="__span-1-8"><a id="__codelineno-1-8" name="__codelineno-1-8"></a><span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">http</span>
</span><span id="__span-1-9"><a id="__codelineno-1-9" name="__codelineno-1-9"></a><span class="w"> </span><span class="nt">scheme</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">bearer</span>
</span><span id="__span-1-10"><a id="__codelineno-1-10" name="__codelineno-1-10"></a><span class="w"> </span><span class="nt">bearerFormat</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">JWT</span>
</span><span id="__span-1-11"><a id="__codelineno-1-11" name="__codelineno-1-11"></a><span class="w"> </span><span class="nt">x-server-url</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;{AUTH_SERVER}&quot;</span>
</span><span id="__span-1-12"><a id="__codelineno-1-12" name="__codelineno-1-12"></a><span class="w"> </span><span class="nt">x-introspect-path</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;/introspect&quot;</span>
</span><span id="__span-1-13"><a id="__codelineno-1-13" name="__codelineno-1-13"></a><span class="w"> </span><span class="nt">x-login-path</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;/login&quot;</span>
</span><span id="__span-1-14"><a id="__codelineno-1-14" name="__codelineno-1-14"></a><span class="w"> </span><span class="nt">x-register-path</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;/register&quot;</span>
</span><span id="__span-1-15"><a id="__codelineno-1-15" name="__codelineno-1-15"></a><span class="w"> </span><span class="nt">x-logout-path</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;/logout&quot;</span>
</span><span id="__span-1-16"><a id="__codelineno-1-16" name="__codelineno-1-16"></a><span class="w"> </span><span class="nt">x-me-path</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;/me&quot;</span>
</span></code></pre></div></td></tr></table></div>
<p><code>openapi_first</code> reads <code>securitySchemes</code> + <code>security</code> and generates a FastAPI
dependency that extracts the bearer token, POSTs <code>{"token": &lt;jwt&gt;}</code> to
<code>{AUTH_SERVER}/introspect</code>, and returns the user when <code>active == true</code>. It
raises <code>401</code> on an invalid or inactive token and <code>503</code> when the auth server is
unreachable. The generated handlers receive the authenticated user — you author
<strong>no</strong> authentication code.</p>
<h3 id="public-operations">Public operations</h3>
<p>A route that must stay open opts out by overriding <code>security</code> with an empty
list:</p>
<div class="language-yaml highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-2-1">1</a></span>
<span class="normal"><a href="#__codelineno-2-2">2</a></span>
<span class="normal"><a href="#__codelineno-2-3">3</a></span>
<span class="normal"><a href="#__codelineno-2-4">4</a></span>
<span class="normal"><a href="#__codelineno-2-5">5</a></span>
<span class="normal"><a href="#__codelineno-2-6">6</a></span>
<span class="normal"><a href="#__codelineno-2-7">7</a></span>
<span class="normal"><a href="#__codelineno-2-8">8</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a><span class="nt">paths</span><span class="p">:</span>
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a><span class="w"> </span><span class="nt">/health</span><span class="p">:</span>
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3"></a><span class="w"> </span><span class="nt">get</span><span class="p">:</span>
</span><span id="__span-2-4"><a id="__codelineno-2-4" name="__codelineno-2-4"></a><span class="w"> </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">health</span>
</span><span id="__span-2-5"><a id="__codelineno-2-5" name="__codelineno-2-5"></a><span class="w"> </span><span class="nt">security</span><span class="p">:</span><span class="w"> </span><span class="p p-Indicator">[]</span><span class="w"> </span><span class="c1"># overrides the global requirement</span>
</span><span id="__span-2-6"><a id="__codelineno-2-6" name="__codelineno-2-6"></a><span class="w"> </span><span class="nt">responses</span><span class="p">:</span>
</span><span id="__span-2-7"><a id="__codelineno-2-7" name="__codelineno-2-7"></a><span class="w"> </span><span class="s">&quot;200&quot;</span><span class="p p-Indicator">:</span>
</span><span id="__span-2-8"><a id="__codelineno-2-8" name="__codelineno-2-8"></a><span class="w"> </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">OK</span>
</span></code></pre></div></td></tr></table></div>
<h3 id="environment">Environment</h3>
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-3-1">1</a></span>
<span class="normal"><a href="#__codelineno-3-2">2</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1"></a><span class="nv">AUTH_SERVER</span><span class="o">=</span>http://localhost:8000<span class="w"> </span><span class="c1"># auth-server base URL</span>
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2"></a><span class="nv">JWT_SERVER</span><span class="o">=</span>http://localhost:8000<span class="w"> </span><span class="c1"># jwtlib client base URL (seeding/CLI)</span>
</span></code></pre></div></td></tr></table></div>
<hr />
<h2 id="path-1-jwtlib-client-no-generator">Path 1: <code>jwtlib</code> client (no generator)</h2>
<p>Apps that do not use a generated dependency verify requests with the <code>py-jwt</code>
client:</p>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-4-1">1</a></span>
<span class="normal"><a href="#__codelineno-4-2">2</a></span>
<span class="normal"><a href="#__codelineno-4-3">3</a></span>
<span class="normal"><a href="#__codelineno-4-4">4</a></span>
<span class="normal"><a href="#__codelineno-4-5">5</a></span>
<span class="normal"><a href="#__codelineno-4-6">6</a></span>
<span class="normal"><a href="#__codelineno-4-7">7</a></span>
<span class="normal"><a href="#__codelineno-4-8">8</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1"></a><span class="kn">from</span><span class="w"> </span><span class="nn">jwtlib.introspection</span><span class="w"> </span><span class="kn">import</span> <span class="n">authenticate_request</span>
</span><span id="__span-4-2"><a id="__codelineno-4-2" name="__codelineno-4-2"></a>
</span><span id="__span-4-3"><a id="__codelineno-4-3" name="__codelineno-4-3"></a><span class="n">authorized</span> <span class="o">=</span> <span class="k">await</span> <span class="n">authenticate_request</span><span class="p">(</span>
</span><span id="__span-4-4"><a id="__codelineno-4-4" name="__codelineno-4-4"></a> <span class="n">should_skip_authentication</span><span class="p">,</span> <span class="c1"># allow public paths</span>
</span><span id="__span-4-5"><a id="__codelineno-4-5" name="__codelineno-4-5"></a> <span class="n">method</span><span class="p">,</span>
</span><span id="__span-4-6"><a id="__codelineno-4-6" name="__codelineno-4-6"></a> <span class="n">path</span><span class="p">,</span>
</span><span id="__span-4-7"><a id="__codelineno-4-7" name="__codelineno-4-7"></a> <span class="n">authorization_token</span><span class="p">,</span> <span class="c1"># &quot;Bearer &lt;jwt&gt;&quot; or None</span>
</span><span id="__span-4-8"><a id="__codelineno-4-8" name="__codelineno-4-8"></a><span class="p">)</span>
</span></code></pre></div></td></tr></table></div>
<p><code>authenticate_request</code> POSTs to <code>{JWT_SERVER}/introspect</code> (3 second timeout) and
treats <code>{"active": true, "user": {...}}</code> as valid. Use this for seeding scripts
and tests (<code>jwtlib.register_user</code>, <code>jwtlib.login_user</code>).</p>
<hr />
<h2 id="golden-rules">Golden rules</h2>
<ol>
<li><strong>Never trust an unverified token.</strong> Decode through the generated dependency
or <code>jwtlib</code> before handling the request.</li>
<li><strong>Treat <code>active: false</code> as unauthenticated</strong>, even if the JWT decodes.</li>
<li><strong>Do not implement your own JWT parsing.</strong> Revocation and issuer changes must
stay centralized in the auth-server.</li>
<li><strong>Do not add auth until the app's outcome is validated.</strong> Ship the app
authless first; layer the security scheme once the core behaviour works.</li>
</ol>
<hr />
<h2 id="notes">Notes</h2>
<ul>
<li>The default template is <strong>authless</strong> on purpose. Add the <code>securitySchemes</code>
block above only when auth is required. Pull the shared slice with
<code>get_app_component("auth")</code>.</li>
<li>The auth-server is deployed as a service (no importable package); the client
surface is the spec, plus <code>jwtlib</code> for the low-level path.</li>
</ul>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.sections", "navigation.expand", "navigation.top", "navigation.instant", "navigation.tracking", "navigation.indexes", "content.code.copy", "content.code.annotate", "content.tabs.link", "content.action.edit", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.f55a23d4.min.js"></script>
</body>
</html>

View File

@@ -457,6 +457,28 @@
<li class="md-nav__item">
<a href="/14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="/13_library_registry/" class="md-nav__link"> <a href="/13_library_registry/" class="md-nav__link">

View File

@@ -474,6 +474,28 @@
<li class="md-nav__item">
<a href="14_auth_integration/" class="md-nav__link">
<span class="md-ellipsis">
Auth Integration
</span>
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="13_library_registry/" class="md-nav__link"> <a href="13_library_registry/" class="md-nav__link">
@@ -540,6 +562,8 @@ patterns, and the internal library catalog that agents consult while coding.</p>
<li><strong>Human-in-the-Loop</strong> — guardrails and approvals inside automations.</li> <li><strong>Human-in-the-Loop</strong> — guardrails and approvals inside automations.</li>
<li><strong>Infra Adapters</strong> — how infrastructure connects without leaking into domain.</li> <li><strong>Infra Adapters</strong> — how infrastructure connects without leaking into domain.</li>
<li><strong>Genericity Matrix</strong> — what is library-backed, template-provided, or authored.</li> <li><strong>Genericity Matrix</strong> — what is library-backed, template-provided, or authored.</li>
<li><strong>Auth Integration</strong> — consuming the shared auth-server service (spec-driven,
no auth code).</li>
<li><strong>Library Registry</strong> — the catalog of internal libraries behind all of it.</li> <li><strong>Library Registry</strong> — the catalog of internal libraries behind all of it.</li>
</ul> </ul>
<p>Read the <strong>Wiki</strong> for the philosophy and rationale; these pages are the <p>Read the <strong>Wiki</strong> for the philosophy and rationale; these pages are the

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -42,6 +42,12 @@
"placeholder": "", "placeholder": "",
"description": "Route registry: flat re-export of every handler namespace." "description": "Route registry: flat re-export of every handler namespace."
}, },
{
"kind": "auth",
"path": "interfaces/auth/security.yaml",
"placeholder": "",
"description": "Mergeable bearerAuth security scheme + global security for openapi.yaml."
},
{ {
"kind": "test-model", "kind": "test-model",
"path": "tests/unit/test_item_models.py", "path": "tests/unit/test_item_models.py",

View File

@@ -1,6 +1,6 @@
{ {
"project": "standards", "project": "standards",
"type": "standards", "type": "standards",
"modules_count": 12, "modules_count": 13,
"source": "hand-authored" "source": "hand-authored"
} }

View File

@@ -68,12 +68,13 @@
}, },
{ {
"name": "Auth Server", "name": "Auth Server",
"import": "auth-server", "import": null,
"purpose": "Central identity service (register/login/me/introspect).", "purpose": "Central identity service (register/login/me/logout/introspect).",
"use_instead_of": "building authentication endpoints yourself", "use_instead_of": "building authentication endpoints yourself",
"capabilities": ["auth", "identity", "service", "register", "login", "introspect"], "capabilities": ["auth", "identity", "service", "register", "login", "logout", "introspect", "bearer"],
"status": "deferred", "spec": "docs://spec",
"mcp": { "server": null, "port": null, "note": "service; no MCP bundle; deferred" } "recipe": "docs://modules/auth_integration",
"mcp": { "server": null, "port": null, "note": "service; read contract via docs://spec; no MCP bundle" }
} }
] ]
} }

View File

@@ -0,0 +1,8 @@
{
"module": "auth_integration",
"content": {
"path": "auth_integration",
"docstring": "# Auth Integration\n\n## Summary\n\nAuthentication is a **service**, not a library. Every Aetos application shares a\nsingle identity service — the **auth-server** — and consumes it through its HTTP\ncontract. An app never implements registration, login, password hashing, or\ntoken issuance itself.\n\nThe agent does **not** need an MCP bundle for the auth-server. It reads the\npinned OpenAPI contract (below) and declares a bearer security scheme in its own\nspec; `openapi_first` then generates the verifying dependency. **No auth code is\nwritten.**\n\n---\n\n## Read the contract\n\nThe auth-server OpenAPI document is pinned in the standards bundle and served as\nan MCP resource:\n\n```\ndocs://spec\n```\n\nIt is the authoritative client contract (OpenAPI 3.1). Read it to learn the\nendpoints and the request/response schemas:\n\n| Endpoint | Purpose |\n|---|---|\n| `POST /register` | Create a user (201, returns `PublicUser`) |\n| `POST /login` | Authenticate and issue a JWT (`LoginResponse`) |\n| `GET /me` | Current user's `PublicUser` (bearer required) |\n| `POST /logout` | Stateless logout (`LogoutResponse`) |\n| `POST /introspect` | Internal token introspection (`active`, `user`) |\n\nThe pinned snapshot is versioned (currently `0.0.5`). If the caller has a live\nauth-server and needs the *current* contract, they can pass the full endpoint\n(`{AUTH_SERVER}/openapi.json`) and read it directly instead of the pin.\n\n---\n\n## Wire it up: declare, don't code\n\nAuthentication is added by editing the app's **own** `openapi.yaml` — nothing\nelse. Declare a global bearer requirement and point the scheme at the auth\nserver (values come from the environment):\n\n```yaml\nsecurity:\n - bearerAuth: []\npaths:\n # ...\ncomponents:\n securitySchemes:\n bearerAuth:\n type: http\n scheme: bearer\n bearerFormat: JWT\n x-server-url: \"{AUTH_SERVER}\"\n x-introspect-path: \"/introspect\"\n x-login-path: \"/login\"\n x-register-path: \"/register\"\n x-logout-path: \"/logout\"\n x-me-path: \"/me\"\n```\n\n`openapi_first` reads `securitySchemes` + `security` and generates a FastAPI\ndependency that extracts the bearer token, POSTs `{\"token\": <jwt>}` to\n`{AUTH_SERVER}/introspect`, and returns the user when `active == true`. It\nraises `401` on an invalid or inactive token and `503` when the auth server is\nunreachable. The generated handlers receive the authenticated user — you author\n**no** authentication code.\n\n### Public operations\n\nA route that must stay open opts out by overriding `security` with an empty\nlist:\n\n```yaml\npaths:\n /health:\n get:\n operationId: health\n security: [] # overrides the global requirement\n responses:\n \"200\":\n description: OK\n```\n\n### Environment\n\n```bash\nAUTH_SERVER=http://localhost:8000 # auth-server base URL\nJWT_SERVER=http://localhost:8000 # jwtlib client base URL (seeding/CLI)\n```\n\n---\n\n## Path 1: `jwtlib` client (no generator)\n\nApps that do not use a generated dependency verify requests with the `py-jwt`\nclient:\n\n```python\nfrom jwtlib.introspection import authenticate_request\n\nauthorized = await authenticate_request(\n should_skip_authentication, # allow public paths\n method,\n path,\n authorization_token, # \"Bearer <jwt>\" or None\n)\n```\n\n`authenticate_request` POSTs to `{JWT_SERVER}/introspect` (3 second timeout) and\ntreats `{\"active\": true, \"user\": {...}}` as valid. Use this for seeding scripts\nand tests (`jwtlib.register_user`, `jwtlib.login_user`).\n\n---\n\n## Golden rules\n\n1. **Never trust an unverified token.** Decode through the generated dependency\n or `jwtlib` before handling the request.\n2. **Treat `active: false` as unauthenticated**, even if the JWT decodes.\n3. **Do not implement your own JWT parsing.** Revocation and issuer changes must\n stay centralized in the auth-server.\n4. **Do not add auth until the app's outcome is validated.** Ship the app\n authless first; layer the security scheme once the core behaviour works.\n\n---\n\n## Notes\n\n- The default template is **authless** on purpose. Add the `securitySchemes`\n block above only when auth is required. Pull the shared slice with\n `get_app_component(\"auth\")`.\n- The auth-server is deployed as a service (no importable package); the client\n surface is the spec, plus `jwtlib` for the low-level path.\n",
"objects": {}
}
}

View File

@@ -2,7 +2,7 @@
"module": "index", "module": "index",
"content": { "content": {
"path": "index", "path": "index",
"docstring": "# Standards Reference\n\nThe reference half of the Standards description: the enforceable rules,\npatterns, and the internal library catalog that agents consult while coding.\n\n- **Build Recipe** — the ordered, agentic checklist for a new app.\n- **Layering Rules** — the strict request → domain → repository layering and\n the rules that enforce it.\n- **Composition Root** — a single composition point wiring the whole app.\n- **Spec-First Routing** — the OpenAPI contract as the routing source of truth.\n- **CRUD Template** — the standardized generic resource endpoint scaffold.\n- **Pipeline Scaffold** — the deterministic pipeline skeleton for multi-stage\n jobs.\n- **Worker & Progress** — queued work, progress reporting, and visibility.\n- **Human-in-the-Loop** — guardrails and approvals inside automations.\n- **Infra Adapters** — how infrastructure connects without leaking into domain.\n- **Genericity Matrix** — what is library-backed, template-provided, or authored.\n- **Library Registry** — the catalog of internal libraries behind all of it.\n\nRead the **Wiki** for the philosophy and rationale; these pages are the\nreference that mirrors the `standards` MCP bundle.\n", "docstring": "# Standards Reference\n\nThe reference half of the Standards description: the enforceable rules,\npatterns, and the internal library catalog that agents consult while coding.\n\n- **Build Recipe** — the ordered, agentic checklist for a new app.\n- **Layering Rules** — the strict request → domain → repository layering and\n the rules that enforce it.\n- **Composition Root** — a single composition point wiring the whole app.\n- **Spec-First Routing** — the OpenAPI contract as the routing source of truth.\n- **CRUD Template** — the standardized generic resource endpoint scaffold.\n- **Pipeline Scaffold** — the deterministic pipeline skeleton for multi-stage\n jobs.\n- **Worker & Progress** — queued work, progress reporting, and visibility.\n- **Human-in-the-Loop** — guardrails and approvals inside automations.\n- **Infra Adapters** — how infrastructure connects without leaking into domain.\n- **Genericity Matrix** — what is library-backed, template-provided, or authored.\n- **Auth Integration** — consuming the shared auth-server service (spec-driven,\n no auth code).\n- **Library Registry** — the catalog of internal libraries behind all of it.\n\nRead the **Wiki** for the philosophy and rationale; these pages are the\nreference that mirrors the `standards` MCP bundle.\n",
"objects": {} "objects": {}
} }
} }

View File

@@ -2,7 +2,7 @@
"module": "library_registry", "module": "library_registry",
"content": { "content": {
"path": "library_registry", "path": "library_registry",
"docstring": "# Library Registry\n\n## Summary\n\nThe Aetos ecosystem provides internal libraries for the shared concerns of\nevery application. **Use them.** Writing a private copy of any capability below\nis a defect.\n\nEach library also exposes its own documentation as an MCP server. An AI agent\ncan read a library's full API by connecting to its MCP and reading\n`docs://index`, `docs://nav`, and `docs://modules/{module}`.\n\n---\n\n## Catalog\n\n| Library | Import | Purpose | Use instead of | MCP |\n|---|---|---|---|---|\n| DAG Pipe | `dagpipe` | Deterministic state-transformation DAG engine | hand-rolled orchestration/topological ordering | `:8006` |\n| Hexa | `hexa` | Declarative DI / composition-root tooling | manual wiring, hand-written factories | `:8008` |\n| Mongo Ops | `mongo_ops` | Async MongoDB repositories, cache, populate, transactions | raw Motor CRUD and connection lifecycle | `:8007` |\n| Omniread | `omniread` | Format-agnostic fetch + parse (HTML, PDF, CSV, XLSX) | custom scrapers/parsers | `:8003` |\n| OpenAPI First | `openapi_first` | Spec-first FastAPI bootstrap, client, codegen | decorator routers and hand-written clients | `:8004` |\n| Mail Intake | `mail_intake` | Provider-agnostic read-only email ingestion | Gmail OAuth + MIME parsing | `:8002` |\n| Py JWT | `jwtlib` | JWT auth primitives (register/login/introspect) | password hashing + token code | `:8005` |\n| Doc Forge | `docforge` | Docstring compiler → MkDocs + MCP docs | custom doc generators | `:8001` |\n| Auth Server | *(service)* | Central identity service (register/login/me/introspect) | building auth endpoints yourself | *(deferred)* |\n\n## How to choose\n\n| You need to… | Use |\n|---|---|\n| persist and query documents | Mongo Ops |\n| compose dependencies | Hexa |\n| run a multi-step transformation | DAG Pipe |\n| read/parse an arbitrary input format | Omniread |\n| expose a contract-first HTTP API | OpenAPI First |\n| ingest email | Mail Intake |\n| authenticate users / verify tokens | Py JWT (+ Auth Server) |\n| generate documentation | Doc Forge |\n\n## Discovery\n\nA single **standards MCP** exposes `find_library(capability)`, which returns the\nmatching library, its import name, its \"use instead of\" guidance, and its MCP\nendpoint. Query it before writing any infrastructure.\n\n## Notes\n\n- **Hexa** is a full library with its own MCP server on `:8008`: read\n `docs://index`, `docs://nav`, and `docs://modules/{module}` (e.g.\n `docs://modules/hexa.container`) to discover the DI surface — `build(cls, *,\n instances, config)` and the `PortNode` model.\n- **Auth Server** is deferred: it has no `pyproject.toml`/importable package and\n no MCP bundle. Do not reach for it yet; `py-jwt` covers token primitives.\n- Library MCP servers expose **documentation resources** (not executable tools):\n they tell an agent the available API surface so it does not invent code.\n", "docstring": "# Library Registry\n\n## Summary\n\nThe Aetos ecosystem provides internal libraries for the shared concerns of\nevery application. **Use them.** Writing a private copy of any capability below\nis a defect.\n\nEach library also exposes its own documentation as an MCP server. An AI agent\ncan read a library's full API by connecting to its MCP and reading\n`docs://index`, `docs://nav`, and `docs://modules/{module}`.\n\n---\n\n## Catalog\n\n| Library | Import | Purpose | Use instead of | MCP |\n|---|---|---|---|---|\n| DAG Pipe | `dagpipe` | Deterministic state-transformation DAG engine | hand-rolled orchestration/topological ordering | `:8006` |\n| Hexa | `hexa` | Declarative DI / composition-root tooling | manual wiring, hand-written factories | `:8008` |\n| Mongo Ops | `mongo_ops` | Async MongoDB repositories, cache, populate, transactions | raw Motor CRUD and connection lifecycle | `:8007` |\n| Omniread | `omniread` | Format-agnostic fetch + parse (HTML, PDF, CSV, XLSX) | custom scrapers/parsers | `:8003` |\n| OpenAPI First | `openapi_first` | Spec-first FastAPI bootstrap, client, codegen | decorator routers and hand-written clients | `:8004` |\n| Mail Intake | `mail_intake` | Provider-agnostic read-only email ingestion | Gmail OAuth + MIME parsing | `:8002` |\n| Py JWT | `jwtlib` | JWT auth primitives (register/login/introspect) | password hashing + token code | `:8005` |\n| Doc Forge | `docforge` | Docstring compiler → MkDocs + MCP docs | custom doc generators | `:8001` |\n| Auth Server | *(service)* | Central identity service (register/login/me/logout/introspect) | building auth endpoints yourself | spec: `docs://spec` |\n\n## How to choose\n\n| You need to… | Use |\n|---|---|\n| persist and query documents | Mongo Ops |\n| compose dependencies | Hexa |\n| run a multi-step transformation | DAG Pipe |\n| read/parse an arbitrary input format | Omniread |\n| expose a contract-first HTTP API | OpenAPI First |\n| ingest email | Mail Intake |\n| authenticate users / verify tokens | Py JWT (+ Auth Server) |\n| generate documentation | Doc Forge |\n\n## Discovery\n\nA single **standards MCP** exposes `find_library(capability)`, which returns the\nmatching library, its import name, its \"use instead of\" guidance, and its MCP\nendpoint (or, for the auth-server service, a spec pointer). It also serves the\npinned auth-server OpenAPI contract at `docs://spec`. Query it before writing\nany infrastructure.\n\n## Notes\n\n- **Hexa** is a full library with its own MCP server on `:8008`: read\n `docs://index`, `docs://nav`, and `docs://modules/{module}` (e.g.\n `docs://modules/hexa.container`) to discover the DI surface — `build(cls, *,\n instances, config)` and the `PortNode` model.\n- **Auth Server** is a **service**, not an importable library: it has no MCP\n bundle by design. Read its client contract from the pinned OpenAPI document\n via `docs://spec` and wire it by declaring a `bearerAuth` security scheme in\n the app's own spec (see **Auth Integration**); `py-jwt` covers the\n low-level token path.\n- Library MCP servers expose **documentation resources** (not executable tools):\n they tell an agent the available API surface so it does not invent code.\n",
"objects": {} "objects": {}
} }
} }

View File

@@ -54,6 +54,11 @@
"resource": "docs://modules/library_registry", "resource": "docs://modules/library_registry",
"title": "Library Registry" "title": "Library Registry"
}, },
{
"module": "auth_integration",
"resource": "docs://modules/auth_integration",
"title": "Auth Integration"
},
{ {
"module": "index", "module": "index",
"resource": "docs://modules/index", "resource": "docs://modules/index",

View File

@@ -0,0 +1,563 @@
{
"openapi": "3.1.0",
"info": {
"title": "Aetoskia Auth Server",
"description": "\n The Aetoskia Authentication Service.\n\n Provides identity, access control, and JWT token issuance for the Aetoskia\n ecosystem. Fully async, built with FastAPI and MongoDB.\n ",
"termsOfService": "https://aetoskia.com/terms",
"contact": {
"name": "Aetoskia Dev Team",
"url": "https://dev.aetoskia.com/contact",
"email": "dev@aetoskia.com"
},
"license": {
"name": "MIT License",
"url": "https://opensource.org/licenses/MIT"
},
"version": "0.0.5"
},
"servers": [
{
"url": "https://auth.aetoskia.com",
"description": "Production Authentication Service"
},
{
"url": "http://server-pi:9002",
"description": "Internal staging environment"
},
{
"url": "http://localhost:8000",
"description": "Local development server"
}
],
"paths": {
"/register": {
"post": {
"tags": [
"Auth"
],
"summary": "Register a new user",
"description": "Creates a new user account and returns the public user profile.",
"operationId": "create_user_register_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RegisterRequest"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PublicUser"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/login": {
"post": {
"tags": [
"Auth"
],
"summary": "Login user",
"description": "Authenticates a user and issues a JWT access token.",
"operationId": "login_login_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/me": {
"get": {
"tags": [
"Auth"
],
"summary": "Get current user",
"description": "Returns the currently authenticated user's public profile.",
"operationId": "read_users_me_me_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PublicUser"
}
}
}
}
},
"security": [
{
"HTTPBearer": []
}
]
}
},
"/logout": {
"post": {
"tags": [
"Auth"
],
"summary": "Logout user",
"description": "Stateless logout endpoint. The client must discard the JWT access token.",
"operationId": "logout_logout_post",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LogoutResponse"
}
}
}
}
},
"security": [
{
"HTTPBearer": []
}
]
}
},
"/introspect": {
"post": {
"tags": [
"Auth",
"Internal"
],
"summary": "Token introspection (internal)",
"description": "Internal service-to-service endpoint for verifying JWTs. Not intended for direct client use.",
"operationId": "introspect_introspect_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/IntrospectRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/IntrospectResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/health": {
"get": {
"tags": [
"Health"
],
"summary": "Health Check",
"description": "Report service health.\n\nReturns:\n dict:\n A simple ``{\"status\": \"ok\"}`` payload used by load balancers and\n the container health check.",
"operationId": "health_check_health_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
},
"components": {
"schemas": {
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
},
"IntrospectRequest": {
"properties": {
"token": {
"type": "string",
"title": "Token",
"description": "JWT access token to introspect",
"examples": [
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
]
}
},
"type": "object",
"required": [
"token"
],
"title": "IntrospectRequest",
"description": "Payload for requesting token introspection.\n\nUsed by internal services to verify the validity of a JWT and retrieve\nthe associated public user information.\n\nFields:\n token: JWT access token to introspect.\n\nNotes:\n - Intended for service-to-service communication.\n - Not meant for direct end-user consumption.",
"example": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
},
"IntrospectResponse": {
"properties": {
"active": {
"type": "boolean",
"title": "Active",
"description": "Indicates whether the provided token is valid and active",
"examples": [
true,
false
]
},
"user": {
"anyOf": [
{
"$ref": "#/components/schemas/PublicUser"
},
{
"type": "null"
}
],
"description": "Public user details if the token is valid; null otherwise"
}
},
"type": "object",
"required": [
"active"
],
"title": "IntrospectResponse",
"description": "Result of a token introspection operation.\n\nThis model communicates whether a JWT is valid and, if so, provides\nthe associated public user information.\n\nFields:\n active: Indicates whether the token is valid and active.\n user: Public user details if the token is valid; otherwise null.\n\nNotes:\n - This model is designed to avoid raising exceptions.\n - All introspection outcomes are represented as data.",
"example": {
"active": true,
"user": {
"email": "tester@example.com",
"is_active": true,
"username": "tester"
}
}
},
"LoginRequest": {
"properties": {
"password": {
"type": "string",
"minLength": 6,
"title": "Password",
"description": "User password (minimum 6 characters). Stored only as a hash.",
"examples": [
"SuperSecure123"
]
},
"username": {
"type": "string",
"maxLength": 50,
"minLength": 3,
"title": "Username",
"description": "Unique username used for authentication and display",
"examples": [
"tester"
]
},
"email": {
"anyOf": [
{
"type": "string",
"format": "email"
},
{
"type": "null"
}
],
"title": "Email",
"description": "Primary email address associated with the account",
"examples": [
"tester@example.com"
]
}
},
"type": "object",
"required": [
"password",
"username"
],
"title": "LoginRequest",
"description": "Payload for authenticating a user and issuing a JWT.\n\nThis model is used to verify user credentials and request an access token.\n\nFields:\n username: Username identifier.\n password: Plain-text password to be verified.\n\nNotes:\n - Successful authentication results in a LoginResponse.\n - Failed authentication raises an AuthError.",
"example": {
"password": "SuperSecure123",
"username": "tester"
}
},
"LoginResponse": {
"properties": {
"access_token": {
"type": "string",
"title": "Access Token",
"description": "JWT access token to be used for authenticated requests",
"examples": [
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
]
},
"user": {
"$ref": "#/components/schemas/PublicUser",
"description": "Authenticated user's public profile"
}
},
"type": "object",
"required": [
"access_token",
"user"
],
"title": "LoginResponse",
"description": "Response returned after successful authentication.\n\nContains the issued JWT access token and the authenticated user's\npublic profile.\n\nFields:\n access_token: JWT access token for authenticated requests.\n user: Public profile of the authenticated user.\n\nNotes:\n - The token is stateless and must be stored client-side.\n - Token expiration and validation are handled elsewhere.",
"example": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"email": "tester@example.com",
"is_active": true,
"username": "tester"
}
}
},
"LogoutResponse": {
"properties": {
"message": {
"type": "string",
"title": "Message",
"description": "Human-readable logout confirmation message",
"examples": [
"Successfully logged out"
]
}
},
"type": "object",
"required": [
"message"
],
"title": "LogoutResponse",
"description": "Response returned after a logout operation.\n\nSince logout is stateless, this response serves only as a confirmation\nmessage instructing the client to discard its token.\n\nFields:\n message: Human-readable logout confirmation.",
"example": {
"message": "Successfully logged out"
}
},
"PublicUser": {
"properties": {
"is_active": {
"type": "boolean",
"title": "Is Active",
"description": "Indicates whether the account is active and allowed to authenticate",
"default": true,
"examples": [
true
]
},
"username": {
"type": "string",
"maxLength": 50,
"minLength": 3,
"title": "Username",
"description": "Unique username used for authentication and display",
"examples": [
"tester"
]
},
"email": {
"anyOf": [
{
"type": "string",
"format": "email"
},
{
"type": "null"
}
],
"title": "Email",
"description": "Primary email address associated with the account",
"examples": [
"tester@example.com"
]
}
},
"type": "object",
"required": [
"username"
],
"title": "PublicUser",
"description": "Public-facing user representation returned by authentication APIs.\n\nThis model represents a user profile that is safe to expose outside\nthe authentication system.\n\nFields:\n username: Unique username identifier.\n email: User's email address.\n is_active: Whether the user account is active.\n\nNotes:\n - Contains no sensitive data.\n - Can be constructed from persistence models via `from_attributes`.",
"example": {
"email": "tester@example.com",
"is_active": true,
"username": "tester"
}
},
"RegisterRequest": {
"properties": {
"password": {
"type": "string",
"minLength": 6,
"title": "Password",
"description": "User password (minimum 6 characters). Stored only as a hash.",
"examples": [
"SuperSecure123"
]
},
"username": {
"type": "string",
"maxLength": 50,
"minLength": 3,
"title": "Username",
"description": "Unique username used for authentication and display",
"examples": [
"tester"
]
},
"email": {
"anyOf": [
{
"type": "string",
"format": "email"
},
{
"type": "null"
}
],
"title": "Email",
"description": "Primary email address associated with the account",
"examples": [
"tester@example.com"
]
}
},
"type": "object",
"required": [
"password",
"username"
],
"title": "RegisterRequest",
"description": "Payload for registering a new user account.\n\nThis model contains the minimum required identity and credential\ninformation to create a new user.\n\nFields:\n username: Unique username identifier.\n email: User's email address.\n password: Plain-text password (to be hashed by the repository layer).\n\nNotes:\n - Validation and normalization handled by mixins.\n - This model is never returned in responses.",
"example": {
"email": "tester@example.com",
"password": "SuperSecure123",
"username": "tester"
}
},
"ValidationError": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
}
},
"securitySchemes": {
"HTTPBearer": {
"type": "http",
"scheme": "bearer"
}
}
},
"tags": [
{
"name": "Auth",
"description": "Login, registration, token issuance, refresh, and identity operations."
},
{
"name": "Health",
"description": "Service health and diagnostics."
}
]
}

View File

@@ -2,4 +2,6 @@ DEBUG=false
MONGO_URI=mongodb://localhost:27017 MONGO_URI=mongodb://localhost:27017
MONGO_DB=app_template MONGO_DB=app_template
WORKER_POLL_SECONDS=30 WORKER_POLL_SECONDS=30
WORKER_MAX_RETRIES=3 WORKER_MAX_RETRIES=3
AUTH_SERVER=http://localhost:8000
JWT_SERVER=http://localhost:8000

View File

@@ -0,0 +1,9 @@
"""# Summary
Auth interface: thin FastAPI entry points related to authentication, wired to
openapi.yaml operationIds by the OpenAPI-first router.
Authentication itself is provided by the shared auth-server service. Wire it by
declaring a ``bearerAuth`` security scheme in ``openapi.yaml`` (see
``security.yaml``) and reading the contract at ``docs://spec``.
"""

View File

@@ -0,0 +1,14 @@
security:
- bearerAuth: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
x-server-url: "{AUTH_SERVER}"
x-introspect-path: "/introspect"
x-login-path: "/login"
x-register-path: "/register"
x-logout-path: "/logout"
x-me-path: "/me"

View File

@@ -24,7 +24,7 @@ can read a library's full API by connecting to its MCP and reading
| Mail Intake | `mail_intake` | Provider-agnostic read-only email ingestion | Gmail OAuth + MIME parsing | `:8002` | | Mail Intake | `mail_intake` | Provider-agnostic read-only email ingestion | Gmail OAuth + MIME parsing | `:8002` |
| Py JWT | `jwtlib` | JWT auth primitives (register/login/introspect) | password hashing + token code | `:8005` | | Py JWT | `jwtlib` | JWT auth primitives (register/login/introspect) | password hashing + token code | `:8005` |
| Doc Forge | `docforge` | Docstring compiler → MkDocs + MCP docs | custom doc generators | `:8001` | | Doc Forge | `docforge` | Docstring compiler → MkDocs + MCP docs | custom doc generators | `:8001` |
| Auth Server | *(service)* | Central identity service (register/login/me/introspect) | building auth endpoints yourself | *(deferred)* | | Auth Server | *(service)* | Central identity service (register/login/me/logout/introspect) | building auth endpoints yourself | spec: `docs://spec` |
## How to choose ## How to choose
@@ -43,7 +43,9 @@ can read a library's full API by connecting to its MCP and reading
A single **standards MCP** exposes `find_library(capability)`, which returns the A single **standards MCP** exposes `find_library(capability)`, which returns the
matching library, its import name, its "use instead of" guidance, and its MCP matching library, its import name, its "use instead of" guidance, and its MCP
endpoint. Query it before writing any infrastructure. endpoint (or, for the auth-server service, a spec pointer). It also serves the
pinned auth-server OpenAPI contract at `docs://spec`. Query it before writing
any infrastructure.
## Notes ## Notes
@@ -51,7 +53,10 @@ endpoint. Query it before writing any infrastructure.
`docs://index`, `docs://nav`, and `docs://modules/{module}` (e.g. `docs://index`, `docs://nav`, and `docs://modules/{module}` (e.g.
`docs://modules/hexa.container`) to discover the DI surface — `build(cls, *, `docs://modules/hexa.container`) to discover the DI surface — `build(cls, *,
instances, config)` and the `PortNode` model. instances, config)` and the `PortNode` model.
- **Auth Server** is deferred: it has no `pyproject.toml`/importable package and - **Auth Server** is a **service**, not an importable library: it has no MCP
no MCP bundle. Do not reach for it yet; `py-jwt` covers token primitives. bundle by design. Read its client contract from the pinned OpenAPI document
via `docs://spec` and wire it by declaring a `bearerAuth` security scheme in
the app's own spec (see **Auth Integration**); `py-jwt` covers the
low-level token path.
- Library MCP servers expose **documentation resources** (not executable tools): - Library MCP servers expose **documentation resources** (not executable tools):
they tell an agent the available API surface so it does not invent code. they tell an agent the available API surface so it does not invent code.

View File

@@ -0,0 +1,140 @@
# Auth Integration
## Summary
Authentication is a **service**, not a library. Every Aetos application shares a
single identity service — the **auth-server** — and consumes it through its HTTP
contract. An app never implements registration, login, password hashing, or
token issuance itself.
The agent does **not** need an MCP bundle for the auth-server. It reads the
pinned OpenAPI contract (below) and declares a bearer security scheme in its own
spec; `openapi_first` then generates the verifying dependency. **No auth code is
written.**
---
## Read the contract
The auth-server OpenAPI document is pinned in the standards bundle and served as
an MCP resource:
```
docs://spec
```
It is the authoritative client contract (OpenAPI 3.1). Read it to learn the
endpoints and the request/response schemas:
| Endpoint | Purpose |
|---|---|
| `POST /register` | Create a user (201, returns `PublicUser`) |
| `POST /login` | Authenticate and issue a JWT (`LoginResponse`) |
| `GET /me` | Current user's `PublicUser` (bearer required) |
| `POST /logout` | Stateless logout (`LogoutResponse`) |
| `POST /introspect` | Internal token introspection (`active`, `user`) |
The pinned snapshot is versioned (currently `0.0.5`). If the caller has a live
auth-server and needs the *current* contract, they can pass the full endpoint
(`{AUTH_SERVER}/openapi.json`) and read it directly instead of the pin.
---
## Wire it up: declare, don't code
Authentication is added by editing the app's **own** `openapi.yaml` — nothing
else. Declare a global bearer requirement and point the scheme at the auth
server (values come from the environment):
```yaml
security:
- bearerAuth: []
paths:
# ...
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
x-server-url: "{AUTH_SERVER}"
x-introspect-path: "/introspect"
x-login-path: "/login"
x-register-path: "/register"
x-logout-path: "/logout"
x-me-path: "/me"
```
`openapi_first` reads `securitySchemes` + `security` and generates a FastAPI
dependency that extracts the bearer token, POSTs `{"token": <jwt>}` to
`{AUTH_SERVER}/introspect`, and returns the user when `active == true`. It
raises `401` on an invalid or inactive token and `503` when the auth server is
unreachable. The generated handlers receive the authenticated user — you author
**no** authentication code.
### Public operations
A route that must stay open opts out by overriding `security` with an empty
list:
```yaml
paths:
/health:
get:
operationId: health
security: [] # overrides the global requirement
responses:
"200":
description: OK
```
### Environment
```bash
AUTH_SERVER=http://localhost:8000 # auth-server base URL
JWT_SERVER=http://localhost:8000 # jwtlib client base URL (seeding/CLI)
```
---
## Path 1: `jwtlib` client (no generator)
Apps that do not use a generated dependency verify requests with the `py-jwt`
client:
```python
from jwtlib.introspection import authenticate_request
authorized = await authenticate_request(
should_skip_authentication, # allow public paths
method,
path,
authorization_token, # "Bearer <jwt>" or None
)
```
`authenticate_request` POSTs to `{JWT_SERVER}/introspect` (3 second timeout) and
treats `{"active": true, "user": {...}}` as valid. Use this for seeding scripts
and tests (`jwtlib.register_user`, `jwtlib.login_user`).
---
## Golden rules
1. **Never trust an unverified token.** Decode through the generated dependency
or `jwtlib` before handling the request.
2. **Treat `active: false` as unauthenticated**, even if the JWT decodes.
3. **Do not implement your own JWT parsing.** Revocation and issuer changes must
stay centralized in the auth-server.
4. **Do not add auth until the app's outcome is validated.** Ship the app
authless first; layer the security scheme once the core behaviour works.
---
## Notes
- The default template is **authless** on purpose. Add the `securitySchemes`
block above only when auth is required. Pull the shared slice with
`get_app_component("auth")`.
- The auth-server is deployed as a service (no importable package); the client
surface is the spec, plus `jwtlib` for the low-level path.

View File

@@ -15,6 +15,8 @@ patterns, and the internal library catalog that agents consult while coding.
- **Human-in-the-Loop** — guardrails and approvals inside automations. - **Human-in-the-Loop** — guardrails and approvals inside automations.
- **Infra Adapters** — how infrastructure connects without leaking into domain. - **Infra Adapters** — how infrastructure connects without leaking into domain.
- **Genericity Matrix** — what is library-backed, template-provided, or authored. - **Genericity Matrix** — what is library-backed, template-provided, or authored.
- **Auth Integration** — consuming the shared auth-server service (spec-driven,
no auth code).
- **Library Registry** — the catalog of internal libraries behind all of it. - **Library Registry** — the catalog of internal libraries behind all of it.
Read the **Wiki** for the philosophy and rationale; these pages are the Read the **Wiki** for the philosophy and rationale; these pages are the

View File

@@ -64,4 +64,5 @@ nav:
- Human-in-the-Loop: 08_human_in_loop.md - Human-in-the-Loop: 08_human_in_loop.md
- Infra Adapters: 09_infra_adaptors.md - Infra Adapters: 09_infra_adaptors.md
- Genericity Matrix: 12_genericity_matrix.md - Genericity Matrix: 12_genericity_matrix.md
- Auth Integration: 14_auth_integration.md
- Library Registry: 13_library_registry.md - Library Registry: 13_library_registry.md

View File

@@ -2,4 +2,6 @@ DEBUG=false
MONGO_URI=mongodb://localhost:27017 MONGO_URI=mongodb://localhost:27017
MONGO_DB=app_template MONGO_DB=app_template
WORKER_POLL_SECONDS=30 WORKER_POLL_SECONDS=30
WORKER_MAX_RETRIES=3 WORKER_MAX_RETRIES=3
AUTH_SERVER=http://localhost:8000
JWT_SERVER=http://localhost:8000

View File

@@ -0,0 +1,9 @@
"""# Summary
Auth interface: thin FastAPI entry points related to authentication, wired to
openapi.yaml operationIds by the OpenAPI-first router.
Authentication itself is provided by the shared auth-server service. Wire it by
declaring a ``bearerAuth`` security scheme in ``openapi.yaml`` (see
``security.yaml``) and reading the contract at ``docs://spec``.
"""

View File

@@ -0,0 +1,14 @@
security:
- bearerAuth: []
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
x-server-url: "{AUTH_SERVER}"
x-introspect-path: "/introspect"
x-login-path: "/login"
x-register-path: "/register"
x-logout-path: "/logout"
x-me-path: "/me"

Binary file not shown.