mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
rewrite for use linebridge 0.10.x
This commit is contained in:
parent
fec72bcfb3
commit
6c29315a8e
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@ragestudio/server",
|
||||
"name": "@comty/server",
|
||||
"version": "0.15.0",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
@ -7,22 +7,22 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@corenode/utils": "^0.28.26",
|
||||
"axios": "^0.24.0",
|
||||
"bcrypt": "^5.0.1",
|
||||
"connect-mongo": "^4.6.0",
|
||||
"corenode": "^0.28.26",
|
||||
"dicebar_lib": "^1.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"linebridge": "^0.8.4",
|
||||
"moment": "^2.29.1",
|
||||
"mongoose": "^5.12.14",
|
||||
"nanoid": "^3.1.23",
|
||||
"passport": "^0.5.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"socket.io": "^4.2.0"
|
||||
"@corenode/utils": "0.28.26",
|
||||
"@nanoexpress/middleware-file-upload": "^1.0.6",
|
||||
"axios": "0.25.0",
|
||||
"bcrypt": "5.0.1",
|
||||
"connect-mongo": "4.6.0",
|
||||
"corenode": "0.28.26",
|
||||
"dicebar_lib": "1.0.1",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"linebridge": "0.10.13",
|
||||
"moment": "2.29.1",
|
||||
"mongoose": "6.1.9",
|
||||
"nanoid": "3.2.0",
|
||||
"passport": "0.5.2",
|
||||
"passport-jwt": "4.0.0",
|
||||
"passport-local": "1.0.0",
|
||||
"path-to-regexp": "6.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
|
12
packages/server/src/controllers/ConfigController/index.js
Normal file
12
packages/server/src/controllers/ConfigController/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { ComplexController } from "linebridge/dist/classes"
|
||||
|
||||
export default class ConfigController extends ComplexController {
|
||||
static refName = "ConfigController"
|
||||
static useMiddlewares = ["withAuthentication", "onlyAdmin"]
|
||||
|
||||
post = {
|
||||
"/update_config": async (req, res) => {
|
||||
|
||||
},
|
||||
}
|
||||
}
|
65
packages/server/src/controllers/FilesController/index.js
Normal file
65
packages/server/src/controllers/FilesController/index.js
Normal file
@ -0,0 +1,65 @@
|
||||
import { ComplexController } from "linebridge/dist/classes"
|
||||
import path from "path"
|
||||
import fs from "fs"
|
||||
import stream from "stream"
|
||||
|
||||
function resolveToUrl(filepath) {
|
||||
return `${global.globalPublicURI}/uploads/${filepath}`
|
||||
}
|
||||
|
||||
export default class FilesController extends ComplexController {
|
||||
static refName = "FilesController"
|
||||
|
||||
get = {
|
||||
"/uploads/:id": (req, res) => {
|
||||
const filePath = path.join(global.uploadPath, req.params?.id)
|
||||
|
||||
const readStream = fs.createReadStream(filePath)
|
||||
const passTrough = new stream.PassThrough()
|
||||
|
||||
stream.pipeline(readStream, passTrough, (err) => {
|
||||
if (err) {
|
||||
return res.status(400)
|
||||
}
|
||||
})
|
||||
|
||||
return passTrough.pipe(res)
|
||||
}
|
||||
}
|
||||
|
||||
post = {
|
||||
"/upload": {
|
||||
middlewares: ["withAuthentication", "fileUpload"],
|
||||
fn: async (req, res) => {
|
||||
const urls = []
|
||||
const failed = []
|
||||
|
||||
if (!fs.existsSync(global.uploadPath)) {
|
||||
await fs.promises.mkdir(global.uploadPath, { recursive: true })
|
||||
}
|
||||
|
||||
if (req.files) {
|
||||
for await (let file of req.files) {
|
||||
try {
|
||||
const filename = `${req.decodedToken.user_id}-${new Date().getTime()}-${file.filename}`
|
||||
|
||||
const diskPath = path.join(global.uploadPath, filename)
|
||||
|
||||
await fs.promises.writeFile(diskPath, file.data)
|
||||
|
||||
urls.push(resolveToUrl(filename))
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
failed.push(file.filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.json({
|
||||
urls: urls,
|
||||
failed: failed,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
packages/server/src/controllers/PublicController/index.js
Normal file
17
packages/server/src/controllers/PublicController/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { ComplexController } from "linebridge/dist/classes"
|
||||
|
||||
export default class PublicController extends ComplexController {
|
||||
static refName = "PublicController"
|
||||
|
||||
post = {
|
||||
"/only_managers_test": {
|
||||
middlewares: ["withAuthentication", "permissions"],
|
||||
fn: (req, res) => {
|
||||
return res.json({
|
||||
message: "Congrats!, Only managers can access this route (or you are an admin)",
|
||||
assertedPermissions: req.assertedPermissions
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +1,113 @@
|
||||
import { Role, User } from '../../models'
|
||||
import { ComplexController } from "linebridge/dist/classes"
|
||||
import { Role, User } from "../../models"
|
||||
import { Schematized } from "../../lib"
|
||||
|
||||
export default {
|
||||
get: Schematized({
|
||||
select: ["user_id", "username"],
|
||||
}, async (req, res) => {
|
||||
const { user_id, username } = req.selection
|
||||
export default class RolesController extends ComplexController {
|
||||
static refName = "RolesController"
|
||||
static useMiddlewares = ["roles"]
|
||||
|
||||
if (typeof user_id !== "undefined" || typeof username !== "undefined") {
|
||||
const user = await User.findOne(req.selection)
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "No user founded" })
|
||||
}
|
||||
return res.json(user.roles)
|
||||
}
|
||||
get = {
|
||||
"/roles": Schematized({
|
||||
select: ["user_id", "username"],
|
||||
}, async (req, res) => {
|
||||
const roles = await Role.find()
|
||||
|
||||
const roles = await Role.find({})
|
||||
return res.json(roles)
|
||||
}),
|
||||
"/user_roles": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: Schematized({
|
||||
select: ["username"],
|
||||
}, async (req, res) => {
|
||||
const user = await User.findOne(req.selection)
|
||||
|
||||
return res.json(roles)
|
||||
}),
|
||||
set: (req, res, next) => {
|
||||
const { name, description } = req.body
|
||||
Role.findOne({ name }).then((data) => {
|
||||
if (data) {
|
||||
return res.status(409).json("This role is already created")
|
||||
}
|
||||
let document = new Role({
|
||||
name,
|
||||
description
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "No user founded" })
|
||||
}
|
||||
|
||||
return res.json(user.roles)
|
||||
})
|
||||
document.save()
|
||||
return res.json(true)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
post = {
|
||||
"/role": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: Schematized({
|
||||
required: ["name"],
|
||||
select: ["name", "description"],
|
||||
}, async (req, res) => {
|
||||
await Role.findOne(req.selection).then((data) => {
|
||||
if (data) {
|
||||
return res.status(409).json("This role is already created")
|
||||
}
|
||||
|
||||
let role = new Role({
|
||||
name: req.selection.name,
|
||||
description: req.selection.description,
|
||||
})
|
||||
|
||||
role.save()
|
||||
|
||||
return res.json(role)
|
||||
})
|
||||
})
|
||||
},
|
||||
"/update_user_roles": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: Schematized({
|
||||
required: ["update"],
|
||||
select: ["update"],
|
||||
}, async (req, res) => {
|
||||
// check if issuer user is admin
|
||||
if (!req.isAdmin()) {
|
||||
return res.status(403).send("You do not have administrator permission")
|
||||
}
|
||||
|
||||
if (!Array.isArray(req.selection.update)) {
|
||||
return res.status(400).send("Invalid update request")
|
||||
}
|
||||
|
||||
req.selection.update.forEach(async (update) => {
|
||||
const user = await User.findById(update._id).catch(err => {
|
||||
return false
|
||||
})
|
||||
|
||||
console.log(update.roles)
|
||||
|
||||
if (user) {
|
||||
user.roles = update.roles
|
||||
|
||||
await user.save()
|
||||
}
|
||||
})
|
||||
|
||||
return res.send("done")
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
delete = {
|
||||
"/role": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: Schematized({
|
||||
required: ["name"],
|
||||
select: ["name"],
|
||||
}, async (req, res) => {
|
||||
if (req.selection.name === "admin") {
|
||||
return res.status(409).json("You can't delete admin role")
|
||||
}
|
||||
|
||||
await Role.findOne(req.selection).then((data) => {
|
||||
if (!data) {
|
||||
return res.status(404).json("This role is not found")
|
||||
}
|
||||
|
||||
data.remove()
|
||||
|
||||
return res.json(data)
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
@ -1,102 +1,110 @@
|
||||
import { Session } from '../../models'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { Token } from '../../lib'
|
||||
import { ComplexController } from "linebridge/dist/classes"
|
||||
import { Session } from "../../models"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
export default {
|
||||
regenerate: async (req, res) => {
|
||||
jwt.verify(req.jwtToken, req.jwtStrategy.secretOrKey, async (err, decoded) => {
|
||||
if (err && !decoded?.allowRegenerate) {
|
||||
return res.status(403).send("This token is invalid and is not allowed to be regenerated")
|
||||
export default class SessionController extends ComplexController {
|
||||
static refName = "SessionController"
|
||||
|
||||
get = {
|
||||
"/sessions": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
// get current session _id
|
||||
const { _id } = req.user
|
||||
const sessions = await Session.find({ user_id: _id }, { token: 0 })
|
||||
|
||||
return res.json(sessions)
|
||||
},
|
||||
},
|
||||
"/current_session": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
return res.json(req.currentSession)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const sessionToken = await Session.findOneAndDelete({ token: req.jwtToken, user_id: decoded.user_id })
|
||||
|
||||
if (sessionToken) {
|
||||
delete decoded["iat"]
|
||||
delete decoded["exp"]
|
||||
delete decoded["date"]
|
||||
post = {
|
||||
"/validate_session": {
|
||||
middlewares: ["useJwtStrategy"],
|
||||
fn: async (req, res) => {
|
||||
const token = req.body.session
|
||||
|
||||
const token = await Token.signNew({
|
||||
...decoded,
|
||||
}, req.jwtStrategy)
|
||||
|
||||
return res.json({ token })
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteAll: async (req, res) => {
|
||||
const { user_id } = req.body
|
||||
|
||||
if (typeof user_id === "undefined") {
|
||||
return res.status(400).send("No user_id provided")
|
||||
}
|
||||
|
||||
const allSessions = await Session.deleteMany({ user_id })
|
||||
if (allSessions) {
|
||||
return res.send("done")
|
||||
}
|
||||
|
||||
return res.status(404).send("not found")
|
||||
},
|
||||
delete: async (req, res) => {
|
||||
const { token, user_id } = req.body
|
||||
|
||||
if (typeof user_id === "undefined") {
|
||||
return res.status(400).send("No user_id provided")
|
||||
}
|
||||
if (typeof token === "undefined") {
|
||||
return res.status(400).send("No token provided")
|
||||
}
|
||||
|
||||
const session = await Session.findOneAndDelete({ user_id, token })
|
||||
if (session) {
|
||||
return res.send("done")
|
||||
}
|
||||
|
||||
return res.status(404).send("not found")
|
||||
},
|
||||
validate: async (req, res) => {
|
||||
const token = req.body.session
|
||||
let result = {
|
||||
expired: false,
|
||||
valid: true
|
||||
}
|
||||
|
||||
await jwt.verify(token, req.jwtStrategy.secretOrKey, async (err, decoded) => {
|
||||
if (err) {
|
||||
result.valid = false
|
||||
result.error = err.message
|
||||
|
||||
if (err.message === "jwt expired") {
|
||||
result.expired = true
|
||||
let result = {
|
||||
expired: false,
|
||||
valid: true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
result = { ...result, ...decoded }
|
||||
await jwt.verify(token, req.jwtStrategy.secretOrKey, async (err, decoded) => {
|
||||
if (err) {
|
||||
result.valid = false
|
||||
result.error = err.message
|
||||
|
||||
const sessions = await Session.find({ user_id: result.user_id })
|
||||
const sessionsTokens = sessions.map((session) => {
|
||||
if (session.user_id === result.user_id) {
|
||||
return session.token
|
||||
if (err.message === "jwt expired") {
|
||||
result.expired = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
result = { ...result, ...decoded }
|
||||
|
||||
const sessions = await Session.find({ user_id: result.user_id })
|
||||
const sessionsTokens = sessions.map((session) => {
|
||||
if (session.user_id === result.user_id) {
|
||||
return session.token
|
||||
}
|
||||
})
|
||||
|
||||
if (!sessionsTokens.includes(token)) {
|
||||
result.valid = false
|
||||
result.error = "Session token not found"
|
||||
} else {
|
||||
result.valid = true
|
||||
}
|
||||
})
|
||||
|
||||
return res.json(result)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
delete = {
|
||||
"/session": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const { token, user_id } = req.body
|
||||
|
||||
if (typeof user_id === "undefined") {
|
||||
return res.status(400).send("No user_id provided")
|
||||
}
|
||||
if (typeof token === "undefined") {
|
||||
return res.status(400).send("No token provided")
|
||||
}
|
||||
})
|
||||
|
||||
if (!sessionsTokens.includes(token)) {
|
||||
result.valid = false
|
||||
result.error = "Session token not found"
|
||||
} else {
|
||||
result.valid = true
|
||||
const session = await Session.findOneAndDelete({ user_id, token })
|
||||
if (session) {
|
||||
return res.send("done")
|
||||
}
|
||||
|
||||
return res.status(404).send("not found")
|
||||
},
|
||||
},
|
||||
"/sessions": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const { user_id } = req.body
|
||||
|
||||
if (typeof user_id === "undefined") {
|
||||
return res.status(400).send("No user_id provided")
|
||||
}
|
||||
|
||||
const allSessions = await Session.deleteMany({ user_id })
|
||||
if (allSessions) {
|
||||
return res.send("done")
|
||||
}
|
||||
|
||||
return res.status(404).send("not found")
|
||||
}
|
||||
})
|
||||
|
||||
return res.json(result)
|
||||
},
|
||||
get: async (req, res) => {
|
||||
// get current session _id
|
||||
const { _id } = req.user
|
||||
const sessions = await Session.find({ user_id: _id }, { token: 0 })
|
||||
|
||||
return res.json(sessions)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -1,263 +1,195 @@
|
||||
import passport from 'passport'
|
||||
import bcrypt from 'bcrypt'
|
||||
import { ComplexController } from "linebridge/dist/classes"
|
||||
import passport from "passport"
|
||||
import bcrypt from "bcrypt"
|
||||
|
||||
import { User } from '../../models'
|
||||
import SessionController from '../SessionController'
|
||||
import { Token, Schematized } from '../../lib'
|
||||
import AvatarController from 'dicebar_lib'
|
||||
import { User } from "../../models"
|
||||
import SessionController from "../SessionController"
|
||||
import { Token, Schematized } from "../../lib"
|
||||
import AvatarController from "dicebar_lib"
|
||||
import _ from "lodash"
|
||||
|
||||
import _ from 'lodash'
|
||||
const AllowedUserUpdateKeys = [
|
||||
"username",
|
||||
"email",
|
||||
"fullName",
|
||||
]
|
||||
|
||||
export default {
|
||||
isAuth: (req, res) => {
|
||||
return res.json(`You look nice today 😎`)
|
||||
},
|
||||
getSelf: (req, res) => {
|
||||
return res.json(req.user)
|
||||
},
|
||||
get: Schematized({
|
||||
select: ["_id", "username"],
|
||||
}, async (req, res) => {
|
||||
let result = []
|
||||
let selectQueryKeys = []
|
||||
export default class UserController extends ComplexController {
|
||||
static refName = "UserController"
|
||||
|
||||
if (Array.isArray(req.selection._id)) {
|
||||
for await (let _id of req.selection._id) {
|
||||
const user = await User.findById(_id).catch(err => {
|
||||
get = {
|
||||
"/self": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
return res.json(req.user)
|
||||
},
|
||||
},
|
||||
"/user": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: Schematized({
|
||||
select: ["_id", "username"],
|
||||
}, async (req, res) => {
|
||||
const 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": async (req, res) => {
|
||||
User.findOne({ username: req.body.username })
|
||||
.then((data) => {
|
||||
if (data) {
|
||||
return res.status(409).json("Username is already exists")
|
||||
}
|
||||
|
||||
const avatar = AvatarController.generate({ seed: req.body.username, type: "initials" })
|
||||
const hash = bcrypt.hashSync(req.body.password, parseInt(process.env.BCRYPT_ROUNDS))
|
||||
|
||||
let document = new User({
|
||||
username: req.body.username,
|
||||
fullName: req.body.fullName,
|
||||
avatar: avatar.uri,
|
||||
email: req.body.email,
|
||||
password: hash
|
||||
})
|
||||
|
||||
return document.save()
|
||||
})
|
||||
.then(data => {
|
||||
return res.send(data)
|
||||
})
|
||||
.catch(err => {
|
||||
return res.json(err)
|
||||
})
|
||||
},
|
||||
"/update_user": {
|
||||
middlewares: ["withAuthentication", "roles"],
|
||||
fn: Schematized({
|
||||
required: ["_id", "update"],
|
||||
select: ["_id", "update"],
|
||||
}, async (req, res) => {
|
||||
let user = await User.findById(req.selection._id).catch(() => {
|
||||
return false
|
||||
})
|
||||
if (user) {
|
||||
result.push(user)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not exists" })
|
||||
}
|
||||
}
|
||||
} 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 = {}
|
||||
}
|
||||
if ((user._id.toString() !== req.user._id.toString()) && (req.hasRole("admin") === false)) {
|
||||
return res.status(403).json({ error: "You are not allowed to update this user" })
|
||||
}
|
||||
|
||||
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))
|
||||
AllowedUserUpdateKeys.forEach((key) => {
|
||||
if (typeof req.selection.update[key] !== "undefined") {
|
||||
user[key] = req.selection.update[key]
|
||||
}
|
||||
})
|
||||
|
||||
return pass
|
||||
})
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return res.status(404).json({ error: "Users not found" })
|
||||
}
|
||||
|
||||
return res.json(result)
|
||||
}),
|
||||
getOne: Schematized({
|
||||
select: ["_id", "username"],
|
||||
}, async (req, res) => {
|
||||
const user = await User.findOne(req.selection)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: "User not exists" })
|
||||
}
|
||||
|
||||
return res.json(user)
|
||||
}),
|
||||
register: (req, res, next) => {
|
||||
User.findOne({ username: req.body.username })
|
||||
.then((data) => {
|
||||
if (data) {
|
||||
return res.status(409).json("Username is already exists")
|
||||
}
|
||||
|
||||
const avatar = AvatarController.generate({ seed: req.body.username, type: "initials" })
|
||||
const hash = bcrypt.hashSync(req.body.password, parseInt(process.env.BCRYPT_ROUNDS))
|
||||
|
||||
let document = new User({
|
||||
username: req.body.username,
|
||||
fullName: req.body.fullName,
|
||||
avatar: avatar.uri,
|
||||
email: req.body.email,
|
||||
roles: ["registered"],
|
||||
password: hash
|
||||
})
|
||||
|
||||
return document.save()
|
||||
})
|
||||
.then(data => {
|
||||
return res.send(data)
|
||||
})
|
||||
.catch(err => {
|
||||
return next(err)
|
||||
})
|
||||
},
|
||||
denyRole: async (req, res) => {
|
||||
// check if issuer user is admin
|
||||
if (!req.isAdmin()) {
|
||||
return res.status(403).send("You do not have administrator permission")
|
||||
}
|
||||
|
||||
let { user_id, username, roles } = req.body
|
||||
const userQuery = {
|
||||
username: username,
|
||||
user_id: user_id,
|
||||
}
|
||||
|
||||
// parse requested roles
|
||||
if (typeof roles === "string") {
|
||||
roles = roles.split(",").map(role => role.trim())
|
||||
} else {
|
||||
return res.send("No effect")
|
||||
}
|
||||
|
||||
// get current user roles
|
||||
const user = await User.findOne({ ...userQuery })
|
||||
if (typeof user === "undefined") {
|
||||
return res.status(404).send(`[${username}] User not found`)
|
||||
}
|
||||
|
||||
// query all roles mutation
|
||||
let queryRoles = []
|
||||
if (Array.isArray(roles)) {
|
||||
queryRoles = roles
|
||||
} else if (typeof roles === 'string') {
|
||||
queryRoles.push(roles)
|
||||
}
|
||||
|
||||
// mutate all roles
|
||||
if (queryRoles.length > 0 && Array.isArray(user.roles)) {
|
||||
queryRoles.forEach(role => {
|
||||
user.roles = user.roles.filter(_role => _role !== role)
|
||||
})
|
||||
}
|
||||
|
||||
// update user roles
|
||||
await user.save()
|
||||
return res.send("done")
|
||||
},
|
||||
grantRole: async (req, res) => {
|
||||
// check if issuer user is admin
|
||||
if (!req.isAdmin()) {
|
||||
return res.status(403).send("You do not have administrator permission")
|
||||
}
|
||||
|
||||
let { user_id, username, roles } = req.body
|
||||
const userQuery = {
|
||||
username: username,
|
||||
user_id: user_id,
|
||||
}
|
||||
|
||||
// parse requested roles
|
||||
if (typeof roles === "string") {
|
||||
roles = roles.split(",").map(role => role.trim())
|
||||
} else {
|
||||
return res.send("No effect")
|
||||
}
|
||||
|
||||
// get current user roles
|
||||
const user = await User.findOne({ ...userQuery })
|
||||
if (typeof user === "undefined") {
|
||||
return res.status(404).send(`[${username}] User not found`)
|
||||
}
|
||||
|
||||
// query all roles mutation
|
||||
let queryRoles = []
|
||||
if (Array.isArray(roles)) {
|
||||
queryRoles = roles
|
||||
} else if (typeof roles === 'string') {
|
||||
queryRoles.push(roles)
|
||||
}
|
||||
|
||||
|
||||
// mutate all roles
|
||||
if (queryRoles.length > 0 && Array.isArray(user.roles)) {
|
||||
queryRoles.forEach(role => {
|
||||
if (!user.roles.includes(role)) {
|
||||
user.roles.push(role)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// update user roles
|
||||
await user.save()
|
||||
return res.send("done")
|
||||
},
|
||||
updatePassword: async (req, res) => {
|
||||
//TODO
|
||||
},
|
||||
updateSelf: async (req, res) => {
|
||||
Object.keys(req.body).forEach(key => {
|
||||
req.user[key] = req.body[key]
|
||||
})
|
||||
|
||||
User.findOneAndUpdate({ _id: req.user._id }, req.user)
|
||||
.then(() => {
|
||||
return res.send(req.user)
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.send(500).send(err)
|
||||
})
|
||||
},
|
||||
update: async (req, res) => {
|
||||
// TODO
|
||||
},
|
||||
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 payload = {
|
||||
user_id: user._id,
|
||||
username: user.username,
|
||||
email: user.email
|
||||
}
|
||||
|
||||
if (req.body.allowRegenerate) {
|
||||
payload.allowRegenerate = true
|
||||
}
|
||||
|
||||
// generate token
|
||||
const token = Token.signNew(payload, options)
|
||||
|
||||
// send result
|
||||
res.json({ token: token })
|
||||
})(req, res)
|
||||
},
|
||||
logout: async (req, res, next) => {
|
||||
req.body = {
|
||||
user_id: req.decodedToken.user_id,
|
||||
token: req.jwtToken
|
||||
}
|
||||
|
||||
return SessionController.delete(req, res, next)
|
||||
},
|
||||
user.save()
|
||||
.then(() => {
|
||||
return res.send(user)
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.send(500).send(err)
|
||||
})
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
@ -1,3 +1,15 @@
|
||||
export { default as RolesController } from './RolesController'
|
||||
export { default as SessionController } from './SessionController'
|
||||
export { default as UserController } from './UserController'
|
||||
import { default as ConfigController } from "./ConfigController"
|
||||
import { default as RolesController } from "./RolesController"
|
||||
import { default as SessionController } from "./SessionController"
|
||||
import { default as UserController } from "./UserController"
|
||||
import { default as FilesController } from "./FilesController"
|
||||
import { default as PublicController } from "./PublicController"
|
||||
|
||||
export default [
|
||||
ConfigController,
|
||||
PublicController,
|
||||
RolesController,
|
||||
SessionController,
|
||||
UserController,
|
||||
FilesController,
|
||||
]
|
@ -1,109 +0,0 @@
|
||||
module.exports = [
|
||||
{
|
||||
route: "/regenerate",
|
||||
method: "POST",
|
||||
middleware: ["ensureAuthenticated", "useJwtStrategy"],
|
||||
fn: "SessionController.regenerate"
|
||||
},
|
||||
{
|
||||
route: "/role",
|
||||
method: 'PUT',
|
||||
middleware: ["ensureAuthenticated", "roles"],
|
||||
fn: "UserController.grantRole"
|
||||
},
|
||||
{
|
||||
route: "/role",
|
||||
method: 'DELETE',
|
||||
middleware: ["ensureAuthenticated", "roles"],
|
||||
fn: "UserController.denyRole"
|
||||
},
|
||||
{
|
||||
route: "/roles",
|
||||
method: "GET",
|
||||
fn: "RolesController.get",
|
||||
},
|
||||
{
|
||||
route: "/session",
|
||||
method: 'DELETE',
|
||||
middleware: "ensureAuthenticated",
|
||||
fn: "SessionController.delete",
|
||||
},
|
||||
{
|
||||
route: "/sessions",
|
||||
method: 'DELETE',
|
||||
middleware: "ensureAuthenticated",
|
||||
fn: "SessionController.deleteAll",
|
||||
},
|
||||
{
|
||||
route: "/validate_session",
|
||||
method: "POST",
|
||||
middleware: "useJwtStrategy",
|
||||
fn: "SessionController.validate",
|
||||
},
|
||||
{
|
||||
route: "/sessions",
|
||||
method: "GET",
|
||||
middleware: "ensureAuthenticated",
|
||||
fn: "SessionController.get",
|
||||
},
|
||||
{
|
||||
route: "/has_permissions",
|
||||
method: "POST",
|
||||
middleware: [
|
||||
"ensureAuthenticated",
|
||||
"hasPermissions"
|
||||
]
|
||||
},
|
||||
{
|
||||
route: "/self",
|
||||
method: "GET",
|
||||
middleware: "ensureAuthenticated",
|
||||
fn: "UserController.getSelf",
|
||||
},
|
||||
{
|
||||
route: "/users",
|
||||
method: "GET",
|
||||
middleware: "ensureAuthenticated",
|
||||
fn: "UserController.get",
|
||||
},
|
||||
{
|
||||
route: "/user",
|
||||
method: "GET",
|
||||
middleware: "ensureAuthenticated",
|
||||
fn: "UserController.getOne",
|
||||
},
|
||||
{
|
||||
route: "/self_user",
|
||||
method: "PUT",
|
||||
middleware: "ensureAuthenticated",
|
||||
fn: "UserController.updateSelf",
|
||||
},
|
||||
{
|
||||
route: "/user",
|
||||
method: "PUT",
|
||||
middleware: ["ensureAuthenticated", "privileged"],
|
||||
fn: "UserController.update",
|
||||
},
|
||||
{
|
||||
route: "/login",
|
||||
method: "POST",
|
||||
fn: "UserController.login",
|
||||
},
|
||||
{
|
||||
route: "/logout",
|
||||
method: "POST",
|
||||
middleware: ["ensureAuthenticated"],
|
||||
fn: "UserController.logout",
|
||||
},
|
||||
{
|
||||
route: "/register",
|
||||
method: "POST",
|
||||
fn: "UserController.register",
|
||||
},
|
||||
{
|
||||
route: "/is_auth",
|
||||
method: "POST",
|
||||
middleware: "ensureAuthenticated",
|
||||
fn: "UserController.isAuth",
|
||||
}
|
||||
]
|
@ -1,94 +1,107 @@
|
||||
import LinebridgeServer from 'linebridge/server'
|
||||
import bcrypt from 'bcrypt'
|
||||
import mongoose from 'mongoose'
|
||||
import passport from 'passport'
|
||||
import { User } from './models'
|
||||
import socketIo from 'socket.io'
|
||||
Array.prototype.updateFromObjectKeys = function (obj) {
|
||||
this.forEach((value, index) => {
|
||||
if (obj[value] !== undefined) {
|
||||
this[index] = obj[value]
|
||||
}
|
||||
})
|
||||
|
||||
const b64Decode = global.b64Decode = (data) => {
|
||||
return Buffer.from(data, 'base64').toString('utf-8')
|
||||
return this
|
||||
}
|
||||
|
||||
const b64Encode = global.b64Encode = (data) => {
|
||||
return Buffer.from(data, 'utf-8').toString('base64')
|
||||
}
|
||||
import path from "path"
|
||||
import LinebridgeServer from "linebridge/dist/server"
|
||||
import bcrypt from "bcrypt"
|
||||
import mongoose from "mongoose"
|
||||
import passport from "passport"
|
||||
import { User, Session } from "./models"
|
||||
import socketIo from "socket.io"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
const JwtStrategy = require('passport-jwt').Strategy
|
||||
const ExtractJwt = require('passport-jwt').ExtractJwt
|
||||
const LocalStrategy = require('passport-local').Strategy
|
||||
const { Buffer } = require("buffer")
|
||||
const b64Decode = global.b64Decode = (data) => {
|
||||
return Buffer.from(data, "base64").toString("utf-8")
|
||||
}
|
||||
const b64Encode = global.b64Encode = (data) => {
|
||||
return Buffer.from(data, "utf-8").toString("base64")
|
||||
}
|
||||
|
||||
const ExtractJwt = require("passport-jwt").ExtractJwt
|
||||
const LocalStrategy = require("passport-local").Strategy
|
||||
|
||||
function parseConnectionString(obj) {
|
||||
const { db_user, db_driver, db_name, db_pwd, db_hostname, db_port } = obj
|
||||
return `${db_driver}://${db_user}:${db_pwd}@${db_hostname}:${db_port}/${db_name}`
|
||||
}
|
||||
|
||||
class Server {
|
||||
constructor() {
|
||||
this.env = _env
|
||||
this.env = process.env
|
||||
this.listenPort = this.env.listenPort ?? 3000
|
||||
this.wsListenPort = this.env.wsListenPort ?? 3001
|
||||
|
||||
this.controllers = require("./controllers").default
|
||||
this.middlewares = require("./middlewares")
|
||||
this.controllers = require("./controllers")
|
||||
this.endpoints = require("./endpoints")
|
||||
|
||||
this.instance = new LinebridgeServer({
|
||||
listen: "0.0.0.0",
|
||||
middlewares: this.middlewares,
|
||||
controllers: this.controllers,
|
||||
endpoints: this.endpoints,
|
||||
port: this.listenPort
|
||||
})
|
||||
port: this.listenPort,
|
||||
wsPort: this.wsListenPort,
|
||||
headers: {
|
||||
"Access-Control-Expose-Headers": "regenerated_token",
|
||||
},
|
||||
onWSClientConnection: this.onWSClientConnection,
|
||||
onWSClientDisconnection: this.onWSClientDisconnection,
|
||||
}, this.controllers, this.middlewares)
|
||||
|
||||
this.server = this.instance.httpServer
|
||||
this.io = new socketIo.Server(3001, {
|
||||
maxHttpBufferSize: 100000000,
|
||||
connectTimeout: 5000,
|
||||
transports: ['websocket', 'polling'],
|
||||
pingInterval: 25 * 1000,
|
||||
pingTimeout: 5000,
|
||||
allowEIO3: true,
|
||||
cors: {
|
||||
origin: "http://localhost:8000",
|
||||
methods: ["GET", "POST"],
|
||||
}
|
||||
}).of("/main")
|
||||
this.server = this.instance.httpInterface
|
||||
|
||||
this.options = {
|
||||
jwtStrategy: {
|
||||
sessionLocationSign: this.instance.id,
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: this.instance.oskid,
|
||||
algorithms: ['sha1', 'RS256', 'HS256'],
|
||||
expiresIn: "1h"
|
||||
algorithms: ["sha1", "RS256", "HS256"],
|
||||
expiresIn: this.env.signLifetime ?? "1h",
|
||||
}
|
||||
}
|
||||
|
||||
this.instance.wsInterface["clients"] = []
|
||||
this.instance.wsInterface["findUserIdFromClientID"] = (searchClientId) => {
|
||||
return this.instance.wsInterface.clients.find(client => client.id === searchClientId)?.userId ?? false
|
||||
}
|
||||
this.instance.wsInterface["getClientSockets"] = (userId) => {
|
||||
return this.instance.wsInterface.clients.filter(client => client.userId === userId).map((client) => {
|
||||
return client?.socket
|
||||
})
|
||||
}
|
||||
this.instance.wsInterface["broadcast"] = async (channel, ...args) => {
|
||||
for await (const client of this.instance.wsInterface.clients) {
|
||||
client.socket.emit(channel, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
global.wsInterface = this.instance.wsInterface
|
||||
global.httpListenPort = this.listenPort
|
||||
global.globalPublicURI = this.env.globalPublicURI
|
||||
global.uploadPath = this.env.uploadPath ?? path.resolve(process.cwd(), "uploads")
|
||||
global.jwtStrategy = this.options.jwtStrategy
|
||||
global.signLocation = this.env.signLocation
|
||||
|
||||
this.initialize()
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
await this.connectToDB()
|
||||
await this.initPassport()
|
||||
await this.initWebsockets()
|
||||
|
||||
// register middlewares
|
||||
this.instance.middlewares["useJwtStrategy"] = (req, res, next) => {
|
||||
req.jwtStrategy = this.options.jwtStrategy
|
||||
next()
|
||||
}
|
||||
|
||||
this.io.on("connection", (socket) => {
|
||||
console.log(socket.id)
|
||||
})
|
||||
|
||||
await this.instance.init()
|
||||
}
|
||||
|
||||
getDBConnectionString() {
|
||||
const { db_user, db_driver, db_name, db_pwd, db_hostname, db_port } = _env
|
||||
return `${db_driver}://${db_user}:${db_pwd}@${db_hostname}:${db_port}/${db_name}`
|
||||
await this.instance.initialize()
|
||||
}
|
||||
|
||||
connectToDB = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
console.log("🌐 Trying to connect to DB...")
|
||||
mongoose.connect(this.getDBConnectionString(), { useNewUrlParser: true, useFindAndModify: false })
|
||||
mongoose.connect(parseConnectionString(this.env), { useNewUrlParser: true, useUnifiedTopology: true })
|
||||
.then((res) => { return resolve(true) })
|
||||
.catch((err) => { return reject(err) })
|
||||
} catch (err) {
|
||||
@ -105,26 +118,22 @@ class Server {
|
||||
})
|
||||
}
|
||||
|
||||
setWebsocketRooms = () => {
|
||||
this.ws.register("/test", {
|
||||
onOpen: (socket) => {
|
||||
console.log(socket)
|
||||
setInterval(() => {
|
||||
socket.send("Hello")
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
|
||||
this.ws.listen()
|
||||
}
|
||||
|
||||
initPassport() {
|
||||
this.instance.middlewares["useJwtStrategy"] = (req, res, next) => {
|
||||
req.jwtStrategy = this.options.jwtStrategy
|
||||
next()
|
||||
}
|
||||
this.instance.middlewares["useWS"] = (req, res, next) => {
|
||||
req.ws = global.wsInterface
|
||||
next()
|
||||
}
|
||||
|
||||
passport.use(new LocalStrategy({
|
||||
usernameField: "username",
|
||||
passwordField: "password",
|
||||
session: false
|
||||
}, (username, password, done) => {
|
||||
User.findOne({ username: b64Decode(username) }).select('+password')
|
||||
User.findOne({ username: b64Decode(username) }).select("+password")
|
||||
.then((data) => {
|
||||
if (data === null) {
|
||||
return done(null, false, this.options.jwtStrategy)
|
||||
@ -138,22 +147,97 @@ class Server {
|
||||
.catch(err => done(err, null, this.options.jwtStrategy))
|
||||
}))
|
||||
|
||||
passport.use(new JwtStrategy(this.options.jwtStrategy, (token, callback) => {
|
||||
User.findOne({ _id: token.user_id })
|
||||
.then((data) => {
|
||||
if (data === null) {
|
||||
return callback(null, false)
|
||||
} else {
|
||||
return callback(null, data, token)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
return callback(err, null)
|
||||
})
|
||||
}))
|
||||
|
||||
this.server.use(passport.initialize())
|
||||
}
|
||||
|
||||
initWebsockets() {
|
||||
const onAuthenticated = (socket, user_id) => {
|
||||
this.attachClientSocket(socket, user_id)
|
||||
socket.emit("authenticated")
|
||||
}
|
||||
|
||||
const onAuthenticatedFailed = (socket, error) => {
|
||||
this.detachClientSocket(socket)
|
||||
socket.emit("authenticateFailed", {
|
||||
error,
|
||||
})
|
||||
}
|
||||
|
||||
this.instance.wsInterface.eventsChannels.push(["/main", "authenticate", async (socket, token) => {
|
||||
const session = await Session.findOne({ token }).catch(err => {
|
||||
return false
|
||||
})
|
||||
|
||||
if (!session) {
|
||||
return onAuthenticatedFailed(socket, "Session not found")
|
||||
}
|
||||
|
||||
this.verifyJwt(token, async (err, decoded) => {
|
||||
if (err) {
|
||||
return onAuthenticatedFailed(socket, err)
|
||||
} else {
|
||||
const user = await User.findById(decoded.user_id).catch(err => {
|
||||
return false
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return onAuthenticatedFailed(socket, "User not found")
|
||||
}
|
||||
|
||||
return onAuthenticated(socket, user)
|
||||
}
|
||||
})
|
||||
}])
|
||||
}
|
||||
|
||||
onWSClientConnection = async (socket) => {
|
||||
console.log(`🌐 Client connected: ${socket.id}`)
|
||||
}
|
||||
|
||||
onWSClientDisconnection = async (socket) => {
|
||||
console.log(`🌐 Client disconnected: ${socket.id}`)
|
||||
this.detachClientSocket(socket)
|
||||
}
|
||||
|
||||
attachClientSocket = async (client, userData) => {
|
||||
const socket = this.instance.wsInterface.clients.find(c => c.id === client.id)
|
||||
|
||||
if (socket) {
|
||||
socket.socket.disconnect()
|
||||
}
|
||||
|
||||
const clientObj = {
|
||||
id: client.id,
|
||||
socket: client,
|
||||
userId: userData._id.toString(),
|
||||
user: userData,
|
||||
}
|
||||
|
||||
this.instance.wsInterface.clients.push(clientObj)
|
||||
|
||||
this.instance.wsInterface.io.emit("userConnected", userData)
|
||||
}
|
||||
|
||||
detachClientSocket = async (client) => {
|
||||
const socket = this.instance.wsInterface.clients.find(c => c.id === client.id)
|
||||
|
||||
if (socket) {
|
||||
socket.socket.disconnect()
|
||||
this.instance.wsInterface.clients = this.instance.wsInterface.clients.filter(c => c.id !== client.id)
|
||||
}
|
||||
|
||||
this.instance.wsInterface.io.emit("userDisconnect", client.id)
|
||||
}
|
||||
|
||||
verifyJwt = (token, callback) => {
|
||||
jwt.verify(token, this.options.jwtStrategy.secretOrKey, async (err, decoded) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
return callback(null, decoded)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
new Server()
|
70
packages/server/src/lib/essc/index.js
Normal file
70
packages/server/src/lib/essc/index.js
Normal file
@ -0,0 +1,70 @@
|
||||
// random 5 digits number
|
||||
const random5 = () => Math.floor(Math.random() * 90000) + 10000
|
||||
|
||||
// secure random 5 digits number
|
||||
const random5Secure = () => {
|
||||
const random = random5()
|
||||
return random.toString().padStart(5, '0')
|
||||
}
|
||||
|
||||
// aa-bbbbb-cccc
|
||||
//* a: type (2 digits)
|
||||
//* b: serial (5 digits)
|
||||
//* c: manufacturer (4 digits)
|
||||
|
||||
const typesNumber = {
|
||||
"computers-desktop": [1],
|
||||
"computers-laptop": [2],
|
||||
"computers-tablet": [3],
|
||||
"computers-smartphone": [4],
|
||||
"networking": [5],
|
||||
"peripherals-printer": [6],
|
||||
"peripherals-monitor": [7],
|
||||
}
|
||||
|
||||
export function genV1(params) {
|
||||
let { type, serial, manufacturer } = params // please in that order
|
||||
type = type.toLowerCase()
|
||||
|
||||
let str = []
|
||||
|
||||
// Type parsing
|
||||
let typeBuf = []
|
||||
|
||||
if (typeof typesNumber[type] === "undefined") {
|
||||
typeBuf[0] = 0
|
||||
typeBuf[1] = "X"
|
||||
} else {
|
||||
typeBuf[0] = typesNumber[type][0]
|
||||
typeBuf[1] = typesNumber[type][1] ?? "X"
|
||||
}
|
||||
|
||||
str.push(typeBuf.join(""))
|
||||
|
||||
// Serial parsing
|
||||
// if serial is not defined, generate a random 4 digits number
|
||||
if (typeof serial === "undefined") {
|
||||
str.push(random5().toString())
|
||||
} else {
|
||||
// push last 5 digits of serial, if serial is not 5 digits, pad with 0
|
||||
let serialBuf = []
|
||||
|
||||
serialBuf[0] = serial.slice(-5, -4) ?? "0"
|
||||
serialBuf[1] = serial.slice(-4, -3) ?? "0"
|
||||
serialBuf[2] = serial.slice(-3, -2) ?? "0"
|
||||
serialBuf[3] = serial.slice(-2, -1) ?? "0"
|
||||
serialBuf[4] = serial.slice(-1) ?? "0"
|
||||
|
||||
str.push(serialBuf.join(""))
|
||||
}
|
||||
|
||||
// Manufacturer parsing
|
||||
// abreviate manufacturer name to 4 letters
|
||||
if (typeof manufacturer === "undefined") {
|
||||
str.push("GENR")
|
||||
} else {
|
||||
str.push(manufacturer.slice(0, 4).toUpperCase())
|
||||
}
|
||||
|
||||
return str.join("-")
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
export default (schema = {}, fn) => {
|
||||
return async (req, res, next) => {
|
||||
// if is nullish
|
||||
req.body = req.body ?? {}
|
||||
req.query = req.query ?? {}
|
||||
|
||||
if (typeof req.body === "undefined") {
|
||||
req.body = {}
|
||||
}
|
||||
if (typeof req.query === "undefined") {
|
||||
req.query = {}
|
||||
}
|
||||
|
||||
if (schema.required) {
|
||||
if (Array.isArray(schema.required)) {
|
||||
const missingKeys = []
|
||||
|
@ -1,29 +1,54 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { Session } from '../../models'
|
||||
import jwt from "jsonwebtoken"
|
||||
import { nanoid } from "nanoid"
|
||||
import { Session, User } from "../../models"
|
||||
|
||||
export function signNew(payload, options) {
|
||||
const data = {
|
||||
uuid: nanoid(),
|
||||
allowRegenerate: false,
|
||||
...payload
|
||||
export async function createNewAuthToken(user, options = {}) {
|
||||
const payload = {
|
||||
user_id: user._id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
refreshToken: nanoid(),
|
||||
signLocation: global.signLocation,
|
||||
}
|
||||
|
||||
const token = jwt.sign(data, options.secretOrKey, {
|
||||
await User.findByIdAndUpdate(user._id, { refreshToken: payload.refreshToken })
|
||||
|
||||
return await signNew(payload, options)
|
||||
}
|
||||
|
||||
export async function signNew(payload, options = {}) {
|
||||
if (options.updateSession) {
|
||||
const sessionData = await Session.findById(options.updateSession)
|
||||
payload.session_uuid = sessionData.session_uuid
|
||||
} else {
|
||||
payload.session_uuid = nanoid()
|
||||
}
|
||||
|
||||
const token = jwt.sign(payload, options.secretOrKey, {
|
||||
expiresIn: options.expiresIn ?? "1h",
|
||||
algorithm: options.algorithm ?? "HS256"
|
||||
})
|
||||
|
||||
let newSession = new Session({
|
||||
uuid: data.uuid,
|
||||
user_id: data.user_id,
|
||||
allowRegenerate: data.allowRegenerate,
|
||||
const session = {
|
||||
token: token,
|
||||
session_uuid: payload.session_uuid,
|
||||
username: payload.username,
|
||||
user_id: payload.user_id,
|
||||
date: new Date().getTime(),
|
||||
location: options.sessionLocationSign
|
||||
})
|
||||
location: payload.signLocation ?? "rs-auth",
|
||||
}
|
||||
|
||||
newSession.save()
|
||||
if (options.updateSession) {
|
||||
await Session.findByIdAndUpdate(options.updateSession, {
|
||||
token: session.token,
|
||||
date: session.date,
|
||||
location: session.location,
|
||||
})
|
||||
} else {
|
||||
let newSession = new Session(session)
|
||||
|
||||
newSession.save()
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import passport from 'passport'
|
||||
import { Session } from '../../models'
|
||||
|
||||
export default (req, res, next) => {
|
||||
function unauthorized() {
|
||||
console.log("Returning failed session")
|
||||
return res.status(401).send({ error: 'Invalid session', })
|
||||
}
|
||||
|
||||
const authHeader = req.headers?.authorization?.split(' ')
|
||||
|
||||
if (authHeader && authHeader[0] === 'Bearer') {
|
||||
const token = authHeader[1]
|
||||
|
||||
passport.authenticate('jwt', { session: false }, async (err, user, decodedToken) => {
|
||||
if (err) {
|
||||
return res.status(500).send({ error: err.message })
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).send({ error: "No user data found" })
|
||||
}
|
||||
|
||||
const sessions = await Session.find({ user_id: decodedToken.user_id })
|
||||
const sessionsTokens = sessions.map(session => session.token)
|
||||
|
||||
if (!sessionsTokens.includes(token)) {
|
||||
return unauthorized()
|
||||
}
|
||||
|
||||
req.user = user
|
||||
req.jwtToken = token
|
||||
req.decodedToken = decodedToken
|
||||
|
||||
return next()
|
||||
})(req, res, next)
|
||||
} else {
|
||||
return unauthorized()
|
||||
}
|
||||
}
|
@ -1,4 +1,10 @@
|
||||
export { default as ensureAuthenticated } from './ensureAuthenticated'
|
||||
export { default as errorHandler } from './errorHandler'
|
||||
export { default as hasPermissions } from './hasPermissions'
|
||||
export { default as roles } from './roles'
|
||||
const fileUpload = require("@nanoexpress/middleware-file-upload/cjs")()
|
||||
|
||||
export { default as withAuthentication } from "./withAuthentication"
|
||||
export { default as errorHandler } from "./errorHandler"
|
||||
export { default as hasPermissions } from "./hasPermissions"
|
||||
export { default as roles } from "./roles"
|
||||
export { default as onlyAdmin } from "./onlyAdmin"
|
||||
export { default as permissions } from "./permissions"
|
||||
|
||||
export { fileUpload as fileUpload }
|
@ -1,6 +1,6 @@
|
||||
export default (req, res, next) => {
|
||||
if (!req.user.roles.includes("admin")) {
|
||||
return res.status(401).send({ error: "To make this request it is necessary to have administrator permissions" })
|
||||
return res.status(403).send({ error: "To make this request it is necessary to have administrator permissions" })
|
||||
}
|
||||
|
||||
next()
|
39
packages/server/src/middlewares/permissions/index.js
Normal file
39
packages/server/src/middlewares/permissions/index.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { Config } from "../../models"
|
||||
|
||||
export default (req, res, next) => {
|
||||
const requestedPath = `${req.method.toLowerCase()}${req.path.toLowerCase()}`
|
||||
|
||||
Config.findOne({ key: "permissions" }, undefined, {
|
||||
lean: true,
|
||||
}).then(({ value }) => {
|
||||
req.assertedPermissions = []
|
||||
|
||||
const pathRoles = value.pathRoles ?? {}
|
||||
|
||||
if (typeof pathRoles[requestedPath] === "undefined") {
|
||||
console.warn(`[Permissions] No permissions defined for path ${requestedPath}`)
|
||||
return next()
|
||||
}
|
||||
|
||||
const requiredRoles = Array.isArray(pathRoles[requestedPath]) ? pathRoles[requestedPath] : [pathRoles[requestedPath]]
|
||||
|
||||
requiredRoles.forEach((role) => {
|
||||
if (req.user.roles.includes(role)) {
|
||||
req.assertedPermissions.push(role)
|
||||
}
|
||||
})
|
||||
|
||||
if (req.user.roles.includes("admin")) {
|
||||
req.assertedPermissions.push("admin")
|
||||
}
|
||||
|
||||
if (req.assertedPermissions.length === 0 && !req.user.roles.includes("admin")) {
|
||||
return res.status(403).json({
|
||||
error: "forbidden",
|
||||
message: "You don't have permission to access this resource",
|
||||
})
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
}
|
@ -7,5 +7,13 @@ export default (req, res, next) => {
|
||||
return false
|
||||
}
|
||||
|
||||
req.hasRole = (role) => {
|
||||
if (req.user.roles.includes(role)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
63
packages/server/src/middlewares/withAuthentication/index.js
Normal file
63
packages/server/src/middlewares/withAuthentication/index.js
Normal file
@ -0,0 +1,63 @@
|
||||
import { Session, User } from "../../models"
|
||||
import { Token } from "../../lib"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
export default (req, res, next) => {
|
||||
function reject(description) {
|
||||
return res.status(401).send({ error: `${description ?? "Invalid session"}` })
|
||||
}
|
||||
|
||||
const authHeader = req.headers?.authorization?.split(" ")
|
||||
|
||||
if (authHeader && authHeader[0] === "Bearer") {
|
||||
const token = authHeader[1]
|
||||
let decoded = null
|
||||
|
||||
try {
|
||||
decoded = jwt.decode(token)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
if (!decoded) {
|
||||
return reject("Cannot decode token")
|
||||
}
|
||||
|
||||
jwt.verify(token, global.jwtStrategy.secretOrKey, async (err) => {
|
||||
const sessions = await Session.find({ user_id: decoded.user_id })
|
||||
const currentSession = sessions.find((session) => session.token === token)
|
||||
|
||||
if (!currentSession) {
|
||||
return reject("Cannot find session")
|
||||
}
|
||||
|
||||
const userData = await User.findOne({ _id: currentSession.user_id }).select("+refreshToken")
|
||||
|
||||
if (!userData) {
|
||||
return res.status(404).send({ error: "No user data found" })
|
||||
}
|
||||
|
||||
if (err) {
|
||||
if (decoded.refreshToken === userData.refreshToken) {
|
||||
const regeneratedToken = await Token.createNewAuthToken(userData, {
|
||||
...global.jwtStrategy,
|
||||
updateSession: currentSession._id,
|
||||
})
|
||||
|
||||
res.setHeader("regenerated_token", regeneratedToken)
|
||||
} else {
|
||||
return reject("Token expired, cannot refresh token either")
|
||||
}
|
||||
}
|
||||
|
||||
req.user = userData
|
||||
req.jwtToken = token
|
||||
req.decodedToken = decoded
|
||||
req.currentSession = currentSession
|
||||
|
||||
return next()
|
||||
})
|
||||
} else {
|
||||
return reject("Missing token header")
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
import mongoose from 'mongoose'
|
||||
import { Schema } from 'mongoose'
|
||||
import mongoose, { Schema } from "mongoose"
|
||||
|
||||
function getSchemas() {
|
||||
const obj = Object()
|
||||
|
||||
|
||||
const _schemas = require("../schemas")
|
||||
Object.keys(_schemas).forEach(key => {
|
||||
Object.keys(_schemas).forEach((key) => {
|
||||
obj[key] = Schema(_schemas[key])
|
||||
})
|
||||
|
||||
@ -14,6 +13,7 @@ function getSchemas() {
|
||||
|
||||
const schemas = getSchemas()
|
||||
|
||||
export const Role = mongoose.model('Role', schemas.Role, 'roles')
|
||||
export const User = mongoose.model('User', schemas.User, "accounts")
|
||||
export const Session = mongoose.model('Session', schemas.Session, "sessions")
|
||||
export const Config = mongoose.model("Config", schemas.Config, "config")
|
||||
export const User = mongoose.model("User", schemas.User, "accounts")
|
||||
export const Session = mongoose.model("Session", schemas.Session, "sessions")
|
||||
export const Role = mongoose.model("Role", schemas.Role, "roles")
|
3
packages/server/src/schemas/config/index.js
Normal file
3
packages/server/src/schemas/config/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export default {
|
||||
key: { type: String, required: true },
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export { default as User } from './user'
|
||||
export { default as Role } from './role'
|
||||
export { default as Session } from './session'
|
||||
export { default as User } from "./user"
|
||||
export { default as Role } from "./role"
|
||||
export { default as Session } from "./session"
|
||||
export { default as Config } from "./config"
|
@ -1,9 +1,8 @@
|
||||
export default {
|
||||
allowRegenerate: { type: Boolean, default: false },
|
||||
uuid: { type: String, required: true },
|
||||
session_uuid: { type: String, required: true },
|
||||
token: { type: String, required: true },
|
||||
username: { type: String, required: true },
|
||||
user_id: { type: String, required: true },
|
||||
date: { type: Number, default: 0 },
|
||||
location: { type: String, default: "Unknown" },
|
||||
geo: { type: String, default: "Unknown" },
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
export default {
|
||||
refreshToken: { type: String, select: false },
|
||||
username: { type: String, required: true },
|
||||
password: { type: String, required: true, select: false },
|
||||
fullName: String,
|
||||
avatar: { type: String },
|
||||
email: String,
|
||||
roles: [],
|
||||
legal_id: Object,
|
||||
phone: Number,
|
||||
roles: { type: Array, default: [] },
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user