8.0 KiB
Phase 8: OAuth2.1 authorization server - Discussion Log
Audit trail only. Do not use as input to planning, research, or execution agents. Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
Date: 2026-09-23 Phase: 08-oauth2-1-authorization-server Areas discussed: Server engine, Package home and the oauth guard, End-to-end proof and the header contract, Parity corpus and lifecycle coverage
Server engine
| Option | Description | Selected |
|---|---|---|
| Direct port on stdlib | Port OAuthCodeManager and the four controllers on crypto/rand, sha256, subtle, net/url; decision note drops zitadel/oidc | ✓ |
| zitadel/oidc with overrides | Implement AuthStorage/OPStorage and override metadata, error and token handlers to reach parity | |
| zitadel/oidc only where it fits | Use it for PKCE and request validation only |
User's choice: Direct port on stdlib.
| Option | Description | Selected |
|---|---|---|
| Form body plus query, like PHP | r.FormValue merges urlencoded body and query; JSON on /token is invalid_request; register stays JSON-only | ✓ |
| Strict form body only | Ignore the query string | |
| Match Laravel fully including JSON | Also accept application/json on /token |
User's choice: Form body plus query.
| Option | Description | Selected |
|---|---|---|
| Config keys with PHP defaults | TTLs and caps under plugin config with PHP values; issuer from app.url, resource from fonoteka.mcp.resource | ✓ |
| Constants exactly as PHP | Go constants in the code manager | |
| You decide |
User's choice: Config keys with PHP defaults.
| Option | Description | Selected |
|---|---|---|
| Yes, threat model plus review | T-08-xx threats per plan and a closing 08-SECURITY-REVIEW.md mapping threats to tests | ✓ |
| Review only | Security-review agent at the end, no threat IDs | |
| You decide |
User's choice: Threat model plus review.
Package home and the oauth guard
| Option | Description | Selected |
|---|---|---|
| Plugin owns it, framework gets primitives | Server in fonoteka.go, framework gains PKCE/base64url/compare/form helpers | |
| Framework oauth package with storage interfaces | Generic RFC 6749/7591/8414 server in summercms.go; fonoteka plugs its models in | ✓ |
| Everything in the plugin | No framework additions |
User's choice: Framework oauth package with storage interfaces (against the recommendation).
| Option | Description | Selected |
|---|---|---|
| New package, PHP shapes are the defaults | New package beside bouncer emitting PHP's RFC-minimal bodies; metadata, scopes, paths, TTLs are config | ✓ |
| Inside bouncer | Extend bouncer | |
| New package with response hooks | Generic shapes plus app override hooks |
User's choice: New package with PHP shapes as defaults; user asked for name suggestions before creation.
| Option | Description | Selected |
|---|---|---|
| wristband | Festival wristband, checked by the bouncer, issued at the gate after approval | ✓ |
| visa | Permission an outside party applies for and a consenting authority stamps | |
| lanyard | Delegated credential naming issuer and grant |
User's choice: wristband.
| Option | Description | Selected |
|---|---|---|
| Wristband owns RFC surface, app owns consent and tokens | Stores and AccessTokenIssuer implemented by the app; consent, connected apps, /connect URL, collection ids stay in the app | ✓ |
| Wristband also owns consent and connected apps | More interfaces, fuller reuse | |
| You decide |
User's choice: Wristband owns the RFC surface, app owns consent and tokens.
| Option | Description | Selected |
|---|---|---|
| Retire it, document why | No oauth guard; inv_token guard authenticates OAuth-issued tokens | ✓ |
| Register oauth as a restricted alias | Guard accepting only inv_ tokens with oauth_client_id, unmounted | |
| You decide |
User's choice: Retire it. Notes: the user added that inv_ is a legacy prefix from the Inventory app Płytarium was forked from and could become summer-themed.
| Option | Description | Selected |
|---|---|---|
| Keep inv_ for Płytarium, prefix becomes app config | Phase 7 token manager reads the prefix from config; summer-themed default deferred | ✓ |
| Keep inv_ hardcoded, note the rename | No code change | |
| Pick the new prefix now as the framework default | Choose it in this discussion |
User's choice: Keep inv_ for Płytarium, prefix becomes app config.
End-to-end proof and the header contract
| Option | Description | Selected |
|---|---|---|
| Correct the roadmap, prove the real chain | SC3 reworded; backend emits only Basic realm="OAuth" on invalid_client; no new headers on backend 401s | ✓ |
| Add RFC 6750 headers to backend 401s | Contract deviation with a harness allow-list | |
| Keep the wording, satisfy it via the MCP |
User's choice: Correct the roadmap, prove the real chain.
| Option | Description | Selected |
|---|---|---|
| Both: tide replay in go test, live MCP in a gate script | Recorded flows in go test plus check-phase8.sh running the real Node fonoteka-mcp against the Go backend | ✓ |
| Tide replay only | ||
| Live MCP only, in go test |
User's choice: Both.
| Option | Description | Selected |
|---|---|---|
| Manual UAT step with Claude, ChatGPT if available | ||
| Automated gates only | First vendor connect at cutover | ✓ |
| Required before phase close |
User's choice: Automated gates only.
Parity corpus and lifecycle coverage
| Option | Description | Selected |
|---|---|---|
| Record a second PHP flow covering the full lifecycle | mcp-lifecycle: DCR, authorize, consent, token, refresh, replay kill, connected-apps, revoke, refresh-after-revoke, deny, ceiling client with client_secret_basic | ✓ |
| Existing fixtures plus Go-only tests | ||
| You decide |
User's choice: Record a second PHP flow.
| Option | Description | Selected |
|---|---|---|
| Match PHP now, Phase 11 job later | No cleanup this phase | |
| Add an expiry sweep now | Additive cleanup of expired rows | ✓ |
| You decide |
User's choice: Add an expiry sweep now.
| Option | Description | Selected |
|---|---|---|
| On /register and /token, expired rows only | Delete only rows past expires_at; revoked or rotated unexpired rows stay; no timer | ✓ |
| Background ticker in plugin Boot | ||
| You decide |
User's choice: On /register and /token, expired rows only.
| Option | Description | Selected |
|---|---|---|
| Port all as Go tests, split framework vs app | Wristband tests on in-memory store; app tests on Postgres; each PHP method maps to a named Go test | ✓ |
| Port security classes only | ||
| You decide |
User's choice: Port all as Go tests.
| Option | Description | Selected |
|---|---|---|
| Port it in fonoteka.go with the same flags | bonfire command, thin over wristband | ✓ |
| Generic wristband command in the framework | ||
| Defer the command |
User's choice: Port it in fonoteka.go with the same flags.
Claude's Discretion
- Wristband interface names and signatures, transaction seam, in-memory store, helper placement.
- Config key layout on the fonoteka side.
- The gate's scripted client and Postgres provisioning.
- Logging policy, error-text constants, control-character stripping, Content-Type judgement for /register.
Deferred Ideas
- Summer-themed default token prefix (inv_ stays for Płytarium).
- River sweep job — Phase 11.
- Framework-side client-issuing command.
- Live vendor connects — cutover UAT.
- Social login and oauth-identities routes — still deferred from Phase 7.