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:
2
build.py
2
build.py
@@ -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",
|
||||||
|
|||||||
27
registry.py
27
registry.py
@@ -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()
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
971
standards/lib/14_auth_integration/index.html
Normal file
971
standards/lib/14_auth_integration/index.html
Normal 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'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'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">"{AUTH_SERVER}"</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">"/introspect"</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">"/login"</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">"/register"</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">"/logout"</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">"/me"</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": <jwt>}</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">"200"</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"># "Bearer <jwt>" 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>
|
||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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.
@@ -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",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"project": "standards",
|
"project": "standards",
|
||||||
"type": "standards",
|
"type": "standards",
|
||||||
"modules_count": 12,
|
"modules_count": 13,
|
||||||
"source": "hand-authored"
|
"source": "hand-authored"
|
||||||
}
|
}
|
||||||
@@ -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" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
8
standards/mcp/modules/auth_integration.json
Normal file
8
standards/mcp/modules/auth_integration.json
Normal 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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
563
standards/mcp/specs/auth-server.json
Normal file
563
standards/mcp/specs/auth-server.json
Normal 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."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
9
standards/mcp/templates/app/interfaces/auth/__init__.py
Normal file
9
standards/mcp/templates/app/interfaces/auth/__init__.py
Normal 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``.
|
||||||
|
"""
|
||||||
14
standards/mcp/templates/app/interfaces/auth/security.yaml
Normal file
14
standards/mcp/templates/app/interfaces/auth/security.yaml
Normal 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"
|
||||||
@@ -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.
|
||||||
|
|||||||
140
standards/src/lib/14_auth_integration.md
Normal file
140
standards/src/lib/14_auth_integration.md
Normal 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.
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
9
standards/src/templates/app/interfaces/auth/__init__.py
Normal file
9
standards/src/templates/app/interfaces/auth/__init__.py
Normal 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``.
|
||||||
|
"""
|
||||||
14
standards/src/templates/app/interfaces/auth/security.yaml
Normal file
14
standards/src/templates/app/interfaces/auth/security.yaml
Normal 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.
Reference in New Issue
Block a user