diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js
index e8965ec..59e7aa0 100644
--- a/frontend/rollup.config.js
+++ b/frontend/rollup.config.js
@@ -17,7 +17,7 @@ function serve() {
return {
writeBundle() {
if (server) return;
- server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
+ server = require('child_process').spawn('yarn', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
diff --git a/frontend/src/components/Message.svelte b/frontend/src/components/Message.svelte
index 5aaf066..fe8c492 100644
--- a/frontend/src/components/Message.svelte
+++ b/frontend/src/components/Message.svelte
@@ -50,7 +50,7 @@
{ message.author_username }
{ message.content }
- {#if userInfoStore.value && message.author_id === userInfoStore.value.id}
+ {#if userInfoStore.value && (message.author_id === userInfoStore.value.id || userInfoStore.value.is_superuser)}
diff --git a/frontend/src/components/Sidebar.svelte b/frontend/src/components/Sidebar.svelte
index fda2169..1bda3ac 100644
--- a/frontend/src/components/Sidebar.svelte
+++ b/frontend/src/components/Sidebar.svelte
@@ -42,7 +42,7 @@
{#if channel._hasUnreads}
•
{/if}
- {#if $userInfoStore && channel.owner_id === $userInfoStore.id}
+ {#if $userInfoStore && (channel.owner_id === $userInfoStore.id || $userInfoStore.is_superuser)}
diff --git a/frontend/src/components/overlays/OverlayProvider.svelte b/frontend/src/components/overlays/OverlayProvider.svelte
index 47a2dc2..9da9b21 100644
--- a/frontend/src/components/overlays/OverlayProvider.svelte
+++ b/frontend/src/components/overlays/OverlayProvider.svelte
@@ -7,6 +7,7 @@
import CreateAccount from "./CreateAccount.svelte";
import EditMessage from "./EditMessage.svelte";
import Settings from "./Settings.svelte";
+ import Prompt from "./Prompt.svelte";
{#if $overlayStore.createChannel}
@@ -30,3 +31,6 @@
{#if $overlayStore.settings}
{/if}
+{#if $overlayStore.prompt}
+
+{/if}
diff --git a/frontend/src/components/overlays/Prompt.svelte b/frontend/src/components/overlays/Prompt.svelte
new file mode 100644
index 0000000..6c939fc
--- /dev/null
+++ b/frontend/src/components/overlays/Prompt.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/overlays/Settings.svelte b/frontend/src/components/overlays/Settings.svelte
index beb4aa1..473e12f 100644
--- a/frontend/src/components/overlays/Settings.svelte
+++ b/frontend/src/components/overlays/Settings.svelte
@@ -4,6 +4,8 @@
import { overlayStore, userInfoStore, smallViewport, theme, doAnimations } from "../../stores";
import { logOut } from "../../auth";
import { maybeFade, maybeFly } from "../../animations";
+ import request from "../../request";
+ import { apiRoute } from "../../storage";
const close = () => overlayStore.close("settings");
@@ -14,6 +16,23 @@
message: "Logged out"
});
};
+
+ const doSuperuserPrompt = () => {
+ overlayStore.open("prompt", {
+ heading: "Become Superuser",
+ valueName: "Superuser Key",
+ async onSubmit(value) {
+ const { ok } = await request("POST", apiRoute("users/self/promote"), true, {
+ key: value
+ });
+ if (ok) {
+ overlayStore.open("toast", { message: "You have been promoted to superuser" });
+ } else {
+ overlayStore.open("toast", { message: "Failed to promote to superuser" });
+ }
+ }
+ });
+ };
@@ -64,7 +83,10 @@
{ $userInfoStore ? $userInfoStore.username : "" }
-
+
+
+
+
diff --git a/frontend/src/stores.js b/frontend/src/stores.js
index 826e779..09f4f31 100644
--- a/frontend/src/stores.js
+++ b/frontend/src/stores.js
@@ -303,6 +303,7 @@ class OverlayStore extends Store {
createAccount: null,
editMessage: null,
settings: null,
+ prompt: null,
}, "OverlayStore");
}
diff --git a/src/database/init.ts b/src/database/init.ts
index 96d1a5b..cbda67c 100644
--- a/src/database/init.ts
+++ b/src/database/init.ts
@@ -5,7 +5,8 @@ export default async function databaseInit() {
CREATE TABLE IF NOT EXISTS users(
id SERIAL PRIMARY KEY,
username VARCHAR(32) UNIQUE NOT NULL,
- password TEXT
+ password TEXT,
+ is_superuser BOOLEAN
);
CREATE TABLE IF NOT EXISTS channels(
diff --git a/src/errors.ts b/src/errors.ts
index 65a4a15..f660013 100644
--- a/src/errors.ts
+++ b/src/errors.ts
@@ -4,6 +4,7 @@ export const errors = {
BAD_AUTH: { code: 6003, message: "Bad authentication" },
NOT_FOUND: { code: 6004, message: "Not found" },
FORBIDDEN_DUE_TO_MISSING_PERMISSIONS: { code: 6005, message: "Forbidden due to missing permission(s)" },
+ BAD_SUPERUSER_KEY: { code: 6006, message: "Bad superuser key" },
GOT_NO_DATABASE_DATA: { code: 7001, message: "Unexpectedly got no data from database" },
FEATURE_DISABLED: { code: 7002, message: "This feature is disabled" }
};
diff --git a/src/routes/api/v1/channels.ts b/src/routes/api/v1/channels.ts
index ff882b1..17d2cfa 100644
--- a/src/routes/api/v1/channels.ts
+++ b/src/routes/api/v1/channels.ts
@@ -6,6 +6,7 @@ import { getMessageById, getMessagesByChannelFirstPage, getMessagesByChannelPage
import { errors } from "../../../errors";
import { dispatch, dispatchChannelSubscribe } from "../../../gateway";
import { GatewayPayloadType } from "../../../gateway/gatewaypayloadtype";
+import serverConfig from "../../../serverconfig";
const router = express.Router();
@@ -19,6 +20,10 @@ router.post(
return res.status(400).json({ ...errors.INVALID_DATA, errors: validationErrors.array() });
}
+ if (serverConfig.superuserRequirement.createChannel && !req.user.is_superuser) {
+ return res.status(403).json({ ...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS });
+ }
+
const { name } = req.body;
const result = await query("INSERT INTO channels(name, owner_id) VALUES ($1, $2) RETURNING id, name, owner_id", [name, req.user.id]);
if (result.rowCount < 1) {
@@ -60,7 +65,7 @@ router.put(
...errors.NOT_FOUND
});
}
- if (permissionCheckResult.rows[0].owner_id !== req.user.id) {
+ if (permissionCheckResult.rows[0].owner_id !== req.user.id && !req.user.is_superuser) {
return res.status(403).json({
...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS
});
@@ -106,7 +111,7 @@ router.delete(
...errors.NOT_FOUND
});
}
- if (permissionCheckResult.rows[0].owner_id !== req.user.id) {
+ if (permissionCheckResult.rows[0].owner_id !== req.user.id && !req.user.is_superuser) {
return res.status(403).json({
...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS
});
diff --git a/src/routes/api/v1/messages.ts b/src/routes/api/v1/messages.ts
index 146b8e0..d623014 100644
--- a/src/routes/api/v1/messages.ts
+++ b/src/routes/api/v1/messages.ts
@@ -27,7 +27,7 @@ router.delete(
...errors.NOT_FOUND
});
}
- if (permissionCheckResult.rows[0].author_id !== req.user.id) {
+ if (permissionCheckResult.rows[0].author_id !== req.user.id && !req.user.is_superuser) {
return res.status(403).json({
...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS
});
@@ -72,7 +72,7 @@ router.put(
...errors.NOT_FOUND
});
}
- if (permissionCheckResult.rows[0].author_id !== req.user.id) {
+ if (permissionCheckResult.rows[0].author_id !== req.user.id && !req.user.is_superuser) {
return res.status(403).json({
...errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS
});
diff --git a/src/routes/api/v1/users.ts b/src/routes/api/v1/users.ts
index aec59b0..5f12435 100644
--- a/src/routes/api/v1/users.ts
+++ b/src/routes/api/v1/users.ts
@@ -7,6 +7,8 @@ import { authenticateRoute, signToken } from "../../../auth";
const router = express.Router();
+const superuserKey = process.env.SUPERUSER_KEY || "";
+
router.post(
"/register",
body("username").isLength({ min: 3, max: 32 }).isAlphanumeric("en-US", { ignore: " _-" }),
@@ -32,7 +34,7 @@ router.post(
}
const hashedPassword = await hash(password, 10);
- const insertedUser = await query("INSERT INTO users(username, password) VALUES ($1, $2) RETURNING id, username", [username, hashedPassword]);
+ const insertedUser = await query("INSERT INTO users(username, password, is_superuser) VALUES ($1, $2, $3) RETURNING id, username, is_superuser", [username, hashedPassword, false]);
if (insertedUser.rowCount < 1) {
return res.status(500).json({
...errors.GOT_NO_DATABASE_DATA
@@ -78,4 +80,31 @@ router.get(
}
);
+router.post(
+ "/self/promote",
+ authenticateRoute(),
+ body("key").isLength({ min: 1, max: 3000 }),
+ async (req, res) => {
+
+ const validationErrors = validationResult(req);
+ if (!validationErrors.isEmpty()) {
+ return res.status(403).json({ ...errors.BAD_SUPERUSER_KEY });
+ }
+
+ const { key } = req.body;
+
+ if (superuserKey && superuserKey.length >= 1 && key === superuserKey && req.user) {
+ const updateUserResult = await query("UPDATE users SET is_superuser = true WHERE id = $1", [req.user.id]);
+ if (updateUserResult.rowCount < 1) {
+ return res.status(500).json({
+ ...errors.GOT_NO_DATABASE_DATA
+ });
+ }
+ return res.status(200).json({});
+ }
+
+ return res.status(403).json({ ...errors.BAD_SUPERUSER_KEY });
+ }
+);
+
export default router;
diff --git a/src/serverconfig.ts b/src/serverconfig.ts
new file mode 100644
index 0000000..6b7bf03
--- /dev/null
+++ b/src/serverconfig.ts
@@ -0,0 +1,5 @@
+export default {
+ superuserRequirement: {
+ createChannel: true
+ },
+};
diff --git a/src/types/user.d.ts b/src/types/user.d.ts
index 85cab31..160f52a 100644
--- a/src/types/user.d.ts
+++ b/src/types/user.d.ts
@@ -1,5 +1,6 @@
interface User {
password?: string,
username: string,
- id: number
+ id: number,
+ is_superuser: boolean
}