Files
homarr/packages/api/src/router/app/app-access-control.ts
T
Meier Lukasandajnart 8811af5e6e Merge commit from fork
* fix(security): resolve ssrf issue with simple ping

* refactor: improve testability of app-access-control and fix tests

* fix: unnecessary space

Co-authored-by: ajnart <thomas@ajnart.fr>

---------

Co-authored-by: ajnart <thomas@ajnart.fr>
2026-01-30 18:31:13 +01:00

53 lines
1.5 KiB
TypeScript

import SuperJSON from "superjson";
import type { Session } from "@homarr/auth";
import type { Database } from "@homarr/db";
import { eq, or } from "@homarr/db";
import { items } from "@homarr/db/schema";
import type { WidgetComponentProps } from "../../../../widgets/src";
export class AppAccessControl {
constructor(
private db: Database,
private user: Session["user"] | null,
) {}
async canUserSeeAppAsync(appId: string) {
return await this.canUserSeeAppsAsync([appId]);
}
async canUserSeeAppsAsync(appIds: string[]) {
// Currently any user can see all apps if they are logged in
if (this.user) return true;
const appIdsOnPublicBoards = await this.getAllAppIdsOnPublicBoardsAsync();
return appIds.every((appId) => appIdsOnPublicBoards.includes(appId));
}
private async getAllAppIdsOnPublicBoardsAsync() {
const itemsWithApps = await this.db.query.items.findMany({
where: or(eq(items.kind, "app"), eq(items.kind, "bookmarks")),
with: {
board: {
columns: {
isPublic: true,
},
},
},
});
return itemsWithApps
.filter((item) => item.board.isPublic)
.flatMap((item) => {
if (item.kind === "app") {
const parsedOptions = SuperJSON.parse<WidgetComponentProps<"app">["options"]>(item.options);
return [parsedOptions.appId];
}
const parsedOptions = SuperJSON.parse<WidgetComponentProps<"bookmarks">["options"]>(item.options);
return parsedOptions.items;
});
}
}