Skip to content

Commit

Permalink
refactor: extract force join organization
Browse files Browse the repository at this point in the history
  • Loading branch information
douglasduteil committed Feb 18, 2025
1 parent 0b1017d commit ecfccb2
Show file tree
Hide file tree
Showing 22 changed files with 160 additions and 58 deletions.
1 change: 1 addition & 0 deletions packages/identite/src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class NotFoundError extends Error {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { describe } from "mocha";
import { forceJoinOrganizationFactory } from "./force-join-organization.js";

describe(forceJoinOrganizationFactory.name, () => {});
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//

import { NotFoundError } from "#src/errors";
import type { FindEmailDomainsByOrganizationIdHandler } from "#src/repositories/email-domain";
import type {
FindByIdHandler as FindOrganizationByIdHandler,
LinkUserToOrganizationHandler,
} from "#src/repositories/organization";
import type { FindByIdHandler as FindUserByIdHandler } from "#src/repositories/user";
import type { BaseUserOrganizationLink } from "#src/types";
import { getEmailDomain } from "@gouvfr-lasuite/proconnect.core/services/email";
import { isEmpty, some } from "lodash-es";

//

type FactoryDependencies = {
findById: FindOrganizationByIdHandler;
findEmailDomainsByOrganizationId: FindEmailDomainsByOrganizationIdHandler;
findUserById: FindUserByIdHandler;
linkUserToOrganization: LinkUserToOrganizationHandler;
};

//

export function forceJoinOrganizationFactory({
findById,
findEmailDomainsByOrganizationId,
findUserById,
linkUserToOrganization,
}: FactoryDependencies) {
return async function forceJoinOrganization({
organization_id,
user_id,
is_external = false,
}: {
organization_id: number;
user_id: number;
is_external?: boolean;
}) {
const user = await findUserById(user_id);
const organization = await findById(organization_id);
if (isEmpty(user) || isEmpty(organization)) {
throw new NotFoundError();
}
const { email } = user;
const domain = getEmailDomain(email);
const organizationEmailDomains =
await findEmailDomainsByOrganizationId(organization_id);

let link_verification_type: BaseUserOrganizationLink["verification_type"];
if (
some(organizationEmailDomains, {
domain,
verification_type: "verified",
}) ||
some(organizationEmailDomains, {
domain,
verification_type: "trackdechets_postal_mail",
}) ||
some(organizationEmailDomains, { domain, verification_type: "external" })
) {
link_verification_type = "domain";
} else {
link_verification_type = "no_validation_means_available";
}

return await linkUserToOrganization({
organization_id,
user_id,
is_external,
verification_type: link_verification_type,
});
};
}
1 change: 1 addition & 0 deletions packages/identite/src/managers/organization/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//

export * from "./force-join-organization.js";
export * from "./get-organization-info.js";
export * from "./mark-domain-as-verified.js";
1 change: 1 addition & 0 deletions packages/identite/src/repositories/organization/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

export * from "./find-by-id.js";
export * from "./get-users-by-organization.js";
export * from "./link-user-to-organization.js";
export * from "./upsert.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { describe } from "mocha";
import { linkUserToOrganizationFactory } from "./link-user-to-organization.js";

describe(linkUserToOrganizationFactory.name, () => {});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { DatabaseContext, UserOrganizationLink } from "#src/types";
import type { QueryResult } from "pg";

export function linkUserToOrganizationFactory({ pg }: DatabaseContext) {
return async function linkUserToOrganization({
organization_id,
user_id,
is_external = false,
verification_type,
needs_official_contact_email_verification = false,
}: {
organization_id: number;
user_id: number;
is_external?: boolean;
verification_type: UserOrganizationLink["verification_type"];
needs_official_contact_email_verification?: UserOrganizationLink["needs_official_contact_email_verification"];
}) {
const { rows }: QueryResult<UserOrganizationLink> = await pg.query(
`
INSERT INTO users_organizations
(user_id,
organization_id,
is_external,
verification_type,
needs_official_contact_email_verification,
updated_at,
created_at)
VALUES
($1, $2, $3, $4, $5, $6, $7)
RETURNING *
`,
[
user_id,
organization_id,
is_external,
verification_type,
needs_official_contact_email_verification,
new Date(),
new Date(),
],
);

return rows.shift()! as UserOrganizationLink;
};
}

export type LinkUserToOrganizationHandler = ReturnType<
typeof linkUserToOrganizationFactory
>;
2 changes: 2 additions & 0 deletions packages/identite/src/repositories/organization/upsert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,5 @@ export function upsertFactory({ pg }: DatabaseContext) {
return rows.shift()!;
};
}

