Skip to content

Commit

Permalink
Ajoute les comptes utilisateurs (#396)
Browse files Browse the repository at this point in the history
* add endpoint to create an verify new account

* add endpoint to signin

* add swagger doc

* use mjml template

* only allow one valid verification link at a time

* add route to resend verification link

* move @types/bcrypt dependency to devDependencies

* add new account page

* add login page

* add user dropdown

* store auth token

* check if token is expired

* display a message on the original tab when account is verified

* fix header dropdown

* show error messages when email is already used or email has wrong format

* fix conflict error on unverified users

* fix focus displacement in create account step 2

* dont show generic error notification on 409

* Account creation emails (#397)

* design account verification email template

* design account creation confirmation email template

* send confirmation email when account is verified

* get user email directly from token

* identify user using a unique id instead of their email (#399)

* check error status code on token verification and decrease verification pooling rate

* use DsfrField

* add invalid verification token message

* improve semantic, remove unused tabindex

* fix: focus invalid field on NewAccountForm

* handle login errors on login form

* Feat/authenticate user (#427)

* replace jsonwebtoken with @nestjs/jwt

* add profile module and AuthRequired decorator

* Modifier le profil utilisateur (#433)

* add account settings page and profile section

* add name and orgname to user model + start patch account route

* move things to profile namespace

* fetch user on user account page load

* hide display settings in menu

* hide future sections separators

* backend feedbacks

* Suppression du compte (#438)

* add route to delete account

* add account deletion form and feedback page

* generate feedback token

* save feedback to airtable

* plug api to delete account

* plug api to send feedback

* remove user email and name from audits

* Update Account.vue

---------

Co-authored-by: Adrien Boutigny <[email protected]>

* Lister les audits (#462)

* add missing audit page and alert

* add empty state when no audit

* add elements inside audit row

* add sub actions dropdown

* fix dropdown component items with and layout

* to reset: trying to fix zIndex issue between dropdowns

* plug copy links actions

* add audit copy action

* add audit deletion action

* fake fetch audits in account store

* add fake data in store and plug everything else

* add row doc + a11y text

* hide compliance level if audit type is not full

* add subtle transitions

* add GET /api/audits route to fetch audit listing

* plug app store with api

* change audit store to store multiple audits

* move audit listing to auditStore

---------

Co-authored-by: Adrien Boutigny <[email protected]>

* 383 changer de mot de passe (#447)

* add new account page

* add update password section on settings page

* add confirmation email template

* handle form errors

* plug password update to store

* add route to update password

* send password update confirmation email

* fix typo in emails

* fix forgotten conflicts

---------

Co-authored-by: Adrien Boutigny <[email protected]>

* Changer l'adresse email (#441)

* add user dropdown

* fix header dropdown

* design email section

* create email update email template

* setup error messages

* add todo

* update email confirmation title

* add api requests to front

* send new email verification email

* send token to API to verify new email

* update token and redirect to account settings

* disconnected user success message

* resend confirmation email

* wait for update confirmation

* save token to storage on refresh

* document new api methods

* add email update confirmation

* fix rebase artifact

* pr feedback

* fix hardcoded new email

* use abort controller

---------

Co-authored-by: Adrien Boutigny <[email protected]>

* Réinitialiser le mot de passe (#450)

* add reset password 2-step form

* add email templates

* add password form + refacto in sub components

* uncomment link to password reset page

* restructure password reset steps

* add store method

* add request password reset route

* reset password

* resend email

* dont send email for unknown email address

---------

Co-authored-by: Adrien Boutigny <[email protected]>

* remove unused log

* fix stikcy indicator ts error

* do not add request payloads to authentication related sentry reports

* redirect user when disconnected

* Renseigne les meta des pages de compte (#495)

* complete meta on account pages

* remove log

* remove/add copy report link button (#509)

* Valide le formulaire de création de compte avant la soumission (#511)

* validator new account form before submit

* add source for email regex

* remove unused log

* Retours test e-mails (#517)

* force email links color

* harmonize spacing between headings and paragraphs

* remove useless paragraph in account confirmation email template

* reset toggle password state when changing step (#518)

* Retours test mise à jour du profil (#513)

* update text sizes

* add alert close button when modifying email

* rename show email in report toggle label

* delete account banner size and checkbox icon

* update wording

* close dropdown when changing route

* clear pwd update form and hide success alert on new attempt

* dont delete account if validation sentence is incorrect

* update airtable column label

* fix profile update buttons display condition

* remove duplicate ref after merging too fast

* always show alert + add close button inside (#522)

* Test 2 : connexion (#525)

* add validation errors on login form

* add validation errors on new password form

* fix dropdown not closing when opening another dropdown (#530)

* Test 2 : mise à jour du profil (#523)

* display same email error on updating email address

* display button to revert email update

* handle focus after error when updating email

* handle delete account field validation

* add validation errors on update email form

* add api route to cancel email update

* use token to get email in request

* trim + lowercase email before sending to backend (#531)

* include user profile in jwt to simplify profile reads and writes (#532)

* include user profile in jwt to simplify profile reads and writes

* remove unused code

* Correction des retours sur la liste des audits (#521)

* set home link to audit list when logged in

* update account deletion message

* adjust style on missing audit page

* close dropdown on button/link click

* add cancel button in copy toast

* style toast action

* redirect to audit list on audit deletion

* remove unused imports

* fix ts error

* Créer un composant `<DsfrPassword />` (#526)

* create component

* use component in new account form

* use component everywhere + fix ids

* Corrige le lien de navigation "Audit XXX" toujours actif (#538)

* refacto main navigation menu items

* clean files

* update complementary and fast audit compliance info in dashboard (#537)

* remove remember checkbox (#536)

* Annonce le changement de page aux techno d'assistance (#534)

* announce page title on page change

* empty container after 2sec

* Test 2 : liste des audits (#528)

* fix typos

* fix download link in audit list

* show the welcome alert at least once per account

* Retours: démarrer un nouvel audit (#533)

* do not display auditor email on report

* remove showAuditorEmail api prop

* prefill and hide auditor fields based to profile

* update user profile when they fill the name/org field on a new audit

* remove 'show email in report' checkbox from profile

* fix ts errors

* Wording email reset de mdp (#550)

* correct typo in reset password email template

* remove mailto link from email template footer

* fix hidden labels for audit actions (#551)

* Fix/expired password reset link (#560)

* show a 'link expired' text when the password reset link is expired

* update wording

* Add privacy details (#573)

---------

Co-authored-by: Quentin Bellanger <[email protected]>
Co-authored-by: Benoît Dequick <[email protected]>
  • Loading branch information
3 people authored Nov 30, 2023
1 parent ef59213 commit 0899eba
Show file tree
Hide file tree
Showing 122 changed files with 6,101 additions and 356 deletions.
7 changes: 4 additions & 3 deletions confiture-rest-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@
"@nestjs/common": "^9.4.0",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.4.0",
"@nestjs/jwt": "^10.0.3",
"@nestjs/platform-express": "^9.4.0",
"@nestjs/swagger": "^6.3.0",
"@prisma/client": "^4.1.1",
"@types/lodash": "^4.14.194",
"@vegardit/prisma-generator-nestjs-dto": "^1.5.1",
"bcrypt": "^5.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"got": "^11.8.5",
"handlebars": "^4.7.7",
"joi": "^17.6.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"mjml": "^4.14.1",
"morgan": "^1.10.0",
"nanoid": "^3.3.4",
Expand All @@ -56,9 +56,10 @@
"@nestjs/cli": "^9.1.8",
"@nestjs/schematics": "^9.0.4",
"@nestjs/testing": "^9.2.1",
"@types/bcrypt": "^5.0.0",
"@types/express": "^4.17.13",
"@types/jest": "27.4.1",
"@types/lodash-es": "^4.17.7",
"@types/lodash": "^4.14.194",
"@types/mjml": "^4.7.1",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"username" TEXT NOT NULL,
"password" TEXT NOT NULL,
"isVerified" BOOLEAN NOT NULL DEFAULT false,

CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "EmailType" ADD VALUE 'ACCOUNT_VERIFICATION';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "verificationJti" TEXT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "EmailType" ADD VALUE 'ACCOUNT_CONFIRMATION';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
Warnings:
- A unique constraint covering the columns `[uid]` on the table `User` will be added. If there are existing duplicate values, this will fail.
- The required column `uid` was added to the `User` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
*/
-- AlterTable
ALTER TABLE "User" ADD COLUMN "uid" TEXT NOT NULL;

-- CreateIndex
CREATE UNIQUE INDEX "User_uid_key" ON "User"("uid");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "name" TEXT,
ADD COLUMN "orgName" TEXT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- CreateTable
CREATE TABLE "ActiveFeedbackToken" (
"uid" TEXT NOT NULL,

CONSTRAINT "ActiveFeedbackToken_pkey" PRIMARY KEY ("uid")
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Audit" ALTER COLUMN "auditorEmail" DROP NOT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "EmailType" ADD VALUE 'PASSWORD_UPDATE_CONFIRMATION';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "newEmail" TEXT,
ADD COLUMN "newEmailVerificationJti" TEXT;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "EmailType" ADD VALUE 'EMAIL_UPDATE_VERIFICATION';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "EmailType" ADD VALUE 'EMAIL_UPDATE_CONFIRMATION';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "EmailType" ADD VALUE 'PASSWORD_RESET_REQUEST';
35 changes: 34 additions & 1 deletion confiture-rest-api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ model Audit {
/// @DtoEntityHidden
pages AuditedPage[]
auditorName String?
auditorEmail String
auditorEmail String?
showAuditorEmailInReport Boolean @default(false)
auditorOrganisation String
Expand Down Expand Up @@ -183,6 +183,12 @@ enum EmailStatus {

enum EmailType {
AUDIT_CREATION
ACCOUNT_VERIFICATION
ACCOUNT_CONFIRMATION
PASSWORD_UPDATE_CONFIRMATION
EMAIL_UPDATE_VERIFICATION
EMAIL_UPDATE_CONFIRMATION
PASSWORD_RESET_REQUEST
}

model EmailLog {
Expand All @@ -195,3 +201,30 @@ model EmailLog {
createdAt DateTime @default(now())
lastAttempt DateTime @default(now())
}

model User {
id Int @id @default(autoincrement())
/// @DtoEntityHidden
uid String @unique @default(uuid())
username String @unique
/// @DtoEntityHidden
password String
/// @DtoEntityHidden
isVerified Boolean @default(false)
/// @DtoEntityHidden
verificationJti String?
name String?
orgName String?
/// @DtoEntityHidden
newEmail String?
/// @DtoEntityHidden
newEmailVerificationJti String?
}

model ActiveFeedbackToken {
uid String @id
}
4 changes: 4 additions & 0 deletions confiture-rest-api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { AuditsModule } from './audits/audits.module';
import { HealthCheckController } from './health-check.controller';
import { configValidationSchema } from './config-validation-schema';
import { MailModule } from './mail/mail.module';
import { AuthModule } from './auth/auth.module';
import { ProfileModule } from './profile/profile.module';

@Module({
imports: [
Expand All @@ -15,6 +17,8 @@ import { MailModule } from './mail/mail.module';
FeedbackModule,
AuditsModule,
MailModule,
AuthModule,
ProfileModule,
],
controllers: [HealthCheckController],
})
Expand Down
104 changes: 98 additions & 6 deletions confiture-rest-api/src/audits/audit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from '@prisma/client';
import { nanoid } from 'nanoid';
import sharp from 'sharp';
import { omit, setWith } from 'lodash';
import { omit, pick, setWith } from 'lodash';

import { PrismaService } from '../prisma.service';
import * as RGAA from '../rgaa.json';
Expand Down Expand Up @@ -57,7 +57,6 @@ export class AuditService {
auditType: data.auditType,

auditorEmail: data.auditorEmail,
showAuditorEmailInReport: data.showAuditorEmailInReport,
auditorName: data.auditorName,
auditorOrganisation: data.auditorOrganisation,

Expand Down Expand Up @@ -198,7 +197,6 @@ export class AuditService {
initiator: data.initiator,

auditorEmail: data.auditorEmail,
showAuditorEmailInReport: data.showAuditorEmailInReport,
auditorName: data.auditorName,
auditorOrganisation: data.auditorOrganisation,

Expand Down Expand Up @@ -766,9 +764,7 @@ export class AuditService {
// FIXME: some of the return data is never asked to the user
context: {
auditorName: audit.auditorName,
auditorEmail: audit.showAuditorEmailInReport
? audit.auditorEmail
: null,
auditorEmail: null,
desktopEnvironments: audit.environments
.filter((e) => e.platform === 'desktop')
.map((e) => ({
Expand Down Expand Up @@ -1112,4 +1108,100 @@ export class AuditService {

return newAudit;
}

async anonymiseAudits(userEmail: string) {
await this.prisma.audit.updateMany({
where: {
auditorEmail: userEmail,
},
data: {
auditorEmail: null,
auditorName: null,
showAuditorEmailInReport: false,
},
});
}

async getAuditsByAuditorEmail(email: string) {
const audits = await this.prisma.audit.findMany({
where: {
auditorEmail: email,
},
select: {
procedureName: true,
creationDate: true,
auditType: true,
editUniqueId: true,
consultUniqueId: true,
pages: {
select: {
results: true,
},
},
},
});

return audits.map((a) => {
const results = a.pages.flatMap((p) => p.results);

const progress =
results.filter((r) => r.status !== CriterionResultStatus.NOT_TESTED)
.length /
(CRITERIA_BY_AUDIT_TYPE[a.auditType].length * a.pages.length);

let complianceLevel = null;

if (progress >= 1) {
const resultsGroupedById = results.reduce<
Record<string, CriterionResult[]>
>((acc, c) => {
const key = `${c.topic}.${c.criterium}`;
if (acc[key]) {
acc[key].push(c);
} else {
acc[key] = [c];
}
return acc;
}, {});

const results2 = CRITERIA_BY_AUDIT_TYPE[a.auditType].map(
(c) => resultsGroupedById[`${c.topic}.${c.criterium}`] ?? null,
);

const applicableCriteria = results2.filter(
(criteria) =>
criteria &&
criteria.some(
(c) => c.status !== CriterionResultStatus.NOT_APPLICABLE,
),
);

const compliantCriteria = applicableCriteria.filter((criteria) =>
criteria.every(
(c) =>
c.status === CriterionResultStatus.COMPLIANT ||
c.status === CriterionResultStatus.NOT_APPLICABLE,
),
);

complianceLevel = Math.round(
(compliantCriteria.length / applicableCriteria.length) * 100,
);
}

return {
...pick(
a,
'procedureName',
'editUniqueId',
'consultUniqueId',
'creationDate',
'auditType',
),
complianceLevel,
status: progress < 1 ? 'IN_PROGRESS' : 'COMPLETED',
estimatedCsvSize: 502 + a.pages.length * 318,
};
});
}
}
15 changes: 13 additions & 2 deletions confiture-rest-api/src/audits/audits.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
Delete,
Get,
GoneException,
Header,
HttpStatus,
NotFoundException,
Param,
Expand Down Expand Up @@ -37,6 +36,9 @@ import { UpdateAuditDto } from './update-audit.dto';
import { PatchAuditDto } from './patch-audit.dto';
import { UpdateResultsDto } from './update-results.dto';
import { UploadImageDto } from './upload-image.dto';
import { AuthRequired } from 'src/auth/auth-required.decorator';
import { User } from 'src/auth/user.decorator';
import { AuthenticationJwtPayload } from 'src/auth/jwt-payloads';

@Controller('audits')
@ApiTags('Audits')
Expand Down Expand Up @@ -64,6 +66,15 @@ export class AuditsController {
return audit;
}

/**
* Retrieve list of audits to be displayed on user's dashboard.
*/
@Get()
@AuthRequired()
async getAuditList(@User() user: AuthenticationJwtPayload) {
return this.auditService.getAuditsByAuditorEmail(user.email);
}

/** Retrieve an audit from the database. */
@Get('/:uniqueId')
@ApiOkResponse({ description: 'The audit was found.', type: Audit })
Expand Down Expand Up @@ -269,7 +280,7 @@ export class AuditsController {
console.error(err);
});

return newAudit.editUniqueId;
return newAudit;
}

@Get('/:uniqueId/exports/csv')
Expand Down
1 change: 1 addition & 0 deletions confiture-rest-api/src/audits/audits.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ import { AuditExportService } from './audit-export.service';
},
}),
],
exports: [AuditService],
})
export class AuditsModule {}
7 changes: 0 additions & 7 deletions confiture-rest-api/src/audits/create-audit.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { AuditType } from '@prisma/client';
import { Type } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsEmail,
IsIn,
IsNumber,
Expand Down Expand Up @@ -64,12 +63,6 @@ export class CreateAuditDto {
@IsOptional()
auditorEmail?: string;

/**
* @example true
*/
@IsBoolean()
showAuditorEmailInReport: boolean;

/**
* @example "WEB AUDIT SARL"
*/
Expand Down
13 changes: 13 additions & 0 deletions confiture-rest-api/src/auth/auth-required.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { UseGuards, applyDecorators } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger';

export function AuthRequired() {
return applyDecorators(
ApiBearerAuth(),
ApiUnauthorizedResponse({
description: 'You must authenticate yourself using a bearer token.',
}),
UseGuards(AuthGuard),
);
}
Loading

0 comments on commit 0899eba

Please sign in to comment.