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,35 @@
1
+ export const getSafeReturnURL = (returnURL: string, returnUrlParamName: string, quiet = false) => {
2
+ /** Dissalow redirect to other domains */
3
+ if(returnURL) {
4
+ const allowedOrigin = "https://localhost";
5
+ const { origin, pathname, search, searchParams } = new URL(returnURL, allowedOrigin);
6
+ if(
7
+ origin !== allowedOrigin ||
8
+ returnURL !== `${pathname}${search}` ||
9
+ searchParams.get(returnUrlParamName)
10
+ ){
11
+ if(!quiet){
12
+ console.error(`Unsafe returnUrl: ${returnURL}. Redirecting to /`);
13
+ }
14
+ return "/";
15
+ }
16
+
17
+ return returnURL;
18
+ }
19
+ }
20
+
21
+ const issue = ([
22
+ ["https://localhost", "/"],
23
+ ["//localhost.bad.com", "/"],
24
+ ["//localhost.com", "/"],
25
+ ["/localhost/com", "/localhost/com"],
26
+ ["/localhost/com?here=there", "/localhost/com?here=there"],
27
+ ["/localhost/com?returnUrl=there", "/"],
28
+ ["//http://localhost.com", "/"],
29
+ ["//abc.com", "/"],
30
+ ["///abc.com", "/"],
31
+ ] as const).find(([returnURL, expected]) => getSafeReturnURL(returnURL, "returnUrl", true) !== expected);
32
+
33
+ if(issue){
34
+ throw new Error(`getSafeReturnURL failed for ${issue[0]}. Expected: ${issue[1]}`);
35
+ }
@@ -0,0 +1,83 @@
1
+ import { Email, SMTPConfig } from "./AuthTypes";
2
+ import nodemailer from "nodemailer";
3
+ import aws from "@aws-sdk/client-ses";
4
+ import SESTransport from "nodemailer/lib/ses-transport";
5
+
6
+ type SESTransporter = nodemailer.Transporter<SESTransport.SentMessageInfo, SESTransport.Options>;
7
+ type SMTPTransporter = nodemailer.Transporter<nodemailer.SentMessageInfo, nodemailer.TransportOptions>;
8
+ type Transporter = SESTransporter | SMTPTransporter;
9
+
10
+ const transporterCache: Map<string, Transporter> = new Map();
11
+
12
+ /**
13
+ * Allows sending emails using nodemailer default config or AWS SES
14
+ * https://www.nodemailer.com/transports/ses/
15
+ */
16
+ export const sendEmail = (smptConfig: SMTPConfig, email: Email) => {
17
+ const configStr = JSON.stringify(smptConfig);
18
+ const transporter = transporterCache.get(configStr) ?? getTransporter(smptConfig);
19
+ if(!transporterCache.has(configStr)){
20
+ transporterCache.set(configStr, transporter);
21
+ }
22
+
23
+ return send(transporter, email);
24
+ }
25
+
26
+ const getTransporter = (smptConfig: SMTPConfig) => {
27
+ let transporter: Transporter | undefined;
28
+ if(smptConfig.type === "aws-ses"){
29
+ const {
30
+ region,
31
+ accessKeyId,
32
+ secretAccessKey,
33
+ /**
34
+ * max 1 messages/second
35
+ */
36
+ sendingRate = 1
37
+ } = smptConfig;
38
+ const ses = new aws.SES({
39
+ apiVersion: "2010-12-01",
40
+ region,
41
+ credentials: {
42
+ accessKeyId,
43
+ secretAccessKey
44
+ }
45
+ });
46
+
47
+ transporter = nodemailer.createTransport({
48
+ SES: { ses, aws },
49
+ maxConnections: 1,
50
+ sendingRate
51
+ });
52
+
53
+ } else {
54
+ const { user, pass, host, port, secure } = smptConfig;
55
+ transporter = nodemailer.createTransport({
56
+ host,
57
+ port,
58
+ secure,
59
+ auth: { user, pass }
60
+ });
61
+ }
62
+
63
+ return transporter;
64
+ }
65
+
66
+ const send = (transporter: Transporter, email: Email) => {
67
+ return new Promise((resolve, reject) => {
68
+ transporter.once('idle', () => {
69
+ if (transporter.isIdle()) {
70
+ transporter.sendMail(
71
+ email,
72
+ (err, info) => {
73
+ if(err){
74
+ reject(err);
75
+ } else {
76
+ resolve(info);
77
+ }
78
+ }
79
+ );
80
+ }
81
+ });
82
+ });
83
+ };
@@ -0,0 +1,128 @@
1
+ import type e from "express";
2
+ import { RequestHandler } from "express";
3
+ import { Strategy as FacebookStrategy } from "passport-facebook";
4
+ import { Strategy as GitHubStrategy } from "passport-github2";
5
+ import { Strategy as GoogleStrategy } from "passport-google-oauth20";
6
+ import { Strategy as MicrosoftStrategy } from "passport-microsoft";
7
+ import { AuthSocketSchema, getObjectEntries, isEmpty } from "prostgles-types";
8
+ import { getErrorAsObject } from "../DboBuilder/dboBuilderUtils";
9
+ import { removeExpressRouteByName } from "../FileManager/FileManager";
10
+ import { AUTH_ROUTES_AND_PARAMS, AuthHandler, getLoginClientInfo } from "./AuthHandler";
11
+ import { Auth } from './AuthTypes';
12
+ import { setEmailProvider } from "./setEmailProvider";
13
+ /** For some reason normal import is undefined */
14
+ const passport = require("passport") as typeof import("passport");
15
+
16
+ export const upsertNamedExpressMiddleware = (app: e.Express, handler: RequestHandler, name: string) => {
17
+ const funcName = name;
18
+ Object.defineProperty(handler, "name", { value: funcName });
19
+ removeExpressRouteByName(app, name);
20
+ app.use(handler);
21
+ }
22
+
23
+ export async function setAuthProviders (this: AuthHandler, { registrations, app }: Required<Auth>["expressConfig"]) {
24
+ if(!registrations) return;
25
+ const { onProviderLoginFail, onProviderLoginStart, websiteUrl, OAuthProviders } = registrations;
26
+
27
+ await setEmailProvider.bind(this)(app);
28
+
29
+ if(!OAuthProviders || isEmpty(OAuthProviders)){
30
+ return;
31
+ }
32
+
33
+ upsertNamedExpressMiddleware(app, passport.initialize(), "prostglesPassportMiddleware");
34
+
35
+ getObjectEntries(OAuthProviders).forEach(([providerName, providerConfig]) => {
36
+
37
+ if(!providerConfig?.clientID){
38
+ return;
39
+ }
40
+
41
+ const { authOpts, ...config } = providerConfig;
42
+
43
+ const strategy = providerName === "google" ? GoogleStrategy :
44
+ providerName === "github" ? GitHubStrategy :
45
+ providerName === "facebook" ? FacebookStrategy :
46
+ providerName === "microsoft" ? MicrosoftStrategy :
47
+ undefined
48
+ ;
49
+
50
+ const callbackPath = `${AUTH_ROUTES_AND_PARAMS.loginWithProvider}/${providerName}/callback`;
51
+ passport.use(
52
+ new (strategy as typeof GoogleStrategy)(
53
+ {
54
+ ...config,
55
+ callbackURL: `${websiteUrl}${callbackPath}`,
56
+ },
57
+ async (accessToken, refreshToken, profile, done) => {
58
+ // This callback is where you would normally store or retrieve user info from the database
59
+ return done(null, profile, { accessToken, refreshToken, profile });
60
+ }
61
+ )
62
+ );
63
+
64
+ app.get(`${AUTH_ROUTES_AND_PARAMS.loginWithProvider}/${providerName}`,
65
+ passport.authenticate(providerName, authOpts ?? {})
66
+ );
67
+
68
+ app.get(
69
+ callbackPath,
70
+ async (req, res) => {
71
+ try {
72
+ const clientInfo = getLoginClientInfo({ httpReq: req });
73
+ const db = this.db;
74
+ const dbo = this.dbo as any;
75
+ const args = { provider: providerName, req, res, clientInfo, db, dbo };
76
+ const startCheck = await onProviderLoginStart?.(args);
77
+ if(startCheck && "error" in startCheck){
78
+ res.status(500).json({ error: startCheck.error });
79
+ return;
80
+ }
81
+ passport.authenticate(
82
+ providerName,
83
+ {
84
+ session: false,
85
+ failureRedirect: "/login",
86
+ failWithError: true,
87
+ },
88
+ async (error: any, _profile: any, authInfo: any) => {
89
+ if(error){
90
+ await onProviderLoginFail?.({ ...args, error });
91
+ res.status(500).json({
92
+ error: "Failed to login with provider",
93
+ });
94
+ } else {
95
+ this.loginThrottledAndSetCookie(req, res, { type: "provider", provider: providerName, ...authInfo })
96
+ .catch((e: any) => {
97
+ res.status(500).json(getErrorAsObject(e));
98
+ });
99
+ }
100
+ }
101
+ )(req, res);
102
+
103
+ } catch (_e) {
104
+ res.status(500).json({ error: "Something went wrong" });
105
+ }
106
+ }
107
+ );
108
+
109
+ });
110
+ }
111
+
112
+ export function getProviders(this: AuthHandler): AuthSocketSchema["providers"] | undefined {
113
+ const { registrations } = this.opts?.expressConfig ?? {}
114
+ if(!registrations) return undefined;
115
+ const { OAuthProviders } = registrations;
116
+ if(!OAuthProviders || isEmpty(OAuthProviders)) return undefined;
117
+
118
+ const result: AuthSocketSchema["providers"] = {}
119
+ getObjectEntries(OAuthProviders).forEach(([providerName, config]) => {
120
+ if(config?.clientID){
121
+ result[providerName] = {
122
+ url: `${AUTH_ROUTES_AND_PARAMS.loginWithProvider}/${providerName}`,
123
+ }
124
+ }
125
+ });
126
+
127
+ return result;
128
+ }
@@ -0,0 +1,85 @@
1
+ import e from "express";
2
+ import { AUTH_ROUTES_AND_PARAMS, AuthHandler } from "./AuthHandler";
3
+ import { Email, SMTPConfig } from "./AuthTypes";
4
+ import { sendEmail } from "./sendEmail";
5
+ import { promises } from "node:dns";
6
+
7
+ export async function setEmailProvider(this: AuthHandler, app: e.Express) {
8
+
9
+ const { email, websiteUrl } = this.opts?.expressConfig?.registrations ?? {};
10
+ if(!email) return;
11
+ if(websiteUrl){
12
+ await checkDmarc(websiteUrl);
13
+ }
14
+
15
+ app.post(AUTH_ROUTES_AND_PARAMS.emailSignup, async (req, res) => {
16
+ const { username, password } = req.body;
17
+ let validationError = "";
18
+ if(typeof username !== "string"){
19
+ validationError = "Invalid username";
20
+ }
21
+ if(email.signupType === "withPassword"){
22
+ const { minPasswordLength = 8 } = email;
23
+ if(typeof password !== "string"){
24
+ validationError = "Invalid password";
25
+ } else if(password.length < minPasswordLength){
26
+ validationError = `Password must be at least ${minPasswordLength} characters long`;
27
+ }
28
+ }
29
+ if(validationError){
30
+ res.status(400).json({ error: validationError });
31
+ return;
32
+ }
33
+ try {
34
+ let emailMessage: undefined | { message: Email; smtp: SMTPConfig };
35
+ if(email.signupType === "withPassword"){
36
+ if(email.emailConfirmation){
37
+ const { onSend, smtp } = email.emailConfirmation;
38
+ const message = await onSend({ email: username, confirmationUrlPath: `${websiteUrl}${AUTH_ROUTES_AND_PARAMS.confirmEmail}` });
39
+ emailMessage = { message: { ...message, to: username }, smtp };
40
+ }
41
+ } else {
42
+ const { emailMagicLink } = email;
43
+ const message = await emailMagicLink.onSend({ email: username, magicLinkPath: `${websiteUrl}${AUTH_ROUTES_AND_PARAMS.magicLinksRoute}` });
44
+ emailMessage = { message: { ...message, to: username }, smtp: emailMagicLink.smtp };
45
+ }
46
+
47
+ if(emailMessage){
48
+ await sendEmail(emailMessage.smtp, emailMessage.message);
49
+ res.json({ msg: "Email sent" });
50
+ }
51
+ } catch {
52
+ res.status(500).json({ error: "Failed to send email" });
53
+ }
54
+ });
55
+
56
+ if(email.signupType === "withPassword" && email.emailConfirmation){
57
+ app.get(AUTH_ROUTES_AND_PARAMS.confirmEmailExpressRoute, async (req, res) => {
58
+ const { id } = req.params ?? {};
59
+ try {
60
+ await email.emailConfirmation?.onConfirmed({ confirmationCode: id });
61
+ res.json({ msg: "Email confirmed" });
62
+ } catch (_e) {
63
+ res.status(500).json({ error: "Failed to confirm email" });
64
+ }
65
+ });
66
+ }
67
+ }
68
+
69
+ const checkDmarc = async (websiteUrl: string) => {
70
+ const { host, hostname } = new URL(websiteUrl);
71
+ const ignoredHosts = ["localhost", "127.0.0.1"]
72
+ if(!hostname || ignoredHosts.includes(hostname)){
73
+ return;
74
+ }
75
+ const dmarc = await promises.resolveTxt(`_dmarc.${host}`);
76
+ const dmarkTxt = dmarc[0]?.[0];
77
+ if(
78
+ !dmarkTxt?.includes("v=DMARC1") ||
79
+ (!dmarkTxt?.includes("p=reject") && !dmarkTxt?.includes("p=quarantine"))
80
+ ){
81
+ throw new Error("DMARC not set to reject/quarantine");
82
+ } else {
83
+ console.log("DMARC set to reject")
84
+ }
85
+ }
@@ -0,0 +1,161 @@
1
+ import { RequestHandler } from "express";
2
+ import { DBOFullyTyped } from "../DBSchemaBuilder";
3
+ import { AUTH_ROUTES_AND_PARAMS, AuthHandler, getLoginClientInfo, HTTPCODES } from "./AuthHandler";
4
+ import { AuthClientRequest, ExpressReq, ExpressRes, LoginParams } from "./AuthTypes";
5
+ import { setAuthProviders, upsertNamedExpressMiddleware } from "./setAuthProviders";
6
+
7
+ export async function setupAuthRoutes(this: AuthHandler) {
8
+ if (!this.opts) return;
9
+
10
+ const { login, getUser, expressConfig } = this.opts;
11
+
12
+ if (!login) {
13
+ throw "Invalid auth: Provide { sidKeyName: string } ";
14
+ }
15
+
16
+ if (this.sidKeyName === "sid") {
17
+ throw "sidKeyName cannot be 'sid' due to collision with socket.io";
18
+ }
19
+
20
+ if (!getUser) throw "getUser missing from auth config";
21
+
22
+ if (!expressConfig) {
23
+ return
24
+ }
25
+ const { app, publicRoutes = [], onGetRequestOK, magicLinks, use } = expressConfig;
26
+ if (publicRoutes.find(r => typeof r !== "string" || !r)) {
27
+ throw "Invalid or empty string provided within publicRoutes "
28
+ }
29
+
30
+ await setAuthProviders.bind(this)(expressConfig);
31
+
32
+ if(use){
33
+ const prostglesUseMiddleware: RequestHandler = (req, res, next) => {
34
+ use({
35
+ req,
36
+ res,
37
+ next,
38
+ getUser: () => this.getUser({ httpReq: req }) as any,
39
+ dbo: this.dbo as DBOFullyTyped,
40
+ db: this.db,
41
+ })
42
+ };
43
+ upsertNamedExpressMiddleware(app, prostglesUseMiddleware, "prostglesUseMiddleware");
44
+ }
45
+
46
+ if (magicLinks) {
47
+ const { check } = magicLinks;
48
+ if (!check) {
49
+ throw "Check must be defined for magicLinks";
50
+ }
51
+
52
+ app.get(AUTH_ROUTES_AND_PARAMS.magicLinksExpressRoute, async (req: ExpressReq, res: ExpressRes) => {
53
+ const { id } = req.params ?? {};
54
+
55
+ if (typeof id !== "string" || !id) {
56
+ res.status(HTTPCODES.BAD_REQUEST).json({ msg: "Invalid magic-link id. Expecting a string" });
57
+ } else {
58
+ try {
59
+ const session = await this.throttledFunc(async () => {
60
+ return check(id, this.dbo as any, this.db, getLoginClientInfo({ httpReq: req }));
61
+ });
62
+ if (!session) {
63
+ res.status(HTTPCODES.AUTH_ERROR).json({ msg: "Invalid magic-link" });
64
+ } else {
65
+ this.setCookieAndGoToReturnURLIFSet(session, { req, res });
66
+ }
67
+
68
+ } catch (e) {
69
+ res.status(HTTPCODES.AUTH_ERROR).json({ msg: e });
70
+ }
71
+ }
72
+ });
73
+ }
74
+
75
+ app.post(AUTH_ROUTES_AND_PARAMS.login, async (req: ExpressReq, res: ExpressRes) => {
76
+ try {
77
+ const loginParams: LoginParams = {
78
+ type: "username",
79
+ ...req.body,
80
+ };
81
+
82
+ await this.loginThrottledAndSetCookie(req, res, loginParams);
83
+ } catch (err) {
84
+ console.log(err)
85
+ res.status(HTTPCODES.AUTH_ERROR).json({ err });
86
+ }
87
+
88
+ });
89
+
90
+ const onLogout = async (req: ExpressReq, res: ExpressRes) => {
91
+ const sid = this.validateSid(req?.cookies?.[this.sidKeyName]);
92
+ if (sid) {
93
+ try {
94
+ await this.throttledFunc(() => {
95
+ return this.opts?.logout?.(req?.cookies?.[this.sidKeyName], this.dbo as any, this.db);
96
+ })
97
+ } catch (err) {
98
+ console.error(err);
99
+ }
100
+ }
101
+ res.redirect("/")
102
+ }
103
+
104
+ /* Redirect if not logged in and requesting non public content */
105
+ app.get(AUTH_ROUTES_AND_PARAMS.catchAll, async (req: ExpressReq, res: ExpressRes, next) => {
106
+
107
+ const clientReq: AuthClientRequest = { httpReq: req };
108
+ const getUser = this.getUser;
109
+ if(this.prostgles.restApi){
110
+ if(Object.values(this.prostgles.restApi.routes).some(restRoute => this.matchesRoute(restRoute.split("/:")[0], req.path))){
111
+ next();
112
+ return;
113
+ }
114
+ }
115
+ try {
116
+ const returnURL = this.getReturnUrl(req);
117
+
118
+ if(this.matchesRoute(AUTH_ROUTES_AND_PARAMS.logoutGetPath, req.path)){
119
+ await onLogout(req, res);
120
+ return;
121
+ }
122
+
123
+ if(this.matchesRoute(AUTH_ROUTES_AND_PARAMS.loginWithProvider, req.path)){
124
+ next();
125
+ return;
126
+ }
127
+ /**
128
+ * Requesting a User route
129
+ */
130
+ if (this.isUserRoute(req.path)) {
131
+
132
+ /* Check auth. Redirect to login if unauthorized */
133
+ const u = await getUser(clientReq);
134
+ if (!u) {
135
+ res.redirect(`${AUTH_ROUTES_AND_PARAMS.login}?returnURL=${encodeURIComponent(req.originalUrl)}`);
136
+ return;
137
+ }
138
+
139
+ /* If authorized and going to returnUrl then redirect. Otherwise serve file */
140
+ } else if (returnURL && (await getUser(clientReq))) {
141
+
142
+ res.redirect(returnURL);
143
+ return;
144
+
145
+ /** If Logged in and requesting login then redirect to main page */
146
+ } else if (this.matchesRoute(AUTH_ROUTES_AND_PARAMS.login, req.path) && (await getUser(clientReq))) {
147
+
148
+ res.redirect("/");
149
+ return;
150
+ }
151
+
152
+ onGetRequestOK?.(req, res, { getUser: () => getUser(clientReq), dbo: this.dbo as DBOFullyTyped, db: this.db })
153
+
154
+ } catch (error) {
155
+ console.error(error);
156
+ const errorMessage = typeof error === "string" ? error : error instanceof Error ? error.message : "";
157
+ res.status(HTTPCODES.AUTH_ERROR).json({ msg: "Something went wrong when processing your request" + (errorMessage? (": " + errorMessage) : "") });
158
+ }
159
+
160
+ });
161
+ }
@@ -0,0 +1,178 @@
1
+ import { PostgresNotifListenManager, PrglNotifListener } from "./PostgresNotifListenManager";
2
+ import { DB, PGP } from "./Prostgles";
3
+ import { getKeys, CHANNELS } from "prostgles-types";
4
+ import { PRGLIOSocket } from "./DboBuilder/DboBuilder";
5
+
6
+ export class DBEventsManager {
7
+
8
+ notifies: {
9
+ [key: string]: {
10
+ socketChannel: string;
11
+ sockets: any[];
12
+ localFuncs: ((payload: string) => void)[];
13
+ notifMgr: PostgresNotifListenManager;
14
+ }
15
+ } = {};
16
+
17
+ notice: {
18
+ socketChannel: string;
19
+ socketUnsubChannel: string;
20
+ sockets: any[];
21
+ } = {
22
+ socketChannel: CHANNELS.NOTICE_EV,
23
+ socketUnsubChannel: CHANNELS.NOTICE_EV + "unsubscribe",
24
+ sockets: []
25
+ };
26
+
27
+ notifManager?: PostgresNotifListenManager;
28
+
29
+ db_pg: DB;
30
+ pgp: PGP
31
+ constructor(db_pg: DB, pgp: PGP){
32
+ this.db_pg = db_pg;
33
+ this.pgp = pgp;
34
+ }
35
+
36
+ private onNotif: PrglNotifListener = ({ channel, payload }) => {
37
+
38
+ // console.log(36, { channel, payload }, Object.keys(this.notifies));
39
+
40
+ getKeys(this.notifies)
41
+ .filter(ch => ch === channel)
42
+ .map(ch => {
43
+ const sub = this.notifies[ch]!;
44
+
45
+ sub.sockets.map(s => {
46
+ s.emit(sub.socketChannel, payload)
47
+ });
48
+ sub.localFuncs.map(lf => {
49
+ lf(payload);
50
+ })
51
+ });
52
+ }
53
+
54
+ onNotice = (notice: any) => {
55
+ if(this.notice && this.notice.sockets.length){
56
+ this.notice.sockets.map(s => {
57
+ s.emit(this.notice.socketChannel, notice);
58
+ })
59
+ }
60
+ }
61
+
62
+ getNotifChannelName = async (channel: string) => {
63
+ const c = await this.db_pg.one("SELECT quote_ident($1) as c", channel);
64
+ return c.c;
65
+ }
66
+
67
+ async addNotify(query: string, socket?: PRGLIOSocket, func?: any): Promise<{
68
+ socketChannel: string;
69
+ socketUnsubChannel: string;
70
+ notifChannel: string;
71
+ unsubscribe?: () => void;
72
+ }> {
73
+ if(typeof query !== "string" || (!socket && !func)){
74
+ throw "Expecting (query: string, socket?, localFunc?) But received: " + JSON.stringify({ query, socket, func });
75
+ }
76
+
77
+ /* Remove comments */
78
+ let q = query.trim()
79
+ .replace(/\/\*[\s\S]*?\*\/|\/\/.*/g,'\n')
80
+ .split("\n").map(v => v.trim()).filter(v => v && !v.startsWith("--"))
81
+ .join("\n");
82
+
83
+ /* Find the notify channel name */
84
+ if(!q.toLowerCase().startsWith("listen")){
85
+ throw "Expecting a LISTEN query but got: " + query;
86
+ }
87
+ q = q.slice(7).trim(); // Remove listen
88
+ if(q.endsWith(";")) q = q.slice(0, -1);
89
+
90
+ if(q.startsWith('"') && q.endsWith('"')) {
91
+ q = q.slice(1, -1);
92
+ } else {
93
+ /* Replicate PG by lowercasing identifier if not quoted */
94
+ q = q.toLowerCase();
95
+ }
96
+ q = q.replace(/""/g, `"`);
97
+
98
+ const channel = q;
99
+ let notifChannel = await this.getNotifChannelName(channel)
100
+
101
+ notifChannel = notifChannel.replace(/""/g, `"`);
102
+ if(notifChannel.startsWith('"')) notifChannel = notifChannel.slice(1, -1);
103
+
104
+ const socketChannel = CHANNELS.LISTEN_EV + notifChannel,
105
+ socketUnsubChannel = socketChannel + "unsubscribe";
106
+
107
+ if(!this.notifies[notifChannel]){
108
+ this.notifies[notifChannel] = {
109
+ socketChannel,
110
+ sockets: socket? [socket] : [],
111
+ localFuncs: func? [func] : [],
112
+ notifMgr: await PostgresNotifListenManager.create(this.db_pg, this.onNotif, channel)
113
+ }
114
+
115
+ } else {
116
+ if(socket && !this.notifies[notifChannel]!.sockets.find(s => s.id === socket.id)) {
117
+ this.notifies[notifChannel]!.sockets.push(socket);
118
+
119
+ } else if(func) {
120
+ this.notifies[notifChannel]!.localFuncs.push(func);
121
+ }
122
+ }
123
+
124
+ if(socket){
125
+ socket.removeAllListeners(socketUnsubChannel);
126
+ socket.on(socketUnsubChannel, ()=>{
127
+ this.removeNotify(notifChannel, socket);
128
+ });
129
+ }
130
+
131
+ return {
132
+ socketChannel,
133
+ socketUnsubChannel,
134
+ notifChannel,
135
+ }
136
+ }
137
+
138
+ removeNotify(channel?: string, socket?: PRGLIOSocket, func?: any){
139
+ const notifChannel = channel && this.notifies[channel]
140
+ if(notifChannel){
141
+ if(socket){
142
+ notifChannel.sockets = notifChannel.sockets.filter(s => s.id !== socket.id);
143
+ } else if(func){
144
+ notifChannel.localFuncs = notifChannel.localFuncs.filter(f => f !== func);
145
+ }
146
+
147
+ /* UNLISTEN if no listeners ?? */
148
+ }
149
+
150
+ if(socket){
151
+ getKeys(this.notifies).forEach(channel => {
152
+ this.notifies[channel]!.sockets = this.notifies[channel]!.sockets.filter(s => s.id !== socket.id);
153
+ })
154
+ }
155
+ }
156
+
157
+ addNotice(socket: PRGLIOSocket){
158
+ if(!socket || !socket.id) throw "Expecting a socket obj with id";
159
+
160
+ if(!this.notice.sockets.find(s => s.id === socket.id)){
161
+ this.notice.sockets.push(socket);
162
+ }
163
+
164
+ const { socketChannel, socketUnsubChannel } = this.notice;
165
+
166
+ socket.removeAllListeners(socketUnsubChannel);
167
+ socket.on(socketUnsubChannel, () => {
168
+ this.removeNotice(socket);
169
+ });
170
+
171
+ return { socketChannel, socketUnsubChannel, }
172
+ }
173
+
174
+ removeNotice(socket: PRGLIOSocket){
175
+ if(!socket || !socket.id) throw "Expecting a socket obj with id";
176
+ this.notice.sockets = this.notice.sockets.filter(s => s.id !== socket.id)
177
+ }
178
+ }