@cruk/fundraising-data-validations 4.2.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/README.md +131 -0
- package/dist/@types/field-schema.d.ts +18 -0
- package/dist/@types/field-schema.js +1 -0
- package/dist/common.d.ts +10 -0
- package/dist/common.js +46 -0
- package/dist/direct-debit.d.ts +11 -0
- package/dist/direct-debit.js +51 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/supporter.d.ts +22 -0
- package/dist/supporter.js +235 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Fundraising data validations
|
|
2
|
+
|
|
3
|
+
This internal CRUKorg package provides regular expressions to validate commonly collected supporter data in fundraising products, ensuring the data meets the minimum requirements for downstream systems.
|
|
4
|
+
|
|
5
|
+
The motivation for creating this package is to standardise these validation rules to avoid duplicated interpretation of them in multiple products and minimise risks of passing invalid data downstream. This package is designed to be used in frontend forms and on backend services accepting any payloads of data to be sent downstream.
|
|
6
|
+
|
|
7
|
+
### Usage
|
|
8
|
+
|
|
9
|
+
#### To install the package locally
|
|
10
|
+
|
|
11
|
+
1. Run `npm install @crukorg/fundraising-data-validations` in your terminal.
|
|
12
|
+
1. Import the relevant schema into your validation files, e.g.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { supporterSchema } from "@crukorg/fundraising-data-validations";
|
|
16
|
+
|
|
17
|
+
const isValidFirstName =
|
|
18
|
+
supporterSchema.firstName.allowedCharacters.regex.test(inputFirstName);
|
|
19
|
+
|
|
20
|
+
if (!isValidFirstName) {
|
|
21
|
+
return supporterSchema.firstName.allowedCharacters.errorMessage;
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Zod example:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
import { supporterSchema } from "@cruk/fundraising-data-validations";
|
|
30
|
+
|
|
31
|
+
const isValidSupporter = z
|
|
32
|
+
.object({
|
|
33
|
+
firstName: z
|
|
34
|
+
.string()
|
|
35
|
+
.trim()
|
|
36
|
+
.regex(
|
|
37
|
+
supporterSchema.firstName.minMaxLength?.regex,
|
|
38
|
+
supporterSchema.firstName.minMaxLength?.errorMessage,
|
|
39
|
+
)
|
|
40
|
+
.regex(
|
|
41
|
+
supporterSchema.firstName.allowedCharacters.regex,
|
|
42
|
+
supporterSchema.firstName.allowedCharacters.errorMessage,
|
|
43
|
+
),
|
|
44
|
+
})
|
|
45
|
+
.required({
|
|
46
|
+
firstName: supporterSchema.firstName.isRequired?.required,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
isValidSupporter.parse(inputSupporter);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error instanceof ZodError) {
|
|
53
|
+
const errorMessages = prettifyError(error);
|
|
54
|
+
console.error(errorMessages);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
1. You may add additional, stricter rules on top of these validations, but these rules should form the foundation of any validations for form data.
|
|
60
|
+
|
|
61
|
+
#### Required and optional validation
|
|
62
|
+
|
|
63
|
+
Each field has several rules available for use. In each case, **required** and **allowedCharacters** regexes are the minimum required rules.
|
|
64
|
+
Most of the fields also have a **minMaxLength** rule, the only current exception being the **createdAt** field.
|
|
65
|
+
|
|
66
|
+
- **firstName** and **lastName**: **forbiddenSubstrings** should be enforced.
|
|
67
|
+
- **phoneNumber**: **forbiddenSubstrings** are optional depending on if your validation requires phoneNumber to be a landline.
|
|
68
|
+
|
|
69
|
+
### Limitations
|
|
70
|
+
|
|
71
|
+
- The regular expressions are written to be language agnostic, but they have only been tested in TypeScript applications.
|
|
72
|
+
- The regular expressions are compatible with AWS tools that allow regular expressions for string comparison, such as Step Functions, since allowed regex in AWS have some limitations.
|
|
73
|
+
|
|
74
|
+
### ReDoS regression tests
|
|
75
|
+
|
|
76
|
+
This package includes a dedicated ReDoS regression suite in `src/__tests__/redos.unit.test.ts`.
|
|
77
|
+
|
|
78
|
+
The test iterates over all runtime regexes in the exported schemas and runs long adversarial-style near-miss inputs against each pattern. This helps detect catastrophic backtracking risks early if a future regex introduces an OWASP-style "evil regex" shape.
|
|
79
|
+
|
|
80
|
+
Run the targeted suite with:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm test -- --run src/__tests__/redos.unit.test.ts
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Notes:
|
|
87
|
+
|
|
88
|
+
- If this test starts timing out, treat it as a potential ReDoS regression and review any recently changed regex.
|
|
89
|
+
- Keep this file as a fast guardrail. If you add new schema regexes, ensure they are still collected by this suite.
|
|
90
|
+
|
|
91
|
+
### Transformations
|
|
92
|
+
|
|
93
|
+
> [!IMPORTANT]
|
|
94
|
+
> The following data transformation must be handled by the products, as they is not included in the validations package to minimise friction to the user journey.
|
|
95
|
+
|
|
96
|
+
#### firstName
|
|
97
|
+
|
|
98
|
+
- remove whitespace before or after a hyphen or an apostrophe, e.g. " - " is converted to "-"
|
|
99
|
+
- must be in Proper Case
|
|
100
|
+
|
|
101
|
+
#### lastName
|
|
102
|
+
|
|
103
|
+
- remove whitespace before or after a hyphen or an apostrophe, e.g. " - " is converted to "-"
|
|
104
|
+
|
|
105
|
+
#### email
|
|
106
|
+
|
|
107
|
+
- must be in lowercase
|
|
108
|
+
|
|
109
|
+
#### phoneNumber / mobileNumber
|
|
110
|
+
|
|
111
|
+
- remove whitespace
|
|
112
|
+
|
|
113
|
+
#### address lines
|
|
114
|
+
|
|
115
|
+
- replace comma with space
|
|
116
|
+
- must be no duplicates across lines
|
|
117
|
+
|
|
118
|
+
#### postcode
|
|
119
|
+
|
|
120
|
+
- must be UPPERCASE
|
|
121
|
+
|
|
122
|
+
### Contributing
|
|
123
|
+
|
|
124
|
+
1. Run `npm install` to install packages.
|
|
125
|
+
1. Make any changes in `src` and add test coverage.
|
|
126
|
+
1. Run `npm run build` to compile the changes to `dist`.
|
|
127
|
+
1. Run `npm run lint:fix` to format code.
|
|
128
|
+
1. Commit, push, create PR etc. **NOTE: this project uses conventional commits and you will be prompted on the command line to follow this convention.**
|
|
129
|
+
1. PRs require at least one review by a code owner to be merged
|
|
130
|
+
1. When your PR is merged, it triggers a PR close workflow which updates the package version according to the conventional commits in the PR. Ensure this workflow is complete before progressin to the next step.
|
|
131
|
+
1. The package is published publicly to npm. Trigger the action manually from GitHub to deploy the latest version.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface FieldSchema {
|
|
2
|
+
isRequired: {
|
|
3
|
+
required: boolean;
|
|
4
|
+
errorMessage?: string;
|
|
5
|
+
};
|
|
6
|
+
allowedCharacters: {
|
|
7
|
+
regex: RegExp;
|
|
8
|
+
errorMessage: string;
|
|
9
|
+
};
|
|
10
|
+
minMaxLength?: {
|
|
11
|
+
regex: RegExp;
|
|
12
|
+
errorMessage: string;
|
|
13
|
+
};
|
|
14
|
+
forbiddenSubstrings?: {
|
|
15
|
+
regex: RegExp;
|
|
16
|
+
errorMessage: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/common.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FieldSchema } from "./@types/field-schema";
|
|
2
|
+
export declare const CommonField: {
|
|
3
|
+
readonly AMOUNT: "amount";
|
|
4
|
+
readonly CREATED_AT: "createdAt";
|
|
5
|
+
readonly IN_MEMORY_NAME: "inMemoryName";
|
|
6
|
+
};
|
|
7
|
+
export type CommonField = (typeof CommonField)[keyof typeof CommonField];
|
|
8
|
+
export declare const commonSchema: {
|
|
9
|
+
[key in CommonField]: FieldSchema;
|
|
10
|
+
};
|
package/dist/common.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const CommonField = {
|
|
2
|
+
AMOUNT: "amount",
|
|
3
|
+
CREATED_AT: "createdAt",
|
|
4
|
+
IN_MEMORY_NAME: "inMemoryName",
|
|
5
|
+
};
|
|
6
|
+
export const commonSchema = {
|
|
7
|
+
[CommonField.AMOUNT]: {
|
|
8
|
+
isRequired: {
|
|
9
|
+
required: true,
|
|
10
|
+
errorMessage: "Please enter an amount.",
|
|
11
|
+
},
|
|
12
|
+
minMaxLength: {
|
|
13
|
+
regex: /^.{1,20}$/,
|
|
14
|
+
errorMessage: "Please enter the amount between 1 - 20 digits.",
|
|
15
|
+
},
|
|
16
|
+
allowedCharacters: {
|
|
17
|
+
regex: /^\d+(\.\d{1,2})?$/,
|
|
18
|
+
errorMessage:
|
|
19
|
+
"Please enter an amount, only two decimal places are allowed.",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
[CommonField.CREATED_AT]: {
|
|
23
|
+
isRequired: {
|
|
24
|
+
required: false,
|
|
25
|
+
},
|
|
26
|
+
allowedCharacters: {
|
|
27
|
+
regex:
|
|
28
|
+
/^\d{4}-(0[1-9]|1[0-2])-([0-2]\d|3[01])T([01]\d|2[0-3]):[0-5]\d:[0-5]\dZ$/,
|
|
29
|
+
errorMessage: "Please enter a date time string in UTC timezone.",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
[CommonField.IN_MEMORY_NAME]: {
|
|
33
|
+
isRequired: {
|
|
34
|
+
required: false,
|
|
35
|
+
},
|
|
36
|
+
minMaxLength: {
|
|
37
|
+
regex: /^.{1,100}$/,
|
|
38
|
+
errorMessage: "Please complete with a maximum of 100 characters.",
|
|
39
|
+
},
|
|
40
|
+
allowedCharacters: {
|
|
41
|
+
regex:
|
|
42
|
+
/^[0-9A-Za-zÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƼƽƾƿDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟ][ '‘’0-9A-Za-zÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƼƽƾƿDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟ-]*$/i,
|
|
43
|
+
errorMessage: "Please enter a valid name.",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FieldSchema } from "./@types/field-schema";
|
|
2
|
+
export declare const DirectDebitField: {
|
|
3
|
+
readonly ACCOUNT_HOLDER_NAME: "accountHolderName";
|
|
4
|
+
readonly BANK_ACCOUNT_NUMBER: "bankAccountNumber";
|
|
5
|
+
readonly BANK_SORT_CODE: "bankSortCode";
|
|
6
|
+
};
|
|
7
|
+
export type DirectDebitField =
|
|
8
|
+
(typeof DirectDebitField)[keyof typeof DirectDebitField];
|
|
9
|
+
export declare const directDebitSchema: {
|
|
10
|
+
[key in DirectDebitField]: FieldSchema;
|
|
11
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const DirectDebitField = {
|
|
2
|
+
ACCOUNT_HOLDER_NAME: "accountHolderName",
|
|
3
|
+
BANK_ACCOUNT_NUMBER: "bankAccountNumber",
|
|
4
|
+
BANK_SORT_CODE: "bankSortCode",
|
|
5
|
+
};
|
|
6
|
+
export const directDebitSchema = {
|
|
7
|
+
[DirectDebitField.ACCOUNT_HOLDER_NAME]: {
|
|
8
|
+
isRequired: {
|
|
9
|
+
required: true,
|
|
10
|
+
errorMessage: "Please enter the account holder's name.",
|
|
11
|
+
},
|
|
12
|
+
minMaxLength: {
|
|
13
|
+
regex: /^.{1,50}$/,
|
|
14
|
+
errorMessage:
|
|
15
|
+
"Please enter the account holder's name between 1 - 50 characters.",
|
|
16
|
+
},
|
|
17
|
+
allowedCharacters: {
|
|
18
|
+
regex:
|
|
19
|
+
/^[0-9A-Za-zÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƼƽƾƿDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟ-][ '‘’0-9A-Za-zÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƼƽƾƿDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟ-]*$/i,
|
|
20
|
+
errorMessage: "Please enter a valid name.",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
[DirectDebitField.BANK_ACCOUNT_NUMBER]: {
|
|
24
|
+
isRequired: {
|
|
25
|
+
required: true,
|
|
26
|
+
errorMessage: "Please enter your account number.",
|
|
27
|
+
},
|
|
28
|
+
minMaxLength: {
|
|
29
|
+
regex: /^.{8}$/,
|
|
30
|
+
errorMessage: "Please enter account number with 8 digits.",
|
|
31
|
+
},
|
|
32
|
+
allowedCharacters: {
|
|
33
|
+
regex: /^[\d]+$/,
|
|
34
|
+
errorMessage: "Please enter a valid account number.",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
[DirectDebitField.BANK_SORT_CODE]: {
|
|
38
|
+
isRequired: {
|
|
39
|
+
required: true,
|
|
40
|
+
errorMessage: "Please enter your sort code.",
|
|
41
|
+
},
|
|
42
|
+
minMaxLength: {
|
|
43
|
+
regex: /^.{6}$/,
|
|
44
|
+
errorMessage: "Your sort code should have 6 digits.",
|
|
45
|
+
},
|
|
46
|
+
allowedCharacters: {
|
|
47
|
+
regex: /^[\d]+$/,
|
|
48
|
+
errorMessage: "Please enter a valid sort code.",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FieldSchema } from "./@types/field-schema";
|
|
2
|
+
export declare const SupporterField: {
|
|
3
|
+
readonly ADDRESS_LINE_1: "addressLine1";
|
|
4
|
+
readonly ADDRESS_LINE_2: "addressLine2";
|
|
5
|
+
readonly ADDRESS_LINE_3: "addressLine3";
|
|
6
|
+
readonly ADDRESS_LINE_4: "addressLine4";
|
|
7
|
+
readonly COUNTRY: "country";
|
|
8
|
+
readonly EMAIL_ADDRESS: "emailAddress";
|
|
9
|
+
readonly FIRST_NAME: "firstName";
|
|
10
|
+
readonly LAST_NAME: "lastName";
|
|
11
|
+
readonly MOBILE_NUMBER_UK: "mobileNumberUK";
|
|
12
|
+
readonly PHONE_NUMBER: "phoneNumber";
|
|
13
|
+
readonly POSTCODE_UK: "postcodeUK";
|
|
14
|
+
readonly POSTCODE_INTL: "postcodeIntl";
|
|
15
|
+
readonly TITLE: "title";
|
|
16
|
+
readonly TOWN: "town";
|
|
17
|
+
};
|
|
18
|
+
export type SupporterField =
|
|
19
|
+
(typeof SupporterField)[keyof typeof SupporterField];
|
|
20
|
+
export declare const supporterSchema: {
|
|
21
|
+
[key in SupporterField]: FieldSchema;
|
|
22
|
+
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
export const SupporterField = {
|
|
2
|
+
ADDRESS_LINE_1: "addressLine1",
|
|
3
|
+
ADDRESS_LINE_2: "addressLine2",
|
|
4
|
+
ADDRESS_LINE_3: "addressLine3",
|
|
5
|
+
ADDRESS_LINE_4: "addressLine4",
|
|
6
|
+
COUNTRY: "country",
|
|
7
|
+
EMAIL_ADDRESS: "emailAddress",
|
|
8
|
+
FIRST_NAME: "firstName",
|
|
9
|
+
LAST_NAME: "lastName",
|
|
10
|
+
MOBILE_NUMBER_UK: "mobileNumberUK",
|
|
11
|
+
PHONE_NUMBER: "phoneNumber",
|
|
12
|
+
POSTCODE_UK: "postcodeUK",
|
|
13
|
+
POSTCODE_INTL: "postcodeIntl",
|
|
14
|
+
TITLE: "title",
|
|
15
|
+
TOWN: "town",
|
|
16
|
+
};
|
|
17
|
+
export const supporterSchema = {
|
|
18
|
+
[SupporterField.TITLE]: {
|
|
19
|
+
isRequired: {
|
|
20
|
+
required: false,
|
|
21
|
+
},
|
|
22
|
+
allowedCharacters: {
|
|
23
|
+
regex: /^(dr|mr|mrs|miss|ms|mx)$/i,
|
|
24
|
+
errorMessage: "Enter one of 'Dr', 'Mr', 'Mrs', 'Miss', 'Ms', 'Mx'",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
[SupporterField.FIRST_NAME]: {
|
|
28
|
+
isRequired: {
|
|
29
|
+
required: true,
|
|
30
|
+
errorMessage: "First name is required.",
|
|
31
|
+
},
|
|
32
|
+
allowedCharacters: {
|
|
33
|
+
regex:
|
|
34
|
+
/^[A-Za-zÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƼƽƾƿDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟ][ '‘’A-Za-zÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƼƽƾƿDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟ-]*$/i,
|
|
35
|
+
errorMessage:
|
|
36
|
+
"First names can only contain letters, hyphens and apostrophes.",
|
|
37
|
+
},
|
|
38
|
+
minMaxLength: {
|
|
39
|
+
regex: /^.{2,40}$/,
|
|
40
|
+
errorMessage: "First name must be between 2 and 40 characters.",
|
|
41
|
+
},
|
|
42
|
+
forbiddenSubstrings: {
|
|
43
|
+
regex: /\b(and|plus|test|undefined|unknown)\b/i,
|
|
44
|
+
errorMessage:
|
|
45
|
+
"First name must be a valid name and must be an individual.",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
[SupporterField.LAST_NAME]: {
|
|
49
|
+
isRequired: {
|
|
50
|
+
required: true,
|
|
51
|
+
errorMessage: "Last name is required.",
|
|
52
|
+
},
|
|
53
|
+
allowedCharacters: {
|
|
54
|
+
regex:
|
|
55
|
+
/^[A-Za-zÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƼƽƾƿDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟ][ '‘’A-Za-zÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƼƽƾƿDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏɐɑɒɓɔɕɖɗɘəɚɛɜɝɞɟ-]*$/i,
|
|
56
|
+
errorMessage:
|
|
57
|
+
"Last names can only contain letters, hyphens and apostrophes.",
|
|
58
|
+
},
|
|
59
|
+
minMaxLength: {
|
|
60
|
+
regex: /^.{2,50}$/,
|
|
61
|
+
errorMessage: "Last name must be between 2 and 50 characters.",
|
|
62
|
+
},
|
|
63
|
+
forbiddenSubstrings: {
|
|
64
|
+
regex: /\b(and|plus|test|undefined|unknown)\b/i,
|
|
65
|
+
errorMessage: "Last name must be a valid name and must be an individual.",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
[SupporterField.ADDRESS_LINE_1]: {
|
|
69
|
+
isRequired: {
|
|
70
|
+
required: true,
|
|
71
|
+
errorMessage: "First line of address is required.",
|
|
72
|
+
},
|
|
73
|
+
allowedCharacters: {
|
|
74
|
+
regex: /^[\d &'‘’.,/A-Za-z-]+$/,
|
|
75
|
+
errorMessage:
|
|
76
|
+
"The first line of address can only contain letters, numbers and the following special characters '-/.&.",
|
|
77
|
+
},
|
|
78
|
+
minMaxLength: {
|
|
79
|
+
regex: /^.{1,200}$/,
|
|
80
|
+
errorMessage:
|
|
81
|
+
"The first line of address must be between 1 and 200 characters.",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
[SupporterField.ADDRESS_LINE_2]: {
|
|
85
|
+
isRequired: {
|
|
86
|
+
required: false,
|
|
87
|
+
},
|
|
88
|
+
allowedCharacters: {
|
|
89
|
+
regex: /^[\d &'‘’.,/A-Za-z-]+$/,
|
|
90
|
+
errorMessage:
|
|
91
|
+
"The second line of address can only contain letters, numbers and the following special characters '-/.&.",
|
|
92
|
+
},
|
|
93
|
+
minMaxLength: {
|
|
94
|
+
regex: /^.{1,100}$/,
|
|
95
|
+
errorMessage: "The second line of address must be under 100 characters.",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
[SupporterField.ADDRESS_LINE_3]: {
|
|
99
|
+
isRequired: {
|
|
100
|
+
required: false,
|
|
101
|
+
},
|
|
102
|
+
allowedCharacters: {
|
|
103
|
+
regex: /^[\d &'‘’.,/A-Za-z-]+$/,
|
|
104
|
+
errorMessage:
|
|
105
|
+
"The third line of address can only contain letters, numbers and the following special characters '-/.&.",
|
|
106
|
+
},
|
|
107
|
+
minMaxLength: {
|
|
108
|
+
regex: /^.{1,100}$/,
|
|
109
|
+
errorMessage: "The third line of address must be under 100 characters.",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
[SupporterField.ADDRESS_LINE_4]: {
|
|
113
|
+
isRequired: {
|
|
114
|
+
required: false,
|
|
115
|
+
},
|
|
116
|
+
allowedCharacters: {
|
|
117
|
+
regex: /^[\d &'‘’.,/A-Za-z-]+$/,
|
|
118
|
+
errorMessage:
|
|
119
|
+
"The fourth line of address can only contain letters, numbers and the following special characters '-/.&.",
|
|
120
|
+
},
|
|
121
|
+
minMaxLength: {
|
|
122
|
+
regex: /^.{1,100}$/,
|
|
123
|
+
errorMessage: "The fourth line of address must be under 100 characters.",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
[SupporterField.TOWN]: {
|
|
127
|
+
isRequired: {
|
|
128
|
+
required: true,
|
|
129
|
+
errorMessage: "Town is required.",
|
|
130
|
+
},
|
|
131
|
+
allowedCharacters: {
|
|
132
|
+
regex: /^[\d &'‘’./A-Za-z-]+$/,
|
|
133
|
+
errorMessage:
|
|
134
|
+
"Town can only contain letters, numbers and the following special characters ' - / . &.",
|
|
135
|
+
},
|
|
136
|
+
minMaxLength: {
|
|
137
|
+
regex: /^.{1,40}$/,
|
|
138
|
+
errorMessage: "Town must be between 1 and 40 characters.",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
[SupporterField.POSTCODE_UK]: {
|
|
142
|
+
isRequired: {
|
|
143
|
+
required: true,
|
|
144
|
+
errorMessage: "Postcode is required.",
|
|
145
|
+
},
|
|
146
|
+
allowedCharacters: {
|
|
147
|
+
regex: /^(?!.*[\n\r\t])[^,;"“”#_.'‘’()*+:<=>?\[\]{}~¿£$@!¡%€&]+$/,
|
|
148
|
+
errorMessage:
|
|
149
|
+
"The postcode can only contain letters, numbers and spaces.",
|
|
150
|
+
},
|
|
151
|
+
minMaxLength: {
|
|
152
|
+
regex: /^.{2,8}$/,
|
|
153
|
+
errorMessage: "The postcode must be between 2 and 8 characters.",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
[SupporterField.POSTCODE_INTL]: {
|
|
157
|
+
isRequired: {
|
|
158
|
+
required: true,
|
|
159
|
+
errorMessage: "Postcode is required.",
|
|
160
|
+
},
|
|
161
|
+
allowedCharacters: {
|
|
162
|
+
regex: /^(?!.*[\n\r\t])[^,;"“”#_.'‘’()*+:<=>?\[\]{}~¿£$@!¡%€&]+$/,
|
|
163
|
+
errorMessage:
|
|
164
|
+
"The postcode can only contain letters, numbers and spaces.",
|
|
165
|
+
},
|
|
166
|
+
minMaxLength: {
|
|
167
|
+
regex: /^.{2,10}$/,
|
|
168
|
+
errorMessage: "The postcode must be between 2 and 10 characters.",
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
[SupporterField.COUNTRY]: {
|
|
172
|
+
isRequired: {
|
|
173
|
+
required: true,
|
|
174
|
+
errorMessage: "Country is required.",
|
|
175
|
+
},
|
|
176
|
+
allowedCharacters: {
|
|
177
|
+
regex: /^(?!.*[\n\r\t])[^;"“”_#()*+:<=>?\[\]{}~¿£$@!¡%€\d]+$/,
|
|
178
|
+
errorMessage: "The country can only contain letters and spaces.",
|
|
179
|
+
},
|
|
180
|
+
minMaxLength: {
|
|
181
|
+
regex: /^.{1,60}$/,
|
|
182
|
+
errorMessage: "The country must be between 1 and 60 characters.",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
[SupporterField.EMAIL_ADDRESS]: {
|
|
186
|
+
isRequired: {
|
|
187
|
+
required: true,
|
|
188
|
+
errorMessage: "An email address is required.",
|
|
189
|
+
},
|
|
190
|
+
allowedCharacters: {
|
|
191
|
+
regex:
|
|
192
|
+
/^[^ "“”'‘’(),:;\\<>\[\]@]+@[^\s"“”'‘’(),.:;\\<>\[\]@_]+\.[^\s"“”'‘’(),:;\\<>\[\]@_]+$/,
|
|
193
|
+
errorMessage:
|
|
194
|
+
"This email address doesn't follow the correct format - for example name@domain.com",
|
|
195
|
+
},
|
|
196
|
+
minMaxLength: {
|
|
197
|
+
regex: /^.{1,64}@.{1,64}\..{2,}$/,
|
|
198
|
+
errorMessage: "The email address is too long.",
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
[SupporterField.PHONE_NUMBER]: {
|
|
202
|
+
isRequired: {
|
|
203
|
+
required: false,
|
|
204
|
+
},
|
|
205
|
+
allowedCharacters: {
|
|
206
|
+
regex: /^[\+\d ]+$/,
|
|
207
|
+
errorMessage:
|
|
208
|
+
"The phone number can only contain numbers, spaces and plus sign.",
|
|
209
|
+
},
|
|
210
|
+
minMaxLength: {
|
|
211
|
+
regex: /^.{0,16}$/,
|
|
212
|
+
errorMessage:
|
|
213
|
+
"The phone number must be between 1 and 16 numbers, optionally starting with +.",
|
|
214
|
+
},
|
|
215
|
+
forbiddenSubstrings: {
|
|
216
|
+
regex: /^(\+447|00447|07|\+4407|004407)/,
|
|
217
|
+
errorMessage: "Mobile numbers are not allowed.",
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
[SupporterField.MOBILE_NUMBER_UK]: {
|
|
221
|
+
isRequired: {
|
|
222
|
+
required: false,
|
|
223
|
+
},
|
|
224
|
+
allowedCharacters: {
|
|
225
|
+
regex: /^(\+447[ \d]+|00447[ \d]+|07[ \d]+|\+4407[ \d]+|004407[ \d]+)$/,
|
|
226
|
+
errorMessage:
|
|
227
|
+
"The phone number can only contain numbers, spaces and plus sign.",
|
|
228
|
+
},
|
|
229
|
+
minMaxLength: {
|
|
230
|
+
regex: /^.{1,16}$/,
|
|
231
|
+
errorMessage:
|
|
232
|
+
"The mobile number must be between 1 and 16 numbers, optionally starting with +.",
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cruk/fundraising-data-validations",
|
|
3
|
+
"version": "4.2.0",
|
|
4
|
+
"description": "Validation rules for fundraising data",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "rm -r ./dist && tsc",
|
|
14
|
+
"lint": "eslint . && prettier --check .",
|
|
15
|
+
"lint:fix": "eslint . --fix && prettier --write --log-level=warn .",
|
|
16
|
+
"prepare": "husky",
|
|
17
|
+
"release": "semantic-release",
|
|
18
|
+
"test": "vitest",
|
|
19
|
+
"version": "echo $npm_package_version"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/CRUKorg/fundraising-data-validations.git"
|
|
24
|
+
},
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"registry": "https://registry.npmjs.org"
|
|
29
|
+
},
|
|
30
|
+
"config": {
|
|
31
|
+
"commitizen": {
|
|
32
|
+
"path": "./node_modules/cz-conventional-changelog"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/CRUKorg/fundraising-data-validations#readme",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@commitlint/cli": "^20.1.0",
|
|
38
|
+
"@commitlint/config-conventional": "^20.0.0",
|
|
39
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
40
|
+
"@semantic-release/git": "^10.0.1",
|
|
41
|
+
"commitizen": "^4.3.1",
|
|
42
|
+
"eslint": "^9.36.0",
|
|
43
|
+
"eslint-plugin-unicorn": "^61.0.2",
|
|
44
|
+
"husky": "^9.1.7",
|
|
45
|
+
"prettier": "^3.6.2",
|
|
46
|
+
"semantic-release": "^24.2.9",
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"typescript-eslint": "^8.45.0",
|
|
49
|
+
"vitest": "^3.2.4"
|
|
50
|
+
}
|
|
51
|
+
}
|