diff --git a/README.md b/README.md index cbb7730da3..613eeb89e8 100644 --- a/README.md +++ b/README.md @@ -283,3 +283,70 @@ $ docker compose run --rm init-core > This `init-core-fca-low` container is a dependency of the `core-fca-low` container. > It will run the migration script every time the `core-fca-low` container is started. > No need to do it manually when using the `dks switch` + + +## Generate a new version of class-validator-0.14.2 + +We forked the class-validator package because we needed inhertiance features and is not yet merged [into the main branch](https://github.com/typestack/class-validator/pull/2641). + +To update the package, follow these steps. + +Get and update the project: +```bash +git clone git@github.com:proconnect-gouv/class-validator.git +``` + +Update the version attribute of `class-validator/package.json`: +```json +{ + "name": "class-validator", + "version": "0.14.2-proconnect.1", + "...": "..." +} +``` + +Build the new package: +```bash +rm -rf build +npm ci --ignore-scripts +npm run prettier:check +npm run lint:check +npm run test:ci +npm run build:es2015 +npm run build:esm5 +npm run build:cjs +npm run build:umd +npm run build:types +cp LICENSE build/LICENSE +cp README.md build/README.md +jq 'del(.devDependencies) | del(.scripts)' package.json > build/package.json +npm pack ./build +``` + +Then push the built package in a dedicated branch: +```bash +git checkout -b build-0.14.2-proconnect.1 +find . -mindepth 1 -maxdepth 1 \ + ! -name '.git' \ + ! -name '.idea' \ + ! -name '.gitignore' \ + ! -name 'build' \ + -exec rm -rf {} + +mv build/* . +rmdir build +git add . +git commit -m "Build version 0.14.2-proconnect.1" +git push +``` + +Update `federation/back/package.json`: + +```json +{ + "dependencies": { + "class-validator": "git+https://github.com/proconnect-gouv/class-validator.git#build-0.14.2-proconnect.1", + } +} +``` + +Run yarn install. diff --git a/back/apps/core-fca-low/src/controllers/interaction.controller.spec.ts b/back/apps/core-fca-low/src/controllers/interaction.controller.spec.ts index ee95321a21..e60ff2014d 100644 --- a/back/apps/core-fca-low/src/controllers/interaction.controller.spec.ts +++ b/back/apps/core-fca-low/src/controllers/interaction.controller.spec.ts @@ -16,7 +16,7 @@ import { ISessionService, SessionService } from '@fc/session'; import { getLoggerMock } from '@mocks/logger'; // --- Mocks for external dependencies --- -import { UserSession } from '../dto'; +import { GetVerifySessionDto, UserSession } from '../dto'; import { CoreFcaAgentNotFromPublicServiceException } from '../exceptions'; import { CoreFcaControllerService } from '../services'; import { InteractionController } from './interaction.controller'; @@ -380,7 +380,7 @@ describe('InteractionController', () => { idpIdentity: { sub: 'user1', extraClaims: 'extra' }, }), set: jest.fn(), - } as unknown as ISessionService; + } as unknown as ISessionService; const interactionAcr = 'high'; identityProviderMock.isActiveById.mockResolvedValue(true); @@ -422,7 +422,7 @@ describe('InteractionController', () => { isSilentAuthentication: true, idpId: 'idp123', }), - } as unknown as ISessionService; + } as unknown as ISessionService; identityProviderMock.isActiveById.mockResolvedValue(false); @@ -440,7 +440,7 @@ describe('InteractionController', () => { interactionId: 'interaction123', idpId: 'idp123', }), - } as unknown as ISessionService; + } as unknown as ISessionService; identityProviderMock.isActiveById.mockResolvedValue(false); configServiceMock.get.mockReturnValueOnce({ urlPrefix: '/prefix' }); @@ -461,7 +461,7 @@ describe('InteractionController', () => { idpId: 'idp123', idpIdentity: { is_service_public: false }, }), - } as unknown as ISessionService; + } as unknown as ISessionService; identityProviderMock.isActiveById.mockResolvedValue(true); serviceProviderMock.getById.mockResolvedValue({ type: 'public' }); @@ -479,7 +479,7 @@ describe('InteractionController', () => { spEssentialAcr: 'high', idpAcr: 'low', }), - } as unknown as ISessionService; + } as unknown as ISessionService; identityProviderMock.isActiveById.mockResolvedValue(true); serviceProviderMock.getById.mockResolvedValue({ type: 'public' }); diff --git a/back/apps/core-fca-low/src/controllers/interaction.controller.ts b/back/apps/core-fca-low/src/controllers/interaction.controller.ts index d474cb59cd..aa5fad1175 100644 --- a/back/apps/core-fca-low/src/controllers/interaction.controller.ts +++ b/back/apps/core-fca-low/src/controllers/interaction.controller.ts @@ -211,7 +211,7 @@ export class InteractionController { @Res() res: Response, @Param() _params: Interaction, @UserSessionDecorator(GetVerifySessionDto) - userSessionService: ISessionService, + userSessionService: ISessionService, ) { const { amr, diff --git a/back/apps/core-fca-low/src/controllers/oidc-client.controller.spec.ts b/back/apps/core-fca-low/src/controllers/oidc-client.controller.spec.ts index 3873844398..eb0d99b565 100644 --- a/back/apps/core-fca-low/src/controllers/oidc-client.controller.spec.ts +++ b/back/apps/core-fca-low/src/controllers/oidc-client.controller.spec.ts @@ -5,7 +5,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AccountFcaService } from '@fc/account-fca'; import { validateDto } from '@fc/common'; import { ConfigService } from '@fc/config'; -import { UserSession } from '@fc/core'; +import { GetIdentityProviderSelectionSessionDto, UserSession } from '@fc/core'; import { CryptographyService } from '@fc/cryptography'; import { CsrfService } from '@fc/csrf'; import { LoggerService } from '@fc/logger'; @@ -128,7 +128,7 @@ describe('OidcClientController', () => { const email = 'user@example.com'; const userSession = { get: jest.fn().mockReturnValue({ idpLoginHint: email }), - } as unknown as ISessionService; + } as unknown as ISessionService; const providers = [ { title: 'Provider One', uid: 'idp1' }, diff --git a/back/apps/core-fca-low/src/controllers/oidc-client.controller.ts b/back/apps/core-fca-low/src/controllers/oidc-client.controller.ts index c20c9cfd6d..5a5c839359 100644 --- a/back/apps/core-fca-low/src/controllers/oidc-client.controller.ts +++ b/back/apps/core-fca-low/src/controllers/oidc-client.controller.ts @@ -68,7 +68,7 @@ export class OidcClientController { async getIdentityProviderSelection( @Res() res: Response, @UserSessionDecorator(GetIdentityProviderSelectionSessionDto) - userSession: ISessionService, + userSession: ISessionService, ) { const { idpLoginHint: email } = userSession.get(); @@ -201,7 +201,7 @@ export class OidcClientController { * @ticket FC-1020 */ @UserSessionDecorator(GetOidcCallbackSessionDto) - userSession: ISessionService, + userSession: ISessionService, ) { // The session is duplicated here to mitigate cookie-theft-based attacks. // For more information, refer to: https://gitlab.dev-franceconnect.fr/france-connect/fc/-/issues/1288 diff --git a/back/apps/core-fca-low/src/decorators/user-session.decorator.spec.ts b/back/apps/core-fca-low/src/decorators/user-session.decorator.spec.ts index 3d8d86408f..2233ae42f9 100644 --- a/back/apps/core-fca-low/src/decorators/user-session.decorator.spec.ts +++ b/back/apps/core-fca-low/src/decorators/user-session.decorator.spec.ts @@ -63,45 +63,25 @@ describe('UserSessionDecoratorFactory', () => { jest.restoreAllMocks(); }); - it('should return the bound session service with all methods when validations pass (with mandatory DTO)', async () => { + it('should return the bound session service with all methods when validations pass', async () => { // Define a dummy DTO for mandatory validation. class DummyDto {} - // Simulate no validation errors for both the mandatory DTO and the UserSession. const validateMock = jest.mocked(validate); - validateMock.mockResolvedValueOnce([]); // For validating DummyDto instance. - validateMock.mockResolvedValueOnce([]); // For validating UserSession instance. + validateMock.mockResolvedValueOnce([]); const result = await UserSessionDecoratorFactory( DummyDto, fakeExecutionContext, ); - // Expect the bound session service to have all expected methods. - expect(typeof result.get).toBe('function'); - expect(typeof result.set).toBe('function'); - expect(typeof result.commit).toBe('function'); - expect(typeof result.duplicate).toBe('function'); - expect(typeof result.reset).toBe('function'); - expect(typeof result.destroy).toBe('function'); - // Call duplicate and ensure that it calls the underlying sessionService.duplicate with the response. const duplicateResult = result.duplicate(); expect(fakeSessionService.duplicate).toHaveBeenCalledWith(fakeResponse); expect(duplicateResult).toBe('duplicatedSession'); - - // Call reset and destroy to verify they are bound with the response. - await result.reset(); - expect(fakeSessionService.reset).toHaveBeenCalledWith(fakeResponse); - await result.destroy(); - expect(fakeSessionService.destroy).toHaveBeenCalledWith(fakeResponse); - - // Commit is not bound with the response so just check it is called. - await result.commit(); - expect(fakeSessionService.commit).toHaveBeenCalled(); }); - it('should throw SessionInvalidSessionException if mandatory DTO validation fails', async () => { + it('should throw SessionInvalidSessionException if a session DTO validation fails', async () => { class DummyDto {} const validationError = [ { @@ -110,9 +90,8 @@ describe('UserSessionDecoratorFactory', () => { }, ]; - // Simulate a validation error for the mandatory DTO. const validateMock = jest.mocked(validate); - validateMock.mockResolvedValueOnce(validationError); // First call (for DummyDto) + validateMock.mockResolvedValueOnce(validationError); await expect( UserSessionDecoratorFactory(DummyDto, fakeExecutionContext), @@ -121,28 +100,9 @@ describe('UserSessionDecoratorFactory', () => { expect(validateMock).toHaveBeenCalledTimes(1); }); - it('should throw SessionInvalidSessionException if UserSession validation fails', async () => { - class DummyDto {} - const validationError = [ - { property: 'user', constraints: { isDefined: 'user must be defined' } }, - ]; - - // Simulate successful validation for the mandatory DTO, then a failure for the UserSession. - const validateMock = jest.mocked(validate); - validateMock.mockResolvedValueOnce([]); // For DummyDto - validateMock.mockResolvedValueOnce(validationError); // For UserSession - - await expect( - UserSessionDecoratorFactory(DummyDto, fakeExecutionContext), - ).rejects.toThrowError(SessionInvalidSessionException); - - expect(validateMock).toHaveBeenCalledTimes(2); - }); - - it('should validate only UserSession when no mandatory DTO is provided', async () => { - // When no mandatory DTO is provided, only one validation (for UserSession) occurs. + it('should validate only UserSession when no session DTO is provided', async () => { const validateMock = jest.mocked(validate); - validateMock.mockResolvedValueOnce([]); // For UserSession + validateMock.mockResolvedValueOnce([]); const result = await UserSessionDecoratorFactory( undefined, @@ -152,14 +112,13 @@ describe('UserSessionDecoratorFactory', () => { expect(typeof result.get).toBe('function'); }); - it('should throw SessionInvalidSessionException if UserSession validation fails when no mandatory DTO is provided', async () => { + it('should throw SessionInvalidSessionException if UserSession validation fails when no session DTO is provided', async () => { const validationError = [ { property: 'user', constraints: { isDefined: 'user must be defined' } }, ]; - // Simulate a validation error for UserSession when no mandatory DTO is provided. const validateMock = jest.mocked(validate); - validateMock.mockResolvedValueOnce(validationError); // For UserSession + validateMock.mockResolvedValueOnce(validationError); await expect( UserSessionDecoratorFactory(undefined, fakeExecutionContext), diff --git a/back/apps/core-fca-low/src/decorators/user-session.decorator.ts b/back/apps/core-fca-low/src/decorators/user-session.decorator.ts index db766c888a..c6adebb7ff 100644 --- a/back/apps/core-fca-low/src/decorators/user-session.decorator.ts +++ b/back/apps/core-fca-low/src/decorators/user-session.decorator.ts @@ -14,7 +14,7 @@ import { ISessionService } from '@fc/session/interfaces'; import { SessionService } from '@fc/session/services'; export const UserSessionDecoratorFactory = async ( - mandatoryPropertiesDto: Class, + userSessionDto: Class = UserSession, ctx: ExecutionContext, ): Promise> => { const sessionService = @@ -40,22 +40,17 @@ export const UserSessionDecoratorFactory = async ( const sessionData = boundSessionService.get(); - if (mandatoryPropertiesDto) { - const object = plainToInstance(mandatoryPropertiesDto, sessionData); - const mandatoryPropertiesErrors = await validate(object as object); + const object = plainToInstance(userSessionDto, sessionData); + const validationErrors = await validate(object as object); - if (mandatoryPropertiesErrors.length) { + if (validationErrors.length) { + if (userSessionDto === UserSession) { + throw new SessionInvalidSessionException(); + } else { throw new SessionInvalidMandatoryFieldsException(); } } - const object = plainToInstance(UserSession, sessionData); - const typeErrors = await validate(object as object); - - if (typeErrors.length) { - throw new SessionInvalidSessionException(); - } - return boundSessionService; }; diff --git a/back/apps/core-fca-low/src/dto/base-identity.dto.ts b/back/apps/core-fca-low/src/dto/base-identity.dto.ts deleted file mode 100644 index 1833b7fbcf..0000000000 --- a/back/apps/core-fca-low/src/dto/base-identity.dto.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - IsAscii, - IsBoolean, - IsEmail, - IsOptional, - IsString, - MaxLength, - MinLength, -} from 'class-validator'; - -export abstract class BaseIdentityDto { - @MinLength(1) - @MaxLength(256) - @IsAscii() - sub: string; - - @IsString() - @MinLength(1) - @MaxLength(256) - given_name: string; - - @IsString() - @MinLength(1) - @MaxLength(256) - usual_name: string; - - @IsEmail() - email: string; - - @MinLength(1) - @MaxLength(256) - @IsAscii() - uid: string; - - @IsString() - @MinLength(1) - @MaxLength(256) - @IsOptional() - siren?: string; - - @IsString() - @MaxLength(256) - siret?: string; - - @IsString() - @MinLength(1) - @MaxLength(256) - @IsOptional() - organizational_unit?: string; - - @IsString() - @MinLength(1) - @MaxLength(256) - @IsOptional() - belonging_population?: string; - - @IsString() - @MinLength(1) - @MaxLength(256) - @IsOptional() - 'chorusdt:societe'?: string; - - @IsString() - @MinLength(1) - @MaxLength(256) - @IsOptional() - 'chorusdt:matricule'?: string; - - @IsBoolean() - @IsOptional() - is_service_public?: boolean; -} diff --git a/back/apps/core-fca-low/src/dto/get-identity-provider-selection-session-dto.ts b/back/apps/core-fca-low/src/dto/get-identity-provider-selection-session-dto.ts index bf7255aec3..ff1d9aa432 100644 --- a/back/apps/core-fca-low/src/dto/get-identity-provider-selection-session-dto.ts +++ b/back/apps/core-fca-low/src/dto/get-identity-provider-selection-session-dto.ts @@ -1,6 +1,8 @@ import { IsDefined } from 'class-validator'; -export class GetIdentityProviderSelectionSessionDto { +import { UserSession } from '@fc/core/dto/user-session.dto'; + +export class GetIdentityProviderSelectionSessionDto extends UserSession { @IsDefined() - readonly idpLoginHint: string; + declare idpLoginHint: string; } diff --git a/back/apps/core-fca-low/src/dto/get-oidc-callback-session-dto.ts b/back/apps/core-fca-low/src/dto/get-oidc-callback-session-dto.ts index f33b4655f6..12075b2209 100644 --- a/back/apps/core-fca-low/src/dto/get-oidc-callback-session-dto.ts +++ b/back/apps/core-fca-low/src/dto/get-oidc-callback-session-dto.ts @@ -1,18 +1,20 @@ import { IsDefined } from 'class-validator'; -export class GetOidcCallbackSessionDto { +import { UserSession } from '@fc/core/dto/user-session.dto'; + +export class GetOidcCallbackSessionDto extends UserSession { @IsDefined() - readonly idpId: string; + declare idpId: string; @IsDefined() - readonly idpName: string; + declare idpName: string; @IsDefined() - readonly idpLabel: string; + declare idpLabel: string; @IsDefined() - readonly idpState: string; + declare idpState: string; @IsDefined() - readonly idpNonce: string; + declare idpNonce: string; } diff --git a/back/apps/core-fca-low/src/dto/get-verify-session-dto.ts b/back/apps/core-fca-low/src/dto/get-verify-session-dto.ts index c2de186ce1..e0493beaac 100644 --- a/back/apps/core-fca-low/src/dto/get-verify-session-dto.ts +++ b/back/apps/core-fca-low/src/dto/get-verify-session-dto.ts @@ -1,17 +1,21 @@ -import { IsDefined } from 'class-validator'; +import { Type } from 'class-transformer'; +import { IsDefined, ValidateNested } from 'class-validator'; import { IdentityFromIdpDto } from './identity-from-idp.dto'; +import { UserSession } from './user-session.dto'; -export class GetVerifySessionDto { +export class GetVerifySessionDto extends UserSession { @IsDefined() - readonly idpId: string; + declare idpId: string; @IsDefined() - readonly idpName: string; + declare idpName: string; @IsDefined() - readonly idpLabel: string; + declare idpLabel: string; @IsDefined() - readonly idpIdentity: Partial; + @ValidateNested() + @Type(() => IdentityFromIdpDto) + declare idpIdentity: IdentityFromIdpDto; } diff --git a/back/apps/core-fca-low/src/dto/identity-for-sp.dto.ts b/back/apps/core-fca-low/src/dto/identity-for-sp.dto.ts index c7868c11f1..5ef3d1b1a3 100644 --- a/back/apps/core-fca-low/src/dto/identity-for-sp.dto.ts +++ b/back/apps/core-fca-low/src/dto/identity-for-sp.dto.ts @@ -1,22 +1,21 @@ import { + IsDefined, IsObject, - IsOptional, IsString, MaxLength, MinLength, } from 'class-validator'; -import { BaseIdentityDto } from '@fc/core/dto/base-identity.dto'; - import { IsPhoneNumberSimpleValidator } from '../validators/is-phone-number-simple-validator.validator'; import { IsSiret } from '../validators/is-siret-validator'; +import { IdentityFromIdpDto } from './identity-from-idp.dto'; -export class IdentityForSpDto extends BaseIdentityDto { +export class IdentityForSpDto extends IdentityFromIdpDto { + @IsDefined({ groups: ['siret'] }) @MinLength(1, { groups: ['siret'] }) @IsSiret({ groups: ['siret'] }) declare siret: string; - @IsOptional({ groups: ['phone_number'] }) @IsString({ groups: ['phone_number'] }) @MinLength(1, { groups: ['phone_number'] }) @MaxLength(256, { groups: ['phone_number'] }) diff --git a/back/apps/core-fca-low/src/dto/identity-from-idp.dto.ts b/back/apps/core-fca-low/src/dto/identity-from-idp.dto.ts index 0c78063eaa..d4e6bdca8f 100644 --- a/back/apps/core-fca-low/src/dto/identity-from-idp.dto.ts +++ b/back/apps/core-fca-low/src/dto/identity-from-idp.dto.ts @@ -1,15 +1,80 @@ import { Transform } from 'class-transformer'; -import { IsOptional } from 'class-validator'; +import { + IsAscii, + IsBoolean, + IsEmail, + IsOptional, + IsString, + MaxLength, + MinLength, +} from 'class-validator'; import { isString } from 'lodash'; -import { BaseIdentityDto } from '@fc/core/dto/base-identity.dto'; +export class IdentityFromIdpDto { + @MinLength(1) + @MaxLength(256) + @IsAscii() + sub: string; -export class IdentityFromIdpDto extends BaseIdentityDto { + @IsString() + @MinLength(1) + @MaxLength(256) + given_name: string; + + @IsString() + @MinLength(1) + @MaxLength(256) + usual_name: string; + + @IsEmail() + email: string; + + @MinLength(1) + @MaxLength(256) + @IsAscii() + uid: string; + + @IsString() + @MinLength(1) + @MaxLength(256) + @IsOptional() + siren?: string; + + @IsString() + @MaxLength(256) @IsOptional() @Transform(({ value }) => isString(value) ? value.replace(/\s/g, '') : value, ) - declare siret?: string; + siret?: string; + + @IsString() + @MinLength(1) + @MaxLength(256) + @IsOptional() + organizational_unit?: string; + + @IsString() + @MinLength(1) + @MaxLength(256) + @IsOptional() + belonging_population?: string; + + @IsString() + @MinLength(1) + @MaxLength(256) + @IsOptional() + 'chorusdt:societe'?: string; + + @IsString() + @MinLength(1) + @MaxLength(256) + @IsOptional() + 'chorusdt:matricule'?: string; + + @IsBoolean() + @IsOptional() + is_service_public?: boolean; @IsOptional() phone_number?: any; diff --git a/back/apps/core-fca-low/src/dto/index.ts b/back/apps/core-fca-low/src/dto/index.ts index 091d8d498a..5ded411767 100644 --- a/back/apps/core-fca-low/src/dto/index.ts +++ b/back/apps/core-fca-low/src/dto/index.ts @@ -1,6 +1,5 @@ export * from './active-user-session.dto'; export * from './app-config.dto'; -export * from './base-identity.dto'; export * from './core-fca-config.dto'; export * from './core-fca-session.dto'; export * from './get-identity-provider-selection-session-dto'; diff --git a/back/apps/core-fca-low/src/dto/user-session.dto.ts b/back/apps/core-fca-low/src/dto/user-session.dto.ts index ab38117e14..9f9bb304cb 100644 --- a/back/apps/core-fca-low/src/dto/user-session.dto.ts +++ b/back/apps/core-fca-low/src/dto/user-session.dto.ts @@ -92,7 +92,7 @@ export class UserSession { @IsString() @IsNotEmpty() @Expose() - readonly idpId?: string; + idpId?: string; @IsOptional() @IsString() diff --git a/back/apps/core-fca-low/src/services/identity.sanitizer.ts b/back/apps/core-fca-low/src/services/identity.sanitizer.ts index 676a445c0a..fe279f3834 100644 --- a/back/apps/core-fca-low/src/services/identity.sanitizer.ts +++ b/back/apps/core-fca-low/src/services/identity.sanitizer.ts @@ -97,7 +97,6 @@ export class IdentitySanitizer { const identityValidationErrors = await validate(identityForSp); // But we may yet throw if the IdP has no default value // to substitute for an incorrect siret - /* istanbul ignore next */ await this.throwIfInvalid( identityValidationErrors, idpId, diff --git a/back/package.json b/back/package.json index 785b9c6048..8b3f77723c 100644 --- a/back/package.json +++ b/back/package.json @@ -49,7 +49,7 @@ "axios": "^1.12.0", "body-parser": "^2.2.0", "class-transformer": "^0.5.1", - "class-validator": "^0.14.2", + "class-validator": "proconnect-gouv/class-validator#build-0.14.2-proconnect.1", "cookie-parser": "^1.4.7", "deep-freeze": "^0.0.1", "ejs": "^3.1.10", diff --git a/back/yarn.lock b/back/yarn.lock index bcf3140ba8..a109860a95 100644 --- a/back/yarn.lock +++ b/back/yarn.lock @@ -2426,10 +2426,9 @@ class-transformer@^0.5.1: resolved "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz" integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== -class-validator@^0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.2.tgz#a3de95edd26b703e89c151a2023d3c115030340d" - integrity sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw== +class-validator@proconnect-gouv/class-validator#build-0.14.2-proconnect.1: + version "0.14.2-proconnect.1" + resolved "https://codeload.github.com/proconnect-gouv/class-validator/tar.gz/2c1e9983782a509917df586298333f55982fcaf0" dependencies: "@types/validator" "^13.11.8" libphonenumber-js "^1.11.1" @@ -6685,7 +6684,14 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -7329,7 +7335,7 @@ word-wrap@^1.2.5: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -7347,6 +7353,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"