diff --git a/.gitignore b/.gitignore index b8abdc62e..d144b8511 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # OS files .DS_Store */DS_Store - +**/*.DS_Store # Generated code tmp/ */dist/* @@ -29,7 +29,7 @@ packages/javascript-sdk/lib/ .env.serve.development # Certificates -*.pem +# *.pem # IDEs .vscode @@ -62,4 +62,6 @@ docs/packages/javascript-sdk **/playwright-report **/playwright/.cache -.nx/cache +.nx/* +!.nx/workflows +**/vite.config.ts.timestamp-* diff --git a/e2e/autoscript-suites/playwright.config.ts b/e2e/autoscript-suites/playwright.config.ts index 736f86d9d..c4e4e56fb 100644 --- a/e2e/autoscript-suites/playwright.config.ts +++ b/e2e/autoscript-suites/playwright.config.ts @@ -28,6 +28,13 @@ const config: PlaywrightTestConfig = { reuseExistingServer: !process.env.CI, cwd: workspaceRoot, }, + { + command: 'npx nx serve e2e-mock-api-v2', + url: 'http://localhost:9444/healthcheck', + ignoreHTTPSErrors: true, + reuseExistingServer: !process.env.CI, + cwd: workspaceRoot, + }, { command: 'npx nx serve autoscript-apps', url: 'http://localhost:8443', diff --git a/e2e/mock-api-v2/.eslintrc.json b/e2e/mock-api-v2/.eslintrc.json new file mode 100644 index 000000000..1bff3304a --- /dev/null +++ b/e2e/mock-api-v2/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*", "README.md", ".DS_Store"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": { + "@typescript-eslint/no-empty-interface": [ + "error", + { + "allowSingleExtends": true + } + ] + } + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/e2e/mock-api-v2/README.md b/e2e/mock-api-v2/README.md new file mode 100644 index 000000000..dcff2fc81 --- /dev/null +++ b/e2e/mock-api-v2/README.md @@ -0,0 +1,109 @@ +### Mock Api + +## Docs + +you can run the server and visit `http://localhost:9443/docs#` to visit the swagger docs +The swagger docs are automatically created (with effect-http) by way of the schemas that are defined in the [spec file]('./src/spec.ts') + +## Creating an endpoint in the Api specification + +To create an endpoint, visit the [spec]('./src/spec.ts') file and add your endpoint. For organization and cleanliness, endpoints can be abstracted into the [endpoints]('./src/endpoints/') folder. + +When an endpoint is made, you can add it to the [`spec`]('./src/spec.ts') `pipe`. + +## Handling your new endpoint + +When you have created an endpoint in the specification, you need to now handle the endpoint. This is the actual code implementation of your endpoint. + +handlers are saved in the [handlers]('./src/handlers/') folder. + +You use RouterBuilder.handler, passing in the [api spec]('./src/spec.ts') and the name of the route, and then an Effect returning function (`Effect.gen` is the simplest form of this). + +The request arguments, you define in your endpoint specification, (query params, path params, request bodies, etc) are the arguments passed to the callback function of `RouterBuilder.handler`. + +Ensure that you also add your `handler` to the RouterBuilder.handle [here]('./src/main.ts'); + +## Adding a journey / flow to the response map + +If you are adding a flow to the response map the first thing to do is open up [responseMap]('./src/responses/index.ts') + +This file is a `map` of Names -> Array where a Step is the response you want to return (in order). Order is key here. + +If the Response you want to return is not already defined as a schema, you will have to define a new Schema and add the response. + +A schema is defined in the schemas folder [here]('./src/schemas/'); +A response is defined in the responses folder [here]('./src/responses/') + +This is still a work in progress in terms of making it more scalable. + +## Validating your code in Test + +After adding a journey/flow to the response map and defining a schema, you next want to have some validation on the submitted request. You can do this by adding it to the `validator` function [here]('./src/helpers/match.ts'); + +This functions job is to `match` the type passed in, and validate based on the condition provided. If it passes, a boolean is returned, if it fails, a new Error should be returned. + +## Services + +To make it so types line up easier, each route, has a service dedicated to itself. The service under the hood, uses the `Requester` service. The `Requester` service is to mimic a call to the authorization server. + +Let's look at the `Authorize` service. This service is the workhorse of the `authorize` handler. + +`Authorize`, the service, uses `Requester` which will fetch a response from the authorization server. + +After retrieving the response, the service will catch any errors that may be thrown, and mold them into HttpErrors to respond back to the client. + +In a mock environment, rather than fetching from the client, authorization service will grab the next response from the `responseMap`. + +In a live environment, it will forward a request to the Fetch service, and return that response. + +## Creating Errors + +If you want to create an error, it is simple. This is the skeleton of how to create an Error in `Effect` + +``` +class MyErrorName { + readonly _tag = 'MyErrorName' +} +``` + +The `_tag` is important as this is the name of the error, and how we can `catchTags` in our error handling. For simplicity, you can name is the same as your error class. + +## Handling Errors + +We want to return our errors back to the client, but typically we need an error response body that informs the client of the issue. + +You should add your error responses to the response folder [here]('./src/responses'); + +In the service where you want to handle your error, you will see a `catchTags` function. + +Let's pause here to understand the `Effect` type. + +```ts +Effect; +``` + +When reading an Effect type, the first generic, is what is returned if the effect is successful. + +The second argument is what is returned if the effect is unsuccessful. + +The third argument is any services (or layers) that are required to run this effect. + +So if we have an `effect` like this `Effect` + +This tells us the `effect` returns `users`, and can error two ways, `NoSuchElementException`, and with an `HttpError`. + +We would rather handle this `NoSuchElementException` and send back to the client an HttpError informing them of the error that occurred. + +We can do something like this now + +```ts +Effect.catchTag('NoSuchElementException', () => + HttpError.unauthorizedError('no such element found'), +); +``` + +This will return a 401 with that message. + +When handling errors, we try to keep the handler always returning an `HttpError`, so we should handle any other errors we have deeper in the call stack, to return HttpError unless there is a valid reason to allow the error to bubble up. + +If you have a shape of an error that you want to return from a handler, that does not match the current schema, you can add it to the `api spec`. diff --git a/e2e/mock-api-v2/project.json b/e2e/mock-api-v2/project.json new file mode 100644 index 000000000..ae1bece0b --- /dev/null +++ b/e2e/mock-api-v2/project.json @@ -0,0 +1,73 @@ +{ + "name": "e2e-mock-api-v2", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "e2e/mock-api-v2/src", + "projectType": "application", + "tags": ["e2e"], + "targets": { + "build": { + "executor": "@nx/esbuild:esbuild", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "platform": "node", + "outputPath": "dist/e2e/mock-api-v2", + "format": ["cjs"], + "bundle": false, + "main": "e2e/mock-api-v2/src/main.ts", + "tsConfig": "e2e/mock-api-v2/tsconfig.app.json", + "assets": ["e2e/mock-api-v2/src/assets"], + "generatePackageJson": false, + "esbuildOptions": { + "sourcemap": true, + "outExtension": { + ".js": ".js" + } + } + }, + "configurations": { + "development": { + "watch": true + }, + "production": { + "esbuildOptions": { + "sourcemap": false, + "outExtension": { + ".js": ".js" + } + } + } + } + }, + "serve": { + "executor": "@nx/js:node", + "defaultConfiguration": "development", + "options": { + "buildTarget": "e2e-mock-api-v2:build" + }, + "configurations": { + "development": { + "buildTarget": "e2e-mock-api-v2:build:development" + }, + "production": { + "buildTarget": "e2e-mock-api-v2:build:production" + } + } + }, + "lint": { + "executor": "@nx/eslint:lint" + }, + "test": { + "executor": "@nx/vite:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/e2e/mock-api-v2" + }, + "configurations": { + "watch": { + "watch": true + } + } + } + } +} diff --git a/e2e/mock-api-v2/src/assets/.gitkeep b/e2e/mock-api-v2/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/mock-api-v2/src/endpoints/custom-html.endpoint.ts b/e2e/mock-api-v2/src/endpoints/custom-html.endpoint.ts new file mode 100644 index 000000000..5773f6cf3 --- /dev/null +++ b/e2e/mock-api-v2/src/endpoints/custom-html.endpoint.ts @@ -0,0 +1,43 @@ +import { Schema } from '@effect/schema'; +import { pipe } from 'effect'; +import { Api, ApiResponse } from 'effect-http'; + +import { + DavinciAuthorizeHeaders, + DavinciAuthorizeResponseHeaders, +} from '../schemas/authorize.schema'; +import { + PingOneCustomHtmlRequestBody, + PingOneRequestQuery, +} from '../schemas/custom-html-template/custom-html-template-request.schema'; +import { + PingOneCustomHtmlResponseBody, + PingOneCustomHtmlResponseErrorBody, + PingOnePathParams, +} from '../schemas/custom-html-template/custom-html-template-response.schema'; +import { SuccessResponseRedirect } from '../schemas/return-success-response-redirect.schema'; + +const customHtmlEndPoint = Api.addEndpoint( + pipe( + Api.post( + 'PingOneCustomHtml', + '/:envid/davinci/connections/:connectionid/capabilities/customHTMLTemplate', + ).pipe( + Api.setRequestPath(PingOnePathParams), + Api.setRequestQuery(PingOneRequestQuery), + Api.setRequestBody(PingOneCustomHtmlRequestBody), + + Api.setRequestHeaders(DavinciAuthorizeHeaders), + + Api.setResponseBody(Schema.Union(PingOneCustomHtmlResponseBody, SuccessResponseRedirect)), + Api.setResponseHeaders(DavinciAuthorizeResponseHeaders), + + Api.addResponse( + ApiResponse.make(401, Schema.Union(PingOneCustomHtmlResponseErrorBody, Schema.String)), + ), + Api.addResponse(ApiResponse.make(403, Schema.String)), + ), + ), +); + +export { customHtmlEndPoint }; diff --git a/e2e/mock-api-v2/src/endpoints/davinci-authorize.endpoint.ts b/e2e/mock-api-v2/src/endpoints/davinci-authorize.endpoint.ts new file mode 100644 index 000000000..465f76ab9 --- /dev/null +++ b/e2e/mock-api-v2/src/endpoints/davinci-authorize.endpoint.ts @@ -0,0 +1,28 @@ +import { Schema } from '@effect/schema'; +import { pipe } from 'effect'; +import { Api } from 'effect-http'; + +import { + AuthorizePath, + DavinciAuthorizeHeaders, + DavinciAuthorizeQuery, + DavinciAuthorizeResponseHeaders, +} from '../schemas/authorize.schema'; +import { PingOneCustomHtmlResponseBody } from '../schemas/custom-html-template/custom-html-template-response.schema'; +import { SuccessResponseRedirect } from '../schemas/return-success-response-redirect.schema'; + +const davinciAuthorize = Api.addEndpoint( + pipe( + Api.get('DavinciAuthorize', '/:envid/as/authorize').pipe( + Api.setRequestPath(AuthorizePath), + Api.setRequestQuery(DavinciAuthorizeQuery), + Api.setRequestHeaders(DavinciAuthorizeHeaders), + ), + + Api.setResponseBody(Schema.Union(PingOneCustomHtmlResponseBody, SuccessResponseRedirect)), + Api.setResponseHeaders(DavinciAuthorizeResponseHeaders), + Api.setResponseStatus(200), + ), +); + +export { davinciAuthorize }; diff --git a/e2e/mock-api-v2/src/endpoints/open-id-configuration.endpoint.ts b/e2e/mock-api-v2/src/endpoints/open-id-configuration.endpoint.ts new file mode 100644 index 000000000..9afb75a09 --- /dev/null +++ b/e2e/mock-api-v2/src/endpoints/open-id-configuration.endpoint.ts @@ -0,0 +1,16 @@ +import { Schema } from '@effect/schema'; +import { pipe } from 'effect'; +import { Api } from 'effect-http'; +import { openIdConfigurationResponseSchema } from '../schemas/open-id-configuration/open-id-configuration-response.schema'; + +const openidConfiguration = Api.addEndpoint( + pipe( + Api.get('openidConfiguration', '/:envid/as/.well-known/openid-configuration').pipe( + Api.setRequestPath(Schema.Struct({ envid: Schema.String })), + Api.setResponseBody(openIdConfigurationResponseSchema), + Api.setResponseStatus(200), + ), + ), +); + +export { openidConfiguration }; diff --git a/e2e/mock-api-v2/src/endpoints/token.endpoint.ts b/e2e/mock-api-v2/src/endpoints/token.endpoint.ts new file mode 100644 index 000000000..901399def --- /dev/null +++ b/e2e/mock-api-v2/src/endpoints/token.endpoint.ts @@ -0,0 +1,20 @@ +import { Schema } from '@effect/schema'; +import { pipe } from 'effect'; +import { Api } from 'effect-http'; + +import { TokenResponseBody } from '../schemas/token/token.schema'; + +const pingOneToken = Api.addEndpoint( + pipe( + Api.post('PingOneToken', '/:envid/as/token').pipe( + Api.setRequestPath(Schema.Struct({ envid: Schema.String })), + Api.setRequestBody(Api.FormData), + + // Responses + Api.setResponseBody(TokenResponseBody), + Api.setResponseStatus(200), + ), + ), +); + +export { pingOneToken }; diff --git a/e2e/mock-api-v2/src/endpoints/userinfo.endpoint.ts b/e2e/mock-api-v2/src/endpoints/userinfo.endpoint.ts new file mode 100644 index 000000000..c6d7f5b97 --- /dev/null +++ b/e2e/mock-api-v2/src/endpoints/userinfo.endpoint.ts @@ -0,0 +1,19 @@ +import { Schema } from '@effect/schema'; +import { pipe } from 'effect'; +import { Api, ApiResponse, Security } from 'effect-http'; + +import { UserInfoSchema } from '../schemas/userinfo/userinfo.schema'; + +const userInfo = Api.addEndpoint( + pipe( + Api.get('UserInfo', '/:envid/as/userinfo').pipe( + Api.setRequestPath(Schema.Struct({ envid: Schema.String })), + Api.setSecurity(Security.bearer({})), + Api.setResponseStatus(200), + Api.setResponseBody(UserInfoSchema), + Api.addResponse(ApiResponse.make(401, Schema.String)), + ), + ), +); + +export { userInfo }; diff --git a/e2e/mock-api-v2/src/errors/index.ts b/e2e/mock-api-v2/src/errors/index.ts new file mode 100644 index 000000000..a456c16a2 --- /dev/null +++ b/e2e/mock-api-v2/src/errors/index.ts @@ -0,0 +1,16 @@ +class InvalidUsernamePassword { + readonly _tag = 'InvalidUsernamePassword'; +} + +class FetchError { + readonly _tag = 'FetchError'; +} + +class InvalidProtectNode { + readonly _tag = 'InvalidProtectNode'; +} +class UnableToFindNextStep { + readonly _tag = 'UnableToFindNextStep'; +} + +export { FetchError, InvalidUsernamePassword, InvalidProtectNode, UnableToFindNextStep }; diff --git a/e2e/mock-api-v2/src/handlers/authorize.handler.ts b/e2e/mock-api-v2/src/handlers/authorize.handler.ts new file mode 100644 index 000000000..30a853b45 --- /dev/null +++ b/e2e/mock-api-v2/src/handlers/authorize.handler.ts @@ -0,0 +1,33 @@ +import { toCookieHeader } from '@effect/platform/Cookies'; +import { Effect } from 'effect'; +import { RouterBuilder } from 'effect-http'; + +import { Authorize } from '../services/authorize.service'; +import { CookieService } from '../services/cookie.service'; +import { apiSpec } from '../spec'; + +const authorizeHandler = RouterBuilder.handler(apiSpec, 'DavinciAuthorize', ({ headers, query }) => + Effect.gen(function* () { + const { handleAuthorize } = yield* Authorize; + + /** + * Forward our request to AS + */ + const response = yield* handleAuthorize(headers, query); + + const { writeCookie } = yield* CookieService; + /** + * Write our cookies to send to the client + */ + const cookie = yield* writeCookie(headers); + + return { + ...response, + headers: { + 'Set-Cookie': toCookieHeader(cookie), + }, + }; + }), +); + +export { authorizeHandler }; diff --git a/e2e/mock-api-v2/src/handlers/custom-html-template.handler.ts b/e2e/mock-api-v2/src/handlers/custom-html-template.handler.ts new file mode 100644 index 000000000..f087d48da --- /dev/null +++ b/e2e/mock-api-v2/src/handlers/custom-html-template.handler.ts @@ -0,0 +1,35 @@ +import { toCookieHeader } from '@effect/platform/Cookies'; +import { Effect } from 'effect'; +import { RouterBuilder } from 'effect-http'; + +import { CookieService } from '../services/cookie.service'; +import { CustomHtmlTemplate } from '../services/custom-html-template.service'; +import { apiSpec } from '../spec'; + +const customHtmlHandler = RouterBuilder.handler( + apiSpec, + 'PingOneCustomHtml', + ({ headers, query, body }) => + Effect.gen(function* () { + const { handleCustomHtmlTemplate } = yield* CustomHtmlTemplate; + const response = yield* handleCustomHtmlTemplate( + headers, + query, + body, + ); + + const { writeCookie } = yield* CookieService; + + const cookie = yield* writeCookie(headers, response.interactionToken); + + return { + status: 200 as const, + body: response, + headers: { + 'Set-Cookie': toCookieHeader(cookie), + }, + }; + }), +); + +export { customHtmlHandler }; diff --git a/e2e/mock-api-v2/src/handlers/open-id-configuration.handler.ts b/e2e/mock-api-v2/src/handlers/open-id-configuration.handler.ts new file mode 100644 index 000000000..6a535022b --- /dev/null +++ b/e2e/mock-api-v2/src/handlers/open-id-configuration.handler.ts @@ -0,0 +1,13 @@ +import { Effect } from 'effect'; +import { RouterBuilder } from 'effect-http'; +import { apiSpec } from '../spec'; +import { openidConfigurationResponse } from '../responses/open-id-configuration'; + +const openidConfiguration = RouterBuilder.handler(apiSpec, 'openidConfiguration', () => + // eslint-disable-next-line require-yield + Effect.gen(function* () { + return openidConfigurationResponse; + }), +); + +export { openidConfiguration }; diff --git a/e2e/mock-api-v2/src/handlers/token.handler.ts b/e2e/mock-api-v2/src/handlers/token.handler.ts new file mode 100644 index 000000000..f8d763089 --- /dev/null +++ b/e2e/mock-api-v2/src/handlers/token.handler.ts @@ -0,0 +1,15 @@ +import { Effect } from 'effect'; +import { RouterBuilder } from 'effect-http'; +import { apiSpec } from '../spec'; +import { Tokens } from '../services/tokens.service'; + +const tokenHandler = RouterBuilder.handler(apiSpec, 'PingOneToken', () => + Effect.gen(function* () { + const { getTokens } = yield* Tokens; + const tokens = yield* getTokens(null); + + return tokens; + }), +); + +export { tokenHandler }; diff --git a/e2e/mock-api-v2/src/handlers/userinfo.handler.ts b/e2e/mock-api-v2/src/handlers/userinfo.handler.ts new file mode 100644 index 000000000..40a2cefd6 --- /dev/null +++ b/e2e/mock-api-v2/src/handlers/userinfo.handler.ts @@ -0,0 +1,16 @@ +import { Effect } from 'effect'; +import { RouterBuilder } from 'effect-http'; +import { apiSpec } from '../spec'; +import { UserInfo } from '../services/userinfo.service'; + +const userInfoHandler = RouterBuilder.handler(apiSpec, 'UserInfo', (request, security) => + Effect.gen(function* () { + const { getUserInfo } = yield* UserInfo; + + const response = yield* getUserInfo(security, {}); + + return response; + }), +); + +export { userInfoHandler }; diff --git a/e2e/mock-api-v2/src/helpers/cookie.ts b/e2e/mock-api-v2/src/helpers/cookie.ts new file mode 100644 index 000000000..2b82e3531 --- /dev/null +++ b/e2e/mock-api-v2/src/helpers/cookie.ts @@ -0,0 +1,70 @@ +import { Cookies } from '@effect/platform'; +import { Effect, Option, pipe } from 'effect'; + +import { ResponseMapKeys, responseMap } from '../responses'; +import { returnSuccessResponseRedirect } from '../responses/return-success-redirect'; + +import { HeaderTypes } from '../types'; + +/** + * + * This will parse the `headers.cookie` into an `Option` + * If there is no headers.cookie, it will be a `None` + * When we have a headers.cookie value, it will turn + * it into a Record of cookieName => value + * Then it specifically will grab the `stepIndex` value. + * If it doesn't exist, it will be a `None` + * If it exists, we will have a `Some(value: number)` + */ +const parseCookieHeaderForIndex = (headers: HeaderTypes) => { + return pipe( + /* + * We create an Option from the headers.cookie + * As long as we have some headers, this will be a `Some` + */ + Option.fromNullable(headers?.cookie), + + Option.map(Cookies.parseHeader), + + /** + * We try to get the `stepIndex` key from the record + * of headers that we created. + */ + Option.flatMapNullable((record) => record['stepIndex']), + ); +}; + +/** + * Increment the cookie header + * This will parses the cookie header first, and turn it + * into a record of cookie name and value + * then will incremement the stepIndex header by parsing the + * string into a number and adding 1 to it. + */ +const incrementCookieHeader = (headers: HeaderTypes) => + pipe( + parseCookieHeaderForIndex(headers), + Option.map(parseInt), + Option.map((num) => num + 1), + Option.map((num) => num.toString()), + Option.getOrElse(() => '1'), + ); + +/** + * Read the cookie for where we are in the flow + * Then return the next item + */ + +const getElementFromCookie = (arr: (typeof responseMap)[ResponseMapKeys], headers: HeaderTypes) => + pipe( + parseCookieHeaderForIndex(headers), + Option.map(parseInt), + Effect.flatMap((number) => + Effect.if(number <= arr.length - 1, { + onTrue: () => Effect.succeed(arr[number]), + onFalse: () => Effect.succeed(returnSuccessResponseRedirect), + }), + ), + ); + +export { incrementCookieHeader, parseCookieHeaderForIndex, getElementFromCookie }; diff --git a/e2e/mock-api-v2/src/helpers/match.ts b/e2e/mock-api-v2/src/helpers/match.ts new file mode 100644 index 000000000..720ef609d --- /dev/null +++ b/e2e/mock-api-v2/src/helpers/match.ts @@ -0,0 +1,32 @@ +import { Schema } from '@effect/schema'; +import { Effect, Match } from 'effect'; + +import { InvalidUsernamePassword, InvalidProtectNode } from '../errors'; +import { PingOneCustomHtmlRequestBody } from '../schemas/custom-html-template/custom-html-template-request.schema'; + +type PingRequestData = Schema.Schema.Type< + typeof PingOneCustomHtmlRequestBody +>['parameters']['data']['formData']['value']; +/** + * Using this to match on the data types, realistically, this will be a schema of possible + * response bodies we want to validate against they validate to our conditions. + * + * We can then return back either an Error to respond with, if validation fails + * or we can continue to the next step in the flow + */ +const validator = Match.type().pipe( + Match.when({ username: Match.string, password: Match.string }, ({ username, password }) => { + return Effect.if(username == 'testuser' && password === 'Password', { + onFalse: () => Effect.fail(new InvalidUsernamePassword()), + onTrue: () => Effect.succeed(true), + }); + }), + Match.when({ pingprotectsdk: Match.string }, ({ pingprotectsdk }) => { + return Effect.if(pingprotectsdk.length > 1, { + onTrue: () => Effect.succeed(true), + onFalse: () => Effect.fail(new InvalidProtectNode()), + }); + }), + Match.exhaustive, +); +export { validator, PingRequestData }; diff --git a/e2e/mock-api-v2/src/helpers/test/cookie.test.ts b/e2e/mock-api-v2/src/helpers/test/cookie.test.ts new file mode 100644 index 000000000..af7f4acde --- /dev/null +++ b/e2e/mock-api-v2/src/helpers/test/cookie.test.ts @@ -0,0 +1,68 @@ +import { it, expect } from '@effect/vitest'; +import { getElementFromCookie, incrementCookieHeader, parseCookieHeaderForIndex } from '../cookie'; +import { HeaderTypes } from '../../types'; +import { Effect, Exit } from 'effect'; +import { responseMap } from '../../responses'; +import { returnSuccessResponseRedirect } from '../../responses/return-success-redirect'; + +it.effect('should parse a cookie header for an index value', () => + Effect.gen(function* () { + const header: HeaderTypes = { + cookie: 'stepIndex=1', + }; + + const result = yield* parseCookieHeaderForIndex(header); + expect(result).toEqual('1'); + }), +); + +it.effect('should parse a cookie header for an index value', () => + Effect.gen(function* () { + const header: HeaderTypes = { + cookie: '', + }; + + const result = yield* parseCookieHeaderForIndex(header).pipe(Effect.exit); + expect(result).toEqual(Exit.fail('is a none')); + }), +); + +it('should increment the cookie header', () => { + const headers: HeaderTypes = { + cookie: 'stepIndex=1', + }; + + const result = incrementCookieHeader(headers); + expect(result).toEqual('2'); +}); + +it('should return 1 if no cookie header passed to incrementCookieHeader', () => { + const headers: HeaderTypes = {}; + + const result = incrementCookieHeader(headers); + expect(result).toEqual('1'); +}); + +it('should get an element from the response map based off the cookie header', () => + Effect.gen(function* () { + const headers: HeaderTypes = { + cookie: 'stepIndex=1', + }; + + const expected = responseMap['UsernamePassword'][1]; + const result = yield* getElementFromCookie(responseMap['UsernamePassword'], headers); + expect(expected).toEqual(result); + })); +it.effect('should return responesRedirect when we have exceeded the index for a given flow', () => + Effect.gen(function* () { + const headers: HeaderTypes = { + cookie: 'stepIndex=4', + }; + + const arr = responseMap['UsernamePassword']; + const expected = returnSuccessResponseRedirect; + const result = yield* getElementFromCookie(arr, headers); + + expect(expected).toEqual(result); + }), +); diff --git a/e2e/mock-api-v2/src/helpers/test/match.test.ts b/e2e/mock-api-v2/src/helpers/test/match.test.ts new file mode 100644 index 000000000..a78d98a73 --- /dev/null +++ b/e2e/mock-api-v2/src/helpers/test/match.test.ts @@ -0,0 +1,45 @@ +import { it, expect } from '@effect/vitest'; +import { PingRequestData, validator } from '../match'; +import { Effect, Exit } from 'effect'; + +it.effect('match validation function passes username password validation', () => + Effect.gen(function* () { + const body: PingRequestData = { + username: 'testuser', + password: 'Password', + }; + const result = yield* validator(body); + expect(result).toEqual(true); + }), +); +it.effect('match validation function fails username password validation', () => + Effect.gen(function* () { + const body: PingRequestData = { + username: 'testuser', + password: 'bad-password', + }; + const result = yield* validator(body).pipe(Effect.exit); + expect(result).toEqual(Exit.fail('invalid username password')); + }), +); + +it.effect('match validation function passes ping protect node validaiton', () => + Effect.gen(function* () { + const body: PingRequestData = { + pingprotectsdk: '12321321980123', + }; + + const result = yield* validator(body); + expect(result).toEqual(true); + }), +); +it.effect('match validation function passes ping protect node validaiton', () => + Effect.gen(function* () { + const body: PingRequestData = { + pingprotectsdk: '', + }; + + const result = yield* validator(body).pipe(Effect.exit); + expect(result).toEqual(Exit.fail('error')); + }), +); diff --git a/e2e/mock-api-v2/src/main.ts b/e2e/mock-api-v2/src/main.ts new file mode 100644 index 000000000..2685bcf96 --- /dev/null +++ b/e2e/mock-api-v2/src/main.ts @@ -0,0 +1,47 @@ +import { Config, Effect, Layer, pipe } from 'effect'; +import { RouterBuilder, Middlewares } from 'effect-http'; +import { NodeRuntime } from '@effect/platform-node'; +import { NodeServer } from 'effect-http-node'; +import { apiSpec } from './spec'; + +import { authorizeHandler } from './handlers/authorize.handler'; +import { customHtmlHandler } from './handlers/custom-html-template.handler'; +import { openidConfiguration } from './handlers/open-id-configuration.handler'; +import { tokenHandler } from './handlers/token.handler'; +import { userInfoHandler } from './handlers/userinfo.handler'; + +import { authorizeMock } from './services/authorize.service'; +import { CookieService, cookieServiceTest } from './services/cookie.service'; +import { mockCustomHtmlTemplate } from './services/custom-html-template.service'; +import { mockRequest } from './services/request.service'; +import { mockTokens } from './services/tokens.service'; +import { UserInfo, userInfoMock } from './services/userinfo.service'; + +const app = RouterBuilder.make(apiSpec).pipe( + RouterBuilder.handle('HealthCheck', () => Effect.succeed('Healthy!')), + RouterBuilder.handle(authorizeHandler), + RouterBuilder.handle(customHtmlHandler), + RouterBuilder.handle(openidConfiguration), + RouterBuilder.handle(tokenHandler), + RouterBuilder.handle(userInfoHandler), + RouterBuilder.build, + Middlewares.errorLog, +); + +const Layers = Layer.mergeAll(mockTokens, authorizeMock, mockCustomHtmlTemplate).pipe( + Layer.provide(mockRequest), +); + +const PORT = Config.integer('PORT').pipe(Config.withDefault(9444)); + +const program = app.pipe( + Effect.provide(Layers), + Effect.provide(mockRequest), + Effect.provideService(UserInfo, userInfoMock), + Effect.provideService(CookieService, cookieServiceTest), +); + +pipe( + Effect.flatMap(PORT, (port) => program.pipe(NodeServer.listen({ port }))), + NodeRuntime.runMain, +); diff --git a/e2e/mock-api-v2/src/responses/custom-html-template/ping-protect-node.ts b/e2e/mock-api-v2/src/responses/custom-html-template/ping-protect-node.ts new file mode 100644 index 000000000..f9b89c316 --- /dev/null +++ b/e2e/mock-api-v2/src/responses/custom-html-template/ping-protect-node.ts @@ -0,0 +1,41 @@ +const PingProtectNode = { + interactionId: '1894775c-ba76-4989-a303-320f0547c66e', + connectorId: 'api', + interactionToken: + '7d242c81eabd4987b902448b3d92a5ed3d7c07ccddc681b5b3b3cbcde624e5466b560d2f47ac63605a45057a823144c55a8b5ebe70ac9cbab00921c0efcd1b707738ab5593d8e2cad20c9c4ba3834e15bb7eed4ca29673b2fa24192c7916bdc28b13d0414c1e421c6f7d197c1bafef00fa8ab725ee9ede76abbc068afa3b8605', + success: true, + startUiSubFlow: true, + _links: { + next: { + href: 'https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/davinci/connections/867ed4363b2bc21c860085ad2baa817d/capabilities/customHTMLTemplate', + }, + }, + eventName: 'continue', + isResponseCompatibleWithMobileAndWebSdks: true, + id: 'tavl3e1h2q', + companyId: '02fb4743-189a-4bc7-9d6c-a919edfe6447', + flowId: 'f17221dd5a67fef0382db1e77791d436', + connectionId: '867ed4363b2bc21c860085ad2baa817d', + capabilityName: 'customHTMLTemplate', + formData: { + value: { + protectsdk: '', + }, + }, + form: { + name: 'Start Node', + description: '', + category: 'CUSTOM_HTML', + components: { + fields: [ + { + type: 'TEXT', + key: 'protectsdk', + label: 'Protect Payload', + }, + ], + }, + }, +}; + +export { PingProtectNode }; diff --git a/e2e/mock-api-v2/src/responses/custom-html-template/username-password.ts b/e2e/mock-api-v2/src/responses/custom-html-template/username-password.ts new file mode 100644 index 000000000..48550e1b4 --- /dev/null +++ b/e2e/mock-api-v2/src/responses/custom-html-template/username-password.ts @@ -0,0 +1,14 @@ +const customHtmlUsernamePassword = { + id: 'cq77vwelou', + eventName: 'continue', + interactionId: '18127a84-2fdb-40c7-8d61-78f9116449a5', + parameters: { + eventType: 'submit', + data: { + actionKey: 'SIGNON', + formData: { username: 'demouser', password: 'password' }, + }, + }, +}; + +export { customHtmlUsernamePassword }; diff --git a/e2e/mock-api-v2/src/responses/index.ts b/e2e/mock-api-v2/src/responses/index.ts new file mode 100644 index 000000000..ca9582841 --- /dev/null +++ b/e2e/mock-api-v2/src/responses/index.ts @@ -0,0 +1,17 @@ +import { Array } from 'effect'; +import { UsernamePassword } from './username-password'; +import { PingProtectNode } from './custom-html-template/ping-protect-node'; +import { InvalidUsernamePassword } from './invalid-username-password'; + +type ResponseMapKeys = keyof typeof responseMap; +const responseMap = { + UsernamePassword: Array.make(PingProtectNode, UsernamePassword), +} as const; + +type ErrorMapKeys = keyof typeof errorMap; + +const errorMap = { + InvalidUsernamePassword, +} as const; + +export { ResponseMapKeys, responseMap, errorMap, ErrorMapKeys }; diff --git a/e2e/mock-api-v2/src/responses/invalid-username-password.ts b/e2e/mock-api-v2/src/responses/invalid-username-password.ts new file mode 100644 index 000000000..5c4400858 --- /dev/null +++ b/e2e/mock-api-v2/src/responses/invalid-username-password.ts @@ -0,0 +1,38 @@ +const InvalidUsernamePassword = { + interactionId: '18127a84-2fdb-40c7-8d61-78f9116449a5', + companyId: '02fb4743-189a-4bc7-9d6c-a919edfe6447', + connectionId: '94141bf2f1b9b59a5f5365ff135e02bb', + connectorId: 'pingOneSSOConnector', + id: 'dnu7jt3sjz', + capabilityName: 'checkPassword', + errorCategory: 'Unexpected', + code: ' Invalid username and/or password', + cause: null, + expected: false, + message: ' Invalid username and/or password', + httpResponseCode: 400, + details: [ + { + rawResponse: { + id: '9785a004-3481-4070-9277-5c0497290315', + code: 'INVALID_DATA', + message: + 'The request could not be completed. One or more validation errors were in the request.', + details: [ + { + code: 'INVALID_VALUE', + target: 'password', + message: 'The provided password did not match provisioned password', + innerError: { + failuresRemaining: 4, + }, + }, + ], + }, + statusCode: 400, + }, + ], + isResponseCompatibleWithMobileAndWebSdks: true, +}; + +export { InvalidUsernamePassword }; diff --git a/e2e/mock-api-v2/src/responses/open-id-configuration.ts b/e2e/mock-api-v2/src/responses/open-id-configuration.ts new file mode 100644 index 000000000..73d36783e --- /dev/null +++ b/e2e/mock-api-v2/src/responses/open-id-configuration.ts @@ -0,0 +1,87 @@ +const openidConfigurationResponse = { + issuer: 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as', + authorization_endpoint: 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/authorize', + pushed_authorization_request_endpoint: + 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/par', + token_endpoint: 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/token', + userinfo_endpoint: 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/userinfo', + jwks_uri: 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/jwks', + end_session_endpoint: 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/signoff', + check_session_iframe: + 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/checksession', + introspection_endpoint: + 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/introspect', + revocation_endpoint: 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/revoke', + device_authorization_endpoint: + 'http://localhost:9443/02fb4743-189a-4bc7-9d6c-a919edfe6447/as/device_authorization', + claims_parameter_supported: false, + request_parameter_supported: true, + request_uri_parameter_supported: false, + require_pushed_authorization_requests: false, + scopes_supported: ['openid', 'profile', 'email', 'address', 'phone'], + response_types_supported: [ + 'code', + 'id_token', + 'token id_token', + 'code id_token', + 'code token', + 'code token id_token', + ], + response_modes_supported: ['pi.flow', 'query', 'fragment', 'form_post'], + grant_types_supported: [ + 'authorization_code', + 'implicit', + 'client_credentials', + 'refresh_token', + 'urn:ietf:params:oauth:grant-type:device_code', + ], + subject_types_supported: ['public'], + id_token_signing_alg_values_supported: ['RS256'], + userinfo_signing_alg_values_supported: ['none'], + request_object_signing_alg_values_supported: [ + 'none', + 'HS256', + 'HS384', + 'HS512', + 'RS256', + 'RS384', + 'RS512', + ], + token_endpoint_auth_methods_supported: [ + 'client_secret_basic', + 'client_secret_post', + 'client_secret_jwt', + 'private_key_jwt', + ], + token_endpoint_auth_signing_alg_values_supported: [ + 'HS256', + 'HS384', + 'HS512', + 'RS256', + 'RS384', + 'RS512', + ], + claim_types_supported: ['normal'], + claims_supported: [ + 'sub', + 'iss', + 'auth_time', + 'acr', + 'name', + 'given_name', + 'family_name', + 'middle_name', + 'preferred_username', + 'profile', + 'picture', + 'zoneinfo', + 'phone_number', + 'updated_at', + 'address', + 'email', + 'locale', + ], + code_challenge_methods_supported: ['plain', 'S256'], +}; + +export { openidConfigurationResponse }; diff --git a/e2e/mock-api-v2/src/responses/return-success-redirect.ts b/e2e/mock-api-v2/src/responses/return-success-redirect.ts new file mode 100644 index 000000000..6ad791f95 --- /dev/null +++ b/e2e/mock-api-v2/src/responses/return-success-redirect.ts @@ -0,0 +1,38 @@ +const returnSuccessResponseRedirect = { + interactionId: '174edeee-6d5d-4a4c-925e-d5e6a91b9956', + companyId: '02fb4743-189a-4bc7-9d6c-a919edfe6447', + connectionId: 'c3e6a164bde107954e93f5c09f0c8bce', + connectorId: 'pingOneAuthenticationConnector', + id: 'fbx7x6gnus', + capabilityName: 'returnSuccessResponseRedirect', + environment: { + id: '02fb4743-189a-4bc7-9d6c-a919edfe6447', + }, + session: { + id: '739ff7cd-1944-4e8a-b770-2af7ed053fe2', + }, + status: 'COMPLETED', + authorizeResponse: { + code: '1748be69-f940-4a22-9a6e-0459ac730079', + state: 'NzQxMzIxMzk0MTI0ODE5NzI0MTYwODQyMjAxODAxODcyNTUyMjIyMzA2', + }, + isResponseCompatibleWithMobileAndWebSdks: true, + success: true, + resetCookie: true, + interactionToken: + '250f2754f8060f6e4ba6aad04bc926cec8c19f6213895441021e2930d726b58199bfdbb1105602ee19de5076d52f217e2a537306440e3f2ca867f9e52196791ae09ddf5524a54e558a8609eb90cfe2c8a163097b4ec1debc1959cef3989cc94ea5fc9d484a2c790e5b272ffed38a3d9849e33719bea1b2f61c51402ad87aff2d', + subFlowSettings: { + reactSkUrl: 'https://assets.pingone.ca/davinci/latest/davinci.js', + cssUrl: null, + cssLinks: [], + jsLinks: [], + loadingScreenSettings: '{}', + }, + _links: { + next: { + href: 'https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/davinci/connections/c3e6a164bde107954e93f5c09f0c8bce/capabilities/returnSuccessResponseRedirect', + }, + }, +}; + +export { returnSuccessResponseRedirect }; diff --git a/e2e/mock-api-v2/src/responses/token/token.ts b/e2e/mock-api-v2/src/responses/token/token.ts new file mode 100644 index 000000000..5a9bcfc99 --- /dev/null +++ b/e2e/mock-api-v2/src/responses/token/token.ts @@ -0,0 +1,22 @@ +const tokenRequestBody = { + client_id: '724ec718-c41c-4d51-98b0-84a583f450f9', + code: '17a4a4fd-61b4-46c6-a185-3bbb4e3f297e', + grant_type: 'authorization_code', + redirect_uri: 'https://localhost:8443/callback', + code_verifier: + 'MjcxMzEwOTIzOTI1MTE1MzEzNjEzMTc0Njc0MjM4MTIxNzMyMjQ1OTE4NDkwMTY1MTQ3MTg5NDM1NDA0MjU0MTk4OTAyMjA2MDU0OTk', +}; + +const tokenResponseBody = { + access_token: + 'eyJraWQiOiJmNDY5N2NjMC1mMDgzLTExZWUtOTJkOC0zZGNjN2IzNGQ2NDciLCJhbGciOiJSUzI1NiJ9.eyJjbGllbnRfaWQiOiI3MjRlYzcxOC1jNDFjLTRkNTEtOThiMC04NGE1ODNmNDUwZjkiLCJpc3MiOiJodHRwczovL2F1dGgucGluZ29uZS5jYS8wMmZiNDc0My0xODlhLTRiYzctOWQ2Yy1hOTE5ZWRmZTY0NDcvYXMiLCJpYXQiOjE3MTk4NTkyNzAsImV4cCI6MTcxOTg2Mjg3MCwiYXVkIjpbImh0dHBzOi8vYXBpLnBpbmdvbmUuY2EiXSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBlbWFpbCIsInN1YiI6IjZmMDBhMWJhLTFkODYtNGI4Ny1hZmIwLTBlYTFhOTUyYmMyZSIsInNpZCI6IjIxNjkzZDg5LTJjNzItNGNjMC1iMDU1LTlmYzcyOGYxODdkMyIsImVudiI6IjAyZmI0NzQzLTE4OWEtNGJjNy05ZDZjLWE5MTllZGZlNjQ0NyIsIm9yZyI6IjExZjY5ZTgxLWVkNWMtNGNiMC1hYzI4LTQwYmViOWQ4N2RiYSJ9.O-u1dM1q4-J8DU0-NfaKK3A_0c5Qu7eUXVzvREVLAap60UnJ7JdR7bVfVPD97hitdVItScIHFEL1G8kU4Gz1FFO39R9rT5XrneEvnbrve-_xEDxluQp1ppUp53s_e2K9P_Ub-YAEgl78GQK6maNmaljdZ__XCh6MVzryGnSXti27Ste4SFDLjUKRDF1KzkR-6moCp_3B0qlcxUnYzEiIyZpa1BOTx3d-ia74cf9DxUi6HYYFfM1LIsRC-Q0z_-6EFNNHYbomNonbOdvBr0kRd0XiN5z2k2tgYf2OETGMca4-K7WoBYKcDrtc-v0Scc5zkNpx0w6lgobad7hZHo2oNQ', + token_type: 'Bearer', + expires_in: 3600, + refresh_token: + 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImRlZmF1bHQifQ.eyJzdWIiOiI2ZjAwYTFiYS0xZDg2LTRiODctYWZiMC0wZWExYTk1MmJjMmUiLCJqdGkiOiI1NzQ1NWQzNC0xM2EzLTRkZGYtYWJkMy1jOTdhZWFkYmNiYWYiLCJleHAiOjE3MjI0NTEyNzAsInNpZCI6IjIxNjkzZDg5LTJjNzItNGNjMC1iMDU1LTlmYzcyOGYxODdkMyIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJhdXRoX3RpbWUiOjE3MTk4NTksImFjciI6IjljMTVmNTY3NDQxYzAzZTZkODlmN2YzMmY0MzBkZGM0IiwiYW1yIjpbInB3ZCJdLCJpc3MiOiJodHRwczovL2F1dGgucGluZ29uZS5jYS8wMmZiNDc0My0xODlhLTRiYzctOWQ2Yy1hOTE5ZWRmZTY0NDcvYXMifQ.XmhzoZcRgEvjnvyvrE1v9W8kt7ipSzp3A5sBX8mQ_nz75iLgg2FO-Rl9vVy_JEJaHp-fRIuDvTJFBDMVC9OFF0SEMEc815UGQNSq5ln_DQnAzVGvk8qXSC13iNXaxPYy9Z3ixuggm5YnA2pXD-r_bk9nl3g4sokYCla7Ho63aeo0qSOBQvfI2AOVEdCmpekwJp8GQIjTpnpF1MztipaSBBjVXhR7Bve-9Ye1Ob_UT1PHfwsUUcBM8OwsWrOpl3oB-V-5VyE1C3WpNxsAAWwA0yi6hxBgc7Cfx1-i_NxWwhanLZ8iOQ8LTJg_CbWY2SZR5YldBbcIGELmyRMC24O0mg', + scope: 'openid profile email', + id_token: + 'eyJraWQiOiJmNDY5N2NjMC1mMDgzLTExZWUtOTJkOC0zZGNjN2IzNGQ2NDciLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2F1dGgucGluZ29uZS5jYS8wMmZiNDc0My0xODlhLTRiYzctOWQ2Yy1hOTE5ZWRmZTY0NDcvYXMiLCJzdWIiOiI2ZjAwYTFiYS0xZDg2LTRiODctYWZiMC0wZWExYTk1MmJjMmUiLCJhdWQiOiI3MjRlYzcxOC1jNDFjLTRkNTEtOThiMC04NGE1ODNmNDUwZjkiLCJpYXQiOjE3MTk4NTkyNzAsImV4cCI6MTcxOTg2Mjg3MCwiYWNyIjoiOWMxNWY1Njc0NDFjMDNlNmQ4OWY3ZjMyZjQzMGRkYzQiLCJhbXIiOlsicHdkIl0sImF1dGhfdGltZSI6MTcxOTg1OSwiYXRfaGFzaCI6InFELUlLY25ESDh6VnZVWTJiSkpNZGciLCJzaWQiOiIyMTY5M2Q4OS0yYzcyLTRjYzAtYjA1NS05ZmM3MjhmMTg3ZDMiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJkZW1vdXNlciIsImdpdmVuX25hbWUiOiJVc2VyIiwidXBkYXRlZF9hdCI6MTcxOTg1OTI3MCwiZmFtaWx5X25hbWUiOiJEZW1vIChkbyBub3QgY2hhbmdlKSIsImVtYWlsIjoiZGVtb0B1c2VyLmNvbSIsImVudiI6IjAyZmI0NzQzLTE4OWEtNGJjNy05ZDZjLWE5MTllZGZlNjQ0NyIsIm9yZyI6IjExZjY5ZTgxLWVkNWMtNGNiMC1hYzI4LTQwYmViOWQ4N2RiYSIsInAxLnJlZ2lvbiI6IkNBIn0.iOydEFG5i6JveC-c49NvznUbV80Wfw94Z-M5K23Mc5KU3o7xUdcOvnBa2BJ7dbbqC-GNevkpH4AAHPkHlNsUXch3aDReSOWIQFNwpasVo1cwM_WUl5EfGZpVAGGhnWbpEd2hqCsDjpWbPtrU--flpi1ILnYedOGRsAdY_F_mWloqKz_Wl6ietKvh0oK7Mstp5u4reZ-nOM91o0LxJedPM_3rUoEC7-zYLNzdiaLz5lXe86zparpGMBWAvOGR84pizfMX5LFhBSFx8BCpM9oOnSJ6WzAE3uzLtsmrVfoT9LSWE_iGPY2_Gprq8rqQqEa1q6ETPvcRmv87FCGWELa8_g', +}; + +export { tokenRequestBody, tokenResponseBody }; diff --git a/e2e/mock-api-v2/src/responses/userinfo/userinfo.ts b/e2e/mock-api-v2/src/responses/userinfo/userinfo.ts new file mode 100644 index 000000000..7eb79ed7b --- /dev/null +++ b/e2e/mock-api-v2/src/responses/userinfo/userinfo.ts @@ -0,0 +1,13 @@ +const userInfoResponse = { + sub: '6f00a1ba-1d86-4b87-afb0-0ea1a952bc2e', + preferred_username: 'demouser', + given_name: 'User', + updated_at: 1719859270, + family_name: 'Demo (do not change)', + email: 'demo@user.com', + env: '02fb4743-189a-4bc7-9d6c-a919edfe6447', + org: '11f69e81-ed5c-4cb0-ac28-40beb9d87dba', + 'p1.region': 'CA', +}; + +export { userInfoResponse }; diff --git a/e2e/mock-api-v2/src/responses/username-password.ts b/e2e/mock-api-v2/src/responses/username-password.ts new file mode 100644 index 000000000..636b59d99 --- /dev/null +++ b/e2e/mock-api-v2/src/responses/username-password.ts @@ -0,0 +1,59 @@ +const UsernamePassword = { + interactionId: '1857e57f-aaad-43a5-9054-259683ae6e36', + interactionToken: + 'd3c182ce4838859dcee69d991cada2b0f7130fbcadbe46c885e3a2a696f9b233f651cbb42cd65a3f0e47b096a33b7525c09816dc35ea67daa3c50df15b7b4f5c6a8680621b9e25b030ec636e4cb1ba402a2d1381592fe03b18876f37b11e8172585ff14c39ab265a9a0d10b20c62c27d05ab8b2022944e95adb3c075f6f621c2', + _links: { + next: { + href: 'https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/davinci/connections/867ed4363b2bc21c860085ad2baa817d/capabilities/customHTMLTemplate', + }, + }, + eventName: 'continue', + isResponseCompatibleWithMobileAndWebSdks: true, + id: 'cq77vwelou', + companyId: '02fb4743-189a-4bc7-9d6c-a919edfe6447', + flowId: 'f17221dd5a67fef0382db1e77791d436', + connectionId: '867ed4363b2bc21c860085ad2baa817d', + capabilityName: 'customHTMLTemplate', + formData: { + value: { + username: '', + password: '', + }, + }, + form: { + name: 'Username/Password Form', + description: '', + category: 'CUSTOM_HTML', + components: { + fields: [ + { + type: 'TEXT', + key: 'username', + label: 'Username', + }, + { + type: 'PASSWORD', + key: 'password', + label: 'Password', + }, + { + type: 'SUBMIT_BUTTON', + key: 'SIGNON', + label: 'Sign On', + }, + { + type: 'FLOW_BUTTON', + key: 'TROUBLE', + label: 'Having trouble signing on?', + }, + { + type: 'FLOW_BUTTON', + key: 'REGISTER', + label: 'No account? Register now!', + }, + ], + }, + }, +}; + +export { UsernamePassword }; diff --git a/e2e/mock-api-v2/src/schemas/authorize.schema.ts b/e2e/mock-api-v2/src/schemas/authorize.schema.ts new file mode 100644 index 000000000..970fab7c9 --- /dev/null +++ b/e2e/mock-api-v2/src/schemas/authorize.schema.ts @@ -0,0 +1,40 @@ +import { Schema } from '@effect/schema'; + +const AuthorizePath = Schema.Struct({ envid: Schema.String }); + +const DavinciAuthorizeResponseHeaders = Schema.Struct({ + 'Set-Cookie': Schema.String, +}); + +const DavinciAuthorizeHeaders = Schema.Struct({ + cookie: Schema.optional(Schema.String), +}); + +const _DavinciAuthorizeQuery = Schema.Struct({ + response_mode: Schema.String, + client_id: Schema.String, + redirect_uri: Schema.String, + response_type: Schema.String, + scope: Schema.String, + state: Schema.String, + code: Schema.String, + code_challenge: Schema.String, + code_challenge_method: Schema.String, + acr_values: Schema.String, // this should be optional +}); +interface DavinciAuthorizeQuery extends Schema.Schema.Type {} +const DavinciAuthorizeQuery: Schema.Schema = + _DavinciAuthorizeQuery; + +const DavinciAuthorizeFailure = Schema.Struct({ + error: Schema.String, + error_description: Schema.String, +}); + +export { + AuthorizePath, + DavinciAuthorizeHeaders, + DavinciAuthorizeQuery, + DavinciAuthorizeFailure, + DavinciAuthorizeResponseHeaders, +}; diff --git a/e2e/mock-api-v2/src/schemas/custom-html-template/custom-html-template-request.schema.ts b/e2e/mock-api-v2/src/schemas/custom-html-template/custom-html-template-request.schema.ts new file mode 100644 index 000000000..36618822f --- /dev/null +++ b/e2e/mock-api-v2/src/schemas/custom-html-template/custom-html-template-request.schema.ts @@ -0,0 +1,52 @@ +import { Schema } from '@effect/schema'; + +/** + * Schemas of what FormData may look like in a Ping Request + * + */ +const FormDataResponseUsernamePassword = Schema.Struct({ + username: Schema.String, + password: Schema.String, +}); + +const PingProtectSDKResponse = Schema.Struct({ pingprotectsdk: Schema.String }); + +const PossibleFormDatas = Schema.Struct({ + value: Schema.Union(FormDataResponseUsernamePassword, PingProtectSDKResponse), +}); + +/** + * Shape of the query parameters in a PingOne request + */ +const PingOneRequestQuery = Schema.Struct({ + acr_values: Schema.String, +}); + +/** + * + * The body, composed with the `PossibleFormDatas` + * for a PingOneRequest + */ +const _PingOneCustomHtmlRequestBody = Schema.Struct({ + id: Schema.String, + eventName: Schema.String, + interactionId: Schema.String, + parameters: Schema.Struct({ + eventType: Schema.String, + data: Schema.Struct({ + /** + * Consider making action keys literal values. + */ + actionKey: Schema.String, + formData: PossibleFormDatas, + }), + }), +}); +interface PingOneCustomHtmlRequestBody + extends Schema.Schema.Type {} +const PingOneCustomHtmlRequestBody: Schema.Schema< + PingOneCustomHtmlRequestBody, + PingOneCustomHtmlRequestBody +> = _PingOneCustomHtmlRequestBody; + +export { PingOneCustomHtmlRequestBody, PingOneRequestQuery }; diff --git a/e2e/mock-api-v2/src/schemas/custom-html-template/custom-html-template-response.schema.ts b/e2e/mock-api-v2/src/schemas/custom-html-template/custom-html-template-response.schema.ts new file mode 100644 index 000000000..d94c0aac9 --- /dev/null +++ b/e2e/mock-api-v2/src/schemas/custom-html-template/custom-html-template-response.schema.ts @@ -0,0 +1,101 @@ +import { Schema } from '@effect/schema'; + +const PingOnePathParams = Schema.Struct({ envid: Schema.String, connectionid: Schema.String }); + +const ProtectSDKRequestFormData = Schema.Struct({ + value: Schema.Struct({ + protectsdk: Schema.String, + }), +}); + +const UsernamePasswordFormData = Schema.Struct({ + value: Schema.Struct({ + username: Schema.String, + password: Schema.String, + }), +}); + +const PossibleFormDatas = Schema.Union(ProtectSDKRequestFormData, UsernamePasswordFormData); + +const _PingOneCustomHtmlResponseBody = Schema.Struct({ + interactionId: Schema.String, + interactionToken: Schema.String, + _links: Schema.Struct({ + next: Schema.Struct({ + href: Schema.String, + }), + }), + eventName: Schema.String, + isResponseCompatibleWithMobileAndWebSdks: Schema.Boolean, + id: Schema.String, + companyId: Schema.String, + flowId: Schema.String, + connectionId: Schema.String, + capabilityName: Schema.String, + formData: PossibleFormDatas, + form: Schema.Struct({ + name: Schema.String, + description: Schema.String, + category: Schema.String, + components: Schema.Struct({ + fields: Schema.Array( + Schema.Struct({ + type: Schema.String, + key: Schema.String, + label: Schema.String, + }), + ), + }), + }), +}); +interface PingOneCustomHtmlResponseBody + extends Schema.Schema.Type {} +const PingOneCustomHtmlResponseBody: Schema.Schema< + PingOneCustomHtmlResponseBody, + PingOneCustomHtmlResponseBody +> = _PingOneCustomHtmlResponseBody; + +const _PingOneCustomHtmlResponseErrorBody = Schema.Struct({ + interactionId: Schema.String, + companyId: Schema.String, + connectionId: Schema.String, + connectorId: Schema.String, + id: Schema.String, + capabilityName: Schema.String, + errorCategory: Schema.String, + code: Schema.String, + cause: Schema.NullOr(Schema.String), + expected: Schema.Boolean, + message: Schema.String, + httpResponseCode: Schema.Number, + details: Schema.Array( + Schema.Struct({ + rawResponse: Schema.Struct({ + id: Schema.String, + code: Schema.String, + message: Schema.String, + details: Schema.Array( + Schema.Struct({ + code: Schema.String, + target: Schema.String, + message: Schema.String, + innerError: Schema.Struct({ + failuresRemaining: Schema.Number, + }), + }), + ), + }), + statusCode: Schema.Number, + }), + ), + isResponseCompatibleWithMobileAndWebSdks: Schema.Boolean, +}); + +interface PingOneCustomHtmlResponseErrorBody + extends Schema.Schema.Type {} +const PingOneCustomHtmlResponseErrorBody: Schema.Schema< + PingOneCustomHtmlResponseErrorBody, + PingOneCustomHtmlResponseErrorBody +> = _PingOneCustomHtmlResponseErrorBody; + +export { PingOnePathParams, PingOneCustomHtmlResponseBody, PingOneCustomHtmlResponseErrorBody }; diff --git a/e2e/mock-api-v2/src/schemas/open-id-configuration/open-id-configuration-response.schema.ts b/e2e/mock-api-v2/src/schemas/open-id-configuration/open-id-configuration-response.schema.ts new file mode 100644 index 000000000..71bd3ea49 --- /dev/null +++ b/e2e/mock-api-v2/src/schemas/open-id-configuration/open-id-configuration-response.schema.ts @@ -0,0 +1,42 @@ +import { Schema } from '@effect/schema'; + +const _openIdConfigurationResponseSchema = Schema.Struct({ + issuer: Schema.String, + authorization_endpoint: Schema.String, + pushed_authorization_request_endpoint: Schema.String, + token_endpoint: Schema.String, + userinfo_endpoint: Schema.String, + jwks_uri: Schema.String, + end_session_endpoint: Schema.String, + check_session_iframe: Schema.String, + introspection_endpoint: Schema.String, + revocation_endpoint: Schema.String, + device_authorization_endpoint: Schema.String, + claims_parameter_supported: Schema.Boolean, + request_parameter_supported: Schema.Boolean, + request_uri_parameter_supported: Schema.Boolean, + require_pushed_authorization_requests: Schema.Boolean, + scopes_supported: Schema.Array(Schema.String), + response_types_supported: Schema.Array(Schema.String), + response_modes_supported: Schema.Array(Schema.String), + grant_types_supported: Schema.Array(Schema.String), + subject_types_supported: Schema.Array(Schema.String), + id_token_signing_alg_values_supported: Schema.Array(Schema.String), + userinfo_signing_alg_values_supported: Schema.Array(Schema.String), + request_object_signing_alg_values_supported: Schema.Array(Schema.String), + token_endpoint_auth_methods_supported: Schema.Array(Schema.String), + token_endpoint_auth_signing_alg_values_supported: Schema.Array(Schema.String), + claim_types_supported: Schema.Array(Schema.String), + claims_supported: Schema.Array(Schema.String), + code_challenge_methods_supported: Schema.Array(Schema.String), +}); + +interface openIdConfigurationResponseSchema + extends Schema.Schema.Type {} + +const openIdConfigurationResponseSchema: Schema.Schema< + openIdConfigurationResponseSchema, + openIdConfigurationResponseSchema +> = _openIdConfigurationResponseSchema; + +export { openIdConfigurationResponseSchema }; diff --git a/e2e/mock-api-v2/src/schemas/return-success-response-redirect.schema.ts b/e2e/mock-api-v2/src/schemas/return-success-response-redirect.schema.ts new file mode 100644 index 000000000..b3d2562d9 --- /dev/null +++ b/e2e/mock-api-v2/src/schemas/return-success-response-redirect.schema.ts @@ -0,0 +1,42 @@ +import { Schema } from '@effect/schema'; + +const _SuccessResponseRedirect = Schema.Struct({ + interactionId: Schema.String, + companyId: Schema.String, + connectionId: Schema.String, + connectorId: Schema.String, + id: Schema.String, + capabilityName: Schema.String, + environment: Schema.Struct({ + id: Schema.String, + }), + session: Schema.Struct({ + id: Schema.String, + }), + status: Schema.String, // maybe we can make this Literals? + authorizeResponse: Schema.Struct({ + code: Schema.String, + state: Schema.String, + }), + isResponseCompatibleWithMobileAndWebSdks: Schema.Boolean, + success: Schema.Boolean, + resetCookie: Schema.Boolean, + interactionToken: Schema.String, + subFlowSettings: Schema.Struct({ + reactSkUrl: Schema.String, + cssUrl: Schema.NullOr(Schema.String), + cssLinks: Schema.Array(Schema.String), + jsLinks: Schema.Array(Schema.String), + loadingScreenSettings: Schema.String, + }), + _links: Schema.Struct({ + next: Schema.Struct({ + href: Schema.String, + }), + }), +}); + +interface SuccessResponseRedirect extends Schema.Schema.Type {} +const SuccessResponseRedirect: Schema.Schema = + _SuccessResponseRedirect; +export { SuccessResponseRedirect }; diff --git a/e2e/mock-api-v2/src/schemas/token/token.schema.ts b/e2e/mock-api-v2/src/schemas/token/token.schema.ts new file mode 100644 index 000000000..11cadbe1f --- /dev/null +++ b/e2e/mock-api-v2/src/schemas/token/token.schema.ts @@ -0,0 +1,26 @@ +import { Schema } from '@effect/schema'; + +const _TokenRequestBody = Schema.Struct({ + client_id: Schema.String, + code: Schema.String, + grant_type: Schema.Union(Schema.Literal('authorization_code')), + redirect_uri: Schema.String, + code_verifier: Schema.String, +}); + +interface TokenRequestBody extends Schema.Schema.Type {} +const TokenRequestBody: Schema.Schema = _TokenRequestBody; + +const _TokenResponseBody = Schema.Struct({ + access_token: Schema.String, + token_type: Schema.String, //Schema.Union(Schema.Literal('Bearer')), + expires_in: Schema.Number, + refresh_token: Schema.String, + scope: Schema.String, + id_token: Schema.String, +}); + +interface TokenResponseBody extends Schema.Schema.Type {} +const TokenResponseBody: Schema.Schema = _TokenResponseBody; + +export { TokenRequestBody, TokenResponseBody }; diff --git a/e2e/mock-api-v2/src/schemas/userinfo/userinfo.schema.ts b/e2e/mock-api-v2/src/schemas/userinfo/userinfo.schema.ts new file mode 100644 index 000000000..b31b11232 --- /dev/null +++ b/e2e/mock-api-v2/src/schemas/userinfo/userinfo.schema.ts @@ -0,0 +1,19 @@ +import { Schema } from '@effect/schema'; + +const _UserInfoSchema = Schema.Struct({ + sub: Schema.String, + preferred_username: Schema.String, + given_name: Schema.String, + updated_at: Schema.Number, + family_name: Schema.String, + email: Schema.String, + env: Schema.String, + org: Schema.String, + 'p1.region': Schema.String, +}); + +interface UserInfo extends Schema.Schema.Type {} + +const UserInfoSchema: Schema.Schema = _UserInfoSchema; + +export { UserInfoSchema }; diff --git a/e2e/mock-api-v2/src/services/authorize.service.ts b/e2e/mock-api-v2/src/services/authorize.service.ts new file mode 100644 index 000000000..71022fbe8 --- /dev/null +++ b/e2e/mock-api-v2/src/services/authorize.service.ts @@ -0,0 +1,71 @@ +import { Schema } from '@effect/schema'; +import { Context, Effect, Layer, pipe } from 'effect'; +import { HttpError } from 'effect-http'; + +import { getFirstElementAndRespond } from './mock-env-helpers'; +import { Request } from './request.service'; +import { PingOneCustomHtmlResponseBody } from '../schemas/custom-html-template/custom-html-template-response.schema'; + +import { HeaderTypes, QueryTypes } from '../types'; + +type AuthorizeResponseBody = Schema.Schema.Type; + +class Authorize extends Context.Tag('@services/authorize')< + Authorize, + { + handleAuthorize: ( + headers: Headers, + query: Query, + ) => Effect.Effect<{ status: 200; body: AuthorizeResponseBody }, HttpError.HttpError, never>; + } +>() {} + +const authorizeMock = Layer.effect( + Authorize, + Effect.gen(function* () { + const { get } = yield* Request; + + return { + handleAuthorize: (headers, query) => + Effect.gen(function* () { + /** in a mock env lets throw away this **/ + yield* get('/authorize', { + headers, + query, + }); + + return yield* pipe( + query, + getFirstElementAndRespond, + Effect.catchTags({ + NoSuchElementException: (err) => + HttpError.notFound(`failure to get journey from map, ${err}`), + }), + ); + }), + }; + }), +); + +const authorizeLive = Layer.effect( + Authorize, + Effect.gen(function* () { + const { get } = yield* Request; + return { + handleAuthorize: (headers, query) => + Effect.gen(function* () { + const response = yield* get( + '/authorize', + { + headers, + query, + }, + ); + + return { status: 200 as const, body: response }; + }), + }; + }), +); + +export { Authorize, authorizeLive, authorizeMock }; diff --git a/e2e/mock-api-v2/src/services/cookie.service.ts b/e2e/mock-api-v2/src/services/cookie.service.ts new file mode 100644 index 000000000..cb9a940db --- /dev/null +++ b/e2e/mock-api-v2/src/services/cookie.service.ts @@ -0,0 +1,52 @@ +import * as Cookies from '@effect/platform/Cookies'; +import { Effect, Context, Either } from 'effect'; + +import { incrementCookieHeader } from '../helpers/cookie'; +import { HeaderTypes } from '../types'; + +/* + * Define the interface for the Cookie Service + */ +interface CookieService { + writeCookie: ( + headers: HeaderTypes, + interactionToken?: string, + ) => Effect.Effect; +} +const CookieService = Context.GenericTag('CookieService'); + +/* + * Mock out the test servers cookie writing + */ +const cookieServiceTest = CookieService.of({ + writeCookie: (headers, interactionToken?) => { + const cookieOptions: Cookies.Cookie['options'] = { + httpOnly: true, + expires: new Date(Date.now() + 36000), + path: '/', + maxAge: 36000, + // sameSite: "none" add this in when we need it. + }; + return Effect.succeed( + Cookies.setAll(Cookies.empty, [ + ['interactionId', '123', cookieOptions], + ['interactionToken', interactionToken || '456', cookieOptions], + ['stepIndex', incrementCookieHeader(headers), cookieOptions], + ]).pipe( + /** + * `setAll` returns an `Either`. + * Either have two possible values, Left, and Right + * Left is the error channel, Right is a success channel + * In this case, I want to unwrap the either, so I use `getOrThrow` + * if we throw, we failed to make the cookies for some reason + * otherwise we will return the cookie object + * This throws a default error of trying to `get` on a Left + * We can use `getOrThrowWith` to throw our own erroor + */ + Either.getOrThrow, + ), + ); + }, +}); + +export { CookieService, cookieServiceTest }; diff --git a/e2e/mock-api-v2/src/services/custom-html-template.service.ts b/e2e/mock-api-v2/src/services/custom-html-template.service.ts new file mode 100644 index 000000000..a4dce3405 --- /dev/null +++ b/e2e/mock-api-v2/src/services/custom-html-template.service.ts @@ -0,0 +1,73 @@ +import { Context, Effect, Layer } from 'effect'; +import { HttpError } from 'effect-http'; +import { Request } from './request.service'; + +import { CustomHtmlRequestBody, CustomHtmlResponseBody, HeaderTypes, QueryTypes } from '../types'; +import { validateCustomHtmlRequest } from './mock-env-helpers'; + +class CustomHtmlTemplate extends Context.Tag('@services/CustomHtmlTemplate')< + CustomHtmlTemplate, + { + handleCustomHtmlTemplate: ( + headers: Headers, + query: Query, + body: CustomHtmlRequestBody, + ) => Effect.Effect; + } +>() {} + +const mockCustomHtmlTemplate = Layer.effect( + CustomHtmlTemplate, + Effect.gen(function* () { + const { post } = yield* Request; + return { + handleCustomHtmlTemplate: (headers, query, body) => + Effect.gen(function* () { + yield* validateCustomHtmlRequest(body).pipe( + Effect.catchTags({ + NoSuchElementException: () => HttpError.notFound('could not find the element'), + InvalidProtectNode: () => + HttpError.unauthorized('invalid protect node, did not pass validation'), + InvalidUsernamePassword: () => + HttpError.unauthorized('invalid username or password, did not pass validation'), + }), + ); + + const response = yield* post< + typeof headers, + typeof query, + typeof body, + CustomHtmlResponseBody + >('/customHtmlTemplate', { headers, query, body }); + + return response; + }), + }; + }), +); + +const liveCustomHtmlTemplate = Layer.effect( + CustomHtmlTemplate, + Effect.gen(function* () { + const { post } = yield* Request; + return { + handleCustomHtmlTemplate: (headers, query, body) => + Effect.gen(function* () { + const response = yield* post< + typeof headers, + typeof query, + typeof body, + CustomHtmlResponseBody + >('/customHtmlTemplate', { headers, query, body }); + + return response; + }), + }; + }), +); +export { + CustomHtmlTemplate, + mockCustomHtmlTemplate, + liveCustomHtmlTemplate, + CustomHtmlResponseBody, +}; diff --git a/e2e/mock-api-v2/src/services/mock-env-helpers/index.ts b/e2e/mock-api-v2/src/services/mock-env-helpers/index.ts new file mode 100644 index 000000000..83e52eddd --- /dev/null +++ b/e2e/mock-api-v2/src/services/mock-env-helpers/index.ts @@ -0,0 +1,84 @@ +import { Schema } from '@effect/schema'; +import { Array, Effect, Option, pipe } from 'effect'; + +import { UnableToFindNextStep } from '../../errors'; +import { PingOneCustomHtmlRequestBody } from '../../schemas/custom-html-template/custom-html-template-request.schema'; +import { ResponseMapKeys, responseMap } from '../../responses'; + +import { CustomHtmlRequestBody, QueryTypes } from '../../types'; +import { validator } from '../../helpers/match'; + +type DavinciFormData = Schema.Schema.Type< + typeof PingOneCustomHtmlRequestBody +>['parameters']['data']; + +/** + * Given data in the shape of Ping's Request formData.value + * We will dive into the object by accessing `formData` + * And then `value` off the object. + * + */ +const mapDataToValue = (data: Option.Option) => + pipe( + data, + Option.map((data) => data.formData), + Option.map((data) => data.value), + ); + +const getArrayFromResponseMap = (query: QueryTypes) => + Effect.succeed(responseMap[query?.acr_values as ResponseMapKeys]); +/** + * A helper function that will use `acr_values` from query object + * to grab the array from the `responseMap`. + */ +const getNextStep = (bool: boolean, query: QueryTypes) => + Effect.if(bool, { + onTrue: () => getArrayFromResponseMap(query), + onFalse: () => Effect.fail(new UnableToFindNextStep()), + }); + +/** + * Get the first element in the responseMap + */ +const getFirstElement = (arr: (typeof responseMap)[ResponseMapKeys]) => + Effect.succeed(pipe(Array.headNonEmpty(arr))); + +/** + * Gets the first element from the responseMap + * And then creates a basic HttpResponse object that + * will succeed + * + */ +const getFirstElementAndRespond = (query: QueryTypes) => + pipe( + Option.fromNullable(query?.acr_values), + Option.map((acr) => responseMap[acr as ResponseMapKeys]), + Effect.flatMap(getFirstElement), + Effect.map((body) => ({ + status: 200 as const, + body, + })), + ); + +/** + * helper function that dives into a request body for CustomHtmlRequestBody + * and will apply a validator function to ensure the request passes validation + */ +const validateCustomHtmlRequest = (body: Body) => + pipe( + body, + Option.fromNullable, + Option.map((body) => body.parameters), + Option.map((body) => body.data), + Option.map((data) => data.formData), + Option.map((data) => data.value), + Effect.flatMap(validator), + ); + +export { + getNextStep, + getFirstElementAndRespond, + getArrayFromResponseMap, + mapDataToValue, + validateCustomHtmlRequest, +}; diff --git a/e2e/mock-api-v2/src/services/mock-env-helpers/tests/index.test.ts b/e2e/mock-api-v2/src/services/mock-env-helpers/tests/index.test.ts new file mode 100644 index 000000000..2b7caf46d --- /dev/null +++ b/e2e/mock-api-v2/src/services/mock-env-helpers/tests/index.test.ts @@ -0,0 +1,74 @@ +import { it, expect } from '@effect/vitest'; +import { Effect, Option } from 'effect'; +import { + getArrayFromResponseMap, + getFirstElementAndRespond, + getNextStep, + mapDataToValue, + validateCustomHtmlRequest, +} from '..'; +import { CustomHtmlRequestBody, QueryTypes } from '../../../types'; +import { PingProtectNode } from '../../../responses/custom-html-template/ping-protect-node'; +import { UsernamePassword } from '../../../responses/username-password'; + +it('should map data to value in a DavinciFormData', () => { + const data = Option.some({ + actionKey: 'the action key', + formData: { + value: { + username: 'ryan', + password: 'password', + }, + }, + }); + const result = mapDataToValue(data); + expect(Option.some({ username: 'ryan', password: 'password' })).toEqual(result); +}); + +it('should get an array from the response map', () => + Effect.gen(function* () { + const query: QueryTypes = { acr_values: 'UsernamePassword' }; + const responseMap = { + UsernamePassword: [PingProtectNode], + }; + const result = yield* getArrayFromResponseMap(query); + expect(responseMap[0]).toEqual(result); + })); + +it('should getNextStep', () => + Effect.gen(function* () { + const query: QueryTypes = { acr_values: 'UsernamePassword' }; + const result = yield* getNextStep(true, query); + + expect(UsernamePassword).toEqual(result); + })); + +it('should get first element and respond', () => + Effect.gen(function* () { + const query: QueryTypes = { acr_values: 'UsernamePassword' }; + const result = yield* getFirstElementAndRespond(query); + expect(Effect.succeed({ status: 200 as const, body: UsernamePassword })).toEqual(result); + })); + +it('should validateCustomHtml', () => + Effect.gen(function* () { + const body: CustomHtmlRequestBody = { + id: 'id', + eventName: 'continue', + interactionId: '13213', + parameters: { + eventType: 'event type', + data: { + actionKey: 'actionKey', + formData: { + value: { + pingprotectsdk: '12312412412', + }, + }, + }, + }, + }; + const result = yield* validateCustomHtmlRequest(body); + + expect(true).toEqual(result); + })); diff --git a/e2e/mock-api-v2/src/services/request.service.ts b/e2e/mock-api-v2/src/services/request.service.ts new file mode 100644 index 000000000..f8390eabd --- /dev/null +++ b/e2e/mock-api-v2/src/services/request.service.ts @@ -0,0 +1,85 @@ +import { Effect, Context, pipe, Layer } from 'effect'; +import { HttpError } from 'effect-http'; + +import { getElementFromCookie } from '../helpers/cookie'; +import { getNextStep } from './mock-env-helpers'; + +import { HeaderTypes, QueryTypes } from '../types'; + +type Init = { + headers: Headers; + query: Query; + body?: Body; +}; +const liveGet = (route, init): Effect.Effect => + Effect.tryPromise({ + try: (signal) => fetch(route, { signal, method: 'GET', ...init }), + catch: (err) => HttpError.unauthorized(`failure to fetch ${route}: \n ${err}`), + }).pipe( + Effect.tryMapPromise({ + try: (response) => response.json(), + catch: () => HttpError.internalServerError('failure to parse response body'), + }), + ); + +const livePost = < + Query extends QueryTypes, + Headers extends HeaderTypes, + RequestBody = any, + ResponseBody = any, +>( + route, + init: Init, +): Effect.Effect => + Effect.tryPromise({ + try: (signal) => + fetch(route, { + signal, + method: 'POST', + body: JSON.stringify(init.body), + headers: init.headers, + } as RequestInit), + catch: () => HttpError.badRequest(`failure to post to route: ${route}`), + }).pipe( + Effect.tryMapPromise({ + try: (response) => response.json(), + catch: () => + HttpError.internalServerError(`failure to make response from Post request to: ${route}`), + }), + ); + +function mockGet( + route, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + init: Init, +): Effect.Effect { + return Effect.tryPromise({ + try: () => Promise.resolve({}) as unknown as PromiseLike, + catch: () => HttpError.internalServerError(`failure to fetch ${route}:`), + }); +} + +function mockPost( + route, + { headers, query }: Init, +): Effect.Effect { + return pipe( + getNextStep(true, query), + Effect.andThen((arr) => getElementFromCookie(arr, headers)), + ) as unknown as Effect.Effect; +} +class Request extends Context.Tag('@services/request')< + Request, + { get: typeof mockGet; post: typeof mockPost } +>() {} + +const mockRequest = Layer.succeed(Request, { + get: mockGet, + post: mockPost, +}); + +const liveRequest = Layer.succeed(Request, { + get: liveGet, + post: livePost, +}); +export { Request, mockRequest, liveRequest }; diff --git a/e2e/mock-api-v2/src/services/tests/authorize.service.test.ts b/e2e/mock-api-v2/src/services/tests/authorize.service.test.ts new file mode 100644 index 000000000..4636ce56f --- /dev/null +++ b/e2e/mock-api-v2/src/services/tests/authorize.service.test.ts @@ -0,0 +1,33 @@ +import { it, expect } from '@effect/vitest'; +import { Effect, Layer } from 'effect'; +import { Authorize, authorizeMock } from '../authorize.service'; +import { mockRequest } from '../request.service'; +import { PingProtectNode } from '../../responses/custom-html-template/ping-protect-node'; + +const queryParams = { + response_mode: 'pi.flow', + client_id: '724ec718-c41c-4d51-98b0-84a583f450f9', + redirect_uri: 'http%3A%2F%2Flocalhost%3A8443%2Fcallback', + response_type: 'code', + scope: 'openid%20profile%20email', + state: 'MTg1MjI5MTEzMTIzMjQwMjU5OTcxMjAxMjI4NDIxNDA0MzE4MTA4MjQ1', + code_challenge: 'E8YevbSo7Y8jLE43QN3v8e8aVeD-ek-LjG6AcFLP5rg', + code_challenge_method: 'S256', + code: 'test ', + acr_values: 'UsernamePassword', +}; +const headers = { + cookie: undefined, +}; + +it.effect('should handle authorize service', () => + Effect.gen(function* () { + const { handleAuthorize } = yield* Authorize; + const result = yield* handleAuthorize(headers, queryParams); + + expect(result).toEqual({ + status: 200 as const, + body: PingProtectNode, + }); + }).pipe(Effect.provide(Layer.provideMerge(authorizeMock, mockRequest))), +); diff --git a/e2e/mock-api-v2/src/services/tests/cookie.service.test.ts b/e2e/mock-api-v2/src/services/tests/cookie.service.test.ts new file mode 100644 index 000000000..afe210e05 --- /dev/null +++ b/e2e/mock-api-v2/src/services/tests/cookie.service.test.ts @@ -0,0 +1,36 @@ +import { it, expect } from '@effect/vitest'; +import { CookieService, cookieServiceTest } from '../cookie.service'; +import { Effect, Either } from 'effect'; +import { Cookies } from '@effect/platform'; + +beforeEach(() => { + vi.useFakeTimers(); + const date = new Date(2000, 1, 1, 13); + vi.setSystemTime(date); +}); +afterEach(() => { + vi.useRealTimers(); +}); + +it.effect('should write cookies', () => + Effect.gen(function* () { + const cookieOptions: Cookies.Cookie['options'] = { + httpOnly: true, + expires: new Date(Date.now() + 36000), + path: '/', + maxAge: 36000, + // sameSite: "none" add this in when we need it. + }; + const { writeCookie } = yield* CookieService; + + const cookie = yield* writeCookie({ cookie: 'stepIndex=2' }, '123456'); + + const expected = Cookies.setAll(Cookies.empty, [ + ['interactionId', '123', cookieOptions], + ['interactionToken', '123456', cookieOptions], + ['stepIndex', '3', cookieOptions], + ]); + + expect(cookie).toEqual(Either.getOrNull(expected)); + }).pipe(Effect.provideService(CookieService, cookieServiceTest)), +); diff --git a/e2e/mock-api-v2/src/services/tests/custom-html-template.service.test.ts b/e2e/mock-api-v2/src/services/tests/custom-html-template.service.test.ts new file mode 100644 index 000000000..490fad2d4 --- /dev/null +++ b/e2e/mock-api-v2/src/services/tests/custom-html-template.service.test.ts @@ -0,0 +1,75 @@ +import { it, expect } from '@effect/vitest'; +import { CustomHtmlTemplate, mockCustomHtmlTemplate } from '../custom-html-template.service'; +import { Effect, Exit, Layer } from 'effect'; +import { mockRequest } from '../request.service'; +import { UsernamePassword } from '../../responses/username-password'; + +const queryParams = { + response_mode: 'pi.flow', + client_id: '724ec718-c41c-4d51-98b0-84a583f450f9', + redirect_uri: 'http%3A%2F%2Flocalhost%3A8443%2Fcallback', + response_type: 'code', + scope: 'openid%20profile%20email', + state: 'MTg1MjI5MTEzMTIzMjQwMjU5OTcxMjAxMjI4NDIxNDA0MzE4MTA4MjQ1', + code_challenge: 'E8YevbSo7Y8jLE43QN3v8e8aVeD-ek-LjG6AcFLP5rg', + code_challenge_method: 'S256', + code: 'test ', + acr_values: 'UsernamePassword', +}; +const headers = { + cookie: 'stepIndex=1', +}; +const body = { + id: 'cq77vwelou', + eventName: 'continue', + interactionId: '18a833b0-32e8-4e81-aba4-85d5e6f62077', + parameters: { + eventType: 'submit', + data: { + actionKey: 'SIGNON', + formData: { + value: { + pingprotectsdk: '123432432423', + }, + }, + }, + }, +}; +it.effect('should return index 1 of responseMap with customHtmlHandler', () => + Effect.gen(function* () { + const { handleCustomHtmlTemplate } = yield* CustomHtmlTemplate; + const result = yield* handleCustomHtmlTemplate(headers, queryParams, body); + + expect(result).toEqual(UsernamePassword); + }).pipe(Effect.provide(Layer.provideMerge(mockCustomHtmlTemplate, mockRequest))), +); + +it.effect('should return error', () => + Effect.gen(function* () { + const body = { + id: 'cq77vwelou', + eventName: 'continue', + interactionId: '18a833b0-32e8-4e81-aba4-85d5e6f62077', + parameters: { + eventType: 'submit', + data: { + actionKey: 'SIGNON', + formData: { + value: { + pingprotectsdk: '', + }, + }, + }, + }, + }; + + const { handleCustomHtmlTemplate } = yield* CustomHtmlTemplate; + const result = yield* handleCustomHtmlTemplate( + { cookie: 'stepIndex=1' }, + queryParams, + body, + ).pipe(Effect.exit); + + expect(result).toEqual(Exit.fail('unauthorized')); + }).pipe(Effect.provide(Layer.provideMerge(mockCustomHtmlTemplate, mockRequest))), +); diff --git a/e2e/mock-api-v2/src/services/tests/request.service.test.ts b/e2e/mock-api-v2/src/services/tests/request.service.test.ts new file mode 100644 index 000000000..f7097046a --- /dev/null +++ b/e2e/mock-api-v2/src/services/tests/request.service.test.ts @@ -0,0 +1,54 @@ +import { it, expect } from '@effect/vitest'; +import { Request, mockRequest } from '../request.service'; +import { Effect } from 'effect'; +import { CustomHtmlResponseBody } from '../custom-html-template.service'; +import { UsernamePassword } from '../../responses/username-password'; + +it('should make a get request', () => + Effect.gen(function* () { + const { get } = yield* Request; + const result = yield* get('/myroute', { headers: null, query: null }); + + expect(result).toEqual({}); + })); + +it('should make a post request', () => + Effect.gen(function* () { + const queryParams = { + response_mode: 'pi.flow', + client_id: '724ec718-c41c-4d51-98b0-84a583f450f9', + redirect_uri: 'http%3A%2F%2Flocalhost%3A8443%2Fcallback', + response_type: 'code', + scope: 'openid%20profile%20email', + state: 'MTg1MjI5MTEzMTIzMjQwMjU5OTcxMjAxMjI4NDIxNDA0MzE4MTA4MjQ1', + code_challenge: 'E8YevbSo7Y8jLE43QN3v8e8aVeD-ek-LjG6AcFLP5rg', + code_challenge_method: 'S256', + code: 'test ', + acr_values: 'UsernamePassword', + }; + const body = { + id: 'cq77vwelou', + eventName: 'continue', + interactionId: '18a833b0-32e8-4e81-aba4-85d5e6f62077', + parameters: { + eventType: 'submit', + data: { + actionKey: 'SIGNON', + formData: { + value: { + pingprotectsdk: '123432432423', + }, + }, + }, + }, + }; + const { post } = yield* Request; + const result = yield* post< + { cookie: 'somecookie' }, + typeof queryParams, + typeof body, + CustomHtmlResponseBody + >('/myroute', { headers: null, query: null, body }); + + expect(result).toEqual(UsernamePassword); + }).pipe(Effect.provide(mockRequest))); diff --git a/e2e/mock-api-v2/src/services/tests/token.service.test.ts b/e2e/mock-api-v2/src/services/tests/token.service.test.ts new file mode 100644 index 000000000..212670a39 --- /dev/null +++ b/e2e/mock-api-v2/src/services/tests/token.service.test.ts @@ -0,0 +1,23 @@ +import { it, expect } from '@effect/vitest'; +import { Tokens, mockTokens } from '../tokens.service'; +import { Effect, Exit, Layer } from 'effect'; +import { mockRequest } from '../request.service'; +import { tokenResponseBody } from '../../responses/token/token'; + +it.effect('should return tokens', () => + Effect.gen(function* () { + const { getTokens } = yield* Tokens; + const result = yield* getTokens({ cookie: 'the cookie' }); + + expect(result).toEqual(tokenResponseBody); + }).pipe(Effect.provide(Layer.provideMerge(mockTokens, mockRequest))), +); + +it.effect('should return error', () => + Effect.gen(function* () { + const { getTokens } = yield* Tokens; + const result = yield* getTokens({ cookie: 'the cookie' }).pipe(Effect.flip, Effect.exit); + + expect(result).toEqual(Exit.fail('failed')); + }).pipe(Effect.provide(Layer.provideMerge(mockTokens, mockRequest))), +); diff --git a/e2e/mock-api-v2/src/services/tests/userinfo.service.test.ts b/e2e/mock-api-v2/src/services/tests/userinfo.service.test.ts new file mode 100644 index 000000000..548150c75 --- /dev/null +++ b/e2e/mock-api-v2/src/services/tests/userinfo.service.test.ts @@ -0,0 +1,14 @@ +import { expect, it } from '@effect/vitest'; +import { UserInfo, userInfoMock } from '../userinfo.service'; +import { userInfoResponse } from '../../responses/userinfo/userinfo'; +import { Effect } from 'effect'; + +it.effect('should get userinfo', () => + Effect.gen(function* () { + const { getUserInfo } = yield* UserInfo; + + const result = yield* getUserInfo('mytoken', {}); + + expect(result).toEqual(userInfoResponse); + }).pipe(Effect.provideService(UserInfo, userInfoMock)), +); diff --git a/e2e/mock-api-v2/src/services/tokens.service.ts b/e2e/mock-api-v2/src/services/tokens.service.ts new file mode 100644 index 000000000..7405559fa --- /dev/null +++ b/e2e/mock-api-v2/src/services/tokens.service.ts @@ -0,0 +1,61 @@ +import { Schema } from '@effect/schema'; +import { Context, Effect, Layer } from 'effect'; +import { HttpError } from 'effect-http'; + +import { Request } from './request.service'; +import { tokenResponseBody } from '../responses/token/token'; +import { TokenResponseBody } from '../schemas/token/token.schema'; + +import { HeaderTypes } from '../types'; + +type TokensResponseBody = Schema.Schema.Type; + +class Tokens extends Context.Tag('@services/Tokens')< + Tokens, + { + getTokens: ( + headers: Headers, + ) => Effect.Effect; + } +>() {} + +const mockTokens = Layer.effect( + Tokens, + Effect.gen(function* () { + const { get } = yield* Request; + return { + getTokens: (headers) => + Effect.gen(function* () { + // throw away our get call in mock env; + yield* get('/tokens', { + headers, + query: null, + }); + + const response = yield* Effect.tryPromise({ + try: () => Promise.resolve(tokenResponseBody), + catch: () => HttpError.unauthorized('unable to retrieve tokens'), + }); + return response; + }), + }; + }), +); + +const liveTokens = Layer.effect( + Tokens, + Effect.gen(function* () { + const { get } = yield* Request; + return { + getTokens: (headers) => + Effect.gen(function* () { + return yield* get('/tokens', { + headers, + query: null, + }); + }), + }; + }), +); + +export { mockTokens, liveTokens, Tokens }; diff --git a/e2e/mock-api-v2/src/services/userinfo.service.ts b/e2e/mock-api-v2/src/services/userinfo.service.ts new file mode 100644 index 000000000..3a60fa8c9 --- /dev/null +++ b/e2e/mock-api-v2/src/services/userinfo.service.ts @@ -0,0 +1,54 @@ +import { Schema } from '@effect/schema'; +import { Effect, Context } from 'effect'; +import { HttpError } from 'effect-http'; + +import { userInfoResponse } from '../responses/userinfo/userinfo'; +import { UserInfoSchema } from '../schemas/userinfo/userinfo.schema'; + +/*** + * This file should be converted to a Layer that uses Request + */ + +type UserInfoResponse = Schema.Schema.Type; + +function live(bearerToken: string, headers: Headers) { + return Effect.tryPromise({ + try: (signal) => + fetch('/userinfo', { + signal, + method: 'GET', + headers: { + Authorization: `Bearer ${bearerToken}`, + ...headers, + }, + }), + catch: () => HttpError.unauthorized('failure to get userinfo'), + }).pipe( + Effect.tryMapPromise({ + try: (response) => response.json() as PromiseLike, + catch: () => HttpError.internalServerError('failure to parse the response body of user info'), + }), + ); +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function mock(bearerToken: string, headers: Headers) { + return Effect.tryPromise({ + try: () => Promise.resolve(userInfoResponse), + catch: () => HttpError.unauthorized('failure to get user info'), + }); +} +class UserInfo extends Context.Tag('@services/userinfo')< + UserInfo, + { getUserInfo: typeof live } +>() {} + +const userInfoLive = UserInfo.of({ + getUserInfo: live, +}); + +const userInfoMock = UserInfo.of({ + getUserInfo: mock, +}); + +export { UserInfo, userInfoLive, userInfoMock }; diff --git a/e2e/mock-api-v2/src/spec.ts b/e2e/mock-api-v2/src/spec.ts new file mode 100644 index 000000000..14af203b2 --- /dev/null +++ b/e2e/mock-api-v2/src/spec.ts @@ -0,0 +1,23 @@ +import { pipe } from 'effect'; +import { Schema } from '@effect/schema'; +import { Api } from 'effect-http'; + +import { openidConfiguration } from './endpoints/open-id-configuration.endpoint'; +import { davinciAuthorize } from './endpoints/davinci-authorize.endpoint'; +import { customHtmlEndPoint } from './endpoints/custom-html.endpoint'; +import { pingOneToken } from './endpoints/token.endpoint'; +import { userInfo } from './endpoints/userinfo.endpoint'; + +const apiSpec = pipe( + Api.make({ title: 'MockApi' }), + openidConfiguration, + davinciAuthorize, + customHtmlEndPoint, + pingOneToken, + userInfo, + Api.addEndpoint( + pipe(Api.get('HealthCheck', '/healthcheck').pipe(Api.setResponseBody(Schema.String))), + ), +); + +export { apiSpec }; diff --git a/e2e/mock-api-v2/src/types/index.ts b/e2e/mock-api-v2/src/types/index.ts new file mode 100644 index 000000000..ffada56f2 --- /dev/null +++ b/e2e/mock-api-v2/src/types/index.ts @@ -0,0 +1,23 @@ +import { Schema } from '@effect/schema'; +import { DavinciAuthorizeHeaders, DavinciAuthorizeQuery } from '../schemas/authorize.schema'; +import { + PingOneCustomHtmlRequestBody, + PingOneRequestQuery, +} from '../schemas/custom-html-template/custom-html-template-request.schema'; +import { PingOneCustomHtmlResponseBody } from '../schemas/custom-html-template/custom-html-template-response.schema'; +import { SuccessResponseRedirect } from '../schemas/return-success-response-redirect.schema'; + +type QueryTypes = + | Schema.Schema.Type + | Schema.Schema.Type + | null; + +type HeaderTypes = Schema.Schema.Type | null; + +type CustomHtmlResponseBody = + | Schema.Schema.Type + | Schema.Schema.Type; + +type CustomHtmlRequestBody = Schema.Schema.Type; + +export { CustomHtmlRequestBody, CustomHtmlResponseBody, QueryTypes, HeaderTypes }; diff --git a/e2e/mock-api-v2/tsconfig.app.json b/e2e/mock-api-v2/tsconfig.app.json new file mode 100644 index 000000000..c3b007648 --- /dev/null +++ b/e2e/mock-api-v2/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "Node16", + "target": "ES2020", + "types": ["node"], + "exactOptionalPropertyTypes": true, + "strictNullChecks": true, + "noErrorTruncation": true + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/e2e/mock-api-v2/tsconfig.json b/e2e/mock-api-v2/tsconfig.json new file mode 100644 index 000000000..c1e2dd4e8 --- /dev/null +++ b/e2e/mock-api-v2/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/e2e/mock-api-v2/tsconfig.spec.json b/e2e/mock-api-v2/tsconfig.spec.json new file mode 100644 index 000000000..fccd5ec57 --- /dev/null +++ b/e2e/mock-api-v2/tsconfig.spec.json @@ -0,0 +1,29 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "ES2020", + "moduleResolution": "NodeNext", + "target": "ES2020", + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "node", + "vitest" + ] + }, + "include": [ + "vite.config.ts", + "vitest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/e2e/mock-api-v2/vite.config.ts b/e2e/mock-api-v2/vite.config.ts new file mode 100644 index 000000000..7e02da0d7 --- /dev/null +++ b/e2e/mock-api-v2/vite.config.ts @@ -0,0 +1,31 @@ +/// +import { defineConfig } from 'vite'; + +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + +export default defineConfig({ + root: __dirname, + cacheDir: '../../node_modules/.vite/e2e/mock-api-v2', + + plugins: [nxViteTsPaths()], + + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + + test: { + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/e2e/mock-api-v2', + provider: 'v8', + }, + }, +}); diff --git a/nx.json b/nx.json index d18166490..a86fc66eb 100644 --- a/nx.json +++ b/nx.json @@ -38,6 +38,11 @@ "cache": true, "dependsOn": ["^build"], "inputs": ["default", "^default"] + }, + "@nx/esbuild:esbuild": { + "cache": true, + "dependsOn": ["^build"], + "inputs": ["production", "^production"] } }, "parallel": 1, diff --git a/package-lock.json b/package-lock.json index 139caf869..b2e14ed89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,14 @@ "@commitlint/cli": "^19.1.0", "@commitlint/config-conventional": "^19.1.0", "@commitlint/prompt": "^19.1.0", + "@effect/language-service": "^0.1.0", + "@effect/platform": "^0.58.27", + "@effect/platform-node": "^0.53.26", + "@effect/schema": "^0.68.23", + "@effect/vitest": "^0.6.7", "@nrwl/devkit": "18.2.4", "@nx/devkit": "18.2.4", + "@nx/esbuild": "18.2.4", "@nx/eslint": "18.2.4", "@nx/eslint-plugin": "18.2.4", "@nx/express": "18.2.4", @@ -22,6 +28,7 @@ "@nx/node": "18.2.4", "@nx/playwright": "18.2.4", "@nx/vite": "18.2.4", + "@nx/web": "18.2.4", "@nx/workspace": "18.2.4", "@playwright/test": "^1.43.0", "@swc-node/register": "1.8.0", @@ -40,16 +47,22 @@ "cors": "^2.8.5", "cz-conventional-changelog": "^3.3.0", "cz-git": "^1.6.1", + "effect": "^3.5.3", + "effect-http": "^0.73.0", + "effect-http-node": "^0.16.1", + "esbuild": "^0.19.2", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-playwright": "^1.5.1", "eslint-plugin-prettier": "^5.1.3", "express": "^4.18.3", + "fast-check": "^3.19.0", "husky": "^8.0.3", "jsdom": "22.1.0", "jsonc-eslint-parser": "^2.1.0", "lint-staged": "^13.2.2", + "mkcert": "^3.2.0", "npm-cli-login": "^1.0.0", "nx": "18.2.4", "nx-cloud": "18.0.0", @@ -2421,10 +2434,95 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@effect/language-service": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@effect/language-service/-/language-service-0.1.0.tgz", + "integrity": "sha512-BnlM8LlaqCAYgdRfxlbR7gXGh/FD1scL1fPgNVJEPoOM08od1jtJz+iKhwfaud8TPnnhZR+TED2h5ynjanLeCQ==", + "dev": true + }, + "node_modules/@effect/platform": { + "version": "0.58.27", + "resolved": "https://registry.npmjs.org/@effect/platform/-/platform-0.58.27.tgz", + "integrity": "sha512-J/5ykWaFNo7rYC0b5/f/v8IC8i6QaOHM+eyUm7YnI01TeamHNvsVd9MB6YFjLBlgkogrKpOvcYDI+2jwyr0Mrw==", + "dev": true, + "dependencies": { + "find-my-way-ts": "^0.1.4", + "multipasta": "^0.2.2", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "@effect/schema": "^0.68.23", + "effect": "^3.5.3" + } + }, + "node_modules/@effect/platform-node": { + "version": "0.53.26", + "resolved": "https://registry.npmjs.org/@effect/platform-node/-/platform-node-0.53.26.tgz", + "integrity": "sha512-TPjRJqgp7a3DC30/f1U4vPEH0EgW336rbr+9+0DCYhVkArwVsrZBADNLZLWGHOMfVCTV0/DdCrTzvjdQ0pfG9w==", + "dev": true, + "dependencies": { + "@effect/platform-node-shared": "^0.8.26", + "mime": "^3.0.0", + "undici": "^6.19.2", + "ws": "^8.17.1" + }, + "peerDependencies": { + "@effect/platform": "^0.58.27", + "effect": "^3.5.3" + } + }, + "node_modules/@effect/platform-node-shared": { + "version": "0.8.26", + "resolved": "https://registry.npmjs.org/@effect/platform-node-shared/-/platform-node-shared-0.8.26.tgz", + "integrity": "sha512-c7yYFvQwse5ar8JZitBM1fTGAQGfBQUqMRKVxKYux4GDMKw6oaZ8g7eQf9PRpMCxIdBMZlPilIablSJ0DtoPVQ==", + "dev": true, + "dependencies": { + "@parcel/watcher": "^2.4.1", + "multipasta": "^0.2.2" + }, + "peerDependencies": { + "@effect/platform": "^0.58.27", + "effect": "^3.5.3" + } + }, + "node_modules/@effect/platform-node/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@effect/schema": { + "version": "0.68.23", + "resolved": "https://registry.npmjs.org/@effect/schema/-/schema-0.68.23.tgz", + "integrity": "sha512-yG8PShEII70yunOO4fKUJPTSzmwLDOrkGI3zc9O5LEgA93TPQKu/Y9jASxgeSMa7Wx7UI8fd9no8oheapfK9Yw==", + "dev": true, + "dependencies": { + "fast-check": "^3.19.0" + }, + "peerDependencies": { + "effect": "^3.5.3" + } + }, + "node_modules/@effect/vitest": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@effect/vitest/-/vitest-0.6.7.tgz", + "integrity": "sha512-zBvZBHjn0W7FFn7fOzK79WPk41Mt4PjZUtjbJKVevvYZQBk2so2nWrgWeV2J098YZBjTyG6nWZiW7RfFfwV7cw==", + "dev": true, + "peerDependencies": { + "effect": "^3.5.3", + "vitest": "^1.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", "cpu": [ "ppc64" ], @@ -2438,9 +2536,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -2454,9 +2552,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -2470,9 +2568,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -2486,9 +2584,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -2502,9 +2600,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -2518,9 +2616,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -2534,9 +2632,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -2550,9 +2648,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -2566,9 +2664,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -2582,9 +2680,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -2598,9 +2696,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -2614,9 +2712,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -2630,9 +2728,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -2646,9 +2744,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -2662,9 +2760,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -2678,9 +2776,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -2694,9 +2792,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -2710,9 +2808,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -2726,9 +2824,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -2742,9 +2840,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -2758,9 +2856,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -2774,9 +2872,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -3763,6 +3861,15 @@ "@nx/devkit": "18.2.4" } }, + "node_modules/@nrwl/esbuild": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@nrwl/esbuild/-/esbuild-18.2.4.tgz", + "integrity": "sha512-qwKYvdd0qXQTX4Oid4Y8ujVq09Ago1xTJaWz8Kk19Hj2HzW1lMgU415cPfp5SSuxe2i+uBVVNuDyMRsK4EDgyA==", + "dev": true, + "dependencies": { + "@nx/esbuild": "18.2.4" + } + }, "node_modules/@nrwl/eslint-plugin-nx": { "version": "18.2.4", "resolved": "https://registry.npmjs.org/@nrwl/eslint-plugin-nx/-/eslint-plugin-nx-18.2.4.tgz", @@ -3839,6 +3946,15 @@ "@nx/vite": "18.2.4" } }, + "node_modules/@nrwl/web": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@nrwl/web/-/web-18.2.4.tgz", + "integrity": "sha512-Sew9Wpo9aWSgcvrW4OTejnLpk/QlnqmfUmfHOG4gdnn4p8XNLAYEASH1OqFf5f+wPrTUm/iUjc/iQAyumkQijQ==", + "dev": true, + "dependencies": { + "@nx/web": "18.2.4" + } + }, "node_modules/@nrwl/workspace": { "version": "18.2.4", "resolved": "https://registry.npmjs.org/@nrwl/workspace/-/workspace-18.2.4.tgz", @@ -3867,6 +3983,73 @@ "nx": ">= 16 <= 18" } }, + "node_modules/@nx/esbuild": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@nx/esbuild/-/esbuild-18.2.4.tgz", + "integrity": "sha512-xHhMefA9zyneymFUf7zhd5A7wAXSATZilNc5/dxfupsJYo17zMuRMwAHhP58Z8SfRsc5CRv3E4saXhBU1CMtjA==", + "dev": true, + "dependencies": { + "@nrwl/esbuild": "18.2.4", + "@nx/devkit": "18.2.4", + "@nx/js": "18.2.4", + "chalk": "^4.1.0", + "fast-glob": "3.2.7", + "fs-extra": "^11.1.0", + "tsconfig-paths": "^4.1.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "esbuild": "~0.19.2" + }, + "peerDependenciesMeta": { + "esbuild": { + "optional": true + } + } + }, + "node_modules/@nx/esbuild/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nx/esbuild/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@nx/esbuild/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, "node_modules/@nx/eslint": { "version": "18.2.4", "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-18.2.4.tgz", @@ -4358,6 +4541,64 @@ "vitest": "^1.3.1" } }, + "node_modules/@nx/web": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@nx/web/-/web-18.2.4.tgz", + "integrity": "sha512-cNrT6hj0EZkbfUCI085rN2k5ASa1MmufvKzANU9e1lnRvS38bVt07Is1Mcyzg1LUnNM7UVNXLZsrs4kN/QPApQ==", + "dev": true, + "dependencies": { + "@nrwl/web": "18.2.4", + "@nx/devkit": "18.2.4", + "@nx/js": "18.2.4", + "chalk": "^4.1.0", + "detect-port": "^1.5.1", + "http-server": "^14.1.0", + "tslib": "^2.3.0" + } + }, + "node_modules/@nx/web/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nx/web/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@nx/web/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, "node_modules/@nx/workspace": { "version": "18.2.4", "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-18.2.4.tgz", @@ -4416,35 +4657,308 @@ "node": ">=7.0.0" } }, - "node_modules/@phenomnomnominal/tsquery": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-5.0.1.tgz", - "integrity": "sha512-3nVv+e2FQwsW8Aw6qTU6f+1rfcJ3hrcnvH/mu9i8YhxO+9sqbOfpL8m6PbET5+xKOlz/VSbp0RoYWYCtIsnmuA==", + "node_modules/@parcel/watcher": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", + "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", "dev": true, "dependencies": { - "esquery": "^1.4.0" + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" }, - "peerDependencies": { - "typescript": "^3 || ^4 || ^5" - } - }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/@playwright/test": { - "version": "1.43.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz", - "integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==", - "dev": true, + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.4.1", + "@parcel/watcher-darwin-arm64": "2.4.1", + "@parcel/watcher-darwin-x64": "2.4.1", + "@parcel/watcher-freebsd-x64": "2.4.1", + "@parcel/watcher-linux-arm-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-musl": "2.4.1", + "@parcel/watcher-linux-x64-glibc": "2.4.1", + "@parcel/watcher-linux-x64-musl": "2.4.1", + "@parcel/watcher-win32-arm64": "2.4.1", + "@parcel/watcher-win32-ia32": "2.4.1", + "@parcel/watcher-win32-x64": "2.4.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz", + "integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz", + "integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz", + "integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz", + "integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz", + "integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz", + "integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz", + "integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz", + "integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz", + "integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz", + "integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz", + "integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz", + "integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@phenomnomnominal/tsquery": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-5.0.1.tgz", + "integrity": "sha512-3nVv+e2FQwsW8Aw6qTU6f+1rfcJ3hrcnvH/mu9i8YhxO+9sqbOfpL8m6PbET5+xKOlz/VSbp0RoYWYCtIsnmuA==", + "dev": true, + "dependencies": { + "esquery": "^1.4.0" + }, + "peerDependencies": { + "typescript": "^3 || ^4 || ^5" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@playwright/test": { + "version": "1.43.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.43.1.tgz", + "integrity": "sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==", + "dev": true, "dependencies": { "playwright": "1.43.1" }, @@ -7454,6 +7968,24 @@ } ] }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -8607,6 +9139,15 @@ "node": ">= 0.10" } }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -9038,6 +9579,18 @@ "node": ">=8" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -9228,11 +9781,71 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, + "node_modules/effect": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.5.3.tgz", + "integrity": "sha512-emKAbdsB4jTeppa6fdfdWGhbEjKdvr1xfltpGIwiR46mD97JvWlpbrTIdqDCCizuMbxVys6YrSjKL7aHHa93Gg==", + "dev": true + }, + "node_modules/effect-http": { + "version": "0.73.0", + "resolved": "https://registry.npmjs.org/effect-http/-/effect-http-0.73.0.tgz", + "integrity": "sha512-VEBhvyP/rD4JU5OtbSE1zZE9B+s+KaPXaSL7DXtP46QjTItjRUuYAqM7xzDgonmYN9kSY3JpxNuQctB0wvCQEA==", + "dev": true, + "dependencies": { + "effect-http-error": "^0.4.0", + "effect-http-security": "^0.4.0" + }, + "peerDependencies": { + "@effect/platform": "^0.58.0", + "@effect/schema": "^0.68.0", + "effect": "^3.5.0" + } + }, + "node_modules/effect-http-error": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/effect-http-error/-/effect-http-error-0.4.0.tgz", + "integrity": "sha512-ABrUA6pHsQPE349oKVWZjem308tMboZBErIFyyWdh6o3LIX6KW6BfrYg7x8xghyNJA8gChdGMOeIFwkdKoKxFg==", + "dev": true, + "peerDependencies": { + "@effect/platform": "^0.58.0", + "@effect/schema": "^0.68.0", + "effect": "^3.5.0" + } + }, + "node_modules/effect-http-node": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/effect-http-node/-/effect-http-node-0.16.1.tgz", + "integrity": "sha512-GhsVvFs6B0KliCRdQUpPLtedRupgNgzCqtv/patLT253suWUX8WxBtGi/Im5KKk64VmfZwz6DKHdgD00wJSYhQ==", + "dev": true, + "dependencies": { + "swagger-ui-dist": "^5.17.14" + }, + "peerDependencies": { + "@effect/platform": "^0.58.0", + "@effect/platform-node": "^0.53.0", + "@effect/schema": "^0.68.0", + "effect": "^3.5.0", + "effect-http": "^0.73.0" + } + }, + "node_modules/effect-http-security": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/effect-http-security/-/effect-http-security-0.4.0.tgz", + "integrity": "sha512-jeuMKWr5kSuOMbHL454ks/Y59WlqvlopfimaCNaG7w39/XfMOQ9C/Emy8GwyHGozBwi42kFiZBCQ4megzng91Q==", + "dev": true, + "peerDependencies": { + "@effect/platform": "^0.58.0", + "@effect/schema": "^0.68.0", + "effect": "^3.5.0", + "effect-http-error": "^0.4.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, "dependencies": { "jake": "^10.8.5" }, @@ -9479,9 +10092,9 @@ "dev": true }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -9491,29 +10104,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/escalade": { @@ -10359,6 +10972,34 @@ "node >=0.6.0" ] }, + "node_modules/fast-check": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.19.0.tgz", + "integrity": "sha512-CO2JX/8/PT9bDGO1iXa5h5ey1skaKI1dvecERyhH4pp3PGjwd3KIjMAXEg79Ps9nclsdt4oPbfqiAnLU0EwrAQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -10411,6 +11052,15 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, "node_modules/fast-redact": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", @@ -10552,6 +11202,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/find-my-way-ts": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.4.tgz", + "integrity": "sha512-naNl2YZ8m9LlYtPZathQBjXQQ8069uYBFq8We6w9AEGddJErVh0JZw8jd/C/2W9Ib3BjTnu+YN0/rR+ytWxNdw==", + "dev": true, + "dependencies": { + "fast-querystring": "^1.0.0" + } + }, "node_modules/find-node-modules": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/find-node-modules/-/find-node-modules-2.1.3.tgz", @@ -11477,6 +12136,20 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -11491,6 +12164,82 @@ "node": ">= 6" } }, + "node_modules/http-proxy/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/http-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/http-server/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -14762,6 +15511,22 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/mkcert": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mkcert/-/mkcert-3.2.0.tgz", + "integrity": "sha512-026Eivq9RoOjOuLJGzbhGwXUAjBxRX11Z7Jbm4/7lqT/Av+XNy9SPrJte6+UpEt7i+W3e/HZYxQqlQcqXZWSzg==", + "dev": true, + "dependencies": { + "commander": "^11.0.0", + "node-forge": "^1.3.1" + }, + "bin": { + "mkcert": "dist/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -14829,6 +15594,12 @@ "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", "dev": true }, + "node_modules/multipasta": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.2.tgz", + "integrity": "sha512-KKGdmXIJUmt9BV45LsbUdMnju8eCNSyF9KpbyqK2E3wQXjpPQOg52/Hc+nsmBacmEkNxLVT5h1y3ZgEXB4prXg==", + "dev": true + }, "node_modules/mute-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", @@ -14968,6 +15739,12 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -15010,6 +15787,15 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -15534,6 +16320,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -16069,6 +16864,50 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/portfinder/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -17090,6 +17929,12 @@ "node": ">=v12.22.7" } }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true + }, "node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -17861,6 +18706,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-ui-dist": { + "version": "5.17.14", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz", + "integrity": "sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==", + "dev": true + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -18537,6 +19388,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "6.19.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.2.tgz", + "integrity": "sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==", + "dev": true, + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -18595,6 +19455,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -18658,6 +19530,12 @@ "punycode": "^2.1.0" } }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, "node_modules/url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", @@ -19225,36 +20103,442 @@ } } }, - "node_modules/vitest": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", - "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], "dev": true, - "dependencies": { - "@vitest/expect": "1.5.0", - "@vitest/runner": "1.5.0", - "@vitest/snapshot": "1.5.0", - "@vitest/spy": "1.5.0", - "@vitest/utils": "1.5.0", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.5.0", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/vitest": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz", + "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.5.0", + "@vitest/runner": "1.5.0", + "@vitest/snapshot": "1.5.0", + "@vitest/spy": "1.5.0", + "@vitest/utils": "1.5.0", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.5.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, "engines": { "node": "^18.0.0 || >=20.0.0" }, @@ -19584,9 +20868,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -19739,6 +21023,15 @@ "node": "^12.20.0 || >=14" } }, + "packages/davinci-client": { + "name": "@forgerock/davinci-client", + "version": "0.0.1", + "extraneous": true, + "dependencies": { + "@forgerock/javascript-sdk": "*", + "@reduxjs/toolkit": "^2.2.5" + } + }, "packages/javascript-sdk": { "name": "@forgerock/javascript-sdk", "version": "4.4.2", diff --git a/package.json b/package.json index a27ba3c7d..3e272f3ae 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,14 @@ "@commitlint/cli": "^19.1.0", "@commitlint/config-conventional": "^19.1.0", "@commitlint/prompt": "^19.1.0", + "@effect/language-service": "^0.1.0", + "@effect/platform": "^0.58.27", + "@effect/platform-node": "^0.53.26", + "@effect/schema": "^0.68.23", + "@effect/vitest": "^0.6.7", "@nrwl/devkit": "18.2.4", "@nx/devkit": "18.2.4", + "@nx/esbuild": "18.2.4", "@nx/eslint": "18.2.4", "@nx/eslint-plugin": "18.2.4", "@nx/express": "18.2.4", @@ -45,6 +51,7 @@ "@nx/node": "18.2.4", "@nx/playwright": "18.2.4", "@nx/vite": "18.2.4", + "@nx/web": "18.2.4", "@nx/workspace": "18.2.4", "@playwright/test": "^1.43.0", "@swc-node/register": "1.8.0", @@ -63,16 +70,22 @@ "cors": "^2.8.5", "cz-conventional-changelog": "^3.3.0", "cz-git": "^1.6.1", + "effect": "^3.5.3", + "effect-http": "^0.73.0", + "effect-http-node": "^0.16.1", + "esbuild": "^0.19.2", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-playwright": "^1.5.1", "eslint-plugin-prettier": "^5.1.3", "express": "^4.18.3", + "fast-check": "^3.19.0", "husky": "^8.0.3", "jsdom": "22.1.0", "jsonc-eslint-parser": "^2.1.0", "lint-staged": "^13.2.2", + "mkcert": "^3.2.0", "npm-cli-login": "^1.0.0", "nx": "18.2.4", "nx-cloud": "18.0.0", @@ -104,5 +117,6 @@ }, "nx": { "includedScripts": [] - } + }, + "dependencies": {} } diff --git a/vitest.workspace.ts b/vitest.workspace.ts new file mode 100644 index 000000000..3c983a248 --- /dev/null +++ b/vitest.workspace.ts @@ -0,0 +1 @@ +export default ['**/*/vite.config.ts', '**/*/vitest.config.ts'];