chore: check permissions for various clan requests (#1175)
All checks were successful
Build / build (18) (push) Successful in 45s
Build / build (20) (push) Successful in 1m4s
Build / build (22) (push) Successful in 41s
Build Docker image / docker (push) Successful in 51s

Reviewed-on: #1175
This commit is contained in:
Sainan 2025-03-14 07:09:28 -07:00
parent 236cccc137
commit 0facdd1af9
16 changed files with 216 additions and 58 deletions

View File

@ -1,14 +1,34 @@
import { getDojoClient, getGuildForRequestEx, removeDojoDeco, removeDojoRoom } from "@/src/services/guildService";
import {
getDojoClient,
getGuildForRequestEx,
hasAccessToDojo,
hasGuildPermission,
removeDojoDeco,
removeDojoRoom
} from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const abortDojoComponentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
const request = JSON.parse(String(req.body)) as IAbortDojoComponentRequest;
if (
!hasAccessToDojo(inventory) ||
!(await hasGuildPermission(
guild,
accountId,
request.DecoId ? GuildPermission.Decorator : GuildPermission.Architect
))
) {
res.json({ DojoRequestStatus: -1 });
return;
}
if (request.DecoId) {
removeDojoDeco(guild, request.ComponentId, request.DecoId);
} else {

View File

@ -1,8 +1,17 @@
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const abortDojoComponentDestructionController: RequestHandler = async (req, res) => {
const guild = await getGuildForRequest(req);
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const componentId = req.query.componentId as string;
guild.DojoComponents.id(componentId)!.DestructionTime = undefined;

View File

@ -1,11 +1,11 @@
import { Guild, GuildMember } from "@/src/models/guildModel";
import { Account } from "@/src/models/loginModel";
import { fillInInventoryDataForGuildMember } from "@/src/services/guildService";
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
import { createMessage } from "@/src/services/inboxService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { IOid } from "@/src/types/commonTypes";
import { IGuildMemberClient } from "@/src/types/guildTypes";
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
import { ExportFlavour } from "warframe-public-export-plus";
@ -19,7 +19,10 @@ export const addToGuildController: RequestHandler = async (req, res) => {
}
const guild = (await Guild.findOne({ _id: payload.GuildId.$oid }, "Name"))!;
// TODO: Check sender is allowed to send invites for this guild.
const senderAccount = await getAccountForRequest(req);
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
res.status(400).json("Invalid permission");
}
if (
await GuildMember.exists({
@ -37,7 +40,6 @@ export const addToGuildController: RequestHandler = async (req, res) => {
status: 2 // outgoing invite
});
const senderAccount = await getAccountForRequest(req);
const senderInventory = await getInventory(senderAccount._id.toString(), "ActiveAvatarImageType");
await createMessage(account._id.toString(), [
{

View File

@ -1,12 +1,19 @@
import { RequestHandler } from "express";
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { logger } from "@/src/utils/logger";
import { IDojoComponentDatabase } from "@/src/types/guildTypes";
import { GuildPermission, IDojoComponentDatabase } from "@/src/types/guildTypes";
import { Types } from "mongoose";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
export const changeDojoRootController: RequestHandler = async (req, res) => {
const guild = await getGuildForRequest(req);
// At this point, we know that a member of the guild is making this request. Assuming they are allowed to change the root.
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const idToNode: Record<string, INode> = {};
guild.DojoComponents.forEach(x => {

View File

@ -1,28 +1,38 @@
import { GuildMember } from "@/src/models/guildModel";
import { getGuildForRequest, hasGuildPermissionEx } from "@/src/services/guildService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const changeGuildRankController: RequestHandler = async (req, res) => {
// TODO: Verify permissions
const guildMember = (await GuildMember.findOne({
const accountId = await getAccountIdForRequest(req);
const member = (await GuildMember.findOne({
accountId: accountId,
guildId: req.query.guildId as string
}))!;
const newRank: number = parseInt(req.query.rankChange as string);
const guild = await getGuildForRequest(req);
if (newRank < member.rank || !hasGuildPermissionEx(guild, member, GuildPermission.Promoter)) {
res.status(400).json("Invalid permission");
return;
}
const target = (await GuildMember.findOne({
guildId: req.query.guildId as string,
accountId: req.query.targetId as string
}))!;
guildMember.rank = parseInt(req.query.rankChange as string);
await guildMember.save();
target.rank = parseInt(req.query.rankChange as string);
await target.save();
if (guildMember.rank == 0) {
if (newRank == 0) {
// If we just promoted someone else to Founding Warlord, we need to demote ourselves to Warlord.
await GuildMember.findOneAndUpdate(
{
guildId: req.query.guildId as string,
accountId: req.query.accountId as string
},
{ rank: 1 }
);
member.rank = 1;
await member.save();
}
res.json({
_id: req.query.targetId as string,
Rank: guildMember.rank
Rank: newRank
});
};

View File

@ -3,6 +3,7 @@ import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/invento
import {
getDojoClient,
getGuildForRequestEx,
hasAccessToDojo,
processDojoBuildMaterialsGathered,
scaleRequiredCount,
setDojoRoomLogFunded
@ -28,8 +29,12 @@ interface IContributeToDojoComponentRequest {
export const contributeToDojoComponentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const guild = await getGuildForRequestEx(req, inventory);
// Any clan member should have permission to contribute although notably permission is denied if they have not crafted the dojo key and were simply invited in.
if (!hasAccessToDojo(inventory)) {
res.json({ DojoRequestStatus: -1 });
return;
}
const guild = await getGuildForRequestEx(req, inventory);
const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
const component = guild.DojoComponents.id(request.ComponentId)!;

View File

@ -1,11 +1,16 @@
import { getGuildForRequest } from "@/src/services/guildService";
import { IGuildRank } from "@/src/types/guildTypes";
import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission, IGuildRank } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const customizeGuildRanksController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const guild = await getGuildForRequest(req);
const payload = JSON.parse(String(req.body)) as ICustomizeGuildRanksRequest;
// TODO: Verify permissions
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Ruler))) {
res.status(400).json("Invalid permission");
return;
}
guild.Ranks = payload.GuildRanks;
await guild.save();
res.end();

View File

@ -1,8 +1,23 @@
import { getDojoClient, getGuildForRequest, removeDojoDeco } from "@/src/services/guildService";
import {
getDojoClient,
getGuildForRequestEx,
hasAccessToDojo,
hasGuildPermission,
removeDojoDeco
} from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const destroyDojoDecoController: RequestHandler = async (req, res) => {
const guild = await getGuildForRequest(req);
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest;
removeDojoDeco(guild, request.ComponentId, request.DecoId);

View File

@ -1,4 +1,4 @@
import { getDojoClient, getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IDojoContributable } from "@/src/types/guildTypes";
@ -17,6 +17,10 @@ interface IDojoComponentRushRequest {
export const dojoComponentRushController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
if (!hasAccessToDojo(inventory)) {
res.json({ DojoRequestStatus: -1 });
return;
}
const guild = await getGuildForRequestEx(req, inventory);
const request = JSON.parse(String(req.body)) as IDojoComponentRushRequest;
const component = guild.DojoComponents.id(request.ComponentId)!;

View File

@ -1,5 +1,11 @@
import { RequestHandler } from "express";
import { getGuildForRequestEx, getGuildVault, scaleRequiredCount } from "@/src/services/guildService";
import {
getGuildForRequestEx,
getGuildVault,
hasAccessToDojo,
hasGuildPermission,
scaleRequiredCount
} from "@/src/services/guildService";
import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "@/src/services/loginService";
import {
@ -13,7 +19,7 @@ import {
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { config } from "@/src/services/configService";
import { ITechProjectClient, ITechProjectDatabase } from "@/src/types/guildTypes";
import { GuildPermission, ITechProjectClient, ITechProjectDatabase } from "@/src/types/guildTypes";
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
@ -48,6 +54,10 @@ export const guildTechController: RequestHandler = async (req, res) => {
}
res.json({ TechProjects: techProjects });
} else if (action == "Start") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
res.status(400).send("-1").end();
return;
}
const recipe = ExportDojoRecipes.research[data.RecipeType!];
guild.TechProjects ??= [];
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
@ -71,6 +81,10 @@ export const guildTechController: RequestHandler = async (req, res) => {
await guild.save();
res.end();
} else if (action == "Contribute") {
if (!hasAccessToDojo(inventory)) {
res.status(400).send("-1").end();
return;
}
const contributions = data as IGuildTechContributeFields;
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
@ -133,9 +147,12 @@ export const guildTechController: RequestHandler = async (req, res) => {
Vault: getGuildVault(guild)
});
} else if (action == "Buy") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
res.status(400).send("-1").end();
return;
}
const purchase = data as IGuildTechBuyFields;
const quantity = parseInt(data.Action.split(",")[1]);
const inventory = await getInventory(accountId);
const recipeChanges = [
{
ItemType: purchase.RecipeType,
@ -157,9 +174,12 @@ export const guildTechController: RequestHandler = async (req, res) => {
}
});
} else if (action == "Fabricate") {
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
res.status(400).send("-1").end();
return;
}
const payload = data as IGuildTechFabricateRequest;
const recipe = ExportDojoRecipes.fabrications[payload.RecipeType];
const inventory = await getInventory(accountId);
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, recipe.price, false);
inventoryChanges.MiscItems = recipe.ingredients.map(x => ({
ItemType: x.ItemType,

View File

@ -1,12 +1,20 @@
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
import { Types } from "mongoose";
import { ExportDojoRecipes } from "warframe-public-export-plus";
export const placeDecoInComponentController: RequestHandler = async (req, res) => {
const guild = await getGuildForRequest(req);
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Decorator))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const request = JSON.parse(String(req.body)) as IPlaceDecoInComponentRequest;
// At this point, we know that a member of the guild is making this request. Assuming they are allowed to place decorations.
const component = guild.DojoComponents.id(request.ComponentId)!;
if (component.DecoCapacity === undefined) {

View File

@ -1,9 +1,18 @@
import { config } from "@/src/services/configService";
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const queueDojoComponentDestructionController: RequestHandler = async (req, res) => {
const guild = await getGuildForRequest(req);
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const componentId = req.query.componentId as string;
guild.DojoComponents.id(componentId)!.DestructionTime = new Date(

View File

@ -1,15 +1,20 @@
import { GuildMember } from "@/src/models/guildModel";
import { Account } from "@/src/models/loginModel";
import { getGuildForRequest } from "@/src/services/guildService";
import { getGuildForRequest, hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const removeFromGuildController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const guild = await getGuildForRequest(req);
// TODO: Check permissions
const payload = JSON.parse(String(req.body)) as IRemoveFromGuildRequest;
const isKick = !account._id.equals(payload.userId);
if (isKick && !(await hasGuildPermission(guild, account._id, GuildPermission.Regulator))) {
res.status(400).json("Invalid permission");
return;
}
const guildMember = (await GuildMember.findOne({ accountId: payload.userId, guildId: guild._id }))!;
if (guildMember.status == 0) {
@ -36,21 +41,19 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
await GuildMember.deleteOne({ _id: guildMember._id });
guild.RosterActivity ??= [];
if (account._id.equals(payload.userId)) {
// Leave
guild.RosterActivity.push({
dateTime: new Date(),
entryType: 7,
details: getSuffixedName(account)
});
} else {
// Kick
if (isKick) {
const kickee = (await Account.findOne({ _id: payload.userId }))!;
guild.RosterActivity.push({
dateTime: new Date(),
entryType: 12,
details: getSuffixedName(kickee) + "," + getSuffixedName(account)
});
} else {
guild.RosterActivity.push({
dateTime: new Date(),
entryType: 7,
details: getSuffixedName(account)
});
}
await guild.save();

View File

@ -1,13 +1,18 @@
import { Guild } from "@/src/models/guildModel";
import { hasGuildPermission } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { RequestHandler } from "express";
export const setGuildMotdController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const inventory = await getInventory(account._id.toString());
const inventory = await getInventory(account._id.toString(), "GuildId");
const guild = (await Guild.findOne({ _id: inventory.GuildId! }))!;
// TODO: Check permissions
if (!(await hasGuildPermission(guild, account._id, GuildPermission.Herald))) {
res.status(400).json("Invalid permission");
return;
}
const IsLongMOTD = "longMOTD" in req.query;
const MOTD = req.body ? String(req.body) : undefined;

View File

@ -1,14 +1,18 @@
import { RequestHandler } from "express";
import { IDojoComponentClient } from "@/src/types/guildTypes";
import { GuildPermission, IDojoComponentClient } from "@/src/types/guildTypes";
import {
getDojoClient,
getGuildForRequest,
getGuildForRequestEx,
hasAccessToDojo,
hasGuildPermission,
processDojoBuildMaterialsGathered,
setDojoRoomLogFunded
} from "@/src/services/guildService";
import { Types } from "mongoose";
import { ExportDojoRecipes } from "warframe-public-export-plus";
import { config } from "@/src/services/configService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
interface IStartDojoRecipeRequest {
PlacedComponent: IDojoComponentClient;
@ -16,8 +20,13 @@ interface IStartDojoRecipeRequest {
}
export const startDojoRecipeController: RequestHandler = async (req, res) => {
const guild = await getGuildForRequest(req);
// At this point, we know that a member of the guild is making this request. Assuming they are allowed to start a build.
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "GuildId LevelKeys");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
res.json({ DojoRequestStatus: -1 });
return;
}
const request = JSON.parse(String(req.body)) as IStartDojoRecipeRequest;
const room = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == request.PlacedComponent.pf);

View File

@ -4,6 +4,7 @@ import { addRecipes, getInventory } from "@/src/services/inventoryService";
import { Guild, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import {
GuildPermission,
IDojoClient,
IDojoComponentClient,
IDojoComponentDatabase,
@ -11,6 +12,7 @@ import {
IDojoDecoClient,
IGuildClient,
IGuildMemberClient,
IGuildMemberDatabase,
IGuildVault
} from "@/src/types/guildTypes";
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
@ -322,3 +324,28 @@ export const createUniqueClanName = async (name: string): Promise<string> => {
} while (discriminator != initialDiscriminator);
throw new Error(`clan name is so unoriginal it's already been done 1000 times: ${name}`);
};
export const hasAccessToDojo = (inventory: TInventoryDatabaseDocument): boolean => {
return inventory.LevelKeys.find(x => x.ItemType == "/Lotus/Types/Keys/DojoKey") !== undefined;
};
export const hasGuildPermission = async (
guild: TGuildDatabaseDocument,
accountId: string | Types.ObjectId,
perm: GuildPermission
): Promise<boolean> => {
const member = await GuildMember.findOne({ accountId: accountId, guildId: guild._id });
if (member) {
return hasGuildPermissionEx(guild, member, perm);
}
return false;
};
export const hasGuildPermissionEx = (
guild: TGuildDatabaseDocument,
member: IGuildMemberDatabase,
perm: GuildPermission
): boolean => {
const rank = guild.Ranks[member.rank];
return (rank.Permissions & perm) != 0;
};