Skip to content

Commit

Permalink
Merge pull request #7 from DISIC/feat/domains-white-list
Browse files Browse the repository at this point in the history
feat: domains white list
  • Loading branch information
ClementNumericite authored Sep 14, 2023
2 parents 8899149 + 56e4ba1 commit 8926325
Show file tree
Hide file tree
Showing 10 changed files with 6,721 additions and 38 deletions.
38 changes: 38 additions & 0 deletions components/auth/RegisterConfirmMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { fr } from '@codegouvfr/react-dsfr';
import Link from 'next/link';

export type RegisterValidationMessage = 'classic' | 'from_otp' | undefined;

type Props = {
mode: RegisterValidationMessage;
};

export const RegisterValidationMessage = (props: Props) => {
const { mode } = props;

if (!mode) return;

return (
<div>
<h5>Se créer un compte</h5>
{mode === 'classic' ? (
<p>
Votre compte a été créé avec succès. <br />
<br />
Vous allez recevoir un email contenant un lien de validation dans les
prochaines minutes.
</p>
) : (
<p>
Votre ancien compte de l&apos;observatoire a été configuré avec
succès. <br />
<br />
<Link className={fr.cx('fr-link')} href="/login">
Connectez-vous dès maintenant
</Link>{' '}
pour gérer vos boutons Je donne mon avis dans ce nouvel espace.
</p>
)}
</div>
);
};
47 changes: 19 additions & 28 deletions components/auth/RegisterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import Link from 'next/link';
import { useRouter } from 'next/router';
import { ReactNode, useEffect, useState } from 'react';
import { tss } from 'tss-react/dsfr';
import { RegisterValidationMessage } from './RegisterConfirmMessage';
import { RegisterNotWhiteListed } from './RegisterNotWhiteListed';

type Props = {
userPresetInfos?: UserInfos;
otp_id?: string;
};

type UserInfos = {
export type UserInfos = {
firstName?: string;
lastName?: string;
email?: string;
Expand All @@ -27,7 +29,11 @@ type UserInfos = {
type FormErrors = {
firstName: { required: boolean };
lastName: { required: boolean };
email: { required: boolean; format: boolean; conflict: boolean };
email: {
required: boolean;
format: boolean;
conflict: boolean;
};
password: { required: boolean; format: boolean };
};

Expand Down Expand Up @@ -63,10 +69,9 @@ export const RegisterForm = (props: Props) => {
};

const [userInfos, setUserInfos] = useState<UserInfos>({});
const [userNotWhiteListed, setUserNotWhiteListed] = useState<boolean>(false);
const [errors, setErrors] = useState<FormErrors>({ ...defaultErrors });
const [registered, setRegistered] = useState<
'classic' | 'from_otp' | undefined
>();
const [registered, setRegistered] = useState<RegisterValidationMessage>();

const { classes, cx } = useStyles({ errors });

Expand Down Expand Up @@ -183,6 +188,10 @@ export const RegisterForm = (props: Props) => {
else if (res.status === 409) {
errors.email.conflict = true;
setErrors({ ...errors });
} else if (res.status === 401) {
router.query.request = 'whitelist';
router.push(router);
setUserNotWhiteListed(true);
}
});
};
Expand All @@ -197,30 +206,12 @@ export const RegisterForm = (props: Props) => {
setUserInfos(userPresetInfos);
}, [userPresetInfos]);

if (userNotWhiteListed) {
return <RegisterNotWhiteListed userInfos={userInfos} />;
}

if (registered) {
return (
<div>
<h5>Se créer un compte</h5>
{registered === 'classic' ? (
<p>
Votre compte a été créé avec succès. <br />
<br />
Vous allez recevoir un email contenant un lien de validation dans
les prochaines minutes.
</p>
) : (
<p>
Votre ancien compte de l&apos;observatoire a été configuré avec
succès. <br />
<br />
<Link className={fr.cx('fr-link')} href="/login">
Connectez-vous dès maintenant
</Link>{' '}
pour gérer vos boutons Je donne mon avis dans ce nouvel espace.
</p>
)}
</div>
);
return <RegisterValidationMessage mode={registered} />;
}

return (
Expand Down
61 changes: 61 additions & 0 deletions components/auth/RegisterNotWhiteListed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useState } from 'react';
import { UserInfos } from './RegisterForm';
import { tss } from 'tss-react/dsfr';
import { Input } from '@codegouvfr/react-dsfr/Input';
import { Button } from '@codegouvfr/react-dsfr/Button';

type Props = {
userInfos: UserInfos;
};

export const RegisterNotWhiteListed = (props: Props) => {
const { userInfos } = props;

const { classes, cx } = useStyles();

const [reason, setReason] = useState<string | undefined>();

const validateRequest = () => {
// create user & user request
};

return (
<div>
<h5>Demande de création de compte</h5>
<form
onSubmit={e => {
e.preventDefault();
}}
>
<Input
label="Votre situation"
textArea
nativeTextAreaProps={{
onChange: e => {
setReason(e.target.value);
},
value: reason,
name: 'reason'
}}
state={reason === '' ? 'error' : 'default'}
stateRelatedMessage={
"L'explication de votre situation est obligatoire."
}
/>
<Button className={cx(classes.button)} type="submit">
Valider
</Button>
</form>
</div>
);
};

