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. */
|
/** 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
|
const [row] = await db
|
||||||
.select({ files: soulseekBrowseDirs.files })
|
.select({ files: soulseekBrowseDirs.files })
|
||||||
.from(soulseekBrowseDirs)
|
.from(soulseekBrowseDirs)
|
||||||
.innerJoin(soulseekBrowseSnapshots, eq(soulseekBrowseDirs.snapshotId, soulseekBrowseSnapshots.id))
|
.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);
|
.limit(1);
|
||||||
return row ? (row.files as BrowsedFile[]) : null;
|
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();
|
if (req.method !== 'GET') return methodNotAllowed();
|
||||||
const dirId = Number(segments[3]);
|
const dirId = Number(segments[3]);
|
||||||
if (!Number.isInteger(dirId) || dirId <= 0) return badRequest('invalid directory id');
|
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();
|
return files ? Response.json(files) : notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user