@apifuse/provider-sdk 2.2.0-beta.31 → 2.2.0-beta.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/error-resolution.js +0 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/dist/runtime/browser.d.ts +1 -0
- package/dist/runtime/browser.js +344 -27
- package/dist/runtime/choice.d.ts +0 -1
- package/dist/runtime/choice.js +1 -78
- package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
- package/dist/runtime/resolver-vendors/browser.js +68 -16
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +526 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +46 -13
- package/dist/runtime/resolver-vendors/types.d.ts +1 -1
- package/dist/runtime/resolver-vendors/types.js +6 -3
- package/dist/runtime/resolver.d.ts +1 -1
- package/dist/runtime/resolver.js +19 -4
- package/dist/server/serve-implementation.js +1 -2
- package/dist/types.d.ts +9 -1
- package/package.json +1 -1
- package/src/error-resolution.ts +0 -1
- package/src/index.ts +0 -1
- package/src/provider.ts +0 -1
- package/src/runtime/browser.ts +430 -31
- package/src/runtime/choice.ts +1 -91
- package/src/runtime/resolver-vendors/browser.ts +82 -15
- package/src/runtime/resolver-vendors/capsolver.ts +700 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +43 -8
- package/src/runtime/resolver-vendors/types.ts +6 -3
- package/src/runtime/resolver.ts +21 -6
- package/src/server/serve-implementation.ts +0 -2
- package/src/types.ts +10 -1
package/src/runtime/choice.ts
CHANGED
|
@@ -39,8 +39,6 @@ import type {
|
|
|
39
39
|
|
|
40
40
|
export const PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV =
|
|
41
41
|
"APIFUSE__PROVIDER_RUNTIME__CHOICE_TOKEN_MASTER_SECRET";
|
|
42
|
-
export const PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV =
|
|
43
|
-
"APIFUSE__PROVIDER_RUNTIME__CHOICE_WORD_ISSUANCE";
|
|
44
42
|
|
|
45
43
|
const PRIMARY_CHOICE_TOKEN_KID = "v1";
|
|
46
44
|
const MANAGED_CHOICE_TOKEN_VERSION = 1;
|
|
@@ -133,7 +131,6 @@ export function createProviderChoiceContext(
|
|
|
133
131
|
const issuedAtMs = issueOptions.nowMs ?? Date.now();
|
|
134
132
|
const resolvedStorage = resolveIssueStorage(issueOptions.storage, issueOptions.payload);
|
|
135
133
|
if (resolvedStorage.mode === "server") {
|
|
136
|
-
const issuance = resolveChoiceWordIssuance(options.env);
|
|
137
134
|
const keys = hasRequestedChoiceBinding(issueOptions.bind)
|
|
138
135
|
? deriveManagedChoiceKeys({
|
|
139
136
|
masterSecret: resolveMasterSecret(),
|
|
@@ -159,29 +156,8 @@ export function createProviderChoiceContext(
|
|
|
159
156
|
ttl_ms: issueOptions.ttlMs,
|
|
160
157
|
binding,
|
|
161
158
|
} satisfies Omit<ManagedChoiceEnvelope, "payload">;
|
|
162
|
-
if (issuance === "legacy") {
|
|
163
|
-
const legacyKeys =
|
|
164
|
-
keys ??
|
|
165
|
-
deriveManagedChoiceKeys({
|
|
166
|
-
masterSecret: resolveMasterSecret(),
|
|
167
|
-
providerId: options.providerId,
|
|
168
|
-
purpose: issueOptions.purpose,
|
|
169
|
-
kid,
|
|
170
|
-
});
|
|
171
|
-
return issueLegacyServerStoredChoice({
|
|
172
|
-
baseEnvelope,
|
|
173
|
-
issueOptions,
|
|
174
|
-
storage: resolvedStorage.storage,
|
|
175
|
-
contextState: options.state,
|
|
176
|
-
kid,
|
|
177
|
-
keys: legacyKeys,
|
|
178
|
-
issuedAtMs,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
159
|
return issueWordServerStoredChoice({
|
|
182
|
-
baseEnvelope
|
|
183
|
-
...baseEnvelope,
|
|
184
|
-
},
|
|
160
|
+
baseEnvelope,
|
|
185
161
|
issueOptions,
|
|
186
162
|
storage: resolvedStorage.storage,
|
|
187
163
|
contextState: options.state,
|
|
@@ -511,22 +487,6 @@ function createLegacyExplicitParseResult(options: {
|
|
|
511
487
|
};
|
|
512
488
|
}
|
|
513
489
|
|
|
514
|
-
function resolveChoiceWordIssuance(env?: EnvContext): "legacy" | "word" {
|
|
515
|
-
const configured = env?.get(PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV);
|
|
516
|
-
const value = configured?.trim() ?? "";
|
|
517
|
-
if (value === "" || value === "legacy") return "legacy";
|
|
518
|
-
if (value === "word") return "word";
|
|
519
|
-
throw new ProviderError(
|
|
520
|
-
`Unsupported provider choice word issuance mode "${value}". Expected "legacy" or "word".`,
|
|
521
|
-
{
|
|
522
|
-
code: "CHOICE_WORD_ISSUANCE_INVALID",
|
|
523
|
-
category: "input_validation",
|
|
524
|
-
retryable: false,
|
|
525
|
-
details: { env: PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV },
|
|
526
|
-
},
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
490
|
function resolveChoiceMasterSecret(options: CreateProviderChoiceContextOptions): string {
|
|
531
491
|
const configured =
|
|
532
492
|
options.masterSecret ?? options.env?.get(PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV);
|
|
@@ -657,56 +617,6 @@ async function issueWordServerStoredChoice<TPayload extends ProviderChoiceTokenP
|
|
|
657
617
|
});
|
|
658
618
|
}
|
|
659
619
|
|
|
660
|
-
/**
|
|
661
|
-
* Beta.28-compatible server handle issuance. The state stores the payload and
|
|
662
|
-
* the client receives the existing six-part encrypted envelope whose payload
|
|
663
|
-
* is a server-state handle.
|
|
664
|
-
*/
|
|
665
|
-
async function issueLegacyServerStoredChoice<TPayload extends ProviderChoiceTokenPayload>(options: {
|
|
666
|
-
readonly baseEnvelope: Omit<ManagedChoiceEnvelope, "payload">;
|
|
667
|
-
readonly issueOptions: ProviderChoiceIssueOptions<TPayload>;
|
|
668
|
-
readonly storage: ServerProviderChoiceStorageOptions;
|
|
669
|
-
readonly contextState?: ProviderRuntimeState;
|
|
670
|
-
readonly kid: string;
|
|
671
|
-
readonly keys: ManagedChoiceKeys;
|
|
672
|
-
readonly issuedAtMs: number;
|
|
673
|
-
}): Promise<string> {
|
|
674
|
-
const serializedPayload = serializeChoicePayload(options.issueOptions.payload);
|
|
675
|
-
const payloadBytes = Buffer.byteLength(serializedPayload, "utf8");
|
|
676
|
-
if (payloadBytes > options.storage.maxValueBytes) {
|
|
677
|
-
throw new ProviderError("Provider choice payload exceeds state storage policy.", {
|
|
678
|
-
code: "CHOICE_STATE_PAYLOAD_TOO_LARGE",
|
|
679
|
-
category: "input_validation",
|
|
680
|
-
retryable: false,
|
|
681
|
-
details: { maxValueBytes: options.storage.maxValueBytes, payloadBytes },
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
const stateId = `choice_${randomBytes(16).toString("base64url")}`;
|
|
685
|
-
const namespace = resolveChoiceStateNamespace({
|
|
686
|
-
storage: options.storage,
|
|
687
|
-
contextState: options.contextState,
|
|
688
|
-
ttlMs: options.issueOptions.ttlMs,
|
|
689
|
-
});
|
|
690
|
-
await namespace.set(optionsStateKey(stateId), options.issueOptions.payload, {
|
|
691
|
-
ttl: stateTtl(options.storage, options.issueOptions.ttlMs),
|
|
692
|
-
});
|
|
693
|
-
const envelope: ManagedChoiceEnvelope = {
|
|
694
|
-
...options.baseEnvelope,
|
|
695
|
-
payload: {
|
|
696
|
-
storage: "server",
|
|
697
|
-
state_id: stateId,
|
|
698
|
-
payload_digest: digestChoicePayload(serializedPayload),
|
|
699
|
-
created_at_ms: options.issuedAtMs,
|
|
700
|
-
},
|
|
701
|
-
};
|
|
702
|
-
return encryptManagedChoiceToken({
|
|
703
|
-
prefix: options.issueOptions.prefix,
|
|
704
|
-
kid: options.kid,
|
|
705
|
-
envelope,
|
|
706
|
-
keys: options.keys,
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
|
|
710
620
|
async function parseWordServerStoredChoice(options: {
|
|
711
621
|
readonly stateKey: string;
|
|
712
622
|
readonly parseOptions: ProviderChoiceParseOptions;
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
|
|
19
19
|
const BROWSER_VENDOR_ID = "browser" as const;
|
|
20
20
|
const DEFAULT_COOKIE_POLL_INTERVAL_MS = 100;
|
|
21
|
+
const AWS_WAF_CHALLENGE_INFRASTRUCTURE_HOST_SUFFIX = ".awswaf.com";
|
|
22
|
+
const RESOLVER_DOCUMENT_CONTENT_SECURITY_POLICY = "connect-src http: https:; worker-src 'none'";
|
|
21
23
|
|
|
22
24
|
const SUCCESS_COOKIE_NAMES = {
|
|
23
25
|
aws_waf: "aws-waf-token",
|
|
@@ -28,6 +30,22 @@ type SupportedBrowserChallengeKind = keyof typeof SUCCESS_COOKIE_NAMES;
|
|
|
28
30
|
|
|
29
31
|
type BrowserClientFactory = (options: BrowserClientOptions) => BrowserClient;
|
|
30
32
|
|
|
33
|
+
let createResolverBrowserClient: BrowserClientFactory = createBrowserClient;
|
|
34
|
+
|
|
35
|
+
/** Internal test seam; deliberately not re-exported from the package root. */
|
|
36
|
+
export function swapBrowserResolverClientFactoryForTests(
|
|
37
|
+
factory: BrowserClientFactory | undefined,
|
|
38
|
+
): () => void {
|
|
39
|
+
const original = createResolverBrowserClient;
|
|
40
|
+
createResolverBrowserClient = factory ?? createBrowserClient;
|
|
41
|
+
let restored = false;
|
|
42
|
+
return () => {
|
|
43
|
+
if (restored) return;
|
|
44
|
+
restored = true;
|
|
45
|
+
createResolverBrowserClient = original;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
export interface BrowserResolverVendorOptions {
|
|
32
50
|
readonly cdpUrl?: string;
|
|
33
51
|
readonly timeoutMs: number;
|
|
@@ -200,31 +218,77 @@ function selectSuccessCookie(
|
|
|
200
218
|
|
|
201
219
|
async function solveInPage(
|
|
202
220
|
page: BrowserPage,
|
|
221
|
+
challengeKind: SupportedBrowserChallengeKind,
|
|
203
222
|
pageUrl: string,
|
|
223
|
+
allowedHosts: readonly string[],
|
|
204
224
|
successCookieName: string,
|
|
205
225
|
pollIntervalMs: number,
|
|
206
226
|
signal: AbortSignal,
|
|
207
227
|
): Promise<Extract<ChallengeSolution, { readonly form: "cookies" }>> {
|
|
208
|
-
await
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
228
|
+
return await page.withResourcePolicy(
|
|
229
|
+
{
|
|
230
|
+
allowedMethods: ["GET", "HEAD", "POST"],
|
|
231
|
+
documentContentSecurityPolicy: RESOLVER_DOCUMENT_CONTENT_SECURITY_POLICY,
|
|
232
|
+
routes: [
|
|
233
|
+
{
|
|
234
|
+
match: () => true,
|
|
235
|
+
handle: (request) => ({
|
|
236
|
+
action: isResolverBrowserRequestAllowed(request.url, challengeKind, allowedHosts)
|
|
237
|
+
? "continue"
|
|
238
|
+
: "block",
|
|
239
|
+
}),
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
async () => {
|
|
214
244
|
const userAgent = await raceWithAbort(
|
|
215
245
|
() => page.evaluate<string>("navigator.userAgent"),
|
|
216
246
|
signal,
|
|
217
247
|
);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
248
|
+
await raceWithAbort(() => page.goto(pageUrl), signal);
|
|
249
|
+
|
|
250
|
+
while (true) {
|
|
251
|
+
const cookies = await raceWithAbort(() => page.cookies(), signal);
|
|
252
|
+
const successCookie = selectSuccessCookie(cookies, successCookieName, pageUrl);
|
|
253
|
+
if (successCookie) {
|
|
254
|
+
return {
|
|
255
|
+
form: "cookies",
|
|
256
|
+
cookies: { [successCookieName]: successCookie.value },
|
|
257
|
+
userAgent,
|
|
258
|
+
...(successCookie.expires === undefined ? {} : { expires: successCookie.expires }),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
await abortableDelay(pollIntervalMs, signal);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
);
|
|
266
|
+
}
|
|
225
267
|
|
|
226
|
-
|
|
268
|
+
function isResolverBrowserRequestAllowed(
|
|
269
|
+
targetUrl: string,
|
|
270
|
+
challengeKind: SupportedBrowserChallengeKind,
|
|
271
|
+
allowedHosts: readonly string[],
|
|
272
|
+
): boolean {
|
|
273
|
+
try {
|
|
274
|
+
assertResolverHostAllowed(targetUrl, allowedHosts);
|
|
275
|
+
return true;
|
|
276
|
+
} catch {
|
|
277
|
+
if (challengeKind !== "aws_waf") return false;
|
|
227
278
|
}
|
|
279
|
+
|
|
280
|
+
let target: URL;
|
|
281
|
+
try {
|
|
282
|
+
target = new URL(targetUrl);
|
|
283
|
+
} catch {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
const hostname = normalizedResolverHostname(target.hostname);
|
|
287
|
+
return (
|
|
288
|
+
target.protocol === "https:" &&
|
|
289
|
+
hostname.length > AWS_WAF_CHALLENGE_INFRASTRUCTURE_HOST_SUFFIX.length &&
|
|
290
|
+
hostname.endsWith(AWS_WAF_CHALLENGE_INFRASTRUCTURE_HOST_SUFFIX)
|
|
291
|
+
);
|
|
228
292
|
}
|
|
229
293
|
|
|
230
294
|
const POOL_ALLOCATION_EXHAUSTED_CODES = new Set([
|
|
@@ -297,7 +361,7 @@ async function closeBrowserClient(
|
|
|
297
361
|
export function createBrowserResolverVendorAdapter(
|
|
298
362
|
options: BrowserResolverVendorOptions,
|
|
299
363
|
): BrowserResolverVendorAdapter {
|
|
300
|
-
const createClient = options.createClient ??
|
|
364
|
+
const createClient = options.createClient ?? createResolverBrowserClient;
|
|
301
365
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_COOKIE_POLL_INTERVAL_MS;
|
|
302
366
|
|
|
303
367
|
return {
|
|
@@ -352,12 +416,15 @@ export function createBrowserResolverVendorAdapter(
|
|
|
352
416
|
cdpUrl: cdpUrl ?? "",
|
|
353
417
|
...(proxyUrl === undefined ? {} : { proxy: proxyUrl }),
|
|
354
418
|
requireCdpPool: cdpUrl !== undefined,
|
|
419
|
+
serviceWorkers: "block",
|
|
355
420
|
});
|
|
356
421
|
const contextOperation = client.withIsolatedContext(async (page) => {
|
|
357
422
|
handlerEntered = true;
|
|
358
423
|
return await solveInPage(
|
|
359
424
|
page,
|
|
425
|
+
challengeKind,
|
|
360
426
|
challenge.pageUrl,
|
|
427
|
+
options.allowedHosts,
|
|
361
428
|
SUCCESS_COOKIE_NAMES[challengeKind],
|
|
362
429
|
pollIntervalMs,
|
|
363
430
|
solveController.signal,
|