const useStyles = tss
.withName(RegisterNotWhiteListed.name)
.withParams()
.create(() => ({
button: {
display: 'block',
marginLeft: 'auto'
}
}));
21 changes: 20 additions & 1 deletion pages/api/auth/register.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import { sendMail } from '@/utils/mailer';
import { generateRandomString, getRegisterEmailHtml } from '@/utils/tools';
import {
extractDomainFromEmail,
generateRandomString,
getRegisterEmailHtml
} from '@/utils/tools';
import { PrismaClient, User } from '@prisma/client';
import { NextApiRequest, NextApiResponse } from 'next';
import crypto from 'crypto';

const prisma = new PrismaClient();

export async function checkUserDomain(email: string) {
const domain = extractDomainFromEmail(email);
if (!domain) return false;

const domainWhiteListed = await prisma.whiteListedDomain.findFirst({
where: { domain }
});
return !!domainWhiteListed;
}

export async function userExists(email: string) {
const tmpUser = await prisma.user.findFirst({ where: { email } });
return !!tmpUser;
Expand Down Expand Up @@ -95,6 +109,11 @@ export default async function handler(

if (hasConflict) return res.status(409).send('User already exists');

const isWhiteListed = await checkUserDomain(email);

if (!isWhiteListed)
return res.status(401).send('User email domain not whitelisted');

const user = await registerUser({
firstName,
lastName,
Expand Down
22 changes: 20 additions & 2 deletions pages/register/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type UserPresetInfos = {
export default function Register() {
const router = useRouter();

const { otp_id, registered } = router.query;
const { otp_id, registered, request } = router.query;

const { classes, cx } = useStyles();

Expand Down Expand Up @@ -46,7 +46,7 @@ export default function Register() {
<div className={fr.cx('fr-grid-row', 'fr-grid-row--center')}>
<div className={fr.cx('fr-col-12', 'fr-col-md-6')}>
<h2 className={fr.cx('fr-mb-12v')}>Création de compte</h2>
{!registered && !otp_id && (
{!registered && !otp_id && !request && (
<Alert
className={fr.cx('fr-mb-16v')}
closable
Expand All @@ -69,6 +69,24 @@ export default function Register() {
title="Nouvel hébergement"
/>
)}
{!!request && (
<Alert
className={fr.cx('fr-mb-16v')}
closable
description={
<>
L’outil JDMA est réservé aux établissements publics. Votre
adresse e-mail n’est pas reconnue comme une adresse
d’administration publique. Si vous pensez qu’il s’agit d’une
erreur, merci de nous décrire votre situation. Nous
reviendrons vers vous sous 48 heures.
</>
}
onClose={function noRefCheck() {}}
severity="info"
title=""
/>
)}
<div
className={cx(
classes.formContainer,
Expand Down
19 changes: 19 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ generator client {
provider = "prisma-client-js"
}

enum RequestMode {
whitelist
superuser
}

// Next auth
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
Expand All @@ -18,6 +23,15 @@ model User {
password String
UserOTPs UserOTP[]
UserValidationTokens UserValidationToken[]
UserRequests UserRequest[]
}

model UserRequest {
id String @id @default(auto()) @map("_id") @db.ObjectId
user User @relation(fields: [user_id], references: [id])
user_id String @db.ObjectId
reason String
mode RequestMode
}

model UserOTP {
Expand All @@ -34,3 +48,8 @@ model UserValidationToken {
user_id String @db.ObjectId
token String @unique
}

model WhiteListedDomain {
id String @id @default(auto()) @map("_id") @db.ObjectId
domain String @unique
}
31 changes: 24 additions & 7 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { PrismaClient, User } from '@prisma/client';
import { PrismaClient, User, WhiteListedDomain } from '@prisma/client';
import { users } from './seeds/users';
import { whiteListedDomains } from './seeds/white-listed-domains';

const prisma = new PrismaClient();

async function main() {
const promises: Promise<User>[] = [];
const promisesUsers: Promise<User>[] = [];
const promisesWLDs: Promise<WhiteListedDomain>[] = [];

users.forEach(user => {
promises.push(
promisesUsers.push(
prisma.user.upsert({
where: {
email: user.email
Expand All @@ -20,10 +22,25 @@ async function main() {
);
});

Promise.all(promises).then(responses => {
let log: { [key: string]: User } = {};
responses.forEach(r => {
if ('email' in r) log[`user ${r.email}`] = r;
whiteListedDomains.forEach(wld => {
promisesWLDs.push(
prisma.whiteListedDomain.upsert({
where: {
domain: wld.domain
},
update: {},
create: {
...wld
}
})
);
});

Promise.all([...promisesUsers, ...promisesWLDs]).then(responses => {
let log: { [key: string]: string } = {};
responses.forEach((r, i) => {
if ('email' in r) log[`${i}] user added`] = r.email;
if ('domain' in r) log[`${i}] domain added : `] = r.domain;
});
console.log(log);
});
Expand Down
Loading

0 comments on commit 8926325

Please sign in to comment.