prostgles-server 3.0.69 → 3.0.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/PubSubManager/PubSubManager.d.ts +4 -4
  2. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  3. package/dist/PubSubManager/PubSubManager.js +1 -1
  4. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  5. package/dist/PubSubManager/initPubSubManager.d.ts.map +1 -1
  6. package/dist/PubSubManager/initPubSubManager.js +52 -36
  7. package/dist/PubSubManager/initPubSubManager.js.map +1 -1
  8. package/lib/AuthHandler.js +213 -209
  9. package/lib/DBEventsManager.js +34 -31
  10. package/lib/DboBuilder/QueryBuilder/QueryBuilder.js +163 -155
  11. package/lib/DboBuilder/TableHandler.js +21 -20
  12. package/lib/DboBuilder/ViewHandler.js +23 -8
  13. package/lib/DboBuilder/runSQL.js +5 -5
  14. package/lib/DboBuilder.js +85 -65
  15. package/lib/FileManager.js +369 -364
  16. package/lib/PostgresNotifListenManager.js +26 -20
  17. package/lib/Prostgles.js +194 -177
  18. package/lib/PubSubManager/PubSubManager.d.ts +4 -4
  19. package/lib/PubSubManager/PubSubManager.d.ts.map +1 -1
  20. package/lib/PubSubManager/PubSubManager.js +250 -240
  21. package/lib/PubSubManager/PubSubManager.ts +2 -2
  22. package/lib/PubSubManager/initPubSubManager.d.ts.map +1 -1
  23. package/lib/PubSubManager/initPubSubManager.js +52 -36
  24. package/lib/PubSubManager/initPubSubManager.ts +53 -37
  25. package/lib/PublishParser.js +7 -2
  26. package/lib/SchemaWatch.js +2 -1
  27. package/lib/TableConfig.js +94 -91
  28. package/package.json +1 -1
  29. package/tests/client/PID.txt +1 -1
  30. package/tests/client/tsconfig.json +2 -2
  31. package/tests/server/package-lock.json +1 -1
  32. package/tests/server/tsconfig.json +2 -2
@@ -3,216 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const prostgles_types_1 = require("prostgles-types");
4
4
  const FileManager_1 = require("./FileManager");
