@defra-fish/business-rules-lib 1.49.0-rc.0 → 1.49.0-rc.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra-fish/business-rules-lib",
3
- "version": "1.49.0-rc.0",
3
+ "version": "1.49.0-rc.10",
4
4
  "description": "Shared business rules for the rod licensing digital services",
5
5
  "type": "module",
6
6
  "engines": {
@@ -37,5 +37,5 @@
37
37
  "moment": "^2.29.1",
38
38
  "uuid": "^8.3.2"
39
39
  },
40
- "gitHead": "7e16adc1d78e8619339261efe774e81a6f87d68c"
40
+ "gitHead": "fac8dbbd7e237e1f417c46bc4d6b138ffc035609"
41
41
  }
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as contactValidation from './validators/contact.validators.js'
2
+ import * as dateValidation from './validators/date.validators.js'
2
3
  import * as permissionValidation from './validators/permission.validators.js'
3
4
  export * from './util/ages.js'
4
5
  export * from './util/permissions.js'
@@ -6,5 +7,6 @@ export * from './constants.js'
6
7
 
7
8
  export const validation = {
8
9
  contact: contactValidation,
10
+ date: dateValidation,
9
11
  permission: permissionValidation
10
12
  }
@@ -5,6 +5,9 @@ import moment from 'moment'
5
5
  const INVALID_DATE_ERROR_MESSAGE = '"value" must be in [YYYY-MM-DD] format'
6
6
 
7
7
  describe('contact validators', () => {
8
+ beforeEach(() => {
9
+ jest.resetAllMocks()
10
+ })
8
11
  describe('birthDateValidator', () => {
9
12
  const validDate = moment().subtract(1, 'day')
10
13
 
@@ -128,6 +131,24 @@ describe('contact validators', () => {
128
131
  '"value" must contain at least 3 alpha characters'
129
132
  )
130
133
  })
134
+
135
+ it.each([
136
+ 'M̵i̵c̵h̵a̵e̵l̵',
137
+ 'M̷i̷c̷h̷a̷e̷l̷',
138
+ 'M͟i͟c͟h͟a͟e͟l͟',
139
+ '𝔐𝔦𝔠𝔥𝔞𝔢𝔩',
140
+ '𝕄𝕚𝕔𝕙𝕒𝕖𝕝',
141
+ 'ןǝɐɥɔıW',
142
+ 'Ⓜⓘⓒⓗⓐⓔⓛ',
143
+ '🄼🄸🄲🄷🄰🄴🄻',
144
+ 'Mɪᴄʜᴀᴇʟ',
145
+ 'ᴹᶦᶜʰᵃᵉˡ',
146
+ '𖢑𖥣𖥐𖦙𖧥𖠢ꛚ',
147
+ 'ℳ𝒾𝒸𝒽𝒶ℯ𝓁',
148
+ '𝙼𝚒𝚌𝚑𝚊𝚎𝚕 '
149
+ ])('prohibits a string with non-standard characters: %s', async c => {
150
+ await expect(contactValidation.createFirstNameValidator(Joi).validateAsync(c)).rejects.toThrow()
151
+ })
131
152
  })
132
153
 
133
154
  describe('lastNameValidator', () => {
@@ -229,6 +250,24 @@ describe('contact validators', () => {
229
250
  '"value" must contain at least 3 alpha characters'
230
251
  )
231
252
  })
253
+
254
+ it.each([
255
+ 'M̵i̵c̵h̵a̵e̵l̵',
256
+ 'M̷i̷c̷h̷a̷e̷l̷',
257
+ 'M͟i͟c͟h͟a͟e͟l͟',
258
+ '𝔐𝔦𝔠𝔥𝔞𝔢𝔩',
259
+ '𝕄𝕚𝕔𝕙𝕒𝕖𝕝',
260
+ 'ןǝɐɥɔıW',
261
+ 'Ⓜⓘⓒⓗⓐⓔⓛ',
262
+ '🄼🄸🄲🄷🄰🄴🄻',
263
+ 'Mɪᴄʜᴀᴇʟ',
264
+ 'ᴹᶦᶜʰᵃᵉˡ',
265
+ '𖢑𖥣𖥐𖦙𖧥𖠢ꛚ',
266
+ 'ℳ𝒾𝒸𝒽𝒶ℯ𝓁',
267
+ '𝙼𝚒𝚌𝚑𝚊𝚎𝚕'
268
+ ])('prohibits a string with non-standard characters: %s', async c => {
269
+ await expect(contactValidation.createLastNameValidator(Joi).validateAsync(c)).rejects.toThrow()
270
+ })
232
271
  })
