@proconnect-gouv/proconnect.identite 1.5.0 → 1.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @proconnect-gouv/proconnect.identite
2
2
 
3
+ ## 1.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1764](https://github.com/proconnect-gouv/proconnect-identite/pull/1764) [`6484e64`](https://github.com/proconnect-gouv/proconnect-identite/commit/6484e64f558493b5963d32681dab645484b87693) Thanks [@rdubigny](https://github.com/rdubigny)! - Ajout d'une catégorie SuperWeakLinkTypes pour gérer les cas des domaines non vérifiés
8
+
3
9
  ## 1.5.0
4
10
 
5
11
  ### Minor Changes
@@ -71,8 +71,8 @@ export function certificationScore(identitePivot, sourceDirigeant) {
71
71
  else {
72
72
  // For foreigners: check country code (with conversion if needed from RNE)
73
73
  // Both should already be in COG format (99XXX)
74
- if (identitePivot.birthcountry &&
75
- sourceDirigeant.birthcountry &&
74
+ if (identitePivot.birthcountry === null ||
75
+ sourceDirigeant.birthcountry === null ||
76
76
  identitePivot.birthcountry === sourceDirigeant.birthcountry) {
77
77
  matches.add(MatchCriteria.enum.birth_country);
78
78
  }
@@ -13,7 +13,7 @@ export declare function forceJoinOrganizationFactory({ findEmailDomainsByOrganiz
13
13
  is_external?: boolean;
14
14
  }) => Promise<{
15
15
  is_external: boolean;
16
- verification_type: "code_sent_to_official_contact_email" | "domain" | "domain_not_verified_yet" | "imported_from_coop_mediation_numerique" | "imported_from_inclusion_connect" | "in_liste_dirigeants_rna" | "in_liste_dirigeants_rne" | "official_contact_email" | "ordre_professionnel_domain" | "proof_received" | "verified_by_coop_mediation_numerique" | "bypassed" | "organization_dirigeant" | "no_validation_means_available" | "no_verification_means_for_entreprise_unipersonnelle" | "no_verification_means_for_small_association";
16
+ verification_type: "organization_dirigeant" | "code_sent_to_official_contact_email" | "domain" | "imported_from_coop_mediation_numerique" | "imported_from_inclusion_connect" | "in_liste_dirigeants_rna" | "in_liste_dirigeants_rne" | "official_contact_email" | "ordre_professionnel_domain" | "proof_received" | "verified_by_coop_mediation_numerique" | "bypassed" | "domain_not_verified_yet" | "no_validation_means_available" | "no_verification_means_for_entreprise_unipersonnelle" | "no_verification_means_for_small_association";
17
17
  verified_at: Date | null;
18
18
  has_been_greeted: boolean;
19
19
  needs_official_contact_email_verification: boolean;
@@ -1,5 +1,5 @@
1
1
  //
2
- import { LinkTypes, UnverifiedLinkTypes } from "#src/types";
2
+ import { LinkTypes, SuperWeakLinkTypes, UnverifiedLinkTypes } from "#src/types";
3
3
  import { getEmailDomain } from "@proconnect-gouv/proconnect.core/services/email";
4
4
  import { match } from "ts-pattern";
5
5
  export function assignUserVerificationTypeToDomainFactory({ getUsers, updateUserOrganizationLink, }) {
@@ -9,7 +9,7 @@ export function assignUserVerificationTypeToDomainFactory({ getUsers, updateUser
9
9
  const userDomain = getEmailDomain(email);
10
10
  if (userDomain === domain &&
11
11
  match(link_verification_type)
12
- .with(...UnverifiedLinkTypes, () => true)
12
+ .with(...UnverifiedLinkTypes, ...SuperWeakLinkTypes, () => true)
13
13
  .otherwise(() => false)) {
14
14
  return updateUserOrganizationLink(organization_id, id, {
15
15
  verification_type: LinkTypes.enum.domain,
@@ -1,7 +1,7 @@
1
1
  import type { DatabaseContext, Organization } from "#src/types";
2
2
  export declare function findByUserIdFactory({ pg }: DatabaseContext): (userId: number) => Promise<(Organization & {
3
3
  is_external: boolean;
4
- verification_type: "code_sent_to_official_contact_email" | "domain" | "domain_not_verified_yet" | "imported_from_coop_mediation_numerique" | "imported_from_inclusion_connect" | "in_liste_dirigeants_rna" | "in_liste_dirigeants_rne" | "official_contact_email" | "ordre_professionnel_domain" | "proof_received" | "verified_by_coop_mediation_numerique" | "bypassed" | "organization_dirigeant" | "no_validation_means_available" | "no_verification_means_for_entreprise_unipersonnelle" | "no_verification_means_for_small_association";
4
+ verification_type: "organization_dirigeant" | "code_sent_to_official_contact_email" | "domain" | "imported_from_coop_mediation_numerique" | "imported_from_inclusion_connect" | "in_liste_dirigeants_rna" | "in_liste_dirigeants_rne" | "official_contact_email" | "ordre_professionnel_domain" | "proof_received" | "verified_by_coop_mediation_numerique" | "bypassed" | "domain_not_verified_yet" | "no_validation_means_available" | "no_verification_means_for_entreprise_unipersonnelle" | "no_verification_means_for_small_association";
5
5
  verified_at: Date | null;
6
6
  has_been_greeted: boolean;
7
7
  needs_official_contact_email_verification: boolean;
@@ -1,7 +1,7 @@
1
1
  import type { DatabaseContext, User } from "#src/types";
2
2
  export declare function getUsersByOrganizationFactory({ pg }: DatabaseContext): (organization_id: number, additionalWhereClause?: string, additionalParams?: any[]) => Promise<(User & {
3
3
  is_external: boolean;
4
- verification_type: "code_sent_to_official_contact_email" | "domain" | "domain_not_verified_yet" | "imported_from_coop_mediation_numerique" | "imported_from_inclusion_connect" | "in_liste_dirigeants_rna" | "in_liste_dirigeants_rne" | "official_contact_email" | "ordre_professionnel_domain" | "proof_received" | "verified_by_coop_mediation_numerique" | "bypassed" | "organization_dirigeant" | "no_validation_means_available" | "no_verification_means_for_entreprise_unipersonnelle" | "no_verification_means_for_small_association";
4
+ verification_type: "organization_dirigeant" | "code_sent_to_official_contact_email" | "domain" | "imported_from_coop_mediation_numerique" | "imported_from_inclusion_connect" | "in_liste_dirigeants_rna" | "in_liste_dirigeants_rne" | "official_contact_email" | "ordre_professionnel_domain" | "proof_received" | "verified_by_coop_mediation_numerique" | "bypassed" | "domain_not_verified_yet" | "no_validation_means_available" | "no_verification_means_for_entreprise_unipersonnelle" | "no_verification_means_for_small_association";
5
5
  verified_at: Date | null;
6
6
  has_been_greeted: boolean;
7
7
  needs_official_contact_email_verification: boolean;
@@ -1,7 +1,7 @@
1
1
  import type { DatabaseContext, InsertUserOrganizationLink } from "#src/types";
2
2
  export declare function linkUserToOrganizationFactory({ pg }: DatabaseContext): ({ is_external, needs_official_contact_email_verification, organization_id, user_id, verification_type, }: InsertUserOrganizationLink) => Promise<{
3
3
  is_external: boolean;
4
- verification_type: "code_sent_to_official_contact_email" | "domain" | "domain_not_verified_yet" | "imported_from_coop_mediation_numerique" | "imported_from_inclusion_connect" | "in_liste_dirigeants_rna" | "in_liste_dirigeants_rne" | "official_contact_email" | "ordre_professionnel_domain" | "proof_received" | "verified_by_coop_mediation_numerique" | "bypassed" | "organization_dirigeant" | "no_validation_means_available" | "no_verification_means_for_entreprise_unipersonnelle" | "no_verification_means_for_small_association";
4
+ verification_type: "organization_dirigeant" | "code_sent_to_official_contact_email" | "domain" | "imported_from_coop_mediation_numerique" | "imported_from_inclusion_connect" | "in_liste_dirigeants_rna" | "in_liste_dirigeants_rne" | "official_contact_email" | "ordre_professionnel_domain" | "proof_received" | "verified_by_coop_mediation_numerique" | "bypassed" | "domain_not_verified_yet" | "no_validation_means_available" | "no_verification_means_for_entreprise_unipersonnelle" | "no_verification_means_for_small_association";
5
5
  verified_at: Date | null;
6
6
  has_been_greeted: boolean;
7
7
  needs_official_contact_email_verification: boolean;
@@ -1,7 +1,7 @@
1
1
  import type { DatabaseContext, UserOrganizationLink } from "#src/types";
2
2
  export declare function updateUserOrganizationLinkFactory({ pg }: DatabaseContext): (organization_id: number, user_id: number, fieldsToUpdate: Partial<UserOrganizationLink>) => Promise<{
3
3
  is_external: boolean;
4
- verification_type: "code_sent_to_official_contact_email" | "domain" | "domain_not_verified_yet" | "imported_from_coop_mediation_numerique" | "imported_from_inclusion_connect" | "in_liste_dirigeants_rna" | "in_liste_dirigeants_rne" | "official_contact_email" | "ordre_professionnel_domain" | "proof_received" | "verified_by_coop_mediation_numerique" | "bypassed" | "organization_dirigeant" | "no_validation_means_available" | "no_verification_means_for_entreprise_unipersonnelle" | "no_verification_means_for_small_association";
4
+ verification_type: "organization_dirigeant" | "code_sent_to_official_contact_email" | "domain" | "imported_from_coop_mediation_numerique" | "imported_from_inclusion_connect" | "in_liste_dirigeants_rna" | "in_liste_dirigeants_rne" | "official_contact_email" | "ordre_professionnel_domain" | "proof_received" | "verified_by_coop_mediation_numerique" | "bypassed" | "domain_not_verified_yet" | "no_validation_means_available" | "no_verification_means_for_entreprise_unipersonnelle" | "no_verification_means_for_small_association";
5
5
  verified_at: Date | null;
6
6
  has_been_greeted: boolean;
7
7
  needs_official_contact_email_verification: boolean;
@@ -1,12 +1,12 @@
1
1
  import { z } from "zod";
2
- export declare const WeakLinkTypes: readonly ["code_sent_to_official_contact_email", "domain", "domain_not_verified_yet", "imported_from_coop_mediation_numerique", "imported_from_inclusion_connect", "in_liste_dirigeants_rna", "in_liste_dirigeants_rne", "official_contact_email", "ordre_professionnel_domain", "proof_received", "verified_by_coop_mediation_numerique", "bypassed"];
3
2
  export declare const StrongLinkTypes: readonly ["organization_dirigeant"];
4
- export declare const VerifiedLinkTypes: readonly ["code_sent_to_official_contact_email", "domain", "domain_not_verified_yet", "imported_from_coop_mediation_numerique", "imported_from_inclusion_connect", "in_liste_dirigeants_rna", "in_liste_dirigeants_rne", "official_contact_email", "ordre_professionnel_domain", "proof_received", "verified_by_coop_mediation_numerique", "bypassed", "organization_dirigeant"];
3
+ export declare const WeakLinkTypes: readonly ["code_sent_to_official_contact_email", "domain", "imported_from_coop_mediation_numerique", "imported_from_inclusion_connect", "in_liste_dirigeants_rna", "in_liste_dirigeants_rne", "official_contact_email", "ordre_professionnel_domain", "proof_received", "verified_by_coop_mediation_numerique", "bypassed"];
4
+ export declare const SuperWeakLinkTypes: readonly ["domain_not_verified_yet"];
5
5
  export declare const UnverifiedLinkTypes: readonly ["no_validation_means_available", "no_verification_means_for_entreprise_unipersonnelle", "no_verification_means_for_small_association"];
6
6
  export declare const LinkTypes: z.ZodEnum<{
7
+ organization_dirigeant: "organization_dirigeant";
7
8
  code_sent_to_official_contact_email: "code_sent_to_official_contact_email";
8
9
  domain: "domain";
9
- domain_not_verified_yet: "domain_not_verified_yet";
10
10
  imported_from_coop_mediation_numerique: "imported_from_coop_mediation_numerique";
11
11
  imported_from_inclusion_connect: "imported_from_inclusion_connect";
12
12
  in_liste_dirigeants_rna: "in_liste_dirigeants_rna";
@@ -16,7 +16,7 @@ export declare const LinkTypes: z.ZodEnum<{
16
16
  proof_received: "proof_received";
17
17
  verified_by_coop_mediation_numerique: "verified_by_coop_mediation_numerique";
18
18
  bypassed: "bypassed";
19
- organization_dirigeant: "organization_dirigeant";
19
+ domain_not_verified_yet: "domain_not_verified_yet";
20
20
  no_validation_means_available: "no_validation_means_available";
21
21
  no_verification_means_for_entreprise_unipersonnelle: "no_verification_means_for_entreprise_unipersonnelle";
22
22
  no_verification_means_for_small_association: "no_verification_means_for_small_association";
@@ -24,9 +24,9 @@ export declare const LinkTypes: z.ZodEnum<{
24
24
  export declare const BaseUserOrganizationLinkSchema: z.ZodObject<{
25
25
  is_external: z.ZodBoolean;
26
26
  verification_type: z.ZodEnum<{
27
+ organization_dirigeant: "organization_dirigeant";
27
28
  code_sent_to_official_contact_email: "code_sent_to_official_contact_email";
28
29
  domain: "domain";
29
- domain_not_verified_yet: "domain_not_verified_yet";
30
30
  imported_from_coop_mediation_numerique: "imported_from_coop_mediation_numerique";
31
31
  imported_from_inclusion_connect: "imported_from_inclusion_connect";
32
32
  in_liste_dirigeants_rna: "in_liste_dirigeants_rna";
@@ -36,7 +36,7 @@ export declare const BaseUserOrganizationLinkSchema: z.ZodObject<{
36
36
  proof_received: "proof_received";
37
37
  verified_by_coop_mediation_numerique: "verified_by_coop_mediation_numerique";
38
38
  bypassed: "bypassed";
39
- organization_dirigeant: "organization_dirigeant";
39
+ domain_not_verified_yet: "domain_not_verified_yet";
40
40
  no_validation_means_available: "no_validation_means_available";
41
41
  no_verification_means_for_entreprise_unipersonnelle: "no_verification_means_for_entreprise_unipersonnelle";
42
42
  no_verification_means_for_small_association: "no_verification_means_for_small_association";
@@ -51,9 +51,9 @@ export type BaseUserOrganizationLink = z.output<typeof BaseUserOrganizationLinkS
51
51
  export declare const UserOrganizationLinkSchema: z.ZodObject<{
52
52
  is_external: z.ZodBoolean;
53
53
  verification_type: z.ZodEnum<{
54
+ organization_dirigeant: "organization_dirigeant";
54
55
  code_sent_to_official_contact_email: "code_sent_to_official_contact_email";
55
56
  domain: "domain";
56
- domain_not_verified_yet: "domain_not_verified_yet";
57
57
  imported_from_coop_mediation_numerique: "imported_from_coop_mediation_numerique";
58
58
  imported_from_inclusion_connect: "imported_from_inclusion_connect";
59
59
  in_liste_dirigeants_rna: "in_liste_dirigeants_rna";
@@ -63,7 +63,7 @@ export declare const UserOrganizationLinkSchema: z.ZodObject<{
63
63
  proof_received: "proof_received";
64
64
  verified_by_coop_mediation_numerique: "verified_by_coop_mediation_numerique";
65
65
  bypassed: "bypassed";
66
- organization_dirigeant: "organization_dirigeant";
66
+ domain_not_verified_yet: "domain_not_verified_yet";
67
67
  no_validation_means_available: "no_validation_means_available";
68
68
  no_verification_means_for_entreprise_unipersonnelle: "no_verification_means_for_entreprise_unipersonnelle";
69
69
  no_verification_means_for_small_association: "no_verification_means_for_small_association";
@@ -83,9 +83,9 @@ export declare const InsertUserOrganizationLinkSchema: z.ZodObject<{
83
83
  user_id: z.ZodNumber;
84
84
  organization_id: z.ZodNumber;
85
85
  verification_type: z.ZodEnum<{
86
+ organization_dirigeant: "organization_dirigeant";
86
87
  code_sent_to_official_contact_email: "code_sent_to_official_contact_email";
87
88
  domain: "domain";
88
- domain_not_verified_yet: "domain_not_verified_yet";
89
89
  imported_from_coop_mediation_numerique: "imported_from_coop_mediation_numerique";
90
90
  imported_from_inclusion_connect: "imported_from_inclusion_connect";
91
91
  in_liste_dirigeants_rna: "in_liste_dirigeants_rna";
@@ -95,7 +95,7 @@ export declare const InsertUserOrganizationLinkSchema: z.ZodObject<{
95
95
  proof_received: "proof_received";
96
96
  verified_by_coop_mediation_numerique: "verified_by_coop_mediation_numerique";
97
97
  bypassed: "bypassed";
98
- organization_dirigeant: "organization_dirigeant";
98
+ domain_not_verified_yet: "domain_not_verified_yet";
99
99
  no_validation_means_available: "no_validation_means_available";
100
100
  no_verification_means_for_entreprise_unipersonnelle: "no_verification_means_for_entreprise_unipersonnelle";
101
101
  no_verification_means_for_small_association: "no_verification_means_for_small_association";
@@ -1 +1 @@
1
- {"version":3,"file":"user-organization-link.d.ts","sourceRoot":"","sources":["../../src/types/user-organization-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,wVAchB,CAAC;AAEX,eAAO,MAAM,eAAe,qCAAsC,CAAC;AAEnE,eAAO,MAAM,iBAAiB,kXAGpB,CAAC;AAEX,eAAO,MAAM,mBAAmB,kJAItB,CAAC;AAEX,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;EAAyD,CAAC;AAEhF,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;iBASzC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAC7C,OAAO,8BAA8B,CACtC,CAAC;AAIF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOtC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI/E,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;iBAW5C,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAC/C,OAAO,gCAAgC,CACxC,CAAC"}
1
+ {"version":3,"file":"user-organization-link.d.ts","sourceRoot":"","sources":["../../src/types/user-organization-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,eAAe,qCAAsC,CAAC;AAEnE,eAAO,MAAM,aAAa,6TAahB,CAAC;AAMX,eAAO,MAAM,kBAAkB,sCAAuC,CAAC;AAQvE,eAAO,MAAM,mBAAmB,kJAItB,CAAC;AAEX,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;EAAyD,CAAC;AAEhF,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;iBASzC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAC7C,OAAO,8BAA8B,CACtC,CAAC;AAIF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOtC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI/E,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;iBAW5C,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAC/C,OAAO,gCAAgC,CACxC,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import { z } from "zod";
2
+ export const StrongLinkTypes = ["organization_dirigeant"];
2
3
  export const WeakLinkTypes = [
3
4
  "code_sent_to_official_contact_email",
4
5
  "domain",
5
- "domain_not_verified_yet",
6
6
  "imported_from_coop_mediation_numerique",
7
7
  "imported_from_inclusion_connect",
8
8
  "in_liste_dirigeants_rna",
@@ -14,10 +14,15 @@ export const WeakLinkTypes = [
14
14
  // Used in the sandbox environment to bypass the verification process
15
15
  "bypassed",
16
16
  ];
17
- export const StrongLinkTypes = ["organization_dirigeant"];
18
- export const VerifiedLinkTypes = [
19
- ...WeakLinkTypes,
17
+ // This link type should be considered as unverified.
18
+ // However, doing so would trigger a FranceConnect authentication requirement for the user.
19
+ // Users shouldn't face inconvenience while waiting for domain review completion.
20
+ // Instead, we should remove these unverified domains and then eliminate this special case from the codebase.
21
+ export const SuperWeakLinkTypes = ["domain_not_verified_yet"];
22
+ const VerifiedLinkTypes = [
20
23
  ...StrongLinkTypes,
24
+ ...WeakLinkTypes,
25
+ ...SuperWeakLinkTypes,
21
26
  ];
22
27
  export const UnverifiedLinkTypes = [
23
28
  "no_validation_means_available",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proconnect-gouv/proconnect.identite",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "homepage": "https://github.com/proconnect-gouv/proconnect-identite/tree/main/packages/identite#readme",
5
5
  "bugs": "https://github.com/proconnect-gouv/proconnect-identite/issues",
6
6
  "repository": {
@@ -96,8 +96,8 @@ export function certificationScore(
96
96
  // For foreigners: check country code (with conversion if needed from RNE)
97
97
  // Both should already be in COG format (99XXX)
98
98
  if (
99
- identitePivot.birthcountry &&
100
- sourceDirigeant.birthcountry &&
99
+ identitePivot.birthcountry === null ||
100
+ sourceDirigeant.birthcountry === null ||
101
101
  identitePivot.birthcountry === sourceDirigeant.birthcountry
102
102
  ) {
103
103
  matches.add(MatchCriteria.enum.birth_country);
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { IdentityVectorSchema } from "#src/types";
4
4
  import {
5
+ AmberleyVailFranceConnectUserInfo,
5
6
  LiElJonsonFranceConnectUserInfo,
6
7
  RogalDornFranceConnectUserInfo,
7
8
  } from "#testing/seed/franceconnect";
@@ -18,17 +19,57 @@ import {
18
19
  RogalDornEstablishment,
19
20
  } from "@proconnect-gouv/proconnect.insee/testing/seed";
20
21
  import {
22
+ AmberleyVailPouvoir,
21
23
  RogalDornPouvoir,
22
24
  UlysseTosiPouvoir,
23
25
  } from "@proconnect-gouv/proconnect.registre_national_entreprises/testing/seed";
24
26
  import assert from "node:assert/strict";
25
27
  import { describe, it } from "node:test";
28
+ import * as ApiEntreprise from "./adapters/api_entreprise.js";
29
+ import * as INSEE from "./adapters/insee.js";
30
+ import * as RNE from "./adapters/rne.js";
26
31
  import { processCertificationDirigeantFactory } from "./process-certification-dirigeant.js";
27
32
 
28
33
  //
29
34
 
30
35
  describe("processCertificationDirigeantFactory", () => {
31
36
  it("should recognize a user as executive of a auto-entrepreneur", async () => {
37
+ const processCertificationDirigeant = processCertificationDirigeantFactory({
38
+ ApiEntrepriseInfogreffeRepository: {
39
+ findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
40
+ },
41
+ RegistreNationalEntreprisesApiRepository: {
42
+ findPouvoirsBySiren: () => Promise.reject(new Error("💣")),
43
+ },
44
+ InseeApiRepository: {
45
+ findBySiren: () => Promise.resolve(LiElJonsonEstablishment),
46
+ },
47
+ });
48
+
49
+ const certificationDirigeantResult = await processCertificationDirigeant(
50
+ rogal_dorn_org_info,
51
+ LiElJonsonFranceConnectUserInfo,
52
+ );
53
+
54
+ assert.deepEqual(certificationDirigeantResult, {
55
+ cause: "exact_match",
56
+ details: {
57
+ dirigeant: INSEE.toIdentityVector(LiElJonsonEstablishment),
58
+ matches: new Set([
59
+ "family_name",
60
+ "first_name",
61
+ "gender",
62
+ "birth_date",
63
+ "birth_place",
64
+ ]),
65
+ identity: IdentityVectorSchema.parse(LiElJonsonFranceConnectUserInfo),
66
+ source: "api.insee.fr/api-sirene/private",
67
+ },
68
+ ok: true,
69
+ });
70
+ });
71
+
72
+ it("should recognize a foreign user as executive of a auto-entrepreneur", async () => {
32
73
  const processCertificationDirigeant = processCertificationDirigeantFactory({
33
74
  ApiEntrepriseInfogreffeRepository: {
34
75
  findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
@@ -49,10 +90,7 @@ describe("processCertificationDirigeantFactory", () => {
49
90
  assert.deepEqual(certificationDirigeantResult, {
50
91
  cause: "exact_match",
51
92
  details: {
52
- dirigeant: {
53
- ...IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
54
- birthplace: null, // INSEE adapter returns null for foreigners
55
- },
93
+ dirigeant: INSEE.toIdentityVector(RogalDornEstablishment),
56
94
  matches: new Set([
57
95
  "family_name",
58
96
  "first_name",
@@ -88,11 +126,8 @@ describe("processCertificationDirigeantFactory", () => {
88
126
  assert.deepEqual(certificationDirigeantResult, {
89
127
  cause: "below_threshold",
90
128
  details: {
91
- dirigeant: {
92
- ...IdentityVectorSchema.parse(LiElJonsonFranceConnectUserInfo),
93
- birthcountry: null, // INSEE adapter returns null for French people
94
- },
95
- matches: new Set(["gender"]),
129
+ dirigeant: INSEE.toIdentityVector(LiElJonsonEstablishment),
130
+ matches: new Set(["gender", "birth_country"]),
96
131
  identity: IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
97
132
  source: "api.insee.fr/api-sirene/private",
98
133
  },
@@ -122,10 +157,7 @@ describe("processCertificationDirigeantFactory", () => {
122
157
  assert.deepEqual(certificationDirigeantResult, {
123
158
  cause: "exact_match",
124
159
  details: {
125
- dirigeant: {
126
- ...IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
127
- birthplace: null, // RNE adapter returns null for foreigners
128
- },
160
+ dirigeant: RNE.toIdentityVector(RogalDornPouvoir),
129
161
  matches: new Set([
130
162
  "family_name",
131
163
  "first_name",
@@ -140,6 +172,43 @@ describe("processCertificationDirigeantFactory", () => {
140
172
  });
141
173
  });
142
174
 
175
+ it("should match Amberley Vail among the executive of Papillon in RNE", async () => {
176
+ const processCertificationDirigeant = processCertificationDirigeantFactory({
177
+ ApiEntrepriseInfogreffeRepository: {
178
+ findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
179
+ },
180
+ RegistreNationalEntreprisesApiRepository: {
181
+ findPouvoirsBySiren: () =>
182
+ Promise.resolve([UlysseTosiPouvoir, AmberleyVailPouvoir]),
183
+ },
184
+ InseeApiRepository: {
185
+ findBySiren: () => Promise.reject(new Error("💣")),
186
+ },
187
+ });
188
+
189
+ const certificationDirigeantResult = await processCertificationDirigeant(
190
+ papillon_org_info,
191
+ AmberleyVailFranceConnectUserInfo,
192
+ );
193
+
194
+ assert.deepEqual(certificationDirigeantResult, {
195
+ cause: "exact_match",
196
+ details: {
197
+ dirigeant: RNE.toIdentityVector(AmberleyVailPouvoir),
198
+ matches: new Set([
199
+ "family_name",
200
+ "first_name",
201
+ "gender",
202
+ "birth_date",
203
+ "birth_country",
204
+ ]),
205
+ identity: IdentityVectorSchema.parse(AmberleyVailFranceConnectUserInfo),
206
+ source: "registre-national-entreprises.inpi.fr/api",
207
+ },
208
+ ok: true,
209
+ });
210
+ });
211
+
143
212
  it("should match Rogal Dorn among the executive of Papillon in Infogreffe", async () => {
144
213
  const processCertificationDirigeant = processCertificationDirigeantFactory({
145
214
  ApiEntrepriseInfogreffeRepository: {
@@ -162,11 +231,7 @@ describe("processCertificationDirigeantFactory", () => {
162
231
  assert.deepEqual(certificationDirigeantResult, {
163
232
  cause: "exact_match",
164
233
  details: {
165
- dirigeant: {
166
- ...IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
167
- birthplace: null, // API Entreprise adapter doesn't provide birthplace
168
- gender: null, // API Entreprise adapter doesn't provide gender
169
- },
234
+ dirigeant: ApiEntreprise.toIdentityVector(RogalDornMandataire),
170
235
  matches: new Set([
171
236
  "family_name",
172
237
  "first_name",
@@ -2,7 +2,7 @@
2
2
 
3
3
  import type { GetUsersByOrganizationHandler } from "#src/repositories/organization";
4
4
  import type { UpdateUserOrganizationLinkHandler } from "#src/repositories/user";
5
- import { LinkTypes, UnverifiedLinkTypes } from "#src/types";
5
+ import { LinkTypes, SuperWeakLinkTypes, UnverifiedLinkTypes } from "#src/types";
6
6
  import { getEmailDomain } from "@proconnect-gouv/proconnect.core/services/email";
7
7
  import { match } from "ts-pattern";
8
8
 
@@ -30,7 +30,7 @@ export function assignUserVerificationTypeToDomainFactory({
30
30
  if (
31
31
  userDomain === domain &&
32
32
  match(link_verification_type)
33
- .with(...UnverifiedLinkTypes, () => true)
33
+ .with(...UnverifiedLinkTypes, ...SuperWeakLinkTypes, () => true)
34
34
  .otherwise(() => false)
35
35
  ) {
36
36
  return updateUserOrganizationLink(organization_id, id, {
@@ -1,9 +1,10 @@
1
1
  import { z } from "zod";
2
2
 
3
+ export const StrongLinkTypes = ["organization_dirigeant"] as const;
4
+
3
5
  export const WeakLinkTypes = [
4
6
  "code_sent_to_official_contact_email",
5
7
  "domain",
6
- "domain_not_verified_yet",
7
8
  "imported_from_coop_mediation_numerique",
8
9
  "imported_from_inclusion_connect",
9
10
  "in_liste_dirigeants_rna",
@@ -16,11 +17,16 @@ export const WeakLinkTypes = [
16
17
  "bypassed",
17
18
  ] as const;
18
19
 
19
- export const StrongLinkTypes = ["organization_dirigeant"] as const;
20
+ // This link type should be considered as unverified.
21
+ // However, doing so would trigger a FranceConnect authentication requirement for the user.
22
+ // Users shouldn't face inconvenience while waiting for domain review completion.
23
+ // Instead, we should remove these unverified domains and then eliminate this special case from the codebase.
24
+ export const SuperWeakLinkTypes = ["domain_not_verified_yet"] as const;
20
25
 
21
- export const VerifiedLinkTypes = [
22
- ...WeakLinkTypes,
26
+ const VerifiedLinkTypes = [
23
27
  ...StrongLinkTypes,
28
+ ...WeakLinkTypes,
29
+ ...SuperWeakLinkTypes,
24
30
  ] as const;
25
31
 
26
32
  export const UnverifiedLinkTypes = [
@@ -41,3 +41,17 @@ export const RogalDornFranceConnectUserInfo: FranceConnectUserInfo = {
41
41
  updated_at: new Date(),
42
42
  user_id: NaN,
43
43
  };
44
+
45
+ export const AmberleyVailFranceConnectUserInfo: FranceConnectUserInfo = {
46
+ birthcountry: "99102",
47
+ birthdate: new Date(Date.UTC(29000, 6, 7)),
48
+ birthplace: "",
49
+ created_at: new Date("2023-04-10"),
50
+ family_name: "Vail",
51
+ gender: "female",
52
+ given_name: "Amberley",
53
+ preferred_username: "Holy Inquisition",
54
+ sub: "ORDOS546",
55
+ updated_at: new Date(),
56
+ user_id: NaN,
57
+ };