Skip to content

Commit 5826e9b

Browse files
committed
https://github.com/haxtheweb/issues/security/advisories/GHSA-9jr9-8ff3-m894
1 parent 2843599 commit 5826e9b

20 files changed

+995
-912
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"scripts": {
1111
"dev:build": "npm run build && nodemon dist/app.js",
1212
"dev": "nodemon src/app.js",
13+
"dev:nologin": "nodemon src/local.js",
1314
"start": "open http://localhost:3000 && npm run dev",
1415
"build": "rm -rf dist && babel src --out-dir dist --copy-files --include-dotfiles && chmod 774 dist/local.js && chmod 774 dist/app.js && chmod 774 dist/cli.js",
1516
"release": "npm run build && commit-and-tag-version && git push --follow-tags origin main && npm publish",

src/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ var helmetPolicies = {
2222
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'", "'wasm-unsafe-eval'", "www.youtube.com"],
2323
styleSrc: ["'self'", "'unsafe-inline'", "data:", "https:"],
2424
mediaSrc: ["'self'", "data:", "https:"],
25-
imgSrc: ["'self'", "data:", "https:"],
25+
imgSrc: ["'self'", "data:", "https:", "http:", "blob:"],
2626
connectSrc: ["'self'", "https:", "ws:"],
2727
defaultSrc: ["'self'", "data:", "https:"],
2828
objectSrc: ["'none'"],

src/lib/HAXCMS.js

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,19 +1957,31 @@ class HAXCMSClass {
19571957
*/
19581958
validateRequestToken(token = null, value = '', query = {})
19591959
{
1960-
if (this.isCLI() || this.HAXCMS_DISABLE_JWT_CHECKS) {
1961-
return true;
1962-
}
1963-
// default token is POST
1964-
if (token == null && query['token']) {
1965-
token = query['token'];
1966-
}
1967-
if (token != null) {
1968-
if (token == this.getRequestToken(value)) {
1969-
return true;
1970-
}
1960+
if (this.isCLI() || this.HAXCMS_DISABLE_JWT_CHECKS) {
1961+
return true;
1962+
}
1963+
// default token is POST
1964+
if (token == null && query['token']) {
1965+
token = query['token'];
1966+
}
1967+
if (token != null) {
1968+
if (token == this.getRequestToken(value)) {
1969+
return true;
19711970
}
1972-
return false;
1971+
}
1972+
return false;
1973+
}
1974+
/**
1975+
* Get the active user name based on the session
1976+
* or the super user if the session is not set
1977+
*/
1978+
getActiveUserName() {
1979+
if (this.user.name != null && this.user.name != '') {
1980+
return this.user.name;
1981+
}
1982+
else if (this.superUser.name) {
1983+
return this.superUser.name;
1984+
}
19731985
}
19741986
getRequestToken(value = '')
19751987
{
@@ -1980,7 +1992,7 @@ class HAXCMSClass {
19801992
var buf1 = crypto.createHmac("sha256", "0").update(data).digest();
19811993
var buf2 = Buffer.from(key);
19821994
// generate the hash
1983-
return Buffer.concat([buf1, buf2]).toString('base64');
1995+
return Buffer.concat([buf1, buf2]).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
19841996
}
19851997
/**
19861998
* load form and spitting out HAXschema + values in our standard transmission method
@@ -2462,7 +2474,7 @@ class HAXCMSClass {
24622474
/**
24632475
* Generate a valid HAX App store specification schema for connecting to this site via JSON.
24642476
*/
2465-
siteConnectionJSON()
2477+
siteConnectionJSON(siteToken = '')
24662478
{
24672479
return {
24682480
"details": {
@@ -2479,7 +2491,7 @@ class HAXCMSClass {
24792491
"operations": {
24802492
"browse": {
24812493
"method": "GET",
2482-
"endPoint": this.systemRequestBase + "listFiles",
2494+
"endPoint": this.systemRequestBase + "listFiles?site_token=" + siteToken,
24832495
"pagination": {
24842496
"style": "link",
24852497
"props": {
@@ -2518,7 +2530,7 @@ class HAXCMSClass {
25182530
},
25192531
"add": {
25202532
"method": "POST",
2521-
"endPoint": this.systemRequestBase + "saveFile",
2533+
"endPoint": this.systemRequestBase + "saveFile?site_token=" + siteToken,
25222534
"acceptsGizmoTypes": [
25232535
"audio",
25242536
"image",

src/openapi/spec.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@
242242
"operationId": "Operations::generateAppStore",
243243
"parameters": [
244244
{
245-
"name": "app-store-token",
245+
"name": "appstore_token",
246246
"in": "query",
247247
"description": "security token for appstore",
248248
"required": true,

src/openapi/spec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ paths:
167167
operationId: 'Operations::generateAppStore'
168168
parameters:
169169
-
170-
name: app-store-token
170+
name: appstore_token
171171
in: query
172172
description: 'security token for appstore'
173173
required: true

src/routes/archiveSite.js

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,26 @@ const { HAXCMS } = require('../lib/HAXCMS.js');
3636
* )
3737
*/
3838
async function archiveSite(req, res) {
39-
let site = await HAXCMS.loadSite(req.body['site']['name']);
40-
if (site.name) {
41-
// create archived directory in this tree if it doesn't exist already
42-
if (!fs.existsSync(HAXCMS.HAXCMS_ROOT + HAXCMS.archivedDirectory)) {
43-
fs.mkdirSync(HAXCMS.HAXCMS_ROOT + HAXCMS.archivedDirectory);
39+
if (req.query['user_token'] && HAXCMS.validateRequestToken(req.query['user_token'], HAXCMS.getActiveUserName())) {
40+
let site = await HAXCMS.loadSite(req.body['site']['name']);
41+
if (site.name) {
42+
// create archived directory in this tree if it doesn't exist already
43+
if (!fs.existsSync(HAXCMS.HAXCMS_ROOT + HAXCMS.archivedDirectory)) {
44+
fs.mkdirSync(HAXCMS.HAXCMS_ROOT + HAXCMS.archivedDirectory);
45+
}
46+
await fs.rename(
47+
HAXCMS.HAXCMS_ROOT + HAXCMS.sitesDirectory + '/' + site.manifest.metadata.site.name,
48+
HAXCMS.HAXCMS_ROOT + HAXCMS.archivedDirectory + '/' + site.manifest.metadata.site.name);
49+
res.send({
50+
'name': site.name,
51+
'detail': 'Site archived',
52+
});
4453
}
45-
await fs.rename(
46-
HAXCMS.HAXCMS_ROOT + HAXCMS.sitesDirectory + '/' + site.manifest.metadata.site.name,
47-
HAXCMS.HAXCMS_ROOT + HAXCMS.archivedDirectory + '/' + site.manifest.metadata.site.name);
48-
res.send({
49-
'name': site.name,
50-
'detail': 'Site archived',
51-
});
52-
}
53-
else {
54-
res.sendStatus(500);
54+
else {
55+
res.sendStatus(500);
56+
}
57+
} else {
58+
res.sendStatus(403);
5559
}
5660
}
5761
module.exports = archiveSite;

src/routes/cloneSite.js

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,48 +35,52 @@ const { HAXCMS } = require('../lib/HAXCMS.js');
3535
* )
3636
*/
3737
async function cloneSite(req, res) {
38-
let site = await HAXCMS.loadSite(req.body['site']['name']);
39-
let originalPathForReplacement = HAXCMS.sitesDirectory + site.manifest.metadata.site.name + "/files/";
38+
if (req.query['user_token'] && HAXCMS.validateRequestToken(req.query['user_token'], HAXCMS.getActiveUserName())) {
39+
let site = await HAXCMS.loadSite(req.body['site']['name']);
40+
let originalPathForReplacement = HAXCMS.sitesDirectory + site.manifest.metadata.site.name + "/files/";
4041

41-
let cloneName = HAXCMS.getUniqueName(site.name);
42-
// ensure the path to the new folder is valid
43-
await HAXCMS.recurseCopy(
44-
HAXCMS.HAXCMS_ROOT + HAXCMS.sitesDirectory + '/' + site.name,
45-
HAXCMS.HAXCMS_ROOT + HAXCMS.sitesDirectory + '/' + cloneName
46-
);
47-
// we need to then load and rewrite the site name var or it will conflict given the name change
48-
let newSite = await HAXCMS.loadSite(cloneName);
49-
newSite.manifest.metadata.site.name = cloneName;
50-
newSite.manifest.id = HAXCMS.generateUUID();
51-
// loop through all items and rewrite the path to files as we cloned it
52-
for (var delta in newSite.manifest.items) {
53-
let item = newSite.manifest.items[delta];
54-
if (item.metadata.files) {
55-
for (var delta2 in item.metadata.files) {
56-
if (newSite.manifest.items[delta].metadata.files[delta2].path) {
57-
newSite.manifest.items[delta].metadata.files[delta2].path = newSite.manifest.items[delta].metadata.files[delta2].path.replace(
58-
originalPathForReplacement,
59-
'/sites/' + cloneName + '/files/',
60-
);
61-
}
62-
if (newSite.manifest.items[delta].metadata.files[delta2].fullUrl) {
63-
newSite.manifest.items[delta].metadata.files[delta2].fullUrl = newSite.manifest.items[delta].metadata.files[delta2].fullUrl.replace(
64-
originalPathForReplacement,
65-
'/sites/' + cloneName + '/files/',
66-
);
42+
let cloneName = HAXCMS.getUniqueName(site.name);
43+
// ensure the path to the new folder is valid
44+
await HAXCMS.recurseCopy(
45+
HAXCMS.HAXCMS_ROOT + HAXCMS.sitesDirectory + '/' + site.name,
46+
HAXCMS.HAXCMS_ROOT + HAXCMS.sitesDirectory + '/' + cloneName
47+
);
48+
// we need to then load and rewrite the site name var or it will conflict given the name change
49+
let newSite = await HAXCMS.loadSite(cloneName);
50+
newSite.manifest.metadata.site.name = cloneName;
51+
newSite.manifest.id = HAXCMS.generateUUID();
52+
// loop through all items and rewrite the path to files as we cloned it
53+
for (var delta in newSite.manifest.items) {
54+
let item = newSite.manifest.items[delta];
55+
if (item.metadata.files) {
56+
for (var delta2 in item.metadata.files) {
57+
if (newSite.manifest.items[delta].metadata.files[delta2].path) {
58+
newSite.manifest.items[delta].metadata.files[delta2].path = newSite.manifest.items[delta].metadata.files[delta2].path.replace(
59+
originalPathForReplacement,
60+
'/sites/' + cloneName + '/files/',
61+
);
62+
}
63+
if (newSite.manifest.items[delta].metadata.files[delta2].fullUrl) {
64+
newSite.manifest.items[delta].metadata.files[delta2].fullUrl = newSite.manifest.items[delta].metadata.files[delta2].fullUrl.replace(
65+
originalPathForReplacement,
66+
'/sites/' + cloneName + '/files/',
67+
);
68+
}
6769
}
6870
}
6971
}
70-
}
7172

72-
await newSite.save();
73-
res.send({
74-
'link':
75-
HAXCMS.basePath +
76-
HAXCMS.sitesDirectory +
77-
'/' +
78-
cloneName,
79-
'name': cloneName
80-
});
73+
await newSite.save();
74+
res.send({
75+
'link':
76+
HAXCMS.basePath +
77+
HAXCMS.sitesDirectory +
78+
'/' +
79+
cloneName,
80+
'name': cloneName
81+
});
82+
} else {
83+
res.sendStatus(403);
84+
}
8185
}
82-
module.exports = cloneSite;
86+
module.exports = cloneSite;

src/routes/connectionSettings.js

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,48 +22,62 @@ async function connectionSettings(req, res) {
2222
if (req.headers && req.headers.referer && !req.headers.referer.includes('/sites/')) {
2323
baseAPIPath = HAXCMS.systemRequestBase;
2424
}
25+
var sitename = '';
2526
// express gives this up on requests but doesn't know it ahead of time
2627
if (req.headers && req.headers.referer) {
2728
let details = new url.URL(req.headers.referer);
2829
HAXCMS.protocol = details.protocol.replace(':', '');
2930
HAXCMS.domain = details.host;
3031
HAXCMS.request_url = details;
32+
33+
const sitepath = req.headers.referer.replace(`${HAXCMS.protocol}://${HAXCMS.domain}${HAXCMS.basePath}${HAXCMS.sitesDirectory}/`, '');
34+
const siteparts = sitepath.split('/');
35+
// should always be at the base here
36+
sitename = siteparts[0];
3137
}
38+
const siteToken = HAXCMS.getRequestToken(HAXCMS.getActiveUserName() + ':' + sitename);
39+
// user token is just the name of the logged in user
40+
const userToken = HAXCMS.getRequestToken(HAXCMS.getActiveUserName());
3241
const returnData = JSON.stringify({
3342
token: HAXCMS.getRequestToken(),
43+
login: `${baseAPIPath}login`,
44+
refreshUrl: `${baseAPIPath}refreshAccessToken`,
45+
logout: `${baseAPIPath}logout`,
46+
connectionSettings: `${baseAPIPath}connectionSettings`,
47+
// enables redirecting back to site root if JWT really is dead
48+
redirectUrl: HAXCMS.basePath,
49+
saveNodePath: `${baseAPIPath}saveNode?site_token=${siteToken}`,
50+
saveManifestPath: `${baseAPIPath}saveManifest?site_token=${siteToken}`,
51+
saveOutlinePath: `${baseAPIPath}saveOutline?site_token=${siteToken}`,
52+
getSiteFieldsPath: `${baseAPIPath}formLoad?haxcms_form_id=siteSettings`,
53+
// form token to validate form submissions as unique to the session
3454
getFormToken: HAXCMS.getRequestToken('form'),
55+
createNodePath: `${baseAPIPath}createNode?site_token=${siteToken}`,
56+
deleteNodePath: `${baseAPIPath}deleteNode?site_token=${siteToken}`,
57+
58+
getUserDataPath: `${baseAPIPath}getUserData?user_token=${userToken}`,
59+
createSite: `${baseAPIPath}createSite?user_token=${userToken}`,
60+
downloadSite: `${baseAPIPath}downloadSite?user_token=${userToken}`,
61+
archiveSite: `${baseAPIPath}archiveSite?user_token=${userToken}`,
62+
copySite: `${baseAPIPath}cloneSite?user_token=${userToken}`,
63+
getSitesList: `${baseAPIPath}listSites?user_token=${userToken}`,
3564
appStore: {
3665
url: `${baseAPIPath}generateAppStore`,
3766
params: {
38-
"app-store-token": HAXCMS.getRequestToken('appstore'),
67+
'appstore_token': HAXCMS.getRequestToken('appstore'),
68+
'site_token': siteToken,
69+
'siteName': sitename,
3970
}
4071
},
4172
themes: themes,
42-
connectionSettings: `${baseAPIPath}connectionSettings`,
43-
login: `${baseAPIPath}login`,
44-
refreshUrl: `${baseAPIPath}refreshAccessToken`,
45-
logout: `${baseAPIPath}logout`,
46-
redirectUrl: HAXCMS.basePath,
47-
saveNodePath: `${baseAPIPath}saveNode`,
48-
saveManifestPath: `${baseAPIPath}saveManifest`,
49-
saveOutlinePath: `${baseAPIPath}saveOutline`,
50-
getSiteFieldsPath: `${baseAPIPath}formLoad?haxcms_form_id=siteSettings`,
51-
createNodePath: `${baseAPIPath}createNode`,
52-
getUserDataPath: `${baseAPIPath}getUserData`,
53-
deleteNodePath: `${baseAPIPath}deleteNode`,
54-
createSite: `${baseAPIPath}createSite`,
55-
downloadSite: `${baseAPIPath}downloadSite`,
56-
archiveSite: `${baseAPIPath}archiveSite`,
57-
copySite: `${baseAPIPath}cloneSite`,
58-
getSitesList: `${baseAPIPath}listSites`,
5973
});
60-
let after;
74+
let after='';
6175
if (HAXCMS.HAXCMS_DISABLE_JWT_CHECKS) {
6276
after = `window.appSettings.jwt = "${HAXCMS.getJWT(HAXCMS.superUser.name)}"`;
6377
}
6478
res.send(`// force vercel calls to go from production
6579
window.MicroFrontendRegistryConfig = window.MicroFrontendRegistryConfig || {};
66-
window.MicroFrontendRegistryConfig.base = "https://haxapi.vercel.app";window.appSettings =${returnData};${after}`);
80+
window.MicroFrontendRegistryConfig.base = "https://open-apis.hax.cloud";window.appSettings =${returnData};${after}`);
6781
}
6882

6983
module.exports = connectionSettings;

0 commit comments

Comments
 (0)