233
272
 
234
273
  describe('emailValidator', () => {
@@ -241,6 +280,28 @@ describe('contact validators', () => {
241
280
  '"value" must be a valid email'
242
281
  )
243
282
  })
283
+
284
+ it('allows a range of unicode characters from plane 1', async () => {
285
+ const internationStr = 'æçéñøķť@email.com'
286
+ await expect(contactValidation.createEmailValidator(Joi).validateAsync(internationStr)).resolves.toEqual('æçéñøķť@email.com')
287
+ })
288
+
289
+ it.each([
290
+ 'ᵐᵢᶜₕᵃₑˡ@ᵉₘᵃᵢˡ.ᶜₒᵐ',
291
+ '𝓂𝒾𝒸𝒽𝒶ℯ𝓁@ℯ𝓂𝒶𝒾𝓁.𝒸ℴ𝓂',
292
+ 'm̶i̶c̶h̶a̶e̶l̶@̶e̶m̶a̶i̶l̶.̶c̶o̶m̶',
293
+ 'm̷i̷c̷h̷a̷e̷l̷@̷e̷m̷a̷i̷l̷.̷c̷o̷m̷',
294
+ '𝖒𝖎𝖈𝖍𝖆𝖊𝖑@𝖊𝖒𝖆𝖎𝖑.𝖈𝖔𝖒',
295
+ 'm̲i̲c̲h̲a̲e̲l̲@̲e̲m̲a̲i̲l̲.̲c̲o̲m̲',
296
+ 'ꕮꕯꖀꖾꗇꗍꝆ@ꗍꕮꗇꕯꝆ.ꖀꗞꕮ',
297
+ '🅼🅘🅲🅗🅰🅔🅻@🅴🅜🅰🅘🅻.🅲🅞🅼',
298
+ 'ɯoɔ˙ןıɐɯǝ@ןǝɐɥɔıɯ',
299
+ 'ꮇꮖꮯꮋꭺꭼꮮ@ꭼꮇꭺꮖꮮ.ꮯꮎꮇ',
300
+ 'michael@email.com',
301
+ 'ₘᵢcₕaₑₗ@ₑₘaᵢₗ.cₒₘ'
302
+ ])('prohibits a string with non-standard characters: %s', async c => {
303
+ await expect(contactValidation.createEmailValidator(Joi).validateAsync(c)).rejects.toThrow()
304
+ })
244
305
  })
245
306
 
246
307
  describe('mobilePhoneValidator', () => {
@@ -251,6 +312,24 @@ describe('contact validators', () => {
251
312
  it.each(['test', '07700 test'])('rejects the invalid number %s', async number => {
252
313
  await expect(contactValidation.createMobilePhoneValidator(Joi).validateAsync(number)).rejects.toThrow()
253
314
  })
315
+
316
+ it.each([
317
+ '𝟘𝟟𝟟𝟘𝟘 𝟡𝟘𝟘𝟘𝟠𝟠',
318
+ '0𝟟7𝟘0 9𝟘0𝟘8𝟠',
319
+ '0𝟽𝟽00 𝟿000𝟾𝟾',
320
+ '0̵7̵7̵0̵0̵ ̵9̵0̵0̵0̵8̵8̵',
321
+ '0͟7͟7͟0͟0͟ ͟9͟0͟0͟0͟8͟8͟',
322
+ '0̸7̸7̸0̸0̸ ̸9̸0̸0̸0̸8̸8̸',
323
+ '07700 900088',
324
+ '⁰⁷⁷⁰⁰ ⁹⁰⁰⁰⁸⁸',
325
+ '⓿➐➐⓿⓿ ➒⓿⓿⓿➑➑',
326
+ '+ㄐㄐワワㄖㄖ ㄢㄖㄖㄖ曰曰',
327
+ '07700👏 900088',
328
+ '0̐̈7̐̈7̐̈0̐̈0̐̈ ̐̈9̐̈0̐̈0̐̈0̐̈8̐̈8̐̈',
329
+ '+4④7⑦0⓪ ⑨0⓪0⑧8'
330
+ ])('prohibits a string with non-standard characters: %s', async c => {
331
+ await expect(contactValidation.createMobilePhoneValidator(Joi).validateAsync(c)).rejects.toThrow()
332
+ })
254
333
  })
255
334
 
256
335
  describe('ukPostcodeValidator', () => {
@@ -265,33 +344,74 @@ describe('contact validators', () => {
265
344
  it('expects a minimum of 1 character', async () => {
266
345
  await expect(contactValidation.createUKPostcodeValidator(Joi).validateAsync('')).rejects.toThrow('"value" is not allowed to be empty')
267
346
  })
347
+
268
348
  it('expects a maximum of 12 characters', async () => {
269
349
  await expect(contactValidation.createUKPostcodeValidator(Joi).validateAsync('0123456789ABC')).rejects.toThrow(
270
350
  '"value" length must be less than or equal to 12 characters long'
271
351
  )
272
352
  })
353
+
273
354
  it('expects postcodes to conform to the pattern used in the UK', async () => {
274
355
  await expect(contactValidation.createUKPostcodeValidator(Joi).validateAsync('0123456789')).rejects.toThrow(
275
356
  /fails to match the required pattern/
276
357
  )
277
358
  })
359
+
360
+ it.each([
361
+ 'A⃣B⃣1⃣2⃣ 1⃣A⃣B⃣',
362
+ '🄐🄑⑴⑵ ⑴🄐🄑',
363
+ '丹乃丨己 丨丹乃',
364
+ 'A͢B͢1͢2͢ ͢1͢A͢B͢',
365
+ 'A⃟B⃟1⃟2⃟ 1⃟A⃟B⃟',
366
+ 'AB12👏 1AB',
367
+ '🅐Ⓑ➊② ①🅐Ⓑ',
368
+ '𝘼𝐵𝟭𝟮 1𝘼𝘽',
369
+ '🅰𝙱12 1𝕬𝓑',
370
+ '1ᴬAᴮB¹²',
371
+ 'ᴬᴮ¹² ¹ᴬᴮ',
372
+ 'q∀1 21q∀',
373
+ 'A̶B̶1̶2̶ ̶1̶A̶B̶'
374
+ ])('prohibits a string with non-standard characters: %s', async c => {
375
+ await expect(contactValidation.createUKPostcodeValidator(Joi).validateAsync(c)).rejects.toThrow()
376
+ })
278
377
  })
279
378
 
280
379
  describe('overseasPostcodeValidator', () => {
281
380
  it('converts to uppercase and trims', async () => {
282
381
  await expect(contactValidation.createOverseasPostcodeValidator(Joi).validateAsync('a ')).resolves.toEqual('A')
283
382
  })
383
+
284
384
  it('expects a minimum of 1 character', async () => {
285
385
  await expect(contactValidation.createOverseasPostcodeValidator(Joi).validateAsync('')).rejects.toThrow(
286
386
  '"value" is not allowed to be empty'
287
387
  )
288
388
  })
389
+
289
390
  it('expects a maximum of 12 characters', async () => {
290
391
  await expect(contactValidation.createOverseasPostcodeValidator(Joi).validateAsync('123456789AAAA')).rejects.toThrow()
291
392
  })
393
+
292
394
  it('will not accept special characters', async () => {
293
395
  await expect(contactValidation.createOverseasPostcodeValidator(Joi).validateAsync('12£4')).rejects.toThrow()
294
396
  })
397
+
398
+ it.each([
399
+ 'A⃣B⃣1⃣2⃣ 1⃣A⃣B⃣',
400
+ '🄐🄑⑴⑵ ⑴🄐🄑',
401
+ '丹乃丨己 丨丹乃',
402
+ 'A͢B͢1͢2͢ ͢1͢A͢B͢',
403
+ 'A⃟B⃟1⃟2⃟ 1⃟A⃟B⃟',
404
+ 'AB12👏 1AB',
405
+ '🅐Ⓑ➊② ①🅐Ⓑ',
406
+ '𝘼𝐵𝟭𝟮 1𝘼𝘽',
407
+ '🅰𝙱12 1𝕬𝓑',
408
+ '1ᴬAᴮB¹²',
409
+ 'ᴬᴮ¹² ¹ᴬᴮ',
410
+ 'q∀1 21q∀',
411
+ 'A̶B̶1̶2̶ ̶1̶A̶B̶'
412
+ ])('prohibits a string with non-standard characters: %s', async c => {
413
+ await expect(contactValidation.createOverseasPostcodeValidator(Joi).validateAsync(c)).rejects.toThrow()
414
+ })
295
415
  })
296
416
 
297
417
  describe('premisesValidator', () => {
@@ -308,6 +428,23 @@ describe('contact validators', () => {
308
428
  '"value" length must be less than or equal to 50 characters long'
309
429
  )
310
430
  })
431
+
432
+ it.each([
433
+ '15 𐝥ꗞꕷꗍ ꖀꗞꖡꖡꗇꗱꗍ',
434
+ '15 Rose Cottage',
435
+ '¹⁵ ᴿᵒˢᵉ ᶜᵒᵗᵗᵃᵍᵉ',
436
+ '15 ᖇᐤᔆᕪ ᐸᐤᐩᐩᐞᕐᕪ',
437
+ '15👏 Rose👏 Cottage',
438
+ '1̶5̶ ̶R̶o̶s̶e̶ ̶C̶o̶t̶t̶a̶g̶e̶',
439
+ '1̲5̲ ̲R̲o̲s̲e̲ ̲C̲o̲t̲t̲a̲g̲e̲',
440
+ '1̸5̸ ̸R̸o̸s̸e̸ ̸C̸o̸t̸t̸a̸g̸e̸',
441
+ '➊➎ 🅡🅞🅢🅔 🅒🅞🅣🅣🅐🅖🅔',
442
+ '①➎ 🅡ⓞ🅢ⓔ Ⓒ🅞ⓣ🅣ⓐ🅖ⓔ',
443
+ '1⑤ 𝑅𝐨𝑠ⓔ 𝒞𝓸𝘁𝕥𝒶ℊ🄴',
444
+ '𝟏𝟓 𝐑𝐨𝐬𝐞 𝐂𝐨𝐭𝐭𝐚𝐠𝐞'
445
+ ])('prohibits a string with non-standard characters: %s', async c => {
446
+ await expect(contactValidation.createPremisesValidator(Joi).validateAsync(c)).rejects.toThrow()
447
+ })
311
448
  })
312
449
 
313
450
  describe('streetValidator', () => {
@@ -373,6 +510,13 @@ describe('contact validators', () => {
373
510
  '"value" length must be less than or equal to 100 characters long'
374
511
  )
375
512
  })
513
+
514
+ it.each(['London', 'ᴸᵒⁿᵈᵒⁿ', '𝐋𝐨𝐧𝐝𝐨𝐧', 'ㄥL口o几n冂d口o几n', 'L⃠o⃠n⃠d⃠o⃠n⃠', '🅛🅞🅝🅓🅞🅝', 'L̸o̸n̸d̸o̸n̸', 'L̲o̲n̲d̲o̲n̲'])(
515
+ 'prohibits a string with non-standard characters: %s',
516
+ async c => {
517
+ await expect(contactValidation.createTownValidator(Joi).validateAsync(c)).rejects.toThrow()
518
+ }
519
+ )
376
520
  })
377
521
 
378
522
  describe('nationalInsuranceNumberValidator', () => {
@@ -381,11 +525,28 @@ describe('contact validators', () => {
381
525
  'AB 12 34 56 A'
382
526
  )
383
527
  })
528
+
384
529
  it('Disallows invalid NI number QQ123456A', async () => {
385
530
  await expect(contactValidation.createNationalInsuranceNumberValidator(Joi).validateAsync('QQ123456A')).rejects.toThrow()
386
531
  })
532
+
387
533
  it('Disallows invalid NI number BG123456A', async () => {
388
534
  await expect(contactValidation.createNationalInsuranceNumberValidator(Joi).validateAsync('QQ123456A')).rejects.toThrow()
389
535
  })
536
+
537
+ it.each([
538
+ 'A̲B̲1̲2̲3̲4̲5̲6̲A̲',
539
+ 'A̸B̸1̸2̸3̸4̸5̸6̸A̸',
540
+ 'AB123456A',
541
+ 'ᴬᴮ¹²³⁴⁵⁶ᴬ',
542
+ '🅐🅑①②③④⑤⑥🅐',
543
+ '🅐ⓑ①②③④⑤⑥ⓐ',
544
+ 'A⃠B⃠1⃠2⃠3⃠4⃠5⃠6⃠A⃠',
545
+ '升A马B丨1己2ヨ3ㄐ4丂5石6刄A',
546
+ '✌️AB123456A✌️',
547
+ '𝐀𝐁𝟏𝟐𝟑𝟒𝟓𝟔𝐀'
548
+ ])('prohibits a string with non-standard characters: %s', async c => {
549
+ await expect(contactValidation.createNationalInsuranceNumberValidator(Joi).validateAsync(c)).rejects.toThrow()
550
+ })
390
551
  })
391
552
  })
