qa360 2.2.20 → 2.3.1
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/README.md +155 -262
- package/{cli/dist → dist}/commands/ai.js +1 -1
- package/{cli/dist → dist}/commands/coverage.js +1 -1
- package/{cli/dist → dist}/commands/crawl.d.ts +12 -1
- package/{cli/dist → dist}/commands/crawl.js +70 -9
- package/{cli/dist → dist}/commands/doctor.js +2 -2
- package/{cli/dist → dist}/commands/explain.js +2 -2
- package/{cli/dist → dist}/commands/flakiness.js +1 -1
- package/{cli/dist → dist}/commands/generate.js +1 -1
- package/{cli/dist → dist}/commands/history.js +1 -1
- package/{cli/dist → dist}/commands/monitor.js +3 -3
- package/{cli/dist → dist}/commands/ollama.js +1 -1
- package/{cli/dist → dist}/commands/pack.js +2 -2
- package/{cli/dist → dist}/commands/regression.js +1 -1
- package/{cli/dist → dist}/commands/repair.js +1 -1
- package/{cli/dist → dist}/commands/retry.js +1 -1
- package/{cli/dist → dist}/commands/run.d.ts +1 -1
- package/{cli/dist → dist}/commands/run.js +1 -1
- package/{cli/dist → dist}/commands/secrets.js +1 -1
- package/{cli/dist → dist}/commands/serve.js +1 -1
- package/{cli/dist → dist}/commands/slo.js +1 -1
- package/{cli/dist → dist}/commands/verify.js +1 -1
- package/{cli/dist → dist}/core/adapters/playwright-native-api.d.ts +2 -0
- package/{cli/dist → dist}/core/adapters/playwright-native-api.js +20 -1
- package/{cli/dist → dist}/core/adapters/playwright-ui.d.ts +21 -0
- package/dist/core/adapters/playwright-ui.js +2050 -0
- package/{cli/dist → dist}/core/ai/ollama-provider.js +15 -3
- package/{cli/dist → dist}/core/artifacts/ui-artifacts.js +24 -4
- package/dist/core/auth/backup-codes-provider.d.ts +91 -0
- package/dist/core/auth/backup-codes-provider.js +215 -0
- package/{cli/dist → dist}/core/auth/basic-auth-provider.d.ts +6 -0
- package/{cli/dist → dist}/core/auth/basic-auth-provider.js +24 -6
- package/dist/core/auth/digest-auth-provider.d.ts +116 -0
- package/dist/core/auth/digest-auth-provider.js +244 -0
- package/dist/core/auth/hcaptcha-handler.d.ts +103 -0
- package/dist/core/auth/hcaptcha-handler.js +288 -0
- package/{cli/dist → dist}/core/auth/index.d.ts +81 -4
- package/{cli/dist → dist}/core/auth/index.js +15 -1
- package/dist/core/auth/oauth-handler.d.ts +408 -0
- package/dist/core/auth/oauth-handler.js +636 -0
- package/{cli/dist → dist}/core/auth/oauth2-provider.d.ts +9 -0
- package/dist/core/auth/oauth2-provider.js +227 -0
- package/dist/core/auth/otp-provider.d.ts +93 -0
- package/dist/core/auth/otp-provider.js +288 -0
- package/dist/core/auth/recaptcha-handler.d.ts +119 -0
- package/dist/core/auth/recaptcha-handler.js +301 -0
- package/dist/core/auth/remember-me-handler.d.ts +142 -0
- package/dist/core/auth/remember-me-handler.js +255 -0
- package/dist/core/auth/saml-handler.d.ts +173 -0
- package/dist/core/auth/saml-handler.js +364 -0
- package/dist/core/auth/webauthn-handler.d.ts +182 -0
- package/dist/core/auth/webauthn-handler.js +310 -0
- package/dist/core/crawler/advanced-interactions.d.ts +342 -0
- package/dist/core/crawler/advanced-interactions.js +1069 -0
- package/dist/core/crawler/blob-url-download-handler.d.ts +145 -0
- package/dist/core/crawler/blob-url-download-handler.js +392 -0
- package/dist/core/crawler/consent-handler.d.ts +49 -0
- package/dist/core/crawler/consent-handler.js +258 -0
- package/dist/core/crawler/cookie-manager.d.ts +166 -0
- package/dist/core/crawler/cookie-manager.js +353 -0
- package/dist/core/crawler/coop-coep-handler.d.ts +136 -0
- package/dist/core/crawler/coop-coep-handler.js +338 -0
- package/dist/core/crawler/csp-handler.d.ts +151 -0
- package/dist/core/crawler/csp-handler.js +415 -0
- package/dist/core/crawler/download-handler.d.ts +155 -0
- package/dist/core/crawler/download-handler.js +370 -0
- package/dist/core/crawler/email-testing-handler.d.ts +214 -0
- package/dist/core/crawler/email-testing-handler.js +398 -0
- package/dist/core/crawler/error-tracking-handler.d.ts +177 -0
- package/dist/core/crawler/error-tracking-handler.js +378 -0
- package/dist/core/crawler/form-handler.d.ts +100 -0
- package/dist/core/crawler/form-handler.js +465 -0
- package/dist/core/crawler/framework-wait-handler.d.ts +96 -0
- package/dist/core/crawler/framework-wait-handler.js +464 -0
- package/dist/core/crawler/geolocation-handler.d.ts +112 -0
- package/dist/core/crawler/geolocation-handler.js +276 -0
- package/dist/core/crawler/index.d.ts +78 -0
- package/{cli/dist → dist}/core/crawler/index.js +74 -1
- package/dist/core/crawler/intelligent-selector-generator.d.ts +164 -0
- package/dist/core/crawler/intelligent-selector-generator.js +612 -0
- package/{cli/dist → dist}/core/crawler/journey-generator.js +44 -1
- package/{cli/dist → dist}/core/crawler/page-analyzer.d.ts +16 -1
- package/{cli/dist → dist}/core/crawler/page-analyzer.js +469 -17
- package/dist/core/crawler/permissions-handler.d.ts +112 -0
- package/dist/core/crawler/permissions-handler.js +236 -0
- package/dist/core/crawler/permissions-policy-handler.d.ts +113 -0
- package/dist/core/crawler/permissions-policy-handler.js +402 -0
- package/dist/core/crawler/presets.d.ts +100 -0
- package/dist/core/crawler/presets.js +887 -0
- package/dist/core/crawler/repl-debug-handler.d.ts +105 -0
- package/dist/core/crawler/repl-debug-handler.js +552 -0
- package/dist/core/crawler/reporting-api-handler.d.ts +212 -0
- package/dist/core/crawler/reporting-api-handler.js +344 -0
- package/{cli/dist → dist}/core/crawler/selector-generator.d.ts +9 -0
- package/{cli/dist → dist}/core/crawler/selector-generator.js +99 -23
- package/dist/core/crawler/site-profiler.d.ts +89 -0
- package/dist/core/crawler/site-profiler.js +290 -0
- package/dist/core/crawler/sourcemaps-handler.d.ts +144 -0
- package/dist/core/crawler/sourcemaps-handler.js +420 -0
- package/dist/core/crawler/stacked-modals-handler.d.ts +118 -0
- package/dist/core/crawler/stacked-modals-handler.js +429 -0
- package/dist/core/crawler/trusted-types-handler.d.ts +149 -0
- package/dist/core/crawler/trusted-types-handler.js +413 -0
- package/{cli/dist → dist}/core/crawler/types.d.ts +68 -2
- package/dist/core/crawler/wait-strategies.d.ts +108 -0
- package/dist/core/crawler/wait-strategies.js +399 -0
- package/dist/core/fixtures/factories.d.ts +180 -0
- package/dist/core/fixtures/factories.js +279 -0
- package/dist/core/fixtures/index.d.ts +6 -0
- package/dist/core/fixtures/index.js +6 -0
- package/{cli/dist → dist}/core/generation/crawler-pack-generator.d.ts +13 -3
- package/dist/core/generation/crawler-pack-generator.js +232 -0
- package/{cli/dist → dist}/core/generation/index.d.ts +2 -0
- package/{cli/dist → dist}/core/generation/index.js +2 -0
- package/{cli/dist → dist}/core/index.d.ts +2 -0
- package/{cli/dist → dist}/core/index.js +4 -0
- package/dist/core/network/index.d.ts +7 -0
- package/dist/core/network/index.js +7 -0
- package/dist/core/network/network-manager.d.ts +237 -0
- package/dist/core/network/network-manager.js +343 -0
- package/dist/core/network/network-simulator.d.ts +158 -0
- package/dist/core/network/network-simulator.js +261 -0
- package/{cli/dist → dist}/core/pack/validator.js +2 -2
- package/{cli/dist → dist}/core/pack-v2/migrator.d.ts +5 -0
- package/{cli/dist → dist}/core/pack-v2/migrator.js +81 -6
- package/{cli/dist → dist}/core/pack-v2/validator.js +4 -3
- package/{cli/dist → dist}/core/pom/base-page.js +1 -1
- package/{cli/dist → dist}/core/pom/loader.js +1 -1
- package/dist/core/reporting/index.d.ts +9 -0
- package/dist/core/reporting/index.js +10 -0
- package/dist/core/reporting/junit-reporter.d.ts +114 -0
- package/dist/core/reporting/junit-reporter.js +306 -0
- package/{cli/dist → dist}/core/runner/e2e-helpers.d.ts +1 -1
- package/{cli/dist → dist}/core/runner/e2e-helpers.js +2 -2
- package/{cli/dist → dist}/core/runner/phase3-runner.d.ts +3 -0
- package/{cli/dist → dist}/core/runner/phase3-runner.js +45 -14
- package/dist/core/sharding/test-sharding.d.ts +137 -0
- package/dist/core/sharding/test-sharding.js +233 -0
- package/dist/core/storage/cookie-manager.d.ts +160 -0
- package/dist/core/storage/cookie-manager.js +268 -0
- package/dist/core/storage/index.d.ts +7 -0
- package/dist/core/storage/index.js +7 -0
- package/dist/core/storage/storage-helpers.d.ts +138 -0
- package/dist/core/storage/storage-helpers.js +315 -0
- package/dist/core/test-helpers/index.d.ts +6 -0
- package/dist/core/test-helpers/index.js +6 -0
- package/dist/core/test-helpers/state-reset.d.ts +119 -0
- package/dist/core/test-helpers/state-reset.js +234 -0
- package/{cli/dist → dist}/core/types/pack-v1.d.ts +15 -2
- package/{cli/dist → dist}/core/types/pack-v2.d.ts +1 -1
- package/dist/core/upload/chunked-uploader.d.ts +150 -0
- package/dist/core/upload/chunked-uploader.js +289 -0
- package/dist/core/upload/index.d.ts +11 -0
- package/dist/core/upload/index.js +8 -0
- package/dist/core/upload/mime-validator.d.ts +119 -0
- package/dist/core/upload/mime-validator.js +373 -0
- package/dist/core/upload/presigned-uploader.d.ts +118 -0
- package/dist/core/upload/presigned-uploader.js +274 -0
- package/dist/core/utils/device-emulation.d.ts +194 -0
- package/dist/core/utils/device-emulation.js +380 -0
- package/dist/core/utils/index.d.ts +8 -0
- package/dist/core/utils/index.js +8 -0
- package/dist/core/utils/retry.d.ts +145 -0
- package/dist/core/utils/retry.js +242 -0
- package/dist/core/utils/smart-wait.d.ts +133 -0
- package/dist/core/utils/smart-wait.js +417 -0
- package/dist/core/visual/index.d.ts +7 -0
- package/dist/core/visual/index.js +7 -0
- package/dist/core/visual/pixel-diff.d.ts +87 -0
- package/dist/core/visual/pixel-diff.js +213 -0
- package/dist/core/visual/screenshot-helper.d.ts +130 -0
- package/dist/core/visual/screenshot-helper.js +223 -0
- package/{cli/dist → dist}/utils/config.d.ts +1 -1
- package/examples/README.md +160 -0
- package/examples/accessibility.yml +48 -0
- package/examples/api-basic.yml +27 -0
- package/examples/complete.yml +146 -0
- package/examples/crawler.yml +38 -0
- package/examples/fullstack.yml +78 -0
- package/examples/security.yml +58 -0
- package/examples/ui-advanced.yml +49 -0
- package/examples/ui-basic.yml +24 -0
- package/package.json +33 -67
- package/CHANGELOG.md +0 -262
- package/CONTRIBUTING.md +0 -273
- package/QUICK_START.md +0 -191
- package/cli/CHANGELOG.md +0 -84
- package/cli/LICENSE +0 -24
- package/cli/README.md +0 -222
- package/cli/dist/core/adapters/playwright-ui.js +0 -864
- package/cli/dist/core/auth/oauth2-provider.js +0 -114
- package/cli/dist/core/coverage/analyzer.d.ts +0 -101
- package/cli/dist/core/coverage/analyzer.js +0 -415
- package/cli/dist/core/coverage/collector.d.ts +0 -74
- package/cli/dist/core/coverage/collector.js +0 -459
- package/cli/dist/core/coverage/config.d.ts +0 -37
- package/cli/dist/core/coverage/config.js +0 -156
- package/cli/dist/core/coverage/index.d.ts +0 -11
- package/cli/dist/core/coverage/index.js +0 -15
- package/cli/dist/core/coverage/types.d.ts +0 -267
- package/cli/dist/core/coverage/types.js +0 -6
- package/cli/dist/core/coverage/vault.d.ts +0 -95
- package/cli/dist/core/coverage/vault.js +0 -405
- package/cli/dist/core/crawler/index.d.ts +0 -57
- package/cli/dist/core/fixtures/index.d.ts +0 -8
- package/cli/dist/core/fixtures/index.js +0 -8
- package/cli/dist/core/generation/crawler-pack-generator.js +0 -231
- package/cli/dist/core/reporting/index.d.ts +0 -6
- package/cli/dist/core/reporting/index.js +0 -6
- package/cli/dist/core/visual/index.d.ts +0 -6
- package/cli/dist/core/visual/index.js +0 -6
- package/cli/package.json +0 -76
- package/core/LICENSE +0 -24
- package/core/README.md +0 -64
- package/core/package.json +0 -81
- package/core/schemas/pack.schema.json +0 -236
- /package/{cli/bin → bin}/qa360.js +0 -0
- /package/{cli/dist → dist}/cli-minimal.d.ts +0 -0
- /package/{cli/dist → dist}/cli-minimal.js +0 -0
- /package/{cli/dist → dist}/commands/ai.d.ts +0 -0
- /package/{cli/dist → dist}/commands/ask.d.ts +0 -0
- /package/{cli/dist → dist}/commands/ask.js +0 -0
- /package/{cli/dist → dist}/commands/coverage.d.ts +0 -0
- /package/{cli/dist → dist}/commands/doctor.d.ts +0 -0
- /package/{cli/dist → dist}/commands/examples.d.ts +0 -0
- /package/{cli/dist → dist}/commands/examples.js +0 -0
- /package/{cli/dist → dist}/commands/explain.d.ts +0 -0
- /package/{cli/dist → dist}/commands/flakiness.d.ts +0 -0
- /package/{cli/dist → dist}/commands/generate.d.ts +0 -0
- /package/{cli/dist → dist}/commands/history.d.ts +0 -0
- /package/{cli/dist → dist}/commands/init.d.ts +0 -0
- /package/{cli/dist → dist}/commands/init.js +0 -0
- /package/{cli/dist → dist}/commands/monitor.d.ts +0 -0
- /package/{cli/dist → dist}/commands/ollama.d.ts +0 -0
- /package/{cli/dist → dist}/commands/pack.d.ts +0 -0
- /package/{cli/dist → dist}/commands/regression.d.ts +0 -0
- /package/{cli/dist → dist}/commands/repair.d.ts +0 -0
- /package/{cli/dist → dist}/commands/report.d.ts +0 -0
- /package/{cli/dist → dist}/commands/report.js +0 -0
- /package/{cli/dist → dist}/commands/retry.d.ts +0 -0
- /package/{cli/dist → dist}/commands/scan.d.ts +0 -0
- /package/{cli/dist → dist}/commands/scan.js +0 -0
- /package/{cli/dist → dist}/commands/secrets.d.ts +0 -0
- /package/{cli/dist → dist}/commands/serve.d.ts +0 -0
- /package/{cli/dist → dist}/commands/slo.d.ts +0 -0
- /package/{cli/dist → dist}/commands/verify.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/gitleaks-secrets.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/gitleaks-secrets.js +0 -0
- /package/{cli/dist → dist}/core/adapters/jest-adapter.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/jest-adapter.js +0 -0
- /package/{cli/dist → dist}/core/adapters/k6-perf.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/k6-perf.js +0 -0
- /package/{cli/dist → dist}/core/adapters/osv-deps.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/osv-deps.js +0 -0
- /package/{cli/dist → dist}/core/adapters/playwright-native-adapter.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/playwright-native-adapter.js +0 -0
- /package/{cli/dist → dist}/core/adapters/pytest-adapter.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/pytest-adapter.js +0 -0
- /package/{cli/dist → dist}/core/adapters/semgrep-sast.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/semgrep-sast.js +0 -0
- /package/{cli/dist → dist}/core/adapters/unit-test-types.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/unit-test-types.js +0 -0
- /package/{cli/dist → dist}/core/adapters/vitest-adapter.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/vitest-adapter.js +0 -0
- /package/{cli/dist → dist}/core/adapters/zap-dast.d.ts +0 -0
- /package/{cli/dist → dist}/core/adapters/zap-dast.js +0 -0
- /package/{cli/dist → dist}/core/ai/anthropic-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/anthropic-provider.js +0 -0
- /package/{cli/dist → dist}/core/ai/deepseek-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/deepseek-provider.js +0 -0
- /package/{cli/dist → dist}/core/ai/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/index.js +0 -0
- /package/{cli/dist → dist}/core/ai/llm-client.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/llm-client.js +0 -0
- /package/{cli/dist → dist}/core/ai/mock-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/mock-provider.js +0 -0
- /package/{cli/dist → dist}/core/ai/ollama-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/openai-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/openai-provider.js +0 -0
- /package/{cli/dist → dist}/core/ai/provider-factory.d.ts +0 -0
- /package/{cli/dist → dist}/core/ai/provider-factory.js +0 -0
- /package/{cli/dist → dist}/core/artifacts/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/artifacts/index.js +0 -0
- /package/{cli/dist → dist}/core/artifacts/ui-artifacts.d.ts +0 -0
- /package/{cli/dist → dist}/core/assertions/engine.d.ts +0 -0
- /package/{cli/dist → dist}/core/assertions/engine.js +0 -0
- /package/{cli/dist → dist}/core/assertions/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/assertions/index.js +0 -0
- /package/{cli/dist → dist}/core/assertions/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/assertions/types.js +0 -0
- /package/{cli/dist → dist}/core/auth/api-key-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/api-key-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/aws-iam-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/aws-iam-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/azure-ad-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/azure-ad-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/gcp-adc-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/gcp-adc-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/jwt-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/jwt-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/manager.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/manager.js +0 -0
- /package/{cli/dist → dist}/core/auth/totp-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/totp-provider.js +0 -0
- /package/{cli/dist → dist}/core/auth/ui-login-provider.d.ts +0 -0
- /package/{cli/dist → dist}/core/auth/ui-login-provider.js +0 -0
- /package/{cli/dist → dist}/core/cache/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/cache/index.js +0 -0
- /package/{cli/dist → dist}/core/cache/lru-cache.d.ts +0 -0
- /package/{cli/dist → dist}/core/cache/lru-cache.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/analyzer.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/analyzer.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/collector.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/collector.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/config.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/config.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/index.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/index.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/types.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/types.js +0 -0
- /package/{cli/dist/core → dist}/core/coverage/vault.d.ts +0 -0
- /package/{cli/dist/core → dist}/core/coverage/vault.js +0 -0
- /package/{cli/dist → dist}/core/crawler/journey-generator.d.ts +0 -0
- /package/{cli/dist → dist}/core/crawler/types.js +0 -0
- /package/{cli/dist → dist}/core/dashboard/assets.d.ts +0 -0
- /package/{cli/dist → dist}/core/dashboard/assets.js +0 -0
- /package/{cli/dist → dist}/core/dashboard/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/dashboard/index.js +0 -0
- /package/{cli/dist → dist}/core/dashboard/server.d.ts +0 -0
- /package/{cli/dist → dist}/core/dashboard/server.js +0 -0
- /package/{cli/dist → dist}/core/dashboard/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/dashboard/types.js +0 -0
- /package/{cli/dist → dist}/core/discoverer/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/discoverer/index.js +0 -0
- /package/{cli/dist → dist}/core/fixtures/loader.d.ts +0 -0
- /package/{cli/dist → dist}/core/fixtures/loader.js +0 -0
- /package/{cli/dist → dist}/core/fixtures/resolver.d.ts +0 -0
- /package/{cli/dist → dist}/core/fixtures/resolver.js +0 -0
- /package/{cli/dist → dist}/core/fixtures/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/fixtures/types.js +0 -0
- /package/{cli/dist → dist}/core/flakiness/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/flakiness/index.js +0 -0
- /package/{cli/dist → dist}/core/generation/code-formatter.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/code-formatter.js +0 -0
- /package/{cli/dist → dist}/core/generation/code-generator.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/code-generator.js +0 -0
- /package/{cli/dist → dist}/core/generation/generator.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/generator.js +0 -0
- /package/{cli/dist → dist}/core/generation/pack-generator.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/pack-generator.js +0 -0
- /package/{cli/dist → dist}/core/generation/prompt-builder.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/prompt-builder.js +0 -0
- /package/{cli/dist → dist}/core/generation/source-analyzer.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/source-analyzer.js +0 -0
- /package/{cli/dist → dist}/core/generation/test-optimizer.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/test-optimizer.js +0 -0
- /package/{cli/dist → dist}/core/generation/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/generation/types.js +0 -0
- /package/{cli/dist → dist}/core/hooks/compose.d.ts +0 -0
- /package/{cli/dist → dist}/core/hooks/compose.js +0 -0
- /package/{cli/dist → dist}/core/hooks/runner.d.ts +0 -0
- /package/{cli/dist → dist}/core/hooks/runner.js +0 -0
- /package/{cli/dist → dist}/core/pack/migrator.d.ts +0 -0
- /package/{cli/dist → dist}/core/pack/migrator.js +0 -0
- /package/{cli/dist → dist}/core/pack/validator.d.ts +0 -0
- /package/{cli/dist → dist}/core/pack-v2/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/pack-v2/index.js +0 -0
- /package/{cli/dist → dist}/core/pack-v2/loader.d.ts +0 -0
- /package/{cli/dist → dist}/core/pack-v2/loader.js +0 -0
- /package/{cli/dist → dist}/core/pack-v2/validator.d.ts +0 -0
- /package/{cli/dist → dist}/core/parallel/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/parallel/index.js +0 -0
- /package/{cli/dist → dist}/core/parallel/parallel-runner.d.ts +0 -0
- /package/{cli/dist → dist}/core/parallel/parallel-runner.js +0 -0
- /package/{cli/dist → dist}/core/pom/base-page.d.ts +0 -0
- /package/{cli/dist → dist}/core/pom/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/pom/index.js +0 -0
- /package/{cli/dist → dist}/core/pom/loader.d.ts +0 -0
- /package/{cli/dist → dist}/core/pom/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/pom/types.js +0 -0
- /package/{cli/dist → dist}/core/proof/bundle.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/bundle.js +0 -0
- /package/{cli/dist → dist}/core/proof/canonicalize.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/canonicalize.js +0 -0
- /package/{cli/dist → dist}/core/proof/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/index.js +0 -0
- /package/{cli/dist → dist}/core/proof/schema.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/schema.js +0 -0
- /package/{cli/dist → dist}/core/proof/signer.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/signer.js +0 -0
- /package/{cli/dist → dist}/core/proof/verifier.d.ts +0 -0
- /package/{cli/dist → dist}/core/proof/verifier.js +0 -0
- /package/{cli/dist → dist}/core/regression/detector.d.ts +0 -0
- /package/{cli/dist → dist}/core/regression/detector.js +0 -0
- /package/{cli/dist → dist}/core/regression/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/regression/index.js +0 -0
- /package/{cli/dist → dist}/core/regression/trend-analyzer.d.ts +0 -0
- /package/{cli/dist → dist}/core/regression/trend-analyzer.js +0 -0
- /package/{cli/dist → dist}/core/regression/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/regression/types.js +0 -0
- /package/{cli/dist → dist}/core/regression/vault.d.ts +0 -0
- /package/{cli/dist → dist}/core/regression/vault.js +0 -0
- /package/{cli/dist → dist}/core/repair/engine/fixer.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/engine/fixer.js +0 -0
- /package/{cli/dist → dist}/core/repair/engine/suggestion-engine.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/engine/suggestion-engine.js +0 -0
- /package/{cli/dist → dist}/core/repair/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/index.js +0 -0
- /package/{cli/dist → dist}/core/repair/repairer.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/repairer.js +0 -0
- /package/{cli/dist → dist}/core/repair/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/types.js +0 -0
- /package/{cli/dist → dist}/core/repair/utils/error-analyzer.d.ts +0 -0
- /package/{cli/dist → dist}/core/repair/utils/error-analyzer.js +0 -0
- /package/{cli/dist → dist}/core/reporting/html-reporter.d.ts +0 -0
- /package/{cli/dist → dist}/core/reporting/html-reporter.js +0 -0
- /package/{cli/dist → dist}/core/retry/flakiness-integration.d.ts +0 -0
- /package/{cli/dist → dist}/core/retry/flakiness-integration.js +0 -0
- /package/{cli/dist → dist}/core/retry/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/retry/index.js +0 -0
- /package/{cli/dist → dist}/core/retry/retry-engine.d.ts +0 -0
- /package/{cli/dist → dist}/core/retry/retry-engine.js +0 -0
- /package/{cli/dist → dist}/core/retry/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/retry/types.js +0 -0
- /package/{cli/dist → dist}/core/retry/vault.d.ts +0 -0
- /package/{cli/dist → dist}/core/retry/vault.js +0 -0
- /package/{cli/dist → dist}/core/schemas/pack.schema.json +0 -0
- /package/{cli/dist → dist}/core/secrets/crypto.d.ts +0 -0
- /package/{cli/dist → dist}/core/secrets/crypto.js +0 -0
- /package/{cli/dist → dist}/core/secrets/manager.d.ts +0 -0
- /package/{cli/dist → dist}/core/secrets/manager.js +0 -0
- /package/{cli/dist → dist}/core/security/redaction-patterns-extended.d.ts +0 -0
- /package/{cli/dist → dist}/core/security/redaction-patterns-extended.js +0 -0
- /package/{cli/dist → dist}/core/security/redactor.d.ts +0 -0
- /package/{cli/dist → dist}/core/security/redactor.js +0 -0
- /package/{cli/dist → dist}/core/self-healing/assertion-healer.d.ts +0 -0
- /package/{cli/dist → dist}/core/self-healing/assertion-healer.js +0 -0
- /package/{cli/dist → dist}/core/self-healing/engine.d.ts +0 -0
- /package/{cli/dist → dist}/core/self-healing/engine.js +0 -0
- /package/{cli/dist → dist}/core/self-healing/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/self-healing/index.js +0 -0
- /package/{cli/dist → dist}/core/self-healing/selector-healer.d.ts +0 -0
- /package/{cli/dist → dist}/core/self-healing/selector-healer.js +0 -0
- /package/{cli/dist → dist}/core/self-healing/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/self-healing/types.js +0 -0
- /package/{cli/dist → dist}/core/serve/diagnostics-collector.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/diagnostics-collector.js +0 -0
- /package/{cli/dist → dist}/core/serve/health-checker.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/health-checker.js +0 -0
- /package/{cli/dist → dist}/core/serve/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/index.js +0 -0
- /package/{cli/dist → dist}/core/serve/metrics-collector.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/metrics-collector.js +0 -0
- /package/{cli/dist → dist}/core/serve/process-manager.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/process-manager.js +0 -0
- /package/{cli/dist → dist}/core/serve/server.d.ts +0 -0
- /package/{cli/dist → dist}/core/serve/server.js +0 -0
- /package/{cli/dist → dist}/core/slo/config.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/config.js +0 -0
- /package/{cli/dist → dist}/core/slo/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/index.js +0 -0
- /package/{cli/dist → dist}/core/slo/sli-calculator.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/sli-calculator.js +0 -0
- /package/{cli/dist → dist}/core/slo/slo-tracker.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/slo-tracker.js +0 -0
- /package/{cli/dist → dist}/core/slo/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/types.js +0 -0
- /package/{cli/dist → dist}/core/slo/vault.d.ts +0 -0
- /package/{cli/dist → dist}/core/slo/vault.js +0 -0
- /package/{cli/dist → dist}/core/tui/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/tui/index.js +0 -0
- /package/{cli/dist → dist}/core/tui/monitor.d.ts +0 -0
- /package/{cli/dist → dist}/core/tui/monitor.js +0 -0
- /package/{cli/dist → dist}/core/tui/renderer.d.ts +0 -0
- /package/{cli/dist → dist}/core/tui/renderer.js +0 -0
- /package/{cli/dist → dist}/core/tui/types.d.ts +0 -0
- /package/{cli/dist → dist}/core/tui/types.js +0 -0
- /package/{cli/dist → dist}/core/types/pack-v1.js +0 -0
- /package/{cli/dist → dist}/core/types/pack-v2.js +0 -0
- /package/{cli/dist → dist}/core/types/trust-score.d.ts +0 -0
- /package/{cli/dist → dist}/core/types/trust-score.js +0 -0
- /package/{cli/dist → dist}/core/vault/cas.d.ts +0 -0
- /package/{cli/dist → dist}/core/vault/cas.js +0 -0
- /package/{cli/dist → dist}/core/vault/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/vault/index.js +0 -0
- /package/{cli/dist → dist}/core/visual/visual-regression.d.ts +0 -0
- /package/{cli/dist → dist}/core/visual/visual-regression.js +0 -0
- /package/{cli/dist → dist}/core/watch/index.d.ts +0 -0
- /package/{cli/dist → dist}/core/watch/index.js +0 -0
- /package/{cli/dist → dist}/core/watch/watch-mode.d.ts +0 -0
- /package/{cli/dist → dist}/core/watch/watch-mode.js +0 -0
- /package/{cli/dist → dist}/generators/index.d.ts +0 -0
- /package/{cli/dist → dist}/generators/index.js +0 -0
- /package/{cli/dist → dist}/generators/json-reporter.d.ts +0 -0
- /package/{cli/dist → dist}/generators/json-reporter.js +0 -0
- /package/{cli/dist → dist}/generators/test-generator.d.ts +0 -0
- /package/{cli/dist → dist}/generators/test-generator.js +0 -0
- /package/{cli/dist → dist}/index.d.ts +0 -0
- /package/{cli/dist → dist}/index.js +0 -0
- /package/{cli/dist → dist}/scanners/dom-scanner.d.ts +0 -0
- /package/{cli/dist → dist}/scanners/dom-scanner.js +0 -0
- /package/{cli/dist → dist}/scanners/index.d.ts +0 -0
- /package/{cli/dist → dist}/scanners/index.js +0 -0
- /package/{cli/dist → dist}/schemas/pack.schema.json +0 -0
- /package/{cli/dist → dist}/types/scan.d.ts +0 -0
- /package/{cli/dist → dist}/types/scan.js +0 -0
- /package/{cli/dist → dist}/utils/config.js +0 -0
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 OAuth Handler
|
|
3
|
+
*
|
|
4
|
+
* P0 Features: OAuth2/OIDC flow testing support
|
|
5
|
+
* - Authorization Code flow with PKCE
|
|
6
|
+
* - Implicit flow (legacy) - P1
|
|
7
|
+
* - Client Credentials flow
|
|
8
|
+
* - Refresh Token flow
|
|
9
|
+
* - Token storage and management
|
|
10
|
+
* - OpenID Connect support - P1
|
|
11
|
+
*
|
|
12
|
+
* Supports testing against:
|
|
13
|
+
* - Real OAuth providers (Google, GitHub, Auth0, etc.)
|
|
14
|
+
* - Mock OAuth servers for development
|
|
15
|
+
*/
|
|
16
|
+
import { createHash, randomBytes } from 'crypto';
|
|
17
|
+
/**
|
|
18
|
+
* OAuth Handler class
|
|
19
|
+
*/
|
|
20
|
+
export class OAuthHandler {
|
|
21
|
+
context;
|
|
22
|
+
config;
|
|
23
|
+
accessToken = null;
|
|
24
|
+
refreshToken = null;
|
|
25
|
+
codeVerifier = null;
|
|
26
|
+
generatedState = null;
|
|
27
|
+
constructor(context, config) {
|
|
28
|
+
this.context = context;
|
|
29
|
+
this.config = config;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate random string for PKCE code verifier and state
|
|
33
|
+
* Uses cryptographically secure random bytes
|
|
34
|
+
*/
|
|
35
|
+
generateRandomString(length = 32) {
|
|
36
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
|
|
37
|
+
// Generate random bytes and map to character set
|
|
38
|
+
const randomValues = randomBytes(length);
|
|
39
|
+
let result = '';
|
|
40
|
+
for (let i = 0; i < length; i++) {
|
|
41
|
+
result += chars.charAt(randomValues[i] % chars.length);
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Generate code challenge for PKCE
|
|
47
|
+
* Uses SHA256 to create challenge from verifier
|
|
48
|
+
*/
|
|
49
|
+
generateCodeChallenge(verifier) {
|
|
50
|
+
// SHA-256 hash the verifier using Node.js crypto
|
|
51
|
+
const hash = createHash('sha256').update(verifier).digest('base64url');
|
|
52
|
+
return hash;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build authorization URL
|
|
56
|
+
* P0: Construct the OAuth authorization URL with all required parameters
|
|
57
|
+
*/
|
|
58
|
+
buildAuthorizationURL(options) {
|
|
59
|
+
const config = this.config;
|
|
60
|
+
const scope = typeof config.scope === 'string' ? config.scope : config.scope?.join(' ');
|
|
61
|
+
const state = options?.state || config.state || this.generateRandomString(32);
|
|
62
|
+
// Store state for verification
|
|
63
|
+
this.generatedState = state;
|
|
64
|
+
const params = new URLSearchParams({
|
|
65
|
+
response_type: config.responseType || 'code',
|
|
66
|
+
client_id: config.clientId,
|
|
67
|
+
redirect_uri: config.redirectUri,
|
|
68
|
+
scope: scope || '',
|
|
69
|
+
state,
|
|
70
|
+
});
|
|
71
|
+
// Add PKCE code challenge if using PKCE
|
|
72
|
+
if (config.usePKCE && options?.codeChallenge) {
|
|
73
|
+
params.append('code_challenge', options.codeChallenge);
|
|
74
|
+
params.append('code_challenge_method', 'S256');
|
|
75
|
+
}
|
|
76
|
+
return `${config.authEndpoint}?${params.toString()}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Start Authorization Code flow with PKCE
|
|
80
|
+
* P0: Begin OAuth login flow
|
|
81
|
+
*
|
|
82
|
+
* @returns Authorization URL to navigate user to
|
|
83
|
+
*/
|
|
84
|
+
async startAuthorizationFlow() {
|
|
85
|
+
const verifier = this.generateRandomString(64);
|
|
86
|
+
this.codeVerifier = verifier;
|
|
87
|
+
const state = this.generateRandomString(32);
|
|
88
|
+
this.generatedState = state;
|
|
89
|
+
const challenge = this.generateCodeChallenge(verifier);
|
|
90
|
+
const authUrl = this.buildAuthorizationURL({ state, codeChallenge: challenge });
|
|
91
|
+
return {
|
|
92
|
+
authUrl,
|
|
93
|
+
verifier,
|
|
94
|
+
state,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Wait for OAuth callback
|
|
99
|
+
* P0: Wait for redirect to redirect_uri with code or error
|
|
100
|
+
*
|
|
101
|
+
* @returns Promise that resolves when callback is received
|
|
102
|
+
*/
|
|
103
|
+
async waitForCallback(page, timeout = 60000) {
|
|
104
|
+
const redirectUri = new URL(this.config.redirectUri).pathname;
|
|
105
|
+
try {
|
|
106
|
+
// Wait for navigation to redirect URI
|
|
107
|
+
await page.waitForURL(`**${redirectUri}*`, { timeout });
|
|
108
|
+
// Get the URL parameters
|
|
109
|
+
const url = page.url();
|
|
110
|
+
const params = new URLSearchParams(url.split('?')[1] || '');
|
|
111
|
+
const code = params.get('code');
|
|
112
|
+
const error = params.get('error');
|
|
113
|
+
const errorDescription = params.get('error_description');
|
|
114
|
+
const state = params.get('state');
|
|
115
|
+
// Verify state if one was generated
|
|
116
|
+
if (this.generatedState && state !== this.generatedState) {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
error: { error: 'invalid_state', error_description: 'State mismatch', state: state || undefined },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (error) {
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
error: {
|
|
126
|
+
error,
|
|
127
|
+
error_description: errorDescription || undefined,
|
|
128
|
+
state: state || undefined,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
code: code || undefined,
|
|
135
|
+
state: state || undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: { error: 'timeout', error_description: 'Callback timeout' },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Exchange authorization code for tokens
|
|
147
|
+
* P0: Token exchange endpoint call
|
|
148
|
+
*/
|
|
149
|
+
async exchangeCodeForToken(code) {
|
|
150
|
+
const config = this.config;
|
|
151
|
+
const body = new URLSearchParams({
|
|
152
|
+
grant_type: 'authorization_code',
|
|
153
|
+
code,
|
|
154
|
+
redirect_uri: config.redirectUri,
|
|
155
|
+
client_id: config.clientId,
|
|
156
|
+
});
|
|
157
|
+
// Add client secret if provided (confidential clients)
|
|
158
|
+
if (config.clientSecret) {
|
|
159
|
+
body.append('client_secret', config.clientSecret);
|
|
160
|
+
}
|
|
161
|
+
// Add PKCE code verifier if using PKCE
|
|
162
|
+
if (config.usePKCE && this.codeVerifier) {
|
|
163
|
+
body.append('code_verifier', this.codeVerifier);
|
|
164
|
+
}
|
|
165
|
+
// Make token request
|
|
166
|
+
const response = await this.context.request.post(config.tokenEndpoint, {
|
|
167
|
+
data: body.toString(),
|
|
168
|
+
headers: {
|
|
169
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
if (!response.ok()) {
|
|
173
|
+
throw new Error(`Token request failed: ${response.status()} ${response.statusText()}`);
|
|
174
|
+
}
|
|
175
|
+
const token = await response.json();
|
|
176
|
+
// Store tokens
|
|
177
|
+
this.accessToken = token.access_token;
|
|
178
|
+
if (token.refresh_token) {
|
|
179
|
+
this.refreshToken = token.refresh_token;
|
|
180
|
+
}
|
|
181
|
+
return token;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Client Credentials flow
|
|
185
|
+
* P1: Server-to-server authentication without user interaction
|
|
186
|
+
* Used for machine-to-machine communication (service accounts, daemons, etc.)
|
|
187
|
+
*
|
|
188
|
+
* @returns OAuth token response (no refresh token in this flow)
|
|
189
|
+
*/
|
|
190
|
+
async clientCredentials() {
|
|
191
|
+
const config = this.config;
|
|
192
|
+
if (!config.clientSecret) {
|
|
193
|
+
throw new Error('Client secret is required for Client Credentials flow');
|
|
194
|
+
}
|
|
195
|
+
const scope = typeof config.scope === 'string' ? config.scope : config.scope?.join(' ');
|
|
196
|
+
const body = new URLSearchParams({
|
|
197
|
+
grant_type: 'client_credentials',
|
|
198
|
+
client_id: config.clientId,
|
|
199
|
+
client_secret: config.clientSecret,
|
|
200
|
+
});
|
|
201
|
+
if (scope) {
|
|
202
|
+
body.append('scope', scope);
|
|
203
|
+
}
|
|
204
|
+
const response = await this.context.request.post(config.tokenEndpoint, {
|
|
205
|
+
data: body.toString(),
|
|
206
|
+
headers: {
|
|
207
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
if (!response.ok()) {
|
|
211
|
+
const errorText = await response.text().catch(() => 'Unknown error');
|
|
212
|
+
throw new Error(`Client Credentials request failed: ${response.status()} ${response.statusText()} - ${errorText}`);
|
|
213
|
+
}
|
|
214
|
+
const token = await response.json();
|
|
215
|
+
// Store access token (no refresh token in Client Credentials flow)
|
|
216
|
+
this.accessToken = token.access_token;
|
|
217
|
+
return token;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Refresh access token
|
|
221
|
+
* P0: Use refresh token to get new access token
|
|
222
|
+
*/
|
|
223
|
+
async refreshAccessToken() {
|
|
224
|
+
if (!this.refreshToken) {
|
|
225
|
+
throw new Error('No refresh token available');
|
|
226
|
+
}
|
|
227
|
+
const config = this.config;
|
|
228
|
+
const body = new URLSearchParams({
|
|
229
|
+
grant_type: 'refresh_token',
|
|
230
|
+
refresh_token: this.refreshToken,
|
|
231
|
+
client_id: config.clientId,
|
|
232
|
+
});
|
|
233
|
+
if (config.clientSecret) {
|
|
234
|
+
body.append('client_secret', config.clientSecret);
|
|
235
|
+
}
|
|
236
|
+
const response = await this.context.request.post(config.tokenEndpoint, {
|
|
237
|
+
data: body.toString(),
|
|
238
|
+
headers: {
|
|
239
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
if (!response.ok()) {
|
|
243
|
+
throw new Error(`Token refresh failed: ${response.status()} ${response.statusText()}`);
|
|
244
|
+
}
|
|
245
|
+
const token = await response.json();
|
|
246
|
+
// Update tokens
|
|
247
|
+
this.accessToken = token.access_token;
|
|
248
|
+
if (token.refresh_token) {
|
|
249
|
+
this.refreshToken = token.refresh_token;
|
|
250
|
+
}
|
|
251
|
+
return token;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get current access token
|
|
255
|
+
*/
|
|
256
|
+
getAccessToken() {
|
|
257
|
+
return this.accessToken;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get refresh token
|
|
261
|
+
*/
|
|
262
|
+
getRefreshToken() {
|
|
263
|
+
return this.refreshToken;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Set tokens manually (useful for testing)
|
|
267
|
+
*/
|
|
268
|
+
setTokens(tokens) {
|
|
269
|
+
this.accessToken = tokens.accessToken;
|
|
270
|
+
this.refreshToken = tokens.refreshToken || null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Clear all stored tokens
|
|
274
|
+
*/
|
|
275
|
+
clearTokens() {
|
|
276
|
+
this.accessToken = null;
|
|
277
|
+
this.refreshToken = null;
|
|
278
|
+
this.codeVerifier = null;
|
|
279
|
+
this.generatedState = null;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Full Authorization Code flow with PKCE
|
|
283
|
+
* P0: Complete OAuth flow from start to token exchange
|
|
284
|
+
*
|
|
285
|
+
* @param page - Playwright page instance
|
|
286
|
+
* @param userInteraction - Optional function to handle user login
|
|
287
|
+
* @returns OAuth token response
|
|
288
|
+
*/
|
|
289
|
+
async completeFlow(page, userInteraction) {
|
|
290
|
+
// Start flow
|
|
291
|
+
const { authUrl, verifier, state } = await this.startAuthorizationFlow();
|
|
292
|
+
// Navigate to auth URL or call user interaction handler
|
|
293
|
+
if (userInteraction) {
|
|
294
|
+
await userInteraction(page, authUrl);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
await page.goto(authUrl);
|
|
298
|
+
}
|
|
299
|
+
// Wait for callback
|
|
300
|
+
const callback = await this.waitForCallback(page);
|
|
301
|
+
if (!callback.success || !callback.code) {
|
|
302
|
+
throw new Error(`OAuth flow failed: ${callback.error?.error || 'unknown'}`);
|
|
303
|
+
}
|
|
304
|
+
// Exchange code for token
|
|
305
|
+
return await this.exchangeCodeForToken(callback.code);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Set authorization header with Bearer token
|
|
309
|
+
* P0: Helper to set Authorization header for API requests
|
|
310
|
+
*/
|
|
311
|
+
async setAuthHeader(page) {
|
|
312
|
+
if (!this.accessToken) {
|
|
313
|
+
throw new Error('No access token available. Call completeFlow() first.');
|
|
314
|
+
}
|
|
315
|
+
await page.setExtraHTTPHeaders({
|
|
316
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* P1: Implicit Flow (legacy OAuth2)
|
|
321
|
+
* Token is returned directly in URL fragment after redirect
|
|
322
|
+
*
|
|
323
|
+
* Note: Implicit flow is deprecated in OAuth 2.1. Use Authorization Code with PKCE instead.
|
|
324
|
+
* This method is provided for testing legacy applications.
|
|
325
|
+
*
|
|
326
|
+
* @param page - Playwright page instance
|
|
327
|
+
* @param userInteraction - Optional function to handle user login
|
|
328
|
+
* @returns Implicit flow result with access token from URL fragment
|
|
329
|
+
*/
|
|
330
|
+
async implicitFlow(page, userInteraction) {
|
|
331
|
+
// Set response type to token for implicit flow
|
|
332
|
+
const originalResponseType = this.config.responseType;
|
|
333
|
+
this.config.responseType = 'token';
|
|
334
|
+
const state = this.generateRandomString(32);
|
|
335
|
+
this.generatedState = state;
|
|
336
|
+
const authUrl = this.buildAuthorizationURL({ state });
|
|
337
|
+
// Navigate to auth URL or call user interaction handler
|
|
338
|
+
if (userInteraction) {
|
|
339
|
+
await userInteraction(page, authUrl);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
await page.goto(authUrl);
|
|
343
|
+
}
|
|
344
|
+
// Wait for callback with token in fragment
|
|
345
|
+
const result = await this.waitForImplicitCallback(page);
|
|
346
|
+
// Restore original response type
|
|
347
|
+
this.config.responseType = originalResponseType;
|
|
348
|
+
if (result.success && result.accessToken) {
|
|
349
|
+
this.accessToken = result.accessToken;
|
|
350
|
+
}
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* P1: Wait for Implicit Flow callback
|
|
355
|
+
* Extracts token from URL fragment (hash)
|
|
356
|
+
*/
|
|
357
|
+
async waitForImplicitCallback(page, timeout = 60000) {
|
|
358
|
+
const redirectUri = new URL(this.config.redirectUri).pathname;
|
|
359
|
+
try {
|
|
360
|
+
// Wait for navigation to redirect URI
|
|
361
|
+
await page.waitForURL(`**${redirectUri}*`, { timeout });
|
|
362
|
+
// Get URL fragment (hash) - tokens are in fragment for implicit flow
|
|
363
|
+
const fragment = await page.evaluate(() => window.location.hash.substring(1));
|
|
364
|
+
if (!fragment) {
|
|
365
|
+
// Also check query params for error
|
|
366
|
+
const url = page.url();
|
|
367
|
+
const params = new URLSearchParams(url.split('?')[1] || '');
|
|
368
|
+
const error = params.get('error');
|
|
369
|
+
const errorDescription = params.get('error_description');
|
|
370
|
+
const state = params.get('state');
|
|
371
|
+
return {
|
|
372
|
+
success: false,
|
|
373
|
+
error: {
|
|
374
|
+
error: error || 'no_fragment',
|
|
375
|
+
error_description: errorDescription || 'No fragment in callback URL',
|
|
376
|
+
state: state || undefined,
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
// Parse fragment parameters
|
|
381
|
+
const params = new URLSearchParams(fragment);
|
|
382
|
+
const accessToken = params.get('access_token');
|
|
383
|
+
const idToken = params.get('id_token');
|
|
384
|
+
const tokenType = params.get('token_type');
|
|
385
|
+
const expiresIn = params.get('expires_in');
|
|
386
|
+
const state = params.get('state');
|
|
387
|
+
const error = params.get('error');
|
|
388
|
+
const errorDescription = params.get('error_description');
|
|
389
|
+
// Verify state
|
|
390
|
+
if (this.generatedState && state !== this.generatedState) {
|
|
391
|
+
return {
|
|
392
|
+
success: false,
|
|
393
|
+
error: { error: 'invalid_state', error_description: 'State mismatch', state: state || undefined },
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if (error) {
|
|
397
|
+
return {
|
|
398
|
+
success: false,
|
|
399
|
+
error: {
|
|
400
|
+
error,
|
|
401
|
+
error_description: errorDescription || undefined,
|
|
402
|
+
state: state || undefined,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
success: true,
|
|
408
|
+
accessToken: accessToken || undefined,
|
|
409
|
+
idToken: idToken || undefined,
|
|
410
|
+
tokenType: tokenType || undefined,
|
|
411
|
+
expires_in: expiresIn || undefined,
|
|
412
|
+
state: state || undefined,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
catch (e) {
|
|
416
|
+
return {
|
|
417
|
+
success: false,
|
|
418
|
+
error: { error: 'timeout', error_description: 'Callback timeout' },
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* P1: OpenID Connect - Parse and validate ID token
|
|
424
|
+
*
|
|
425
|
+
* @param idToken - JWT ID token from OAuth response
|
|
426
|
+
* @returns Parsed OIDC claims
|
|
427
|
+
*/
|
|
428
|
+
async parseIDToken(idToken) {
|
|
429
|
+
try {
|
|
430
|
+
// Split JWT into parts
|
|
431
|
+
const parts = idToken.split('.');
|
|
432
|
+
if (parts.length !== 3) {
|
|
433
|
+
throw new Error('Invalid ID token format');
|
|
434
|
+
}
|
|
435
|
+
// Decode payload (base64url)
|
|
436
|
+
const payload = parts[1];
|
|
437
|
+
// Add padding if needed
|
|
438
|
+
const paddedPayload = payload + '='.repeat((4 - payload.length % 4) % 4);
|
|
439
|
+
const decoded = Buffer.from(paddedPayload, 'base64url').toString('utf-8');
|
|
440
|
+
const claims = JSON.parse(decoded);
|
|
441
|
+
// Validate basic claims
|
|
442
|
+
if (!claims.iss || !claims.sub || !claims.aud || !claims.exp || !claims.iat) {
|
|
443
|
+
throw new Error('ID token missing required claims');
|
|
444
|
+
}
|
|
445
|
+
// Validate expiration
|
|
446
|
+
const now = Math.floor(Date.now() / 1000);
|
|
447
|
+
if (claims.exp < now) {
|
|
448
|
+
throw new Error('ID token has expired');
|
|
449
|
+
}
|
|
450
|
+
// Validate issuer if configured
|
|
451
|
+
const expectedIssuer = this.config.oidc?.expectedIssuer || this.config.oidc?.issuer;
|
|
452
|
+
if (expectedIssuer && claims.iss !== expectedIssuer) {
|
|
453
|
+
throw new Error(`ID token issuer mismatch: expected ${expectedIssuer}, got ${claims.iss}`);
|
|
454
|
+
}
|
|
455
|
+
// Validate audience if configured
|
|
456
|
+
const expectedAudience = this.config.oidc?.expectedAudience || this.config.clientId;
|
|
457
|
+
if (expectedAudience) {
|
|
458
|
+
const audiences = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
|
|
459
|
+
if (!audiences.includes(expectedAudience) && !audiences.includes(this.config.clientId)) {
|
|
460
|
+
throw new Error(`ID token audience mismatch: expected ${expectedAudience}, got ${claims.aud}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return claims;
|
|
464
|
+
}
|
|
465
|
+
catch (e) {
|
|
466
|
+
throw new Error(`Failed to parse ID token: ${e instanceof Error ? e.message : 'unknown error'}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* P1: OpenID Connect - Fetch UserInfo from endpoint
|
|
471
|
+
*
|
|
472
|
+
* @param accessToken - Access token for authorization
|
|
473
|
+
* @returns UserInfo response
|
|
474
|
+
*/
|
|
475
|
+
async fetchUserInfo(accessToken) {
|
|
476
|
+
const userInfoEndpoint = this.config.oidc?.userInfoEndpoint;
|
|
477
|
+
if (!userInfoEndpoint) {
|
|
478
|
+
throw new Error('UserInfo endpoint not configured');
|
|
479
|
+
}
|
|
480
|
+
const response = await this.context.request.get(userInfoEndpoint, {
|
|
481
|
+
headers: {
|
|
482
|
+
Authorization: `Bearer ${accessToken}`,
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
if (!response.ok()) {
|
|
486
|
+
throw new Error(`UserInfo request failed: ${response.status()} ${response.statusText()}`);
|
|
487
|
+
}
|
|
488
|
+
return await response.json();
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* P1: OpenID Connect - Complete OIDC flow
|
|
492
|
+
* Performs Authorization Code flow and fetches UserInfo
|
|
493
|
+
*
|
|
494
|
+
* @param page - Playwright page instance
|
|
495
|
+
* @param userInteraction - Optional function to handle user login
|
|
496
|
+
* @returns OAuth token + parsed ID token claims + UserInfo
|
|
497
|
+
*/
|
|
498
|
+
async completeOIDCFlow(page, userInteraction) {
|
|
499
|
+
// Ensure OpenID scope is included
|
|
500
|
+
const scopes = typeof this.config.scope === 'string'
|
|
501
|
+
? this.config.scope.split(' ')
|
|
502
|
+
: this.config.scope || [];
|
|
503
|
+
if (!scopes.includes('openid')) {
|
|
504
|
+
scopes.push('openid');
|
|
505
|
+
this.config.scope = scopes.join(' ');
|
|
506
|
+
}
|
|
507
|
+
// Complete standard OAuth flow
|
|
508
|
+
const token = await this.completeFlow(page, userInteraction);
|
|
509
|
+
const result = { token };
|
|
510
|
+
// Parse ID token if present
|
|
511
|
+
if (token.id_token) {
|
|
512
|
+
try {
|
|
513
|
+
result.idTokenClaims = await this.parseIDToken(token.id_token);
|
|
514
|
+
}
|
|
515
|
+
catch (e) {
|
|
516
|
+
console.warn('Failed to parse ID token:', e);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// Fetch UserInfo if access token available
|
|
520
|
+
if (token.access_token && this.config.oidc?.userInfoEndpoint) {
|
|
521
|
+
try {
|
|
522
|
+
result.userInfo = await this.fetchUserInfo(token.access_token);
|
|
523
|
+
}
|
|
524
|
+
catch (e) {
|
|
525
|
+
console.warn('Failed to fetch UserInfo:', e);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return result;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* P1: Validate ID token signature (basic validation)
|
|
532
|
+
* For full signature validation, you need the JWKS and crypto verification
|
|
533
|
+
*
|
|
534
|
+
* @param idToken - JWT ID token to validate
|
|
535
|
+
* @returns Validation result with claims if valid
|
|
536
|
+
*/
|
|
537
|
+
async validateIDToken(idToken) {
|
|
538
|
+
try {
|
|
539
|
+
const claims = await this.parseIDToken(idToken);
|
|
540
|
+
// Note: Full signature validation requires JWKS and crypto verification
|
|
541
|
+
// This is a basic implementation that validates structure and timestamps
|
|
542
|
+
// For production use, implement full JWT signature verification with JWKS
|
|
543
|
+
return {
|
|
544
|
+
valid: true,
|
|
545
|
+
claims,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
catch (e) {
|
|
549
|
+
return {
|
|
550
|
+
valid: false,
|
|
551
|
+
error: e instanceof Error ? e.message : 'Unknown validation error',
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Mock OAuth Server for testing
|
|
558
|
+
* Simulates OAuth endpoints for development/testing
|
|
559
|
+
*/
|
|
560
|
+
export class MockOAuthServer {
|
|
561
|
+
baseUrl;
|
|
562
|
+
authorizedCodes = new Map();
|
|
563
|
+
constructor(baseUrl = 'http://localhost:3001') {
|
|
564
|
+
this.baseUrl = baseUrl;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Get authorization endpoint URL
|
|
568
|
+
*/
|
|
569
|
+
getAuthEndpoint() {
|
|
570
|
+
return `${this.baseUrl}/authorize`;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Get token endpoint URL
|
|
574
|
+
*/
|
|
575
|
+
getTokenEndpoint() {
|
|
576
|
+
return `${this.baseUrl}/token`;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Generate a mock access token
|
|
580
|
+
*/
|
|
581
|
+
generateMockToken(expiresIn = 3600) {
|
|
582
|
+
return {
|
|
583
|
+
access_token: `mock_token_${Date.now()}_${Math.random().toString(36).substring(7)}`,
|
|
584
|
+
token_type: 'Bearer',
|
|
585
|
+
expires_in: expiresIn,
|
|
586
|
+
refresh_token: `mock_refresh_${Date.now()}_${Math.random().toString(36).substring(7)}`,
|
|
587
|
+
scope: 'openid profile email',
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Create a mock authorization code
|
|
592
|
+
* Stores code-to-token mapping for later exchange
|
|
593
|
+
*/
|
|
594
|
+
createAuthorizationCode() {
|
|
595
|
+
const code = `mock_auth_code_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
596
|
+
const token = this.generateMockToken();
|
|
597
|
+
this.authorizedCodes.set(code, token);
|
|
598
|
+
return { code, token };
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Validate and redeem authorization code
|
|
602
|
+
*/
|
|
603
|
+
redeemCode(code) {
|
|
604
|
+
const token = this.authorizedCodes.get(code);
|
|
605
|
+
if (token) {
|
|
606
|
+
this.authorizedCodes.delete(code); // One-time use
|
|
607
|
+
return token;
|
|
608
|
+
}
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Get OAuth config for mock server
|
|
613
|
+
*/
|
|
614
|
+
getOAuthConfig(clientId = 'mock-client-id', redirectUri = 'http://localhost:3000/callback') {
|
|
615
|
+
return {
|
|
616
|
+
authEndpoint: this.getAuthEndpoint(),
|
|
617
|
+
tokenEndpoint: this.getTokenEndpoint(),
|
|
618
|
+
clientId,
|
|
619
|
+
redirectUri,
|
|
620
|
+
responseType: 'code',
|
|
621
|
+
usePKCE: true,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Convenience function to create OAuth handler
|
|
627
|
+
*/
|
|
628
|
+
export function createOAuthHandler(context, config) {
|
|
629
|
+
return new OAuthHandler(context, config);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Convenience function to create mock OAuth server
|
|
633
|
+
*/
|
|
634
|
+
export function createMockOAuthServer(baseUrl) {
|
|
635
|
+
return new MockOAuthServer(baseUrl);
|
|
636
|
+
}
|
|
@@ -5,12 +5,21 @@
|
|
|
5
5
|
* - Client credentials grant
|
|
6
6
|
* - Password grant (resource owner)
|
|
7
7
|
* - Authorization code grant (requires pre-issued code)
|
|
8
|
+
*
|
|
9
|
+
* P0: Refresh token support for automatic token renewal
|
|
8
10
|
*/
|
|
9
11
|
import { AuthProvider, AuthResult, OAuth2AuthConfig } from './index.js';
|
|
10
12
|
export declare class OAuth2Provider implements AuthProvider<OAuth2AuthConfig> {
|
|
11
13
|
readonly type: "oauth2";
|
|
12
14
|
authenticate(config: OAuth2AuthConfig): Promise<AuthResult>;
|
|
15
|
+
/**
|
|
16
|
+
* P0: Refresh OAuth2 access token using refresh token
|
|
17
|
+
*/
|
|
13
18
|
refresh(config: OAuth2AuthConfig): Promise<AuthResult>;
|
|
19
|
+
/**
|
|
20
|
+
* P0: Perform refresh token flow
|
|
21
|
+
*/
|
|
22
|
+
private performRefresh;
|
|
14
23
|
clear(config: OAuth2AuthConfig): Promise<void>;
|
|
15
24
|
validate(config: OAuth2AuthConfig): Promise<boolean>;
|
|
16
25
|
private getCacheKey;
|