prostgles-server 4.2.159 → 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 (107) hide show
  1. package/dist/Auth/setEmailProvider.js +2 -2
  2. package/dist/Auth/setEmailProvider.js.map +1 -1
  3. package/lib/Auth/AuthHandler.ts +436 -0
  4. package/lib/Auth/AuthTypes.ts +280 -0
  5. package/lib/Auth/getSafeReturnURL.ts +35 -0
  6. package/lib/Auth/sendEmail.ts +83 -0
  7. package/lib/Auth/setAuthProviders.ts +128 -0
  8. package/lib/Auth/setEmailProvider.ts +85 -0
  9. package/lib/Auth/setupAuthRoutes.ts +161 -0
  10. package/lib/DBEventsManager.ts +178 -0
  11. package/lib/DBSchemaBuilder.ts +225 -0
  12. package/lib/DboBuilder/DboBuilder.ts +319 -0
  13. package/lib/DboBuilder/DboBuilderTypes.ts +361 -0
  14. package/lib/DboBuilder/QueryBuilder/Functions.ts +1153 -0
  15. package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +288 -0
  16. package/lib/DboBuilder/QueryBuilder/getJoinQuery.ts +263 -0
  17. package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +271 -0
  18. package/lib/DboBuilder/QueryBuilder/getSelectQuery.ts +136 -0
  19. package/lib/DboBuilder/QueryBuilder/prepareHaving.ts +22 -0
  20. package/lib/DboBuilder/QueryStreamer.ts +250 -0
  21. package/lib/DboBuilder/TableHandler/DataValidator.ts +428 -0
  22. package/lib/DboBuilder/TableHandler/TableHandler.ts +205 -0
  23. package/lib/DboBuilder/TableHandler/delete.ts +115 -0
  24. package/lib/DboBuilder/TableHandler/insert.ts +183 -0
  25. package/lib/DboBuilder/TableHandler/insertTest.ts +78 -0
  26. package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +62 -0
  27. package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +134 -0
  28. package/lib/DboBuilder/TableHandler/update.ts +126 -0
  29. package/lib/DboBuilder/TableHandler/updateBatch.ts +49 -0
  30. package/lib/DboBuilder/TableHandler/updateFile.ts +48 -0
  31. package/lib/DboBuilder/TableHandler/upsert.ts +34 -0
  32. package/lib/DboBuilder/ViewHandler/ViewHandler.ts +393 -0
  33. package/lib/DboBuilder/ViewHandler/count.ts +38 -0
  34. package/lib/DboBuilder/ViewHandler/find.ts +153 -0
  35. package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +73 -0
  36. package/lib/DboBuilder/ViewHandler/getExistsFilters.ts +74 -0
  37. package/lib/DboBuilder/ViewHandler/getInfo.ts +32 -0
  38. package/lib/DboBuilder/ViewHandler/getTableJoinQuery.ts +84 -0
  39. package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +96 -0
  40. package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +105 -0
  41. package/lib/DboBuilder/ViewHandler/parseJoinPath.ts +208 -0
  42. package/lib/DboBuilder/ViewHandler/prepareSortItems.ts +163 -0
  43. package/lib/DboBuilder/ViewHandler/prepareWhere.ts +90 -0
  44. package/lib/DboBuilder/ViewHandler/size.ts +37 -0
  45. package/lib/DboBuilder/ViewHandler/subscribe.ts +118 -0
  46. package/lib/DboBuilder/ViewHandler/validateViewRules.ts +70 -0
  47. package/lib/DboBuilder/dboBuilderUtils.ts +222 -0
  48. package/lib/DboBuilder/getColumns.ts +114 -0
  49. package/lib/DboBuilder/getCondition.ts +201 -0
  50. package/lib/DboBuilder/getSubscribeRelatedTables.ts +190 -0
  51. package/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +426 -0
  52. package/lib/DboBuilder/insertNestedRecords.ts +355 -0
  53. package/lib/DboBuilder/parseUpdateRules.ts +187 -0
  54. package/lib/DboBuilder/prepareShortestJoinPaths.ts +186 -0
  55. package/lib/DboBuilder/runSQL.ts +182 -0
  56. package/lib/DboBuilder/runTransaction.ts +50 -0
  57. package/lib/DboBuilder/sqlErrCodeToMsg.ts +254 -0
  58. package/lib/DboBuilder/uploadFile.ts +69 -0
  59. package/lib/Event_Trigger_Tags.ts +118 -0
  60. package/lib/FileManager/FileManager.ts +358 -0
  61. package/lib/FileManager/getValidatedFileType.ts +69 -0
  62. package/lib/FileManager/initFileManager.ts +187 -0
  63. package/lib/FileManager/upload.ts +62 -0
  64. package/lib/FileManager/uploadStream.ts +79 -0
  65. package/lib/Filtering.ts +463 -0
  66. package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +502 -0
  67. package/lib/JSONBValidation/validation.ts +143 -0
  68. package/lib/Logging.ts +127 -0
  69. package/lib/PostgresNotifListenManager.ts +143 -0
  70. package/lib/Prostgles.ts +485 -0
  71. package/lib/ProstglesTypes.ts +196 -0
  72. package/lib/PubSubManager/PubSubManager.ts +609 -0
  73. package/lib/PubSubManager/addSub.ts +138 -0
  74. package/lib/PubSubManager/addSync.ts +141 -0
  75. package/lib/PubSubManager/getCreatePubSubManagerError.ts +72 -0
  76. package/lib/PubSubManager/getPubSubManagerInitQuery.ts +662 -0
  77. package/lib/PubSubManager/initPubSubManager.ts +79 -0
  78. package/lib/PubSubManager/notifListener.ts +173 -0
  79. package/lib/PubSubManager/orphanTriggerCheck.ts +70 -0
  80. package/lib/PubSubManager/pushSubData.ts +55 -0
  81. package/lib/PublishParser/PublishParser.ts +162 -0
  82. package/lib/PublishParser/getFileTableRules.ts +124 -0
  83. package/lib/PublishParser/getSchemaFromPublish.ts +141 -0
  84. package/lib/PublishParser/getTableRulesWithoutFileTable.ts +177 -0
  85. package/lib/PublishParser/publishTypesAndUtils.ts +399 -0
  86. package/lib/RestApi.ts +127 -0
  87. package/lib/SchemaWatch/SchemaWatch.ts +90 -0
  88. package/lib/SchemaWatch/createSchemaWatchEventTrigger.ts +3 -0
  89. package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
  90. package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
  91. package/lib/SyncReplication.ts +557 -0
  92. package/lib/TableConfig/TableConfig.ts +468 -0
  93. package/lib/TableConfig/getColumnDefinitionQuery.ts +111 -0
  94. package/lib/TableConfig/getConstraintDefinitionQueries.ts +95 -0
  95. package/lib/TableConfig/getFutureTableSchema.ts +64 -0
  96. package/lib/TableConfig/getPGIndexes.ts +53 -0
  97. package/lib/TableConfig/getTableColumnQueries.ts +129 -0
  98. package/lib/TableConfig/initTableConfig.ts +326 -0
  99. package/lib/index.ts +13 -0
  100. package/lib/initProstgles.ts +319 -0
  101. package/lib/onSocketConnected.ts +102 -0
  102. package/lib/runClientRequest.ts +129 -0
  103. package/lib/shortestPath.ts +122 -0
  104. package/lib/typeTests/DBoGenerated.d.ts +320 -0
  105. package/lib/typeTests/dboTypeCheck.ts +81 -0
  106. package/lib/utils.ts +15 -0
  107. package/package.json +1 -1
@@ -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)
@@ -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
+ }