diff --git a/package-lock.json b/package-lock.json index 3eb95b10..ec87b407 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "moment": "^2.29.4", "multer": "^1.4.5-lts.1", "node-pg-migrate": "^6.2.2", - "pg": "^8.11.3", + "pg": "^8.11.5", "pg-promise": "^10.15.4", "uuid": "^9.0.1", "winston": "^3.13.0", @@ -9454,15 +9454,13 @@ } }, "node_modules/pg": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", - "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", + "version": "8.11.5", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.5.tgz", + "integrity": "sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==", "dependencies": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.6.2", - "pg-pool": "^3.6.1", - "pg-protocol": "^1.6.0", + "pg-connection-string": "^2.6.4", + "pg-pool": "^3.6.2", + "pg-protocol": "^1.6.1", "pg-types": "^2.1.0", "pgpass": "1.x" }, @@ -9488,9 +9486,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", - "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", + "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==" }, "node_modules/pg-int8": { "version": "1.0.1", @@ -9509,9 +9507,9 @@ } }, "node_modules/pg-pool": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", - "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", + "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", "peerDependencies": { "pg": ">=8.0" } @@ -9556,9 +9554,9 @@ } }, "node_modules/pg-protocol": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", - "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", + "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==" }, "node_modules/pg-types": { "version": "2.2.0", diff --git a/package.json b/package.json index 829becb2..fd5bc712 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "moment": "^2.29.4", "multer": "^1.4.5-lts.1", "node-pg-migrate": "^6.2.2", - "pg": "^8.11.3", + "pg": "^8.11.5", "pg-promise": "^10.15.4", "uuid": "^9.0.1", "winston": "^3.13.0", diff --git a/src/server/controllers/auth/init-user-controller.spec.ts b/src/server/controllers/auth/init-user-controller.spec.ts index fa9b293e..3f992a5d 100644 --- a/src/server/controllers/auth/init-user-controller.spec.ts +++ b/src/server/controllers/auth/init-user-controller.spec.ts @@ -6,37 +6,50 @@ import { AllowedRoles } from "../../middleware/authorization-middleware" jest.mock("../../../db/db") const mockResponse = () => { - const res: Partial = {} - res.send = jest.fn().mockReturnValue(res) - res.status = jest.fn().mockReturnValue(res) - return res + const res: Partial = {} + res.send = jest.fn().mockReturnValue(res) + res.status = jest.fn().mockReturnValue(res) + return res } describe("initUserController", () => { - it("should call createNewUserController if no user exists", async function () { - const querySpy = jest.spyOn(require("../users/create-new-user-controller"), "createNewUserController"); - (db.manyOrNone as any).mockResolvedValue([]) - const nextFunction: NextFunction = jest.fn() - const response = mockResponse() - const request = { body: { username: "test", password: "123" } } - await initUserController( - request as unknown as IGetUserAuthInfoRequest, - response as unknown as Response, nextFunction) - expect(querySpy).toHaveBeenCalledTimes(1) - expect(request.body).toEqual({ username: "test", password: "123", role: AllowedRoles.Admin }) - }) - it("should return an error if user already exists", async function () { - const querySpy = jest.spyOn(require("boom"), "forbidden"); - - (db.manyOrNone as any).mockResolvedValue(["test"]) - const nextFunction: NextFunction = jest.fn() - const response = mockResponse() - const request = {} - await initUserController( - request as unknown as IGetUserAuthInfoRequest, - response as unknown as Response, nextFunction) - expect(nextFunction).toHaveBeenCalledTimes(1) - expect(querySpy).toHaveBeenCalledTimes(1) - expect(querySpy).toHaveBeenCalledWith("User was already initialized") - }) + it("should call createNewUserController if no user exists", async function () { + const querySpy = jest.spyOn(require("../users/create-new-user-controller"), "createNewUserController"); + (db.oneOrNone as any).mockResolvedValueOnce({}); // migration has finished + (db.manyOrNone as any).mockResolvedValue([]) + const nextFunction: NextFunction = jest.fn() + const response = mockResponse() + const request = { body: { username: "test", password: "123" } } + await initUserController( + request as unknown as IGetUserAuthInfoRequest, + response as unknown as Response, nextFunction) + expect(querySpy).toHaveBeenCalledTimes(1) + expect(request.body).toEqual({ username: "test", password: "123", role: AllowedRoles.Admin }) + }) + it("should return an error if user already exists", async function () { + const querySpy = jest.spyOn(require("boom"), "forbidden"); + (db.oneOrNone as any).mockResolvedValueOnce({}); // migration has finished + (db.manyOrNone as any).mockResolvedValue(["test"]) + const nextFunction: NextFunction = jest.fn() + const response = mockResponse() + const request = {} + await initUserController( + request as unknown as IGetUserAuthInfoRequest, + response as unknown as Response, nextFunction) + expect(nextFunction).toHaveBeenCalledTimes(1) + expect(querySpy).toHaveBeenCalledTimes(1) + expect(querySpy).toHaveBeenCalledWith("User was already initialized") + }) + it("should throw exception when migrations have not finished", async () => { + const spy = jest.spyOn(require("boom"), "preconditionRequired") + const nextFunction: NextFunction = jest.fn() + const response = mockResponse() + const request = {} + await initUserController( + request as unknown as IGetUserAuthInfoRequest, + response as unknown as Response, nextFunction) + expect(nextFunction).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith("Migrations were not finished, please try it again later.") + }) }) diff --git a/src/server/controllers/auth/init-user-controller.ts b/src/server/controllers/auth/init-user-controller.ts index 67bd30ee..7c44ceb1 100644 --- a/src/server/controllers/auth/init-user-controller.ts +++ b/src/server/controllers/auth/init-user-controller.ts @@ -2,21 +2,28 @@ import boom = require("boom") import { Request, Response, NextFunction } from "express" import { db } from "../../../db/db" import { AllowedRoles } from "../../middleware/authorization-middleware" -import { getUsers } from "../../queries/auth" +import { getUsers, getRoleMigration } from "../../queries/auth" import { createNewUserController } from "../users/create-new-user-controller" +import { MigrationNotFinishedException } from "../../errors/migration-not-finished-exception" export const initUserController = async (req: Request, res: Response, next: NextFunction) => { - try { - const users = await db.manyOrNone(getUsers()) - if (users && users.length > 0) { - return next(boom.forbidden("User was already initialized")) - } - req.body.role = AllowedRoles.Admin - await createNewUserController(req, res, next) - + try { + const roleMigration = await db.oneOrNone(getRoleMigration()) + if (!roleMigration) { + throw new MigrationNotFinishedException("role migration has not finished") + } + const users = await db.manyOrNone(getUsers()) + if (users && users.length > 0) { + return next(boom.forbidden("User was already initialized")) + } + req.body.role = AllowedRoles.Admin + await createNewUserController(req, res, next) - } catch(error) { - return next(error) - } + } catch(error) { + if (error.code === "42P01" || error instanceof MigrationNotFinishedException) { + return next(boom.preconditionRequired("Migrations were not finished, please try it again later.")) + } + return next(error) + } } diff --git a/src/server/errors/migration-not-finished-exception.ts b/src/server/errors/migration-not-finished-exception.ts new file mode 100644 index 00000000..9f324ffe --- /dev/null +++ b/src/server/errors/migration-not-finished-exception.ts @@ -0,0 +1,9 @@ +export class MigrationNotFinishedException extends Error { + constructor (message) { + super(message) + + this.name = this.constructor.name + + Error.captureStackTrace(this, this.constructor) + } +} diff --git a/src/server/queries/auth.ts b/src/server/queries/auth.ts index 12dc45e7..45cab7a5 100644 --- a/src/server/queries/auth.ts +++ b/src/server/queries/auth.ts @@ -32,3 +32,10 @@ export const updatePassword = (id, password) => { values: [id, password], } } + +export const getRoleMigration = () => { + return { + text: "SELECT * FROM pgmigrations WHERE name = $1", + values: ["1643273224321_role"], + } +}