@proconnect-gouv/proconnect.identite 1.0.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.
Files changed (252) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/data/organization/domains-whitelist.d.ts +2 -0
  3. package/dist/data/organization/domains-whitelist.d.ts.map +1 -0
  4. package/dist/data/organization/domains-whitelist.js +7 -0
  5. package/dist/data/organization/index.d.ts +2 -0
  6. package/dist/data/organization/index.d.ts.map +1 -0
  7. package/dist/data/organization/index.js +2 -0
  8. package/dist/errors/index.d.ts +22 -0
  9. package/dist/errors/index.d.ts.map +1 -0
  10. package/dist/errors/index.js +43 -0
  11. package/dist/managers/certification/distance.d.ts +4 -0
  12. package/dist/managers/certification/distance.d.ts.map +1 -0
  13. package/dist/managers/certification/distance.js +16 -0
  14. package/dist/managers/certification/index.d.ts +3 -0
  15. package/dist/managers/certification/index.d.ts.map +1 -0
  16. package/dist/managers/certification/index.js +3 -0
  17. package/dist/managers/certification/is-organization-dirigeant.d.ts +15 -0
  18. package/dist/managers/certification/is-organization-dirigeant.d.ts.map +1 -0
  19. package/dist/managers/certification/is-organization-dirigeant.js +60 -0
  20. package/dist/managers/franceconnect/index.d.ts +2 -0
  21. package/dist/managers/franceconnect/index.d.ts.map +1 -0
  22. package/dist/managers/franceconnect/index.js +2 -0
  23. package/dist/managers/franceconnect/openid-client.d.ts +37 -0
  24. package/dist/managers/franceconnect/openid-client.d.ts.map +1 -0
  25. package/dist/managers/franceconnect/openid-client.js +54 -0
  26. package/dist/managers/organization/force-join-organization.d.ts +29 -0
  27. package/dist/managers/organization/force-join-organization.d.ts.map +1 -0
  28. package/dist/managers/organization/force-join-organization.js +33 -0
  29. package/dist/managers/organization/get-organization-info.d.ts +5 -0
  30. package/dist/managers/organization/get-organization-info.d.ts.map +1 -0
  31. package/dist/managers/organization/get-organization-info.js +33 -0
  32. package/dist/managers/organization/index.d.ts +4 -0
  33. package/dist/managers/organization/index.d.ts.map +1 -0
  34. package/dist/managers/organization/index.js +4 -0
  35. package/dist/managers/organization/mark-domain-as-verified.d.ts +20 -0
  36. package/dist/managers/organization/mark-domain-as-verified.d.ts.map +1 -0
  37. package/dist/managers/organization/mark-domain-as-verified.js +63 -0
  38. package/dist/managers/user/assign-user-verification-type-to-domain.d.ts +10 -0
  39. package/dist/managers/user/assign-user-verification-type-to-domain.d.ts.map +1 -0
  40. package/dist/managers/user/assign-user-verification-type-to-domain.js +21 -0
  41. package/dist/managers/user/index.d.ts +2 -0
  42. package/dist/managers/user/index.d.ts.map +1 -0
  43. package/dist/managers/user/index.js +1 -0
  44. package/dist/mappers/certification/index.d.ts +4 -0
  45. package/dist/mappers/certification/index.d.ts.map +1 -0
  46. package/dist/mappers/certification/index.js +11 -0
  47. package/dist/mappers/index.d.ts +2 -0
  48. package/dist/mappers/index.d.ts.map +1 -0
  49. package/dist/mappers/index.js +2 -0
  50. package/dist/mappers/organization/from-siret.d.ts +5 -0
  51. package/dist/mappers/organization/from-siret.d.ts.map +1 -0
  52. package/dist/mappers/organization/from-siret.js +51 -0
  53. package/dist/mappers/organization/index.d.ts +2 -0
  54. package/dist/mappers/organization/index.d.ts.map +1 -0
  55. package/dist/mappers/organization/index.js +2 -0
  56. package/dist/repositories/email-domain/add-domain.d.ts +8 -0
  57. package/dist/repositories/email-domain/add-domain.d.ts.map +1 -0
  58. package/dist/repositories/email-domain/add-domain.js +25 -0
  59. package/dist/repositories/email-domain/delete-email-domains-by-verification-types.d.ts +8 -0
  60. package/dist/repositories/email-domain/delete-email-domains-by-verification-types.d.ts.map +1 -0
  61. package/dist/repositories/email-domain/delete-email-domains-by-verification-types.js +21 -0
  62. package/dist/repositories/email-domain/find-email-domains-by-organization-id.d.ts +4 -0
  63. package/dist/repositories/email-domain/find-email-domains-by-organization-id.d.ts.map +1 -0
  64. package/dist/repositories/email-domain/find-email-domains-by-organization-id.js +11 -0
  65. package/dist/repositories/email-domain/index.d.ts +12 -0
  66. package/dist/repositories/email-domain/index.d.ts.map +1 -0
  67. package/dist/repositories/email-domain/index.js +3 -0
  68. package/dist/repositories/organization/find-by-id.d.ts +4 -0
  69. package/dist/repositories/organization/find-by-id.d.ts.map +1 -0
  70. package/dist/repositories/organization/find-by-id.js +11 -0
  71. package/dist/repositories/organization/find-by-user-id.d.ts +12 -0
  72. package/dist/repositories/organization/find-by-user-id.d.ts.map +1 -0
  73. package/dist/repositories/organization/find-by-user-id.js +22 -0
  74. package/dist/repositories/organization/get-by-id.d.ts +4 -0
  75. package/dist/repositories/organization/get-by-id.d.ts.map +1 -0
  76. package/dist/repositories/organization/get-by-id.js +15 -0
  77. package/dist/repositories/organization/get-users-by-organization.d.ts +12 -0
  78. package/dist/repositories/organization/get-users-by-organization.d.ts.map +1 -0
  79. package/dist/repositories/organization/get-users-by-organization.js +23 -0
  80. package/dist/repositories/organization/index.d.ts +7 -0
  81. package/dist/repositories/organization/index.d.ts.map +1 -0
  82. package/dist/repositories/organization/index.js +7 -0
  83. package/dist/repositories/organization/link-user-to-organization.d.ts +16 -0
  84. package/dist/repositories/organization/link-user-to-organization.d.ts.map +1 -0
  85. package/dist/repositories/organization/link-user-to-organization.js +22 -0
  86. package/dist/repositories/organization/upsert.d.ts +7 -0
  87. package/dist/repositories/organization/upsert.d.ts.map +1 -0
  88. package/dist/repositories/organization/upsert.js +103 -0
  89. package/dist/repositories/user/create.d.ts +7 -0
  90. package/dist/repositories/user/create.d.ts.map +1 -0
  91. package/dist/repositories/user/create.js +25 -0
  92. package/dist/repositories/user/find-by-email.d.ts +4 -0
  93. package/dist/repositories/user/find-by-email.d.ts.map +1 -0
  94. package/dist/repositories/user/find-by-email.js +12 -0
  95. package/dist/repositories/user/find-by-id.d.ts +4 -0
  96. package/dist/repositories/user/find-by-id.d.ts.map +1 -0
  97. package/dist/repositories/user/find-by-id.js +13 -0
  98. package/dist/repositories/user/get-by-id.d.ts +4 -0
  99. package/dist/repositories/user/get-by-id.d.ts.map +1 -0
  100. package/dist/repositories/user/get-by-id.js +15 -0
  101. package/dist/repositories/user/get-franceconnect-user-info.d.ts +15 -0
  102. package/dist/repositories/user/get-franceconnect-user-info.d.ts.map +1 -0
  103. package/dist/repositories/user/get-franceconnect-user-info.js +13 -0
  104. package/dist/repositories/user/index.d.ts +9 -0
  105. package/dist/repositories/user/index.d.ts.map +1 -0
  106. package/dist/repositories/user/index.js +9 -0
  107. package/dist/repositories/user/update-user-organization-link.d.ts +16 -0
  108. package/dist/repositories/user/update-user-organization-link.d.ts.map +1 -0
  109. package/dist/repositories/user/update-user-organization-link.js +20 -0
  110. package/dist/repositories/user/update.d.ts +4 -0
  111. package/dist/repositories/user/update.d.ts.map +1 -0
  112. package/dist/repositories/user/update.js +19 -0
  113. package/dist/repositories/user/upsert-franceconnect-userinfo.d.ts +15 -0
  114. package/dist/repositories/user/upsert-franceconnect-userinfo.d.ts.map +1 -0
  115. package/dist/repositories/user/upsert-franceconnect-userinfo.js +23 -0
  116. package/dist/services/organization/index.d.ts +3 -0
  117. package/dist/services/organization/index.d.ts.map +1 -0
  118. package/dist/services/organization/index.js +3 -0
  119. package/dist/services/organization/is-domain-allowed-for-organization.d.ts +2 -0
  120. package/dist/services/organization/is-domain-allowed-for-organization.d.ts.map +1 -0
  121. package/dist/services/organization/is-domain-allowed-for-organization.js +8 -0
  122. package/dist/services/organization/is-entreprise-unipersonnelle.d.ts +9 -0
  123. package/dist/services/organization/is-entreprise-unipersonnelle.d.ts.map +1 -0
  124. package/dist/services/organization/is-entreprise-unipersonnelle.js +18 -0
  125. package/dist/services/postgres/hash-to-postgres-params.d.ts +6 -0
  126. package/dist/services/postgres/hash-to-postgres-params.d.ts.map +1 -0
  127. package/dist/services/postgres/hash-to-postgres-params.js +21 -0
  128. package/dist/services/postgres/index.d.ts +2 -0
  129. package/dist/services/postgres/index.d.ts.map +1 -0
  130. package/dist/services/postgres/index.js +2 -0
  131. package/dist/types/claims.d.ts +16 -0
  132. package/dist/types/claims.d.ts.map +1 -0
  133. package/dist/types/claims.js +16 -0
  134. package/dist/types/contexts.d.ts +5 -0
  135. package/dist/types/contexts.d.ts.map +1 -0
  136. package/dist/types/contexts.js +2 -0
  137. package/dist/types/dirigeant.d.ts +9 -0
  138. package/dist/types/dirigeant.d.ts.map +1 -0
  139. package/dist/types/dirigeant.js +9 -0
  140. package/dist/types/email-domain.d.ts +25 -0
  141. package/dist/types/email-domain.d.ts.map +1 -0
  142. package/dist/types/email-domain.js +14 -0
  143. package/dist/types/franceconnect.d.ts +28 -0
  144. package/dist/types/franceconnect.d.ts.map +1 -0
  145. package/dist/types/franceconnect.js +22 -0
  146. package/dist/types/index.d.ts +10 -0
  147. package/dist/types/index.d.ts.map +1 -0
  148. package/dist/types/index.js +10 -0
  149. package/dist/types/organization-info.d.ts +27 -0
  150. package/dist/types/organization-info.d.ts.map +1 -0
  151. package/dist/types/organization-info.js +26 -0
  152. package/dist/types/organization.d.ts +26 -0
  153. package/dist/types/organization.d.ts.map +1 -0
  154. package/dist/types/organization.js +2 -0
  155. package/dist/types/user-organization-link.d.ts +92 -0
  156. package/dist/types/user-organization-link.d.ts.map +1 -0
  157. package/dist/types/user-organization-link.js +44 -0
  158. package/dist/types/user.d.ts +28 -0
  159. package/dist/types/user.d.ts.map +1 -0
  160. package/dist/types/user.js +2 -0
  161. package/package.json +68 -0
  162. package/src/data/organization/domains-whitelist.ts +8 -0
  163. package/src/data/organization/index.ts +3 -0
  164. package/src/errors/index.ts +50 -0
  165. package/src/managers/certification/distance.test.ts +109 -0
  166. package/src/managers/certification/distance.ts +41 -0
  167. package/src/managers/certification/index.ts +4 -0
  168. package/src/managers/certification/is-organization-dirigeant.test.ts +125 -0
  169. package/src/managers/certification/is-organization-dirigeant.ts +136 -0
  170. package/src/managers/franceconnect/index.ts +3 -0
  171. package/src/managers/franceconnect/openid-client.ts +131 -0
  172. package/src/managers/organization/force-join-organization.test.ts +94 -0
  173. package/src/managers/organization/force-join-organization.ts +90 -0
  174. package/src/managers/organization/get-organization-info.test.ts +47 -0
  175. package/src/managers/organization/get-organization-info.test.ts.snapshot +68 -0
  176. package/src/managers/organization/get-organization-info.ts +58 -0
  177. package/src/managers/organization/index.ts +5 -0
  178. package/src/managers/organization/mark-domain-as-verified.test.ts +90 -0
  179. package/src/managers/organization/mark-domain-as-verified.test.ts.snapshot +52 -0
  180. package/src/managers/organization/mark-domain-as-verified.ts +140 -0
  181. package/src/managers/user/assign-user-verification-type-to-domain.ts +50 -0
  182. package/src/managers/user/index.ts +1 -0
  183. package/src/mappers/certification/index.ts +18 -0
  184. package/src/mappers/index.ts +3 -0
  185. package/src/mappers/organization/from-siret.test.ts +26 -0
  186. package/src/mappers/organization/from-siret.test.ts.snapshot +68 -0
  187. package/src/mappers/organization/from-siret.ts +75 -0
  188. package/src/mappers/organization/index.ts +3 -0
  189. package/src/repositories/email-domain/add-domain.test.ts +43 -0
  190. package/src/repositories/email-domain/add-domain.ts +48 -0
  191. package/src/repositories/email-domain/delete-email-domains-by-verification-types.test.ts +49 -0
  192. package/src/repositories/email-domain/delete-email-domains-by-verification-types.ts +45 -0
  193. package/src/repositories/email-domain/find-email-domains-by-organization-id.test.ts +55 -0
  194. package/src/repositories/email-domain/find-email-domains-by-organization-id.ts +28 -0
  195. package/src/repositories/email-domain/index.ts +13 -0
  196. package/src/repositories/organization/find-by-id.test.ts +51 -0
  197. package/src/repositories/organization/find-by-id.ts +22 -0
  198. package/src/repositories/organization/find-by-user-id.test.ts +70 -0
  199. package/src/repositories/organization/find-by-user-id.test.ts.snapshot +102 -0
  200. package/src/repositories/organization/find-by-user-id.ts +38 -0
  201. package/src/repositories/organization/get-by-id.test.ts +60 -0
  202. package/src/repositories/organization/get-by-id.ts +21 -0
  203. package/src/repositories/organization/get-users-by-organization.test.ts +50 -0
  204. package/src/repositories/organization/get-users-by-organization.test.ts.snapshot +38 -0
  205. package/src/repositories/organization/get-users-by-organization.ts +46 -0
  206. package/src/repositories/organization/index.ts +8 -0
  207. package/src/repositories/organization/link-user-to-organization.test.ts +65 -0
  208. package/src/repositories/organization/link-user-to-organization.test.ts.snapshot +31 -0
  209. package/src/repositories/organization/link-user-to-organization.ts +45 -0
  210. package/src/repositories/organization/upsert.ts +142 -0
  211. package/src/repositories/organization/upset.test.ts +95 -0
  212. package/src/repositories/user/create.test.ts +49 -0
  213. package/src/repositories/user/create.ts +44 -0
  214. package/src/repositories/user/find-by-email.test.ts +62 -0
  215. package/src/repositories/user/find-by-email.ts +22 -0
  216. package/src/repositories/user/find-by-id.test.ts +64 -0
  217. package/src/repositories/user/find-by-id.ts +23 -0
  218. package/src/repositories/user/get-by-id.test.ts +63 -0
  219. package/src/repositories/user/get-by-id.ts +21 -0
  220. package/src/repositories/user/get-franceconnect-user-info.test.ts +58 -0
  221. package/src/repositories/user/get-franceconnect-user-info.ts +25 -0
  222. package/src/repositories/user/index.ts +10 -0
  223. package/src/repositories/user/update-user-organization-link.test.ts +46 -0
  224. package/src/repositories/user/update-user-organization-link.ts +42 -0
  225. package/src/repositories/user/update.test.ts +26 -0
  226. package/src/repositories/user/update.ts +34 -0
  227. package/src/repositories/user/upsert-franceconnect-userinfo.test.ts +100 -0
  228. package/src/repositories/user/upsert-franceconnect-userinfo.ts +41 -0
  229. package/src/services/organization/index.ts +4 -0
  230. package/src/services/organization/is-domain-allowed-for-organization.ts +10 -0
  231. package/src/services/organization/is-entreprise-unipersonnelle.test.ts +32 -0
  232. package/src/services/organization/is-entreprise-unipersonnelle.ts +31 -0
  233. package/src/services/postgres/hash-to-postgres-params.ts +34 -0
  234. package/src/services/postgres/index.ts +3 -0
  235. package/src/types/claims.ts +21 -0
  236. package/src/types/contexts.ts +9 -0
  237. package/src/types/dirigeant.ts +13 -0
  238. package/src/types/email-domain.ts +48 -0
  239. package/src/types/franceconnect.ts +37 -0
  240. package/src/types/index.ts +11 -0
  241. package/src/types/organization-info.ts +32 -0
  242. package/src/types/organization.ts +30 -0
  243. package/src/types/user-organization-link.ts +71 -0
  244. package/src/types/user.ts +29 -0
  245. package/testing/index.ts +31 -0
  246. package/testing/seed/franceconnect/index.ts +40 -0
  247. package/testing/seed/insee/index.ts +22 -0
  248. package/testing/seed/mandataires/index.ts +32 -0
  249. package/testing/seed/organizations/index.ts +41 -0
  250. package/tsconfig.json +17 -0
  251. package/tsconfig.lib.json +9 -0
  252. package/tsconfig.lib.tsbuildinfo +1 -0
