import { APITokenMode } from "~/prisma/client/enums"; import prisma from "../db/database"; import sessionHandler from "../session"; import type { MinimumRequestObject } from "~/server/h3"; export const userACLs = [ "read", "store:read", "object:read", "object:update", "object:delete", "notifications:read", "notifications:mark", "notifications:listen", "notifications:delete", "screenshots:new", "screenshots:read", "screenshots:delete", "collections:new", "collections:read", "collections:delete", "collections:add", "collections:remove", "library:add", "library:remove", "clients:read", "clients:revoke", "news:read", "settings:read", ] as const; const userACLPrefix = "user:"; export type UserACL = Array<(typeof userACLs)[number]>; export const systemACLs = [ "setup", "auth:read", "auth:simple:invitation:read", "auth:simple:invitation:new", "auth:simple:invitation:delete", "notifications:read", "notifications:mark", "notifications:listen", "notifications:delete", "library:read", "library:sources:read", "library:sources:new", "library:sources:update", "library:sources:delete", "game:read", "game:update", "game:delete", "game:version:update", "game:version:delete", "game:image:new", "game:image:delete", "company:read", "company:update", "company:create", "company:delete", "import:version:read", "import:version:new", "import:game:read", "import:game:new", "user:read", "user:delete", "news:read", "news:create", "news:delete", "tags:read", "tags:create", "tags:delete", "task:read", "task:start", "maintenance:read", "settings:update", ] as const; const systemACLPrefix = "system:"; export type SystemACL = Array<(typeof systemACLs)[number]>; export type GlobalACL = | `${typeof systemACLPrefix}${(typeof systemACLs)[number]}` | `${typeof userACLPrefix}${(typeof userACLs)[number]}`; class ACLManager { private getAuthorizationToken(request: MinimumRequestObject) { const [type, token] = request.headers.get("Authorization")?.split(" ") ?? []; if (!type || !token) return undefined; if (type != "Bearer") return undefined; return token; } /** * Get userId and require one of the specified acls * @param request * @param acls * @returns */ async getUserIdACL(request: MinimumRequestObject | undefined, acls: UserACL) { if (!request) throw new Error("Native web requests not available - weird deployment?"); // Sessions automatically have all ACLs const user = await sessionHandler.getSession(request); if (user) return user.userId; const authorizationToken = this.getAuthorizationToken(request); if (!authorizationToken) return undefined; const token = await prisma.aPIToken.findUnique({ where: { token: authorizationToken, mode: { in: [APITokenMode.User, APITokenMode.Client] }, }, }); if (!token) return undefined; if (!token.userId) throw new Error( "No userId on user or client token - is something broken?", ); for (const acl of acls) { const tokenACLIndex = token.acls.findIndex((e) => e == acl); if (tokenACLIndex != -1) return token.userId; } return undefined; } async getUserACL(request: MinimumRequestObject | undefined, acls: UserACL) { if (!request) throw new Error("Native web requests not available - weird deployment?"); const userId = await this.getUserIdACL(request, acls); if (!userId) return undefined; const user = await prisma.user.findUnique({ where: { id: userId } }); if (user) return user; return undefined; } async allowSystemACL( request: MinimumRequestObject | undefined, acls: SystemACL, ) { if (!request) throw new Error("Native web requests not available - weird deployment?"); const userSession = await sessionHandler.getSession(request); if (userSession) { const user = await prisma.user.findUnique({ where: { id: userSession.userId }, }); if (user) { if (!user) return false; if (user.admin) return true; return false; } } const authorizationToken = this.getAuthorizationToken(request); if (!authorizationToken) return false; const token = await prisma.aPIToken.findUnique({ where: { token: authorizationToken }, }); if (!token) return false; if (token.mode != APITokenMode.System) return false; // If empty, we just want to check we are an admin *at all*, not specific ACLs if (acls.length == 0) return true; for (const acl of acls) { const tokenACLIndex = token.acls.findIndex((e) => e == acl); if (tokenACLIndex != -1) return true; } return false; } async hasACL(request: MinimumRequestObject | undefined, acls: string[]) { for (const acl of acls) { if (acl.startsWith(userACLPrefix)) { const rawACL = acl.substring(userACLPrefix.length); const userId = await this.getUserIdACL(request, [ rawACL as UserACL[number], ]); if (!userId) return false; } if (acl.startsWith(systemACLPrefix)) { const rawACL = acl.substring(systemACLPrefix.length); const allowed = await this.allowSystemACL(request, [ rawACL as SystemACL[number], ]); if (!allowed) return false; } } return true; } async fetchAllACLs( request: MinimumRequestObject, ): Promise { const userSession = await sessionHandler.getSession(request); if (!userSession) { const authorizationToken = this.getAuthorizationToken(request); if (!authorizationToken) return undefined; const token = await prisma.aPIToken.findUnique({ where: { token: authorizationToken }, }); if (!token) return undefined; return token.acls as GlobalACL[]; } const user = await prisma.user.findUnique({ where: { id: userSession.userId }, select: { admin: true, }, }); if (!user) throw new Error("User session without user - did something break?"); const acls = userACLs.map((e) => `${userACLPrefix}${e}`); if (user.admin) { acls.push(...systemACLs.map((e) => `${systemACLPrefix}${e}`)); } return acls as GlobalACL[]; } } export const aclManager = new ACLManager(); export default aclManager;