Compare commits
1 Commits
d53d3143ae
...
1db482b958
| Author | SHA1 | Date | |
|---|---|---|---|
| 1db482b958 |
@ -1,56 +0,0 @@
|
|||||||
# TASK METADATA
|
|
||||||
|
|
||||||
- **Target Files:** `server/auth-session.ts`, `server/routes/sessions.ts`, `server/routes/events.ts`, `server/main.ts`, `ui/mod.ts`, `server/main.test.ts`
|
|
||||||
- **Core Objective:** Implement strict Zero-Trust scope enforcement and permission guards across all Auth-Yes internal API routes and SSR UI pages to ensure delegated and read-only sessions cannot escalate privileges, perform unauthorized mutations, or access admin interfaces.
|
|
||||||
- **Dependencies:** None
|
|
||||||
- **Additional Important Notes:** Delegated sessions (`is_agent = true`) must hold strictly the intersection of user permissions and assigned `custom_scopes`. Self-revocation of a delegated session is permitted, but revoking other sessions is prohibited without `write:sessions` or `*`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architectural Considerations & Risks
|
|
||||||
|
|
||||||
- **Risks:**
|
|
||||||
- **Backward-Compatibility with Cached Sessions:** Existing delegated sessions in the Valkey cache might lack explicit scopes if they were created before this strict enforcement. The middleware must default to a safe deny state for unknown or missing scopes but handle legacy `is_agent = true` sessions gracefully by denying mutating actions unless explicitly permitted.
|
|
||||||
- **Self-Revocation Edge Case:** The guard logic must correctly distinguish between a session revoking itself (which is always allowed for clean logouts) and a session attempting to revoke a sibling or parent session.
|
|
||||||
- **Global Admin Resolution:** The `isGlobalAdmin` check relies on database queries or cache. Ensure that the new middleware does not introduce significant latency by repeatedly querying PostgreSQL on every protected route. Use Valkey caching where possible.
|
|
||||||
|
|
||||||
- **Alternatives:**
|
|
||||||
- Instead of injecting `customScopes` into the Valkey cache session payload, we could perform a database lookup on every privileged API request to determine exact grants. However, this violates the Auth-Yes architectural principle of fast-path caching at the edge and would significantly degrade performance. The chosen approach of checking scopes from the cached `AuthenticatedUser` object is highly modular and performant.
|
|
||||||
|
|
||||||
## Proposed Implementation
|
|
||||||
|
|
||||||
### 1. Scope Guard Helpers (`server/auth-session.ts`)
|
|
||||||
Implement reusable composable functions to evaluate the current user's capabilities.
|
|
||||||
- `hasScope(auth: AuthenticatedUser, requiredScope: string): boolean`: Evaluates if `auth.customScopes` contains `*` or the `requiredScope`. If `!auth.isAgent`, returns `true` (primary sessions have all capabilities).
|
|
||||||
- Create Hono middleware helpers:
|
|
||||||
- `requirePrimarySession()`: Denies access with `403 Forbidden` if `auth.isAgent === true`.
|
|
||||||
- `requireScope(scope: string)`: Denies access if `hasScope(auth, scope)` returns false.
|
|
||||||
- `requireAdmin(auth: AuthenticatedUser)`: Uses `isGlobalAdmin(auth.userId)` and verifies `!auth.isAgent` OR `auth.customScopes.includes("*")`.
|
|
||||||
|
|
||||||
### 2. Guard Internal API Routes
|
|
||||||
Apply the newly created guards to critical endpoints.
|
|
||||||
|
|
||||||
- **`server/routes/sessions.ts`**
|
|
||||||
- `DELETE /api/sessions/:id`: Allow if target `id === auth.sessionId`. Otherwise, require `write:sessions` scope.
|
|
||||||
- `PUT /api/sessions/:id/scopes`: Require `admin` or `*` scope, OR `write:sessions`.
|
|
||||||
- `POST /api/sessions/:id/extend`: Require `write:sessions` or `*`.
|
|
||||||
- `POST /api/sessions/delegate`: Prevent privilege escalation. Require `admin` or `*` scope to delegate an `admin` mode session. If creating a `read_only` or `operator` session, require the delegator to have at least those equivalent scopes.
|
|
||||||
|
|
||||||
- **`server/routes/events.ts`**
|
|
||||||
- `POST /api/events`: Require `write:events` or `*`.
|
|
||||||
- `POST /api/events/:id/end`: Require `write:events` or `*`.
|
|
||||||
- `POST /api/events/:id/extend`: Require `write:events` or `*`.
|
|
||||||
|
|
||||||
- **`server/main.ts`**
|
|
||||||
- Apply `requirePrimarySession()` or `requireAdmin()` to `/api/admin/*` and `/api/passkeys/*` endpoints.
|
|
||||||
|
|
||||||
### 3. SSR UI Gates (`ui/mod.ts`)
|
|
||||||
Guard the user interfaces against unauthorized delegated sessions.
|
|
||||||
- `/admin/*`: Verify `isAdmin && (!auth.isAgent || auth.customScopes.includes("*"))`. Redirect unauthorized sessions to `/dashboard` or return a `403 Forbidden` page.
|
|
||||||
- `/dashboard/passkeys`: Verify `!auth.isAgent`. Delegated sessions cannot manage passkeys.
|
|
||||||
|
|
||||||
### 4. Testing (`server/main.test.ts`)
|
|
||||||
- Write tests that create a delegated session with only `read_only` scopes.
|
|
||||||
- Assert that this session gets a `403 Forbidden` when attempting to call `POST /api/sessions/delegate`, `DELETE /api/sessions/:other_id`, and `POST /api/events`.
|
|
||||||
- Assert that the delegated session can successfully call `DELETE /api/sessions/:own_id`.
|
|
||||||
- Write tests verifying that a delegated session attempting to access `/admin/users` is redirected/blocked.
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
# TASK METADATA
|
|
||||||
|
|
||||||
- **Target Files:**
|
|
||||||
- `ui/components/SessionsPage.tsx`
|
|
||||||
- `ui/ui_scripts.test.ts`
|
|
||||||
- **Core Objective:** Redesign the Sessions management page header and drawer to eliminate dual competing top buttons, restoring the clean single `[+ Delegate Session]` header action, and unifying both pass creation workflows (1:1 Direct Pass vs. Multi-Claim Workshop Pass) into clean internal drawer tabs.
|
|
||||||
- **Dependencies:** None.
|
|
||||||
- **Additional Important Notes:** Must maintain 100% compliance with zero-framework SSR JSX standards and pass `ui/ui_scripts.test.ts` without client-side syntax errors.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architectural Considerations & Risks
|
|
||||||
|
|
||||||
- **Risks:** The primary risk involves introducing JavaScript syntax errors when consolidating the drawer forms and adding the tab-switching logic. Since this is an SSR JSX environment without a framework like React, all DOM manipulation and state management (like toggling tabs) rely on raw inline or embedded `<script>` blocks. A syntax error here could break the page interactions and fail the `ui/ui_scripts.test.ts` gate.
|
|
||||||
- **Alternatives:** We could keep the forms completely separate, but this violates the core objective of eliminating cognitive clutter and unified navigation. The proposed vanilla JavaScript tab toggle is the most native and compliant approach for this specific architectural boundary.
|
|
||||||
- **UX Consistency:** The Event Cockpit Deck must remain rendered *above* the Active Sessions table to ensure real-time visibility for workshop hosts.
|
|
||||||
|
|
||||||
## Proposed Implementation
|
|
||||||
|
|
||||||
### 1. Header Consolidation
|
|
||||||
- In `ui/components/SessionsPage.tsx`, locate the top header section.
|
|
||||||
- Remove the `[🎟️ + Create Workshop Pass]` button completely.
|
|
||||||
- Retain only the single, primary `[🔑 + Delegate Session]` button (and ensure it triggers `openDelegateDrawer()`).
|
|
||||||
|
|
||||||
### 2. Drawer Consolidation (`#delegateDrawer`)
|
|
||||||
- Remove the entire `#eventDrawer` DOM node.
|
|
||||||
- Inside `#delegateDrawer`, introduce a segmented flexbox tab selector at the top of the form area.
|
|
||||||
- Add the following styling for the tab container:
|
|
||||||
`display: flex; background: var(--surface-muted); padding: 4px; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); gap: 4px;`
|
|
||||||
- Add styling for the tab buttons:
|
|
||||||
`flex: 1; padding: 0.5rem 0.75rem; border-radius: var(--radius-sm); border: none; font-weight: 600; cursor: pointer; transition: all 0.15s;`
|
|
||||||
(Use an `.active` class with `background: var(--surface-card); color: var(--primary); box-shadow: var(--shadow-xs);`).
|
|
||||||
- Wrap the existing 1:1 Direct Pass form fields inside a `<div id="tabDirectPass">`.
|
|
||||||
- Move the fields from the deleted `#eventDrawer` into a new `<div id="tabWorkshopPass" style="display: none;">` inside `#delegateDrawer`.
|
|
||||||
|
|
||||||
### 3. JavaScript Tab-Switching Logic
|
|
||||||
- Add vanilla JavaScript functions to the embedded `<script>` block in `SessionsPage.tsx` to handle tab switching.
|
|
||||||
- Implement a function (e.g., `switchDelegateTab(tabId)`) that:
|
|
||||||
- Toggles `display: block` and `display: none` between `#tabDirectPass` and `#tabWorkshopPass`.
|
|
||||||
- Updates the active state styling on the corresponding tab buttons.
|
|
||||||
|
|
||||||
### 4. Code Cleanup
|
|
||||||
- Remove the `openEventDrawer()` and `closeEventDrawer()` functions from the `<script>` block.
|
|
||||||
- Ensure `closeDelegateDrawer()` correctly resets the forms or hides the drawer.
|
|
||||||
|
|
||||||
### 5. Quality Gates
|
|
||||||
- Run `deno fmt` and `deno task lint` to ensure code style compliance.
|
|
||||||
- Run `deno task test` to ensure `ui/ui_scripts.test.ts` passes, verifying there are no syntax errors in the newly consolidated `<script>` block.
|
|
||||||
Loading…
x
Reference in New Issue
Block a user