Skip to content

Commit

Permalink
fix: server / shared
Browse files Browse the repository at this point in the history
  • Loading branch information
moroine committed Oct 17, 2024
1 parent 912bd7e commit 0355a2d
Show file tree
Hide file tree
Showing 38 changed files with 297 additions and 220 deletions.
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,9 @@
"vitest.commandLine": "yarn test --single-thread",
"ansible.python.interpreterPath": "/opt/homebrew/bin/python3",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"vitest.workspaceConfig": "vitest.workspace.ts"
"vitest.workspaceConfig": "vitest.workspace.ts",
"compareFolders.ignoreAllWhiteSpaces": true,
"compareFolders.ignoreEmptyLines": true,
"compareFolders.ignoreWhiteSpaces": true,
"compareFolders.respectGitIgnore": true
}
2 changes: 1 addition & 1 deletion server/src/actions/auth.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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 type { IUser } from "shared/models/user.model";

import { sendEmail } from "@/services/mailer/mailer";
import { getDbCollection } from "@/services/mongodb/mongodbService";
Expand Down
2 changes: 1 addition & 1 deletion server/src/actions/emails.actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ObjectId } from "mongodb";
import type { IEmailError, IEmailEvent } from "shared/src/models/email_event.model";
import type { IEmailError, IEmailEvent } from "shared/models/email_event.model";

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

Expand Down
6 changes: 3 additions & 3 deletions server/src/actions/sessions.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ 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 type { ISession } from "shared/models/session.model";
import type { IUser } from "shared/models/user.model";
import type { UserWithType } from "shared/security/permissions";

import config from "@/config";
import { withCause } from "@/services/errors/withCause";
Expand Down
2 changes: 1 addition & 1 deletion server/src/actions/users.actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ObjectId } from "mongodb";
import type { IApiKeyPrivate, IUser } from "shared/src/models/user.model";
import type { IApiKeyPrivate, IUser } from "shared/models/user.model";
import { adjectives, animals, colors, uniqueNamesGenerator } from "unique-names-generator";

import config from "@/config";
Expand Down
2 changes: 1 addition & 1 deletion server/src/jobs/db/schemaValidation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { captureException } from "@sentry/node";
import { modelDescriptors } from "shared/src/models/models";
import { modelDescriptors } from "shared/models/models";

import { getDatabase } from "@/services/mongodb/mongodbService";

Expand Down
2 changes: 1 addition & 1 deletion server/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { captureException } from "@sentry/node";
import { modelDescriptors } from "shared/src/models/models";
import { modelDescriptors } from "shared/models/models";

import { startCLI } from "./commands";
import config from "./config";
Expand Down
6 changes: 3 additions & 3 deletions server/src/server/middlewares/apiKeyUsageMiddleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { useMongo } from "@tests/mongo.test.utils";
import { fastify } from "fastify";
import type { ZodTypeProvider } from "fastify-type-provider-zod";
import { serializerCompiler, validatorCompiler } from "fastify-type-provider-zod";
import { generateUserFixture } from "shared/src/models/fixtures/index";
import type { IUser } from "shared/src/models/user.model";
import type { IRouteSchema, ISecuredRouteSchema, WithSecurityScheme } from "shared/src/routes/common.routes";
import { generateUserFixture } from "shared/models/fixtures/index";
import type { IUser } from "shared/models/user.model";
import type { IRouteSchema, ISecuredRouteSchema, WithSecurityScheme } from "shared/routes/common.routes";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { z } from "zod";

Expand Down
2 changes: 1 addition & 1 deletion server/src/server/middlewares/authMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
RawServerDefault,
RouteGenericInterface,
} from "fastify";
import type { IRouteSchema, SecurityScheme, WithSecurityScheme } from "shared/src/routes/common.routes";
import type { IRouteSchema, SecurityScheme, WithSecurityScheme } from "shared/routes/common.routes";

import { authenticationMiddleware } from "@/services/security/authenticationService";
import { authorizationnMiddleware } from "@/services/security/authorisationService";
Expand Down
2 changes: 1 addition & 1 deletion server/src/server/middlewares/errorMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { badRequest, Boom, internal, isBoom } from "@hapi/boom";
import { captureException } from "@sentry/node";
import type { FastifyError } from "fastify";
import { ResponseValidationError } from "fastify-type-provider-zod";
import type { IResError } from "shared/src/models/errors";
import type { IResError } from "shared/models/errors";
import { ZodError } from "zod";