@@ -78,6 +78,12 @@ const createDateStringValidator = joi =>
78
78
  }
79
79
  })
80
80
 
81
+ const allowedUnicodeBlocks = '\\u0000-\\u024F'
82
+ const forbiddenCharsRegex = new RegExp(`[^${allowedUnicodeBlocks}\\s'’()-]`, 'u')
83
+ const forbiddenEmailRegex = new RegExp(`[^A-Za-z0-9\\s'’@._${allowedUnicodeBlocks}]`, 'u')
84
+ const forbiddenCharsNumbersRegex = new RegExp(`[^A-Za-z0-9\\s${allowedUnicodeBlocks}]`, 'u')
85
+ const forbiddenMobileRegex = /[^+()0-9\-.\s]+/g
86
+
81
87
  /**
82
88
  * Create a validator to check a contact's birth date
83
89
  *
@@ -92,7 +98,8 @@ export const createBirthDateValidator = joi => createDateStringValidator(joi).tr
92
98
  * @param {Joi.Root} joi the joi validator used by the consuming project
93
99
  * @returns {Joi.StringSchema}
94
100
  */
95
- export const createEmailValidator = joi => joi.string().trim().email().max(100).lowercase().example('person@example.com')
101
+ export const createEmailValidator = joi =>
102
+ checkCopyPasteValidator(joi, forbiddenEmailRegex).string().allowable().trim().email().max(100).lowercase().example('person@example.com')
96
103
 
