From f8fa8a7a5119ae56168a7b6a2a3ccc9f708d2804 Mon Sep 17 00:00:00 2001 From: prostgles Date: Mon, 25 Nov 2024 15:21:07 +0200 Subject: [PATCH] remove auth from onReady updates --- lib/Auth/AuthHandler.ts | 4 +- lib/Auth/setAuthProviders.ts | 13 ++++- lib/Auth/setupAuthRoutes.ts | 8 ++- lib/FileManager/FileManager.ts | 11 ++++ lib/Prostgles.ts | 100 +------------------------------- lib/RestApi.ts | 7 +++ lib/initProstgles.ts | 20 ++++++- lib/onSocketConnected.ts | 102 +++++++++++++++++++++++++++++++++ package-lock.json | 4 +- package.json | 2 +- tests/server/package-lock.json | 2 +- 11 files changed, 163 insertions(+), 110 deletions(-) create mode 100644 lib/onSocketConnected.ts diff --git a/lib/Auth/AuthHandler.ts b/lib/Auth/AuthHandler.ts index 85d8b184..b85e35c9 100644 --- a/lib/Auth/AuthHandler.ts +++ b/lib/Auth/AuthHandler.ts @@ -160,8 +160,8 @@ export class AuthHandler { destroy = () => { const app = this.opts?.expressConfig?.app; - const { login, logoutGetPath, magicLinksExpressRoute, catchAll, loginWithProvider } = AUTH_ROUTES_AND_PARAMS; - removeExpressRoute(app, [login, logoutGetPath, magicLinksExpressRoute, catchAll, loginWithProvider]); + const { login, logoutGetPath, magicLinksExpressRoute, catchAll, loginWithProvider, emailSignup, magicLinksRoute } = AUTH_ROUTES_AND_PARAMS; + removeExpressRoute(app, [login, logoutGetPath, magicLinksExpressRoute, catchAll, loginWithProvider, emailSignup, magicLinksRoute]); } throttledFunc = (func: () => Promise, throttle = 500): Promise => { diff --git a/lib/Auth/setAuthProviders.ts b/lib/Auth/setAuthProviders.ts index b994ddfc..1a65641d 100644 --- a/lib/Auth/setAuthProviders.ts +++ b/lib/Auth/setAuthProviders.ts @@ -7,6 +7,17 @@ import { Strategy as MicrosoftStrategy } from "passport-microsoft"; import { Strategy as FacebookStrategy } from "passport-facebook"; import { AuthSocketSchema, getKeys, isDefined, isEmpty } from "prostgles-types"; import { AUTH_ROUTES_AND_PARAMS, AuthHandler } from "./AuthHandler"; +import type e from "express"; +import { RequestHandler } from "express"; +import { removeExpressRouteByName } from "../FileManager/FileManager"; + + +export const upsertNamedExpressMiddleware = (app: e.Express, handler: RequestHandler, name: string) => { + const funcName = name; + Object.defineProperty(handler, "name", { value: funcName }); + removeExpressRouteByName(app, name); + app.use(handler); +} export function setAuthProviders (this: AuthHandler, { registrations, app }: Required["expressConfig"]) { if(!registrations) return; @@ -23,7 +34,7 @@ export function setAuthProviders (this: AuthHandler, { registrations, app }: Req } if(!isEmpty(providers)){ - app.use(passport.initialize()); + upsertNamedExpressMiddleware(app, passport.initialize(), "prostglesPassportMiddleware"); } ([ diff --git a/lib/Auth/setupAuthRoutes.ts b/lib/Auth/setupAuthRoutes.ts index 2b51832c..469ebe70 100644 --- a/lib/Auth/setupAuthRoutes.ts +++ b/lib/Auth/setupAuthRoutes.ts @@ -1,7 +1,8 @@ +import { RequestHandler } from "express"; import { DBOFullyTyped } from "../DBSchemaBuilder"; import { AUTH_ROUTES_AND_PARAMS, AuthHandler, getLoginClientInfo, HTTPCODES } from "./AuthHandler"; import { AuthClientRequest, ExpressReq, ExpressRes } from "./AuthTypes"; -import { setAuthProviders } from "./setAuthProviders"; +import { setAuthProviders, upsertNamedExpressMiddleware } from "./setAuthProviders"; export async function setupAuthRoutes(this: AuthHandler) { if (!this.opts) return; @@ -29,7 +30,7 @@ export async function setupAuthRoutes(this: AuthHandler) { setAuthProviders.bind(this)(expressConfig); if(use){ - app.use((req, res, next) => { + const prostglesUseMiddleware: RequestHandler = (req, res, next) => { use({ req, res, @@ -38,7 +39,8 @@ export async function setupAuthRoutes(this: AuthHandler) { dbo: this.dbo as DBOFullyTyped, db: this.db, }) - }) + }; + upsertNamedExpressMiddleware(app, prostglesUseMiddleware, "prostglesUseMiddleware"); } if (magicLinks) { diff --git a/lib/FileManager/FileManager.ts b/lib/FileManager/FileManager.ts index 329a32d0..6fddc4e7 100644 --- a/lib/FileManager/FileManager.ts +++ b/lib/FileManager/FileManager.ts @@ -293,6 +293,17 @@ export const removeExpressRoute = (app: ExpressApp | undefined, routePaths: (str } } +export const removeExpressRouteByName = (app: ExpressApp | undefined, name: string) => { + const routes = app?._router?.stack; + if(routes){ + routes.forEach((route, i) => { + if(route.name === name){ + routes.splice(i, 1); + } + }) + } +} + export const getFileTypeFromFilename = (fileName: string): { mime: ALLOWED_CONTENT_TYPE; ext: ALLOWED_EXTENSION | string } | undefined => { const nameParts = fileName.split("."); diff --git a/lib/Prostgles.ts b/lib/Prostgles.ts index ddb62ccf..c030182f 100644 --- a/lib/Prostgles.ts +++ b/lib/Prostgles.ts @@ -8,7 +8,8 @@ import { AuthHandler } from "./Auth/AuthHandler"; import { FileManager } from "./FileManager/FileManager"; import { SchemaWatch } from "./SchemaWatch/SchemaWatch"; import { OnInitReason, initProstgles } from "./initProstgles"; -import { clientCanRunSqlRequest, runClientMethod, runClientRequest, runClientSqlRequest } from "./runClientRequest"; +import { makeSocketError, onSocketConnected } from "./onSocketConnected"; +import { clientCanRunSqlRequest, runClientSqlRequest } from "./runClientRequest"; import pg = require('pg-promise/typescript/pg-subset'); const { version } = require('../package.json'); @@ -21,7 +22,6 @@ export { DBHandlerServer }; export type PGP = pgPromise.IMain<{}, pg.IClient>; import { - AnyObject, CHANNELS, ClientSchema, DBSchemaTable, @@ -357,85 +357,7 @@ export class Prostgles { this.opts.io?.sockets.sockets.forEach(socket => this.onSocketConnected(socket)) } - onSocketConnected = async (socket: PRGLIOSocket) => { - if (this.destroyed) { - console.log("Socket connected to destroyed instance"); - socket.disconnect(); - return - } - this.connectedSockets.push(socket); - - try { - await this.opts.onLog?.({ - type: "connect", - sid: this.authHandler?.getSID({ socket }), - socketId: socket.id, - connectedSocketIds: this.connectedSockets.map(s => s.id) - }); - - if (!this.db || !this.dbo) throw new Error("db/dbo missing"); - const { dbo, db } = this; - - if (this.opts.onSocketConnect) { - try { - const getUser = async () => { return await this.authHandler?.getClientInfo({ socket }); } - await this.opts.onSocketConnect({ socket, dbo: dbo as any, db, getUser }); - } catch(error) { - const connectionError = error instanceof Error? error.message : typeof error === "string"? error : JSON.stringify(error); - socket.emit(CHANNELS.CONNECTION, { connectionError }); - socket.disconnect(); - return; - } - } - - socket.removeAllListeners(CHANNELS.DEFAULT) - socket.on(CHANNELS.DEFAULT, async (args: SocketRequestParams, cb = (..._callback: any[]) => { /* Empty */}) => { - runClientRequest.bind(this)({ ...args, type: "socket", socket }) - .then(res => { - cb(null, res) - }).catch(err => { - cb(err); - }); - }); - - socket.on("disconnect", () => { - - this.dbEventsManager?.removeNotice(socket); - this.dbEventsManager?.removeNotify(undefined, socket); - this.connectedSockets = this.connectedSockets.filter(s => s.id !== socket.id); - this.dboBuilder.queryStreamer.onDisconnect(socket.id); - this.opts.onLog?.({ - type: "disconnect", - sid: this.authHandler?.getSID({ socket }), - socketId: socket.id, - connectedSocketIds: this.connectedSockets.map(s => s.id) - }); - - if (this.opts.onSocketDisconnect) { - const getUser = async () => { return await this.authHandler?.getClientInfo({ socket }); } - this.opts.onSocketDisconnect({ socket, dbo: dbo as any, db, getUser }); - } - }); - - socket.removeAllListeners(CHANNELS.METHOD) - socket.on(CHANNELS.METHOD, async ({ method, params }: SocketMethodRequest, cb = (..._callback: any) => { /* Empty */ }) => { - runClientMethod.bind(this)({ - type: "socket", - socket, - method, - params - }).then(res => { - cb(null, res) - }).catch(err => { - makeSocketError(cb, err) - }); - }); - - this.pushSocketSchema(socket); - } catch (e) { - console.trace("setSocketEvents: ", e) - } - } + onSocketConnected = onSocketConnected.bind(this); getClientSchema = async (clientReq: Pick) => { @@ -551,22 +473,6 @@ export class Prostgles { } } -function makeSocketError(cb: (err: AnyObject) => void, err: any) { - cb(getErrorAsObject(err)); -} - -type SocketRequestParams = { - tableName: string; - command: typeof TABLE_METHODS[number]; - param1: any; - param2: any; - param3: any; -} -type SocketMethodRequest = { - method: string; - params: any; -} - export async function getIsSuperUser(db: DB): Promise { return db.oneOrNone("select usesuper from pg_user where usename = CURRENT_USER;").then(r => r.usesuper); diff --git a/lib/RestApi.ts b/lib/RestApi.ts index c95073eb..ff48ad21 100644 --- a/lib/RestApi.ts +++ b/lib/RestApi.ts @@ -11,11 +11,18 @@ const jsonParser = bodyParser.json(); export type ExpressApp = { _router?: { stack?: { + name: string; handle: VoidFunction; path: undefined, keys?: any[]; route?: { path?: string; + methods?: { + get?: boolean; + post?: boolean; + put?: boolean; + delete?: boolean; + }; } }[] } diff --git a/lib/initProstgles.ts b/lib/initProstgles.ts index a2de9805..f4e984e5 100644 --- a/lib/initProstgles.ts +++ b/lib/initProstgles.ts @@ -25,7 +25,7 @@ export type OnInitReason = } | { type: "prgl.update"; - newOpts: UpdateableOptions; + newOpts: Omit; } | { type: "init" | "prgl.restart" | "TableConfig" @@ -61,6 +61,8 @@ export type InitResult = { options: ProstglesInitOptions; } +const clientOnlyUpdateKeys = ["auth"] as const satisfies (keyof UpdateableOptions)[]; + export const initProstgles = async function(this: Prostgles, onReady: OnReadyCallbackBasic, reason: OnInitReason): Promise { this.loaded = false; @@ -123,7 +125,9 @@ export const initProstgles = async function(this: Prostgles, onReady: OnReadyCal if (this.opts.publish) { - if (!this.opts.io) console.warn("IO missing. Publish has no effect without io"); + if (!this.opts.io) { + console.warn("IO missing. Publish has no effect without io"); + } /* 3.9 Check auth config */ this.authHandler = new AuthHandler(this as any); @@ -174,6 +178,7 @@ export const initProstgles = async function(this: Prostgles, onReady: OnReadyCal this.opts[k] = newOpts[k]; }); + if("fileTable" in newOpts){ await this.initFileTable(); } @@ -193,7 +198,16 @@ export const initProstgles = async function(this: Prostgles, onReady: OnReadyCal this.authHandler = new AuthHandler(this as any); await this.authHandler.init(); } - if(!isEmpty(newOpts)){ + + if(isEmpty(newOpts)) return; + + /** + * Some of these changes require clients to reconnect + * While others also affect the server and onReady should be called + */ + if(getKeys(newOpts).every(updatedKey => clientOnlyUpdateKeys.includes(updatedKey as any))){ + await this.setSocketEvents(); + } else { await this.init(onReady, { type: "prgl.update", newOpts }); } }, diff --git a/lib/onSocketConnected.ts b/lib/onSocketConnected.ts new file mode 100644 index 00000000..24a3d07a --- /dev/null +++ b/lib/onSocketConnected.ts @@ -0,0 +1,102 @@ +import { AnyObject, CHANNELS } from "prostgles-types"; +import type { Prostgles, TABLE_METHODS } from "./Prostgles"; +import { PRGLIOSocket } from "./DboBuilder/DboBuilderTypes"; +import { runClientMethod, runClientRequest } from "./runClientRequest"; +import { getErrorAsObject } from "./DboBuilder/dboBuilderUtils"; + +export async function onSocketConnected(this: Prostgles, socket: PRGLIOSocket) { + if (this.destroyed) { + console.log("Socket connected to destroyed instance"); + socket.disconnect(); + return + } + this.connectedSockets.push(socket); + + try { + await this.opts.onLog?.({ + type: "connect", + sid: this.authHandler?.getSID({ socket }), + socketId: socket.id, + connectedSocketIds: this.connectedSockets.map(s => s.id) + }); + + if (!this.db || !this.dbo) throw new Error("db/dbo missing"); + const { dbo, db } = this; + + if (this.opts.onSocketConnect) { + try { + const getUser = async () => { return await this.authHandler?.getClientInfo({ socket }); } + await this.opts.onSocketConnect({ socket, dbo: dbo as any, db, getUser }); + } catch(error) { + const connectionError = error instanceof Error? error.message : typeof error === "string"? error : JSON.stringify(error); + socket.emit(CHANNELS.CONNECTION, { connectionError }); + socket.disconnect(); + return; + } + } + + socket.removeAllListeners(CHANNELS.DEFAULT) + socket.on(CHANNELS.DEFAULT, async (args: SocketRequestParams, cb = (..._callback: any[]) => { /* Empty */}) => { + runClientRequest.bind(this)({ ...args, type: "socket", socket }) + .then(res => { + cb(null, res) + }).catch(err => { + cb(err); + }); + }); + + socket.on("disconnect", () => { + + this.dbEventsManager?.removeNotice(socket); + this.dbEventsManager?.removeNotify(undefined, socket); + this.connectedSockets = this.connectedSockets.filter(s => s.id !== socket.id); + this.dboBuilder.queryStreamer.onDisconnect(socket.id); + this.opts.onLog?.({ + type: "disconnect", + sid: this.authHandler?.getSID({ socket }), + socketId: socket.id, + connectedSocketIds: this.connectedSockets.map(s => s.id) + }); + + if (this.opts.onSocketDisconnect) { + const getUser = async () => { return await this.authHandler?.getClientInfo({ socket }); } + this.opts.onSocketDisconnect({ socket, dbo: dbo as any, db, getUser }); + } + }); + + socket.removeAllListeners(CHANNELS.METHOD) + socket.on(CHANNELS.METHOD, async ({ method, params }: SocketMethodRequest, cb = (..._callback: any) => { /* Empty */ }) => { + runClientMethod.bind(this)({ + type: "socket", + socket, + method, + params + }).then(res => { + cb(null, res) + }).catch(err => { + makeSocketError(cb, err) + }); + }); + + this.pushSocketSchema(socket); + } catch (e) { + console.trace("setSocketEvents: ", e) + } +} + + +export function makeSocketError(cb: (err: AnyObject) => void, err: any) { + cb(getErrorAsObject(err)); +} + +type SocketRequestParams = { + tableName: string; + command: typeof TABLE_METHODS[number]; + param1: any; + param2: any; + param3: any; +} +type SocketMethodRequest = { + method: string; + params: any; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e70b532e..010f00e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prostgles-server", - "version": "4.2.142", + "version": "4.2.143", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prostgles-server", - "version": "4.2.142", + "version": "4.2.143", "license": "MIT", "dependencies": { "body-parser": "^1.20.3", diff --git a/package.json b/package.json index cb4d6609..be5cb46d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prostgles-server", - "version": "4.2.142", + "version": "4.2.143", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/tests/server/package-lock.json b/tests/server/package-lock.json index 8b45ff32..d86021cb 100644 --- a/tests/server/package-lock.json +++ b/tests/server/package-lock.json @@ -21,7 +21,7 @@ }, "../..": { "name": "prostgles-server", - "version": "4.2.142", + "version": "4.2.143", "license": "MIT", "dependencies": { "body-parser": "^1.20.3",