feat: enforce character ownership — players own their characters, DMs can modify any
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
56d46166cd
commit
fae1e75f6f
1 changed files with 40 additions and 5 deletions
|
|
@ -5,6 +5,7 @@ import type { Server } from "socket.io";
|
|||
import db from "../db.js";
|
||||
import { broadcastToCampaign } from "../socket.js";
|
||||
import { parseJson } from "../utils/parseJson.js";
|
||||
import { requireAuth } from "../auth/middleware.js";
|
||||
|
||||
type CampaignParams = ParamsDictionary & { campaignId: string };
|
||||
|
||||
|
|
@ -29,6 +30,24 @@ function parseTalents(rows: RowDataPacket[]) {
|
|||
return rows.map((r) => ({ ...r, effect: parseJson(r.effect) }));
|
||||
}
|
||||
|
||||
async function canModifyCharacter(characterId: string, userId: number): Promise<boolean> {
|
||||
// DMs can modify any character in their campaign
|
||||
const [dmCheck] = await db.execute<RowDataPacket[]>(
|
||||
`SELECT cm.role FROM campaign_members cm
|
||||
JOIN characters c ON c.campaign_id = cm.campaign_id
|
||||
WHERE c.id = ? AND cm.user_id = ? AND cm.role = 'dm'`,
|
||||
[characterId, userId]
|
||||
);
|
||||
if (dmCheck.length > 0) return true;
|
||||
|
||||
// Players can only modify their own characters
|
||||
const [ownerCheck] = await db.execute<RowDataPacket[]>(
|
||||
"SELECT id FROM characters WHERE id = ? AND user_id = ?",
|
||||
[characterId, userId]
|
||||
);
|
||||
return ownerCheck.length > 0;
|
||||
}
|
||||
|
||||
async function enrichCharacters(characters: RowDataPacket[]) {
|
||||
return Promise.all(
|
||||
characters.map(async (char) => {
|
||||
|
|
@ -72,7 +91,7 @@ router.get<CampaignParams>("/", async (req, res) => {
|
|||
});
|
||||
|
||||
// POST /api/campaigns/:campaignId/characters
|
||||
router.post<CampaignParams>("/", async (req, res) => {
|
||||
router.post<CampaignParams>("/", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { campaignId } = req.params;
|
||||
const { name, class: charClass, ancestry, hp_max } = req.body;
|
||||
|
|
@ -82,12 +101,15 @@ router.post<CampaignParams>("/", async (req, res) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const userId = req.user?.userId ?? null;
|
||||
|
||||
const [result] = await db.execute<ResultSetHeader>(
|
||||
`INSERT INTO characters
|
||||
(campaign_id, name, class, ancestry, hp_current, hp_max, color)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
(campaign_id, user_id, name, class, ancestry, hp_current, hp_max, color)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
campaignId,
|
||||
userId,
|
||||
name.trim(),
|
||||
charClass ?? "Fighter",
|
||||
ancestry ?? "Human",
|
||||
|
|
@ -129,9 +151,16 @@ router.post<CampaignParams>("/", async (req, res) => {
|
|||
});
|
||||
|
||||
// PATCH /api/characters/:id
|
||||
router.patch("/:id", async (req, res) => {
|
||||
router.patch("/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const allowed = await canModifyCharacter(String(id), req.user!.userId);
|
||||
if (!allowed) {
|
||||
res.status(403).json({ error: "Not authorized to modify this character" });
|
||||
return;
|
||||
}
|
||||
|
||||
const allowedFields = [
|
||||
"name", "class", "ancestry", "level", "xp", "hp_current", "hp_max",
|
||||
"ac", "alignment", "title", "notes", "background", "deity", "languages",
|
||||
|
|
@ -182,7 +211,7 @@ router.patch("/:id", async (req, res) => {
|
|||
});
|
||||
|
||||
// DELETE /api/characters/:id
|
||||
router.delete("/:id", async (req, res) => {
|
||||
router.delete("/:id", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [rows] = await db.execute<RowDataPacket[]>(
|
||||
"SELECT * FROM characters WHERE id = ?",
|
||||
|
|
@ -193,6 +222,12 @@ router.delete("/:id", async (req, res) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const allowed = await canModifyCharacter(String(req.params.id), req.user!.userId);
|
||||
if (!allowed) {
|
||||
res.status(403).json({ error: "Not authorized to delete this character" });
|
||||
return;
|
||||
}
|
||||
|
||||
await db.execute("DELETE FROM characters WHERE id = ?", [req.params.id]);
|
||||
|
||||
const io: Server = req.app.get("io");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue