@joystick.js/node-canary 0.0.0-canary.5 → 0.0.0-canary.50

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.
package/_package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joystick.js/node",
3
- "version": "1.0.0-beta.165",
3
+ "version": "1.0.0-beta.166",
4
4
  "developmentVersion": "1.0.0-beta.1419",
5
5
  "type": "module",
6
6
  "description": "A Node.js framework for building web apps.",
@@ -1,4 +1,5 @@
1
1
  import { isObject } from "../../validation/lib/typeValidators";
2
+ import escapeKeyValuePair from "../../lib/escapeKeyValuePair.js";
2
3
  var getBrowserSafeUser_default = (user = null) => {
3
4
  if (!user || !isObject(user)) {
4
5
  return null;
@@ -16,7 +17,7 @@ var getBrowserSafeUser_default = (user = null) => {
16
17
  return fields;
17
18
  }
18
19
  }, {});
19
- return browserSafeUser;
20
+ return escapeKeyValuePair(browserSafeUser);
20
21
  };
21
22
  export {
22
23
  getBrowserSafeUser_default as default
@@ -5,8 +5,7 @@ var hasLoginTokenExpired_default = async (res, token = null, tokenExpiresAt = nu
5
5
  unsetAuthenticationCookie(res);
6
6
  return true;
7
7
  }
8
- const _dayjs = process.env.NODE_ENV === "test" ? (await import("../../tests/mocks/dayjs")).default : null;
9
- const hasExpired = process.env.NODE_ENV === "test" ? _dayjs().isAfter(_dayjs(tokenExpiresAt)) : dayjs().isAfter(dayjs(tokenExpiresAt));
8
+ const hasExpired = dayjs().isAfter(dayjs(tokenExpiresAt));
10
9
  if (hasExpired) {
11
10
  unsetAuthenticationCookie(res);
12
11
  return true;
@@ -17,7 +17,7 @@ var mongodb_default = async (settings = {}, databasePort = 2610) => {
17
17
  const connectionOptions = {
18
18
  useNewUrlParser: true,
19
19
  useUnifiedTopology: true,
20
- ssl: !process.env.NODE_ENV?.includes("development"),
20
+ ssl: !["development", "test"].includes(process.env.NODE_ENV),
21
21
  ...settings?.options || {}
22
22
  };
23
23
  if (settings?.options?.ca) {
@@ -1,8 +1,9 @@
1
1
  import getBrowserSafeUser from "./accounts/getBrowserSafeUser";
2
+ import escapeKeyValuePair from "../lib/escapeKeyValuePair.js";
2
3
  var getBrowserSafeRequest_default = (req = {}) => {
3
4
  const browserSafeRequest = {};
4
- browserSafeRequest.params = req.params;
5
- browserSafeRequest.query = req.query;
5
+ browserSafeRequest.params = escapeKeyValuePair(req.params);
6
+ browserSafeRequest.query = escapeKeyValuePair(req.query);
6
7
  browserSafeRequest.context = {
7
8
  user: getBrowserSafeUser(req.context.user)
8
9
  };
package/dist/app/index.js CHANGED
@@ -53,6 +53,7 @@ class App {
53
53
  this.initWebsockets(options?.websockets || {});
54
54
  this.initDevelopmentRoutes();
55
55
  this.initAccounts();
56
+ this.initTests();
56
57
  this.initDeploy();
57
58
  this.initAPI(options?.api);
58
59
  this.initRoutes(options?.routes);
@@ -143,7 +144,9 @@ class App {
143
144
  process.BUILD_ERROR = JSON.parse(message);
144
145
  }
145
146
  });
146
- console.log(`App running at: http://localhost:${express.port}`);
147
+ if (process.env.NODE_ENV !== "test") {
148
+ console.log(`App running at: http://localhost:${express.port}`);
149
+ }
147
150
  }
148
151
  setMachineId() {
149
152
  generateMachineId();
@@ -156,6 +159,13 @@ class App {
156
159
  fs.writeFileSync("./.joystick/PROCESS_ID", `${generateId(32)}`);
157
160
  }
158
161
  }
162
+ initTests() {
163
+ if (process.env.NODE_ENV === "test") {
164
+ this.express.app.get("/api/_test/bootstrap", async (req, res) => {
165
+ res.status(200).send({ ping: "pong" });
166
+ });
167
+ }
168
+ }
159
169
  initDeploy() {
160
170
  if (process.env.NODE_ENV === "production" && process.env.IS_PUSH_DEPLOYED) {
161
171
  this.express.app.get("/api/_push/pre-version", async (req, res) => {
@@ -401,7 +411,7 @@ class App {
401
411
  });
402
412
  }
403
413
  initDevelopmentRoutes() {
404
- if (process.env.NODE_ENV === "development") {
414
+ if (["development", "test"].includes(process.env.NODE_ENV)) {
405
415
  this.express.app.get("/api/_joystick/sessions", async (req, res) => {
406
416
  const sessions = Array.from(this.sessions.entries())?.reduce((acc = {}, [key, value]) => {
407
417
  acc[key] = value;
@@ -3,6 +3,7 @@ import compression from "compression";
3
3
  import cookieParser from "cookie-parser";
4
4
  import favicon from "serve-favicon";
5
5
  import fs from "fs";
6
+ import { __package } from "../../index.js";
6
7
  import insecure from "./insecure.js";
7
8
  import requestMethods from "./requestMethods.js";
8
9
  import bodyParser from "./bodyParser.js";
@@ -14,11 +15,10 @@ import runUserQuery from "../accounts/runUserQuery.js";
14
15
  import replaceBackslashesWithForwardSlashes from "../../lib/replaceBackslashesWithForwardSlashes.js";
15
16
  import replaceFileProtocol from "../../lib/replaceFileProtocol.js";
16
17
  import getBuildPath from "../../lib/getBuildPath.js";
17
- import sanitizeQueryParameters from "./sanitizeQueryParameters.js";
18
18
  import session from "./session.js";
19
19
  import csp from "./csp.js";
20
20
  const cwd = replaceFileProtocol(replaceBackslashesWithForwardSlashes(process.cwd()));
21
- const faviconPath = process.env.NODE_ENV === "test" ? `${cwd}/src/tests/mocks/app/public/favicon.ico` : "public/favicon.ico";
21
+ const faviconPath = "public/favicon.ico";
22
22
  var middleware_default = ({
23
23
  app,
24
24
  port,
@@ -41,7 +41,6 @@ var middleware_default = ({
41
41
  }
42
42
  next();
43
43
  });
44
- app.use(sanitizeQueryParameters);
45
44
  app.use(requestMethods);
46
45
  if (cspConfig) {
47
46
  app.use((req, res, next) => csp(req, res, next, cspConfig));
@@ -54,7 +53,7 @@ var middleware_default = ({
54
53
  });
55
54
  app.use("/_joystick/utils/process.js", (_req, res) => {
56
55
  res.set("Content-Type", "text/javascript");
57
- const processPolyfill = fs.readFileSync(`${cwd}/node_modules/@joystick.js/node/dist/app/utils/process.js`, "utf-8");
56
+ const processPolyfill = fs.readFileSync(`${__package}/app/utils/process.js`, "utf-8");
58
57
  res.send(processPolyfill.replace("${NODE_ENV}", process.env.NODE_ENV));
59
58
  });
60
59
  app.use("/_joystick/index.client.js", express.static(`${buildPath}index.client.js`, {
@@ -65,7 +64,7 @@ var middleware_default = ({
65
64
  app.use("/_joystick/ui", express.static(`${buildPath}ui`, { eTag: false, maxAge: "0" }));
66
65
  app.use("/_joystick/hmr/client.js", (_req, res) => {
67
66
  res.set("Content-Type", "text/javascript");
68
- const hmrClient = fs.readFileSync(`${cwd}/node_modules/@joystick.js/node/dist/app/middleware/hmr/client.js`, "utf-8");
67
+ const hmrClient = fs.readFileSync(`${__package}/app/middleware/hmr/client.js`, "utf-8");
69
68
  res.send(hmrClient.replace("${process.env.PORT}", parseInt(process.env.PORT, 10) + 1));
70
69
  });
71
70
  app.use(favicon(faviconPath));
@@ -8,6 +8,8 @@ import generateErrorPage from "../../lib/generateErrorPage.js";
8
8
  import replaceFileProtocol from "../../lib/replaceFileProtocol.js";
9
9
  import replaceBackslashesWithForwardSlashes from "../../lib/replaceBackslashesWithForwardSlashes.js";
10
10
  import getBuildPath from "../../lib/getBuildPath.js";
11
+ import escapeHTML from "../../lib/escapeHTML.js";
12
+ import escapeKeyValuePair from "../../lib/escapeKeyValuePair.js";
11
13
  const generateHash = (input = "") => {
12
14
  return crypto.createHash("sha256").update(input).digest("hex");
13
15
  };
@@ -65,10 +67,10 @@ const getCachedHTML = ({ cachePath, cacheFileName, currentDiff }) => {
65
67
  const getUrl = (request = {}) => {
66
68
  const [path = null] = request.url?.split("?");
67
69
  return {
68
- params: request.params,
69
- query: request.query,
70
- route: request.route.path,
71
- path
70
+ params: escapeKeyValuePair(request.params),
71
+ query: escapeKeyValuePair(request.query),
72
+ route: escapeHTML(request.route.path),
73
+ path: escapeHTML(path)
72
74
  };
73
75
  };
74
76
  const getFile = async (buildPath = "") => {
@@ -1,10 +1,5 @@
1
1
  import util from "util";
2
- import { HTML_ENTITY_MAP } from "../lib/constants.js";
3
- const escapeHTML = (string = "") => {
4
- return String(string).replace(/[&<>"'`=\/]/g, function(match) {
5
- return HTML_ENTITY_MAP[match];
6
- });
7
- };
2
+ import escapeHTML from "../lib/escapeHTML.js";
8
3
  const sanitizeAPIResponse = (data = null) => {
9
4
  let sanitizedData = data;
10
5
  if (!util.isString(sanitizedData) && !util.isObject(sanitizedData) && !Array.isArray(sanitizedData)) {
@@ -3,6 +3,9 @@ var validateSession_default = (req = null, res = null, sessions = null) => {
3
3
  const sessionToken = req?.cookies?.joystickSession;
4
4
  const csrfToken = req?.headers["x-joystick-csrf"];
5
5
  const session = sessions?.get(sessionToken);
6
+ if (csrfToken === "joystick_test") {
7
+ return true;
8
+ }
6
9
  if (!session || session && session.csrf !== csrfToken) {
7
10
  res.status(403).send(JSON.stringify({
8
11
  errors: [formatAPIError(new Error("Unauthorized request."))]
@@ -1,6 +1,5 @@
1
1
  import ui from "@joystick.js/ui";
2
2
  const ResetPassword = ui.component({
3
- id: process.env.NODE_ENV === "test" ? "testComponent1234" : null,
4
3
  render: ({ props }) => {
5
4
  return `
6
5
  <p>A password reset was requested for this email address (${props.emailAddress}). If you requested this reset, click the link below to reset your password:</p>
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
- import sanitizeHTML from "sanitize-html";
2
1
  import fs from "fs";
2
+ import { fileURLToPath } from "url";
3
+ import { dirname } from "path";
4
+ import sanitizeHTML from "sanitize-html";
3
5
  import _accounts from "./app/accounts";
4
6
  import _action from "./action/index.js";
5
7
  import _websockets from "./websockets";
@@ -27,16 +29,18 @@ const sanitize = {
27
29
  allowedTags: sanitizeHTML.defaults.allowedTags
28
30
  }
29
31
  };
32
+ const currentFilePath = fileURLToPath(import.meta.url);
33
+ const __package = dirname(currentFilePath);
30
34
  const __filename = nodeUrlPolyfills.__filename;
31
35
  const __dirname = nodeUrlPolyfills.__dirname;
32
36
  const id = generateId;
33
37
  const origin = getOrigin();
34
38
  const settings = loadSettings();
35
- console.log("HERE", fs.readFileSync(__dirname("dist/app/utils/process.js"), "utf-8"));
36
39
  global.joystick = {
37
40
  id: generateId,
38
41
  emitters: {},
39
42
  settings,
43
+ __package,
40
44
  __dirname,
41
45
  __filename
42
46
  };
@@ -58,6 +62,7 @@ var src_default = {
58
62
  export {
59
63
  __dirname,
60
64
  __filename,
65
+ __package,
61
66
  accounts,
62
67
  action,
63
68
  src_default as default,
@@ -0,0 +1,9 @@
1
+ import { HTML_ENTITY_MAP } from "./constants.js";
2
+ var escapeHTML_default = (string = "") => {
3
+ return String(string).replace(/[&<>"'`=]/g, function(match) {
4
+ return HTML_ENTITY_MAP[match];
5
+ });
6
+ };
7
+ export {
8
+ escapeHTML_default as default
9
+ };
@@ -0,0 +1,13 @@
1
+ import escapeHTML from "./escapeHTML.js";
2
+ var escapeKeyValuePair_default = (target = {}) => {
3
+ const parameters = Object.entries(target || {});
4
+ for (let i = 0; i < parameters?.length; i += 1) {
5
+ const [key, value] = parameters[i];
6
+ delete target[key];
7
+ target[escapeHTML(key)] = escapeHTML(value);
8
+ }
9
+ return target;
10
+ };
11
+ export {
12
+ escapeKeyValuePair_default as default
13
+ };
@@ -1,6 +1,6 @@
1
1
  import fs from "fs";
2
2
  var getBuildPath_default = () => {
3
- if (process.env.NODE_ENV === "development" || fs.existsSync(".joystick/build")) {
3
+ if (["development", "test"].includes(process.env.NODE_ENV) || fs.existsSync(".joystick/build")) {
4
4
  return ".joystick/build/";
5
5
  }
6
6
  return "";
@@ -6,7 +6,7 @@ var getSSLCertificates_default = (ssl = null) => {
6
6
  const keyPath = process.env.IS_PUSH_DEPLOYED ? pushKeyPath : ssl?.key || null;
7
7
  const certExists = fs.existsSync(certPath);
8
8
  const keyExists = fs.existsSync(keyPath);
9
- if (process.env.NODE_ENV === "development" || !certExists || !keyExists) {
9
+ if (["development", "test"].includes(process.env.NODE_ENV) || !certExists || !keyExists) {
10
10
  return null;
11
11
  }
12
12
  return {
package/dist/lib/log.js CHANGED
@@ -1,9 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import rainbowRoad from "./rainbowRoad.js";
3
3
  var log_default = (message = "", options = {}) => {
4
- if (process.env.NODE_ENV === "test") {
5
- return;
6
- }
7
4
  const colors = {
8
5
  info: "blue",
9
6
  success: "green",
@@ -1,16 +1,12 @@
1
- import { URL } from "url";
1
+ import { fileURLToPath } from "url";
2
+ import { dirname } from "path";
2
3
  var nodeUrlPolyfills_default = {
3
- __filename: (url = null) => {
4
- if (!url) {
5
- return "";
6
- }
7
- return new URL("", url).pathname;
4
+ __filename: (url = "") => {
5
+ return fileURLToPath(url);
8
6
  },
9
- __dirname: (url = null) => {
10
- if (!url) {
11
- return "";
12
- }
13
- return new URL(".", url).pathname;
7
+ __dirname: (url = "") => {
8
+ const currentFilePath = fileURLToPath(url);
9
+ return dirname(currentFilePath);
14
10
  }
15
11
  };
16
12
  export {
package/dist/ssr/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from "fs";
2
+ import { __package } from "../index.js";
2
3
  import get from "../api/get";
3
4
  import set from "../api/set";
4
5
  import getBrowserSafeRequest from "../app/getBrowserSafeRequest";
@@ -184,7 +185,7 @@ const getBaseCSS = (baseHTMLName = "") => {
184
185
  try {
185
186
  const customBaseCSSPathForEmail = baseHTMLName ? `${process.cwd()}/email/base_${baseHTMLName}.css` : null;
186
187
  const customDefaultBaseCSSPathForEmail = `${process.cwd()}/email/base.css`;
187
- const defaultBaseCSSPathForEmail = process.env.NODE_ENV === "test" ? `${process.cwd()}/src/email/templates/base.css` : `${process.cwd()}/node_modules/@joystick.js/node/dist/email/templates/base.css`;
188
+ const defaultBaseCSSPathForEmail = process.env.NODE_ENV === "test" ? `${process.cwd()}/src/email/templates/base.css` : `${__package}/email/templates/base.css`;
188
189
  let baseCSSPathToFetch = defaultBaseCSSPathForEmail;
189
190
  if (fs.existsSync(customDefaultBaseCSSPathForEmail)) {
190
191
  baseCSSPathToFetch = customDefaultBaseCSSPathForEmail;
@@ -242,7 +243,7 @@ const getBaseHTML = (isEmailRender = false, baseEmailHTMLName = "") => {
242
243
  if (isEmailRender) {
243
244
  const customBaseHTMLPathForEmail = baseEmailHTMLName ? `${process.cwd()}/email/base_${baseEmailHTMLName}.html` : null;
244
245
  const customDefaultBaseHTMLPathForEmail = `${process.cwd()}/email/base.html`;
245
- const defaultBaseHTMLPathForEmail = process.env.NODE_ENV === "test" ? `${process.cwd()}/src/email/templates/base.html` : `${process.cwd()}/node_modules/@joystick.js/node/dist/email/templates/base.html`;
246
+ const defaultBaseHTMLPathForEmail = process.env.NODE_ENV === "test" ? `${process.cwd()}/src/email/templates/base.html` : `${__package}/email/templates/base.html`;
246
247
  baseHTMLPathToFetch = defaultBaseHTMLPathForEmail;
247
248
  if (fs.existsSync(customDefaultBaseHTMLPathForEmail)) {
248
249
  baseHTMLPathToFetch = customDefaultBaseHTMLPathForEmail;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joystick.js/node-canary",
3
- "version": "0.0.0-canary.5",
3
+ "version": "0.0.0-canary.50",
4
4
  "type": "module",
5
5
  "description": "A Node.js framework for building web apps.",
6
6
  "main": "./dist/index.js",
@@ -1,123 +0,0 @@
1
- import { jest } from "@jest/globals";
2
- import { killPortProcess } from "kill-port-process";
3
- import setAppSettingsForTest from "../../../tests/lib/setAppSettingsForTest";
4
- import startTestDatabase from "../../../tests/lib/databases/start";
5
- import stopTestDatabase from "../../../tests/lib/databases/stop";
6
- import { beforeEach, expect, test } from "@jest/globals";
7
- import roles from "./index";
8
- import accounts from "../index";
9
- jest.mock("../../../../node_modules/dayjs", () => {
10
- const _dayjs = jest.requireActual("../../../../node_modules/dayjs");
11
- const _utc = jest.requireActual("../../../../node_modules/dayjs/plugin/utc");
12
- _dayjs.extend(_utc);
13
- return () => _dayjs("2022-01-01T00:00:00.000Z");
14
- });
15
- setAppSettingsForTest({
16
- "config": {
17
- "databases": [
18
- {
19
- "provider": "mongodb",
20
- "users": true,
21
- "options": {}
22
- }
23
- ],
24
- "i18n": {
25
- "defaultLanguage": "en-US"
26
- },
27
- "middleware": {},
28
- "email": {
29
- "from": "app@test.com",
30
- "smtp": {
31
- "host": "fake.email.com",
32
- "port": 587,
33
- "username": "test",
34
- "password": "password"
35
- }
36
- }
37
- },
38
- "global": {},
39
- "public": {},
40
- "private": {}
41
- });
42
- global.joystick = {
43
- settings: {
44
- config: {
45
- databases: [{
46
- "provider": "mongodb",
47
- "users": true,
48
- "options": {}
49
- }]
50
- }
51
- }
52
- };
53
- const app = (await import("../../index")).default;
54
- let instance;
55
- describe("app/accounts/roles/index.js", () => {
56
- beforeAll(async () => {
57
- process.env.PORT = 3600;
58
- });
59
- beforeEach(async () => {
60
- instance = await app({});
61
- });
62
- afterEach(async () => {
63
- if (instance?.server?.close && typeof instance.server.close === "function") {
64
- instance.server.close();
65
- }
66
- await killPortProcess(process.env.PORT);
67
- });
68
- afterAll(async () => {
69
- });
70
- test("roles.add adds role to roles collection in database", async () => {
71
- await roles.add("admin");
72
- const roleExists = await process.databases.mongodb.collection("roles").findOne({ role: "admin" });
73
- expect(!!roleExists).toBe(true);
74
- });
75
- test("roles.remove removes role from roles collection in database", async () => {
76
- await roles.add("admin");
77
- await roles.remove("admin");
78
- const roleExists = await process.databases.mongodb.collection("roles").findOne({ role: "admin" });
79
- expect(!!roleExists).toBe(false);
80
- });
81
- test("roles.list returns a list of roles in the roles collection in database", async () => {
82
- await roles.add("admin");
83
- await roles.add("manager");
84
- await roles.add("employee");
85
- const rolesInDatabase = await roles.list();
86
- await roles.remove("admin");
87
- await roles.remove("manager");
88
- await roles.remove("employee");
89
- expect(rolesInDatabase).toEqual(["admin", "manager", "employee"]);
90
- });
91
- test("roles.grant adds role to user in users collection in database", async () => {
92
- await process.databases.mongodb.collection("users").deleteOne({ emailAddress: "test@test.com" });
93
- const user = await accounts.signup({ emailAddress: "test@test.com", password: "password" });
94
- await roles.grant(user?.userId, "admin");
95
- await roles.grant(user?.userId, "rolethatdoesntexist");
96
- const userAfterGrant = await process.databases.mongodb.collection("users").findOne({ _id: user?.userId });
97
- const rolesCreated = await process.databases.mongodb.collection("roles").find().toArray();
98
- expect(userAfterGrant?.roles?.includes("admin")).toBe(true);
99
- expect(rolesCreated?.length).toBe(2);
100
- });
101
- test("roles.revoke removes role from user in users collection in database", async () => {
102
- await process.databases.mongodb.collection("users").deleteOne({ emailAddress: "test@test.com" });
103
- const user = await accounts.signup({ emailAddress: "test@test.com", password: "password" });
104
- await roles.grant(user?.userId, "admin");
105
- await roles.revoke(user?.userId, "admin");
106
- const userAfterGrant = await process.databases.mongodb.collection("users").findOne({ _id: user?.userId });
107
- expect(userAfterGrant?.roles?.includes("admin")).toBe(false);
108
- });
109
- test("roles.userHasRole returns true if user has role", async () => {
110
- await process.databases.mongodb.collection("users").deleteOne({ emailAddress: "test@test.com" });
111
- const user = await accounts.signup({ emailAddress: "test@test.com", password: "password" });
112
- await roles.grant(user?.userId, "admin");
113
- const userHasRole = await roles.userHasRole(user?.userId, "admin");
114
- expect(userHasRole).toBe(true);
115
- });
116
- test("roles.userHasRole returns false if user does not have role", async () => {
117
- await process.databases.mongodb.collection("users").deleteOne({ emailAddress: "test@test.com" });
118
- const user = await accounts.signup({ emailAddress: "test@test.com", password: "password" });
119
- await roles.grant(user?.userId, "admin");
120
- const userHasRole = await roles.userHasRole(user?.userId, "manager");
121
- expect(userHasRole).toBe(false);
122
- });
123
- });
@@ -1,575 +0,0 @@
1
- import { afterAll, afterEach, beforeAll, expect, jest, test } from "@jest/globals";
2
- import { mockRequest, mockResponse } from "jest-mock-req-res";
3
- import { killPortProcess } from "kill-port-process";
4
- import assertRoutesDoNotExistInRegexes from "../tests/lib/assertRoutesDoNotExistInRegexes";
5
- import assertRoutesExistInRegexes from "../tests/lib/assertRoutesExistInRegexes";
6
- import setAppSettingsForTest from "../tests/lib/setAppSettingsForTest";
7
- import startTestDatabase from "../tests/lib/databases/start";
8
- import stopTestDatabase from "../tests/lib/databases/stop";
9
- import getRouteRegexes from "../tests/lib/getRouteRegexes";
10
- jest.mock("../../node_modules/crypto-extra", () => {
11
- return {
12
- randomString: () => "abc1234"
13
- };
14
- });
15
- jest.mock("../../node_modules/bcrypt", () => {
16
- return {
17
- hashSync: () => "hashed$password",
18
- compareSync: () => {
19
- return true;
20
- }
21
- };
22
- });
23
- jest.mock("../../node_modules/dayjs", () => {
24
- const _dayjs = jest.requireActual("../../node_modules/dayjs");
25
- const _utc = jest.requireActual("../../node_modules/dayjs/plugin/utc");
26
- _dayjs.extend(_utc);
27
- return () => _dayjs("2022-01-01T00:00:00.000Z");
28
- });
29
- const nodemailer = {
30
- smtp: {
31
- sendMail: jest.fn()
32
- }
33
- };
34
- jest.mock("../../node_modules/nodemailer", () => {
35
- return {
36
- createTransport: jest.fn(() => {
37
- return {
38
- sendMail: nodemailer.smtp.sendMail
39
- };
40
- })
41
- };
42
- });
43
- jest.mock("../../node_modules/@joystick.js/ui/dist/component/generateId.js", () => {
44
- return "component1234";
45
- });
46
- setAppSettingsForTest({
47
- "config": {
48
- "databases": [
49
- {
50
- "provider": "mongodb",
51
- "users": true,
52
- "options": {}
53
- }
54
- ],
55
- "i18n": {
56
- "defaultLanguage": "en-US"
57
- },
58
- "middleware": {},
59
- "email": {
60
- "from": "app@test.com",
61
- "smtp": {
62
- "host": "fake.email.com",
63
- "port": 587,
64
- "username": "test",
65
- "password": "password"
66
- }
67
- }
68
- },
69
- "global": {},
70
- "public": {},
71
- "private": {}
72
- });
73
- const dayjs = (await import("dayjs")).default;
74
- const app = (await import("./index")).default;
75
- const generateId = (await import("../lib/generateId")).default;
76
- global.joystick = {
77
- settings: {
78
- config: {
79
- databases: [{
80
- "provider": "mongodb",
81
- "users": true,
82
- "options": {}
83
- }]
84
- }
85
- }
86
- };
87
- describe("index.js", () => {
88
- beforeAll(async () => {
89
- process.env.PORT = 3600;
90
- await startTestDatabase("mongodb");
91
- });
92
- afterEach(async () => {
93
- await killPortProcess(process.env.PORT);
94
- });
95
- afterAll(async () => {
96
- await stopTestDatabase();
97
- });
98
- test("registers render-related routes", async () => {
99
- const instance = await app({});
100
- if (instance?.server?.close && typeof instance.server.close === "function") {
101
- instance.server.close();
102
- }
103
- if (instance?.app?._router) {
104
- const routeRegexes = getRouteRegexes(instance.app._router.stack);
105
- assertRoutesExistInRegexes(routeRegexes, [
106
- "/_joystick/heartbeat",
107
- "/_joystick/utils/process.js",
108
- "/_joystick/index.client.js",
109
- "/_joystick/index.css",
110
- "/_joystick/ui",
111
- "/_joystick/hmr/client.js"
112
- ]);
113
- }
114
- });
115
- test("registers accounts-related routes", async () => {
116
- const instance = await app({});
117
- if (instance?.server?.close && typeof instance.server.close === "function") {
118
- instance.server.close();
119
- }
120
- if (instance?.app?._router) {
121
- const routeRegexes = getRouteRegexes(instance.app._router.stack);
122
- assertRoutesExistInRegexes(routeRegexes, [
123
- "/api/_accounts/login",
124
- "/api/_accounts/logout",
125
- "/api/_accounts/signup",
126
- "/api/_accounts/recover-password",
127
- "/api/_accounts/reset-password"
128
- ]);
129
- }
130
- });
131
- test("registers getter routes", async () => {
132
- const instance = await app({
133
- api: {
134
- getters: {
135
- posts: {
136
- input: {},
137
- get: () => {
138
- }
139
- },
140
- post: {
141
- input: {},
142
- get: () => {
143
- }
144
- }
145
- }
146
- }
147
- });
148
- if (instance?.server?.close && typeof instance.server.close === "function") {
149
- instance.server.close();
150
- }
151
- if (instance?.app?._router) {
152
- const routeRegexes = getRouteRegexes(instance.app._router.stack);
153
- assertRoutesExistInRegexes(routeRegexes, [
154
- "/api/_getters/posts",
155
- "/api/_getters/post"
156
- ]);
157
- }
158
- });
159
- test("registers setter routes", async () => {
160
- const instance = await app({
161
- api: {
162
- setters: {
163
- createPost: {
164
- input: {},
165
- set: () => {
166
- }
167
- },
168
- updatePost: {
169
- input: {},
170
- set: () => {
171
- }
172
- }
173
- }
174
- }
175
- });
176
- if (instance?.server?.close && typeof instance.server.close === "function") {
177
- instance.server.close();
178
- }
179
- if (instance?.app?._router) {
180
- const routeRegexes = getRouteRegexes(instance.app._router.stack);
181
- assertRoutesExistInRegexes(routeRegexes, [
182
- "/api/_setters/createPost",
183
- "/api/_setters/updatePost"
184
- ]);
185
- }
186
- });
187
- test("registers custom routes if passed as function", async () => {
188
- const instance = await app({
189
- routes: {
190
- "/latest": () => {
191
- },
192
- "/profile/:_id": () => {
193
- },
194
- "/category/:category/posts": () => {
195
- }
196
- }
197
- });
198
- if (instance?.app?._router) {
199
- const routeRegexes = getRouteRegexes(instance.app._router.stack);
200
- assertRoutesExistInRegexes(routeRegexes, [
201
- "/latest",
202
- "/profile/123",
203
- "/category/tutorials/posts"
204
- ]);
205
- }
206
- if (instance?.server?.close && typeof instance.server.close === "function") {
207
- instance.server.close();
208
- }
209
- });
210
- test("does not register custom routes if passed as function not set to a function", async () => {
211
- const instance = await app({
212
- routes: {
213
- "/latest": "function",
214
- "/profile/:_id": "function",
215
- "/category/:category/posts": "function"
216
- }
217
- });
218
- if (instance?.app?._router) {
219
- const routeRegexes = getRouteRegexes(instance.app._router.stack);
220
- assertRoutesDoNotExistInRegexes(routeRegexes, [
221
- "/latest",
222
- "/profile/123",
223
- "/category/tutorials/posts"
224
- ]);
225
- }
226
- if (instance?.server?.close && typeof instance.server.close === "function") {
227
- instance.server.close();
228
- }
229
- });
230
- test("registers custom routes if passed as an object", async () => {
231
- const instance = await app({
232
- routes: {
233
- "/latest": {
234
- method: "GET",
235
- handler: () => {
236
- }
237
- },
238
- "/profile/:_id": {
239
- method: "POST",
240
- handler: () => {
241
- }
242
- },
243
- "/category/:category/posts": {
244
- method: "PUT",
245
- handler: () => {
246
- }
247
- }
248
- }
249
- });
250
- if (instance?.server?.close && typeof instance.server.close === "function") {
251
- instance.server.close();
252
- }
253
- if (instance?.app?._router) {
254
- const routeRegexes = getRouteRegexes(instance.app._router.stack);
255
- assertRoutesExistInRegexes(routeRegexes, [
256
- "/latest",
257
- "/profile/123",
258
- "/category/tutorials/posts"
259
- ]);
260
- }
261
- });
262
- test("does not register custom routes if passed as an object without a valid method", async () => {
263
- const instance = await app({
264
- routes: {
265
- "/latest": {
266
- method: "CAT"
267
- },
268
- "/profile/:_id": {
269
- handler: () => {
270
- }
271
- },
272
- "/category/:category/posts": {
273
- method: "PUT",
274
- handler: () => {
275
- }
276
- }
277
- }
278
- });
279
- if (instance?.server?.close && typeof instance.server.close === "function") {
280
- instance.server.close();
281
- }
282
- if (instance?.app?._router) {
283
- const routeRegexes = getRouteRegexes(instance.app._router.stack);
284
- assertRoutesDoNotExistInRegexes(routeRegexes, [
285
- "/latest",
286
- "/profile/123"
287
- ]);
288
- assertRoutesExistInRegexes(routeRegexes, [
289
- "/category/tutorials/posts"
290
- ]);
291
- }
292
- });
293
- test("does not register custom routes if passed as an object without a handler function", async () => {
294
- const instance = await app({
295
- routes: {
296
- "/latest": {
297
- method: "GET"
298
- },
299
- "/profile/:_id": {
300
- method: "POST"
301
- },
302
- "/category/:category/posts": {
303
- method: "PUT"
304
- }
305
- }
306
- });
307
- if (instance?.server?.close && typeof instance.server.close === "function") {
308
- instance.server.close();
309
- }
310
- if (instance?.app?._router) {
311
- const routeRegexes = getRouteRegexes(instance.app._router.stack);
312
- assertRoutesDoNotExistInRegexes(routeRegexes, [
313
- "/latest",
314
- "/profile/123",
315
- "/category/tutorials/posts"
316
- ]);
317
- }
318
- });
319
- test("sets build errors on process", async () => {
320
- const instance = await app({});
321
- const testMessage = { id: generateId() };
322
- process.emit("message", JSON.stringify(testMessage));
323
- if (instance?.server?.close && typeof instance.server.close === "function") {
324
- instance.server.close();
325
- }
326
- expect(process.BUILD_ERROR).toEqual(testMessage);
327
- });
328
- test("if callback function is assigned to function-based route, it is called as expected", async () => {
329
- const instance = await app({
330
- routes: {
331
- "/test": (req2, res2) => {
332
- res2.status(200).send("Called as expected.");
333
- }
334
- }
335
- });
336
- const route = instance?.app?._router?.stack.find((stackItem) => {
337
- return stackItem?.route?.path === "/test";
338
- });
339
- if (instance?.server?.close && typeof instance.server.close === "function") {
340
- instance.server.close();
341
- }
342
- const req = mockRequest();
343
- const res = mockResponse();
344
- const handler = route?.route?.stack[0] && route?.route?.stack[0].handle;
345
- if (handler) {
346
- await handler(req, res, () => {
347
- });
348
- expect(res.status).toHaveBeenCalledWith(200);
349
- expect(res.send).toHaveBeenCalledWith("Called as expected.");
350
- }
351
- });
352
- test("if callback function is assigned to an object-based route, it is called as expected", async () => {
353
- const instance = await app({
354
- routes: {
355
- "/test": {
356
- method: "GET",
357
- handler: (req2, res2) => {
358
- res2.status(200).send("Called as expected.");
359
- }
360
- }
361
- }
362
- });
363
- const route = instance?.app?._router?.stack.find((stackItem) => {
364
- return stackItem?.route?.path === "/test";
365
- });
366
- if (instance?.server?.close && typeof instance.server.close === "function") {
367
- instance.server.close();
368
- }
369
- const req = mockRequest();
370
- const res = mockResponse();
371
- const handler = route?.route?.stack[0] && route?.route?.stack[0].handle;
372
- if (handler) {
373
- await handler(req, res, () => {
374
- });
375
- expect(res.status).toHaveBeenCalledWith(200);
376
- expect(res.send).toHaveBeenCalledWith("Called as expected.");
377
- }
378
- });
379
- test("verify that a /_accounts/authenticated request will return true if passed a valid cookie", async () => {
380
- const instance = await app({});
381
- const route = instance?.app?._router?.stack.find((stackItem) => {
382
- return stackItem?.route?.path === "/api/_accounts/authenticated";
383
- });
384
- const req = mockRequest({
385
- cookies: {
386
- joystickLoginToken: "testToken123",
387
- joystickLoginTokenExpiresAt: dayjs().add(1, "minute").format()
388
- }
389
- });
390
- const res = mockResponse();
391
- const handler = route?.route?.stack[0] && route?.route?.stack[0].handle;
392
- if (handler) {
393
- await handler(req, res, () => {
394
- });
395
- expect(res.status).toHaveBeenCalledWith(200);
396
- expect(res.send).toHaveBeenCalledWith('{"status":200,"authenticated":true}');
397
- }
398
- if (instance?.server?.close && typeof instance.server.close === "function") {
399
- instance.server.close();
400
- }
401
- });
402
- test("verify that a /_accounts/authenticated request will return false if passed an invalid cookie", async () => {
403
- const instance = await app({});
404
- const route = instance?.app?._router?.stack.find((stackItem) => {
405
- return stackItem?.route?.path === "/api/_accounts/authenticated";
406
- });
407
- if (instance?.server?.close && typeof instance.server.close === "function") {
408
- instance.server.close();
409
- }
410
- const req = mockRequest({
411
- cookies: {
412
- joystickLoginToken: "testToken123",
413
- joystickLoginTokenExpiresAt: jest.requireActual("../../node_modules/dayjs")().subtract(1, "minute").format()
414
- }
415
- });
416
- const res = mockResponse();
417
- const handler = route?.route?.stack[0] && route?.route?.stack[0].handle;
418
- if (handler) {
419
- await handler(req, res, () => {
420
- });
421
- expect(res.status).toHaveBeenCalledWith(401);
422
- expect(res.send).toHaveBeenCalledWith('{"status":401,"authenticated":false}');
423
- }
424
- });
425
- test("verify that a /_accounts/signup request will set a cookie and return a user if passed a valid signup request", async () => {
426
- const instance = await app({});
427
- const route = instance?.app?._router?.stack.find((stackItem) => {
428
- return stackItem?.route?.path === "/api/_accounts/signup";
429
- });
430
- if (instance?.server?.close && typeof instance.server.close === "function") {
431
- instance.server.close();
432
- }
433
- const req = mockRequest({
434
- body: {
435
- emailAddress: "test@test.com",
436
- password: "password",
437
- metadata: {}
438
- }
439
- });
440
- const res = mockResponse();
441
- const handler = route?.route?.stack[0] && route?.route?.stack[0].handle;
442
- if (handler) {
443
- await handler(req, res, () => {
444
- });
445
- expect(res.cookie).toHaveBeenCalledWith("joystickLoginToken", "abc1234", { secure: true, httpOnly: true, expires: dayjs().toDate() });
446
- expect(res.cookie).toHaveBeenCalledWith("joystickLoginTokenExpiresAt", "2022-01-31T00:00:00Z", { secure: true, httpOnly: true, expires: dayjs().toDate() });
447
- expect(res.status).toHaveBeenCalledWith(200);
448
- expect(res.send).toHaveBeenCalledWith('{"_id":"abc1234","password":"hashed$password","emailAddress":"test@test.com"}');
449
- }
450
- });
451
- test("verify that a /_accounts/login request will set a cookie and return a user if passed a valid login request", async () => {
452
- const instance = await app({});
453
- const route = instance?.app?._router?.stack.find((stackItem) => {
454
- return stackItem?.route?.path === "/api/_accounts/login";
455
- });
456
- if (instance?.server?.close && typeof instance.server.close === "function") {
457
- instance.server.close();
458
- }
459
- const req = mockRequest({
460
- body: {
461
- emailAddress: "test@test.com",
462
- password: "password"
463
- }
464
- });
465
- const res = mockResponse();
466
- const handler = route?.route?.stack[0] && route?.route?.stack[0].handle;
467
- if (handler) {
468
- await handler(req, res, () => {
469
- });
470
- expect(res.cookie).toHaveBeenCalledWith("joystickLoginToken", "abc1234", { secure: true, httpOnly: true, expires: dayjs().toDate() });
471
- expect(res.cookie).toHaveBeenCalledWith("joystickLoginTokenExpiresAt", "2022-01-31T00:00:00Z", { secure: true, httpOnly: true, expires: dayjs().toDate() });
472
- expect(res.status).toHaveBeenCalledWith(200);
473
- expect(res.send).toHaveBeenCalledWith('{"_id":"abc1234","emailAddress":"test@test.com"}');
474
- }
475
- });
476
- test("verify that a /_accounts/logout request deletes the cookie", async () => {
477
- const instance = await app({});
478
- const loginRoute = instance?.app?._router?.stack.find((stackItem) => {
479
- return stackItem?.route?.path === "/api/_accounts/login";
480
- });
481
- const logoutRoute = instance?.app?._router?.stack.find((stackItem) => {
482
- return stackItem?.route?.path === "/api/_accounts/logout";
483
- });
484
- if (instance?.server?.close && typeof instance.server.close === "function") {
485
- instance.server.close();
486
- }
487
- const loginRequest = mockRequest({
488
- body: {
489
- emailAddress: "test@test.com",
490
- password: "password"
491
- }
492
- });
493
- const loginResponse = mockResponse();
494
- const loginHandler = loginRoute?.route?.stack[0] && loginRoute?.route?.stack[0].handle;
495
- const logoutRequest = mockRequest();
496
- const logoutResponse = mockResponse();
497
- const logoutHandler = logoutRoute?.route?.stack[0] && logoutRoute?.route?.stack[0].handle;
498
- if (loginHandler && logoutHandler) {
499
- await loginHandler(loginRequest, loginResponse, () => {
500
- });
501
- await logoutHandler(logoutRequest, logoutResponse, () => {
502
- });
503
- expect(loginResponse.cookie).toHaveBeenCalledWith("joystickLoginToken", "abc1234", { secure: true, httpOnly: true, expires: dayjs().toDate() });
504
- expect(loginResponse.cookie).toHaveBeenCalledWith("joystickLoginTokenExpiresAt", "2022-01-31T00:00:00Z", { secure: true, httpOnly: true, expires: dayjs().toDate() });
505
- expect(logoutResponse.cookie).toHaveBeenCalledWith("joystickLoginToken", null, { secure: true, httpOnly: true, expires: dayjs().toDate() });
506
- expect(logoutResponse.cookie).toHaveBeenCalledWith("joystickLoginTokenExpiresAt", null, { secure: true, httpOnly: true, expires: dayjs().toDate() });
507
- }
508
- });
509
- test("verify that a /_accounts/recover-password request attempts to send an email", async () => {
510
- const instance = await app({});
511
- const route = instance?.app?._router?.stack.find((stackItem) => {
512
- return stackItem?.route?.path === "/api/_accounts/recover-password";
513
- });
514
- if (instance?.server?.close && typeof instance.server.close === "function") {
515
- instance.server.close();
516
- }
517
- const req = mockRequest({
518
- body: {
519
- emailAddress: "test@test.com"
520
- }
521
- });
522
- const res = mockResponse();
523
- const handler = route?.route?.stack[0] && route?.route?.stack[0].handle;
524
- if (handler) {
525
- await handler(req, res, () => {
526
- });
527
- expect(nodemailer.smtp.sendMail).toHaveBeenCalledWith({
528
- from: "app@test.com",
529
- to: "test@test.com",
530
- subject: "Reset Your Password",
531
- html: `<!doctype html>
532
- <html class="no-js" lang="en">
533
- <head>
534
- <meta charset="utf-8">
535
- <title>Joystick</title>
536
-
537
- </head>
538
- <body style="color: #000; font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; font-size: 16px; line-height: 24px;">
539
- <div id="email"><div js-c="testComponent1234">
540
- <p>A password reset was requested for this email address (test@test.com). If you requested this reset, click the link below to reset your password:</p>
541
- <p><a href="http://localhost:3600/reset-password/abc1234">Reset Password</a></p>
542
- </div></div>
543
- </body>
544
- </html>
545
- `,
546
- text: "A password reset was requested for this email address (test@test.com). If you\nrequested this reset, click the link below to reset your password:\n\nReset Password [http://localhost:3600/reset-password/abc1234]"
547
- });
548
- }
549
- });
550
- test("verify that a /_accounts/reset-password request sets a cookie and returns the user", async () => {
551
- const instance = await app({});
552
- const route = instance?.app?._router?.stack.find((stackItem) => {
553
- return stackItem?.route?.path === "/api/_accounts/reset-password";
554
- });
555
- if (instance?.server?.close && typeof instance.server.close === "function") {
556
- instance.server.close();
557
- }
558
- const req = mockRequest({
559
- body: {
560
- token: "abc1234",
561
- password: "test@test.com"
562
- }
563
- });
564
- const res = mockResponse();
565
- const handler = route?.route?.stack[0] && route?.route?.stack[0].handle;
566
- if (handler) {
567
- await handler(req, res, () => {
568
- });
569
- expect(res.cookie).toHaveBeenCalledWith("joystickLoginToken", "abc1234", { secure: true, httpOnly: true, expires: dayjs().toDate() });
570
- expect(res.cookie).toHaveBeenCalledWith("joystickLoginTokenExpiresAt", "2022-01-31T00:00:00Z", { secure: true, httpOnly: true, expires: dayjs().toDate() });
571
- expect(res.status).toHaveBeenCalledWith(200);
572
- expect(res.send).toHaveBeenCalledWith('{"_id":"abc1234","password":"hashed$password","emailAddress":"test@test.com","sessions":[{"token":"abc1234","tokenExpiresAt":"2022-01-31T00:00:00Z"}],"passwordResetTokens":[]}');
573
- }
574
- });
575
- });
@@ -1,16 +0,0 @@
1
- var sanitizeQueryParameters_default = (req, res, next) => {
2
- const queryParameters = Object.entries(req?.query);
3
- const htmlRegex = new RegExp(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g);
4
- for (let i = 0; i < queryParameters?.length; i += 1) {
5
- const [key, value] = queryParameters[i];
6
- const keyHTMLMatches = key?.match(htmlRegex);
7
- const valueHTMLMatches = value?.match(htmlRegex);
8
- if (keyHTMLMatches?.length > 0 || valueHTMLMatches?.length > 0) {
9
- delete req.query[key];
10
- }
11
- }
12
- next();
13
- };
14
- export {
15
- sanitizeQueryParameters_default as default
16
- };
@@ -1,37 +0,0 @@
1
- import { jest } from "@jest/globals";
2
- import chalk from "chalk";
3
- import send from "./send";
4
- import setAppSettingsForTest from "../tests/lib/setAppSettingsForTest";
5
- setAppSettingsForTest({
6
- "config": {
7
- "databases": [
8
- {
9
- "provider": "mongodb",
10
- "users": true,
11
- "options": {}
12
- }
13
- ],
14
- "i18n": {
15
- "defaultLanguage": "en-US"
16
- },
17
- "middleware": {},
18
- "email": {
19
- "from": "app@test.com",
20
- "smtp": {}
21
- }
22
- },
23
- "global": {},
24
- "public": {},
25
- "private": {}
26
- });
27
- console.warn = jest.fn();
28
- describe("send.js", () => {
29
- test("console.warns an error when bad smtp settings are passed", async () => {
30
- const result = await send({ template: "test", props: {} });
31
- expect(result).toBe(null);
32
- expect(console.warn.mock.calls).toEqual([
33
- [chalk.redBright("Invalid SMTP settings: config.smtp not defined in settings.test.js")],
34
- [chalk.redBright("Cannot send email, invalid SMTP settings.")]
35
- ]);
36
- });
37
- });