diff --git a/.planning/phases/19.4-oidc-provider-on-wristband/19.4-02-SUMMARY.md b/.planning/phases/19.4-oidc-provider-on-wristband/19.4-02-SUMMARY.md new file mode 100644 index 00000000..381e644a --- /dev/null +++ b/.planning/phases/19.4-oidc-provider-on-wristband/19.4-02-SUMMARY.md @@ -0,0 +1,333 @@ +--- +phase: 19.4-oidc-provider-on-wristband +plan: 02 +subsystem: auth +tags: [oidc, oauth, wristband, plugin, gorm, postgres, keyring, rs256, es256, consent, branding, throttle, mcp, go-oidc] + +requires: + - phase: 19.4-01 + provides: wristband OIDC flavour (NewOIDCServer, SetOIDCBackend, OIDCBackend/OIDCTx, KeyStore, OIDCHost, ConsentRequest/DecideConsent, kinds) + - phase: 19.3 + provides: bouncer cookie transport (LoadCookieConfig, ForgetTokenCookie) used by EndRequestSession +provides: + - new repository sm-oauthserver-plugin (module git.golem15.com/golem15/sm-oauthserver-plugin, plugin golem15.oauthserver, requires golem15.user) + - golem15_oauthserver_* tables (PHP names and columns) plus _keys, _refresh_tokens, _settings, _mcp_codes + - classes.Store (wristband.OIDCBackend) and classes.MCPStore (wristband.Backend) over the same tables, filtered by kind in SQL + - classes.KeyRing (wristband.KeyStore) with encrypted private keys, lazy race-safe first key, Rotate and Prune for plan 03's commands + - branded consent and error pages, redirect consent mode with JSON endpoints, PHP claims, PHP throttles + - authorized-apps API (list, revoke), user.Deleting / user.Deactivating cascade + - MCP routes behind golem15.oauthserver.mcp.enabled (default false) + - sm-user-plugin additive exports RequestUser, EndRequestSession, AvatarURL, DeactivatingEvent, FireDeactivatingEvent + - TestRelyingPartyInterop with go-oidc v3.21.0 and x/oauth2 v0.37.0 (test-only) on RS256 and ES256 rings +affects: [19.4-03, 19.4-04, 19.7, fonoteka, sm-user-plugin consumers] + +actuals: + tokens: 59000 # chars/4: sm-oauthserver-plugin tracked files without go.sum (225159 chars) + sm-user-plugin added lines (10671 chars) + tasks: 5 # 4 executed tasks + the Task 4 checkpoint + commits: 7 # measured: sm-user-plugin c077029..80b436d = 3, sm-oauthserver-plugin rev-list --count HEAD = 4 (root commit is the plan's first) +plan_head_before: "sm-user-plugin c077029; sm-oauthserver-plugin (new repository, no commits); summercms.go 1c0d22a216d074e77c1809740bcf6f8af9481a7d" +plan_head_after: "sm-user-plugin 80b436d; sm-oauthserver-plugin f1cc3b9; summercms.go: this SUMMARY commit" + +tech-stack: + added: + - "github.com/coreos/go-oidc/v3 v3.21.0 (test-only, sm-oauthserver-plugin)" + - "golang.org/x/oauth2 v0.37.0 (test-only, sm-oauthserver-plugin; was already v0.36.0 indirect)" + - "github.com/go-jose/go-jose/v4 v4.1.4 (indirect, via go-oidc)" + patterns: + - "Shared plugin publishes one *oauthserver.Servers wrapper (OIDC, MCP, KeyRing), never a bare *wristband.Server" + - "Lazy stores resolve *gorm.DB at request time and fail closed" + - "Key material read and written with a silent GORM logger; first key via INSERT ... ON CONFLICT (active) WHERE active DO NOTHING" + - "Plugin-owned body-limit middleware factory for GroupRaw routes (surf body.limit ignores raw groups)" + +key-files: + created: + - ../sm-oauthserver-plugin/plugin.go + - ../sm-oauthserver-plugin/servers.go + - ../sm-oauthserver-plugin/routes.go + - ../sm-oauthserver-plugin/buckets.go + - ../sm-oauthserver-plugin/middleware.go + - ../sm-oauthserver-plugin/classes/oidc_store.go + - ../sm-oauthserver-plugin/classes/mcp_store.go + - ../sm-oauthserver-plugin/classes/keyring.go + - ../sm-oauthserver-plugin/classes/claims.go + - ../sm-oauthserver-plugin/classes/host.go + - ../sm-oauthserver-plugin/classes/branding.go + - ../sm-oauthserver-plugin/classes/cascade.go + - ../sm-oauthserver-plugin/classes/lazy.go + - ../sm-oauthserver-plugin/controllers/authorizations.go + - ../sm-oauthserver-plugin/controllers/consent_api.go + - ../sm-oauthserver-plugin/updates/202610190001_create_golem15_oauthserver_tables.go + - ../sm-oauthserver-plugin/updates/202610190002_create_golem15_oauthserver_settings.go + - ../sm-oauthserver-plugin/updates/202610190003_create_golem15_oauthserver_mcp_codes.go + - ../sm-oauthserver-plugin/views/consent.html.tmpl + - ../sm-oauthserver-plugin/views/error.html.tmpl + - ../sm-oauthserver-plugin/interop_test.go + - ../fonoteka.go/plugins/golem15/user/classes/session_user.go + - ../fonoteka.go/plugins/golem15/user/classes/avatar.go + modified: + - ../fonoteka.go/plugins/golem15/user/classes/events.go + - ../fonoteka.go/plugins/golem15/user/controllers/api_controller.go + - ../fonoteka.go/plugins/golem15/user/controllers/registration.go + - ../fonoteka.go/plugins/golem15/user/controllers/users_admin_controller.go + - ../fonoteka.go/plugins/golem15/user/README.md + +key-decisions: + - "Task 4 package check answered \"approved\": go-oidc v3.21.0 and x/oauth2 v0.37.0 are test-only requirements of sm-oauthserver-plugin; only interop_test.go imports them; the framework go.mod is untouched" + - "Lazy first signing key uses INSERT ... ON CONFLICT (active) WHERE active DO NOTHING and key reads/writes run with a silent GORM logger, so a losing insert never prints private-key ciphertext (T-19.4-15)" + - "The plugin registers its own middleware factory oauthserver.body.limit: before throttle:oauth-token, because surf ignores body.limit on GroupRaw routes" + - "SanitizeCustomCSS repeats tag-opener removal until stable; the PHP single pass rebuilds ` (`BodyLimitMiddleware`, `DefaultTokenBodyLimit`, new file `middleware.go`), placed before `throttle:oauth-token`; the MCP token route uses it too. +- **Verification:** Task 2 acceptance grep (`throttle:oauth-token` line carries `body.limit`) and the smokes. +- **Committed in:** b9cd399 + +**3. [Rule 1/2 - Bug, security] PHP custom-CSS sanitiser bypass** +- **Found during:** Task 2 +- **Issue:** A single removal pass turns `<<