@@ -0,0 +1,64 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, mock, suite, test } from "node:test";
6
+ import { findByIdFactory } from "./find-by-id.js";
7
+
8
+ //
9
+
10
+ const findById = findByIdFactory({ pg: pg as any });
11
+
12
+ suite("findByIdFactory", () => {
13
+ before(migrate);
14
+ beforeEach(emptyDatabase);
15
+
16
+ test("should find a user by id", async () => {
17
+ mock.timers.enable({ apis: ["Date"], now: new Date("4444-04-04") });
18
+
19
+ await pg.sql`
20
+ INSERT INTO users
21
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
22
+ VALUES
23
+ (1, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'i', 'primarque'),
24
+ (2, 'perturabo@ironwarriors.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'iv', 'primarque')
25
+ ;
26
+ `;
27
+
28
+ const user = await findById(1);
29
+
30
+ assert.deepEqual(user, {
31
+ created_at: new Date("4444-04-04"),
32
+ current_challenge: null,
33
+ email: "lion.eljonson@darkangels.world",
34
+ email_verified: false,
35
+ email_verified_at: null,
36
+ encrypted_password: "",
37
+ encrypted_totp_key: null,
38
+ family_name: "el'jonson",
39
+ force_2fa: false,
40
+ given_name: "lion",
41
+ id: 1,
42
+ job: "primarque",
43
+ last_sign_in_at: null,
44
+ magic_link_sent_at: null,
45
+ magic_link_token: null,
46
+ needs_inclusionconnect_onboarding_help: false,
47
+ needs_inclusionconnect_welcome_page: false,
48
+ phone_number: "i",
49
+ reset_password_sent_at: null,
50
+ reset_password_token: null,
51
+ sign_in_count: 0,
52
+ totp_key_verified_at: null,
53
+ updated_at: new Date("4444-04-04"),
54
+ verify_email_sent_at: null,
55
+ verify_email_token: null,
56
+ });
57
+ });
58
+
59
+ test("❎ fail to find the God-Emperor of Mankind", async () => {
60
+ const user = await findById(42);
61
+
62
+ assert.equal(user, undefined);
63
+ });
64
+ });
@@ -0,0 +1,23 @@
1
+ //
2
+
3
+ import type { DatabaseContext, User } from "#src/types";
4
+ import { type QueryResult } from "pg";
5
+
6
+ //
7
+
8
+ export function findByIdFactory({ pg }: DatabaseContext) {
9
+ return async function findById(id: number) {
10
+ const { rows }: QueryResult<User> = await pg.query(
11
+ `
12
+ SELECT *
13
+ FROM users
14
+ WHERE id = $1
15
+ `,
16
+ [id],
17
+ );
18
+
19
+ return rows.shift();
20
+ };
21
+ }
22
+
23
+ export type FindByIdHandler = ReturnType<typeof findByIdFactory>;
@@ -0,0 +1,63 @@
1
+ //
2
+
3
+ import { UserNotFoundError } from "#src/errors";
4
+ import { emptyDatabase, migrate, pg } from "#testing";
5
+ import assert from "node:assert/strict";
6
+ import { before, beforeEach, mock, suite, test } from "node:test";
7
+ import { getByIdFactory } from "./get-by-id.js";
8
+
9
+ //
10
+
11
+ const getById = getByIdFactory({ pg: pg as any });
12
+
13
+ suite("getByIdFactory", () => {
14
+ before(migrate);
15
+ beforeEach(emptyDatabase);
16
+
17
+ test("should find a user by id", async () => {
18
+ mock.timers.enable({ apis: ["Date"], now: new Date("4444-04-04") });
19
+
20
+ await pg.sql`
21
+ INSERT INTO users
22
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
23
+ VALUES
24
+ (1, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'i', 'primarque'),
25
+ (2, 'perturabo@ironwarriors.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'iv', 'primarque')
26
+ ;
27
+ `;
28
+
29
+ const user = await getById(1);
30
+
31
+ assert.deepEqual(user, {
32
+ created_at: new Date("4444-04-04"),
33
+ current_challenge: null,
34
+ email: "lion.eljonson@darkangels.world",
35
+ email_verified: false,
36
+ email_verified_at: null,
37
+ encrypted_password: "",
38
+ encrypted_totp_key: null,
39
+ family_name: "el'jonson",
40
+ force_2fa: false,
41
+ given_name: "lion",
42
+ id: 1,
43
+ job: "primarque",
44
+ last_sign_in_at: null,
45
+ magic_link_sent_at: null,
46
+ magic_link_token: null,
47
+ needs_inclusionconnect_onboarding_help: false,
48
+ needs_inclusionconnect_welcome_page: false,
49
+ phone_number: "i",
50
+ reset_password_sent_at: null,
51
+ reset_password_token: null,
52
+ sign_in_count: 0,
53
+ totp_key_verified_at: null,
54
+ updated_at: new Date("4444-04-04"),
55
+ verify_email_sent_at: null,
56
+ verify_email_token: null,
57
+ });
58
+ });
59
+
60
+ test("❎ fail to find the God-Emperor of Mankind", async () => {
61
+ await assert.rejects(getById(42), new UserNotFoundError("User not found"));
62
+ });
63
+ });
@@ -0,0 +1,21 @@
1
+ //
2
+
3
+ import { UserNotFoundError } from "#src/errors";
4
+ import type { DatabaseContext } from "#src/types";
5
+ import { isEmpty } from "lodash-es";
6
+ import { findByIdFactory } from "./find-by-id.js";
7
+
8
+ //
9
+
10
+ export function getByIdFactory({ pg }: DatabaseContext) {
11
+ const findById = findByIdFactory({ pg });
12
+ return async function getById(id: number) {
13
+ const user = await findById(id);
14
+ if (isEmpty(user)) {
15
+ throw new UserNotFoundError("User not found");
16
+ }
17
+ return user;
18
+ };
19
+ }
20
+
21
+ export type GetByIdHandler = ReturnType<typeof getByIdFactory>;
@@ -0,0 +1,58 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, describe, it, mock } from "node:test";
6
+ import { getFranceConnectUserInfoFactory } from "./get-franceconnect-user-info.js";
7
+
8
+ //
9
+
10
+ const getFranceConnectUserInfo = getFranceConnectUserInfoFactory({
11
+ pg: pg as any,
12
+ });
13
+
14
+ describe("getFranceConnectUserInfo", () => {
15
+ before(migrate);
16
+ beforeEach(emptyDatabase);
17
+ before(() => {
18
+ mock.timers.enable({ apis: ["Date"], now: new Date("4444-04-04") });
19
+ });
20
+
21
+ it("should get franceconnect user info", async () => {
22
+ await pg.sql`
23
+ INSERT INTO users
24
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
25
+ VALUES
26
+ (1, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'i', 'primarque')
27
+ ;
28
+ `;
29
+ await pg.sql`
30
+ INSERT INTO franceconnect_userinfo
31
+ (user_id, birthdate, birthplace, family_name, gender, given_name, preferred_username, sub, created_at, updated_at)
32
+ VALUES
33
+ (1, '8888-08-08', 'Caliban', 'El’Jonson', 'male', 'Li', 'Li', 'abcdefghijklmnopqrstuvwxyz', '4444-04-04', '4444-04-04')
34
+ ;
35
+ `;
36
+
37
+ const user = await getFranceConnectUserInfo(1);
38
+ assert.ok(user);
39
+ assert.deepEqual(user, {
40
+ birthdate: new Date("8888-08-08"),
41
+ birthplace: "Caliban",
42
+ created_at: new Date("4444-04-04"),
43
+ family_name: "El’Jonson",
44
+ gender: "male",
45
+ given_name: "Li",
46
+ preferred_username: "Li",
47
+ sub: "abcdefghijklmnopqrstuvwxyz",
48
+ updated_at: new Date(),
49
+ user_id: 1,
50
+ });
51
+ });
52
+
53
+ it("❎ fail to get an unknown user", async () => {
54
+ const user = await getFranceConnectUserInfo(42);
55
+
56
+ assert.equal(user, undefined);
57
+ });
58
+ });
@@ -0,0 +1,25 @@
1
+ //
2
+
3
+ import type { DatabaseContext, FranceConnectUserInfo } from "#src/types";
4
+ import { type QueryResult } from "pg";
5
+
6
+ //
7
+
8
+ export function getFranceConnectUserInfoFactory({ pg }: DatabaseContext) {
9
+ return async function getFranceConnectUserInfo(user_id: number) {
10
+ const { rows }: QueryResult<FranceConnectUserInfo> = await pg.query(
11
+ `
12
+ SELECT *
13
+ FROM franceconnect_userinfo
14
+ WHERE user_id = $1
15
+ `,
16
+ [user_id],
17
+ );
18
+
19
+ return rows.shift();
20
+ };
21
+ }
22
+
23
+ export type GetFranceConnectUserInfoHandler = ReturnType<
24
+ typeof getFranceConnectUserInfoFactory
25
+ >;
@@ -0,0 +1,10 @@
1
+ //
2
+
3
+ export * from "./create.js";
4
+ export * from "./find-by-email.js";
5
+ export * from "./find-by-id.js";
6
+ export * from "./get-by-id.js";
7
+ export * from "./get-franceconnect-user-info.js";
8
+ export * from "./update-user-organization-link.js";
9
+ export * from "./update.js";
10
+ export * from "./upsert-franceconnect-userinfo.js";
@@ -0,0 +1,46 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, suite, test } from "node:test";
6
+ import { updateUserOrganizationLinkFactory } from "./update-user-organization-link.js";
7
+
8
+ //
9
+
10
+ const updateUserOrganizationLink = updateUserOrganizationLinkFactory({
11
+ pg: pg as any,
12
+ });
13
+
14
+ suite("updateUserOrganizationLink", () => {
15
+ before(migrate);
16
+ beforeEach(emptyDatabase);
17
+
18
+ test("should update the user organization link", async () => {
19
+ await pg.sql`
20
+ INSERT INTO organizations
21
+ (cached_libelle, cached_nom_complet, id, siret, created_at, updated_at)
22
+ VALUES
23
+ ('Necron', 'Necrontyr', 1, '⚰️', '1967-12-19', '1967-12-19')
24
+ ;
25
+ `;
26
+ await pg.sql`
27
+ INSERT INTO users
28
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
29
+ VALUES
30
+ (1, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'Lion', 'El''Jonson', 'I', 'Primarque')
31
+ ;
32
+ `;
33
+ await pg.sql`
34
+ INSERT INTO users_organizations
35
+ (user_id, organization_id, created_at, updated_at, is_external, verification_type, needs_official_contact_email_verification, official_contact_email_verification_token, official_contact_email_verification_sent_at)
36
+ VALUES
37
+ (1, 1, '4444-04-04', '4444-04-04', false, 'no_verification_means_available', false, null, null)
38
+ ;
39
+ `;
40
+
41
+ const user = await updateUserOrganizationLink(1, 1, {
42
+ is_external: true,
43
+ });
44
+ assert.ok(user.is_external);
45
+ });
46
+ });
@@ -0,0 +1,42 @@
1
+ //
2
+
3
+ import { hashToPostgresParams } from "#src/services/postgres";
4
+ import type { DatabaseContext, User, UserOrganizationLink } from "#src/types";
5
+ import type { QueryResult } from "pg";
6
+
7
+ //
8
+
9
+ export function updateUserOrganizationLinkFactory({ pg }: DatabaseContext) {
10
+ return async function updateUserOrganizationLink(
11
+ organization_id: number,
12
+ user_id: number,
13
+ fieldsToUpdate: Partial<UserOrganizationLink>,
14
+ ) {
15
+ const connection = pg;
16
+
17
+ const fieldsToUpdateWithTimestamps = {
18
+ ...fieldsToUpdate,
19
+ updated_at: new Date(),
20
+ };
21
+
22
+ const { paramsString, valuesString, values } = hashToPostgresParams<User>(
23
+ fieldsToUpdateWithTimestamps,
24
+ );
25
+
26
+ const { rows }: QueryResult<UserOrganizationLink> = await connection.query(
27
+ `
28
+ UPDATE users_organizations SET ${paramsString} = ${valuesString}
29
+ WHERE organization_id = $${values.length + 1}
30
+ AND user_id = $${values.length + 2}
31
+ RETURNING *
32
+ `,
33
+ [...values, organization_id, user_id],
34
+ );
35
+
36
+ return rows.shift()!;
37
+ };
38
+ }
39
+
40
+ export type UpdateUserOrganizationLinkHandler = ReturnType<
41
+ typeof updateUserOrganizationLinkFactory
42
+ >;
@@ -0,0 +1,26 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, it, suite } from "node:test";
6
+ import { updateUserFactory } from "./update.js";
7
+
8
+ //
9
+
10
+ const updateUser = updateUserFactory({ pg: pg as any });
11
+
12
+ suite("updateUserFactory", () => {
13
+ before(migrate);
14
+ beforeEach(emptyDatabase);
15
+
16
+ it("should update the user job", async () => {
17
+ await pg.sql`
18
+ INSERT INTO users
19
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
20
+ VALUES
21
+ (1, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'Lion', 'El''Jonson', 'I', 'Primarque');
22
+ `;
23
+ const user = await updateUser(1, { job: "Chevalier de l'Ordre" });
24
+ assert.equal(user.job, "Chevalier de l'Ordre");
25
+ });
26
+ });
@@ -0,0 +1,34 @@
1
+ //
2
+
3
+ import { hashToPostgresParams } from "#src/services/postgres";
4
+ import type { DatabaseContext, User } from "#src/types";
5
+ import type { QueryResult } from "pg";
6
+
7
+ //
8
+
9
+ export function updateUserFactory({ pg }: DatabaseContext) {
10
+ return async function updateUser(id: number, fieldsToUpdate: Partial<User>) {
11
+ const fieldsToUpdateWithTimestamps = {
12
+ ...fieldsToUpdate,
13
+ updated_at: new Date(),
14
+ };
15
+
16
+ const { paramsString, valuesString, values } = hashToPostgresParams<User>(
17
+ fieldsToUpdateWithTimestamps,
18
+ );
19
+
20
+ const { rows }: QueryResult<User> = await pg.query(
21
+ `
22
+ UPDATE users
23
+ SET ${paramsString} = ${valuesString}
24
+ WHERE id = $${values.length + 1}
25
+ RETURNING *
26
+ `,
27
+ [...values, id],
28
+ );
29
+
30
+ return rows.shift()!;
31
+ };
32
+ }
33
+
34
+ export type UpdateUserHandler = ReturnType<typeof updateUserFactory>;
@@ -0,0 +1,100 @@
1
+ //
2
+
3
+ import { emptyDatabase, migrate, pg } from "#testing";
4
+ import assert from "node:assert/strict";
5
+ import { before, beforeEach, describe, it, mock } from "node:test";
6
+ import { upsertFranceconnectUserinfoFactory } from "./upsert-franceconnect-userinfo.js";
7
+
8
+ //
9
+
10
+ const upsertFranceconnectUserinfo = upsertFranceconnectUserinfoFactory({
11
+ pg: pg as any,
12
+ });
13
+
14
+ describe("upsertFranceconnectUserinfo", () => {
15
+ before(migrate);
16
+ beforeEach(emptyDatabase);
17
+ before(() => {
18
+ mock.timers.enable({ apis: ["Date"], now: new Date("4444-04-04") });
19
+ });
20
+
21
+ it("should insert a user franceconnect userinfo", async () => {
22
+ await pg.sql`
23
+ INSERT INTO users
24
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
25
+ VALUES
26
+ (1, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'i', 'primarque')
27
+ ;
28
+ `;
29
+
30
+ const user = await upsertFranceconnectUserinfo({
31
+ birthdate: new Date("8888-08-08"),
32
+ birthplace: "Caliban",
33
+ created_at: new Date("4444-04-01"),
34
+ family_name: "El’Jonson",
35
+ gender: "male",
36
+ given_name: "Lion",
37
+ preferred_username: "Li",
38
+ sub: "abcdefghijklmnopqrstuvwxyz",
39
+ updated_at: new Date("4444-04-02"),
40
+ user_id: 1,
41
+ });
42
+
43
+ assert.deepEqual(user, {
44
+ birthdate: new Date("8888-08-08"),
45
+ birthplace: "Caliban",
46
+ created_at: new Date("4444-04-01"),
47
+ family_name: "El’Jonson",
48
+ gender: "male",
49
+ given_name: "Lion",
50
+ preferred_username: "Li",
51
+ sub: "abcdefghijklmnopqrstuvwxyz",
52
+ updated_at: new Date("4444-04-04"),
53
+ user_id: 1,
54
+ });
55
+ });
56
+
57
+ it("should update a user Verification link", async () => {
58
+ await pg.sql`
59
+ INSERT INTO users
60
+ (id, email, created_at, updated_at, given_name, family_name, phone_number, job)
61
+ VALUES
62
+ (1, 'lion.eljonson@darkangels.world', '4444-04-04', '4444-04-04', 'lion', 'el''jonson', 'i', 'primarque')
63
+ ;
64
+ `;
65
+ await pg.sql`
66
+ INSERT INTO franceconnect_userinfo
67
+ (user_id, created_at, updated_at, given_name)
68
+ VALUES
69
+ (1, '4444-04-01', '4444-04-02', 'Lion')
70
+ ;
71
+ `;
72
+
73
+ const user = await upsertFranceconnectUserinfo({
74
+ user_id: 1,
75
+ preferred_username: "Li",
76
+ });
77
+
78
+ assert.deepEqual(user, {
79
+ birthdate: null,
80
+ birthplace: null,
81
+ created_at: new Date("4444-04-01"),
82
+ family_name: null,
83
+ gender: null,
84
+ given_name: "Lion",
85
+ preferred_username: "Li",
86
+ sub: null,
87
+ updated_at: new Date("4444-04-04"),
88
+ user_id: 1,
89
+ });
90
+ });
91
+
92
+ it("❎ fail to update an unknown user", async () => {
93
+ await assert.rejects(
94
+ upsertFranceconnectUserinfo({
95
+ user_id: 42,
96
+ }),
97
+ `error: insert or update on table "franceconnect_userinfo" violates foreign key constraint "franceconnect_userinfo_user_id_fkey"`,
98
+ );
99
+ });
100
+ });
@@ -0,0 +1,41 @@
1
+ //
2
+
3
+ import { hashToPostgresParams } from "#src/services/postgres";
4
+ import type { DatabaseContext, FranceConnectUserInfo } from "#src/types";
5
+
6
+ //
7
+
8
+ export function upsertFranceconnectUserinfoFactory({ pg }: DatabaseContext) {
9
+ return async function upsertFranceconnectUserinfo(
10
+ value: Pick<FranceConnectUserInfo, "user_id"> &
11
+ Partial<FranceConnectUserInfo>,
12
+ ) {
13
+ const fieldsWithTimestamps = {
14
+ ...value,
15
+ updated_at: new Date(),
16
+ };
17
+
18
+ const { paramsString, valuesString, values } =
19
+ hashToPostgresParams<FranceConnectUserInfo>(fieldsWithTimestamps);
20
+
21
+ const { rows } = await pg.query<FranceConnectUserInfo>(
22
+ `
23
+ INSERT INTO franceconnect_userinfo
24
+ ${paramsString}
25
+ VALUES
26
+ ${valuesString}
27
+ ON CONFLICT (user_id)
28
+ DO UPDATE
29
+ SET ${paramsString} = ${valuesString}
30
+ RETURNING *
31
+ `,
32
+ [...values],
33
+ );
34
+
35
+ return rows.shift()!;
36
+ };
37
+ }
38
+
39
+ export type UpsetFranceconnectUserinfoHandler = ReturnType<
40
+ typeof upsertFranceconnectUserinfoFactory
41
+ >;
@@ -0,0 +1,4 @@
1
+ //
2
+
3
+ export * from "./is-domain-allowed-for-organization.js";
4
+ export * from "./is-entreprise-unipersonnelle.js";
@@ -0,0 +1,10 @@
1
+ import { DOMAINS_WHITELIST } from "@proconnect-gouv/proconnect.identite/data/organization";
2
+
3
+ export function isDomainAllowedForOrganization(siret: string, domain: string) {
4
+ const whitelist = DOMAINS_WHITELIST.get(siret);
5
+
6
+ // Allow unknown siret to ignore whitelisting
7
+ if (!whitelist) return true;
8
+
9
+ return whitelist.includes(domain);
10
+ }
@@ -0,0 +1,32 @@
1
+ //
2
+
3
+ import { isEntrepriseUnipersonnelle } from "#src/services/organization";
4
+ import type { Organization } from "#src/types";
5
+ import {
6
+ association_org_info,
7
+ entreprise_unipersonnelle_org_info,
8
+ small_association_org_info,
9
+ } from "#testing/seed/organizations";
10
+ import assert from "node:assert/strict";
11
+ import { describe, it } from "node:test";
12
+
13
+ describe("isEntrepriseUnipersonnelle", () => {
14
+ it("should return false for bad call", () => {
15
+ assert.equal(isEntrepriseUnipersonnelle({} as Organization), false);
16
+ });
17
+
18
+ it("should return true for unipersonnelle organization", () => {
19
+ assert.equal(
20
+ isEntrepriseUnipersonnelle(entreprise_unipersonnelle_org_info),
21
+ true,
22
+ );
23
+ });
24
+
25
+ it("should return false for association", () => {
26
+ assert.equal(isEntrepriseUnipersonnelle(association_org_info), false);
27
+ });
28
+
29
+ it("should return false for small association", () => {
30
+ assert.equal(isEntrepriseUnipersonnelle(small_association_org_info), false);
31
+ });
32
+ });
@@ -0,0 +1,31 @@
1
+ //
2
+
3
+ import type { Organization } from "#src/types";
4
+
5
+ /**
6
+ * These fonctions return approximate results. As the data tranche effectifs is
7
+ * two years old. Consequently, an organization that growths quickly within the
8
+ * first two years of his existence can be miss-identified as unipersonnelle by
9
+ * this fonction.
10
+ */
11
+ export function isEntrepriseUnipersonnelle({
12
+ cached_libelle_categorie_juridique,
13
+ cached_tranche_effectifs,
14
+ }: Pick<
15
+ Organization,
16
+ "cached_libelle_categorie_juridique" | "cached_tranche_effectifs"
17
+ >): boolean {
18
+ // check that the organization has the right catégorie juridique
19
+ const cat_jur_ok = [
20
+ "Entrepreneur individuel",
21
+ "Société à responsabilité limitée (sans autre indication)",
22
+ "SAS, société par actions simplifiée",
23
+ ].includes(cached_libelle_categorie_juridique || "");
24
+
25
+ // check that the organization has the right tranche effectifs
26
+ const tra_eff_ok = [null, "NN", "00", "01"].includes(
27
+ cached_tranche_effectifs,
28
+ );
29
+
30
+ return cat_jur_ok && tra_eff_ok;
31
+ }
@@ -0,0 +1,34 @@
1
+ //
2
+
3
+ import { chain } from "lodash-es";
4
+
5
+ //
6
+
7
+ export function hashToPostgresParams<T>(fieldsToUpdate: Partial<T>): {
8
+ // postgres column-list syntax
9
+ paramsString: string;
10
+ // postgres column-list syntax for prepared statement
11
+ valuesString: string;
12
+ values: any[];
13
+ } {
14
+ const paramsString = "(" + Object.keys(fieldsToUpdate).join(", ") + ")";
15
+ // 'email, encrypted_password'
16
+
17
+ const valuesString =
18
+ "(" +
19
+ chain(fieldsToUpdate)
20
+ // { email: 'email@xy.z', encrypted_password: 'hash' }
21
+ .toPairs()
22
+ // [[ 'email', 'email@xy.z'], ['encrypted_password', 'hash' ]]
23
+ .map((_value, index) => `$${index + 1}`)
24
+ // [ '$1', '$2' ]
25
+ .join(", ")
26
+ // '$1, $2'
27
+ .value() +
28
+ ")";
29
+
30
+ const values = Object.values(fieldsToUpdate);
31
+ // [ 'email@xy.z', 'hash' ]
32
+
33
+ return { paramsString, valuesString, values };
34
+ }