5
5
  class AuthHandler {
6
+ opts;
7
+ dbo;
8
+ db;
9
+ sidKeyName;
10
+ routes = {
11
+ catchAll: "*"
12
+ };
6
13
  constructor(prostgles) {
7
- this.routes = {
8
- catchAll: "*"
9
- };
10
- this.validateSid = (sid) => {
11
- if (!sid)
12
- return undefined;
13
- if (typeof sid !== "string")
14
- throw "sid missing or not a string";
15
- return sid;
16
- };
17
- this.matchesRoute = (route, clientFullRoute) => {
18
- return route && clientFullRoute && (route === clientFullRoute ||
19
- clientFullRoute.startsWith(route) && ["/", "?", "#"].includes(clientFullRoute[route.length]));
20
- };
21
- this.isUserRoute = (pathname) => {
22
- const pubRoutes = [
23
- ...this.opts?.expressConfig?.publicRoutes || [],
24
- ];
25
- if (this.routes?.login)
26
- pubRoutes.push(this.routes.login);
27
- if (this.routes?.logoutGetPath)
28
- pubRoutes.push(this.routes.logoutGetPath);
29
- if (this.routes?.magicLinks?.route)
30
- pubRoutes.push(this.routes.magicLinks.route);
31
- return !pubRoutes.some(publicRoute => {
32
- return this.matchesRoute(publicRoute, pathname); // publicRoute === pathname || pathname.startsWith(publicRoute) && ["/", "?", "#"].includes(pathname.slice(-1));
33
- });
34
- };
35
- this.setCookieAndGoToReturnURLIFSet = (cookie, r) => {
36
- const { sid, expires } = cookie;
37
- const { res, req } = r;
38
- if (sid) {
39
- const maxAgeOneDay = 60 * 60 * 24; // 24 hours;
40
- let cookieDuration = {
41
- maxAge: maxAgeOneDay
42
- };
43
- if (expires && Number.isFinite(expires) && !isNaN(+new Date(expires))) {
44
- // const maxAge = (+new Date(expires)) - Date.now();
45
- cookieDuration = { expires: new Date(expires) };
46
- const days = (+cookieDuration.expires - Date.now()) / (24 * 60 * 60e3);
47
- if (days >= 400) {
48
- console.warn(`Cookie expiration higher the limit of 400 days for Chrome: ${days}days`);
49
- }
50
- }
51
- const cookieOpts = {
52
- ...cookieDuration,
53
- httpOnly: true,
54
- //signed: true // Indicates if the cookie should be signed
55
- secure: true,
56
- sameSite: "strict",
57
- ...(this.opts?.expressConfig?.cookieOptions || {})
58
- };
59
- const cookieData = sid;
60
- if (!this.sidKeyName || !this.routes?.returnURL)
61
- throw "sidKeyName or returnURL missing";
62
- res.cookie(this.sidKeyName, cookieData, cookieOpts);
63
- const successURL = getReturnUrl(req, this.routes.returnURL) || "/";
64
- res.redirect(successURL);
65
- }
66
- else {
67
- throw ("no user or session");
68
- }
69
- };
70
- this.getUser = async (clientReq) => {
71
- if (!this.sidKeyName || !this.opts?.getUser)
72
- throw "sidKeyName or this.opts.getUser missing";
73
- const sid = clientReq.httpReq?.cookies?.[this.sidKeyName];
74
- if (!sid)
75
- return undefined;
76
- try {
77
- return this.throttledFunc(async () => {
78
- return this.opts.getUser(this.validateSid(sid), this.dbo, this.db, clientReq);
79
- }, 50);
80
- }
81
- catch (err) {
82
- console.error(err);
83
- }
84
- return undefined;
85
- };
86
- this.destroy = () => {
87
- const app = this.opts?.expressConfig?.app;
88
- const { login, logoutGetPath, magicLinks } = this.routes;
89
- (0, FileManager_1.removeExpressRoute)(app, [login, logoutGetPath, magicLinks?.expressRoute]);
90
- };
91
- this.throttledFunc = (func, throttle = 500) => {
92
- return new Promise(async (resolve, reject) => {
93
- let interval, result, error, finished = false;
94
- /**
95
- * Throttle response times to prevent timing attacks
96
- */
97
- interval = setInterval(() => {
98
- if (finished) {
99
- clearInterval(interval);
100
- if (error) {
101
- reject(error);
102
- }
103
- else {
104
- resolve(result);
105
- }
106
- }
107
- }, throttle);
108
- try {
109
- result = await func();
110
- }
111
- catch (err) {
112
- console.log(err);
113
- error = err;
114
- }
115
- finished = true;
116
- });
117
- };
118
- this.loginThrottled = async (params, client) => {
119
- if (!this.opts?.login)
120
- throw "Auth login config missing";
121
- const { responseThrottle = 500 } = this.opts;
122
- return this.throttledFunc(async () => {
123
- let result = await this.opts?.login?.(params, this.dbo, this.db, client);
124
- const err = {
125
- msg: "Bad login result type. \nExpecting: undefined | null | { sid: string; expires: number } but got: " + JSON.stringify(result)
126
- };
127
- if (!result)
128
- throw err;
129
- if (result && (typeof result.sid !== "string" || typeof result.expires !== "number") || !result && ![undefined, null].includes(result)) {
130
- throw err;
131
- }
132
- if (result && result.expires < Date.now()) {
133
- throw { msg: "auth.login() is returning an expired session. Can only login with a session.expires greater than Date.now()" };
134
- }
135
- return result;
136
- }, responseThrottle);
137
- };
138
- this.isValidSocketSession = (socket, session) => {
139
- const hasExpired = Boolean(session && session.expires <= Date.now());
140
- if (this.opts?.expressConfig?.publicRoutes && !this.opts.expressConfig?.disableSocketAuthGuard) {
141
- const error = "Session has expired";
142
- if (hasExpired) {
143
- if (session.onExpiration === "redirect")
144
- socket.emit(prostgles_types_1.CHANNELS.AUTHGUARD, {
145
- shouldReload: session.onExpiration === "redirect",
146
- error
147
- });
148
- throw error;
149
- }
150
- }
151
- return Boolean(session && !hasExpired);
152
- };
153
- this.makeSocketAuth = async (socket) => {
154
- if (!this.opts)
155
- return {};
156
- let auth = {};
157
- if (this.opts.expressConfig?.publicRoutes && !this.opts.expressConfig?.disableSocketAuthGuard) {
158
- auth.pathGuard = true;
159
- socket.removeAllListeners(prostgles_types_1.CHANNELS.AUTHGUARD);
160
- socket.on(prostgles_types_1.CHANNELS.AUTHGUARD, async (params, cb = (err, res) => { }) => {
161
- try {
162
- const { pathname, origin } = typeof params === "string" ? JSON.parse(params) : (params || {});
163
- if (pathname && typeof pathname !== "string") {
164
- console.warn("Invalid pathname provided for AuthGuardLocation: ", pathname);
165
- }
166
- /** These origins */
167
- const IGNORED_API_ORIGINS = ["file://"];
168
- if (!IGNORED_API_ORIGINS.includes(origin) && pathname && typeof pathname === "string" && this.isUserRoute(pathname) && !(await this.getClientInfo({ socket }))?.user) {
169
- cb(null, { shouldReload: true });
170
- }
171
- else {
172
- cb(null, { shouldReload: false });
173
- }
174
- }
175
- catch (err) {
176
- console.error("AUTHGUARD err: ", err);
177
- cb(err);
178
- }
179
- });
180
- }
181
- const { register, logout } = this.opts;
182
- const login = this.loginThrottled;
183
- let handlers = [
184
- { func: (params, dbo, db, client) => register?.(params, dbo, db), ch: prostgles_types_1.CHANNELS.REGISTER, name: "register" },
185
- { func: (params, dbo, db, client) => login(params, client), ch: prostgles_types_1.CHANNELS.LOGIN, name: "login" },
186
- { func: (params, dbo, db, client) => logout?.(this.getSID({ socket }), dbo, db), ch: prostgles_types_1.CHANNELS.LOGOUT, name: "logout" }
187
- ].filter(h => h.func);
188
- const userData = await this.getClientInfo({ socket });
189
- if (userData) {
190
- auth.user = userData.clientUser;
191
- handlers = handlers.filter(h => h.name === "logout");
192
- }
193
- handlers.map(({ func, ch, name }) => {
194
- auth[name] = true;
195
- socket.removeAllListeners(ch);
196
- socket.on(ch, async (params, cb = (...callback) => { }) => {
197
- try {
198
- if (!socket)
199
- throw "socket missing??!!";
200
- const id_address = socket?.conn?.remoteAddress;
201
- const user_agent = socket.handshake?.headers?.["user-agent"];
202
- const res = await func(params, this.dbo, this.db, { user_agent, id_address });
203
- if (name === "login" && res && res.sid) {
204
- /* TODO: Re-send schema to client */
205
- }
206
- cb(null, true);
207
- }
208
- catch (err) {
209
- console.error(name + " err", err);
210
- cb(err);
211
- }
212
- });
213
- });
214
- return { auth, userData };
215
- };
216
14
  this.opts = prostgles.opts.auth;
217
15
  if (prostgles.opts.auth?.expressConfig) {
218
16
  const { magicLinks, returnURL, loginRoute, logoutGetPath } = prostgles.opts.auth.expressConfig;
@@ -233,6 +31,82 @@ class AuthHandler {
233
31
  this.dbo = prostgles.dbo;
234
32
  this.db = prostgles.db;
235
33
  }
34
+ validateSid = (sid) => {
35
+ if (!sid)
36
+ return undefined;
37
+ if (typeof sid !== "string")
38
+ throw "sid missing or not a string";
39
+ return sid;
40
+ };
41
+ matchesRoute = (route, clientFullRoute) => {
42
+ return route && clientFullRoute && (route === clientFullRoute ||
43
+ clientFullRoute.startsWith(route) && ["/", "?", "#"].includes(clientFullRoute[route.length]));
44
+ };
45
+ isUserRoute = (pathname) => {
46
+ const pubRoutes = [
47
+ ...this.opts?.expressConfig?.publicRoutes || [],
48
+ ];
49
+ if (this.routes?.login)
50
+ pubRoutes.push(this.routes.login);
51
+ if (this.routes?.logoutGetPath)
52
+ pubRoutes.push(this.routes.logoutGetPath);
53
+ if (this.routes?.magicLinks?.route)
54
+ pubRoutes.push(this.routes.magicLinks.route);
55
+ return !pubRoutes.some(publicRoute => {
56
+ return this.matchesRoute(publicRoute, pathname); // publicRoute === pathname || pathname.startsWith(publicRoute) && ["/", "?", "#"].includes(pathname.slice(-1));
57
+ });
58
+ };
59
+ setCookieAndGoToReturnURLIFSet = (cookie, r) => {
60
+ const { sid, expires } = cookie;
61
+ const { res, req } = r;
62
+ if (sid) {
63
+ const maxAgeOneDay = 60 * 60 * 24; // 24 hours;
64
+ let cookieDuration = {
65
+ maxAge: maxAgeOneDay
66
+ };
67
+ if (expires && Number.isFinite(expires) && !isNaN(+new Date(expires))) {
68
+ // const maxAge = (+new Date(expires)) - Date.now();
69
+ cookieDuration = { expires: new Date(expires) };
70
+ const days = (+cookieDuration.expires - Date.now()) / (24 * 60 * 60e3);
71
+ if (days >= 400) {
72
+ console.warn(`Cookie expiration higher the limit of 400 days for Chrome: ${days}days`);
73
+ }
74
+ }
75
+ const cookieOpts = {
76
+ ...cookieDuration,
77
+ httpOnly: true,
78
+ //signed: true // Indicates if the cookie should be signed
79
+ secure: true,
80
+ sameSite: "strict",
81
+ ...(this.opts?.expressConfig?.cookieOptions || {})
82
+ };
83
+ const cookieData = sid;
84
+ if (!this.sidKeyName || !this.routes?.returnURL)
85
+ throw "sidKeyName or returnURL missing";
86
+ res.cookie(this.sidKeyName, cookieData, cookieOpts);
87
+ const successURL = getReturnUrl(req, this.routes.returnURL) || "/";
88
+ res.redirect(successURL);
89
+ }
90
+ else {
91
+ throw ("no user or session");
92
+ }
93
+ };
94
+ getUser = async (clientReq) => {
95
+ if (!this.sidKeyName || !this.opts?.getUser)
96
+ throw "sidKeyName or this.opts.getUser missing";
97
+ const sid = clientReq.httpReq?.cookies?.[this.sidKeyName];
98
+ if (!sid)
99
+ return undefined;
100
+ try {
101
+ return this.throttledFunc(async () => {
102
+ return this.opts.getUser(this.validateSid(sid), this.dbo, this.db, clientReq);
103
+ }, 50);
104
+ }
105
+ catch (err) {
106
+ console.error(err);
107
+ }
108
+ return undefined;
109
+ };
236
110
  async init() {
237
111
  if (!this.opts)
238
112
  return;
@@ -369,6 +243,58 @@ class AuthHandler {
369
243
  }
370
244
  }
371
245
  }
246
+ destroy = () => {
247
+ const app = this.opts?.expressConfig?.app;
248
+ const { login, logoutGetPath, magicLinks } = this.routes;
249
+ (0, FileManager_1.removeExpressRoute)(app, [login, logoutGetPath, magicLinks?.expressRoute]);
250
+ };
251
+ throttledFunc = (func, throttle = 500) => {
252
+ return new Promise(async (resolve, reject) => {
253
+ let interval, result, error, finished = false;
254
+ /**
255
+ * Throttle response times to prevent timing attacks
256
+ */
257
+ interval = setInterval(() => {
258
+ if (finished) {
259
+ clearInterval(interval);
260
+ if (error) {
261
+ reject(error);
262
+ }
263
+ else {
264
+ resolve(result);
265
+ }
266
+ }
267
+ }, throttle);
268
+ try {
269
+ result = await func();
270
+ }
271
+ catch (err) {
272
+ console.log(err);
273
+ error = err;
274
+ }
275
+ finished = true;
276
+ });
277
+ };
278
+ loginThrottled = async (params, client) => {
279
+ if (!this.opts?.login)
280
+ throw "Auth login config missing";
281
+ const { responseThrottle = 500 } = this.opts;
282
+ return this.throttledFunc(async () => {
283
+ let result = await this.opts?.login?.(params, this.dbo, this.db, client);
284
+ const err = {
285
+ msg: "Bad login result type. \nExpecting: undefined | null | { sid: string; expires: number } but got: " + JSON.stringify(result)
286
+ };
287
+ if (!result)
288
+ throw err;
289
+ if (result && (typeof result.sid !== "string" || typeof result.expires !== "number") || !result && ![undefined, null].includes(result)) {
290
+ throw err;
291
+ }
292
+ if (result && result.expires < Date.now()) {
293
+ throw { msg: "auth.login() is returning an expired session. Can only login with a session.expires greater than Date.now()" };
294
+ }
295
+ return result;
296
+ }, responseThrottle);
297
+ };
372
298
  /**
373
299
  * Will return first sid value found in : http cookie or query params
374
300
  * Based on sid names in auth
@@ -458,6 +384,84 @@ class AuthHandler {
458
384
  }, 5);
459
385
  return res;
460
386
  }
387
+ isValidSocketSession = (socket, session) => {
388
+ const hasExpired = Boolean(session && session.expires <= Date.now());
389
+ if (this.opts?.expressConfig?.publicRoutes && !this.opts.expressConfig?.disableSocketAuthGuard) {
390
+ const error = "Session has expired";
391
+ if (hasExpired) {
392
+ if (session.onExpiration === "redirect")
393
+ socket.emit(prostgles_types_1.CHANNELS.AUTHGUARD, {
394
+ shouldReload: session.onExpiration === "redirect",
395
+ error
396
+ });
397
+ throw error;
398
+ }
399
+ }
400
+ return Boolean(session && !hasExpired);
401
+ };
402
+ makeSocketAuth = async (socket) => {
403
+ if (!this.opts)
404
+ return {};
405
+ let auth = {};
406
+ if (this.opts.expressConfig?.publicRoutes && !this.opts.expressConfig?.disableSocketAuthGuard) {
407
+ auth.pathGuard = true;
408
+ socket.removeAllListeners(prostgles_types_1.CHANNELS.AUTHGUARD);
409
+ socket.on(prostgles_types_1.CHANNELS.AUTHGUARD, async (params, cb = (err, res) => { }) => {
410
+ try {
411
+ const { pathname, origin } = typeof params === "string" ? JSON.parse(params) : (params || {});
412
+ if (pathname && typeof pathname !== "string") {
413
+ console.warn("Invalid pathname provided for AuthGuardLocation: ", pathname);
414
+ }
415
+ /** These origins */
416
+ const IGNORED_API_ORIGINS = ["file://"];
417
+ if (!IGNORED_API_ORIGINS.includes(origin) && pathname && typeof pathname === "string" && this.isUserRoute(pathname) && !(await this.getClientInfo({ socket }))?.user) {
418
+ cb(null, { shouldReload: true });
419
+ }
420
+ else {
421
+ cb(null, { shouldReload: false });
422
+ }
423
+ }
424
+ catch (err) {
425
+ console.error("AUTHGUARD err: ", err);
426
+ cb(err);
427
+ }
428
+ });
429
+ }
430
+ const { register, logout } = this.opts;
431
+ const login = this.loginThrottled;
432
+ let handlers = [
433
+ { func: (params, dbo, db, client) => register?.(params, dbo, db), ch: prostgles_types_1.CHANNELS.REGISTER, name: "register" },
434
+ { func: (params, dbo, db, client) => login(params, client), ch: prostgles_types_1.CHANNELS.LOGIN, name: "login" },
435
+ { func: (params, dbo, db, client) => logout?.(this.getSID({ socket }), dbo, db), ch: prostgles_types_1.CHANNELS.LOGOUT, name: "logout" }
436
+ ].filter(h => h.func);
437
+ const userData = await this.getClientInfo({ socket });
438
+ if (userData) {
439
+ auth.user = userData.clientUser;
440
+ handlers = handlers.filter(h => h.name === "logout");
441
+ }
442
+ handlers.map(({ func, ch, name }) => {
443
+ auth[name] = true;
444
+ socket.removeAllListeners(ch);
445
+ socket.on(ch, async (params, cb = (...callback) => { }) => {
446
+ try {
447
+ if (!socket)
448
+ throw "socket missing??!!";
449
+ const id_address = socket?.conn?.remoteAddress;
450
+ const user_agent = socket.handshake?.headers?.["user-agent"];
451
+ const res = await func(params, this.dbo, this.db, { user_agent, id_address });
452
+ if (name === "login" && res && res.sid) {
453
+ /* TODO: Re-send schema to client */
454
+ }
455
+ cb(null, true);
456
+ }
457
+ catch (err) {
458
+ console.error(name + " err", err);
459
+ cb(err);
460
+ }
461
+ });
462
+ });
463
+ return { auth, userData };
464
+ };
461
465
  }
