Skip to content
This repository was archived by the owner on Sep 9, 2024. It is now read-only.

Commit 5e44120

Browse files
committed
Version 1.1.0
1 parent cffc76c commit 5e44120

File tree

11 files changed

+1380
-3281
lines changed

11 files changed

+1380
-3281
lines changed

.prettierrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"arrowParens": "avoid",
3+
"trailingComma": "all",
4+
"singleQuote": true,
5+
"printWidth": 100
6+
}

package.json

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@staticcms/proxy-server",
33
"description": "Proxy server to be used with Static CMS proxy backend",
4-
"version": "1.0.15",
4+
"version": "1.1.0",
55
"repository": "https://github.com/staticjscms/static-cms-proxy-server",
66
"bugs": "https://github.com/staticjscms/static-cms-proxy-server/issues",
77
"license": "MIT",
@@ -26,33 +26,32 @@
2626
"test": "jest"
2727
},
2828
"dependencies": {
29-
"async-mutex": "0.3.2",
29+
"async-mutex": "0.4.0",
3030
"cors": "2.8.5",
31-
"dotenv": "10.0.0",
31+
"dotenv": "16.0.3",
3232
"express": "4.18.2",
3333
"joi": "17.7.0",
3434
"morgan": "1.10.0",
35-
"simple-git": "3.14.1",
35+
"simple-git": "3.15.1",
3636
"winston": "3.8.2"
3737
},
3838
"devDependencies": {
39-
"@types/cors": "2.8.12",
40-
"@types/express": "4.17.14",
41-
"@types/hapi__joi": "17.1.8",
42-
"@types/jest": "27.5.2",
43-
"@types/morgan": "1.9.3",
44-
"@types/node": "16.11.64",
45-
"@types/vfile-message": "2.0.0",
39+
"@types/cors": "2.8.13",
40+
"@types/express": "4.17.15",
41+
"@types/jest": "29.2.5",
42+
"@types/morgan": "1.9.4",
43+
"@types/node": "^16.11.64",
4644
"cross-env": "7.0.3",
47-
"jest": "27.5.1",
45+
"jest": "29.3.1",
4846
"nodemon": "2.0.20",
49-
"ts-jest": "27.1.5",
50-
"ts-loader": "8.4.0",
47+
"prettier": "2.8.2",
48+
"ts-jest": "29.0.3",
49+
"ts-loader": "9.4.2",
5150
"ts-node": "10.9.1",
52-
"tsconfig-paths-webpack-plugin": "3.5.2",
53-
"typescript": "4.8.4",
54-
"webpack": "4.46.0",
55-
"webpack-cli": "4.10.0",
51+
"tsconfig-paths-webpack-plugin": "4.0.0",
52+
"typescript": "4.9.4",
53+
"webpack": "5.75.0",
54+
"webpack-cli": "5.0.1",
5655
"webpack-node-externals": "3.0.0"
5756
},
5857
"engines": {

src/middlewares/joi/customValidators.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import Joi from "@hapi/joi";
2-
import path from "path";
1+
import Joi from 'joi';
2+
import path from 'path';
33

44
export function pathTraversal(repoPath: string) {
55
return Joi.extend({
6-
type: "path",
6+
type: 'path',
77
base: Joi.string().required(),
88
messages: {
9-
"path.invalid": "{{#label}} must resolve to a path under the configured repository",
9+
'path.invalid': '{{#label}} must resolve to a path under the configured repository',
1010
},
1111
validate(value, helpers) {
1212
const resolvedPath = path.join(repoPath, value);
1313
if (!resolvedPath.startsWith(repoPath)) {
14-
return { value, errors: helpers.error("path.invalid") };
14+
return { value, errors: helpers.error('path.invalid') };
1515
}
1616
},
1717
}).path();

src/middlewares/joi/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { defaultSchema, joi } from '.';
22

33
import type express from 'express';
4-
import type Joi from '@hapi/joi';
4+
import type Joi from 'joi';
55

66
function assetFailure(result: Joi.ValidationResult, expectedMessage: string) {
77
const { error } = result;

src/middlewares/joi/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Joi from '@hapi/joi';
1+
import Joi from 'joi';
22

33
import type express from 'express';
44

Lines changed: 166 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
/* eslint-disable @typescript-eslint/no-var-requires */
2-
import { getSchema } from ".";
1+
import winston from 'winston';
2+
import { join } from 'path';
33

4-
import type Joi from "@hapi/joi";
5-
import { join } from "path";
4+
import { getSchema, localFsMiddleware } from '.';
5+
import { listRepoFiles } from '../utils/fs';
6+
7+
import type Joi from 'joi';
8+
import type express from 'express';
9+
10+
jest.mock('../utils/fs');
611

712
function assetFailure(result: Joi.ValidationResult, expectedMessage: string) {
813
const { error } = result;
@@ -13,81 +18,208 @@ function assetFailure(result: Joi.ValidationResult, expectedMessage: string) {
1318
}
1419

1520
const defaultParams = {
16-
branch: "master",
21+
branch: 'master',
1722
};
1823

19-
describe("localFsMiddleware", () => {
24+
describe('localFsMiddleware', () => {
2025
beforeEach(() => {
2126
jest.clearAllMocks();
2227
});
2328

24-
describe("getSchema", () => {
25-
it("should throw on path traversal", () => {
26-
const schema = getSchema({ repoPath: join("Users", "user", "documents", "code", "repo") });
29+
describe('getSchema', () => {
30+
it('should throw on path traversal', () => {
31+
const schema = getSchema({ repoPath: join('Users', 'user', 'documents', 'code', 'repo') });
2732

2833
assetFailure(
2934
schema.validate({
30-
action: "getEntry",
31-
params: { ...defaultParams, path: "../" },
35+
action: 'getEntry',
36+
params: { ...defaultParams, path: '../' },
3237
}),
33-
'"params.path" must resolve to a path under the configured repository'
38+
'"params.path" must resolve to a path under the configured repository',
3439
);
3540
});
3641

37-
it("should not throw on valid path", () => {
38-
const schema = getSchema({ repoPath: join("Users", "user", "documents", "code", "repo") });
42+
it('should not throw on valid path', () => {
43+
const schema = getSchema({ repoPath: join('Users', 'user', 'documents', 'code', 'repo') });
3944

4045
const { error } = schema.validate({
41-
action: "getEntry",
42-
params: { ...defaultParams, path: "src/content/posts/title.md" },
46+
action: 'getEntry',
47+
params: { ...defaultParams, path: 'src/content/posts/title.md' },
4348
});
4449

4550
expect(error).toBeUndefined();
4651
});
4752

48-
it("should throw on folder traversal", () => {
49-
const schema = getSchema({ repoPath: join("Users", "user", "documents", "code", "repo") });
53+
it('should throw on folder traversal', () => {
54+
const schema = getSchema({ repoPath: join('Users', 'user', 'documents', 'code', 'repo') });
5055

5156
assetFailure(
5257
schema.validate({
53-
action: "entriesByFolder",
54-
params: { ...defaultParams, folder: "../", extension: "md", depth: 1 },
58+
action: 'entriesByFolder',
59+
params: { ...defaultParams, folder: '../', extension: 'md', depth: 1 },
5560
}),
56-
'"params.folder" must resolve to a path under the configured repository'
61+
'"params.folder" must resolve to a path under the configured repository',
5762
);
5863
});
5964

60-
it("should not throw on valid folder", () => {
61-
const schema = getSchema({ repoPath: join("Users", "user", "documents", "code", "repo") });
65+
it('should not throw on valid folder', () => {
66+
const schema = getSchema({ repoPath: join('Users', 'user', 'documents', 'code', 'repo') });
6267

6368
const { error } = schema.validate({
64-
action: "entriesByFolder",
65-
params: { ...defaultParams, folder: "src/posts", extension: "md", depth: 1 },
69+
action: 'entriesByFolder',
70+
params: { ...defaultParams, folder: 'src/posts', extension: 'md', depth: 1 },
6671
});
6772

6873
expect(error).toBeUndefined();
6974
});
7075

71-
it("should throw on media folder traversal", () => {
72-
const schema = getSchema({ repoPath: join("Users", "user", "documents", "code", "repo") });
76+
it('should throw on media folder traversal', () => {
77+
const schema = getSchema({ repoPath: join('Users', 'user', 'documents', 'code', 'repo') });
7378

7479
assetFailure(
7580
schema.validate({
76-
action: "getMedia",
77-
params: { ...defaultParams, mediaFolder: "../" },
81+
action: 'getMedia',
82+
params: { ...defaultParams, mediaFolder: '../' },
7883
}),
79-
'"params.mediaFolder" must resolve to a path under the configured repository'
84+
'"params.mediaFolder" must resolve to a path under the configured repository',
8085
);
8186
});
8287

83-
it("should not throw on valid folder", () => {
84-
const schema = getSchema({ repoPath: join("Users", "user", "documents", "code", "repo") });
88+
it('should not throw on valid folder', () => {
89+
const schema = getSchema({ repoPath: join('Users', 'user', 'documents', 'code', 'repo') });
8590
const { error } = schema.validate({
86-
action: "getMedia",
87-
params: { ...defaultParams, mediaFolder: "static/images" },
91+
action: 'getMedia',
92+
params: { ...defaultParams, mediaFolder: 'static/images' },
8893
});
8994

9095
expect(error).toBeUndefined();
9196
});
9297
});
98+
99+
describe('getMedia', () => {
100+
it('should get media files', async () => {
101+
(listRepoFiles as jest.Mock).mockResolvedValue([
102+
'mediaFolder/asset1.jpg',
103+
'mediaFolder/asset2.jpg',
104+
'mediaFolder/asset3.jpg',
105+
]);
106+
107+
const json = jest.fn();
108+
const res: express.Response = { json } as unknown as express.Response;
109+
110+
const req = {
111+
body: {
112+
action: 'getMedia',
113+
params: {
114+
mediaFolder: 'mediaFolder',
115+
branch: 'main',
116+
},
117+
},
118+
} as express.Request;
119+
120+
const repoPath = '.';
121+
122+
await localFsMiddleware({ repoPath, logger: winston.createLogger() })(req, res);
123+
124+
expect(json).toHaveBeenCalledTimes(1);
125+
expect(json).toHaveBeenCalledWith([
126+
{
127+
path: 'mediaFolder/asset1.jpg',
128+
url: 'mediaFolder/asset1.jpg',
129+
},
130+
{
131+
path: 'mediaFolder/asset2.jpg',
132+
url: 'mediaFolder/asset2.jpg',
133+
},
134+
{
135+
path: 'mediaFolder/asset3.jpg',
136+
url: 'mediaFolder/asset3.jpg',
137+
},
138+
]);
139+
});
140+
141+
it('should translate media path to public path even when media path is absolute', async () => {
142+
(listRepoFiles as jest.Mock).mockResolvedValue([
143+
'mediaFolder/asset1.jpg',
144+
'mediaFolder/asset2.jpg',
145+
'mediaFolder/asset3.jpg',
146+
]);
147+
148+
const json = jest.fn();
149+
const res: express.Response = { json } as unknown as express.Response;
150+
151+
const req = {
152+
body: {
153+
action: 'getMedia',
154+
params: {
155+
mediaFolder: '/mediaFolder',
156+
publicFolder: '/publicFolder',
157+
branch: 'main',
158+
},
159+
},
160+
} as express.Request;
161+
162+
const repoPath = '.';
163+
164+
await localFsMiddleware({ repoPath, logger: winston.createLogger() })(req, res);
165+
166+
expect(json).toHaveBeenCalledTimes(1);
167+
expect(json).toHaveBeenCalledWith([
168+
{
169+
path: 'mediaFolder/asset1.jpg',
170+
url: '/publicFolder/asset1.jpg',
171+
},
172+
{
173+
path: 'mediaFolder/asset2.jpg',
174+
url: '/publicFolder/asset2.jpg',
175+
},
176+
{
177+
path: 'mediaFolder/asset3.jpg',
178+
url: '/publicFolder/asset3.jpg',
179+
},
180+
]);
181+
});
182+
183+
it('should translate media path to public path', async () => {
184+
(listRepoFiles as jest.Mock).mockResolvedValue([
185+
'mediaFolder/asset1.jpg',
186+
'mediaFolder/asset2.jpg',
187+
'mediaFolder/asset3.jpg',
188+
]);
189+
190+
const json = jest.fn();
191+
const res: express.Response = { json } as unknown as express.Response;
192+
193+
const req = {
194+
body: {
195+
action: 'getMedia',
196+
params: {
197+
mediaFolder: 'mediaFolder',
198+
publicFolder: '/publicFolder',
199+
branch: 'main',
200+
},
201+
},
202+
} as express.Request;
203+
204+
const repoPath = '.';
205+
206+
await localFsMiddleware({ repoPath, logger: winston.createLogger() })(req, res);
207+
208+
expect(json).toHaveBeenCalledTimes(1);
209+
expect(json).toHaveBeenCalledWith([
210+
{
211+
path: 'mediaFolder/asset1.jpg',
212+
url: '/publicFolder/asset1.jpg',
213+
},
214+
{
215+
path: 'mediaFolder/asset2.jpg',
216+
url: '/publicFolder/asset2.jpg',
217+
},
218+
{
219+
path: 'mediaFolder/asset3.jpg',
220+
url: '/publicFolder/asset3.jpg',
221+
},
222+
]);
223+
});
224+
});
93225
});

0 commit comments

Comments
 (0)