Fix websocket auth

This commit is contained in:
Zoe Roux 2025-12-23 14:32:34 +01:00
parent 6d21eeab07
commit 41e00532d8
No known key found for this signature in database
9 changed files with 78 additions and 27 deletions

View File

@ -450,6 +450,12 @@ export const historyH = new Elysia({ tags: ["profiles"] })
"/profiles/me/history",
async ({ body, jwt: { sub }, status }) => {
const profilePk = await getOrCreateProfile(sub);
if (!profilePk) {
return status(401, {
status: 401,
message: "Guest don't have history",
});
}
const ret = await updateProgress(profilePk, body);
return status(ret.status, ret);
@ -465,6 +471,7 @@ export const historyH = new Elysia({ tags: ["profiles"] })
description: "The number of history entry inserted",
}),
}),
401: { ...KError, description: "Non logged users don't have history" },
404: {
...KError,
description: "No entry found with the given id or slug.",

View File

@ -3,6 +3,9 @@ import { db } from "~/db";
import { profiles } from "~/db/schema";
export async function getOrCreateProfile(userId: string) {
// id of the guest user
if (userId === "00000000-0000-0000-0000-000000000000") return null;
let [profile] = await db
.select({ pk: profiles.pk })
.from(profiles)

View File

@ -34,21 +34,17 @@ import {
} from "~/models/watchlist";
import { getOrCreateProfile } from "./profile";
console.log();
async function setWatchStatus({
show,
status,
userId,
userPk,
}: {
show:
| { pk: number; kind: "movie" }
| { pk: number; kind: "serie"; entriesCount: number };
status: SeedSerieWatchStatus;
userId: string;
userPk: number;
}) {
const profilePk = await getOrCreateProfile(userId);
const firstEntryQ = db
.select({ pk: entries.pk })
.from(entries)
@ -61,7 +57,7 @@ async function setWatchStatus({
.values({
...status,
startedAt: coalesce(sql`${status.startedAt ?? null}`, sql`now()`),
profilePk: profilePk,
profilePk: userPk,
seenCount:
status.status === "completed"
? show.kind === "movie"
@ -269,6 +265,14 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
.post(
"/series/:id/watchstatus",
async ({ params: { id }, body, jwt: { sub }, status }) => {
const profilePk = await getOrCreateProfile(sub);
if (!profilePk) {
return status(401, {
status: 401,
message: "Guest can't set watchstatus",
});
}
const [show] = await db
.select({ pk: shows.pk, entriesCount: shows.entriesCount })
.from(shows)
@ -287,7 +291,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
}
return await setWatchStatus({
show: { pk: show.pk, kind: "serie", entriesCount: show.entriesCount },
userId: sub,
userPk: profilePk,
status: body,
});
},
@ -302,6 +306,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
body: SeedSerieWatchStatus,
response: {
200: t.Intersect([SerieWatchStatus, DbMetadata]),
401: { ...KError, description: "Guest can't set their watchstatus" },
404: KError,
},
permissions: ["core.read"],
@ -310,6 +315,14 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
.post(
"/movies/:id/watchstatus",
async ({ params: { id }, body, jwt: { sub }, status }) => {
const profilePk = await getOrCreateProfile(sub);
if (!profilePk) {
return status(401, {
status: 401,
message: "Guest can't set watchstatus",
});
}
const [show] = await db
.select({ pk: shows.pk })
.from(shows)
@ -329,7 +342,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
return await setWatchStatus({
show: { pk: show.pk, kind: "movie" },
userId: sub,
userPk: profilePk,
status: {
...body,
startedAt: body.completedAt,
@ -347,6 +360,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
body: SeedMovieWatchStatus,
response: {
200: t.Intersect([MovieWatchStatus, DbMetadata]),
401: { ...KError, description: "Guest can't set their watchstatus" },
404: KError,
},
permissions: ["core.read"],
@ -356,6 +370,12 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
"/series/:id/watchstatus",
async ({ params: { id }, jwt: { sub }, status }) => {
const profilePk = await getOrCreateProfile(sub);
if (!profilePk) {
return status(401, {
status: 401,
message: "Guest can't set watchstatus",
});
}
const rows = await db.execute(sql`
delete from ${watchlist} using ${shows}
@ -396,6 +416,12 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
"/movies/:id/watchstatus",
async ({ params: { id }, jwt: { sub }, status }) => {
const profilePk = await getOrCreateProfile(sub);
if (!profilePk) {
return status(401, {
status: 401,
message: "Guest can't set watchstatus",
});
}
const rows = await db.execute(sql`
delete from ${watchlist} using ${shows}
@ -428,6 +454,7 @@ export const watchlistH = new Elysia({ tags: ["profiles"] })
}),
response: {
200: t.Intersect([MovieWatchStatus, DbMetadata]),
401: { ...KError, description: "Guest can't set their watchstatus" },
404: KError,
},
permissions: ["core.read"],

View File

@ -26,6 +26,14 @@ const actionMap = {
permissions: ["core.read"],
async message(ws, body) {
const profilePk = await getOrCreateProfile(ws.data.jwt.sub);
if (!profilePk) {
ws.send({
action: "watch",
status: 401,
message: "Guest can't set watchstatus",
});
return;
}
const ret = await updateProgress(profilePk, [
{ ...body, playedDate: null },

View File

@ -44,16 +44,16 @@ func (h *Handler) CreateJwt(c echo.Context) error {
var token string
if auth == "" {
cookie, _ := c.Request().Cookie("X-Bearer")
if cookie != nil {
token = cookie.Value
protocol, ok := c.Request().Header["Sec-Websocket-Protocol"]
if ok &&
len(protocol) == 2 &&
protocol[0] == "kyoo" &&
strings.HasPrefix(protocol[1], "Bearer ") {
token = protocol[1][len("Bearer "):]
} else {
protocol, ok := c.Request().Header["Sec-Websocket-Protocol"]
if ok &&
len(protocol) == 2 &&
protocol[0] == "kyoo" &&
strings.HasPrefix(protocol[1], "Bearer") {
token = protocol[1][len("Bearer "):]
cookie, _ := c.Request().Cookie("X-Bearer")
if cookie != nil {
token = cookie.Value
}
}
} else if strings.HasPrefix(auth, "Bearer ") {

View File

@ -66,6 +66,7 @@ data:
- "Authorization"
- "X-Api-Key"
- "Cookie"
- "Sec-WebSocket-Protocol"
authResponseHeaders:
- Authorization
services:

View File

@ -23,7 +23,7 @@ x-transcoder: &transcoder-base
- "traefik.http.routers.transcoder.rule=PathPrefix(`/video`)"
- "traefik.http.routers.transcoder.middlewares=phantom-token"
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key,Sec-WebSocket-Protocol"
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
develop:
watch:
@ -95,7 +95,7 @@ services:
- "traefik.http.routers.api.rule=PathPrefix(`/api/`)"
- "traefik.http.routers.api.middlewares=phantom-token"
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key,Sec-WebSocket-Protocol"
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
develop:
watch:
@ -131,7 +131,7 @@ services:
- "traefik.http.routers.scanner.rule=PathPrefix(`/scanner/`)"
- "traefik.http.routers.scanner.middlewares=phantom-token"
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key,Sec-WebSocket-Protocol"
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
command: fastapi dev scanner --host 0.0.0.0 --port 4389
develop:

View File

@ -19,7 +19,7 @@ x-transcoder: &transcoder-base
- "traefik.http.routers.transcoder.rule=PathPrefix(`/video`)"
- "traefik.http.routers.transcoder.middlewares=phantom-token"
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key,Sec-WebSocket-Protocol"
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
services:
@ -65,7 +65,7 @@ services:
- "traefik.http.routers.api.rule=PathPrefix(`/api/`)"
- "traefik.http.routers.api.middlewares=phantom-token"
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key,Sec-WebSocket-Protocol"
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
scanner:
@ -88,7 +88,7 @@ services:
- "traefik.http.routers.scanner.rule=PathPrefix(`/scanner/`)"
- "traefik.http.routers.scanner.middlewares=phantom-token"
- "traefik.http.middlewares.phantom-token.forwardauth.address=http://auth:4568/auth/jwt"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key"
- "traefik.http.middlewares.phantom-token.forwardauth.authRequestHeaders=Authorization,Cookie,X-Api-Key,Sec-WebSocket-Protocol"
- "traefik.http.middlewares.phantom-token.forwardauth.authResponseHeaders=Authorization"
transcoder:

View File

@ -1,4 +1,5 @@
import { useEffect } from "react";
import { Platform } from "react-native";
import useWebSocket from "react-use-websocket";
import { useToken } from "~/providers/account-context";
@ -9,13 +10,17 @@ export const useWebsockets = ({
}) => {
const { apiUrl, authToken } = useToken();
const ret = useWebSocket(`${apiUrl}/api/ws`, {
protocols: authToken ? ["kyoo", `Bearer ${authToken}`] : undefined,
// on web use cookies, firefox doesn't like protocols idk why
protocols:
Platform.OS !== "web" && authToken
? ["kyoo", `Bearer ${authToken}`]
: ["kyoo"],
filter: (msg) => filterActions.includes(msg.data.action),
share: true,
retryOnError: true,
heartbeat: {
message: `{ "action": "ping" }`,
returnMessage: `{ "response": "pong" }`,
message: `{"action": "ping"}`,
returnMessage: `{"action":"ping","response":"pong"}`,
interval: 25_000,
},
});