462
466
  exports.default = AuthHandler;
463
467
  /**
@@ -4,41 +4,44 @@ exports.DBEventsManager = void 0;
4
4
  const PostgresNotifListenManager_1 = require("./PostgresNotifListenManager");
5
5
  const prostgles_types_1 = require("prostgles-types");
6
6
  class DBEventsManager {
7
+ notifies = {};
8
+ notice = {
9
+ socketChannel: prostgles_types_1.CHANNELS.NOTICE_EV,
10
+ socketUnsubChannel: prostgles_types_1.CHANNELS.NOTICE_EV + "unsubscribe",
11
+ sockets: []
12
+ };
13
+ notifManager;
14
+ db_pg;
15
+ pgp;
7
16
  constructor(db_pg, pgp) {
8
- this.notifies = {};
9
- this.notice = {
10
- socketChannel: prostgles_types_1.CHANNELS.NOTICE_EV,
11
- socketUnsubChannel: prostgles_types_1.CHANNELS.NOTICE_EV + "unsubscribe",
12
- sockets: []
13
- };
14
- this.onNotif = ({ channel, payload }) => {
15
- // console.log(36, { channel, payload }, Object.keys(this.notifies));
16
- Object.keys(this.notifies)
17
- .filter(ch => ch === channel)
18
- .map(ch => {
19
- const sub = this.notifies[ch];
20
- sub.sockets.map(s => {
21
- s.emit(sub.socketChannel, payload);
22
- });
23
- sub.localFuncs.map(lf => {
24
- lf(payload);
25
- });
26
- });
27
- };
28
- this.onNotice = (notice) => {
29
- if (this.notice && this.notice.sockets.length) {
30
- this.notice.sockets.map(s => {
31
- s.emit(this.notice.socketChannel, notice);
32
- });
33
- }
34
- };
35
- this.getNotifChannelName = async (channel) => {
36
- const c = await this.db_pg.one("SELECT quote_ident($1) as c", channel);
37
- return c.c;
38
- };
39
17
  this.db_pg = db_pg;
40
18
  this.pgp = pgp;
41
19
  }
20
+ onNotif = ({ channel, payload }) => {
21
+ // console.log(36, { channel, payload }, Object.keys(this.notifies));
22
+ Object.keys(this.notifies)
23
+ .filter(ch => ch === channel)
24
+ .map(ch => {
25
+ const sub = this.notifies[ch];
26
+ sub.sockets.map(s => {
27
+ s.emit(sub.socketChannel, payload);
28
+ });
29
+ sub.localFuncs.map(lf => {
30
+ lf(payload);
31
+ });
32
+ });
33
+ };
34
+ onNotice = (notice) => {
35
+ if (this.notice && this.notice.sockets.length) {
36
+ this.notice.sockets.map(s => {
37
+ s.emit(this.notice.socketChannel, notice);
38
+ });
39
+ }
40
+ };
41
+ getNotifChannelName = async (channel) => {
42
+ const c = await this.db_pg.one("SELECT quote_ident($1) as c", channel);
43
+ return c.c;
44
+ };
42
45
  async addNotify(query, socket, func) {
43
46
  if (typeof query !== "string" || (!socket && !func)) {
44
47
  throw "Expecting (query: string, socket?, localFunc?) But received: " + JSON.stringify({ query, socket, func });