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.
This commit is contained in:
parent
2ec7761504
commit
01da195c13
51 changed files with 2215 additions and 41 deletions
93
coulomb_social/apps/identity/user_engine.py
Normal file
93
coulomb_social/apps/identity/user_engine.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
"""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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue