From aa9211e75c908ae954f411d16009283ae7cf8d34 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Fri, 5 Aug 2022 20:04:02 +0200 Subject: [PATCH] split controller init --- packages/server/src/api.js | 273 +++++++++++++++++++++++ packages/server/src/controllers/index.js | 27 +-- packages/server/src/index.js | 268 +--------------------- 3 files changed, 289 insertions(+), 279 deletions(-) create mode 100644 packages/server/src/api.js diff --git a/packages/server/src/api.js b/packages/server/src/api.js new file mode 100644 index 00000000..62916974 --- /dev/null +++ b/packages/server/src/api.js @@ -0,0 +1,273 @@ +import path from "path" +import { Server as LinebridgeServer } from "linebridge/dist/server" +import express from "express" +import bcrypt from "bcrypt" +import passport from "passport" + +import jwt from "jsonwebtoken" + +import { User, Session, Config } from "./models" +import DbManager from "./classes/DbManager" + +const ExtractJwt = require("passport-jwt").ExtractJwt +const LocalStrategy = require("passport-local").Strategy + +const controllers = require("./controllers") +const middlewares = require("./middlewares") + +export default class Server { + env = process.env + + DB = new DbManager() + + httpListenPort = this.env.listenPort ?? 3000 + + controllers = [ + controllers.ConfigController, + controllers.RolesController, + controllers.SessionController, + controllers.UserController, + controllers.FilesController, + controllers.PublicController, + controllers.PostsController, + controllers.StreamingController, + ] + + middlewares = middlewares + + server = new LinebridgeServer({ + port: this.httpListenPort, + headers: { + "Access-Control-Expose-Headers": "regenerated_token", + }, + onWSClientConnection: this.onWSClientConnection, + onWSClientDisconnection: this.onWSClientDisconnection, + }, this.controllers, this.middlewares) + + options = { + jwtStrategy: { + sessionLocationSign: this.server.id, + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: this.server.oskid, + algorithms: ["sha1", "RS256", "HS256"], + expiresIn: this.env.signLifetime ?? "1h", + } + } + + constructor() { + this.server.engineInstance.use(express.json()) + this.server.engineInstance.use(express.urlencoded({ extended: true })) + + this.server.engineInstance.use("/storage", express.static(path.join(__dirname, "../uploads"))) + + this.server.wsInterface["clients"] = [] + this.server.wsInterface["findUserIdFromClientID"] = (searchClientId) => { + return this.server.wsInterface.clients.find(client => client.id === searchClientId)?.userId ?? false + } + this.server.wsInterface["getClientSockets"] = (userId) => { + return this.server.wsInterface.clients.filter(client => client.userId === userId).map((client) => { + return client?.socket + }) + } + this.server.wsInterface["broadcast"] = async (channel, ...args) => { + for await (const client of this.server.wsInterface.clients) { + client.socket.emit(channel, ...args) + } + } + + global.wsInterface = this.server.wsInterface + global.httpListenPort = this.listenPort + + global.publicHostname = this.env.publicHostname + global.publicProtocol = this.env.publicProtocol + global.globalPublicUri = `${this.env.publicProtocol}://${this.env.publicHost}` + + global.uploadPath = this.env.uploadPath ?? path.resolve(process.cwd(), "uploads") + global.uploadCachePath = this.env.uploadCachePath ?? path.resolve(process.cwd(), "cache") + + global.jwtStrategy = this.options.jwtStrategy + global.signLocation = this.env.signLocation + } + + async initialize() { + await this.DB.connect() + await this.initializeConfigDB() + + await this.checkSetup() + await this.initPassport() + await this.initWebsockets() + + await this.server.initialize() + } + + initializeConfigDB = async () => { + let serverConfig = await Config.findOne({ key: "server" }).catch(() => { + return false + }) + + if (!serverConfig) { + serverConfig = new Config({ + key: "server", + value: { + setup: false, + }, + }) + + + await serverConfig.save() + } + } + + checkSetup = async () => { + return new Promise(async (resolve, reject) => { + let setupOk = (await Config.findOne({ key: "server" })).value?.setup ?? false + + if (!setupOk) { + console.log("āš ļø Server setup is not complete, running setup proccess.") + let setupScript = await import("./mainAPI/setup") + + setupScript = setupScript.default ?? setupScript + + try { + for await (let script of setupScript) { + await script() + } + + console.log("āœ… Server setup complete.") + + await Config.updateOne({ key: "server" }, { value: { setup: true } }) + + return resolve() + } catch (error) { + console.log("āŒ Server setup failed.") + console.error(error) + process.exit(1) + } + } + + return resolve() + }) + } + + initPassport() { + this.server.middlewares["useJwtStrategy"] = (req, res, next) => { + req.jwtStrategy = this.options.jwtStrategy + next() + } + + passport.use(new LocalStrategy({ + usernameField: "username", + passwordField: "password", + session: false + }, (username, password, done) => { + User.findOne({ username }).select("+password") + .then((data) => { + if (data === null) { + return done(null, false, this.options.jwtStrategy) + } else if (!bcrypt.compareSync(password, data.password)) { + return done(null, false, this.options.jwtStrategy) + } + + // create a token + return done(null, data, this.options.jwtStrategy, { username, password }) + }) + .catch(err => done(err, null, this.options.jwtStrategy)) + })) + + this.server.engineInstance.use(passport.initialize()) + } + + initWebsockets() { + this.server.middlewares["useWS"] = (req, res, next) => { + req.ws = global.wsInterface + next() + } + + 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.server.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.server.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.server.wsInterface.clients.push(clientObj) + + this.server.wsInterface.io.emit("userConnected", userData) + } + + detachClientSocket = async (client) => { + const socket = this.server.wsInterface.clients.find(c => c.id === client.id) + + if (socket) { + socket.socket.disconnect() + this.server.wsInterface.clients = this.server.wsInterface.clients.filter(c => c.id !== client.id) + } + + this.server.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) + }) + } +} \ No newline at end of file diff --git a/packages/server/src/controllers/index.js b/packages/server/src/controllers/index.js index adf6affe..1a493622 100644 --- a/packages/server/src/controllers/index.js +++ b/packages/server/src/controllers/index.js @@ -1,19 +1,8 @@ -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" -import { default as PostsController } from "./PostsController" -import { default as StreamingController } from "./StreamingController" - -export default [ - PostsController, - ConfigController, - PublicController, - RolesController, - SessionController, - UserController, - FilesController, - StreamingController, -] \ No newline at end of file +export { default as ConfigController } from "./ConfigController" +export { default as RolesController } from "./RolesController" +export { default as SessionController } from "./SessionController" +export { default as UserController } from "./UserController" +export { default as FilesController } from "./FilesController" +export { default as PublicController } from "./PublicController" +export { default as PostsController } from "./PostsController" +export { default as StreamingController } from "./StreamingController" \ No newline at end of file diff --git a/packages/server/src/index.js b/packages/server/src/index.js index 990d68e1..813eebc2 100644 --- a/packages/server/src/index.js +++ b/packages/server/src/index.js @@ -20,267 +20,15 @@ Array.prototype.updateFromObjectKeys = function (obj) { return this } -import path from "path" -import { Server as LinebridgeServer } from "linebridge/dist/server" -import express from "express" -import bcrypt from "bcrypt" -import passport from "passport" +import API from "./api" -import jwt from "jsonwebtoken" +async function main() { + const mainAPI = new API() -import { User, Session, Config } from "./models" -import DbManager from "./classes/DbManager" - -const ExtractJwt = require("passport-jwt").ExtractJwt -const LocalStrategy = require("passport-local").Strategy - -class Server { - env = process.env - - DB = new DbManager() - - httpListenPort = this.env.listenPort ?? 3000 - - controllers = require("./controllers").default - middlewares = require("./middlewares") - - server = new LinebridgeServer({ - port: this.httpListenPort, - headers: { - "Access-Control-Expose-Headers": "regenerated_token", - }, - onWSClientConnection: this.onWSClientConnection, - onWSClientDisconnection: this.onWSClientDisconnection, - }, this.controllers, this.middlewares) - - options = { - jwtStrategy: { - sessionLocationSign: this.server.id, - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: this.server.oskid, - algorithms: ["sha1", "RS256", "HS256"], - expiresIn: this.env.signLifetime ?? "1h", - } - } - - constructor() { - this.server.engineInstance.use(express.json()) - this.server.engineInstance.use(express.urlencoded({ extended: true })) - - this.server.engineInstance.use("/storage", express.static(path.join(__dirname, "../uploads"))) - - this.server.wsInterface["clients"] = [] - this.server.wsInterface["findUserIdFromClientID"] = (searchClientId) => { - return this.server.wsInterface.clients.find(client => client.id === searchClientId)?.userId ?? false - } - this.server.wsInterface["getClientSockets"] = (userId) => { - return this.server.wsInterface.clients.filter(client => client.userId === userId).map((client) => { - return client?.socket - }) - } - this.server.wsInterface["broadcast"] = async (channel, ...args) => { - for await (const client of this.server.wsInterface.clients) { - client.socket.emit(channel, ...args) - } - } - - global.wsInterface = this.server.wsInterface - global.httpListenPort = this.listenPort - - global.publicHostname = this.env.publicHostname - global.publicProtocol = this.env.publicProtocol - global.globalPublicUri = `${this.env.publicProtocol}://${this.env.publicHost}` - - global.uploadPath = this.env.uploadPath ?? path.resolve(process.cwd(), "uploads") - global.uploadCachePath = this.env.uploadCachePath ?? path.resolve(process.cwd(), "cache") - - global.jwtStrategy = this.options.jwtStrategy - global.signLocation = this.env.signLocation - - this.initialize() - } - - async initialize() { - await this.DB.connect() - await this.initializeConfigDB() - - await this.checkSetup() - await this.initPassport() - await this.initWebsockets() - - await this.server.initialize() - } - - initializeConfigDB = async () => { - let serverConfig = await Config.findOne({ key: "server" }).catch(() => { - return false - }) - - if (!serverConfig) { - serverConfig = new Config({ - key: "server", - value: { - setup: false, - }, - }) - - - await serverConfig.save() - } - } - - checkSetup = async () => { - return new Promise(async (resolve, reject) => { - let setupOk = (await Config.findOne({ key: "server" })).value?.setup ?? false - - if (!setupOk) { - console.log("āš ļø Server setup is not complete, running setup proccess.") - let setupScript = await import("./setup") - - setupScript = setupScript.default ?? setupScript - - try { - for await (let script of setupScript) { - await script() - } - - console.log("āœ… Server setup complete.") - - await Config.updateOne({ key: "server" }, { value: { setup: true } }) - - return resolve() - } catch (error) { - console.log("āŒ Server setup failed.") - console.error(error) - process.exit(1) - } - } - - return resolve() - }) - } - - initPassport() { - this.server.middlewares["useJwtStrategy"] = (req, res, next) => { - req.jwtStrategy = this.options.jwtStrategy - next() - } - - passport.use(new LocalStrategy({ - usernameField: "username", - passwordField: "password", - session: false - }, (username, password, done) => { - User.findOne({ username }).select("+password") - .then((data) => { - if (data === null) { - return done(null, false, this.options.jwtStrategy) - } else if (!bcrypt.compareSync(password, data.password)) { - return done(null, false, this.options.jwtStrategy) - } - - // create a token - return done(null, data, this.options.jwtStrategy, { username, password }) - }) - .catch(err => done(err, null, this.options.jwtStrategy)) - })) - - this.server.engineInstance.use(passport.initialize()) - } - - initWebsockets() { - this.server.middlewares["useWS"] = (req, res, next) => { - req.ws = global.wsInterface - next() - } - - 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.server.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.server.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.server.wsInterface.clients.push(clientObj) - - this.server.wsInterface.io.emit("userConnected", userData) - } - - detachClientSocket = async (client) => { - const socket = this.server.wsInterface.clients.find(c => c.id === client.id) - - if (socket) { - socket.socket.disconnect() - this.server.wsInterface.clients = this.server.wsInterface.clients.filter(c => c.id !== client.id) - } - - this.server.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) - }) - } + console.log("\nā–¶ļø Initializing main API...\n") + await mainAPI.initialize() } -new Server() \ No newline at end of file +main().catch((error) => { + console.error(`šŸ†˜ [FATAL ERROR] >`, error) +}) \ No newline at end of file