diff --git a/backend/index.js b/backend/index.js index ed37fc9..a735b53 100644 --- a/backend/index.js +++ b/backend/index.js @@ -4,8 +4,6 @@ let express = require("express") let app = express() let bodyParser = require("body-parser") let os = require("os") -const cookieParser = require("cookie-parser") -app.use(cookieParser()) app.set("trust proxy", true) const socket = require("./lib/socket") const server = require("http").createServer(app) @@ -45,7 +43,6 @@ app.all("/api/*", (req, res) => { console.log(os.hostname()) app.use(require("./lib/errorHandler")) - server.listen(23998, () => { console.log("Initialized") console.log("Listening on port 0.0.0.0:" + 23998) diff --git a/backend/lib/authorize.js b/backend/lib/authorize.js index 611620c..f2bdb7b 100644 --- a/backend/lib/authorize.js +++ b/backend/lib/authorize.js @@ -1,10 +1,18 @@ const { User, Theme, Session } = require("../models") const Errors = require("./errors") +const { Op } = require("sequelize") module.exports = async function (req, res, next) { try { if (req.header("Authorization") && req.header("Authorization") !== "null") { const token = req.header("Authorization") - const session = await Session.findOne({ where: { session: token } }) + const session = await Session.findOne({ + where: { + session: token, + expiredAt: { + [Op.gt]: new Date() + } + } + }) if (session) { const user = await User.findOne({ where: { id: session.userId }, diff --git a/backend/lib/authorize_socket.js b/backend/lib/authorize_socket.js index e700453..ca25d38 100644 --- a/backend/lib/authorize_socket.js +++ b/backend/lib/authorize_socket.js @@ -1,51 +1,35 @@ const { User, Theme, Session } = require("../models") +const { Op } = require("sequelize") -const parseCookie = (str) => - str - .split(";") - .map((v) => v.split("=")) - .reduce((acc, v) => { - acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim()) - return acc - }, {}) module.exports = async function (socket, next) { try { - const cookies = parseCookie(socket.handshake.headers.cookie) - if (cookies["session"]) { - const session = await Session.findOne({ - where: { session: cookies["session"] } - }) - if (session) { - const user = await User.findOne({ - where: { id: session.userId }, - attributes: { - exclude: ["totp", "password", "emailToken"] - }, - include: [ - { - model: Theme, - as: "themeObject" - } - ], - raw: true - }) - if (user) { - if (user.banned) { - socket.user = { - id: null, - username: "Not Authenticated" - } - next() - } else { - socket.user = user - next() + const token = socket.handshake.auth.token + const session = await Session.findOne({ + where: { + session: token, + expiredAt: { + [Op.gt]: new Date() + } + } + }) + if (session) { + const user = await User.findOne({ + where: { id: session.userId }, + attributes: { + exclude: ["totp", "compassSession", "password"] + }, + include: [ + { + model: Theme, + as: "themeObject" } - } - } else { - socket.user = { - id: null, - username: "Not Authenticated" - } + ] + }) + if (user) { + await user.update({ + lastSeenAt: new Date().toISOString() + }) + socket.user = user next() } } else { diff --git a/backend/lib/errors.js b/backend/lib/errors.js index e2b75ac..1495f4d 100644 --- a/backend/lib/errors.js +++ b/backend/lib/errors.js @@ -45,7 +45,8 @@ let Errors = { 401 ], mailFail: ["There was an error sending the verification email.", 400], - invalidToken: ["The token you provided is invalid.", 400] + invalidToken: ["The token you provided is invalid.", 400], + cannotFriendYourself: ["You cannot friend yourself.", 400] } function processErrors(errorName) { diff --git a/backend/lib/socket.js b/backend/lib/socket.js index d576d98..8471311 100644 --- a/backend/lib/socket.js +++ b/backend/lib/socket.js @@ -122,126 +122,10 @@ module.exports = { socket.emit("unauthorized", { message: "Please reauth." }) - socket.on("token", async (token) => { - const session = await Session.findOne({ where: { session: token } }) - if (session) { - const user = await User.findOne({ - where: { id: session.userId }, - attributes: { - exclude: ["totp", "password", "emailToken"] - }, - include: [ - { - model: Theme, - as: "themeObject" - } - ] - }) - if (user) { - socket.user = user - socket.join(user.id) - socket.emit("authorized") - socket.join(user.id) - socket.emit("siteState", { - release: process.env.RELEASE, - notification: process.env.NOTIFICATION, - notificationType: process.env.NOTIFICATION_TYPE, - latestVersion: require("../../frontend/package.json").version - }) - const friends = await Friend.findAll({ - where: { - userId: user.id, - status: "accepted" - } - }) - await user.update({ - status: - user.storedStatus === "invisible" - ? "offline" - : user.storedStatus - }) - friends.forEach((friend) => { - io.to(friend.friendId).emit("userStatus", { - userId: user.id, - status: - user.storedStatus === "invisible" - ? "offline" - : user.storedStatus - }) - }) - socket.on("ping", () => { - socket.emit("pong") - }) - socket.on("bcBots/deleteMessage", (e) => { - if (socket.user.bot) { - socket.to(e.userId).emit("deleteMessage", e) - } else { - socket.emit("bcBots/deleteMessage", { - error: "You cannot perform this action." - }) - } - }) - socket.on("idle", async () => { - const user = await User.findOne({ - where: { - id: socket.user.id - } - }) - if (user.storedStatus === "online") { - friends.forEach((friend) => { - io.to(friend.friendId).emit("userStatus", { - userId: user.id, - status: "away" - }) - }) - io.to(user.id).emit("userStatus", { - userId: user.id, - status: "away" - }) - await user.update({ - status: "away" - }) - } - }) - socket.on("online", async () => { - const user = await User.findOne({ - where: { - id: socket.user.id - } - }) - if (user.storedStatus === "online") { - friends.forEach((friend) => { - io.to(friend.friendId).emit("userStatus", { - userId: user.id, - status: "online" - }) - }) - io.to(user.id).emit("userStatus", { - userId: user.id, - status: "online" - }) - await user.update({ - status: "online" - }) - } - }) - socket.on("disconnect", async function () { - const clients = - io.sockets.adapter.rooms.get(user.id) || new Set() - if (!clients.size || clients.size === 0) { - friends.forEach((friend) => { - io.to(friend.friendId).emit("userStatus", { - userId: user.id, - status: "offline" - }) - }) - await user.update({ - status: "offline" - }) - } - }) - } - } + socket.on("token", async () => { + socket.emit("unsupported", { + message: "This authentication method is unsupported." + }) }) console.log("Unauthenticated user") socket.on("reAuth", async () => { diff --git a/backend/routes/friends.js b/backend/routes/friends.js index 787cac9..2d95acb 100644 --- a/backend/routes/friends.js +++ b/backend/routes/friends.js @@ -50,6 +50,9 @@ router.post("/", auth, async (req, res, next) => { } }) if (user) { + if (user.id === req.user.id) { + throw Errors.cannotFriendYourself + } const friend = await Friend.findOne({ where: { userId: req.user.id, diff --git a/backend/routes/user.js b/backend/routes/user.js index a507216..f944e17 100644 --- a/backend/routes/user.js +++ b/backend/routes/user.js @@ -133,27 +133,29 @@ router.post("/verify/resend", auth, mailLimiter, async (req, res, next) => { } }) -router.post("/verify/confirm/:token", auth, async (req, res, next) => { +router.post("/verify/confirm/:token", async (req, res, next) => { try { - const user = await User.findOne({ - where: { - id: req.user.id - } - }) if (process.env.EMAIL_VERIFICATION !== "true") { throw Errors.invalidParameter("Email verification is disabled") } if (!req.params.token) { throw Errors.invalidToken } - if (req.params.token !== user.emailToken) { + const user = await User.findOne({ + where: { + emailToken: req.params.token, + emailVerified: false + } + }) + if (user) { + await user.update({ + emailVerified: true, + emailToken: null + }) + res.json({ success: true }) + } else { throw Errors.invalidToken } - await user.update({ - emailVerified: true, - emailToken: null - }) - res.json({ success: true }) } catch (e) { next(e) } @@ -198,12 +200,6 @@ router.post("/login", async (req, res, next) => { osVersion: ua.os.version } }) - res.cookie("session", session.session, { - maxAge: 1000 * 60 * 60 * 24 * 365, - httpOnly: true, - secure: false, - sameSite: "strict" - }) res.json({ session: session.session, success: true, @@ -287,12 +283,6 @@ router.post("/register", limiter, async (req, res, next) => { osVersion: ua.os.version } }) - res.cookie("session", session.session, { - maxAge: 1000 * 60 * 60 * 24 * 365, - httpOnly: true, - secure: false, - sameSite: "strict" - }) res.json({ session: session.session, success: true, @@ -371,15 +361,6 @@ router.delete("/sessions/:id", auth, async (req, res, next) => { } }) -router.post("/logout", (req, res, next) => { - try { - res.clearCookie("session") - res.sendStatus(204) - } catch (e) { - next(e) - } -}) - router.post( "/settings/avatar", auth, diff --git a/frontend/package.json b/frontend/package.json index 06b83ad..2df4a14 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,8 +1,26 @@ { "name": "colubrina", - "version": "1.0.12", + "version": "1.0.13", "private": true, "author": "Troplo ", + "build": { + "appId": "com.troplo.colubrina", + "win": { + "publish": [ + "github" + ] + }, + "linux": { + "publish": [ + "github" + ] + }, + "mac": { + "publish": [ + "github" + ] + } + }, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", @@ -12,9 +30,11 @@ "postinstall": "patch-package && electron-builder install-app-deps", "postuninstall": "electron-builder install-app-deps" }, + "main": "background.js", "repository": "https://github.com/Troplo/Colubrina", "dependencies": { "@babel/preset-env": "^7.17.10", + "@mdi/font": "6.5.95", "@mdi/js": "^6.6.95", "axios": "^0.26.1", "brace": "^0.11.1", @@ -23,8 +43,6 @@ "dayjs": "^1.11.0", "ejs": "^3.1.7", "glob-parent": "^5.1.2", - "graphql": "^16.3.0", - "graphql-tag": "^2.12.6", "highlight.js": "^11.6.0", "markdown-it": "^13.0.1", "markdown-it-emoji": "git+https://github.com/Troplo/markdown-it-unicode-emoji", @@ -39,16 +57,13 @@ "socket.io-client": "^4.5.1", "twemoji": "^14.0.2", "vue": "^2.6.11", - "vue-apollo": "^3.1.0", "vue-axios": "^3.4.1", "vue-chartjs": "^4.0.1", "vue-final-modal": "^2.4.1", - "vue-matomo": "^4.1.0", "vue-native-notification": "^1.1.1", "vue-router": "^3.2.0", "vue-sanitize": "^0.2.1", "vue-shortkey": "^3.1.7", - "vue-swatches": "^2.1.1", "vue-toastification": "^1.7.14", "vue2-ace-editor": "^0.0.15", "vuetify": "^2.6.4", @@ -56,7 +71,6 @@ }, "devDependencies": { "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@mdi/font": "6.5.95", "@vue/cli-plugin-babel": "~4.5.15", "@vue/cli-plugin-eslint": "~4.5.15", "@vue/cli-plugin-pwa": "~4.5.15", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 604383c..865ad15 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -437,12 +437,15 @@ is limited. - +
+ +
@@ -451,7 +454,7 @@ import AjaxErrorHandler from "@/lib/errorHandler" import { VueFinalModal } from "vue-final-modal" import Header from "@/components/Header" -import Vue from "vue" + export default { name: "App", components: { @@ -479,7 +482,9 @@ export default { results: [], searchInput: null, themes: [], - cssTips: true + cssTips: true, + touchStartX: null, + touchEndX: null }), computed: { creatorJSON: { @@ -702,6 +707,19 @@ export default { }) }) }, + touchEnd(event) { + this.touchEndX = event.changedTouches[0].screenX + if (this.touchEndX > this.touchStartX) { + if (this.touchEndX - this.touchStartX > 100) { + this.touchStartX = null + this.touchEndX = null + this.$store.state.drawer = true + } + } + }, + touchStart(event) { + this.touchStartX = event.changedTouches[0].screenX + }, setTheme(theme) { const name = theme.id const dark = theme.dark @@ -728,23 +746,8 @@ export default { }) } }, - mounted() { - Vue.axios.defaults.headers.common["X-Colubrina"] = true - Vue.axios.defaults.headers.common["Authorization"] = - localStorage.getItem("session") - console.log(localStorage.getItem("instance")) - if (process.env.IS_ELECTRON) { - this.axios.defaults.baseURL = localStorage.getItem("instance") - this.$store.state.baseURL = localStorage.getItem("instance") - } - if (localStorage.getItem("customHeaders")) { - console.log(JSON.parse(localStorage.getItem("customHeaders"))) - for (let header in JSON.parse(localStorage.getItem("customHeaders"))) { - Vue.axios.defaults.headers[header] = JSON.parse( - localStorage.getItem("customHeaders") - )[header] - } - } + async mounted() { + await this.$store.dispatch("doInit") if (this.$vuetify.breakpoint.mobile) { this.$store.state.drawer = false } @@ -772,7 +775,8 @@ export default { }) this.$socket.connect() this.$socket.on("unauthorized", () => { - this.$socket.emit("token", localStorage.getItem("session")) + console.log("Reauth requested") + this.$socket.emit("token", localStorage.getItem("token")) }) document.title = this.$route.name ? this.$route.name + " - " + this.$store.state.site.name @@ -780,13 +784,10 @@ export default { this.$store.commit("setLoading", true) this.$vuetify.theme.dark = this.$store.state.user?.theme === "dark" || true this.$store.dispatch("getState") - this.getThemes() - this.communicationsIdleCheck() this.$store .dispatch("getUserInfo") .then(() => { console.log(window.location.pathname) - // check if its /email/confirm/ if ( !window.location.pathname.includes("/email/confirm/") && !window.location.pathname.includes("/email/verify") && @@ -797,10 +798,10 @@ export default { } }) .catch(() => { - if (!["/login", "/register"].includes(this.$route.path)) { - this.$router.push("/login") - } + this.$router.push("/login") }) + this.getThemes() + this.communicationsIdleCheck() this.registerSocket() }, watch: { diff --git a/frontend/src/assets/styles.css b/frontend/src/assets/styles.css index c256ec7..848e30c 100644 --- a/frontend/src/assets/styles.css +++ b/frontend/src/assets/styles.css @@ -1,5 +1,5 @@ .offset-message { - padding-left: 47px; + padding-left: 53px; } .message-action-card { position: absolute; diff --git a/frontend/src/background.js b/frontend/src/background.js index 8e9a7aa..f09bdce 100644 --- a/frontend/src/background.js +++ b/frontend/src/background.js @@ -1,6 +1,6 @@ "use strict" -import { app, protocol, BrowserWindow } from "electron" +import { app, protocol, BrowserWindow, screen } from "electron" import { createProtocol } from "vue-cli-plugin-electron-builder/lib" import installExtension, { VUEJS_DEVTOOLS } from "electron-devtools-installer" const isDevelopment = process.env.NODE_ENV !== "production" @@ -12,15 +12,25 @@ protocol.registerSchemesAsPrivileged([ async function createWindow() { // Create the browser window. + const { bounds } = screen.getDisplayNearestPoint( + screen.getCursorScreenPoint() + ) + const height = Math.floor(bounds.height * (2 / 3)) + const width = Math.floor(bounds.width * (2 / 3)) + const y = Math.floor(bounds.y + (bounds.height - height) / 2) + const x = Math.floor(bounds.x + (bounds.width - width) / 2) const win = new BrowserWindow({ - width: 1400, - height: 1000, + width, + height, + x, + y, webPreferences: { // Use pluginOptions.nodeIntegration, leave this alone // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION, contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION, preload: "window-preload.js", + // required for multi-instance support webSecurity: false } }) diff --git a/frontend/src/components/CommsInput.vue b/frontend/src/components/CommsInput.vue index 03102d1..1f2562f 100644 --- a/frontend/src/components/CommsInput.vue +++ b/frontend/src/components/CommsInput.vue @@ -21,8 +21,8 @@ - + @@ -414,7 +415,7 @@