Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: securisation du flow de dépot rapide d'offre #763

Merged
merged 21 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f43fc9b
fix: securisation de getFormulaire
remy-auricoste Oct 26, 2023
750d4e1
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Oct 26, 2023
b6cf6f3
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Oct 26, 2023
7aea6ad
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Oct 30, 2023
56963ff
fix: securisation du depot simplifié
remy-auricoste Oct 30, 2023
5a91104
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Oct 30, 2023
c91ea16
fix: onRequests
remy-auricoste Oct 30, 2023
5a9b14d
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Oct 30, 2023
729bc2f
fix: securisation du lien de renvoi de mail
remy-auricoste Oct 30, 2023
2661646
fix: renommage de la route en /login/:userId/resend-confirmation-email
remy-auricoste Oct 30, 2023
a2ee533
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Oct 30, 2023
dca2ab9
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Oct 31, 2023
f6a49db
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Oct 31, 2023
7a598f3
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Oct 31, 2023
33344b6
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Nov 2, 2023
ab766c1
fix: affichage backoffice
remy-auricoste Nov 2, 2023
0d805c7
fix: remplacement du retry par un enabled
remy-auricoste Nov 2, 2023
c483176
fix: suppression Promise.reject
remy-auricoste Nov 2, 2023
19af611
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Nov 2, 2023
1edcace
feat: update seed with fiche metier
kevbarns Nov 2, 2023
7dabf8d
Merge branch 'main' into securisation-get-formulaire
remy-auricoste Nov 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .infra/files/configs/mongodb/seed.gpg
Git LFS file not shown
2 changes: 1 addition & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fileignoreconfig:
- filename: .infra/files/configs/mailpit/auth
checksum: 0c02db6c367747bfd696e8c93ed78c456fe311452e7a4ee1d7d5acc88f3b2189
- filename: .infra/files/configs/mongodb/seed.gpg
checksum: 3cb836a5c356c64ac6634c8aeb03b9737950a66ed61d9ae8f6b1528ad7b17434
checksum: e09bc952da0dbeb33331dc317980e6537dc4d37674ca08748246d4d093b82294
- filename: .infra/vault/vault.yml
checksum: 1a62fbc5e3e877b5241d59c61fda05a61b6c8bb4fb7f5662f2f1bc44c288782b
- filename: server/.env.test
Expand Down
37 changes: 13 additions & 24 deletions server/src/http/routes/auth/login.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,23 @@ import { Server } from "../../server"

