prostgles-server 4.2.160 → 4.2.161

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 (105) hide show
  1. package/lib/Auth/AuthHandler.ts +436 -0
  2. package/lib/Auth/AuthTypes.ts +280 -0
  3. package/lib/Auth/getSafeReturnURL.ts +35 -0
  4. package/lib/Auth/sendEmail.ts +83 -0
  5. package/lib/Auth/setAuthProviders.ts +128 -0
  6. package/lib/Auth/setEmailProvider.ts +85 -0
  7. package/lib/Auth/setupAuthRoutes.ts +161 -0
  8. package/lib/DBEventsManager.ts +178 -0
  9. package/lib/DBSchemaBuilder.ts +225 -0
  10. package/lib/DboBuilder/DboBuilder.ts +319 -0
  11. package/lib/DboBuilder/DboBuilderTypes.ts +361 -0
  12. package/lib/DboBuilder/QueryBuilder/Functions.ts +1153 -0
  13. package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +288 -0
  14. package/lib/DboBuilder/QueryBuilder/getJoinQuery.ts +263 -0
  15. package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +271 -0
  16. package/lib/DboBuilder/QueryBuilder/getSelectQuery.ts +136 -0
  17. package/lib/DboBuilder/QueryBuilder/prepareHaving.ts +22 -0
  18. package/lib/DboBuilder/QueryStreamer.ts +250 -0
  19. package/lib/DboBuilder/TableHandler/DataValidator.ts +428 -0
  20. package/lib/DboBuilder/TableHandler/TableHandler.ts +205 -0
  21. package/lib/DboBuilder/TableHandler/delete.ts +115 -0
  22. package/lib/DboBuilder/TableHandler/insert.ts +183 -0
  23. package/lib/DboBuilder/TableHandler/insertTest.ts +78 -0
  24. package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +62 -0
  25. package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +134 -0
  26. package/lib/DboBuilder/TableHandler/update.ts +126 -0
  27. package/lib/DboBuilder/TableHandler/updateBatch.ts +49 -0
  28. package/lib/DboBuilder/TableHandler/updateFile.ts +48 -0
  29. package/lib/DboBuilder/TableHandler/upsert.ts +34 -0
  30. package/lib/DboBuilder/ViewHandler/ViewHandler.ts +393 -0
  31. package/lib/DboBuilder/ViewHandler/count.ts +38 -0
  32. package/lib/DboBuilder/ViewHandler/find.ts +153 -0
  33. package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +73 -0
  34. package/lib/DboBuilder/ViewHandler/getExistsFilters.ts +74 -0
  35. package/lib/DboBuilder/ViewHandler/getInfo.ts +32 -0
  36. package/lib/DboBuilder/ViewHandler/getTableJoinQuery.ts +84 -0
  37. package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +96 -0
  38. package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +105 -0
  39. package/lib/DboBuilder/ViewHandler/parseJoinPath.ts +208 -0
  40. package/lib/DboBuilder/ViewHandler/prepareSortItems.ts +163 -0
  41. package/lib/DboBuilder/ViewHandler/prepareWhere.ts +90 -0
  42. package/lib/DboBuilder/ViewHandler/size.ts +37 -0
  43. package/lib/DboBuilder/ViewHandler/subscribe.ts +118 -0
  44. package/lib/DboBuilder/ViewHandler/validateViewRules.ts +70 -0
  45. package/lib/DboBuilder/dboBuilderUtils.ts +222 -0
  46. package/lib/DboBuilder/getColumns.ts +114 -0
  47. package/lib/DboBuilder/getCondition.ts +201 -0
  48. package/lib/DboBuilder/getSubscribeRelatedTables.ts +190 -0
  49. package/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +426 -0
  50. package/lib/DboBuilder/insertNestedRecords.ts +355 -0
  51. package/lib/DboBuilder/parseUpdateRules.ts +187 -0
  52. package/lib/DboBuilder/prepareShortestJoinPaths.ts +186 -0
  53. package/lib/DboBuilder/runSQL.ts +182 -0
  54. package/lib/DboBuilder/runTransaction.ts +50 -0
  55. package/lib/DboBuilder/sqlErrCodeToMsg.ts +254 -0
  56. package/lib/DboBuilder/uploadFile.ts +69 -0
  57. package/lib/Event_Trigger_Tags.ts +118 -0
  58. package/lib/FileManager/FileManager.ts +358 -0
  59. package/lib/FileManager/getValidatedFileType.ts +69 -0
  60. package/lib/FileManager/initFileManager.ts +187 -0
  61. package/lib/FileManager/upload.ts +62 -0
  62. package/lib/FileManager/uploadStream.ts +79 -0
  63. package/lib/Filtering.ts +463 -0
  64. package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +502 -0
  65. package/lib/JSONBValidation/validation.ts +143 -0
  66. package/lib/Logging.ts +127 -0
  67. package/lib/PostgresNotifListenManager.ts +143 -0
  68. package/lib/Prostgles.ts +485 -0
  69. package/lib/ProstglesTypes.ts +196 -0
  70. package/lib/PubSubManager/PubSubManager.ts +609 -0
  71. package/lib/PubSubManager/addSub.ts +138 -0
  72. package/lib/PubSubManager/addSync.ts +141 -0
  73. package/lib/PubSubManager/getCreatePubSubManagerError.ts +72 -0
  74. package/lib/PubSubManager/getPubSubManagerInitQuery.ts +662 -0
  75. package/lib/PubSubManager/initPubSubManager.ts +79 -0
  76. package/lib/PubSubManager/notifListener.ts +173 -0
  77. package/lib/PubSubManager/orphanTriggerCheck.ts +70 -0
  78. package/lib/PubSubManager/pushSubData.ts +55 -0
  79. package/lib/PublishParser/PublishParser.ts +162 -0
  80. package/lib/PublishParser/getFileTableRules.ts +124 -0
  81. package/lib/PublishParser/getSchemaFromPublish.ts +141 -0
  82. package/lib/PublishParser/getTableRulesWithoutFileTable.ts +177 -0
  83. package/lib/PublishParser/publishTypesAndUtils.ts +399 -0
  84. package/lib/RestApi.ts +127 -0
  85. package/lib/SchemaWatch/SchemaWatch.ts +90 -0
  86. package/lib/SchemaWatch/createSchemaWatchEventTrigger.ts +3 -0
  87. package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
  88. package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
  89. package/lib/SyncReplication.ts +557 -0
  90. package/lib/TableConfig/TableConfig.ts +468 -0
  91. package/lib/TableConfig/getColumnDefinitionQuery.ts +111 -0
  92. package/lib/TableConfig/getConstraintDefinitionQueries.ts +95 -0
  93. package/lib/TableConfig/getFutureTableSchema.ts +64 -0
  94. package/lib/TableConfig/getPGIndexes.ts +53 -0
  95. package/lib/TableConfig/getTableColumnQueries.ts +129 -0
  96. package/lib/TableConfig/initTableConfig.ts +326 -0
  97. package/lib/index.ts +13 -0
  98. package/lib/initProstgles.ts +319 -0
  99. package/lib/onSocketConnected.ts +102 -0
  100. package/lib/runClientRequest.ts +129 -0
  101. package/lib/shortestPath.ts +122 -0
  102. package/lib/typeTests/DBoGenerated.d.ts +320 -0
  103. package/lib/typeTests/dboTypeCheck.ts +81 -0
  104. package/lib/utils.ts +15 -0
  105. package/package.json +1 -1
