@squiz/dx-common-lib 1.2.12 → 1.2.13-alpha.1
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/CHANGELOG.md +9 -69
- package/lib/api-key-validation/ApiKeyValidationService.d.ts +4 -0
- package/lib/api-key-validation/ApiKeyValidationService.js +3 -0
- package/lib/api-key-validation/ApiKeyValidationService.js.map +1 -0
- package/lib/api-key-validation/CloudflareApiKeyService.d.ts +17 -0
- package/lib/api-key-validation/CloudflareApiKeyService.js +72 -0
- package/lib/api-key-validation/CloudflareApiKeyService.js.map +1 -0
- package/lib/api-key-validation/CloudflareApiKeyService.spec.d.ts +1 -0
- package/lib/api-key-validation/CloudflareApiKeyService.spec.js +93 -0
- package/lib/api-key-validation/CloudflareApiKeyService.spec.js.map +1 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.d.ts +5 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.js +13 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.js.map +1 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.spec.d.ts +1 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.spec.js +17 -0
- package/lib/api-key-validation/DevelopmentApiKeyService.spec.js.map +1 -0
- package/lib/api-key-validation/getApiKeyService.d.ts +6 -0
- package/lib/api-key-validation/getApiKeyService.js +23 -0
- package/lib/api-key-validation/getApiKeyService.js.map +1 -0
- package/lib/api-key-validation/getApiKeyService.spec.d.ts +1 -0
- package/lib/api-key-validation/getApiKeyService.spec.js +24 -0
- package/lib/api-key-validation/getApiKeyService.spec.js.map +1 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.d.ts +19 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.js +21 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.js.map +1 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.spec.d.ts +1 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.spec.js +97 -0
- package/lib/cache/applyDefaultRulesToCacheControlObject.spec.js.map +1 -0
- package/lib/cache/cacheControlToString.d.ts +2 -0
- package/lib/cache/cacheControlToString.js +24 -0
- package/lib/cache/cacheControlToString.js.map +1 -0
- package/lib/cache/cacheControlToString.spec.d.ts +1 -0
- package/lib/cache/cacheControlToString.spec.js +34 -0
- package/lib/cache/cacheControlToString.spec.js.map +1 -0
- package/lib/cache/index.d.ts +4 -0
- package/lib/cache/index.js +21 -0
- package/lib/cache/index.js.map +1 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.d.ts +5 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.js +26 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.js.map +1 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.spec.d.ts +1 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.spec.js +21 -0
- package/lib/cache/parseAndSanitiseCacheControlHeader.spec.js.map +1 -0
- package/lib/cache/parseCacheControl.d.ts +19 -0
- package/lib/cache/parseCacheControl.js +46 -0
- package/lib/cache/parseCacheControl.js.map +1 -0
- package/lib/cache/parseCacheControl.spec.d.ts +1 -0
- package/lib/cache/parseCacheControl.spec.js +70 -0
- package/lib/cache/parseCacheControl.spec.js.map +1 -0
- package/lib/error/InvalidTokenError.d.ts +5 -0
- package/lib/error/InvalidTokenError.js +12 -0
- package/lib/error/InvalidTokenError.js.map +1 -0
- package/lib/error/UnAuthenticatedRequestError.d.ts +4 -0
- package/lib/error/UnAuthenticatedRequestError.js +11 -0
- package/lib/error/UnAuthenticatedRequestError.js.map +1 -0
- package/lib/error/UnprivilegedError.d.ts +5 -0
- package/lib/error/UnprivilegedError.js +12 -0
- package/lib/error/UnprivilegedError.js.map +1 -0
- package/lib/error/index.d.ts +3 -0
- package/lib/error/index.js +3 -0
- package/lib/error/index.js.map +1 -1
- package/lib/formatted-text/formattedTextToHtmlSting.d.ts +4 -0
- package/lib/formatted-text/formattedTextToHtmlSting.js +85 -0
- package/lib/formatted-text/formattedTextToHtmlSting.js.map +1 -0
- package/lib/formatted-text/formattedTextToHtmlSting.spec.d.ts +1 -0
- package/lib/formatted-text/formattedTextToHtmlSting.spec.js +212 -0
- package/lib/formatted-text/formattedTextToHtmlSting.spec.js.map +1 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +7 -0
- package/lib/index.js.map +1 -1
- package/lib/server-utils/apiKeyMiddleware.d.ts +3 -0
- package/lib/server-utils/apiKeyMiddleware.js +20 -0
- package/lib/server-utils/apiKeyMiddleware.js.map +1 -0
- package/lib/server-utils/apiKeyMiddleware.spec.d.ts +1 -0
- package/lib/server-utils/apiKeyMiddleware.spec.js +39 -0
- package/lib/server-utils/apiKeyMiddleware.spec.js.map +1 -0
- package/package.json +9 -7
- package/src/api-key-validation/ApiKeyValidationService.ts +4 -0
- package/src/api-key-validation/CloudflareApiKeyService.spec.ts +122 -0
- package/src/api-key-validation/CloudflareApiKeyService.ts +96 -0
- package/src/api-key-validation/DevelopmentApiKeyService.spec.ts +17 -0
- package/src/api-key-validation/DevelopmentApiKeyService.ts +10 -0
- package/src/api-key-validation/getApiKeyService.spec.ts +32 -0
- package/src/api-key-validation/getApiKeyService.ts +27 -0
- package/src/cache/applyDefaultRulesToCacheControlObject.spec.ts +126 -0
- package/src/cache/applyDefaultRulesToCacheControlObject.ts +23 -0
- package/src/cache/cacheControlToString.spec.ts +43 -0
- package/src/cache/cacheControlToString.ts +22 -0
- package/src/cache/index.ts +4 -0
- package/src/cache/parseAndSanitiseCacheControlHeader.spec.ts +25 -0
- package/src/cache/parseAndSanitiseCacheControlHeader.ts +28 -0
- package/src/cache/parseCacheControl.spec.ts +89 -0
- package/src/cache/parseCacheControl.ts +74 -0
- package/src/error/InvalidTokenError.ts +8 -0
- package/src/error/UnAuthenticatedRequestError.ts +7 -0
- package/src/error/UnprivilegedError.ts +8 -0
- package/src/error/index.ts +4 -0
- package/src/formatted-text/formattedTextToHtmlSting.spec.ts +235 -0
- package/src/formatted-text/formattedTextToHtmlSting.ts +100 -0
- package/src/index.ts +7 -0
- package/src/server-utils/apiKeyMiddleware.spec.ts +50 -0
- package/src/server-utils/apiKeyMiddleware.ts +23 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,212 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const formattedTextToHtmlSting_1 = require("./formattedTextToHtmlSting");
|
4
|
+
describe('formattedTextToHtmlSting', () => {
|
5
|
+
it('should convert text nodes to HTML', () => {
|
6
|
+
const textNode = {
|
7
|
+
type: 'text',
|
8
|
+
value: 'Hello, world!',
|
9
|
+
};
|
10
|
+
expect((0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(textNode)).toBe('Hello, world!');
|
11
|
+
});
|
12
|
+
it('should convert tag nodes to HTML', () => {
|
13
|
+
const tagNode = {
|
14
|
+
type: 'tag',
|
15
|
+
tag: 'p',
|
16
|
+
children: [{ type: 'text', value: 'Hello, world!' }],
|
17
|
+
};
|
18
|
+
expect((0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(tagNode)).toBe('<p>Hello, world!</p>');
|
19
|
+
});
|
20
|
+
it('should convert tag nodes with attributes to HTML', () => {
|
21
|
+
const tagNode = {
|
22
|
+
type: 'tag',
|
23
|
+
tag: 'p',
|
24
|
+
attributes: {
|
25
|
+
id: 'hello-world',
|
26
|
+
class: 'greeting',
|
27
|
+
},
|
28
|
+
children: [{ type: 'text', value: 'Hello, world!' }],
|
29
|
+
};
|
30
|
+
expect((0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(tagNode)).toBe('<p id="hello-world" class="greeting">Hello, world!</p>');
|
31
|
+
});
|
32
|
+
it('should convert tag nodes with font properties to HTML', () => {
|
33
|
+
const tagNode = {
|
34
|
+
type: 'tag',
|
35
|
+
tag: 'p',
|
36
|
+
font: {
|
37
|
+
bold: true,
|
38
|
+
underline: true,
|
39
|
+
italics: false,
|
40
|
+
color: 'red',
|
41
|
+
size: 'large',
|
42
|
+
fontFamily: 'Arial',
|
43
|
+
},
|
44
|
+
children: [{ type: 'text', value: 'Hello, world!' }],
|
45
|
+
};
|
46
|
+
expect((0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(tagNode)).toBe('<p class="bold underline color-red size-large font-Arial">Hello, world!</p>');
|
47
|
+
});
|
48
|
+
it('converts a tag node with attributes but no children to html', () => {
|
49
|
+
const node = {
|
50
|
+
type: 'tag',
|
51
|
+
tag: 'p',
|
52
|
+
children: [],
|
53
|
+
attributes: { class: 'test-class' },
|
54
|
+
};
|
55
|
+
expect((0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(node)).toEqual('<p class="test-class"></p>');
|
56
|
+
});
|
57
|
+
it('converts a tag node with children but no attributes to html', () => {
|
58
|
+
const node = {
|
59
|
+
type: 'tag',
|
60
|
+
tag: 'p',
|
61
|
+
children: [
|
62
|
+
{ type: 'text', value: 'This is a test' },
|
63
|
+
{ type: 'text', value: 'This is another test' },
|
64
|
+
],
|
65
|
+
};
|
66
|
+
expect((0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(node)).toEqual('<p>This is a testThis is another test</p>');
|
67
|
+
});
|
68
|
+
it('converts a tag node with children and attributes to html', () => {
|
69
|
+
const node = {
|
70
|
+
type: 'tag',
|
71
|
+
tag: 'p',
|
72
|
+
attributes: { class: 'test-class' },
|
73
|
+
children: [
|
74
|
+
{ type: 'text', value: 'This is a test' },
|
75
|
+
{ type: 'text', value: 'This is another test' },
|
76
|
+
],
|
77
|
+
};
|
78
|
+
expect((0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(node)).toEqual('<p class="test-class">This is a testThis is another test</p>');
|
79
|
+
});
|
80
|
+
it('should convert a tag node with attributes to HTML', () => {
|
81
|
+
const input = {
|
82
|
+
type: 'tag',
|
83
|
+
tag: 'div',
|
84
|
+
children: [],
|
85
|
+
attributes: {
|
86
|
+
id: 'my-div',
|
87
|
+
'data-foo': 'bar',
|
88
|
+
},
|
89
|
+
};
|
90
|
+
const expectedOutput = '<div id="my-div" data-foo="bar"></div>';
|
91
|
+
expect((0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(input)).toEqual(expectedOutput);
|
92
|
+
});
|
93
|
+
it('should add the correct class for left alignment', () => {
|
94
|
+
const formattedNode = {
|
95
|
+
type: 'tag',
|
96
|
+
children: [],
|
97
|
+
tag: 'p',
|
98
|
+
formattingOptions: {
|
99
|
+
alignment: 'left',
|
100
|
+
},
|
101
|
+
};
|
102
|
+
const html = (0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(formattedNode);
|
103
|
+
expect(html).toEqual('<p class="left"></p>');
|
104
|
+
});
|
105
|
+
it('should add the correct class for right alignment', () => {
|
106
|
+
const formattedNode = {
|
107
|
+
type: 'tag',
|
108
|
+
children: [],
|
109
|
+
tag: 'p',
|
110
|
+
formattingOptions: {
|
111
|
+
alignment: 'right',
|
112
|
+
},
|
113
|
+
};
|
114
|
+
const html = (0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(formattedNode);
|
115
|
+
expect(html).toEqual('<p class="right"></p>');
|
116
|
+
});
|
117
|
+
it('should add the correct class for center alignment', () => {
|
118
|
+
const formattedNode = {
|
119
|
+
type: 'tag',
|
120
|
+
children: [],
|
121
|
+
tag: 'p',
|
122
|
+
formattingOptions: {
|
123
|
+
alignment: 'center',
|
124
|
+
},
|
125
|
+
};
|
126
|
+
const html = (0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(formattedNode);
|
127
|
+
expect(html).toEqual('<p class="center"></p>');
|
128
|
+
});
|
129
|
+
it('should add the correct class for justify alignment', () => {
|
130
|
+
const formattedNode = {
|
131
|
+
type: 'tag',
|
132
|
+
children: [],
|
133
|
+
tag: 'p',
|
134
|
+
formattingOptions: {
|
135
|
+
alignment: 'justify',
|
136
|
+
},
|
137
|
+
};
|
138
|
+
const html = (0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(formattedNode);
|
139
|
+
expect(html).toEqual('<p class="justify"></p>');
|
140
|
+
});
|
141
|
+
it('should handle input with formattingOptions, font and class attributes', () => {
|
142
|
+
const formattedTextTag = {
|
143
|
+
type: 'tag',
|
144
|
+
tag: 'p',
|
145
|
+
formattingOptions: {
|
146
|
+
alignment: 'center',
|
147
|
+
},
|
148
|
+
font: {
|
149
|
+
bold: true,
|
150
|
+
color: 'red',
|
151
|
+
},
|
152
|
+
attributes: {
|
153
|
+
class: 'my-class',
|
154
|
+
},
|
155
|
+
children: [
|
156
|
+
{
|
157
|
+
type: 'text',
|
158
|
+
value: 'Hello, World!',
|
159
|
+
},
|
160
|
+
],
|
161
|
+
};
|
162
|
+
const expectedResult = '<p class="my-class bold color-red center">Hello, World!</p>';
|
163
|
+
const result = (0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(formattedTextTag);
|
164
|
+
expect(result).toBe(expectedResult);
|
165
|
+
});
|
166
|
+
it('should return an error message for an unknown node type', () => {
|
167
|
+
const formattedNodes = {
|
168
|
+
type: 'unknown',
|
169
|
+
value: 'Hello World!',
|
170
|
+
};
|
171
|
+
const expectedHTML = `<p class="invalid-node-type"> node of type "unknown" cannot be converted to html</p>`;
|
172
|
+
expect((0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(formattedNodes)).toEqual(expectedHTML);
|
173
|
+
});
|
174
|
+
it('should handle deeply nested FormattedNodes object', () => {
|
175
|
+
const formattedNodes = {
|
176
|
+
type: 'tag',
|
177
|
+
tag: 'div',
|
178
|
+
formattingOptions: {
|
179
|
+
alignment: 'justify',
|
180
|
+
},
|
181
|
+
children: [
|
182
|
+
{
|
183
|
+
type: 'text',
|
184
|
+
value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ',
|
185
|
+
},
|
186
|
+
{
|
187
|
+
type: 'tag',
|
188
|
+
tag: 'strong',
|
189
|
+
font: {
|
190
|
+
bold: true,
|
191
|
+
},
|
192
|
+
children: [
|
193
|
+
{
|
194
|
+
type: 'text',
|
195
|
+
value: 'Vestibulum',
|
196
|
+
},
|
197
|
+
{
|
198
|
+
type: 'text',
|
199
|
+
value: ' dictum',
|
200
|
+
},
|
201
|
+
],
|
202
|
+
},
|
203
|
+
{
|
204
|
+
type: 'text',
|
205
|
+
value: ' mi vel urna maximus, et luctus ipsum tincidunt',
|
206
|
+
},
|
207
|
+
],
|
208
|
+
};
|
209
|
+
expect((0, formattedTextToHtmlSting_1.formattedTextToHtmlSting)(formattedNodes)).toMatchInlineSnapshot(`"<div class=\\"justify\\">Lorem ipsum dolor sit amet, consectetur adipiscing elit. <strong class=\\"bold\\">Vestibulum dictum</strong> mi vel urna maximus, et luctus ipsum tincidunt</div>"`);
|
210
|
+
});
|
211
|
+
});
|
212
|
+
//# sourceMappingURL=formattedTextToHtmlSting.spec.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"formattedTextToHtmlSting.spec.js","sourceRoot":"","sources":["../../src/formatted-text/formattedTextToHtmlSting.spec.ts"],"names":[],"mappings":";;AACA,yEAAsE;AAKtE,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,QAAQ,GAAmB;YAC/B,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,eAAe;SACvB,CAAC;QACF,MAAM,CAAC,IAAA,mDAAwB,EAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,OAAO,GAAmB;YAC9B,IAAI,EAAE,KAAK;YACX,GAAG,EAAE,GAAG;YACR,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;SACrD,CAAC;QACF,MAAM,CAAC,IAAA,mDAAwB,EAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,OAAO,GAAmB;YAC9B,IAAI,EAAE,KAAK;YACX,GAAG,EAAE,GAAG;YACR,UAAU,EAAE;gBACV,EAAE,EAAE,aAAa;gBACjB,KAAK,EAAE,UAAU;aAClB;YACD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;SACrD,CAAC;QACF,MAAM,CAAC,IAAA,mDAAwB,EAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;IAC3G,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,OAAO,GAAmB;YAC9B,IAAI,EAAE,KAAK;YACX,GAAG,EAAE,GAAG;YACR,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI;gBACV,SAAS,EAAE,IAAI;gBACf,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,KAAK;gBACZ,IAAI,EAAE,OAAO;gBACb,UAAU,EAAE,OAAO;aACpB;YACD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;SACrD,CAAC;QACF,MAAM,CAAC,IAAA,mDAAwB,EAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAC5C,6EAA6E,CAC9E,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,IAAI,GAAmB;YAC3B,IAAI,EAAE,KAAK;YACX,GAAG,EAAE,GAAG;YACR,QAAQ,EAAE,EAAE;YACZ,UAAU,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE;SACpC,CAAC;QACF,MAAM,CAAC,IAAA,mDAAwB,EAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,IAAI,GAAmB;YAC3B,IAAI,EAAE,KAAK;YACX,GAAG,EAAE,GAAG;YACR,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE;gBACzC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,EAAE;aAChD;SACF,CAAC;QACF,MAAM,CAAC,IAAA,mDAAwB,EAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,IAAI,GAAmB;YAC3B,IAAI,EAAE,KAAK;YACX,GAAG,EAAE,GAAG;YACR,UAAU,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE;YACnC,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,EAAE;gBACzC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB,EAAE;aAChD;SACF,CAAC;QACF,MAAM,CAAC,IAAA,mDAAwB,EAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,8DAA8D,CAAC,CAAC;IACjH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,KAAK,GAAmB;YAC5B,IAAI,EAAE,KAAK;YACX,GAAG,EAAE,KAAK;YACV,QAAQ,EAAE,EAAE;YACZ,UAAU,EAAE;gBACV,EAAE,EAAE,QAAQ;gBACZ,UAAU,EAAE,KAAK;aAClB;SACF,CAAC;QACF,MAAM,cAAc,GAAG,wCAAwC,CAAC;QAChE,MAAM,CAAC,IAAA,mDAAwB,EAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,aAAa,GAAqB;YACtC,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,EAAE;YACZ,GAAG,EAAE,GAAG;YACR,iBAAiB,EAAE;gBACjB,SAAS,EAAE,MAAM;aAClB;SACF,CAAC;QACF,MAAM,IAAI,GAAG,IAAA,mDAAwB,EAAC,aAAa,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,aAAa,GAAqB;YACtC,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,EAAE;YACZ,GAAG,EAAE,GAAG;YACR,iBAAiB,EAAE;gBACjB,SAAS,EAAE,OAAO;aACnB;SACF,CAAC;QACF,MAAM,IAAI,GAAG,IAAA,mDAAwB,EAAC,aAAa,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,aAAa,GAAqB;YACtC,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,EAAE;YACZ,GAAG,EAAE,GAAG;YACR,iBAAiB,EAAE;gBACjB,SAAS,EAAE,QAAQ;aACpB;SACF,CAAC;QACF,MAAM,IAAI,GAAG,IAAA,mDAAwB,EAAC,aAAa,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,aAAa,GAAqB;YACtC,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,EAAE;YACZ,GAAG,EAAE,GAAG;YACR,iBAAiB,EAAE;gBACjB,SAAS,EAAE,SAAS;aACrB;SACF,CAAC;QACF,MAAM,IAAI,GAAG,IAAA,mDAAwB,EAAC,aAAa,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,gBAAgB,GAAqB;YACzC,IAAI,EAAE,KAAK;YACX,GAAG,EAAE,GAAG;YACR,iBAAiB,EAAE;gBACjB,SAAS,EAAE,QAAQ;aACpB;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI;gBACV,KAAK,EAAE,KAAK;aACb;YACD,UAAU,EAAE;gBACV,KAAK,EAAE,UAAU;aAClB;YACD,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,eAAe;iBACvB;aACF;SACF,CAAC;QAEF,MAAM,cAAc,GAAG,6DAA6D,CAAC;QAErF,MAAM,MAAM,GAAG,IAAA,mDAAwB,EAAC,gBAAgB,CAAC,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,cAAc,GAAmB;YACrC,IAAI,EAAE,SAAgB;YACtB,KAAK,EAAE,cAAc;SACtB,CAAC;QACF,MAAM,YAAY,GAAG,sFAAsF,CAAC;QAC5G,MAAM,CAAC,IAAA,mDAAwB,EAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,cAAc,GAAmB;YACrC,IAAI,EAAE,KAAK;YACX,GAAG,EAAE,KAAK;YACV,iBAAiB,EAAE;gBACjB,SAAS,EAAE,SAAS;aACrB;YACD,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,2DAA2D;iBACnE;gBACD;oBACE,IAAI,EAAE,KAAK;oBACX,GAAG,EAAE,QAAQ;oBACb,IAAI,EAAE;wBACJ,IAAI,EAAE,IAAI;qBACX;oBACD,QAAQ,EAAE;wBACR;4BACE,IAAI,EAAE,MAAM;4BACZ,KAAK,EAAE,YAAY;yBACpB;wBACD;4BACE,IAAI,EAAE,MAAM;4BACZ,KAAK,EAAE,SAAS;yBACjB;qBACF;iBACF;gBACD;oBACE,IAAI,EAAE,MAAM;oBACZ,KAAK,EAAE,iDAAiD;iBACzD;aACF;SACF,CAAC;QAEF,MAAM,CAAC,IAAA,mDAAwB,EAAC,cAAc,CAAC,CAAC,CAAC,qBAAqB,CACpE,8LAA8L,CAC/L,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/lib/index.d.ts
CHANGED
@@ -4,3 +4,10 @@ export * from './util';
|
|
4
4
|
export * from './zip/zipDirectory';
|
5
5
|
export * from './server-utils/errorMiddleware';
|
6
6
|
export * from './server-utils/requestLogger';
|
7
|
+
export * from './api-key-validation/ApiKeyValidationService';
|
8
|
+
export * from './api-key-validation/CloudflareApiKeyService';
|
9
|
+
export * from './api-key-validation/DevelopmentApiKeyService';
|
10
|
+
export * from './api-key-validation/getApiKeyService';
|
11
|
+
export * from './server-utils/apiKeyMiddleware';
|
12
|
+
export * from './cache';
|
13
|
+
export * from './formatted-text/formattedTextToHtmlSting';
|
package/lib/index.js
CHANGED
@@ -20,4 +20,11 @@ __exportStar(require("./util"), exports);
|
|
20
20
|
__exportStar(require("./zip/zipDirectory"), exports);
|
21
21
|
__exportStar(require("./server-utils/errorMiddleware"), exports);
|
22
22
|
__exportStar(require("./server-utils/requestLogger"), exports);
|
23
|
+
__exportStar(require("./api-key-validation/ApiKeyValidationService"), exports);
|
24
|
+
__exportStar(require("./api-key-validation/CloudflareApiKeyService"), exports);
|
25
|
+
__exportStar(require("./api-key-validation/DevelopmentApiKeyService"), exports);
|
26
|
+
__exportStar(require("./api-key-validation/getApiKeyService"), exports);
|
27
|
+
__exportStar(require("./server-utils/apiKeyMiddleware"), exports);
|
28
|
+
__exportStar(require("./cache"), exports);
|
29
|
+
__exportStar(require("./formatted-text/formattedTextToHtmlSting"), exports);
|
23
30
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gDAA8B;AAC9B,0CAAwB;AACxB,yCAAuB;AACvB,qDAAmC;AACnC,iEAA+C;AAC/C,+DAA6C"}
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,gDAA8B;AAC9B,0CAAwB;AACxB,yCAAuB;AACvB,qDAAmC;AACnC,iEAA+C;AAC/C,+DAA6C;AAC7C,+EAA6D;AAC7D,+EAA6D;AAC7D,gFAA8D;AAC9D,wEAAsD;AACtD,kEAAgD;AAChD,0CAAwB;AACxB,4EAA0D"}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.apiKeyMiddleware = void 0;
|
4
|
+
const UnAuthenticatedRequestError_1 = require("../error/UnAuthenticatedRequestError");
|
5
|
+
function apiKeyMiddleware(apiKeyService) {
|
6
|
+
return function (req, res, next) {
|
7
|
+
const key = req.header('x-api-key');
|
8
|
+
if (req.path == '/health') {
|
9
|
+
next();
|
10
|
+
return;
|
11
|
+
}
|
12
|
+
if (apiKeyService.keyIsValid(key) || apiKeyService.matrixKeyIsValid(key)) {
|
13
|
+
next();
|
14
|
+
return;
|
15
|
+
}
|
16
|
+
throw new UnAuthenticatedRequestError_1.UnAuthenticatedRequestError(`API KEY IS INVALID`);
|
17
|
+
};
|
18
|
+
}
|
19
|
+
exports.apiKeyMiddleware = apiKeyMiddleware;
|
20
|
+
//# sourceMappingURL=apiKeyMiddleware.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"apiKeyMiddleware.js","sourceRoot":"","sources":["../../src/server-utils/apiKeyMiddleware.ts"],"names":[],"mappings":";;;AAEA,sFAAmF;AAEnF,SAAgB,gBAAgB,CAC9B,aAAsC;IAEtC,OAAO,UAAU,GAAY,EAAE,GAAa,EAAE,IAAS;QACrD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAEpC,IAAI,GAAG,CAAC,IAAI,IAAI,SAAS,EAAE;YACzB,IAAI,EAAE,CAAC;YACP,OAAO;SACR;QAED,IAAI,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE;YACxE,IAAI,EAAE,CAAC;YACP,OAAO;SACR;QAED,MAAM,IAAI,yDAA2B,CAAC,oBAAoB,CAAC,CAAC;IAC9D,CAAC,CAAC;AACJ,CAAC;AAlBD,4CAkBC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,39 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const UnAuthenticatedRequestError_1 = require("../error/UnAuthenticatedRequestError");
|
4
|
+
const apiKeyMiddleware_1 = require("./apiKeyMiddleware");
|
5
|
+
const keyIsValid = jest.fn().mockImplementation(() => true);
|
6
|
+
const matrixKeyIsValid = jest.fn().mockImplementation(() => true);
|
7
|
+
describe('apiKeyMiddleware', () => {
|
8
|
+
const middleware = (0, apiKeyMiddleware_1.apiKeyMiddleware)({ keyIsValid, matrixKeyIsValid });
|
9
|
+
it('should return an express middleware which', () => {
|
10
|
+
expect(middleware).toBeInstanceOf(Function);
|
11
|
+
});
|
12
|
+
describe('middleware', () => {
|
13
|
+
it('should call next is key is invalid but matrix ke is valid', () => {
|
14
|
+
const nextFunc = jest.fn().mockImplementation(() => {
|
15
|
+
/* no op */
|
16
|
+
});
|
17
|
+
keyIsValid.mockImplementation(() => false);
|
18
|
+
keyIsValid.mockImplementation(() => true);
|
19
|
+
middleware({ header: () => 'some-key' }, {}, nextFunc);
|
20
|
+
expect(nextFunc).toHaveBeenCalled();
|
21
|
+
});
|
22
|
+
it('should call next if api key is valid', () => {
|
23
|
+
const nextFunc = jest.fn().mockImplementation(() => {
|
24
|
+
/* no op */
|
25
|
+
});
|
26
|
+
middleware({ header: () => 'some-key' }, {}, nextFunc);
|
27
|
+
expect(nextFunc).toHaveBeenCalled();
|
28
|
+
});
|
29
|
+
it('should throw an UnAuthenticatedRequestError if key is invalid', () => {
|
30
|
+
const nextFunc = jest.fn().mockImplementation(() => {
|
31
|
+
/* no op */
|
32
|
+
});
|
33
|
+
keyIsValid.mockImplementation(() => false);
|
34
|
+
matrixKeyIsValid.mockImplementation(() => false);
|
35
|
+
expect(() => middleware({ header: () => 'some-key' }, {}, nextFunc)).toThrowError(new UnAuthenticatedRequestError_1.UnAuthenticatedRequestError(`API KEY IS INVALID`));
|
36
|
+
});
|
37
|
+
});
|
38
|
+
});
|
39
|
+
//# sourceMappingURL=apiKeyMiddleware.spec.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"apiKeyMiddleware.spec.js","sourceRoot":"","sources":["../../src/server-utils/apiKeyMiddleware.spec.ts"],"names":[],"mappings":";;AAAA,sFAAmF;AACnF,yDAAsD;AAEtD,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAC5D,MAAM,gBAAgB,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAElE,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,MAAM,UAAU,GAAG,IAAA,mCAAgB,EAAC,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAEtE,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,UAAU,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;YACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE;gBACjD,WAAW;YACb,CAAC,CAAC,CAAC;YACH,UAAU,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;YAC3C,UAAU,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YAE1C,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,EAAS,EAAE,EAAS,EAAE,QAAQ,CAAC,CAAC;YAErE,MAAM,CAAC,QAAQ,CAAC,CAAC,gBAAgB,EAAE,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE;gBACjD,WAAW;YACb,CAAC,CAAC,CAAC;YAEH,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,EAAS,EAAE,EAAS,EAAE,QAAQ,CAAC,CAAC;YAErE,MAAM,CAAC,QAAQ,CAAC,CAAC,gBAAgB,EAAE,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;YACvE,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,EAAE;gBACjD,WAAW;YACb,CAAC,CAAC,CAAC;YAEH,UAAU,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;YAC3C,gBAAgB,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;YAEjD,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,EAAS,EAAE,EAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,YAAY,CAC7F,IAAI,yDAA2B,CAAC,oBAAoB,CAAC,CACtD,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
@@ -1,29 +1,31 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/dx-common-lib",
|
3
|
-
"version": "1.2.
|
3
|
+
"version": "1.2.13-alpha.1",
|
4
4
|
"description": "",
|
5
5
|
"main": "lib/index.js",
|
6
6
|
"scripts": {
|
7
|
-
"compile": "
|
7
|
+
"compile": "tsc",
|
8
8
|
"lint": "eslint ./src --ext .ts",
|
9
9
|
"test": "jest -c jest.config.ts",
|
10
10
|
"test:update-snapshots": "jest -c jest.config.ts --updateSnapshot",
|
11
|
-
"clean": "rimraf \".tsbuildinfo\" \"./lib\""
|
11
|
+
"clean": "rimraf \"tsconfig.tsbuildinfo\" \"./lib\""
|
12
12
|
},
|
13
13
|
"author": "",
|
14
14
|
"license": "ISC",
|
15
15
|
"dependencies": {
|
16
|
-
"@
|
16
|
+
"@aws-sdk/client-secrets-manager": "3.218.0",
|
17
|
+
"@squiz/dx-json-schema-lib": "1.2.13-alpha.1",
|
18
|
+
"@squiz/dx-logger-lib": "1.2.13-alpha.1",
|
17
19
|
"archiver": "5.3.1",
|
18
20
|
"fs-extra": "10.1.0"
|
19
21
|
},
|
20
22
|
"devDependencies": {
|
21
23
|
"@types/archiver": "5.3.1",
|
22
|
-
"@types/express": "4.17.
|
24
|
+
"@types/express": "4.17.14",
|
23
25
|
"@types/fs-extra": "9.0.13",
|
24
26
|
"@types/jest": "28.1.8",
|
25
27
|
"@types/node": "17.0.27",
|
26
|
-
"dotenv": "16.0.
|
28
|
+
"dotenv": "16.0.3",
|
27
29
|
"eslint": "8.22.0",
|
28
30
|
"jest": "28.1.3",
|
29
31
|
"rimraf": "3.0.2",
|
@@ -32,5 +34,5 @@
|
|
32
34
|
"ts-node": "10.9.1",
|
33
35
|
"typescript": "4.9.3"
|
34
36
|
},
|
35
|
-
"gitHead": "
|
37
|
+
"gitHead": "25fd4ac4502cd93a5cfcd7e7a945a6f2f4fa925b"
|
36
38
|
}
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
|
2
|
+
import { UnAuthenticatedRequestError } from '../error/UnAuthenticatedRequestError';
|
3
|
+
import { CloudflareApiKeyService } from './CloudflareApiKeyService';
|
4
|
+
|
5
|
+
const sendSpy = jest.spyOn(SecretsManagerClient.prototype, 'send');
|
6
|
+
|
7
|
+
jest.useFakeTimers();
|
8
|
+
jest.spyOn(global, 'setInterval');
|
9
|
+
|
10
|
+
describe('CloudflareApiKeyService', () => {
|
11
|
+
const service = new CloudflareApiKeyService('my-secret-name');
|
12
|
+
|
13
|
+
describe('refreshApiKeys', () => {
|
14
|
+
it('should throw an error if the secret value is not in the expected format', async () => {
|
15
|
+
sendSpy.mockImplementationOnce(() => Promise.resolve({ SecretString: 'bad json' }));
|
16
|
+
|
17
|
+
await expect(service.refreshApiKeys()).rejects.toThrowError(
|
18
|
+
new Error('failed to decode cloudflare api key values'),
|
19
|
+
);
|
20
|
+
});
|
21
|
+
|
22
|
+
it('should make a request to the aws secrets manager when refreshing the keys', async () => {
|
23
|
+
sendSpy.mockClear();
|
24
|
+
sendSpy.mockImplementationOnce(() => Promise.resolve({ SecretString: '{"keys":[]}' }));
|
25
|
+
await service.refreshApiKeys();
|
26
|
+
|
27
|
+
expect(sendSpy.mock.lastCall[0].input).toEqual({ SecretId: 'my-secret-name' });
|
28
|
+
expect(sendSpy.mock.lastCall[0]).toBeInstanceOf(GetSecretValueCommand);
|
29
|
+
});
|
30
|
+
|
31
|
+
it('should start a refresh timer once called, but only once', (done) => {
|
32
|
+
sendSpy.mockClear();
|
33
|
+
sendSpy.mockImplementationOnce(() => Promise.resolve({ SecretString: '{"keys":["my-new-key"]}' }));
|
34
|
+
const spy = jest.spyOn(service, 'refreshApiKeys');
|
35
|
+
|
36
|
+
expect(spy).not.toBeCalled();
|
37
|
+
expect(setInterval).toHaveBeenCalledTimes(1); // for the above 2 tests
|
38
|
+
jest.runOnlyPendingTimers();
|
39
|
+
|
40
|
+
expect(spy).toHaveBeenCalled();
|
41
|
+
|
42
|
+
jest.useRealTimers();
|
43
|
+
setTimeout(() => {
|
44
|
+
expect(service.keyIsValid('my-new-key')).toEqual(true);
|
45
|
+
done();
|
46
|
+
}, 5);
|
47
|
+
});
|
48
|
+
});
|
49
|
+
|
50
|
+
describe('keyIsValid', () => {
|
51
|
+
describe('when there are no api keys', () => {
|
52
|
+
beforeAll(async () => {
|
53
|
+
sendSpy.mockImplementation(() => Promise.resolve({ SecretString: '{"keys":[]}' }));
|
54
|
+
await service.refreshApiKeys();
|
55
|
+
});
|
56
|
+
|
57
|
+
it('should throw an unauthenticated request error when validating a key if there are no keys to validate against', () => {
|
58
|
+
expect(() => service.keyIsValid('anything')).toThrowError(
|
59
|
+
new UnAuthenticatedRequestError('No api keys to check against'),
|
60
|
+
);
|
61
|
+
});
|
62
|
+
});
|
63
|
+
|
64
|
+
describe('when there are api keys', () => {
|
65
|
+
beforeAll(async () => {
|
66
|
+
sendSpy.mockImplementation(() =>
|
67
|
+
Promise.resolve({ SecretString: '{"keys":["my-key", "my-other-key"], "matrix-keys":["my-matrix-key"]}' }),
|
68
|
+
);
|
69
|
+
await service.refreshApiKeys();
|
70
|
+
});
|
71
|
+
it('should return true when there are keys loaded and the input key is in the list of valid keys', () => {
|
72
|
+
expect(service.keyIsValid('my-key')).toEqual(true);
|
73
|
+
expect(service.keyIsValid('my-other-key')).toEqual(true);
|
74
|
+
});
|
75
|
+
|
76
|
+
it('should return false when there are keys loaded and the input key is not in the list of valid keys', async () => {
|
77
|
+
expect(service.keyIsValid('not-my-key')).toEqual(false);
|
78
|
+
});
|
79
|
+
|
80
|
+
it('should return false if trying to validate a matrix key', () => {
|
81
|
+
expect(service.keyIsValid('my-matrix-key')).toEqual(false);
|
82
|
+
});
|
83
|
+
});
|
84
|
+
});
|
85
|
+
|
86
|
+
describe('matrixKeyIsValid', () => {
|
87
|
+
describe('when there are no api keys', () => {
|
88
|
+
beforeAll(async () => {
|
89
|
+
sendSpy.mockImplementation(() => Promise.resolve({ SecretString: '{"keys":[], "matrix-keys":[]}' }));
|
90
|
+
await service.refreshApiKeys();
|
91
|
+
});
|
92
|
+
|
93
|
+
it('should throw an unauthenticated request error when validating a key if there are no keys to validate against', () => {
|
94
|
+
expect(() => service.matrixKeyIsValid('anything')).toThrowError(
|
95
|
+
new UnAuthenticatedRequestError('No api keys to check against'),
|
96
|
+
);
|
97
|
+
});
|
98
|
+
});
|
99
|
+
|
100
|
+
describe('when there are api keys', () => {
|
101
|
+
beforeAll(async () => {
|
102
|
+
sendSpy.mockImplementation(() =>
|
103
|
+
Promise.resolve({ SecretString: '{"keys":["my-key", "my-other-key"], "matrix-keys":["my-matrix-key"]}' }),
|
104
|
+
);
|
105
|
+
await service.refreshApiKeys();
|
106
|
+
});
|
107
|
+
|
108
|
+
it('should return true when there are keys loaded and the input key is in the list of valid matrix keys', () => {
|
109
|
+
expect(service.matrixKeyIsValid('my-matrix-key')).toEqual(true);
|
110
|
+
});
|
111
|
+
|
112
|
+
it('should return false when there are keys loaded and the input key is not in the list of valid matrix keys', async () => {
|
113
|
+
expect(service.matrixKeyIsValid('not-my-key')).toEqual(false);
|
114
|
+
});
|
115
|
+
|
116
|
+
it('should return false when using non matrix keys', async () => {
|
117
|
+
expect(service.matrixKeyIsValid('my-key')).toEqual(false);
|
118
|
+
expect(service.matrixKeyIsValid('my-other-key')).toEqual(false);
|
119
|
+
});
|
120
|
+
});
|
121
|
+
});
|
122
|
+
});
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import {
|
2
|
+
GetSecretValueCommand,
|
3
|
+
GetSecretValueCommandOutput,
|
4
|
+
SecretsManagerClient,
|
5
|
+
} from '@aws-sdk/client-secrets-manager';
|
6
|
+
import { UnAuthenticatedRequestError } from '../error/UnAuthenticatedRequestError';
|
7
|
+
import { ApiKeyValidationService } from './ApiKeyValidationService';
|
8
|
+
import { getLogger, Logger } from '@squiz/dx-logger-lib';
|
9
|
+
|
10
|
+
export interface CloudFlareKeys {
|
11
|
+
keys: string[];
|
12
|
+
'matrix-keys': string[];
|
13
|
+
}
|
14
|
+
|
15
|
+
let validKeys: CloudFlareKeys = {
|
16
|
+
keys: [],
|
17
|
+
'matrix-keys': [],
|
18
|
+
};
|
19
|
+
|
20
|
+
let refreshInterval: ReturnType<typeof setInterval>;
|
21
|
+
// number must be smaller than 24 days.
|
22
|
+
// This is because the value is int32 and maxes at than 2^31-1 milliseconds
|
23
|
+
const REFRESH_TIMER = 7 * 24 * 3600 * 1000; // days, hours, seconds, milliseconds
|
24
|
+
|
25
|
+
export class CloudflareApiKeyService implements ApiKeyValidationService {
|
26
|
+
protected secretsClient: SecretsManagerClient;
|
27
|
+
protected logger: Logger;
|
28
|
+
|
29
|
+
constructor(protected secretName: string, logger?: Logger) {
|
30
|
+
this.secretsClient = new SecretsManagerClient({
|
31
|
+
region: 'ap-southeast-2',
|
32
|
+
});
|
33
|
+
|
34
|
+
if (!logger) {
|
35
|
+
logger = getLogger({ name: 'CloudflareApiKeyService' });
|
36
|
+
}
|
37
|
+
|
38
|
+
this.logger = logger;
|
39
|
+
}
|
40
|
+
|
41
|
+
public keyIsValid(key: string): boolean {
|
42
|
+
if (validKeys.keys.length == 0) {
|
43
|
+
throw new UnAuthenticatedRequestError('No api keys to check against');
|
44
|
+
}
|
45
|
+
|
46
|
+
return validKeys.keys.includes(key);
|
47
|
+
}
|
48
|
+
|
49
|
+
public matrixKeyIsValid(key: string): boolean {
|
50
|
+
if (validKeys['matrix-keys'].length == 0) {
|
51
|
+
throw new UnAuthenticatedRequestError('No api keys to check against');
|
52
|
+
}
|
53
|
+
|
54
|
+
return validKeys['matrix-keys'].includes(key);
|
55
|
+
}
|
56
|
+
|
57
|
+
protected async getValidApiKeys(): Promise<CloudFlareKeys> {
|
58
|
+
let secretValue: GetSecretValueCommandOutput;
|
59
|
+
|
60
|
+
try {
|
61
|
+
secretValue = await this.secretsClient.send(
|
62
|
+
new GetSecretValueCommand({
|
63
|
+
SecretId: this.secretName,
|
64
|
+
}),
|
65
|
+
);
|
66
|
+
} catch (e: any) {
|
67
|
+
console.error(e);
|
68
|
+
throw new Error('Failed to request api key from aws' + e.message);
|
69
|
+
}
|
70
|
+
|
71
|
+
try {
|
72
|
+
const secret = JSON.parse(secretValue.SecretString!);
|
73
|
+
|
74
|
+
if (secret.keys) {
|
75
|
+
return secret;
|
76
|
+
}
|
77
|
+
|
78
|
+
throw new Error('api keys retrieved and decoded successfully but contained no values');
|
79
|
+
} catch (e) {
|
80
|
+
throw new Error('failed to decode cloudflare api key values');
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
public async refreshApiKeys() {
|
85
|
+
this.logger.info('refreshing keys');
|
86
|
+
validKeys = await this.getValidApiKeys();
|
87
|
+
|
88
|
+
this.logger.info(`number of valid keys found: ${validKeys.keys.length}`);
|
89
|
+
|
90
|
+
if (!refreshInterval) {
|
91
|
+
refreshInterval = setInterval(async () => {
|
92
|
+
await this.refreshApiKeys();
|
93
|
+
}, REFRESH_TIMER);
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import { DevelopmentApiKeyService } from './DevelopmentApiKeyService';
|
2
|
+
|
3
|
+
describe('DevelopmentApiKeyService', () => {
|
4
|
+
const service = new DevelopmentApiKeyService();
|
5
|
+
|
6
|
+
describe('keyIsValid', () => {
|
7
|
+
it('should always return true', () => {
|
8
|
+
expect(service.keyIsValid()).toEqual(true);
|
9
|
+
});
|
10
|
+
});
|
11
|
+
|
12
|
+
describe('matrixKeyIsValid', () => {
|
13
|
+
it('should always return true', () => {
|
14
|
+
expect(service.matrixKeyIsValid()).toEqual(true);
|
15
|
+
});
|
16
|
+
});
|
17
|
+
});
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { ApiKeyValidationService } from './ApiKeyValidationService';
|
2
|
+
|
3
|
+
export class DevelopmentApiKeyService implements ApiKeyValidationService {
|
4
|
+
public keyIsValid(): boolean {
|
5
|
+
return true;
|
6
|
+
}
|
7
|
+
public matrixKeyIsValid(): boolean {
|
8
|
+
return true;
|
9
|
+
}
|
10
|
+
}
|