improve ws & cleanup

This commit is contained in:
srgooglo 2022-10-28 22:06:26 +00:00
parent 41ae2d81e8
commit 92879b0312

View File

@ -5,29 +5,34 @@ import bcrypt from "bcrypt"
import passport from "passport" import passport from "passport"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import EventEmitter from "@foxify/events"
import { User, Session, Config } from "./models" import { User, Session, Config } from "./models"
import DbManager from "./classes/DbManager" import DbManager from "./classes/DbManager"
import { createStorageClientInstance } from "./classes/StorageClient" import { createStorageClientInstance } from "./classes/StorageClient"
import internalEvents from "./events"
const ExtractJwt = require("passport-jwt").ExtractJwt const ExtractJwt = require("passport-jwt").ExtractJwt
const LocalStrategy = require("passport-local").Strategy const LocalStrategy = require("passport-local").Strategy
const controllers = require("./controllers") const controllers = require("./controllers")
const middlewares = require("./middlewares") const middlewares = require("./middlewares")
global.httpListenPort = process.env.PORT || 3000
global.signLocation = process.env.signLocation
export default class Server { export default class Server {
env = process.env DB = new DbManager()
eventBus = new EventEmitter()
storage = global.storage = createStorageClientInstance() storage = global.storage = createStorageClientInstance()
DB = new DbManager()
httpListenPort = this.env.listenPort ?? 3000
controllers = [ controllers = [
controllers.ConfigController, controllers.ConfigController,
controllers.RolesController, controllers.RolesController,
controllers.FollowerController,
controllers.SessionController, controllers.SessionController,
controllers.UserController, controllers.UserController,
controllers.FilesController, controllers.FilesController,
@ -43,24 +48,28 @@ export default class Server {
middlewares = middlewares middlewares = middlewares
server = new LinebridgeServer({ server = new LinebridgeServer({
port: this.httpListenPort, port: httpListenPort,
headers: { headers: {
"Access-Control-Expose-Headers": "regenerated_token", "Access-Control-Expose-Headers": "regenerated_token",
}, },
onWSClientConnection: this.onWSClientConnection, onWSClientConnection: (...args) => {
onWSClientDisconnection: this.onWSClientDisconnection, this.onWSClientConnection(...args)
}, this.controllers, this.middlewares) },
onWSClientDisconnect: (...args) => {
this.onWSClientDisconnect(...args)
},
},
this.controllers,
this.middlewares
)
options = { jwtStrategy = global.jwtStrategy = {
jwtStrategy: {
sessionLocationSign: this.server.id,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: this.server.oskid, secretOrKey: this.server.oskid,
algorithms: ["sha1", "RS256", "HS256"], algorithms: ["sha1", "RS256", "HS256"],
expiresIn: this.env.signLifetime ?? "1h", expiresIn: process.env.signLifetime ?? "1h",
enforceRegenerationTokenExpiration: false, enforceRegenerationTokenExpiration: false,
} }
}
constructor() { constructor() {
this.server.engineInstance.use(express.json()) this.server.engineInstance.use(express.json())
@ -82,12 +91,8 @@ export default class Server {
} }
global.wsInterface = this.server.wsInterface global.wsInterface = this.server.wsInterface
global.httpListenPort = this.listenPort
global.uploadCachePath = this.env.uploadCachePath ?? path.resolve(process.cwd(), "cache") global.uploadCachePath = process.env.uploadCachePath ?? path.resolve(process.cwd(), "cache")
global.jwtStrategy = this.options.jwtStrategy
global.signLocation = this.env.signLocation
global.DEFAULT_POSTING_POLICY = { global.DEFAULT_POSTING_POLICY = {
maxMessageLength: 512, maxMessageLength: 512,
@ -111,7 +116,14 @@ export default class Server {
maximumFileSize: 80 * 1024 * 1024, maximumFileSize: 80 * 1024 * 1024,
maximunFilesPerRequest: 20, maximunFilesPerRequest: 20,
} }
// register internal events
for (const [eventName, eventHandler] of Object.entries(internalEvents)) {
this.eventBus.on(eventName, eventHandler)
} }
}
events = internalEvents
async initialize() { async initialize() {
await this.DB.connect() await this.DB.connect()
@ -176,7 +188,7 @@ export default class Server {
initPassport() { initPassport() {
this.server.middlewares["useJwtStrategy"] = (req, res, next) => { this.server.middlewares["useJwtStrategy"] = (req, res, next) => {
req.jwtStrategy = this.options.jwtStrategy req.jwtStrategy = this.jwtStrategy
next() next()
} }
@ -193,40 +205,41 @@ export default class Server {
User.findOne(query).select("+password") User.findOne(query).select("+password")
.then((data) => { .then((data) => {
if (data === null) { if (data === null) {
return done(null, false, this.options.jwtStrategy) return done(null, false, this.jwtStrategy)
} else if (!bcrypt.compareSync(password, data.password)) { } else if (!bcrypt.compareSync(password, data.password)) {
return done(null, false, this.options.jwtStrategy) return done(null, false, this.jwtStrategy)
} }
// create a token // create a token
return done(null, data, this.options.jwtStrategy, { username, password }) return done(null, data, this.jwtStrategy, { username, password })
}) })
.catch(err => done(err, null, this.options.jwtStrategy)) .catch(err => done(err, null, this.jwtStrategy))
})) }))
this.server.engineInstance.use(passport.initialize()) this.server.engineInstance.use(passport.initialize())
} }
initWebsockets() { initWebsockets() {
this.server.middlewares["useWS"] = (req, res, next) => { const onAuthenticated = async (socket, userData) => {
req.ws = global.wsInterface await this.attachClientSocket(socket, userData)
next()
return socket.emit("authenticated")
} }
const onAuthenticated = (socket, user_id) => { const onAuthenticatedFailed = async (socket, error) => {
this.attachClientSocket(socket, user_id) await this.detachClientSocket(socket)
socket.emit("authenticated")
}
const onAuthenticatedFailed = (socket, error) => { return socket.emit("authenticateFailed", {
this.detachClientSocket(socket)
socket.emit("authenticateFailed", {
error, error,
}) })
} }
this.server.wsInterface.eventsChannels.push(["/main", "authenticate", async (socket, token) => { this.server.wsInterface.eventsChannels.push(["/main", "authenticate", async (socket, authPayload) => {
const session = await Session.findOne({ token }).catch(err => { if (!authPayload) {
return onAuthenticatedFailed(socket, "missing_auth_payload")
}
const session = await Session.findOne({ token: authPayload.token }).catch((err) => {
return false return false
}) })
@ -234,20 +247,20 @@ export default class Server {
return onAuthenticatedFailed(socket, "Session not found") return onAuthenticatedFailed(socket, "Session not found")
} }
this.verifyJwt(token, async (err, decoded) => { await jwt.verify(authPayload.token, this.jwtStrategy.secretOrKey, async (err, decoded) => {
if (err) { if (err) {
return onAuthenticatedFailed(socket, err) return onAuthenticatedFailed(socket, err)
} else { }
const user = await User.findById(decoded.user_id).catch(err => {
const userData = await User.findById(decoded.user_id).catch((err) => {
return false return false
}) })
if (!user) { if (!userData) {
return onAuthenticatedFailed(socket, "User not found") return onAuthenticatedFailed(socket, "User not found")
} }
return onAuthenticated(socket, user) return onAuthenticated(socket, userData)
}
}) })
}]) }])
} }
@ -256,48 +269,40 @@ export default class Server {
console.log(`🌐 Client connected: ${socket.id}`) console.log(`🌐 Client connected: ${socket.id}`)
} }
onWSClientDisconnection = async (socket) => { onWSClientDisconnect = async (socket) => {
console.log(`🌐 Client disconnected: ${socket.id}`) console.log(`🌐 Client disconnected: ${socket.id}`)
this.detachClientSocket(socket) this.detachClientSocket(socket)
} }
attachClientSocket = async (client, userData) => { attachClientSocket = async (socket, userData) => {
const socket = this.server.wsInterface.clients.find(c => c.id === client.id) const client = this.server.wsInterface.clients.find(c => c.id === socket.id)
if (socket) { if (client) {
socket.socket.disconnect() client.socket.disconnect()
} }
const clientObj = { const clientObj = {
id: client.id, id: socket.id,
socket: client, socket: socket,
userId: userData._id.toString(), user_id: userData._id.toString(),
user: userData,
} }
this.server.wsInterface.clients.push(clientObj) this.server.wsInterface.clients.push(clientObj)
this.server.wsInterface.io.emit("userConnected", userData) console.log(`📣 Client [${socket.id}] authenticated as ${userData.username}`)
this.eventBus.emit("user.connected", clientObj.user_id)
} }
detachClientSocket = async (client) => { detachClientSocket = async (socket) => {
const socket = this.server.wsInterface.clients.find(c => c.id === client.id) const client = this.server.wsInterface.clients.find(c => c.id === socket.id)
if (socket) { if (client) {
socket.socket.disconnect() this.server.wsInterface.clients = this.server.wsInterface.clients.filter(c => c.id !== socket.id)
this.server.wsInterface.clients = this.server.wsInterface.clients.filter(c => c.id !== client.id)
}
this.server.wsInterface.io.emit("userDisconnect", client.id) console.log(`📣🔴 Client [${socket.id}] authenticated as ${client.user_id} disconnected`)
}
verifyJwt = (token, callback) => { this.eventBus.emit("user.disconnected", client.user_id)
jwt.verify(token, this.options.jwtStrategy.secretOrKey, async (err, decoded) => { }
if (err) {
return callback(err)
}
return callback(null, decoded)
})
} }
} }