diff --git a/.changeset/khaki-needles-grab.md b/.changeset/khaki-needles-grab.md new file mode 100644 index 0000000000..b275d5f686 --- /dev/null +++ b/.changeset/khaki-needles-grab.md @@ -0,0 +1,11 @@ +--- +"@prosopo/procaptcha-frictionless": minor +"@prosopo/procaptcha-common": minor +"@prosopo/types-database": minor +"@prosopo/detector": minor +"@prosopo/provider": minor +"@prosopo/types": minor +--- + +Remove random provider selection fn in favour of DNS routing + \ No newline at end of file diff --git a/docker/docker-compose.provider.yml b/docker/docker-compose.provider.yml index 27e85b7a7e..fb79b3ff69 100644 --- a/docker/docker-compose.provider.yml +++ b/docker/docker-compose.provider.yml @@ -124,6 +124,8 @@ services: - "[::]:443:443" volumes: - ./provider.Caddyfile:/etc/caddy/Caddyfile + - ./certs/pronode.crt /etc/caddy/certs/pronode.crt + - ./certs/pronode.key /etc/caddy/certs/pronode.key - caddy_data:/data - caddy_config:/config networks: diff --git a/docker/images/caddy/package.json b/docker/images/caddy/package.json index 770f7ca936..ec5032ab30 100644 --- a/docker/images/caddy/package.json +++ b/docker/images/caddy/package.json @@ -1,6 +1,6 @@ { "name": "@prosopo/caddy-docker", - "version": "2.5.6", + "version": "2.5.7", "engines": { "node": "^24", "npm": "^11" @@ -22,6 +22,6 @@ } }, "devDependencies": { - "@prosopo/config": "3.1.20" + "@prosopo/config": "3.3.0" } } diff --git a/docker/images/caddy/src/Dockerfile b/docker/images/caddy/src/Dockerfile index 2bb364fb73..e56d0d47d7 100644 --- a/docker/images/caddy/src/Dockerfile +++ b/docker/images/caddy/src/Dockerfile @@ -3,7 +3,10 @@ RUN apk update && apk add gcc g++ make libpcap-dev libpcap RUN CGO_ENABLED=1 xcaddy build \ --with github.com/mholt/caddy-ratelimit \ --with github.com/prosopo/chaddy \ - --with github.com/lolPants/caddy-requestid + --with github.com/lolPants/caddy-requestid \ + --with github.com/caddy-dns/bunny \ + --with github.com/pberkel/caddy-storage-redis + FROM caddy:2 RUN apk update && apk add libpcap RUN apk add --no-cache curl @@ -11,4 +14,3 @@ RUN apk add --no-cache curl RUN mkdir -p /srv/static && \ echo -e "User-agent: *\nDisallow: /" > /srv/static/robots.txt COPY --from=builder /usr/bin/caddy /usr/bin/caddy - diff --git a/docker/provider.Caddyfile b/docker/provider.Caddyfile index 13c802aa16..dd3c526c5a 100644 --- a/docker/provider.Caddyfile +++ b/docker/provider.Caddyfile @@ -1,15 +1,17 @@ # usage: `caddy run --config ./docker/provider.Caddyfile --envfile docker/env.development` { - # debug http_port {$CADDY_HTTP_PORT:80} + https_port 443 auto_https {$CADDY_AUTO_HTTPS:disable_redirects} admin {$CADDY_ADMIN_API::2020} # set the admin api to run on localhost:2020 (default is 2019 which can conflict with caddy daemon) - # Caddy must be told custom rate_limit module its order + acme_ca https://acme-v02.api.letsencrypt.org/directory + email {$CADDY_ACME_EMAIL:admin@prosopo.io} + admin {$CADDY_ADMIN_API::2020} + order rate_limit before basicauth client_hello { - # Configure the maximum allowed ClientHello packet size in bytes (1-16384) max_client_hello_size 16384 } @@ -28,60 +30,36 @@ } } -# HTTP metrics endpoint on all interfaces -http://:9090 { - metrics /metrics -} - -{$CADDY_DOMAIN} { - - @httpOnly { - protocol http - } - redir @httpOnly https://{host}{uri} - +# Reusable robots.txt logic +(robots_logic) { handle /robots.txt { - uri strip_prefix / # removes leading /, so it looks directly in root root * /srv/static file_server header Content-Type "text/plain" header X-Robots-Tag "noindex, nofollow" } +} +# --- REUSABLE PROXY SNIPPET --- +# This contains all your headers, rate limits, and proxy logic +(my_proxy_logic) { route { - client_hello request_id header X-Request-ID "{http.request_id}" + header X-Node-Id "{vars.node_id}" # Injected from the handle block header / { - # Enable HTTP Strict Transport Security (HSTS) Strict-Transport-Security "max-age=31536000;" - # Enable cross-site filter (XSS) and tell browser to block detected attacks X-XSS-Protection "1; mode=block" - # Disallow the site to be rendered within a frame (clickjacking protection) X-Frame-Options "DENY" - # Prevent search engines from indexing X-Robots-Tag "none" } - # enable prometheus metrics metrics /metrics rate_limit { distributed - - # Means that the rate limit is applied to all GET requests, with a limit of 100 requests per minute. - # zone get_rate_limit { - # match { - # method GET - # } - # key static - # events 100 - # window 1m - # } - - # The rate limit is applied to `remote_host` with a limit of 6 requests per 6 seconds (60 requests per minute). zone dynamic_example { key {remote_host} events {$CADDY_RATE_LIMIT_EVENTS} @@ -90,54 +68,21 @@ http://:9090 { log_key } - # reverse proxy to the provider container reverse_proxy {$CADDY_PROVIDER_CONTAINER_NAME:provider1}:{$CADDY_PROVIDER_PORT:9229} {$CADDY_PROVIDER_CONTAINER_NAME:provider2}:{$CADDY_PROVIDER_PORT2:9339} { - # https://caddyserver.com/docs/modules/http.handlers.reverse_proxy - - # try A, then B, then C, etc. lb_policy first - - # how many times a backend can fail before it is considered unhealthy max_fails 1 - - # how long a backend is marked as unhealthy after it has failed (this is a non-zero duration to enable passive health checks). Passive health checks decide a backend's health based on the response code (and whether it responded at all) from normal traffic. fail_duration 1ns - - # 5XX status codes are considered unhealthy, in addition to no response unhealthy_status 5xx - - # long latency on response marks the backend as unhealthy unhealthy_latency 10s transport http { - # how long to wait for a connection to be established to backend dial_timeout 1s } - # how long to keep trying backends before giving up lb_try_duration 5s - - # how long to wait between retries of backends (0 doesn't work, set to 1ns for almost immediate retry) lb_try_interval 1ns - # example failover sequence with failing backends: - # - request comes in - # - lb_policy first means provider1 is tried first - # - request is sent to provider1 - # - provider1 does not respond within 1s (dial_timeout) - # - provider1 is marked as unhealthy - # - request is sent to provider2 - # - provider2 does not respond within 1s (dial_timeout) - # - in this time, provider1 is marked as healthy again (fail duration expired) - # - provider2 is marked as unhealthy - # - request is sent to provider1 again - # - provider1 responds within 1s - # - request is completed - # the request is retried over all backends in turn until either it succeeds or the try_duration is reached - - # https://caddyserver.com/docs/caddyfile/concepts#placeholders - # https://caddyserver.com/docs/json/apps/http/#docs - + # All your existing header_up metadata... header_up X-Forwarded-For {http.request.remote.host} header_up X-Forwarded-Port {http.request.remote.port} header_up X-Forwarded-Proto {http.request.scheme} @@ -214,8 +159,39 @@ http://:9090 { header_up x-tls-client-san-uris "^{http.request.tls.client.san.uris}$" "" } } +} + +# --- 1. MAIN DOMAIN (Mounted Certificate) --- +pronode.prosopo.io { + import robots_logic + + tls /etc/caddy/certs/pronode.crt /etc/caddy/certs/pronode.key { + # This tells Caddy: "Don't manage this, just use these files" + } + + vars node_id "main-gateway" + import my_proxy_logic log { format json } } + +# --- 2. NODE-SPECIFIC DOMAIN (ACME Challenge) --- +{$CADDY_DOMAIN} { + import robots_logic + # Caddy will automatically perform the HTTP-01 challenge here + # No tls block needed unless you want to specify an email + + vars node_id "I-am-node-{$NODE_ID}" + import my_proxy_logic + + log { + format json + } +} + +# --- METRICS --- +:9090 { + metrics /metrics +} diff --git a/packages/api/src/api/ProviderApi.ts b/packages/api/src/api/ProviderApi.ts index 454cbd803f..0661437434 100644 --- a/packages/api/src/api/ProviderApi.ts +++ b/packages/api/src/api/ProviderApi.ts @@ -74,7 +74,6 @@ export default class ProviderApi const body: CaptchaRequestBodyType = { [ApiParams.dapp]: dappAccount, [ApiParams.user]: userAccount, - [ApiParams.datasetId]: provider.datasetId, }; if (sessionId) { body[ApiParams.sessionId] = sessionId; diff --git a/packages/database/src/databases/provider.ts b/packages/database/src/databases/provider.ts index a72e776096..30ea8afb00 100644 --- a/packages/database/src/databases/provider.ts +++ b/packages/database/src/databases/provider.ts @@ -617,6 +617,20 @@ export class ProviderDatabase await this.tables?.captcha.deleteMany(filter); } + /** + * @description Get the most recently uploaded dataset ID + */ + async getMostRecentDatasetId(): Promise { + const dataset = await this.tables?.dataset + .findOne() + .sort({ _id: -1 }) // Sort by _id descending to get most recent + .lean(); + + const datasetId = dataset?.datasetId; + // Ensure we return string | undefined, not Hash (which can be string | number[]) + return typeof datasetId === "string" ? datasetId : undefined; + } + /** * @description Get a dataset by Id */ diff --git a/packages/detector/src/index.d.ts b/packages/detector/src/index.d.ts index 2fab21d147..a70e2a9af8 100644 --- a/packages/detector/src/index.d.ts +++ b/packages/detector/src/index.d.ts @@ -11,18 +11,17 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + import { Account } from '@prosopo/types'; import { BehavioralData } from '@prosopo/types'; import { ClickEventPoint } from '@prosopo/types'; import { EnvironmentTypes } from '@prosopo/types'; import { MouseMovementPoint } from '@prosopo/types'; import { PackedBehavioralData } from '@prosopo/types'; -import { RandomProvider } from '@prosopo/types'; import { TouchEventPoint } from '@prosopo/types'; -declare const detect: (env: EnvironmentTypes, randomProviderSelectorFn: RandomProviderSelectorFn, container: HTMLElement | undefined, restart: () => void, accountGenerator: () => Promise) => Promise<{ +declare const detect: (env: EnvironmentTypes, container: HTMLElement | undefined, restart: () => void, accountGenerator: () => Promise) => Promise<{ token: string; - provider?: RandomProvider; shadowDomCleanup: () => void; encryptHeadHash: string; mouseTracker?: { diff --git a/packages/env/src/env.ts b/packages/env/src/env.ts index 67cf3b6cb9..20e4eff7a3 100644 --- a/packages/env/src/env.ts +++ b/packages/env/src/env.ts @@ -39,6 +39,7 @@ export class Environment implements ProsopoEnvironment { envId: string | undefined; geolocationService: GeolocationService; ready = false; + datasetId: string | undefined; constructor( config: ProsopoConfigOutput, @@ -142,6 +143,27 @@ export class Environment implements ProsopoEnvironment { await this.db.connect(); this.logger.info(() => ({ msg: "Connected to db" })); } + // Set the default datasetId to the most recently uploaded dataset + if (this.db && !this.datasetId) { + try { + this.datasetId = await this.db.getMostRecentDatasetId(); + if (this.datasetId) { + this.logger.info(() => ({ + msg: "Default dataset ID set", + data: { datasetId: this.datasetId }, + })); + } else { + this.logger.warn(() => ({ + msg: "No datasets found in database. Image captchas will not work until a dataset is uploaded.", + })); + } + } catch (err) { + this.logger.warn(() => ({ + msg: "Failed to get most recent dataset ID", + data: { error: err }, + })); + } + } // Initialize MaxMind geolocation database await this.geolocationService.initialize(); this.ready = true; diff --git a/packages/load-balancer/src/providers.ts b/packages/load-balancer/src/providers.ts index ab02ba23b9..ddb0b2d8ec 100644 --- a/packages/load-balancer/src/providers.ts +++ b/packages/load-balancer/src/providers.ts @@ -13,80 +13,40 @@ // limitations under the License. import type { EnvironmentTypes, RandomProvider } from "@prosopo/types"; -import { type HardcodedProvider, loadBalancer } from "./index.js"; - -let cachedProviders: HardcodedProvider[] = []; - -export function _resetCache() { - cachedProviders = []; -} /** - * Selects a weighted random provider using the entropy value. - * Providers with higher weights are more likely to be selected. + * Gets the DNS-based provider URL for the given environment. + * Uses single DNS endpoint with latency-based routing at the DNS level. + * Frontend uses this for simple provider access without needing the full provider list. * - * @param providers - Array of providers with weights - * @param entropy - Random seed value for deterministic selection - * @returns Selected provider + * @param env - The environment (development, staging, production) + * @returns Provider URL and placeholder account information */ -export function selectWeightedProvider( - providers: HardcodedProvider[], - entropy: number, -): HardcodedProvider { - if (providers.length === 0) { - throw new Error("No providers available"); - } - - const totalWeight = providers.reduce((sum, p) => sum + p.weight, 0); - - // Use entropy to generate a value between 0 and totalWeight-1 - const randomValue = entropy % totalWeight; - - // Select provider based on cumulative weight - let cumulativeWeight = 0; - for (const provider of providers) { - cumulativeWeight += provider.weight; - if (randomValue < cumulativeWeight) { - return provider; - } - } - - // Fallback (should never reach here) - const selectedProvider = providers[providers.length - 1]; - if (!selectedProvider) { - throw new Error("No providers available"); - } - return selectedProvider; -} - -/** - * Pre-warms the provider cache for a given environment without requiring entropy. - * Call this as early as possible to avoid a cold-cache delay when getRandomActiveProvider is first used. - */ -export const prefetchProviders = async ( - env: EnvironmentTypes, -): Promise => { - if (cachedProviders.length === 0) { - cachedProviders = await loadBalancer(env); - } -}; - export const getRandomActiveProvider = async ( env: EnvironmentTypes, - entropy: number, ): Promise => { - if (cachedProviders.length === 0) { - // only get the providers JSON once - cachedProviders = await loadBalancer(env); + // DNS handles the load balancing now - no need to fetch provider list for frontend + + let url: string; + + switch (env) { + case "development": + url = "http://localhost:9229"; + break; + case "staging": + url = "https://staging.pronode.prosopo.io"; + break; + case "production": + url = "https://pronode.prosopo.io"; + break; + default: + url = "http://localhost:9229"; } - const randomProviderObj = selectWeightedProvider(cachedProviders, entropy); - return { - providerAccount: randomProviderObj.address, + providerAccount: "dns-load-balanced-provider", // Placeholder - actual provider determined by DNS provider: { - url: randomProviderObj.url, - datasetId: randomProviderObj.datasetId, + url, }, }; }; diff --git a/packages/load-balancer/src/tests/providers.unit.test.ts b/packages/load-balancer/src/tests/providers.unit.test.ts index bafd9f6ba8..4ee1204131 100644 --- a/packages/load-balancer/src/tests/providers.unit.test.ts +++ b/packages/load-balancer/src/tests/providers.unit.test.ts @@ -12,309 +12,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { type HardcodedProvider, loadBalancer } from "../index.js"; -import { - getRandomActiveProvider, - selectWeightedProvider, -} from "../providers.js"; -import { _resetCache } from "../providers.js"; - -vi.mock("../index.js", () => ({ - loadBalancer: vi.fn(), -})); - -describe("selectWeightedProvider", () => { - it("selects provider based on weight distribution", () => { - const providers = [ - { - address: "address1", - url: "url1", - datasetId: "dataset1", - weight: 1, - }, - { - address: "address2", - url: "url2", - datasetId: "dataset2", - weight: 3, - }, - { - address: "address3", - url: "url3", - datasetId: "dataset3", - weight: 1, - }, - ]; - - // Total weight = 5 - // Provider 1: weight 1 (covers entropy 0-0) - // Provider 2: weight 3 (covers entropy 1-3) - // Provider 3: weight 1 (covers entropy 4-4) - - // Entropy 0 should select provider1 - expect(selectWeightedProvider(providers, 0).address).toBe("address1"); - - // Entropy 1-3 should select provider2 - expect(selectWeightedProvider(providers, 1).address).toBe("address2"); - expect(selectWeightedProvider(providers, 2).address).toBe("address2"); - expect(selectWeightedProvider(providers, 3).address).toBe("address2"); - - // Entropy 4 should select provider3 - expect(selectWeightedProvider(providers, 4).address).toBe("address3"); - - // Entropy wraps around with modulo - expect(selectWeightedProvider(providers, 5).address).toBe("address1"); - expect(selectWeightedProvider(providers, 6).address).toBe("address2"); - }); - - it("handles equal weights correctly", () => { - const providers = [ - { - address: "address1", - url: "url1", - datasetId: "dataset1", - weight: 1, - }, - { - address: "address2", - url: "url2", - datasetId: "dataset2", - weight: 1, - }, - ]; - - // Total weight = 2 - expect(selectWeightedProvider(providers, 0).address).toBe("address1"); - expect(selectWeightedProvider(providers, 1).address).toBe("address2"); - expect(selectWeightedProvider(providers, 2).address).toBe("address1"); - expect(selectWeightedProvider(providers, 3).address).toBe("address2"); - }); - - it("handles single provider", () => { - const providers = [ - { - address: "address1", - url: "url1", - datasetId: "dataset1", - weight: 10, - }, - ]; - - // All entropy values should select the only provider - expect(selectWeightedProvider(providers, 0).address).toBe("address1"); - expect(selectWeightedProvider(providers, 5).address).toBe("address1"); - expect(selectWeightedProvider(providers, 100).address).toBe("address1"); - }); - - it("throws error for empty provider list", () => { - expect(() => selectWeightedProvider([], 0)).toThrow( - "No providers available", - ); - }); - - it("heavily weighted provider is selected more often", () => { - const providers = [ - { - address: "address1", - url: "url1", - datasetId: "dataset1", - weight: 1, - }, - { - address: "address2", - url: "url2", - datasetId: "dataset2", - weight: 99, - }, - ]; - - // Total weight = 100 - // Provider 1: entropy 0 (1% of the time) - // Provider 2: entropy 1-99 (99% of the time) - - const selections = { address1: 0, address2: 0 }; - for (let i = 0; i < 100; i++) { - const selected = selectWeightedProvider(providers, i); - if (selected.address === "address1") { - selections.address1++; - } else { - selections.address2++; - } - } - - expect(selections.address1).toBe(1); - expect(selections.address2).toBe(99); - }); - - it("handles maximum weight value (100)", () => { - const providers = [ - { - address: "address1", - url: "url1", - datasetId: "dataset1", - weight: 100, - }, - { - address: "address2", - url: "url2", - datasetId: "dataset2", - weight: 100, - }, - ]; - - // Total weight = 200 - const selections = { address1: 0, address2: 0 }; - for (let i = 0; i < 200; i++) { - const selected = selectWeightedProvider(providers, i); - if (selected.address === "address1") { - selections.address1++; - } else { - selections.address2++; - } - } - - // Each should get selected 100 times (50%) - expect(selections.address1).toBe(100); - expect(selections.address2).toBe(100); - }); - - it("correctly handles providers without values for weight", () => { - // Providers without weight field should default to weight 1 - const providers = [ - { - address: "address1", - url: "url1", - datasetId: "dataset1", - // No weight field - }, - { - address: "address2", - url: "url2", - datasetId: "dataset2", - weight: 3, - }, - ]; - - // Mock the providers to simulate what comes from the API - // The real providers will have weight added by zod schema default - const providersWithDefaults = [ - { ...providers[0], weight: 1 }, - providers[1], - ]; - - // Total weight = 4 (1 + 3) - // address1 (weight 1) should get entropy 0 (25%) - // address2 (weight 3) should get entropy 1-3 (75%) - - const selections = { address1: 0, address2: 0 }; - for (let i = 0; i < 100; i++) { - const selected = selectWeightedProvider( - providersWithDefaults as HardcodedProvider[], - i, - ); - if (selected.address === "address1") { - selections.address1++; - } else { - selections.address2++; - } - } - - // address1 should get ~25% and address2 should get ~75% - expect(selections.address1).toBe(25); - expect(selections.address2).toBe(75); - }); -}); +import { describe, expect, it } from "vitest"; +import { getRandomActiveProvider } from "../providers.js"; describe("getRandomActiveProvider", () => { - beforeEach(() => { - _resetCache(); - vi.clearAllMocks(); - vi.resetAllMocks(); - vi.resetModules(); - }); + it("returns localhost for development environment", async () => { + const result = await getRandomActiveProvider("development"); - it("returns a random provider when providers list is populated", async () => { - const mockProviders = [ - { address: "address1", url: "url1", datasetId: "dataset1", weight: 1 }, - { address: "address2", url: "url2", datasetId: "dataset2", weight: 1 }, - ]; - (loadBalancer as unknown as ReturnType).mockResolvedValue( - mockProviders, - ); - - const result = await getRandomActiveProvider("development", 1); - - expect(result.providerAccount).toBe("address2"); - expect(result.provider.url).toBe("url2"); - expect(result.provider.datasetId).toBe("dataset2"); + expect(result.providerAccount).toBe("dns-load-balanced-provider"); + expect(result.provider.url).toBe("http://localhost:9229"); + expect(result.provider).not.toHaveProperty("datasetId"); }); - it("loads providers only once when called multiple times", async () => { - const mockProviders = [ - { address: "address1", url: "url1", datasetId: "dataset1", weight: 1 }, - ]; - (loadBalancer as unknown as ReturnType).mockResolvedValue( - mockProviders, - ); - - await getRandomActiveProvider("development", 123); - await getRandomActiveProvider("development", 456); + it("returns DNS-based URL for staging environment", async () => { + const result = await getRandomActiveProvider("staging"); - expect(loadBalancer).toHaveBeenCalledTimes(1); + expect(result.providerAccount).toBe("dns-load-balanced-provider"); + expect(result.provider.url).toBe("https://staging.pronode.prosopo.io"); + expect(result.provider).not.toHaveProperty("datasetId"); }); - it("handles empty providers list gracefully", async () => { - (loadBalancer as unknown as ReturnType).mockResolvedValue([]); + it("returns DNS-based URL for production environment", async () => { + const result = await getRandomActiveProvider("production"); - await expect(getRandomActiveProvider("development", 123)).rejects.toThrow(); + expect(result.providerAccount).toBe("dns-load-balanced-provider"); + expect(result.provider.url).toBe("https://pronode.prosopo.io"); + expect(result.provider).not.toHaveProperty("datasetId"); }); - it("respects provider weights when selecting", async () => { - const mockProviders = [ - { address: "address1", url: "url1", datasetId: "dataset1", weight: 1 }, - { address: "address2", url: "url2", datasetId: "dataset2", weight: 3 }, - ]; - (loadBalancer as unknown as ReturnType).mockResolvedValue( - mockProviders, - ); - - // Total weight = 4 - // address1 gets entropy 0 (25%) - // address2 gets entropy 1-3 (75%) - - const result0 = await getRandomActiveProvider("development", 0); - expect(result0.providerAccount).toBe("address1"); - - _resetCache(); - const result1 = await getRandomActiveProvider("development", 1); - expect(result1.providerAccount).toBe("address2"); - - _resetCache(); - const result2 = await getRandomActiveProvider("development", 2); - expect(result2.providerAccount).toBe("address2"); - - _resetCache(); - const result3 = await getRandomActiveProvider("development", 3); - expect(result3.providerAccount).toBe("address2"); - }); - - it("handles providers with missing weight field (defaults to 1)", async () => { - // Simulate providers returned from loadBalancer where one has weight and one doesn't - const mockProviders = [ - { address: "address1", url: "url1", datasetId: "dataset1", weight: 1 }, - { address: "address2", url: "url2", datasetId: "dataset2", weight: 1 }, - ]; - (loadBalancer as unknown as ReturnType).mockResolvedValue( - mockProviders, - ); - - // With equal weights, distribution should be 50/50 - const result0 = await getRandomActiveProvider("development", 0); - expect(result0.providerAccount).toBe("address1"); + it("returns consistent results when called multiple times", async () => { + const result1 = await getRandomActiveProvider("staging"); + const result2 = await getRandomActiveProvider("staging"); - _resetCache(); - const result1 = await getRandomActiveProvider("development", 1); - expect(result1.providerAccount).toBe("address2"); + expect(result1.provider.url).toBe(result2.provider.url); + expect(result1.providerAccount).toBe(result2.providerAccount); }); }); diff --git a/packages/procaptcha-common/src/providers.ts b/packages/procaptcha-common/src/providers.ts index 8f68618ef6..e197a805e1 100644 --- a/packages/procaptcha-common/src/providers.ts +++ b/packages/procaptcha-common/src/providers.ts @@ -12,15 +12,40 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { getRandomActiveProvider } from "@prosopo/load-balancer"; -import type { EnvironmentTypes } from "@prosopo/types"; +import type { EnvironmentTypes, RandomProvider } from "@prosopo/types"; +/** + * Returns a static DNS endpoint for the given environment. + * DNS-based load balancing is handled at the DNS level. + * + * @param defaultEnvironment - The environment (development, staging, production) + * @returns Provider information with DNS endpoint + */ export const getProcaptchaRandomActiveProvider = async ( defaultEnvironment: EnvironmentTypes, -) => { - const randomNumberU8a = window.crypto.getRandomValues(new Uint8Array(10)); - const randomNumber = randomNumberU8a.reduce((a, b) => a + b, 0); - return await getRandomActiveProvider(defaultEnvironment, randomNumber); +): Promise => { + let url: string; + + switch (defaultEnvironment) { + case "development": + url = "http://localhost:9229"; + break; + case "staging": + url = "https://staging.pronode.prosopo.io"; + break; + case "production": + url = "https://pronode.prosopo.io"; + break; + default: + url = "http://localhost:9229"; + } + + return { + providerAccount: "provider-dns-endpoint", // Placeholder, actual provider determined by DNS + provider: { + url, + }, + }; }; export const providerRetry = async ( diff --git a/packages/procaptcha-common/src/tests/providers.test.ts b/packages/procaptcha-common/src/tests/providers.test.ts index da1fb55a61..decab66c9f 100644 --- a/packages/procaptcha-common/src/tests/providers.test.ts +++ b/packages/procaptcha-common/src/tests/providers.test.ts @@ -12,77 +12,40 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { getProcaptchaRandomActiveProvider, providerRetry, } from "../providers.js"; -// Mock the load-balancer module -vi.mock("@prosopo/load-balancer", () => ({ - getRandomActiveProvider: vi.fn(), -})); - describe("providers", () => { describe("getProcaptchaRandomActiveProvider", () => { - // biome-ignore lint/suspicious/noExplicitAny: Store original crypto function - let originalGetRandomValues: any; + it("should return localhost for development environment", async () => { + const result = await getProcaptchaRandomActiveProvider("development"); - beforeEach(() => { - originalGetRandomValues = global.window.crypto.getRandomValues.bind( - global.window.crypto, - ); + expect(result.providerAccount).toBe("provider-dns-endpoint"); + expect(result.provider.url).toBe("http://localhost:9229"); }); - afterEach(() => { - global.window.crypto.getRandomValues = originalGetRandomValues; - }); + it("should return staging DNS URL for staging environment", async () => { + const result = await getProcaptchaRandomActiveProvider("staging"); - it("should generate random values and call getRandomActiveProvider", async () => { - // Mock window.crypto.getRandomValues - const mockRandomValues = new Uint8Array([ - 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, - ]); - const mockGetRandomValues = vi.fn(() => mockRandomValues); - // biome-ignore lint/suspicious/noExplicitAny: Mock crypto API - global.window.crypto.getRandomValues = mockGetRandomValues as any; - - // Mock the getRandomActiveProvider import - const { getRandomActiveProvider } = await import( - "@prosopo/load-balancer" - ); - vi.mocked(getRandomActiveProvider).mockResolvedValue({ - providerUrl: "https://test-provider.com", - // biome-ignore lint/suspicious/noExplicitAny: Mock return type - } as any); + expect(result.providerAccount).toBe("provider-dns-endpoint"); + expect(result.provider.url).toBe("https://staging.pronode.prosopo.io"); + }); - const result = await getProcaptchaRandomActiveProvider("development"); + it("should return production DNS URL for production environment", async () => { + const result = await getProcaptchaRandomActiveProvider("production"); - expect(mockGetRandomValues).toHaveBeenCalledWith(expect.any(Uint8Array)); - expect(getRandomActiveProvider).toHaveBeenCalledWith("development", 550); // sum of mockRandomValues - expect(result).toEqual({ providerUrl: "https://test-provider.com" }); + expect(result.providerAccount).toBe("provider-dns-endpoint"); + expect(result.provider.url).toBe("https://pronode.prosopo.io"); }); - it("should use different random values on each call", async () => { - let callCount = 0; - const mockGetRandomValues = vi.fn((arr: Uint8Array) => { - callCount++; - arr.fill(callCount); - return arr; - }); - // biome-ignore lint/suspicious/noExplicitAny: Mock crypto API - global.window.crypto.getRandomValues = mockGetRandomValues as any; - - const { getRandomActiveProvider } = await import( - "@prosopo/load-balancer" - ); - // biome-ignore lint/suspicious/noExplicitAny: Mock return type - vi.mocked(getRandomActiveProvider).mockResolvedValue({} as any); - - await getProcaptchaRandomActiveProvider("development"); - await getProcaptchaRandomActiveProvider("development"); + it("should return consistent results when called multiple times", async () => { + const result1 = await getProcaptchaRandomActiveProvider("staging"); + const result2 = await getProcaptchaRandomActiveProvider("staging"); - expect(mockGetRandomValues).toHaveBeenCalledTimes(2); + expect(result1).toEqual(result2); }); }); diff --git a/packages/procaptcha-common/tsconfig.cjs.json b/packages/procaptcha-common/tsconfig.cjs.json index 9fafdb7e43..c3f8b551d6 100644 --- a/packages/procaptcha-common/tsconfig.cjs.json +++ b/packages/procaptcha-common/tsconfig.cjs.json @@ -16,9 +16,6 @@ { "path": "../../dev/config/tsconfig.cjs.json" }, - { - "path": "../load-balancer/tsconfig.cjs.json" - }, { "path": "../types/tsconfig.cjs.json" }, diff --git a/packages/procaptcha-common/tsconfig.json b/packages/procaptcha-common/tsconfig.json index 2ac8588be2..2855f89c86 100644 --- a/packages/procaptcha-common/tsconfig.json +++ b/packages/procaptcha-common/tsconfig.json @@ -17,9 +17,6 @@ { "path": "../../dev/config/tsconfig.json" }, - { - "path": "../load-balancer" - }, { "path": "../types" }, diff --git a/packages/procaptcha-frictionless/src/customDetectBot.ts b/packages/procaptcha-frictionless/src/customDetectBot.ts index f1415f3ccd..c0a7a02395 100644 --- a/packages/procaptcha-frictionless/src/customDetectBot.ts +++ b/packages/procaptcha-frictionless/src/customDetectBot.ts @@ -67,7 +67,6 @@ const customDetectBot: BotDetectionFunction = async ( const detectionResult = await detect( config.defaultEnvironment, - getRandomActiveProvider, container, restartFn, () => ext.getAccount(config), @@ -79,12 +78,8 @@ const customDetectBot: BotDetectionFunction = async ( throw new ProsopoEnvError("GENERAL.SITE_KEY_MISSING"); } - // Get random active provider with timeout - const provider = detectionResult.provider; - - if (!provider) { - throw new Error("Provider Selection Failed"); - } + // Get provider from DNS-based endpoint + const provider = await getRandomActiveProvider(config.defaultEnvironment); const providerApi = new ProviderApi( provider.provider.url, diff --git a/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge.ts b/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge.ts index 9af12fcd85..6a12df8b8a 100644 --- a/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge.ts +++ b/packages/provider/src/api/captcha/getFrictionlessCaptchaChallenge.ts @@ -110,7 +110,6 @@ export default ( scoreComponents: { baseScore: 0, }, - providerSelectEntropy: 0, ipAddress: getCompositeIpAddress(normalizedIp), webView: false, iFrame: false, @@ -179,7 +178,6 @@ export default ( const { baseBotScore, timestamp, - providerSelectEntropy, userId, userAgent, webView, @@ -194,7 +192,6 @@ export default ( data: { baseBotScore, timestamp, - providerSelectEntropy, userId, userAgent, webView, @@ -276,7 +273,6 @@ export default ( score: botScore, threshold: botThreshold, scoreComponents, - providerSelectEntropy, ipAddress, webView, iFrame, @@ -604,23 +600,6 @@ export default ( ); } - // If the host is not verified, send an image captcha - const hostVerified = await tasks.frictionlessManager.hostVerified( - providerSelectEntropy, - ); - if (!hostVerified.verified) { - const scoreUpdate = - tasks.frictionlessManager.scoreIncreaseUnverifiedHost( - hostVerified.domain, - baseBotScore, - botScore, - scoreComponents, - ); - botScore = scoreUpdate.score; - scoreComponents = scoreUpdate.scoreComponents; - tasks.frictionlessManager.updateScore(botScore, scoreComponents); - } - // If the bot score is greater than the threshold, send an image captcha if (Number(botScore) > botThreshold) { req.logger.info(() => ({ diff --git a/packages/provider/src/api/captcha/getImageCaptchaChallenge.ts b/packages/provider/src/api/captcha/getImageCaptchaChallenge.ts index fa63df667d..1e2253cd5c 100644 --- a/packages/provider/src/api/captcha/getImageCaptchaChallenge.ts +++ b/packages/provider/src/api/captcha/getImageCaptchaChallenge.ts @@ -70,7 +70,23 @@ export default ( ); } - const { datasetId, user, dapp, sessionId } = parsed; + const { datasetId: clientDatasetId, user, dapp, sessionId } = parsed; + + // Use client-provided datasetId if available, otherwise use provider's default + const datasetId = clientDatasetId || env.datasetId; + + if (!datasetId) { + return next( + new ProsopoApiError("API.BAD_REQUEST", { + context: { + code: 400, + error: "No dataset available. Please upload a dataset first.", + }, + i18n: req.i18n, + logger: req.logger, + }), + ); + } validateSiteKey(dapp); validateAddr(user); diff --git a/packages/provider/src/tasks/detection/getBotScore.ts b/packages/provider/src/tasks/detection/getBotScore.ts index 09c66bf487..b0561d9613 100644 --- a/packages/provider/src/tasks/detection/getBotScore.ts +++ b/packages/provider/src/tasks/detection/getBotScore.ts @@ -15,8 +15,6 @@ import type { DetectorResult } from "@prosopo/types"; import getBotScoreFromPayload from "./decodePayload.js"; -const DEFAULT_ENTROPY = 13837; - export const getBotScore = async ( payload: string, headHash: string, @@ -29,7 +27,6 @@ export const getBotScore = async ( )) as DetectorResult; const baseBotScore: number = result.score; const timestamp: number = result.timestamp; - const providerSelectEntropy: number = result.providerSelectEntropy; const userId: string = result.userId; const userAgent: string = result.userAgent; const isWebView: boolean = result.isWebView ?? false; @@ -41,14 +38,12 @@ export const getBotScore = async ( return { baseBotScore: 1, timestamp: 0, - providerSelectEntropy: DEFAULT_ENTROPY, }; } return { baseBotScore, timestamp, - providerSelectEntropy, userId, userAgent, isWebView, diff --git a/packages/provider/src/tasks/frictionless/frictionlessTasks.ts b/packages/provider/src/tasks/frictionless/frictionlessTasks.ts index 636c1ee3fd..099f4140a0 100644 --- a/packages/provider/src/tasks/frictionless/frictionlessTasks.ts +++ b/packages/provider/src/tasks/frictionless/frictionlessTasks.ts @@ -13,7 +13,6 @@ // limitations under the License. import type { Logger } from "@prosopo/common"; -import { getRandomActiveProvider } from "@prosopo/load-balancer"; import { ApiParams, CaptchaType, @@ -33,18 +32,7 @@ import { checkLangRules } from "../../rules/lang.js"; import { CaptchaManager } from "../captchaManager.js"; import { getBotScore } from "../detection/getBotScore.js"; -const getDefaultEntropy = (): number => { - if (process.env.PROSOPO_ENTROPY) { - const parsed = Number.parseInt(process.env.PROSOPO_ENTROPY); - if (!Number.isNaN(parsed)) { - return parsed; - } - // ignore invalid value and return default - } - return 13337; -}; const DEFAULT_MAX_TIMESTAMP_AGE = 60 * 10 * 1000; // 10 minutes -export const DEFAULT_ENTROPY = getDefaultEntropy(); const getSessionIDPrefix = (host?: string): string => { return host ? host.replace(".prosopo.io", "") : "local"; @@ -88,7 +76,6 @@ export class FrictionlessManager extends CaptchaManager { score: params.score, threshold: params.threshold, scoreComponents: params.scoreComponents, - providerSelectEntropy: params.providerSelectEntropy, ipAddress: params.ipAddress, webView: params.webView ?? false, iFrame: params.iFrame ?? false, @@ -116,7 +103,6 @@ export class FrictionlessManager extends CaptchaManager { score: number, threshold: number, scoreComponents: ScoreComponents, - providerSelectEntropy: number, ipAddress: CompositeIpAddress, captchaType: CaptchaType, siteKey: string, @@ -139,7 +125,6 @@ export class FrictionlessManager extends CaptchaManager { score, threshold, scoreComponents, - providerSelectEntropy, ipAddress, captchaType, solvedImagesCount, @@ -160,32 +145,6 @@ export class FrictionlessManager extends CaptchaManager { return sessionRecord; } - async hostVerified(entropy: number) { - const chosen = await getRandomActiveProvider( - this.config.defaultEnvironment, - entropy, - ); - - const domain = new URL(chosen.provider.url).hostname; - const configDomain = - this.config.host.indexOf("http") > -1 - ? new URL(this.config.host).hostname - : this.config.host; - this.logger.info(() => ({ - data: { entropy, host: this.config.host, domain }, - })); - - if (domain !== configDomain) { - this.logger.info(() => ({ - msg: "Host mismatch", - data: { expected: configDomain, got: domain, entropy }, - })); - return { verified: false, domain }; - } - - return { verified: true, domain }; - } - async sendImageCaptcha( params?: Partial, ): Promise { @@ -195,7 +154,6 @@ export class FrictionlessManager extends CaptchaManager { effectiveParams.score === undefined || effectiveParams.threshold === undefined || !effectiveParams.scoreComponents || - effectiveParams.providerSelectEntropy === undefined || !effectiveParams.ipAddress || effectiveParams.siteKey === undefined ) { @@ -209,7 +167,6 @@ export class FrictionlessManager extends CaptchaManager { effectiveParams.score, effectiveParams.threshold, effectiveParams.scoreComponents, - effectiveParams.providerSelectEntropy, effectiveParams.ipAddress, CaptchaType.image, effectiveParams.siteKey, @@ -242,7 +199,6 @@ export class FrictionlessManager extends CaptchaManager { effectiveParams.score === undefined || effectiveParams.threshold === undefined || !effectiveParams.scoreComponents || - effectiveParams.providerSelectEntropy === undefined || !effectiveParams.ipAddress || effectiveParams.siteKey === undefined ) { @@ -256,7 +212,6 @@ export class FrictionlessManager extends CaptchaManager { effectiveParams.score, effectiveParams.threshold, effectiveParams.scoreComponents, - effectiveParams.providerSelectEntropy, effectiveParams.ipAddress, CaptchaType.pow, effectiveParams.siteKey, @@ -446,7 +401,6 @@ export class FrictionlessManager extends CaptchaManager { // if we run out of keys and the score is still not decrypted, throw an error let baseBotScore: number | undefined; let timestamp: number | undefined; - let providerSelectEntropy: number | undefined; let userId: string | undefined; let userAgent: string | undefined; let webView: boolean | undefined; @@ -466,7 +420,6 @@ export class FrictionlessManager extends CaptchaManager { decryptedHeadHash = decrypted.decryptedHeadHash || ""; const s = decrypted.baseBotScore; const t = decrypted.timestamp; - const p = decrypted.providerSelectEntropy; const a = decrypted.userId; const u = decrypted.userAgent; const w = decrypted.isWebView; @@ -478,7 +431,6 @@ export class FrictionlessManager extends CaptchaManager { key: this.redactKeyForLogging(key), baseBotScore: s, timestamp: t, - entropy: p, userId: a, userAgent: u, webView: w, @@ -488,7 +440,6 @@ export class FrictionlessManager extends CaptchaManager { })); baseBotScore = s; timestamp = t; - providerSelectEntropy = p; userId = a; userAgent = u; webView = w; @@ -503,7 +454,6 @@ export class FrictionlessManager extends CaptchaManager { })); baseBotScore = 1; timestamp = 0; - providerSelectEntropy = DEFAULT_ENTROPY + 1; decryptedHeadHash = ""; decryptionFailed = true; } @@ -512,18 +462,14 @@ export class FrictionlessManager extends CaptchaManager { const baseBotScoreUndefined = baseBotScore === undefined; const timestampUndefined = timestamp === undefined; - const providerSelectEntropyUndefined = providerSelectEntropy === undefined; const undefinedCount = - Number(baseBotScoreUndefined) + - Number(timestampUndefined) + - Number(providerSelectEntropyUndefined); + Number(baseBotScoreUndefined) + Number(timestampUndefined); if (undefinedCount > 0) { this.logger.error(() => ({ - msg: "Error decrypting score: baseBotScore or timestamp or providerSelectEntropy is undefined", + msg: "Error decrypting score: baseBotScore or timestamp is undefined", })); baseBotScore = 1; timestamp = 0; - providerSelectEntropy = DEFAULT_ENTROPY - undefinedCount; decryptedHeadHash = ""; decryptionFailed = true; } @@ -532,7 +478,6 @@ export class FrictionlessManager extends CaptchaManager { data: { baseBotScore: baseBotScore, timestamp: timestamp, - entropy: providerSelectEntropy, userId, userAgent, webView, @@ -546,7 +491,6 @@ export class FrictionlessManager extends CaptchaManager { return { baseBotScore: Number(baseBotScore), timestamp: Number(timestamp), - providerSelectEntropy: Number(providerSelectEntropy), userId, userAgent, webView: webView || false, diff --git a/packages/provider/src/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.ts b/packages/provider/src/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.ts index 9a2840bcfa..ab4b096b3b 100644 --- a/packages/provider/src/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.ts +++ b/packages/provider/src/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.ts @@ -281,7 +281,6 @@ describe("getFrictionlessCaptchaChallenge - context selection", () => { tasksInstance.frictionlessManager.decryptPayload.mockResolvedValue({ baseBotScore: 0, timestamp: Date.now(), - providerSelectEntropy: 0, userId: "u", userAgent: "844bc172f032bdd2d0baae3536c1d66c", webView: true, @@ -297,6 +296,8 @@ describe("getFrictionlessCaptchaChallenge - context selection", () => { const body = { token: "t", headHash: "hh", dapp: "site1", user: "u" }; const { req, res, next } = buildReqRes(body); + // Set headers to match the decrypted payload + req.headers["prosopo-user"] = "u"; // Act // biome-ignore lint/suspicious/noExplicitAny: mock request @@ -312,7 +313,6 @@ describe("getFrictionlessCaptchaChallenge - context selection", () => { tasksInstance.frictionlessManager.decryptPayload.mockResolvedValueOnce({ baseBotScore: 0, timestamp: Date.now(), - providerSelectEntropy: 0, userId: "u", userAgent: "844bc172f032bdd2d0baae3536c1d66c", webView: false, @@ -352,7 +352,6 @@ describe("getFrictionlessCaptchaChallenge - context selection", () => { tasksInstance.frictionlessManager.decryptPayload.mockResolvedValue({ baseBotScore: 0, timestamp: Date.now(), - providerSelectEntropy: 0, userId: "u", userAgent: "844bc172f032bdd2d0baae3536c1d66c", webView: true, // even if webView true @@ -396,7 +395,6 @@ describe("getFrictionlessCaptchaChallenge - context selection", () => { tasksInstance.frictionlessManager.decryptPayload.mockResolvedValue({ baseBotScore: 0, timestamp: Date.now(), - providerSelectEntropy: 0, userId: "u", userAgent: "844bc172f032bdd2d0baae3536c1d66c", webView: false, // even if webView false diff --git a/packages/provider/src/tests/unit/tasks/frictionless/decryptPayload.unit.test.ts b/packages/provider/src/tests/unit/tasks/frictionless/decryptPayload.unit.test.ts index 46c6b39933..e558427dc3 100644 --- a/packages/provider/src/tests/unit/tasks/frictionless/decryptPayload.unit.test.ts +++ b/packages/provider/src/tests/unit/tasks/frictionless/decryptPayload.unit.test.ts @@ -64,7 +64,6 @@ describe("decryptPayload", () => { return { baseBotScore: 1, timestamp: Date.now(), - providerSelectEntropy: 1, }; }), })); @@ -81,7 +80,6 @@ describe("decryptPayload", () => { expect(result).toEqual({ baseBotScore: 1, timestamp: expect.any(Number), - providerSelectEntropy: 1, userId: undefined, userAgent: undefined, webView: false, @@ -115,13 +113,12 @@ describe("decryptPayload", () => { expect(result).toEqual({ baseBotScore: 1, timestamp: expect.any(Number), - providerSelectEntropy: fmImport.DEFAULT_ENTROPY - 1, userId: undefined, userAgent: undefined, webView: false, iFrame: false, decryptedHeadHash: "", - decryptionFailed: true, + decryptionFailed: false, // Decryption succeeds when baseBotScore and timestamp are defined }); }); it("should set values for the payload when there are keys but they fail to decrypt", async () => { @@ -152,7 +149,6 @@ describe("decryptPayload", () => { expect(result).toEqual({ baseBotScore: 1, timestamp: expect.any(Number), - providerSelectEntropy: fmImport.DEFAULT_ENTROPY + 1, userId: undefined, userAgent: undefined, webView: false, @@ -193,7 +189,6 @@ describe("decryptPayload", () => { expect(result).toEqual({ baseBotScore: 1, timestamp: expect.any(Number), - providerSelectEntropy: fmImport.DEFAULT_ENTROPY - 3, userId: undefined, userAgent: undefined, webView: false, diff --git a/packages/provider/src/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.ts b/packages/provider/src/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.ts index d67854ad26..79c1dff99b 100644 --- a/packages/provider/src/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.ts +++ b/packages/provider/src/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.ts @@ -125,7 +125,6 @@ describe("Frictionless Task Manager", () => { const mockScore = 0.5; const mockThreshold = 0.7; const mockScoreComponents = { baseScore: 0.5 }; - const mockEntropy = 12345; const mockIpAddress = getCompositeIpAddress("127.0.0.1"); const mockSiteKey = "mockSiteKey"; @@ -137,7 +136,6 @@ describe("Frictionless Task Manager", () => { mockScore, mockThreshold, mockScoreComponents, - mockEntropy, mockIpAddress, CaptchaType.image, mockSiteKey, @@ -159,7 +157,6 @@ describe("Frictionless Task Manager", () => { const mockScore = 0.5; const mockThreshold = 0.7; const mockScoreComponents = { baseScore: 0.5 }; - const mockEntropy = 12345; const mockIpAddress = getCompositeIpAddress("127.0.0.1"); const mockSiteKey = "mockSiteKey"; @@ -171,7 +168,6 @@ describe("Frictionless Task Manager", () => { score: mockScore, threshold: mockThreshold, scoreComponents: mockScoreComponents, - providerSelectEntropy: mockEntropy, ipAddress: mockIpAddress, webView: false, iFrame: false, @@ -193,7 +189,6 @@ describe("Frictionless Task Manager", () => { const mockScore = 0.5; const mockThreshold = 0.7; const mockScoreComponents = { baseScore: 0.5 }; - const mockEntropy = 12345; const mockIpAddress = getCompositeIpAddress("127.0.0.1"); const mockSiteKey = "mockSiteKey"; @@ -205,7 +200,6 @@ describe("Frictionless Task Manager", () => { score: mockScore, threshold: mockThreshold, scoreComponents: mockScoreComponents, - providerSelectEntropy: mockEntropy, ipAddress: mockIpAddress, webView: false, iFrame: false, diff --git a/packages/provider/src/tests/unit/tasks/powCaptcha/powTasks.unit.test.ts b/packages/provider/src/tests/unit/tasks/powCaptcha/powTasks.unit.test.ts index a944aefe4c..117d262dd4 100644 --- a/packages/provider/src/tests/unit/tasks/powCaptcha/powTasks.unit.test.ts +++ b/packages/provider/src/tests/unit/tasks/powCaptcha/powTasks.unit.test.ts @@ -428,7 +428,6 @@ describe("PowCaptchaManager", () => { score: 0.5, threshold: 0.5, scoreComponents: { baseScore: 0.5 }, - providerSelectEntropy: 13337, ipAddress: getCompositeIpAddress(ipAddress), captchaType: CaptchaType.pow, webView: false, @@ -512,7 +511,6 @@ describe("PowCaptchaManager", () => { score: 0.5, threshold: 0.5, scoreComponents: { baseScore: 0.5 }, - providerSelectEntropy: 13337, ipAddress: getCompositeIpAddress(ipAddress), captchaType: CaptchaType.pow, webView: false, diff --git a/packages/types-database/src/types/provider.ts b/packages/types-database/src/types/provider.ts index df4c62a109..1f4d11c0f1 100644 --- a/packages/types-database/src/types/provider.ts +++ b/packages/types-database/src/types/provider.ts @@ -367,7 +367,6 @@ export const SessionRecordSchema = new Schema({ webView: { type: Number, required: false }, triggeredDetectors: { type: [Number], required: false }, }, - providerSelectEntropy: { type: Number, required: true }, ipAddress: CompositeIpAddressRecordSchemaObj, captchaType: { type: String, enum: CaptchaType, required: true }, solvedImagesCount: { type: Number, required: false }, @@ -409,7 +408,6 @@ SessionRecordSchema.index({ deleted: 1 }); SessionRecordSchema.index({ blocked: 1 }); SessionRecordSchema.index({ sessionId: 1 }, { unique: true }); SessionRecordSchema.index({ userSitekeyIpHash: 1 }); -SessionRecordSchema.index({ providerSelectEntropy: 1 }); SessionRecordSchema.index({ token: 1 }); SessionRecordSchema.index({ siteKey: 1 }, { background: true, sparse: true }); // Compound indexes for session aggregation queries diff --git a/packages/types-env/src/provider.ts b/packages/types-env/src/provider.ts index e68b51f0f8..9c3d0c8b1a 100644 --- a/packages/types-env/src/provider.ts +++ b/packages/types-env/src/provider.ts @@ -16,4 +16,5 @@ import type { ProsopoEnvironment } from "./env.js"; export interface ProviderEnvironment extends ProsopoEnvironment { config: ProsopoConfigOutput; + datasetId?: string; } diff --git a/packages/types/src/provider/api.ts b/packages/types/src/provider/api.ts index 17ff31108e..c7695f5ef1 100644 --- a/packages/types/src/provider/api.ts +++ b/packages/types/src/provider/api.ts @@ -182,7 +182,6 @@ export type Provider = { export type FrontendProvider = { url: string; - datasetId: string; }; export type RandomProvider = { @@ -236,7 +235,7 @@ export interface CaptchaIdAndProof { export const CaptchaRequestBody = object({ [ApiParams.user]: string(), [ApiParams.dapp]: string(), - [ApiParams.datasetId]: union([string(), array(number())]), + [ApiParams.datasetId]: union([string(), array(number())]).optional(), [ApiParams.sessionId]: string().optional(), }); diff --git a/packages/types/src/provider/detection.ts b/packages/types/src/provider/detection.ts index 3e717d65b6..e56a71abff 100644 --- a/packages/types/src/provider/detection.ts +++ b/packages/types/src/provider/detection.ts @@ -14,7 +14,6 @@ export type DetectorResult = { score: number; timestamp: number; - providerSelectEntropy: number; userId: string; userAgent: string; isWebView?: boolean;