feat(access): admin/operator roles + server-side login sessions - #81
Open
pyramation wants to merge 1 commit into
Open
feat(access): admin/operator roles + server-side login sessions#81pyramation wants to merge 1 commit into
pyramation wants to merge 1 commit into
Conversation
- Roles on users (admin/operator); first user in an empty project is admin, later users default to operator; last-admin demote/remove is guarded. - Cheap server-side login sessions in appstash JSON (sessions.json, 0600), bound to short-lived JWTs (sid + role + 1h exp); pruned on read. - Role-gated HTTP admin surface: /api/admin/sessions, /api/admin/users (list/create/role/delete). Bearer JWT + active session required; the shared receiver key can never administer. - Desktop Access route: role column + inline promote/demote, a Sessions tab (who's logged in, revoke), role on Add-user. Local Electron uses the main-process store directly; no secrets cross IPC. - Receiver + WebSocket protocol unchanged.
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an admin vs. operator distinction plus cheap server-visible login sessions, so you can see who's using the UI and manage access — without touching the receiver, WebSocket protocol, or sockets. No PGlite; everything lives in the appstash/project JSON that already syncs.
Roles (
packages/settings/src/users.ts). Each user carriesrole: 'admin' | 'operator'. Role resolution is defensive so nothing breaks:listUsers(project): string[]is preserved for existing callers; newlistUserInfos/authenticatereturn{username, role}.Sessions (
packages/settings/src/sessions.ts, new). A login writes a session row todata/projects/<p>/sessions.json(mode0600, pruned on read, not part of portable export):The JWT is bound to it —
signJwt(sub, { sid, role, ttlSec })now carriessid,role, and a 1hexp;verifyJwtrejects expired tokens (legacy no-exptokens still accepted).HTTP admin surface (
packages/server/src/http-app.ts)./api/logincreates a session and returns{ username, role, token, expiresAt };/api/meechoes role. New role-gated endpoints, each requiring a valid Bearer JWT + still-active session viarequireAdmin(the shared receiver key can never administer):Revocation is intentionally lazy — it drops the session row; the client loses access on its next token refresh. Open sockets are never force-closed, keeping the socket layer untouched.
Desktop Access route (
packages/desktop/...). The Users tab gains a role column with inline promote/demote (last-admin guarded), Add-user gets a role picker, and a new Sessions tab lists who's logged in (user, role, IP, last-seen) with revoke. This is the local privileged path — Electron main reads/writes the store directly over typed IPC; no secrets or password hashes cross the bridge. A remote Electron would instead use the role-gated/api/adminendpoints above.Testing
packages/settings/__tests__/roles-sessions.test.ts(new): role defaults, explicit roles, legacy records, last-admin protection, session create/list/get/touch/revoke/prune, revoke-all-for-user.packages/server/__tests__/http-app.test.ts: login returns role+expiry, admin session list/revoke, operator + anonymous denial, admin user list/create/promote/delete,/api/me.pnpm build,pnpm -r lint,pnpm -r testgreen (in a clean env — the one local layout-config failure is this box's~/.wavegridbeing set to thenovaproject, not a code issue).Link to Devin session: https://app.devin.ai/sessions/972698f89f494b86828010666a002b8f
Requested by: @pyramation