tba-app-backend-tenant-administration 0.4.0.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.4
2
+ Name: tba-app-backend-tenant-administration
3
+ Version: 0.4.0.dev2
4
+ Summary: Tenant-scoped administration backend application for TBA.
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.10,<3.15
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Dist: tba-app-backend-core (==0.4.0.dev2)
14
+ Requires-Dist: tba-auth (==0.4.0.dev2)
15
+ Requires-Dist: tba-router-administration-gate (==0.4.0.dev2)
16
+ Project-URL: Homepage, https://github.com/tigrbl/tigrbl_auth
17
+ Project-URL: Repository, https://github.com/tigrbl/tigrbl_auth
18
+ Project-URL: Source, https://github.com/tigrbl/tigrbl_auth/tree/master/pkgs/90-backend-apps/tba-app-backend-tenant-administration
19
+ Description-Content-Type: text/markdown
20
+
21
+ # tba-app-backend-tenant-administration
22
+
23
+ Tenant-scoped identity, admin delegation, JWKS, and local policy API front door.
24
+
25
+ ## Surface
26
+
27
+ | Property | Value |
28
+ | --- | --- |
29
+ | Product surface | `tenant-admin-app` |
30
+ | Intended UIX | `@tba/ui-tenant-administration` |
31
+ | Exposes | Tenant identities, clients, consents, sessions, tenant keys/JWKS, tenant-local audit |
32
+ | Excludes | Platform tenant creation/deletion and cross-tenant authority assignment |
33
+
34
+ ## Entrypoint
35
+
36
+ ```python
37
+ from tba_app_backend_tenant_administration import app, build_app
38
+ ```
39
+
40
+ ## Local Dev Deployment
41
+
42
+ The Docker dev surface listens on `http://localhost:8016` and uses the
43
+ admin API key `dev-tenant-admin-key` unless overridden.
44
+
45
+ ```bash
46
+ docker compose -f docker/docker-compose.tenant-admin-app.yml up -d --build
47
+ ```
48
+
49
+ Useful checks:
50
+
51
+ - `GET /openapi.json`
52
+ - `GET /openrpc.json`
53
+ - `POST /rpc` with `X-API-Key: dev-tenant-admin-key`
54
+ - `GET /user`, `/client`, `/clientregistration`, `/consent`
55
+
56
+ The tenant-admin surface intentionally does not expose `/tenant`, public
57
+ OAuth/OIDC routes, platform tenant lifecycle RPC, or service/workload admin
58
+ resources.
59
+
@@ -0,0 +1,38 @@
1
+ # tba-app-backend-tenant-administration
2
+
3
+ Tenant-scoped identity, admin delegation, JWKS, and local policy API front door.
4
+
5
+ ## Surface
6
+
7
+ | Property | Value |
8
+ | --- | --- |
9
+ | Product surface | `tenant-admin-app` |
10
+ | Intended UIX | `@tba/ui-tenant-administration` |
11
+ | Exposes | Tenant identities, clients, consents, sessions, tenant keys/JWKS, tenant-local audit |
12
+ | Excludes | Platform tenant creation/deletion and cross-tenant authority assignment |
13
+
14
+ ## Entrypoint
15
+
16
+ ```python
17
+ from tba_app_backend_tenant_administration import app, build_app
18
+ ```
19
+
20
+ ## Local Dev Deployment
21
+
22
+ The Docker dev surface listens on `http://localhost:8016` and uses the
23
+ admin API key `dev-tenant-admin-key` unless overridden.
24
+
25
+ ```bash
26
+ docker compose -f docker/docker-compose.tenant-admin-app.yml up -d --build
27
+ ```
28
+
29
+ Useful checks:
30
+
31
+ - `GET /openapi.json`
32
+ - `GET /openrpc.json`
33
+ - `POST /rpc` with `X-API-Key: dev-tenant-admin-key`
34
+ - `GET /user`, `/client`, `/clientregistration`, `/consent`
35
+
36
+ The tenant-admin surface intentionally does not expose `/tenant`, public
37
+ OAuth/OIDC routes, platform tenant lifecycle RPC, or service/workload admin
38
+ resources.
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "tba-app-backend-tenant-administration"
3
+ version = "0.4.0.dev2"
4
+ description = "Tenant-scoped administration backend application for TBA."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10,<3.15"
7
+ license = "Apache-2.0"
8
+ dependencies = [
9
+ "tba-auth==0.4.0.dev2",
10
+ "tba-app-backend-core==0.4.0.dev2",
11
+ "tba-router-administration-gate==0.4.0.dev2",
12
+ ]
13
+
14
+ [project.urls]
15
+ Homepage = "https://github.com/tigrbl/tigrbl_auth"
16
+ Repository = "https://github.com/tigrbl/tigrbl_auth"
17
+ Source = "https://github.com/tigrbl/tigrbl_auth/tree/master/pkgs/90-backend-apps/tba-app-backend-tenant-administration"
18
+
19
+ [build-system]
20
+ requires = ["poetry-core>=1.0.0"]
21
+ build-backend = "poetry.core.masonry.api"
22
+
23
+ [tool.poetry]
24
+ packages = [{ include = "tba_app_backend_tenant_administration", from = "src" }]
@@ -0,0 +1,14 @@
1
+ """Tenant admin backend application package for TBA."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .app import PRODUCT_SURFACE, app, build_app
6
+ from .contract import TENANT_ADMIN_BACKEND_APP_CONTRACT, TenantAdminBackendAppContract
7
+
8
+ __all__ = [
9
+ "PRODUCT_SURFACE",
10
+ "TENANT_ADMIN_BACKEND_APP_CONTRACT",
11
+ "TenantAdminBackendAppContract",
12
+ "app",
13
+ "build_app",
14
+ ]
@@ -0,0 +1,130 @@
1
+ """ASGI entrypoint for the tenant admin TBA backend application."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable
7
+
8
+ from tba_app_backend_core import build_product_app
9
+ from tba_app_backend_tenant_administration.contract import (
10
+ TENANT_ADMIN_BACKEND_APP_CONTRACT,
11
+ )
12
+ from tba_auth.runtime import LazyASGIApplication
13
+
14
+ if TYPE_CHECKING:
15
+ from tigrbl import TigrblApp
16
+
17
+ PRODUCT_SURFACE = "tenant-admin-app"
18
+ DEFAULT_CORS_ORIGINS = ("http://localhost:3012", "http://127.0.0.1:3012")
19
+
20
+
21
+ class TenantAdminCors:
22
+ """Small ASGI CORS wrapper for the standalone tenant admin UIX demo."""
23
+
24
+ def __init__(
25
+ self, app: Any, origins: tuple[str, ...] = DEFAULT_CORS_ORIGINS
26
+ ) -> None:
27
+ self.app = app
28
+ self.origins = origins
29
+
30
+ def __getattr__(self, name: str) -> Any:
31
+ return getattr(self.app, name)
32
+
33
+ async def __call__(
34
+ self,
35
+ scope: dict[str, Any],
36
+ receive: Callable[[], Awaitable[dict[str, Any]]],
37
+ send: Callable[[dict[str, Any]], Awaitable[None]],
38
+ ) -> None:
39
+ if scope.get("type") != "http":
40
+ await self.app(scope, receive, send)
41
+ return
42
+
43
+ headers = {
44
+ key.decode("latin-1").lower(): value.decode("latin-1")
45
+ for key, value in scope.get("headers", [])
46
+ }
47
+ origin = headers.get("origin")
48
+ allowed_origin = origin if origin in self.origins else None
49
+
50
+ if str(scope.get("method", "")).upper() == "OPTIONS" and allowed_origin:
51
+ await send(
52
+ {
53
+ "type": "http.response.start",
54
+ "status": 204,
55
+ "headers": self._cors_headers(allowed_origin)
56
+ + [
57
+ (
58
+ b"access-control-allow-methods",
59
+ b"GET,POST,PATCH,DELETE,OPTIONS",
60
+ ),
61
+ (
62
+ b"access-control-allow-headers",
63
+ headers.get(
64
+ "access-control-request-headers",
65
+ "content-type,authorization,x-api-key",
66
+ ).encode("latin-1"),
67
+ ),
68
+ (b"content-length", b"0"),
69
+ ],
70
+ }
71
+ )
72
+ await send({"type": "http.response.body", "body": b""})
73
+ return
74
+
75
+ async def cors_send(message: dict[str, Any]) -> None:
76
+ if message.get("type") == "http.response.start" and allowed_origin:
77
+ message = dict(message)
78
+ response_headers = list(message.get("headers", []))
79
+ response_headers.extend(self._cors_headers(allowed_origin))
80
+ message["headers"] = response_headers
81
+ await send(message)
82
+
83
+ await self.app(scope, receive, cors_send)
84
+
85
+ @staticmethod
86
+ def _cors_headers(origin: str) -> list[tuple[bytes, bytes]]:
87
+ return [
88
+ (b"access-control-allow-origin", origin.encode("latin-1")),
89
+ (b"access-control-allow-credentials", b"true"),
90
+ (b"vary", b"Origin"),
91
+ ]
92
+
93
+
94
+ def _default_settings() -> object:
95
+ from tba_auth.config.settings import Settings
96
+
97
+ settings = Settings()
98
+ env_overrides = {
99
+ "issuer": os.environ.get("AUTHN_ISSUER"),
100
+ "protected_resource_identifier": os.environ.get(
101
+ "TBA_AUTH_PROTECTED_RESOURCE_IDENTIFIER"
102
+ ),
103
+ "deployment_profile": os.environ.get("TBA_AUTH_PROFILE"),
104
+ "require_tls": os.environ.get("TBA_AUTH_REQUIRE_TLS"),
105
+ "admin_api_key": os.environ.get("TBA_AUTH_ADMIN_API_KEY"),
106
+ "admin_api_key_dir": os.environ.get("TBA_AUTH_ADMIN_API_KEY_DIR"),
107
+ }
108
+ for name, value in env_overrides.items():
109
+ if value in {None, ""}:
110
+ continue
111
+ current = getattr(settings, name, None)
112
+ if isinstance(current, bool):
113
+ value = str(value).lower() in {"1", "true", "yes", "on"}
114
+ setattr(settings, name, value)
115
+ return settings
116
+
117
+
118
+ def build_app(settings_obj: object | None = None) -> "TigrblApp":
119
+ if settings_obj is None:
120
+ settings_obj = _default_settings()
121
+ return TenantAdminCors(
122
+ build_product_app(
123
+ PRODUCT_SURFACE, settings_obj, contract=TENANT_ADMIN_BACKEND_APP_CONTRACT
124
+ )
125
+ )
126
+
127
+
128
+ app = LazyASGIApplication(build_app)
129
+
130
+ __all__ = ["PRODUCT_SURFACE", "app", "build_app"]
@@ -0,0 +1,73 @@
1
+ """Tenant-admin backend-app contract for TBA."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True, slots=True)
9
+ class TenantAdminBackendAppContract:
10
+ """Machine-readable boundary for the tenant control-plane backend app."""
11
+
12
+ product_surface: str
13
+ intended_uix: str
14
+ admin_resources: tuple[str, ...]
15
+ admin_rest_groups: tuple[str, ...]
16
+ forbidden_route_prefixes: tuple[str, ...]
17
+ forbidden_exact_routes: tuple[str, ...]
18
+ consumed_packages: tuple[str, ...]
19
+ mounted_router_packages: tuple[str, ...]
20
+ app_local_route_groups: tuple[str, ...]
21
+
22
+
23
+ TENANT_ADMIN_BACKEND_APP_CONTRACT = TenantAdminBackendAppContract(
24
+ product_surface="tenant-admin-app",
25
+ intended_uix="@tba/ui-tenant-administration",
26
+ admin_resources=(
27
+ "User",
28
+ "Client",
29
+ "ClientRegistration",
30
+ "Consent",
31
+ "AuditEvent",
32
+ "CryptoKey",
33
+ "CryptoKeyVersion",
34
+ "PrincipalKeyBinding",
35
+ "KeyEnvelope",
36
+ "KeyAttestationEvidence",
37
+ "KeyRotationEvent",
38
+ ),
39
+ admin_rest_groups=("admin_auth", "admin_identities"),
40
+ forbidden_route_prefixes=(
41
+ "/tenant",
42
+ "/serviceidentity",
43
+ "/credentialservicekey",
44
+ "/credentialapikey",
45
+ "/tokenrecord",
46
+ "/revokedtoken",
47
+ "/authsession",
48
+ ),
49
+ forbidden_exact_routes=(
50
+ "/login",
51
+ "/authorize",
52
+ "/token",
53
+ "/register",
54
+ "/logout",
55
+ "/revoke",
56
+ "/userinfo",
57
+ "/introspect",
58
+ "/.well-known/jwks.json",
59
+ "/.well-known/openid-configuration",
60
+ ),
61
+ consumed_packages=(
62
+ "tba-auth",
63
+ "tba-server-identity",
64
+ "tba-runtime-identity",
65
+ "tba-runtime-identity-administration",
66
+ "tba-concrete-identity-principals",
67
+ "tba-runtime-authorization-policy",
68
+ ),
69
+ mounted_router_packages=("tba-router-administration-gate",),
70
+ app_local_route_groups=(),
71
+ )
72
+
73
+ __all__ = ["TENANT_ADMIN_BACKEND_APP_CONTRACT", "TenantAdminBackendAppContract"]