This commit is contained in:
Troplo 2022-07-29 19:20:19 +10:00
parent f17a9d3027
commit 98218285ef
19 changed files with 226 additions and 425 deletions

View file

@ -277,7 +277,8 @@ async function init() {
username: "Colubrina",
id: 0,
bot: true,
email: "colubrina@troplo.com"
email: "colubrina@troplo.com",
banned: true
})
await User.update(
{

View file

@ -16,6 +16,7 @@ app.use(bodyParser.urlencoded({ extended: true }))
app.use("/api/v1/user", require("./routes/user.js"))
app.use("/api/v1/themes", require("./routes/theme.js"))
app.use("/api/v1/communications", require("./routes/communications.js"))
app.use("/api/v1/friends", require("./routes/friends.js"))
app.use("/api/v1/admin", require("./routes/admin.js"))
app.use("/usercontent", require("./routes/usercontent.js"))
app.use("/api/v1/usercontent", require("./routes/usercontent.js"))

View file

@ -19,6 +19,10 @@ module.exports = async function (req, res, next) {
]
})
if (user) {
if (user.banned) {
res.status(401).json(Errors.banned)
return
}
await user.update({
lastSeenAt: new Date().toISOString()
})

View file

@ -30,18 +30,22 @@ module.exports = async function (socket, next) {
raw: true
})
if (user) {
socket.user = user
next()
if (user.banned) {
socket.user = {
id: null,
username: "Not Authenticated"
}
next()
} else {
socket.user = user
next()
}
}
} else {
socket.user = {
id: null,
username: "Not Authenticated"
}
socket.compassUser = {
id: null,
username: "BC-NOAUTH"
}
next()
}
} else {
@ -49,10 +53,6 @@ module.exports = async function (socket, next) {
id: null,
username: "Not Authenticated"
}
socket.compassUser = {
id: null,
username: "BC-NOAUTH"
}
next()
}
} catch (error) {
@ -60,10 +60,6 @@ module.exports = async function (socket, next) {
id: null,
username: "Not Authenticated"
}
socket.compassUser = {
id: null,
username: "BC-NOAUTH"
}
next()
}
}

View file

@ -37,7 +37,8 @@ let Errors = {
registrationsDisabled: [
"Registrations are currently disabled on this instance. Please try again later.",
400
]
],
banned: ["You are banned from this instance.", 400]
}
function processErrors(errorName) {

View file

@ -11,17 +11,11 @@ module.exports = {
session: {
type: Sequelize.STRING
},
compassUserId: {
type: Sequelize.BIGINT
},
sussiId: {
type: Sequelize.STRING
},
other: {
type: Sequelize.JSON
},
instance: {
type: Sequelize.STRING
expiredAt: {
type: Sequelize.DATE
},
createdAt: {
allowNull: false,

View file

@ -1,18 +0,0 @@
"use strict"
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn("Sessions", "expiredAt", {
type: Sequelize.DATE
})
},
down: async (queryInterface, Sequelize) => {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
}
}

View file

@ -0,0 +1,20 @@
"use strict"
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.addColumn("Users", "banned", {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false
})
},
async down(queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
}
}

View file

