Skip to content

feat(access): shared guest passphrase — one low-privilege operator to hand out - #82

Merged
pyramation merged 1 commit into
mainfrom
feat/shared-guest-access
Aug 7, 2026
Merged

feat(access): shared guest passphrase — one low-privilege operator to hand out#82
pyramation merged 1 commit into
mainfrom
feat/shared-guest-access

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

Adds a first-class shared "guest" passphrase per project: one credential an admin mints and hands out so casual users can drive the show without their own account. It always maps to the operator role — never admin — so sharing it can never grant administration, and the receiver key is untouched. This is the "public password everyone shares, like a lower-level user" ask, built on the admin/operator split from #81.

Model (no new socket/receiver/protocol surface — same shape as sessions):

data/projects/<p>/guest.json   { enabled, salt, hash, updatedAt }   # 0600, no cleartext
  • The passphrase is stored only as a salted scrypt hash; the cleartext is returned exactly once — from a rotate/mint action — for the admin to copy and share. Forgotten → rotate, not recover.
  • Rotating invalidates the previous passphrase (old one stops working on the next token refresh). Enable/disable flips logins on/off without changing it.

New @wavegrid/settings API (guest.ts, exposed on the store facade):

guestStatus(project): { configured, enabled, updatedAt }
rotateGuestPassphrase(project): string     // cleartext, once
setGuestEnabled(project, enabled): GuestStatus
clearGuest(project): void
authenticateGuest(project, passphrase): UserInfo | null   // → { username: 'guest', role: 'operator' } or null

Login now falls back to the guest passphrase after real-user auth fails, so a shared password "just works" regardless of the username typed:

const user =
  store.authenticate(project, username, password) ??
  store.authenticateGuest(project, password);   // always operator, never admin

The username guest is reserved (can't be created as a normal account); /api/me resolves the guest as an operator while guest access stays enabled.

New role-gated HTTP endpoints (admin JWT only — receiver key rejected, operators get 403):

GET    /api/admin/guest
POST   /api/admin/guest/rotate     → { passphrase, guest }   # cleartext once
POST   /api/admin/guest/enabled    { enabled }
DELETE /api/admin/guest

Desktop Access → Guest access tab: create/rotate (reveals the passphrase once in a copy dialog), turn on/off, remove. Values never persist in the renderer beyond the one reveal; only status crosses IPC otherwise. CLI: wavegrid projects guest status|new|enable|disable|rm (also as the guest alias).

Tests

  • packages/settings/__tests__/guest.test.ts: mint/rotate, operator-not-admin, hash-only persistence (cleartext never on disk), disable/re-enable, clear, reserved guest username.
  • packages/server/__tests__/http-app.test.ts: admin mint/rotate/disable/delete, guest login → operator, operator denied guest admin.
  • Full suite green with isolated HOME: settings 68, server 55, cli 106, desktop 8, + all others. pnpm build and pnpm -r run lint clean.

Not in scope

No PGlite, no receiver/WebSocket/protocol changes, no per-device keys, no forced socket close on disable (takes effect on next token refresh, like session revocation).

Link to Devin session: https://app.devin.ai/sessions/972698f89f494b86828010666a002b8f
Requested by: @pyramation

@pyramation pyramation self-assigned this Aug 7, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit 7e011f7 into main Aug 7, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant