From 4af6310a9c42fbc81ab82d6253becf1f3fdeebac Mon Sep 17 00:00:00 2001 From: Kevin J Hoerr Date: Thu, 7 Apr 2022 06:13:09 +0000 Subject: #17 Move TOKEN from EnvConfig to database --- .vscode/launch.json | 15 ++ ...ode-pre-gyp-npm-1.0.9-7ef8e73557-1b9c4c87a6.zip | Bin 0 -> 53912 bytes ...ypes-bcrypt-npm-5.0.0-c074c165c2-063c32c7a5.zip | Bin 0 -> 3751 bytes .../aproba-npm-2.0.0-8716bcfde6-5615cadcfb.zip | Bin 0 -> 4560 bytes ...e-there-yet-npm-2.0.0-7d2f5201ce-6c80b4fd04.zip | Bin 0 -> 6878 bytes .../bcrypt-npm-5.0.1-6815be1cfe-b59625519f.zip | Bin 0 -> 41301 bytes ...lor-support-npm-1.1.3-3be5c53455-9b73568176.zip | Bin 0 -> 4890 bytes ...detect-libc-npm-2.0.1-2699cb2ac4-ccb05fcabb.zip | Bin 0 -> 8117 bytes .../gauge-npm-3.0.2-9e22f7af9e-81296c00c7.zip | Bin 0 -> 20114 bytes ...e-addon-api-npm-3.2.1-a29528f81d-2369986bb0.zip | Bin 0 -> 71562 bytes .../node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip | Bin 0 -> 44979 bytes .../npmlog-npm-5.0.1-366cab64a2-516b266302.zip | Bin 0 -> 6690 bytes ...tring-width-npm-4.2.3-2c27177bae-e52c10dc3f.zip | Bin 0 -> 3604 bytes .../cache/tr46-npm-0.0.3-de53018915-726321c5ea.zip | Bin 0 -> 64886 bytes ...conversions-npm-3.0.1-60310f6a2b-c92a0a6ab9.zip | Bin 0 -> 5694 bytes .../whatwg-url-npm-5.0.0-374fb45e60-b8daed4ad3.zip | Bin 0 -> 12839 bytes .../wide-align-npm-1.1.5-889d77e592-d5fc37cd56.zip | Bin 0 -> 2750 bytes package.json | 2 + src/config.test.ts | 258 +++++---------------- src/config.ts | 35 +-- src/index.ts | 15 +- src/metadata.test.ts | 60 ++++- src/metadata.ts | 81 ++++++- src/routes.test.ts | 11 +- src/routes.ts | 99 ++++---- yarn.lock | 181 ++++++++++++++- 26 files changed, 440 insertions(+), 317 deletions(-) create mode 100644 .yarn/cache/@mapbox-node-pre-gyp-npm-1.0.9-7ef8e73557-1b9c4c87a6.zip create mode 100644 .yarn/cache/@types-bcrypt-npm-5.0.0-c074c165c2-063c32c7a5.zip create mode 100644 .yarn/cache/aproba-npm-2.0.0-8716bcfde6-5615cadcfb.zip create mode 100644 .yarn/cache/are-we-there-yet-npm-2.0.0-7d2f5201ce-6c80b4fd04.zip create mode 100644 .yarn/cache/bcrypt-npm-5.0.1-6815be1cfe-b59625519f.zip create mode 100644 .yarn/cache/color-support-npm-1.1.3-3be5c53455-9b73568176.zip create mode 100644 .yarn/cache/detect-libc-npm-2.0.1-2699cb2ac4-ccb05fcabb.zip create mode 100644 .yarn/cache/gauge-npm-3.0.2-9e22f7af9e-81296c00c7.zip create mode 100644 .yarn/cache/node-addon-api-npm-3.2.1-a29528f81d-2369986bb0.zip create mode 100644 .yarn/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip create mode 100644 .yarn/cache/npmlog-npm-5.0.1-366cab64a2-516b266302.zip create mode 100644 .yarn/cache/string-width-npm-4.2.3-2c27177bae-e52c10dc3f.zip create mode 100644 .yarn/cache/tr46-npm-0.0.3-de53018915-726321c5ea.zip create mode 100644 .yarn/cache/webidl-conversions-npm-3.0.1-60310f6a2b-c92a0a6ab9.zip create mode 100644 .yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-b8daed4ad3.zip create mode 100644 .yarn/cache/wide-align-npm-1.1.5-889d77e592-d5fc37cd56.zip diff --git a/.vscode/launch.json b/.vscode/launch.json index 1ab3567..957ff80 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,6 +15,21 @@ "outFiles": [ "${workspaceFolder}/**/*.js" ] + }, + { + "type": "node", + "name": "vscode-jest-tests", + "request": "launch", + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "cwd": "/workspace/src", + "runtimeExecutable": "yarn", + "args": [ + "jest", + "--runInBand", + "--watchAll=false" + ] } ] } \ No newline at end of file diff --git a/.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.9-7ef8e73557-1b9c4c87a6.zip b/.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.9-7ef8e73557-1b9c4c87a6.zip new file mode 100644 index 0000000..0af20fc Binary files /dev/null and b/.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.9-7ef8e73557-1b9c4c87a6.zip differ diff --git a/.yarn/cache/@types-bcrypt-npm-5.0.0-c074c165c2-063c32c7a5.zip b/.yarn/cache/@types-bcrypt-npm-5.0.0-c074c165c2-063c32c7a5.zip new file mode 100644 index 0000000..24c07c9 Binary files /dev/null and b/.yarn/cache/@types-bcrypt-npm-5.0.0-c074c165c2-063c32c7a5.zip differ diff --git a/.yarn/cache/aproba-npm-2.0.0-8716bcfde6-5615cadcfb.zip b/.yarn/cache/aproba-npm-2.0.0-8716bcfde6-5615cadcfb.zip new file mode 100644 index 0000000..6b14888 Binary files /dev/null and b/.yarn/cache/aproba-npm-2.0.0-8716bcfde6-5615cadcfb.zip differ diff --git a/.yarn/cache/are-we-there-yet-npm-2.0.0-7d2f5201ce-6c80b4fd04.zip b/.yarn/cache/are-we-there-yet-npm-2.0.0-7d2f5201ce-6c80b4fd04.zip new file mode 100644 index 0000000..41d8c66 Binary files /dev/null and b/.yarn/cache/are-we-there-yet-npm-2.0.0-7d2f5201ce-6c80b4fd04.zip differ diff --git a/.yarn/cache/bcrypt-npm-5.0.1-6815be1cfe-b59625519f.zip b/.yarn/cache/bcrypt-npm-5.0.1-6815be1cfe-b59625519f.zip new file mode 100644 index 0000000..7f16cd9 Binary files /dev/null and b/.yarn/cache/bcrypt-npm-5.0.1-6815be1cfe-b59625519f.zip differ diff --git a/.yarn/cache/color-support-npm-1.1.3-3be5c53455-9b73568176.zip b/.yarn/cache/color-support-npm-1.1.3-3be5c53455-9b73568176.zip new file mode 100644 index 0000000..625a79f Binary files /dev/null and b/.yarn/cache/color-support-npm-1.1.3-3be5c53455-9b73568176.zip differ diff --git a/.yarn/cache/detect-libc-npm-2.0.1-2699cb2ac4-ccb05fcabb.zip b/.yarn/cache/detect-libc-npm-2.0.1-2699cb2ac4-ccb05fcabb.zip new file mode 100644 index 0000000..f4bfff0 Binary files /dev/null and b/.yarn/cache/detect-libc-npm-2.0.1-2699cb2ac4-ccb05fcabb.zip differ diff --git a/.yarn/cache/gauge-npm-3.0.2-9e22f7af9e-81296c00c7.zip b/.yarn/cache/gauge-npm-3.0.2-9e22f7af9e-81296c00c7.zip new file mode 100644 index 0000000..92db251 Binary files /dev/null and b/.yarn/cache/gauge-npm-3.0.2-9e22f7af9e-81296c00c7.zip differ diff --git a/.yarn/cache/node-addon-api-npm-3.2.1-a29528f81d-2369986bb0.zip b/.yarn/cache/node-addon-api-npm-3.2.1-a29528f81d-2369986bb0.zip new file mode 100644 index 0000000..038beb4 Binary files /dev/null and b/.yarn/cache/node-addon-api-npm-3.2.1-a29528f81d-2369986bb0.zip differ diff --git a/.yarn/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip b/.yarn/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip new file mode 100644 index 0000000..db222e2 Binary files /dev/null and b/.yarn/cache/node-fetch-npm-2.6.7-777aa2a6df-8d816ffd1e.zip differ diff --git a/.yarn/cache/npmlog-npm-5.0.1-366cab64a2-516b266302.zip b/.yarn/cache/npmlog-npm-5.0.1-366cab64a2-516b266302.zip new file mode 100644 index 0000000..d2eec07 Binary files /dev/null and b/.yarn/cache/npmlog-npm-5.0.1-366cab64a2-516b266302.zip differ diff --git a/.yarn/cache/string-width-npm-4.2.3-2c27177bae-e52c10dc3f.zip b/.yarn/cache/string-width-npm-4.2.3-2c27177bae-e52c10dc3f.zip new file mode 100644 index 0000000..9b4c088 Binary files /dev/null and b/.yarn/cache/string-width-npm-4.2.3-2c27177bae-e52c10dc3f.zip differ diff --git a/.yarn/cache/tr46-npm-0.0.3-de53018915-726321c5ea.zip b/.yarn/cache/tr46-npm-0.0.3-de53018915-726321c5ea.zip new file mode 100644 index 0000000..2e6949b Binary files /dev/null and b/.yarn/cache/tr46-npm-0.0.3-de53018915-726321c5ea.zip differ diff --git a/.yarn/cache/webidl-conversions-npm-3.0.1-60310f6a2b-c92a0a6ab9.zip b/.yarn/cache/webidl-conversions-npm-3.0.1-60310f6a2b-c92a0a6ab9.zip new file mode 100644 index 0000000..96867a6 Binary files /dev/null and b/.yarn/cache/webidl-conversions-npm-3.0.1-60310f6a2b-c92a0a6ab9.zip differ diff --git a/.yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-b8daed4ad3.zip b/.yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-b8daed4ad3.zip new file mode 100644 index 0000000..5deef33 Binary files /dev/null and b/.yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-b8daed4ad3.zip differ diff --git a/.yarn/cache/wide-align-npm-1.1.5-889d77e592-d5fc37cd56.zip b/.yarn/cache/wide-align-npm-1.1.5-889d77e592-d5fc37cd56.zip new file mode 100644 index 0000000..4dc7fcc Binary files /dev/null and b/.yarn/cache/wide-align-npm-1.1.5-889d77e592-d5fc37cd56.zip differ diff --git a/package.json b/package.json index 2c1d74e..01d60c5 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "license": "Parity-7.0.0", "dependencies": { "badgen": "^3.2.2", + "bcrypt": "^5.0.1", "bson": "^4.6.2", "dotenv": "^16.0.0", "express": "^4.17.3", @@ -39,6 +40,7 @@ }, "devDependencies": { "@microsoft/tsdoc": "^0.13.2", + "@types/bcrypt": "^5.0.0", "@types/express": "^4.17.13", "@types/jest": "^27.4.1", "@types/jsdom": "^16.2.14", diff --git a/src/config.test.ts b/src/config.test.ts index b7c869f..456a6e3 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -2,177 +2,34 @@ const exit = jest .spyOn(process, "exit") .mockImplementation(() => undefined as never); -import { Writable } from "stream"; import winston from "winston"; -import loggerConfig from "./util/logger"; - -let output = ""; -const logger = winston.createLogger(loggerConfig("TEST", "debug")); - -jest.mock("./util/logger", () => { - const stream = new Writable(); - stream._write = (chunk, _encoding, next) => { - output = output += chunk.toString(); - next(); - }; - const streamTransport = new winston.transports.Stream({ stream }); - - return { - __esModule: true, - default: () => ({ - format: winston.format.combine( - winston.format.splat(), - winston.format.simple() - ), - transports: [streamTransport], - }), - }; -}); - import { configOrError, persistTemplate, handleStartup, handleShutdown, - initializeToken, } from "./config"; -import { - Logger, - MongoClient, - MongoError, - ReadConcern, - ReadPreference, - WriteConcern, -} from "mongodb"; +import { MongoClient, MongoError } from "mongodb"; import { Server } from "http"; import path from "path"; import fs from "fs"; + +import loggerConfig from "./util/logger"; import * as templates from "./templates"; -import { EnvConfig } from "./metadata"; - -const CommonMocks = { - connect: jest.fn(), - isConnected: jest.fn(), - logout: jest.fn(), - watch: jest.fn(), - startSession: jest.fn(), - withSession: jest.fn(), - addListener: jest.fn(), - on: jest.fn(), - once: jest.fn(), - prependListener: jest.fn(), - prependOnceListener: jest.fn(), - removeAllListeners: jest.fn(), - removeListener: jest.fn(), - off: jest.fn(), - setMaxListeners: jest.fn(), - getMaxListeners: jest.fn(), - listeners: jest.fn(), - listenerCount: jest.fn(), - rawListeners: jest.fn(), - emit: jest.fn(), - eventNames: jest.fn(), -}; - -const MongoMock = (p: Promise): jest.Mock => - jest.fn(() => ({ - ...CommonMocks, - close: jest.fn(() => p), - readPreference: ReadPreference.nearest, - bsonOptions: {}, - logger: new Logger("a"), - getLogger: jest.fn(), - options: { - hosts: [], - readPreference: ReadPreference.nearest, - readConcern: new ReadConcern("local"), - loadBalanced: true, - serverApi: { version: "1" }, - compressors: [], - writeConcern: new WriteConcern(), - dbName: "", - metadata: { - driver: { name: "", version: "" }, - os: { type: "", name: "linux", architecture: "", version: "" }, - platform: "linx", - }, - tls: true, - toURI: jest.fn(), - autoEncryption: {}, - connectTimeoutMS: 0, - directConnection: true, - driverInfo: {}, - forceServerObjectId: true, - minHeartbeatFrequencyMS: 0, - heartbeatFrequencyMS: 0, - keepAlive: false, - keepAliveInitialDelay: 0, - localThresholdMS: 0, - logger: new Logger("a"), - maxIdleTimeMS: 0, - maxPoolSize: 0, - minPoolSize: 0, - monitorCommands: true, - noDelay: true, - pkFactory: { createPk: jest.fn() }, - promiseLibrary: {}, - raw: true, - replicaSet: "", - retryReads: true, - retryWrites: true, - serverSelectionTimeoutMS: 0, - socketTimeoutMS: 0, - tlsAllowInvalidCertificates: true, - tlsAllowInvalidHostnames: true, - tlsInsecure: false, - waitQueueTimeoutMS: 0, - zlibCompressionLevel: 0, - srvMaxHosts: 1, - srvServiceName: "", - }, - serverApi: { version: "1" }, - autoEncrypter: undefined, - readConcern: new ReadConcern("local"), - writeConcern: new WriteConcern(), - db: jest.fn(), - })); -const ServerMock = (mockErr: Error | undefined): jest.Mock => - jest.fn(() => ({ - ...CommonMocks, - connections: 0, - setTimeout: jest.fn(), - timeout: 0, - headersTimeout: 0, - keepAliveTimeout: 0, - close: function (c: (err?: Error | undefined) => void): Server { - c(mockErr); - return this; - }, - maxHeadersCount: 0, - maxConnections: 0, - maxRequestsPerSocket: 0, - listen: jest.fn(), - listening: true, - address: jest.fn(), - getConnections: jest.fn(), - ref: jest.fn(), - requestTimeout: 3600, - unref: jest.fn(), - })); - -describe("initializeToken", () => { - it("Should generate a UUID", () => { - // Arrange - output = ""; +import Metadata, { EnvConfig } from "./metadata"; - // Act - const result = initializeToken(logger); +jest.mock("./util/logger", () => ({ + __esModule: true, + default: () => ({ + format: winston.format.combine( + winston.format.splat(), + winston.format.simple() + ), + transports: [new winston.transports.Console({ silent: true })], + }), +})); - // Assert - expect(result).toMatch(/([a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8})/); - expect(output).toContain(result); - }); -}); +const LOGGER = winston.createLogger(loggerConfig("TEST", "debug")); describe("configOrError", () => { beforeEach(() => { @@ -185,7 +42,7 @@ describe("configOrError", () => { // Act let result; try { - result = configOrError("APPLESAUCE", logger); + result = configOrError("APPLESAUCE", LOGGER); } catch (err) { // } @@ -200,7 +57,7 @@ describe("configOrError", () => { process.env.CHRYSANTHEMUM = "hello"; // Act - const result = configOrError("CHRYSANTHEMUM", logger); + const result = configOrError("CHRYSANTHEMUM", LOGGER); // Assert expect(result).toEqual(process.env.CHRYSANTHEMUM); @@ -226,7 +83,7 @@ describe("persistTemplate", () => { ); const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue(); - await persistTemplate(template, logger); + await persistTemplate(template, LOGGER); expect(processTemplate).toHaveBeenCalledWith(template); expect(fsAccess).not.toHaveBeenCalled(); @@ -246,7 +103,7 @@ describe("persistTemplate", () => { .mockRejectedValue("baa"); const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue(); - await persistTemplate(template, logger); + await persistTemplate(template, LOGGER); expect(processTemplate).toHaveBeenCalledWith(template); expect(fsAccess).toHaveBeenCalledWith( @@ -269,7 +126,7 @@ describe("persistTemplate", () => { .mockRejectedValue("baz"); const fsAccess = jest.spyOn(fs.promises, "access").mockRejectedValue("bar"); - await persistTemplate(template, logger); + await persistTemplate(template, LOGGER); expect(processTemplate).toHaveBeenCalledWith(template); expect(fsAccess).toHaveBeenCalledWith( @@ -290,40 +147,13 @@ describe("handleStartup", () => { const config = { hostDir: "/apple", publicDir: "/public", + dbUri: "localhost:27017", + dbName: "bongo", targetUrl: "localhost", + logLevel: "trace", } as EnvConfig; - const confStartup = (): Promise => handleStartup(config, logger); - - it("should pass back MongoClient", async () => { - const superClient = {} as MongoClient; - const fsAccess = jest.spyOn(fs.promises, "access").mockResolvedValue(); - const pathAbsolute = jest.spyOn(path, "isAbsolute").mockReturnValue(true); - const pathJoin = jest.spyOn(path, "join").mockReturnValue("path"); - const mongoClient = jest - .spyOn(MongoClient, "connect") - .mockImplementation( - () => new Promise((res) => res(superClient)) - ); - const processTemplate = jest - .spyOn(templates, "default") - .mockImplementation( - (template: templates.Template) => new Promise((res) => res(template)) - ); - - const result = await confStartup(); - - expect(fsAccess).toHaveBeenCalledTimes(1); - expect(pathAbsolute).toHaveBeenCalledTimes(1); - expect(mongoClient).toHaveBeenCalledTimes(1); - expect(processTemplate).toHaveBeenCalledTimes(2); - expect(exit).not.toHaveBeenCalled(); - expect(result).toEqual(superClient); - processTemplate.mockRestore(); - mongoClient.mockRestore(); - pathAbsolute.mockRestore(); - pathJoin.mockRestore(); - fsAccess.mockRestore(); - }); + const confStartup = (): Promise => + handleStartup(config, undefined, LOGGER); it("should exit if HOST_DIR is not read/write accessible", async () => { const superClient = {} as MongoClient; @@ -426,12 +256,18 @@ describe("handleShutdown", () => { it("should exit gracefully without error", async () => { // Arrange - const mongo = MongoMock(new Promise((r) => r()))(); - const server = ServerMock(undefined)(); + const metadata = { + close: () => Promise.resolve(), + } as Metadata; + const server = { + close: (callback?: ((err?: Error | undefined) => void) | undefined) => { + callback !== undefined && callback(); + }, + } as Server; // Act try { - await handleShutdown(mongo, server, logger)("SIGINT"); + await handleShutdown(metadata, server, LOGGER)("SIGINT"); } catch (err) { // } @@ -440,14 +276,20 @@ describe("handleShutdown", () => { expect(exit).toHaveBeenCalledWith(0); }); - it("should exit with error with Mongo error", async () => { + it("should exit with error with Metadata error", async () => { // Arrange - const mongo = MongoMock(new Promise((_, r) => r()))(); - const server = ServerMock(undefined)(); + const metadata = { + close: () => Promise.reject(), + } as Metadata; + const server = { + close: (callback?: ((err?: Error | undefined) => void) | undefined) => { + callback !== undefined && callback(); + }, + } as Server; // Act try { - await handleShutdown(mongo, server, logger)("SIGINT"); + await handleShutdown(metadata, server, LOGGER)("SIGINT"); } catch (err) { // } @@ -458,12 +300,18 @@ describe("handleShutdown", () => { it("should exit with error with Server error", async () => { // Arrange - const mongo = MongoMock(new Promise((r) => r()))(); - const server = ServerMock(Error("oh noooo"))(); + const metadata = { + close: () => Promise.resolve(), + } as Metadata; + const server = { + close: (callback?: ((err?: Error | undefined) => void) | undefined) => { + callback !== undefined && callback(Error("NOOO")); + }, + } as Server; // Act try { - await handleShutdown(mongo, server, logger)("SIGINT"); + await handleShutdown(metadata, server, LOGGER)("SIGINT"); } catch (err) { // } diff --git a/src/config.ts b/src/config.ts index 1908c10..4f4a970 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,28 +3,9 @@ import { MongoClient, MongoError } from "mongodb"; import { Server } from "http"; import path from "path"; import fs from "fs"; -import { v4 as uuid } from "uuid"; import processTemplate, { Template } from "./templates"; -import { EnvConfig } from "./metadata"; - -/** - * Generate a token for use as the user self-identifier - */ -export const initializeToken = (logger: winston.Logger): string => { - //TODO check for token in hostDir/persist created token in hostDir so it's not regenerated on startup - const newToken = uuid(); - - logger.warn( - "TOKEN variable not provided, using this value instead: %s", - newToken - ); - logger.warn( - "Use this provided token to push your coverage reports to the server." - ); - - return newToken; -}; +import Metadata, { EnvConfig } from "./metadata"; /** * Get environment variable or exit application if it doesn't exist @@ -84,8 +65,9 @@ export const persistTemplate = async ( */ export const handleStartup = async ( config: EnvConfig, + token: string | undefined, logger: winston.Logger -): Promise => { +): Promise => { try { const { hostDir, publicDir, dbUri, targetUrl } = config; await fs.promises.access(hostDir, fs.constants.R_OK | fs.constants.W_OK); @@ -119,7 +101,11 @@ export const handleStartup = async ( logger ); - return mongo; + const metadata = new Metadata(mongo, config); + + await metadata.initializeToken(token); + + return metadata; } catch (err) { logger.error("Error occurred during startup: %s", err); process.exit(1); @@ -131,13 +117,12 @@ export const handleStartup = async ( * and close open connections. */ export const handleShutdown = - (mongo: MongoClient, server: Server, logger: winston.Logger) => + (metadata: Metadata, server: Server, logger: winston.Logger) => async (signal: NodeJS.Signals): Promise => { logger.warn("%s signal received. Closing shop.", signal); try { - await mongo.close(); - logger.info("MongoDB client connection closed."); + await metadata.close(); // must await for callback - wrapped in Promise await new Promise((res, rej) => diff --git a/src/index.ts b/src/index.ts index ae15509..dcfc011 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,14 +5,9 @@ import expressWinston from "express-winston"; import path from "path"; import routes from "./routes"; -import Metadata, { EnvConfig } from "./metadata"; +import { EnvConfig } from "./metadata"; import loggerConfig from "./util/logger"; -import { - configOrError, - handleStartup, - handleShutdown, - initializeToken, -} from "./config"; +import { configOrError, handleStartup, handleShutdown } from "./config"; dotenv.config(); @@ -38,15 +33,13 @@ const ENV_CONFIG: EnvConfig = { hostDir: configOrError("HOST_DIR", logger), // Application configuration - token: process.env.TOKEN ?? initializeToken(logger), stage1: Number(process.env.STAGE_1 ?? 95), stage2: Number(process.env.STAGE_2 ?? 80), logLevel: LOG_LEVEL, }; -handleStartup(ENV_CONFIG, logger).then((mongo) => { +handleStartup(ENV_CONFIG, process.env.TOKEN, logger).then((metadata) => { const app: express.Application = express(); - const metadata = new Metadata(mongo.db(ENV_CONFIG.dbName), ENV_CONFIG); app.use( expressWinston.logger({ @@ -75,6 +68,6 @@ handleStartup(ENV_CONFIG, logger).then((mongo) => { // application exit handling const signalCodes: NodeJS.Signals[] = ["SIGTERM", "SIGHUP", "SIGINT"]; signalCodes.map((code: NodeJS.Signals) => { - process.on(code, handleShutdown(mongo, server, logger)); + process.on(code, handleShutdown(metadata, server, logger)); }); }); diff --git a/src/metadata.test.ts b/src/metadata.test.ts index 439bf7e..62a235c 100644 --- a/src/metadata.test.ts +++ b/src/metadata.test.ts @@ -1,18 +1,44 @@ -import Metadata, { isError, EnvConfig, HeadIdentity } from "./metadata"; import { Db, MongoClient, Collection } from "mongodb"; +import { Writable } from "stream"; +import winston from "winston"; import { BranchNotFoundError } from "./errors"; +import Metadata, { isError, EnvConfig, HeadIdentity } from "./metadata"; jest.mock("mongodb"); +let output = ""; +jest.mock("./util/logger", () => { + const stream = new Writable(); + stream._write = (chunk, _encoding, next) => { + output = output += chunk.toString(); + next(); + }; + const streamTransport = new winston.transports.Stream({ stream }); + + return { + __esModule: true, + default: () => ({ + format: winston.format.combine( + winston.format.splat(), + winston.format.simple() + ), + transports: [streamTransport], + }), + }; +}); + const defaultEnvConfig = { - token: "llama", uploadLimit: 12, hostDir: "pineapple", publicDir: ".dir", stage1: 132, stage2: 1.0, }; -const defaultMetadata = () => - new Metadata(new Db({} as MongoClient, ""), defaultEnvConfig as EnvConfig); +const defaultMetadata = () => { + jest + .spyOn(MongoClient.prototype, "db") + .mockImplementation(() => new Db({} as MongoClient, "")); + return new Metadata(new MongoClient(""), defaultEnvConfig as EnvConfig); +}; describe("isError", () => { it("should return false when object is a HeadContext", () => { @@ -372,16 +398,34 @@ describe("createRepository", () => { }); }); -describe("getToken", () => { - it("should return the token from EnvConfig", () => { +describe("initializeToken", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("Should generate a UUID", async () => { // Arrange const metadata = defaultMetadata(); + const collectionMethod = jest + .spyOn(Db.prototype, "collection") + .mockImplementation(() => new Collection()); + const countMethod = jest + .spyOn(Collection.prototype, "countDocuments") + .mockImplementation(() => Promise.resolve(0)); + const replaceMethod = jest + .spyOn(Collection.prototype, "findOneAndReplace") + .mockImplementation(() => Promise.resolve({ ok: 1 })); + output = ""; // Act - const result = metadata.getToken(); + const result = await metadata.initializeToken(); // Assert - expect(result).toEqual(defaultEnvConfig.token); + expect(result).toEqual(true); + expect(output).toMatch(/([a-f0-9]{8}(-[a-f0-9]{4}){4}[a-f0-9]{8})/); + expect(collectionMethod).toHaveBeenCalledTimes(2); + expect(countMethod).toHaveBeenCalledTimes(1); + expect(replaceMethod).toHaveBeenCalledTimes(1); }); }); diff --git a/src/metadata.ts b/src/metadata.ts index 184580f..8899e37 100644 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -1,5 +1,7 @@ -import { Db } from "mongodb"; +import { Db, MongoClient } from "mongodb"; import winston from "winston"; +import bcrypt from "bcrypt"; +import { v4 as uuid } from "uuid"; import loggerConfig from "./util/logger"; import { BranchNotFoundError } from "./errors"; @@ -18,6 +20,10 @@ interface BranchList { [branch: string]: Branch; } +interface SystemConfig { + tokenHashed: string; +} + export interface HeadIdentity { organization: string; repository: string; @@ -51,8 +57,6 @@ export interface EnvConfig { hostDir: string; /** The public directory for static files */ publicDir: string; - /** The application server token to serve as user self-identifier */ - token: string; /** Gradient setting 1 */ stage1: number; /** Gradient setting 2 */ @@ -74,14 +78,21 @@ export const isError = ( * Handles data routing for application */ class Metadata { - database: Db; + private dbClient: MongoClient; + private database: Db; config: EnvConfig; logger: winston.Logger; - constructor(client: Db, data: EnvConfig) { - this.logger = winston.createLogger(loggerConfig("META", data.logLevel)); - this.database = client; + constructor(client: MongoClient, data: EnvConfig) { + this.dbClient = client; + this.database = client.db(data.dbName); this.config = data; + this.logger = winston.createLogger(loggerConfig("META", data.logLevel)); + } + + async close(): Promise { + await this.dbClient.close(); + this.logger.info("Database client connection closed."); } /** @@ -157,10 +168,60 @@ class Metadata { } /** - * Retrieve the application token from configuration + * Check whether the provided token matches the hashed token */ - getToken(): string { - return this.config.token; + async checkToken(token: string): Promise { + const result = await this.database + .collection("sysconfig") + .findOne({}); + + if (result !== null) { + return bcrypt.compare(token, result.tokenHashed); + } else { + return Promise.reject(Error("No system configuration in place")); + } + } + + /** + * Generate a token for use as the user self-identifier. + * + * If the token is passed after it already exists, it will be overwritten. + */ + async initializeToken(token?: string | undefined): Promise { + const config = await this.database + .collection("sysconfig") + .countDocuments(); + + if (config > 0 && token === undefined) { + return true; + } + + const useToken = + token === undefined + ? (() => { + const newToken = uuid(); + + this.logger.warn( + "TOKEN variable not provided, using this value instead: %s", + newToken + ); + this.logger.warn( + "Use this provided token to push your coverage reports to the server." + ); + + return newToken; + })() + : token; + + const sysconfig = { + tokenHashed: await bcrypt.hash(useToken, 10), + }; + + const result = await this.database + .collection("sysconfig") + .findOneAndReplace({}, sysconfig, { upsert: true }); + + return result.ok === 1; } /** diff --git a/src/routes.test.ts b/src/routes.test.ts index f151d79..188cd13 100644 --- a/src/routes.test.ts +++ b/src/routes.test.ts @@ -24,7 +24,6 @@ jest.mock("./util/logger", () => ({ import loggerConfig from "./util/logger"; import dotenv from "dotenv"; -import { Db } from "mongodb"; dotenv.config(); const LOGGER = winston.createLogger(loggerConfig("TEST", "debug")); @@ -33,7 +32,7 @@ const HOST_DIR = (() => { const dir = path.join(__dirname, "..", "dist"); console.warn( - `WARNING: HOST_DIR is not set - this is used to query files in src/routes.test.ts. Using '${dir}' as default HOST_DIR.` + `HOST_DIR is not set - this is used to query files in src/routes.test.ts. Using '${dir}' as default HOST_DIR.` ); return dir; })(); @@ -41,7 +40,6 @@ const TARGET_URL = "https://localhost:3000"; const TOKEN = "THISISCORRECT"; const config: EnvConfig = { - token: TOKEN, // should be just larger than the example report used uploadLimit: Number(40000), hostDir: HOST_DIR, @@ -58,9 +56,7 @@ const config: EnvConfig = { const defaultMetadata = { logger: LOGGER, - database: {} as Db, config: config, - getToken: () => config.token, getUploadLimit: () => config.uploadLimit, getHostDir: () => config.hostDir, getPublicDir: () => config.publicDir, @@ -79,11 +75,14 @@ const defaultMetadata = { ), updateBranch: jest.fn(() => Promise.resolve(true)), createRepository: jest.fn(() => Promise.resolve(true)), + checkToken: (token: string) => Promise.resolve(token === TOKEN), + close: jest.fn(), }; const request = async (): Promise> => { const app = express(); - app.use(routes(defaultMetadata as Metadata)); + // The unknown is dbClient, since it's a private member. It's not used anyways for routes + app.use(routes(defaultMetadata as unknown as Metadata)); return _request(app); }; diff --git a/src/routes.ts b/src/routes.ts index 1c8bd07..a9006e5 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -89,61 +89,68 @@ const routes = (metadata: Metadata): Router => { ); // Upload file - router.post("/v1/:org/:repo/:branch/:commit.:ext(html|xml)", (req, res) => { - const { org, repo, branch, commit } = req.params; - - const { token, format } = req.query; - if (token != metadata.getToken()) { - return res.status(401).send(Messages.InvalidToken); - } + router.post( + "/v1/:org/:repo/:branch/:commit.:ext(html|xml)", + async (req, res) => { + const { org, repo, branch, commit } = req.params; - if (typeof format !== "string" || !formats.listFormats().includes(format)) { - return res.status(406).send(Messages.InvalidFormat); - } + const { token, format } = req.query; + const clarifiedToken = (token ?? "").toString(); + if (!(await metadata.checkToken(clarifiedToken))) { + return res.status(401).send(Messages.InvalidToken); + } - const limit = metadata.getUploadLimit(); - if (Number(req.headers["content-length"] ?? 0) > limit) { - return res.status(413).send(Messages.FileTooLarge); - } + if ( + typeof format !== "string" || + !formats.listFormats().includes(format) + ) { + return res.status(406).send(Messages.InvalidFormat); + } - let contents = ""; - req.on("data", (raw) => { - if (contents.length <= limit) { - contents += raw; + const limit = metadata.getUploadLimit(); + if (Number(req.headers["content-length"] ?? 0) > limit) { + return res.status(413).send(Messages.FileTooLarge); } - }); - req.on("end", async () => { - const formatter = formats.getFormat(format); - const identity = { - organization: org, - repository: repo, - branch, - head: { commit, format }, - }; - try { - const result = await commitFormatDocs(contents, identity, formatter); + let contents = ""; + req.on("data", (raw) => { + if (contents.length <= limit) { + contents += raw; + } + }); + req.on("end", async () => { + const formatter = formats.getFormat(format); + const identity = { + organization: org, + repository: repo, + branch, + head: { commit, format }, + }; + + try { + const result = await commitFormatDocs(contents, identity, formatter); - if (typeof result === "boolean") { - if (result) { - return res.status(200).send(); + if (typeof result === "boolean") { + if (result) { + return res.status(200).send(); + } else { + logger.error( + "Unknown error while attempting to commit branch update" + ); + return res.status(500).send(Messages.UnknownError); + } } else { - logger.error( - "Unknown error while attempting to commit branch update" - ); - return res.status(500).send(Messages.UnknownError); + return res.status(400).send(Messages.InvalidFormat); } - } else { - return res.status(400).send(Messages.InvalidFormat); + } catch (err) { + logger.error( + err ?? "Unknown error occurred while processing POST request" + ); + return res.status(500).send(Messages.UnknownError); } - } catch (err) { - logger.error( - err ?? "Unknown error occurred while processing POST request" - ); - return res.status(500).send(Messages.UnknownError); - } - }); - }); + }); + } + ); /** * Read a file from the host directory. diff --git a/yarn.lock b/yarn.lock index 62c0ae6..dc8cd1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -967,6 +967,25 @@ __metadata: languageName: node linkType: hard +"@mapbox/node-pre-gyp@npm:^1.0.0": + version: 1.0.9 + resolution: "@mapbox/node-pre-gyp@npm:1.0.9" + dependencies: + detect-libc: ^2.0.0 + https-proxy-agent: ^5.0.0 + make-dir: ^3.1.0 + node-fetch: ^2.6.7 + nopt: ^5.0.0 + npmlog: ^5.0.1 + rimraf: ^3.0.2 + semver: ^7.3.5 + tar: ^6.1.11 + bin: + node-pre-gyp: bin/node-pre-gyp + checksum: 1b9c4c87a68d200daa13151d0fe033aa7aa8f7b26f3585255424dd8dfee2ec672c3e9bea4071c624469bc0aebbbcde08f8a300c8a958db52c50abadd5fb56920 + languageName: node + linkType: hard + "@microsoft/tsdoc-config@npm:0.15.2": version: 0.15.2 resolution: "@microsoft/tsdoc-config@npm:0.15.2" @@ -1106,6 +1125,15 @@ __metadata: languageName: node linkType: hard +"@types/bcrypt@npm:^5.0.0": + version: 5.0.0 + resolution: "@types/bcrypt@npm:5.0.0" + dependencies: + "@types/node": "*" + checksum: 063c32c7a519d64768dfc0169a319b8244d6a6cb50a355c93992b3c5fee1dbc236526a1111f0e7bb25abc8b0473e5f40a5edfeb8b33cad2a6ea35aa2d7d7db14 + languageName: node + linkType: hard + "@types/body-parser@npm:*": version: 1.19.1 resolution: "@types/body-parser@npm:1.19.1" @@ -1671,6 +1699,7 @@ __metadata: resolution: "ao-coverage@workspace:." dependencies: "@microsoft/tsdoc": ^0.13.2 + "@types/bcrypt": ^5.0.0 "@types/express": ^4.17.13 "@types/jest": ^27.4.1 "@types/jsdom": ^16.2.14 @@ -1682,6 +1711,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^5.18.0 "@typescript-eslint/parser": ^5.18.0 badgen: ^3.2.2 + bcrypt: ^5.0.1 bson: ^4.6.2 dotenv: ^16.0.0 eslint: ^8.12.0 @@ -1716,6 +1746,23 @@ __metadata: languageName: node linkType: hard +"aproba@npm:^1.0.3 || ^2.0.0": + version: 2.0.0 + resolution: "aproba@npm:2.0.0" + checksum: 5615cadcfb45289eea63f8afd064ab656006361020e1735112e346593856f87435e02d8dcc7ff0d11928bc7d425f27bc7c2a84f6c0b35ab0ff659c814c138a24 + languageName: node + linkType: hard + +"are-we-there-yet@npm:^2.0.0": + version: 2.0.0 + resolution: "are-we-there-yet@npm:2.0.0" + dependencies: + delegates: ^1.0.0 + readable-stream: ^3.6.0 + checksum: 6c80b4fd04ecee6ba6e737e0b72a4b41bdc64b7d279edfc998678567ff583c8df27e27523bc789f2c99be603ffa9eaa612803da1d886962d2086e7ff6fa90c7c + languageName: node + linkType: hard + "are-we-there-yet@npm:~1.1.2": version: 1.1.7 resolution: "are-we-there-yet@npm:1.1.7" @@ -1875,6 +1922,16 @@ __metadata: languageName: node linkType: hard +"bcrypt@npm:^5.0.1": + version: 5.0.1 + resolution: "bcrypt@npm:5.0.1" + dependencies: + "@mapbox/node-pre-gyp": ^1.0.0 + node-addon-api: ^3.1.0 + checksum: b59625519f2b2891010b8094208588462b1c759ccacebfd74f0b9a4c1885743434ede246c26b615b94a5cf203dfcb9eb25a1e8dec315afd3098da2b848c0fa12 + languageName: node + linkType: hard + "body-parser@npm:1.19.2": version: 1.19.2 resolution: "body-parser@npm:1.19.2" @@ -2210,6 +2267,15 @@ __metadata: languageName: node linkType: hard +"color-support@npm:^1.1.2": + version: 1.1.3 + resolution: "color-support@npm:1.1.3" + bin: + color-support: bin.js + checksum: 9b7356817670b9a13a26ca5af1c21615463b500783b739b7634a0c2047c16cef4b2865d7576875c31c3cddf9dd621fa19285e628f20198b233a5cfdda6d0793b + languageName: node + linkType: hard + "color@npm:3.0.x": version: 3.0.0 resolution: "color@npm:3.0.0" @@ -2260,7 +2326,7 @@ __metadata: languageName: node linkType: hard -"console-control-strings@npm:^1.0.0, console-control-strings@npm:~1.1.0": +"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0, console-control-strings@npm:~1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed @@ -2479,6 +2545,13 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^2.0.0": + version: 2.0.1 + resolution: "detect-libc@npm:2.0.1" + checksum: ccb05fcabbb555beb544d48080179c18523a343face9ee4e1a86605a8715b4169f94d663c21a03c310ac824592f2ba9a5270218819bb411ad7be578a527593d7 + languageName: node + linkType: hard + "detect-newline@npm:^3.0.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" @@ -3226,6 +3299,23 @@ fsevents@^2.3.2: languageName: node linkType: hard +"gauge@npm:^3.0.0": + version: 3.0.2 + resolution: "gauge@npm:3.0.2" + dependencies: + aproba: ^1.0.3 || ^2.0.0 + color-support: ^1.1.2 + console-control-strings: ^1.0.0 + has-unicode: ^2.0.1 + object-assign: ^4.1.1 + signal-exit: ^3.0.0 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wide-align: ^1.1.2 + checksum: 81296c00c7410cdd48f997800155fbead4f32e4f82109be0719c63edc8560e6579946cc8abd04205297640691ec26d21b578837fd13a4e96288ab4b40b1dc3e9 + languageName: node + linkType: hard + "gauge@npm:~2.7.3": version: 2.7.4 resolution: "gauge@npm:2.7.4" @@ -3410,7 +3500,7 @@ fsevents@^2.3.2: languageName: node linkType: hard -"has-unicode@npm:^2.0.0": +"has-unicode@npm:^2.0.0, has-unicode@npm:^2.0.1": version: 2.0.1 resolution: "has-unicode@npm:2.0.1" checksum: 1eab07a7436512db0be40a710b29b5dc21fa04880b7f63c9980b706683127e3c1b57cb80ea96d47991bdae2dfe479604f6a1ba410106ee1046a41d1bd0814400 @@ -4604,7 +4694,7 @@ fsevents@^2.3.2: languageName: node linkType: hard -"make-dir@npm:^3.0.0": +"make-dir@npm:^3.0.0, make-dir@npm:^3.1.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" dependencies: @@ -4949,6 +5039,15 @@ fsevents@^2.3.2: languageName: node linkType: hard +"node-addon-api@npm:^3.1.0": + version: 3.2.1 + resolution: "node-addon-api@npm:3.2.1" + dependencies: + node-gyp: latest + checksum: 2369986bb0881ccd9ef6bacdf39550e07e089a9c8ede1cbc5fc7712d8e2faa4d50da0e487e333d4125f8c7a616c730131d1091676c9d499af1d74560756b4a18 + languageName: node + linkType: hard + "node-cleanup@npm:^2.1.2": version: 2.1.2 resolution: "node-cleanup@npm:2.1.2" @@ -4956,6 +5055,20 @@ fsevents@^2.3.2: languageName: node linkType: hard +"node-fetch@npm:^2.6.7": + version: 2.6.7 + resolution: "node-fetch@npm:2.6.7" + dependencies: + whatwg-url: ^5.0.0 + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 8d816ffd1ee22cab8301c7756ef04f3437f18dace86a1dae22cf81db8ef29c0bf6655f3215cb0cdb22b420b6fe141e64b26905e7f33f9377a7fa59135ea3e10b + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 8.2.0 resolution: "node-gyp@npm:8.2.0" @@ -5036,6 +5149,18 @@ fsevents@^2.3.2: languageName: node linkType: hard +"npmlog@npm:^5.0.1": + version: 5.0.1 + resolution: "npmlog@npm:5.0.1" + dependencies: + are-we-there-yet: ^2.0.0 + console-control-strings: ^1.1.0 + gauge: ^3.0.0 + set-blocking: ^2.0.0 + checksum: 516b2663028761f062d13e8beb3f00069c5664925871a9b57989642ebe09f23ab02145bf3ab88da7866c4e112cafff72401f61a672c7c8a20edc585a7016ef5f + languageName: node + linkType: hard + "number-is-nan@npm:^1.0.0": version: 1.0.1 resolution: "number-is-nan@npm:1.0.1" @@ -5050,7 +5175,7 @@ fsevents@^2.3.2: languageName: node linkType: hard -"object-assign@npm:^4.1.0": +"object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -5708,7 +5833,7 @@ resolve@~1.19.0: languageName: node linkType: hard -"set-blocking@npm:~2.0.0": +"set-blocking@npm:^2.0.0, set-blocking@npm:~2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" checksum: 6e65a05f7cf7ebdf8b7c75b101e18c0b7e3dff4940d480efed8aad3a36a4005140b660fa1d804cb8bce911cac290441dc728084a30504d3516ac2ff7ad607b02 @@ -5963,6 +6088,17 @@ resolve@~1.19.0: languageName: node linkType: hard +"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: ^8.0.0 + is-fullwidth-code-point: ^3.0.0 + strip-ansi: ^6.0.1 + checksum: e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb + languageName: node + linkType: hard + "string-width@npm:^4.1.0, string-width@npm:^4.2.0": version: 4.2.2 resolution: "string-width@npm:4.2.2" @@ -6122,7 +6258,7 @@ resolve@~1.19.0: languageName: node linkType: hard -"tar@npm:^6.0.2, tar@npm:^6.1.2": +"tar@npm:^6.0.2, tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.1.11 resolution: "tar@npm:6.1.11" dependencies: @@ -6244,6 +6380,13 @@ resolve@~1.19.0: languageName: node linkType: hard +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 726321c5eaf41b5002e17ffbd1fb7245999a073e8979085dacd47c4b4e8068ff5777142fc6726d6ca1fd2ff16921b48788b87225cbc57c72636f6efa8efbffe3 + languageName: node + linkType: hard + "triple-beam@npm:^1.3.0": version: 1.3.0 resolution: "triple-beam@npm:1.3.0" @@ -6562,6 +6705,13 @@ typescript@^4.6.3: languageName: node linkType: hard +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: c92a0a6ab95314bde9c32e1d0a6dfac83b578f8fa5f21e675bc2706ed6981bc26b7eb7e6a1fab158e5ce4adf9caa4a0aee49a52505d4d13c7be545f15021b17c + languageName: node + linkType: hard + "webidl-conversions@npm:^5.0.0": version: 5.0.0 resolution: "webidl-conversions@npm:5.0.0" @@ -6635,6 +6785,16 @@ typescript@^4.6.3: languageName: node linkType: hard +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: ~0.0.3 + webidl-conversions: ^3.0.0 + checksum: b8daed4ad3356cc4899048a15b2c143a9aed0dfae1f611ebd55073310c7b910f522ad75d727346ad64203d7e6c79ef25eafd465f4d12775ca44b90fa82ed9e2c + languageName: node + linkType: hard + "whatwg-url@npm:^8.0.0, whatwg-url@npm:^8.5.0": version: 8.7.0 resolution: "whatwg-url@npm:8.7.0" @@ -6666,6 +6826,15 @@ typescript@^4.6.3: languageName: node linkType: hard +"wide-align@npm:^1.1.2": + version: 1.1.5 + resolution: "wide-align@npm:1.1.5" + dependencies: + string-width: ^1.0.2 || 2 || 3 || 4 + checksum: d5fc37cd561f9daee3c80e03b92ed3e84d80dde3365a8767263d03dacfc8fa06b065ffe1df00d8c2a09f731482fcacae745abfbb478d4af36d0a891fad4834d3 + languageName: node + linkType: hard + "winston-transport@npm:^4.5.0": version: 4.5.0 resolution: "winston-transport@npm:4.5.0" -- cgit