diff --git a/.envrc b/.envrc index e60feb2742..3fa8c01ecb 100644 --- a/.envrc +++ b/.envrc @@ -12,7 +12,7 @@ export CT_URL='https://cloudtamer.cms.gov/' export CT_AWS_ROLE='ct-ado-managedcare-developer-admin' export CT_IDMS='2' -# values formerly in .env (required) +# required values export SASS_PATH='src:../../node_modules' export REACT_APP_AUTH_MODE='LOCAL' export REACT_APP_STAGE_NAME='local' @@ -26,6 +26,7 @@ export DATABASE_URL='postgresql://postgres:shhhsecret@localhost:5432/postgres?sc export EMAILER_MODE='LOCAL' export LD_SDK_KEY='this-value-must-be-set-in-local' export PARAMETER_STORE_MODE='LOCAL' +export JWT_SECRET='3fd2e448ed2cec1fa46520f1b64bcb243c784f68db41ea67ef9abc45c12951d3e770162829103c439f01d2b860d06ed0da1a08895117b1ef338f1e4ed176448a' # pragma: allowlist secret export REACT_APP_OTEL_COLLECTOR_URL='http://localhost:4318/v1/traces' export REACT_APP_LD_CLIENT_ID='this-value-can-be-set-in-local-if-desired' diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index 98bc3bed0f..53c29dd055 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -133,7 +133,7 @@ jobs: working-directory: services/uploads/src/avLayer run: ./dockerbuild.sh - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: lambda-layers-clamav path: ./services/uploads/src/avLayer/build/lambda_layer.zip diff --git a/docs/technical-design/launch-darkly-testing-approach.md b/docs/technical-design/launch-darkly-testing-approach.md index f697fb47ed..abd1c8a423 100644 --- a/docs/technical-design/launch-darkly-testing-approach.md +++ b/docs/technical-design/launch-darkly-testing-approach.md @@ -23,60 +23,98 @@ export LD_SDK_KEY='Place Launch Darkly SDK key here' ## Feature flag unit testing ### Client side unit testing -Client side unit testing utilizes `jest.spyOn()` to mock the LaunchDarkly `useLDClient` hook and return default flag values or values specified. This implementation is done in our jest helper function `ldUseClientSpy()` located in `app-web/src/testHelpers/jestHelpers.tsx`. +Client side unit testing utilizes the `LDProvider`, from `launchdarkly-react-client-sdk`, in our [renderWithProviders](../../services/app-web/src/testHelpers/jestHelpers.tsx) function to set up feature flags for each of the tests. Currently, LaunchDarkly does not provide an actual mock `LDProvider` so if or when they do, then we could update this with that provider. -`ldUseClientSpy` takes in an object of feature flags and values as an argument. You can configure multiple flags with the object passed into `ldUseClientSpy`. +We use this method for testing because the official documented [unit testing](https://docs.launchdarkly.com/guides/sdk/unit-tests/?q=unit+test) method by LaunchDarkly does not work with our LaunchDarkly implementation. Our implementation follow exactly the documentation, so it could be how we are setting up our unit tests. Previously we had used `jest.spyOn` to intercept `useLDClient` and mock the `useLDClient.variation()` function with our defined feature flag values. With `launchdarkly-react-client-sdk@3.0.10` that method did not work anymore. -```javascript -ldUseClientSpy({ - 'rates-across-submissions': true, - 'rate-cert-assurance': true, -}) -``` +#### Configuration +When using the `LDProvider` we need to pass in a mocked `ldClient` in the configuration. This allows us to initialize `ldClient` outside of the provider, which would have required the provider to perform an API call to LaunchDarkly. Now that this API call does not happen it isolates our unit tests from the feature flag values on the LaunchDarkly server and only use the values we define in each test. -To configure feature flags for a single test place `ldUseClientSpy` at the beginning of your test. +The configuration below, in `renderWithProviders`, the `ldClient` field is how we initialize `ldClient` with our defined flag values. We are using the `ldClientMock()` function to generate a mock that matches the type this field requires. -```javascript -it('cannot continue if no documents are added to the second rate', async () => { - ldUseClientSpy({ 'rates-across-submissions': true }) - const mockUpdateDraftFn = jest.fn() - renderWithProviders( - , - { - apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], - }, - } - ) +You will also see that, compared to our configuration in [app-web/src/index.tsx](../../services/app-web/src/index.tsx), the config needed to connect to LaunchDarkly is replaced with `test-url`. - ... -}) +```typescript +const ldProviderConfig: ProviderConfig = { + clientSideID: 'test-url', + options: { + bootstrap: flags, + baseUrl: 'test-url', + streamUrl: 'test-url', + eventsUrl: 'test-url', + }, + ldClient: ldClientMock(flags) +} ``` -To configure multiple tests inside a `describe` block you can: -- Follow the method for single test on each test inside the `describe`. -- If all the tests require the same flag configuration place `ldUseClientSpy` at the top of the block in `beforeEach()`. +The two important functions in the `ldCientMock` are `variation` and `allFlags`. These two functions are the ones we use in the app to get feature flags and here we are mocking them with the flag values we define in each test. If we need any other functions in `ldClient` we would just add the mock to `ldClientMock()`. ```javascript -describe('rates across submissions', () => { - beforeEach(() => - ldUseClientSpy({ - 'rates-across-submissions': true, - }) - ) - afterEach(() => { - jest.clearAllMocks() - }) - - ... +const ldClientMock = (featureFlags: FeatureFlagSettings): LDClient => ({ + ... other functions, + variation: jest.fn( + ( + flag: FeatureFlagLDConstant, + defaultValue: FlagValue | undefined + ) => featureFlags[flag] ?? defaultValue + ), + allFlags: jest.fn(() => featureFlags), }) ``` -It's always best to `jest.clearAllMocks()` after each test with either one of these methods, otherwise, preceding tests may have the same flag configured as the previous test. +We define our initial feature flag values in the `flags` variable by combining the default feature flag values with values passed into `renderWithProviders` for each test. Looking at the code snippet below from `renderWithProviders`, we get the default flag values from [flags.ts](../../services/app-web/src/common-code/featureFlags/flags.ts) using `getDefaultFeatureFlags()` then merge that with `option.featureFlags` values passed into `renderWithProviders`. This will allow each test to configure the specific feature flag values for that test and supply default values for flags the test did not define. + +```typescript +const { + routerProvider = {}, + apolloProvider = {}, + authProvider = {}, + s3Provider = undefined, + location = undefined, + featureFlags = undefined +} = options || {} + +const flags = { + ...getDefaultFeatureFlags(), + ...featureFlags +} + +const ldProviderConfig: ProviderConfig = { + clientSideID: 'test-url', + options: { + bootstrap: flags, + baseUrl: 'test-url', + streamUrl: 'test-url', + eventsUrl: 'test-url', + }, + ldClient: ldClientMock(flags) +} +``` + +#### Examples + +Using this method in our unit tests is simple and similar to how we configure the other providers. When calling `renderWithProdivers` we need to supply the second argument `options` with the `featureFlag` field. + +In the example below we set `featureFlag` with an object that contains two feature flags and their values. When this test is run, the component will be supplied with these two flag values along with the other default flag values from [flags.ts](../../services/app-web/src/common-code/featureFlags/flags.ts). Take note that the `featureFlag` field is type `FeatureFlagSettings` so you will only be allowed to define flags that exists in [flags.ts](../../services/app-web/src/common-code/featureFlags/flags.ts). + +```javascript +renderWithProviders( + , + { + apolloProvider: { + mocks: [fetchCurrentUserMock({ statusCode: 200 })], + }, + featureFlags: { + 'rate-edit-unlock': false, + '438-attestation': true + } + } +) +``` ### Server side unit testing LaunchDarkly server side implementation is done by configuring our resolvers with `ldService` dependency. In our resolver we then can use the method `getFeatureFlag` from `ldService` to get the flag value from LaunchDarkly. diff --git a/docs/technical-design/third_party_api_access.md b/docs/technical-design/third_party_api_access.md new file mode 100644 index 0000000000..a13487aa3d --- /dev/null +++ b/docs/technical-design/third_party_api_access.md @@ -0,0 +1,16 @@ +# Third Party API Access + +## Overview +In order to make a request to the API, users (3rd party and non) need to be authenticated (signed in) and authorized (have permission to make a particular request). Non 3rd party users are both authenticated and authorized with Cognito. While 3rd party users will be able to authenticate with cognito, authorization will happen separately that we can grant them longer term credentials for authorization. + +## Process for 3rd Parties to Use the MC-Review API + +1. 3rd party is authenticated (signs on) +2. 3rd party requests a JWT +3. 3rd party uses the JWT to make a request to the MC-Review API +4. Request is sent to `v1/graphql/external` via the `graphql` API gateway endpoint, which invokes the lambda authorizer +5. The lambda authorizer, `third_party_api_authorizer`, verifies the JWT that the 3rd party sent with their request. If the JWT is valid (valid user, and not expired) the lambda returns an “allow” policy document, otherwise it returns a “deny”. This policy determines if the request can proceed. +6. When authorization is successful the user ID that was granted the JWT is used to fetch a full user record from postgres. This is user is then a part of the context for the resolver. + +## JWT Security +Like previously mentioned, third parties will need to have a valid JWT in order to access the MC-Review API. More can be found on JWT security [here](api-jwt-security.md) \ No newline at end of file diff --git a/docs/templates/technical-design-discussion-template.md b/docs/templates/technical-design-discussion-template.md index 1e765e26e4..2fe0d8c1a7 100644 --- a/docs/templates/technical-design-discussion-template.md +++ b/docs/templates/technical-design-discussion-template.md @@ -1,79 +1,54 @@ --- -title: Technical Design Discussion Template +title: Technical Discovery Template --- -## Title of Design Doc +# Title of Design Discovery Doc ## Overview -In one or two sentences, -summarize what the problem is and how you intend on solving it. -It's helpful to outline the stakeholders here, -as it informs why this problem merits solving. +In one or two sentences, summarize the problem (may be a feature epic under consideration) + +## Links + +Jira ticket links or diagrams ## Constraints (optional) -Write what this document will not cover. +Write what this document will not cover ## Terminology (optional) -Define any terminology you are using that could be application system jargon +Define any application system jargon -## Background +## Discovery This section is for going into more detail about the problem. -- Who are the stakeholders and how have they been impacted? -- Historically, what effect has the problem it had? -- Is there data to illustrate the impact? -- Is there an existing solution? - If so, why does it need to be improved on? -- Are there past projects or designs to link for context? +### What do we need to be able to do? -## Examples +### How should we do it? -At least one example should be used to help the audience understand the context better. +### What relevant code or tooling do we have already? -## Proposed Solution +### What architectural considerations exist? +examples: new or adjusted programming patterns, new or adjusted infrastructure, new libraries introduced, permissions modeling changes, data model changes. Was there a build vs. buy solution? Was there scope considerations or tradeoff? -Detail your solution here. -Start with a broad overview and then go into detail on each portion of the design. +### How will we test this ? -- What changes will the stakeholder/client see? - This should clearly illustrate how the stakeholders' needs are being met -- How exactly does it solve the problem outlined above? - Explain how this solution applies to the use cases identified. -- Why are you picking this solution? -- What are the limits of the proposed solution? - At what point will the design cease to be a viable solution? -- How will you measure the success of your solution? -- If priorities shift or the solution becomes too cumbersome to implement, how will you roll back? -Visual representations of the solution can be helpful here (ex. a diagram for a request lifecycle). - -## Implementation +## Proposed Solution -Without going too much into individual tasks, -write an overview of what this solution's implementation would look like. +Detail your solution here. Start with a broad overview and then list the ticket-level tasks as you see them. -- Can this be broken down into multiple technical efforts? -- What is the tech stack involved? -- Will additional infrastructure be needed to achieve this? -- How will you test this? +Visual representations of the solution can be helpful here as well. -## Trade-offs +## Dependencies (optional) -- This section should go over other possible solutions, - and why you chose yours over them. -- Was there a build vs. buy solution? -- What industry standards/practices already exist? -- Why is your solution better? +Are there any dependencies on other feature epics or third party tools? Do we need anything from other teams? -## Cross Team Dependencies (optional) +## Open Questions -If there are multiple teams or clients that are dependencies for implementation to be successful, -list them here. -Examples of this are security or design collaboration/reviews. +Add any open questions still remaining. ## Further Reading diff --git a/package.json b/package.json index 2e2a4c1249..e560932034 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "devDependencies": { "@bahmutov/cypress-esbuild-preprocessor": "^2.2.0", "@cypress-audit/pa11y": "^1.3.0", - "chromedriver": "^120.0.1", + "chromedriver": "^121.0.0", "cypress": "^12.16.0", "cypress-pipe": "^2.0.0", "danger": "^11.2.6", diff --git a/services/app-api/serverless.yml b/services/app-api/serverless.yml index abb3d73f1b..7b419b7bcc 100644 --- a/services/app-api/serverless.yml +++ b/services/app-api/serverless.yml @@ -34,6 +34,9 @@ custom: reactAppOtelCollectorUrl: ${env:REACT_APP_OTEL_COLLECTOR_URL, ssm:/configuration/react_app_otel_collector_url} dbURL: ${env:DATABASE_URL} ldSDKKey: ${env:LD_SDK_KEY, ssm:/configuration/ld_sdk_key_feds} + # because the secret is in JSON in secret manager, we have to pass it into jwtSecret when not running locally + jwtSecretJSON: ${env:CF_CONFIG_IGNORED_LOCALLY, ssm:/aws/reference/secretsmanager/api_jwt_secret_${sls:stage}} + jwtSecret: ${env:JWT_SECRET, self:custom.jwtSecretJSON.jwtsigningkey} webpack: webpackConfig: 'webpack.config.js' packager: 'yarn' @@ -154,6 +157,7 @@ provider: AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-handler OPENTELEMETRY_COLLECTOR_CONFIG_FILE: /var/task/collector.yml LD_SDK_KEY: ${self:custom.ldSDKKey} + JWT_SECRET: ${self:custom.jwtSecret} layers: prismaClientMigration: diff --git a/services/app-api/src/authn/index.ts b/services/app-api/src/authn/index.ts index 485ebb656a..c7af248951 100644 --- a/services/app-api/src/authn/index.ts +++ b/services/app-api/src/authn/index.ts @@ -1,6 +1,7 @@ export type { userFromAuthProvider } from './authn' export { userFromCognitoAuthProvider, lookupUserAurora } from './cognitoAuthn' +export { userFromThirdPartyAuthorizer } from './thirdPartyAuthn' export { userFromLocalAuthProvider, diff --git a/services/app-api/src/authn/thirdPartyAuthn.ts b/services/app-api/src/authn/thirdPartyAuthn.ts new file mode 100644 index 0000000000..d8c5c8923d --- /dev/null +++ b/services/app-api/src/authn/thirdPartyAuthn.ts @@ -0,0 +1,39 @@ +import { ok, err } from 'neverthrow' +import type { Store } from '../postgres' +import { lookupUserAurora } from './cognitoAuthn' +import { initTracer, recordException } from '../../../uploads/src/lib/otel' + +export async function userFromThirdPartyAuthorizer( + store: Store, + userId: string +) { + // setup otel tracing + const otelCollectorURL = process.env.REACT_APP_OTEL_COLLECTOR_URL + if (!otelCollectorURL || otelCollectorURL === '') { + const errMsg = + 'Configuration Error: REACT_APP_OTEL_COLLECTOR_URL must be set' + throw errMsg + } + + const serviceName = 'third-party-authorizer' + initTracer(serviceName, otelCollectorURL) + + try { + // Lookup user from postgres + const auroraUser = await lookupUserAurora(store, userId) + if (auroraUser instanceof Error) { + return err(auroraUser) + } + + if (auroraUser === undefined) { + return err(auroraUser) + } + + return ok(auroraUser) + } catch (e) { + const err = new Error('ERROR: failed to look up user in postgres') + + recordException(err, serviceName, 'lookupUser') + throw err + } +} diff --git a/services/app-api/src/handlers/apollo_gql.ts b/services/app-api/src/handlers/apollo_gql.ts index 1634285734..0e675f6f42 100644 --- a/services/app-api/src/handlers/apollo_gql.ts +++ b/services/app-api/src/handlers/apollo_gql.ts @@ -13,6 +13,7 @@ import type { userFromAuthProvider } from '../authn' import { userFromCognitoAuthProvider, userFromLocalAuthProvider, + userFromThirdPartyAuthorizer, } from '../authn' import { newLocalEmailer, newSESEmailer } from '../emailer' import { NewPostgresStore } from '../postgres/postgresStore' @@ -59,10 +60,17 @@ function contextForRequestForFetcher(userFetcher: userFromAuthProvider): ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any const anyContext = context as any const requestSpan = anyContext[requestSpanKey] - const authProvider = event.requestContext.identity.cognitoAuthenticationProvider - if (authProvider) { + // This handler is shared with the third_party_API_authorizer + // when called from the 3rd party authorizer the cognito auth provider + // is not valid for instead the authorizer returns a user ID + // that is used to fetch the user + const fromThirdPartyAuthorizer = event.requestContext.path.includes( + '/v1/graphql/external' + ) + + if (authProvider || fromThirdPartyAuthorizer) { try { // check if the user is stored in postgres // going to clean this up, but we need the store in the @@ -81,8 +89,21 @@ function contextForRequestForFetcher(userFetcher: userFromAuthProvider): ({ } const store = NewPostgresStore(pgResult) - const userResult = await userFetcher(authProvider, store) + const userId = event.requestContext.authorizer?.principalId + + let userResult + if (authProvider && !fromThirdPartyAuthorizer) { + userResult = await userFetcher(authProvider, store) + } else if (fromThirdPartyAuthorizer && userId) { + userResult = await userFromThirdPartyAuthorizer( + store, + userId + ) + } + if (userResult === undefined) { + throw new Error(`Log: userResult must be supplied`) + } if (!userResult.isErr()) { return { user: userResult.value, @@ -98,7 +119,7 @@ function contextForRequestForFetcher(userFetcher: userFromAuthProvider): ({ throw new Error('Log: placing user in gql context failed') } } else { - throw new Error('Log: no AuthProvider') + throw new Error('Log: no AuthProvider from an internal API user.') } } } @@ -132,7 +153,6 @@ function tracingMiddleware(wrapped: Handler): Handler { return async function (event, context, completion) { // get the parent context from headers const ctx = propagation.extract(ROOT_CONTEXT, event.headers) - const span = tracer.startSpan( 'handleRequest', { @@ -177,6 +197,7 @@ async function initializeGQLHandler(): Promise { const otelCollectorUrl = process.env.REACT_APP_OTEL_COLLECTOR_URL const parameterStoreMode = process.env.PARAMETER_STORE_MODE const ldSDKKey = process.env.LD_SDK_KEY + const jwtSecret = process.env.JWT_SECRET // START Assert configuration is valid if (emailerMode !== 'LOCAL' && emailerMode !== 'SES') @@ -212,6 +233,13 @@ async function initializeGQLHandler(): Promise { 'Configuration Error: LD_SDK_KEY is required to run app-api.' ) } + + if (jwtSecret === undefined || jwtSecret === '') { + throw new Error( + 'Configuration Error: JWT_SECRET is required to run app-api.' + ) + } + // END const pgResult = await configurePostgres(dbURL, secretsManagerSecret) @@ -310,8 +338,8 @@ async function initializeGQLHandler(): Promise { // Hard coding this for now, next job is to run this config to this app. const jwtLib = newJWTLib({ - issuer: 'fakeIssuer', - signingKey: 'notrandom', + issuer: `mcreview-${stageName}`, + signingKey: Buffer.from(jwtSecret, 'hex'), expirationDurationS: 90 * 24 * 60 * 60, // 90 days }) diff --git a/services/app-api/src/handlers/third_party_API_authorizer.ts b/services/app-api/src/handlers/third_party_API_authorizer.ts index a3741ba476..61b0e58083 100644 --- a/services/app-api/src/handlers/third_party_API_authorizer.ts +++ b/services/app-api/src/handlers/third_party_API_authorizer.ts @@ -6,10 +6,22 @@ import type { } from 'aws-lambda' import { newJWTLib } from '../jwt' -// Hard coding this for now, next job is to run this config to this app. +const stageName = process.env.stage +const jwtSecret = process.env.JWT_SECRET + +if (stageName === undefined) { + throw new Error('Configuration Error: stage is required') +} + +if (jwtSecret === undefined || jwtSecret === '') { + throw new Error( + 'Configuration Error: JWT_SECRET is required to run app-api.' + ) +} + const jwtLib = newJWTLib({ - issuer: 'fakeIssuer', - signingKey: 'notrandom', + issuer: `mcreview-${stageName}`, + signingKey: Buffer.from(jwtSecret, 'hex'), expirationDurationS: 90 * 24 * 60 * 60, // 90 days }) @@ -19,7 +31,7 @@ export const main: APIGatewayTokenAuthorizerHandler = async ( const authToken = event.authorizationToken.replace('Bearer ', '') try { // authentication step for validating JWT token - const userId = await jwtLib.userIDFromToken(authToken) + const userId = jwtLib.userIDFromToken(authToken) if (userId instanceof Error) { const msg = 'Invalid auth token' diff --git a/services/app-api/src/jwt/jwt.test.ts b/services/app-api/src/jwt/jwt.test.ts index bdec778eee..d5eb3505d3 100644 --- a/services/app-api/src/jwt/jwt.test.ts +++ b/services/app-api/src/jwt/jwt.test.ts @@ -4,7 +4,7 @@ describe('jwtLib', () => { it('works symmetricly', () => { const jwt = newJWTLib({ issuer: 'mctest', - signingKey: 'foo', //pragma: allowlist secret + signingKey: Buffer.from('123af', 'hex'), expirationDurationS: 1000, }) @@ -27,13 +27,13 @@ describe('jwtLib', () => { it('errors with wrong issuer', () => { const jwtWriter = newJWTLib({ issuer: 'wrong', - signingKey: 'foo', + signingKey: Buffer.from('123af', 'hex'), expirationDurationS: 1000, }) const jwtReader = newJWTLib({ issuer: 'mctest', - signingKey: 'foo', + signingKey: Buffer.from('123af', 'hex'), expirationDurationS: 1000, }) @@ -49,13 +49,13 @@ describe('jwtLib', () => { it('errors with bad expiration', () => { const jwtWriter = newJWTLib({ issuer: 'mctest', - signingKey: 'foo', + signingKey: Buffer.from('123af', 'hex'), expirationDurationS: 0, }) const jwtReader = newJWTLib({ issuer: 'mctest', - signingKey: 'foo', + signingKey: Buffer.from('123af', 'hex'), expirationDurationS: 1000, }) @@ -71,13 +71,13 @@ describe('jwtLib', () => { it('errors with bad secret', () => { const jwtWriter = newJWTLib({ issuer: 'mctest', - signingKey: 'wrong', + signingKey: Buffer.from('deadbeef', 'hex'), expirationDurationS: 1000, }) const jwtReader = newJWTLib({ issuer: 'mctest', - signingKey: 'foo', + signingKey: Buffer.from('123af', 'hex'), expirationDurationS: 1000, }) @@ -93,7 +93,7 @@ describe('jwtLib', () => { it('errors with bogus JWT', () => { const jwtReader = newJWTLib({ issuer: 'mctest', - signingKey: 'foo', + signingKey: Buffer.from('123af', 'hex'), expirationDurationS: 1000, }) diff --git a/services/app-api/src/jwt/jwt.ts b/services/app-api/src/jwt/jwt.ts index a1da0c27fd..c817e801d0 100644 --- a/services/app-api/src/jwt/jwt.ts +++ b/services/app-api/src/jwt/jwt.ts @@ -4,7 +4,7 @@ import { sign, verify } from 'jsonwebtoken' interface JWTConfig { issuer: string - signingKey: string + signingKey: Buffer expirationDurationS: number } @@ -13,6 +13,7 @@ function createValidJWT(config: JWTConfig, userID: string): APIKeyType { subject: userID, issuer: config.issuer, expiresIn: config.expirationDurationS, + algorithm: 'HS256', // pin the default algo }) return { @@ -25,6 +26,7 @@ function userIDFromToken(config: JWTConfig, token: string): string | Error { try { const decoded = verify(token, config.signingKey, { issuer: config.issuer, + algorithms: ['HS256'], // pin the default algo }) if (!decoded.sub || typeof decoded === 'string') { @@ -45,6 +47,8 @@ interface JWTLib { function newJWTLib(config: JWTConfig): JWTLib { return { + // this is an experiment, using `curry` here, It seems clean but I'm not sure + // exactly what it's getting us yet -wml createValidJWT: curry(createValidJWT)(config), userIDFromToken: curry(userIDFromToken)(config), } diff --git a/services/app-api/src/resolvers/APIKey/createAPIKey.test.ts b/services/app-api/src/resolvers/APIKey/createAPIKey.test.ts index b6397de517..cf54dc355d 100644 --- a/services/app-api/src/resolvers/APIKey/createAPIKey.test.ts +++ b/services/app-api/src/resolvers/APIKey/createAPIKey.test.ts @@ -7,7 +7,7 @@ describe('createAPIKey', () => { it('creates a new API key', async () => { const jwt = newJWTLib({ issuer: 'mctestiss', - signingKey: 'foo', + signingKey: Buffer.from('123af', 'hex'), expirationDurationS: 1000, }) diff --git a/services/app-api/src/resolvers/rate/fetchRate.ts b/services/app-api/src/resolvers/rate/fetchRate.ts index 90357d8ed4..e3a61991e3 100644 --- a/services/app-api/src/resolvers/rate/fetchRate.ts +++ b/services/app-api/src/resolvers/rate/fetchRate.ts @@ -4,9 +4,12 @@ import { setSuccessAttributesOnActiveSpan, } from '../attributeHelper' import { NotFoundError } from '../../postgres' -import type { QueryResolvers } from '../../gen/gqlServer' +import type { QueryResolvers, State } from '../../gen/gqlServer' import type { Store } from '../../postgres' import { GraphQLError } from 'graphql' +import { isStateUser } from '../../domain-models' +import { logError } from '../../logger' +import { ForbiddenError } from 'apollo-server-core' export function fetchRateResolver(store: Store): QueryResolvers['fetchRate'] { return async (_parent, { input }, context) => { @@ -35,6 +38,23 @@ export function fetchRateResolver(store: Store): QueryResolvers['fetchRate'] { }) } + if (isStateUser(user)) { + const stateForCurrentUser: State['code'] = user.stateCode + if (rateWithHistory.stateCode !== stateForCurrentUser) { + logError( + 'fetchRate', + 'State users are not authorized to fetch rate data from a different state.' + ) + setErrorAttributesOnActiveSpan( + 'State users are not authorized to fetch rate data from a different state.', + span + ) + throw new ForbiddenError( + 'State users are not authorized to fetch rate data from a different state.' + ) + } + } + setSuccessAttributesOnActiveSpan(span) return { rate: rateWithHistory } } diff --git a/services/app-api/src/testHelpers/gqlHelpers.ts b/services/app-api/src/testHelpers/gqlHelpers.ts index 0f07c15dfa..e2f7cc706f 100644 --- a/services/app-api/src/testHelpers/gqlHelpers.ts +++ b/services/app-api/src/testHelpers/gqlHelpers.ts @@ -91,7 +91,7 @@ const constructTestPostgresServer = async (opts?: { opts?.jwt || newJWTLib({ issuer: 'mcreviewtest', - signingKey: 'foo', + signingKey: Buffer.from('123af', 'hex'), expirationDurationS: 1000, }) diff --git a/services/app-web/package.json b/services/app-web/package.json index b0f313b8b2..a4299ec662 100644 --- a/services/app-web/package.json +++ b/services/app-web/package.json @@ -105,7 +105,7 @@ "graphql": "^16.2.0", "jotai": "^2.2.1", "jotai-location": "^0.5.1", - "launchdarkly-react-client-sdk": "^3.0.1", + "launchdarkly-react-client-sdk": "^3.0.10", "path-browserify": "^1.0.1", "qs": "^6.11.0", "react": "^18.2.0", diff --git a/services/app-web/src/components/ActionButton/ActionButton.module.scss b/services/app-web/src/components/ActionButton/ActionButton.module.scss index 9bd3da73f4..edaf911800 100644 --- a/services/app-web/src/components/ActionButton/ActionButton.module.scss +++ b/services/app-web/src/components/ActionButton/ActionButton.module.scss @@ -3,16 +3,16 @@ // preferred way to show a button that is disabled. avoid cursor: none .disabledCursor { - cursor: not-allowed; + cursor: not-allowed; } .buttonTextWithIcon { - vertical-align: middle; - margin-left: .5rem + vertical-align: middle; + margin-left: 0.5rem; } .buttonTextWithoutIcon { - vertical-align: middle; + vertical-align: middle; } .successButton { @@ -21,6 +21,6 @@ background-color: custom.$mcr-success-hover !important; } &:active { - background-color: custom.$mcr-success-hover !important; + background-color: custom.$mcr-success-hover !important; } } diff --git a/services/app-web/src/components/Banner/Banner.module.scss b/services/app-web/src/components/Banner/Banner.module.scss index 2392b4f24e..fa64f2cedc 100644 --- a/services/app-web/src/components/Banner/Banner.module.scss +++ b/services/app-web/src/components/Banner/Banner.module.scss @@ -2,7 +2,7 @@ @use '../../styles/uswdsImports.scss' as uswds; .bannerBodyText { - p { - margin: 0 - } + p { + margin: 0; + } } diff --git a/services/app-web/src/components/Breadcrumbs/Breadcrumbs.module.scss b/services/app-web/src/components/Breadcrumbs/Breadcrumbs.module.scss index 901034450e..41fe506a52 100644 --- a/services/app-web/src/components/Breadcrumbs/Breadcrumbs.module.scss +++ b/services/app-web/src/components/Breadcrumbs/Breadcrumbs.module.scss @@ -9,7 +9,7 @@ [class^='usa-breadcrumb'] { li:last-child a { text-decoration: none; - color: custom.$mcr-foundation-ink + color: custom.$mcr-foundation-ink; } } } diff --git a/services/app-web/src/components/Colors/Colors.module.scss b/services/app-web/src/components/Colors/Colors.module.scss index ca720e3425..33d373327a 100644 --- a/services/app-web/src/components/Colors/Colors.module.scss +++ b/services/app-web/src/components/Colors/Colors.module.scss @@ -2,55 +2,53 @@ @use '../../styles/custom.scss' as custom; :export { - mcr: { - primary: { - lighter: custom.$mcr-primary-lighter; - light: custom.$mcr-primary-light; - base: custom.$mcr-primary-base; - dark: custom.$mcr-primary-dark; - darkest: custom.$mcr-primary-darkest; - } - cmsblue: { - lightest: custom.$mcr-cmsblue-lightest; - base: custom.$mcr-cmsblue-base; - dark: custom.$mcr-cmsblue-dark; - darkest: custom.$mcr-cmsblue-darkest; - } - cyan: { - light: custom.$mcr-cyan-light; - base: custom.$mcr-cyan-base; - dark: custom.$mcr-cyan-dark; - } - gold: { - base: custom.$mcr-gold-base; - dark: custom.$mcr-gold-dark; - darker: custom.$mcr-gold-darker; - } - gray: { - dark: custom.$mcr-gray-dark; - base : custom.$mcr-gray-base; - lighter: custom.$mcr-gray-lighter; - lightest: custom.$mcr-gray-lightest; - } - foundation: { - white: custom.$mcr-foundation-white; - ink : custom.$mcr-foundation-ink; - hint: custom.$mcr-foundation-hint; - link: custom.$mcr-foundation-link; - focus: custom.$mcr-foundation-focus; - visited: custom.$mcr-foundation-visited; - - } - success: { - base: custom.$mcr-success-base; - hover: custom.$mcr-success-hover; - dark: custom.$mcr-success-dark; - } - error: { - light: custom.$mcr-error-light; - base: custom.$mcr-error-base; - dark: custom.$mcr-error-dark; + mcr: { + primary: { + lighter: custom.$mcr-primary-lighter; + light: custom.$mcr-primary-light; + base: custom.$mcr-primary-base; + dark: custom.$mcr-primary-dark; + darkest: custom.$mcr-primary-darkest; + } + cmsblue: { + lightest: custom.$mcr-cmsblue-lightest; + base: custom.$mcr-cmsblue-base; + dark: custom.$mcr-cmsblue-dark; + darkest: custom.$mcr-cmsblue-darkest; + } + cyan: { + light: custom.$mcr-cyan-light; + base: custom.$mcr-cyan-base; + dark: custom.$mcr-cyan-dark; + } + gold: { + base: custom.$mcr-gold-base; + dark: custom.$mcr-gold-dark; + darker: custom.$mcr-gold-darker; + } + gray: { + dark: custom.$mcr-gray-dark; + base: custom.$mcr-gray-base; + lighter: custom.$mcr-gray-lighter; + lightest: custom.$mcr-gray-lightest; + } + foundation: { + white: custom.$mcr-foundation-white; + ink: custom.$mcr-foundation-ink; + hint: custom.$mcr-foundation-hint; + link: custom.$mcr-foundation-link; + focus: custom.$mcr-foundation-focus; + visited: custom.$mcr-foundation-visited; + } + success: { + base: custom.$mcr-success-base; + hover: custom.$mcr-success-hover; + dark: custom.$mcr-success-dark; + } + error: { + light: custom.$mcr-error-light; + base: custom.$mcr-error-base; + dark: custom.$mcr-error-dark; + } } - } - } diff --git a/services/app-web/src/components/DataDetail/DataDetail.module.scss b/services/app-web/src/components/DataDetail/DataDetail.module.scss index c9d63838c2..7f56210cf9 100644 --- a/services/app-web/src/components/DataDetail/DataDetail.module.scss +++ b/services/app-web/src/components/DataDetail/DataDetail.module.scss @@ -17,7 +17,6 @@ margin: 0; line-height: 1.5; } - } .missingInfo { diff --git a/services/app-web/src/components/DocumentWarning/InlineDocumentWarning/InlineDocumentWarning.module.scss b/services/app-web/src/components/DocumentWarning/InlineDocumentWarning/InlineDocumentWarning.module.scss index 0ccbc70d43..48ad699876 100644 --- a/services/app-web/src/components/DocumentWarning/InlineDocumentWarning/InlineDocumentWarning.module.scss +++ b/services/app-web/src/components/DocumentWarning/InlineDocumentWarning/InlineDocumentWarning.module.scss @@ -2,7 +2,7 @@ @use '../../../styles/uswdsImports.scss' as uswds; .missingInfo { - color: custom.$mcr-gold-darker; - font-weight: 700; - display: flex; + color: custom.$mcr-gold-darker; + font-weight: 700; + display: flex; } diff --git a/services/app-web/src/components/DownloadButton/DownloadButton.module.scss b/services/app-web/src/components/DownloadButton/DownloadButton.module.scss index fe85336ec7..036ebe080f 100644 --- a/services/app-web/src/components/DownloadButton/DownloadButton.module.scss +++ b/services/app-web/src/components/DownloadButton/DownloadButton.module.scss @@ -2,14 +2,14 @@ @use '../../styles/uswdsImports.scss' as uswds; .disabledCursor { - cursor: not-allowed; + cursor: not-allowed; } .buttonTextWithIcon { - vertical-align: middle; - margin-left: .5rem + vertical-align: middle; + margin-left: 0.5rem; } .buttonTextWithoutIcon { - vertical-align: middle; + vertical-align: middle; } diff --git a/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.module.scss b/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.module.scss index 1ad0681481..c6ea7fef30 100644 --- a/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.module.scss +++ b/services/app-web/src/components/DynamicStepIndicator/DynamicStepIndicator.module.scss @@ -8,8 +8,7 @@ justify-content: center; } - [class^='usa-step-indicator__header'] { display: inline; } -} \ No newline at end of file +} diff --git a/services/app-web/src/components/ErrorAlert/ErrorAlert.module.scss b/services/app-web/src/components/ErrorAlert/ErrorAlert.module.scss index 28eea9aeae..5fabe03c7f 100644 --- a/services/app-web/src/components/ErrorAlert/ErrorAlert.module.scss +++ b/services/app-web/src/components/ErrorAlert/ErrorAlert.module.scss @@ -1,12 +1,12 @@ @use '../../styles/custom.scss' as custom; @use '../../styles/uswdsImports.scss' as uswds; -.messageBodyText{ - p { - margin: 0 - } +.messageBodyText { + p { + margin: 0; + } } .nowrap { - white-space: nowrap; + white-space: nowrap; } diff --git a/services/app-web/src/components/ExpandableText/ExpandableText.module.scss b/services/app-web/src/components/ExpandableText/ExpandableText.module.scss index 359d92a257..ea08f435fb 100644 --- a/services/app-web/src/components/ExpandableText/ExpandableText.module.scss +++ b/services/app-web/src/components/ExpandableText/ExpandableText.module.scss @@ -21,5 +21,5 @@ background-color: transparent; border: none; cursor: pointer; - margin-top: 0.50rem; + margin-top: 0.5rem; } diff --git a/services/app-web/src/components/FilterAccordion/FilterDateRange/FilterDateRange.module.scss b/services/app-web/src/components/FilterAccordion/FilterDateRange/FilterDateRange.module.scss index 286388fb5e..ca4ad1a0f5 100644 --- a/services/app-web/src/components/FilterAccordion/FilterDateRange/FilterDateRange.module.scss +++ b/services/app-web/src/components/FilterAccordion/FilterDateRange/FilterDateRange.module.scss @@ -2,20 +2,20 @@ @use '../../../styles/uswdsImports.scss' as uswds; .dateRangePicker { - [class='usa-label'] { - font-weight: normal; - } - [class='usa-legend'] { - font-weight: normal; - } + [class='usa-label'] { + font-weight: normal; + } + [class='usa-legend'] { + font-weight: normal; + } - [class='usa-form-group'] { - margin-top: 0rem; - width: 100%; - } + [class='usa-form-group'] { + margin-top: 0rem; + width: 100%; + } - display: grid; - grid-template-columns: repeat(2, 1fr); - column-gap: 32px; - align-items: end; + display: grid; + grid-template-columns: repeat(2, 1fr); + column-gap: 32px; + align-items: end; } diff --git a/services/app-web/src/components/Form/ErrorSummary/ErrorSummary.module.scss b/services/app-web/src/components/Form/ErrorSummary/ErrorSummary.module.scss index 561a099fcc..a4b55a7f50 100644 --- a/services/app-web/src/components/Form/ErrorSummary/ErrorSummary.module.scss +++ b/services/app-web/src/components/Form/ErrorSummary/ErrorSummary.module.scss @@ -8,7 +8,8 @@ cursor: pointer; font-weight: normal; text-align: left; - &, &:visited { + &, + &:visited { color: custom.$mcr-foundation-ink; } } @@ -22,4 +23,4 @@ padding-left: 1rem; } margin-bottom: 1rem; -} \ No newline at end of file +} diff --git a/services/app-web/src/components/Form/FieldTextarea/FieldTextarea.module.scss b/services/app-web/src/components/Form/FieldTextarea/FieldTextarea.module.scss index d031dcbcb2..ad94ef721c 100644 --- a/services/app-web/src/components/Form/FieldTextarea/FieldTextarea.module.scss +++ b/services/app-web/src/components/Form/FieldTextarea/FieldTextarea.module.scss @@ -4,4 +4,4 @@ .requiredOptionalText { display: block; color: custom.$mcr-foundation-hint; -} \ No newline at end of file +} diff --git a/services/app-web/src/components/Form/FieldYesNo/FieldYesNo.module.scss b/services/app-web/src/components/Form/FieldYesNo/FieldYesNo.module.scss index 1ae5145129..eab31d1af6 100644 --- a/services/app-web/src/components/Form/FieldYesNo/FieldYesNo.module.scss +++ b/services/app-web/src/components/Form/FieldYesNo/FieldYesNo.module.scss @@ -6,11 +6,11 @@ } .optionsContainer label { - margin-top: .8em; + margin-top: 0.8em; } .yesnofieldsecondary { - margin-top: .8em; + margin-top: 0.8em; margin-bottom: 1.5em; } @@ -25,4 +25,4 @@ .requiredOptionalText { display: block; color: custom.$mcr-foundation-hint; -} \ No newline at end of file +} diff --git a/services/app-web/src/components/Header/Header.module.scss b/services/app-web/src/components/Header/Header.module.scss index 97ca6dc9bb..a44175c323 100644 --- a/services/app-web/src/components/Header/Header.module.scss +++ b/services/app-web/src/components/Header/Header.module.scss @@ -6,7 +6,8 @@ padding: 0 uswds.units(1); color: custom.$mcr-foundation-white; - button, a { + button, + a { color: custom.$mcr-foundation-white; &:hover, @@ -21,7 +22,6 @@ padding: 0 uswds.units(1); } - .landingPageHeading { background-color: custom.$mcr-cmsblue-dark; color: custom.$mcr-foundation-white; @@ -59,7 +59,7 @@ } .dashboardHeading { - background-color: custom.$mcr-cmsblue-dark;; + background-color: custom.$mcr-cmsblue-dark; color: custom.$mcr-foundation-white; & h1 { display: flex; diff --git a/services/app-web/src/components/Logo/Logo.module.scss b/services/app-web/src/components/Logo/Logo.module.scss index 64498b737a..7f9864a696 100644 --- a/services/app-web/src/components/Logo/Logo.module.scss +++ b/services/app-web/src/components/Logo/Logo.module.scss @@ -1,4 +1,4 @@ -.override{ +.override { margin-bottom: 1rem; margin-top: 1rem; -} \ No newline at end of file +} diff --git a/services/app-web/src/components/Modal/Modal.module.scss b/services/app-web/src/components/Modal/Modal.module.scss index 01eb7c0a6e..622e5b871b 100644 --- a/services/app-web/src/components/Modal/Modal.module.scss +++ b/services/app-web/src/components/Modal/Modal.module.scss @@ -2,10 +2,10 @@ @use '../../styles/uswdsImports.scss' as uswds; .modal { - max-width: 50rem; - div { + max-width: 50rem; div { - margin: 0; + div { + margin: 0; + } } - } } diff --git a/services/app-web/src/components/Modal/UnlockSubmitModal.module.scss b/services/app-web/src/components/Modal/UnlockSubmitModal.module.scss index dfbb775b5b..6cb877323f 100644 --- a/services/app-web/src/components/Modal/UnlockSubmitModal.module.scss +++ b/services/app-web/src/components/Modal/UnlockSubmitModal.module.scss @@ -5,11 +5,10 @@ max-width: custom.$mcr-container-standard-width-fixed; } - .submitButton { background: custom.$mcr-success-base; &:hover { - background-color: custom.$mcr-success-hover !important; + background-color: custom.$mcr-success-hover !important; } } diff --git a/services/app-web/src/components/SectionCard/SectionCard.module.scss b/services/app-web/src/components/SectionCard/SectionCard.module.scss index 7970b61b72..48712cea78 100644 --- a/services/app-web/src/components/SectionCard/SectionCard.module.scss +++ b/services/app-web/src/components/SectionCard/SectionCard.module.scss @@ -1,10 +1,10 @@ @use '../../styles/custom.scss' as custom; .section { - @include custom.sectionCard; - - >h3, fieldset>h3{ - margin-top: 0; // adjust vertical space between section edge and the first heading - } + @include custom.sectionCard; + > h3, + fieldset > h3 { + margin-top: 0; // adjust vertical space between section edge and the first heading + } } diff --git a/services/app-web/src/components/Select/Select.module.scss b/services/app-web/src/components/Select/Select.module.scss index dc213cdac8..86090d3646 100644 --- a/services/app-web/src/components/Select/Select.module.scss +++ b/services/app-web/src/components/Select/Select.module.scss @@ -4,23 +4,26 @@ // react-select draws the chips in two parts, so we round the outer and square in the inner corners .multiSelect { - padding-top: 12px; - [class*='select__multi-value'] { - border-radius: 99rem 99rem 99rem 99rem; - } - [class*='select__multi-value__label'] { - background:custom.$mcr-primary-lighter; - color: custom.$mcr-foundation-ink; - border-radius: 99rem 0rem 0rem 99rem; - } - [class*='select__multi-value__remove'] { - background:custom.$mcr-primary-lighter; - color: custom.$mcr-foundation-ink; - border-radius: 0rem 99rem 99rem 0rem; + padding-top: 12px; + [class*='select__multi-value'] { + border-radius: 99rem 99rem 99rem 99rem; + } + [class*='select__multi-value__label'] { + background: custom.$mcr-primary-lighter; + color: custom.$mcr-foundation-ink; + border-radius: 99rem 0rem 0rem 99rem; + } + [class*='select__multi-value__remove'] { + background: custom.$mcr-primary-lighter; + color: custom.$mcr-foundation-ink; + border-radius: 0rem 99rem 99rem 0rem; - &:hover { - background: color.adjust(custom.$mcr-primary-lighter, $lightness: -5); - color: custom.$mcr-foundation-ink; + &:hover { + background: color.adjust( + custom.$mcr-primary-lighter, + $lightness: -5 + ); + color: custom.$mcr-foundation-ink; + } } - } } diff --git a/services/app-web/src/components/SubmissionCard/SubmissionCard.module.scss b/services/app-web/src/components/SubmissionCard/SubmissionCard.module.scss index 5f556a9ef3..99c8b1b065 100644 --- a/services/app-web/src/components/SubmissionCard/SubmissionCard.module.scss +++ b/services/app-web/src/components/SubmissionCard/SubmissionCard.module.scss @@ -1,7 +1,6 @@ @use '../../styles/custom.scss' as custom; @use '../../styles/uswdsImports.scss' as uswds; - .submissionList { padding-left: 0; } @@ -16,7 +15,7 @@ transition: background-color 200ms linear; &:hover { - background-color: custom.$mcr-gray-lightest + background-color: custom.$mcr-gray-lightest; } } diff --git a/services/app-web/src/components/SubmissionSummarySection/ContactsSummarySection/ContactsSummarySection.tsx b/services/app-web/src/components/SubmissionSummarySection/ContactsSummarySection/ContactsSummarySection.tsx index 8a721a18bd..05a3cb7714 100644 --- a/services/app-web/src/components/SubmissionSummarySection/ContactsSummarySection/ContactsSummarySection.tsx +++ b/services/app-web/src/components/SubmissionSummarySection/ContactsSummarySection/ContactsSummarySection.tsx @@ -51,6 +51,7 @@ export const ContactsSummarySection = ({ submission.stateContacts.map( (stateContact, index) => ( { - afterEach(() => { - jest.restoreAllMocks() - }) const defaultApolloMocks = { mocks: [fetchCurrentUserMock({ statusCode: 200 })], } @@ -110,8 +104,7 @@ describe('ContractDetailsSummarySection', () => { }) }) - it('can render all contract details fields', () => { - ldUseClientSpy({ '438-attestation': true }) + it('can render all contract details fields', async () => { const submission = mockContractAndRatesDraft({ statutoryRegulatoryAttestation: true, }) @@ -124,14 +117,18 @@ describe('ContractDetailsSummarySection', () => { />, { apolloProvider: defaultApolloMocks, + featureFlags: { '438-attestation': true }, } ) - expect( - screen.getByRole('definition', { - name: StatutoryRegulatoryAttestationQuestion, - }) - ).toBeInTheDocument() + await waitFor(() => { + expect( + screen.getByRole('definition', { + name: StatutoryRegulatoryAttestationQuestion, + }) + ).toBeInTheDocument() + }) + expect( screen.getByRole('definition', { name: 'Contract status' }) ).toBeInTheDocument() @@ -161,7 +158,6 @@ describe('ContractDetailsSummarySection', () => { }) it('displays correct contract 438 attestation yes and no text and description', async () => { - ldUseClientSpy({ '438-attestation': true }) const submission = mockContractAndRatesDraft({ statutoryRegulatoryAttestation: false, statutoryRegulatoryAttestationDescription: 'No compliance', @@ -175,14 +171,18 @@ describe('ContractDetailsSummarySection', () => { />, { apolloProvider: defaultApolloMocks, + featureFlags: { '438-attestation': true }, } ) - expect( - screen.getByRole('definition', { - name: StatutoryRegulatoryAttestationQuestion, - }) - ).toBeInTheDocument() + await waitFor(() => { + expect( + screen.getByRole('definition', { + name: StatutoryRegulatoryAttestationQuestion, + }) + ).toBeInTheDocument() + }) + expect( screen.getByRole('definition', { name: 'Non-compliance description', diff --git a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx index 5ad5218f7b..15697941ab 100644 --- a/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx +++ b/services/app-web/src/components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection.test.tsx @@ -1,7 +1,4 @@ -import { - ldUseClientSpy, - renderWithProviders, -} from '../../../testHelpers/jestHelpers' +import { renderWithProviders } from '../../../testHelpers/jestHelpers' import { SingleRateSummarySection } from './SingleRateSummarySection' import { fetchCurrentUserMock, @@ -14,13 +11,6 @@ import { packageName } from '../../../common-code/healthPlanFormDataType' import { RateRevision } from '../../../gen/gqlClient' describe('SingleRateSummarySection', () => { - beforeEach(() => { - ldUseClientSpy({ 'rate-edit-unlock': true }) - }) - afterEach(() => { - jest.resetAllMocks() - }) - it('can render rate details without errors', async () => { const rateData = rateDataMock() await waitFor(() => { @@ -39,6 +29,7 @@ describe('SingleRateSummarySection', () => { }), ], }, + featureFlags: { 'rate-edit-unlock': true }, } ) }) @@ -134,6 +125,7 @@ describe('SingleRateSummarySection', () => { }), ], }, + featureFlags: { 'rate-edit-unlock': true }, } ) }) @@ -234,6 +226,7 @@ describe('SingleRateSummarySection', () => { }), ], }, + featureFlags: { 'rate-edit-unlock': true }, } ) @@ -273,6 +266,7 @@ describe('SingleRateSummarySection', () => { }), ], }, + featureFlags: { 'rate-edit-unlock': true }, } ) @@ -313,6 +307,7 @@ describe('SingleRateSummarySection', () => { }), ], }, + featureFlags: { 'rate-edit-unlock': true }, } ) expect( @@ -345,6 +340,7 @@ describe('SingleRateSummarySection', () => { }), ], }, + featureFlags: { 'rate-edit-unlock': true }, } ) await waitFor(() => { @@ -379,6 +375,7 @@ describe('SingleRateSummarySection', () => { }), ], }, + featureFlags: { 'rate-edit-unlock': true }, } ) expect( diff --git a/services/app-web/src/components/SubmissionSummarySection/SubmissionSummarySection.module.scss b/services/app-web/src/components/SubmissionSummarySection/SubmissionSummarySection.module.scss index 163f33d409..e080ad1289 100644 --- a/services/app-web/src/components/SubmissionSummarySection/SubmissionSummarySection.module.scss +++ b/services/app-web/src/components/SubmissionSummarySection/SubmissionSummarySection.module.scss @@ -8,9 +8,9 @@ .summarySection { margin: uswds.units(2) auto; background: custom.$mcr-foundation-white; - padding: uswds.units(2)uswds.units(4); + padding: uswds.units(2) uswds.units(4); border: 1px solid custom.$mcr-gray-lighter; - line-height:uswds.units(3); + line-height: uswds.units(3); h2 { margin: 0; @@ -28,7 +28,7 @@ padding: 0; li { - padding:uswds.units(1) 0; + padding: uswds.units(1) 0; } } @@ -37,7 +37,7 @@ padding: 0; div { - padding-bottom:uswds.units(2); + padding-bottom: uswds.units(2); } // dont bottom pad last two grid items @@ -46,7 +46,7 @@ } } table:last-of-type { - margin-bottom:uswds.units(2); + margin-bottom: uswds.units(2); } // with nested sections, collapse bottom margin/padding for last in list @@ -57,7 +57,7 @@ } .contactInfo p { - margin:uswds.units(1) 0; + margin: uswds.units(1) 0; } .documentDesc { @@ -74,11 +74,11 @@ .rateName { display: block; font-weight: bold; - margin: 0 + margin: 0; } .certifyingActuaryDetail { - margin-bottom:uswds.units(4); + margin-bottom: uswds.units(4); @include uswds.at-media(tablet) { margin-bottom: 0; @@ -98,7 +98,7 @@ .singleColumnGrid { @include uswds.at-media(tablet) { > * { - margin-bottom:uswds.units(2); + margin-bottom: uswds.units(2); } } } diff --git a/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss b/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss index e916afda82..ba432ef0da 100644 --- a/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss +++ b/services/app-web/src/components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss @@ -4,7 +4,6 @@ @mixin docs-table { width: 100%; - th { font-size: uswds.size('body', '2xs'); } @@ -22,7 +21,7 @@ } } .uploadedDocumentsTable { - @include docs-table + @include docs-table; } .supportingDocsEmpty { @@ -38,8 +37,8 @@ justify-content: space-between; } -.inlineLink{ - display: inline +.inlineLink { + display: inline; } .inlineTag { @@ -66,4 +65,3 @@ caption { margin-right: 5px; } } - diff --git a/services/app-web/src/components/Tabs/Tabs.module.scss b/services/app-web/src/components/Tabs/Tabs.module.scss index 2dfafeb352..139cd96afc 100644 --- a/services/app-web/src/components/Tabs/Tabs.module.scss +++ b/services/app-web/src/components/Tabs/Tabs.module.scss @@ -7,9 +7,7 @@ .easi-tabs { overflow: hidden; - height: 85% - - &__navigation { + height: 85% &__navigation { display: flex; justify-content: space-between; position: relative; @@ -85,5 +83,4 @@ padding: 1.5em; min-height: 500px; } - } diff --git a/services/app-web/src/constants/routes.ts b/services/app-web/src/constants/routes.ts index 22e98b4ded..890ca59c97 100644 --- a/services/app-web/src/constants/routes.ts +++ b/services/app-web/src/constants/routes.ts @@ -9,9 +9,11 @@ const ROUTES = [ 'DASHBOARD_SUBMISSIONS', 'DASHBOARD_RATES', 'GRAPHQL_EXPLORER', + 'API_ACCESS', 'HELP', 'SETTINGS', 'RATES_SUMMARY', + 'RATE_EDIT', 'SUBMISSIONS', 'SUBMISSIONS_NEW', 'SUBMISSIONS_TYPE', @@ -44,9 +46,11 @@ const RoutesRecord: Record = { DASHBOARD_SUBMISSIONS: '/dashboard/submissions', DASHBOARD_RATES: '/dashboard/rate-reviews', GRAPHQL_EXPLORER: '/dev/graphql-explorer', + API_ACCESS: '/dev/api-access', HELP: '/help', SETTINGS: '/settings', - RATES_SUMMARY: 'rates/:id', + RATES_SUMMARY: '/rates/:id', + RATE_EDIT: '/rates/:id/edit', SUBMISSIONS: '/submissions', SUBMISSIONS_NEW: '/submissions/new', SUBMISSIONS_EDIT_TOP_LEVEL: '/submissions/:id/edit/*', @@ -116,12 +120,14 @@ const PageTitlesRecord: Record = { ROOT: 'Home', AUTH: 'Login', GRAPHQL_EXPLORER: 'GraphQL explorer', + API_ACCESS: 'API Access', HELP: 'Help', SETTINGS: 'Settings', DASHBOARD: 'Dashboard', DASHBOARD_RATES: 'Rate review dashboard', DASHBOARD_SUBMISSIONS: 'Dashboard', RATES_SUMMARY: 'Rate summary', + RATE_EDIT: 'Edit rate', SUBMISSIONS: 'Submissions', SUBMISSIONS_NEW: 'New submission', SUBMISSIONS_EDIT_TOP_LEVEL: 'Submissions', diff --git a/services/app-web/src/constants/tealium.ts b/services/app-web/src/constants/tealium.ts index 02dbad6468..4aaf4ad0b1 100644 --- a/services/app-web/src/constants/tealium.ts +++ b/services/app-web/src/constants/tealium.ts @@ -36,8 +36,10 @@ const CONTENT_TYPE_BY_ROUTE: Record = { DASHBOARD_RATES: 'table', HELP: 'glossary', GRAPHQL_EXPLORER: 'dev', + API_ACCESS: 'dev', SETTINGS: 'table', RATES_SUMMARY: 'summary', + RATE_EDIT: 'form', SUBMISSIONS: 'form', SUBMISSIONS_NEW: 'form', SUBMISSIONS_EDIT_TOP_LEVEL: 'form', diff --git a/services/app-web/src/gqlHelpers/apolloErrors.ts b/services/app-web/src/gqlHelpers/apolloErrors.ts index 7e6a6db282..eb1fc3a65f 100644 --- a/services/app-web/src/gqlHelpers/apolloErrors.ts +++ b/services/app-web/src/gqlHelpers/apolloErrors.ts @@ -37,7 +37,7 @@ const handleNetworkError = ( } const handleGQLErrors = (graphQLErrors: GraphQLErrors) => { - graphQLErrors.forEach(({ message, locations, path, extensions }) => { + graphQLErrors.forEach(({ message, locations, path }) => { recordJSException( // Graphql errors mean something is wrong inside our api, maybe bad request or errors we return from api for known edge cases `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` diff --git a/services/app-web/src/hooks/useTealium.ts b/services/app-web/src/hooks/useTealium.ts index c6bf8cec59..7680a9dad2 100644 --- a/services/app-web/src/hooks/useTealium.ts +++ b/services/app-web/src/hooks/useTealium.ts @@ -99,15 +99,22 @@ const useTealium = (): { // Add page view // this effect should fire on each page view or if something changes about logged in user useEffect(() => { + // Do not add tealium for local dev or review apps if (process.env.REACT_APP_AUTH_MODE !== 'IDM') { - // console.info(`mock tealium page view: ${tealiumPageName}`) return } - // Guardrail - protect against trying to call utag before its loaded. + const waitForUtag = async () => { + return new Promise(resolve => setTimeout(resolve, 1000)); + } + + if (!window.utag) { - return + waitForUtag().catch(() => { /* All of this is a guardrail - protect against trying to call utag before its loaded*/ }) + if (!window.utag) { + return + } } // eslint-disable-next-line @typescript-eslint/no-empty-function diff --git a/services/app-web/src/index.tsx b/services/app-web/src/index.tsx index c78bd155dc..33c55cd844 100644 --- a/services/app-web/src/index.tsx +++ b/services/app-web/src/index.tsx @@ -16,6 +16,11 @@ import type { S3BucketConfigType } from './s3/s3Amplify' const gqlSchema = loader('../../app-web/src/gen/schema.graphql') +const apiURL = process.env.REACT_APP_API_URL +if (!apiURL || apiURL === '') { + throw new Error('REACT_APP_API_URL must be set to the url for the API') +} + // We are using Amplify for communicating with Cognito, for now. Amplify.configure({ Auth: { @@ -44,7 +49,7 @@ Amplify.configure({ endpoints: [ { name: 'api', - endpoint: process.env.REACT_APP_API_URL, + endpoint: apiURL, }, ], }, diff --git a/services/app-web/src/localAuth/LocalLogin.module.scss b/services/app-web/src/localAuth/LocalLogin.module.scss index f078200c53..6aaf4dcae2 100644 --- a/services/app-web/src/localAuth/LocalLogin.module.scss +++ b/services/app-web/src/localAuth/LocalLogin.module.scss @@ -2,5 +2,5 @@ @use '../styles/uswdsImports.scss' as uswds; .userCard { - width: 200px; + width: 200px; } diff --git a/services/app-web/src/pages/APIAccess/APIAccess.module.scss b/services/app-web/src/pages/APIAccess/APIAccess.module.scss new file mode 100644 index 0000000000..14a32be4ea --- /dev/null +++ b/services/app-web/src/pages/APIAccess/APIAccess.module.scss @@ -0,0 +1,22 @@ +@use '../../styles/custom.scss' as custom; + +.pageContainer { + padding: 1em 5em; + max-width: custom.$mcr-container-standard-width-fixed; + background-color: custom.$mcr-foundation-white; +} + +.centerButtonContainer { + display: flex; + margin: 1em; + justify-content: center; +} + +.wrapKey { + word-wrap: break-word; + white-space: pre-wrap; + background: custom.$mcr-gray-lighter; + display: block; + padding: 1em; + margin-top: 1em; +} diff --git a/services/app-web/src/pages/APIAccess/APIAccess.test.tsx b/services/app-web/src/pages/APIAccess/APIAccess.test.tsx new file mode 100644 index 0000000000..2d68816800 --- /dev/null +++ b/services/app-web/src/pages/APIAccess/APIAccess.test.tsx @@ -0,0 +1,109 @@ +import { screen } from '@testing-library/react' +import { Route, Routes } from 'react-router-dom' +import { RoutesRecord } from '../../constants' +import { renderWithProviders } from '../../testHelpers' +import { + createAPIKeySuccess, + fetchCurrentUserMock, + mockValidCMSUser, + createAPIKeyNetworkError, +} from '../../testHelpers/apolloMocks' +import { APIAccess } from './APIAccess' + +describe('APIAccess', () => { + afterEach(() => { + jest.resetAllMocks() + }) + + it('renders without errors', async () => { + renderWithProviders( + + } /> + , + { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + user: mockValidCMSUser(), + statusCode: 200, + }), + ], + }, + routerProvider: { + route: '/dev/api-access', + }, + } + ) + + const instructions = await screen.findByText( + 'To interact with the MC-Review API you will need a valid JWT' + ) + expect(instructions).toBeInTheDocument() + }) + + it('displays an API key on success', async () => { + const { user } = renderWithProviders( + + } /> + , + { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + user: mockValidCMSUser(), + statusCode: 200, + }), + createAPIKeySuccess(), + ], + }, + routerProvider: { + route: '/dev/api-access', + }, + } + ) + + const generateButton = await screen.findByRole('button', { + name: 'Generate API Key', + }) + await user.click(generateButton) + + const apiKey = await screen.findByRole('code', { name: 'API Key Text' }) + const curlCmd = await screen.findByRole('code', { + name: 'Example Curl Command', + }) + + expect(apiKey).toBeInTheDocument() + expect(apiKey.textContent).toBe('foo.bar.baz.key123') + expect(curlCmd).toBeInTheDocument() + }) + + it('displays error on error', async () => { + const { user } = renderWithProviders( + + } /> + , + { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + user: mockValidCMSUser(), + statusCode: 200, + }), + createAPIKeyNetworkError(), + ], + }, + routerProvider: { + route: '/dev/api-access', + }, + } + ) + + const generateButton = await screen.findByRole('button', { + name: 'Generate API Key', + }) + await user.click(generateButton) + + const errorMsg = await screen.findByText('System error') + expect(errorMsg).toBeInTheDocument() + }) +}) diff --git a/services/app-web/src/pages/APIAccess/APIAccess.tsx b/services/app-web/src/pages/APIAccess/APIAccess.tsx new file mode 100644 index 0000000000..da8dab7849 --- /dev/null +++ b/services/app-web/src/pages/APIAccess/APIAccess.tsx @@ -0,0 +1,155 @@ +import { ApolloError } from '@apollo/client' +import { Button, Grid, GridContainer, Link } from '@trussworks/react-uswds' +import path from 'path-browserify' +import { useState } from 'react' +import { RoutesRecord } from '../../constants' +import { + CreateApiKeyPayload, + useCreateApiKeyMutation, +} from '../../gen/gqlClient' +import { handleApolloError } from '../../gqlHelpers/apolloErrors' +import { recordJSException } from '../../otelHelpers' +import { GenericErrorPage } from '../Errors/GenericErrorPage' +import styles from './APIAccess.module.scss' + +function APIAccess(): React.ReactElement { + const apiURL = process.env.REACT_APP_API_URL + + const thirdPartyAPIURL = !apiURL + ? undefined + : path.join(apiURL, '/v1/graphql/external') + + const [getAPIKey] = useCreateApiKeyMutation() + + const [displayErrorPage, setDisplayErrorPage] = useState(false) + const [apiKey, setAPIKey] = useState( + undefined + ) + + const callAPIKeyMutation = async () => { + try { + const result = await getAPIKey() + + setAPIKey(result.data?.createAPIKey) + } catch (err) { + console.error('unexpected error generating a new API Key', err) + if (err instanceof ApolloError) { + handleApolloError(err, true) + } + recordJSException(err) + setDisplayErrorPage(true) + } + } + + if (displayErrorPage) { + return + } + + const copyKeyToClipboard = async () => { + if (apiKey) { + await navigator.clipboard.writeText(apiKey.key) + } + } + + const curlCommand = !apiKey + ? undefined + : ` +curl -s ${thirdPartyAPIURL} -X POST \\ +-H "Authorization: Bearer ${apiKey.key}" \\ +-H "Content-Type: application/json" \\ +--data '{"query":"query IndexRates { indexRates { totalCount edges { node { id } } } }"}' +` + return ( + + +

