diff --git a/brainlet/api/v1/content.js b/brainlet/api/v1/content.js
index e471753..1a01e4a 100755
--- a/brainlet/api/v1/content.js
+++ b/brainlet/api/v1/content.js
@@ -21,6 +21,8 @@ app.post("/channel/create", [
createLimiter,
body("title").not().isEmpty().trim().isLength({ min: 3, max: 32 }).escape()
], authenticateEndpoint(async (req, res, user) => {
+ if (!config.policies.allowChannelCreation) return res.status(403).json({ error: true, message: "ERROR_FORBIDDEN_BY_POLICY" });
+
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() });
@@ -47,6 +49,8 @@ app.post("/post/create", [
body("title").not().isEmpty().trim().isLength({ min: 3, max: 32 }).escape(),
body("body").not().isEmpty().trim().isLength({ min: 3, max: 1000 }).escape(),
], authenticateEndpoint(async (req, res, user) => {
+ if (!config.policies.allowPostCreation) return res.status(403).json({ error: true, message: "ERROR_FORBIDDEN_BY_POLICY" });
+
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() });
diff --git a/brainlet/api/v1/index.js b/brainlet/api/v1/index.js
index d81acae..14f7990 100755
--- a/brainlet/api/v1/index.js
+++ b/brainlet/api/v1/index.js
@@ -9,8 +9,7 @@ app.use("/users", usersAPI);
app.use("/content", contentAPI);
app.get("/", (req, res) => {
- // TODO: Add more checks for this, or maybe remove
- res.json({ apiStatus: "OK", apis: [ "users", "content" ] });
+ res.json({ error: false });
});
module.exports = app;
\ No newline at end of file
diff --git a/brainlet/api/v1/users.js b/brainlet/api/v1/users.js
index fb7fb9d..75b7823 100755
--- a/brainlet/api/v1/users.js
+++ b/brainlet/api/v1/users.js
@@ -46,6 +46,8 @@ app.post("/account/create", [
body("password").not().isEmpty().isLength({ min: 8, max: 128 }),
body("specialCode").optional().isLength({ min: 12, max: 12 }).isAlphanumeric()
], async (req, res) => {
+ if (!config.policies.allowAccountCreation) return res.status(403).json({ error: true, message: "ERROR_FORBIDDEN_BY_POLICY" });
+
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
@@ -114,6 +116,8 @@ app.post("/token/create", [
body("username").not().isEmpty().trim().isAlphanumeric(),
body("password").not().isEmpty()
], async (req, res) => {
+ if (!config.policies.allowLogin) return res.status(403).json({ error: true, message: "ERROR_FORBIDDEN_BY_POLICY" });
+
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(400).json({ error: true, message: "ERROR_REQUEST_LOGIN_INVALID" });
@@ -141,7 +145,7 @@ app.post("/token/create", [
return;
}
- jwt.sign({ username }, secret.jwtPrivateKey, { expiresIn: "3h" }, async (err, token) => {
+ jwt.sign({ username }, secret.jwtPrivateKey, { expiresIn: config.tokenExpiresIn }, async (err, token) => {
if (err) {
res.status(500).json({
error: true,
@@ -150,13 +154,6 @@ app.post("/token/create", [
return;
}
- // TODO: Ugly fix for setting httponly cookies
- if (req.body.alsoSetCookie) {
- res.cookie("token", token, {
- maxAge: 3 * 60 * 60 * 1000, httpOnly: true, domain: config.address,
- });
- }
-
const userObject = await existingUser.getPublicObject();
console.log("[*] [logger] [users] [token create] Token created", userObject);
@@ -176,10 +173,7 @@ app.get("/current/info", authenticateEndpoint(async (req, res, user) => {
res.status(200).json({
error: false,
message: "SUCCESS_USER_DATA_FETCHED",
- user: {
- token: req.cookies.token, // TODO: Passing the token like this is *terribly* insecure
- ...userObject
- },
+ user: userObject
});
}, undefined, 0));
@@ -209,9 +203,4 @@ app.get("/user/:userid/info", [
});
}, undefined, config.roleMap.USER));
-app.post("/browser/token/clear", authenticateEndpoint((req, res) => {
- res.clearCookie("token");
- res.sendStatus(200);
-}));
-
module.exports = app;
diff --git a/brainlet/api/v2/gateway/index.js b/brainlet/api/v2/gateway/index.js
index 868e415..8742e36 100644
--- a/brainlet/api/v2/gateway/index.js
+++ b/brainlet/api/v2/gateway/index.js
@@ -3,6 +3,7 @@ const EventEmitter = require("events");
const uuid = require("uuid");
const werift = require("werift");
+const { policies } = require("../../../config");
const { experiments } = require("../../../experiments");
const User = require("../../../models/User");
const Channel = require("../../../models/Channel");
@@ -61,6 +62,7 @@ class GatewayServer extends EventEmitter {
});
this.wss.on("connection", (ws) => {
+ if (!policies.allowGatewayConnection) return ws.close(4007, "Disallowed by policy.");
// Send HELLO message as soon as the client connects
ws.send(packet("HELLO", {}));
ws.session = {
diff --git a/brainlet/app/app.html b/brainlet/app/app.html
deleted file mode 100755
index ed2ba9d..0000000
--- a/brainlet/app/app.html
+++ /dev/null
@@ -1,207 +0,0 @@
-
-
-
-
-
- App
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Debug info and shit
-
-
- gateway.isConnected: {{ gateway.isConnected }}
- gateway.socket.id: {{ gateway.socket.id }}
- gateway.debugInfo: {{ JSON.stringify(gateway.debugInfo) }}
- userLoggedIn: true
- userLoggedIn: false
-
-
loggedInUser.username: {{ loggedInUser.username }}
-
loggedInUser._id: {{ loggedInUser._id }}
-
loggedInUser.permissionLevel: {{ loggedInUser.permissionLevel }}
-
loggedInUser.role: {{ loggedInUser.role }}
-
-
-
- Dump
- Close
-
-
-
-
-
- Create channel
-
-
-
-
-
-
-
-
- Close
- Create
-
-
-
-
-
- Create post for {{ selection.channel.title }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- Close
- Create
-
-
-
-
-
- {{ viewingProfile.username }}
-
-
- Role: {{ viewingProfile.role }}
-
-
- Close
-
-
-
-
-
- Brainlet
-
- {{ loggedInUser.username }}
-
- Manage accountperson
- Debug info and shitcode
-
-
-
-
-
- Browsing channel: {{ selection.channel.title }}
- Browsing {{ selection.channel.title }}
-
- Browsing {{ selection.channel.title }} with
- {{ user.user.username }}
-
- arrow_back
- refresh
-
-
-
-
-
-
- by {{ post.creator.username }}
-
-
-
-
-
- {{ button.text }}
-
-
-
-
-
-
-
- add
- edit
-
-
-
-
- add
- Create a new post
-
-
-
- category
- Create a new channel
-
-
-
-
-
-
-
-
-
-
- {{ snackbarNotification }}
- {{ snackbarButtonText }}
-
-
-
-
\ No newline at end of file
diff --git a/brainlet/app/auth.html b/brainlet/app/auth.html
deleted file mode 100755
index 4f76fb9..0000000
--- a/brainlet/app/auth.html
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
-
-
- Auth
-
-
-
-
-
-
-
-
-
-
-
-
- Brainlet
- Home
-
-
-
-
-
- {{ modeName }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- The owner of this Brainlet instance has made it so that signing up requires a special code.
-
-
-
-
-
-
-
-
-
-
- Log in instead
- Sign up instead
-
- Go back
- Sign up
-
- Log in
-
- Log out
-
-
-
-
-
-
- {{ snackbarNotification }}
- {{ snackbarButtonText }}
-
-
-
-
\ No newline at end of file
diff --git a/brainlet/app/gatewaytest/app.js b/brainlet/app/gatewaytest/app.js
deleted file mode 100644
index 90fa8f0..0000000
--- a/brainlet/app/gatewaytest/app.js
+++ /dev/null
@@ -1,210 +0,0 @@
-const opcodes = {
- 0: { name: "HELLO", data: "JSON" },
- 1: { name: "YOO", data: "JSON" },
- 2: { name: "YOO_ACK", data: "JSON" },
- 3: { name: "ACTION_CREATE_MESSAGE", data: "JSON" },
- 4: { name: "EVENT_CREATE_MESSAGE", data: "JSON" },
- 21: { name: "ACTION_VOICE_REQUEST_SESSION", data: "JSON" },
- 22: { name: "EVENT_VOICE_ASSIGN_SERVER", data: "JSON" },
- 23: { name: "ACTION_VOICE_CONNECTION_REQUEST", data: "JSON" },
- 24: { name: "EVENT_VOICE_CONNECTION_ANSWER", data: "JSON" }
-};
-
-const opcodeSeparator = "@";
-
-const parseMessage = (message) => {
- if (typeof message !== "string") throw new Error("msg: message not a string");
- const stringParts = message.split(opcodeSeparator);
- if (stringParts < 2) throw new Error("msg: message does not split into more than 2 parts");
- const components = [ stringParts.shift(), stringParts.join(opcodeSeparator) ];
- const op = parseInt(components[0]);
- if (isNaN(op)) throw new Error(`msg: message does not contain valid opcode: ${op}`);
-
- const opcodeData = opcodes[op];
- let data = components[1];
- if (!opcodeData) throw new Error(`msg: message contains unknown opcode ${op}`);
- if (opcodeData.data === "JSON") {
- data = JSON.parse(data);
- } else if (opcodeData.data === "string") {
- data = data.toString(); // NOTE: This isnt needed lol
- } else {
- throw new Error(`msg: invalid data type on opcode ${op}`);
- }
-
- return {
- opcode: op,
- data: data,
- dataType: opcodeData.data,
- opcodeType: opcodeData.name || null
- };
-};
-
-const getOpcodeByName = (name) => {
- for (const [key, value] of Object.entries(opcodes)) if (value.name === name) return key;
-};
-
-
-class GatewayConnection {
- constructor(token, gatewayUrl) {
- this.ws = new WebSocket(gatewayUrl);
-
- this.handshakeCompleted = false;
- this.sessionInformation = null;
-
- this.ws.onopen = () => console.log("gateway: open");
- this.ws.onclose = (e) => {
- this.handshakeCompleted = false;
- console.log(`gateway: close: ${e.code}:${e.reason}`);
- this.fire("onclose", e);
- };
- this.ws.onmessage = (message) => {
- try {
- const packet = parseMessage(message.data);
- if (!packet) return console.error("gateway: invalid packet from server");
-
- switch (packet.opcodeType) {
- case "HELLO": {
- // Got HELLO from server, send YOO as soon as possible
- console.log("gateway: got HELLO", packet.data);
- console.log("gateway: sending YOO");
- this.ws.send(this.packet("YOO", { token }));
- break;
- }
- case "YOO_ACK": {
- // Server accepted connection
- console.log("gateway: got YOO_ACK", packet.data);
- this.handshakeCompleted = true;
- this.sessionInformation = packet.data;
- console.log("gateway: handshake complete");
- this.fire("onopen", packet.data);
- break;
- }
- case "EVENT_CREATE_MESSAGE": {
- // New message
- // console.log("gateway: got new message", packet.data);
- this.fire("onmessage", packet.data);
- break;
- }
- default: {
- console.log("gateway: got unknown packet", message.data);
- break;
- }
- }
- } catch(e) {
- return console.error("gateway:", e);
- }
- };
- }
-}
-
-GatewayConnection.prototype.sendMessage = function(content, channelId) {
- if (!this.sessionInformation) throw new Error("gateway: tried to send message before handshake completion");
-
- this.ws.send(this.packet("ACTION_CREATE_MESSAGE", {
- content,
- channel: {
- _id: channelId
- }
- }));
-};
-
-
-GatewayConnection.prototype.packet = function(op, data) {
- if (typeof op === "string") op = getOpcodeByName(op);
- return `${op}${opcodeSeparator}${JSON.stringify(data)}`;
-};
-
-GatewayConnection.prototype.fire = function(eventName, ...args) {
- if (this[eventName]) return this[eventName](...args);
-};
-
-
-class AppState {
- constructor(token, gatewayUrl) {
- this.connection = new GatewayConnection(token, gatewayUrl);
- this.tc = new Tricarbon();
-
- this.elements = {
- messagesContainer: "#messages",
- messageInput: "#message-input",
- channels: "#channels",
- topBar: "#top-bar"
- };
-
- this.tcEvents = this.tc.useEvents();
- this.messageObserver = new MutationObserver((mutationsList) => {
- for (const mutation of mutationsList) {
- if (mutation.type === "childList") {
- const messages = this.tc.A(this.elements.messagesContainer);
- messages.scrollTop = messages.scrollHeight;
- }
- }
- });
-
- this.messageStore = {};
- this.selectedChannelId = null;
-
- this.Sidebar = (channels) => (ev) => `
- ${Object.keys(channels).map(k => `
-
- `).join("")}
- `;
-
- this.ChannelMessages = (messages) => () => `
- ${Object.keys(messages).map(k => `
-
- ${messages[k].author.username}
- ${messages[k].content}
-
- `).join("")}
- `;
-
- this.ChannelTopBar = (channel) => () => `
- ${channel.title}
- `;
-
- this.connection.onopen = (sessionInfo) => {
- this.renderSidebar(sessionInfo.channels);
- this.messageObserver.observe(this.tc.A(this.elements.messagesContainer), { childList: true });
- this.tc.A(this.elements.messageInput).addEventListener("keydown", (e) => {
- if (e.code === "Enter") {
- if (!this.selectedChannelId) return;
- const messageContent = this.tc.A(this.elements.messageInput).value;
- if (!messageContent) return;
- this.connection.sendMessage(messageContent, this.selectedChannelId);
- this.tc.A(this.elements.messageInput).value = "";
- }
- });
- };
- this.connection.onmessage = (message) => {
- this.appendMessage(message);
- };
- }
-}
-
-AppState.prototype.appendMessage = function(message) {
- if (!this.messageStore[message.channel._id]) this.messageStore[message.channel._id] = [];
- this.messageStore[message.channel._id].push({ content: message.content, author: message.author });
- if (this.selectedChannelId === message.channel._id) {
- this.tc.push(this.ChannelMessages(this.messageStore[message.channel._id] || []), this.elements.messagesContainer, false, this.tcEvents(this.elements.messagesContainer));
- }
-};
-
-AppState.prototype.navigateToChannel = function(channel) {
- console.log("app: navigating to channel", channel);
- if (this.selectedChannelId !== channel._id) {
- this.selectedChannelId = channel._id;
- this.tc.push(this.ChannelTopBar(channel), this.elements.topBar, false);
- this.tc.push(this.ChannelMessages(this.messageStore[channel._id] || []), this.elements.messagesContainer, false, this.tcEvents(this.elements.messagesContainer));
- }
-};
-
-AppState.prototype.renderSidebar = function(channels) {
- this.tc.push(this.Sidebar(channels), this.elements.channels, false, this.tcEvents(this.elements.channels));
-};
-
-let app;
-document.addEventListener("DOMContentLoaded", () => {
- if (!localStorage.getItem("gatewayUrl")) localStorage.setItem("gatewayUrl", `ws://${window.location.hostname}/gateway?v=2`);
- app = new AppState(localStorage.getItem("token"), localStorage.getItem("gatewayUrl"));
-});
\ No newline at end of file
diff --git a/brainlet/app/gatewaytest/index.html b/brainlet/app/gatewaytest/index.html
deleted file mode 100644
index 7ecfe06..0000000
--- a/brainlet/app/gatewaytest/index.html
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- █████
-
-
-
- █████
- ██████████
-
-
- ████
- ██████
-
-
- █████
- ████████
-
-
- ███
- █████████████
-
-
-
-
-
-
-
diff --git a/brainlet/app/gatewaytest/style.css b/brainlet/app/gatewaytest/style.css
deleted file mode 100644
index e7b8c9d..0000000
--- a/brainlet/app/gatewaytest/style.css
+++ /dev/null
@@ -1,121 +0,0 @@
-/* This CSS is very hacky and extremely inefficient. No human being, alive or dead, shall go through the pain of reading or maintaining this code */
-
-:root {
- --bg: #000000;
- --fg: #ffffff;
-}
-
-body {
- align-items: center;
- justify-content: center;
- display: flex;
- margin: 0;
- height: 100%;
- font-family: Verdana, Geneva, Tahoma, sans-serif;
-
- background-color: var(--bg);
- color: var(--fg);
-}
-
-main {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- flex-grow: 0;
- width: 800px;
- height: 600px;
- margin: 10px;
-
- background-color: var(--bg);
- color: var(--fg);
- border: 1px solid var(--fg);
-}
-
-.channel-view {
- display: flex;
- flex-direction: column;
- flex: 10;
- height: 100%;
-}
-
-.sidebar {
- min-width: 150px;
- max-width: 150px;
- max-height: 100%;
- display: flex;
- flex-direction: column;
- overflow-y: auto;
-
- border-right: 1px solid var(--fg);
-}
-
-.sidebar-button {
- max-width: inherit;
- border: 0;
- padding: 8px;
- margin-right: 0;
- margin-left: 0;
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
- text-align: left;
- min-height: 34px;
- max-height: 34px;
-
- border-bottom: 1px solid var(--fg);
- background-color: var(--bg);
- color: var(--fg);
-}
-
-.sidebar-button:hover, .sidebar-button.selected {
- font-weight: bold;
-}
-
-.top-bar {
- display: flex;
- min-height: 33px;
- max-height: 33px;
- flex: 1;
- align-items: center;
- justify-content: center;
-
- background-color: var(--bg);
- border-bottom: 1px solid var(--fg);
-}
-
-.bottom-bar {
- min-height: 33px;
- max-height: 33px;
- flex: 1;
- flex-wrap: wrap;
-
- background-color: var(--bg);
- border-top: 1px solid var(--fg);
-}
-
-.channel-message-container {
- padding: 15px;
- flex: 1;
- flex-wrap: wrap;
- overflow-y: auto;
- height: 100%;
-
- background-color: var(--bg);
-}
-
-input {
- background-color: var(--bg);
- color: var(--fg);
- border: none;
-}
-
-@media only screen and (max-width: 800px) {
- main {
- width: 95%;
- height: 95%;
- }
- .sidebar {
- min-width: 100px;
- max-width: 100px;
- }
-}
\ No newline at end of file
diff --git a/brainlet/app/index.html b/brainlet/app/index.html
new file mode 100644
index 0000000..dccdec9
--- /dev/null
+++ b/brainlet/app/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ Document
+
+
+ Welcome to the API server!
+ This server is hosting the Brainlet API server. Clients may now use it.
+
+ Setting up the React frontend:
+
+ - Clone the repository
+ - Enter the frontend folder
+ - Install the dependencies using `npm i`
+ - Build the static files with `npm run build`
+ - Copy the static files from the build folder into the app folder on the server (replacing these files)
+
+
+ This server is running Brainlet.
+
+
\ No newline at end of file
diff --git a/brainlet/app/resources/css/base.css b/brainlet/app/resources/css/base.css
deleted file mode 100755
index e69de29..0000000
diff --git a/brainlet/app/resources/js/app.js b/brainlet/app/resources/js/app.js
deleted file mode 100755
index 64161c9..0000000
--- a/brainlet/app/resources/js/app.js
+++ /dev/null
@@ -1,577 +0,0 @@
-Vue.use(VueMaterial.default);
-
-const getCreatePostError = (json) => {
- switch (json.message) {
- case 'ERROR_REQUEST_INVALID_DATA': {
- switch (json.errors[0].param) {
- case 'title': {
- return 'Invalid title. Must be between 3 and 32 characters.';
- }
- case 'body': {
- return 'Invalid content. Must be between 3 and 1000 characters';
- }
- case 'channel': {
- return 'Invalid channel. Something went wrong.';
- }
- default: {
- return 'Invalid value sent to server. Something went wrong.';
- }
- }
- }
-
- case 'ERROR_CATEGORY_NOT_FOUND': {
- return 'The channel you tried to post to no longer exists.';
- }
-
- case 'ERROR_ACCESS_DENIED': {
- return 'You are not allowed to perform this action.'
- }
-
- default: {
- return 'Unknown error. Something went wrong.';
- }
- }
-}
-
-const getCreateChannelError = (json) => {
- switch (json.message) {
- case 'ERROR_REQUEST_INVALID_DATA': {
- switch (json.errors[0].param) {
- case 'title': {
- return 'Invalid title. Title must be between 3 and 32 characters.';
- }
- }
- }
-
- case 'ERROR_ACCESS_DENIED': {
- return 'You are not allowed to perform this action.'
- }
-
- default: {
- return 'Unknown error. Something went wrong.';
- }
- }
-}
-
-class GatewayConnection {
- constructor() {
- this.isConnected = false;
- this.socket = null;
-
- // TODO: set up proper event listening and such, not this dumb crap
- this.onDisconnect = () => {}
- this.onConnect = () => {}
- }
-}
-
-GatewayConnection.prototype.disconnect = function() {
- this.socket?.disconnect();
- this.socket = null;
- this.isConnected = false;
-};
-
-GatewayConnection.prototype.connect = function(token) {
- console.log('[*] [gateway] [handshake] Trying to connect to gateway');
- this.socket = io('/gateway', {
- query: {
- token
- },
- transports: ['websocket']
- });
-
- this.socket.on('connect', () => {
- this.socket.once('hello', (debugInfo) => {
- console.log('[*] [gateway] [handshake] Got hello from server, sending yoo...', debugInfo);
- this.socket.emit('yoo');
- this.isConnected = true;
- this.debugInfo = debugInfo;
- this.onConnect('CONNECT_RECEIVED_HELLO');
- console.log('[*] [gateway] [handshake] Assuming that server received yoo and that connection is completed.');
- });
- })
-
- this.socket.on('error', (e) => {
- console.log('[E] [gateway] Gateway error', e);
- this.isConnected = false;
- this.socket = null;
- this.onDisconnect('DISCONNECT_ERR', e);
- });
- this.socket.on('disconnectNotification', (e) => {
- console.log('[E] [gateway] Received disconnect notfication', e);
- this.isConnected = false;
- this.socket = null;
- this.onDisconnect('DISCONNECT_NOTIF', e);
- });
- this.socket.on('disconnect', (e) => {
- console.log('[E] [gateway] Disconnected from gateway: ', e);
- this.isConnected = false;
- this.onDisconnect('DISCONNECT', e);
- });
-};
-
-GatewayConnection.prototype.sendMessage = function(channelId, content) {
- if (!this.isConnected) return 1;
- if (content.length >= 2000) return 1;
-
- this.socket.emit('message', {
- channel: {
- _id: channelId
- },
- content
- });
-};
-
-GatewayConnection.prototype.subscribeToChannelChat = function(channelId) {
- if (!this.isConnected) return;
-
- const request = [channelId];
-
- console.log('[*] [gateway] Subscribing to channel(s)', request);
-
- this.socket.emit('subscribe', request);
-};
-
-
-const app = new Vue({
- el: '#app',
- data: {
- showSnackbarNotification: false,
- snackbarNotification: '',
- snackbarNotificationDuration: 999999,
- snackbarButtonText: 'Ok',
- loggedInUser: {},
- showApp: false,
- menuVisible: false,
- selection: {
- channel: {
- title: '',
- browsing: false,
- _id: undefined,
- isChannel: false,
- isChatContext: false
- },
- posts: []
- },
- cardButtons: [],
- dialog: {
- show: {
- createPost: false,
- createChannel: false,
- debug: false
- },
- text: {
- createPost: {
- title: '',
- body: ''
- },
- createChannel: {
- title: ''
- }
- }
- },
- viewingProfile: {
- show: false,
- _id: '',
- username: '',
- role: ''
- },
- gateway: new GatewayConnection(),
- messages: {
- 'X': [ { username: '__SYSTEM', content: 'TEST MSG' } ]
- },
- userLists: {
- 'X': [ { username: '__SYSTEM', _id: 'INVALID_ID' } ]
- },
- message: {
- typed: ''
- }
- },
- mounted: async function() {
- const res = await fetch(`${window.location.origin}/api/v1/users/current/info`, {
- method: 'GET',
- headers: {
- 'Accept': 'application/json',
- },
- credentials: 'include'
- });
-
- if (res.ok) {
- const json = await res.json();
- if (json.user.permissionLevel >= 1) {
- this.loggedInUser = json.user;
- this.showApp = true;
- this.performGatewayConnection();
- this.browseChannels();
- Notification.requestPermission();
- } else {
- this.showApp = false;
- this.snackbarEditButton('Manage', () => {
- window.location.href = `${window.location.origin}/auth.html`;
- this.resetSnackbarButton();
- this.showSnackbarNotification = false;
- });
- this.notification('Your account does not have the required permissions to enter this page');
- }
- } else {
- this.showApp = false;
- this.snackbarEditButton('Manage', () => {
- window.location.href = `${window.location.origin}/auth.html`;
- this.resetSnackbarButton();
- this.showSnackbarNotification = false;
- });
- this.notification('You are not logged in or your session is invalid');
- }
- },
- methods: {
- // Gateway and chat
- performGatewayConnection: function() {
- // TODO: again, the thing im doing with the token is not very secure, since its being sent by the current user info endpoint and is also being send through query parameters
- this.gateway.onDisconnect = (e) => {
- this.okNotification('Connection lost.');
- };
- this.gateway.connect(this.loggedInUser.token);
- this.gateway.socket.on('message', (e) => {
- //console.log('[*] [gateway] Message received', e);
- this.processMessage(e);
- });
- this.gateway.socket.on('clientListUpdate', (e) => {
- console.log('[*] [gateway] Client list update', e);
- this.processUserListUpdate(e);
- });
- this.gateway.socket.on('refreshClient', (e) => {
- console.log('[*] [gateway] Gateway requested refresh', e);
- this.gateway.disconnect();
- this.messages = {};
- this.userLists = {};
- this.message.typed = '';
- if (e.reason === 'exit') {
- this.showApp = false;
- this.okNotification('The server has exited. Sit tight!');
- } else if (e.reason === 'upd') {
- this.showApp = false;
- this.okNotification('An update has just rolled out! To ensure everything runs smoothly, you need to refresh the page!');
- } else {
- this.showApp = false;
- this.okNotification('Sorry, but something happened and a refresh is required to keep using the app!');
- }
- this.snackbarEditButton('Refresh', () => {
- window.location.reload();
- });
- });
- },
- shouldMergeMessage: function(messageObject, channelMessageList) {
- const lastMessageIndex = channelMessageList.length-1;
- const lastMessage = channelMessageList[lastMessageIndex];
-
- if (!lastMessage) return;
- if (lastMessage.author._id === messageObject.author._id) {
- if (lastMessage.nickAuthor && messageObject.nickAuthor) {
- if (lastMessage.nickAuthor.username === messageObject.nickAuthor.username) {
- return true;
- } else {
- return false;
- }
- }
- }
- if (lastMessage.author._id === messageObject.author._id) return true;
- return false;
- },
- processMessage: async function(messageObject) {
- if (!this.messages[messageObject.channel._id]) this.$set(this.messages, messageObject.channel._id, []);
- const channelMessageList = this.messages[messageObject.channel._id];
- const lastMessageIndex = channelMessageList.length-1;
-
- if (this.shouldMergeMessage(messageObject, channelMessageList)) {
- channelMessageList[lastMessageIndex].content += `\n${messageObject.content}`;
- } else {
- this.messages[messageObject.channel._id].push(messageObject);
- }
-
- if (messageObject.channel._id === this.selection.channel._id) {
- this.$nextTick(() => {
- // TODO: When the user presses back, actually undo this scroll cause its annoying to scroll back up in the channel list
- const container = this.$el.querySelector('#posts-container');
- container.scrollTop = container.scrollHeight;
- });
- }
-
- if (messageObject.author.username !== this.loggedInUser.username && messageObject.channel._id !== this.selection.channel._id) {
- this.okNotification(`${messageObject.channel.title}/${messageObject.author.username}: ${messageObject.content}`);
- }
-
- if (messageObject.author.username !== this.loggedInUser.username) {
- if (Notification.permission === 'granted') {
- try {
- new Notification(`${messageObject.channel.title}/${messageObject.author.username}`, {
- body: messageObject.content
- });
- } catch(e) {
- console.log('[E] [chat] Failed to show notification');
- }
-
- }
- }
- },
- processUserListUpdate: async function(e) {
- const { channel, clientList } = e;
- if (!this.userLists[channel._id]) this.$set(this.userLists, channel._id, []);
- this.userLists[channel._id] = clientList;
- },
- openChatForChannel: async function(channelId) {
- this.gateway.subscribeToChannelChat(channelId);
-
- this.selection.channel.isChatContext = true;
- this.selection.channel.browsing = true;
- this.selection.channel.title = 'Chat';
- this.selection.channel._id = channelId;
- },
- sendCurrentMessage: async function() {
- const status = await this.gateway.sendMessage(this.selection.channel._id, this.message.typed);
- if (status === 1) {
- this.okNotification('Failed to send message!');
- return;
- }
- this.message.typed = '';
- },
-
- // Debug
- toggleDebugDialog: async function() {
- this.dialog.show.debug = !this.dialog.show.debug;
- },
- debugDump: async function() {
- console.log('[DEBUG DUMP] [gateway]', this.gateway);
- console.log('[DEBUG DUMP] [loggedInUser] (this contains sensitive information about the current logged in user, do not leak it to other people lol)', this.loggedInUser);
- },
-
- // Channel and post browsing
- browseChannels: async function() {
- const res = await fetch(`${window.location.origin}/api/v1/content/channel/list?count=50`, {
- method: 'GET',
- headers: {
- 'Accept': 'application/json',
- },
- credentials: 'include'
- });
-
- if (res.ok) {
- const json = await res.json();
-
- this.selection.channel.title = 'channels';
- this.selection.channel.browsing = true;
- this.selection.channel.isChannel = false;
- this.selection.channel.isChatContext = false;
- this.selection.channel._id = '__CATEGORY_LIST';
- this.selection.posts = [];
-
- this.cardButtons = [];
-
- this.button('chat', (post) => {
- if (post._id) {
- this.openChatForChannel(post._id);
- }
- });
- this.button('topic', (post) => {
- this.browse(post);
- });
-
- for (let i = 0; i < json.channels.length; i++) {
- const v = json.channels[i];
- this.selection.posts.push({ title: v.title, body: '', _id: v._id, creator: v.creator });
- }
- } else {
- this.okNotification('Failed to fetch channel list');
- }
- },
- browse: async function(channel) {
- const { _id, title } = channel;
-
- const res = await fetch(`${window.location.origin}/api/v1/content/channel/${_id}/info`, {
- method: 'GET',
- headers: {
- 'Accept': 'application/json',
- },
- credentials: 'include'
- });
-
- if (res.ok) {
- const json = await res.json();
-
- this.selection.channel.title = title;
- this.selection.channel._id = _id;
- this.selection.channel.browsing = true;
- this.selection.channel.isChannel = true;
- this.selection.channel.isChatContext = false;
- this.selection.posts = [];
-
- this.cardButtons = [];
-
- for (let i = 0; i < json.channel.posts.length; i++) {
- const v = json.channel.posts[i];
- this.selection.posts.push({ title: v.title, body: v.body, _id: v._id, creator: v.creator });
- }
- } else {
- this.okNotification('Failed to fetch channel');
- }
- },
- refresh: function() {
- if (this.selection.channel.title === 'channels' && this.selection.channel.isChannel === false) {
- this.browseChannels();
- } else {
- this.browse(this.selection.channel);
- }
- },
- button: function(text, click) {
- this.cardButtons.push({ text, click });
- },
- stopBrowsing: function() {
- this.selection.channel = {
- title: '',
- browsing: false,
- _id: undefined,
- isChannel: false,
- isChatContext: false
- };
- this.selection.posts = [];
- this.cardButtons = [];
- },
- viewProfile: async function(id) {
- // TODO: this just returns the username for now
- const res = await fetch(`${window.location.origin}/api/v1/users/user/${id}/info`, {
- method: 'GET',
- headers: {
- 'Accept': 'application/json',
- },
- credentials: 'include'
- });
-
- if (res.ok) {
- const json = await res.json();
- this.viewingProfile.username = json.user.username;
- this.viewingProfile._id = json.user._id;
- this.viewingProfile.role = json.user.role;
- this.viewingProfile.show = true;
- } else {
- this.okNotification('Failed to fetch user data');
- }
- },
-
- // Content creation
- showCreatePostDialog: function() {
- if (!this.selection.channel.isChannel) {
- this.okNotification('You are not in a channel');
- return;
- }
-
- this.dialog.show.createPost = true;
- },
- createPost: async function() {
- if (!this.selection.channel.isChannel) {
- this.okNotification('You are not in a channel');
- return;
- }
-
- const channel = this.selection.channel;
- const input = this.dialog.text.createPost;
-
- const res = await fetch(`${window.location.origin}/api/v1/content/post/create`, {
- method: 'POST',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
- },
- credentials: 'include',
- body: JSON.stringify({
- channel: channel._id,
- title: input.title,
- body: input.body
- })
- });
-
- if (res.ok) {
- this.okNotification('Successfully created post');
- this.dialog.show.createPost = false;
- this.browse(this.selection.channel);
- return;
- } else {
- if (res.status === 401 || res.status === 403) {
- this.okNotification('You are not allowed to do that');
- return;
- }
- if (res.status === 429) {
- this.okNotification('Chill! You are posting too much!');
- return;
- }
-
- const json = await res.json();
- this.okNotification(getCreatePostError(json));
- return;
- }
- },
- createChannel: async function() {
- const input = this.dialog.text.createChannel;
-
- const res = await fetch(`${window.location.origin}/api/v1/content/channel/create`, {
- method: 'POST',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
- },
- credentials: 'include',
- body: JSON.stringify({
- title: input.title
- })
- });
-
- if (res.ok) {
- this.okNotification('Successfully created channel');
- this.dialog.show.createChannel = false;
- this.browseChannels();
- return;
- } else {
- if (res.status === 401 || res.status === 403) {
- this.okNotification('You are not allowed to do that');
- return;
- }
- if (res.status === 429) {
- this.okNotification('Chill! You are posting too much!');
- return;
- }
-
- const json = await res.json();
- this.okNotification(getCreateChannelError(json));
- return;
- }
- },
-
- // Navigation
- navigateToAccountManager() {
- window.location.href = `${window.location.origin}/auth.html`;
- },
-
- // Snackbar
- snackbarButtonAction: function() {
- this.showSnackbarNotification = false;
- },
- snackbarButtonClick: function() {
- this.snackbarButtonAction();
- },
- snackbarEditButton: function(buttonText="Ok", action) {
- this.snackbarButtonText = buttonText;
- this.snackbarButtonAction = action;
- },
- resetSnackbarButton: function() {
- this.snackbarButtonText = 'Ok';
- this.snackbarButtonAction = () => {
- this.showSnackbarNotification = false;
- };
- },
- notification: function(text) {
- this.snackbarNotification = text;
- this.showSnackbarNotification = true;
- },
- okNotification: function(text) {
- this.resetSnackbarButton();
- this.notification(text);
- }
- }
-});
diff --git a/brainlet/app/resources/js/auth.js b/brainlet/app/resources/js/auth.js
deleted file mode 100755
index f1e6788..0000000
--- a/brainlet/app/resources/js/auth.js
+++ /dev/null
@@ -1,265 +0,0 @@
-Vue.use(VueMaterial.default);
-
-const getLoginMessageFromError = (json) => {
- switch (json.message) {
- case 'ERROR_REQUEST_LOGIN_INVALID': {
- return 'Invalid username or password.';
- }
-
- case 'ERROR_ACCESS_DENIED': {
- return 'You are not allowed to perform this action.'
- }
-
- default: {
- return 'Unknown error. Something went wrong.'
- }
- }
-}
-
-const getSignupMessageFromError = (json) => {
- switch (json.message) {
- case 'ERROR_REQUEST_INVALID_DATA': {
-
- switch (json.errors[0].param) {
- case 'username': {
- return 'Invalid username. Username must be between 3 and 32 characters long, and be alphanumeric.';
- }
- case 'password': {
- return 'Invalid password. Password must be at least 8 characters long and at most 128 characters.';
- }
- case 'email': {
- return 'Invalid email.';
- }
- case 'specialCode': {
- return 'Invalid special code.';
- }
-
- default: {
- return 'Invalid value sent to server. Something went wrong.';
- }
- }
-
- }
-
- case 'ERROR_ACCESS_DENIED': {
- return 'You are not allowed to perform this action.'
- }
-
- case 'ERROR_REQUEST_USERNAME_EXISTS': {
- return 'That username is taken.';
- }
-
- default: {
- return 'Unknown error. Something went wrong.'
- }
- }
-};
-
-const app = new Vue({
- el: '#app',
- data: {
- usernameInput: '',
- emailInput: '',
- passwordInput: '',
- specialCodeInput: '',
- mode: 'SIGNUP',
- showSnackbarNotification: false,
- snackbarNotification: '',
- snackbarNotificationDuration: 999999,
- snackbarButtonText: 'Ok',
- successfulLogin: false,
- loggedInUser: {},
- requiresSpecialCode: null
- },
- mounted: async function() {
- const res = await fetch(`${window.location.origin}/api/v1/users/current/info`, {
- method: 'GET',
- headers: {
- 'Accept': 'application/json',
- },
- credentials: 'include'
- });
-
- if (res.status === 200) {
- const json = await res.json();
- if (json.user.permissionLevel >= 1) {
- this.loggedInUser = json.user;
- this.mode = 'MANAGE';
- } else {
- this.resetSnackbarButton();
- this.notification('Your account has been suspended');
- this.successfulLogin = true;
- }
- } else {
- const resInfo = await fetch(`${window.location.origin}/api/v1/users/account/create/info`, {
- method: 'GET',
- headers: {
- 'Accept': 'application/json',
- }
- });
-
- if (resInfo.ok) {
- const json = await resInfo.json();
- this.requiresSpecialCode = json.requiresSpecialCode;
-
- this.mode = 'SIGNUP';
- } else {
- this.mode = '_ERROR';
- }
- }
- },
- computed: {
- modeName: function() {
- switch (this.mode) {
- case 'SIGNUP': {
- return 'Sign up';
- }
- case 'LOGIN': {
- return 'Log in';
- }
- case 'LOADING': {
- return 'Loading...';
- }
- case 'MANAGE': {
- return this.loggedInUser.username || 'Unknown account';
- }
- case 'SPECIAL_CODE': {
- return 'Just one more step'
- }
- case 'NONE': {
- return '';
- }
- default: {
- return 'Something went wrong.';
- }
- }
- }
- },
- methods: {
- snackbarButtonAction: function() {
- this.showSnackbarNotification = false;
- },
- snackbarButtonClick: function() {
- this.snackbarButtonAction();
- },
- snackbarEditButton: function(buttonText="Ok", action) {
- this.snackbarButtonText = buttonText;
- this.snackbarButtonAction = action;
- },
- resetSnackbarButton: function() {
- this.snackbarButtonText = 'Ok';
- this.snackbarButtonAction = () => {
- this.showSnackbarNotification = false;
- };
- },
- notification: function(text) {
- this.snackbarNotification = text;
- this.showSnackbarNotification = true;
- },
- performTokenRemoval: async function() {
- const res = await fetch(`${window.location.origin}/api/v1/users/browser/token/clear`, {
- method: 'POST',
- credentials: 'include'
- });
-
- if (res.status === 200) {
- this.loggedInUser = {};
- this.snackbarEditButton('Home', () => {
- window.location.href = `${window.location.origin}`;
- this.resetSnackbarButton();
- this.showSnackbarNotification = false;
- });
- this.successfulLogin = true;
- this.notification('Successfully logged out');
- } else {
- this.resetSnackbarButton();
- this.notification('Could not log out');
- }
- },
- navigateHome: function() {
- window.location.href = `${window.location.origin}`;
- },
- performTokenCreation: async function() {
- const res = await fetch(`${window.location.origin}/api/v1/users/token/create`, {
- method: 'POST',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
- },
- credentials: 'include',
- body: JSON.stringify({
- username: this.usernameInput,
- password: this.passwordInput,
- alsoSetCookie: true
- })
- });
-
- const json = await res.json();
-
- if (json.error || res.status !== 200) {
- this.resetSnackbarButton();
- this.notification(getLoginMessageFromError(json));
- return;
- } else {
- document.cookie = `token=${json.token}; HTTPOnly=true; max-age=10800`;
- this.successfulLogin = true;
- this.snackbarEditButton('Home', () => {
- this.navigateHome();
- this.resetSnackbarButton();
- this.showSnackbarNotification = false;
- });
- this.notification(`Successfully logged into account "${json.user.username}"`);
- this.loggedInUser = { username: json.user.username, _id: json.user._id };
- return;
- }
- },
- performAccountCreation: async function() {
- let jsonData = {
- username: this.usernameInput,
- email: this.emailInput,
- password: this.passwordInput
- };
-
- if (this.requiresSpecialCode) {
- if (this.mode === 'SIGNUP') {
- this.mode = 'SPECIAL_CODE';
- return;
- } else if (this.mode !== 'SPECIAL_CODE') {
- return;
- }
-
- jsonData = {
- specialCode: this.specialCodeInput,
- ...jsonData
- }
- }
-
- const res = await fetch(`${window.location.origin}/api/v1/users/account/create`, {
- method: 'POST',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(jsonData)
- });
-
- const json = await res.json();
-
- console.log(json);
-
- if (json.error || res.status !== 200) {
- this.resetSnackbarButton();
- this.notification(getSignupMessageFromError(json));
- return;
- } else {
- this.snackbarEditButton('Login', () => {
- this.mode = 'LOGIN';
- this.resetSnackbarButton();
- this.showSnackbarNotification = false;
- });
- this.notification(`Account "${json.user.username}" successfully created`);
- return;
- }
- }
- }
-});
\ No newline at end of file
diff --git a/brainlet/config.js b/brainlet/config.js
index 7d5a62c..71c47ff 100755
--- a/brainlet/config.js
+++ b/brainlet/config.js
@@ -1,14 +1,37 @@
module.exports = {
- ports: {
- mainServerPort: 3005,
- },
- address: "localhost",
- //restrictions: {
- // signup: {
- // specialCode: ''
- // }
- //},
mongoUrl: "mongodb://127.0.0.1:27017/app",
+ ports: {mainServerPort: 3005},
+ corsAllowList: [
+ // This is the development corsAllowList. Modify it according to your setup and domains.
+ // Please note that the protocol (http://, https://) matters here. If you use https, make sure to add it as such.
+ // Ports also matter. (and obviously the domain matters too)
+
+ // EXAMPLE: If my domain is example.com and I'm hosting brainlet (and brainlet-react's static files in the app folder) with HTTPS:
+ // "https://example.com"
+
+ "http://localhost:3005", // Allow the server itself (provided it's listening on 3005)
+ //"http://localhost:3000" // Optionally allow the react app development server (which listens on 3000 by default)
+ ],
+ policies: {
+ // Currently, policies apply to all users - no matter the role.
+ allowChannelCreation: true,
+ allowPostCreation: false,
+ allowAccountCreation: true,
+ allowLogin: true,
+ allowGatewayConnection: true,
+ },
+ /*
+ --- Adding a special code requirement for account creation
+
+ - uncomment the code below and fill in the specialCode string with *A 12 CHARACTER* string
+ restrictions: {
+ signup: {
+ specialCode: ""
+ }
+ },
+ */
+ address: "localhost",
+ tokenExpiresIn: "8h",
bcryptRounds: 10,
roleMap: {
"BANNED": 0,
@@ -17,14 +40,4 @@ module.exports = {
"BOT": 3,
"ADMIN": 4
},
- gatewayStillNotConnectedTimeoutMS: 15*1000
};
-module.exports.corsAllowList = [
- // Allow the normal web interface
- `http://${module.exports.address}:${module.exports.ports.mainServerPort}`,
- `https://${module.exports.address}:${module.exports.ports.mainServerPort}`,
-
- // Allow brainet-react
- `http://${module.exports.address}:3000`,
- `https://${module.exports.address}:3000`
-];