Skip to content

Commit b5e5a13

Browse files
authored
[#173535710] Adult check by extracting date of birth from fiscalCode data (#687)
1 parent 3f0c829 commit b5e5a13

File tree

4 files changed

+96
-8
lines changed

4 files changed

+96
-8
lines changed

src/services/__tests__/bonusService.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { User } from "../../types/user";
1919
import BonusService from "../bonusService";
2020

2121
const aValidFiscalCode = "XUZTCT88A51Y311X" as FiscalCode;
22+
const aNotAdultFiscalCode = "DROLSS02S20H501F" as FiscalCode;
2223
const aValidSPIDEmail = "[email protected]" as EmailAddress;
2324
const aValidSpidLevel = SpidLevelEnum["https://www.spid.gov.it/SpidL2"];
2425

@@ -94,7 +95,7 @@ const mockedNotAdultUser: User = {
9495
created_at: 1183518855,
9596
date_of_birth: "2020-01-01",
9697
family_name: "Lusso",
97-
fiscal_code: aValidFiscalCode,
98+
fiscal_code: aNotAdultFiscalCode,
9899
name: "Luca",
99100
session_token: "HexToKen" as SessionToken,
100101
spid_email: aValidSPIDEmail,
@@ -222,7 +223,7 @@ describe("BonusService#startBonusEligibilityCheck", () => {
222223
});
223224
});
224225

225-
it("should return an error if the logged in user is not adult", async () => {
226+
it("should return an Unauthorized error if the logged in user is not adult", async () => {
226227
mockStartBonusEligibilityCheck.mockImplementation(() =>
227228
t.success({ status: 123 })
228229
);

src/services/bonusService.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ import {
4242

4343
import { toString } from "fp-ts/lib/function";
4444

45-
import { fromNullable } from "fp-ts/lib/Option";
46-
import { isOlderThan } from "../utils/date";
45+
import { isSome } from "fp-ts/lib/Option";
46+
import { isOlderThan, toBirthDate } from "../utils/date";
4747

4848
const readableProblem = (problem: ProblemJson) =>
4949
`${problem.title} (${problem.type || "no problem type specified"})`;
@@ -79,10 +79,10 @@ export default class BonusService {
7979
> =>
8080
withCatchAsInternalError(async () => {
8181
// check if the current logged in user can start a bonus request
82+
const maybeBirthDate = toBirthDate(user.fiscal_code);
8283
if (
83-
fromNullable(user.date_of_birth).exists(
84-
_ => !isOlderThan(18)(new Date(_), new Date())
85-
)
84+
isSome(maybeBirthDate) &&
85+
!isOlderThan(18)(maybeBirthDate.value, new Date())
8686
) {
8787
return ResponseErrorUnauthorized(
8888
"Unauthorized",

src/utils/__tests__/date.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
import { isOlderThan } from "../date";
1+
import { isNone, isSome } from "fp-ts/lib/Option";
2+
import { FiscalCode } from "italia-ts-commons/lib/strings";
3+
import { isOlderThan, toBirthDate } from "../date";
24

35
const toDate = new Date("2020-01-01");
46
const olderThanValue = 18;
57

8+
const aFiscalCode = "DROLSS85S20H501F" as FiscalCode;
9+
const aWrongFiscalCode = "DROLSS85Z20H501F" as FiscalCode;
10+
const aDateOfBirth = new Date(1985, 10, 20);
11+
612
describe("Check if a birthdate is for an adult user", () => {
713
it("should return true if the user is over 18 years old", () => {
814
const validOlderDate = new Date("2000-01-01");
@@ -20,3 +26,21 @@ describe("Check if a birthdate is for an adult user", () => {
2026
).toBeFalsy();
2127
});
2228
});
29+
30+
describe("User utility", () => {
31+
it("should extract the correct date of birth from fiscalCode", async () => {
32+
const extractedDateOfBirthOrError = toBirthDate(aFiscalCode);
33+
expect(isSome(extractedDateOfBirthOrError)).toBeTruthy();
34+
if (isSome(extractedDateOfBirthOrError)) {
35+
const birthDate = extractedDateOfBirthOrError.value;
36+
expect(birthDate.getFullYear()).toEqual(aDateOfBirth.getFullYear());
37+
expect(birthDate.getDay()).toEqual(aDateOfBirth.getDay());
38+
expect(birthDate.getMonth()).toEqual(aDateOfBirth.getMonth());
39+
}
40+
});
41+
42+
it("should return none if fiscalCode is not recognized", async () => {
43+
const extractedDateOfBirthOrError = toBirthDate(aWrongFiscalCode);
44+
expect(isNone(extractedDateOfBirthOrError)).toBeTruthy();
45+
});
46+
});

src/utils/date.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { addYears, isAfter } from "date-fns";
2+
import { Option, tryCatch } from "fp-ts/lib/Option";
3+
import { FiscalCode } from "generated/backend/FiscalCode";
24

35
/**
46
* Returns a comparator of two dates that returns true if
@@ -10,3 +12,64 @@ export const isOlderThan = (years: number) => (
1012
) => {
1113
return !isAfter(addYears(dateOfBirth, years), when);
1214
};
15+
16+
export const isValidDate = (d: Date) => {
17+
return d instanceof Date && !isNaN(d.getTime());
18+
};
19+
20+
const months: { [k: string]: number } = {
21+
["A"]: 1,
22+
["B"]: 2,
23+
["C"]: 3,
24+
["D"]: 4,
25+
["E"]: 5,
26+
["H"]: 6,
27+
["L"]: 7,
28+
["M"]: 8,
29+
["P"]: 9,
30+
["R"]: 10,
31+
["S"]: 11,
32+
["T"]: 12
33+
};
34+
35+
/**
36+
* Parse birth date from fiscal code
37+
*/
38+
export function toBirthDate(fiscalCode: FiscalCode): Option<Date> {
39+
return tryCatch(() => {
40+
const tempDay = parseInt(fiscalCode.substring(9, 11), 10);
41+
if (isNaN(tempDay)) {
42+
throw new Error();
43+
}
44+
45+
const monthIndx = fiscalCode.charAt(8);
46+
if (!(monthIndx in months)) {
47+
throw new Error();
48+
}
49+
50+
const month = months[fiscalCode.charAt(8)];
51+
52+
// female subjects have 40 added to their birth day
53+
const day = tempDay - 40 > 0 ? tempDay - 40 : tempDay;
54+
55+
const tempYear = parseInt(fiscalCode.substring(6, 8), 10);
56+
if (isNaN(tempYear)) {
57+
throw new Error();
58+
}
59+
60+
// to avoid the century date collision (01 could mean 1901 or 2001)
61+
// we assume that if the birth date is grater than a century, the date
62+
// refers to the new century
63+
const year =
64+
tempYear +
65+
(new Date().getFullYear() - (1900 + tempYear) >= 100 ? 2000 : 1900);
66+
67+
// months are 0-index
68+
const birthDay = new Date(year, month - 1, day);
69+
if (!isValidDate(birthDay)) {
70+
throw new Error();
71+
}
72+
73+
return birthDay;
74+
});
75+
}

0 commit comments

Comments
 (0)