diff --git a/frontend/src/routes/guest/register/steps/register.tsx b/frontend/src/routes/guest/register/steps/register.tsx index 86f52bd241962c59cc61df154f82ce69a3af1430..0499c4b6f557437570b007f992a066dd048963f9 100644 --- a/frontend/src/routes/guest/register/steps/register.tsx +++ b/frontend/src/routes/guest/register/steps/register.tsx @@ -590,7 +590,7 @@ const GuestRegisterStep = forwardRef( // It is not required that the Norwegian national ID number be filled in, the guest may not have // one, so allow empty values for the validation to pass. Note that both "fødselsnummer" and // D-number are allowed as input, and in some cases SO-number - validate: (value) => isValidFnr(value, true), + validate: (value) => isValidFnr(value, true, true), }} render={({ field }) => ( <TextField diff --git a/frontend/src/utils/index.test.ts b/frontend/src/utils/index.test.ts index bdc3108c327e6449021557ba70ed5c9155aec2c9..e297ab291aa12e12b0fc86a1528b35662730078e 100644 --- a/frontend/src/utils/index.test.ts +++ b/frontend/src/utils/index.test.ts @@ -6,6 +6,7 @@ import { isValidEmail, isValidFirstName, isValidFnr, + isValidSoNumber, isValidLastName, isValidMobilePhoneNumber, maybeCsrfToken, @@ -167,6 +168,14 @@ test('Valid fnr', async () => { expect(isValidFnr('04026514903')).toEqual(true) }) +test('SO number validation', async () => { + expect(isValidSoNumber('22522218883')).toEqual(true) + expect(isValidSoNumber('22022218883')).toEqual(false) + + expect(isValidFnr('22522218883')).toEqual("common:validation.invalidIdNumber") + expect(isValidFnr('22522218883', false, true)).toEqual(true) +}) + test('Null fnr', async () => { expect(isValidFnr(undefined)).toEqual('common:validation.invalidIdNumber') }) diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 90854e450499987ff83b45959015cbf070b5ad7c..8ba54d2dc207529325a2c142ac1f9c73aea00e14 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -73,9 +73,83 @@ export function fetchJsonOpts(): RequestInit { } } +export function norwegianIdNumberHasValidChecksum(digits: number[]): boolean { + // from https://github.com/navikt/fnrvalidator/blob/master/src/validator.js + let k1 = + 11 - + ((3 * digits[0] + + 7 * digits[1] + + 6 * digits[2] + + 1 * digits[3] + + 8 * digits[4] + + 9 * digits[5] + + 4 * digits[6] + + 5 * digits[7] + + 2 * digits[8]) % + 11) + let k2 = + 11 - + ((5 * digits[0] + + 4 * digits[1] + + 3 * digits[2] + + 2 * digits[3] + + 7 * digits[4] + + 6 * digits[5] + + 5 * digits[6] + + 4 * digits[7] + + 3 * digits[8] + + 2 * k1) % + 11) + + if (k1 === 11) k1 = 0 + if (k2 === 11) k2 = 0 + + return k1 < 10 && k2 < 10 && k1 === digits[9] && k2 === digits[10] +} + +export function isValidSoNumber(data: string): boolean { + // see https://lovas.info/2013/12/01/identitetsnummer-i-norge/ + + const day = data.slice(0, 2) + const month = data.slice(2, 4) + const year = data.slice(4, 6) + const pnr = data.slice(6, 11) + + // this part should always start with 1 + if (pnr.slice(0, 1) !== '1') { + return false + } + + const actualMonth = parseInt(month) - 50 + const date = new Date( + year === '00' ? 2000 : parseInt(year), + actualMonth - 1, + parseInt(day) + ) + + // date must be a valid date + if ( + !( + date && + date.getMonth() + 1 === actualMonth && + date.getDate() === parseInt(day) + ) + ) { + return false + } + + // checksum must match + const digits = data.split('').map((x) => parseInt(x)) + if (!norwegianIdNumberHasValidChecksum(digits)) { + return false + } + return true +} + export function isValidFnr( data: string | undefined, - allowEmpty = false + allowEmpty = false, + allowSoNumber = false, ): boolean | string { if (!data) { if (allowEmpty) { @@ -83,7 +157,12 @@ export function isValidFnr( } return i18n.t<string>('common:validation.invalidIdNumber').toString() } - if (validator.idnr(data as string).status === 'valid') { + const validation = validator.idnr(data as string) + if (validation.status === 'valid' && ["fnr", "dnr"].includes(validation.type)) { + return true + } + if (allowSoNumber && isValidSoNumber(data)) { + return true } // TypeScript complains if toString is not used on the function result