@joystick.js/node-canary 0.0.0-canary.9 → 0.0.0-canary.90
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 +2 -1
- package/dist/app/accounts/createMetadataTableColumns.js +12 -0
- package/dist/app/accounts/deleteUser.js +7 -0
- package/dist/app/accounts/generateSession.js +2 -4
- package/dist/app/accounts/getBrowserSafeUser.js +2 -1
- package/dist/app/accounts/hasLoginTokenExpired.js +1 -2
- package/dist/app/accounts/index.js +2 -0
- package/dist/app/accounts/signup.js +37 -7
- package/dist/app/databases/mongodb/createAccountsIndexes.js +18 -0
- package/dist/app/databases/mongodb/index.js +1 -1
- package/dist/app/databases/mongodb/queries/accounts.js +8 -0
- package/dist/app/databases/postgresql/queries/accounts.js +3 -0
- package/dist/app/databases/stringToSnakeCase.js +6 -0
- package/dist/app/getBrowserSafeRequest.js +3 -2
- package/dist/app/index.js +102 -12
- package/dist/app/middleware/getTranslations.js +64 -0
- package/dist/app/middleware/index.js +4 -5
- package/dist/app/middleware/render.js +11 -68
- package/dist/app/sanitizeAPIResponse.js +1 -6
- package/dist/app/validateSession.js +3 -0
- package/dist/email/templates/reset-password.js +0 -1
- package/dist/index.js +7 -2
- package/dist/lib/escapeHTML.js +9 -0
- package/dist/lib/escapeKeyValuePair.js +13 -0
- package/dist/lib/getBuildPath.js +1 -1
- package/dist/lib/getSSLCertificates.js +1 -1
- package/dist/lib/importFile.js +7 -0
- package/dist/lib/log.js +0 -3
- package/dist/lib/nodeUrlPolyfills.js +7 -15
- package/dist/lib/wait.js +8 -0
- package/dist/ssr/getAPIForDataFunctions.js +33 -0
- package/dist/ssr/getDataFromComponent.js +14 -0
- package/dist/ssr/index.js +5 -41
- package/dist/ssr/replaceWhenTags.js +2 -3
- package/package.json +2 -1
- package/dist/app/accounts/roles/index.test.js +0 -123
- package/dist/app/index.test.js +0 -575
- package/dist/app/middleware/sanitizeQueryParameters.js +0 -16
- package/dist/email/send.test.js +0 -37
package/_package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joystick.js/node",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
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.",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"node-html-parser": "^5.1.0",
|
|
40
40
|
"nodemailer": "^6.7.0",
|
|
41
41
|
"pg": "^8.7.3",
|
|
42
|
+
"pg-escape": "^0.2.0",
|
|
42
43
|
"process": "^0.11.10",
|
|
43
44
|
"query-string": "^7.0.1",
|
|
44
45
|
"sanitize-html": "^2.7.3",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import escape from "pg-escape";
|
|
2
|
+
var createMetadataTableColumns_default = async (usersDatabase = "", sqlizedMetadata = {}) => {
|
|
3
|
+
if (usersDatabase === "postgresql") {
|
|
4
|
+
const columns = Object.keys(sqlizedMetadata);
|
|
5
|
+
for (let i = 0; i < columns?.length; i += 1) {
|
|
6
|
+
await process.databases.postgresql.query(escape(`ALTER TABLE users ADD COLUMN IF NOT EXISTS %I TEXT;`, columns[i]));
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
export {
|
|
11
|
+
createMetadataTableColumns_default as default
|
|
12
|
+
};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import dayjs from "dayjs";
|
|
2
|
+
import utc from "dayjs/plugin/utc.js";
|
|
2
3
|
import generateId from "../../lib/generateId";
|
|
3
|
-
|
|
4
|
-
const utc = await import("dayjs/plugin/utc");
|
|
5
|
-
dayjs.extend(utc.default);
|
|
6
|
-
}
|
|
4
|
+
dayjs.extend(utc);
|
|
7
5
|
var generateSession_default = () => {
|
|
8
6
|
return {
|
|
9
7
|
token: generateId(64),
|
|
@@ -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
|
|
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;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _setAuthenticationCookie from "./setAuthenticationCookie.js";
|
|
2
2
|
import _unsetAuthenticationCookie from "./unsetAuthenticationCookie.js";
|
|
3
3
|
import defaultUserOutputFields from "./defaultUserOutputFields";
|
|
4
|
+
import deleteUser from "./deleteUser.js";
|
|
4
5
|
import getBrowserSafeUser from "./getBrowserSafeUser";
|
|
5
6
|
import login from "./login";
|
|
6
7
|
import recoverPassword from "./recoverPassword";
|
|
@@ -14,6 +15,7 @@ var accounts_default = {
|
|
|
14
15
|
_setAuthenticationCookie,
|
|
15
16
|
_unsetAuthenticationCookie,
|
|
16
17
|
defaultUserOutputFields,
|
|
18
|
+
deleteUser,
|
|
17
19
|
getBrowserSafeUser,
|
|
18
20
|
login,
|
|
19
21
|
recoverPassword,
|
|
@@ -6,6 +6,9 @@ import { isObject } from "../../validation/lib/typeValidators";
|
|
|
6
6
|
import getOutput from "../getOutput";
|
|
7
7
|
import typesMap from "../databases/typesMap";
|
|
8
8
|
import getTargetDatabaseProvider from "../databases/getTargetDatabaseProvider.js";
|
|
9
|
+
import stringToSnakeCase from "../databases/stringToSnakeCase.js";
|
|
10
|
+
import createMetadataTableColumns from "./createMetadataTableColumns.js";
|
|
11
|
+
import roles from "./roles/index.js";
|
|
9
12
|
const addSessionToUser = (userId = null, session = null) => {
|
|
10
13
|
try {
|
|
11
14
|
return runUserQuery("addSession", { userId, session });
|
|
@@ -27,6 +30,16 @@ const insertUserInDatabase = async (user = {}) => {
|
|
|
27
30
|
throw new Error(formatErrorString("signup.insertUserInDatabase", exception));
|
|
28
31
|
}
|
|
29
32
|
};
|
|
33
|
+
const sqlizeMetadata = (metadata = {}) => {
|
|
34
|
+
try {
|
|
35
|
+
return Object.entries(metadata).reduce((sqlized = {}, [key, value]) => {
|
|
36
|
+
sqlized[stringToSnakeCase(key)] = value;
|
|
37
|
+
return sqlized;
|
|
38
|
+
}, {});
|
|
39
|
+
} catch (exception) {
|
|
40
|
+
throw new Error(`[actionName.sqlizeMetadata] ${exception.message}`);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
30
43
|
const getUserToCreate = async (options = {}) => {
|
|
31
44
|
try {
|
|
32
45
|
const usersDatabase = getTargetDatabaseProvider("users");
|
|
@@ -40,12 +53,19 @@ const getUserToCreate = async (options = {}) => {
|
|
|
40
53
|
if (options?.username) {
|
|
41
54
|
user.username = options?.username;
|
|
42
55
|
}
|
|
43
|
-
if (options?.metadata && isObject(options.metadata) && usersDatabaseType === "sql"
|
|
44
|
-
|
|
56
|
+
if (options?.metadata && isObject(options.metadata) && usersDatabaseType === "sql") {
|
|
57
|
+
const sqlizedMetadata = sqlizeMetadata(options.metadata);
|
|
58
|
+
await createMetadataTableColumns(usersDatabase, sqlizedMetadata);
|
|
59
|
+
const { roles: [], ...restOfMetadata } = options?.metadata;
|
|
60
|
+
user = {
|
|
61
|
+
...sqlizeMetadata(restOfMetadata),
|
|
62
|
+
...user
|
|
63
|
+
};
|
|
45
64
|
}
|
|
46
65
|
if (options?.metadata && isObject(options.metadata) && usersDatabaseType === "nosql") {
|
|
66
|
+
const { roles: [], ...restOfMetadata } = options?.metadata;
|
|
47
67
|
user = {
|
|
48
|
-
...
|
|
68
|
+
...restOfMetadata || {},
|
|
49
69
|
...user
|
|
50
70
|
};
|
|
51
71
|
}
|
|
@@ -70,15 +90,25 @@ const signup = async (options, { resolve, reject }) => {
|
|
|
70
90
|
return reject("Password is required.");
|
|
71
91
|
}
|
|
72
92
|
const existingUser = await getExistingUser(options.emailAddress, options.metadata?.username);
|
|
73
|
-
if (existingUser) {
|
|
93
|
+
if (existingUser && process.env.NODE_ENV !== "test") {
|
|
74
94
|
throw new Error(`A user with the ${existingUser.existingUsername ? "username" : "email address"} ${existingUser.existingUsername || existingUser.existingEmailAddress} already exists.`);
|
|
75
95
|
}
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
let userToCreate;
|
|
97
|
+
let userId = existingUser?._id || existingUser?.user_id;
|
|
98
|
+
if (!existingUser) {
|
|
99
|
+
userToCreate = await getUserToCreate(options);
|
|
100
|
+
userId = await insertUserInDatabase(userToCreate);
|
|
101
|
+
}
|
|
78
102
|
const user = await getUserByUserId(userId);
|
|
79
103
|
const session = generateSession();
|
|
80
104
|
if (user?._id || user?.user_id) {
|
|
81
|
-
await addSessionToUser(user
|
|
105
|
+
await addSessionToUser(user?._id || user?.user_id, session);
|
|
106
|
+
}
|
|
107
|
+
if (options?.metadata?.roles?.length > 0 && process.env.NODE_ENV === "test") {
|
|
108
|
+
for (let i = 0; i < options?.metadata?.roles?.length; i += 1) {
|
|
109
|
+
const role = options?.metadata?.roles[i];
|
|
110
|
+
roles.grant(user?._id || user?.user_id, role);
|
|
111
|
+
}
|
|
82
112
|
}
|
|
83
113
|
return resolve({
|
|
84
114
|
...session,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const createIndex = async (collectionName = "", index = {}) => {
|
|
2
|
+
return process.databases._users?.collection(collectionName).createIndex(index);
|
|
3
|
+
};
|
|
4
|
+
var createAccountsIndexes_default = async () => {
|
|
5
|
+
await createIndex("users", { _id: 1 });
|
|
6
|
+
await createIndex("users", { emailAddress: 1 });
|
|
7
|
+
await createIndex("users", { username: 1 });
|
|
8
|
+
await createIndex("users", { "sessions.token": 1 });
|
|
9
|
+
await createIndex("users", { "passwordResetTokens.token": 1 });
|
|
10
|
+
await createIndex("users", { "passwordResetTokens.token": 1, _id: 1 });
|
|
11
|
+
await createIndex("users", { roles: 1 });
|
|
12
|
+
await createIndex("users", { roles: 1, _id: 1 });
|
|
13
|
+
await createIndex("roles", { role: 1 });
|
|
14
|
+
await createIndex("roles", { role: 1, userId: 1 });
|
|
15
|
+
};
|
|
16
|
+
export {
|
|
17
|
+
createAccountsIndexes_default as default
|
|
18
|
+
};
|
|
@@ -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
|
|
20
|
+
ssl: !["development", "test"].includes(process.env.NODE_ENV),
|
|
21
21
|
...settings?.options || {}
|
|
22
22
|
};
|
|
23
23
|
if (settings?.options?.ca) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import generateId from "../../../../lib/generateId";
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
|
+
import utc from "dayjs/plugin/utc.js";
|
|
4
|
+
dayjs.extend(utc);
|
|
3
5
|
var accounts_default = {
|
|
4
6
|
existingUser: async (input = {}) => {
|
|
5
7
|
let existingUserWithEmailAddress;
|
|
@@ -35,6 +37,11 @@ var accounts_default = {
|
|
|
35
37
|
}
|
|
36
38
|
return null;
|
|
37
39
|
},
|
|
40
|
+
deleteUser: async (input = {}) => {
|
|
41
|
+
return process.databases._users?.collection("users").deleteOne({
|
|
42
|
+
_id: input?.userId
|
|
43
|
+
});
|
|
44
|
+
},
|
|
38
45
|
deleteOldSessions: async (input = {}) => {
|
|
39
46
|
const user = await process.databases._users?.collection("users").findOne({
|
|
40
47
|
_id: input?.userId
|
|
@@ -65,6 +72,7 @@ var accounts_default = {
|
|
|
65
72
|
const user = await process.databases._users?.collection("users").findOne({
|
|
66
73
|
"sessions.token": input?.token
|
|
67
74
|
});
|
|
75
|
+
console.log({ userWithLoginToken: user });
|
|
68
76
|
return user;
|
|
69
77
|
},
|
|
70
78
|
createVerifyEmailToken: async (input) => {
|
|
@@ -43,6 +43,9 @@ var accounts_default = {
|
|
|
43
43
|
}
|
|
44
44
|
return null;
|
|
45
45
|
},
|
|
46
|
+
deleteUser: async (input = {}) => {
|
|
47
|
+
await process.databases._users?.query(`DELETE FROM users WHERE user_id = $1`, [input?.userId]);
|
|
48
|
+
},
|
|
46
49
|
deleteOldSessions: async (input = {}) => {
|
|
47
50
|
await process.databases._users?.query(`DELETE FROM users_sessions WHERE user_id = $1 AND token_expires_at::date < NOW()`, [input?.userId]);
|
|
48
51
|
},
|
|
@@ -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
|
@@ -25,6 +25,7 @@ import runUploader from "./runUploader";
|
|
|
25
25
|
import generateId from "../lib/generateId.js";
|
|
26
26
|
import getOutput from "./getOutput.js";
|
|
27
27
|
import defaultUserOutputFields from "./accounts/defaultUserOutputFields.js";
|
|
28
|
+
import createMongoDBAccountsIndexes from "./databases/mongodb/createAccountsIndexes";
|
|
28
29
|
import createPostgreSQLAccountsTables from "./databases/postgresql/createAccountsTables";
|
|
29
30
|
import createPostgreSQLAccountsIndexes from "./databases/postgresql/createAccountsIndexes";
|
|
30
31
|
import loadSettings from "../settings/load.js";
|
|
@@ -32,8 +33,15 @@ import Queue from "./queues/index.js";
|
|
|
32
33
|
import readDirectory from "../lib/readDirectory.js";
|
|
33
34
|
import getBuildPath from "../lib/getBuildPath.js";
|
|
34
35
|
import generateMachineId from "../lib/generateMachineId.js";
|
|
36
|
+
import importFile from "../lib/importFile.js";
|
|
35
37
|
import emitWebsocketEvent from "../websockets/emitWebsocketEvent.js";
|
|
36
38
|
import getTargetDatabaseConnection from "./databases/getTargetDatabaseConnection.js";
|
|
39
|
+
import getAPIForDataFunctions from "../ssr/getAPIForDataFunctions.js";
|
|
40
|
+
import getBrowserSafeRequest from "./getBrowserSafeRequest.js";
|
|
41
|
+
import getDataFromComponent from "../ssr/getDataFromComponent.js";
|
|
42
|
+
import getTranslations from "./middleware/getTranslations.js";
|
|
43
|
+
import runUserQuery from "./accounts/runUserQuery.js";
|
|
44
|
+
import wait from "../lib/wait.js";
|
|
37
45
|
process.setMaxListeners(0);
|
|
38
46
|
class App {
|
|
39
47
|
constructor(options = {}) {
|
|
@@ -53,6 +61,7 @@ class App {
|
|
|
53
61
|
this.initWebsockets(options?.websockets || {});
|
|
54
62
|
this.initDevelopmentRoutes();
|
|
55
63
|
this.initAccounts();
|
|
64
|
+
this.initTests();
|
|
56
65
|
this.initDeploy();
|
|
57
66
|
this.initAPI(options?.api);
|
|
58
67
|
this.initRoutes(options?.routes);
|
|
@@ -85,6 +94,9 @@ class App {
|
|
|
85
94
|
const hasQueuesDatabase = settings?.config?.databases?.some((database = {}) => {
|
|
86
95
|
return !!database?.queues;
|
|
87
96
|
});
|
|
97
|
+
const hasMongoDBUsersDatabase = settings?.config?.databases?.some((database = {}) => {
|
|
98
|
+
return database?.provider === "mongodb" && database?.users;
|
|
99
|
+
});
|
|
88
100
|
const hasPostgreSQLUsersDatabase = settings?.config?.databases?.some((database = {}) => {
|
|
89
101
|
return database?.provider === "postgresql" && database?.users;
|
|
90
102
|
});
|
|
@@ -131,6 +143,9 @@ class App {
|
|
|
131
143
|
if (hasQueuesDatabase) {
|
|
132
144
|
process.databases._queues = getTargetDatabaseConnection("queues")?.connection;
|
|
133
145
|
}
|
|
146
|
+
if (hasMongoDBUsersDatabase) {
|
|
147
|
+
await createMongoDBAccountsIndexes();
|
|
148
|
+
}
|
|
134
149
|
if (hasPostgreSQLUsersDatabase) {
|
|
135
150
|
await createPostgreSQLAccountsTables();
|
|
136
151
|
await createPostgreSQLAccountsIndexes();
|
|
@@ -143,7 +158,9 @@ class App {
|
|
|
143
158
|
process.BUILD_ERROR = JSON.parse(message);
|
|
144
159
|
}
|
|
145
160
|
});
|
|
146
|
-
|
|
161
|
+
if (process.env.NODE_ENV !== "test") {
|
|
162
|
+
console.log(`App running at: http://localhost:${express.port}`);
|
|
163
|
+
}
|
|
147
164
|
}
|
|
148
165
|
setMachineId() {
|
|
149
166
|
generateMachineId();
|
|
@@ -156,6 +173,61 @@ class App {
|
|
|
156
173
|
fs.writeFileSync("./.joystick/PROCESS_ID", `${generateId(32)}`);
|
|
157
174
|
}
|
|
158
175
|
}
|
|
176
|
+
initTests() {
|
|
177
|
+
if (process.env.NODE_ENV === "test") {
|
|
178
|
+
this.express.app.get("/api/_test/bootstrap", async (req, res) => {
|
|
179
|
+
const buildPath = `${process.cwd()}/.joystick/build`;
|
|
180
|
+
const Component = req?.query?.pathToComponent ? await importFile(`${buildPath}/${req?.query?.pathToComponent}`) : null;
|
|
181
|
+
if (Component) {
|
|
182
|
+
const componentInstance = Component();
|
|
183
|
+
const apiForDataFunctions = await getAPIForDataFunctions(req, this?.options?.api);
|
|
184
|
+
const browserSafeRequest = getBrowserSafeRequest(req);
|
|
185
|
+
const data = await getDataFromComponent(componentInstance, apiForDataFunctions, browserSafeRequest);
|
|
186
|
+
const translations = await getTranslations({ build: buildPath, page: req?.query?.pathToComponent }, req);
|
|
187
|
+
const settings = loadSettings();
|
|
188
|
+
return res.status(200).send({
|
|
189
|
+
data: {
|
|
190
|
+
[data?.componentId]: data?.data
|
|
191
|
+
},
|
|
192
|
+
req: browserSafeRequest,
|
|
193
|
+
settings,
|
|
194
|
+
translations
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
res.status(200).send({ data: {}, translations: {} });
|
|
198
|
+
});
|
|
199
|
+
this.express.app.post("/api/_test/accounts/signup", async (req, res) => {
|
|
200
|
+
const signup = await accounts.signup({
|
|
201
|
+
emailAddress: req?.body?.emailAddress,
|
|
202
|
+
password: req?.body?.password,
|
|
203
|
+
metadata: req?.body?.metadata,
|
|
204
|
+
output: req?.body?.output || defaultUserOutputFields
|
|
205
|
+
});
|
|
206
|
+
res.status(200).send(JSON.stringify({
|
|
207
|
+
...signup?.user || {},
|
|
208
|
+
joystickLoginToken: signup?.token,
|
|
209
|
+
joystickLoginTokenExpiresAt: signup?.tokenExpiresAt
|
|
210
|
+
}));
|
|
211
|
+
});
|
|
212
|
+
this.express.app.post("/api/_test/accounts/login", async (req, res) => {
|
|
213
|
+
const login = await accounts.login({
|
|
214
|
+
emailAddress: req?.body?.emailAddress,
|
|
215
|
+
username: req?.body?.username,
|
|
216
|
+
password: req?.body?.password,
|
|
217
|
+
output: req?.body?.output || defaultUserOutputFields
|
|
218
|
+
});
|
|
219
|
+
res.status(200).send(JSON.stringify({
|
|
220
|
+
...login?.user || {},
|
|
221
|
+
joystickLoginToken: login?.token,
|
|
222
|
+
joystickLoginTokenExpiresAt: login?.tokenExpiresAt
|
|
223
|
+
}));
|
|
224
|
+
});
|
|
225
|
+
this.express.app.delete("/api/_test/accounts", async (req, res) => {
|
|
226
|
+
await runUserQuery("deleteUser", { userId: req?.body?.userId });
|
|
227
|
+
res.status(200).send({ data: {} });
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
159
231
|
initDeploy() {
|
|
160
232
|
if (process.env.NODE_ENV === "production" && process.env.IS_PUSH_DEPLOYED) {
|
|
161
233
|
this.express.app.get("/api/_push/pre-version", async (req, res) => {
|
|
@@ -401,7 +473,7 @@ class App {
|
|
|
401
473
|
});
|
|
402
474
|
}
|
|
403
475
|
initDevelopmentRoutes() {
|
|
404
|
-
if (process.env.NODE_ENV
|
|
476
|
+
if (["development", "test"].includes(process.env.NODE_ENV)) {
|
|
405
477
|
this.express.app.get("/api/_joystick/sessions", async (req, res) => {
|
|
406
478
|
const sessions = Array.from(this.sessions.entries())?.reduce((acc = {}, [key, value]) => {
|
|
407
479
|
acc[key] = value;
|
|
@@ -431,11 +503,20 @@ class App {
|
|
|
431
503
|
metadata: req?.body?.metadata,
|
|
432
504
|
output: req?.body?.output || defaultUserOutputFields
|
|
433
505
|
});
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
506
|
+
if (!process.env.NODE_ENV !== "test") {
|
|
507
|
+
accounts._setAuthenticationCookie(res, {
|
|
508
|
+
token: signup?.token,
|
|
509
|
+
tokenExpiresAt: signup?.tokenExpiresAt
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
const response = {
|
|
513
|
+
...signup?.user || {}
|
|
514
|
+
};
|
|
515
|
+
if (process.env.NODE_ENV === "test") {
|
|
516
|
+
response.joystickToken = signup?.token;
|
|
517
|
+
response.joystickLoginTokenExpiresAt = signup?.tokenExpiresAt;
|
|
518
|
+
}
|
|
519
|
+
res.status(200).send(JSON.stringify(response));
|
|
439
520
|
} catch (exception) {
|
|
440
521
|
console.log(exception);
|
|
441
522
|
return res.status(500).send(JSON.stringify({
|
|
@@ -451,11 +532,20 @@ class App {
|
|
|
451
532
|
password: req?.body?.password,
|
|
452
533
|
output: req?.body?.output || defaultUserOutputFields
|
|
453
534
|
});
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
535
|
+
if (!process.env.NODE_ENV !== "test") {
|
|
536
|
+
accounts._setAuthenticationCookie(res, {
|
|
537
|
+
token: login?.token,
|
|
538
|
+
tokenExpiresAt: login?.tokenExpiresAt
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
const response = {
|
|
542
|
+
...login?.user || {}
|
|
543
|
+
};
|
|
544
|
+
if (process.env.NODE_ENV === "test") {
|
|
545
|
+
response.joystickToken = login?.token;
|
|
546
|
+
response.joystickLoginTokenExpiresAt = login?.tokenExpiresAt;
|
|
547
|
+
}
|
|
548
|
+
res.status(200).send(JSON.stringify(response));
|
|
459
549
|
} catch (exception) {
|
|
460
550
|
console.log(exception);
|
|
461
551
|
return res.status(500).send(JSON.stringify({
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import importFile from "../../lib/importFile.js";
|
|
3
|
+
import { isObject } from "../../validation/lib/typeValidators.js";
|
|
4
|
+
import settings from "../../settings/index.js";
|
|
5
|
+
const getTranslationsFile = async (languageFilePath = "", paths = "") => {
|
|
6
|
+
const languageFile = await importFile(`${paths.build}/i18n/${languageFilePath}`);
|
|
7
|
+
const isValidLanguageFile = languageFile && isObject(languageFile);
|
|
8
|
+
if (isValidLanguageFile) {
|
|
9
|
+
const translationsForPage = languageFile[paths.page];
|
|
10
|
+
return translationsForPage ? translationsForPage : languageFile;
|
|
11
|
+
}
|
|
12
|
+
return {};
|
|
13
|
+
};
|
|
14
|
+
const getLanguagePreferenceRegexes = (userLanguage = "", browserLanguages = []) => {
|
|
15
|
+
let languagePreferences = [];
|
|
16
|
+
if (userLanguage) {
|
|
17
|
+
languagePreferences.push(userLanguage);
|
|
18
|
+
}
|
|
19
|
+
const filteredBrowserLanguages = browserLanguages?.filter((language) => {
|
|
20
|
+
return !language?.includes("*");
|
|
21
|
+
});
|
|
22
|
+
languagePreferences.push(...filteredBrowserLanguages);
|
|
23
|
+
languagePreferences.push(settings?.config?.i18n?.defaultLanguage);
|
|
24
|
+
return languagePreferences?.flatMap((language) => {
|
|
25
|
+
const variants = [language];
|
|
26
|
+
if (language?.length === 2) {
|
|
27
|
+
variants.push(`${language.substring(0, 2)}-`);
|
|
28
|
+
}
|
|
29
|
+
if (language?.length > 2) {
|
|
30
|
+
variants.push(`${language?.split("-")[0]}`);
|
|
31
|
+
variants.push(`${language?.split("-")[0]}-`);
|
|
32
|
+
}
|
|
33
|
+
return variants;
|
|
34
|
+
})?.map((languageString) => {
|
|
35
|
+
const lastCharacter = languageString[languageString.length - 1];
|
|
36
|
+
if (lastCharacter === "-") {
|
|
37
|
+
return new RegExp(`^${languageString}[A-Z]+.js`, "g");
|
|
38
|
+
}
|
|
39
|
+
return new RegExp(`^${languageString}.js`, "g");
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const parseBrowserLanguages = (languages = "") => {
|
|
43
|
+
const rawLanguages = languages.split(",");
|
|
44
|
+
return rawLanguages?.map((rawLanguage) => rawLanguage.split(";")[0]);
|
|
45
|
+
};
|
|
46
|
+
var getTranslations_default = async (paths = {}, req = {}) => {
|
|
47
|
+
const languageFiles = fs.readdirSync(`${paths.build}/i18n`);
|
|
48
|
+
const browserLanguages = parseBrowserLanguages(req?.headers["accept-language"]);
|
|
49
|
+
const languagePreferences = getLanguagePreferenceRegexes(req?.context?.user?.language, browserLanguages);
|
|
50
|
+
let matchingFile = null;
|
|
51
|
+
for (let i = 0; i < languagePreferences.length; i += 1) {
|
|
52
|
+
const languageRegex = languagePreferences[i];
|
|
53
|
+
const match = languageFiles.find((languageFile) => !!languageFile.match(languageRegex));
|
|
54
|
+
if (match) {
|
|
55
|
+
matchingFile = match;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const translationsFile = await getTranslationsFile(matchingFile, paths);
|
|
60
|
+
return translationsFile;
|
|
61
|
+
};
|
|
62
|
+
export {
|
|
63
|
+
getTranslations_default as default
|
|
64
|
+
};
|
|
@@ -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 =
|
|
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(`${
|
|
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(`${
|
|
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,10 @@ 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";
|
|
13
|
+
import importFile from "../../lib/importFile.js";
|
|
14
|
+
import getTranslations from "./getTranslations.js";
|
|
11
15
|
const generateHash = (input = "") => {
|
|
12
16
|
return crypto.createHash("sha256").update(input).digest("hex");
|
|
13
17
|
};
|
|
@@ -65,71 +69,12 @@ const getCachedHTML = ({ cachePath, cacheFileName, currentDiff }) => {
|
|
|
65
69
|
const getUrl = (request = {}) => {
|
|
66
70
|
const [path = null] = request.url?.split("?");
|
|
67
71
|
return {
|
|
68
|
-
params: request.params,
|
|
69
|
-
query: request.query,
|
|
70
|
-
route: request.route.path,
|
|
71
|
-
path
|
|
72
|
+
params: escapeKeyValuePair(request.params),
|
|
73
|
+
query: escapeKeyValuePair(request.query),
|
|
74
|
+
route: escapeHTML(request.route.path),
|
|
75
|
+
path: escapeHTML(path)
|
|
72
76
|
};
|
|
73
77
|
};
|
|
74
|
-
const getFile = async (buildPath = "") => {
|
|
75
|
-
const file = await import(buildPath);
|
|
76
|
-
return file.default;
|
|
77
|
-
};
|
|
78
|
-
const getTranslationsFile = async (languageFilePath = "", paths = "") => {
|
|
79
|
-
const languageFile = await getFile(`${paths.build}/i18n/${languageFilePath}`);
|
|
80
|
-
const isValidLanguageFile = languageFile && isObject(languageFile);
|
|
81
|
-
if (isValidLanguageFile) {
|
|
82
|
-
const translationsForPage = languageFile[paths.page];
|
|
83
|
-
return translationsForPage ? translationsForPage : languageFile;
|
|
84
|
-
}
|
|
85
|
-
return {};
|
|
86
|
-
};
|
|
87
|
-
const getTranslations = async (paths = {}, languagePreferences = []) => {
|
|
88
|
-
const languageFiles = fs.readdirSync(`${paths.build}/i18n`);
|
|
89
|
-
let matchingFile = null;
|
|
90
|
-
for (let i = 0; i < languagePreferences.length; i += 1) {
|
|
91
|
-
const languageRegex = languagePreferences[i];
|
|
92
|
-
const match = languageFiles.find((languageFile) => !!languageFile.match(languageRegex));
|
|
93
|
-
if (match) {
|
|
94
|
-
matchingFile = match;
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const translationsFile = await getTranslationsFile(matchingFile, paths);
|
|
99
|
-
return translationsFile;
|
|
100
|
-
};
|
|
101
|
-
const getLanguagePreferenceRegexes = (userLanguage = "", browserLanguages = []) => {
|
|
102
|
-
let languagePreferences = [];
|
|
103
|
-
if (userLanguage) {
|
|
104
|
-
languagePreferences.push(userLanguage);
|
|
105
|
-
}
|
|
106
|
-
const filteredBrowserLanguages = browserLanguages?.filter((language) => {
|
|
107
|
-
return !language?.includes("*");
|
|
108
|
-
});
|
|
109
|
-
languagePreferences.push(...filteredBrowserLanguages);
|
|
110
|
-
languagePreferences.push(settings?.config?.i18n?.defaultLanguage);
|
|
111
|
-
return languagePreferences?.flatMap((language) => {
|
|
112
|
-
const variants = [language];
|
|
113
|
-
if (language?.length === 2) {
|
|
114
|
-
variants.push(`${language.substring(0, 2)}-`);
|
|
115
|
-
}
|
|
116
|
-
if (language?.length > 2) {
|
|
117
|
-
variants.push(`${language?.split("-")[0]}`);
|
|
118
|
-
variants.push(`${language?.split("-")[0]}-`);
|
|
119
|
-
}
|
|
120
|
-
return variants;
|
|
121
|
-
})?.map((languageString) => {
|
|
122
|
-
const lastCharacter = languageString[languageString.length - 1];
|
|
123
|
-
if (lastCharacter === "-") {
|
|
124
|
-
return new RegExp(`^${languageString}[A-Z]+.js`, "g");
|
|
125
|
-
}
|
|
126
|
-
return new RegExp(`^${languageString}.js`, "g");
|
|
127
|
-
});
|
|
128
|
-
};
|
|
129
|
-
const parseBrowserLanguages = (languages = "") => {
|
|
130
|
-
const rawLanguages = languages.split(",");
|
|
131
|
-
return rawLanguages?.map((rawLanguage) => rawLanguage.split(";")[0]);
|
|
132
|
-
};
|
|
133
78
|
var render_default = (req, res, next, appInstance = {}) => {
|
|
134
79
|
res.render = async function(path = "", options = {}) {
|
|
135
80
|
const urlFormattedForCache = req?.url?.split("/")?.filter((part) => !!part)?.join("_");
|
|
@@ -167,13 +112,11 @@ var render_default = (req, res, next, appInstance = {}) => {
|
|
|
167
112
|
return res.send(cachedHTML);
|
|
168
113
|
}
|
|
169
114
|
}
|
|
170
|
-
const pageFile = await
|
|
115
|
+
const pageFile = await importFile(pagePath);
|
|
171
116
|
const Page = pageFile;
|
|
172
|
-
const layoutFile = layoutPath ? await
|
|
117
|
+
const layoutFile = layoutPath ? await importFile(layoutPath) : null;
|
|
173
118
|
const Layout = layoutFile;
|
|
174
|
-
const
|
|
175
|
-
const languagePreferenceRegexes = getLanguagePreferenceRegexes(req?.context?.user?.language, browserLanguages);
|
|
176
|
-
const translations = await getTranslations({ build: buildPath, page: path }, languagePreferenceRegexes);
|
|
119
|
+
const translations = await getTranslations({ build: buildPath, page: path }, req);
|
|
177
120
|
const url = getUrl(req);
|
|
178
121
|
const props = { ...options?.props || {} };
|
|
179
122
|
if (layoutPath && fs.existsSync(layoutPath)) {
|