Skip to content

Commit b05bdcb

Browse files
committed
Added package sources
1 parent 8ff980b commit b05bdcb

22 files changed

+2429
-5
lines changed

.github/workflows/deploy.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,4 @@ jobs:
4646
- name: Update (email) Lambda file
4747
run: aws lambda update-function-code --function-name stevenvachon-email --zip-file fileb://./packages/email/bundle.zip
4848
- name: Import--and auto-deploy--API Gateway OpenAPI
49-
# This will remove tags and CORS settings
5049
run: aws apigatewayv2 reimport-api --api-id ioi9i226o3 --body file://./packages/api/openapi.yaml
51-
- name: Reconfigure deployed API Gateway CORS
52-
run: aws apigatewayv2 update-api --api-id ioi9i226o3 --cors-configuration AllowOrigins='https://svachon.com',AllowMethods='OPTIONS,POST',AllowHeaders='Content-Type,X-Requested-With',MaxAge=604800
53-
- name: Re-tag deployed API Gateway
54-
run: aws apigatewayv2 tag-resource --resource-arn arn:aws:apigateway:us-east-1::/apis/ioi9i226o3 --tags Key=stevenvachon
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { WEBSITE_URL } from './misc.mjs';
2+
3+
export const CONTENT_TYPE = 'Content-Type';
4+
5+
export const CORS_HEADERS = {
6+
'Access-Control-Allow-Headers': `${CONTENT_TYPE}, X-Requested-With`,
7+
'Access-Control-Allow-Methods': 'POST',
8+
'Access-Control-Allow-Origin': `${WEBSITE_URL}`,
9+
};
10+
11+
export const JSON_CONTENT_TYPE = 'application/json';
12+
export const TEXT_CONTENT_TYPE = 'text/plain';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './headers.mjs';
2+
export * from './messages.mjs';
3+
export * from './misc.mjs';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const UNPARSABLE_CONTENT = 'Content could not be parsed';
2+
export const UNSUPPORTED_ACCEPT_TYPES = 'Unsupported Accept type(s)';
3+
export const UNSUPPORTED_CONTENT_TYPE = 'Unsupported Content-Type';
4+
export const VALIDATION_ERROR = 'Validation error';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const WEBSITE_HOSTNAME = 'svachon.com';
2+
export const WEBSITE_URL = `https://${WEBSITE_HOSTNAME}/`;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import sanitizeHTML from 'sanitize-html';
2+
import { stripLow } from 'validator';
3+
4+
const REMOVE_HTML_CONFIG = { allowedAttributes: {}, allowedTags: [] };
5+
6+
const createArgs = (joi) => [
7+
{
8+
name: 'disallowHTML',
9+
assert: joi.boolean(),
10+
},
11+
];
12+
13+
const createMethod = (name) => {
14+
return function (disallowHTML = false) {
15+
return this.$_addRule({ name, args: { disallowHTML } });
16+
};
17+
};
18+
19+
const createValidate = (multiline = false) => {
20+
return (value, { error }, { disallowHTML }) =>
21+
stripLow(value, multiline) !== value ||
22+
sanitizeHTML(value, disallowHTML ? REMOVE_HTML_CONFIG : undefined) !== value
23+
? error('string.safe')
24+
: value;
25+
};
26+
27+
export default (joi) => ({
28+
type: 'string',
29+
base: joi.string(),
30+
messages: {
31+
'string.safe': '{{#label}} contains unsafe characters',
32+
},
33+
rules: {
34+
safe: {
35+
args: createArgs(joi),
36+
method: createMethod('safe'),
37+
validate: createValidate(),
38+
},
39+
safeMultiline: {
40+
args: createArgs(joi),
41+
method: createMethod('safeMultiline'),
42+
validate: createValidate(true),
43+
},
44+
},
45+
});
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { expect, test as it } from 'vitest';
2+
import Joi from 'joi';
3+
import JoiSafe from './';
4+
5+
const j = () => Joi.extend(JoiSafe);
6+
7+
const test = ({ disallowHTML, input, multiline, valid }) => {
8+
let error, value;
9+
if (multiline) {
10+
({ error, value } = j()
11+
.string()
12+
.safeMultiline(disallowHTML)
13+
.validate(input));
14+
} else {
15+
({ error, value } = j().string().safe(disallowHTML).validate(input));
16+
}
17+
if (valid) {
18+
expect(error).toBe(undefined);
19+
} else {
20+
expect(error).not.toBe(undefined);
21+
}
22+
expect(value).toBe(input);
23+
};
24+
25+
it('can be loaded by Joi', () => expect(() => j()).not.toThrow());
26+
27+
it('only applies to string types', () => {
28+
for (const type of [
29+
j().alternatives(),
30+
j().any(),
31+
j().array(),
32+
j().binary(),
33+
j().boolean(),
34+
j().date(),
35+
j().function(),
36+
j().link(),
37+
j().number(),
38+
j().object(),
39+
j().symbol(),
40+
]) {
41+
expect(() => type.safe()).toThrow();
42+
expect(() => type.safe(true)).toThrow();
43+
expect(() => type.safeMultiline()).toThrow();
44+
expect(() => type.safeMultiline(true)).toThrow();
45+
}
46+
47+
expect(() => j().string().safe()).not.toThrow();
48+
expect(() => j().string().safe(true)).not.toThrow();
49+
expect(() => j().string().safeMultiline()).not.toThrow();
50+
expect(() => j().string().safeMultiline(true)).not.toThrow();
51+
});
52+
53+
it('disallows unsafe characters', () => {
54+
test({
55+
input: 'safe\x00',
56+
multiline: false,
57+
valid: false,
58+
});
59+
test({
60+
input: 'safe\x00',
61+
multiline: true,
62+
valid: false,
63+
});
64+
test({
65+
input: 'safe\x0A\x0D',
66+
multiline: false,
67+
valid: false,
68+
});
69+
test({
70+
input: 'safe\x0A\x0D',
71+
multiline: true,
72+
valid: true,
73+
});
74+
test({
75+
input: 'safe\n\r',
76+
multiline: false,
77+
valid: false,
78+
});
79+
test({
80+
input: 'safe\n\r',
81+
multiline: true,
82+
valid: true,
83+
});
84+
test({
85+
input: 'safe\t',
86+
multiline: false,
87+
valid: false,
88+
});
89+
test({
90+
input: 'safe\t',
91+
multiline: true,
92+
valid: false,
93+
});
94+
});
95+
96+
it('disallows unsafe HTML', () => {
97+
test({
98+
disallowHTML: false,
99+
input: '<div>safe html</div>',
100+
multiline: false,
101+
valid: true,
102+
});
103+
test({
104+
disallowHTML: false,
105+
input: '<div>safe\n\rhtml</div>',
106+
multiline: true,
107+
valid: true,
108+
});
109+
test({
110+
disallowHTML: false,
111+
input: `unsafe<script>alert('test');</script>html`,
112+
multiline: false,
113+
valid: false,
114+
});
115+
});
116+
117+
it('can disallow HTML completely', () => {
118+
test({
119+
disallowHTML: true,
120+
input: '<div>safe html</div>',
121+
multiline: false,
122+
valid: false,
123+
});
124+
test({
125+
disallowHTML: true,
126+
input: 'safe\n\rhtml',
127+
multiline: true,
128+
valid: true,
129+
});
130+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { CORS_HEADERS } from '@sv-common/constants';
2+
3+
export default (response) => ({
4+
...response,
5+
headers: {
6+
...CORS_HEADERS,
7+
...response.headers,
8+
},
9+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { CONTENT_TYPE, JSON_CONTENT_TYPE } from '@sv-common/constants';
2+
import createResponse from './createResponse.mjs';
3+
import { expect } from 'vitest';
4+
import toBeJSONMessage from './toBeJSONMessage.mjs';
5+
6+
expect.extend({ toBeJSONMessage });
7+
8+
export default (messageStartsWith) =>
9+
createResponse({
10+
body: expect.toBeJSONMessage(messageStartsWith),
11+
headers: { [CONTENT_TYPE]: JSON_CONTENT_TYPE },
12+
statusCode: 400,
13+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { CONTENT_TYPE, TEXT_CONTENT_TYPE } from '@sv-common/constants';
2+
import createResponse from './createResponse.mjs';
3+
import { expect } from 'vitest';
4+
import stringStartingWith from './stringStartingWith.mjs';
5+
6+
expect.extend({ stringStartingWith });
7+
8+
export default (messageStartsWith) =>
9+
createResponse({
10+
body: expect.stringStartingWith(messageStartsWith),
11+
headers: { [CONTENT_TYPE]: TEXT_CONTENT_TYPE },
12+
statusCode: 406,
13+
});

0 commit comments

Comments
 (0)