-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a0f9699
commit bdfdf97
Showing
149 changed files
with
5,262 additions
and
3,722 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": [] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
4 changes: 2 additions & 2 deletions
4
server/src/modules/actions/emails.actions.ts → server/src/actions/emails.actions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
Oops, something went wrong.