diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dae94fa..6608e959 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ **If you need help updating/installing or have found any bugs, please join [our Discord server](https://discord.gg/eGnrPqEH7U) or open a [GitHub issue here](https://github.com/Dev-CasperTheGhost/snaily-cadv3/issues/new/choose)** +## 1.3.9 + +- Minor code improvements +- Added `rank` to officers, assignable by moderators, admins and owners +- Added `supervisor` rank to users, this rank can only manage officers + ## 1.3.8 - Fixed small bug not being able to search for medical records diff --git a/client/src/components/AuthRoute.tsx b/client/src/components/AuthRoute.tsx index 04a2b8b3..776e6528 100644 --- a/client/src/components/AuthRoute.tsx +++ b/client/src/components/AuthRoute.tsx @@ -10,7 +10,7 @@ interface Props { isAuth: boolean; loading: boolean; path: string; - requirement?: "admin" | "leo" | "dispatch" | "tow" | "ems_fd"; + requirement?: "admin" | "leo" | "dispatch" | "tow" | "ems_fd" | "supervisor"; user: User; } @@ -24,25 +24,33 @@ const AuthRoute: React.FC = ({ Component, loading, isAuth, path, user, re if (requirement && !loading && isAuth) { switch (requirement) { case "leo": - if (user?.leo !== "1") { + if (user?.leo === "0") { return history.push("/forbidden"); } break; case "dispatch": - if (user?.dispatch !== "1") { + if (user?.dispatch === "0") { return history.push("/forbidden"); } break; case "tow": - if (user?.tow !== "1") { + if (user?.tow === "0") { history.push("/forbidden"); } break; case "ems_fd": - if (user?.ems_fd !== "1") { + if (user?.ems_fd === "0") { history.push("/forbidden"); } break; + case "supervisor": + if (user.supervisor === "1") break; + + if (!adminRanks.includes(user.rank)) { + history.push("/forbidden"); + } + break; + case "admin": if (!adminRanks.includes(user.rank)) { history.push("/forbidden"); diff --git a/client/src/components/leo/ModalButtons.tsx b/client/src/components/leo/ModalButtons.tsx index 4f07a137..39910441 100644 --- a/client/src/components/leo/ModalButtons.tsx +++ b/client/src/components/leo/ModalButtons.tsx @@ -3,6 +3,8 @@ import { Link } from "react-router-dom"; import socket from "../../lib/socket"; import Officer from "../../interfaces/Officer"; import lang from "../../language.json"; +import User from "../../interfaces/User"; +import { adminRanks } from "../AuthRoute"; export interface MButton { name: string; @@ -46,9 +48,10 @@ const modalButtons: MButton[] = [ interface Props { activeOfficer: Officer | null; + user: User | null; } -const ModalButtons: React.FC = ({ activeOfficer }) => { +const ModalButtons: React.FC = ({ user, activeOfficer }) => { function panicButton() { socket.emit("PANIC_BUTTON", activeOfficer); } @@ -63,6 +66,15 @@ const ModalButtons: React.FC = ({ activeOfficer }) => { {lang.officers.my_officers} + {user?.supervisor === "1" ? ( + + Manage officers + + ) : adminRanks.includes(`${user?.rank}`) ? ( + + Manage officers + + ) : null} {/* modals */} {modalButtons.map((mButton: MButton, idx: number) => { diff --git a/client/src/index.tsx b/client/src/index.tsx index 8416ec8a..87bf00b9 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -186,12 +186,12 @@ ReactDOM.render( /> diff --git a/client/src/interfaces/Officer.ts b/client/src/interfaces/Officer.ts index 92e16b57..1f3e4a66 100644 --- a/client/src/interfaces/Officer.ts +++ b/client/src/interfaces/Officer.ts @@ -5,6 +5,7 @@ interface Officer { officer_name: string; officer_dept: string; callsign?: string; + rank: string; } export default Officer; diff --git a/client/src/interfaces/User.ts b/client/src/interfaces/User.ts index ff012e20..f30f7d30 100644 --- a/client/src/interfaces/User.ts +++ b/client/src/interfaces/User.ts @@ -7,6 +7,7 @@ interface User { rank: "owner" | "admin" | "moderator" | "user"; leo: Perm; ems_fd: Perm; + supervisor: Perm; dispatch: Perm; tow: Perm; banned: Perm; diff --git a/client/src/lib/Logger.ts b/client/src/lib/Logger.ts index dd217602..1a1047fb 100644 --- a/client/src/lib/Logger.ts +++ b/client/src/lib/Logger.ts @@ -10,6 +10,10 @@ class Logger { error(type: string, error: string): void { console.error(`[${type.toUpperCase()}][${this.now()}]: ${error}`); } + + warn(type: string, message: string): void { + console.warn(`[${type.toUpperCase()}][${this.now()}]: ${message}`); + } } export default new Logger(); diff --git a/client/src/lib/actions/admin.ts b/client/src/lib/actions/admin.ts index ab414d30..5e65cef5 100644 --- a/client/src/lib/actions/admin.ts +++ b/client/src/lib/actions/admin.ts @@ -334,6 +334,7 @@ export const getOfficerById = (id: string) => async (dispatch: Dispatch async ( diff --git a/client/src/pages/admin/management/members/manage-member.tsx b/client/src/pages/admin/management/members/manage-member.tsx index 6951d7fd..cbdbe667 100644 --- a/client/src/pages/admin/management/members/manage-member.tsx +++ b/client/src/pages/admin/management/members/manage-member.tsx @@ -45,6 +45,7 @@ const ManageMember: React.FC = ({ const id = match.params.id; const [rank, setRank] = React.useState(""); const [leo, setLeo] = React.useState(""); + const [supervisor, setSupervisor] = React.useState(""); const [dispatch, setDispatch] = React.useState(""); const [emsFd, setEmsFd] = React.useState(""); const [tow, setTow] = React.useState(""); @@ -61,6 +62,7 @@ const ManageMember: React.FC = ({ setDispatch(member?.dispatch); setEmsFd(member?.ems_fd); setTow(member?.tow); + setSupervisor(member.supervisor); } }, [member]); @@ -73,6 +75,7 @@ const ManageMember: React.FC = ({ dispatch, emsFd, tow, + supervisor, }); } @@ -144,6 +147,25 @@ const ManageMember: React.FC = ({ +
+ + +
+
+ + + setRank(e.currentTarget.value)} + className="form-control bg-dark border-dark text-light" + /> +
diff --git a/client/src/pages/admin/management/officers/officers-management.tsx b/client/src/pages/admin/management/officers/officers-management.tsx index 6c033ca7..fe8beb76 100644 --- a/client/src/pages/admin/management/officers/officers-management.tsx +++ b/client/src/pages/admin/management/officers/officers-management.tsx @@ -74,6 +74,10 @@ const OfficersManagementPage: React.FC = ({ officers, message, getAllOffi Callsign: {officer.callsign || "None set"} + + Rank: + {officer.rank || "None set"} +
diff --git a/client/src/pages/leo/dash.tsx b/client/src/pages/leo/dash.tsx index e66a56ce..3044b087 100644 --- a/client/src/pages/leo/dash.tsx +++ b/client/src/pages/leo/dash.tsx @@ -24,7 +24,7 @@ import Officer from "../../interfaces/Officer"; import { playSound } from "../../lib/functions"; import { getPenalCodes } from "../../lib/actions/admin"; import { useLocation } from "react-router-dom"; -import { Perm } from "../../interfaces/User"; +import User, { Perm } from "../../interfaces/User"; import CadInfo from "../../interfaces/CadInfo"; interface Props { @@ -32,6 +32,7 @@ interface Props { message: Message; activeOfficer: Officer | null; cadInfo: CadInfo; + user: User | null; getPenalCodes: () => void; } @@ -108,7 +109,7 @@ const LeoDash: React.FC = (props) => { {new Date(time).toLocaleString()}
- +
@@ -146,6 +147,7 @@ const mapToProps = (state: State) => ({ message: state.global.message, activeOfficer: state.officers.activeOfficer, cadInfo: state.global.cadInfo, + user: state.auth.user, }); export default connect(mapToProps, { getPenalCodes })(React.memo(LeoDash)); diff --git a/package.json b/package.json index 59270c8d..04f7f16e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "snaily-cadv3", - "version": "1.3.8", + "version": "1.3.9", "description": "An open source Computer Aided Dispatch (CAD) for FiveM, this is a web based integration for communities who love police roleplaying and dispatching. ", "main": "index.js", "scripts": { diff --git a/server/.eslintrc.json b/server/.eslintrc.json index 53407d8e..66a200a2 100644 --- a/server/.eslintrc.json +++ b/server/.eslintrc.json @@ -3,7 +3,11 @@ "browser": true, "es2021": true }, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 12, @@ -15,12 +19,13 @@ "semi": ["error", "always"], "no-multi-spaces": ["error"], "eqeqeq": ["warn", "always"], - "no-unused-vars": ["error"], "no-duplicate-case": ["error"], "no-extra-semi": ["error"], "no-unreachable": ["error"], "default-case": ["warn"], "default-case-last": ["error"], - "no-useless-catch": ["warn"] + "no-useless-catch": ["warn"], + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error"] } } diff --git a/server/src/hooks/useAuth.ts b/server/src/hooks/useAuth.ts index c967fde3..b22a83ca 100644 --- a/server/src/hooks/useAuth.ts +++ b/server/src/hooks/useAuth.ts @@ -18,7 +18,7 @@ async function useAuth(req: IRequest, res: Response, next: NextFunction): Promis try { const vToken = jwt.verify(token, secret) as IUser; const user = await processQuery( - "SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url` FROM `users` WHERE `id` = ?", + "SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url`, `supervisor` FROM `users` WHERE `id` = ?", [vToken.id] ); diff --git a/server/src/hooks/useMarkdown.ts b/server/src/hooks/useMarkdown.ts index ea0cc474..3b4e84bc 100644 --- a/server/src/hooks/useMarkdown.ts +++ b/server/src/hooks/useMarkdown.ts @@ -12,15 +12,6 @@ const dompurify = createDompurify(window); export default function (data: string): string { return dompurify.sanitize(marked(data), { FORBID_ATTR: ["style", "onerror", "onload"], - FORBID_TAGS: [ - "script", - "audio", - "video", - "style", - "iframe", - "textarea", - "frame", - "frameset", - ], + FORBID_TAGS: ["script", "audio", "video", "style", "iframe", "textarea", "frame", "frameset", "table", "td", "th"], }); } diff --git a/server/src/hooks/usePermission.ts b/server/src/hooks/usePermission.ts index 54fcf056..bc6bd4d4 100644 --- a/server/src/hooks/usePermission.ts +++ b/server/src/hooks/usePermission.ts @@ -6,7 +6,7 @@ import { processQuery } from "../lib/database"; import Logger from "../lib/Logger"; // rank, leo, ems_fd, dispatch, tow -type UserPermsArr = [RanksType, Perm, Perm, Perm, Perm]; +type UserPermsArr = [RanksType, Perm, Perm, Perm, Perm, Perm]; type Permissions = RanksType | "leo" | "ems_fd" | "dispatch" | "tow"; const usePermission = (perms: Permissions[]) => async ( @@ -15,12 +15,19 @@ const usePermission = (perms: Permissions[]) => async ( next: NextFunction, ): Promise => { try { - const user = await processQuery( - "SELECT `rank`, `leo`, `dispatch`, `tow`, `ems_fd` FROM `users` WHERE `id` = ?", + const user = await processQuery( + "SELECT `rank`, `leo`, `dispatch`, `tow`, `ems_fd`, `supervisor` FROM `users` WHERE `id` = ?", [req.user?.id], ); - const userPerms: UserPermsArr = [user[0].rank, user[0].leo, user[0].ems_fd, user[0].dispatch, user[0].tow]; + const userPerms: UserPermsArr = [ + user[0].rank, + user[0].leo, + user[0].ems_fd, + user[0].dispatch, + user[0].tow, + user[0].supervisor, + ]; if (!user[0]) { return res.json({ @@ -70,6 +77,14 @@ const usePermission = (perms: Permissions[]) => async ( } break; } + case "supervisor": { + if (userPerms[5] === "0") { + invalidPerms.push("supervisor"); + } else { + invalid = false; + } + break; + } default: { // 0 = rank | defaults to 'default' if (!RanksArr.includes(user[0].rank)) { diff --git a/server/src/interfaces/IUser.ts b/server/src/interfaces/IUser.ts index 58f4302b..3e92b81d 100644 --- a/server/src/interfaces/IUser.ts +++ b/server/src/interfaces/IUser.ts @@ -7,6 +7,7 @@ interface IUser { password: string; rank: RanksType; leo: Perm; + supervisor: Perm; ems_fd: Perm; dispatch: Perm; tow: Perm; diff --git a/server/src/interfaces/Officer.ts b/server/src/interfaces/Officer.ts index 92a75dcb..5213c5db 100644 --- a/server/src/interfaces/Officer.ts +++ b/server/src/interfaces/Officer.ts @@ -5,7 +5,8 @@ interface Officer { user_id: string; status: string; status2: string; - callsign?: string; + callsign: string; + rank: string; } export default Officer; diff --git a/server/src/lib/constants.ts b/server/src/lib/constants.ts index 4871059b..7c59c8e8 100644 --- a/server/src/lib/constants.ts +++ b/server/src/lib/constants.ts @@ -1,11 +1,12 @@ export const RanksArr = ["owner", "admin", "moderator"]; -export type RanksType = "owner" | "admin" | "moderator" | "user"; +export type RanksType = "owner" | "admin" | "moderator" | "user" | "supervisor"; export const enum Ranks { owner = "owner", admin = "admin", moderator = "moderator", user = "user", + supervisor = "supervisor", } export const enum Whitelist { diff --git a/server/src/lib/database.ts b/server/src/lib/database.ts index 55213578..f35be133 100644 --- a/server/src/lib/database.ts +++ b/server/src/lib/database.ts @@ -16,7 +16,7 @@ export async function connect(): Promise { return await mysql.createConnection(options); } -export async function processQuery(query: string, data?: any[]): Promise { +export async function processQuery(query: string, data?: any[]): Promise { const conn = await connect(); const result = await conn.query(query, data); conn.end(); @@ -46,6 +46,8 @@ async function updateLine(sql: string) { async function updateDb() { import("./insert"); + updateLine("ALTER TABLE `users` ADD `supervisor` varchar(255) NOT NULL AFTER `leo`;"); + updateLine("ALTER TABLE `officers` ADD `rank` varchar(255) NOT NULL AFTER `callsign`;"); updateLine("ALTER TABLE `citizens` ADD `phone_nr` varchar(255) NOT NULL AFTER `note`;"); updateLine("ALTER TABLE `cad_info` ADD `steam_api_key` varchar(255) NOT NULL AFTER `webhook_url`;"); updateLine("ALTER TABLE `911calls` ADD `hidden` varchar(255) NOT NULL AFTER `assigned_unit`;"); diff --git a/server/src/lib/functions.ts b/server/src/lib/functions.ts index 0b053863..e5d8f8b0 100644 --- a/server/src/lib/functions.ts +++ b/server/src/lib/functions.ts @@ -88,8 +88,8 @@ export async function postWebhook(webhook: WebHook, data: WebHookData): Promise< } export async function logoutActiveUnits(userId: string | undefined): Promise { - const officers = await processQuery("SELECT * FROM `officers` WHERE `user_id` = ?", [userId]); - const emsFd = await processQuery("SELECT * FROM `ems-fd` WHERE `user_id` = ?", [userId]); + const officers = await processQuery("SELECT * FROM `officers` WHERE `user_id` = ?", [userId]); + const emsFd = await processQuery("SELECT * FROM `ems-fd` WHERE `user_id` = ?", [userId]); [...officers, ...emsFd] .filter((o) => o.status === "on-duty") diff --git a/server/src/routes/auth/index.ts b/server/src/routes/auth/index.ts index 676f65c7..c4efade5 100644 --- a/server/src/routes/auth/index.ts +++ b/server/src/routes/auth/index.ts @@ -23,7 +23,7 @@ router.post("/register", async (req: IRequest, res: Response) => { return res.json({ status: "error", error: "Passwords do not match" }); } - const existing = await processQuery("SELECT * FROM `users` WHERE `username` = ?", [username]); + const existing = await processQuery("SELECT * FROM `users` WHERE `username` = ?", [username]); if (existing?.length > 0) { return res.json({ @@ -33,13 +33,13 @@ router.post("/register", async (req: IRequest, res: Response) => { } const hash = hashSync(password, saltRounds); - const users = await processQuery("SELECT `username` FROM `users`"); + const users = await processQuery("SELECT `username` FROM `users`"); const insertSQL = "INSERT INTO `users` (`id`, `username`, `password`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; // There are existing users - create the account at user level if (users?.length > 0) { - const cadInfo = await processQuery("SELECT * FROM `cad_info`"); + const cadInfo = await processQuery("SELECT * FROM `cad_info`"); const whitelistStatus = cadInfo[0].whitelisted === "1" ? "pending" : "accepted"; const towAccess = cadInfo[0].tow_whitelisted === "1" ? false : true; const id = uuidv4(); @@ -119,8 +119,8 @@ router.post("/login", async (req: IRequest, res: Response) => { const { username, password } = req.body; if (username && password) { - const user = await processQuery("SELECT * FROM `users` WHERE `username` = ?", [username]); - const cadInfo = await processQuery("SELECT * FROM `cad_info`"); + const user = await processQuery("SELECT * FROM `users` WHERE `username` = ?", [username]); + const cadInfo = await processQuery("SELECT * FROM `cad_info`"); if (!user[0]) { return res.json({ error: "User was not found", status: "error" }); @@ -177,7 +177,7 @@ router.get("/logout", useAuth, async (req: IRequest, res: Response) => { router.delete("/delete-account", useAuth, async (req: IRequest, res: Response) => { const userId = req.user?.id; - const user = await processQuery("SELECT `rank` FROM `users` WHERE `id` = ?", [userId]); + const user = await processQuery("SELECT `rank` FROM `users` WHERE `id` = ?", [userId]); if (user[0].rank === "owner") { return res.json({ @@ -186,7 +186,7 @@ router.delete("/delete-account", useAuth, async (req: IRequest, res: Response) = }); } - const citizens = await processQuery("SELECT * FROM `citizens` WHERE `user_id` = ?", [userId]); + const citizens = await processQuery("SELECT * FROM `citizens` WHERE `user_id` = ?", [userId]); citizens.forEach(async (citizen: Citizen) => { await processQuery("DELETE FROM `arrest_reports` WHERE `citizen_id` = ?", [citizen.id]); @@ -215,7 +215,7 @@ router.put("/update-pw", useAuth, async (req: IRequest, res: Response) => { const { oldPassword, newPassword, newPassword2 } = req.body; if (oldPassword && newPassword && newPassword2) { - const user = await processQuery("SELECT * FROM `users` WHERE `id` = ?", [userId]); + const user = await processQuery("SELECT * FROM `users` WHERE `id` = ?", [userId]); if (!user[0]) { return res.json({ error: "User was not found", status: "error" }); diff --git a/server/src/routes/auth/steam.ts b/server/src/routes/auth/steam.ts index 942ccc81..8eb11702 100644 --- a/server/src/routes/auth/steam.ts +++ b/server/src/routes/auth/steam.ts @@ -10,7 +10,7 @@ const router = Router(); router.get("/", useAuth, async (req: IRequest, res: Response) => { const callbackUrl = req.query.callback_url; - const cadInfo = await processQuery("SELECT `steam_api_key` FROM `cad_info`"); + const cadInfo = await processQuery("SELECT `steam_api_key` FROM `cad_info`"); if (!cadInfo[0].steam_api_key) { return res.json({ @@ -25,7 +25,7 @@ router.get("/", useAuth, async (req: IRequest, res: Response) => { }); router.get("/callback", useAuth, async (req: IRequest, res: Response) => { - const cadInfo = await processQuery("SELECT `steam_api_key` FROM `cad_info`"); + const cadInfo = await processQuery("SELECT `steam_api_key` FROM `cad_info`"); if (!cadInfo[0].steam_api_key) { return res.json({ diff --git a/server/src/routes/bleeter.ts b/server/src/routes/bleeter.ts index 7c54e5a0..bc61aa12 100644 --- a/server/src/routes/bleeter.ts +++ b/server/src/routes/bleeter.ts @@ -5,6 +5,7 @@ import { RanksArr, SupportedFileTypes } from "../lib/constants"; import { v4 as uuidv4 } from "uuid"; import { UploadedFile } from "express-fileupload"; import IRequest from "../interfaces/IRequest"; +import IUser from "../interfaces/IUser"; const router = Router(); @@ -109,7 +110,8 @@ router.put("/:id", useAuth, async (req: IRequest, res: Response) => { router.delete("/:id", useAuth, async (req: IRequest, res: Response) => { const { id } = req.params; - const rank = String(req.user?.rank); + const user = await processQuery("SELECT `rank` FROM `users` WHERE `id` = ?", [req.user?.id]); + const rank = user[0].rank; const bleet = await processQuery("SELECT * FROM `bleets` WHERE `id` = ?", [id]); if (!bleet[0]) { diff --git a/server/src/routes/citizen/index.ts b/server/src/routes/citizen/index.ts index 1c5007b4..4ca2e6d7 100644 --- a/server/src/routes/citizen/index.ts +++ b/server/src/routes/citizen/index.ts @@ -224,7 +224,7 @@ router.post("/info", useAuth, async (req: IRequest, res: Response) => { }); } - const citizen = await processQuery("SELECT * FROM `citizens` WHERE `full_name` = ?", [name]); + const citizen = await processQuery("SELECT * FROM `citizens` WHERE `full_name` = ?", [name]); if (!citizen[0]) { return res.json({ @@ -254,7 +254,7 @@ router.post("/expungement-request/:id", useAuth, async (req: IRequest, res: Resp // The requested options to be removed const { warrants, arrest_reports, tickets } = req.body; - const citizen = await processQuery("SELECT * FROM `citizens` WHERE `id` = ?", [id]); + const citizen = await processQuery("SELECT * FROM `citizens` WHERE `id` = ?", [id]); if (!citizen) { return res.json({ diff --git a/server/src/routes/dispatch.ts b/server/src/routes/dispatch.ts index 7a9c6697..afe5620d 100644 --- a/server/src/routes/dispatch.ts +++ b/server/src/routes/dispatch.ts @@ -77,7 +77,7 @@ router.put("/calls/:id", useAuth, usePermission(["dispatch"]), async (req: IRequ let status = ""; if (location) { - const call = await processQuery("SELECT `pos` FROM `911calls` WHERE `id` = ?", [id]); + const call = await processQuery("SELECT `pos` FROM `911calls` WHERE `id` = ?", [id]); let position = {}; diff --git a/server/src/routes/global.ts b/server/src/routes/global.ts index d98b4928..ef63dfdc 100644 --- a/server/src/routes/global.ts +++ b/server/src/routes/global.ts @@ -28,7 +28,7 @@ export function mapCalls(calls: Call[]): Call[] { } router.get("/911-calls", async (_req: IRequest, res: Response) => { - const calls = await processQuery("SELECT * FROM `911calls`"); + const calls = await processQuery("SELECT * FROM `911calls`"); const mappedCalls = mapCalls(calls); @@ -46,7 +46,7 @@ router.post("/911-calls", async (req: IRequest, res: Response) => { z: coordsArr?.[2] || 0, }; - await processQuery( + await processQuery( "INSERT INTO `911calls` (`id`, `description`, `name`, `location`, `status`, `assigned_unit`, `pos`, `hidden`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [id, description, caller, location, "Not assigned", "[]", JSON.stringify(coords), coordsArr ? "0" : "1"], ); @@ -58,7 +58,7 @@ router.post("/911-calls", async (req: IRequest, res: Response) => { }); router.post("/cad-info", useAuth, async (_req: IRequest, res: Response) => { - const cadInfo = await processQuery("SELECT * FROM `cad_info`"); + const cadInfo = await processQuery("SELECT * FROM `cad_info`"); return res.json({ cadInfo: cadInfo[0], status: "success" }); }); diff --git a/server/src/routes/management.ts b/server/src/routes/management.ts index ef839915..5580f249 100644 --- a/server/src/routes/management.ts +++ b/server/src/routes/management.ts @@ -28,7 +28,7 @@ export function parse10Codes(codes: Code10[]): Code10[] { /* Cad settings */ router.put("/cad-settings", useAuth, usePermission(["owner"]), async (req: IRequest, res: Response) => { - const user = await processQuery("SELECT `rank` from `users` WHERE `id` = ?", [req.user?.id]); + const user = await processQuery("SELECT `rank` from `users` WHERE `id` = ?", [req.user?.id]); if (user[0].rank !== "owner") { return res.json({ error: "Forbidden", status: "error" }).status(403); @@ -63,8 +63,8 @@ router.get( useAuth, usePermission(["admin", "owner", "moderator"]), async (_req: IRequest, res: Response) => { - const members = await processQuery( - "SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url` FROM `users` ORDER BY `username` ASC", + const members = await processQuery( + "SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url`, `supervisor` FROM `users` ORDER BY `username` ASC", ); return res.json({ status: "success", members }); @@ -77,8 +77,8 @@ router.get( usePermission(["admin", "owner", "moderator", "dispatch"]), async (req: IRequest, res: Response) => { const { id } = req.params; - const member = await processQuery( - "SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url` FROM `users` WHERE `id` = ?", + const member = await processQuery( + "SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url`, `supervisor` FROM `users` WHERE `id` = ?", [id], ); @@ -92,16 +92,16 @@ router.put( usePermission(["admin", "owner", "moderator"]), async (req: IRequest, res: Response) => { const { id } = req.params; - const { rank, leo, dispatch, emsFd, tow } = req.body; + const { rank, leo, dispatch, emsFd, tow, supervisor } = req.body; - if (rank && leo && dispatch && emsFd && tow) { + if (rank && leo && dispatch && emsFd && supervisor) { await processQuery( - "UPDATE `users` SET `rank` = ?, `leo` = ?, `dispatch` = ?, `ems_fd` = ?, `tow` = ? WHERE `id` = ?", - [rank, leo, dispatch, emsFd, tow, id], + "UPDATE `users` SET `rank` = ?, `leo` = ?, `dispatch` = ?, `ems_fd` = ?, `tow` = ?, `supervisor` = ? WHERE `id` = ?", + [rank, leo, dispatch, emsFd, tow, supervisor, id], ); - const updated = await processQuery( - "SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url` FROM `users` WHERE `id` = ?", + const updated = await processQuery( + "SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url`, `supervisor` FROM `users` WHERE `id` = ?", [id], ); @@ -157,10 +157,10 @@ router.put( } } - const members = await processQuery( + const members = await processQuery( "SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url` FROM `users`", ); - const updated = await processQuery( + const updated = await processQuery( "SELECT `id`, `username`, `rank`, `leo`, `ems_fd`, `dispatch`, `tow`, `banned`, `ban_reason`, `whitelist_status`, `steam_id`, `avatar_url` FROM `users` WHERE `id` = ?", [id], ); @@ -314,7 +314,7 @@ router.delete( async (req: IRequest, res: Response) => { const { id } = req.params; - const employees = await processQuery("SELECT * FROM `citizens` WHERE `business_id` = ?", [id]); + const employees = await processQuery("SELECT * FROM `citizens` WHERE `business_id` = ?", [id]); employees?.forEach(async (em: Citizen) => { await processQuery( @@ -334,10 +334,10 @@ router.delete( router.get( "/officers", useAuth, - usePermission(["admin", "owner", "moderator"]), + usePermission(["admin", "owner", "moderator", "supervisor"]), async (_req: IRequest, res: Response) => { try { - const officers = await processQuery("SELECT * FROM `officers`"); + const officers = await processQuery("SELECT * FROM `officers`"); return res.json({ status: "success", officers }); } catch (e) { @@ -350,12 +350,12 @@ router.get( router.get( "/officers/:id", useAuth, - usePermission(["admin", "owner", "moderator"]), + usePermission(["admin", "owner", "moderator", "supervisor"]), async (req: IRequest, res: Response) => { const { id } = req.params; try { - const officer = await processQuery("SELECT * FROM `officers` WHERE `id` = ?", [id]); + const officer = await processQuery("SELECT * FROM `officers` WHERE `id` = ?", [id]); return res.json({ status: "success", officer: officer[0] }); } catch (e) { @@ -368,10 +368,10 @@ router.get( router.put( "/officers/:officerId", useAuth, - usePermission(["admin", "owner", "moderator"]), + usePermission(["admin", "owner", "moderator", "supervisor"]), async (req: IRequest, res: Response) => { const { officerId } = req.params; - const callsign = req.body.callsign; + const { callsign, rank } = req.body; if (!callsign) { return res.json({ @@ -381,7 +381,11 @@ router.put( } try { - await processQuery("UPDATE `officers` SET `callsign` = ? WHERE `id` = ?", [callsign, officerId]); + await processQuery("UPDATE `officers` SET `callsign` = ?, `rank` = ? WHERE `id` = ?", [ + callsign, + rank, + officerId, + ]); return res.json({ status: "success" }); } catch (e) { diff --git a/server/src/routes/notifications.ts b/server/src/routes/notifications.ts index e6715df4..5146cf1b 100644 --- a/server/src/routes/notifications.ts +++ b/server/src/routes/notifications.ts @@ -26,7 +26,7 @@ export async function createNotification( } router.get("/", useAuth, async (req: IRequest, res: Response) => { - const notifications = await processQuery("SELECT * FROM `notifications` WHERE `user_id` = ?", [ + const notifications = await processQuery("SELECT * FROM `notifications` WHERE `user_id` = ?", [ req.user?.id, ]); @@ -38,7 +38,7 @@ router.get("/", useAuth, async (req: IRequest, res: Response) => { router.delete("/:id", useAuth, async (req: IRequest, res: Response) => { const { id } = req.params; - const notification = await processQuery("SELECT * FROM `notifications` WHERE `id`= ?", [id]); + const notification = await processQuery("SELECT * FROM `notifications` WHERE `id`= ?", [id]); if (!notification[0]) { return res.json({ @@ -55,7 +55,7 @@ router.delete("/:id", useAuth, async (req: IRequest, res: Response) => { } await processQuery("DELETE FROM `notifications` WHERE `id` = ?", [id]); - const notifications = await processQuery("SELECT * FROM `notifications` WHERE `user_id` = ?", [ + const notifications = await processQuery("SELECT * FROM `notifications` WHERE `user_id` = ?", [ req.user?.id, ]); diff --git a/server/src/routes/officer.ts b/server/src/routes/officer.ts index 83bf49b7..70543004 100644 --- a/server/src/routes/officer.ts +++ b/server/src/routes/officer.ts @@ -44,7 +44,7 @@ router.delete("/:id", useAuth, usePermission(["leo"]), async (req: IRequest, res router.get("/status/:id", useAuth, usePermission(["leo", "dispatch"]), async (req: IRequest, res: Response) => { const { id } = req.params; - const officer = await processQuery("SELECT * FROM `officers` WHERE `id` = ?", [id]); + const officer = await processQuery("SELECT * FROM `officers` WHERE `id` = ?", [id]); return res.json({ officer: officer[0], status: "success" }); }); @@ -60,7 +60,7 @@ router.put("/status/:id", useAuth, usePermission(["leo", "dispatch"]), async (re req.user?.id, ]); - const code = await processQuery("SELECT * FROM `10_codes` WHERE `code` = ?", [status2]); + const code = await processQuery("SELECT * FROM `10_codes` WHERE `code` = ?", [status2]); await processQuery("UPDATE `officers` SET `status` = ?, `status2` = ? WHERE `id` = ?", [ code[0]?.should_do === "set_off_duty" ? "off-duty" : status, @@ -68,7 +68,7 @@ router.put("/status/:id", useAuth, usePermission(["leo", "dispatch"]), async (re id, ]); - const updatedOfficer = await processQuery("SELECT * FROM `officers` WHERE `id` = ?", [id]); + const updatedOfficer = await processQuery("SELECT * FROM `officers` WHERE `id` = ?", [id]); return res.json({ status: "success", officer: updatedOfficer[0] }); } else { diff --git a/server/src/server.ts b/server/src/server.ts index 0c30d9a4..dd8274c0 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -19,6 +19,9 @@ const io = new Server(server, { methods: ["GET", "POST", "PUT", "DELETE"], credentials: true, }, + cookie: { + httpOnly: true, + }, }); const protection = csurf({ cookie: true }); app.use("/api/v1", api, protection); diff --git a/snaily-cad.sql b/snaily-cad.sql index 2b832b3b..13ec4559 100644 --- a/snaily-cad.sql +++ b/snaily-cad.sql @@ -300,6 +300,7 @@ CREATE TABLE `officers` ( `officer_name` varchar(255) NOT NULL, `officer_dept` varchar(255) NOT NULL, `callsign` varchar(255) NOT NULL, + `rank` varchar(255) NOT NULL, `user_id` varchar(255) NOT NULL, `status` varchar(255) NOT NULL, `status2` varchar(255) NOT NULL