97
104
  export const mobilePhoneRegex = /^[+]*[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/
98
105
  /**
@@ -101,7 +108,10 @@ export const mobilePhoneRegex = /^[+]*[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/
101
108
  * @param {Joi.Root} joi the joi validator used by the consuming project
102
109
  * @returns {Joi.StringSchema}
103
110
  */
104
- export const createMobilePhoneValidator = joi => joi.string().trim().pattern(mobilePhoneRegex).example('+44 7700 900088')
111
+ export const createMobilePhoneValidator = joi =>
112
+ checkCopyPasteValidator(joi, forbiddenMobileRegex).string().allowable().trim().pattern(mobilePhoneRegex).example('person@example.com')
113
+
114
+ const maxPremises = 50
105
115
 
106
116
  /**
107
117
  * Create a validator to check a contact's address premises
@@ -109,7 +119,16 @@ export const createMobilePhoneValidator = joi => joi.string().trim().pattern(mob
109
119
  * @param {Joi.Root} joi the joi validator used by the consuming project
110
120
  * @returns {Joi.StringSchema}
111
121
  */
112
- export const createPremisesValidator = joi => joi.string().trim().min(1).max(50).external(toTitleCase()).required().example('Example House')
122
+ export const createPremisesValidator = joi =>
123
+ checkCopyPasteValidator(joi, forbiddenCharsNumbersRegex)
124
+ .string()
125
+ .allowable()
126
+ .trim()
127
+ .min(1)
128
+ .max(maxPremises)
129
+ .external(toTitleCase())
130
+ .required()
131
+ .example('Example House')
113
132
 
114
133
  /**
115
134
  * Create a validator to check a contact's address street
@@ -134,8 +153,9 @@ export const createLocalityValidator = joi => joi.string().trim().max(100).exter
134
153
  * @returns {Joi.StringSchema}
135
154
  */
136
155
  export const createTownValidator = joi =>
137
- joi
156
+ checkCopyPasteValidator(joi, forbiddenCharsRegex)
138
157
  .string()
158
+ .allowable()
139
159
  .trim()
140
160
  .max(100)
141
161
  .external(toTitleCase(['under', 'upon', 'in', 'on', 'cum', 'next', 'the', 'en', 'le', 'super']))
@@ -144,6 +164,7 @@ export const createTownValidator = joi =>
144
164
 
145
165
  export const ukPostcodeRegex = /^([A-PR-UWYZ][0-9]{1,2}[A-HJKPSTUW]?|[A-PR-UWYZ][A-HK-Y][0-9]{1,2}[ABEHMNPRVWXY]?)\s{0,6}([0-9][A-Z]{2})$/i
146
166
  export const overseasPostcodeRegex = /^([a-zA-Z0-9 ]{1,12})$/i
167
+ const maxPostcode = 12
147
168
 
148
169
  /**
149
170
  * Create a validator to check a contact's postcode
@@ -154,7 +175,17 @@ export const overseasPostcodeRegex = /^([a-zA-Z0-9 ]{1,12})$/i
154
175
  * @returns {Joi.StringSchema}
155
176
  */
156
177
  export const createUKPostcodeValidator = joi =>
157
- joi.string().trim().min(1).max(12).required().pattern(ukPostcodeRegex).replace(ukPostcodeRegex, '$1 $2').uppercase().example('AB12 3CD')
178
+ checkCopyPasteValidator(joi, forbiddenCharsNumbersRegex)
179
+ .string()
180
+ .allowable()
181
+ .trim()
182
+ .min(1)
183
+ .max(maxPostcode)
184
+ .required()
185
+ .pattern(ukPostcodeRegex)
186
+ .replace(ukPostcodeRegex, '$1 $2')
187
+ .uppercase()
188
+ .example('AB12 3CD')
158
189
 
159
190
  /**
160
191
  * Create a validator to check/format overseas postcodes
@@ -162,7 +193,15 @@ export const createUKPostcodeValidator = joi =>
162
193
  * @returns {Joi.StringSchema}
163
194
  */
164
195
  export const createOverseasPostcodeValidator = joi =>
165
- joi.string().trim().min(1).max(12).uppercase().required().pattern(overseasPostcodeRegex)
196
+ checkCopyPasteValidator(joi, forbiddenCharsNumbersRegex)
197
+ .string()
198
+ .allowable()
199
+ .trim()
200
+ .min(1)
201
+ .max(maxPostcode)
202
+ .uppercase()
203
+ .required()
204
+ .pattern(overseasPostcodeRegex)
166
205
 
167
206
  const nameStringSubstitutions = [
168
207
  /*
@@ -251,7 +290,7 @@ const createNameStringValidator = (joi, { minimumLength }) =>
251
290
  return helpers.error('string.min')
252
291
  }
253
292
  value = standardiseName(value)
254
- if (!nameStringRegex.test(value)) {
293
+ if (!nameStringRegex.test(value) || forbiddenCharsRegex.test(value)) {
255
294
  return helpers.error('string.forbidden')
256
295
  }
257
296
  return value
@@ -302,8 +341,9 @@ const ukNINORegEx =
302
341
  /^([ABCEGHJ-PRSTW-Z][ABCEGHJ-NPRSTW-Z])(?<!(?:BG|GB|KN|NK|NT|TN|ZZ))\s?([0-9]{2})\s{0,3}([0-9]{2})\s{0,3}([0-9]{2})\s{0,3}([ABCD])$/
303
342
 
304
343
  export const createNationalInsuranceNumberValidator = joi =>
305
- joi
344
+ checkCopyPasteValidator(joi, forbiddenCharsNumbersRegex)
306
345
  .string()
346
+ .allowable()
307
347
  .trim()
308
348
  .uppercase()
309
349
  .pattern(ukNINORegEx)
@@ -311,3 +351,22 @@ export const createNationalInsuranceNumberValidator = joi =>
311
351
  .required()
312
352
  .description('A UK national insurance number')
313
353
  .example('NH 12 34 56 A')
354
+
355
+ const checkCopyPasteValidator = (joi, forbiddenRegex) =>
356
+ joi.extend({
357
+ type: 'string',
358
+ base: joi.string(),
359
+ rules: {
360
+ allowable: {
361
+ validate (value, helpers) {
362
+ if (forbiddenRegex.test(value)) {
363
+ return helpers.error('string.forbidden')
364
+ }
365
+ return value
366
+ }
367
+ }
368
+ },
369
+ messages: {
370
+ 'string.forbidden': '{{#label}} contains forbidden characters'
371
+ }
372
+ })