import config from "@/config";
Expand Down
2 changes: 1 addition & 1 deletion server/src/server/routes/_private/admin/user.routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { notFound } from "@hapi/boom";
import type { RootFilterOperators } from "mongodb";
import { zRoutes } from "shared";
import type { IUser } from "shared/src/models/user.model";
import type { IUser } from "shared/models/user.model";

import type { Server } from "@/server/server";
import { getDbCollection } from "@/services/mongodb/mongodbService";
Expand Down
2 changes: 1 addition & 1 deletion server/src/server/routes/_private/auth.route.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMongo } from "@tests/mongo.test.utils";
import { ObjectId } from "mongodb";
import { generateUserFixture } from "shared/src/models/fixtures/index";
import { generateUserFixture } from "shared/models/fixtures/index";
import { beforeAll, describe, expect, it, vi } from "vitest";

import { generateMagicLinkToken, generateRegisterToken } from "@/actions/auth.actions";
Expand Down
2 changes: 1 addition & 1 deletion server/src/server/routes/_private/auth.routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { internal } from "@hapi/boom";
import { zRoutes } from "shared";
import { toPublicUser } from "shared/src/models/user.model";
import { toPublicUser } from "shared/models/user.model";

import { registerUser, sendRegisterFeedbackEmail, sendRequestLoginEmail } from "@/actions/auth.actions";
import { startSession, stopSession } from "@/actions/sessions.actions";
Expand Down
2 changes: 1 addition & 1 deletion server/src/server/routes/_private/emails.routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forbidden } from "@hapi/boom";
import { zRoutes } from "shared";
import type { IEmailError } from "shared/src/models/email_event.model";
import type { IEmailError } from "shared/models/email_event.model";

import { markEmailAsDelivered, markEmailAsFailed, markEmailAsOpened, unsubscribe } from "@/actions/emails.actions";
import config from "@/config";
Expand Down
2 changes: 1 addition & 1 deletion server/src/server/routes/_private/user.route.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useMongo } from "@tests/mongo.test.utils";
import jwt from "jsonwebtoken";
import { ObjectId } from "mongodb";
import { generateUserFixture } from "shared/src/models/fixtures/index";
import { generateUserFixture } from "shared/models/fixtures/index";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";

import { createSession, createSessionToken } from "@/actions/sessions.actions";
Expand Down
2 changes: 1 addition & 1 deletion server/src/server/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { RouteOptions } from "fastify";
import { fastify } from "fastify";
import type { ZodTypeProvider } from "fastify-type-provider-zod";
import { zRoutes } from "shared/index";
import type { IRouteSchemaGet, IRouteSchemaWrite, SecurityScheme } from "shared/src/routes/common.routes";
import type { IRouteSchemaGet, IRouteSchemaWrite, SecurityScheme } from "shared/routes/common.routes";
import { describe, it } from "vitest";

