chore(api): remove User.prvkey field + thread-through helpers (Q1.2 Option b) #84

Merged
padreug merged 1 commit from chore/remove-user-prvkey-field into dev 2026-06-03 16:33:49 +00:00
Owner

Summary

Atomic phase-1 final per design-questions Q1.2 Option (b) and the consolidated decisions in log:2026-05-29T00:30Z. Removing prvkey?: string from the User interface flips the type system into the pressure mechanism that forces phase-2 to start: every remaining bucket-B sign-site (chat / forum / nostr-feed / activities-bookmarks/RSVP / market / tasks) now fails vue-tsc until it migrates to signEventViaLnbits() against POST /api/v1/auth/sign-event (aiolabs/lnbits PR #29, deployed on aio-demo).

This is part 3 of a stacked PR chain: #82 → #83 → this PR. Deploying this branch's tip exercises all three changes at once.

Changes

  • src/lib/api/lnbits.ts:
    • Drop prvkey?: string from User interface.
    • getCurrentUser(): /auth/nostr/me used to merge prvkey alongside pubkey; post-cascade the endpoint returns only the pubkey. Updated the comment + cleaned the merge object.
  • src/modules/base/auth/auth-service.ts:
    • updateProfile() no longer threads prvkey through the merge. Server-side PATCH /auth publishes kind-0 via the signer per 869f67c3; the webapp doesn't keep prvkey at all.
  • src/modules/nostr-feed/components/NostrFeed.vue + ScheduledEventCard.vue:
    • Repoint the ScheduledEvent type import from the deleted ../services/ScheduledEventService to @/modules/tasks/services/TaskService. Trivial post-#81-merge cleanup that fell through the dedup PR; same file exports the same interface.

vue-tsc --noEmit after this commit: 8 errors, all TS2339 "Property 'prvkey' does not exist"

The failing sites are exactly the bucket-B targets the design doc enumerates as phase-2 migration work:

Failing site Bucket B kind
activities/composables/useBookmarks.ts:92,113 kind 10003 (NIP-51 bookmarks)
activities/composables/useRSVP.ts:153,187 kind 31925 (NIP-52 RSVP)
base/services/NostrTransportService.ts:100,112 kind 21000 (NIP-44 v2 RPC envelope)
market/composables/useMarket.ts:455 NIP-44 gift-wrap (kind 1059) unwrap
nostr-feed/components/NostrFeed.vue:408 kind 5 (deletion of own post)

NOT caught by vue-tsc but still bucket-B

The BaseService injection pattern types this.authService as any, so optional chaining (this.authService?.user?.value?.prvkey) bypasses the type check. These sites will runtime-fail (prvkey is undefined from the API post-cascade) but won't surface at compile time. Phase-2's per-module migration (Q5.2) catches them as each module flips.

  • chat/services/chat-service.ts:341, 511, 714
  • forum/services/SubmissionService.ts:755, 1167
  • nostr-feed/services/SubmissionService.ts:769, 1226
  • nostr-feed/components/NoteComposer.vue:306
  • nostr-feed/components/RideshareComposer.vue:423
  • tasks/services/TaskService.ts:507, 562, 616

⚠️ This PR intentionally breaks the build

The webapp WILL NOT BUILD CLEANLY after this PR merges to dev until phase 2 lands. That's the intended trade-off per Q5.1 + Q1.2; the broken-build interval is the design-intended pressure mechanism to start phase 2. server-deploy's webapp-demo flake.lock bump will fail until phase 2 lands; demo stays on the pre-PR webapp during that interval.

Phase-2 gate

Per lnbits's log:2026-05-29T20:30Z audit, the gate for phase-2 webapp bucket-B PRs is:

  1. aiolabs/lnbits#31 merges — env-pinned lnbits_cors_allowed_origins: list[str] allowlist, switches CORS from wildcard to explicit allowlist + Access-Control-Allow-Credentials: true when populated.
  2. server-deploy:flake.lock bumps lnbits-dev to pull #31.
  3. services.lnbits.env.LNBITS_CORS_ALLOWED_ORIGINS = ["https://demo.aiolabs.dev"] set on aio-demo.
  4. ./deploy.sh aio-demo verifies OPTIONS /api/v1/auth/sign-event returns Access-Control-Allow-Origin: https://demo.aiolabs.dev + Access-Control-Allow-Credentials: true.

Why CORS-only, no SameSite changes: aiolabs.dev is not on the Public Suffix List, so demo.aiolabs.dev and lnbits.demo.aiolabs.dev are same-siteSameSite=Lax cookies including the csrf_token already ride cross-subdomain cleanly. The CORS adaptation is the only thing needed because /auth/sign-event uses credentials: 'include' for the CSRF double-submit, which forbids wildcard origin.

Stacked PR chain context

This PR's base is chore/delete-activities-nostr-service-publish (#83), NOT dev. The stack:

  • dev
  • #82 chore(base): delete nostr-metadata-service
  • #83 chore(activities): reroute CreateActivityDialog through TicketApiService.createEvent
  • #84 (this PR) chore(api): remove User.prvkey field + thread-through helpers

To test all three together on aio-demo, bump services.webapp.src (or equivalent flake input) to point at this branch's tip (85fc83c). The diff this PR shows in Forgejo is just the prvkey-removal piece (its base is #83's rebased tip).