export type UpsertHandler = ReturnType<typeof upsertFactory>;
2 changes: 2 additions & 0 deletions packages/identite/src/repositories/user/find-by-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ export function findByIdFactory({ pg }: DatabaseContext) {
return rows.shift();
};
}

export type FindByIdHandler = ReturnType<typeof findByIdFactory>;
2 changes: 0 additions & 2 deletions src/config/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ export class InseeNotActiveError extends Error {}

export class UserNotFoundError extends Error {}

export class NotFoundError extends Error {}

export class ForbiddenError extends Error {}

export class UnableToAutoJoinOrganizationError extends Error {
Expand Down
7 changes: 2 additions & 5 deletions src/controllers/api.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import type { NextFunction, Request, Response } from "express";
import HttpErrors from "http-errors";
import { z, ZodError } from "zod";
import {
InseeConnectionError,
InseeNotFoundError,
NotFoundError,
} from "../config/errors";
import { InseeConnectionError, InseeNotFoundError } from "../config/errors";
import notificationMessages from "../config/notification-messages";
import { getOrganizationInfo } from "../connectors/api-sirene";
import { sendModerationProcessedEmail } from "../managers/moderation";
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/organization.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getEmailDomain } from "@gouvfr-lasuite/proconnect.core/services/email";
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import type { NextFunction, Request, Response } from "express";
import HttpErrors from "http-errors";
import { isEmpty } from "lodash-es";
Expand All @@ -7,7 +8,6 @@ import {
InseeConnectionError,
InseeNotActiveError,
InvalidSiretError,
NotFoundError,
UnableToAutoJoinOrganizationError,
UserAlreadyAskedToJoinOrganizationError,
UserInOrganizationAlreadyError,
Expand Down
3 changes: 2 additions & 1 deletion src/controllers/totp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import type { NextFunction, Request, Response } from "express";
import { z } from "zod";
import { InvalidTotpTokenError, NotFoundError } from "../config/errors";
import { InvalidTotpTokenError } from "../config/errors";
import {
addAuthenticationMethodReferenceInSession,
getUserFromAuthenticatedSession,
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/user/edit-moderation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import type { NextFunction, Request, Response } from "express";
import { z } from "zod";
import { NotFoundError } from "../../config/errors";
import { cancelModeration } from "../../managers/moderation";
import { getUserFromAuthenticatedSession } from "../../managers/session/authenticated";
import { idSchema } from "../../services/custom-zod-schemas";
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/webauthn.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import type {
AuthenticationResponseJSON,
RegistrationResponseJSON,
Expand All @@ -6,7 +7,6 @@ import type { NextFunction, Request, Response } from "express";
import HttpErrors from "http-errors";
import { z, ZodError } from "zod";
import {
NotFoundError,
UserNotLoggedInError,
WebauthnAuthenticationFailedError,
WebauthnRegistrationFailedError,
Expand Down
3 changes: 2 additions & 1 deletion src/managers/moderation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ModerationProcessed } from "@gouvfr-lasuite/proconnect.email";
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import type { User } from "@gouvfr-lasuite/proconnect.identite/types";
import { isEmpty } from "lodash-es";
import { HOST } from "../config/env";
import { ForbiddenError, NotFoundError } from "../config/errors";
import { ForbiddenError } from "../config/errors";
import { sendMail } from "../connectors/mail";
import {
deleteModeration,
Expand Down
2 changes: 1 addition & 1 deletion src/managers/oidc-client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import * as Sentry from "@sentry/node";
import { isEmpty, isString } from "lodash-es";
import type { KoaContextWithOIDC } from "oidc-provider";
import { NotFoundError } from "../config/errors";
import { addConnection, findByClientId } from "../repositories/oidc-client";
import { getSelectedOrganizationId } from "../repositories/redis/selected-organization";
import { logger } from "../services/log";
Expand Down
49 changes: 8 additions & 41 deletions src/managers/organization/join.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { isEmailValid } from "@gouvfr-lasuite/proconnect.core/security";
import { getEmailDomain } from "@gouvfr-lasuite/proconnect.core/services/email";
import { Welcome } from "@gouvfr-lasuite/proconnect.email";
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import { forceJoinOrganizationFactory } from "@gouvfr-lasuite/proconnect.identite/managers/organization";
import type {
BaseUserOrganizationLink,
Organization,
OrganizationInfo,
UserOrganizationLink,
Expand All @@ -19,7 +20,6 @@ import {
InseeConnectionError,
InseeNotActiveError,
InvalidSiretError,
NotFoundError,
UnableToAutoJoinOrganizationError,
UserAlreadyAskedToJoinOrganizationError,
UserInOrganizationAlreadyError,
Expand Down Expand Up @@ -361,46 +361,13 @@ export const joinOrganization = async ({

throw new UnableToAutoJoinOrganizationError(moderation_id);
};
export const forceJoinOrganization = async ({
organization_id,
user_id,
is_external = false,
}: {
organization_id: number;
user_id: number;
is_external?: boolean;
}) => {
const user = await findUserById(user_id);
const organization = await findById(organization_id);
if (isEmpty(user) || isEmpty(organization)) {
throw new NotFoundError();
}
const { email } = user;
const domain = getEmailDomain(email);
const organizationEmailDomains =
await findEmailDomainsByOrganizationId(organization_id);

let link_verification_type: BaseUserOrganizationLink["verification_type"];
if (
some(organizationEmailDomains, { domain, verification_type: "verified" }) ||
some(organizationEmailDomains, {
domain,
verification_type: "trackdechets_postal_mail",
}) ||
some(organizationEmailDomains, { domain, verification_type: "external" })
) {
link_verification_type = "domain";
} else {
link_verification_type = "no_validation_means_available";
}

return await linkUserToOrganization({
organization_id,
user_id,
is_external,
verification_type: link_verification_type,
});
};
export const forceJoinOrganization = forceJoinOrganizationFactory({
findById,
findEmailDomainsByOrganizationId,
findUserById,
linkUserToOrganization,
});

export const greetForJoiningOrganization = async ({
user_id,
Expand Down
2 changes: 1 addition & 1 deletion src/managers/organization/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import { markDomainAsVerifiedFactory } from "@gouvfr-lasuite/proconnect.identite/managers/organization";
import type { Organization } from "@gouvfr-lasuite/proconnect.identite/types";
import { isEmpty } from "lodash-es";
import { NotFoundError } from "../../config/errors";
import {
addDomain,
findEmailDomainsByOrganizationId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { generateDicewarePassword } from "@gouvfr-lasuite/proconnect.core/security";
import { OfficialContactEmailVerification } from "@gouvfr-lasuite/proconnect.email";
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import type { UserOrganizationLink } from "@gouvfr-lasuite/proconnect.identite/types";
import { isEmpty } from "lodash-es";
import { HOST } from "../../config/env";
import {
ApiAnnuaireError,
InvalidTokenError,
NotFoundError,
OfficialContactEmailVerificationNotNeededError,
} from "../../config/errors";
import { getAnnuaireEducationNationaleContactEmail } from "../../connectors/api-annuaire-education-nationale";
Expand Down
2 changes: 1 addition & 1 deletion src/managers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
UpdatePersonalDataMail,
VerifyEmail,
} from "@gouvfr-lasuite/proconnect.email";
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import type { User } from "@gouvfr-lasuite/proconnect.identite/types";
import { isEmpty } from "lodash-es";
import {
Expand All @@ -35,7 +36,6 @@ import {
InvalidTokenError,
LeakedPasswordError,
NoNeedVerifyEmailAddressError,
NotFoundError,
UserNotFoundError,
WeakPasswordError,
} from "../config/errors";
Expand Down
2 changes: 1 addition & 1 deletion src/managers/webauthn.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NotFoundError } from "@gouvfr-lasuite/proconnect.identite/errors";
import {
generateAuthenticationOptions,
generateRegistrationOptions,
Expand All @@ -15,7 +16,6 @@ import moment from "moment";
import "moment-timezone";
import { APPLICATION_NAME, HOST, WEBSITE_IDENTIFIER } from "../config/env";
import {
NotFoundError,
UserNotFoundError,
WebauthnAuthenticationFailedError,
WebauthnRegistrationFailedError,
Expand Down

0 comments on commit ecfccb2

Please sign in to comment.