diff --git a/src/models/userPermissionChangeLog.js b/src/models/userPermissionChangeLog.js index 051a597dc..35e42dffa 100644 --- a/src/models/userPermissionChangeLog.js +++ b/src/models/userPermissionChangeLog.js @@ -4,7 +4,6 @@ const { Schema } = mongoose; const User = require('./userProfile'); - const UserPermissionChangeLog = new Schema({ logDateTime: { type: String, required: true }, userId: { @@ -14,10 +13,16 @@ const UserPermissionChangeLog = new Schema({ }, individualName: { type: String }, permissions: { type: [String], required: true }, + removedRolePermissions: { type: [String], default: [] }, permissionsAdded: { type: [String], default: [] }, permissionsRemoved: { type: [String], default: [] }, requestorRole: { type: String }, requestorEmail: { type: String, required: true }, + reason: { type: String, default: '' }, }); -module.exports = mongoose.model('UserPermissionChangeLog', UserPermissionChangeLog, 'UserPermissionChangeLogs'); +module.exports = mongoose.model( + 'UserPermissionChangeLog', + UserPermissionChangeLog, + 'UserPermissionChangeLogs', +); diff --git a/src/utilities/__tests__/logUserPermissionChangeByAccount.test.js b/src/utilities/__tests__/logUserPermissionChangeByAccount.test.js new file mode 100644 index 000000000..640bfe6e4 --- /dev/null +++ b/src/utilities/__tests__/logUserPermissionChangeByAccount.test.js @@ -0,0 +1,271 @@ +const UserPermissionChangeLog = require('../../models/userPermissionChangeLog'); +const UserProfile = require('../../models/userProfile'); +const logUserPermissionChangeByAccount = require('../logUserPermissionChangeByAccount'); +const { mockReq } = require('../../test'); + +jest.mock('moment-timezone'); +jest.mock('../../models/userPermissionChangeLog'); +jest.mock('../../models/userProfile'); + +describe('logUserPermissionChangeByAccount', () => { + const user = { + firstName: 'Jane', + lastName: 'Smith', + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('returns when permissions are missing', async () => { + const req = { + ...mockReq, + body: { + ...mockReq.body, + permissions: undefined, + }, + }; + + const res = await logUserPermissionChangeByAccount(req, user); + expect(res).toBeUndefined(); + }); + + test('handles error during latest document retrieval', async () => { + const req = { + ...mockReq, + body: { + ...mockReq.body, + permissions: { + frontPermissions: ['addDeleteEditOwners', 'postTeam'], + backPermissions: ['postTeam'], + }, + }, + }; + + jest.spyOn(UserProfile, 'findById').mockImplementationOnce(() => ({ + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + exec: jest.fn().mockResolvedValue({ + email: 'requestor@example.com', + }), + })); + + const findOneSpy = jest + .spyOn(UserPermissionChangeLog, 'findOne') + .mockImplementationOnce(() => ({ + sort: jest.fn().mockReturnThis(), + exec: jest.fn().mockImplementationOnce((callback) => { + callback(new Error('No document found')); + }), + })); + + const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + await logUserPermissionChangeByAccount(req, user); + expect(findOneSpy).toHaveBeenCalledWith({ + userId: req.params.userId, + }); + + expect(consoleError).toHaveBeenCalledWith( + 'Error logging permission change:', + expect.any(Error), + ); + consoleError.mockRestore(); + }); + + test('creates new document when no previous log for user is found', async () => { + const req = { + ...mockReq, + body: { + ...mockReq.body, + permissions: { + frontPermissions: ['addDeleteEditOwners', 'postTeam'], + backPermissions: ['postTeam'], + }, + reason: 'Permissions Changed', + }, + }; + + jest.spyOn(UserProfile, 'findById').mockImplementationOnce(() => ({ + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + exec: jest.fn().mockResolvedValue({ + email: 'requestor@example.com', + }), + })); + + jest.spyOn(UserPermissionChangeLog, 'findOne').mockImplementationOnce(() => ({ + sort: jest.fn().mockReturnThis(), + exec: jest.fn().mockImplementationOnce((callback) => { + callback(null, null); + }), + })); + + const saveSpy = jest.spyOn(UserPermissionChangeLog.prototype, 'save').mockResolvedValueOnce({ + individualName: `INDIVIDUAL: Jane Smith`, + permissions: ['addDeleteEditOwners', 'postTeam'], + removedRolePermissions: [], + permissionsAdded: ['addDeleteEditOwners', 'postTeam'], + permissionsRemoved: [], + requestorRole: req.body.requestor.role, + reason: req.body.reason, + requestorEmail: 'requestor@example.com', + }); + + await logUserPermissionChangeByAccount(req, user); + expect(saveSpy).toHaveBeenCalled(); + + const savedLog = saveSpy.mock.instances[0].toObject(); + expect(savedLog.userId.toString()).toBe(req.params.userId); + expect(savedLog.logDateTime).toEqual(expect.any(String)); + expect(savedLog).toMatchObject({ + individualName: 'INDIVIDUAL: Jane Smith', + permissions: ['addDeleteEditOwners', 'postTeam'], + removedRolePermissions: [], + permissionsAdded: ['addDeleteEditOwners', 'postTeam'], + permissionsRemoved: [], + requestorRole: req.body.requestor.role, + reason: req.body.reason, + requestorEmail: 'requestor@example.com', + }); + }); + + test('creates new document for user', async () => { + const req = { + ...mockReq, + body: { + ...mockReq.body, + permissions: { + frontPermissions: ['addDeleteEditOwners', 'postTeam'], + removedDefaultPermissions: ['seeAllUsers'], + backPermissions: ['postTeam'], + }, + reason: 'Permissions Changed', + }, + }; + + jest.spyOn(UserProfile, 'findById').mockImplementationOnce(() => ({ + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + exec: jest.fn().mockResolvedValue({ + email: 'requestor@example.com', + }), + })); + + const priorLog = { + permissions: ['addDeleteEditOwners'], + removedRolePermissions: ['seeAllUsers'], + }; + + jest.spyOn(UserPermissionChangeLog, 'findOne').mockImplementationOnce(() => ({ + sort: jest.fn().mockReturnThis(), + exec: jest.fn().mockImplementationOnce((callback) => { + callback(null, priorLog); + }), + })); + + const saveSpy = jest.spyOn(UserPermissionChangeLog.prototype, 'save').mockResolvedValueOnce({ + individualName: `INDIVIDUAL: Jane Smith`, + permissions: ['addDeleteEditOwners', 'postTeam'], + removedRolePermissions: [], + permissionsAdded: ['postTeam'], + permissionsRemoved: [], + requestorRole: req.body.requestor.role, + reason: req.body.reason, + requestorEmail: 'requestor@example.com', + }); + + await logUserPermissionChangeByAccount(req, user); + expect(saveSpy).toHaveBeenCalled(); + + const savedLog = saveSpy.mock.instances[0].toObject(); + expect(savedLog.userId.toString()).toBe(req.params.userId); + expect(savedLog.logDateTime).toEqual(expect.any(String)); + expect(savedLog).toMatchObject({ + individualName: 'INDIVIDUAL: Jane Smith', + permissions: ['addDeleteEditOwners', 'postTeam'], + removedRolePermissions: ['seeAllUsers'], + permissionsAdded: ['postTeam'], + permissionsRemoved: [], + requestorRole: req.body.requestor.role, + reason: req.body.reason, + requestorEmail: 'requestor@example.com', + }); + }); + + test('return early if no new changes in permissions list from last update and no role change', async () => { + const req = { + ...mockReq, + body: { + ...mockReq.body, + permissions: { + frontPermissions: ['addDeleteEditOwners', 'postTeam'], + removedDefaultPermissions: ['seeAllUsers'], + backPermissions: ['postTeam'], + }, + reason: 'Permissions Changed', + }, + }; + + jest.spyOn(UserProfile, 'findById').mockImplementationOnce(() => ({ + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + exec: jest.fn().mockResolvedValue({ + email: 'requestor@example.com', + }), + })); + + const priorLog = { + permissions: ['addDeleteEditOwners', 'postTeam'], + removedRolePermissions: ['seeAllUsers'], + }; + + jest.spyOn(UserPermissionChangeLog, 'findOne').mockImplementationOnce(() => ({ + sort: jest.fn().mockReturnThis(), + exec: jest.fn().mockImplementationOnce((callback) => { + callback(null, priorLog); + }), + })); + + const saveSpy = jest.spyOn(UserPermissionChangeLog.prototype, 'save').mockResolvedValueOnce({}); + + const res = await logUserPermissionChangeByAccount(req, user); + expect(saveSpy).not.toHaveBeenCalled(); + expect(res).toBeUndefined(); + }); + + test('return for edge case of no changes in permissions for user with no previous change log', async () => { + const req = { + ...mockReq, + body: { + ...mockReq.body, + permissions: { + frontPermissions: [], + removedDefaultPermissions: [], + backPermissions: [], + }, + reason: 'Permissions Changed', + }, + }; + + jest.spyOn(UserProfile, 'findById').mockImplementationOnce(() => ({ + select: jest.fn().mockReturnThis(), + lean: jest.fn().mockReturnThis(), + exec: jest.fn().mockResolvedValue({ + email: 'requestor@example.com', + }), + })); + + jest.spyOn(UserPermissionChangeLog, 'findOne').mockImplementationOnce(() => ({ + sort: jest.fn().mockReturnThis(), + exec: jest.fn().mockImplementationOnce((callback) => { + callback(null, null); + }), + })); + + const saveSpy = jest.spyOn(UserPermissionChangeLog.prototype, 'save').mockResolvedValueOnce({}); + + const res = await logUserPermissionChangeByAccount(req, user); + expect(saveSpy).not.toHaveBeenCalled(); + expect(res).toBeUndefined(); + }); +}); diff --git a/src/utilities/logUserPermissionChangeByAccount.js b/src/utilities/logUserPermissionChangeByAccount.js index e310e5956..565256739 100644 --- a/src/utilities/logUserPermissionChangeByAccount.js +++ b/src/utilities/logUserPermissionChangeByAccount.js @@ -18,8 +18,10 @@ const findLatestRelatedLog = (userId) => // +const checkPermissionArray = (permArray) => (Array.isArray(permArray) ? permArray : []); + const logUserPermissionChangeByAccount = async (req, user) => { - const { permissions, requestor } = req.body; + const { permissions, requestor, reason } = req.body; const dateTime = moment().tz('America/Los_Angeles').format(); try { @@ -27,11 +29,6 @@ const logUserPermissionChangeByAccount = async (req, user) => { return; } - const Permissions = Array.isArray(permissions.frontPermissions) - ? permissions.frontPermissions - : []; - const { userId } = req.params; - // Fetch requestor email (may be null if requestor deleted) const requestorDoc = await UserProfile.findById(requestor.requestorId) .select('email') @@ -39,28 +36,53 @@ const logUserPermissionChangeByAccount = async (req, user) => { .exec(); const requestorEmail = requestorDoc?.email ?? 'unknown'; - const { firstName, lastName } = user; + // const { firstName, lastName } = user; let permissionsAdded = []; let permissionsRemoved = []; + const roleChanged = reason?.includes('Role Changed'); + const { userId } = req.params; + const Permissions = checkPermissionArray(permissions.frontPermissions); + const removedPermissions = checkPermissionArray(permissions.removedDefaultPermissions); // removed default permissions + const rolePermissions = checkPermissionArray(permissions.defaultPermissions); // default permissions for user provided by their role + const changedPermissions = [...Permissions, ...removedPermissions]; + + // Use the user object passed from controller (already fetched) + const { firstName, lastName } = user; const document = await findLatestRelatedLog(userId); if (document) { - const docPermissions = Array.isArray(document.permissions) ? document.permissions : []; - // const sortedDoc = [...docPermissions].sort(); - const sortedDoc = [...docPermissions].sort((a, b) => a.localeCompare(b)); - // const sortedCurrent = [...Permissions].sort(); - const sortedCurrent = [...Permissions].sort((a, b) => a.localeCompare(b)); - if (JSON.stringify(sortedDoc) === JSON.stringify(sortedCurrent)) { + const docPermissions = checkPermissionArray(document.permissions); + const docRemovedRolePermissions = checkPermissionArray(document.removedRolePermissions); + const docSavedChanges = [...docPermissions, ...docRemovedRolePermissions]; + const sortedSaved = [...docSavedChanges].sort((a, b) => a.localeCompare(b)); + const sortedChanged = [...changedPermissions].sort((a, b) => a.localeCompare(b)); + // no new changes in permissions list from last update and no role change + if ( + sortedSaved.length === sortedChanged.length && + sortedSaved.every((value, index) => value === sortedChanged[index]) && + !roleChanged + ) { return; } - permissionsRemoved = docPermissions.filter((item) => !Permissions.includes(item)); - permissionsAdded = Permissions.filter((item) => !docPermissions.includes(item)); + + const updatedRemovedPermissions = removedPermissions.filter((item) => !docRemovedRolePermissions.includes(item)); + const removedUserAddedPermissions = docPermissions.filter( + (item) => !Permissions.includes(item) && !rolePermissions.includes(item), + ); // removed user added permissions + const updatedAddedPermissions = Permissions.filter((item) => !docPermissions.includes(item)); + const readdedUserRemovedPermissions = docRemovedRolePermissions.filter( + (item) => !removedPermissions.includes(item) && rolePermissions.includes(item), + ); + permissionsRemoved = [...updatedRemovedPermissions, ...removedUserAddedPermissions]; + permissionsAdded = [...updatedAddedPermissions, ...readdedUserRemovedPermissions]; } else { permissionsAdded = Permissions; + permissionsRemoved = removedPermissions; // adds removed default permissions to permissionsRemoved for inital log } - if (permissionsRemoved.length === 0 && permissionsAdded.length === 0) { + // no permission added nor removed nor role change + if (permissionsRemoved.length === 0 && permissionsAdded.length === 0 && !roleChanged) { return; } @@ -69,9 +91,11 @@ const logUserPermissionChangeByAccount = async (req, user) => { userId, individualName: `INDIVIDUAL: ${firstName} ${lastName}`, permissions: Permissions, + removedRolePermissions: removedPermissions, permissionsAdded, permissionsRemoved, requestorRole: requestor.role, + reason, requestorEmail, }); diff --git a/yarn.lock b/yarn.lock index c9efba5fb..822a45dd1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1870,6 +1870,136 @@ resolved "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz" integrity sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA== +"@img/sharp-libvips-darwin-x64@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz" + integrity sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw== + +"@img/sharp-libvips-linux-arm@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz" + integrity sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ== + +"@img/sharp-libvips-linux-arm64@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz" + integrity sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA== + +"@img/sharp-libvips-linux-ppc64@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz" + integrity sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw== + +"@img/sharp-libvips-linux-riscv64@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz" + integrity sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w== + +"@img/sharp-libvips-linux-s390x@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz" + integrity sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ== + +"@img/sharp-libvips-linux-x64@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz" + integrity sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w== + +"@img/sharp-libvips-linuxmusl-arm64@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz" + integrity sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw== + +"@img/sharp-libvips-linuxmusl-x64@1.3.2": + version "1.3.2" + resolved "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz" + integrity sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ== + +"@img/sharp-linux-arm@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz" + integrity sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.3.2" + +"@img/sharp-linux-arm64@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz" + integrity sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.3.2" + +"@img/sharp-linux-ppc64@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz" + integrity sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA== + optionalDependencies: + "@img/sharp-libvips-linux-ppc64" "1.3.2" + +"@img/sharp-linux-riscv64@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz" + integrity sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ== + optionalDependencies: + "@img/sharp-libvips-linux-riscv64" "1.3.2" + +"@img/sharp-linux-s390x@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz" + integrity sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.3.2" + +"@img/sharp-linux-x64@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz" + integrity sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.3.2" + +"@img/sharp-linuxmusl-arm64@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz" + integrity sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.3.2" + +"@img/sharp-linuxmusl-x64@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz" + integrity sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.3.2" + +"@img/sharp-wasm32@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz" + integrity sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w== + dependencies: + "@emnapi/runtime" "^1.11.1" + +"@img/sharp-webcontainers-wasm32@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz" + integrity sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q== + dependencies: + "@img/sharp-wasm32" "0.35.3" + +"@img/sharp-win32-arm64@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz" + integrity sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w== + +"@img/sharp-win32-ia32@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz" + integrity sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw== + +"@img/sharp-win32-x64@0.35.3": + version "0.35.3" + resolved "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz" + integrity sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" @@ -7968,16 +8098,12 @@ mimic-function@^5.0.0: integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.5" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz" - integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== + version "3.1.2" dependencies: brace-expansion "^1.1.7" minimatch@^8.0.2: - version "8.0.7" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz" - integrity sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg== + version "8.0.4" dependencies: brace-expansion "^2.0.1" @@ -8769,9 +8895,7 @@ possible-typed-array-names@^1.0.0: integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== postcss@^8.3.11: - version "8.5.12" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz" - integrity sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA== + version "8.5.6" dependencies: nanoid "^3.3.11" picocolors "^1.1.1"