@ -14,10 +14,7 @@ module.exports = (sequelize, DataTypes) => {
Session.init(
{
session: DataTypes.STRING,
compassUserId: DataTypes.BIGINT,
sussiId: DataTypes.STRING,
other: DataTypes.JSON,
instance: DataTypes.STRING,
userId: DataTypes.BIGINT,
expiredAt: DataTypes.DATE
},

View file

@ -153,6 +153,11 @@ module.exports = (sequelize, DataTypes) => {
lastSeenAt: {
type: DataTypes.DATE,
allowNull: true
},
banned: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
}
},
{

View file

@ -58,7 +58,7 @@ router.get("/metrics", auth, async (req, res, next) => {
createdAt: createdAt
},
attributes: {
exclude: ["totp", "compassSession", "password"]
exclude: ["totp", "password"]
}
})
const messages = await Message.findAll({
@ -66,7 +66,7 @@ router.get("/metrics", auth, async (req, res, next) => {
createdAt: createdAt
},
attributes: {
exclude: ["totp", "compassSession", "password"]
exclude: ["totp", "password"]
}
})
@ -137,7 +137,7 @@ router.get("/users", auth, async (req, res, next) => {
}
],
attributes: {
exclude: ["totp", "compassSession", "password"]
exclude: ["totp", "password"]
}
})
res.json(users)
@ -154,7 +154,7 @@ router.get("/themes", auth, async (req, res, next) => {
model: User,
as: "user",
attributes: {
exclude: ["totp", "compassSession", "password"]
exclude: ["totp", "password"]
}
},
{

View file

@ -47,6 +47,7 @@ const upload = multer({
const resolveEmbeds = require("../lib/resolveEmbeds.js")
const paginate = require("jw-paginate")
async function createMessage(req, type, content, association, userId) {
const io = req.app.get("io")
const message = await Message.create({
@ -133,6 +134,7 @@ async function createMessage(req, type, content, association, userId) {
})
})
}
router.get("/", auth, async (req, res, next) => {
try {
let chats = await ChatAssociation.findAll({
@ -748,31 +750,6 @@ router.post("/association/:id", auth, async (req, res, next) => {
}
})
router.get("/friends", auth, async (req, res, next) => {
try {
let friends = await Friend.findAll({
where: {
userId: req.user.id
},
include: [
{
model: User,
as: "user",
attributes: ["id", "username", "avatar", "createdAt", "updatedAt"]
},
{
model: User,
as: "user2",
attributes: ["id", "username", "avatar", "createdAt", "updatedAt"]
}
]
})
res.json(friends)
} catch (err) {
next(err)
}
})
router.get("/users", auth, async (req, res, next) => {
try {
const users = await User.findAll({
@ -780,11 +757,9 @@ router.get("/users", auth, async (req, res, next) => {
"id",
"username",
"name",
"avatar",
"createdAt",
"updatedAt",
"status",
"admin"
]
@ -795,141 +770,6 @@ router.get("/users", auth, async (req, res, next) => {
}
})
router.post("/friends", auth, async (req, res, next) => {
try {
const io = req.app.get("io")
let friendRes
try {
friendRes = req.body.friend.split(":")
} catch {
friendRes = req.body.friend
}
const user = await User.findOne({
where: {
username: friendRes[0] || friendRes
}
})
if (user) {
const friend = await Friend.findOne({
where: {
userId: req.user.id,
friendId: user.id
}
})
if (friend) {
throw Errors.friendAlreadyFriends
} else {
if (!user.privacy.communications.enabled) {
throw Errors.communicationsUserNotOptedIn
} else {
const newFriend = await Friend.create({
userId: req.user.id,
friendId: user.id
})
const remoteFriend = await Friend.create({
userId: user.id,
friendId: req.user.id,
status: "pendingCanAccept"
})
io.to(user.id).emit("friendUpdate", {})
io.to(req.user.id).emit("friendUpdate", {})
io.to(user.id).emit("friendRequest", {
...remoteFriend.dataValues,
user: {
username: req.user.username,
discussionsImage: req.user.discussionsImage,
avatar: req.user.avatar,
id: req.user.id
}
})
res.json(newFriend)
}
}
} else {
throw Errors.communicationsUserNotFound
}
} catch (err) {
next(err)
}
})
router.delete("/friends/:id", auth, async (req, res, next) => {
try {
const io = req.app.get("io")
const friend = await Friend.findOne({
where: {
userId: req.user.id,
id: req.params.id
}
})
if (friend) {
await friend.destroy()
await Friend.destroy({
where: {
userId: friend.friendId,
friendId: req.user.id
}
})
io.to(friend.friendId).emit("friendUpdate", {})
io.to(req.user.id).emit("friendUpdate", {})
res.sendStatus(204)
} else {
throw Errors.friendNotFound
}
} catch (err) {
next(err)
}
})
router.put("/friends/:id", auth, async (req, res, next) => {
try {
const io = req.app.get("io")
const friend = await Friend.findOne({
where: {
id: req.params.id,
userId: req.user.id,
status: "pendingCanAccept"
}
})
if (friend) {
await friend.update({
status: "accepted"
})
const remoteFriend = await Friend.findOne({
where: {
userId: friend.friendId,
friendId: friend.userId
},
include: [
{
model: User,
as: "user",
attributes: ["id", "username", "createdAt", "updatedAt"]
},
{
model: User,
as: "user2",
attributes: ["id", "username", "createdAt", "updatedAt"]
}
]
})
await remoteFriend.update({
status: "accepted"
})
io.to(friend.userId).emit("friendUpdate", {})
io.to(remoteFriend.userId).emit("friendUpdate", {})
io.to(remoteFriend.userId).emit("friendAccepted", {
...remoteFriend.dataValues
})
res.json(friend)
} else {
throw Errors.friendNotFound
}
} catch (err) {
next(err)
}
})
router.get("/search", auth, async (req, res, next) => {
try {
const friends = await Friend.findAll({
@ -988,7 +828,6 @@ router.get("/:id", auth, async (req, res, next) => {
attributes: [
"username",
"name",
"avatar",
"id",
"createdAt",
@ -1230,7 +1069,6 @@ router.get("/:id/search", auth, async (req, res, next) => {
attributes: [
"username",
"name",
"avatar",
"id",
"createdAt",
@ -1247,7 +1085,6 @@ router.get("/:id/search", auth, async (req, res, next) => {
attributes: [
"username",
"name",
"avatar",
"id",
"createdAt",
@ -1309,7 +1146,6 @@ router.delete("/association/:id", auth, async (req, res, next) => {
attributes: [
"username",
"name",
"avatar",
"id",
"createdAt",
@ -1337,7 +1173,6 @@ router.delete("/association/:id", auth, async (req, res, next) => {
attributes: [
"username",
"name",
"avatar",
"id",
"createdAt",
@ -1356,7 +1191,6 @@ router.delete("/association/:id", auth, async (req, res, next) => {
attributes: [
"username",
"name",
"avatar",
"id",
"createdAt",
@ -1371,7 +1205,6 @@ router.delete("/association/:id", auth, async (req, res, next) => {
attributes: [
"username",
"name",
"avatar",
"id",
"createdAt",

167
backend/routes/friends.js Normal file
View file

@ -0,0 +1,167 @@
const auth = require("../lib/authorize")
const { Friend, User } = require("../models")
const Errors = require("../lib/errors")
const express = require("express")
const router = express.Router()
router.get("/", auth, async (req, res, next) => {
try {
let friends = await Friend.findAll({
where: {
userId: req.user.id
},
include: [
{
model: User,
as: "user",
attributes: ["id", "username", "avatar", "createdAt", "updatedAt"]
},
{
model: User,
as: "user2",
attributes: ["id", "username", "avatar", "createdAt", "updatedAt"]
}
]
})
res.json(friends)
} catch (err) {
next(err)
}
})
router.post("/", auth, async (req, res, next) => {
try {
const io = req.app.get("io")
let friendRes
try {
friendRes = req.body.friend.split(":")
} catch {
friendRes = req.body.friend
}
const user = await User.findOne({
where: {
username: friendRes[0] || friendRes
}
})
if (user) {
const friend = await Friend.findOne({
where: {
userId: req.user.id,
friendId: user.id
}
})
if (friend) {
throw Errors.friendAlreadyFriends
} else {
if (!user.privacy.communications.enabled) {
throw Errors.communicationsUserNotOptedIn
} else {
const newFriend = await Friend.create({
userId: req.user.id,
friendId: user.id
})
const remoteFriend = await Friend.create({
userId: user.id,
friendId: req.user.id,
status: "pendingCanAccept"
})
io.to(user.id).emit("friendUpdate", {})
io.to(req.user.id).emit("friendUpdate", {})
io.to(user.id).emit("friendRequest", {
...remoteFriend.dataValues,
user: {
username: req.user.username,
discussionsImage: req.user.discussionsImage,
avatar: req.user.avatar,
id: req.user.id
}
})
res.json(newFriend)
}
}
} else {
throw Errors.communicationsUserNotFound
}
} catch (err) {
next(err)
}
})
router.delete("/:id", auth, async (req, res, next) => {
try {
const io = req.app.get("io")
const friend = await Friend.findOne({
where: {
userId: req.user.id,
id: req.params.id
}
})
if (friend) {
await friend.destroy()
await Friend.destroy({
where: {
userId: friend.friendId,
friendId: req.user.id
}
})
io.to(friend.friendId).emit("friendUpdate", {})
io.to(req.user.id).emit("friendUpdate", {})
res.sendStatus(204)
} else {
throw Errors.friendNotFound
}
} catch (err) {
next(err)
}
})
router.put("/:id", auth, async (req, res, next) => {
try {
const io = req.app.get("io")
const friend = await Friend.findOne({
where: {
id: req.params.id,
userId: req.user.id,
status: "pendingCanAccept"
}
})
if (friend) {
await friend.update({
status: "accepted"
})
const remoteFriend = await Friend.findOne({
where: {
userId: friend.friendId,
friendId: friend.userId
},
include: [
{
model: User,
as: "user",
attributes: ["id", "username", "createdAt", "updatedAt"]
},
{
model: User,
as: "user2",
attributes: ["id", "username", "createdAt", "updatedAt"]
}
]
})
await remoteFriend.update({
status: "accepted"
})
io.to(friend.userId).emit("friendUpdate", {})
io.to(remoteFriend.userId).emit("friendUpdate", {})
io.to(remoteFriend.userId).emit("friendAccepted", {
...remoteFriend.dataValues
})
res.json(friend)
} else {
throw Errors.friendNotFound
}
} catch (err) {
next(err)
}
})
module.exports = router

View file

@ -68,12 +68,8 @@ router.post("/login", async (req, res, next) => {
.catch(() => {})
const session = await Session.create({
userId: user.id,
instance: req.body.instance || "",
session: "COLUBRINA-" + cryptoRandomString({ length: 128 }),
compassUserId: user.compassUserId,
sussiId: user.sussiId,
expiredAt: new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 30),
compassSession: user.compassSession,
other: {
ip: req.header("x-real-ip") || req.ip,
location: ip.country
@ -113,6 +109,7 @@ router.post("/login", async (req, res, next) => {
}
})
if (user) {
if (user.banned) throw Errors.banned
if (await checkPassword(req.body.password, user.password)) {
if (user.totpEnabled) {
const verified = speakeasy.totp.verify({
@ -160,12 +157,8 @@ router.post("/register", async (req, res, next) => {
.catch(() => {})
const session = await Session.create({
userId: user.id,
instance: req.body.instance || "",
session: "COLUBRINA-" + cryptoRandomString({ length: 128 }),
compassUserId: user.compassUserId,
sussiId: user.sussiId,
expiredAt: new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * 30),
compassSession: user.compassSession,
other: {
ip: req.header("x-real-ip") || req.ip,
location: ip.country

View file

@ -9,10 +9,8 @@ import Toast from "vue-toastification"
import "./assets/styles.css"
import "vue-toastification/dist/index.css"
import "./registerServiceWorker"
import VueSanitize from "vue-sanitize"
import "@mdi/font/css/materialdesignicons.css"
import "./plugins/dayjs"
import VueApollo from "./plugins/apollo"
import SocketIO from "socket.io-client"
import twemoji from "twemoji"
import VueNativeNotification from "vue-native-notification"
@ -84,129 +82,6 @@ Vue.use({
}
})
Vue.use(VueSanitize, {
allowedTags: [
"address",
"article",
"aside",
"footer",
"header",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"hgroup",
"main",
"nav",
"section",
"blockquote",
"dd",
"div",
"dl",
"dt",
"figcaption",
"figure",
"hr",
"li",
"main",
"ol",
"p",
"pre",
"ul",
"a",
"abbr",
"b",
"bdi",
"bdo",
"br",
"cite",
"code",
"data",
"dfn",
"em",
"i",
"kbd",
"mark",
"q",
"rb",
"rp",
"rt",
"rtc",
"ruby",
"s",
"samp",
"small",
"span",
"strong",
"sub",
"sup",
"time",
"u",
"var",
"wbr",
"caption",
"col",
"colgroup",
"table",
"tbody",
"td",
"tfoot",
"th",
"thead",
"tr",
"img"
],
disallowedTagsMode: "discard",
allowedAttributes: {
a: ["href", "name", "target"],
img: [
"src",
"srcset",
"alt",
"title",
"width",
"height",
"loading",
"style"
],
tr: ["style"],
td: ["style"],
table: ["style", "border", "cellpadding", "cellspacing"]
},
allowedStyles: {
"*": {
// Match HEX and RGB
"text-align": [/^left$/, /^right$/, /^center$/],
// Match any number with px, em, or %
"font-size": [/^\d+(?:px|em|%)$/],
"font-weight": [/^\d+$/],
"font-style": [/^\d+$/],
height: [/^\d+(?:px|em|%)$/],
width: [/^\d+(?:px|em|%)$/]
}
},
// Lots of these won't come up by default because we don't allow them
selfClosing: [
"img",
"br",
"hr",
"area",
"base",
"basefont",
"input",
"link",
"meta"
],
// URL schemes we permit
allowedSchemes: ["http", "https", "ftp", "mailto", "tel"],
allowedSchemesByTag: {},
allowedSchemesAppliedToAttributes: ["href", "src", "cite"],
allowProtocolRelative: true,
enforceHtmlBoundary: false
})
Vue.use(require("vue-shortkey"))
Vue.config.productionTip = false
Vue.use(VueAxios, axios)
@ -220,6 +95,5 @@ new Vue({
router,
store,
vuetify,
VueApollo,
render: (h) => h(App)
}).$mount("#app")

View file

@ -1,23 +0,0 @@
import ApolloClient, { InMemoryCache } from "apollo-boost"
import Vue from "vue"
const cache = new InMemoryCache()
const apolloClient = new ApolloClient({
uri: "/graphql",
headers: {
CompassAPIKey: localStorage.getItem("apiKey"),
compassInstance: localStorage.getItem("schoolInstance")
},
cache
})
Object.defineProperties(Vue.prototype, {
$apollo: {
get() {
return apolloClient
}
}
})
export default apolloClient

View file

@ -44,7 +44,7 @@ export default {
value: "name"
},
{
text: "Compass User ID",
text: "User ID",
value: "user.id"
},
{

View file

@ -300,29 +300,6 @@
</p>
</v-col>
</v-row>
<v-row
v-else-if="embed.type === 'compass'"
@click="$router.push(embed.path)"
style="cursor: pointer"
>
<v-container
:style="
'background: url(/' +
embed.compass.banner +
')'
"
style="color: white"
class="rounded"
>
<h4>BetterCompass</h4>
<h3>
{{ embed.compass.name }}
</h3>
<p>
{{ embed.compass.displayName }}
</p>
</v-container>
</v-row>
<template v-else-if="embed.type === 'image'">
<v-hover v-slot="{ hover }">
<div>
@ -1108,27 +1085,6 @@
</p>
</v-col>
</v-row>
<v-row
v-if="embed.type === 'compass'"
@click="$router.push(embed.path)"
style="cursor: pointer"
>
<v-container
:style="
'background: url(/' +
embed.compass.banner +
')'
"
>
<h4>BetterCompass</h4>
<h3>
{{ embed.compass.name }}
</h3>
<p>
{{ embed.compass.displayName }}
</p>
</v-container>
</v-row>
<template v-else-if="embed.type === 'image'">
<v-hover v-slot="{ hover }">
<div>

View file

@ -281,7 +281,7 @@ export default {
},
acceptFriend(friend) {
this.axios
.put("/api/v1/communications/friends/" + friend.id, {
.put("/api/v1/friends/" + friend.id, {
friend: friend.id,
status: "accepted"
})
@ -294,7 +294,7 @@ export default {
},
removeFriend(friend) {
this.axios
.delete("/api/v1/communications/friends/" + friend.id)
.delete("/api/v1/friends/" + friend.id)
.then(() => {
this.getFriends()
})
@ -305,7 +305,7 @@ export default {
addFriend(user) {
if (user) {
this.axios
.post("/api/v1/communications/friends", {
.post("/api/v1/friends", {
friend: user.username + ":" + user.instance
})
.then(() => {
@ -317,7 +317,7 @@ export default {
})
} else {
this.axios
.post("/api/v1/communications/friends", {
.post("/api/v1/friends", {
friend: this.friend
})
.then(() => {
@ -330,7 +330,7 @@ export default {
}
},
getFriends() {
this.axios.get("/api/v1/communications/friends").then((res) => {
this.axios.get("/api/v1/friends").then((res) => {
this.friends = res.data
})
}