When the stack merges in order (#82#83 → this), dev ends up with all three changes.

Test plan

  • vue-tsc --noEmit -p tsconfig.app.json shows 8 expected TS2339 prvkey errors at the bucket-B sites listed above. NO other errors.
  • Deploy stack tip to aio-demo via server-deploy flake bump:
    • Verify login / register / profile-update flows work (kind-0 publishes server-side via the gap-fill)
    • Verify activities create (PR #83 reroute) works for both admin and non-admin (proposal queue caveat)
    • Verify all bucket-B flows are broken (or runtime-fail with a clean error) — this is expected and intentional
  • Phase-2 PRs migrate each bucket-B module to signEventViaLnbits() (separate work, lnbits's #31 CORS allowlist must land first)

Refs

  • log:2026-05-29T00:30Z — consolidated decisions; Q1.2 Option (b) + Q5.1 risk acknowledged (demo gap acceptable)
  • log:2026-05-29T17:30Z — lnbits confirming this PR stays atomic-after-the-two-bucket-A PRs
  • log:2026-05-29T20:30Z — lnbits CORS-only audit result (SameSite changes not needed thanks to PSL)
  • ~/dev/coordination/webapp-design-questions.md Q1.2 + Q5.1 + Q3.1.6 (updated for CORS-only gate)
  • Parent initiative: aiolabs/lnbits#9 (signer abstraction / bunker integration)
  • Phase-2 gate: aiolabs/lnbits#31 (CORS allowlist)
  • Sibling PRs in the stack: #82 (open), #83 (open, rebased on #82's branch)

🤖 Generated with Claude Code

## Summary **Atomic phase-1 final** per design-questions `Q1.2` Option (b) and the consolidated decisions in `log:2026-05-29T00:30Z`. Removing `prvkey?: string` from the `User` interface flips the type system into the pressure mechanism that forces phase-2 to start: every remaining bucket-B sign-site (chat / forum / nostr-feed / activities-bookmarks/RSVP / market / tasks) now fails `vue-tsc` until it migrates to `signEventViaLnbits()` against `POST /api/v1/auth/sign-event` (`aiolabs/lnbits` PR #29, deployed on `aio-demo`). This is part 3 of a **stacked PR chain**: `#82 → #83 → this PR`. Deploying this branch's tip exercises all three changes at once. ## Changes - **`src/lib/api/lnbits.ts`**: - Drop `prvkey?: string` from `User` interface. - `getCurrentUser()`: `/auth/nostr/me` used to merge `prvkey` alongside `pubkey`; post-cascade the endpoint returns only the pubkey. Updated the comment + cleaned the merge object. - **`src/modules/base/auth/auth-service.ts`**: - `updateProfile()` no longer threads `prvkey` through the merge. Server-side `PATCH /auth` publishes kind-0 via the signer per `869f67c3`; the webapp doesn't keep prvkey at all. - **`src/modules/nostr-feed/components/NostrFeed.vue` + `ScheduledEventCard.vue`**: - Repoint the `ScheduledEvent` type import from the deleted `../services/ScheduledEventService` to `@/modules/tasks/services/TaskService`. Trivial post-#81-merge cleanup that fell through the dedup PR; same file exports the same interface. ## `vue-tsc --noEmit` after this commit: **8 errors**, all `TS2339 "Property 'prvkey' does not exist"` The failing sites are exactly the bucket-B targets the design doc enumerates as phase-2 migration work: | Failing site | Bucket B kind | |---|---| | `activities/composables/useBookmarks.ts:92,113` | kind 10003 (NIP-51 bookmarks) | | `activities/composables/useRSVP.ts:153,187` | kind 31925 (NIP-52 RSVP) | | `base/services/NostrTransportService.ts:100,112` | kind 21000 (NIP-44 v2 RPC envelope) | | `market/composables/useMarket.ts:455` | NIP-44 gift-wrap (kind 1059) unwrap | | `nostr-feed/components/NostrFeed.vue:408` | kind 5 (deletion of own post) | ## NOT caught by vue-tsc but still bucket-B The BaseService injection pattern types `this.authService` as `any`, so optional chaining (`this.authService?.user?.value?.prvkey`) bypasses the type check. These sites will runtime-fail (prvkey is undefined from the API post-cascade) but won't surface at compile time. Phase-2's per-module migration (Q5.2) catches them as each module flips. - `chat/services/chat-service.ts:341, 511, 714` - `forum/services/SubmissionService.ts:755, 1167` - `nostr-feed/services/SubmissionService.ts:769, 1226` - `nostr-feed/components/NoteComposer.vue:306` - `nostr-feed/components/RideshareComposer.vue:423` - `tasks/services/TaskService.ts:507, 562, 616` ## ⚠️ This PR intentionally breaks the build **The webapp WILL NOT BUILD CLEANLY after this PR merges to `dev`** until phase 2 lands. That's the intended trade-off per `Q5.1` + `Q1.2`; the broken-build interval is the design-intended pressure mechanism to start phase 2. `server-deploy`'s `webapp-demo` flake.lock bump will fail until phase 2 lands; demo stays on the pre-PR webapp during that interval. ## Phase-2 gate Per lnbits's `log:2026-05-29T20:30Z` audit, the gate for phase-2 webapp bucket-B PRs is: 1. **`aiolabs/lnbits#31` merges** — env-pinned `lnbits_cors_allowed_origins: list[str]` allowlist, switches CORS from wildcard to explicit allowlist + `Access-Control-Allow-Credentials: true` when populated. 2. **`server-deploy:flake.lock` bumps `lnbits-dev`** to pull #31. 3. **`services.lnbits.env.LNBITS_CORS_ALLOWED_ORIGINS = ["https://demo.aiolabs.dev"]`** set on `aio-demo`. 4. **`./deploy.sh aio-demo`** verifies `OPTIONS /api/v1/auth/sign-event` returns `Access-Control-Allow-Origin: https://demo.aiolabs.dev` + `Access-Control-Allow-Credentials: true`. **Why CORS-only, no SameSite changes:** `aiolabs.dev` is not on the Public Suffix List, so `demo.aiolabs.dev` and `lnbits.demo.aiolabs.dev` are **same-site** → `SameSite=Lax` cookies including the `csrf_token` already ride cross-subdomain cleanly. The CORS adaptation is the only thing needed because `/auth/sign-event` uses `credentials: 'include'` for the CSRF double-submit, which forbids wildcard origin. ## Stacked PR chain context This PR's base is `chore/delete-activities-nostr-service-publish` (#83), NOT `dev`. The stack: - `dev` → - `#82` `chore(base): delete nostr-metadata-service` → - `#83` `chore(activities): reroute CreateActivityDialog through TicketApiService.createEvent` → - **`#84` (this PR)** `chore(api): remove User.prvkey field + thread-through helpers` To test all three together on `aio-demo`, bump `services.webapp.src` (or equivalent flake input) to point at this branch's tip (`85fc83c`). The diff this PR shows in Forgejo is just the prvkey-removal piece (its base is #83's rebased tip). When the stack merges in order (#82 → #83 → this), `dev` ends up with all three changes. ## Test plan - [x] `vue-tsc --noEmit -p tsconfig.app.json` shows 8 expected `TS2339 prvkey` errors at the bucket-B sites listed above. NO other errors. - [ ] Deploy stack tip to `aio-demo` via `server-deploy` flake bump: - Verify login / register / profile-update flows work (kind-0 publishes server-side via the gap-fill) - Verify activities create (PR #83 reroute) works for both admin and non-admin (proposal queue caveat) - Verify all bucket-B flows are broken (or runtime-fail with a clean error) — this is **expected and intentional** - [ ] Phase-2 PRs migrate each bucket-B module to `signEventViaLnbits()` (separate work, lnbits's `#31` CORS allowlist must land first) ## Refs - `log:2026-05-29T00:30Z` — consolidated decisions; Q1.2 Option (b) + Q5.1 risk acknowledged (demo gap acceptable) - `log:2026-05-29T17:30Z` — lnbits confirming this PR stays atomic-after-the-two-bucket-A PRs - `log:2026-05-29T20:30Z` — lnbits CORS-only audit result (SameSite changes not needed thanks to PSL) - `~/dev/coordination/webapp-design-questions.md` Q1.2 + Q5.1 + Q3.1.6 (updated for CORS-only gate) - Parent initiative: `aiolabs/lnbits#9` (signer abstraction / bunker integration) - Phase-2 gate: `aiolabs/lnbits#31` (CORS allowlist) - Sibling PRs in the stack: `#82` (open), `#83` (open, rebased on #82's branch) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Atomic phase-1 final per design-questions Q1.2 Option (b) and the
2026-05-29T00:30Z architecture-decisions lock-in. Removing the
prvkey?: string field from the User interface flips the type system
into the pressure mechanism that forces phase-2 to start: every
remaining bucket-B sign-site (chat / forum / nostr-feed /
activities-bookmarks/RSVP / market / tasks) now fails vue-tsc until
it migrates to signEventViaLnbits() against POST /api/v1/auth/sign-event
(aiolabs/lnbits PR #29, deployed on aio-demo).

Changes:
- src/lib/api/lnbits.ts:
  - Drop `prvkey?: string` from User interface.
  - getCurrentUser(): /auth/nostr/me used to merge prvkey alongside
    pubkey; post-cascade the endpoint returns only the pubkey.
    Updated the comment + cleaned the merge object.
- src/modules/base/auth/auth-service.ts:
  - updateProfile() no longer threads `prvkey` through the merge.
    Server-side PATCH /auth publishes kind-0 via the signer per
    869f67c3; the webapp doesn't keep prvkey at all.
- src/modules/nostr-feed/components/NostrFeed.vue +
  src/modules/nostr-feed/components/ScheduledEventCard.vue:
  - Repoint the `ScheduledEvent` type import from the deleted
    `../services/ScheduledEventService` to
    `@/modules/tasks/services/TaskService`. Trivial post-#81-merge
    cleanup that fell through the dedup PR; same file exports the
    same interface.

vue-tsc --noEmit fails with 8 errors after this commit, all
TS2339 "Property 'prvkey' does not exist". The failing sites are
exactly the bucket-B targets the design doc enumerates as
phase-2 migration work:

| Failing site                                  | Bucket B kind |
|-----------------------------------------------|---------------|
| activities/composables/useBookmarks.ts:92,113 | kind 10003 (NIP-51 bookmarks) |
| activities/composables/useRSVP.ts:153,187     | kind 31925 (NIP-52 RSVP) |
| base/services/NostrTransportService.ts:100,112| kind 21000 (NIP-44 v2 RPC envelope) |
| market/composables/useMarket.ts:455           | NIP-44 gift-wrap (kind 1059) unwrap |
| nostr-feed/components/NostrFeed.vue:408       | kind 5 (deletion of own post) |

NOT caught by vue-tsc but still bucket-B (BaseService injection
pattern types `this.authService` as `any`, so optional chaining
bypasses the type check):

- chat/services/chat-service.ts:341,511,714
- forum/services/SubmissionService.ts:755,1167
- nostr-feed/services/SubmissionService.ts:769,1226
- nostr-feed/components/NoteComposer.vue:306
- nostr-feed/components/RideshareComposer.vue:423
- tasks/services/TaskService.ts:507,562,616

Those sites will runtime-fail (prvkey is undefined from the API
post-cascade) but won't surface at compile time. Phase 2's
per-module migration (Q5.2) catches them as each module flips.

The webapp WILL NOT BUILD CLEANLY after this PR merges to dev
until phase 2 lands. That's the intended trade-off per Q5.1 +
Q1.2; the broken-build interval is the design-intended pressure
mechanism to start phase 2. server-deploy's webapp-demo flake.lock
bump will fail until phase 2 lands; demo will stay on the
pre-PR-#84 webapp during that interval.

Refs:
- log:2026-05-29T00:30Z (consolidated decisions; Q1.2 Option (b)
  + Q5.1 risk: demo gap acceptable)
- log:2026-05-29T17:30Z (lnbits confirming this PR stays
  atomic-after-the-two-bucket-A PRs)
- ~/dev/coordination/webapp-design-questions.md Q1.2 + Q5.1
- Parent initiative: aiolabs/lnbits#9 (signer abstraction / bunker)
- Sibling PRs (stacked base→head): #82#83 → this

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
padreug force-pushed chore/remove-user-prvkey-field from 85fc83cb42 to be182cff3f 2026-05-30 05:50:18 +00:00 Compare
padreug force-pushed chore/remove-user-prvkey-field from be182cff3f to 1a0738576a 2026-05-30 06:04:42 +00:00 Compare
padreug changed target branch from chore/delete-activities-nostr-service-publish to dev 2026-05-30 15:26:07 +00:00
padreug force-pushed chore/remove-user-prvkey-field from 1a0738576a to 9a300c1679 2026-05-30 15:38:51 +00:00 Compare
padreug deleted branch chore/remove-user-prvkey-field 2026-06-03 16:33:49 +00:00
Sign in to join this conversation.
No description provided.