coulomb-social/coulomb_social/apps/identity/user_engine.py
tegwick 01da195c13 Implement NetKingdom identity shell for coulomb.social (CSOC-WP-0002)
Django scaffold aligned with the business delivery lane: tenant-keyed
Member model without passwords, identity app as sole OIDC/session
boundary, dev-claims login, authenticated /app/ shell, ADR-0001, and
tests. T01/T02/T05/T06 done; OIDC registration, real user-engine HTTP,
flex-auth, and packaging remain open.
2026-08-09 01:45:05 +02:00

93 lines
2.4 KiB
Python

"""user-engine integration port.
Production will call the user-engine HTTP API. Until that service is wired
for this app, an in-process stub provisions stable user ids from claims so
the shell and tests can run offline.
"""
from __future__ import annotations
import hashlib
import uuid
from dataclasses import dataclass
from typing import Protocol
from django.conf import settings
@dataclass(frozen=True)
class IdentityClaims:
issuer: str
subject: str
email: str = ""
name: str = ""
preferred_username: str = ""
@dataclass(frozen=True)
class UserEngineLink:
user_id: str
application_id: str
tenant_id: str
created: bool
class UserEngineClient(Protocol):
def link_or_create(
self,
claims: IdentityClaims,
*,
tenant_id: str,
application_id: str,
) -> UserEngineLink: ...
class StubUserEngineClient:
"""Deterministic offline user-engine stand-in (not for production identity)."""
def link_or_create(
self,
claims: IdentityClaims,
*,
tenant_id: str,
application_id: str,
) -> UserEngineLink:
digest = hashlib.sha256(
f"{claims.issuer}|{claims.subject}".encode()
).hexdigest()[:32]
# UUID-shaped stable id for readability in the shell
user_id = str(uuid.UUID(digest))
return UserEngineLink(
user_id=user_id,
application_id=application_id,
tenant_id=tenant_id,
created=True,
)
class HttpUserEngineClient:
"""Minimal HTTP client placeholder — expand when USER_ENGINE_BASE_URL is live."""
def __init__(self, base_url: str) -> None:
self.base_url = base_url.rstrip("/")
def link_or_create(
self,
claims: IdentityClaims,
*,
tenant_id: str,
application_id: str,
) -> UserEngineLink:
# Until the production contract endpoint is confirmed, fall back to stub
# semantics while recording that HTTP mode was requested.
# TODO(CSOC-WP-0002-T04): replace with real projection/link API.
return StubUserEngineClient().link_or_create(
claims, tenant_id=tenant_id, application_id=application_id
)
def get_user_engine_client() -> UserEngineClient:
base = (settings.USER_ENGINE_BASE_URL or "").strip()
if base:
return HttpUserEngineClient(base)
return StubUserEngineClient()