| 1 | import type { Route } from "./+types/ticket"; |
| 2 | import { getViewer, roleIn } from "../../lib/session.server"; |
| 3 | import { issueTicket, socketPath } from "../../lib/socket-ticket"; |
| 4 | import { ticketSecret } from "../../lib/socket-ticket.server"; |
| 5 | import { bearerToken } from "../../lib/website-token"; |
| 6 | |
| 7 | const PRIVATE = { "cache-control": "no-store", "x-robots-tag": "noindex" }; |
| 8 | |
| 9 | /** |
| 10 | * A socket ticket for a page opened with an access token: |
| 11 | * `GET /-/live/ticket?path=/<workspace>/-/chat/live` answers |
| 12 | * `{ ticket, expires_at }`, good for a minute on that socket alone |
| 13 | * (lib/socket-ticket.ts). Only a request signed in by a token gets one: a |
| 14 | * session's sockets carry its cookie and need none. |
| 15 | */ |
| 16 | export async function loader({ context, request }: Route.LoaderArgs) { |
| 17 | const viewer = getViewer(context); |
| 18 | const token = bearerToken(request); |
| 19 | if (!viewer || !token || !viewer.token?.website) { |
| 20 | return Response.json( |
| 21 | { message: "Socket tickets are for pages opened with an access token; a signed-in browser's sockets use its session." }, |
| 22 | { status: viewer ? 400 : 401, headers: PRIVATE }, |
| 23 | ); |
| 24 | } |
| 25 | const socket = socketPath(new URL(request.url).searchParams.get("path") ?? ""); |
| 26 | if (!socket) return Response.json({ message: "Which socket? `path` is one of the site's live sockets." }, { status: 400, headers: PRIVATE }); |
| 27 | if (socket.workspace && !roleIn(viewer, socket.workspace)) { |
| 28 | return Response.json({ message: "Not found" }, { status: 404, headers: PRIVATE }); |
| 29 | } |
| 30 | const issued = await issueTicket(ticketSecret(), { token, userId: viewer.id, path: socket.path }); |
| 31 | return Response.json(issued, { headers: PRIVATE }); |
| 32 | } |