diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb4fbe..538de9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ The IronWeb SDK NPM releases follow standard [Semantic Versioning](https://semve **Note:** The patch versions of the IronWeb SDK will not be sequential and might jump by multiple numbers between sequential releases. +## v4.4.0 + +- add `IronWeb.user.disableSelf()` which the currently authenticated user can call to disable their own account. Disabled users can still be members of groups but will be unable to call SDK functions. +- add `IronWeb.updateUserStatus(jwtCallback, status)` which uses a JWT to enable or disable a user without an initialized SDK. Use this to re-enable a user that has previously been disabled. + ## v4.3.6 - fix a streaming bug, encrypt could get blocked with no reader yet on writing the header+IV. diff --git a/integration/nightwatch/tests/user-sync/userPasscodeChange.test.js b/integration/nightwatch/tests/user-sync/userPasscodeChange.test.js index 82abdcd..6639606 100644 --- a/integration/nightwatch/tests/user-sync/userPasscodeChange.test.js +++ b/integration/nightwatch/tests/user-sync/userPasscodeChange.test.js @@ -45,7 +45,12 @@ module.exports = { initializeUser.clickInitializeAppButton().enterUserPasscode(firstPasscode).submitPasscode(); - initializeUser.expect.element("@submitPasscode").to.not.have.value; + // The old passcode must be rejected by SDK init, so the user should remain on the + // initialize screen and never reach the document list page. Pause briefly to let the + // async init promise reject before we assert. + browser.pause(2000); + demoApp.expect.element("@browserListPage").to.not.be.present; + initializeUser.expect.element("@submitPasscode").to.be.visible; browser.end(); }, @@ -65,9 +70,12 @@ module.exports = { demoApp .enterPasscodeFields(secondPasscode, secondPasscode) .submitChangePasscode() - .waitForElementPresent("@currentPasscodeInput") - .expect.element("@currentPasscodeInput").to.not.have.value; + .waitForElementPresent("@passwordChangeDialog"); + // On a wrong current passcode, the SDK rejects the change and the component renders + // an "Incorrect passcode" error inside the dialog. That message is what proves this + // specific failure path ran. + demoApp.expect.element("@passwordChangeDialog").text.to.contain("Incorrect passcode"); browser.end(); }, -}; \ No newline at end of file +}; diff --git a/ironweb.d.ts b/ironweb.d.ts index d81ef63..eb97a3b 100644 --- a/ironweb.d.ts +++ b/ironweb.d.ts @@ -155,6 +155,18 @@ export interface UserCreateResponse { userMasterPublicKey: PublicKey; needsRotation: boolean; } +export interface UserUpdateResponse { + accountID: string; + segmentID: number; + status: UserStatus; + userMasterPublicKey: PublicKey; + needsRotation: boolean; +} +export const UserStatus: { + DISABLED: 0; + ENABLED: 1; +}; +export type UserStatus = 0 | 1; export interface DeviceKeys { accountId: string; segmentId: number; @@ -200,6 +212,7 @@ export interface User { listDevices(): Promise; changePasscode(currentPasscode: string, newPasscode: string): Promise; rotateMasterKey(passcode: string): Promise; + disableSelf(): Promise; } export interface Document { @@ -306,6 +319,7 @@ export interface ErrorCodes { USER_DEVICE_DELETE_REQUEST_FAILURE: 210; USER_UPDATE_KEY_REQUEST_FAILURE: 211; USER_PRIVATE_KEY_ROTATION_FAILURE: 212; + USER_UPDATE_STATUS_REQUEST_FAILURE: 214; DOCUMENT_LIST_REQUEST_FAILURE: 300; DOCUMENT_GET_REQUEST_FAILURE: 301; DOCUMENT_CREATE_REQUEST_FAILURE: 302; @@ -360,3 +374,4 @@ export function createNewUser(jwtCallback: JWTCallback, passcode: string, option export function createNewDeviceKeys(jwtCallback: JWTCallback, passcode: string): Promise; export function isInitialized(): boolean; export function deleteDeviceByPublicSigningKey(jwtCallback: JWTCallback, publicSigningKey: Base64String): Promise; +export function updateUserStatus(jwtCallback: JWTCallback, status: UserStatus): Promise; diff --git a/package.json b/package.json index b688013..e17c1b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "license": "AGPL-3.0-only", - "version": "4.3.8", + "version": "4.4.0", "scripts": { "cleanTest": "find dist -type d -name tests -prune -exec rm -rf {} \\;", "lint": "eslint . --ext .ts,.tsx", @@ -73,4 +73,4 @@ "jsxBracketSameLine": true, "arrowParens": "always" } -} \ No newline at end of file +} diff --git a/src/Constants.ts b/src/Constants.ts index dd01c6c..157af64 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -49,6 +49,7 @@ export enum ErrorCodes { USER_UPDATE_KEY_REQUEST_FAILURE = 211, USER_PRIVATE_KEY_ROTATION_FAILURE = 212, USER_DEVICE_LIST_REQUEST_FAILURE = 213, + USER_UPDATE_STATUS_REQUEST_FAILURE = 214, DOCUMENT_LIST_REQUEST_FAILURE = 300, DOCUMENT_GET_REQUEST_FAILURE = 301, DOCUMENT_CREATE_REQUEST_FAILURE = 302, @@ -119,6 +120,16 @@ export const UserAndGroupTypes = { GROUP: "group", }; +/** + * Status values for a user. A disabled user cannot call SDK functions but + * remains a member of any groups they were added to. + */ +export const UserStatus = { + DISABLED: 0, + ENABLED: 1, +} as const; +export type UserStatus = (typeof UserStatus)[keyof typeof UserStatus]; + export const Versions = { //This define is replaced at runtime during development, and at build time in the build script with the proper version SDK_VERSION: SDK_NPM_VERSION_PLACEHOLDER, diff --git a/src/FrameMessageTypes.d.ts b/src/FrameMessageTypes.d.ts index a16d692..6bb2b2a 100644 --- a/src/FrameMessageTypes.d.ts +++ b/src/FrameMessageTypes.d.ts @@ -452,6 +452,21 @@ export interface ListDevicesResponse { type: "LIST_DEVICES_RESPONSE"; message: UserDeviceListResponse; } +export interface DisableUserSelf { + type: "DISABLE_USER_SELF"; + message: null; +} +export interface UpdateUserStatusJwt { + type: "UPDATE_USER_STATUS_JWT"; + message: { + jwtToken: string; + status: number; + }; +} +export interface UpdateUserStatusResponse { + type: "UPDATE_USER_STATUS_RESPONSE"; + message: ApiUserResponse; +} // Blind index search methods export interface BlindSearchIndexCreate { @@ -628,6 +643,8 @@ export type RequestMessage = | DeleteDevice | DeleteDeviceBySigningKey | DeleteDeviceBySigningKeyJwt + | DisableUserSelf + | UpdateUserStatusJwt | DocumentUnmanagedDecryptRequest | DocumentUnmanagedEncryptRequest | BlindSearchIndexCreate @@ -662,6 +679,7 @@ export type ResponseMessage = | CreateDetachedUserDeviceResponse | ListDevicesResponse | DeleteDeviceResponse + | UpdateUserStatusResponse | GroupListResponse | GroupGetResponse | GroupCreateResponse diff --git a/src/frame/endpoints/UserApiEndpoints.ts b/src/frame/endpoints/UserApiEndpoints.ts index 74d3527..c753046 100644 --- a/src/frame/endpoints/UserApiEndpoints.ts +++ b/src/frame/endpoints/UserApiEndpoints.ts @@ -240,6 +240,23 @@ const userUpdate = (userID: string, userPrivateKey?: PrivateKey, sta errorCode: ErrorCodes.USER_UPDATE_REQUEST_FAILURE, }); +/** + * Update a users status using JWT authorization. + * @param {string} userID ID of user to update + * @param {number} status Updated status of user + */ +const userUpdateStatusWithJwt = (userID: string, status: number): RequestMeta => ({ + url: `users/${encodeURIComponent(userID)}`, + options: { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({status}), + }, + errorCode: ErrorCodes.USER_UPDATE_STATUS_REQUEST_FAILURE, +}); + /** * Generate an API request to rotate the users private key passing the augmentation factor that the key is rotated by and * the users encrypted private key that has been augmented by that same factor. @@ -336,6 +353,17 @@ export default { return makeAuthorizedApiRequest(url, errorCode, options); }, + /** + * Invoke user update API to change a user's status using JWT authorization. + * @param {string} jwtToken Authorized JWT for the user + * @param {string} userId ID of the user (must match the JWT subject) + * @param {number} status Status to set for the user + */ + callUserUpdateStatusWithJwt(jwtToken: string, userId: string, status: number): Future { + const {url, options, errorCode} = userUpdateStatusWithJwt(userId, status); + return makeJwtApiRequest(url, errorCode, options, jwtToken); + }, + /** * Invoke the user device add API with the provided device/signing/transform keys * @param {string} jwtToken Users authorized JWT token diff --git a/src/frame/endpoints/tests/UserApiEndpoints.test.ts b/src/frame/endpoints/tests/UserApiEndpoints.test.ts index b7615f0..cbc5ade 100644 --- a/src/frame/endpoints/tests/UserApiEndpoints.test.ts +++ b/src/frame/endpoints/tests/UserApiEndpoints.test.ts @@ -304,6 +304,35 @@ describe("UserApiEndpoints", () => { }); }); + describe("callUserUpdateStatusWithJwt", () => { + it("calls API and updates status using JWT auth", () => { + (ApiRequest.makeJwtApiRequest as unknown as jest.SpyInstance).mockReturnValue( + Future.of({ + id: "user-10", + foo: "bar", + }) + ); + + UserApiEndpoints.callUserUpdateStatusWithJwt("jwtToken", "user-special~!@#$", 0).engage( + (e) => { + throw new Error(e.message); + }, + (response: any) => { + expect(response).toEqual({id: "user-10", foo: "bar"}); + expect(ApiRequest.makeJwtApiRequest).toHaveBeenCalledWith( + "users/user-special~!%40%23%24", + expect.any(Number), + expect.any(Object), + "jwtToken" + ); + const request = (ApiRequest.makeJwtApiRequest as unknown as jest.SpyInstance).mock.calls[0][2]; + expect(request.method).toEqual("PUT"); + expect(JSON.parse(request.body)).toEqual({status: 0}); + } + ); + }); + }); + describe("callUserDeviceAdd", () => { it("calls API and returns data as expected", () => { (ApiRequest.makeJwtApiRequest as unknown as jest.SpyInstance).mockReturnValue( diff --git a/src/frame/index.ts b/src/frame/index.ts index 4f5bab6..1a8aab0 100644 --- a/src/frame/index.ts +++ b/src/frame/index.ts @@ -85,6 +85,12 @@ function onParentPortMessage(data: RequestMessage, callback: (message: ResponseM ); case "LIST_DEVICES": return UserApi.listDevices().engage(errorHandler, (result) => callback({type: "LIST_DEVICES_RESPONSE", message: result})); + case "DISABLE_USER_SELF": + return UserApi.disableSelf().engage(errorHandler, (result) => callback({type: "UPDATE_USER_STATUS_RESPONSE", message: result})); + case "UPDATE_USER_STATUS_JWT": + return UserApi.updateUserStatusWithJwt(data.message.jwtToken, data.message.status).engage(errorHandler, (result) => + callback({type: "UPDATE_USER_STATUS_RESPONSE", message: result}) + ); case "DOCUMENT_LIST": return DocumentApi.list().engage(errorHandler, (documents) => callback({type: "DOCUMENT_LIST_RESPONSE", message: documents})); case "DOCUMENT_META_GET": diff --git a/src/frame/sdk/UserApi.ts b/src/frame/sdk/UserApi.ts index 2b9cee0..55e19b5 100644 --- a/src/frame/sdk/UserApi.ts +++ b/src/frame/sdk/UserApi.ts @@ -1,11 +1,34 @@ +import {decode as utf8Decode} from "@stablelib/utf8"; import Future from "futurejs"; -import SDKError from "src/lib/SDKError"; +import SDKError from "../../lib/SDKError"; +import {ErrorCodes, UserStatus} from "../../Constants"; import * as WMT from "../../WorkerMessageTypes"; import ApiState from "../ApiState"; import UserApiEndpoints from "../endpoints/UserApiEndpoints"; import {clearDeviceAndSigningKeys} from "../FrameUtils"; import * as WorkerMediator from "../WorkerMediator"; +/** + * Extract the `sub` claim from a JWT token to identify the user the JWT is for. The JWT is + * not verified here; the server is the authority. UTF-8 safe for non-ASCII user IDs. + */ +export const userIdFromJwt = (jwt: string): Future => { + try { + const payload = jwt.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"); + const bytes = Uint8Array.from(window.atob(payload), (c) => c.charCodeAt(0)); + const claims = JSON.parse(utf8Decode(bytes)); + if (typeof claims.sub !== "string" || claims.sub.length === 0) { + throw new Error("JWT is missing required 'sub' claim."); + } + return Future.of(claims.sub); + } catch (e) { + // Wrap in a fresh Error so SDKError applies our error code instead of inheriting one from + // the underlying exception (e.g. DOMException from atob exposes a numeric `code` property). + const message = e instanceof Error ? e.message : String(e); + return Future.reject(new SDKError(new Error(message), ErrorCodes.JWT_FORMAT_FAILURE)); + } +}; + /** * Rotate users current private key by taking their current passcode and using it to derive a key to decrypt their user private key. * Then generates and augmentation factor and subtracts that augmentation factor from the users private key. The new private key is then @@ -69,11 +92,7 @@ export const deleteDevice = (deviceId?: number) => { //their device private key from local storage. So mock out a fake ID here that we can use to decision off of. .handleWith(() => Future.of({id: -1})) .map((deleteResponse) => { - const user = ApiState.user(); - - const {id, segmentId} = user; - clearDeviceAndSigningKeys(id, segmentId); - ApiState.clearCurrentUser(); + deleteLocalDeviceAndClearUser(); return deleteResponse.id; }) ); @@ -98,3 +117,29 @@ export const deleteDeviceBySigningKeyWithJwt = (jwtToken: string, publicSigningK * Makes a request to list the devices for the currently logged in user. */ export const listDevices = () => UserApiEndpoints.callUserListDevices(); + +const deleteLocalDeviceAndClearUser = () => { + const user = ApiState.user(); + const {id, segmentId} = user; + clearDeviceAndSigningKeys(id, segmentId); + ApiState.clearCurrentUser(); +}; + +/** + * Disables the currently authenticated user. After this call succeeds the user + * will not be able to invoke any other authorized SDK functions. Disabled users + * remain members of any groups they belonged to but cannot use them. + */ +export const disableSelf = () => + UserApiEndpoints.callUserUpdateApi(undefined, UserStatus.DISABLED).map((r) => { + deleteLocalDeviceAndClearUser(); + return r; + }); + +/** + * Update the status of the user identified by the provided JWT. + * Allows changing a user's status using JWT auth, without an initialized SDK. + * The user id is taken from the `sub` claim of the JWT. + */ +export const updateUserStatusWithJwt = (jwtToken: string, status: number) => + userIdFromJwt(jwtToken).flatMap((userId) => UserApiEndpoints.callUserUpdateStatusWithJwt(jwtToken, userId, status)); diff --git a/src/frame/sdk/tests/UserApi.test.ts b/src/frame/sdk/tests/UserApi.test.ts index d70c224..7bbd0a1 100644 --- a/src/frame/sdk/tests/UserApi.test.ts +++ b/src/frame/sdk/tests/UserApi.test.ts @@ -136,6 +136,164 @@ describe("UserApi", () => { }); }); + describe("disableSelf", () => { + it("calls callUserUpdateApi with status=0 (disabled)", () => { + jest.spyOn(UserApiEndpoints, "callUserUpdateApi").mockReturnValue(Future.of({id: "user-10"})); + UserApi.disableSelf().engage( + (e) => { + throw new Error(e.message); + }, + (result: any) => { + expect(result).toEqual({id: "user-10"}); + expect(UserApiEndpoints.callUserUpdateApi).toHaveBeenCalledWith(undefined, 0); + } + ); + }); + }); + + describe("userIdFromJwt", () => { + // Build a base64url payload from arbitrary JSON. Mirrors what a real JWT issuer produces. + const b64url = (s: string) => Buffer.from(s, "utf8").toString("base64").replace(/=+$/, "").replace(/\+/g, "-").replace(/\//g, "_"); + const makeJwt = (claims: unknown) => `header.${b64url(JSON.stringify(claims))}.sig`; + + const expectOk = (jwt: string, expected: string) => { + UserApi.userIdFromJwt(jwt).engage( + (e) => { + throw new Error(`expected success, got error: ${e.message}`); + }, + (sub) => expect(sub).toEqual(expected) + ); + }; + const expectReject = (jwt: string, done: jest.DoneCallback) => { + UserApi.userIdFromJwt(jwt).engage( + (e) => { + expect(e.code).toBe(100); // JWT_FORMAT_FAILURE + done(); + }, + (sub) => done(new Error(`expected rejection, got sub: ${sub}`)) + ); + }; + + it("extracts a simple ASCII sub claim", () => { + expectOk(makeJwt({sub: "user-10"}), "user-10"); + }); + + it("ignores other claims and returns just sub", () => { + expectOk(makeJwt({iss: "issuer", aud: "aud", sub: "user-10", exp: 1700000000}), "user-10"); + }); + + it("supports a sub containing characters that base64url encodes with - and _", () => { + // Constructing a JSON payload whose base64 encoding contains both '+' and '/' so it + // exercises the base64url -> base64 conversion. The string `???>>>` does that. + expectOk(makeJwt({sub: "???>>>"}), "???>>>"); + }); + + it("supports UTF-8 sub claims (multi-byte)", () => { + expectOk(makeJwt({sub: "用户-10"}), "用户-10"); + expectOk(makeJwt({sub: "user-😀"}), "user-😀"); + }); + + it("handles payloads requiring 0, 1, or 2 base64 padding chars", () => { + // Lengths chosen so the base64 of `{"sub":"X..."}` falls into each padding bucket. + expectOk(makeJwt({sub: "a"}), "a"); // payload bytes mod 3 = 2 → 1 padding + expectOk(makeJwt({sub: "ab"}), "ab"); // payload bytes mod 3 = 0 → 0 padding + expectOk(makeJwt({sub: "abc"}), "abc"); // payload bytes mod 3 = 1 → 2 padding + }); + + it("rejects a JWT with no dots", (done) => { + expectReject("notavalidjwt", done); + }); + + it("rejects a JWT with only a header section", (done) => { + expectReject("header.", done); + }); + + it("rejects a JWT whose payload section is empty", (done) => { + expectReject("header..sig", done); + }); + + it("rejects a JWT whose payload is not valid base64", (done) => { + expectReject("header.!!!not-base64!!!.sig", done); + }); + + it("rejects a JWT whose payload base64 decodes to invalid JSON", (done) => { + expectReject(`header.${b64url("not json")}.sig`, done); + }); + + it("rejects a JWT whose payload is JSON but not an object", (done) => { + // `JSON.parse("123")` returns a number; reading .sub off it returns undefined. + expectReject(`header.${b64url("123")}.sig`, done); + }); + + it("rejects a JWT with no sub claim", (done) => { + expectReject(makeJwt({foo: "bar"}), done); + }); + + it("rejects a JWT whose sub claim is an empty string", (done) => { + expectReject(makeJwt({sub: ""}), done); + }); + + it("rejects a JWT whose sub claim is not a string", (done) => { + expectReject(makeJwt({sub: 42}), done); + }); + + it("rejects a JWT whose sub claim is null", (done) => { + expectReject(makeJwt({sub: null}), done); + }); + + it("rejects a JWT whose sub claim is an object", (done) => { + expectReject(makeJwt({sub: {nested: "x"}}), done); + }); + + it("rejects an empty string", (done) => { + expectReject("", done); + }); + }); + + describe("updateUserStatusWithJwt", () => { + // Header and signature can be anything; payload is base64url(`{"sub":"some-user-id"}`). + const jwt = "header.eyJzdWIiOiJzb21lLXVzZXItaWQifQ.sig"; + + it("decodes the user id from the JWT sub claim and calls the endpoint", () => { + jest.spyOn(UserApiEndpoints, "callUserUpdateStatusWithJwt").mockReturnValue(Future.of({id: "some-user-id"})); + UserApi.updateUserStatusWithJwt(jwt, 0).engage( + (e) => { + throw new Error(e.message); + }, + (result: any) => { + expect(result).toEqual({id: "some-user-id"}); + expect(UserApiEndpoints.callUserUpdateStatusWithJwt).toHaveBeenCalledWith(jwt, "some-user-id", 0); + } + ); + }); + + it("rejects when JWT is malformed without calling the endpoint", (done) => { + jest.spyOn(UserApiEndpoints, "callUserUpdateStatusWithJwt"); + UserApi.updateUserStatusWithJwt("notavalidjwt", 1).engage( + (e) => { + expect(e.code).toBe(100); // JWT_FORMAT_FAILURE + expect(UserApiEndpoints.callUserUpdateStatusWithJwt).not.toHaveBeenCalled(); + done(); + }, + () => done(new Error("expected to fail")) + ); + }); + + it("rejects when JWT has no sub claim without calling the endpoint", (done) => { + // payload is base64url(`{"foo":"bar"}`) + const noSub = "header.eyJmb28iOiJiYXIifQ.sig"; + jest.spyOn(UserApiEndpoints, "callUserUpdateStatusWithJwt"); + UserApi.updateUserStatusWithJwt(noSub, 1).engage( + (e) => { + expect(e.code).toBe(100); + expect(UserApiEndpoints.callUserUpdateStatusWithJwt).not.toHaveBeenCalled(); + done(); + }, + () => done(new Error("expected to fail")) + ); + }); + }); + describe("deleteDeviceBySigningKeyWithJwt", () => { it("calls the correct API", async () => { jest.spyOn(UserApiEndpoints, "callUserDeviceDeleteBySigningKeyWithJwt").mockReturnValue(Future.of({id: 33})); diff --git a/src/shim/Initialize.ts b/src/shim/Initialize.ts index a3269d5..32328da 100644 --- a/src/shim/Initialize.ts +++ b/src/shim/Initialize.ts @@ -1,5 +1,5 @@ import Future from "futurejs"; -import {DeviceKeys, SDKInitializationResult, UserCreateResponse} from "ironweb"; +import {DeviceKeys, SDKInitializationResult, UserCreateResponse, UserStatus, UserUpdateResponse} from "ironweb"; import {ErrorCodes} from "../Constants"; import { CreateDetachedUserDeviceRequest, @@ -13,6 +13,8 @@ import { InitApiPasscodeResponse, InitApiRequest, InitApiSdkResponse, + UpdateUserStatusJwt, + UpdateUserStatusResponse, } from "../FrameMessageTypes"; import SDKError from "../lib/SDKError"; import * as FrameMediator from "./FrameMediator"; @@ -97,7 +99,6 @@ export const createNewUser = (jwtCallback: JWTCallbackToPromise, passcode: strin return FrameMediator.sendMessage(payload); }) //Rename a few fields and strip out the users private key and currentKeyId since they'll probably be confusing that they're getting back an encrypted private key - //eslint-disable-next-line @typescript-eslint/no-unused-vars .map(({message: {id, segmentId, needsRotation, status, userMasterPublicKey}}) => ({ accountID: id, segmentID: segmentId, @@ -171,3 +172,26 @@ export const deleteDeviceByPublicSigningKey = (jwtCallback: JWTCallbackToPromise return FrameMediator.sendMessage(payload).map(({message}) => message); }) .toPromise(); + +/** + * Update the status (enabled or disabled) of the user identified by the provided JWT. + * Authenticates with a JWT and does not require an initialized SDK. The user id is taken + * from the `sub` claim of the JWT. + */ +export const updateUserStatus = (jwtCallback: JWTCallbackToPromise, status: UserStatus): Promise => + getJWT(jwtCallback) + .flatMap((jwtToken) => { + const payload: UpdateUserStatusJwt = { + type: "UPDATE_USER_STATUS_JWT", + message: {jwtToken, status}, + }; + return FrameMediator.sendMessage(payload); + }) + .map(({message: {id, segmentId, needsRotation, status, userMasterPublicKey}}) => ({ + accountID: id, + segmentID: segmentId, + needsRotation, + status: status as UserStatus, + userMasterPublicKey, + })) + .toPromise(); diff --git a/src/shim/index.ts b/src/shim/index.ts index a152475..2b911a3 100644 --- a/src/shim/index.ts +++ b/src/shim/index.ts @@ -3,7 +3,8 @@ import {ErrorCodes} from "../Constants"; import SDKError from "../lib/SDKError"; import * as Init from "./Initialize"; import {checkSDKInitialized} from "./ShimUtils"; -export {deleteDeviceByPublicSigningKey} from "./Initialize"; +export {deleteDeviceByPublicSigningKey, updateUserStatus} from "./Initialize"; +export {UserStatus} from "../Constants"; /** * Checks bowser functionality to ensure random number generation is supported. diff --git a/src/shim/sdk/UserSDK.ts b/src/shim/sdk/UserSDK.ts index bcc627d..2219724 100644 --- a/src/shim/sdk/UserSDK.ts +++ b/src/shim/sdk/UserSDK.ts @@ -1,6 +1,7 @@ import {clearParentWindowSymmetricKey, checkSDKInitialized, clearSDKInitialized} from "../ShimUtils"; import * as FrameMediator from "../FrameMediator"; import * as MT from "../../FrameMessageTypes"; +import {UserStatus} from "../../Constants"; /** * Update an existing users passcode that is used to escrow their private key. The returned Promise will resolve successfully upon passcode change or @@ -96,3 +97,34 @@ export const listDevices = () => { .map(({message: result}) => result) .toPromise(); }; + +/** + * Disables the currently authenticated user. Disabled users are unable to call any + * other authorized SDK functions but remain in any groups they belonged to. Users + * cannot re-enable themselves; an admin must call `updateUserStatus` with a JWT for + * the user to re-enable them. + */ +export const disableSelf = () => { + checkSDKInitialized(); + const payload: MT.DisableUserSelf = { + type: "DISABLE_USER_SELF", + message: null, + }; + // The frame wipes its ApiState and device keys on a successful disable, so mirror the + // current-device path of `deleteDevice` here: drop the parent-window symmetric key now + // and clear the init flag on success. Otherwise `isInitialized()` keeps returning true + // while the frame is empty, and the next SDK call fails confusingly in the frame. + clearParentWindowSymmetricKey(); + return FrameMediator.sendMessage(payload) + .map(({message: {id, segmentId, needsRotation, status, userMasterPublicKey}}) => { + clearSDKInitialized(); + return { + accountID: id, + segmentID: segmentId, + needsRotation, + status: status as UserStatus, + userMasterPublicKey, + }; + }) + .toPromise(); +}; diff --git a/src/shim/sdk/tests/UserSDK.test.ts b/src/shim/sdk/tests/UserSDK.test.ts index 5ffded8..1adc813 100644 --- a/src/shim/sdk/tests/UserSDK.test.ts +++ b/src/shim/sdk/tests/UserSDK.test.ts @@ -105,6 +105,75 @@ describe("UserSDK", () => { }); }); + describe("disableSelf", () => { + it("throws if SDK has not yet been initialized", () => { + ShimUtils.clearSDKInitialized(); + expect(() => UserSDK.disableSelf()).toThrow(); + }); + + it("sends DISABLE_USER_SELF to the frame and remaps the response", (done) => { + ShimUtils.setSDKInitialized(); + jest.spyOn(FrameMediator, "sendMessage").mockReturnValue( + Future.of({ + message: { + id: "user-10", + segmentId: 5, + status: 0, + userMasterPublicKey: {x: "x", y: "y"}, + needsRotation: false, + }, + }) + ); + UserSDK.disableSelf() + .then((result: any) => { + expect(result).toEqual({ + accountID: "user-10", + segmentID: 5, + status: 0, + userMasterPublicKey: {x: "x", y: "y"}, + needsRotation: false, + }); + expect(FrameMediator.sendMessage).toHaveBeenCalledWith({ + type: "DISABLE_USER_SELF", + message: null, + }); + done(); + }) + .catch((e) => done(e)); + }); + + it("clears the shim-side init flag and symmetric key after successful disable", (done) => { + // The frame clears its own ApiState and device keys after a successful disable, but the + // shim still holds the parent-window symmetric key and the `hasInitializedSDK` flag. + // Without clearing those, `IronWeb.isInitialized()` keeps returning true while the + // frame is wiped, leaving the SDK in a half-initialized state where the next call + // fails in a confusing way. This test pins down the contract by mirroring what + // `deleteDevice` does for the current-device path. + ShimUtils.setSDKInitialized(); + jest.spyOn(ShimUtils, "clearParentWindowSymmetricKey"); + jest.spyOn(ShimUtils, "clearSDKInitialized"); + jest.spyOn(FrameMediator, "sendMessage").mockReturnValue( + Future.of({ + message: { + id: "user-10", + segmentId: 5, + status: 0, + userMasterPublicKey: {x: "x", y: "y"}, + needsRotation: false, + }, + }) + ); + UserSDK.disableSelf() + .then(() => { + expect(ShimUtils.clearParentWindowSymmetricKey).toHaveBeenCalledWith(); + expect(ShimUtils.clearSDKInitialized).toHaveBeenCalledWith(); + expect(() => ShimUtils.checkSDKInitialized()).toThrow(); + done(); + }) + .catch((e) => done(e)); + }); + }); + describe("deleteDevice", () => { it("throws if SDK has not yet been initialized", () => { ShimUtils.clearSDKInitialized(); diff --git a/src/shim/tests/Initialize.test.ts b/src/shim/tests/Initialize.test.ts index 4111f62..b6351f9 100644 --- a/src/shim/tests/Initialize.test.ts +++ b/src/shim/tests/Initialize.test.ts @@ -432,6 +432,48 @@ describe("Initialize", () => { }); }); + describe("updateUserStatus", () => { + it("sends UPDATE_USER_STATUS_JWT to the frame with the JWT and status, and remaps the response", (done) => { + ShimUtils.clearSDKInitialized(); + jest.spyOn(FrameMediator, "sendMessage").mockReturnValue( + Future.of({ + message: { + id: "user-10", + segmentId: 5, + status: 1, + userMasterPublicKey: {x: "x", y: "y"}, + needsRotation: false, + }, + }) + ); + Initialize.updateUserStatus(() => Promise.resolve("jwt"), 1) + .then((result: any) => { + expect(result).toEqual({ + accountID: "user-10", + segmentID: 5, + status: 1, + userMasterPublicKey: {x: "x", y: "y"}, + needsRotation: false, + }); + expect(FrameMediator.sendMessage).toHaveBeenCalledWith({ + type: "UPDATE_USER_STATUS_JWT", + message: {jwtToken: "jwt", status: 1}, + }); + done(); + }) + .catch((e) => done(e)); + }); + + it("rejects when JWT callback rejects", (done) => { + Initialize.updateUserStatus(() => Promise.reject(new Error("nope")), 0) + .then(() => done(new Error("expected rejection"))) + .catch((e: Error) => { + expect(e.message).toEqual("nope"); + done(); + }); + }); + }); + describe("deleteDeviceByPublicSigningKey", () => { it("sends the frame delete message, doesn't send delete request type to frame", (done) => { ShimUtils.clearSDKInitialized(); diff --git a/yarn.lock b/yarn.lock index 82111a4..f9b2ca8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -374,6 +374,11 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" @@ -742,7 +747,7 @@ "@nightwatch/chai@5.0.2": version "5.0.2" - resolved "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.2.tgz" + resolved "https://registry.yarnpkg.com/@nightwatch/chai/-/chai-5.0.2.tgz#86b20908fc090dffd5c9567c0392bc6a494cc2e6" integrity sha512-yzILJFCcE75OPoRfBlJ80Y3Ky06ljsdrK4Ld92yhmM477vxO2GEguwnd+ldl7pdSYTcg1gSJ1bPPQrA+/Hrn+A== dependencies: assertion-error "1.1.0" @@ -752,6 +757,11 @@ pathval "1.1.1" type-detect "4.0.8" +"@nightwatch/html-reporter-template@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@nightwatch/html-reporter-template/-/html-reporter-template-0.2.1.tgz#9fa86e8cab6ee703d2e55b47abac92613f97a298" + integrity sha512-GEBeGoXVmTYPtNC4Yq34vfgxf6mlFyEagxpsfH18Qe5BvctF2rprX+wI5dKBm9p5IqHo6ZOcXHCufOeP3cjuOw== + "@noble/ciphers@1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.2.1.tgz#3812b72c057a28b44ff0ad4aff5ca846e5b9cdc9" @@ -1243,7 +1253,7 @@ "@types/ws@^8.5.10": version "8.18.1" - resolved "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== dependencies: "@types/node" "*" @@ -1342,7 +1352,7 @@ "@ungap/promise-all-settled@1.1.2": version "1.1.2" - resolved "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== "@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": @@ -1599,7 +1609,7 @@ ansi-align@^3.0.0: ansi-colors@4.1.1: version "4.1.1" - resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== ansi-escapes@^4.2.1: @@ -1638,9 +1648,9 @@ ansi-styles@^5.0.0: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -ansi-to-html@^0.7.2: +ansi-to-html@0.7.2: version "0.7.2" - resolved "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.7.2.tgz" + resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.7.2.tgz#a92c149e4184b571eb29a0135ca001a8e2d710cb" integrity sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g== dependencies: entities "^2.2.0" @@ -1716,6 +1726,11 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +axe-core@^4.11.1: + version "4.11.4" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.11.4.tgz#5b535e381ff1e61ffdd615e5483d16186d3b46a5" + integrity sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA== + babel-jest@^28.1.3: version "28.1.3" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz" @@ -1902,7 +1917,7 @@ browser-process-hrtime@^1.0.0: browser-stdout@1.3.1: version "1.3.1" - resolved "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== browserslist@^4.20.2, browserslist@^4.28.1: @@ -2018,7 +2033,7 @@ catharsis@^0.9.0: chai-nightwatch@0.5.3: version "0.5.3" - resolved "https://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.5.3.tgz" + resolved "https://registry.yarnpkg.com/chai-nightwatch/-/chai-nightwatch-0.5.3.tgz#980ecf63dde5a04e7f3524370682c7ff01178ffb" integrity sha512-38ixH/mqpY6IwnZkz6xPqx8aB5/KVR+j6VPugcir3EGOsphnWXrPH/mUt8Jp+ninL6ghY0AaJDQ10hSfCPGy/g== dependencies: assertion-error "1.1.0" @@ -2062,7 +2077,7 @@ check-error@1.0.2: chokidar@3.5.3: version "3.5.3" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" @@ -2077,7 +2092,7 @@ chokidar@3.5.3: chokidar@^3.6.0: version "3.6.0" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: anymatch "~3.1.2" @@ -2127,6 +2142,15 @@ cli-spinners@^2.5.0: resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz" integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== +cli-table3@^0.6.3: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" @@ -2362,7 +2386,7 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: debug@4.3.3: version "4.3.3" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== dependencies: ms "2.1.2" @@ -2469,7 +2493,7 @@ detect-node@^2.0.4: didyoumean@1.2.2: version "1.2.2" - resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== diff-sequences@^28.1.1: @@ -2479,7 +2503,7 @@ diff-sequences@^28.1.1: diff@5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== dir-glob@^3.0.1: @@ -2526,7 +2550,7 @@ domexception@^4.0.0: dotenv@10.0.0: version "10.0.0" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== dunder-proto@^1.0.1: @@ -2550,10 +2574,10 @@ ee-first@1.1.1: resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -ejs@^3.1.8: - version "3.1.10" - resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz" - integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== +ejs@3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== dependencies: jake "^10.8.5" @@ -2609,7 +2633,7 @@ entities@~2.1.0: envinfo@7.8.1, envinfo@^7.7.3: version "7.8.1" - resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== error-ex@^1.3.1: @@ -3154,7 +3178,7 @@ fresh@~0.5.2: fs-extra@^10.1.0: version "10.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== dependencies: graceful-fs "^4.2.0" @@ -3218,7 +3242,7 @@ get-caller-file@^2.0.5: get-func-name@^2.0.0: version "2.0.2" - resolved "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: @@ -3293,7 +3317,7 @@ glob-to-regexp@^0.4.1: glob@7.2.0: version "7.2.0" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" @@ -3367,7 +3391,7 @@ grapheme-splitter@^1.0.4: growl@1.10.5: version "1.10.5" - resolved "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== handle-thing@^2.0.0: @@ -3437,7 +3461,7 @@ hasown@^2.0.2: he@1.2.0: version "1.2.0" - resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== hoist-non-react-statics@^2.3.1: @@ -4366,7 +4390,7 @@ jsdoc@^3.6.3: taffydb "2.6.2" underscore "~1.13.2" -jsdom@^19.0.0: +jsdom@19.0.0, jsdom@^19.0.0: version "19.0.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-19.0.0.tgz#93e67c149fe26816d38a849ea30ac93677e16b6a" integrity sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A== @@ -4430,9 +4454,9 @@ json5@^2.1.2, json5@^2.2.1: integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + version "6.2.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.1.tgz#b6e31717f22cc37330b081ce0051ed5de53af2f6" + integrity sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q== dependencies: universalify "^2.0.0" optionalDependencies: @@ -4458,7 +4482,7 @@ jsonwebtoken@^9.0.0: jszip@^3.10.0: version "3.10.1" - resolved "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== dependencies: lie "~3.3.0" @@ -4583,17 +4607,17 @@ locate-path@^6.0.0: lodash._arraycopy@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1" integrity sha512-RHShTDnPKP7aWxlvXKiDT6IX2jCs6YZLCtNhOru/OX2Q/tzX295vVBK5oX1ECtN+2r86S0Ogy8ykP1sgCZAN0A== lodash._arrayeach@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz#bab156b2a90d3f1bbd5c653403349e5e5933ef9e" integrity sha512-Mn7HidOVcl3mkQtbPsuKR0Fj0N6Q6DQB77CtYncZcJc0bx5qv2q4Gl6a0LC1AN+GSxpnBDNnK3CKEm9XNA4zqQ== lodash._baseassign@^3.0.0: version "3.2.0" - resolved "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz" + resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" integrity sha512-t3N26QR2IdSN+gqSy9Ds9pBu/J1EAFEshKlUHpJG3rvyJOYgcELIxcIeKKfZk7sjOz11cFfzJRsyFry/JyabJQ== dependencies: lodash._basecopy "^3.0.0" @@ -4601,7 +4625,7 @@ lodash._baseassign@^3.0.0: lodash._baseclone@^3.0.0: version "3.3.0" - resolved "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz" + resolved "https://registry.yarnpkg.com/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz#303519bf6393fe7e42f34d8b630ef7794e3542b7" integrity sha512-1K0dntf2dFQ5my0WoGKkduewR6+pTNaqX03kvs45y7G5bzl4B3kTR4hDfJIc2aCQDeLyQHhS280tc814m1QC1Q== dependencies: lodash._arraycopy "^3.0.0" @@ -4613,32 +4637,32 @@ lodash._baseclone@^3.0.0: lodash._basecopy@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" integrity sha512-rFR6Vpm4HeCK1WPGvjZSJ+7yik8d8PVUdCJx5rT2pogG4Ve/2ZS7kfmO5l5T2o5V2mqlNIfSF5MZlr1+xOoYQQ== lodash._basefor@^3.0.0: version "3.0.3" - resolved "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz" + resolved "https://registry.yarnpkg.com/lodash._basefor/-/lodash._basefor-3.0.3.tgz#7550b4e9218ef09fad24343b612021c79b4c20c2" integrity sha512-6bc3b8grkpMgDcVJv9JYZAk/mHgcqMljzm7OsbmcE2FGUMmmLQTPHlh/dFqR8LA0GQ7z4K67JSotVKu5058v1A== lodash._bindcallback@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" integrity sha512-2wlI0JRAGX8WEf4Gm1p/mv/SZ+jLijpj0jyaE/AXeuQphzCgD8ZQW4oSpoN8JAopujOFGU3KMuq7qfHBWlGpjQ== lodash._getnative@^3.0.0: version "3.9.1" - resolved "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" integrity sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA== lodash._isiterateecall@^3.0.0: version "3.0.9" - resolved "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz" + resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" integrity sha512-De+ZbrMu6eThFti/CSzhRvTKMgQToLxbij58LMfM8JnYDNSOjkjTCIaa8ixglOeGh2nyPlakbt5bJWJ7gvpYlQ== lodash.clone@3.0.3: version "3.0.3" - resolved "https://registry.npmjs.org/lodash.clone/-/lodash.clone-3.0.3.tgz" + resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-3.0.3.tgz#84688c73d32b5a90ca25616963f189252a997043" integrity sha512-yVYPpFTdZDCLG2p07gVRTvcwN5X04oj2hu4gG6r0fer58JA08wAVxXzWM+CmmxO2bzOH8u8BkZTZqgX6juVF7A== dependencies: lodash._baseclone "^3.0.0" @@ -4647,27 +4671,27 @@ lodash.clone@3.0.3: lodash.defaultsdeep@4.6.1: version "4.6.1" - resolved "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz" + resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA== -lodash.escape@^4.0.1: +lodash.escape@4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw== lodash.isarguments@^3.0.0: version "3.1.0" - resolved "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== lodash.isarray@^3.0.0: version "3.0.4" - resolved "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" integrity sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ== lodash.keys@^3.0.0: version "3.1.2" - resolved "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" integrity sha512-CuBsapFjcubOGMn3VD+24HOAPxM79tH+V6ivJL3CHYjtrawauDJHUk//Yew9Hvc6e9rbCrURGk8z6PC+8WJBfQ== dependencies: lodash._getnative "^3.0.0" @@ -4684,6 +4708,11 @@ lodash.merge@4.6.2, lodash.merge@^4.6.0, lodash.merge@^4.6.2: resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.pick@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q== + lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz" @@ -4716,7 +4745,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4 loupe@2.3.4: version "2.3.4" - resolved "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== dependencies: get-func-name "^2.0.0" @@ -4865,10 +4894,10 @@ minimalistic-assert@^1.0.0: resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +minimatch@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" @@ -4908,14 +4937,9 @@ mkdirp@^1.0.4: resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkpath@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/mkpath/-/mkpath-1.0.0.tgz" - integrity sha512-PbNHr7Y/9Y/2P5pKFv5XOGBfNQqZ+fdiHWcuf7swLACN5ZW5LU7J5tMU8LSBjpluAxAxKYGD9nnaIbdRy9+m1w== - mocha@9.2.2: version "9.2.2" - resolved "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9" integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== dependencies: "@ungap/promise-all-settled" "1.1.2" @@ -4968,7 +4992,7 @@ multicast-dns@^7.2.5: nanoid@3.3.1: version "3.3.1" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== natural-compare@^1.4.0: @@ -4986,37 +5010,50 @@ neo-async@^2.6.2: resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +nightwatch-axe-verbose@^2.1.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.5.1.tgz#6bed281a4b3ba902f70c761d51e36f7177dcc99d" + integrity sha512-vvLUMyIbGHB8CA5XEGfliPstNCplcHeMn/CWi4cyg0CWMqWypGrV2IgP+WmiWpUgs0qvPmqVHeRHf0BTT7Ez2Q== + dependencies: + axe-core "^4.11.1" + nightwatch@^2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/nightwatch/-/nightwatch-2.3.0.tgz" - integrity sha512-JouglJuxReLoCWfwud6U6mKTqTlEapJZYEvFzsBZ8CDJ77jzaiBLkgbpSJ6nt51kHJRH+xZtdTTiKFNjX0vS8w== + version "2.6.25" + resolved "https://registry.yarnpkg.com/nightwatch/-/nightwatch-2.6.25.tgz#99c2abdd8a7f1ce8be2882ac5458a776caf1b5ab" + integrity sha512-aYc5eA6M/iADdbKbD6dMHlhUsaJm/Y4/VOtSHSC23nimGTXMUKbe1Bb14Iz3/SNyz2joHOkpxaDIPIAZCSlOiQ== dependencies: "@nightwatch/chai" "5.0.2" - ansi-to-html "^0.7.2" + "@nightwatch/html-reporter-template" "0.2.1" + ansi-to-html "0.7.2" assertion-error "1.1.0" boxen "5.1.2" chai-nightwatch "0.5.3" ci-info "3.3.0" + cli-table3 "^0.6.3" didyoumean "1.2.2" dotenv "10.0.0" - ejs "^3.1.8" + ejs "3.1.8" envinfo "7.8.1" fs-extra "^10.1.0" glob "^7.2.3" + jsdom "19.0.0" lodash.clone "3.0.3" lodash.defaultsdeep "4.6.1" - lodash.escape "^4.0.1" + lodash.escape "4.0.1" lodash.merge "4.6.2" - minimatch "3.0.4" + lodash.pick "4.4.0" + minimatch "3.1.2" minimist "1.2.6" - mkpath "1.0.0" mocha "9.2.2" - open "^8.4.0" + nightwatch-axe-verbose "^2.1.0" + open "8.4.0" ora "5.4.1" - selenium-webdriver "^4.3.1" + selenium-webdriver "4.6.1" semver "7.3.5" - stacktrace-parser "^0.1.10" + stacktrace-parser "0.1.10" strip-ansi "6.0.1" + untildify "^4.0.0" + uuid "8.3.2" node-fetch@^1.0.1: version "1.7.3" @@ -5159,6 +5196,15 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +open@8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + open@^10.0.3: version "10.1.2" resolved "https://registry.npmjs.org/open/-/open-10.1.2.tgz" @@ -5169,15 +5215,6 @@ open@^10.0.3: is-inside-container "^1.0.0" is-wsl "^3.1.0" -open@^8.4.0: - version "8.4.0" - resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz" - integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz" @@ -5762,10 +5799,10 @@ select-hose@^2.0.0: resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz" integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== -selenium-webdriver@^4.3.1: - version "4.4.0" - resolved "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.4.0.tgz" - integrity sha512-Du+/xfpvNi9zHAeYgXhOWN9yH0hph+cuX+hHDBr7d+SbtQVcfNJwBzLsbdHrB1Wh7MHXFuIkSG88A9TRRQUx3g== +selenium-webdriver@4.6.1: + version "4.6.1" + resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.6.1.tgz#ac66867206542a40c24b5a44f4ccbae992e962dc" + integrity sha512-FT8Dw0tbzaTp8YYLuwhaCnve/nw03HKrOJrA3aUmTKmxaIFSP4kT2R5fN3K0RpV5kbR0ZnM4FGVI2vANBvekaA== dependencies: jszip "^3.10.0" tmp "^0.2.1" @@ -5781,7 +5818,7 @@ selfsigned@^2.4.1: semver@7.3.5: version "7.3.5" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" @@ -5819,7 +5856,7 @@ send@~0.19.0, send@~0.19.1: serialize-javascript@6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== dependencies: randombytes "^2.1.0" @@ -6041,9 +6078,9 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" -stacktrace-parser@^0.1.10: +stacktrace-parser@0.1.10: version "0.1.10" - resolved "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz" + resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg== dependencies: type-fest "^0.7.1" @@ -6428,15 +6465,20 @@ universalify@^0.2.0: integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + update-browserslist-db@^1.2.0: version "1.2.3" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" @@ -6470,9 +6512,9 @@ utils-merge@1.0.1: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^8.3.2: +uuid@8.3.2, uuid@^8.3.2: version "8.3.2" - resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache@^2.0.3: @@ -6757,7 +6799,7 @@ worker-loader@^3.0.8: workerpool@6.2.0: version "6.2.0" - resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== wrap-ansi@^7.0.0: @@ -6782,7 +6824,12 @@ write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@>=8.7.0, ws@^8.18.0, ws@^8.2.3: +ws@>=8.7.0: + version "8.20.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.20.0.tgz#4cd9532358eba60bc863aad1623dfb045a4d4af8" + integrity sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA== + +ws@^8.18.0, ws@^8.2.3: version "8.18.2" resolved "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz" integrity sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ== @@ -6814,12 +6861,12 @@ yallist@^4.0.0: yargs-parser@20.2.4: version "20.2.4" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== yargs-parser@^20.2.2: version "20.2.9" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs-parser@^21.0.0, yargs-parser@^21.0.1: @@ -6829,7 +6876,7 @@ yargs-parser@^21.0.0, yargs-parser@^21.0.1: yargs-unparser@2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== dependencies: camelcase "^6.0.0" @@ -6839,7 +6886,7 @@ yargs-unparser@2.0.0: yargs@16.2.0: version "16.2.0" - resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: cliui "^7.0.2"