@docbox-nz/hapi-gateway 0.2.1 → 0.3.0
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/.github/workflows/build.yml +30 -0
- package/.github/workflows/release.yml +37 -0
- package/.oxfmtrc.json +8 -0
- package/.oxlintrc.json +11 -0
- package/.zed/settings.json +10 -0
- package/README.md +32 -38
- package/cspell.json +1 -4
- package/dist/index.node.cjs +14922 -24139
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.esm.js +14906 -24128
- package/dist/index.node.esm.js.map +1 -1
- package/package.json +20 -30
- package/rolldown.config.js +19 -0
- package/src/index.ts +257 -257
- package/tsconfig.json +15 -15
- package/.eslintignore +0 -6
- package/.eslintrc +0 -131
- package/.prettierignore +0 -11
- package/.prettierrc +0 -6
- package/dist/api/adminService.d.ts +0 -16
- package/dist/api/boxService.d.ts +0 -43
- package/dist/api/client.d.ts +0 -45
- package/dist/api/docboxFile.d.ts +0 -15
- package/dist/api/fileService.d.ts +0 -251
- package/dist/api/folderService.d.ts +0 -45
- package/dist/api/linkService.d.ts +0 -115
- package/dist/api/taskService.d.ts +0 -23
- package/dist/api/utils.d.ts +0 -1
- package/dist/error.d.ts +0 -11
- package/dist/index.browser.cjs +0 -988
- package/dist/index.browser.cjs.map +0 -1
- package/dist/index.browser.esm.js +0 -984
- package/dist/index.browser.esm.js.map +0 -1
- package/dist/index.browser.js +0 -4390
- package/dist/index.browser.js.map +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.node.js +0 -20809
- package/dist/index.node.js.map +0 -1
- package/dist/options.d.ts +0 -85
- package/dist/types/box.d.ts +0 -59
- package/dist/types/file.d.ts +0 -371
- package/dist/types/folder.d.ts +0 -153
- package/dist/types/index.d.ts +0 -22
- package/dist/types/link.d.ts +0 -136
- package/dist/types/search.d.ts +0 -177
- package/dist/types/shared.d.ts +0 -94
- package/dist/types/user.d.ts +0 -20
- package/rollup.config.js +0 -36
package/package.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docbox-nz/hapi-gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Docbox hapi server gateway",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Jacobtread",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/docbox-nz/docbox-hapi-gateway.git"
|
|
10
|
+
},
|
|
4
11
|
"type": "module",
|
|
5
12
|
"main": "./dist/index.browser.cjs",
|
|
6
13
|
"types": "./dist/index.d.ts",
|
|
@@ -15,41 +22,24 @@
|
|
|
15
22
|
}
|
|
16
23
|
}
|
|
17
24
|
},
|
|
18
|
-
"author": "Jacobtread",
|
|
19
|
-
"license": "MIT",
|
|
20
|
-
"description": "Docbox hapi server gateway",
|
|
21
25
|
"dependencies": {
|
|
22
26
|
"@hapi/boom": "^10.0.1",
|
|
23
|
-
"axios": "^1.
|
|
24
|
-
},
|
|
25
|
-
"peerDependencies": {
|
|
26
|
-
"@hapi/hapi": "^20.1.0 || ^21.0.0"
|
|
27
|
+
"axios": "^1.16.1"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@hapi/boom": "^10.0.1",
|
|
30
|
-
"@
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"@
|
|
37
|
-
"eslint": "^8.55.0",
|
|
38
|
-
"eslint-config-airbnb": "^19.0.4",
|
|
39
|
-
"eslint-config-airbnb-typescript": "^18.0.0",
|
|
40
|
-
"eslint-config-prettier": "^9.1.0",
|
|
41
|
-
"eslint-import-resolver-typescript": "^3.6.1",
|
|
42
|
-
"eslint-plugin-import": "^2.29.0",
|
|
43
|
-
"eslint-plugin-perfectionist": "^2.5.0",
|
|
44
|
-
"eslint-plugin-prettier": "^5.0.1",
|
|
45
|
-
"eslint-plugin-unused-imports": "^3.0.0",
|
|
46
|
-
"prettier": "^3.1.0",
|
|
47
|
-
"rollup-plugin-typescript2": "^0.37.0",
|
|
48
|
-
"tslib": "^2.8.1",
|
|
49
|
-
"typescript": "^5.8.3"
|
|
31
|
+
"@types/node": "^24.12.4",
|
|
32
|
+
"oxfmt": "^0.51.0",
|
|
33
|
+
"oxlint": "^1.66.0",
|
|
34
|
+
"rolldown": "^1.0.1"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@hapi/hapi": "^20.1.0 || ^21.0.0"
|
|
50
38
|
},
|
|
51
39
|
"scripts": {
|
|
52
|
-
"build": "
|
|
53
|
-
"watch": "
|
|
40
|
+
"build": "rolldown -c",
|
|
41
|
+
"watch": "rolldown -c --watch",
|
|
42
|
+
"fmt": "oxfmt",
|
|
43
|
+
"lint": "oxlint"
|
|
54
44
|
}
|
|
55
45
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from 'rolldown';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
external: ['@hapi/hapi'],
|
|
5
|
+
input: 'src/index.ts',
|
|
6
|
+
output: [
|
|
7
|
+
{
|
|
8
|
+
file: 'dist/index.node.cjs',
|
|
9
|
+
format: 'cjs',
|
|
10
|
+
sourcemap: true,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
file: 'dist/index.node.esm.js',
|
|
14
|
+
format: 'esm',
|
|
15
|
+
sourcemap: true,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
platform: 'node',
|
|
19
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,257 +1,257 @@
|
|
|
1
|
-
import { badRequest, forbidden } from '@hapi/boom';
|
|
2
|
-
import type { Plugin, Request, ResponseToolkit, RouteOptions, ServerRoute } from '@hapi/hapi';
|
|
3
|
-
import axios from 'axios';
|
|
4
|
-
import type { AxiosInstance } from 'axios';
|
|
5
|
-
import { createHandleAxiosError } from './error';
|
|
6
|
-
import type { DocboxRequestTenant, DocboxRequestUser, PluginOptions } from './options';
|
|
7
|
-
|
|
8
|
-
const DEFAULT_BASE_PATH: string = '/';
|
|
9
|
-
|
|
10
|
-
export const DocboxGateway: Plugin<PluginOptions> = {
|
|
11
|
-
name: '@docbox-nz/hapi-gateway',
|
|
12
|
-
version: '0.1.0',
|
|
13
|
-
register: function (server, options) {
|
|
14
|
-
const routes = createDocboxRoutes(options);
|
|
15
|
-
for (const route of routes) {
|
|
16
|
-
server.route(route);
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export function createDocboxRoutes(options: PluginOptions) {
|
|
22
|
-
let basePath = options.basePath ?? DEFAULT_BASE_PATH;
|
|
23
|
-
|
|
24
|
-
// Remove trailing slashes from base path
|
|
25
|
-
if (basePath.endsWith('/')) basePath = basePath.substring(0, basePath.length - 1);
|
|
26
|
-
|
|
27
|
-
const baseRouteOptions: RouteOptions = options.baseRouteOptions ?? {};
|
|
28
|
-
const baseForwardRouteOptions: RouteOptions = options.baseForwardRouteOptions ?? {};
|
|
29
|
-
|
|
30
|
-
// Axios instance to perform connections
|
|
31
|
-
const axiosInstance = axios.create({
|
|
32
|
-
...options.axiosConfig,
|
|
33
|
-
baseURL: options.docboxBaseURL,
|
|
34
|
-
headers: {
|
|
35
|
-
'Content-Type': 'application/json',
|
|
36
|
-
Accept: 'application/json',
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Add interceptor to handle axios errors
|
|
41
|
-
axiosInstance.interceptors.response.use(
|
|
42
|
-
(res) => res,
|
|
43
|
-
createHandleAxiosError(options.docboxBaseURL)
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const getRequestUser: PluginOptions['getRequestUser'] = options.getRequestUser ?? (() => null);
|
|
47
|
-
const getApiKey: PluginOptions['getApiKey'] = options.getApiKey ?? (() => null);
|
|
48
|
-
|
|
49
|
-
// Base route for creating document boxes
|
|
50
|
-
const createBoxRoute: ServerRoute = {
|
|
51
|
-
path: `${basePath}/box`,
|
|
52
|
-
method: 'POST',
|
|
53
|
-
handler: async function (request, h) {
|
|
54
|
-
const payload = request.payload;
|
|
55
|
-
if (typeof payload !== 'object') {
|
|
56
|
-
return badRequest('request payload must be an object');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const scope = (payload as any).scope;
|
|
60
|
-
if (scope === undefined || typeof scope !== 'string') {
|
|
61
|
-
return badRequest('scope must be defined and a string');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const path = request.path.substring(basePath.length);
|
|
65
|
-
const isAllowed = await options.isAllowedWrite(request, scope, path);
|
|
66
|
-
if (!isAllowed) {
|
|
67
|
-
return forbidden("you don't have permission to access this resource");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const tenant = await options.getRequestTenant(request);
|
|
71
|
-
const user = await getRequestUser(request);
|
|
72
|
-
const apiKey = await getApiKey();
|
|
73
|
-
|
|
74
|
-
return forwardRequest(axiosInstance, request, tenant, user, apiKey, h, path);
|
|
75
|
-
},
|
|
76
|
-
options: baseRouteOptions,
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// Docbox read forwarding endpoint
|
|
80
|
-
const forwardReadRoute: ServerRoute = {
|
|
81
|
-
path: `${basePath}/box/{scope}/{path*}`,
|
|
82
|
-
method: ['GET'],
|
|
83
|
-
handler: async function (request, h) {
|
|
84
|
-
const scope = request.params.scope;
|
|
85
|
-
if (scope === undefined || typeof scope !== 'string') {
|
|
86
|
-
return badRequest('scope must be defined and a string');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const path = request.path.substring(basePath.length);
|
|
90
|
-
const isAllowed = await options.isAllowedRead(request, scope, path);
|
|
91
|
-
if (!isAllowed) {
|
|
92
|
-
return forbidden("you don't have permission to access this resource");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const tenant = await options.getRequestTenant(request);
|
|
96
|
-
const user = await getRequestUser(request);
|
|
97
|
-
const apiKey = await getApiKey();
|
|
98
|
-
|
|
99
|
-
return forwardRequest(axiosInstance, request, tenant, user, apiKey, h, path);
|
|
100
|
-
},
|
|
101
|
-
options: { ...baseRouteOptions, ...baseForwardRouteOptions },
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// Docbox write forwarding endpoint
|
|
105
|
-
const forwardWriteRoute: ServerRoute = {
|
|
106
|
-
path: `${basePath}/box/{scope}/{path*}`,
|
|
107
|
-
method: ['POST', 'PATCH', 'PUT', 'DELETE'],
|
|
108
|
-
handler: async function (request, h) {
|
|
109
|
-
const scope = request.params.scope;
|
|
110
|
-
if (scope === undefined || typeof scope !== 'string') {
|
|
111
|
-
return badRequest('scope must be defined and a string');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const path = request.path.substring(basePath.length);
|
|
115
|
-
|
|
116
|
-
// Searching uses the POST method but only requires read access
|
|
117
|
-
const isWriting = isWriteRequest(request, path);
|
|
118
|
-
|
|
119
|
-
const accessHandler = isWriting ? options.isAllowedWrite : options.isAllowedRead;
|
|
120
|
-
|
|
121
|
-
const isAllowed = await accessHandler(request, scope, path);
|
|
122
|
-
if (!isAllowed) {
|
|
123
|
-
return forbidden("you don't have permission to access this resource");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const tenant = await options.getRequestTenant(request);
|
|
127
|
-
const user = await getRequestUser(request);
|
|
128
|
-
const apiKey = await getApiKey();
|
|
129
|
-
|
|
130
|
-
return forwardRequest(axiosInstance, request, tenant, user, apiKey, h, path);
|
|
131
|
-
},
|
|
132
|
-
options: {
|
|
133
|
-
...baseRouteOptions,
|
|
134
|
-
...baseForwardRouteOptions,
|
|
135
|
-
payload: {
|
|
136
|
-
...baseRouteOptions.payload,
|
|
137
|
-
...baseForwardRouteOptions.payload,
|
|
138
|
-
|
|
139
|
-
// Payload must not be parsed so that files are properly sent to
|
|
140
|
-
// the docbox server
|
|
141
|
-
parse: false,
|
|
142
|
-
|
|
143
|
-
// Ensure we get our data as full buffers to forward onward, not
|
|
144
|
-
// preprocessed by HAPI
|
|
145
|
-
output: 'data',
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
return [createBoxRoute, forwardReadRoute, forwardWriteRoute];
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Additional check for write endpoints to see if the
|
|
155
|
-
* request is actually a write, as some endpoints such
|
|
156
|
-
* as search use the POST method
|
|
157
|
-
*
|
|
158
|
-
* @param request The HTTP request
|
|
159
|
-
* @param path The request path
|
|
160
|
-
* @returns Whether the request is a write request
|
|
161
|
-
*/
|
|
162
|
-
function isWriteRequest(request: Request, path: string) {
|
|
163
|
-
if (request.method === 'post') {
|
|
164
|
-
const parts = path.substring(1).split('/');
|
|
165
|
-
|
|
166
|
-
// Request is a search request (/box/{scope}/search)
|
|
167
|
-
if (parts.length === 3 && parts[0] === 'box' && parts[2] === 'search') {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Request is a file search request (/box/{scope}/file/{file_id}/search)
|
|
172
|
-
if (parts.length === 5 && parts[0] === 'box' && parts[2] === 'file' && parts[4] === 'search') {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Forwards the provided request onto the docbox server
|
|
182
|
-
*
|
|
183
|
-
* @param axios Axios instance to forward the request through
|
|
184
|
-
* @param request The request itself
|
|
185
|
-
* @param user User performing the request
|
|
186
|
-
* @param apiKey API key for requests
|
|
187
|
-
* @param h Response toolkit for creating the response
|
|
188
|
-
* @param path Path requested from the server
|
|
189
|
-
*/
|
|
190
|
-
async function forwardRequest(
|
|
191
|
-
axios: AxiosInstance,
|
|
192
|
-
request: Request,
|
|
193
|
-
tenant: DocboxRequestTenant,
|
|
194
|
-
user: DocboxRequestUser | null,
|
|
195
|
-
apiKey: string | null,
|
|
196
|
-
h: ResponseToolkit,
|
|
197
|
-
path: string
|
|
198
|
-
) {
|
|
199
|
-
const userHeaders: Record<string, any> = request.headers;
|
|
200
|
-
const forwardHeaders: Record<string, any> = {};
|
|
201
|
-
|
|
202
|
-
if (apiKey !== null) {
|
|
203
|
-
forwardHeaders['x-docbox-api-key'] = apiKey;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Forward content related headers
|
|
207
|
-
if (userHeaders['accept'] !== undefined) {
|
|
208
|
-
forwardHeaders['accept'] = userHeaders['accept'];
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (userHeaders['content-type'] !== undefined) {
|
|
212
|
-
forwardHeaders['content-type'] = userHeaders['content-type'];
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (userHeaders['content-length'] !== undefined) {
|
|
216
|
-
forwardHeaders['content-length'] = userHeaders['content-length'];
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Set tenant headers
|
|
220
|
-
forwardHeaders['x-tenant-env'] = tenant.env;
|
|
221
|
-
forwardHeaders['x-tenant-id'] = tenant.id;
|
|
222
|
-
|
|
223
|
-
// Set user headers
|
|
224
|
-
if (user !== null && user.id !== undefined) {
|
|
225
|
-
forwardHeaders['x-user-id'] = encodeURIComponent(user.id);
|
|
226
|
-
|
|
227
|
-
if (user.name) {
|
|
228
|
-
forwardHeaders['x-user-name'] = encodeURIComponent(user.name);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (user.imageId) {
|
|
232
|
-
forwardHeaders['x-user-image-id'] = encodeURIComponent(user.imageId);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Forward the request to docbox
|
|
237
|
-
const docboxResponse = await axios.request({
|
|
238
|
-
method: request.method.toUpperCase(),
|
|
239
|
-
url: path,
|
|
240
|
-
headers: forwardHeaders,
|
|
241
|
-
data: request.payload,
|
|
242
|
-
params: request.query,
|
|
243
|
-
responseType: 'stream',
|
|
244
|
-
// Don't throw if the status code is not an 2xx status code
|
|
245
|
-
// (we pass error statuses onto the consumer)
|
|
246
|
-
validateStatus: () => true,
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
let response = h.response(docboxResponse.data).code(docboxResponse.status);
|
|
250
|
-
|
|
251
|
-
// Set the response headers
|
|
252
|
-
Object.entries(docboxResponse.headers).forEach(
|
|
253
|
-
([name, value]) => (response = response.header(name, value))
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
return response;
|
|
257
|
-
}
|
|
1
|
+
import { badRequest, forbidden } from '@hapi/boom';
|
|
2
|
+
import type { Plugin, Request, ResponseToolkit, RouteOptions, ServerRoute } from '@hapi/hapi';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import type { AxiosInstance } from 'axios';
|
|
5
|
+
import { createHandleAxiosError } from './error';
|
|
6
|
+
import type { DocboxRequestTenant, DocboxRequestUser, PluginOptions } from './options';
|
|
7
|
+
|
|
8
|
+
const DEFAULT_BASE_PATH: string = '/';
|
|
9
|
+
|
|
10
|
+
export const DocboxGateway: Plugin<PluginOptions> = {
|
|
11
|
+
name: '@docbox-nz/hapi-gateway',
|
|
12
|
+
version: '0.1.0',
|
|
13
|
+
register: function (server, options) {
|
|
14
|
+
const routes = createDocboxRoutes(options);
|
|
15
|
+
for (const route of routes) {
|
|
16
|
+
server.route(route);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function createDocboxRoutes(options: PluginOptions) {
|
|
22
|
+
let basePath = options.basePath ?? DEFAULT_BASE_PATH;
|
|
23
|
+
|
|
24
|
+
// Remove trailing slashes from base path
|
|
25
|
+
if (basePath.endsWith('/')) basePath = basePath.substring(0, basePath.length - 1);
|
|
26
|
+
|
|
27
|
+
const baseRouteOptions: RouteOptions = options.baseRouteOptions ?? {};
|
|
28
|
+
const baseForwardRouteOptions: RouteOptions = options.baseForwardRouteOptions ?? {};
|
|
29
|
+
|
|
30
|
+
// Axios instance to perform connections
|
|
31
|
+
const axiosInstance = axios.create({
|
|
32
|
+
...options.axiosConfig,
|
|
33
|
+
baseURL: options.docboxBaseURL,
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
Accept: 'application/json',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Add interceptor to handle axios errors
|
|
41
|
+
axiosInstance.interceptors.response.use(
|
|
42
|
+
(res) => res,
|
|
43
|
+
createHandleAxiosError(options.docboxBaseURL)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const getRequestUser: PluginOptions['getRequestUser'] = options.getRequestUser ?? (() => null);
|
|
47
|
+
const getApiKey: PluginOptions['getApiKey'] = options.getApiKey ?? (() => null);
|
|
48
|
+
|
|
49
|
+
// Base route for creating document boxes
|
|
50
|
+
const createBoxRoute: ServerRoute = {
|
|
51
|
+
path: `${basePath}/box`,
|
|
52
|
+
method: 'POST',
|
|
53
|
+
handler: async function (request, h) {
|
|
54
|
+
const payload = request.payload;
|
|
55
|
+
if (typeof payload !== 'object') {
|
|
56
|
+
return badRequest('request payload must be an object');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const scope = (payload as any).scope;
|
|
60
|
+
if (scope === undefined || typeof scope !== 'string') {
|
|
61
|
+
return badRequest('scope must be defined and a string');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const path = request.path.substring(basePath.length);
|
|
65
|
+
const isAllowed = await options.isAllowedWrite(request, scope, path);
|
|
66
|
+
if (!isAllowed) {
|
|
67
|
+
return forbidden("you don't have permission to access this resource");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const tenant = await options.getRequestTenant(request);
|
|
71
|
+
const user = await getRequestUser(request);
|
|
72
|
+
const apiKey = await getApiKey();
|
|
73
|
+
|
|
74
|
+
return forwardRequest(axiosInstance, request, tenant, user, apiKey, h, path);
|
|
75
|
+
},
|
|
76
|
+
options: baseRouteOptions,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Docbox read forwarding endpoint
|
|
80
|
+
const forwardReadRoute: ServerRoute = {
|
|
81
|
+
path: `${basePath}/box/{scope}/{path*}`,
|
|
82
|
+
method: ['GET'],
|
|
83
|
+
handler: async function (request, h) {
|
|
84
|
+
const scope = request.params.scope;
|
|
85
|
+
if (scope === undefined || typeof scope !== 'string') {
|
|
86
|
+
return badRequest('scope must be defined and a string');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const path = request.path.substring(basePath.length);
|
|
90
|
+
const isAllowed = await options.isAllowedRead(request, scope, path);
|
|
91
|
+
if (!isAllowed) {
|
|
92
|
+
return forbidden("you don't have permission to access this resource");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const tenant = await options.getRequestTenant(request);
|
|
96
|
+
const user = await getRequestUser(request);
|
|
97
|
+
const apiKey = await getApiKey();
|
|
98
|
+
|
|
99
|
+
return forwardRequest(axiosInstance, request, tenant, user, apiKey, h, path);
|
|
100
|
+
},
|
|
101
|
+
options: { ...baseRouteOptions, ...baseForwardRouteOptions },
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Docbox write forwarding endpoint
|
|
105
|
+
const forwardWriteRoute: ServerRoute = {
|
|
106
|
+
path: `${basePath}/box/{scope}/{path*}`,
|
|
107
|
+
method: ['POST', 'PATCH', 'PUT', 'DELETE'],
|
|
108
|
+
handler: async function (request, h) {
|
|
109
|
+
const scope = request.params.scope;
|
|
110
|
+
if (scope === undefined || typeof scope !== 'string') {
|
|
111
|
+
return badRequest('scope must be defined and a string');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const path = request.path.substring(basePath.length);
|
|
115
|
+
|
|
116
|
+
// Searching uses the POST method but only requires read access
|
|
117
|
+
const isWriting = isWriteRequest(request, path);
|
|
118
|
+
|
|
119
|
+
const accessHandler = isWriting ? options.isAllowedWrite : options.isAllowedRead;
|
|
120
|
+
|
|
121
|
+
const isAllowed = await accessHandler(request, scope, path);
|
|
122
|
+
if (!isAllowed) {
|
|
123
|
+
return forbidden("you don't have permission to access this resource");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const tenant = await options.getRequestTenant(request);
|
|
127
|
+
const user = await getRequestUser(request);
|
|
128
|
+
const apiKey = await getApiKey();
|
|
129
|
+
|
|
130
|
+
return forwardRequest(axiosInstance, request, tenant, user, apiKey, h, path);
|
|
131
|
+
},
|
|
132
|
+
options: {
|
|
133
|
+
...baseRouteOptions,
|
|
134
|
+
...baseForwardRouteOptions,
|
|
135
|
+
payload: {
|
|
136
|
+
...baseRouteOptions.payload,
|
|
137
|
+
...baseForwardRouteOptions.payload,
|
|
138
|
+
|
|
139
|
+
// Payload must not be parsed so that files are properly sent to
|
|
140
|
+
// the docbox server
|
|
141
|
+
parse: false,
|
|
142
|
+
|
|
143
|
+
// Ensure we get our data as full buffers to forward onward, not
|
|
144
|
+
// preprocessed by HAPI
|
|
145
|
+
output: 'data',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return [createBoxRoute, forwardReadRoute, forwardWriteRoute];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Additional check for write endpoints to see if the
|
|
155
|
+
* request is actually a write, as some endpoints such
|
|
156
|
+
* as search use the POST method
|
|
157
|
+
*
|
|
158
|
+
* @param request The HTTP request
|
|
159
|
+
* @param path The request path
|
|
160
|
+
* @returns Whether the request is a write request
|
|
161
|
+
*/
|
|
162
|
+
function isWriteRequest(request: Request, path: string) {
|
|
163
|
+
if (request.method === 'post') {
|
|
164
|
+
const parts = path.substring(1).split('/');
|
|
165
|
+
|
|
166
|
+
// Request is a search request (/box/{scope}/search)
|
|
167
|
+
if (parts.length === 3 && parts[0] === 'box' && parts[2] === 'search') {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Request is a file search request (/box/{scope}/file/{file_id}/search)
|
|
172
|
+
if (parts.length === 5 && parts[0] === 'box' && parts[2] === 'file' && parts[4] === 'search') {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Forwards the provided request onto the docbox server
|
|
182
|
+
*
|
|
183
|
+
* @param axios Axios instance to forward the request through
|
|
184
|
+
* @param request The request itself
|
|
185
|
+
* @param user User performing the request
|
|
186
|
+
* @param apiKey API key for requests
|
|
187
|
+
* @param h Response toolkit for creating the response
|
|
188
|
+
* @param path Path requested from the server
|
|
189
|
+
*/
|
|
190
|
+
async function forwardRequest(
|
|
191
|
+
axios: AxiosInstance,
|
|
192
|
+
request: Request,
|
|
193
|
+
tenant: DocboxRequestTenant,
|
|
194
|
+
user: DocboxRequestUser | null,
|
|
195
|
+
apiKey: string | null,
|
|
196
|
+
h: ResponseToolkit,
|
|
197
|
+
path: string
|
|
198
|
+
) {
|
|
199
|
+
const userHeaders: Record<string, any> = request.headers;
|
|
200
|
+
const forwardHeaders: Record<string, any> = {};
|
|
201
|
+
|
|
202
|
+
if (apiKey !== null) {
|
|
203
|
+
forwardHeaders['x-docbox-api-key'] = apiKey;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Forward content related headers
|
|
207
|
+
if (userHeaders['accept'] !== undefined) {
|
|
208
|
+
forwardHeaders['accept'] = userHeaders['accept'];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (userHeaders['content-type'] !== undefined) {
|
|
212
|
+
forwardHeaders['content-type'] = userHeaders['content-type'];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (userHeaders['content-length'] !== undefined) {
|
|
216
|
+
forwardHeaders['content-length'] = userHeaders['content-length'];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Set tenant headers
|
|
220
|
+
forwardHeaders['x-tenant-env'] = tenant.env;
|
|
221
|
+
forwardHeaders['x-tenant-id'] = tenant.id;
|
|
222
|
+
|
|
223
|
+
// Set user headers
|
|
224
|
+
if (user !== null && user.id !== undefined) {
|
|
225
|
+
forwardHeaders['x-user-id'] = encodeURIComponent(user.id);
|
|
226
|
+
|
|
227
|
+
if (user.name) {
|
|
228
|
+
forwardHeaders['x-user-name'] = encodeURIComponent(user.name);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (user.imageId) {
|
|
232
|
+
forwardHeaders['x-user-image-id'] = encodeURIComponent(user.imageId);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Forward the request to docbox
|
|
237
|
+
const docboxResponse = await axios.request({
|
|
238
|
+
method: request.method.toUpperCase(),
|
|
239
|
+
url: path,
|
|
240
|
+
headers: forwardHeaders,
|
|
241
|
+
data: request.payload,
|
|
242
|
+
params: request.query,
|
|
243
|
+
responseType: 'stream',
|
|
244
|
+
// Don't throw if the status code is not an 2xx status code
|
|
245
|
+
// (we pass error statuses onto the consumer)
|
|
246
|
+
validateStatus: () => true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
let response = h.response(docboxResponse.data).code(docboxResponse.status);
|
|
250
|
+
|
|
251
|
+
// Set the response headers
|
|
252
|
+
Object.entries(docboxResponse.headers).forEach(
|
|
253
|
+
([name, value]) => (response = response.header(name, value))
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return response;
|
|
257
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES6",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"declarationDir": "dist/types",
|
|
7
|
-
"outDir": "dist",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"typeRoots": ["./node_modules/@types"],
|
|
12
|
-
"moduleResolution": "node"
|
|
13
|
-
},
|
|
14
|
-
"include": ["src"]
|
|
15
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES6",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationDir": "dist/types",
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"typeRoots": ["./node_modules/@types"],
|
|
12
|
+
"moduleResolution": "node"
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|