API Access

+
+

Credentials for using the MC-Review API

+
+ To interact with the MC-Review API you will need a valid JWT +
+ + {!apiKey ? ( +
+ +
+ ) : ( + <> + + {apiKey.key} + +
+ + +
+ + )} + +

Usage

+
    +
  • Make GraphQL requests to the URL: {apiURL}
  • +
  • + Include the JWT as a Bearer token in the Authorization + header. Example: +
      +
    • + + Authorization: Bearer eyJhbGciOiJIU... + +
    • +
    +
  • +
+ + {curlCommand && ( + <> + Example curl command: + + {curlCommand} + + + )} + +

Resources

+
    +
  • + The MC-Review  + + GraphQL Explorer + +   will allow you to format and run queries against + the API in all environments except production +
  • +
  • + The  + + official documentation for GraphQL + +
  • +
  • + The MC-Review  + + GraphQL Schema + +   for understanding the shape of data returned by + the API +
  • +
+
+
+ ) +} + +export { APIAccess } diff --git a/services/app-web/src/pages/App/AppBody.module.scss b/services/app-web/src/pages/App/AppBody.module.scss index 692e2e7d66..44655ee15e 100644 --- a/services/app-web/src/pages/App/AppBody.module.scss +++ b/services/app-web/src/pages/App/AppBody.module.scss @@ -20,5 +20,5 @@ font-weight: bold; text-align: center; font-size: 1rem; - padding: .5rem; + padding: 0.5rem; } diff --git a/services/app-web/src/pages/App/AppBody.test.tsx b/services/app-web/src/pages/App/AppBody.test.tsx index 9968b35577..2947038058 100644 --- a/services/app-web/src/pages/App/AppBody.test.tsx +++ b/services/app-web/src/pages/App/AppBody.test.tsx @@ -1,7 +1,6 @@ import { screen } from '@testing-library/react' import { - ldUseClientSpy, renderWithProviders, userClickSignIn, } from '../../testHelpers/jestHelpers' @@ -26,16 +25,18 @@ describe('AppBody', () => { }) it('App renders without errors', () => { - ldUseClientSpy({ 'session-expiring-modal': false }) - renderWithProviders() + renderWithProviders(, { + featureFlags: { 'session-expiring-modal': false }, + }) const mainElement = screen.getByRole('main') expect(mainElement).toBeInTheDocument() }) describe('Sign In buttton click', () => { it('displays local login heading when expected', async () => { - ldUseClientSpy({}) - renderWithProviders() + renderWithProviders(, { + featureFlags: { 'session-expiring-modal': false }, + }) await userClickSignIn(screen) @@ -48,8 +49,9 @@ describe('AppBody', () => { }) it('displays Cognito login page when expected', async () => { - ldUseClientSpy({ 'session-expiring-modal': false }) - renderWithProviders() + renderWithProviders(, { + featureFlags: { 'session-expiring-modal': false }, + }) await userClickSignIn(screen) expect( @@ -58,8 +60,9 @@ describe('AppBody', () => { }) it('displays Cognito signup page when expected', async () => { - ldUseClientSpy({ 'session-expiring-modal': false }) - renderWithProviders() + renderWithProviders(, { + featureFlags: { 'session-expiring-modal': false }, + }) await userClickSignIn(screen) expect( @@ -88,7 +91,6 @@ describe('AppBody', () => { it('shows test environment banner in val', () => { process.env.REACT_APP_STAGE_NAME = 'val' - ldUseClientSpy({ 'session-expiring-modal': false }) renderWithProviders(, { apolloProvider: { mocks: [ @@ -96,6 +98,7 @@ describe('AppBody', () => { indexHealthPlanPackagesMockSuccess(), ], }, + featureFlags: { 'session-expiring-modal': false }, }) expect( @@ -105,7 +108,6 @@ describe('AppBody', () => { it('does not show test environment banner in prod', () => { process.env.REACT_APP_STAGE_NAME = 'prod' - ldUseClientSpy({ 'session-expiring-modal': false }) renderWithProviders(, { apolloProvider: { mocks: [ @@ -113,6 +115,7 @@ describe('AppBody', () => { indexHealthPlanPackagesMockSuccess(), ], }, + featureFlags: { 'session-expiring-modal': false }, }) expect(screen.queryByText('THIS IS A TEST ENVIRONMENT')).toBeNull() @@ -121,10 +124,6 @@ describe('AppBody', () => { describe('Site under maintenance banner', () => { it('displays maintenance banner when feature flag is on', async () => { - ldUseClientSpy({ - 'site-under-maintenance-banner': 'UNSCHEDULED', - 'session-expiring-modal': false, - }) renderWithProviders(, { apolloProvider: { mocks: [ @@ -132,6 +131,10 @@ describe('AppBody', () => { indexHealthPlanPackagesMockSuccess(), ], }, + featureFlags: { + 'site-under-maintenance-banner': 'UNSCHEDULED', + 'session-expiring-modal': false, + }, }) expect( await screen.findByRole('heading', { name: 'Site unavailable' }) @@ -144,7 +147,6 @@ describe('AppBody', () => { }) it('does not display maintenance banner when flag is off', async () => { - ldUseClientSpy({ 'session-expiring-modal': false }) renderWithProviders(, { apolloProvider: { mocks: [ @@ -152,6 +154,7 @@ describe('AppBody', () => { indexHealthPlanPackagesMockSuccess(), ], }, + featureFlags: { 'session-expiring-modal': false }, }) expect( screen.queryByRole('heading', { name: 'Site Unavailable' }) @@ -166,7 +169,6 @@ describe('AppBody', () => { describe('Page scrolling', () => { it('scroll top on page load', async () => { - ldUseClientSpy({}) renderWithProviders() await userClickSignIn(screen) expect(window.scrollTo).toHaveBeenCalledWith(0, 0) diff --git a/services/app-web/src/pages/App/AppRoutes.test.tsx b/services/app-web/src/pages/App/AppRoutes.test.tsx index e0bc56c927..a9fd159262 100644 --- a/services/app-web/src/pages/App/AppRoutes.test.tsx +++ b/services/app-web/src/pages/App/AppRoutes.test.tsx @@ -1,9 +1,6 @@ import { screen, waitFor } from '@testing-library/react' -import { - ldUseClientSpy, - renderWithProviders, -} from '../../testHelpers/jestHelpers' +import { renderWithProviders } from '../../testHelpers/jestHelpers' import { AppRoutes } from './AppRoutes' import { fetchCurrentUserMock, @@ -22,7 +19,6 @@ describe('AppRoutes', () => { }) describe('/[root]', () => { it('state dashboard when state user logged in', async () => { - ldUseClientSpy({ 'session-expiring-modal': false }) renderWithProviders(, { apolloProvider: { mocks: [ @@ -30,6 +26,7 @@ describe('AppRoutes', () => { indexHealthPlanPackagesMockSuccess(), ], }, + featureFlags: { 'session-expiring-modal': false }, }) await waitFor(() => { @@ -46,7 +43,6 @@ describe('AppRoutes', () => { }) it('cms dashboard when cms user logged in', async () => { - ldUseClientSpy({ 'session-expiring-modal': false }) renderWithProviders(, { apolloProvider: { mocks: [ @@ -57,6 +53,7 @@ describe('AppRoutes', () => { indexHealthPlanPackagesMockSuccess(), ], }, + featureFlags: { 'session-expiring-modal': false }, }) await waitFor(() => { @@ -73,7 +70,6 @@ describe('AppRoutes', () => { }) it('landing page when no user', async () => { - ldUseClientSpy({ 'session-expiring-modal': false }) renderWithProviders(, { apolloProvider: { mocks: [ @@ -82,6 +78,7 @@ describe('AppRoutes', () => { }), ], }, + featureFlags: { 'session-expiring-modal': false }, }) await waitFor(() => { expect( @@ -102,7 +99,6 @@ describe('AppRoutes', () => { describe('/auth', () => { it('auth header is displayed', async () => { - ldUseClientSpy({ 'session-expiring-modal': false }) renderWithProviders(, { routerProvider: { route: '/auth' }, apolloProvider: { @@ -112,6 +108,7 @@ describe('AppRoutes', () => { }), ], }, + featureFlags: { 'session-expiring-modal': false }, }) await waitFor(() => { @@ -136,8 +133,13 @@ describe('AppRoutes', () => { renderWithProviders(, { routerProvider: { route: '/help' }, apolloProvider: { - mocks: [fetchCurrentUserMock({ statusCode: 200 })], + mocks: [ + fetchCurrentUserMock({ + statusCode: 200, + }), + ], }, + featureFlags: { 'session-expiring-modal': false }, }) await screen.findByTestId('help-authenticated') @@ -162,6 +164,7 @@ describe('AppRoutes', () => { }), ], }, + featureFlags: { 'session-expiring-modal': false }, }) await screen.findByTestId('help-authenticated') await waitFor(() => { @@ -200,7 +203,6 @@ describe('AppRoutes', () => { describe('invalid routes', () => { it('redirect to landing page when no user', async () => { - ldUseClientSpy({ 'session-expiring-modal': false }) renderWithProviders(, { routerProvider: { route: '/not-a-real-place' }, apolloProvider: { @@ -210,6 +212,7 @@ describe('AppRoutes', () => { }), ], }, + featureFlags: { 'session-expiring-modal': false }, }) await waitFor(() => { @@ -223,12 +226,12 @@ describe('AppRoutes', () => { }) it('redirect to 404 error page when user is logged in', async () => { - ldUseClientSpy({ 'session-expiring-modal': false }) renderWithProviders(, { apolloProvider: { mocks: [fetchCurrentUserMock({ statusCode: 200 })], }, routerProvider: { route: '/not-a-real-place' }, + featureFlags: { 'session-expiring-modal': false }, }) await waitFor(() => diff --git a/services/app-web/src/pages/App/AppRoutes.tsx b/services/app-web/src/pages/App/AppRoutes.tsx index 81f1b33d42..800bd24810 100644 --- a/services/app-web/src/pages/App/AppRoutes.tsx +++ b/services/app-web/src/pages/App/AppRoutes.tsx @@ -38,6 +38,8 @@ import { } from '../QuestionResponse' import { GraphQLExplorer } from '../GraphQLExplorer/GraphQLExplorer' import { RateSummary } from '../SubmissionSummary/RateSummary' +import { RateEdit } from '../RateEdit/RateEdit' +import { APIAccess } from '../APIAccess/APIAccess' function componentForAuthMode( authMode: AuthModeType @@ -75,7 +77,7 @@ const StateUserRoutes = ({ }): React.ReactElement => { // feature flag const ldClient = useLDClient() - const showRateSummaryPage: boolean = ldClient?.variation( + const showRatePages: boolean = ldClient?.variation( featureFlags.RATE_EDIT_UNLOCK.flag, featureFlags.RATE_EDIT_UNLOCK.defaultValue ) @@ -106,7 +108,13 @@ const StateUserRoutes = ({ path={RoutesRecord.SUBMISSIONS_NEW} element={} /> - {showRateSummaryPage && ( + {showRatePages && ( + } + /> + )} + {showRatePages && ( } @@ -147,6 +155,7 @@ const StateUserRoutes = ({ element={} /> )} + } /> } /> @@ -232,6 +241,7 @@ const CMSUserRoutes = ({ /> )} } /> + } /> {UniversalRoutes} } /> diff --git a/services/app-web/src/pages/Errors/ErrorForbiddenPage.tsx b/services/app-web/src/pages/Errors/ErrorForbiddenPage.tsx new file mode 100644 index 0000000000..7841f4448b --- /dev/null +++ b/services/app-web/src/pages/Errors/ErrorForbiddenPage.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import styles from './Errors.module.scss' +import { GridContainer } from '@trussworks/react-uswds' +import { PageHeading } from '../../components' + +interface ForbiddenErrorPageProps { + errorMsg?: string +} + +export const ErrorForbiddenPage = ({ + errorMsg, +}: ForbiddenErrorPageProps): React.ReactElement => { + return ( +
+ + Forbidden + {errorMsg ? ( +

{errorMsg}

+ ) : ( +

+ You do not have permission to view the requested file or + resource. +

+ )} +
+
+ ) +} diff --git a/services/app-web/src/pages/Errors/Errors.module.scss b/services/app-web/src/pages/Errors/Errors.module.scss index 1b895bcedc..05f547bd1f 100644 --- a/services/app-web/src/pages/Errors/Errors.module.scss +++ b/services/app-web/src/pages/Errors/Errors.module.scss @@ -11,7 +11,7 @@ align-items: center; } -.errorsFullPage{ +.errorsFullPage { flex: 1; padding: uswds.units(4) 0; } diff --git a/services/app-web/src/pages/GraphQLExplorer/GraphQLExplorer.module.scss b/services/app-web/src/pages/GraphQLExplorer/GraphQLExplorer.module.scss index 7b9e9d7ee1..19999560ee 100644 --- a/services/app-web/src/pages/GraphQLExplorer/GraphQLExplorer.module.scss +++ b/services/app-web/src/pages/GraphQLExplorer/GraphQLExplorer.module.scss @@ -2,14 +2,14 @@ @use '../../styles/uswdsImports.scss' as uswds; .background { - background-color: custom.$mcr-foundation-white; - width: 100%; - height: 100%; - display: flex; - flex: 1; - align-items: stretch; + background-color: custom.$mcr-foundation-white; + width: 100%; + height: 100%; + display: flex; + flex: 1; + align-items: stretch; } .explorer { - width: 100%; + width: 100%; } diff --git a/services/app-web/src/pages/Help/Help.module.scss b/services/app-web/src/pages/Help/Help.module.scss index bb497592f6..1ce8645c4b 100644 --- a/services/app-web/src/pages/Help/Help.module.scss +++ b/services/app-web/src/pages/Help/Help.module.scss @@ -5,7 +5,8 @@ margin: uswds.units(4) 0; table { - th, td { + th, + td { vertical-align: top; } } diff --git a/services/app-web/src/pages/Landing/Landing.module.scss b/services/app-web/src/pages/Landing/Landing.module.scss index dec3795f7a..411f7435aa 100644 --- a/services/app-web/src/pages/Landing/Landing.module.scss +++ b/services/app-web/src/pages/Landing/Landing.module.scss @@ -1,7 +1,6 @@ @use '../../styles/custom.scss' as custom; @use '../../styles/uswdsImports.scss' as uswds; - $details-text-line-height: 1.5; .detailsSection { @@ -68,6 +67,5 @@ $details-text-line-height: 1.5; margin-bottom: uswds.units(1); } } - } } diff --git a/services/app-web/src/pages/Landing/Landing.test.tsx b/services/app-web/src/pages/Landing/Landing.test.tsx index 46aa0d635f..11fbbf34da 100644 --- a/services/app-web/src/pages/Landing/Landing.test.tsx +++ b/services/app-web/src/pages/Landing/Landing.test.tsx @@ -1,17 +1,16 @@ import { screen } from '@testing-library/react' -import { - ldUseClientSpy, - renderWithProviders, -} from '../../testHelpers/jestHelpers' +import { renderWithProviders } from '../../testHelpers/jestHelpers' import { Landing } from './Landing' describe('Landing', () => { afterAll(() => jest.clearAllMocks()) it('displays session expired when query parameter included', async () => { - ldUseClientSpy({ 'site-under-maintenance-banner': false }) renderWithProviders(, { routerProvider: { route: '/?session-timeout' }, + featureFlags: { + 'site-under-maintenance-banner': false, + }, }) expect( screen.queryByRole('heading', { name: 'Session expired' }) @@ -21,9 +20,11 @@ describe('Landing', () => { ).toBeNull() }) it('does not display session expired by default', async () => { - ldUseClientSpy({ 'site-under-maintenance-banner': false }) renderWithProviders(, { routerProvider: { route: '/' }, + featureFlags: { + 'site-under-maintenance-banner': false, + }, }) expect( screen.queryByRole('heading', { diff --git a/services/app-web/src/pages/MccrsId/MccrsId.module.scss b/services/app-web/src/pages/MccrsId/MccrsId.module.scss index d7e59f3d3b..4a6ca64859 100644 --- a/services/app-web/src/pages/MccrsId/MccrsId.module.scss +++ b/services/app-web/src/pages/MccrsId/MccrsId.module.scss @@ -5,7 +5,7 @@ width: 100%; } -.mccrsIDForm { +.mccrsIDForm { [class^='usa-breadcrumb'] { min-width: 40rem; max-width: 20rem; @@ -23,7 +23,6 @@ } .formContainer.tableContainer { - &[class^='usa-form'] { max-width: 100%; width: 75rem; @@ -33,7 +32,7 @@ .formHeader { text-align: center; width: 100%; - padding:uswds.units(4) 0; + padding: uswds.units(4) 0; } .formContainer { @@ -64,11 +63,10 @@ } &[class^='usa-form'] { - min-width: 100%; max-width: 100%; - @include uswds.at-media(tablet){ + @include uswds.at-media(tablet) { min-width: 40rem; max-width: 20rem; margin: 0 auto; @@ -92,10 +90,8 @@ button { margin-top: 16px; margin-bottom: 44px; - margin-right: .25rem; + margin-right: 0.25rem; } } } - } - diff --git a/services/app-web/src/pages/QuestionResponse/QATable/QATable.module.scss b/services/app-web/src/pages/QuestionResponse/QATable/QATable.module.scss index be13ce7c01..0492d76636 100644 --- a/services/app-web/src/pages/QuestionResponse/QATable/QATable.module.scss +++ b/services/app-web/src/pages/QuestionResponse/QATable/QATable.module.scss @@ -1,22 +1,23 @@ @use '../../../styles/custom.scss' as custom; @use '../../../styles/uswdsImports.scss' as uswds; -@use '../../../components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss' as uploadedDocumentsTable; +@use '../../../components/SubmissionSummarySection/UploadedDocumentsTable/UploadedDocumentsTable.module.scss' + as uploadedDocumentsTable; .qaDocumentTable { - @include uploadedDocumentsTable.docs-table; - margin: 0 0 uswds.units(4) 0; + @include uploadedDocumentsTable.docs-table; + margin: 0 0 uswds.units(4) 0; } .tableHeader { - display: flex; - justify-content: space-between; - align-items: center; - padding-bottom: uswds.units(3); - h4 { - padding: 0; - margin: 0; - } - p { - margin: 0; - } + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: uswds.units(3); + h4 { + padding: 0; + margin: 0; + } + p { + margin: 0; + } } diff --git a/services/app-web/src/pages/QuestionResponse/QuestionResponse.module.scss b/services/app-web/src/pages/QuestionResponse/QuestionResponse.module.scss index ed9b83ce77..517ce6a5e8 100644 --- a/services/app-web/src/pages/QuestionResponse/QuestionResponse.module.scss +++ b/services/app-web/src/pages/QuestionResponse/QuestionResponse.module.scss @@ -2,42 +2,42 @@ @use '../../styles/uswdsImports.scss' as uswds; .background { - background-color: custom.$mcr-foundation-white; - width: 100%; + background-color: custom.$mcr-foundation-white; + width: 100%; } .container { - max-width: custom.$mcr-container-standard-width-fixed; - padding: 2rem 0; + max-width: custom.$mcr-container-standard-width-fixed; + padding: 2rem 0; } .questionSection { - margin:uswds.units(2) auto; - background: custom.$mcr-foundation-white; - padding:uswds.units(4)uswds.units(4); - border: 1px solid custom.$mcr-gray-lighter; - line-height:uswds.units(3); - width: 100%; - @include uswds.u-radius('md'); + margin: uswds.units(2) auto; + background: custom.$mcr-foundation-white; + padding: uswds.units(4) uswds.units(4); + border: 1px solid custom.$mcr-gray-lighter; + line-height: uswds.units(3); + width: 100%; + @include uswds.u-radius('md'); - h3 { - display: flex; - justify-content: space-between; - align-items: center; - padding: uswds.units(2) 0; - @include uswds.u-text('normal'); - } + h3 { + display: flex; + justify-content: space-between; + align-items: center; + padding: uswds.units(2) 0; + @include uswds.u-text('normal'); + } } .breadcrumbs { - background: none; - margin:uswds.units(2) auto; + background: none; + margin: uswds.units(2) auto; } .formContainer { > [class^='usa-fieldset'] { padding: uswds.units(4); - margin-bottom:uswds.units(2); - margin-top:uswds.units(2); + margin-bottom: uswds.units(2); + margin-top: uswds.units(2); background: custom.$mcr-foundation-white; border: 1px solid custom.$mcr-gray-lighter; @include uswds.u-radius('md'); @@ -52,11 +52,10 @@ } &[class^='usa-form'] { - min-width: 100%; max-width: 100%; - @include uswds.at-media(tablet){ + @include uswds.at-media(tablet) { min-width: 40rem; max-width: 20rem; margin: 0 auto; diff --git a/services/app-web/src/pages/QuestionResponse/QuestionResponse.test.tsx b/services/app-web/src/pages/QuestionResponse/QuestionResponse.test.tsx index aa3ebb5de6..39ce59d492 100644 --- a/services/app-web/src/pages/QuestionResponse/QuestionResponse.test.tsx +++ b/services/app-web/src/pages/QuestionResponse/QuestionResponse.test.tsx @@ -2,7 +2,7 @@ import { screen, waitFor, within } from '@testing-library/react' import { Route, Routes } from 'react-router-dom' import { SubmissionSideNav } from '../SubmissionSideNav' import { QuestionResponse } from './QuestionResponse' -import { ldUseClientSpy, renderWithProviders } from '../../testHelpers' +import { renderWithProviders } from '../../testHelpers' import { RoutesRecord } from '../../constants/routes' import { @@ -15,13 +15,6 @@ import { import { IndexQuestionsPayload } from '../../gen/gqlClient' describe('QuestionResponse', () => { - beforeEach(() => { - ldUseClientSpy({ 'cms-questions': true }) - }) - afterEach(() => { - jest.resetAllMocks() - }) - it('renders expected questions correctly with rounds', async () => { const mockQuestions = mockQuestionsPayload('15') @@ -50,6 +43,9 @@ describe('QuestionResponse', () => { routerProvider: { route: '/submissions/15/question-and-answers', }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -177,6 +173,9 @@ describe('QuestionResponse', () => { routerProvider: { route: '/submissions/15/question-and-answers', }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -231,6 +230,9 @@ describe('QuestionResponse', () => { routerProvider: { route: '/submissions/15/question-and-answers', }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -291,6 +293,9 @@ describe('QuestionResponse', () => { routerProvider: { route: '/submissions/15/question-and-answers', }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -338,6 +343,9 @@ describe('QuestionResponse', () => { routerProvider: { route: '/submissions/15/question-and-answers?submit=question', }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -372,6 +380,9 @@ describe('QuestionResponse', () => { routerProvider: { route: '/submissions/15/question-and-answers?submit=response', }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -404,6 +415,9 @@ describe('QuestionResponse', () => { routerProvider: { route: '/submissions/15/question-and-answers', }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -438,6 +452,9 @@ describe('QuestionResponse', () => { routerProvider: { route: '/submissions/15/question-and-answers', }, + featureFlags: { + 'cms-questions': true, + }, } ) diff --git a/services/app-web/src/pages/QuestionResponse/UploadQuestions/UploadQuestions.test.tsx b/services/app-web/src/pages/QuestionResponse/UploadQuestions/UploadQuestions.test.tsx index ad388fe0b5..07311432ad 100644 --- a/services/app-web/src/pages/QuestionResponse/UploadQuestions/UploadQuestions.test.tsx +++ b/services/app-web/src/pages/QuestionResponse/UploadQuestions/UploadQuestions.test.tsx @@ -4,7 +4,6 @@ import { Route, Routes } from 'react-router-dom' import { UploadQuestions } from '../../QuestionResponse' import { dragAndDrop, - ldUseClientSpy, renderWithProviders, TEST_DOC_FILE, TEST_PDF_FILE, @@ -26,13 +25,6 @@ import { SubmissionSideNav } from '../../SubmissionSideNav' import { Location } from 'react-router-dom' describe('UploadQuestions', () => { - beforeEach(() => { - ldUseClientSpy({ 'cms-questions': true }) - }) - afterEach(() => { - jest.resetAllMocks() - }) - it('displays file upload for correct cms division', async () => { const division = 'testDivision' renderWithProviders( @@ -59,6 +51,9 @@ describe('UploadQuestions', () => { routerProvider: { route: `/submissions/15/question-and-answers/${division}/upload-questions`, }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -104,6 +99,9 @@ describe('UploadQuestions', () => { routerProvider: { route: `/submissions/15/question-and-answers/dmco/upload-questions`, }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -165,6 +163,9 @@ describe('UploadQuestions', () => { route: `/submissions/15/question-and-answers/dmco/upload-questions`, }, location: (location) => (testLocation = location), + featureFlags: { + 'cms-questions': true, + }, } ) @@ -220,6 +221,9 @@ describe('UploadQuestions', () => { }), ], }, + featureFlags: { + 'cms-questions': true, + }, } ) await screen.findByRole('heading', { @@ -265,6 +269,9 @@ describe('UploadQuestions', () => { }), ], }, + featureFlags: { + 'cms-questions': true, + }, } ) await screen.findByRole('heading', { @@ -315,6 +322,9 @@ describe('UploadQuestions', () => { }), ], }, + featureFlags: { + 'cms-questions': true, + }, } ) await screen.findByRole('heading', { @@ -385,6 +395,9 @@ describe('UploadQuestions', () => { }), ], }, + featureFlags: { + 'cms-questions': true, + }, } ) diff --git a/services/app-web/src/pages/QuestionResponse/UploadResponse/UploadResponse.test.tsx b/services/app-web/src/pages/QuestionResponse/UploadResponse/UploadResponse.test.tsx index de8aab8017..106b143a15 100644 --- a/services/app-web/src/pages/QuestionResponse/UploadResponse/UploadResponse.test.tsx +++ b/services/app-web/src/pages/QuestionResponse/UploadResponse/UploadResponse.test.tsx @@ -4,7 +4,6 @@ import { Route, Routes } from 'react-router-dom' import { UploadResponse } from './UploadResponse' import { dragAndDrop, - ldUseClientSpy, renderWithProviders, TEST_DOC_FILE, TEST_PDF_FILE, @@ -24,13 +23,6 @@ import { import { SubmissionSideNav } from '../../SubmissionSideNav' describe('UploadResponse', () => { - beforeEach(() => { - ldUseClientSpy({ 'cms-questions': true }) - }) - afterEach(() => { - jest.resetAllMocks() - }) - const division = 'testDivision' const questionID = 'testQuestion' @@ -59,6 +51,9 @@ describe('UploadResponse', () => { routerProvider: { route: `/submissions/15/question-and-answers/${division}/${questionID}/upload-response`, }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -98,6 +93,9 @@ describe('UploadResponse', () => { routerProvider: { route: `/submissions/15/question-and-answers/dmco/${questionID}/upload-response`, }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -145,6 +143,9 @@ describe('UploadResponse', () => { routerProvider: { route: `/submissions/15/question-and-answers/dmco/${questionID}/upload-response`, }, + featureFlags: { + 'cms-questions': true, + }, } ) @@ -191,6 +192,9 @@ describe('UploadResponse', () => { routerProvider: { route: `/submissions/15/question-and-answers/dmco/${questionID}/upload-response`, }, + featureFlags: { + 'cms-questions': true, + }, } ) await screen.findByRole('heading', { @@ -241,6 +245,9 @@ describe('UploadResponse', () => { routerProvider: { route: `/submissions/15/question-and-answers/dmco/${questionID}/upload-response`, }, + featureFlags: { + 'cms-questions': true, + }, } ) await screen.findByRole('heading', { @@ -303,6 +310,9 @@ describe('UploadResponse', () => { routerProvider: { route: `/submissions/15/question-and-answers/dmco/${questionID}/upload-response`, }, + featureFlags: { + 'cms-questions': true, + }, } ) await screen.findByRole('heading', { diff --git a/services/app-web/src/pages/RateEdit/RateEdit.test.tsx b/services/app-web/src/pages/RateEdit/RateEdit.test.tsx new file mode 100644 index 0000000000..80b1f4adf1 --- /dev/null +++ b/services/app-web/src/pages/RateEdit/RateEdit.test.tsx @@ -0,0 +1,51 @@ +import { screen, waitFor } from '@testing-library/react' +import { renderWithProviders } from '../../testHelpers' +import { RateEdit } from './RateEdit' +import { + fetchCurrentUserMock, + fetchRateMockSuccess, + mockValidStateUser, +} from '../../testHelpers/apolloMocks' +import { RoutesRecord } from '../../constants' +import { Route, Routes } from 'react-router-dom' + +// Wrap test component in some top level routes to allow getParams to be tested +const wrapInRoutes = (children: React.ReactNode) => { + return ( + + + + ) +} + +describe('RateEdit', () => { + afterAll(() => jest.clearAllMocks()) + + describe('Viewing RateEdit as a state user', () => { + it('renders without errors', async () => { + renderWithProviders(wrapInRoutes(), { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + user: mockValidStateUser(), + statusCode: 200, + }), + fetchRateMockSuccess({ + rate: { id: '1337', status: 'UNLOCKED' }, + }), + ], + }, + routerProvider: { + route: '/rates/1337/edit', + }, + featureFlags: { + 'rate-edit-unlock': true, + }, + }) + + await waitFor(() => { + expect(screen.queryByTestId('rate-edit')).toBeInTheDocument() + }) + }) + }) +}) diff --git a/services/app-web/src/pages/RateEdit/RateEdit.tsx b/services/app-web/src/pages/RateEdit/RateEdit.tsx new file mode 100644 index 0000000000..d66a3127ea --- /dev/null +++ b/services/app-web/src/pages/RateEdit/RateEdit.tsx @@ -0,0 +1,65 @@ +import React from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { useFetchRateQuery } from '../../gen/gqlClient' +import { GridContainer } from '@trussworks/react-uswds' +import { Loading } from '../../components' +import { GenericErrorPage } from '../Errors/GenericErrorPage' +import { ErrorForbiddenPage } from '../Errors/ErrorForbiddenPage' +import { Error404 } from '../Errors/Error404Page' + +type RouteParams = { + id: string +} + +export const RateEdit = (): React.ReactElement => { + const navigate = useNavigate() + const { id } = useParams() + if (!id) { + throw new Error( + 'PROGRAMMING ERROR: id param not set in state submission form.' + ) + } + + const { data, loading, error } = useFetchRateQuery({ + variables: { + input: { + rateID: id, + }, + }, + }) + + const rate = data?.fetchRate.rate + + if (loading) { + return ( + + + + ) + } else if (error || !rate) { + //error handling for a state user that tries to access rates for a different state + if (error?.graphQLErrors[0]?.extensions?.code === 'FORBIDDEN') { + return ( + + ) + } else if (error?.graphQLErrors[0]?.extensions?.code === 'NOT_FOUND') { + return + } else { + return + } + } + + if (rate.status !== 'UNLOCKED') { + navigate(`/rates/${id}`) + } + + return ( +

+ You've reached the '/rates/:id/edit' url placeholder for the + incoming standalone edit rate form +
+ Ticket:{' '} + MCR-3771 +

+ ) +} diff --git a/services/app-web/src/pages/Settings/Settings.module.scss b/services/app-web/src/pages/Settings/Settings.module.scss index 2a235f9848..bcd35cad68 100644 --- a/services/app-web/src/pages/Settings/Settings.module.scss +++ b/services/app-web/src/pages/Settings/Settings.module.scss @@ -2,11 +2,11 @@ @use '../../styles/uswdsImports.scss' as uswds; .table { - h2{ - font-weight: 300 + h2 { + font-weight: 300; } - thead { - th{ + thead { + th { background-color: transparent; border-top: 0; border-left: 0; @@ -25,12 +25,12 @@ .pageContainer { padding: 1em 10em; width: 100%; - background-color: custom.$mcr-foundation-white + background-color: custom.$mcr-foundation-white; } .header { text-align: left; } .wrapper { - height: 100vh; + height: 100vh; } diff --git a/services/app-web/src/pages/StateSubmission/ContractDetails/ContractDetails.test.tsx b/services/app-web/src/pages/StateSubmission/ContractDetails/ContractDetails.test.tsx index c12422ab70..fce5b2a833 100644 --- a/services/app-web/src/pages/StateSubmission/ContractDetails/ContractDetails.test.tsx +++ b/services/app-web/src/pages/StateSubmission/ContractDetails/ContractDetails.test.tsx @@ -17,7 +17,6 @@ import { TEST_PNG_FILE, dragAndDrop, selectYesNoRadio, - ldUseClientSpy, } from '../../../testHelpers/jestHelpers' import { ACCEPTED_SUBMISSION_FILE_TYPES } from '../../../components/FileUpload' import { ContractDetails } from './' @@ -38,10 +37,6 @@ const scrollIntoViewMock = jest.fn() HTMLElement.prototype.scrollIntoView = scrollIntoViewMock describe('ContractDetails', () => { - afterEach(() => { - jest.clearAllMocks() - }) - const emptyContractDetailsDraft = { ...mockDraft(), } @@ -1028,7 +1023,6 @@ describe('ContractDetails', () => { describe('Contract 438 attestation', () => { it('renders 438 attestation question without errors', async () => { - ldUseClientSpy({ '438-attestation': true }) const draft = mockBaseContract({ statutoryRegulatoryAttestation: true, }) @@ -1041,14 +1035,17 @@ describe('ContractDetails', () => { />, { apolloProvider: defaultApolloProvider, + featureFlags: { '438-attestation': true }, } ) }) // expect 438 attestation question to be on the page - expect( - screen.getByText(StatutoryRegulatoryAttestationQuestion) - ).toBeInTheDocument() + await waitFor(() => { + expect( + screen.getByText(StatutoryRegulatoryAttestationQuestion) + ).toBeInTheDocument() + }) const yesRadio = screen.getByRole('radio', { name: StatutoryRegulatoryAttestation.YES, @@ -1073,7 +1070,6 @@ describe('ContractDetails', () => { }) }) it('errors when continuing without answering 438 attestation question', async () => { - ldUseClientSpy({ '438-attestation': true }) const draft = mockContractAndRatesDraft({ contractDateStart: new Date('11-12-2023'), contractDateEnd: new Date('11-12-2024'), @@ -1090,14 +1086,17 @@ describe('ContractDetails', () => { />, { apolloProvider: defaultApolloProvider, + featureFlags: { '438-attestation': true }, } ) }) // expect 438 attestation question to be on the page - expect( - screen.getByText(StatutoryRegulatoryAttestationQuestion) - ).toBeInTheDocument() + await waitFor(() => { + expect( + screen.getByText(StatutoryRegulatoryAttestationQuestion) + ).toBeInTheDocument() + }) const yesRadio = screen.getByRole('radio', { name: StatutoryRegulatoryAttestation.YES, @@ -1140,7 +1139,6 @@ describe('ContractDetails', () => { }) }) it('errors when continuing without description for 438 non-compliance', async () => { - ldUseClientSpy({ '438-attestation': true }) const draft = mockContractAndRatesDraft({ contractDateStart: new Date('11-12-2023'), contractDateEnd: new Date('11-12-2024'), @@ -1157,14 +1155,17 @@ describe('ContractDetails', () => { />, { apolloProvider: defaultApolloProvider, + featureFlags: { '438-attestation': true }, } ) }) // expect 438 attestation question to be on the page - expect( - screen.getByText(StatutoryRegulatoryAttestationQuestion) - ).toBeInTheDocument() + await waitFor(() => { + expect( + screen.getByText(StatutoryRegulatoryAttestationQuestion) + ).toBeInTheDocument() + }) const continueButton = screen.getByRole('button', { name: 'Continue', diff --git a/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss b/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss index ff1a863598..a31760c552 100644 --- a/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss +++ b/services/app-web/src/pages/StateSubmission/StateSubmissionForm.module.scss @@ -1,7 +1,6 @@ @use '../../styles/custom.scss' as custom; @use '../../styles/uswdsImports.scss' as uswds; - .formPage { width: 100%; } @@ -18,7 +17,7 @@ .formHeader { text-align: center; width: 100%; - padding:uswds.units(4) 0; + padding: uswds.units(4) 0; } .formContainer { @@ -31,7 +30,7 @@ @include uswds.u-radius('md'); } // for supporting documents - >& .tableContainer { + > & .tableContainer { &[class^='usa-form'] { max-width: 100%; width: 75rem; @@ -40,7 +39,7 @@ // the first fieldset of the form sets up form container // in cases where form has multiple sub sections using SectionCard - use .withSections class > [class^='usa-fieldset']:not([class~='with-sections']) { - @include custom.sectionCard + @include custom.sectionCard; } > div[class^='usa-form-group']:not(:first-of-type) { @@ -48,11 +47,10 @@ } &[class^='usa-form'] { - min-width: 100%; max-width: 100%; - @include uswds.at-media(tablet){ + @include uswds.at-media(tablet) { min-width: 40rem; max-width: 20rem; margin: 0 auto; @@ -135,11 +133,11 @@ .legendSubHeader { font-weight: normal; &.requiredOptionalText { - margin-bottom:uswds.units(2); + margin-bottom: uswds.units(2); } } -.guidanceTextBlock{ +.guidanceTextBlock { padding-top: 0; display: flex; flex-direction: column; @@ -150,7 +148,7 @@ color: custom.$mcr-foundation-hint; } -.guidanceTextBlockNoPadding{ +.guidanceTextBlockNoPadding { display: flex; flex-direction: column; } @@ -163,7 +161,7 @@ label { max-width: none; } - div[role=note] { + div[role='note'] { margin-top: uswds.units(0); } } diff --git a/services/app-web/src/pages/SubmissionRevisionSummary/SubmissionRevisionSummary.module.scss b/services/app-web/src/pages/SubmissionRevisionSummary/SubmissionRevisionSummary.module.scss index 3fe7c73d25..af32a133ba 100644 --- a/services/app-web/src/pages/SubmissionRevisionSummary/SubmissionRevisionSummary.module.scss +++ b/services/app-web/src/pages/SubmissionRevisionSummary/SubmissionRevisionSummary.module.scss @@ -10,7 +10,7 @@ max-width: custom.$mcr-container-standard-width-fixed; padding: 2rem 0; - [id=submissionTypeSection] { + [id='submissionTypeSection'] { [class^='SectionHeader_summarySectionHeader'] { flex-direction: column; align-items: flex-start; diff --git a/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.module.scss b/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.module.scss index 594d728c78..41fc13c3ec 100644 --- a/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.module.scss +++ b/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.module.scss @@ -1,31 +1,29 @@ @use '../../styles/custom.scss' as custom; @use '../../styles/uswdsImports.scss' as uswds; - .backgroundSidebar { - background-color: custom.$mcr-foundation-white; - width: 100%; - height: 100%; - flex: 1; + background-color: custom.$mcr-foundation-white; + width: 100%; + height: 100%; + flex: 1; } .backgroundForm { - width: 100%; + width: 100%; } - .container { - @include custom.default-page-container; - display: flex; - flex-direction: row; + @include custom.default-page-container; + display: flex; + flex-direction: row; } .sideNavContainer { - padding: 2rem 0; - width: 20rem; - max-width: custom.$mcr-container-max-width-fixed; + padding: 2rem 0; + width: 20rem; + max-width: custom.$mcr-container-max-width-fixed; } .backLinkContainer { - padding-top: uswds.units(2); - padding-bottom: uswds.units(4); + padding-top: uswds.units(2); + padding-bottom: uswds.units(4); } diff --git a/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.test.tsx b/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.test.tsx index 87d6aa9744..49460fdf7c 100644 --- a/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.test.tsx +++ b/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.test.tsx @@ -17,15 +17,8 @@ import { mockUnlockedHealthPlanPackage, mockValidCMSUser, } from '../../testHelpers/apolloMocks' -import { ldUseClientSpy } from '../../testHelpers' describe('SubmissionSideNav', () => { - beforeEach(() => { - ldUseClientSpy({ 'cms-questions': true }) - }) - afterEach(() => { - jest.resetAllMocks() - }) it('loads sidebar nav with expected links', async () => { renderWithProviders( @@ -55,6 +48,7 @@ describe('SubmissionSideNav', () => { routerProvider: { route: '/submissions/15', }, + featureFlags: { 'cms-questions': true }, } ) @@ -124,6 +118,7 @@ describe('SubmissionSideNav', () => { route: '/submissions/15', }, location: (location) => (testLocation = location), + featureFlags: { 'cms-questions': true }, } ) @@ -230,6 +225,7 @@ describe('SubmissionSideNav', () => { routerProvider: { route: '/submissions/15', }, + featureFlags: { 'cms-questions': true }, } ) expect( @@ -266,6 +262,7 @@ describe('SubmissionSideNav', () => { routerProvider: { route: '/submissions/15', }, + featureFlags: { 'cms-questions': true }, } ) @@ -313,6 +310,7 @@ describe('SubmissionSideNav', () => { routerProvider: { route: '/submissions/15', }, + featureFlags: { 'cms-questions': true }, } ) @@ -355,6 +353,7 @@ describe('SubmissionSideNav', () => { routerProvider: { route: '/submissions/15', }, + featureFlags: { 'cms-questions': true }, } ) @@ -398,6 +397,7 @@ describe('SubmissionSideNav', () => { route: '/submissions/15', }, location: (location) => (testLocation = location), + featureFlags: { 'cms-questions': true }, } ) @@ -443,6 +443,7 @@ describe('SubmissionSideNav', () => { route: '/submissions/15', }, location: (location) => (testLocation = location), + featureFlags: { 'cms-questions': true }, } ) @@ -487,6 +488,7 @@ describe('SubmissionSideNav', () => { routerProvider: { route: '/submissions/15', }, + featureFlags: { 'cms-questions': true }, } ) @@ -521,6 +523,7 @@ describe('SubmissionSideNav', () => { ], }, routerProvider: { route: '/submissions/404' }, + featureFlags: { 'cms-questions': true }, } ) @@ -559,6 +562,7 @@ describe('SubmissionSideNav', () => { routerProvider: { route: '/submissions/15/question-and-answers', }, + featureFlags: { 'cms-questions': true }, } ) diff --git a/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.tsx b/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.tsx index 604454012b..dc39c48d6b 100644 --- a/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.tsx +++ b/services/app-web/src/pages/SubmissionSideNav/SubmissionSideNav.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { Link, SideNav, GridContainer, Icon } from '@trussworks/react-uswds' import { NavLink } from 'react-router-dom' import styles from './SubmissionSideNav.module.scss' diff --git a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx b/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx index 2d0cafc331..ca00baba01 100644 --- a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx +++ b/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.test.tsx @@ -1,5 +1,5 @@ import { screen, waitFor } from '@testing-library/react' -import { renderWithProviders, testS3Client, ldUseClientSpy } from '../../../testHelpers' +import { renderWithProviders, testS3Client } from '../../../testHelpers' import { fetchCurrentUserMock, fetchRateMockSuccess, @@ -9,6 +9,7 @@ import { import { RateSummary } from './RateSummary' import { RoutesRecord } from '../../../constants' import { Route, Routes } from 'react-router-dom' +import { RateEdit } from '../../RateEdit/RateEdit' // Wrap test component in some top level routes to allow getParams to be tested const wrapInRoutes = (children: React.ReactNode) => { @@ -20,8 +21,6 @@ const wrapInRoutes = (children: React.ReactNode) => { } describe('RateSummary', () => { - afterAll(() => jest.clearAllMocks()) - describe('Viewing RateSummary as a CMS user', () => { it('renders without errors', async () => { renderWithProviders(wrapInRoutes(), { @@ -38,17 +37,21 @@ describe('RateSummary', () => { route: '/rates/7a', }, }) - + expect( - await screen.findByText('Programs this rate certification covers') + await screen.findByText( + 'Programs this rate certification covers' + ) ).toBeInTheDocument() }) it('renders document download warning banner when download fails', async () => { - const error = jest.spyOn(console, 'error').mockImplementation(() => { - // mock expected console error to keep test output clear - }) - + const error = jest + .spyOn(console, 'error') + .mockImplementation(() => { + // mock expected console error to keep test output clear + }) + const s3Provider = { ...testS3Client(), getBulkDlURL: async ( @@ -73,7 +76,7 @@ describe('RateSummary', () => { }, s3Provider, }) - + await waitFor(() => { expect(screen.getByTestId('warning-alert')).toBeInTheDocument() expect(screen.getByTestId('warning-alert')).toHaveClass( @@ -101,22 +104,18 @@ describe('RateSummary', () => { route: '/rates/7a', }, }) - + const backLink = await screen.findByRole('link', { name: /Back to dashboard/, }) expect(backLink).toBeInTheDocument() - + expect(backLink).toHaveAttribute('href', '/dashboard/rate-reviews') }) }) describe('Viewing RateSummary as a State user', () => { - beforeEach(() => { - ldUseClientSpy({'rate-edit-unlock': true}) - }) - - it('renders without errors', async () => { + it('renders SingleRateSummarySection component without errors for locked rate', async () => { renderWithProviders(wrapInRoutes(), { apolloProvider: { mocks: [ @@ -128,8 +127,9 @@ describe('RateSummary', () => { ], }, routerProvider: { - route: '/rates/1337' + route: '/rates/1337', }, + featureFlags: { 'rate-edit-unlock': true }, }) await waitFor(() => { @@ -137,10 +137,50 @@ describe('RateSummary', () => { }) expect( - await screen.findByText('Programs this rate certification covers') + await screen.findByText( + 'Programs this rate certification covers' + ) ).toBeInTheDocument() }) + it('redirects to RateEdit component from RateSummary without errors for unlocked rate', async () => { + renderWithProviders( + + } + /> + } + /> + , + { + apolloProvider: { + mocks: [ + fetchCurrentUserMock({ + user: mockValidStateUser(), + statusCode: 200, + }), + fetchRateMockSuccess({ + rate: { id: '1337', status: 'UNLOCKED' }, + }), + ], + }, + routerProvider: { + route: '/rates/1337', + }, + featureFlags: { + 'rate-edit-unlock': true, + }, + } + ) + + await waitFor(() => { + expect(screen.queryByTestId('rate-edit')).toBeInTheDocument() + }) + }) + it('renders expected error page when rate ID is invalid', async () => { renderWithProviders(wrapInRoutes(), { apolloProvider: { @@ -154,13 +194,12 @@ describe('RateSummary', () => { }, //purposefully attaching invalid id to url here routerProvider: { - route: '/rates/133' + route: '/rates/133', }, + featureFlags: { 'rate-edit-unlock': true }, }) - expect( - await screen.findByText('System error') - ).toBeInTheDocument() + expect(await screen.findByText('System error')).toBeInTheDocument() }) it('renders back to dashboard link for state users', async () => { @@ -177,13 +216,14 @@ describe('RateSummary', () => { routerProvider: { route: '/rates/7a', }, + featureFlags: { 'rate-edit-unlock': true }, }) - + const backLink = await screen.findByRole('link', { name: /Back to dashboard/, }) expect(backLink).toBeInTheDocument() - + expect(backLink).toHaveAttribute('href', '/dashboard') }) }) diff --git a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx b/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx index 432096ceb3..3bc21a1f13 100644 --- a/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx +++ b/services/app-web/src/pages/SubmissionSummary/RateSummary/RateSummary.tsx @@ -1,6 +1,6 @@ import { GridContainer, Icon, Link } from '@trussworks/react-uswds' import React, { useEffect, useState } from 'react' -import { NavLink, useParams } from 'react-router-dom' +import { NavLink, useNavigate, useParams } from 'react-router-dom' import { Loading } from '../../../components' import { usePage } from '../../../contexts/PageContext' @@ -10,6 +10,8 @@ import { GenericErrorPage } from '../../Errors/GenericErrorPage' import { RoutesRecord } from '../../../constants' import { SingleRateSummarySection } from '../../../components/SubmissionSummarySection/RateDetailsSummarySection/SingleRateSummarySection' import { useAuth } from '../../../contexts/AuthContext' +import { ErrorForbiddenPage } from '../../Errors/ErrorForbiddenPage' +import { Error404 } from '../../Errors/Error404Page' type RouteParams = { id: string @@ -19,6 +21,7 @@ export const RateSummary = (): React.ReactElement => { // Page level state const { loggedInUser } = useAuth() const { updateHeading } = usePage() + const navigate = useNavigate() const [rateName, setRateName] = useState(undefined) const { id } = useParams() if (!id) { @@ -49,7 +52,21 @@ export const RateSummary = (): React.ReactElement => { ) } else if (error || !rate || !currentRateRev?.formData) { - return + //error handling for a state user that tries to access rates for a different state + if (error?.graphQLErrors[0]?.extensions?.code === 'FORBIDDEN') { + return ( + + ) + } else if (error?.graphQLErrors[0]?.extensions?.code === 'NOT_FOUND') { + return + } else { + return + } + } + + //Redirecting a state user to the edit page if rate is unlocked + if (loggedInUser?.role === 'STATE_USER' && rate.status === 'UNLOCKED') { + navigate(`/rates/${id}/edit`) } if ( @@ -68,10 +85,12 @@ export const RateSummary = (): React.ReactElement => {
diff --git a/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.module.scss b/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.module.scss index 876726112b..ec7e00ac2e 100644 --- a/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.module.scss +++ b/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.module.scss @@ -27,7 +27,6 @@ display: inline-block; margin-left: 5px; margin-top: 0px; - } } a.editLink { @@ -42,7 +41,7 @@ .backLinkContainer { padding-top: uswds.units(2); padding-bottom: uswds.units(2); - } - .backLinkContainerNoSideBar { +} +.backLinkContainerNoSideBar { padding-bottom: uswds.units(2); - } \ No newline at end of file +} diff --git a/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.test.tsx b/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.test.tsx index ff487433bc..cdf49b6305 100644 --- a/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.test.tsx +++ b/services/app-web/src/pages/SubmissionSummary/SubmissionSummary.test.tsx @@ -16,21 +16,12 @@ import { mockStateSubmission, mockSubmittedHealthPlanPackage, } from '../../testHelpers/apolloMocks' -import { - ldUseClientSpy, - renderWithProviders, -} from '../../testHelpers/jestHelpers' +import { renderWithProviders } from '../../testHelpers/jestHelpers' import { SubmissionSummary } from './SubmissionSummary' import { SubmissionSideNav } from '../SubmissionSideNav' import { testS3Client } from '../../testHelpers/s3Helpers' describe('SubmissionSummary', () => { - beforeEach(() => { - ldUseClientSpy({ 'cms-questions': false }) - }) - afterEach(() => { - jest.resetAllMocks() - }) it('renders without errors', async () => { renderWithProviders( diff --git a/services/app-web/src/styles/custom.scss b/services/app-web/src/styles/custom.scss index a1e0f28f65..f2296c6e6e 100644 --- a/services/app-web/src/styles/custom.scss +++ b/services/app-web/src/styles/custom.scss @@ -9,13 +9,13 @@ @forward 'mcrColors'; // Allows access to colors anywhere custom is imported @use 'mcrColors' as *; -@use'uswdsImports.scss' as uswds; +@use 'uswdsImports.scss' as uswds; /* CONTAINERS */ // Every page starts with a flex container @mixin container { - display: flex; - flex: 1 0 auto; + display: flex; + flex: 1 0 auto; } // We have some established width limits how far page content should stretch laterally. Right now this is controlled by CSS width properties $mcr-container-standard-width-fixed: 50rem; @@ -44,7 +44,8 @@ $mcr-container-max-width-fixed: 75rem; $mcr-primary-dark, $mcr-cyan-base ); - box-shadow: inset 0 0 1px $mcr-gray-dark, + box-shadow: + inset 0 0 1px $mcr-gray-dark, 0px 0px 24px rgb(0 0 0 / 5%); } @@ -65,4 +66,4 @@ $mcr-container-max-width-fixed: 75rem; width: 1px; height: 1px; overflow: hidden; -} \ No newline at end of file +} diff --git a/services/app-web/src/styles/mcrColors.scss b/services/app-web/src/styles/mcrColors.scss index 9f44c0b857..38e1c8670f 100644 --- a/services/app-web/src/styles/mcrColors.scss +++ b/services/app-web/src/styles/mcrColors.scss @@ -6,46 +6,46 @@ */ $mcr-primary-base: #005ea2; // USWDS 'primary' -$mcr-primary-dark : #1a4480; // USWDS 'primary-dark' -$mcr-primary-darkest: #162e51; // USWDS 'primary-darker', +$mcr-primary-dark: #1a4480; // USWDS 'primary-dark' +$mcr-primary-darkest: #162e51; // USWDS 'primary-darker', $mcr-primary-light: #d9e8f6; // USWDS 'bg-primary-lighter', $mcr-primary-lighter: #e1f3f8; // USWDS 'blue-cool-5v' $mcr-cmsblue-base: #0071bc; // CMSDS 'color-primary' $mcr-cmsblue-dark: #205493; // CMSDS 'color-primary-darker $mcr-cmsblue-darkest: #112e51; // CMSDS 'color-primary-darkest' -$mcr-cmsblue-lightest: #f0fafd;// This color is slightly off from CMSDS 'color-primary-alt-lightest', don't think it maps to either system +$mcr-cmsblue-lightest: #f0fafd; // This color is slightly off from CMSDS 'color-primary-alt-lightest', don't think it maps to either system $mcr-cyan-base: #02bfe7; // CMSDS 'color-primary-alt' $mcr-cyan-dark: #009ec1; // USWDS 'cyan-40v' -$mcr-cyan-light: #99deea;// USWDS 'cyan-20' -$mcr-cyan-lighter: #e7f6F8; // USWDS 'cyan-5' +$mcr-cyan-light: #99deea; // USWDS 'cyan-20' +$mcr-cyan-lighter: #e7f6f8; // USWDS 'cyan-5' $mcr-gold-lighter: #faf3d1; //USWDS yellow-5 -$mcr-gold-light: #fee685; // USWDS yellow 10v +$mcr-gold-light: #fee685; // USWDS yellow 10v $mcr-gold-base: #ffbe2e; // USWDS 'gold-20v' $mcr-gold-dark: #e5a000; // USWDS 'gold-30v' $mcr-gold-darker: #ca9318; // CMSDS 'color-warn-darker' -$mcr-gray-base :#a9aeb1; // USWDS 'gray-cool-30' +$mcr-gray-base: #a9aeb1; // USWDS 'gray-cool-30' $mcr-gray-dark: #565c65; // USWDS 'gray-cool-60' -$mcr-gray-lighter:#dfe1e2; // USWDS 'gray-cool-10' +$mcr-gray-lighter: #dfe1e2; // USWDS 'gray-cool-10' $mcr-gray-lightest: #f0f0f0; /// USWDS 'gray-5' // mcr-foundation is used for text and containers -$mcr-foundation-ink:#1b1b1b; // USWDS 'gray-90' +$mcr-foundation-ink: #1b1b1b; // USWDS 'gray-90' $mcr-foundation-hint: #71767a; // USWDS 'text-base' $mcr-foundation-link: $mcr-primary-base; -$mcr-foundation-focus:#3e94cf; // CMS 'color-focus' +$mcr-foundation-focus: #3e94cf; // CMS 'color-focus' $mcr-foundation-white: #fff; $mcr-foundation-visited: #4c2c92; // CMS 'color-visited' // mcr-success is used for submit buttons and completed actions $mcr-success-base: #2e8540; // CMSDS 'color-success' -$mcr-success-hover:#2a7a3b; // CMSDS 'color-success-dark" // CMSDS 'color-success-dark" +$mcr-success-hover: #2a7a3b; // CMSDS 'color-success-dark" // CMSDS 'color-success-dark" $mcr-success-dark: #4d8055; // USWDS 'green-cool-50' // mcr-error is used for error, validations, and incomplete actions $mcr-error-base: #b50909; // USWDS 'red-60v' $mcr-error-dark: #981b1e; // CMS 'red-darkest -$mcr-error-light: #f4e3db; // USWDS 'red-warm-10' \ No newline at end of file +$mcr-error-light: #f4e3db; // USWDS 'red-warm-10' diff --git a/services/app-web/src/styles/overrides.scss b/services/app-web/src/styles/overrides.scss index 7f763da417..1e01ed95dc 100644 --- a/services/app-web/src/styles/overrides.scss +++ b/services/app-web/src/styles/overrides.scss @@ -8,25 +8,25 @@ @use 'mcrColors' as *; // FORM FIELDS - .usa-label, - .usa-legend { - font-weight: bold; - } +.usa-label, +.usa-legend { + font-weight: bold; +} - .usa-hint span { - display: block; - margin-top: 1rem; - } +.usa-hint span { + display: block; + margin-top: 1rem; +} - .usa-checkbox__label, - .usa-radio__label { - margin-top: 1rem; - } +.usa-checkbox__label, +.usa-radio__label { + margin-top: 1rem; +} // TOOLTIP // This can be removed removed when https://github.com/uswds/uswds/issues/4458 is fixed .usa-tooltip__body { - opacity: 0; + opacity: 0; } // BUTTONS diff --git a/services/app-web/src/styles/theme/_color.scss b/services/app-web/src/styles/theme/_color.scss index 0fcba3c1f2..f8b7e66b61 100644 --- a/services/app-web/src/styles/theme/_color.scss +++ b/services/app-web/src/styles/theme/_color.scss @@ -34,14 +34,12 @@ $theme-color-error-darker: $mcr-error-dark; $theme-color-success: $mcr-success-base; $theme-color-success-dark: $mcr-success-dark; - // Info colors $theme-color-info-dark: $mcr-cyan-dark; // Hint colors $theme-color-hint: $mcr-foundation-hint; - /* ---------------------------------------- General colors diff --git a/services/app-web/src/styles/uswdsSettings.scss b/services/app-web/src/styles/uswdsSettings.scss index d123d3ebd7..bffc0fa875 100644 --- a/services/app-web/src/styles/uswdsSettings.scss +++ b/services/app-web/src/styles/uswdsSettings.scss @@ -30,4 +30,3 @@ $theme-footer-logos-background: #f0fafd; /* MODAL */ $theme-modal-border-radius: 0; - diff --git a/services/app-web/src/testHelpers/apolloMocks/apiKeyGQLMocks.ts b/services/app-web/src/testHelpers/apolloMocks/apiKeyGQLMocks.ts new file mode 100644 index 0000000000..de323df96b --- /dev/null +++ b/services/app-web/src/testHelpers/apolloMocks/apiKeyGQLMocks.ts @@ -0,0 +1,33 @@ +import { MockedResponse } from "@apollo/client/testing" +import { CreateApiKeyDocument, CreateApiKeyMutation } from "../../gen/gqlClient" + +function createAPIKeySuccess(): MockedResponse { + + return { + request: { + query: CreateApiKeyDocument, + }, + result: { + data: { + createAPIKey: { + key: 'foo.bar.baz.key123', + expiresAt: '2025-01-31T21:08:57.951Z', + }, + }, + }, + } +} + +function createAPIKeyNetworkError(): MockedResponse { + return { + request: { + query: CreateApiKeyDocument, + }, + error: new Error('A network error occurred'), + } +} + +export { + createAPIKeySuccess, + createAPIKeyNetworkError, +} diff --git a/services/app-web/src/testHelpers/apolloMocks/index.ts b/services/app-web/src/testHelpers/apolloMocks/index.ts index f71b424cc5..400a7113a7 100644 --- a/services/app-web/src/testHelpers/apolloMocks/index.ts +++ b/services/app-web/src/testHelpers/apolloMocks/index.ts @@ -57,3 +57,8 @@ export { indexRatesMockSuccess, indexRatesMockFailure } from './rateGQLMocks' export { updateUserMockError, updateUserMockSuccess } from './updateUserMock' export { fetchRateMockSuccess } from './rateGQLMocks' + +export { + createAPIKeySuccess, + createAPIKeyNetworkError, +} from './apiKeyGQLMocks' diff --git a/services/app-web/src/testHelpers/index.ts b/services/app-web/src/testHelpers/index.ts index fe1c3524be..0c3e87d7c2 100644 --- a/services/app-web/src/testHelpers/index.ts +++ b/services/app-web/src/testHelpers/index.ts @@ -11,7 +11,6 @@ export { userClickByRole, userClickByTestId, userClickSignIn, - ldUseClientSpy, TEST_DOC_FILE, TEST_DOCX_FILE, TEST_PDF_FILE, diff --git a/services/app-web/src/testHelpers/jestHelpers.tsx b/services/app-web/src/testHelpers/jestHelpers.tsx index 278369d6c0..78b7d25ae7 100644 --- a/services/app-web/src/testHelpers/jestHelpers.tsx +++ b/services/app-web/src/testHelpers/jestHelpers.tsx @@ -18,7 +18,6 @@ import { PageProvider } from '../contexts/PageContext' import { S3Provider } from '../contexts/S3Context' import { testS3Client } from './s3Helpers' import { S3ClientT } from '../s3' -import * as LaunchDarkly from 'launchdarkly-react-client-sdk' import { FeatureFlagLDConstant, FlagValue, @@ -26,6 +25,35 @@ import { featureFlagKeys, featureFlags, } from '../common-code/featureFlags' +import { + LDProvider, + ProviderConfig, + LDClient, +} from 'launchdarkly-react-client-sdk' + +function ldClientMock(featureFlags: FeatureFlagSettings): LDClient { + return { + track: jest.fn(), + identify: jest.fn(), + close: jest.fn(), + flush: jest.fn(), + getContext: jest.fn(), + off: jest.fn(), + on: jest.fn(), + setStreaming: jest.fn(), + variationDetail: jest.fn(), + waitForInitialization: jest.fn(), + waitUntilGoalsReady: jest.fn(), + waitUntilReady: jest.fn(), + variation: jest.fn( + ( + flag: FeatureFlagLDConstant, + defaultValue: FlagValue | undefined + ) => featureFlags[flag] ?? defaultValue + ), + allFlags: jest.fn(() => featureFlags), + } +} /* Render */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -37,6 +65,7 @@ const renderWithProviders = ( authProvider?: Partial // used to pass user authentication state via AuthContext s3Provider?: S3ClientT // used to pass AWS S3 related state via S3Context location?: (location: Location) => Location // used to pass a location url for react-router + featureFlags?: FeatureFlagSettings } ) => { const { @@ -45,23 +74,44 @@ const renderWithProviders = ( authProvider = {}, s3Provider = undefined, location = undefined, + featureFlags = undefined, } = options || {} const { route } = routerProvider const s3Client: S3ClientT = s3Provider ?? testS3Client() const user = userEvent.setup() + const flags: FeatureFlagSettings = { + ...getDefaultFeatureFlags(), + ...featureFlags, + } + + const ldProviderConfig: ProviderConfig = { + clientSideID: 'test-url', + options: { + bootstrap: flags, + baseUrl: 'test-url', + streamUrl: 'test-url', + eventsUrl: 'test-url', + }, + ldClient: ldClientMock(flags), + } + const renderResult = render( - - - - - {location && } - {ui} - - - - + + + + + + {location && ( + + )} + {ui} + + + + + ) return { user, @@ -86,47 +136,6 @@ const getDefaultFeatureFlags = (): FeatureFlagSettings => return Object.assign(a, { [flag]: defaultValue }) }, {} as FeatureFlagSettings) -//WARNING: This required tests using this function to clear mocks afterwards. -const ldUseClientSpy = (featureFlags: FeatureFlagSettings) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - jest.spyOn(LaunchDarkly, 'useLDClient').mockImplementation((): any => { - return { - // Checks to see if flag passed into useLDClient exists in the featureFlag passed in ldUseClientSpy - // If flag passed in useLDClient does not exist, then use defaultValue that was also passed into useLDClient. - // If flag does exist the featureFlag value passed into ldUseClientSpy then use the value in featureFlag. - // - // This is done because testing components may contain more than one instance of useLDClient for a different - // flag. We do not want to apply the value passed in featureFlags to each useLDClient especially if the flag - // passed in useLDClient does not exist in featureFlags passed into ldUseClientSpy. - getUser: jest.fn(), - identify: jest.fn(), - alias: jest.fn(), - variation: ( - flag: FeatureFlagLDConstant, - defaultValue: FlagValue | undefined - ) => { - if ( - featureFlags[flag] === undefined && - defaultValue === undefined - ) { - //ldClient.variation doesn't require a default value, throwing error here if a defaultValue was not provided. - throw new Error( - 'ldUseClientSpy returned an invalid value of undefined' - ) - } - return featureFlags[flag] === undefined - ? defaultValue - : featureFlags[flag] - }, - allFlags: () => { - const defaultFeatureFlags = getDefaultFeatureFlags() - Object.assign(defaultFeatureFlags, featureFlags) - return defaultFeatureFlags - }, - } - }) -} - const prettyDebug = (label?: string, element?: HTMLElement): void => { console.info( `${label ?? 'body'}: @@ -238,7 +247,6 @@ export { userClickByRole, userClickByTestId, userClickSignIn, - ldUseClientSpy, selectYesNoRadio, TEST_DOC_FILE, TEST_DOCX_FILE, diff --git a/services/cypress/integration/thirdPartyAPIAccess/thirdPartyAPIAccess.spec.ts b/services/cypress/integration/thirdPartyAPIAccess/thirdPartyAPIAccess.spec.ts new file mode 100644 index 0000000000..f6befbecee --- /dev/null +++ b/services/cypress/integration/thirdPartyAPIAccess/thirdPartyAPIAccess.spec.ts @@ -0,0 +1,78 @@ +describe('thirdPartyAPIAccess', () => { + + beforeEach(() => { + cy.stubFeatureFlags() + cy.interceptGraphQL() + }) + + + it('gets an error back without authentication', () => { + + // get the API URL! + const url = Cypress.env('API_URL') + const api_url = url + '/v1/graphql/external' + + cy.request({ + url: api_url, + headers: { + 'Authorization': 'Bearer foobar' + }, + failOnStatusCode: false, + }).then(res => { + expect(res.status).to.equal(403) // unauthenticated?? + }) + + }) + + it('gets an error back without no auth header sent', () => { + + // get the API URL! + const url = Cypress.env('API_URL') + const api_url = url + '/v1/graphql/external' + + cy.request({ + url: api_url, + failOnStatusCode: false, + }).then(res => { + expect(res.status).to.equal(401) // unauthenticated + }) + }) + + it('works with a valid key', () => { + + // get the API URL! + const url = Cypress.env('API_URL') + const api_url = url + '/v1/graphql/external' + + // sign in and get to the api key url + cy.logInAsCMSUser() + + cy.visit('/dev/api-access') + + cy.findByRole('button', { + name: "Generate API Key", + }).click() + + + cy.get("[aria-label='API Key Text']").then((codeBlock) => { + + const apiKey = codeBlock.text().trim() + const bearer = `Bearer ${apiKey}` + + cy.request({ + method: 'post', + url: api_url, + headers: { + 'Content-Type': 'application/json', + Authorization: bearer, + }, + body: '{"query":"query IndexRates { indexRates { totalCount edges { node { id } } } }"}', + failOnStatusCode: false, + }).then(res => { + expect(res.status).to.equal(200) + }) + + }) + }) + +}) diff --git a/services/infra-api/serverless.yml b/services/infra-api/serverless.yml index d029562458..ec8742d32b 100644 --- a/services/infra-api/serverless.yml +++ b/services/infra-api/serverless.yml @@ -224,34 +224,19 @@ resources: RoleArn: !GetAtt MetricStreamRole.Arn OutputFormat: 'opentelemetry0.7' - # AppApiGatewayAcl: - # Type: AWS::WAFv2::WebACL - # Properties: - # DefaultAction: - # Block: {} - # Rules: - # - Action: - # Allow: {} - # Name: ${sls:stage}-allow-usa-plus-territories - # Priority: 0 - # Statement: - # GeoMatchStatement: - # CountryCodes: - # - GU # Guam - # - PR # Puerto Rico - # - US # USA - # - UM # US Minor Outlying Islands - # - VI # US Virgin Islands - # - MP # Northern Mariana Islands - # VisibilityConfig: - # SampledRequestsEnabled: true - # CloudWatchMetricsEnabled: true - # MetricName: WafWebAcl - # Scope: REGIONAL - # VisibilityConfig: - # CloudWatchMetricsEnabled: true - # SampledRequestsEnabled: true - # MetricName: ${sls:stage}-webacl + JWTSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: 'api_jwt_secret_${sls:stage}' + Description: 'Dynamically generated secret for JWT signing/validation' + GenerateSecretString: + SecretStringTemplate: '{}' + GenerateStringKey: jwtsigningkey + PasswordLength: 128 + ExcludePunctuation: true + ExcludeUppercase: true + ExcludeCharacters: 'ghijklmnopqrstuvwxyz' # we want to be generating a hex string [0-9a-f] + RequireEachIncludedType: false Outputs: ApiGatewayRestApiId: diff --git a/services/uploads/serverless.yml b/services/uploads/serverless.yml index 5964a08a7c..0a59aee6f2 100644 --- a/services/uploads/serverless.yml +++ b/services/uploads/serverless.yml @@ -60,7 +60,11 @@ custom: scripts: hooks: # This script is run locally when running 'serverless deploy' + package:initialize: | + set -e + curl -L --output lambda_layer.zip https://github.com/CMSgov/lambda-clamav-layer/releases/download/0.7/lambda_layer.zip deploy:finalize: | + rm lambda_layer.zip serverless invoke --stage ${sls:stage} --function avDownloadDefinitions -t Event serverless-offline-ssm: stages: @@ -92,7 +96,8 @@ custom: layers: clamAv: - path: lambda-layers-clamav + package: + artifact: lambda_layer.zip functions: avScan: diff --git a/services/uploads/src/avLayer/LICENSE b/services/uploads/src/avLayer/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/services/uploads/src/avLayer/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/services/uploads/src/avLayer/docker-compose.yml b/services/uploads/src/avLayer/docker-compose.yml deleted file mode 100644 index 51f2388b41..0000000000 --- a/services/uploads/src/avLayer/docker-compose.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '2' -services: - layer: - image: amazonlinux:latest - platform: linux/amd64 - working_dir: /opt/app - volumes: - - ./build:/opt/app - command: [./build.sh] diff --git a/yarn.lock b/yarn.lock index 66cef85390..ba24a20abe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30,10 +30,10 @@ dependencies: tunnel "^0.0.6" -"@adobe/css-tools@^4.3.0": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.2.tgz#a6abc715fb6884851fca9dad37fc34739a04fd11" - integrity sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw== +"@adobe/css-tools@^4.3.2": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.3.tgz#90749bde8b89cd41764224f5aac29cd4138f75ff" + integrity sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ== "@ampproject/remapping@^2.1.0": version "2.2.0" @@ -748,49 +748,49 @@ tslib "^1.8.0" "@aws-sdk/client-amplify@^3.485.0": - version "3.485.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-amplify/-/client-amplify-3.485.0.tgz#816f6c545c40660d71ec7fdabeaa9041cdf1f77a" - integrity sha512-HAslDyi5yNKl7VUF5DwCHa9rWAPVza/U45XP/oT6BAd16IOoMmDzTIGhG68Jc8+gzqAd1qkTai2Qq17KYtwJhw== + version "3.504.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-amplify/-/client-amplify-3.504.0.tgz#940ce3bb5d0cdb280ca224d838ac7cd3aa6171a5" + integrity sha512-YcYjmbEOGveGCPffv2zrQbDuapZwxnLYotjKn1dEaT9iGTuDz8uFujNfx4VRPNr/qqa6mgtn0fH+Px+4YmrLag== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.485.0" - "@aws-sdk/core" "3.485.0" - "@aws-sdk/credential-provider-node" "3.485.0" - "@aws-sdk/middleware-host-header" "3.485.0" - "@aws-sdk/middleware-logger" "3.485.0" - "@aws-sdk/middleware-recursion-detection" "3.485.0" - "@aws-sdk/middleware-signing" "3.485.0" - "@aws-sdk/middleware-user-agent" "3.485.0" - "@aws-sdk/region-config-resolver" "3.485.0" - "@aws-sdk/types" "3.485.0" - "@aws-sdk/util-endpoints" "3.485.0" - "@aws-sdk/util-user-agent-browser" "3.485.0" - "@aws-sdk/util-user-agent-node" "3.485.0" - "@smithy/config-resolver" "^2.0.23" - "@smithy/core" "^1.2.2" - "@smithy/fetch-http-handler" "^2.3.2" - "@smithy/hash-node" "^2.0.18" - "@smithy/invalid-dependency" "^2.0.16" - "@smithy/middleware-content-length" "^2.0.18" - "@smithy/middleware-endpoint" "^2.3.0" - "@smithy/middleware-retry" "^2.0.26" - "@smithy/middleware-serde" "^2.0.16" - "@smithy/middleware-stack" "^2.0.10" - "@smithy/node-config-provider" "^2.1.9" - "@smithy/node-http-handler" "^2.2.2" - "@smithy/protocol-http" "^3.0.12" - "@smithy/smithy-client" "^2.2.1" - "@smithy/types" "^2.8.0" - "@smithy/url-parser" "^2.0.16" - "@smithy/util-base64" "^2.0.1" - "@smithy/util-body-length-browser" "^2.0.1" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.24" - "@smithy/util-defaults-mode-node" "^2.0.32" - "@smithy/util-endpoints" "^1.0.8" - "@smithy/util-retry" "^2.0.9" - "@smithy/util-utf8" "^2.0.2" + "@aws-sdk/client-sts" "3.504.0" + "@aws-sdk/core" "3.496.0" + "@aws-sdk/credential-provider-node" "3.504.0" + "@aws-sdk/middleware-host-header" "3.502.0" + "@aws-sdk/middleware-logger" "3.502.0" + "@aws-sdk/middleware-recursion-detection" "3.502.0" + "@aws-sdk/middleware-signing" "3.502.0" + "@aws-sdk/middleware-user-agent" "3.502.0" + "@aws-sdk/region-config-resolver" "3.502.0" + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-endpoints" "3.502.0" + "@aws-sdk/util-user-agent-browser" "3.502.0" + "@aws-sdk/util-user-agent-node" "3.502.0" + "@smithy/config-resolver" "^2.1.1" + "@smithy/core" "^1.3.1" + "@smithy/fetch-http-handler" "^2.4.1" + "@smithy/hash-node" "^2.1.1" + "@smithy/invalid-dependency" "^2.1.1" + "@smithy/middleware-content-length" "^2.1.1" + "@smithy/middleware-endpoint" "^2.4.1" + "@smithy/middleware-retry" "^2.1.1" + "@smithy/middleware-serde" "^2.1.1" + "@smithy/middleware-stack" "^2.1.1" + "@smithy/node-config-provider" "^2.2.1" + "@smithy/node-http-handler" "^2.3.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/smithy-client" "^2.3.1" + "@smithy/types" "^2.9.1" + "@smithy/url-parser" "^2.1.1" + "@smithy/util-base64" "^2.1.1" + "@smithy/util-body-length-browser" "^2.1.1" + "@smithy/util-body-length-node" "^2.2.1" + "@smithy/util-defaults-mode-browser" "^2.1.1" + "@smithy/util-defaults-mode-node" "^2.1.1" + "@smithy/util-endpoints" "^1.1.1" + "@smithy/util-retry" "^2.1.1" + "@smithy/util-utf8" "^2.1.1" tslib "^2.5.0" "@aws-sdk/client-cloudformation@^3.410.0", "@aws-sdk/client-cloudformation@^3.485.0": @@ -1142,25 +1142,25 @@ tslib "^2.0.0" "@aws-sdk/client-lambda@^3.241.0", "@aws-sdk/client-lambda@^3.485.0", "@aws-sdk/client-lambda@^3.496.0": - version "3.496.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-lambda/-/client-lambda-3.496.0.tgz#66c7534377e8ff17284f9a69eb684497da9b153a" - integrity sha512-HsypBL5BIxK/2MPZ7mKsAypEn98Jws5kWKmOzVACOvboyvpgaDgrFqubNWkHPWlwg8vJBjixhrQpHxp1vzeVUw== + version "3.507.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-lambda/-/client-lambda-3.507.0.tgz#d9685ac32e68c9fd5580e118e53029c1329e2b6e" + integrity sha512-hJmG+CAuG87cNdxNE5LNimE6+nrfzOfRO2LEeS6WUnhvWLsw8YMHMYoKuQCvhdruP9CyBWklRfjxUp/n94ajuA== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.496.0" + "@aws-sdk/client-sts" "3.507.0" "@aws-sdk/core" "3.496.0" - "@aws-sdk/credential-provider-node" "3.496.0" - "@aws-sdk/middleware-host-header" "3.496.0" - "@aws-sdk/middleware-logger" "3.496.0" - "@aws-sdk/middleware-recursion-detection" "3.496.0" - "@aws-sdk/middleware-signing" "3.496.0" - "@aws-sdk/middleware-user-agent" "3.496.0" - "@aws-sdk/region-config-resolver" "3.496.0" - "@aws-sdk/types" "3.496.0" - "@aws-sdk/util-endpoints" "3.496.0" - "@aws-sdk/util-user-agent-browser" "3.496.0" - "@aws-sdk/util-user-agent-node" "3.496.0" + "@aws-sdk/credential-provider-node" "3.507.0" + "@aws-sdk/middleware-host-header" "3.502.0" + "@aws-sdk/middleware-logger" "3.502.0" + "@aws-sdk/middleware-recursion-detection" "3.502.0" + "@aws-sdk/middleware-signing" "3.502.0" + "@aws-sdk/middleware-user-agent" "3.502.0" + "@aws-sdk/region-config-resolver" "3.502.0" + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-endpoints" "3.502.0" + "@aws-sdk/util-user-agent-browser" "3.502.0" + "@aws-sdk/util-user-agent-node" "3.502.0" "@smithy/config-resolver" "^2.1.1" "@smithy/core" "^1.3.1" "@smithy/eventstream-serde-browser" "^2.1.1" @@ -1685,6 +1685,96 @@ tslib "^2.5.0" uuid "^8.3.2" +"@aws-sdk/client-sso-oidc@3.504.0": + version "3.504.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.504.0.tgz#78ed1e921fcda039e8525a92bf5760e8bd0091a8" + integrity sha512-ODA33/nm2srhV08EW0KZAP577UgV0qjyr7Xp2yEo8MXWL4ZqQZprk1c+QKBhjr4Djesrm0VPmSD/np0mtYP68A== + dependencies: + "@aws-crypto/sha256-browser" "3.0.0" + "@aws-crypto/sha256-js" "3.0.0" + "@aws-sdk/client-sts" "3.504.0" + "@aws-sdk/core" "3.496.0" + "@aws-sdk/middleware-host-header" "3.502.0" + "@aws-sdk/middleware-logger" "3.502.0" + "@aws-sdk/middleware-recursion-detection" "3.502.0" + "@aws-sdk/middleware-signing" "3.502.0" + "@aws-sdk/middleware-user-agent" "3.502.0" + "@aws-sdk/region-config-resolver" "3.502.0" + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-endpoints" "3.502.0" + "@aws-sdk/util-user-agent-browser" "3.502.0" + "@aws-sdk/util-user-agent-node" "3.502.0" + "@smithy/config-resolver" "^2.1.1" + "@smithy/core" "^1.3.1" + "@smithy/fetch-http-handler" "^2.4.1" + "@smithy/hash-node" "^2.1.1" + "@smithy/invalid-dependency" "^2.1.1" + "@smithy/middleware-content-length" "^2.1.1" + "@smithy/middleware-endpoint" "^2.4.1" + "@smithy/middleware-retry" "^2.1.1" + "@smithy/middleware-serde" "^2.1.1" + "@smithy/middleware-stack" "^2.1.1" + "@smithy/node-config-provider" "^2.2.1" + "@smithy/node-http-handler" "^2.3.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/smithy-client" "^2.3.1" + "@smithy/types" "^2.9.1" + "@smithy/url-parser" "^2.1.1" + "@smithy/util-base64" "^2.1.1" + "@smithy/util-body-length-browser" "^2.1.1" + "@smithy/util-body-length-node" "^2.2.1" + "@smithy/util-defaults-mode-browser" "^2.1.1" + "@smithy/util-defaults-mode-node" "^2.1.1" + "@smithy/util-endpoints" "^1.1.1" + "@smithy/util-retry" "^2.1.1" + "@smithy/util-utf8" "^2.1.1" + tslib "^2.5.0" + +"@aws-sdk/client-sso-oidc@3.507.0": + version "3.507.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.507.0.tgz#d1357d212d9510146d325ca1e6acd06d5744623b" + integrity sha512-ms5CH2ImhqqCIbo5irxayByuPOlVAmSiqDVfjZKwgIziqng2bVgNZMeKcT6t0bmrcgScEAVnZwY7j/iZTIw73g== + dependencies: + "@aws-crypto/sha256-browser" "3.0.0" + "@aws-crypto/sha256-js" "3.0.0" + "@aws-sdk/client-sts" "3.507.0" + "@aws-sdk/core" "3.496.0" + "@aws-sdk/middleware-host-header" "3.502.0" + "@aws-sdk/middleware-logger" "3.502.0" + "@aws-sdk/middleware-recursion-detection" "3.502.0" + "@aws-sdk/middleware-signing" "3.502.0" + "@aws-sdk/middleware-user-agent" "3.502.0" + "@aws-sdk/region-config-resolver" "3.502.0" + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-endpoints" "3.502.0" + "@aws-sdk/util-user-agent-browser" "3.502.0" + "@aws-sdk/util-user-agent-node" "3.502.0" + "@smithy/config-resolver" "^2.1.1" + "@smithy/core" "^1.3.1" + "@smithy/fetch-http-handler" "^2.4.1" + "@smithy/hash-node" "^2.1.1" + "@smithy/invalid-dependency" "^2.1.1" + "@smithy/middleware-content-length" "^2.1.1" + "@smithy/middleware-endpoint" "^2.4.1" + "@smithy/middleware-retry" "^2.1.1" + "@smithy/middleware-serde" "^2.1.1" + "@smithy/middleware-stack" "^2.1.1" + "@smithy/node-config-provider" "^2.2.1" + "@smithy/node-http-handler" "^2.3.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/smithy-client" "^2.3.1" + "@smithy/types" "^2.9.1" + "@smithy/url-parser" "^2.1.1" + "@smithy/util-base64" "^2.1.1" + "@smithy/util-body-length-browser" "^2.1.1" + "@smithy/util-body-length-node" "^2.2.1" + "@smithy/util-defaults-mode-browser" "^2.1.1" + "@smithy/util-defaults-mode-node" "^2.1.1" + "@smithy/util-endpoints" "^1.1.1" + "@smithy/util-retry" "^2.1.1" + "@smithy/util-utf8" "^2.1.1" + tslib "^2.5.0" + "@aws-sdk/client-sso@3.186.0": version "3.186.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.186.0.tgz#233bdd1312dbf88ef9452f8a62c3c3f1ac580330" @@ -1851,6 +1941,92 @@ "@smithy/util-utf8" "^2.1.1" tslib "^2.5.0" +"@aws-sdk/client-sso@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.502.0.tgz#8cf21d8f52a5bef65bf7b458051b3f61f7db822c" + integrity sha512-OZAYal1+PQgUUtWiHhRayDtX0OD+XpXHKAhjYgEIPbyhQaCMp3/Bq1xDX151piWXvXqXLJHFKb8DUEqzwGO9QA== + dependencies: + "@aws-crypto/sha256-browser" "3.0.0" + "@aws-crypto/sha256-js" "3.0.0" + "@aws-sdk/core" "3.496.0" + "@aws-sdk/middleware-host-header" "3.502.0" + "@aws-sdk/middleware-logger" "3.502.0" + "@aws-sdk/middleware-recursion-detection" "3.502.0" + "@aws-sdk/middleware-user-agent" "3.502.0" + "@aws-sdk/region-config-resolver" "3.502.0" + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-endpoints" "3.502.0" + "@aws-sdk/util-user-agent-browser" "3.502.0" + "@aws-sdk/util-user-agent-node" "3.502.0" + "@smithy/config-resolver" "^2.1.1" + "@smithy/core" "^1.3.1" + "@smithy/fetch-http-handler" "^2.4.1" + "@smithy/hash-node" "^2.1.1" + "@smithy/invalid-dependency" "^2.1.1" + "@smithy/middleware-content-length" "^2.1.1" + "@smithy/middleware-endpoint" "^2.4.1" + "@smithy/middleware-retry" "^2.1.1" + "@smithy/middleware-serde" "^2.1.1" + "@smithy/middleware-stack" "^2.1.1" + "@smithy/node-config-provider" "^2.2.1" + "@smithy/node-http-handler" "^2.3.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/smithy-client" "^2.3.1" + "@smithy/types" "^2.9.1" + "@smithy/url-parser" "^2.1.1" + "@smithy/util-base64" "^2.1.1" + "@smithy/util-body-length-browser" "^2.1.1" + "@smithy/util-body-length-node" "^2.2.1" + "@smithy/util-defaults-mode-browser" "^2.1.1" + "@smithy/util-defaults-mode-node" "^2.1.1" + "@smithy/util-endpoints" "^1.1.1" + "@smithy/util-retry" "^2.1.1" + "@smithy/util-utf8" "^2.1.1" + tslib "^2.5.0" + +"@aws-sdk/client-sso@3.507.0": + version "3.507.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.507.0.tgz#90a5de90f662aa680c0ffdc5e04695734ca8afb2" + integrity sha512-pFeaKwqv4tXD6QVxWC2V4N62DUoP3bPSm/mCe2SPhaNjNsmwwA53viUHz/nwxIbs8w4vV44UQsygb0AgKm+HoQ== + dependencies: + "@aws-crypto/sha256-browser" "3.0.0" + "@aws-crypto/sha256-js" "3.0.0" + "@aws-sdk/core" "3.496.0" + "@aws-sdk/middleware-host-header" "3.502.0" + "@aws-sdk/middleware-logger" "3.502.0" + "@aws-sdk/middleware-recursion-detection" "3.502.0" + "@aws-sdk/middleware-user-agent" "3.502.0" + "@aws-sdk/region-config-resolver" "3.502.0" + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-endpoints" "3.502.0" + "@aws-sdk/util-user-agent-browser" "3.502.0" + "@aws-sdk/util-user-agent-node" "3.502.0" + "@smithy/config-resolver" "^2.1.1" + "@smithy/core" "^1.3.1" + "@smithy/fetch-http-handler" "^2.4.1" + "@smithy/hash-node" "^2.1.1" + "@smithy/invalid-dependency" "^2.1.1" + "@smithy/middleware-content-length" "^2.1.1" + "@smithy/middleware-endpoint" "^2.4.1" + "@smithy/middleware-retry" "^2.1.1" + "@smithy/middleware-serde" "^2.1.1" + "@smithy/middleware-stack" "^2.1.1" + "@smithy/node-config-provider" "^2.2.1" + "@smithy/node-http-handler" "^2.3.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/smithy-client" "^2.3.1" + "@smithy/types" "^2.9.1" + "@smithy/url-parser" "^2.1.1" + "@smithy/util-base64" "^2.1.1" + "@smithy/util-body-length-browser" "^2.1.1" + "@smithy/util-body-length-node" "^2.2.1" + "@smithy/util-defaults-mode-browser" "^2.1.1" + "@smithy/util-defaults-mode-node" "^2.1.1" + "@smithy/util-endpoints" "^1.1.1" + "@smithy/util-retry" "^2.1.1" + "@smithy/util-utf8" "^2.1.1" + tslib "^2.5.0" + "@aws-sdk/client-sts@3.186.3": version "3.186.3" resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.186.3.tgz#1c12355cb9d3cadc64ab74c91c3d57515680dfbd" @@ -2031,7 +2207,7 @@ fast-xml-parser "4.2.5" tslib "^2.5.0" -"@aws-sdk/client-sts@3.499.0", "@aws-sdk/client-sts@^3.410.0": +"@aws-sdk/client-sts@3.499.0": version "3.499.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.499.0.tgz#4c8260ed1fda7ad2c4e5fe12e4eaa5849da77d92" integrity sha512-Eyj9STw2DXMtXL5V/v0HYHO6+JjGPi257M5IYyxwqlvRchq6jbOsedobfxclB/gBUyBRtZdnyAIS8uCKjb4kpA== @@ -2077,6 +2253,96 @@ fast-xml-parser "4.2.5" tslib "^2.5.0" +"@aws-sdk/client-sts@3.504.0": + version "3.504.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz#78a0beaf988ad2647d79c7157083dfd55953f41e" + integrity sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA== + dependencies: + "@aws-crypto/sha256-browser" "3.0.0" + "@aws-crypto/sha256-js" "3.0.0" + "@aws-sdk/core" "3.496.0" + "@aws-sdk/middleware-host-header" "3.502.0" + "@aws-sdk/middleware-logger" "3.502.0" + "@aws-sdk/middleware-recursion-detection" "3.502.0" + "@aws-sdk/middleware-user-agent" "3.502.0" + "@aws-sdk/region-config-resolver" "3.502.0" + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-endpoints" "3.502.0" + "@aws-sdk/util-user-agent-browser" "3.502.0" + "@aws-sdk/util-user-agent-node" "3.502.0" + "@smithy/config-resolver" "^2.1.1" + "@smithy/core" "^1.3.1" + "@smithy/fetch-http-handler" "^2.4.1" + "@smithy/hash-node" "^2.1.1" + "@smithy/invalid-dependency" "^2.1.1" + "@smithy/middleware-content-length" "^2.1.1" + "@smithy/middleware-endpoint" "^2.4.1" + "@smithy/middleware-retry" "^2.1.1" + "@smithy/middleware-serde" "^2.1.1" + "@smithy/middleware-stack" "^2.1.1" + "@smithy/node-config-provider" "^2.2.1" + "@smithy/node-http-handler" "^2.3.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/smithy-client" "^2.3.1" + "@smithy/types" "^2.9.1" + "@smithy/url-parser" "^2.1.1" + "@smithy/util-base64" "^2.1.1" + "@smithy/util-body-length-browser" "^2.1.1" + "@smithy/util-body-length-node" "^2.2.1" + "@smithy/util-defaults-mode-browser" "^2.1.1" + "@smithy/util-defaults-mode-node" "^2.1.1" + "@smithy/util-endpoints" "^1.1.1" + "@smithy/util-middleware" "^2.1.1" + "@smithy/util-retry" "^2.1.1" + "@smithy/util-utf8" "^2.1.1" + fast-xml-parser "4.2.5" + tslib "^2.5.0" + +"@aws-sdk/client-sts@3.507.0", "@aws-sdk/client-sts@^3.410.0": + version "3.507.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.507.0.tgz#0a99b5b04ca8d2e30a52840cc67181b3f2ac990a" + integrity sha512-TOWBe0ApEh32QOib0R+irWGjd1F9wnhbGV5PcB9SakyRwvqwG5MKOfYxG7ocoDqLlaRwzZMidcy/PV8/OEVNKg== + dependencies: + "@aws-crypto/sha256-browser" "3.0.0" + "@aws-crypto/sha256-js" "3.0.0" + "@aws-sdk/core" "3.496.0" + "@aws-sdk/middleware-host-header" "3.502.0" + "@aws-sdk/middleware-logger" "3.502.0" + "@aws-sdk/middleware-recursion-detection" "3.502.0" + "@aws-sdk/middleware-user-agent" "3.502.0" + "@aws-sdk/region-config-resolver" "3.502.0" + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-endpoints" "3.502.0" + "@aws-sdk/util-user-agent-browser" "3.502.0" + "@aws-sdk/util-user-agent-node" "3.502.0" + "@smithy/config-resolver" "^2.1.1" + "@smithy/core" "^1.3.1" + "@smithy/fetch-http-handler" "^2.4.1" + "@smithy/hash-node" "^2.1.1" + "@smithy/invalid-dependency" "^2.1.1" + "@smithy/middleware-content-length" "^2.1.1" + "@smithy/middleware-endpoint" "^2.4.1" + "@smithy/middleware-retry" "^2.1.1" + "@smithy/middleware-serde" "^2.1.1" + "@smithy/middleware-stack" "^2.1.1" + "@smithy/node-config-provider" "^2.2.1" + "@smithy/node-http-handler" "^2.3.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/smithy-client" "^2.3.1" + "@smithy/types" "^2.9.1" + "@smithy/url-parser" "^2.1.1" + "@smithy/util-base64" "^2.1.1" + "@smithy/util-body-length-browser" "^2.1.1" + "@smithy/util-body-length-node" "^2.2.1" + "@smithy/util-defaults-mode-browser" "^2.1.1" + "@smithy/util-defaults-mode-node" "^2.1.1" + "@smithy/util-endpoints" "^1.1.1" + "@smithy/util-middleware" "^2.1.1" + "@smithy/util-retry" "^2.1.1" + "@smithy/util-utf8" "^2.1.1" + fast-xml-parser "4.2.5" + tslib "^2.5.0" + "@aws-sdk/client-textract@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/client-textract/-/client-textract-3.6.1.tgz#b8972f53f0353222b4c052adc784291e602be6aa" @@ -2247,6 +2513,16 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/credential-provider-env@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.502.0.tgz#800e63b2b9d90b078a120d474d5a3b1ec5b48514" + integrity sha512-KIB8Ae1Z7domMU/jU4KiIgK4tmYgvuXlhR54ehwlVHxnEoFPoPuGHFZU7oFn79jhhSLUFQ1lRYMxP0cEwb7XeQ== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/property-provider" "^2.1.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/credential-provider-env@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.6.1.tgz#d8b2dd36836432a9b8ec05a5cf9fe428b04c9964" @@ -2256,6 +2532,21 @@ "@aws-sdk/types" "3.6.1" tslib "^1.8.0" +"@aws-sdk/credential-provider-http@3.503.1": + version "3.503.1" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.503.1.tgz#e882a4b740c9193650053033b3001b03ca4b12c8" + integrity sha512-rTdlFFGoPPFMF2YjtlfRuSgKI+XsF49u7d98255hySwhsbwd3Xp+utTTPquxP+CwDxMHbDlI7NxDzFiFdsoZug== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/fetch-http-handler" "^2.4.1" + "@smithy/node-http-handler" "^2.3.1" + "@smithy/property-provider" "^2.1.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/smithy-client" "^2.3.1" + "@smithy/types" "^2.9.1" + "@smithy/util-stream" "^2.1.1" + tslib "^2.5.0" + "@aws-sdk/credential-provider-imds@3.186.0": version "3.186.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.186.0.tgz#73e0f62832726c7734b4f6c50a02ab0d869c00e1" @@ -2338,6 +2629,40 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/credential-provider-ini@3.504.0": + version "3.504.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.504.0.tgz#d463dae3a309c1e9181811f27c484fd9bfa6821f" + integrity sha512-ODICLXfr8xTUd3wweprH32Ge41yuBa+u3j0JUcLdTUO1N9ldczSMdo8zOPlP0z4doqD3xbnqMkjNQWgN/Q+5oQ== + dependencies: + "@aws-sdk/client-sts" "3.504.0" + "@aws-sdk/credential-provider-env" "3.502.0" + "@aws-sdk/credential-provider-process" "3.502.0" + "@aws-sdk/credential-provider-sso" "3.504.0" + "@aws-sdk/credential-provider-web-identity" "3.504.0" + "@aws-sdk/types" "3.502.0" + "@smithy/credential-provider-imds" "^2.2.1" + "@smithy/property-provider" "^2.1.1" + "@smithy/shared-ini-file-loader" "^2.3.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + +"@aws-sdk/credential-provider-ini@3.507.0": + version "3.507.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.507.0.tgz#c2b9cd1bf172a0057bf0ad888c19ce5450df13f2" + integrity sha512-2CnyduoR9COgd7qH1LPYK8UggGqVs8R4ASDMB5bwGxbg9ZerlStDiHpqvJNNg1k+VlejBr++utxfmHd236XgmQ== + dependencies: + "@aws-sdk/client-sts" "3.507.0" + "@aws-sdk/credential-provider-env" "3.502.0" + "@aws-sdk/credential-provider-process" "3.502.0" + "@aws-sdk/credential-provider-sso" "3.507.0" + "@aws-sdk/credential-provider-web-identity" "3.507.0" + "@aws-sdk/types" "3.502.0" + "@smithy/credential-provider-imds" "^2.2.1" + "@smithy/property-provider" "^2.1.1" + "@smithy/shared-ini-file-loader" "^2.3.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/credential-provider-ini@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.6.1.tgz#0da6d9341e621f8e0815814ed017b88e268fbc3d" @@ -2432,6 +2757,42 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/credential-provider-node@3.504.0": + version "3.504.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.504.0.tgz#c6014f56dd59be295114290164e00375d33f2952" + integrity sha512-6+V5hIh+tILmUjf2ZQWQINR3atxQVgH/bFrGdSR/sHSp/tEgw3m0xWL3IRslWU1e4/GtXrfg1iYnMknXy68Ikw== + dependencies: + "@aws-sdk/credential-provider-env" "3.502.0" + "@aws-sdk/credential-provider-http" "3.503.1" + "@aws-sdk/credential-provider-ini" "3.504.0" + "@aws-sdk/credential-provider-process" "3.502.0" + "@aws-sdk/credential-provider-sso" "3.504.0" + "@aws-sdk/credential-provider-web-identity" "3.504.0" + "@aws-sdk/types" "3.502.0" + "@smithy/credential-provider-imds" "^2.2.1" + "@smithy/property-provider" "^2.1.1" + "@smithy/shared-ini-file-loader" "^2.3.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + +"@aws-sdk/credential-provider-node@3.507.0": + version "3.507.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.507.0.tgz#b6c9f3c2c8294911c4f12e267f16a26e1eba4813" + integrity sha512-tkQnmOLkRBXfMLgDYHzogrqTNdtl0Im0ipzJb2IV5hfM5NoTfCf795e9A9isgwjSP/g/YEU0xQWxa4lq8LRtuA== + dependencies: + "@aws-sdk/credential-provider-env" "3.502.0" + "@aws-sdk/credential-provider-http" "3.503.1" + "@aws-sdk/credential-provider-ini" "3.507.0" + "@aws-sdk/credential-provider-process" "3.502.0" + "@aws-sdk/credential-provider-sso" "3.507.0" + "@aws-sdk/credential-provider-web-identity" "3.507.0" + "@aws-sdk/types" "3.502.0" + "@smithy/credential-provider-imds" "^2.2.1" + "@smithy/property-provider" "^2.1.1" + "@smithy/shared-ini-file-loader" "^2.3.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/credential-provider-node@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.6.1.tgz#0055292a4f0f49d053e8dfcc9174d8d2cf6862bb" @@ -2489,6 +2850,17 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/credential-provider-process@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.502.0.tgz#6c41d8845a1c7073491a064c158363de04640381" + integrity sha512-fJJowOjQ4infYQX0E1J3xFVlmuwEYJAFk0Mo1qwafWmEthsBJs+6BR2RiWDELHKrSK35u4Pf3fu3RkYuCtmQFw== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/property-provider" "^2.1.1" + "@smithy/shared-ini-file-loader" "^2.3.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/credential-provider-process@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.6.1.tgz#5bf851f3ee232c565b8c82608926df0ad28c1958" @@ -2550,6 +2922,32 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/credential-provider-sso@3.504.0": + version "3.504.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.504.0.tgz#ad036805e8677f6a53b24aa82991596aa11ac605" + integrity sha512-4MgH2or2SjPzaxM08DCW+BjaX4DSsEGJlicHKmz6fh+w9JmLh750oXcTnbvgUeVz075jcs6qTKjvUcsdGM/t8Q== + dependencies: + "@aws-sdk/client-sso" "3.502.0" + "@aws-sdk/token-providers" "3.504.0" + "@aws-sdk/types" "3.502.0" + "@smithy/property-provider" "^2.1.1" + "@smithy/shared-ini-file-loader" "^2.3.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + +"@aws-sdk/credential-provider-sso@3.507.0": + version "3.507.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.507.0.tgz#e98cf7fad69b4c12aa85c44affe9aae4cc81d796" + integrity sha512-6WBjou52QukFpDi4ezb19bcAx/bM8ge8qnJnRT02WVRmU6zFQ5yLD2fW1MFsbX3cwbey+wSqKd5FGE1Hukd5wQ== + dependencies: + "@aws-sdk/client-sso" "3.507.0" + "@aws-sdk/token-providers" "3.507.0" + "@aws-sdk/types" "3.502.0" + "@smithy/property-provider" "^2.1.1" + "@smithy/shared-ini-file-loader" "^2.3.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/credential-provider-web-identity@3.186.0": version "3.186.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.186.0.tgz#db43f37f7827b553490dd865dbaa9a2c45f95494" @@ -2589,6 +2987,28 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/credential-provider-web-identity@3.504.0": + version "3.504.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.504.0.tgz#53de7dee538ecaeec534e369bca76c546b8f4cc5" + integrity sha512-L1ljCvGpIEFdJk087ijf2ohg7HBclOeB1UgBxUBBzf4iPRZTQzd2chGaKj0hm2VVaXz7nglswJeURH5PFcS5oA== + dependencies: + "@aws-sdk/client-sts" "3.504.0" + "@aws-sdk/types" "3.502.0" + "@smithy/property-provider" "^2.1.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + +"@aws-sdk/credential-provider-web-identity@3.507.0": + version "3.507.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.507.0.tgz#22e028e2dd2a0a927707da1408099bc4f5b7a606" + integrity sha512-f+aGMfazBimX7S06224JRYzGTaMh1uIhfj23tZylPJ05KxTVi5IO1RoqeI/uHLJ+bDOx+JHBC04g/oCdO4kHvw== + dependencies: + "@aws-sdk/client-sts" "3.507.0" + "@aws-sdk/types" "3.502.0" + "@smithy/property-provider" "^2.1.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/eventstream-codec@3.186.0": version "3.186.0" resolved "https://registry.yarnpkg.com/@aws-sdk/eventstream-codec/-/eventstream-codec-3.186.0.tgz#9da9608866b38179edf72987f2bc3b865d11db13" @@ -2885,6 +3305,16 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/middleware-host-header@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.502.0.tgz#2651fb3509990271c89eb50133fb17cb8ae435f6" + integrity sha512-EjnG0GTYXT/wJBmm5/mTjDcAkzU8L7wQjOzd3FTXuTCNNyvAvwrszbOj5FlarEw5XJBbQiZtBs+I5u9+zy560w== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/protocol-http" "^3.1.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/middleware-host-header@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.6.1.tgz#6e1b4b95c5bfea5a4416fa32f11d8fa2e6edaeff" @@ -2938,6 +3368,15 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/middleware-logger@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.502.0.tgz#558cefdd233779f15687957f9f07497199b22d72" + integrity sha512-FDyv6K4nCoHxbjLGS2H8ex8I0KDIiu4FJgVRPs140ZJy6gE5Pwxzv6YTzZGLMrnqcIs9gh065Lf6DjwMelZqaw== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/middleware-logger@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.6.1.tgz#78b3732cf188d5e4df13488db6418f7f98a77d6d" @@ -2985,6 +3424,16 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/middleware-recursion-detection@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.502.0.tgz#c22e2c0c1d551e58c788264687324bb7186af2cc" + integrity sha512-hvbyGJbxeuezxOu8VfFmcV4ql1hKXLxHTe5FNYfEBat2KaZXVhc1Hg+4TvB06/53p+E8J99Afmumkqbxs2esUA== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/protocol-http" "^3.1.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/middleware-retry@3.186.0": version "3.186.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.186.0.tgz#0ff9af58d73855863683991a809b40b93c753ad1" @@ -3036,21 +3485,6 @@ "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/middleware-sdk-s3@3.485.0": - version "3.485.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.485.0.tgz#89e48e32869afb3568cec40558c9cf04d4b72544" - integrity sha512-3769c4e3UtvaNU5T6dHxhjGI1kEXymldqiP1PMZMX2jVffwSGhbvyLq0Kl6+9Jr51fj2oXN6Tex+8J9+5dzTgQ== - dependencies: - "@aws-sdk/types" "3.485.0" - "@aws-sdk/util-arn-parser" "3.465.0" - "@smithy/node-config-provider" "^2.1.9" - "@smithy/protocol-http" "^3.0.12" - "@smithy/signature-v4" "^2.0.0" - "@smithy/smithy-client" "^2.2.1" - "@smithy/types" "^2.8.0" - "@smithy/util-config-provider" "^2.1.0" - tslib "^2.5.0" - "@aws-sdk/middleware-sdk-s3@3.496.0": version "3.496.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.496.0.tgz#8d15cd44578da34159d99282b5de734a0f50db7c" @@ -3066,6 +3500,21 @@ "@smithy/util-config-provider" "^2.2.1" tslib "^2.5.0" +"@aws-sdk/middleware-sdk-s3@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.502.0.tgz#a2d968414247fd9cbfc90e1071f29e4375cb25b8" + integrity sha512-GbGugrfyL5bNA/zw8iQll92yXBONfWSC8Ns00DtkOU1saPXp4/7WHtyyZGYdvPa73T1IsuZy9egpoYRBmRcd5Q== + dependencies: + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-arn-parser" "3.495.0" + "@smithy/node-config-provider" "^2.2.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/signature-v4" "^2.1.1" + "@smithy/smithy-client" "^2.3.1" + "@smithy/types" "^2.9.1" + "@smithy/util-config-provider" "^2.2.1" + tslib "^2.5.0" + "@aws-sdk/middleware-sdk-sts@3.186.0": version "3.186.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.186.0.tgz#18f3d6b7b42c1345b5733ac3e3119d370a403e94" @@ -3132,6 +3581,19 @@ "@smithy/util-middleware" "^2.1.1" tslib "^2.5.0" +"@aws-sdk/middleware-signing@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.502.0.tgz#48b3503147eecb1a53a63633462de353668f635a" + integrity sha512-4hF08vSzJ7L6sB+393gOFj3s2N6nLusYS0XrMW6wYNFU10IDdbf8Z3TZ7gysDJJHEGQPmTAesPEDBsasGWcMxg== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/property-provider" "^2.1.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/signature-v4" "^2.1.1" + "@smithy/types" "^2.9.1" + "@smithy/util-middleware" "^2.1.1" + tslib "^2.5.0" + "@aws-sdk/middleware-signing@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.6.1.tgz#e70a2f35d85d70e33c9fddfb54b9520f6382db16" @@ -3207,6 +3669,17 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/middleware-user-agent@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.502.0.tgz#dd740f150d6f3110cf5b08fedf361d202f899c93" + integrity sha512-TxbBZbRiXPH0AUxegqiNd9aM9zNSbfjtBs5MEfcBsweeT/B2O7K1EjP9+CkB8Xmk/5FLKhAKLr19b1TNoE27rw== + dependencies: + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-endpoints" "3.502.0" + "@smithy/protocol-http" "^3.1.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/middleware-user-agent@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.6.1.tgz#6845dfb3bc6187897f348c2c87dec833e6a65c99" @@ -3359,18 +3832,30 @@ "@smithy/util-middleware" "^2.1.1" tslib "^2.5.0" +"@aws-sdk/region-config-resolver@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.502.0.tgz#c18a04060879eb03c47c05b05fc296119ee073ba" + integrity sha512-mxmsX2AGgnSM+Sah7mcQCIneOsJQNiLX0COwEttuf8eO+6cLMAZvVudH3BnWTfea4/A9nuri9DLCqBvEmPrilg== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/node-config-provider" "^2.2.1" + "@smithy/types" "^2.9.1" + "@smithy/util-config-provider" "^2.2.1" + "@smithy/util-middleware" "^2.1.1" + tslib "^2.5.0" + "@aws-sdk/s3-request-presigner@^3.485.0": - version "3.485.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.485.0.tgz#c0ae7d1e241318de24307f0b84e01cdf5d0c8a97" - integrity sha512-5TCyl1H/PdBH0XDSILb9y1d/fU+tDEQ7Fkqeb2gIYENDG09dX68TtcZVGs0sMZtC9CLUFpmEp8R/3LtfuoeY6w== + version "3.504.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.504.0.tgz#4288f6381c1a63fae417754eae4f0652cf0a51b5" + integrity sha512-5FxVdRufiFLSUDJ/Qul5JFPHjhFFzo+C6u53bzbi7gaSshA6lLLhJ9KbVk2LmKE1mTR+nh2+JebI6y+3njtkzw== dependencies: - "@aws-sdk/signature-v4-multi-region" "3.485.0" - "@aws-sdk/types" "3.485.0" - "@aws-sdk/util-format-url" "3.485.0" - "@smithy/middleware-endpoint" "^2.3.0" - "@smithy/protocol-http" "^3.0.12" - "@smithy/smithy-client" "^2.2.1" - "@smithy/types" "^2.8.0" + "@aws-sdk/signature-v4-multi-region" "3.502.0" + "@aws-sdk/types" "3.502.0" + "@aws-sdk/util-format-url" "3.502.0" + "@smithy/middleware-endpoint" "^2.4.1" + "@smithy/protocol-http" "^3.1.1" + "@smithy/smithy-client" "^2.3.1" + "@smithy/types" "^2.9.1" tslib "^2.5.0" "@aws-sdk/service-error-classification@3.186.0": @@ -3398,18 +3883,6 @@ dependencies: tslib "^1.8.0" -"@aws-sdk/signature-v4-multi-region@3.485.0": - version "3.485.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.485.0.tgz#1a32f61dc205e9b3b4b590aeccb19743483f3de7" - integrity sha512-168ipXkbG75l9cKQmsBtx/4+AYjGsBoy724bXosW13t2/l/E3IzJAYUjDROiK0JXVMG85xAnGWbFwZkjxVXzrQ== - dependencies: - "@aws-sdk/middleware-sdk-s3" "3.485.0" - "@aws-sdk/types" "3.485.0" - "@smithy/protocol-http" "^3.0.12" - "@smithy/signature-v4" "^2.0.0" - "@smithy/types" "^2.8.0" - tslib "^2.5.0" - "@aws-sdk/signature-v4-multi-region@3.496.0": version "3.496.0" resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.496.0.tgz#0084ad38ab25dc50d5965d31a9c659673d82e86f" @@ -3422,6 +3895,18 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/signature-v4-multi-region@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.502.0.tgz#2d3fab86051eb98a4e4216e0f2f3d957a854b42c" + integrity sha512-NpOXtUXH0ZAgnyI3Y3s2fPrgwbsWoNMwdoXdFZvH0eDzzX80tim7Yuy6dzVA5zrxSzOYs1xjcOhM+4CmM0QZiw== + dependencies: + "@aws-sdk/middleware-sdk-s3" "3.502.0" + "@aws-sdk/types" "3.502.0" + "@smithy/protocol-http" "^3.1.1" + "@smithy/signature-v4" "^2.1.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/signature-v4@3.186.0": version "3.186.0" resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.186.0.tgz#bbd56e71af95548abaeec6307ea1dfe7bd26b4e4" @@ -3592,6 +4077,30 @@ "@smithy/util-utf8" "^2.1.1" tslib "^2.5.0" +"@aws-sdk/token-providers@3.504.0": + version "3.504.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.504.0.tgz#f7f60b1152458e7094529ea3f3ced6ce92eece9f" + integrity sha512-YIJWWsZi2ClUiILS1uh5L6VjmCUSTI6KKMuL9DkGjYqJ0aI6M8bd8fT9Wm7QmXCyjcArTgr/Atkhia4T7oKvzQ== + dependencies: + "@aws-sdk/client-sso-oidc" "3.504.0" + "@aws-sdk/types" "3.502.0" + "@smithy/property-provider" "^2.1.1" + "@smithy/shared-ini-file-loader" "^2.3.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + +"@aws-sdk/token-providers@3.507.0": + version "3.507.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.507.0.tgz#7456cec822a7f59a4b58a2eda1a0ff963c4c3c6b" + integrity sha512-ehOINGjoGJc6Puzon7ev4bXckkaZx18WNgMTNttYJhj3vTpj5LPSQbI/5SS927bEbpGMFz1+hJ6Ra5WGfbTcEQ== + dependencies: + "@aws-sdk/client-sso-oidc" "3.507.0" + "@aws-sdk/types" "3.502.0" + "@smithy/property-provider" "^2.1.1" + "@smithy/shared-ini-file-loader" "^2.3.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/types@3.186.0": version "3.186.0" resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.186.0.tgz#f6fb6997b6a364f399288bfd5cd494bc680ac922" @@ -3613,7 +4122,7 @@ "@smithy/types" "^2.8.0" tslib "^2.5.0" -"@aws-sdk/types@3.496.0", "@aws-sdk/types@^3.1.0", "@aws-sdk/types@^3.222.0": +"@aws-sdk/types@3.496.0": version "3.496.0" resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.496.0.tgz#cdde44a94a57cf8f97cf05e4d0bdce2f56ce4eeb" integrity sha512-umkGadK4QuNQaMoDICMm7NKRI/mYSXiyPjcn3d53BhsuArYU/52CebGQKdt4At7SwwsiVJZw9RNBHyN5Mm0HVw== @@ -3621,6 +4130,14 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/types@3.502.0", "@aws-sdk/types@^3.1.0", "@aws-sdk/types@^3.222.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.502.0.tgz#c23dda4df7fdbe32642d4f5ab23516f455fb6aba" + integrity sha512-M0DSPYe/gXhwD2QHgoukaZv5oDxhW3FfvYIrJptyqUq3OnPJBcDbihHjrE0PBtfh/9kgMZT60/fQ2NVFANfa2g== + dependencies: + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/types@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.6.1.tgz#00686db69e998b521fcd4a5f81ef0960980f80c4" @@ -3654,13 +4171,6 @@ "@aws-sdk/types" "3.6.1" tslib "^1.8.0" -"@aws-sdk/util-arn-parser@3.465.0": - version "3.465.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.465.0.tgz#2896f6b06f69770378586853c97a0f283cbb2e20" - integrity sha512-zOJ82vzDJFqBX9yZBlNeHHrul/kpx/DCoxzW5UBbZeb26kfV53QhMSoEmY8/lEbBqlqargJ/sgRC845GFhHNQw== - dependencies: - tslib "^2.5.0" - "@aws-sdk/util-arn-parser@3.495.0": version "3.495.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.495.0.tgz#539f2d6dfef343a80324348f1f9a1b7eed2390f3" @@ -3800,6 +4310,16 @@ "@smithy/util-endpoints" "^1.1.1" tslib "^2.5.0" +"@aws-sdk/util-endpoints@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.502.0.tgz#aee818c0c53dfedfd49599fc260cd880faea5e82" + integrity sha512-6LKFlJPp2J24r1Kpfoz5ESQn+1v5fEjDB3mtUKRdpwarhm3syu7HbKlHCF3KbcCOyahobvLvhoedT78rJFEeeg== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/types" "^2.9.1" + "@smithy/util-endpoints" "^1.1.1" + tslib "^2.5.0" + "@aws-sdk/util-format-url@3.485.0": version "3.485.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-format-url/-/util-format-url-3.485.0.tgz#eab01bf10153aa6cce0c6edfb1e48c5088e5993c" @@ -3820,6 +4340,16 @@ "@smithy/types" "^2.8.0" tslib "^2.5.0" +"@aws-sdk/util-format-url@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-format-url/-/util-format-url-3.502.0.tgz#3356d2d38b1342f81117bebe630ae378840366cc" + integrity sha512-4+0zBD0ZIJqtTzSE6VRruRwUx3lG+is8Egv+LN99X5y7i6OdrS9ePYHbCJ9FxkzTThgbkUq6k2W7psEDYvn4VA== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/querystring-builder" "^2.1.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/util-hex-encoding@3.186.0": version "3.186.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.186.0.tgz#7ed58b923997c6265f4dce60c8704237edb98895" @@ -3901,6 +4431,16 @@ bowser "^2.11.0" tslib "^2.5.0" +"@aws-sdk/util-user-agent-browser@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.502.0.tgz#87b42abff6944052c78a84981637ac21859dd016" + integrity sha512-v8gKyCs2obXoIkLETAeEQ3AM+QmhHhst9xbM1cJtKUGsRlVIak/XyyD+kVE6kmMm1cjfudHpHKABWk9apQcIZQ== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/types" "^2.9.1" + bowser "^2.11.0" + tslib "^2.5.0" + "@aws-sdk/util-user-agent-browser@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.6.1.tgz#11b9cc8743392761adb304460f4b54ec8acc2ee6" @@ -3949,6 +4489,16 @@ "@smithy/types" "^2.9.1" tslib "^2.5.0" +"@aws-sdk/util-user-agent-node@3.502.0": + version "3.502.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.502.0.tgz#04ac4d0371d4f243f12ddc23b42ca8ceb27dfad9" + integrity sha512-9RjxpkGZKbTdl96tIJvAo+vZoz4P/cQh36SBUt9xfRfW0BtsaLyvSrvlR5wyUYhvRcC12Axqh/8JtnAPq//+Vw== + dependencies: + "@aws-sdk/types" "3.502.0" + "@smithy/node-config-provider" "^2.2.1" + "@smithy/types" "^2.9.1" + tslib "^2.5.0" + "@aws-sdk/util-user-agent-node@3.6.1": version "3.6.1" resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.6.1.tgz#98384095fa67d098ae7dd26f3ccaad028e8aebb6" @@ -11349,16 +11899,16 @@ pretty-format "^27.0.2" "@testing-library/jest-dom@^6.1.3": - version "6.1.3" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.1.3.tgz#443118c9e4043f96396f120de2c7122504a079c5" - integrity sha512-YzpjRHoCBWPzpPNtg6gnhasqtE/5O4qz8WCwDEaxtfnPO6gkaLrnuXusrGSPyhIGPezr1HM7ZH0CFaUTY9PJEQ== + version "6.4.2" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.2.tgz#38949f6b63722900e2d75ba3c6d9bf8cffb3300e" + integrity sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw== dependencies: - "@adobe/css-tools" "^4.3.0" + "@adobe/css-tools" "^4.3.2" "@babel/runtime" "^7.9.2" aria-query "^5.0.0" chalk "^3.0.0" css.escape "^1.5.1" - dom-accessibility-api "^0.5.6" + dom-accessibility-api "^0.6.3" lodash "^4.17.15" redent "^3.0.0" @@ -11873,9 +12423,9 @@ integrity sha512-Abq9fBviLV93OiXMu+f6r0elxCzRwc0RC5f99cU892uBITL44pTvgvEqlRlPRi8EGcO1z7Cp8A4d0s/p3J/+Nw== "@types/node@^16.18.39": - version "16.18.75" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.75.tgz#88460b2706e5be1788f5ed6ef51152283b7703a2" - integrity sha512-+FSfZd5mpMDTcIK7bp2GueIcAespzR4FROOXnEst248c85vwthIEwtXYOLgVc/sI4ihE1K/7yO1lEiSgvwAOxA== + version "16.18.79" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.79.tgz#153e25561b271cf87dc1b28d38f98cebd514d788" + integrity sha512-Qd7jdLR5zmnIyMhfDrfPqN5tUCvreVpP3Qrf2oSM+F7SNzlb/MwHISGUkdFHtevfkPJ3iAGyeQI/jsbh9EStgQ== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -12222,14 +12772,14 @@ debug "^4.3.4" "@typescript-eslint/parser@^6.5.0": - version "6.19.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.19.1.tgz#68a87bb21afaf0b1689e9cdce0e6e75bc91ada78" - integrity sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ== - dependencies: - "@typescript-eslint/scope-manager" "6.19.1" - "@typescript-eslint/types" "6.19.1" - "@typescript-eslint/typescript-estree" "6.19.1" - "@typescript-eslint/visitor-keys" "6.19.1" + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== + dependencies: + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" debug "^4.3.4" "@typescript-eslint/scope-manager@5.33.1": @@ -12256,13 +12806,13 @@ "@typescript-eslint/types" "5.58.0" "@typescript-eslint/visitor-keys" "5.58.0" -"@typescript-eslint/scope-manager@6.19.1": - version "6.19.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.19.1.tgz#2f527ee30703a6169a52b31d42a1103d80acd51b" - integrity sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w== +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== dependencies: - "@typescript-eslint/types" "6.19.1" - "@typescript-eslint/visitor-keys" "6.19.1" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" "@typescript-eslint/type-utils@5.57.0": version "5.57.0" @@ -12289,10 +12839,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.58.0.tgz#54c490b8522c18986004df7674c644ffe2ed77d8" integrity sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g== -"@typescript-eslint/types@6.19.1": - version "6.19.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.19.1.tgz#2d4c9d492a63ede15e7ba7d129bdf7714b77f771" - integrity sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg== +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== "@typescript-eslint/typescript-estree@5.33.1": version "5.33.1" @@ -12333,13 +12883,13 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@6.19.1": - version "6.19.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.1.tgz#796d88d88882f12e85bb33d6d82d39e1aea54ed1" - integrity sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA== +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== dependencies: - "@typescript-eslint/types" "6.19.1" - "@typescript-eslint/visitor-keys" "6.19.1" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -12397,12 +12947,12 @@ "@typescript-eslint/types" "5.58.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@6.19.1": - version "6.19.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.1.tgz#2164073ed4fc34a5ff3b5e25bb5a442100454c4c" - integrity sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ== +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== dependencies: - "@typescript-eslint/types" "6.19.1" + "@typescript-eslint/types" "6.21.0" eslint-visitor-keys "^3.4.1" "@vendia/serverless-express@^4.3.9": @@ -13754,12 +14304,12 @@ axios@0.26.0: dependencies: follow-redirects "^1.14.8" -axios@^1.0.0, axios@^1.1.3, axios@^1.6.0, axios@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" - integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== +axios@^1.0.0, axios@^1.1.3, axios@^1.6.2, axios@^1.6.5: + version "1.6.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" + integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== dependencies: - follow-redirects "^1.15.0" + follow-redirects "^1.15.4" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -15055,13 +15605,13 @@ chrome-trace-event@^1.0.2: resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -chromedriver@^120.0.1: - version "120.0.1" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-120.0.1.tgz#71604e46a463ab133c3b8b7269dba8ac8fd82b43" - integrity sha512-ETTJlkibcAmvoKsaEoq2TFqEsJw18N0O9gOQZX6Uv/XoEiOV8p+IZdidMeIRYELWJIgCZESvlOx5d1QVnB4v0w== +chromedriver@^121.0.0: + version "121.0.0" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-121.0.0.tgz#f8d11dce8e5ce4b6ad75e2b84eed17a0eecabfb9" + integrity sha512-ZIKEdZrQAfuzT/RRofjl8/EZR99ghbdBXNTOcgJMKGP6N/UL6lHUX4n6ONWBV18pDvDFfQJ0x58h5AdOaXIOMw== dependencies: "@testim/chrome-version" "^1.1.4" - axios "^1.6.0" + axios "^1.6.5" compare-versions "^6.1.0" extract-zip "^2.0.1" https-proxy-agent "^5.0.1" @@ -16935,11 +17485,16 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: +dom-accessibility-api@^0.5.9: version "0.5.14" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -18794,11 +19349,16 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@^1.0.0, follow-redirects@^1.14.8, follow-redirects@^1.15.0: +follow-redirects@^1.0.0, follow-redirects@^1.14.8: version "1.15.4" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== +follow-redirects@^1.15.4: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -22205,19 +22765,7 @@ jest-util@^28.1.3: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^29.0.0, jest-util@^29.6.2: - version "29.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.2.tgz#8a052df8fff2eebe446769fd88814521a517664d" - integrity sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w== - dependencies: - "@jest/types" "^29.6.1" - "@types/node" "*" - chalk "^4.0.0" - ci-info "^3.2.0" - graceful-fs "^4.2.9" - picomatch "^2.2.3" - -jest-util@^29.7.0: +jest-util@^29.0.0, jest-util@^29.6.2, jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== @@ -22863,10 +23411,10 @@ launchdarkly-eventsource@2.0.0: resolved "https://registry.yarnpkg.com/launchdarkly-eventsource/-/launchdarkly-eventsource-2.0.0.tgz#2832e73fa8bc0c103a7f8c6dbc3b1b53d22f9acc" integrity sha512-fxZ4IN46juAc3s8/geiutRPbI8cvUBz0Lcsayh3wfd97edYWLIsnaThw2esQ3zc6vgZ1v5IjTbdumNgoT3iRnw== -launchdarkly-js-client-sdk@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/launchdarkly-js-client-sdk/-/launchdarkly-js-client-sdk-3.1.3.tgz#e046439f0e4f0bfd6d38b9eaa4420a6e40ffc0c7" - integrity sha512-/JR/ri8z3bEj9RFTTKDjd+con4F1MsWUea1MmBDtFj4gDA0l9NDm1KzhMKiIeoBdmB2rSaeFYe4CaYOEp8IryA== +launchdarkly-js-client-sdk@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/launchdarkly-js-client-sdk/-/launchdarkly-js-client-sdk-3.1.4.tgz#e613cb53412533c07ccf140ae570fc994c59758d" + integrity sha512-yq0FeklpVuHMSRz7jfUAfyM7I/659RvGztqJ0Y9G5eN/ZrG1o2W61ZU0Nrv/gqZCtLXjarh/u1otxSFFBjTpHw== dependencies: escape-string-regexp "^4.0.0" launchdarkly-js-sdk-common "5.0.3" @@ -22880,13 +23428,13 @@ launchdarkly-js-sdk-common@5.0.3: fast-deep-equal "^2.0.1" uuid "^8.0.0" -launchdarkly-react-client-sdk@^3.0.1: - version "3.0.6" - resolved "https://registry.yarnpkg.com/launchdarkly-react-client-sdk/-/launchdarkly-react-client-sdk-3.0.6.tgz#5c694a4a013757d2afb5213efd28d9c16af1595e" - integrity sha512-r7gSshScugjnJB4lJ6mAOMKpV4Pj/wUks3tsRHdfeXgER9jPdxmZOAkm0besMjK0S7lfQdjxJ2KIXrh+Mn1sQA== +launchdarkly-react-client-sdk@^3.0.10: + version "3.0.10" + resolved "https://registry.yarnpkg.com/launchdarkly-react-client-sdk/-/launchdarkly-react-client-sdk-3.0.10.tgz#33816d939d9bd18b0723c0fd30b4772f2429f3de" + integrity sha512-ssb3KWe9z42+q8X2u32OrlDntGLsv0NP/p4E2Hx4O9RU0OeFm9v6omOlIk9SMsYEQD4QzLSXAp5L3cSN2ssLlA== dependencies: hoist-non-react-statics "^3.3.2" - launchdarkly-js-client-sdk "^3.1.3" + launchdarkly-js-client-sdk "^3.1.4" lodash.camelcase "^4.3.0" lazy-ass@^1.6.0: @@ -26492,9 +27040,9 @@ prettier-linter-helpers@^1.0.0: integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== prettier@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.0.tgz#c6d16474a5f764ea1a4a373c593b779697744d5e" - integrity sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw== + version "3.2.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" + integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== pretty-bytes@^5.3.0, pretty-bytes@^5.4.1, pretty-bytes@^5.6.0: version "5.6.0" @@ -28508,7 +29056,6 @@ serverless-plugin-scripts@^1.0.2: serverless-s3-bucket-helper@CMSgov/serverless-s3-bucket-helper: version "1.0.0" - uid "3e519d15676de237ec8ede3ff9ae26abf3f3ef0a" resolved "https://codeload.github.com/CMSgov/serverless-s3-bucket-helper/tar.gz/3e519d15676de237ec8ede3ff9ae26abf3f3ef0a" serverless-s3-local@^0.7.1: @@ -30457,9 +31004,9 @@ ts-invariant@^0.10.3: tslib "^2.1.0" ts-jest@^29.1.1: - version "29.1.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" - integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== + version "29.1.2" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.2.tgz#7613d8c81c43c8cb312c6904027257e814c40e09" + integrity sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x"