import { describeAuthMiddleware } from "./middlewares/authMiddleware";
Expand Down
4 changes: 2 additions & 2 deletions server/src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import type {
import { fastify } from "fastify";
import type { ZodTypeProvider } from "fastify-type-provider-zod";
import { serializerCompiler, validatorCompiler } from "fastify-type-provider-zod";
import { generateOpenApiSchema } from "shared/src/helpers/openapi/generateOpenapi";
import type { IRouteSchema, WithSecurityScheme } from "shared/src/routes/common.routes";
import { generateOpenApiSchema } from "shared/helpers/openapi/generateOpenapi";
import type { IRouteSchema, WithSecurityScheme } from "shared/routes/common.routes";
import { z } from "zod";

import config from "@/config";
Expand Down
4 changes: 2 additions & 2 deletions server/src/services/mailer/mailer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import type Mail from "nodemailer/lib/mailer";
import type SMTPTransport from "nodemailer/lib/smtp-transport";
import { htmlToText } from "nodemailer-html-to-text";
import { zRoutes } from "shared";
import type { IEmailEvent } from "shared/src/models/email_event.model";
import type { ITemplate } from "shared/src/models/email_event/email_templates";
import type { IEmailEvent } from "shared/models/email_event.model";
import type { ITemplate } from "shared/models/email_event/email_templates";
import { assertUnreachable } from "shared/utils/assertUnreachable";

import { addEmailError, createEmailEvent, isUnsubscribed, setEmailMessageId } from "@/actions/emails.actions";
Expand Down
4 changes: 2 additions & 2 deletions server/src/services/mongodb/mongodbService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { captureException } from "@sentry/node";
import { isEqual } from "lodash-es";
import type { Collection, CollectionInfo, MongoServerError } from "mongodb";
import { MongoClient } from "mongodb";
import type { CollectionName, IDocument, IModelDescriptor } from "shared/src/models/models";
import { modelDescriptors } from "shared/src/models/models";
import type { CollectionName, IDocument, IModelDescriptor } from "shared/models/models";
import { modelDescriptors } from "shared/models/models";
import { zodToMongoSchema } from "zod-mongodb-schema";

import config from "@/config";
Expand Down
25 changes: 6 additions & 19 deletions server/src/services/security/accessTokenService.test.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,13 @@
import { ObjectId } from "mongodb";
import type { IUser } from "shared/src/models/user.model";
import { zRoutes } from "shared/src/routes";
import { zRoutes } from "shared";
import { generateUserFixture } from "shared/models/fixtures/index";
import type { SchemaWithSecurity } from "shared/routes/common.routes";
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { zObjectId } from "zod-mongodb-schema";

import type { SchemaWithSecurity } from "./accessTokenService";
import { generateAccessToken, generateScope, parseAccessToken } from "./accessTokenService";
import { generateAccessToken, generateScope, parseAccessToken } from "./accessTokenService.js";

const mockUser = (email: string): IUser => {
return {
_id: new ObjectId(),
email,
// @ts-expect-error
password: "",
is_admin: false,
api_key: null,
api_key_used_at: null,
updated_at: new Date(),
created_at: new Date(),
};
};
const ids = [new ObjectId().toString(), new ObjectId().toString(), new ObjectId().toString()];

describe("generateScope", () => {
Expand Down Expand Up @@ -92,7 +79,7 @@ describe("generateScope", () => {
});

describe("accessTokenService", () => {
const user = mockUser("[email protected]");
const user = generateUserFixture({ email: "[email protected]" });

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const schema: any = {
Expand Down Expand Up @@ -169,7 +156,7 @@ describe("accessTokenService", () => {
it("should detect an invalid token that is for a different route", () => {
const token = generateAccessToken(user, [
generateScope({
schema: zRoutes.post["/admin/user"],
schema: zRoutes.get["/_private/admin/users"],
resources: {},
options: "all",
}),
Expand Down
59 changes: 19 additions & 40 deletions server/src/services/security/accessTokenService.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { forbidden, internal } from "@hapi/boom";
import { captureException } from "@sentry/node";
import jwt from "jsonwebtoken";
import type { IUser } from "shared/models/user.model";
import type {
IAccessToken,
IAccessTokenScope,
IAccessTokenScopeParam,
ISecuredRouteSchema,
SchemaWithSecurity,
} from "shared/routes/common.routes";
import type { PathParam, QueryString } from "shared/src/helpers/generateUri";
import type { IUser } from "shared/src/models/user.model";
import type { IRouteSchema, ISecuredRouteSchema, WithSecurityScheme } from "shared/src/routes/common.routes";
import type { Jsonify } from "type-fest";
import type { AnyZodObject, z } from "zod";

import config from "@/config";
import config from "@/config.js";

// cf https://www.sistrix.com/ask-sistrix/technical-seo/site-structure/url-length-how-long-can-a-url-be
const INTERNET_EXPLORER_V10_MAX_LENGTH = 2083;
Expand All @@ -16,27 +20,9 @@ const NGINX_URL_MAX_LENGTH = 4096;
const URL_MAX_LENGTH = Math.min(INTERNET_EXPLORER_V10_MAX_LENGTH, OUTLOOK_URL_MAX_LENGTH, NGINX_URL_MAX_LENGTH);
const TOKEN_MAX_LENGTH = URL_MAX_LENGTH - config.publicUrl.length;

export type SchemaWithSecurity = Pick<IRouteSchema, "method" | "path" | "params" | "querystring"> & WithSecurityScheme;

type IScope<S extends SchemaWithSecurity> = {
path: S["path"];
method: S["method"];
options:
| "all"
| {
params: S["params"] extends AnyZodObject ? Partial<Jsonify<z.input<S["params"]>>> : undefined;
querystring: S["querystring"] extends AnyZodObject ? Partial<Jsonify<z.input<S["querystring"]>>> : undefined;
};
resources: {
[key in keyof S["securityScheme"]["ressources"]]: ReadonlyArray<string>;
};
};

type IScopeParam<S extends SchemaWithSecurity> = Pick<IScope<S>, "options" | "resources"> & {
schema: S;
};

export const generateScope = <Schema extends SchemaWithSecurity>(scope: IScopeParam<Schema>): IScope<Schema> => {
export const generateScope = <Schema extends SchemaWithSecurity>(
scope: IAccessTokenScopeParam<Schema>
): IAccessTokenScope<Schema> => {
const { schema, options, resources } = scope;

const requiredResources = Object.keys(schema.securityScheme.ressources);
Expand All @@ -55,21 +41,14 @@ export const generateScope = <Schema extends SchemaWithSecurity>(scope: IScopePa
return { options, resources, path: schema.path, method: schema.method };
};

export type IAccessToken<S extends SchemaWithSecurity = SchemaWithSecurity> = {
identity: {
email: string;
};
scopes: ReadonlyArray<IScope<S>>;
};

export function generateAccessToken<S extends ISecuredRouteSchema>(
export function generateAccessToken<S extends ReadonlyArray<IAccessTokenScope<ISecuredRouteSchema>>>(
user: IUser | IAccessToken["identity"],
scopes: ReadonlyArray<IScope<S>>,
scopes: S,
options: { expiresIn?: string } = {}
): string {
const identity: IAccessToken["identity"] = { email: user.email.toLowerCase() };
const identity: IAccessToken["identity"] = { email: user.email.toLowerCase(), organisation: user.organisation };

const data: IAccessToken<S> = { identity, scopes };
const data: IAccessToken<ISecuredRouteSchema> = { identity, scopes };

const token = jwt.sign(data, config.auth.user.jwtSecret, {
expiresIn: options.expiresIn ?? config.auth.user.expiresIn,
Expand All @@ -87,7 +66,7 @@ export function getAccessTokenScope<S extends SchemaWithSecurity>(
schema: Pick<S, "method" | "path">,
params: PathParam | undefined,
querystring: QueryString | undefined
): IScope<S> | null {
): IAccessTokenScope<S> | null {
return (
token?.scopes.find((s) => {
if (s.path !== schema.path || s.method !== schema.method) {
Expand Down Expand Up @@ -153,12 +132,12 @@ export function parseAccessToken<S extends SchemaWithSecurity>(
const scope = getAccessTokenScope(token, schema, params, querystring);

if (!scope) {
throw forbidden("Scope does not match");
throw forbidden("Le jeton d'accès ne permet pas d'accéder à cette ressource");
}

return token;
} catch (err) {
const error = forbidden("Invalid Access Token");
const error = forbidden("Le jeton d'accès est invalide");
error.cause = err;
throw error;
}
Expand Down
16 changes: 8 additions & 8 deletions server/src/services/security/authenticationService.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useMongo } from "@tests/mongo.test.utils";
import { useMongo } from "@tests/mongo.test.utils.js";
import { ObjectId } from "mongodb";
import { generateOrganisationFixture, generateUserFixture } from "shared/src/models/fixtures/index";
import type { ISecuredRouteSchema } from "shared/src/routes/common.routes";
import { generateOrganisationFixture, generateUserFixture } from "shared/models/fixtures/index";
import type { ISecuredRouteSchema } from "shared/routes/common.routes";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { z } from "zod";

import { createSession, createSessionToken } from "@/actions/sessions.actions";
import { generateApiKey } from "@/actions/users.actions";
import config from "@/config";
import { getDbCollection } from "@/services/mongodb/mongodbService";
import { createSession, createSessionToken } from "@/actions/sessions.actions.js";
import { generateApiKey } from "@/actions/users.actions.js";
import config from "@/config.js";
import { getDbCollection } from "@/services/mongodb/mongodbService.js";

import { authenticationMiddleware } from "./authenticationService";
import { authenticationMiddleware } from "./authenticationService.js";

useMongo();

Expand Down
Loading

0 comments on commit 0355a2d

Please sign in to comment.