export default (server: Server) => {
server.post(
"/login/confirmation-email",
"/login/:userId/resend-confirmation-email",
{
schema: zRoutes.post["/login/confirmation-email"],
preHandler: [],
schema: zRoutes.post["/login/:userId/resend-confirmation-email"],
onRequest: server.auth(zRoutes.post["/login/:userId/resend-confirmation-email"]),
},
async (req, res) => {
try {
const { email } = req.body
const formatedEmail = email.toLowerCase()
const user = await getUser({ email: formatedEmail })

if (!user) {
return res.status(400).send({ error: true, reason: "UNKNOWN" })
}

const { is_email_checked } = user

if (is_email_checked) {
return res.status(400).send({ error: true, reason: "VERIFIED" })
}
await sendUserConfirmationEmail(user)
return res.status(200).send({})
} catch (error) {
return res.status(400).send({
errorMessage: "l'adresse mail n'est pas valide.",
details: error,
})
const { userId } = req.params
const user = await getUser({ _id: userId })
if (!user) {
return res.status(400).send({ error: true, reason: "UNKNOWN" })
}
const { is_email_checked } = user
if (is_email_checked) {
return res.status(400).send({ error: true, reason: "VERIFIED" })
}
await sendUserConfirmationEmail(user)
return res.status(200).send({})
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export default (server: Server) => {
if (result.errorCode === BusinessErrorCodes.ALREADY_EXISTS) throw Boom.forbidden(result.message)
else throw Boom.badRequest(result.message)
}
await startSession(req.body.email, res)
return res.status(200).send(result)
}
case CFA: {
Expand Down
4 changes: 3 additions & 1 deletion server/src/http/routes/formulaire.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default (server: Server) => {
"/formulaire/:establishment_id",
{
schema: zRoutes.get["/formulaire/:establishment_id"],
onRequest: [server.auth(zRoutes.get["/formulaire/:establishment_id"])],
},
async (req, res) => {
const result = await getFormulaire({ establishment_id: req.params.establishment_id })
Expand Down Expand Up @@ -149,7 +150,7 @@ export default (server: Server) => {
"/formulaire/:establishment_id/offre",
{
schema: zRoutes.post["/formulaire/:establishment_id/offre"],
// preHandler: [server.auth(zRoutes.post["/formulaire/:establishment_id/offre"])],
onRequest: [server.auth(zRoutes.post["/formulaire/:establishment_id/offre"])],
bodyLimit: 5 * 1024 ** 2, // 5MB
},
async (req, res) => {
Expand Down Expand Up @@ -195,6 +196,7 @@ export default (server: Server) => {
"/formulaire/offre/:jobId/delegation",
{
schema: zRoutes.post["/formulaire/offre/:jobId/delegation"],
onRequest: [server.auth(zRoutes.post["/formulaire/offre/:jobId/delegation"])],
},
async (req, res) => {
const { etablissementCatalogueIds } = req.body
Expand Down
7 changes: 5 additions & 2 deletions server/src/http/routes/user.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Boom from "boom"
import { IJob, getUserStatus, zRoutes } from "shared/index"

import { stopSession } from "@/common/utils/session.service"

import { Recruiter, UserRecruteur } from "../../common/model/index"
import { getStaticFilePath } from "../../common/utils/getStaticFilePath"
import config from "../../config"
Expand Down Expand Up @@ -170,6 +172,7 @@ export default (server: Server) => {
"/user/status/:userId",
{
schema: zRoutes.get["/user/status/:userId"],
onRequest: [server.auth(zRoutes.get["/user/status/:userId"])],
},
async (req, res) => {
const user = await UserRecruteur.findOne({ _id: req.params.userId }).lean()
Expand Down Expand Up @@ -282,7 +285,7 @@ export default (server: Server) => {
"/user",
{
schema: zRoutes.delete["/user"],
preHandler: [],
onRequest: [server.auth(zRoutes.delete["/user"])],
},
async (req, res) => {
const { userId, recruiterId } = req.query
Expand All @@ -292,7 +295,7 @@ export default (server: Server) => {
if (recruiterId) {
await deleteFormulaire(recruiterId)
}

await stopSession(req, res)
return res.status(200).send({})
}
)
Expand Down
8 changes: 5 additions & 3 deletions server/src/security/authenticationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ async function authCookieSession(req: FastifyRequest): Promise<UserWithType<"IUs

const { email } = jwt.verify(token, config.auth.user.jwtSecret) as JwtPayload

const user = await getUserRecruteur({ email })

return user ? { type: "IUserRecruteur", value: user } : null
const user = await getUserRecruteur({ email: email.toLowerCase() })
remy-auricoste marked this conversation as resolved.
Show resolved Hide resolved
if (!user) {
return null
}
return { type: "IUserRecruteur", value: user }
} catch (error) {
captureException(error)
return null
Expand Down
8 changes: 8 additions & 0 deletions shared/models/job.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,11 @@ export type IDelegation = z.output<typeof ZDelegation>
export type IJob = z.output<typeof ZJob>
export type IJobWritable = z.output<typeof ZJobWrite>
export type IJobJson = Jsonify<z.input<typeof ZJob>>

export const ZNewDelegations = z
.object({
etablissementCatalogueIds: z.array(z.string()),
})
.strict()

export type INewDelegations = z.input<typeof ZNewDelegations>
6 changes: 2 additions & 4 deletions shared/models/usersRecruteur.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ import { ZGlobalAddress } from "./address.model"
import { zObjectId } from "./common"

const etatUtilisateurValues = Object.values(ETAT_UTILISATEUR)
export const ZEtatUtilisateur = z.enum([etatUtilisateurValues[0], ...etatUtilisateurValues.slice(1)]).describe("Statut de l'utilisateur")

export const ZUserStatusValidation = z
.object({
validation_type: z.enum(["AUTOMATIQUE", "MANUELLE"]).describe("Processus de validation lors de l'inscription de l'utilisateur"),
status: z
.enum([etatUtilisateurValues[0], ...etatUtilisateurValues.slice(1)])
.nullish()
.describe("Statut de l'utilisateur"),
status: ZEtatUtilisateur.nullish(),
reason: z.string().nullish().describe("Raison du changement de statut"),
user: z.string().describe("Utilisateur ayant effectué la modification | SERVEUR si le compte a été validé automatiquement"),
date: z.date().nullish().describe("Date de l'évènement"),
Expand Down
31 changes: 22 additions & 9 deletions shared/routes/formulaire.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ export const zFormulaireRoute = {
"/formulaire/:establishment_id": {
method: "get",
path: "/formulaire/:establishment_id",
// TODO_SECURITY_FIX gestion des permissions
// TODO_SECURITY_FIX session gérée par cookie server
params: z.object({ establishment_id: z.string() }).strict(),
response: {
// TODO ANY TO BE FIXED
"200": z.any(),
// "2xx": ZRecruiter,
},
securityScheme: null,
securityScheme: {
auth: "cookie-session",
access: "recruiter:manage",
ressources: {
recruiter: [{ establishment_id: { type: "params", key: "establishment_id" } }],
},
},
},
"/formulaire/offre/f/:jobId": {
method: "get",
Expand Down Expand Up @@ -63,7 +67,6 @@ export const zFormulaireRoute = {
"/formulaire/:establishment_id/offre": {
method: "post",
path: "/formulaire/:establishment_id/offre",
// TODO_SECURITY_FIX gestion des permissions
// TODO_SECURITY_FIX limiter les champs autorisés à la modification. Utiliser un "ZRecruiterNew" (ou un autre nom du genre ZFormulaire)
params: z.object({ establishment_id: z.string() }).strict(),
// TODO nonstrict TO BE FIXED on the frontend
Expand All @@ -73,13 +76,17 @@ export const zFormulaireRoute = {
// "2xx": ZRecruiter,
"200": z.any(),
},
securityScheme: null,
securityScheme: {
auth: "cookie-session",
access: "recruiter:add_job",
ressources: {
recruiter: [{ establishment_id: { type: "params", key: "establishment_id" } }],
},
},
},
"/formulaire/offre/:jobId/delegation": {
method: "post",
path: "/formulaire/offre/:jobId/delegation",
// TODO_SECURITY_FIX gestion des permissions
// TODO_SECURITY_FIX session gérée par cookie server
params: z.object({ jobId: zObjectId }).strict(),
body: z
.object({
Expand All @@ -88,10 +95,16 @@ export const zFormulaireRoute = {
.strict(),
response: {
// TODO ANY TO BE FIXED
"2xx": z.any(),
"200": z.any(),
// "2xx": ZRecruiter,
},
securityScheme: null,
securityScheme: {
auth: "cookie-session",
access: "job:manage",
ressources: {
job: [{ _id: { type: "params", key: "jobId" } }],
},
},
},
},
put: {
Expand Down
10 changes: 9 additions & 1 deletion shared/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,27 @@ const zRoutesGet: typeof zRoutesGetP1 & typeof zRoutesGetP2 & typeof zRoutesGetP
...zRoutesGetP3,
} as const

const zRoutesPost = {
const zRoutesPost1 = {
...zApplicationRoutes.post,
...zLoginRoutes.post,
...zTrainingLinksRoutes.post,
...zUnsubscribeRoute.post,
...zUserRecruteurRoutes.post,
...zV1JobsRoutes.post,
} as const

const zRoutesPost2 = {
...zFormulaireRoute.post,
...zRecruiterRoutes.post,
...zCampaignWebhookRoutes.post,
...zEtablissementRoutes.post,
...zAppointmentsRoute.post,
...zEmailsRoutes.post,
}

const zRoutesPost = {
...zRoutesPost1,
...zRoutesPost2,
} as const

const zRoutesPut = {
Expand Down
18 changes: 12 additions & 6 deletions shared/routes/login.routes.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { z } from "../helpers/zodWithOpenApi"
import { ZUserRecruteurPublic } from "../models"
import { zObjectId } from "../models/common"

import { IRoutesDef } from "./common.routes"

export const zLoginRoutes = {
post: {
"/login/confirmation-email": {
"/login/:userId/resend-confirmation-email": {
method: "post",
path: "/login/confirmation-email",
// TODO_SECURITY_FIX faire en sorte que le lien magique ne soit pas human readable. Rename en /resend-confirmation-email
body: z
path: "/login/:userId/resend-confirmation-email",
params: z
.object({
email: z.string().email(),
userId: zObjectId,
})
.strict(),
response: {
"200": z.object({}).strict(),
},
securityScheme: null,
securityScheme: {
auth: "cookie-session",
access: "user:manage",
ressources: {
user: [{ _id: { key: "userId", type: "params" } }],
},
},
},
"/login/magiclink": {
method: "post",
Expand Down
2 changes: 1 addition & 1 deletion shared/routes/recruiters.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export const zRecruiterRoutes = {
]),
response: {
// TODO ANY TO BE FIXED
"2xx": z.any(),
"200": z.any(),
// "2xx": z.union([
// z
// .object({
Expand Down
36 changes: 29 additions & 7 deletions shared/routes/user.routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "../helpers/zodWithOpenApi"
import { zObjectId } from "../models/common"
import { ZUserRecruteur, ZUserRecruteurWritable, ZUserStatusValidation } from "../models/usersRecruteur.model"
import { ZEtatUtilisateur, ZUserRecruteur, ZUserRecruteurWritable, ZUserStatusValidation } from "../models/usersRecruteur.model"

import { IRoutesDef, ZResError } from "./common.routes"

Expand Down Expand Up @@ -115,10 +115,19 @@ export const zUserRecruteurRoutes = {
})
.strict(),
response: {
// TODO ANY TO BE FIXED
"200": z.any(),
"200": z
.object({
status_current: ZEtatUtilisateur,
})
.strict(),
},
securityScheme: {
auth: "cookie-session",
access: "user:manage",
ressources: {
user: [{ _id: { type: "params", key: "userId" } }],
},
},
securityScheme: null,
},
},
post: {
Expand Down Expand Up @@ -204,8 +213,6 @@ export const zUserRecruteurRoutes = {
"/user": {
method: "delete",
path: "/user",
// TODO_SECURITY_FIX session et cookie + permissions
// TODO return json format
querystring: z
.object({
userId: zObjectId,
Expand All @@ -215,7 +222,22 @@ export const zUserRecruteurRoutes = {
response: {
"200": z.object({}).strict(),
},
securityScheme: null,
securityScheme: {
auth: "cookie-session",
access: "recruiter:manage",
ressources: {
user: [
{
_id: { type: "query", key: "userId" },
},
],
recruiter: [
{
_id: { type: "query", key: "recruiterId" },
},
],
},
},
},
"/admin/users/:userId": {
method: "delete",
Expand Down
Loading