mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-10 12:25:02 +00:00
dashboard/api: add support for Zeppelin staff members; add ViewGuild permission; code cleanup
This commit is contained in:
parent
7e60950900
commit
d03d729438
13 changed files with 175 additions and 75 deletions
|
@ -12,7 +12,7 @@ import { ok } from "./responses";
|
|||
|
||||
interface IPassportApiUser {
|
||||
apiKey: string;
|
||||
userId: number;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
|
|
@ -5,35 +5,46 @@ import { Configs } from "../data/Configs";
|
|||
import { validateGuildConfig } from "../configValidator";
|
||||
import yaml, { YAMLException } from "js-yaml";
|
||||
import { apiTokenAuthHandlers } from "./auth";
|
||||
import { ApiPermissions, hasPermission, permissionArrToSet } from "@shared/apiPermissions";
|
||||
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
|
||||
import { ApiPermissions } from "@shared/apiPermissions";
|
||||
import { hasGuildPermission, requireGuildPermission } from "./permissions";
|
||||
|
||||
export function initGuildsAPI(app: express.Express) {
|
||||
const allowedGuilds = new AllowedGuilds();
|
||||
const apiPermissionAssignments = new ApiPermissionAssignments();
|
||||
const configs = new Configs();
|
||||
|
||||
app.get("/guilds/available", ...apiTokenAuthHandlers(), async (req: Request, res: Response) => {
|
||||
const guildRouter = express.Router();
|
||||
guildRouter.use(...apiTokenAuthHandlers());
|
||||
|
||||
guildRouter.get("/available", async (req: Request, res: Response) => {
|
||||
const guilds = await allowedGuilds.getForApiUser(req.user.userId);
|
||||
res.json(guilds);
|
||||
});
|
||||
|
||||
app.get("/guilds/:guildId/config", ...apiTokenAuthHandlers(), async (req: Request, res: Response) => {
|
||||
const permAssignment = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, req.user.userId);
|
||||
if (!permAssignment || !hasPermission(permissionArrToSet(permAssignment.permissions), ApiPermissions.ReadConfig)) {
|
||||
guildRouter.get("/:guildId", async (req: Request, res: Response) => {
|
||||
if (!(await hasGuildPermission(req.user.userId, req.params.guildId, ApiPermissions.ViewGuild))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const config = await configs.getActiveByKey(`guild-${req.params.guildId}`);
|
||||
res.json({ config: config ? config.config : "" });
|
||||
const guild = await allowedGuilds.find(req.params.guildId);
|
||||
res.json(guild);
|
||||
});
|
||||
|
||||
app.post("/guilds/:guildId/config", ...apiTokenAuthHandlers(), async (req, res) => {
|
||||
const permAssignment = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, req.user.userId);
|
||||
if (!permAssignment || !hasPermission(permissionArrToSet(permAssignment.permissions), ApiPermissions.EditConfig)) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
guildRouter.post("/:guildId/check-permission", async (req: Request, res: Response) => {
|
||||
const permission = req.body.permission;
|
||||
const hasPermission = await hasGuildPermission(req.user.userId, req.params.guildId, permission);
|
||||
res.json({ result: hasPermission });
|
||||
});
|
||||
|
||||
guildRouter.get(
|
||||
"/:guildId/config",
|
||||
requireGuildPermission(ApiPermissions.ReadConfig),
|
||||
async (req: Request, res: Response) => {
|
||||
const config = await configs.getActiveByKey(`guild-${req.params.guildId}`);
|
||||
res.json({ config: config ? config.config : "" });
|
||||
},
|
||||
);
|
||||
|
||||
guildRouter.post("/:guildId/config", requireGuildPermission(ApiPermissions.EditConfig), async (req, res) => {
|
||||
let config = req.body.config;
|
||||
if (config == null) return clientError(res, "No config supplied");
|
||||
|
||||
|
@ -70,4 +81,6 @@ export function initGuildsAPI(app: express.Express) {
|
|||
await configs.saveNewRevision(`guild-${req.params.guildId}`, config, req.user.userId);
|
||||
ok(res);
|
||||
});
|
||||
|
||||
app.use("/guilds", guildRouter);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
import { clientError, error, notFound } from "./responses";
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import { initAuth } from "./auth";
|
||||
import { initGuildsAPI } from "./guilds";
|
||||
import { initArchives } from "./archives";
|
||||
import { initDocs } from "./docs";
|
||||
import { connect } from "../data/db";
|
||||
import path from "path";
|
||||
import { TokenError } from "passport-oauth2";
|
||||
import { PluginError } from "knub";
|
||||
|
||||
require("dotenv").config({ path: path.resolve(process.cwd(), "api.env") });
|
||||
|
||||
|
@ -19,43 +10,8 @@ function errorHandler(err) {
|
|||
|
||||
process.on("unhandledRejection", errorHandler);
|
||||
|
||||
// Connect to the database before loading the rest of the code (that depend on the database connection)
|
||||
console.log("Connecting to database..."); // tslint:disable-line
|
||||
connect().then(() => {
|
||||
const app = express();
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin: process.env.DASHBOARD_URL,
|
||||
}),
|
||||
);
|
||||
app.use(express.json());
|
||||
|
||||
initAuth(app);
|
||||
initGuildsAPI(app);
|
||||
initArchives(app);
|
||||
initDocs(app);
|
||||
|
||||
// Default route
|
||||
app.get("/", (req, res) => {
|
||||
res.json({ status: "cookies", with: "milk" });
|
||||
});
|
||||
|
||||
// Error response
|
||||
app.use((err, req, res, next) => {
|
||||
if (err instanceof TokenError) {
|
||||
clientError(res, "Invalid code");
|
||||
} else {
|
||||
console.error(err); // tslint:disable-line
|
||||
error(res, "Server error", err.status || 500);
|
||||
}
|
||||
});
|
||||
|
||||
// 404 response
|
||||
app.use((req, res, next) => {
|
||||
return notFound(res);
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
// tslint:disable-next-line
|
||||
app.listen(port, () => console.log(`API server listening on port ${port}`));
|
||||
import("./start");
|
||||
});
|
||||
|
|
33
backend/src/api/permissions.ts
Normal file
33
backend/src/api/permissions.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { ApiPermissions, hasPermission, permissionArrToSet } from "@shared/apiPermissions";
|
||||
import { isStaff } from "../staff";
|
||||
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
|
||||
import { Request, Response } from "express";
|
||||
import { unauthorized } from "./responses";
|
||||
|
||||
const apiPermissionAssignments = new ApiPermissionAssignments();
|
||||
|
||||
export const hasGuildPermission = async (userId: string, guildId: string, permission: ApiPermissions) => {
|
||||
if (isStaff(userId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const permAssignment = await apiPermissionAssignments.getByGuildAndUserId(guildId, userId);
|
||||
if (!permAssignment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hasPermission(permissionArrToSet(permAssignment.permissions), permission);
|
||||
};
|
||||
|
||||
/**
|
||||
* Requires `guildId` in req.params
|
||||
*/
|
||||
export function requireGuildPermission(permission: ApiPermissions) {
|
||||
return async (req: Request, res: Response, next) => {
|
||||
if (!(await hasGuildPermission(req.user.userId, req.params.guildId, permission))) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
48
backend/src/api/start.ts
Normal file
48
backend/src/api/start.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { clientError, error, notFound } from "./responses";
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import { initAuth } from "./auth";
|
||||
import { initGuildsAPI } from "./guilds";
|
||||
import { initArchives } from "./archives";
|
||||
import { initDocs } from "./docs";
|
||||
import { connect } from "../data/db";
|
||||
import path from "path";
|
||||
import { TokenError } from "passport-oauth2";
|
||||
import { PluginError } from "knub";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin: process.env.DASHBOARD_URL,
|
||||
}),
|
||||
);
|
||||
app.use(express.json());
|
||||
|
||||
initAuth(app);
|
||||
initGuildsAPI(app);
|
||||
initArchives(app);
|
||||
initDocs(app);
|
||||
|
||||
// Default route
|
||||
app.get("/", (req, res) => {
|
||||
res.json({ status: "cookies", with: "milk" });
|
||||
});
|
||||
|
||||
// Error response
|
||||
app.use((err, req, res, next) => {
|
||||
if (err instanceof TokenError) {
|
||||
clientError(res, "Invalid code");
|
||||
} else {
|
||||
console.error(err); // tslint:disable-line
|
||||
error(res, "Server error", err.status || 500);
|
||||
}
|
||||
});
|
||||
|
||||
// 404 response
|
||||
app.use((req, res, next) => {
|
||||
return notFound(res);
|
||||
});
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
app.listen(port, () => console.log(`API server listening on port ${port}`));
|
|
@ -28,6 +28,10 @@ export class AllowedGuilds extends BaseRepository {
|
|||
return count !== 0;
|
||||
}
|
||||
|
||||
find(guildId) {
|
||||
return this.allowedGuilds.findOne(guildId);
|
||||
}
|
||||
|
||||
getForApiUser(userId) {
|
||||
return this.allowedGuilds
|
||||
.createQueryBuilder("allowed_guilds")
|
||||
|
|
6
backend/src/staff.ts
Normal file
6
backend/src/staff.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Zeppelin staff have full access to the dashboard
|
||||
*/
|
||||
export function isStaff(userId: string) {
|
||||
return (process.env.STAFF ?? "").split(",").includes(userId);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue