feat(rbac): migrate server actions and service layers to RBAC
Replace hasRoles calls with hasPermission in all server actions and page components. Update qualification checks (logistics, intelligence) to use permission-based checks instead of role name matching. Update staff lookup in tickets/staff.ts to query roles collection. Use enlistmentDate field on profile page instead of createdAt.
This commit is contained in:
parent
653caa8064
commit
eef1b11bb1
17 changed files with 181 additions and 97 deletions
|
|
@ -144,11 +144,18 @@ export async function requestDiscordUsernameChange(input: {
|
|||
return { success: false, error: "That Discord username is already in use." };
|
||||
}
|
||||
|
||||
const staffRoles = await payload.find({
|
||||
collection: "roles",
|
||||
where: { slug: { in: ["admin", "developer"] } },
|
||||
limit: 2,
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
});
|
||||
const staffRoleIds = staffRoles.docs.map((d) => d.id);
|
||||
if (staffRoleIds.length > 0) {
|
||||
const staffRes = await payload.find({
|
||||
collection: "users",
|
||||
where: {
|
||||
or: [{ roles: { contains: "admin" } }, { roles: { contains: "developer" } }],
|
||||
},
|
||||
where: { roleDocs: { in: staffRoleIds } },
|
||||
limit: 100,
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
|
|
@ -165,6 +172,7 @@ export async function requestDiscordUsernameChange(input: {
|
|||
link: `/admin/collections/users/${userId}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await notifyUser(payload, {
|
||||
userId,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { notFound, redirect } from "next/navigation";
|
|||
import { getPayload } from "payload";
|
||||
import type { Ticket } from "@/payload-types";
|
||||
import { TicketDetail } from "@/components/frontend/helpdesk/TicketDetail";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { ArrowLeftIcon, LifeBuoyIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
|
|
@ -18,14 +18,16 @@ interface TicketPageProps {
|
|||
|
||||
export default async function TicketPage({ params }: TicketPageProps) {
|
||||
const { id } = await params;
|
||||
const headers = await nextHeaders();
|
||||
const payload = await getPayload({ config });
|
||||
const { user } = await payload.auth({
|
||||
headers: await nextHeaders(),
|
||||
headers,
|
||||
canSetHeaders: false,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
redirect("/login");
|
||||
const pathname = headers.get("x-invoke-path") || `/helpdesk/${id}`;
|
||||
redirect(`/login?returnTo=${encodeURIComponent(pathname)}`);
|
||||
}
|
||||
|
||||
const ticket = await payload
|
||||
|
|
@ -41,7 +43,7 @@ export default async function TicketPage({ params }: TicketPageProps) {
|
|||
}
|
||||
|
||||
const typedTicket = ticket as unknown as Ticket;
|
||||
const isStaff = hasRoles(["admin", "developer"], user);
|
||||
const isStaff = await hasPermission(payload, user, "tickets:staff");
|
||||
const assigneeOptions = isStaff
|
||||
? (
|
||||
await payload.find({
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import config from "@payload-config";
|
||||
import { getPayload } from "payload";
|
||||
import type { Ticket } from "@/payload-types";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { emitGameEvent } from "@/utils/event-log/emit";
|
||||
import { EventTypes } from "@/utils/event-log/eventTypes";
|
||||
import { notifyUser } from "@/lib/notifications";
|
||||
|
|
@ -144,7 +144,7 @@ export async function replyToTicket(ticketId: number, content: string): Promise<
|
|||
const userId = user.id as number;
|
||||
const ticket = await getTicketOrThrow(payload, ticketId);
|
||||
|
||||
const isStaff = hasRoles(["admin", "developer"], user);
|
||||
const isStaff = await hasPermission(payload, user, "tickets:staff");
|
||||
const reporterId = reporterIdOf(ticket);
|
||||
if (!isStaff && reporterId !== userId) {
|
||||
throw new Error("You don't have access to this ticket.");
|
||||
|
|
@ -241,7 +241,7 @@ export async function updateTicketStatus(
|
|||
const ticket = await getTicketOrThrow(payload, ticketId);
|
||||
if (status === ticket.status) return { success: true };
|
||||
|
||||
const isStaff = hasRoles(["admin", "developer"], user);
|
||||
const isStaff = await hasPermission(payload, user, "tickets:staff");
|
||||
const reporterId = reporterIdOf(ticket);
|
||||
if (!isStaff) {
|
||||
const reporterCancel =
|
||||
|
|
@ -299,7 +299,7 @@ export async function assignTicket(
|
|||
const { payload, user } = await authenticate();
|
||||
const userId = user.id as number;
|
||||
|
||||
if (!hasRoles(["admin", "developer"], user)) {
|
||||
if (!(await hasPermission(payload, user, "tickets:staff"))) {
|
||||
throw new Error("Only staff can assign tickets.");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { redirect } from "next/navigation";
|
|||
import { getPayload } from "payload";
|
||||
import type { Ticket } from "@/payload-types";
|
||||
import { TicketsView } from "@/components/frontend/helpdesk/TicketsView";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { LifeBuoyIcon } from "lucide-react";
|
||||
|
||||
export const metadata = {
|
||||
|
|
@ -12,14 +12,16 @@ export const metadata = {
|
|||
};
|
||||
|
||||
export default async function HelpdeskPage() {
|
||||
const headers = await nextHeaders();
|
||||
const payload = await getPayload({ config });
|
||||
const { user } = await payload.auth({
|
||||
headers: await nextHeaders(),
|
||||
headers,
|
||||
canSetHeaders: false,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
redirect("/login");
|
||||
const pathname = headers.get("x-invoke-path") || "/helpdesk";
|
||||
redirect(`/login?returnTo=${encodeURIComponent(pathname)}`);
|
||||
}
|
||||
|
||||
const ticketsRes = await payload.find({
|
||||
|
|
@ -29,7 +31,7 @@ export default async function HelpdeskPage() {
|
|||
depth: 2,
|
||||
});
|
||||
const tickets = ticketsRes.docs as unknown as Ticket[];
|
||||
const isStaff = hasRoles(["admin", "developer"], user);
|
||||
const isStaff = await hasPermission(payload, user, "tickets:staff");
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-5">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import config from "@payload-config";
|
||||
import { getPayload } from "payload";
|
||||
import type { Asset, Loadout, LockerStorage, User } from "@/payload-types";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { emitGameEvent } from "@/utils/event-log/emit";
|
||||
import { EventTypes } from "@/utils/event-log/eventTypes";
|
||||
import {
|
||||
|
|
@ -24,6 +24,7 @@ import {
|
|||
skinAppliesTo,
|
||||
toLockerGridItems,
|
||||
} from "@/lib/locker";
|
||||
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
|
||||
|
||||
export interface ActionResult<T = undefined> {
|
||||
success: boolean;
|
||||
|
|
@ -45,8 +46,9 @@ async function authenticate() {
|
|||
return { payload, user };
|
||||
}
|
||||
|
||||
function isLockerManager(user: User): boolean {
|
||||
return hasRoles(["admin", "developer"], user);
|
||||
async function isLockerManager(payload: Awaited<ReturnType<typeof getPayload>>, user: User): Promise<boolean> {
|
||||
if (await hasPermission(payload, user, "locker-storages:update")) return true;
|
||||
return hasLogisticsQualification(payload, user);
|
||||
}
|
||||
|
||||
async function getLockerWithItems(
|
||||
|
|
@ -114,7 +116,7 @@ export async function addItemToLocker(
|
|||
): Promise<ActionResult<LockerStorage>> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!isLockerManager(user)) {
|
||||
if (!(await hasPermission(payload, user, "locker-storages:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. Admin or Developer role required to add items.",
|
||||
|
|
@ -759,7 +761,7 @@ async function getOwnedLoadout(
|
|||
? (loadout.ownerUser as { id: number }).id
|
||||
: (loadout.ownerUser as number);
|
||||
|
||||
if (ownerId !== user.id && !isLockerManager(user)) return null;
|
||||
if (ownerId !== user.id && !(await hasPermission(payload, user, "locker-storages:update"))) return null;
|
||||
return loadout;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import type { Asset, Loadout, LockerStorage } from "@/payload-types";
|
|||
import { LockKeyholeIcon } from "lucide-react";
|
||||
import { ensureLockerStorage, getLockerGridDimensions } from "@/lib/locker";
|
||||
import { LockerView } from "@/components/frontend/locker/LockerView";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
|
||||
export const metadata = {
|
||||
title: "Locker — Polaris Task Force",
|
||||
|
|
@ -36,7 +36,7 @@ export default async function LockerPage() {
|
|||
});
|
||||
const loadouts = loadoutsRes.docs as unknown as Loadout[];
|
||||
|
||||
const isManager = user ? hasRoles(["admin", "developer"], user) : false;
|
||||
const isManager = user ? (await hasPermission(payload, user, "locker-storages:update")) : false;
|
||||
|
||||
let assetsCatalog: Asset[] = [];
|
||||
if (isManager) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { headers as nextHeaders } from "next/headers";
|
|||
import { notFound } from "next/navigation";
|
||||
import type { BankAccount, LedgerEntry } from "@/payload-types";
|
||||
import { AccountDetail } from "@/components/frontend/banking/AccountDetail";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
|
||||
|
||||
export const metadata = {
|
||||
|
|
@ -38,7 +38,7 @@ export default async function AccountPage({ params }: AccountPageProps) {
|
|||
const typedAccount = account as unknown as BankAccount;
|
||||
|
||||
const isManager = user
|
||||
? hasRoles(["admin", "developer"], user) ||
|
||||
? (await hasPermission(payload, user, "banking:manage")) ||
|
||||
(await hasLogisticsQualification(payload, user).catch(() => false))
|
||||
: false;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import config from "@payload-config";
|
||||
import { getPayload } from "payload";
|
||||
import type { User } from "@/payload-types";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
|
||||
import { emitGameEvent } from "@/utils/event-log/emit";
|
||||
import { EventTypes } from "@/utils/event-log/eventTypes";
|
||||
|
|
@ -42,7 +42,7 @@ async function isBankingManager(
|
|||
payload: Awaited<ReturnType<typeof getPayload>>,
|
||||
user: User,
|
||||
): Promise<boolean> {
|
||||
if (hasRoles(["admin", "developer"], user)) return true;
|
||||
if (await hasPermission(payload, user, "banking:manage")) return true;
|
||||
return hasLogisticsQualification(payload, user);
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ async function moveFunds(
|
|||
): Promise<ActionResult<number>> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "bank-accounts:create"))) {
|
||||
return { success: false, error: "Insufficient permissions." };
|
||||
}
|
||||
if (!amount || amount <= 0) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { headers as nextHeaders } from "next/headers";
|
|||
import type { BankAccount, BankTransaction, Faction, Resource } from "@/payload-types";
|
||||
import { LandmarkIcon } from "lucide-react";
|
||||
import { BankingOverview } from "@/components/frontend/banking/BankingOverview";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
|
||||
|
||||
export const metadata = {
|
||||
|
|
@ -45,7 +45,7 @@ export default async function BankingPage() {
|
|||
}));
|
||||
|
||||
const isManager = user
|
||||
? hasRoles(["admin", "developer"], user) ||
|
||||
? (await hasPermission(payload, user, "banking:manage")) ||
|
||||
(await hasLogisticsQualification(payload, user).catch(() => false))
|
||||
: false;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import config from "@payload-config";
|
||||
import { getPayload } from "payload";
|
||||
import type { Asset, LockerStorage, MarketListing, MarketNegotiation, User } from "@/payload-types";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { emitGameEvent } from "@/utils/event-log/emit";
|
||||
import { EventTypes } from "@/utils/event-log/eventTypes";
|
||||
import {
|
||||
|
|
@ -44,8 +44,8 @@ async function authenticate() {
|
|||
return { payload, user };
|
||||
}
|
||||
|
||||
function isMarketManager(user: User): boolean {
|
||||
return hasRoles(["admin", "developer"], user);
|
||||
async function isMarketManager(payload: Awaited<ReturnType<typeof getPayload>>, user: User): Promise<boolean> {
|
||||
return await hasPermission(payload, user, "market-listings:update");
|
||||
}
|
||||
|
||||
function sellerIdOf(listing: MarketListing): number | null {
|
||||
|
|
@ -467,7 +467,7 @@ export async function cancelMarketListing(listingId: number): Promise<ActionResu
|
|||
return { success: false, error: "Only active listings can be cancelled." };
|
||||
}
|
||||
const sellerId = sellerIdOf(listing);
|
||||
if (sellerId !== userId && !isMarketManager(user)) {
|
||||
if (sellerId !== userId && !(await isMarketManager(payload, user))) {
|
||||
return { success: false, error: "You can only cancel your own listings." };
|
||||
}
|
||||
if (listing.isAutoGenerated) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { headers as nextHeaders } from "next/headers";
|
|||
import type { LockerStorage, MarketListing, MarketNegotiation } from "@/payload-types";
|
||||
import { StoreIcon } from "lucide-react";
|
||||
import { MarketView } from "@/components/frontend/market/MarketView";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { ensureLockerStorage } from "@/lib/locker";
|
||||
import { getMainCurrencyName } from "@/lib/banking";
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ export default async function MarketPage() {
|
|||
return false;
|
||||
});
|
||||
|
||||
const isManager = user ? hasRoles(["admin", "developer"], user) : false;
|
||||
const isManager = user ? (await hasPermission(payload, user, "logistics:manage")) : false;
|
||||
const currencyLabel = await getMainCurrencyName(payload);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import config from "@payload-config";
|
||||
import { getPayload } from "payload";
|
||||
import type { GameStructure, GameVehicle, Resource, Structure, Vehicle } from "@/payload-types";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { emitGameEvent } from "@/utils/event-log/emit";
|
||||
import { EventTypes } from "@/utils/event-log/eventTypes";
|
||||
import { calculateDistance } from "@/lib/distance";
|
||||
|
|
@ -42,7 +42,7 @@ export async function createShipment(params: {
|
|||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
|
||||
if (!hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "shipments:create"))) {
|
||||
return { success: false, error: "Insufficient permissions." };
|
||||
}
|
||||
|
||||
|
|
@ -380,7 +380,7 @@ export async function cancelShipment(shipmentId: number): Promise<ActionResult>
|
|||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
|
||||
if (!hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "shipments:update"))) {
|
||||
return { success: false, error: "Insufficient permissions." };
|
||||
}
|
||||
|
||||
|
|
@ -501,7 +501,7 @@ export async function toggleAutoReturn(
|
|||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
|
||||
if (!hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "shipments:update"))) {
|
||||
return { success: false, error: "Insufficient permissions." };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import config from "@payload-config";
|
||||
import { getPayload } from "payload";
|
||||
import { GameStructure, Resource, Structure } from "@/payload-types";
|
||||
import hasRoles from "@/utils/access-control/hasRoles";
|
||||
import { hasPermission } from "@/utils/access-control/hasPermission";
|
||||
import { emitGameEvent } from "@/utils/event-log/emit";
|
||||
import { EventTypes } from "@/utils/event-log/eventTypes";
|
||||
import { checkStorageDeposit, storageViolationMessage } from "@/lib/storageRules";
|
||||
|
|
@ -74,10 +74,10 @@ export async function addResource(
|
|||
): Promise<ActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. Admin or Developer role required for deposit.",
|
||||
error: "Insufficient permissions. User, Admin, or Developer role required.",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +264,7 @@ export async function removeResource(
|
|||
): Promise<ActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. Admin or Developer role required for withdrawal.",
|
||||
|
|
@ -373,7 +373,7 @@ export async function transferResource(
|
|||
): Promise<ActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. User, Admin, or Developer role required for transfer.",
|
||||
|
|
@ -524,7 +524,7 @@ export async function placeResourceOnGrid(
|
|||
): Promise<GridActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. Admin or Developer role required for placement.",
|
||||
|
|
@ -641,7 +641,7 @@ export async function moveGridItem(
|
|||
): Promise<GridActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. User, Admin, or Developer role required.",
|
||||
|
|
@ -730,7 +730,7 @@ export async function mergeGridStacks(
|
|||
): Promise<GridActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. User, Admin, or Developer role required.",
|
||||
|
|
@ -812,7 +812,7 @@ export async function rotateGridItem(
|
|||
): Promise<GridActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. User, Admin, or Developer role required.",
|
||||
|
|
@ -899,7 +899,7 @@ export async function removeGridItem(
|
|||
): Promise<GridActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. Admin or Developer role required for removal.",
|
||||
|
|
@ -953,7 +953,7 @@ export async function splitGridStack(
|
|||
): Promise<GridActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. User, Admin, or Developer role required.",
|
||||
|
|
@ -1061,7 +1061,7 @@ export async function splitGridStack(
|
|||
export async function retrieveFromVoid(structureId: number): Promise<GridActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return { success: false, error: "Insufficient permissions." };
|
||||
}
|
||||
const structure = await getStructure(payload, structureId);
|
||||
|
|
@ -1159,7 +1159,7 @@ export async function retrieveFromVoid(structureId: number): Promise<GridActionR
|
|||
export async function autoArrangeGrid(structureId: number): Promise<GridActionResult> {
|
||||
try {
|
||||
const { payload, user } = await authenticate();
|
||||
if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
|
||||
if (!(await hasPermission(payload, user, "structures:update"))) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Insufficient permissions. User, Admin, or Developer role required.",
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||
const totalKills = infantryKills + vehicleKills + armorKills + airKills;
|
||||
const kdRatio = deaths > 0 ? (totalKills / deaths).toFixed(2) : totalKills > 0 ? "∞" : "—";
|
||||
|
||||
const enlistDate = new Date(profile.createdAt);
|
||||
const enlistDate = new Date(profile.dossier.enlistmentDate);
|
||||
const enlistDateStr = enlistDate.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
|
|
|
|||
|
|
@ -8,11 +8,19 @@ type PayloadType = Awaited<ReturnType<typeof import("payload").getPayload>>;
|
|||
|
||||
export async function getStaffUserIds(payload: PayloadType): Promise<number[]> {
|
||||
try {
|
||||
const adminRole = await payload.find({
|
||||
collection: "roles",
|
||||
where: { slug: { in: ["admin", "developer"] } },
|
||||
limit: 2,
|
||||
depth: 0,
|
||||
overrideAccess: true,
|
||||
});
|
||||
const roleIds = adminRole.docs.map((d) => d.id);
|
||||
if (roleIds.length === 0) return [];
|
||||
|
||||
const res = await payload.find({
|
||||
collection: "users",
|
||||
where: {
|
||||
or: [{ roles: { contains: "admin" } }, { roles: { contains: "developer" } }],
|
||||
},
|
||||
where: { roleDocs: { in: roleIds } },
|
||||
limit: 50,
|
||||
depth: 0,
|
||||
select: { username: true },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,26 @@
|
|||
import type { Payload } from "payload";
|
||||
import { hasAnyPermission } from "@/utils/access-control/hasPermission";
|
||||
|
||||
const INTELLIGENCE_PERMISSIONS = [
|
||||
"intelligence:manage",
|
||||
"missions:read",
|
||||
"missions:create",
|
||||
"missions:update",
|
||||
"missions:delete",
|
||||
"missions:read-sensitive",
|
||||
"campaigns:read",
|
||||
"campaigns:create",
|
||||
"campaigns:update",
|
||||
"campaigns:delete",
|
||||
"factions:read",
|
||||
"factions:create",
|
||||
"factions:update",
|
||||
"factions:delete",
|
||||
"technologies:read",
|
||||
"technologies:create",
|
||||
"technologies:update",
|
||||
"technologies:delete",
|
||||
] as const;
|
||||
|
||||
export async function hasIntelligenceQualification(
|
||||
payload: Payload,
|
||||
|
|
@ -6,10 +28,7 @@ export async function hasIntelligenceQualification(
|
|||
): Promise<boolean> {
|
||||
if (!user) return false;
|
||||
|
||||
const roles = user.roles as string[] | undefined;
|
||||
if (roles?.includes("developer") || roles?.includes("admin")) {
|
||||
return true;
|
||||
}
|
||||
if (await hasAnyPermission(payload, user, ...INTELLIGENCE_PERMISSIONS)) return true;
|
||||
|
||||
const profile = (await payload.find({
|
||||
collection: "profiles",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,55 @@
|
|||
import type { Payload } from "payload";
|
||||
import { hasAnyPermission } from "@/utils/access-control/hasPermission";
|
||||
|
||||
const LOGISTICS_PERMISSIONS = [
|
||||
"logistics:manage",
|
||||
"assets:read",
|
||||
"assets:create",
|
||||
"assets:update",
|
||||
"assets:delete",
|
||||
"resources:read",
|
||||
"resources:create",
|
||||
"resources:update",
|
||||
"resources:delete",
|
||||
"vehicles:read",
|
||||
"vehicles:create",
|
||||
"vehicles:update",
|
||||
"vehicles:delete",
|
||||
"structures:read",
|
||||
"structures:create",
|
||||
"structures:update",
|
||||
"structures:delete",
|
||||
"shipments:read",
|
||||
"shipments:create",
|
||||
"shipments:update",
|
||||
"shipments:delete",
|
||||
"bank-accounts:read",
|
||||
"bank-accounts:create",
|
||||
"bank-accounts:update",
|
||||
"bank-accounts:delete",
|
||||
"banking:manage",
|
||||
"market-listings:read",
|
||||
"market-listings:create",
|
||||
"market-listings:update",
|
||||
"market-listings:delete",
|
||||
"game-structures:read",
|
||||
"game-structures:create",
|
||||
"game-structures:update",
|
||||
"game-structures:delete",
|
||||
"game-vehicles:read",
|
||||
"game-vehicles:create",
|
||||
"game-vehicles:update",
|
||||
"game-vehicles:delete",
|
||||
"game-npcs:read",
|
||||
"game-npcs:create",
|
||||
"game-npcs:update",
|
||||
"game-npcs:delete",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Checks if a user has the Logistics qualification.
|
||||
* Queries the user's profile for qualifications matching "Logistics" (case-insensitive).
|
||||
* Developers and admins always pass.
|
||||
* Users with ANY logistics-domain RBAC permission always pass.
|
||||
*/
|
||||
export async function hasLogisticsQualification(
|
||||
payload: Payload,
|
||||
|
|
@ -11,10 +57,7 @@ export async function hasLogisticsQualification(
|
|||
): Promise<boolean> {
|
||||
if (!user) return false;
|
||||
|
||||
const roles = user.roles as string[] | undefined;
|
||||
if (roles?.includes("developer") || roles?.includes("admin")) {
|
||||
return true;
|
||||
}
|
||||
if (await hasAnyPermission(payload, user, ...LOGISTICS_PERMISSIONS)) return true;
|
||||
|
||||
const profileRes = await payload.find({
|
||||
collection: "profiles",
|
||||
|
|
|
|||
Loading…
Reference in a new issue