@@ -0,0 +1,436 @@
1
+ import { AnyObject, AuthGuardLocation, AuthGuardLocationResponse, CHANNELS, AuthSocketSchema } from "prostgles-types";
2
+ import { LocalParams, PRGLIOSocket } from "../DboBuilder/DboBuilder";
3
+ import { DBOFullyTyped } from "../DBSchemaBuilder";
4
+ import { removeExpressRoute } from "../FileManager/FileManager";
5
+ import { DB, DBHandlerServer, Prostgles } from "../Prostgles";
6
+ import { Auth, AuthClientRequest, AuthResult, BasicSession, ExpressReq, ExpressRes, LoginClientInfo, LoginParams } from "./AuthTypes"
7
+ import { getSafeReturnURL } from "./getSafeReturnURL";
8
+ import { setupAuthRoutes } from "./setupAuthRoutes";
9
+ import { getProviders } from "./setAuthProviders";
10
+
11
+ export const HTTPCODES = {
12
+ AUTH_ERROR: 401,
13
+ NOT_FOUND: 404,
14
+ BAD_REQUEST: 400,
15
+ INTERNAL_SERVER_ERROR: 500,
16
+ };
17
+
18
+ export const getLoginClientInfo = (req: AuthClientRequest): AuthClientRequest & LoginClientInfo => {
19
+ if("httpReq" in req){
20
+ const ip_address = req.httpReq.ip;
21
+ if(!ip_address) throw new Error("ip_address missing from req.httpReq");
22
+ const user_agent = req.httpReq.headers["user-agent"];
23
+ return {
24
+ ...req,
25
+ ip_address,
26
+ ip_address_remote: req.httpReq.connection.remoteAddress,
27
+ x_real_ip: req.httpReq.headers['x-real-ip'] as any,
28
+ user_agent,
29
+ };
30
+ } else {
31
+ return {
32
+ ...req,
33
+ ip_address: req.socket.handshake.address,
34
+ ip_address_remote: req.socket.request.connection.remoteAddress,
35
+ x_real_ip: req.socket.handshake.headers?.["x-real-ip"],
36
+ user_agent: req.socket.handshake.headers?.['user-agent'],
37
+ }
38
+ }
39
+ }
40
+
41
+ export const AUTH_ROUTES_AND_PARAMS = {
42
+ login: "/login",
43
+ loginWithProvider: "/auth",
44
+ emailSignup: "/register",
45
+ returnUrlParamName: "returnURL",
46
+ sidKeyName: "session_id",
47
+ logoutGetPath: "/logout",
48
+ magicLinksRoute: "/magic-link",
49
+ magicLinksExpressRoute: "/magic-link/:id",
50
+ confirmEmail: "/confirm-email",
51
+ confirmEmailExpressRoute: "/confirm-email/:id",
52
+ catchAll: "*",
53
+ } as const;
54
+
55
+ export class AuthHandler {
56
+ protected prostgles: Prostgles;
57
+ protected opts?: Auth;
58
+ dbo: DBHandlerServer;
59
+ db: DB;
60
+
61
+ constructor(prostgles: Prostgles) {
62
+ this.prostgles = prostgles;
63
+ this.opts = prostgles.opts.auth as any;
64
+ if(!prostgles.dbo || !prostgles.db) throw "dbo or db missing";
65
+ this.dbo = prostgles.dbo;
66
+ this.db = prostgles.db;
67
+ }
68
+
69
+ get sidKeyName() {
70
+ return this.opts?.sidKeyName ?? AUTH_ROUTES_AND_PARAMS.sidKeyName;
71
+ }
72
+
73
+ validateSid = (sid: string | undefined) => {
74
+ if (!sid) return undefined;
75
+ if (typeof sid !== "string") throw "sid missing or not a string";
76
+ return sid;
77
+ }
78
+
79
+ matchesRoute = (route: string | undefined, clientFullRoute: string) => {
80
+ return route && clientFullRoute && (
81
+ route === clientFullRoute ||
82
+ clientFullRoute.startsWith(route) && ["/", "?", "#"].includes(clientFullRoute[route.length] ?? "")
83
+ )
84
+ }
85
+
86
+ isUserRoute = (pathname: string) => {
87
+ const { login, logoutGetPath, magicLinksRoute, loginWithProvider } = AUTH_ROUTES_AND_PARAMS;
88
+ const pubRoutes = [
89
+ ...this.opts?.expressConfig?.publicRoutes || [],
90
+ login, logoutGetPath, magicLinksRoute, loginWithProvider,
91
+ ].filter(publicRoute => publicRoute);
92
+
93
+ return !pubRoutes.some(publicRoute => {
94
+ return this.matchesRoute(publicRoute, pathname);
95
+ });
96
+ }
97
+
98
+ setCookieAndGoToReturnURLIFSet = (cookie: { sid: string; expires: number; }, r: { req: ExpressReq; res: ExpressRes }) => {
99
+ const { sid, expires } = cookie;
100
+ const { res, req } = r;
101
+ if (sid) {
102
+ const maxAgeOneDay = 60 * 60 * 24; // 24 hours;
103
+ type CD = { maxAge: number } | { expires: Date }
104
+ let cookieDuration: CD = {
105
+ maxAge: maxAgeOneDay
106
+ }
107
+ if(expires && Number.isFinite(expires) && !isNaN(+ new Date(expires))){
108
+ // const maxAge = (+new Date(expires)) - Date.now();
109
+ cookieDuration = { expires: new Date(expires) };
110
+ const days = (+cookieDuration.expires - Date.now())/(24 * 60 * 60e3);
111
+ if(days >= 400){
112
+ console.warn(`Cookie expiration is higher than the Chrome 400 day limit: ${days}days`)
113
+ }
114
+ }
115
+
116
+ const cookieOpts = {
117
+ ...cookieDuration,
118
+ httpOnly: true, // The cookie only accessible by the web server
119
+ //signed: true // Indicates if the cookie should be signed
120
+ secure: true,
121
+ sameSite: "strict" as const,
122
+ ...(this.opts?.expressConfig?.cookieOptions || {})
123
+ };
124
+ const cookieData = sid;
125
+ res.cookie(this.sidKeyName, cookieData, cookieOpts);
126
+ const successURL = this.getReturnUrl(req) || "/";
127
+ res.redirect(successURL);
128
+
129
+ } else {
130
+ throw ("no user or session")
131
+ }
132
+ }
133
+
134
+ getUser = async (clientReq: { httpReq: ExpressReq; }): Promise<AuthResult> => {
135
+ if(!this.opts?.getUser) {
136
+ throw "this.opts.getUser missing";
137
+ }
138
+ const sid = clientReq.httpReq?.cookies?.[this.sidKeyName];
139
+ if (!sid) return undefined;
140
+
141
+ try {
142
+ return this.throttledFunc(async () => {
143
+ return this.opts!.getUser(this.validateSid(sid), this.dbo as any, this.db, getLoginClientInfo(clientReq));
144
+ }, 50)
145
+ } catch (err) {
146
+ console.error(err);
147
+ }
148
+ return undefined;
149
+ }
150
+
151
+ init = setupAuthRoutes.bind(this);
152
+
153
+ getReturnUrl = (req: ExpressReq) => {
154
+ const { returnUrlParamName } = AUTH_ROUTES_AND_PARAMS;
155
+ if (returnUrlParamName && req?.query?.[returnUrlParamName]) {
156
+ const returnURL = decodeURIComponent(req?.query?.[returnUrlParamName] as string);
157
+
158
+ return getSafeReturnURL(returnURL, returnUrlParamName);
159
+ }
160
+ return null;
161
+ }
162
+
163
+ destroy = () => {
164
+ const app = this.opts?.expressConfig?.app;
165
+ const { login, logoutGetPath, magicLinksExpressRoute, catchAll, loginWithProvider, emailSignup, magicLinksRoute, confirmEmail, confirmEmailExpressRoute } = AUTH_ROUTES_AND_PARAMS;
166
+ removeExpressRoute(app, [login, logoutGetPath, magicLinksExpressRoute, catchAll, loginWithProvider, emailSignup, magicLinksRoute, confirmEmail, confirmEmailExpressRoute]);
167
+ }
168
+
169
+ throttledFunc = <T>(func: () => Promise<T>, throttle = 500): Promise<T> => {
170
+
171
+ return new Promise(async (resolve, reject) => {
172
+
173
+ let result: any, error: any, finished = false;
174
+
175
+ /**
176
+ * Throttle reject response times to prevent timing attacks
177
+ */
178
+ const interval = setInterval(() => {
179
+ if (finished) {
180
+ clearInterval(interval);
181
+ if (error) {
182
+ reject(error);
183
+ } else {
184
+ resolve(result)
185
+ }
186
+ }
187
+ }, throttle);
188
+
189
+
190
+ try {
191
+ result = await func();
192
+ resolve(result);
193
+ clearInterval(interval);
194
+ } catch (err) {
195
+ console.log(err)
196
+ error = err;
197
+ }
198
+
199
+ finished = true;
200
+ })
201
+ }
202
+
203
+ loginThrottled = async (params: LoginParams, client: LoginClientInfo): Promise<BasicSession> => {
204
+ if (!this.opts?.login) throw "Auth login config missing";
205
+ const { responseThrottle = 500 } = this.opts;
206
+
207
+ return this.throttledFunc(async () => {
208
+ const result = await this.opts?.login?.(params, this.dbo as DBOFullyTyped, this.db, client);
209
+ const err = {
210
+ msg: "Bad login result type. \nExpecting: undefined | null | { sid: string; expires: number } but got: " + JSON.stringify(result)
211
+ }
212
+
213
+ if(!result) throw err;
214
+ if(result && (typeof result.sid !== "string" || typeof result.expires !== "number") || !result && ![undefined, null].includes(result)) {
215
+ throw err
216
+ }
217
+ if(result && result.expires < Date.now()){
218
+ throw { msg: "auth.login() is returning an expired session. Can only login with a session.expires greater than Date.now()" }
219
+ }
220
+
221
+ return result;
222
+ }, responseThrottle);
223
+
224
+ };
225
+
226
+ loginThrottledAndSetCookie = async (req: ExpressReq, res: ExpressRes, loginParams: LoginParams) => {
227
+ const start = Date.now();
228
+ const { sid, expires } = await this.loginThrottled(loginParams, getLoginClientInfo({ httpReq: req })) || {};
229
+ await this.prostgles.opts.onLog?.({
230
+ type: "auth",
231
+ command: "login",
232
+ duration: Date.now() - start,
233
+ sid,
234
+ socketId: undefined,
235
+ });
236
+
237
+ if (sid) {
238
+
239
+ this.setCookieAndGoToReturnURLIFSet({ sid, expires }, { req, res });
240
+
241
+ } else {
242
+ throw ("Internal error: no user or session")
243
+ }
244
+ }
245
+
246
+
247
+ /**
248
+ * Will return first sid value found in:
249
+ * Bearer header
250
+ * http cookie
251
+ * query params
252
+ * Based on sid names in auth
253
+ */
254
+ getSID(localParams: LocalParams): string | undefined {
255
+ if (!this.opts) return undefined;
256
+
257
+ if (!localParams) return undefined;
258
+ const { sidKeyName } = this;
259
+ if (localParams.socket) {
260
+ const { handshake } = localParams.socket;
261
+ const querySid = handshake?.auth?.[sidKeyName] || handshake?.query?.[sidKeyName];
262
+ let rawSid = querySid;
263
+ if (!rawSid) {
264
+ const cookie_str = localParams.socket?.handshake?.headers?.cookie;
265
+ const cookie = parseCookieStr(cookie_str);
266
+ rawSid = cookie[sidKeyName];
267
+ }
268
+ return this.validateSid(rawSid);
269
+
270
+ } else if (localParams.httpReq) {
271
+ const [tokenType, base64Token] = localParams.httpReq.headers.authorization?.split(' ') ?? [];
272
+ let bearerSid: string | undefined;
273
+ if(tokenType && base64Token){
274
+ if(tokenType.trim() !== "Bearer"){
275
+ throw "Only Bearer Authorization header allowed";
276
+ }
277
+ bearerSid = Buffer.from(base64Token, 'base64').toString();
278
+ }
279
+ return this.validateSid(bearerSid ?? localParams.httpReq?.cookies?.[sidKeyName]);
280
+
281
+ } else throw "socket OR httpReq missing from localParams";
282
+
283
+ function parseCookieStr(cookie_str: string | undefined): any {
284
+ if (!cookie_str || typeof cookie_str !== "string") {
285
+ return {}
286
+ }
287
+
288
+ return cookie_str.replace(/\s/g, '')
289
+ .split(";")
290
+ .reduce<AnyObject>((prev, current) => {
291
+ const [name, value] = current.split('=');
292
+ prev[name!] = value;
293
+ return prev;
294
+ }, {});
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Used for logging
300
+ */
301
+ getSIDNoError = (localParams: LocalParams | undefined): string | undefined => {
302
+ if(!localParams) return undefined;
303
+ try {
304
+ return this.getSID(localParams);
305
+ } catch {
306
+ return undefined;
307
+ }
308
+ }
309
+
310
+ async getClientInfo(localParams: Pick<LocalParams, "socket" | "httpReq">): Promise<AuthResult> {
311
+ if (!this.opts) return {};
312
+
313
+ const getSession = this.opts.cacheSession?.getSession;
314
+ const isSocket = "socket" in localParams;
315
+ if(isSocket){
316
+ if(getSession && localParams.socket?.__prglCache){
317
+ const { session, user, clientUser } = localParams.socket.__prglCache;
318
+ const isValid = this.isValidSocketSession(localParams.socket, session)
319
+ if(isValid){
320
+
321
+ return {
322
+ sid: session.sid,
323
+ user,
324
+ clientUser,
325
+ }
326
+ } else return {
327
+ sid: session.sid
328
+ };
329
+ }
330
+ }
331
+
332
+ const authStart = Date.now();
333
+ const res = await this.throttledFunc(async () => {
334
+
335
+ const { getUser } = this.opts ?? {};
336
+
337
+ if (getUser && localParams && (localParams.httpReq || localParams.socket)) {
338
+ const sid = this.getSID(localParams);
339
+ const clientReq = localParams.httpReq? { httpReq: localParams.httpReq } : { socket: localParams.socket! };
340
+ let user, clientUser;
341
+ if(sid){
342
+ const res = await getUser(sid, this.dbo as any, this.db, getLoginClientInfo(clientReq)) as any;
343
+ user = res?.user;
344
+ clientUser = res?.clientUser;
345
+ }
346
+ if(getSession && isSocket){
347
+ const session = await getSession(sid, this.dbo as any, this.db)
348
+ if(session?.expires && user && clientUser && localParams.socket){
349
+ localParams.socket.__prglCache = {
350
+ session,
351
+ user,
352
+ clientUser,
353
+ }
354
+ }
355
+ }
356
+ if(sid) {
357
+ return { sid, user, clientUser }
358
+ }
359
+ }
360
+
361
+ return {};
362
+ }, 5);
363
+
364
+ await this.prostgles.opts.onLog?.({
365
+ type: "auth",
366
+ command: "getClientInfo",
367
+ duration: Date.now() - authStart,
368
+ sid: res.sid,
369
+ socketId: localParams.socket?.id,
370
+ });
371
+ return res;
372
+ }
373
+
374
+ isValidSocketSession = (socket: PRGLIOSocket, session: BasicSession): boolean => {
375
+ const hasExpired = Boolean(session && session.expires <= Date.now())
376
+ if(this.opts?.expressConfig?.publicRoutes && !this.opts.expressConfig?.disableSocketAuthGuard){
377
+ const error = "Session has expired";
378
+ if(hasExpired){
379
+ if(session.onExpiration === "redirect")
380
+ socket.emit(CHANNELS.AUTHGUARD, {
381
+ shouldReload: session.onExpiration === "redirect",
382
+ error
383
+ });
384
+ throw error;
385
+ }
386
+ }
387
+ return Boolean(session && !hasExpired);
388
+ }
389
+
390
+ getClientAuth = async (clientReq: Pick<LocalParams, "socket" | "httpReq">): Promise<{ auth: AuthSocketSchema; userData: AuthResult; }> => {
391
+
392
+ let pathGuard = false;
393
+ if (this.opts?.expressConfig?.publicRoutes && !this.opts.expressConfig?.disableSocketAuthGuard) {
394
+
395
+ pathGuard = true;
396
+
397
+ if("socket" in clientReq && clientReq.socket){
398
+ const { socket } = clientReq;
399
+ socket.removeAllListeners(CHANNELS.AUTHGUARD)
400
+ socket.on(CHANNELS.AUTHGUARD, async (params: AuthGuardLocation, cb = (_err: any, _res?: AuthGuardLocationResponse) => { /** EMPTY */ }) => {
401
+
402
+ try {
403
+
404
+ const { pathname, origin } = typeof params === "string" ? JSON.parse(params) : (params || {});
405
+ if (pathname && typeof pathname !== "string") {
406
+ console.warn("Invalid pathname provided for AuthGuardLocation: ", pathname);
407
+ }
408
+
409
+ /** These origins */
410
+ const IGNORED_API_ORIGINS = ["file://"]
411
+ if (!IGNORED_API_ORIGINS.includes(origin) && pathname && typeof pathname === "string" && this.isUserRoute(pathname) && !(await this.getClientInfo({ socket }))?.user) {
412
+ cb(null, { shouldReload: true });
413
+ } else {
414
+ cb(null, { shouldReload: false });
415
+ }
416
+
417
+ } catch (err) {
418
+ console.error("AUTHGUARD err: ", err);
419
+ cb(err)
420
+ }
421
+ });
422
+
423
+ }
424
+ }
425
+
426
+ const userData = await this.getClientInfo(clientReq);
427
+ const auth: AuthSocketSchema = {
428
+ providers: getProviders.bind(this)(),
429
+ register: this.opts?.expressConfig?.registrations?.email && { type: this.opts?.expressConfig?.registrations?.email.signupType, url: AUTH_ROUTES_AND_PARAMS.emailSignup },
430
+ user: userData?.clientUser,
431
+ loginType: this.opts?.expressConfig?.registrations?.email?.signupType,
432
+ pathGuard,
433
+ };
434
+ return { auth, userData };
435
+ }
436
+ }
@@ -0,0 +1,280 @@
1
+ import { Express, NextFunction, Request, Response } from "express";
2
+ import { AnyObject, FieldFilter, IdentityProvider, UserLike } from "prostgles-types";
3
+ import { DB } from "../Prostgles";
4
+ import { DBOFullyTyped } from "../DBSchemaBuilder";
5
+ import { PRGLIOSocket } from "../DboBuilder/DboBuilderTypes";
6
+ import type { AuthenticateOptions } from "passport";
7
+ import type { StrategyOptions as GoogleStrategy, Profile as GoogleProfile } from "passport-google-oauth20";
8
+ import type { StrategyOptions as GitHubStrategy, Profile as GitHubProfile } from "passport-github2";
9
+ import type { MicrosoftStrategyOptions } from "passport-microsoft";
10
+ import type { StrategyOptions as FacebookStrategy, Profile as FacebookProfile } from "passport-facebook";
11
+ import Mail from "nodemailer/lib/mailer";
12
+
13
+ type Awaitable<T> = T | Promise<T>;
14
+
15
+ export type ExpressReq = Request;
16
+ export type ExpressRes = Response;
17
+
18
+ export type LoginClientInfo = {
19
+ ip_address: string;
20
+ ip_address_remote: string | undefined;
21
+ x_real_ip: string | undefined;
22
+ user_agent: string | undefined;
23
+ };
24
+
25
+ export type BasicSession = {
26
+
27
+ /** Must be hard to bruteforce */
28
+ sid: string;
29
+
30
+ /** UNIX millisecond timestamp */
31
+ expires: number;
32
+
33
+ /** On expired */
34
+ onExpiration: "redirect" | "show_error";
35
+ };
36
+ export type AuthClientRequest = { socket: PRGLIOSocket } | { httpReq: ExpressReq };
37
+
38
+ type ThirdPartyProviders = {
39
+ facebook?: Pick<FacebookStrategy, "clientID" | "clientSecret"> & {
40
+ authOpts?: AuthenticateOptions;
41
+ };
42
+ google?: Pick<GoogleStrategy, "clientID" | "clientSecret"> & {
43
+ authOpts?: AuthenticateOptions;
44
+ };
45
+ github?: Pick<GitHubStrategy, "clientID" | "clientSecret"> & {
46
+ authOpts?: AuthenticateOptions;
47
+ };
48
+ microsoft?: Pick<MicrosoftStrategyOptions, "clientID" | "clientSecret"> & {
49
+ authOpts?: AuthenticateOptions;
50
+ };
51
+ };
52
+
53
+ export type SMTPConfig = {
54
+ type: "smtp";
55
+ host: string;
56
+ port: number;
57
+ secure: boolean;
58
+ user: string;
59
+ pass: string;
60
+ } | {
61
+ type: "aws-ses";
62
+ region: string;
63
+ accessKeyId: string;
64
+ secretAccessKey: string;
65
+ /**
66
+ * Sending rate per second
67
+ * Defaults to 1
68
+ */
69
+ sendingRate?: number;
70
+ }
71
+
72
+ export type Email = {
73
+ from: string;
74
+ to: string;
75
+ subject: string;
76
+ html: string;
77
+ text?: string;
78
+ attachments?: { filename: string; content: string; }[] | Mail.Attachment[];
79
+ }
80
+
81
+ type EmailWithoutTo = Omit<Email, "to">;
82
+
83
+ type EmailProvider =
84
+ | {
85
+ signupType: "withMagicLink";
86
+ onRegistered: (data: { username: string; }) => void | Promise<void>;
87
+ emailMagicLink: {
88
+ onSend: (data: { email: string; magicLinkPath: string; }) => EmailWithoutTo | Promise<EmailWithoutTo>;
89
+ smtp: SMTPConfig;
90
+ };
91
+ }
92
+ | {
93
+ signupType: "withPassword";
94
+ onRegistered: (data: { username: string; password: string; }) => void | Promise<void>;
95
+ /**
96
+ * Defaults to 8
97
+ */
98
+ minPasswordLength?: number;
99
+ /**
100
+ * If provided, the user will be required to confirm their email address
101
+ */
102
+ emailConfirmation?: {
103
+ onSend: (data: { email: string; confirmationUrlPath: string; }) => EmailWithoutTo | Promise<EmailWithoutTo>;
104
+ smtp: SMTPConfig;
105
+ onConfirmed: (data: { confirmationCode: string; }) => void | Promise<void>;
106
+ };
107
+ };
108
+
109
+ export type AuthProviderUserData =
110
+ | {
111
+ provider: "google";
112
+ profile: GoogleProfile;
113
+ accessToken: string;
114
+ refreshToken: string;
115
+ }
116
+ | {
117
+ provider: "github";
118
+ profile: GitHubProfile;
119
+ accessToken: string;
120
+ refreshToken: string;
121
+ }
122
+ | {
123
+ provider: "facebook";
124
+ profile: FacebookProfile;
125
+ accessToken: string;
126
+ refreshToken: string;
127
+ }
128
+ | {
129
+ provider: "microsoft";
130
+ profile: any;
131
+ accessToken: string;
132
+ refreshToken: string;
133
+ }
134
+
135
+ export type RegistrationData =
136
+ | {
137
+ provider: "email";
138
+ profile: {
139
+ username: string;
140
+ password: string;
141
+ }
142
+ }
143
+ | AuthProviderUserData;
144
+
145
+ export type AuthRegistrationConfig<S> = {
146
+ email?: EmailProvider;
147
+
148
+ OAuthProviders?: ThirdPartyProviders;
149
+
150
+ /**
151
+ * Required for social login callback
152
+ */
153
+ websiteUrl: string;
154
+
155
+ /**
156
+ * Used to stop abuse
157
+ */
158
+ onProviderLoginStart?: (data: { provider: IdentityProvider; dbo: DBOFullyTyped<S>, db: DB; req: ExpressReq, res: ExpressRes; clientInfo: LoginClientInfo }) => Promise<{ error: string; } | { ok: true; }>;
159
+
160
+ /**
161
+ * Used to identify abuse
162
+ */
163
+ onProviderLoginFail?: (data: { provider: IdentityProvider; error: any; dbo: DBOFullyTyped<S>, db: DB; req: ExpressReq, res: ExpressRes; clientInfo: LoginClientInfo }) => void | Promise<void>;
164
+ };
165
+
166
+ export type SessionUser<ServerUser extends UserLike = UserLike, ClientUser extends UserLike = UserLike> = {
167
+ /**
168
+ * This user will be available in all serverside prostgles options
169
+ * id and type values will be available in the prostgles.user session variable in postgres
170
+ * */
171
+ user: ServerUser;
172
+ /**
173
+ * Controls which fields from user are available in postgres session variable
174
+ */
175
+ sessionFields?: FieldFilter<ServerUser>;
176
+ /**
177
+ * User data sent to the authenticated client
178
+ */
179
+ clientUser: ClientUser;
180
+ }
181
+
182
+ export type AuthResult<SU = SessionUser> = SU & { sid: string; } | {
183
+ user?: undefined;
184
+ clientUser?: undefined;
185
+ sid?: string;
186
+ } | undefined;
187
+
188
+ export type AuthRequestParams<S, SUser extends SessionUser> = { db: DB, dbo: DBOFullyTyped<S>; getUser: () => Promise<AuthResult<SUser>> }
189
+
190
+ export type Auth<S = void, SUser extends SessionUser = SessionUser> = {
191
+ /**
192
+ * Name of the cookie or socket hadnshake query param that represents the session id.
193
+ * Defaults to "session_id"
194
+ */
195
+ sidKeyName?: string;
196
+
197
+ /**
198
+ * Response time rounding in milliseconds to prevent timing attacks on login. Login response time should always be a multiple of this value. Defaults to 500 milliseconds
199
+ */
200
+ responseThrottle?: number;
201
+
202
+ /**
203
+ * Will setup auth routes
204
+ * /login
205
+ * /logout
206
+ * /magic-link/:id
207
+ */
208
+ expressConfig?: {
209
+
210
+ /**
211
+ * Express app instance. If provided Prostgles will attempt to set sidKeyName to user cookie
212
+ */
213
+ app: Express;
214
+
215
+ /**
216
+ * Options used in setting the cookie after a successful login
217
+ */
218
+ cookieOptions?: AnyObject;
219
+
220
+ /**
221
+ * False by default. If false and userRoutes are provided then the socket will request window.location.reload if the current url is on a user route.
222
+ */
223
+ disableSocketAuthGuard?: boolean;
224
+
225
+ /**
226
+ * If provided, any client requests to NOT these routes (or their subroutes) will be redirected to loginRoute (if logged in) and then redirected back to the initial route after logging in
227
+ * If logged in the user is allowed to access these routes
228
+ */
229
+ publicRoutes?: string[];
230
+
231
+ /**
232
+ * Will attach a app.use listener and will expose getUser
233
+ * Used for blocking access
234
+ */
235
+ use?: (args: { req: ExpressReq; res: ExpressRes, next: NextFunction } & AuthRequestParams<S, SUser>) => void | Promise<void>;
236
+
237
+ /**
238
+ * Will be called after a GET request is authorised
239
+ * This means that
240
+ */
241
+ onGetRequestOK?: (
242
+ req: ExpressReq,
243
+ res: ExpressRes,
244
+ params: AuthRequestParams<S, SUser>
245
+ ) => any;
246
+
247
+ /**
248
+ * If defined, will check the magic link id and log in the user and redirect to the returnUrl if set
249
+ */
250
+ magicLinks?: {
251
+
252
+ /**
253
+ * Used in creating a session/logging in using a magic link
254
+ */
255
+ check: (magicId: string, dbo: DBOFullyTyped<S>, db: DB, client: LoginClientInfo) => Awaitable<BasicSession | undefined>;
256
+ }
257
+
258
+ registrations?: AuthRegistrationConfig<S>;
259
+ }
260
+
261
+ /**
262
+ * undefined sid is allowed to enable public users
263
+ */
264
+ getUser: (sid: string | undefined, dbo: DBOFullyTyped<S>, db: DB, client: AuthClientRequest & LoginClientInfo) => Awaitable<AuthResult<SUser>>;
265
+
266
+ login?: (params: LoginParams, dbo: DBOFullyTyped<S>, db: DB, client: LoginClientInfo) => Awaitable<BasicSession> | BasicSession;
267
+ logout?: (sid: string | undefined, dbo: DBOFullyTyped<S>, db: DB) => Awaitable<any>;
268
+
269
+ /**
270
+ * If provided then session info will be saved on socket.__prglCache and reused from there
271
+ */
272
+ cacheSession?: {
273
+ getSession: (sid: string | undefined, dbo: DBOFullyTyped<S>, db: DB) => Awaitable<BasicSession>
274
+ }
275
+ }
276
+
277
+
278
+ export type LoginParams =
279
+ | { type: "username"; username: string; password: string; [key: string]: any }
280
+ | ({ type: "provider"; } & AuthProviderUserData)