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
85
docs/adr/ADR-0001-netkingdom-identity.md
Normal file
85
docs/adr/ADR-0001-netkingdom-identity.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
id: ADR-0001
|
||||
title: NetKingdom identity for coulomb.social
|
||||
status: accepted
|
||||
date: 2026-08-09
|
||||
workplan: CSOC-WP-0002
|
||||
task: CSOC-WP-0002-T01
|
||||
---
|
||||
|
||||
# ADR-0001 — NetKingdom identity for coulomb.social
|
||||
|
||||
## Status
|
||||
|
||||
Accepted (2026-08-09).
|
||||
|
||||
## Context
|
||||
|
||||
coulomb.social is being reestablished as a standalone business application
|
||||
(DR-2 B) on the Railiance delivery lane. The business-app service contract
|
||||
v0.1 defaults to **app-local accounts** (DR-3 A) with a bounded auth module
|
||||
for a later OIDC switch.
|
||||
|
||||
A social product with many members is a poor fit for a private password
|
||||
database inside the app: credential lifecycle, MFA, and user-domain profiles
|
||||
already exist (or are governed) in the NetKingdom landscape.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **Authentication** uses only the **NetKingdom IAM Profile** (OIDC + PKCE).
|
||||
The app depends on the profile contract, not on KeyCape vs Keycloak
|
||||
implementation details.
|
||||
|
||||
2. **User-domain facts** (account, profile, memberships, identity links) are
|
||||
owned by **user-engine**. This app maps `(issuer, sub)` to a local
|
||||
`Member` row for session convenience and never stores passwords.
|
||||
|
||||
3. **Authorization** for protected actions is decided by **flex-auth** (PDP).
|
||||
The app is a PEP only (fail-closed when the PDP is unavailable for
|
||||
sensitive actions). Dev may use a local deny-by-default stub until the
|
||||
protected system is registered.
|
||||
|
||||
4. **Browser session** after successful OIDC is an application session
|
||||
(Django session / signed cookie). Tokens are not treated as long-lived
|
||||
API credentials in the browser.
|
||||
|
||||
5. **Tenant model**: all client-owned data is keyed by `tenant_id` from the
|
||||
first migration (Binky Hedgehog = client #1 / reference tenant).
|
||||
|
||||
6. **Relationship to DR-3 A**: the contract’s “app-local baseline” remains
|
||||
valid for simple single-tenant tools (e.g. vergabe-teilnahme v1). This ADR
|
||||
**specializes** coulomb.social onto NetKingdom identity as a product
|
||||
decision; the auth code still lives in one module (§2.3) so issuer mode
|
||||
can change without rewriting the product.
|
||||
|
||||
### Issuer modes
|
||||
|
||||
| Mode | When |
|
||||
|------|------|
|
||||
| Dev mock claims (`DEBUG` + `OIDC_ENABLED=false`) | Local unit/integration tests and offline shell work |
|
||||
| local-identity / KeyCape | Bootstrap and lightweight platform environments |
|
||||
| Keycloak expanded | Production / enterprise MFA & federation when platform provides it |
|
||||
|
||||
### Non-goals
|
||||
|
||||
- App-local password / MFA store
|
||||
- Bubble as identity provider
|
||||
- Content migration from Bubble in this identity track
|
||||
- Owning KeyCape, Keycloak, user-engine, or flex-auth codebases
|
||||
|
||||
## Consequences
|
||||
|
||||
- Scaffold uses Django aligned with the business delivery lane, with a
|
||||
dedicated `identity` app as the sole OIDC/session boundary.
|
||||
- OpenBao (or local 0600 files) holds OIDC client secrets; nothing secret
|
||||
in Git.
|
||||
- Content re-claim is a separate workplan after the authenticated shell
|
||||
works.
|
||||
|
||||
## References
|
||||
|
||||
- `workplans/CSOC-WP-0002-netkingdom-user-management-reestablish.md`
|
||||
- `net-kingdom/canon/standards/iam-profile_v0.2.md` (and v0.3)
|
||||
- `net-kingdom/canon/standards/user-engine-boundary-contract_v0.1.md`
|
||||
- `the-custodian/canon/standards/business-app-service-contract_v0.1.md`
|
||||
- `the-custodian/docs/business-platform-decision-records.md` (DR-2, DR-3)
|
||||
25
docs/deploy.md
Normal file
25
docs/deploy.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Deploy notes (stub — CSOC-WP-0002-T08)
|
||||
|
||||
## Shape
|
||||
|
||||
Standalone service: commit-SHA images → registry
|
||||
`gitea.coulomb.social/coulomb/coulomb-social` → `railiance-apps` values →
|
||||
railiance01 (same lane as `vergabe-teilnahme`).
|
||||
|
||||
## Runtime secrets (names only)
|
||||
|
||||
| Secret | Consumer |
|
||||
|--------|----------|
|
||||
| `SECRET_KEY` | Django |
|
||||
| `DATABASE_URL` | Django |
|
||||
| `OIDC_CLIENT_SECRET` | identity app |
|
||||
| future DB password | OpenBao dynamic/KV |
|
||||
|
||||
## Health
|
||||
|
||||
- `GET /healthz` → `{"status":"ok"}`
|
||||
|
||||
## Status
|
||||
|
||||
Container packaging and railiance-apps values are **not** finished; local
|
||||
`runserver` / tests are the current verification path.
|
||||
43
docs/dev.md
Normal file
43
docs/dev.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Local development
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.12+
|
||||
- [`uv`](https://github.com/astral-sh/uv)
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
cd ~/coulomb-social
|
||||
uv sync
|
||||
uv run manage.py migrate
|
||||
uv run manage.py runserver 0.0.0.0:8008
|
||||
```
|
||||
|
||||
Open http://127.0.0.1:8008/ — **Sign in** uses dev claims when
|
||||
`OIDC_ENABLED=false` (default) and `DEBUG=true`.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
make test
|
||||
# or
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `SECRET_KEY` | insecure dev default | Django secret |
|
||||
| `DATABASE_URL` | sqlite `./db.sqlite3` | DB |
|
||||
| `DEFAULT_TENANT_ID` | `binky` | Client #1 tenant key |
|
||||
| `OIDC_ENABLED` | `false` | Use real NetKingdom issuer |
|
||||
| `OIDC_ISSUER` | | Issuer base URL |
|
||||
| `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` | | OIDC client (secret from OpenBao in prod) |
|
||||
| `OIDC_REDIRECT_URI` | | e.g. `http://127.0.0.1:8008/auth/callback/` |
|
||||
| `USER_ENGINE_BASE_URL` | empty (stub) | user-engine HTTP |
|
||||
| `USER_ENGINE_APPLICATION_ID` | `coulomb-social` | App id in user-engine |
|
||||
| `FLEX_AUTH_BASE_URL` | empty (fail-closed except shell:view) | PDP |
|
||||
|
||||
See `docs/adr/ADR-0001-netkingdom-identity.md` and `docs/identity/`.
|
||||
25
docs/identity/oidc-client.md
Normal file
25
docs/identity/oidc-client.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# OIDC client binding (coulomb.social)
|
||||
|
||||
Non-secret registration checklist for NetKingdom IAM Profile issuers.
|
||||
|
||||
| Field | Value / notes |
|
||||
|-------|----------------|
|
||||
| Application | coulomb.social |
|
||||
| Profile | NetKingdom IAM Profile (PKCE required) |
|
||||
| client_id | set per environment (`OIDC_CLIENT_ID`) |
|
||||
| client_secret | OpenBao / local 0600 only — never Git |
|
||||
| redirect_uri (dev) | `http://127.0.0.1:8008/auth/callback/` |
|
||||
| redirect_uri (prod) | `https://coulomb.social/auth/callback/` (confirm at deploy) |
|
||||
| scopes | `openid profile email` (+ profile claims as issuer provides) |
|
||||
| grant | authorization_code + PKCE S256 |
|
||||
| wildcard redirects | **forbidden** (KeyCape constraint) |
|
||||
|
||||
## Status
|
||||
|
||||
| Environment | Issuer mode | Registered |
|
||||
|-------------|-------------|------------|
|
||||
| local | dev claims (`OIDC_ENABLED=false`) | n/a |
|
||||
| platform | KeyCape / local-identity / Keycloak | pending operator registration (T03) |
|
||||
|
||||
When registering, record the live client_id here (not the secret) and the
|
||||
issuer base URL in deploy notes.
|
||||
23
docs/identity/smoke.md
Normal file
23
docs/identity/smoke.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Identity smoke checklist
|
||||
|
||||
## Offline (dev claims)
|
||||
|
||||
1. `uv sync && uv run manage.py migrate && uv run manage.py runserver 8008`
|
||||
2. Open `/` → **Sign in**
|
||||
3. Dev form → submit subject `smoke-1`
|
||||
4. Land on `/app/` with display name and subject shown
|
||||
5. **Sign out** → back to landing; `/app/` redirects to login
|
||||
6. Sign in again with same subject → single `Member` row (idempotent)
|
||||
|
||||
## With platform OIDC
|
||||
|
||||
1. Set `OIDC_ENABLED=true` and issuer/client/redirect env vars
|
||||
2. Register redirect URI at the issuer (no wildcards)
|
||||
3. `/auth/login/` redirects to IdP; callback creates/links Member
|
||||
4. Logout clears app session
|
||||
|
||||
## Automated
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
23
docs/identity/user-engine-binding.md
Normal file
23
docs/identity/user-engine-binding.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# user-engine application binding
|
||||
|
||||
| Field | Value |
|
||||
|-------|--------|
|
||||
| application id | `coulomb-social` (`USER_ENGINE_APPLICATION_ID`) |
|
||||
| display name | coulomb.social |
|
||||
| tenant (v1) | `binky` (client #1) |
|
||||
| identity link | `(issuer, sub)` → `user_engine_user_id` on first login |
|
||||
| local row | `members.Member` (no passwords) |
|
||||
|
||||
## Runtime
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `USER_ENGINE_BASE_URL` empty | `StubUserEngineClient` — deterministic user id from issuer+sub hash |
|
||||
| URL set | `HttpUserEngineClient` placeholder (T04: wire real link/projection API) |
|
||||
|
||||
## Profile attributes (shell only)
|
||||
|
||||
- display name (from OIDC `name` / `preferred_username`)
|
||||
- email (optional)
|
||||
|
||||
Bubble content fields are **not** part of this binding.
|
||||
Loading…
Add table
Add a link
Reference in a new issue