From 11b9648d259aa8ab33716991313359e3a3aaaa4d Mon Sep 17 00:00:00 2001 From: Gimmy Date: Wed, 3 Jul 2024 12:44:16 +0300 Subject: [PATCH] Certificate option name consistency --- README.md | 18 +-- __tests__/integration/index.test.ts | 111 ++++++++++++++- __tests__/is.test.ts | 4 +- __tests__/jwtAuthz.test.ts | 53 ------- __tests__/middleware.test.ts | 10 +- lib/authorizer/index.ts | 6 +- lib/displayStateMap.ts | 20 +-- lib/is.ts | 2 +- lib/jwtAuthz.ts | 7 +- lib/processOptions.ts | 6 +- package.json | 2 + yarn.lock | 206 +++++++++++++++++++++++++++- 12 files changed, 345 insertions(+), 100 deletions(-) delete mode 100644 __tests__/jwtAuthz.test.ts diff --git a/README.md b/README.md index 4670983..4d3f01c 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ type AuthorizerConfig = { tenantId?: string; authorizerApiKey?: string; token?: string; - authorizerCertFile?: string; + caFile?: string; insecure?: boolean; }; ``` @@ -63,14 +63,14 @@ const authClient = new Authorizer({ - `authorizerServiceUrl`: hostname:port of authorizer service (_required_) - `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer) - `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer) -- `authorizerCertFile`: Path to the authorizer CA file. (optional) +- `caFile`: Path to the authorizer CA file. (optional) - `insecure`: Skip server certificate and domain verification. (NOT SECURE!). Defaults to `false`. ### Topaz ```ts const authClient = new Authorizer({ authorizerServiceUrl: "localhost:8282", - authorizerCertFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt` + caFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt` }); ``` @@ -86,7 +86,7 @@ import { const authClient = new Authorizer( { authorizerServiceUrl: "localhost:8282", - authorizerCertFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt` + caFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt` }, ); @@ -820,7 +820,7 @@ By default, `jwtAuthz` derives the policy file name and resource key from the Ex - `instanceLabel`: instance label (_required_ if using hosted authorizer) - `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer) - `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer) -- `authorizerCertFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information. +- `caFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information. - `disableTlsValidation`: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false. - `failWithError`: When set to `true`, will forward errors to `next` instead of ending the response directly. - `useAuthorizationHeader`: When set to `true`, will forward the Authorization header to the authorizer. The authorizer will crack open the JWT and use that as the identity context. Defaults to `true`. @@ -869,7 +869,7 @@ app.use(displayStateMap(options)); - `instanceLabel`: instance label (_required_ if using hosted authorizer) - `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer) - `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer) -- `authorizerCertFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information. +- `caFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information. - `disableTlsValidation`: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false. - `endpointPath`: display state map endpoint path, defaults to `/__displaystatemap`. - `failWithError`: When set to `true`, will forward errors to `next` instead of ending the response directly. Defaults to `false`. @@ -932,7 +932,7 @@ The Express request object. - `instanceLabel`: instance label (_required_ if using hosted authorizer) - `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer) - `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer) -- `authorizerCertFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information. +- `caFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information. - `disableTlsValidation`: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false. - `useAuthorizationHeader`: When set to `true`, will forward the Authorization header to the authorizer. The authorizer will crack open the JWT and use that as the identity context. Defaults to `true`. - `identityHeader`: the name of the header from which to extract the `identity` field to pass into the `authorize` call. This only happens if `useAuthorizationHeader` is false. Defaults to 'identity'. @@ -963,9 +963,9 @@ For a hosted authorizer that has a TLS certificate that is signed by a trusted C In a development environment, [topaz](github.com/aserto-dev/topaz) automatically creates a set of self-signed certificates and certificates of the CA (certificate authority) that signed them. It places them in a well-known location on the filesystem, defaulting to `$HOME/.local/share/topaz/certs/` (or `$HOMEPATH\AppData\Local\topaz\certs\` on Windows). -In order for the `aserto-node` package to perform the TLS handshake, it needs to verify the TLS certificate of Topaz using the certificate of the CA that signed it - which was placed in `$HOME/.local/share/topaz/certs/grpc-ca.crt`. Therefore, in order for this middleware to work successfully, either the `authorizerCertFile` must be set to the correct path for the CA cert file, or the `disableTlsValidation` flag must be set to `true`. The same is true for the `caFile` argument of the `DirectoryClient`. +In order for the `aserto-node` package to perform the TLS handshake, it needs to verify the TLS certificate of Topaz using the certificate of the CA that signed it - which was placed in `$HOME/.local/share/topaz/certs/grpc-ca.crt`. Therefore, in order for this middleware to work successfully, either the `caFile` must be set to the correct path for the CA cert file, or the `disableTlsValidation` flag must be set to `true`. The same is true for the `caFile` argument of the `DirectoryClient`. -Furthermore, when packaging a policy for deployment (e.g. in a Docker container) which uses `aserto-node` to communicate with an authorizer that has a self-signed TLS certificate, you must copy this CA certificate into the container as part of the Docker build (typically performed in the Dockerfile). When you do that, you'll need to override the `authorizerCertFile` option that is passed into any of the API calls defined above with the location of this cert file. +Furthermore, when packaging a policy for deployment (e.g. in a Docker container) which uses `aserto-node` to communicate with an authorizer that has a self-signed TLS certificate, you must copy this CA certificate into the container as part of the Docker build (typically performed in the Dockerfile). When you do that, you'll need to override the `caFile` option that is passed into any of the API calls defined above with the location of this cert file. Alternately, to ignore TLS certificate validation when creating a TLS connection to the authorizer, you can set the `disableTlsValidation` option to `true` and avoid TLS certificate validation. This option is **not recommended for production**. diff --git a/__tests__/integration/index.test.ts b/__tests__/integration/index.test.ts index ade891a..bdb4a50 100644 --- a/__tests__/integration/index.test.ts +++ b/__tests__/integration/index.test.ts @@ -1,9 +1,14 @@ +import express, { Express } from "express"; +import nJwt from "njwt"; +import request from "supertest"; + import { AnonymousIdentityMapper, Authorizer, createAsyncIterable, DirectoryServiceV3, DirectoryV3, + displayStateMap, EtagMismatchError, ImportMsgCase, ImportOpCode, @@ -502,7 +507,7 @@ types: beforeEach(async () => { authorizerClient = new Authorizer({ authorizerServiceUrl: "localhost:8282", - authorizerCertFile: await topaz.caCert(), + caFile: await topaz.caCert(), }); }); @@ -530,4 +535,108 @@ types: }); }); }); + + describe("DisplayStateMap", () => { + const app: Express = express(); + const jwt = nJwt.create({ sub: "rick@the-citadel.com" }, "signingKey"); + + it("returns the correct data", async () => { + const options = { + policyRoot: "todoApp", + instanceLabel: "todo", + instanceName: "todo", + authorizerServiceUrl: "localhost:8282", + caFile: await topaz.caCert(), + failWithError: true, + }; + app.use( + displayStateMap( + options, + undefined, + () => { + return AnonymousIdentityMapper(); + }, + () => { + return new Promise((resolve) => { + resolve(policyContext("todoApp", ["allowed"])); + }); + } + ) + ); + + const response = await request(app) + .get("/__displaystatemap") + .set("Content-type", "application/json") + .set("Authorization", `Bearer ${jwt}`); + expect(response.body).toEqual({ + "todoApp/DELETE/todos/__id": { + allowed: false, + }, + "todoApp/GET/todos": { + allowed: true, + }, + "todoApp/GET/users/__userID": { + allowed: true, + }, + "todoApp/POST/todos": { + allowed: false, + }, + "todoApp/PUT/todos/__id": { + allowed: false, + }, + }); + }); + }); + + describe("DisplayStateMap Legacy `authorizerCertCAFile`", () => { + const app: Express = express(); + const jwt = nJwt.create({ sub: "rick@the-citadel.com" }, "signingKey"); + + it("returns the correct data", async () => { + const options = { + policyRoot: "todoApp", + instanceLabel: "todo", + instanceName: "todo", + authorizerServiceUrl: "localhost:8282", + authorizerCertCAFile: await topaz.caCert(), + failWithError: true, + }; + app.use( + displayStateMap( + options, + undefined, + () => { + return AnonymousIdentityMapper(); + }, + () => { + return new Promise((resolve) => { + resolve(policyContext("todoApp", ["allowed"])); + }); + } + ) + ); + + const response = await request(app) + .get("/__displaystatemap") + .set("Content-type", "application/json") + .set("Authorization", `Bearer ${jwt}`); + expect(response.body).toEqual({ + "todoApp/DELETE/todos/__id": { + allowed: false, + }, + "todoApp/GET/todos": { + allowed: true, + }, + "todoApp/GET/users/__userID": { + allowed: true, + }, + "todoApp/POST/todos": { + allowed: false, + }, + "todoApp/PUT/todos/__id": { + allowed: false, + }, + }); + }); + }); }); diff --git a/__tests__/is.test.ts b/__tests__/is.test.ts index b87c58e..abee0c4 100644 --- a/__tests__/is.test.ts +++ b/__tests__/is.test.ts @@ -39,7 +39,7 @@ describe("should succeed", () => { policyId: "123", authorizerServiceUrl: "localhost:8282", identityHeader: "Authorization", - authorizerCertCAFile: process.env.CA_FILE!, + caFile: process.env.CA_FILE!, }; const allowed = await is(decision, req, options, packageName, resourceMap); @@ -66,7 +66,7 @@ describe("should succeed", () => { policyId: "123", authorizerServiceUrl: "localhost:8282", identityHeader: "Authorization", - authorizerCertCAFile: process.env.CA_FILE!, + caFike: process.env.CA_FILE!, }; const allowed = await is(decision, req, options); diff --git a/__tests__/jwtAuthz.test.ts b/__tests__/jwtAuthz.test.ts deleted file mode 100644 index 1919254..0000000 --- a/__tests__/jwtAuthz.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import dotenv from "dotenv"; -import { NextFunction, Request, Response } from "express"; -import nJwt from "njwt"; -import httpMocks from "node-mocks-http"; - -import { Authorizer } from "../lib/authorizer"; -import { jwtAuthz } from "../lib/jwtAuthz"; - -dotenv.config(); - -describe("should succeed", () => { - beforeEach(() => { - jest.spyOn(Authorizer.prototype, "Is").mockImplementation(() => { - return new Promise((resolve) => { - resolve(true); - }); - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - const jwt = nJwt.create({}, "signingKey"); - - const request: Request = httpMocks.createRequest({ - method: "GET", - route: { - path: "/todos", - }, - headers: { - authorization: `Bearer ${jwt}`, - }, - }); - - it("returns a boolean when all required arguments, packageName, and resourceMap are provided as inputs", async () => { - const options = { - policyRoot: "todoApp", - policyName: "todoApp", - policyId: "123", - authorizerServiceUrl: "localhost:8282", - identityHeader: "Authorization", - authorizerCertCAFile: process.env.CA_FILE!, - failWithError: true, - }; - - const next = jest.fn() as NextFunction; - const res: Response = httpMocks.createResponse(); - const response = jwtAuthz(options); - - await response(request, res, next); - expect(next).toBeCalled(); - }); -}); diff --git a/__tests__/middleware.test.ts b/__tests__/middleware.test.ts index e124896..4b347c7 100644 --- a/__tests__/middleware.test.ts +++ b/__tests__/middleware.test.ts @@ -51,7 +51,6 @@ describe("Middleware", () => { const response = middleware.Check(options); await response(request, res, next); - expect(next).toBeCalled(); expect(next).toHaveBeenCalled(); }); @@ -97,7 +96,7 @@ describe("Middleware", () => { const response = middleware.Check(options); await response(request, res, next); - expect(next).toBeCalledWith({ + expect(next).toHaveBeenCalledWith({ statusCode: 403, error: "Forbidden", message: `aserto-node: Forbidden by policy examplePolicy.check`, @@ -148,7 +147,7 @@ describe("Middleware", () => { const response = middleware.Check(options); await response(request, res, next); - expect(next).toBeCalledWith({ + expect(next).toHaveBeenCalledWith({ statusCode: 403, error: "Forbidden", message: `aserto-node: Authorizer service unavailable`, @@ -188,7 +187,6 @@ describe("Middleware", () => { const response = middleware.Authz(); await response(request, res, next); - expect(next).toBeCalled(); expect(next).toHaveBeenCalled(); }); @@ -228,7 +226,7 @@ describe("Middleware", () => { const response = middleware.Authz(); await response(request, res, next); - expect(next).toBeCalledWith({ + expect(next).toHaveBeenCalledWith({ statusCode: 403, error: "Forbidden", message: `aserto-node: Forbidden by policy examplePolicy.GET.todos`, @@ -272,7 +270,7 @@ describe("Middleware", () => { const response = middleware.Authz(); await response(request, res, next); - expect(next).toBeCalledWith({ + expect(next).toHaveBeenCalledWith({ statusCode: 403, error: "Forbidden", message: `aserto-node: Authorizer service unavailable`, diff --git a/lib/authorizer/index.ts b/lib/authorizer/index.ts index 4442182..318af56 100644 --- a/lib/authorizer/index.ts +++ b/lib/authorizer/index.ts @@ -24,6 +24,7 @@ type AuthorizerConfig = { authorizerApiKey?: string; token?: string; authorizerCertFile?: string; + caFile?: string; insecure?: boolean; }; @@ -50,9 +51,8 @@ export class Authorizer { const baseServiceUrl = config.authorizerServiceUrl || "authorizer.prod.aserto.com:8443"; - const baseCaFile = !!config.authorizerCertFile - ? readFileSync(config.authorizerCertFile) - : undefined; + const caFilePath = config.authorizerCertFile || config.caFile; + const baseCaFile = !!caFilePath ? readFileSync(caFilePath) : undefined; const insecure = config?.insecure || false; const baseNodeOptions = { diff --git a/lib/displayStateMap.ts b/lib/displayStateMap.ts index 2d082ac..999c0ef 100644 --- a/lib/displayStateMap.ts +++ b/lib/displayStateMap.ts @@ -1,6 +1,7 @@ import { NextFunction, Request, Response } from "express"; import { IdentityContext } from "@aserto/node-authorizer/src/gen/cjs/aserto/authorizer/v2/api/identity_context_pb"; +import { AuthzOptions } from "."; import { Authorizer } from "./authorizer"; import BodyResourceMapper from "./authorizer/mapper/resource/body"; import { @@ -15,22 +16,9 @@ import { errorHandler } from "./errorHandler"; import identityContext from "./identityContext"; import processOptions from "./processOptions"; -interface DisplayStateMapOptions { - policyRoot: string; - instanceName: string; - instanceLabel?: string; - authorizerServiceUrl: string; - authorizerApiKey?: string; - tenantId?: string; - authorizerCertCAFile?: string; - disableTlsValidation?: boolean; - useAuthorizationHeader?: boolean; - identityHeader?: string; - failWithError?: boolean; - customUserKey?: string; - customSubjectKey?: string; +type DisplayStateMapOptions = AuthzOptions & { endpointPath?: string; -} +}; const displayStateMap = ( optionsParam: DisplayStateMapOptions, resourceMapper?: ResourceMapper, @@ -81,7 +69,7 @@ const displayStateMap = ( authorizerServiceUrl: authorizerUrl, tenantId: tenantId!, authorizerApiKey: authorizerApiKey!, - authorizerCertFile: authorizerCertCAFile, + caFile: authorizerCertCAFile, insecure: disableTlsValidation, }); diff --git a/lib/is.ts b/lib/is.ts index 5a65121..170c942 100644 --- a/lib/is.ts +++ b/lib/is.ts @@ -48,7 +48,7 @@ const is = async ( authorizerServiceUrl: authorizerUrl, tenantId: tenantId!, authorizerApiKey: authorizerApiKey!, - authorizerCertFile: authorizerCertCAFile, + caFile: authorizerCertCAFile, insecure: disableTlsValidation, }); diff --git a/lib/jwtAuthz.ts b/lib/jwtAuthz.ts index 9d6b357..46bc079 100644 --- a/lib/jwtAuthz.ts +++ b/lib/jwtAuthz.ts @@ -16,7 +16,7 @@ import identityContext from "./identityContext"; import processOptions from "./processOptions"; import { processParams } from "./processParams"; -export interface AuthzOptions { +export type AuthzOptions = { policyRoot: string; instanceName?: string; instanceLabel?: string; @@ -30,7 +30,8 @@ export interface AuthzOptions { failWithError?: boolean; customUserKey?: string; customSubjectKey?: string; -} + caFile?: string; +}; const jwtAuthz = ( optionsParam: AuthzOptions, @@ -75,7 +76,7 @@ const jwtAuthz = ( authorizerServiceUrl: authorizerUrl, tenantId: tenantId!, authorizerApiKey: authorizerApiKey!, - authorizerCertFile: authorizerCertCAFile, + caFile: authorizerCertCAFile, insecure: disableTlsValidation, }); diff --git a/lib/processOptions.ts b/lib/processOptions.ts index df40934..99ef6d4 100644 --- a/lib/processOptions.ts +++ b/lib/processOptions.ts @@ -69,10 +69,8 @@ export default ( disableTlsValidation = options.disableTlsValidation; } - let authorizerCertCAFile: string = ""; - if (options && options.authorizerCertCAFile) { - authorizerCertCAFile = options.authorizerCertCAFile; - } + const authorizerCertCAFile = + options.authorizerCertCAFile || options.caFile || ""; const instanceName = options && typeof options.instanceName === "string" && options.instanceName; diff --git a/package.json b/package.json index 19e8d39..1ea420c 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@types/express": "^4.17.14", "@types/jest": "^29.1.2", "@types/node": "^20.3.1", + "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", "dotenv": "^16.0.3", @@ -69,6 +70,7 @@ "njwt": "^2.0.0", "node-mocks-http": "^1.13.0", "prettier": "^2.5.1", + "supertest": "^7.0.0", "ts-jest": "^29.0.3", "ts-prune": "^0.10.3", "typescript": "^4.5.4" diff --git a/yarn.lock b/yarn.lock index f93bce1..a5759e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -791,6 +791,11 @@ dependencies: "@types/node" "*" +"@types/cookiejar@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" + integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== + "@types/express-serve-static-core@^4.17.33": version "4.17.41" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz#5077defa630c2e8d28aa9ffc2c01c157c305bef6" @@ -855,6 +860,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/methods@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" + integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== + "@types/mime@*": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" @@ -926,6 +936,23 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/superagent@^8.1.0": + version "8.1.7" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-8.1.7.tgz#1153819ed4db34427409a1cc58f3e2f13eeec862" + integrity sha512-NmIsd0Yj4DDhftfWvvAku482PZum4DBW7U51OvS8gvOkDDY0WT1jsVyDV3hK+vplrsYw8oDwi9QxOM7U68iwww== + dependencies: + "@types/cookiejar" "^2.1.5" + "@types/methods" "^1.1.4" + "@types/node" "*" + +"@types/supertest@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-6.0.2.tgz#2af1c466456aaf82c7c6106c6b5cbd73a5e86588" + integrity sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg== + dependencies: + "@types/methods" "^1.1.4" + "@types/superagent" "^8.1.0" + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -1271,6 +1298,11 @@ arraybuffer.prototype.slice@^1.0.2: is-array-buffer "^3.0.2" is-shared-array-buffer "^1.0.2" +asap@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + asynciterator.prototype@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" @@ -1278,6 +1310,11 @@ asynciterator.prototype@^1.0.0: dependencies: has-symbols "^1.0.3" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -1424,6 +1461,17 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: get-intrinsic "^1.2.1" set-function-length "^1.1.1" +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1524,11 +1572,23 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== +component-emitter@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" + integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1561,6 +1621,11 @@ cookie@0.6.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookiejar@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" + integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== + cosmiconfig@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" @@ -1639,6 +1704,15 @@ define-data-property@^1.0.1, define-data-property@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" @@ -1648,6 +1722,11 @@ define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0, de has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -1668,6 +1747,14 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +dezalgo@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== + dependencies: + asap "^2.0.0" + wrappy "1" + diff-sequences@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" @@ -1783,6 +1870,18 @@ es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.13" +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + es-iterator-helpers@^1.0.12: version "1.0.15" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" @@ -2147,6 +2246,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + fastq@^1.6.0: version "1.15.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" @@ -2225,6 +2329,24 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formidable@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.1.tgz#9360a23a656f261207868b1484624c4c8d06ee1a" + integrity sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og== + dependencies: + dezalgo "^1.0.4" + hexoid "^1.0.0" + once "^1.4.0" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -2285,6 +2407,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -2399,6 +2532,13 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.2.2" +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + has-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" @@ -2423,6 +2563,11 @@ hasown@^2.0.0, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hexoid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" + integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" @@ -3332,7 +3477,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -3344,6 +3489,11 @@ mime@1.6.0, mime@^1.3.4: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -3505,7 +3655,7 @@ on-finished@2.4.1: dependencies: ee-first "1.1.1" -once@^1.3.0: +once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -3711,6 +3861,13 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +qs@^6.11.0: + version "6.12.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.2.tgz#5443b587f3bf73ac68968de491e5b25bafe04478" + integrity sha512-x+NLUpx9SYrcwXtX7ob1gnkSems4i/mGZX5SlYxwIau6RrUSODO89TR/XDGGpn5RPWSYIB+aSfuSlV5+CmbTBg== + dependencies: + side-channel "^1.0.6" + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -3916,6 +4073,18 @@ set-function-length@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" @@ -3951,6 +4120,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -4077,6 +4256,29 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +superagent@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-9.0.2.tgz#a18799473fc57557289d6b63960610e358bdebc1" + integrity sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w== + dependencies: + component-emitter "^1.3.0" + cookiejar "^2.1.4" + debug "^4.3.4" + fast-safe-stringify "^2.1.1" + form-data "^4.0.0" + formidable "^3.5.1" + methods "^1.1.2" + mime "2.6.0" + qs "^6.11.0" + +supertest@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/supertest/-/supertest-7.0.0.tgz#cac53b3d6872a0b317980b2b0cfa820f09cd7634" + integrity sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA== + dependencies: + methods "^1.1.2" + superagent "^9.0.1" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"