Skip to content

Commit

Permalink
chore: wip update
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinebigard committed Oct 17, 2024
1 parent a0f9699 commit bdfdf97
Show file tree
Hide file tree
Showing 149 changed files with 5,262 additions and 3,722 deletions.
37 changes: 28 additions & 9 deletions server/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
{
"extends": ["plugin:n/recommended-module"],
"extends": ["plugin:n/recommended-module", "plugin:import/recommended", "plugin:import/typescript"],
"plugins": ["zod"],
"rules": {
"n/no-missing-import": 0
"n/no-missing-import": 0,
"@dword-design/import-alias/prefer-alias": [
"error",
{
"alias": {
"@": "./server/src",
"@tests": "./server/tests"
}
}
],
"n/no-extraneous-import": [
"error",
{
"allowModules": ["shared"]
}
]
},
"overrides": [
{
"files": ["**/*.test.ts"],
"rules": {
"n/no-extraneous-import": ["off"]
}
}
],
"env": {
"es2022": true,
"node": true
},
"parserOptions": {
"project": "server/tsconfig.json"
},
"settings": {
"import/resolver": {
"typescript": {
"project": "server/tsconfig.json"
}
"node": {
"allowModules": []
}
}
}
95 changes: 47 additions & 48 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"author": "Mission apprentissage",
"license": "MIT",
"type": "module",
"private": true,
"engines": {
"node": ">=20",
"node": ">=22",
"npm": "please-use-yarn"
},
"scripts": {
Expand All @@ -17,60 +18,58 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@fastify/auth": "^4.4.0",
"@fastify/cookie": "^9.1.0",
"@fastify/cors": "^8.4.1",
"@fastify/multipart": "^8.0.0",
"@fastify/swagger": "^8.12.0",
"@fastify/swagger-ui": "^1.10.1",
"@fastify/auth": "^4.6.1",
"@fastify/cookie": "^9.4.0",
"@fastify/cors": "^9.0.1",
"@fastify/multipart": "^8.3.0",
"@fastify/rate-limit": "^9.1.0",
"@fastify/swagger": "^8.15.0",
"@fastify/swagger-ui": "^4.1.1",
"@hapi/boom": "^10.0.1",
"@immobiliarelabs/fastify-sentry": "^7.1.1",
"@sentry/integrations": "^7.77.0",
"@sentry/node": "^7.77.0",
"@sentry/tracing": "^7.77.0",
"axios": "^1.5.1",
"axios-cache-interceptor": "^1.3.2",
"axios-retry": "^3.8.0",
"bunyan": "^1.8.15",
"bunyan-prettystream": "^0.1.3",
"commander": "^10.0.1",
"@sentry/node": "^8.34.0",
"@sentry/profiling-node": "^8.34.0",
"axios": "^1.7.7",
"axios-cache-interceptor": "^1.6.0",
"axios-retry": "^4.5.0",
"commander": "^12.1.0",
"cron-parser": "^4.9.0",
"csv-parse": "^5.4.0",
"date-fns": "^2.30.0",
"ejs": "^3.1.9",
"env-var": "^7.3.1",
"fastify": "^4.21.0",
"fastify-type-provider-zod": "^1.1.9",
"job-processor": "^1.4.7",
"jsonwebtoken": "^9.0.1",
"csv-parse": "^5.5.6",
"date-fns": "^4.1.0",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
"env-var": "^7.5.0",
"fastify": "^4.28.1",
"fastify-type-provider-zod": "^2.1.0",
"job-processor": "^1.6.3",
"jsonwebtoken": "^9.0.2",
"lil-http-terminator": "^1.2.3",
"lodash-es": "^4.17.21",
"migrate-mongo": "^10.0.0",
"mjml": "^4.14.1",
"mongodb": "^5.7.0",
"nodemailer": "^6.9.4",
"migrate-mongo": "^11.0.0",
"mjml": "^4.15.3",
"mongodb": "^6.9.0",
"nodemailer": "^6.9.15",
"nodemailer-html-to-text": "^3.2.0",
"pino-pretty": "^10.2.3",
"rate-limiter-flexible": "^2.4.2",
"shared": "workspace:*",
"zod": "^3.22.4",
"zod-mongodb-schema": "^1.0.0"
"pino": "^9.4.0",
"pino-pretty": "^11.2.2",
"rate-limiter-flexible": "^5.0.3",
"shared": "workspace:^",
"unique-names-generator": "^4.7.1",
"zod": "^3.23.8",
"zod-mongodb-schema": "^1.0.2"
},
"devDependencies": {
"@types/bunyan": "^1.8.8",
"@types/bunyan-prettystream": "^0.1.32",
"@types/ejs": "^3.1.2",
"@types/jsonwebtoken": "^9.0.2",
"@types/lodash-es": "^4.17.8",
"@types/migrate-mongo": "^10.0.0",
"@types/mjml": "^4.7.1",
"@types/nodemailer": "^6.4.9",
"@types/nodemailer-html-to-text": "^3.1.0",
"dotenv": "^16.3.1",
"nock": "^13.3.3",
"tsup": "^7.2.0",
"typescript": "^5.1.6",
"vitest": "^0.34.6"
"@tsconfig/node22": "^22.0.0",
"@types/ejs": "^3.1.5",
"@types/jsonwebtoken": "^9.0.7",
"@types/lodash-es": "^4.17.12",
"@types/migrate-mongo": "^10.0.5",
"@types/mjml": "^4.7.4",
"@types/nodemailer": "^6.4.16",
"@types/nodemailer-html-to-text": "^3.1.3",
"nock": "^13.5.5",
"tsup": "^8.3.0",
"typescript": "^5.6.3",
"vitest": "^2.1.3"
},
"files": [
"src/**/*",
Expand Down
113 changes: 113 additions & 0 deletions server/src/actions/auth.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { conflict } from "@hapi/boom";
import { ObjectId } from "mongodb";
import type { IBody, IPostRoutes } from "shared";
import { zRoutes } from "shared";
import type { IUser } from "shared/src/models/user.model";

import { sendEmail } from "@/services/mailer/mailer";
import { getDbCollection } from "@/services/mongodb/mongodbService";
import { generateAccessToken, generateScope } from "@/services/security/accessTokenService";

export function generateRegisterToken(email: string): string {
// No need to provided organisation for register
return generateAccessToken(
{ email, organisation: null },
[
generateScope({
schema: zRoutes.post["/_private/auth/register"],
options: "all",
resources: {},
}),
// generateScope({
// schema: zRoutes.post["/_private/auth/register-feedback"],
// options: "all",
// resources: {},
// }),
],
{ expiresIn: "30d" }
);
}

function sendRegisterEmail(email: string) {
return sendEmail({
name: "register",
to: email,
token: generateRegisterToken(email),
});
}

export function generateMagicLinkToken(email: string): string {
return generateAccessToken(
// No need to provided organisation for login
{ email, organisation: null },
[
generateScope({
schema: zRoutes.post["/_private/auth/login"],
options: "all",
resources: {},
}),
],
{ expiresIn: "7d" }
);
}

function sendMagicLinkEmail(email: string) {
return sendEmail({
name: "magic-link",
to: email,
token: generateMagicLinkToken(email),
});
}

export async function sendRequestLoginEmail(email: string) {
const user = await getDbCollection("users").findOne({ email });

if (!user) {
await sendRegisterEmail(email);
} else {
await sendMagicLinkEmail(email);
}
}

export async function sendRegisterFeedbackEmail(
from: string,
data: IBody<IPostRoutes["/_private/auth/register-feedback"]>
) {
await sendEmail({
name: "register-feedback",
to: "[email protected]",
from,
comment: data.comment,
});
}

export async function registerUser(email: string, data: IBody<IPostRoutes["/_private/auth/register"]>): Promise<IUser> {
const existingUser = await getDbCollection("users").findOne({ email });

if (existingUser) {
await sendMagicLinkEmail(email);
throw conflict(
"Un compte associé à cet email existe déjà. Nous vous avons envoyé un lien de connexion, veuillez consulter vos emails."
);
}

const now = new Date();
const user = {
_id: new ObjectId(),
email,
organisation: null,
type: data.type,
activite: data.activite,
objectif: data.objectif,
cas_usage: data.cas_usage,
is_admin: false,
api_keys: [],
cgu_accepted_at: now,
created_at: now,
updated_at: now,
};

await getDbCollection("users").insertOne(user);

return user;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ObjectId } from "mongodb";
import { IEmailError, IEmailEvent } from "shared/models/email_event.model";
import type { IEmailError, IEmailEvent } from "shared/src/models/email_event.model";

import { getDbCollection } from "@/common/utils/mongodbUtils";
import { getDbCollection } from "@/services/mongodb/mongodbService";

export async function createEmailEvent(template: IEmailEvent["template"]) {
const now = new Date();
Expand Down
91 changes: 91 additions & 0 deletions server/src/actions/sessions.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { internal } from "@hapi/boom";
import type { FastifyReply, FastifyRequest } from "fastify";
import type { JwtPayload } from "jsonwebtoken";
import jwt from "jsonwebtoken";
import type { Filter, FindOptions } from "mongodb";
import { ObjectId } from "mongodb";
import type { ISession } from "shared/src/models/session.model";
import type { IUser } from "shared/src/models/user.model";
import type { UserWithType } from "shared/src/security/permissions";

import config from "@/config";
import { withCause } from "@/services/errors/withCause";
import { getDbCollection } from "@/services/mongodb/mongodbService";

export async function authCookieSession(req: FastifyRequest): Promise<UserWithType<"user", IUser> | null> {
const token = req.cookies?.[config.session.cookieName];

if (!token) {
return null;
}

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

const session = await getSession({ email });

if (!session) {
return null;
}

const user = await getDbCollection("users").findOne({ email: email.toLowerCase() });

return user ? { type: "user", value: user } : user;
} catch (error) {
if (error instanceof jwt.JsonWebTokenError) {
return null;
}

const err = internal("authCookieSession: error when verifying token");
throw withCause(err, error);
}
}

async function createSession(email: string) {
const now = new Date();

const session: ISession = {
_id: new ObjectId(),
email,
updated_at: now,
created_at: now,
expires_at: new Date(now.getTime() + config.session.cookie.maxAge),
};

await getDbCollection("sessions").insertOne(session);

return session;
}

async function getSession(filter: Filter<ISession>, options?: FindOptions): Promise<ISession | null> {
return getDbCollection("sessions").findOne(filter, options);
}

async function deleteSession({ email }: { email: string }) {
await getDbCollection("sessions").deleteMany({ email });
}

function createSessionToken(email: string) {
return jwt.sign({ email }, config.auth.user.jwtSecret, {
issuer: config.publicUrl,
expiresIn: config.session.cookie.maxAge / 1_000,
subject: email,
});
}

async function startSession(email: string, res: FastifyReply) {
const token = createSessionToken(email);
await createSession(email);
res.setCookie(config.session.cookieName, token, config.session.cookie);
}

async function stopSession(req: FastifyRequest, res: FastifyReply) {
const user = await authCookieSession(req);
if (user) {
await deleteSession({ email: user.value.email });
}

res.clearCookie(config.session.cookieName, config.session.cookie);
}

export { getSession, startSession, stopSession, createSessionToken, createSession };
Loading

0 comments on commit bdfdf97

Please sign in to comment.