369 lines
12 KiB
JavaScript
Executable File

import { Controller } from "linebridge/dist/server"
import passport from "passport"
import lodash from "lodash"
import bcrypt from "bcrypt"
import SessionController from "../SessionController"
import { User } from "../../models"
import { Token, Schematized } from "../../lib"
import createUser from "./methods/createUser"
import updatePassword from "./methods/updatePassword"
import getConnectedUsersFollowing from "./methods/getConnectedUsersFollowing"
const AllowedPublicUpdateFields = [
"fullName",
"avatar",
"email",
"cover",
"description",
]
const AllowedAnonPublicGetters = [
"_id",
"username",
"fullName",
"avatar",
"roles"
]
const MaxStringsLengths = {
fullName: 120,
email: 320,
description: 2000,
}
export default class UserController extends Controller {
static refName = "UserController"
methods = {
createNew: async (payload) => {
const user = await createUser(payload)
// maybe for the future can implement a event listener for this
return user
},
update: async (payload) => {
if (typeof payload.user_id === "undefined") {
throw new Error("No user_id provided")
}
if (typeof payload.update === "undefined") {
throw new Error("No update provided")
}
let user = await User.findById(payload.user_id)
if (!user) {
throw new Error("User not found")
}
const updateKeys = Object.keys(payload.update)
updateKeys.forEach((key) => {
user[key] = payload.update[key]
})
await user.save()
global.wsInterface.io.emit(`user.update`, {
...user.toObject(),
})
global.wsInterface.io.emit(`user.update.${payload.user_id}`, {
...user.toObject(),
})
return user.toObject()
},
}
get = {
"/user/public_data": {
middlewares: ["withOptionalAuthentication"],
fn: async (req, res) => {
let user = req.query?.username ?? req.user.username
if (!user) {
return res.status(400).json({
error: "No user provided",
})
}
user = await User.findOne({
username: user,
}).catch(() => null)
if (!user) {
return res.json({
user: null,
})
}
user = lodash.pick(user, AllowedAnonPublicGetters)
return res.json(user)
}
},
"/self": {
middlewares: ["withAuthentication"],
fn: async (req, res) => {
return res.json(req.user)
},
},
"/connected_users_following": {
middlewares: ["withAuthentication"],
fn: async (req, res) => {
const users = await getConnectedUsersFollowing({
from_user_id: req.user._id.toString(),
})
return res.json(users)
}
},
"/user/username-available": async (req, res) => {
const user = await User.findOne({
username: req.query.username,
})
return res.json({
available: !user,
})
},
"/user/email-available": async (req, res) => {
const user = await User.findOne({
email: req.query.email,
})
return res.json({
available: !user,
})
},
"/user": {
middlewares: ["withAuthentication"],
fn: Schematized({
select: ["_id", "username"],
}, async (req, res) => {
let user = await User.findOne(req.selection)
if (!user) {
return res.status(404).json({ error: "User not exists" })
}
return res.json(user)
}),
},
"/users": {
middlewares: ["withAuthentication"],
fn: Schematized({
select: ["_id", "username"],
}, async (req, res) => {
let result = []
let selectQueryKeys = []
if (Array.isArray(req.selection._id)) {
for await (let _id of req.selection._id) {
const user = await User.findById(_id).catch(err => {
return false
})
if (user) {
result.push(user)
}
}
} else {
result = await User.find(req.selection, { username: 1, fullName: 1, _id: 1, roles: 1, avatar: 1 })
}
if (req.query?.select) {
try {
req.query.select = JSON.parse(req.query.select)
} catch (error) {
req.query.select = {}
}
selectQueryKeys = Object.keys(req.query.select)
}
if (selectQueryKeys.length > 0) {
result = result.filter(user => {
let pass = false
const selectFilter = req.query.select
selectQueryKeys.forEach(key => {
if (Array.isArray(selectFilter[key]) && Array.isArray(user[key])) {
// check if arrays includes any of the values
pass = selectFilter[key].some(val => user[key].includes(val))
} else if (typeof selectFilter[key] === "object" && typeof user[key] === "object") {
// check if objects includes any of the values
Object.keys(selectFilter[key]).forEach(objKey => {
pass = user[key][objKey] === selectFilter[key][objKey]
})
}
// check if strings includes any of the values
if (typeof selectFilter[key] === "string" && typeof user[key] === "string") {
pass = selectFilter[key].split(",").some(val => user[key].includes(val))
}
})
return pass
})
}
if (!result) {
return res.status(404).json({ error: "Users not found" })
}
return res.json(result)
})
},
}
post = {
"/login": async (req, res) => {
passport.authenticate("local", { session: false }, async (error, user, options) => {
if (error) {
return res.status(500).json(`Error validating user > ${error.message}`)
}
if (!user) {
return res.status(401).json("Invalid credentials")
}
const token = await Token.createNewAuthToken(user, options)
return res.json({ token: token })
})(req, res)
},
"/logout": {
middlewares: ["withAuthentication"],
fn: async (req, res, next) => {
req.body = {
user_id: req.decodedToken.user_id,
token: req.jwtToken
}
return SessionController.delete(req, res, next)
},
},
"/register": Schematized({
required: ["username", "email", "password"],
select: ["username", "email", "password", "fullName"],
}, async (req, res) => {
const result = await this.methods.createNew(req.selection).catch((err) => {
return res.status(500).json(err.message)
})
return res.json(result)
}),
"/self/update_password": {
middlewares: ["withAuthentication"],
fn: Schematized({
required: ["currentPassword", "newPassword"],
select: ["currentPassword", "newPassword",]
}, async (req, res) => {
const user = await User.findById(req.user._id).select("+password")
if (!user) {
return res.status(404).json({ message: "User not found" })
}
const isPasswordValid = await bcrypt.compareSync(req.selection.currentPassword, user.password)
if (!isPasswordValid) {
return res.status(401).json({
message: "Current password dont match"
})
}
const result = await updatePassword({
user_id: req.user._id,
password: req.selection.newPassword,
}).catch((error) => {
res.status(500).json({ message: error.message })
return null
})
if (result) {
return res.json(result)
}
})
},
"/update_user": {
middlewares: ["withAuthentication", "roles"],
fn: Schematized({
required: ["_id", "update"],
select: ["_id", "update"],
}, async (req, res) => {
if (!req.selection.user_id) {
req.selection.user_id = req.user._id.toString()
}
if ((req.selection.user_id !== req.user._id.toString()) && (req.hasRole("admin") === false)) {
return res.status(403).json({ error: "You are not allowed to update this user" })
}
let update = {}
AllowedPublicUpdateFields.forEach((key) => {
if (typeof req.selection.update[key] !== "undefined") {
// sanitize update
// check maximung strings length
if (typeof req.selection.update[key] === "string" && MaxStringsLengths[key]) {
if (req.selection.update[key].length > MaxStringsLengths[key]) {
// create a substring
req.selection.update[key] = req.selection.update[key].substring(0, MaxStringsLengths[key])
}
}
update[key] = req.selection.update[key]
}
})
this.methods.update({
user_id: req.selection.user_id,
update: update,
}).then((user) => {
return res.json({
...user
})
})
.catch((err) => {
return res.json(500).json({
error: err.message
})
})
}),
},
"/unset_public_name": {
middlewares: ["withAuthentication"],
fn: Schematized({
select: ["user_id", "roles"],
}, async (req, res) => {
if (!req.selection.user_id) {
req.selection.user_id = req.user._id.toString()
}
if ((req.selection.user_id !== req.user._id.toString()) && (req.hasRole("admin") === false)) {
return res.status(403).json({ error: "You are not allowed to update this user" })
}
this.methods.update({
user_id: req.selection.user_id,
update: {
fullName: undefined
}
}).then((user) => {
return res.json({
...user
})
})
.catch((err) => {
return res.json(500).json({
error: err.message
})
})
})
}
}
}