# Add Authentication to `GET /api/channels/:id`

Priority: **High** · Effort: **Trivial (3 lines)** · Risk: **Low**

## Problem

Every sibling channel endpoint calls `getOrCreateUser(req, server)` and returns 401 if there is no user. The GET-state endpoint does not:

```ts
// routes/channels.ts:167-171
export function handleGetChannel(channelId: string): Response {
  const channel = state.channels.get(channelId);
  if (!channel) return new Response("Not found", { status: 404 });
  return Response.json(channel.getState());
}
```

`Channel.getState()` (`channel.ts:142-159`) returns the queue, `currentIndex`, listener count, the `isDefault` flag, and (when requested) the full track list. Any unauthenticated caller can enumerate and read the state of every channel.

## Affected Location

- `routes/channels.ts:167-171` — `handleGetChannel`, missing auth.

Also note: this handler's signature differs from its siblings (it takes only `channelId`, not `req, server, channelId`). The router call at `routes/index.ts:111-114` reflects this. Both must be updated.

## Implementation Plan

### Step 1 — Update the handler signature and add the auth check

```ts
// routes/channels.ts
import { getOrCreateUser } from "./helpers";

// GET /api/channels/:id - get channel state
export function handleGetChannel(req: Request, server: any, channelId: string): Response {
  const { user } = getOrCreateUser(req, server);
  if (!user) {
    return Response.json({ error: "Authentication required" }, { status: 401 });
  }
  const channel = state.channels.get(channelId);
  if (!channel) return new Response("Not found", { status: 404 });
  return Response.json(channel.getState());
}
```

### Step 2 — Update the router call site

`routes/index.ts:111-114`:

```ts
const channelGetMatch = path.match(/^\/api\/channels\/([^/]+)$/);
if (channelGetMatch && req.method === "GET") {
  return handleGetChannel(req, server, channelGetMatch[1]);
}
```

(Pass `req` and `server` through, matching the DELETE/PATCH handlers above.)

### Step 3 — Consider whether state should be filtered by listener

Today `getState()` does not return the `listeners` array (only `listenerCount`) — good. Confirm this remains true; if `listeners` is ever added to `getState()`, also gate it behind the same ownership/permission rules used by `getListInfo()` (`channel.ts:377-389`), which *does* expose usernames. (Out of scope for this fix, but worth a note: the channel *list* endpoint exposes listener usernames to any authenticated user — acceptable for a listen-along app, but verify it matches the product intent.)

## Validation

- **Unauthenticated request is rejected**: `curl -i http://localhost:3001/api/channels/main` → expect `401` (previously `200` with full state).
- **Authenticated request still works**: `curl -i -b cookies.txt http://localhost:3001/api/channels/main` → expect `200` with state JSON.
- **Guest access**: with `allowGuests: true`, an unauthenticated curl should now receive a `Set-Cookie` guest session *and* still be able to read state on the next request (guests can listen). Confirm the second request returns 200.
- **Client still works**: load the app, switch channels, confirm no regression (the client always sends the session cookie).

## Risk / Rollback

- The client already authenticates every request via cookie, so legitimate UI is unaffected.
- If any external/SSR/integration consumer relied on the open endpoint, they will now get 401 — re-grant via a real session. This was never intended public surface.
- Rollback = revert the handler signature and the router call.
