soulseek: scope cached folder reads to their peer
Probing the live routes turned up that /browse/<peer>/dirs/<id>/files only checked the owner, not the peer: dir ids are global, so asking for one peer's folder id under a different peer's name returned the other peer's files with a 200. The UI always sends a matching pair so nothing misbehaved, but the URL was asserting a relationship the query never verified — a mismatched or stale request would show the wrong peer's contents rather than a 404. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -192,12 +192,26 @@ export async function getSoulseekBrowseDirs(params: BrowseDirPageParams): Promis
|
||||
}
|
||||
|
||||
/** One cached folder's files. Scoped by user so a guessed dir id can't read another account's cache. */
|
||||
export async function getSoulseekBrowseDirFiles(userId: number, dirId: number): Promise<BrowsedFile[] | null> {
|
||||
type DirFilesParams = { userId: number; username: string; dirId: number };
|
||||
|
||||
// Joined and scoped by BOTH owner and peer: a dir id alone would happily return another peer's folder
|
||||
// (ids are global), so the username in the request URL has to be part of the predicate, not decoration.
|
||||
export async function getSoulseekBrowseDirFiles({
|
||||
userId,
|
||||
username,
|
||||
dirId,
|
||||
}: DirFilesParams): Promise<BrowsedFile[] | null> {
|
||||
const [row] = await db
|
||||
.select({ files: soulseekBrowseDirs.files })
|
||||
.from(soulseekBrowseDirs)
|
||||
.innerJoin(soulseekBrowseSnapshots, eq(soulseekBrowseDirs.snapshotId, soulseekBrowseSnapshots.id))
|
||||
.where(and(eq(soulseekBrowseDirs.id, dirId), eq(soulseekBrowseSnapshots.userId, userId)))
|
||||
.where(
|
||||
and(
|
||||
eq(soulseekBrowseDirs.id, dirId),
|
||||
eq(soulseekBrowseSnapshots.userId, userId),
|
||||
eq(soulseekBrowseSnapshots.username, username),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
return row ? (row.files as BrowsedFile[]) : null;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ async function handleBrowse({ req, url, userId, segments }: BrowseRouteParams):
|
||||
if (req.method !== 'GET') return methodNotAllowed();
|
||||
const dirId = Number(segments[3]);
|
||||
if (!Number.isInteger(dirId) || dirId <= 0) return badRequest('invalid directory id');
|
||||
const files = await getSoulseekBrowseDirFiles(userId, dirId);
|
||||
const files = await getSoulseekBrowseDirFiles({ userId, username, dirId });
|
||||
return files ? Response.json(files) : notFound();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user