mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-05-14 00:02:47 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba54da28d6 | |||
| 288593d01e | |||
| ddb6c03c0e | |||
| 79d4476713 | |||
| 06f6e8b99b | |||
| 8ee4a9e3fc |
@@ -379,6 +379,17 @@ prowler_scan:
|
||||
PROWLER_API_KEY: $PROWLER_API_KEY
|
||||
```
|
||||
|
||||
## Billing impact
|
||||
|
||||
Each unique cloud account discovered in ingested OCSF findings counts as one **provider** in the Prowler Cloud subscription.
|
||||
|
||||
- **Existing providers**: If a cloud account was already connected as a provider, findings ingested for that account do **not** incur additional billing. The existing provider is reused.
|
||||
- **New accounts**: Ingesting findings from accounts not yet connected to Prowler Cloud will result in new providers being created and counted toward the subscription.
|
||||
- **High-volume ingestion**: Importing findings from many different cloud accounts will create a provider for each account. Review plan limits before large-scale ingestion.
|
||||
- **Deleted providers**: Removing a provider no longer counts toward the subscription.
|
||||
|
||||
For pricing details, see [Prowler Cloud Pricing](https://prowler.com/pricing).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### HTTP 401 Unauthorized
|
||||
|
||||
@@ -57,14 +57,16 @@ class OCSF(Output):
|
||||
if not findings:
|
||||
return
|
||||
|
||||
scan_ids_by_provider = {}
|
||||
scan_ids_by_provider_account = {}
|
||||
for finding in findings:
|
||||
provider = finding.metadata.Provider
|
||||
if provider not in scan_ids_by_provider:
|
||||
scan_ids_by_provider[provider] = _uuid7_from_timestamp(
|
||||
account_uid = finding.account_uid
|
||||
scan_key = (provider, account_uid)
|
||||
if scan_key not in scan_ids_by_provider_account:
|
||||
scan_ids_by_provider_account[scan_key] = _uuid7_from_timestamp(
|
||||
finding.timestamp
|
||||
)
|
||||
scan_id = scan_ids_by_provider[provider]
|
||||
scan_id = scan_ids_by_provider_account[scan_key]
|
||||
finding_activity = ActivityID.Create
|
||||
cloud_account_type = self.get_account_type_id_by_provider(
|
||||
finding.metadata.Provider
|
||||
|
||||
@@ -123,11 +123,11 @@ class TestOCSF:
|
||||
1619600000, tz=timezone.utc
|
||||
)
|
||||
|
||||
def test_scan_id_is_unique_per_provider(self):
|
||||
def test_scan_id_is_unique_per_provider_and_account(self):
|
||||
findings = [
|
||||
generate_finding_output(provider="aws"),
|
||||
generate_finding_output(provider="azure"),
|
||||
generate_finding_output(provider="aws"),
|
||||
generate_finding_output(provider="aws", account_uid="111111111111"),
|
||||
generate_finding_output(provider="aws", account_uid="222222222222"),
|
||||
generate_finding_output(provider="aws", account_uid="111111111111"),
|
||||
]
|
||||
|
||||
ocsf = OCSF(findings)
|
||||
|
||||
@@ -22,9 +22,14 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
- Attack Paths: Catches not found and permissions (for read only queries) errors [(#10140)](https://github.com/prowler-cloud/prowler/pull/10140)
|
||||
- Provider connection flow was unified into a modal wizard with AWS Organizations bulk onboarding, safer secret retry handling, and more stable E2E coverage [(#10153)](https://github.com/prowler-cloud/prowler/pull/10153) [(#10154)](https://github.com/prowler-cloud/prowler/pull/10154) [(#10155)](https://github.com/prowler-cloud/prowler/pull/10155) [(#10156)](https://github.com/prowler-cloud/prowler/pull/10156) [(#10157)](https://github.com/prowler-cloud/prowler/pull/10157) [(#10158)](https://github.com/prowler-cloud/prowler/pull/10158)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Findings Severity Over Time chart on Overview not responding to provider and account filters, and chart clipping at Y-axis maximum values [(#10103)](https://github.com/prowler-cloud/prowler/pull/10103)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- npm dependencies updated to resolve 11 Dependabot alerts (4 HIGH, 7 MEDIUM): fast-xml-parser, @modelcontextprotocol/sdk, tar, @isaacs/brace-expansion, hono, lodash, lodash-es [(#10052)](https://github.com/prowler-cloud/prowler/pull/10052)
|
||||
- npm transitive dependencies patched to resolve 9 Dependabot alerts (2 CRITICAL, 3 HIGH, 2 MEDIUM, 2 LOW): fast-xml-parser, rollup, minimatch, ajv, hono, qs [(#10187)](https://github.com/prowler-cloud/prowler/pull/10187)
|
||||
|
||||
---
|
||||
|
||||
|
||||
+23
-8
@@ -29,6 +29,25 @@ export const FindingSeverityOverTime = ({
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Sync data when SSR re-delivers filtered results (e.g. provider/account filter change).
|
||||
// Uses the "set state during render" pattern so the update is synchronous — no flash of stale data.
|
||||
const [prevInitialData, setPrevInitialData] = useState(initialData);
|
||||
if (initialData !== prevInitialData) {
|
||||
setPrevInitialData(initialData);
|
||||
setData(initialData);
|
||||
setError(null);
|
||||
setTimeRange(DEFAULT_TIME_RANGE);
|
||||
}
|
||||
|
||||
const getActiveProviderFilters = (): Record<string, string> => {
|
||||
const filters: Record<string, string> = {};
|
||||
const providerType = searchParams.get("filter[provider_type__in]");
|
||||
const providerId = searchParams.get("filter[provider_id__in]");
|
||||
if (providerType) filters["filter[provider_type__in]"] = providerType;
|
||||
if (providerId) filters["filter[provider_id__in]"] = providerId;
|
||||
return filters;
|
||||
};
|
||||
|
||||
const handlePointClick = ({
|
||||
point,
|
||||
dataKey,
|
||||
@@ -59,14 +78,9 @@ export const FindingSeverityOverTime = ({
|
||||
}
|
||||
|
||||
// Preserve provider filters from overview
|
||||
const providerType = searchParams.get("filter[provider_type__in]");
|
||||
const providerId = searchParams.get("filter[provider_id__in]");
|
||||
|
||||
if (providerType) {
|
||||
params.set("filter[provider_type__in]", providerType);
|
||||
}
|
||||
if (providerId) {
|
||||
params.set("filter[provider_id__in]", providerId);
|
||||
const providerFilters = getActiveProviderFilters();
|
||||
for (const [key, value] of Object.entries(providerFilters)) {
|
||||
params.set(key, value);
|
||||
}
|
||||
|
||||
router.push(`/findings?${params.toString()}`);
|
||||
@@ -80,6 +94,7 @@ export const FindingSeverityOverTime = ({
|
||||
try {
|
||||
const result = await getSeverityTrendsByTimeRange({
|
||||
timeRange: newRange,
|
||||
filters: getActiveProviderFilters(),
|
||||
});
|
||||
|
||||
if (result.status === "success") {
|
||||
|
||||
@@ -190,13 +190,13 @@ export function LineChart({
|
||||
<div className="w-full">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="w-full overflow-hidden"
|
||||
className="w-full"
|
||||
style={{ height, aspectRatio: "auto" }}
|
||||
>
|
||||
<RechartsLine
|
||||
data={data}
|
||||
margin={{
|
||||
top: 10,
|
||||
top: 20,
|
||||
left: 0,
|
||||
right: 30,
|
||||
bottom: 40,
|
||||
@@ -222,6 +222,7 @@ export function LineChart({
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
padding={{ top: 20 }}
|
||||
tick={{
|
||||
fill: "var(--color-text-neutral-secondary)",
|
||||
fontSize: AXIS_FONT_SIZE,
|
||||
|
||||
@@ -123,7 +123,7 @@ export const SendInvitationForm = ({
|
||||
onValueChange={field.onChange}
|
||||
disabled={isSelectorDisabled}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger aria-label="Select a role">
|
||||
<SelectValue placeholder="Select a role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -95,6 +95,9 @@ describe("useProviderWizardController", () => {
|
||||
expect(result.current.wizardVariant).toBe("organizations");
|
||||
expect(result.current.isProviderFlow).toBe(false);
|
||||
expect(result.current.orgCurrentStep).toBe(ORG_WIZARD_STEP.SETUP);
|
||||
expect(result.current.docsLink).toBe(
|
||||
"https://docs.prowler.com/user-guide/tutorials/prowler-cloud-aws-organizations",
|
||||
);
|
||||
|
||||
// When
|
||||
act(() => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { getProviderHelpText } from "@/lib/external-urls";
|
||||
import { DOCS_URLS, getProviderHelpText } from "@/lib/external-urls";
|
||||
import { useOrgSetupStore } from "@/store/organizations/store";
|
||||
import { useProviderWizardStore } from "@/store/provider-wizard/store";
|
||||
import {
|
||||
@@ -195,9 +195,9 @@ export function useProviderWizardController({
|
||||
};
|
||||
|
||||
const isProviderFlow = wizardVariant === WIZARD_VARIANT.PROVIDER;
|
||||
const docsLink = getProviderHelpText(
|
||||
isProviderFlow ? (providerTypeHint ?? providerType ?? "") : "aws",
|
||||
).link;
|
||||
const docsLink = isProviderFlow
|
||||
? getProviderHelpText(providerTypeHint ?? providerType ?? "").link
|
||||
: DOCS_URLS.AWS_ORGANIZATIONS;
|
||||
const resolvedFooterConfig: WizardFooterConfig =
|
||||
isProviderFlow && currentStep === PROVIDER_WIZARD_STEP.LAUNCH
|
||||
? {
|
||||
|
||||
@@ -58,7 +58,7 @@ export function ProviderWizardModal({
|
||||
initialData,
|
||||
});
|
||||
const scrollHintRefreshToken = `${wizardVariant}-${currentStep}-${orgCurrentStep}-${orgSetupPhase}`;
|
||||
const { containerRef, showScrollHint, handleScroll } = useScrollHint({
|
||||
const { containerRef, sentinelRef, showScrollHint } = useScrollHint({
|
||||
enabled: open,
|
||||
refreshToken: scrollHintRefreshToken,
|
||||
});
|
||||
@@ -106,7 +106,6 @@ export function ProviderWizardModal({
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="minimal-scrollbar h-full w-full overflow-y-scroll [scrollbar-gutter:stable] lg:ml-auto lg:max-w-[620px] xl:max-w-[700px]"
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
{isProviderFlow && currentStep === PROVIDER_WIZARD_STEP.CONNECT && (
|
||||
<ConnectStep
|
||||
@@ -177,6 +176,9 @@ export function ProviderWizardModal({
|
||||
onFooterChange={setFooterConfig}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sentinel element for IntersectionObserver scroll detection */}
|
||||
<div ref={sentinelRef} aria-hidden className="h-px shrink-0" />
|
||||
</div>
|
||||
|
||||
{showScrollHint && (
|
||||
|
||||
+39
-35
@@ -1,63 +1,67 @@
|
||||
"use client";
|
||||
|
||||
import { UIEvent, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
interface UseScrollHintOptions {
|
||||
enabled?: boolean;
|
||||
refreshToken?: string | number;
|
||||
}
|
||||
|
||||
const SCROLL_THRESHOLD_PX = 4;
|
||||
|
||||
function shouldShowScrollHint(element: HTMLDivElement) {
|
||||
const hasOverflow =
|
||||
element.scrollHeight - element.clientHeight > SCROLL_THRESHOLD_PX;
|
||||
const isAtBottom =
|
||||
element.scrollTop + element.clientHeight >=
|
||||
element.scrollHeight - SCROLL_THRESHOLD_PX;
|
||||
|
||||
return hasOverflow && !isAtBottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether a scrollable container has overflow using an
|
||||
* IntersectionObserver on a sentinel element placed at the end of the content.
|
||||
*
|
||||
* Uses callback refs (stored in state) so the observer is set up only after
|
||||
* the DOM elements actually mount — critical for Radix Dialog portals where
|
||||
* useRef would be null when the first useEffect fires.
|
||||
*
|
||||
* When the sentinel is NOT visible inside the container → content overflows
|
||||
* and the user hasn't scrolled to the bottom → show hint.
|
||||
*/
|
||||
export function useScrollHint({
|
||||
enabled = true,
|
||||
refreshToken,
|
||||
}: UseScrollHintOptions = {}) {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [containerEl, setContainerEl] = useState<HTMLDivElement | null>(null);
|
||||
const [sentinelEl, setSentinelEl] = useState<HTMLDivElement | null>(null);
|
||||
const [showScrollHint, setShowScrollHint] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
if (!enabled || !containerEl || !sentinelEl) {
|
||||
setShowScrollHint(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const element = containerRef.current;
|
||||
if (!element) return;
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
setShowScrollHint(!entry.isIntersecting);
|
||||
},
|
||||
{
|
||||
root: containerEl,
|
||||
// Small margin so the hint hides slightly before the absolute bottom
|
||||
rootMargin: "0px 0px 4px 0px",
|
||||
threshold: 0,
|
||||
},
|
||||
);
|
||||
|
||||
const recalculate = () => {
|
||||
const el = containerRef.current;
|
||||
if (!el) return;
|
||||
setShowScrollHint(shouldShowScrollHint(el));
|
||||
};
|
||||
observer.observe(sentinelEl);
|
||||
|
||||
const observer = new ResizeObserver(recalculate);
|
||||
observer.observe(element);
|
||||
return () => observer.disconnect();
|
||||
}, [enabled, refreshToken, containerEl, sentinelEl]);
|
||||
|
||||
recalculate();
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [enabled, refreshToken]);
|
||||
|
||||
const handleScroll = (event: UIEvent<HTMLDivElement>) => {
|
||||
setShowScrollHint(shouldShowScrollHint(event.currentTarget));
|
||||
};
|
||||
// Stable callback refs — setState setters never change identity
|
||||
const containerRef = useCallback(
|
||||
(node: HTMLDivElement | null) => setContainerEl(node),
|
||||
[],
|
||||
);
|
||||
const sentinelRef = useCallback(
|
||||
(node: HTMLDivElement | null) => setSentinelEl(node),
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
containerRef,
|
||||
sentinelRef,
|
||||
showScrollHint,
|
||||
handleScroll,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { IntegrationType } from "../types/integrations";
|
||||
export const DOCS_URLS = {
|
||||
FINDINGS_ANALYSIS:
|
||||
"https://docs.prowler.com/user-guide/tutorials/prowler-app#step-8:-analyze-the-findings",
|
||||
AWS_ORGANIZATIONS:
|
||||
"https://docs.prowler.com/user-guide/tutorials/prowler-cloud-aws-organizations",
|
||||
} as const;
|
||||
|
||||
export const getProviderHelpText = (provider: string) => {
|
||||
|
||||
+8
-2
@@ -171,9 +171,15 @@
|
||||
"@react-aria/interactions>react": "19.2.4",
|
||||
"lodash": "4.17.23",
|
||||
"lodash-es": "4.17.23",
|
||||
"hono": "4.11.7",
|
||||
"hono": "4.11.10",
|
||||
"@isaacs/brace-expansion": "5.0.1",
|
||||
"fast-xml-parser": "5.3.4"
|
||||
"fast-xml-parser": "5.3.6",
|
||||
"rollup@>=4": "4.59.0",
|
||||
"minimatch@<4": "3.1.3",
|
||||
"minimatch@>=9 <10": "9.0.6",
|
||||
"ajv@<7": "6.14.0",
|
||||
"ajv@>=8": "8.18.0",
|
||||
"qs": "6.14.2"
|
||||
}
|
||||
},
|
||||
"version": "0.0.1",
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const localEnvPath = path.resolve(__dirname, ".env.local");
|
||||
if (fs.existsSync(localEnvPath)) {
|
||||
process.loadEnvFile(localEnvPath);
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
|
||||
Generated
+194
-181
@@ -15,9 +15,15 @@ overrides:
|
||||
'@react-aria/interactions>react': 19.2.4
|
||||
lodash: 4.17.23
|
||||
lodash-es: 4.17.23
|
||||
hono: 4.11.7
|
||||
hono: 4.11.10
|
||||
'@isaacs/brace-expansion': 5.0.1
|
||||
fast-xml-parser: 5.3.4
|
||||
fast-xml-parser: 5.3.6
|
||||
rollup@>=4: 4.59.0
|
||||
minimatch@<4: 3.1.3
|
||||
minimatch@>=9 <10: 9.0.6
|
||||
ajv@<7: 6.14.0
|
||||
ajv@>=8: 8.18.0
|
||||
qs: 6.14.2
|
||||
|
||||
importers:
|
||||
|
||||
@@ -1749,7 +1755,7 @@ packages:
|
||||
resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
peerDependencies:
|
||||
hono: 4.11.7
|
||||
hono: 4.11.10
|
||||
|
||||
'@hookform/resolvers@5.2.2':
|
||||
resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==}
|
||||
@@ -4007,7 +4013,7 @@ packages:
|
||||
resolution: {integrity: sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==}
|
||||
engines: {node: '>=16.0.0 || 14 >= 14.17'}
|
||||
peerDependencies:
|
||||
rollup: ^2.68.0||^3.0.0||^4.0.0
|
||||
rollup: 4.59.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
@@ -4016,133 +4022,133 @@ packages:
|
||||
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||
rollup: 4.59.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.55.2':
|
||||
resolution: {integrity: sha512-21J6xzayjy3O6NdnlO6aXi/urvSRjm6nCI6+nF6ra2YofKruGixN9kfT+dt55HVNwfDmpDHJcaS3JuP/boNnlA==}
|
||||
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.55.2':
|
||||
resolution: {integrity: sha512-eXBg7ibkNUZ+sTwbFiDKou0BAckeV6kIigK7y5Ko4mB/5A1KLhuzEKovsmfvsL8mQorkoincMFGnQuIT92SKqA==}
|
||||
'@rollup/rollup-android-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.55.2':
|
||||
resolution: {integrity: sha512-UCbaTklREjrc5U47ypLulAgg4njaqfOVLU18VrCrI+6E5MQjuG0lSWaqLlAJwsD7NpFV249XgB0Bi37Zh5Sz4g==}
|
||||
'@rollup/rollup-darwin-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.55.2':
|
||||
resolution: {integrity: sha512-dP67MA0cCMHFT2g5XyjtpVOtp7y4UyUxN3dhLdt11at5cPKnSm4lY+EhwNvDXIMzAMIo2KU+mc9wxaAQJTn7sQ==}
|
||||
'@rollup/rollup-darwin-x64@4.59.0':
|
||||
resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.55.2':
|
||||
resolution: {integrity: sha512-WDUPLUwfYV9G1yxNRJdXcvISW15mpvod1Wv3ok+Ws93w1HjIVmCIFxsG2DquO+3usMNCpJQ0wqO+3GhFdl6Fow==}
|
||||
'@rollup/rollup-freebsd-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.55.2':
|
||||
resolution: {integrity: sha512-Ng95wtHVEulRwn7R0tMrlUuiLVL/HXA8Lt/MYVpy88+s5ikpntzZba1qEulTuPnPIZuOPcW9wNEiqvZxZmgmqQ==}
|
||||
'@rollup/rollup-freebsd-x64@4.59.0':
|
||||
resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.55.2':
|
||||
resolution: {integrity: sha512-AEXMESUDWWGqD6LwO/HkqCZgUE1VCJ1OhbvYGsfqX2Y6w5quSXuyoy/Fg3nRqiwro+cJYFxiw5v4kB2ZDLhxrw==}
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
|
||||
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.55.2':
|
||||
resolution: {integrity: sha512-ZV7EljjBDwBBBSv570VWj0hiNTdHt9uGznDtznBB4Caj3ch5rgD4I2K1GQrtbvJ/QiB+663lLgOdcADMNVC29Q==}
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.55.2':
|
||||
resolution: {integrity: sha512-uvjwc8NtQVPAJtq4Tt7Q49FOodjfbf6NpqXyW/rjXoV+iZ3EJAHLNAnKT5UJBc6ffQVgmXTUL2ifYiLABlGFqA==}
|
||||
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.55.2':
|
||||
resolution: {integrity: sha512-s3KoWVNnye9mm/2WpOZ3JeUiediUVw6AvY/H7jNA6qgKA2V2aM25lMkVarTDfiicn/DLq3O0a81jncXszoyCFA==}
|
||||
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.55.2':
|
||||
resolution: {integrity: sha512-gi21faacK+J8aVSyAUptML9VQN26JRxe484IbF+h3hpG+sNVoMXPduhREz2CcYr5my0NE3MjVvQ5bMKX71pfVA==}
|
||||
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.55.2':
|
||||
resolution: {integrity: sha512-qSlWiXnVaS/ceqXNfnoFZh4IiCA0EwvCivivTGbEu1qv2o+WTHpn1zNmCTAoOG5QaVr2/yhCoLScQtc/7RxshA==}
|
||||
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.55.2':
|
||||
resolution: {integrity: sha512-rPyuLFNoF1B0+wolH277E780NUKf+KoEDb3OyoLbAO18BbeKi++YN6gC/zuJoPPDlQRL3fIxHxCxVEWiem2yXw==}
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.55.2':
|
||||
resolution: {integrity: sha512-g+0ZLMook31iWV4PvqKU0i9E78gaZgYpSrYPed/4Bu+nGTgfOPtfs1h11tSSRPXSjC5EzLTjV/1A7L2Vr8pJoQ==}
|
||||
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.55.2':
|
||||
resolution: {integrity: sha512-i+sGeRGsjKZcQRh3BRfpLsM3LX3bi4AoEVqmGDyc50L6KfYsN45wVCSz70iQMwPWr3E5opSiLOwsC9WB4/1pqg==}
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.55.2':
|
||||
resolution: {integrity: sha512-C1vLcKc4MfFV6I0aWsC7B2Y9QcsiEcvKkfxprwkPfLaN8hQf0/fKHwSF2lcYzA9g4imqnhic729VB9Fo70HO3Q==}
|
||||
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.55.2':
|
||||
resolution: {integrity: sha512-68gHUK/howpQjh7g7hlD9DvTTt4sNLp1Bb+Yzw2Ki0xvscm2cOdCLZNJNhd2jW8lsTPrHAHuF751BygifW4bkQ==}
|
||||
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.55.2':
|
||||
resolution: {integrity: sha512-1e30XAuaBP1MAizaOBApsgeGZge2/Byd6wV4a8oa6jPdHELbRHBiw7wvo4dp7Ie2PE8TZT4pj9RLGZv9N4qwlw==}
|
||||
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.55.2':
|
||||
resolution: {integrity: sha512-4BJucJBGbuGnH6q7kpPqGJGzZnYrpAzRd60HQSt3OpX/6/YVgSsJnNzR8Ot74io50SeVT4CtCWe/RYIAymFPwA==}
|
||||
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.55.2':
|
||||
resolution: {integrity: sha512-cT2MmXySMo58ENv8p6/O6wI/h/gLnD3D6JoajwXFZH6X9jz4hARqUhWpGuQhOgLNXscfZYRQMJvZDtWNzMAIDw==}
|
||||
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.55.2':
|
||||
resolution: {integrity: sha512-sZnyUgGkuzIXaK3jNMPmUIyJrxu/PjmATQrocpGA1WbCPX8H5tfGgRSuYtqBYAvLuIGp8SPRb1O4d1Fkb5fXaQ==}
|
||||
'@rollup/rollup-openharmony-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.55.2':
|
||||
resolution: {integrity: sha512-sDpFbenhmWjNcEbBcoTV0PWvW5rPJFvu+P7XoTY0YLGRupgLbFY0XPfwIbJOObzO7QgkRDANh65RjhPmgSaAjQ==}
|
||||
'@rollup/rollup-win32-arm64-msvc@4.59.0':
|
||||
resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.55.2':
|
||||
resolution: {integrity: sha512-GvJ03TqqaweWCigtKQVBErw2bEhu1tyfNQbarwr94wCGnczA9HF8wqEe3U/Lfu6EdeNP0p6R+APeHVwEqVxpUQ==}
|
||||
'@rollup/rollup-win32-ia32-msvc@4.59.0':
|
||||
resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.55.2':
|
||||
resolution: {integrity: sha512-KvXsBvp13oZz9JGe5NYS7FNizLe99Ny+W8ETsuCyjXiKdiGrcz2/J/N8qxZ/RSwivqjQguug07NLHqrIHrqfYw==}
|
||||
'@rollup/rollup-win32-x64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.55.2':
|
||||
resolution: {integrity: sha512-xNO+fksQhsAckRtDSPWaMeT1uIM+JrDRXlerpnWNXhn1TdB3YZ6uKBMBTKP0eX9XtYEP978hHk1f8332i2AW8Q==}
|
||||
'@rollup/rollup-win32-x64-msvc@4.59.0':
|
||||
resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
@@ -5197,7 +5203,7 @@ packages:
|
||||
ajv-formats@2.1.1:
|
||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||
peerDependencies:
|
||||
ajv: ^8.0.0
|
||||
ajv: 8.18.0
|
||||
peerDependenciesMeta:
|
||||
ajv:
|
||||
optional: true
|
||||
@@ -5205,7 +5211,7 @@ packages:
|
||||
ajv-formats@3.0.1:
|
||||
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
||||
peerDependencies:
|
||||
ajv: ^8.0.0
|
||||
ajv: 8.18.0
|
||||
peerDependenciesMeta:
|
||||
ajv:
|
||||
optional: true
|
||||
@@ -5213,13 +5219,13 @@ packages:
|
||||
ajv-keywords@5.1.0:
|
||||
resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
|
||||
peerDependencies:
|
||||
ajv: ^8.8.2
|
||||
ajv: 8.18.0
|
||||
|
||||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
ajv@6.14.0:
|
||||
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
|
||||
|
||||
ajv@8.17.1:
|
||||
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
||||
ajv@8.18.0:
|
||||
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
|
||||
|
||||
alert@6.0.2:
|
||||
resolution: {integrity: sha512-Oi8u2HRNN6mzpjgKGii2Uuf9iOhyfbeUAHH/5MwnVmC8DS9GrEBjZBFpoavkNj+ZKnBr/Lqx+6YKLDKrggKfPA==}
|
||||
@@ -5351,6 +5357,10 @@ packages:
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
balanced-match@4.0.4:
|
||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
@@ -5375,8 +5385,9 @@ packages:
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
|
||||
brace-expansion@2.0.2:
|
||||
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
||||
brace-expansion@5.0.3:
|
||||
resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
@@ -6332,8 +6343,8 @@ packages:
|
||||
fast-uri@3.1.0:
|
||||
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
||||
|
||||
fast-xml-parser@5.3.4:
|
||||
resolution: {integrity: sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==}
|
||||
fast-xml-parser@5.3.6:
|
||||
resolution: {integrity: sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==}
|
||||
hasBin: true
|
||||
|
||||
fastq@1.20.1:
|
||||
@@ -6634,8 +6645,8 @@ packages:
|
||||
hoist-non-react-statics@3.3.2:
|
||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||
|
||||
hono@4.11.7:
|
||||
resolution: {integrity: sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==}
|
||||
hono@4.11.10:
|
||||
resolution: {integrity: sha512-kyWP5PAiMooEvGrA9jcD3IXF7ATu8+o7B3KCbPXid5se52NPqnOpM/r9qeW2heMnOekF4kqR1fXJqCYeCLKrZg==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
html-encoding-sniffer@6.0.0:
|
||||
@@ -7536,11 +7547,11 @@ packages:
|
||||
resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
minimatch@3.1.3:
|
||||
resolution: {integrity: sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==}
|
||||
|
||||
minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
minimatch@9.0.6:
|
||||
resolution: {integrity: sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minimist@1.2.8:
|
||||
@@ -8100,8 +8111,8 @@ packages:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
qs@6.14.1:
|
||||
resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==}
|
||||
qs@6.14.2:
|
||||
resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
queue-microtask@1.2.3:
|
||||
@@ -8362,8 +8373,8 @@ packages:
|
||||
robust-predicates@3.0.2:
|
||||
resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
|
||||
|
||||
rollup@4.55.2:
|
||||
resolution: {integrity: sha512-PggGy4dhwx5qaW+CKBilA/98Ql9keyfnb7lh4SR6shQ91QQQi1ORJ1v4UinkdP2i87OBs9AQFooQylcrrRfIcg==}
|
||||
rollup@4.59.0:
|
||||
resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -10227,13 +10238,13 @@ snapshots:
|
||||
'@aws-sdk/xml-builder@3.969.0':
|
||||
dependencies:
|
||||
'@smithy/types': 4.12.0
|
||||
fast-xml-parser: 5.3.4
|
||||
fast-xml-parser: 5.3.6
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/xml-builder@3.972.4':
|
||||
dependencies:
|
||||
'@smithy/types': 4.12.0
|
||||
fast-xml-parser: 5.3.4
|
||||
fast-xml-parser: 5.3.6
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws/lambda-invoke-store@0.2.3': {}
|
||||
@@ -10613,7 +10624,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@eslint/object-schema': 2.1.7
|
||||
debug: 4.4.3
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -10627,14 +10638,14 @@ snapshots:
|
||||
|
||||
'@eslint/eslintrc@3.3.3':
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
ajv: 6.14.0
|
||||
debug: 4.4.3
|
||||
espree: 10.4.0
|
||||
globals: 14.0.0
|
||||
ignore: 5.3.2
|
||||
import-fresh: 3.3.1
|
||||
js-yaml: 4.1.1
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.3
|
||||
strip-json-comments: 3.1.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -10656,7 +10667,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@ndaidong/bellajs': 12.0.1
|
||||
cross-fetch: 4.1.0
|
||||
fast-xml-parser: 5.3.4
|
||||
fast-xml-parser: 5.3.6
|
||||
html-entities: 2.6.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
@@ -11724,9 +11735,9 @@ snapshots:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@hono/node-server@1.19.9(hono@4.11.7)':
|
||||
'@hono/node-server@1.19.9(hono@4.11.10)':
|
||||
dependencies:
|
||||
hono: 4.11.7
|
||||
hono: 4.11.10
|
||||
|
||||
'@hookform/resolvers@5.2.2(react-hook-form@7.62.0(react@19.2.4))':
|
||||
dependencies:
|
||||
@@ -12097,9 +12108,9 @@ snapshots:
|
||||
|
||||
'@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@hono/node-server': 1.19.9(hono@4.11.7)
|
||||
ajv: 8.17.1
|
||||
ajv-formats: 3.0.1(ajv@8.17.1)
|
||||
'@hono/node-server': 1.19.9(hono@4.11.10)
|
||||
ajv: 8.18.0
|
||||
ajv-formats: 3.0.1(ajv@8.18.0)
|
||||
content-type: 1.0.5
|
||||
cors: 2.8.5
|
||||
cross-spawn: 7.0.6
|
||||
@@ -12107,7 +12118,7 @@ snapshots:
|
||||
eventsource-parser: 3.0.6
|
||||
express: 5.2.1
|
||||
express-rate-limit: 8.2.1(express@5.2.1)
|
||||
hono: 4.11.7
|
||||
hono: 4.11.10
|
||||
jose: 6.1.3
|
||||
json-schema-typed: 8.0.2
|
||||
pkce-challenge: 5.0.1
|
||||
@@ -12121,9 +12132,9 @@ snapshots:
|
||||
|
||||
'@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@4.1.11)':
|
||||
dependencies:
|
||||
'@hono/node-server': 1.19.9(hono@4.11.7)
|
||||
ajv: 8.17.1
|
||||
ajv-formats: 3.0.1(ajv@8.17.1)
|
||||
'@hono/node-server': 1.19.9(hono@4.11.10)
|
||||
ajv: 8.18.0
|
||||
ajv-formats: 3.0.1(ajv@8.18.0)
|
||||
content-type: 1.0.5
|
||||
cors: 2.8.5
|
||||
cross-spawn: 7.0.6
|
||||
@@ -12131,7 +12142,7 @@ snapshots:
|
||||
eventsource-parser: 3.0.6
|
||||
express: 5.2.1
|
||||
express-rate-limit: 8.2.1(express@5.2.1)
|
||||
hono: 4.11.7
|
||||
hono: 4.11.10
|
||||
jose: 6.1.3
|
||||
json-schema-typed: 8.0.2
|
||||
pkce-challenge: 5.0.1
|
||||
@@ -14515,9 +14526,9 @@ snapshots:
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.53': {}
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.1(rollup@4.55.2)':
|
||||
'@rollup/plugin-commonjs@28.0.1(rollup@4.59.0)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.55.2)
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.59.0)
|
||||
commondir: 1.0.1
|
||||
estree-walker: 2.0.2
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -14525,89 +14536,89 @@ snapshots:
|
||||
magic-string: 0.30.21
|
||||
picomatch: 4.0.3
|
||||
optionalDependencies:
|
||||
rollup: 4.55.2
|
||||
rollup: 4.59.0
|
||||
|
||||
'@rollup/pluginutils@5.3.0(rollup@4.55.2)':
|
||||
'@rollup/pluginutils@5.3.0(rollup@4.59.0)':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 4.0.3
|
||||
optionalDependencies:
|
||||
rollup: 4.55.2
|
||||
rollup: 4.59.0
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.55.2':
|
||||
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.55.2':
|
||||
'@rollup/rollup-android-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.55.2':
|
||||
'@rollup/rollup-darwin-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.55.2':
|
||||
'@rollup/rollup-darwin-x64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.55.2':
|
||||
'@rollup/rollup-freebsd-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.55.2':
|
||||
'@rollup/rollup-freebsd-x64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.55.2':
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.55.2':
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.55.2':
|
||||
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.55.2':
|
||||
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.55.2':
|
||||
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.55.2':
|
||||
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.55.2':
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.55.2':
|
||||
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.55.2':
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.55.2':
|
||||
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.55.2':
|
||||
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.55.2':
|
||||
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.55.2':
|
||||
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.55.2':
|
||||
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.55.2':
|
||||
'@rollup/rollup-openharmony-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.55.2':
|
||||
'@rollup/rollup-win32-arm64-msvc@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.55.2':
|
||||
'@rollup/rollup-win32-ia32-msvc@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.55.2':
|
||||
'@rollup/rollup-win32-x64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.55.2':
|
||||
'@rollup/rollup-win32-x64-msvc@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rtsao/scc@1.1.0': {}
|
||||
@@ -14706,7 +14717,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/semantic-conventions': 1.39.0
|
||||
'@rollup/plugin-commonjs': 28.0.1(rollup@4.55.2)
|
||||
'@rollup/plugin-commonjs': 28.0.1(rollup@4.59.0)
|
||||
'@sentry-internal/browser-utils': 10.27.0
|
||||
'@sentry/bundler-plugin-core': 4.7.0
|
||||
'@sentry/core': 10.27.0
|
||||
@@ -14717,7 +14728,7 @@ snapshots:
|
||||
'@sentry/webpack-plugin': 4.7.0(webpack@5.104.1)
|
||||
next: 16.1.6(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
resolve: 1.22.8
|
||||
rollup: 4.55.2
|
||||
rollup: 4.59.0
|
||||
stacktrace-parser: 0.1.11
|
||||
transitivePeerDependencies:
|
||||
- '@opentelemetry/context-async-hooks'
|
||||
@@ -14780,7 +14791,7 @@ snapshots:
|
||||
'@sentry/node-core': 10.27.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)
|
||||
'@sentry/opentelemetry': 10.27.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.4.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)
|
||||
import-in-the-middle: 2.0.0
|
||||
minimatch: 9.0.5
|
||||
minimatch: 9.0.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -15699,7 +15710,7 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.53.0
|
||||
'@typescript-eslint/visitor-keys': 8.53.0
|
||||
debug: 4.4.3
|
||||
minimatch: 9.0.5
|
||||
minimatch: 9.0.6
|
||||
semver: 7.7.3
|
||||
tinyglobby: 0.2.15
|
||||
ts-api-utils: 2.4.0(typescript@5.5.4)
|
||||
@@ -15967,27 +15978,27 @@ snapshots:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
zod: 4.1.11
|
||||
|
||||
ajv-formats@2.1.1(ajv@8.17.1):
|
||||
ajv-formats@2.1.1(ajv@8.18.0):
|
||||
optionalDependencies:
|
||||
ajv: 8.17.1
|
||||
ajv: 8.18.0
|
||||
|
||||
ajv-formats@3.0.1(ajv@8.17.1):
|
||||
ajv-formats@3.0.1(ajv@8.18.0):
|
||||
optionalDependencies:
|
||||
ajv: 8.17.1
|
||||
ajv: 8.18.0
|
||||
|
||||
ajv-keywords@5.1.0(ajv@8.17.1):
|
||||
ajv-keywords@5.1.0(ajv@8.18.0):
|
||||
dependencies:
|
||||
ajv: 8.17.1
|
||||
ajv: 8.18.0
|
||||
fast-deep-equal: 3.1.3
|
||||
|
||||
ajv@6.12.6:
|
||||
ajv@6.14.0:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-json-stable-stringify: 2.1.0
|
||||
json-schema-traverse: 0.4.1
|
||||
uri-js: 4.4.1
|
||||
|
||||
ajv@8.17.1:
|
||||
ajv@8.18.0:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-uri: 3.1.0
|
||||
@@ -16143,6 +16154,8 @@ snapshots:
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
balanced-match@4.0.4: {}
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
baseline-browser-mapping@2.9.15: {}
|
||||
@@ -16161,7 +16174,7 @@ snapshots:
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.7.2
|
||||
on-finished: 2.4.1
|
||||
qs: 6.14.1
|
||||
qs: 6.14.2
|
||||
raw-body: 3.0.2
|
||||
type-is: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
@@ -16174,9 +16187,9 @@ snapshots:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
|
||||
brace-expansion@2.0.2:
|
||||
brace-expansion@5.0.3:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
balanced-match: 4.0.4
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
@@ -16996,7 +17009,7 @@ snapshots:
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.3
|
||||
object.fromentries: 2.0.8
|
||||
object.groupby: 1.0.3
|
||||
object.values: 1.2.1
|
||||
@@ -17024,7 +17037,7 @@ snapshots:
|
||||
hasown: 2.0.2
|
||||
jsx-ast-utils: 3.3.5
|
||||
language-tags: 1.0.9
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.3
|
||||
object.fromentries: 2.0.8
|
||||
safe-regex-test: 1.1.0
|
||||
string.prototype.includes: 2.0.1
|
||||
@@ -17035,7 +17048,7 @@ snapshots:
|
||||
eslint-plugin-es: 3.0.1(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-utils: 2.1.0
|
||||
ignore: 5.3.2
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.3
|
||||
resolve: 1.22.11
|
||||
semver: 6.3.1
|
||||
|
||||
@@ -17072,7 +17085,7 @@ snapshots:
|
||||
estraverse: 5.3.0
|
||||
hasown: 2.0.2
|
||||
jsx-ast-utils: 3.3.5
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.3
|
||||
object.entries: 1.1.9
|
||||
object.fromentries: 2.0.8
|
||||
object.values: 1.2.1
|
||||
@@ -17130,7 +17143,7 @@ snapshots:
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
'@types/estree': 1.0.8
|
||||
ajv: 6.12.6
|
||||
ajv: 6.14.0
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.3
|
||||
@@ -17149,7 +17162,7 @@ snapshots:
|
||||
is-glob: 4.0.3
|
||||
json-stable-stringify-without-jsonify: 1.0.1
|
||||
lodash.merge: 4.6.2
|
||||
minimatch: 3.1.2
|
||||
minimatch: 3.1.3
|
||||
natural-compare: 1.4.0
|
||||
optionator: 0.9.4
|
||||
optionalDependencies:
|
||||
@@ -17269,7 +17282,7 @@ snapshots:
|
||||
once: 1.4.0
|
||||
parseurl: 1.3.3
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.14.1
|
||||
qs: 6.14.2
|
||||
range-parser: 1.2.1
|
||||
router: 2.2.0
|
||||
send: 1.2.1
|
||||
@@ -17313,7 +17326,7 @@ snapshots:
|
||||
|
||||
fast-uri@3.1.0: {}
|
||||
|
||||
fast-xml-parser@5.3.4:
|
||||
fast-xml-parser@5.3.6:
|
||||
dependencies:
|
||||
strnum: 2.1.2
|
||||
|
||||
@@ -17489,7 +17502,7 @@ snapshots:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
jackspeak: 3.4.3
|
||||
minimatch: 9.0.5
|
||||
minimatch: 9.0.6
|
||||
minipass: 7.1.2
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
@@ -17673,7 +17686,7 @@ snapshots:
|
||||
dependencies:
|
||||
react-is: 16.13.1
|
||||
|
||||
hono@4.11.7: {}
|
||||
hono@4.11.10: {}
|
||||
|
||||
html-encoding-sniffer@6.0.0(@noble/hashes@1.8.0):
|
||||
dependencies:
|
||||
@@ -18771,13 +18784,13 @@ snapshots:
|
||||
dependencies:
|
||||
'@isaacs/brace-expansion': 5.0.1
|
||||
|
||||
minimatch@3.1.2:
|
||||
minimatch@3.1.3:
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
|
||||
minimatch@9.0.5:
|
||||
minimatch@9.0.6:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
brace-expansion: 5.0.3
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
@@ -19259,7 +19272,7 @@ snapshots:
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qs@6.14.1:
|
||||
qs@6.14.2:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
|
||||
@@ -19637,35 +19650,35 @@ snapshots:
|
||||
|
||||
robust-predicates@3.0.2: {}
|
||||
|
||||
rollup@4.55.2:
|
||||
rollup@4.59.0:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.55.2
|
||||
'@rollup/rollup-android-arm64': 4.55.2
|
||||
'@rollup/rollup-darwin-arm64': 4.55.2
|
||||
'@rollup/rollup-darwin-x64': 4.55.2
|
||||
'@rollup/rollup-freebsd-arm64': 4.55.2
|
||||
'@rollup/rollup-freebsd-x64': 4.55.2
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.55.2
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.55.2
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.55.2
|
||||
'@rollup/rollup-linux-arm64-musl': 4.55.2
|
||||
'@rollup/rollup-linux-loong64-gnu': 4.55.2
|
||||
'@rollup/rollup-linux-loong64-musl': 4.55.2
|
||||
'@rollup/rollup-linux-ppc64-gnu': 4.55.2
|
||||
'@rollup/rollup-linux-ppc64-musl': 4.55.2
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.55.2
|
||||
'@rollup/rollup-linux-riscv64-musl': 4.55.2
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.55.2
|
||||
'@rollup/rollup-linux-x64-gnu': 4.55.2
|
||||
'@rollup/rollup-linux-x64-musl': 4.55.2
|
||||
'@rollup/rollup-openbsd-x64': 4.55.2
|
||||
'@rollup/rollup-openharmony-arm64': 4.55.2
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.55.2
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.55.2
|
||||
'@rollup/rollup-win32-x64-gnu': 4.55.2
|
||||
'@rollup/rollup-win32-x64-msvc': 4.55.2
|
||||
'@rollup/rollup-android-arm-eabi': 4.59.0
|
||||
'@rollup/rollup-android-arm64': 4.59.0
|
||||
'@rollup/rollup-darwin-arm64': 4.59.0
|
||||
'@rollup/rollup-darwin-x64': 4.59.0
|
||||
'@rollup/rollup-freebsd-arm64': 4.59.0
|
||||
'@rollup/rollup-freebsd-x64': 4.59.0
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.59.0
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.59.0
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-arm64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-loong64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-loong64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-ppc64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-ppc64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-riscv64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-x64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-x64-musl': 4.59.0
|
||||
'@rollup/rollup-openbsd-x64': 4.59.0
|
||||
'@rollup/rollup-openharmony-arm64': 4.59.0
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.59.0
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.59.0
|
||||
'@rollup/rollup-win32-x64-gnu': 4.59.0
|
||||
'@rollup/rollup-win32-x64-msvc': 4.59.0
|
||||
fsevents: 2.3.3
|
||||
|
||||
roughjs@4.6.6:
|
||||
@@ -19729,9 +19742,9 @@ snapshots:
|
||||
schema-utils@4.3.3:
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
ajv: 8.17.1
|
||||
ajv-formats: 2.1.1(ajv@8.17.1)
|
||||
ajv-keywords: 5.1.0(ajv@8.17.1)
|
||||
ajv: 8.18.0
|
||||
ajv-formats: 2.1.1(ajv@8.18.0)
|
||||
ajv-keywords: 5.1.0(ajv@8.18.0)
|
||||
|
||||
scroll-into-view-if-needed@3.0.10:
|
||||
dependencies:
|
||||
@@ -20558,7 +20571,7 @@ snapshots:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
postcss: 8.5.6
|
||||
rollup: 4.55.2
|
||||
rollup: 4.59.0
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.8
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { getSessionWithoutCookies, TEST_CREDENTIALS } from "../helpers";
|
||||
import { TEST_CREDENTIALS } from "../helpers";
|
||||
import { ProvidersPage } from "../providers/providers-page";
|
||||
import { ScansPage } from "../scans/scans-page";
|
||||
import { SignInPage } from "../sign-in-base/sign-in-base-page";
|
||||
import { SignUpPage } from "../sign-up/sign-up-page";
|
||||
|
||||
@@ -30,24 +29,46 @@ test.describe("Middleware Error Handling", () => {
|
||||
test(
|
||||
"should maintain protection after session error",
|
||||
{ tag: ["@e2e", "@auth", "@middleware", "@AUTH-MW-E2E-002"] },
|
||||
async ({ page, context }) => {
|
||||
async ({ page, context, browser }) => {
|
||||
const signInPage = new SignInPage(page);
|
||||
const providersPage = new ProvidersPage(page);
|
||||
const scansPage = new ScansPage(page);
|
||||
|
||||
await signInPage.loginAndVerify(TEST_CREDENTIALS.VALID);
|
||||
|
||||
await providersPage.goto();
|
||||
await providersPage.verifyPageLoaded();
|
||||
|
||||
// Remove auth cookies to simulate a broken/expired session deterministically.
|
||||
await context.clearCookies();
|
||||
// Build an isolated context with an explicitly invalid auth token.
|
||||
// This avoids races from active tabs rehydrating cookies in the original context.
|
||||
const authenticatedState = await context.storageState();
|
||||
const authCookies = authenticatedState.cookies.filter((cookie) =>
|
||||
/(authjs|next-auth)/i.test(cookie.name),
|
||||
);
|
||||
expect(authCookies.length).toBeGreaterThan(0);
|
||||
|
||||
const expiredSession = await getSessionWithoutCookies(page);
|
||||
expect(expiredSession).toBeNull();
|
||||
const invalidSessionContext = await browser.newContext({
|
||||
storageState: {
|
||||
origins: authenticatedState.origins,
|
||||
cookies: authenticatedState.cookies.map((cookie) =>
|
||||
/(authjs|next-auth)/i.test(cookie.name)
|
||||
? { ...cookie, value: "invalid.session.token" }
|
||||
: cookie,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
await scansPage.goto();
|
||||
await signInPage.verifyOnSignInPage();
|
||||
try {
|
||||
// Use a fresh page to force a full navigation through proxy in Next.js 16.
|
||||
const freshPage = await invalidSessionContext.newPage();
|
||||
const freshSignInPage = new SignInPage(freshPage);
|
||||
const cacheBuster = Date.now();
|
||||
await freshPage.goto(`/scans?e2e_mw=${cacheBuster}`, {
|
||||
waitUntil: "commit",
|
||||
});
|
||||
await freshSignInPage.verifyRedirectWithCallback("/scans");
|
||||
} finally {
|
||||
await invalidSessionContext.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
+10
-45
@@ -135,47 +135,30 @@ export async function deleteProviderIfExists(page: ProvidersPage, providerUID: s
|
||||
await page.goto();
|
||||
await expect(page.providersTable).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Find and use the search input to filter the table
|
||||
const searchInput = page.page.getByPlaceholder(/search|filter/i);
|
||||
|
||||
await expect(searchInput).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Clear and search for the specific provider
|
||||
await searchInput.clear();
|
||||
await searchInput.fill(providerUID);
|
||||
await searchInput.press("Enter");
|
||||
|
||||
// Additional wait for React table to re-render with the server-filtered data
|
||||
// The filtering happens on the server, but the table component needs time
|
||||
// to process the response and update the DOM after network idle
|
||||
await page.page.waitForTimeout(1500);
|
||||
|
||||
// Get all rows from the table
|
||||
const allRows = page.providersTable.locator("tbody tr");
|
||||
|
||||
// Helper function to check if a row is the "No results" row
|
||||
const isNoResultsRow = async (row: Locator): Promise<boolean> => {
|
||||
const text = await row.textContent();
|
||||
return text?.includes("No results") || text?.includes("No data") || false;
|
||||
};
|
||||
|
||||
// Helper function to find the row with the specific UID
|
||||
const findProviderRow = async (): Promise<Locator | null> => {
|
||||
const count = await allRows.count();
|
||||
const rowByText = page.providersTable
|
||||
.locator("tbody tr")
|
||||
.filter({ hasText: providerUID })
|
||||
.first();
|
||||
if (await rowByText.isVisible().catch(() => false)) {
|
||||
return rowByText;
|
||||
}
|
||||
|
||||
const count = await allRows.count();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const row = allRows.nth(i);
|
||||
|
||||
// Skip "No results" rows
|
||||
if (await isNoResultsRow(row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this row contains the UID in the UID column (column 3)
|
||||
const uidCell = row.locator("td").nth(3);
|
||||
const uidText = await uidCell.textContent();
|
||||
|
||||
if (uidText?.includes(providerUID)) {
|
||||
const rowText = await row.textContent();
|
||||
if (rowText?.includes(providerUID)) {
|
||||
return row;
|
||||
}
|
||||
}
|
||||
@@ -183,24 +166,6 @@ export async function deleteProviderIfExists(page: ProvidersPage, providerUID: s
|
||||
return null;
|
||||
};
|
||||
|
||||
// Wait for filtering to complete (max 0 or 1 data rows)
|
||||
await expect(async () => {
|
||||
|
||||
await findProviderRow();
|
||||
const count = await allRows.count();
|
||||
|
||||
// Count only real data rows (not "No results")
|
||||
let dataRowCount = 0;
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (!(await isNoResultsRow(allRows.nth(i)))) {
|
||||
dataRowCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Should have 0 or 1 data row
|
||||
expect(dataRowCount).toBeLessThanOrEqual(1);
|
||||
}).toPass({ timeout: 20000 });
|
||||
|
||||
// Find the provider row
|
||||
const targetRow = await findProviderRow();
|
||||
|
||||
|
||||
@@ -40,11 +40,10 @@ export class InvitationsPage extends BasePage {
|
||||
// Form inputs
|
||||
this.emailInput = page.getByRole("textbox", { name: "Email" });
|
||||
|
||||
// Form select
|
||||
this.roleSelect = page
|
||||
.getByRole("combobox", { name: /Role|Select a role/i })
|
||||
.or(page.getByRole("button", { name: /Role|Select a role/i }))
|
||||
.first();
|
||||
// Form select (Radix Select renders <button role="combobox"> with aria-label)
|
||||
this.roleSelect = page.getByRole("combobox", {
|
||||
name: /Select a role/i,
|
||||
});
|
||||
|
||||
// Form details
|
||||
this.reviewInvitationDetailsButton = page.getByRole("button", {
|
||||
@@ -96,23 +95,15 @@ export class InvitationsPage extends BasePage {
|
||||
}
|
||||
|
||||
async selectRole(role: string): Promise<void> {
|
||||
// Select the role option
|
||||
|
||||
// Open the role dropdown
|
||||
await expect(this.roleSelect).toBeVisible({ timeout: 15000 });
|
||||
await this.roleSelect.click();
|
||||
|
||||
// Prefer ARIA role option inside listbox
|
||||
const option = this.page.getByRole("option", {
|
||||
name: new RegExp(`^${role}$`, "i"),
|
||||
name: new RegExp(role, "i"),
|
||||
});
|
||||
await expect(option.first()).toBeVisible({ timeout: 10000 });
|
||||
await option.first().click();
|
||||
|
||||
if (await option.count()) {
|
||||
await option.first().click();
|
||||
} else {
|
||||
throw new Error(`Role option ${role} not found`);
|
||||
}
|
||||
// Ensure a role value was selected in the trigger
|
||||
await expect(this.roleSelect).not.toContainText(/Select a role/i);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,69 +5,80 @@ import { SignUpPage } from "../sign-up/sign-up-page";
|
||||
import { SignInPage } from "../sign-in-base/sign-in-base-page";
|
||||
import { UserProfilePage } from "../profile/profile-page";
|
||||
|
||||
const isCloudEnv = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true";
|
||||
|
||||
test.describe("New user invitation", () => {
|
||||
// Invitations page object
|
||||
let invitationsPage: InvitationsPage;
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
invitationsPage = new InvitationsPage(page);
|
||||
});
|
||||
|
||||
// Use admin authentication for invitations management
|
||||
test.use({ storageState: "playwright/.auth/admin_user.json" });
|
||||
|
||||
test(
|
||||
"should invite a new user",
|
||||
"should send an invitation successfully",
|
||||
{
|
||||
tag: ["@critical", "@e2e", "@invitations", "@INVITATION-E2E-001"],
|
||||
},
|
||||
async ({ page, browser }) => {
|
||||
async () => {
|
||||
const suffix = makeSuffix(10);
|
||||
const uniqueEmail = `e2e+${suffix}@prowler.com`;
|
||||
|
||||
await invitationsPage.goto();
|
||||
await invitationsPage.verifyPageLoaded();
|
||||
|
||||
await invitationsPage.clickInviteButton();
|
||||
await invitationsPage.verifyInvitePageLoaded();
|
||||
|
||||
await invitationsPage.fillEmail(uniqueEmail);
|
||||
await invitationsPage.selectRole("admin");
|
||||
|
||||
await invitationsPage.clickSendInviteButton();
|
||||
await invitationsPage.verifyInviteDataPageLoaded();
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"should invite a new user and verify signup and login",
|
||||
{
|
||||
tag: ["@critical", "@e2e", "@invitations", "@INVITATION-E2E-002"],
|
||||
},
|
||||
async ({ browser }) => {
|
||||
test.skip(!isCloudEnv, "Requires email-verification flow (Cloud only)");
|
||||
|
||||
// Test data from environment variables
|
||||
const password = process.env.E2E_NEW_USER_PASSWORD;
|
||||
const organizationId = process.env.E2E_ORGANIZATION_ID;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!password || !organizationId) {
|
||||
throw new Error(
|
||||
"E2E_NEW_USER_PASSWORD or E2E_ORGANIZATION_ID environment variable is not set",
|
||||
);
|
||||
}
|
||||
|
||||
// Generate unique test data
|
||||
const suffix = makeSuffix(10);
|
||||
const uniqueEmail = `e2e+${suffix}@prowler.com`;
|
||||
|
||||
// Navigate to providers page
|
||||
await invitationsPage.goto();
|
||||
await invitationsPage.verifyPageLoaded();
|
||||
|
||||
// Press the invite button
|
||||
await invitationsPage.clickInviteButton();
|
||||
await invitationsPage.verifyInvitePageLoaded();
|
||||
|
||||
// Fill the email
|
||||
await invitationsPage.fillEmail(uniqueEmail);
|
||||
await invitationsPage.selectRole("admin");
|
||||
|
||||
// Select the role option
|
||||
await invitationsPage.selectRole("e2e_admin");
|
||||
|
||||
// Press the send invitation button
|
||||
await invitationsPage.clickSendInviteButton();
|
||||
await invitationsPage.verifyInviteDataPageLoaded();
|
||||
|
||||
// Get the share url
|
||||
const shareUrl = await invitationsPage.getShareUrl();
|
||||
|
||||
// Navigate to the share url with a new context to avoid cookies from the admin context
|
||||
const inviteContext = await browser.newContext({ storageState: { cookies: [], origins: [] } });
|
||||
const inviteContext = await browser.newContext({
|
||||
storageState: { cookies: [], origins: [] },
|
||||
});
|
||||
const signUpPage = new SignUpPage(await inviteContext.newPage());
|
||||
|
||||
// Navigate to the share url
|
||||
await signUpPage.gotoInvite(shareUrl);
|
||||
|
||||
// Fill and submit the sign-up form
|
||||
await signUpPage.signup({
|
||||
name: `E2E User ${suffix}`,
|
||||
email: uniqueEmail,
|
||||
@@ -76,13 +87,9 @@ test.describe("New user invitation", () => {
|
||||
acceptTerms: true,
|
||||
});
|
||||
|
||||
// Verify no errors occurred during sign-up
|
||||
await signUpPage.verifyNoErrors();
|
||||
|
||||
// Verify redirect to login page (OSS environment)
|
||||
await signUpPage.verifyRedirectToLogin();
|
||||
|
||||
// Verify the newly created user can log in successfully with the new context
|
||||
const signInPage = new SignInPage(await inviteContext.newPage());
|
||||
await signInPage.goto();
|
||||
await signInPage.login({
|
||||
@@ -90,15 +97,13 @@ test.describe("New user invitation", () => {
|
||||
password: password,
|
||||
});
|
||||
await signInPage.verifySuccessfulLogin();
|
||||
|
||||
// Navigate to the user profile page
|
||||
const userProfilePage = new UserProfilePage(await inviteContext.newPage());
|
||||
await userProfilePage.goto();
|
||||
|
||||
// Verify if user is added to the organization
|
||||
const userProfilePage = new UserProfilePage(
|
||||
await inviteContext.newPage(),
|
||||
);
|
||||
await userProfilePage.goto();
|
||||
await userProfilePage.verifyOrganizationId(organizationId);
|
||||
|
||||
// Close the invite context
|
||||
await inviteContext.close();
|
||||
},
|
||||
);
|
||||
|
||||
@@ -7,6 +7,16 @@ export interface AWSProviderData {
|
||||
alias?: string;
|
||||
}
|
||||
|
||||
export interface AWSOrganizationsProviderData {
|
||||
organizationId: string;
|
||||
organizationName?: string;
|
||||
}
|
||||
|
||||
export interface AWSOrganizationsProviderCredential {
|
||||
roleArn: string;
|
||||
stackSetDeployed?: boolean;
|
||||
}
|
||||
|
||||
// AZURE provider data
|
||||
export interface AZUREProviderData {
|
||||
subscriptionId: string;
|
||||
@@ -492,13 +502,9 @@ export class ProvidersPage extends BasePage {
|
||||
this.roleArnInput = page.getByRole("textbox", { name: "Role ARN" });
|
||||
this.externalIdInput = page.getByRole("textbox", { name: "External ID" });
|
||||
|
||||
// Inputs for static credentials
|
||||
this.accessKeyIdInput = page.getByRole("textbox", {
|
||||
name: "Access Key ID",
|
||||
});
|
||||
this.secretAccessKeyInput = page.getByRole("textbox", {
|
||||
name: "Secret Access Key",
|
||||
});
|
||||
// Inputs for static credentials (type="password" fields have no textbox role)
|
||||
this.accessKeyIdInput = page.getByLabel(/Access Key ID/i).first();
|
||||
this.secretAccessKeyInput = page.getByLabel(/Secret Access Key/i).first();
|
||||
|
||||
// Delete button in confirmation modal
|
||||
this.deleteProviderConfirmationButton = page.getByRole("button", {
|
||||
@@ -578,17 +584,17 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
async selectAWSSingleAccountMethod(): Promise<void> {
|
||||
await this.page
|
||||
.getByRole("button", {
|
||||
name: "Add A Single AWS Cloud Account",
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
const singleAccountOption = this.page.getByRole("radio", {
|
||||
name: "Add A Single AWS Cloud Account",
|
||||
exact: true,
|
||||
});
|
||||
await expect(singleAccountOption).toBeVisible({ timeout: 10000 });
|
||||
await singleAccountOption.click();
|
||||
}
|
||||
|
||||
async selectAWSOrganizationsMethod(): Promise<void> {
|
||||
await this.page
|
||||
.getByRole("button", {
|
||||
.getByRole("radio", {
|
||||
name: "Add Multiple Accounts With AWS Organizations",
|
||||
exact: true,
|
||||
})
|
||||
@@ -633,16 +639,8 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
async fillAWSProviderDetails(data: AWSProviderData): Promise<void> {
|
||||
// Fill the AWS provider details
|
||||
const singleAccountButton = this.page.getByRole("button", {
|
||||
name: "Add A Single AWS Cloud Account",
|
||||
exact: true,
|
||||
});
|
||||
|
||||
if (await singleAccountButton.isVisible().catch(() => false)) {
|
||||
await singleAccountButton.click();
|
||||
}
|
||||
|
||||
await this.selectAWSSingleAccountMethod();
|
||||
await expect(this.accountIdInput).toBeVisible({ timeout: 10000 });
|
||||
await this.accountIdInput.fill(data.accountId);
|
||||
|
||||
if (data.alias) {
|
||||
@@ -650,6 +648,43 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
}
|
||||
|
||||
async fillAWSOrganizationsProviderDetails(
|
||||
data: AWSOrganizationsProviderData,
|
||||
): Promise<void> {
|
||||
const organizationIdInput = this.page.getByRole("textbox", {
|
||||
name: "Organization ID",
|
||||
exact: true,
|
||||
});
|
||||
await expect(organizationIdInput).toBeVisible({ timeout: 10000 });
|
||||
await organizationIdInput.fill(data.organizationId.toLowerCase());
|
||||
|
||||
if (data.organizationName) {
|
||||
await this.page
|
||||
.getByRole("textbox", { name: "Name (optional)", exact: true })
|
||||
.fill(data.organizationName);
|
||||
}
|
||||
}
|
||||
|
||||
async fillAWSOrganizationsCredentials(
|
||||
credentials: AWSOrganizationsProviderCredential,
|
||||
): Promise<void> {
|
||||
const roleArnInput = this.page.getByRole("textbox", {
|
||||
name: "Role ARN",
|
||||
exact: true,
|
||||
});
|
||||
await expect(roleArnInput).toBeVisible({ timeout: 10000 });
|
||||
await roleArnInput.fill(credentials.roleArn);
|
||||
|
||||
if (credentials.stackSetDeployed ?? true) {
|
||||
const stackSetCheckbox = this.page.getByRole("checkbox", {
|
||||
name: /The StackSet has been successfully deployed in AWS/i,
|
||||
});
|
||||
if (!(await stackSetCheckbox.isChecked())) {
|
||||
await stackSetCheckbox.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fillAZUREProviderDetails(data: AZUREProviderData): Promise<void> {
|
||||
// Fill the AWS provider details
|
||||
|
||||
@@ -705,22 +740,13 @@ export class ProvidersPage extends BasePage {
|
||||
async clickNext(): Promise<void> {
|
||||
await this.verifyWizardModalOpen();
|
||||
|
||||
const launchScanButton = this.page.getByRole("button", {
|
||||
name: "Launch scan",
|
||||
exact: true,
|
||||
});
|
||||
if (await launchScanButton.isVisible().catch(() => false)) {
|
||||
await launchScanButton.click();
|
||||
await this.handleLaunchScanCompletion();
|
||||
return;
|
||||
}
|
||||
|
||||
const actionNames = [
|
||||
"Go to scans",
|
||||
"Authenticate",
|
||||
"Next",
|
||||
"Save",
|
||||
"Check connection",
|
||||
"Launch scan",
|
||||
] as const;
|
||||
|
||||
for (const actionName of actionNames) {
|
||||
@@ -730,6 +756,9 @@ export class ProvidersPage extends BasePage {
|
||||
});
|
||||
if (await button.isVisible().catch(() => false)) {
|
||||
await button.click();
|
||||
if (actionName === "Launch scan") {
|
||||
await this.handleLaunchScanCompletion();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -740,38 +769,36 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
private async handleLaunchScanCompletion(): Promise<void> {
|
||||
const errorMessage = this.page
|
||||
.locator(
|
||||
"div.border-border-error, div.bg-red-100, p.text-text-error-primary, p.text-danger",
|
||||
)
|
||||
.first();
|
||||
const goToScansButton = this.page.getByRole("button", {
|
||||
name: "Go to scans",
|
||||
exact: true,
|
||||
});
|
||||
const connectionError = this.page.locator(
|
||||
"div.border-border-error p.text-text-error-primary",
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.race([
|
||||
this.page.waitForURL(/\/scans/, { timeout: 30000 }),
|
||||
goToScansButton.waitFor({ state: "visible", timeout: 30000 }),
|
||||
errorMessage.waitFor({ state: "visible", timeout: 30000 }),
|
||||
this.page.waitForURL(/\/scans/, { timeout: 20000 }),
|
||||
goToScansButton.waitFor({ state: "visible", timeout: 20000 }),
|
||||
connectionError.waitFor({ state: "visible", timeout: 20000 }),
|
||||
]);
|
||||
} catch {
|
||||
// Continue and inspect visible state below.
|
||||
}
|
||||
|
||||
const isErrorVisible = await errorMessage.isVisible().catch(() => false);
|
||||
if (isErrorVisible) {
|
||||
const errorText = await errorMessage.textContent();
|
||||
if (await connectionError.isVisible().catch(() => false)) {
|
||||
const errorText = await connectionError.textContent();
|
||||
throw new Error(
|
||||
`Test connection failed with error: ${errorText?.trim() || "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
|
||||
const isGoToScansVisible = await goToScansButton
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
if (isGoToScansVisible) {
|
||||
if (this.page.url().includes("/scans")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await goToScansButton.isVisible().catch(() => false)) {
|
||||
await goToScansButton.click();
|
||||
await this.page.waitForURL(/\/scans/, { timeout: 30000 });
|
||||
}
|
||||
@@ -827,19 +854,48 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
async fillRoleCredentials(credentials: AWSProviderCredential): Promise<void> {
|
||||
// Fill the role credentials form
|
||||
await expect(this.roleArnInput).toBeVisible({ timeout: 10000 });
|
||||
const accessKeyInputInWizard = this.wizardModal.getByPlaceholder(
|
||||
"Enter the AWS Access Key ID",
|
||||
);
|
||||
const secretKeyInputInWizard = this.wizardModal.getByPlaceholder(
|
||||
"Enter the AWS Secret Access Key",
|
||||
);
|
||||
const accessKeyId =
|
||||
credentials.accessKeyId || process.env.E2E_AWS_PROVIDER_ACCESS_KEY;
|
||||
const secretAccessKey =
|
||||
credentials.secretAccessKey || process.env.E2E_AWS_PROVIDER_SECRET_KEY;
|
||||
|
||||
if (credentials.accessKeyId) {
|
||||
await this.accessKeyIdInput.fill(credentials.accessKeyId);
|
||||
const shouldFillStaticKeys = Boolean(
|
||||
accessKeyId || secretAccessKey,
|
||||
);
|
||||
if (shouldFillStaticKeys) {
|
||||
const accessKeyIsVisible = await accessKeyInputInWizard
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
|
||||
// In cloud env the default can be SDK mode, so expose Access/Secret explicitly.
|
||||
if (!accessKeyIsVisible) {
|
||||
await this.selectAuthenticationMethod(
|
||||
AWS_CREDENTIAL_OPTIONS.AWS_ROLE_ARN,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (credentials.secretAccessKey) {
|
||||
await this.secretAccessKeyInput.fill(credentials.secretAccessKey);
|
||||
|
||||
if (accessKeyId) {
|
||||
await expect(accessKeyInputInWizard).toBeVisible({ timeout: 10000 });
|
||||
await accessKeyInputInWizard.fill(accessKeyId);
|
||||
await expect(accessKeyInputInWizard).toHaveValue(accessKeyId);
|
||||
}
|
||||
if (secretAccessKey) {
|
||||
await expect(secretKeyInputInWizard).toBeVisible({ timeout: 10000 });
|
||||
await secretKeyInputInWizard.fill(secretAccessKey);
|
||||
await expect(secretKeyInputInWizard).toHaveValue(secretAccessKey);
|
||||
}
|
||||
if (credentials.roleArn) {
|
||||
await this.roleArnInput.fill(credentials.roleArn);
|
||||
}
|
||||
if (credentials.externalId) {
|
||||
// External ID may be prefilled and disabled; only fill if enabled
|
||||
if (await this.externalIdInput.isEnabled()) {
|
||||
await this.externalIdInput.fill(credentials.externalId);
|
||||
}
|
||||
@@ -849,13 +905,26 @@ export class ProvidersPage extends BasePage {
|
||||
async fillStaticCredentials(
|
||||
credentials: AWSProviderCredential,
|
||||
): Promise<void> {
|
||||
// Fill the static credentials form
|
||||
const accessKeyInputInWizard = this.wizardModal.getByPlaceholder(
|
||||
"Enter the AWS Access Key ID",
|
||||
);
|
||||
const secretKeyInputInWizard = this.wizardModal.getByPlaceholder(
|
||||
"Enter the AWS Secret Access Key",
|
||||
);
|
||||
const accessKeyId =
|
||||
credentials.accessKeyId || process.env.E2E_AWS_PROVIDER_ACCESS_KEY;
|
||||
const secretAccessKey =
|
||||
credentials.secretAccessKey || process.env.E2E_AWS_PROVIDER_SECRET_KEY;
|
||||
|
||||
if (credentials.accessKeyId) {
|
||||
await this.accessKeyIdInput.fill(credentials.accessKeyId);
|
||||
if (accessKeyId) {
|
||||
await expect(accessKeyInputInWizard).toBeVisible({ timeout: 10000 });
|
||||
await accessKeyInputInWizard.fill(accessKeyId);
|
||||
await expect(accessKeyInputInWizard).toHaveValue(accessKeyId);
|
||||
}
|
||||
if (credentials.secretAccessKey) {
|
||||
await this.secretAccessKeyInput.fill(credentials.secretAccessKey);
|
||||
if (secretAccessKey) {
|
||||
await expect(secretKeyInputInWizard).toBeVisible({ timeout: 10000 });
|
||||
await secretKeyInputInWizard.fill(secretAccessKey);
|
||||
await expect(secretKeyInputInWizard).toHaveValue(secretAccessKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1126,11 +1195,22 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
async verifyCredentialsPageLoaded(): Promise<void> {
|
||||
// Verify the credentials page is loaded
|
||||
|
||||
await this.verifyPageHasProwlerTitle();
|
||||
await this.verifyWizardModalOpen();
|
||||
await expect(this.roleCredentialsRadio).toBeVisible();
|
||||
|
||||
const selectorRadio = this.wizardModal.getByRole("radio", {
|
||||
name: /Connect assuming IAM Role/i,
|
||||
});
|
||||
const selectorHint = this.wizardModal.getByText(/Using IAM Role/i);
|
||||
const roleArnInForm = this.wizardModal.getByRole("textbox", {
|
||||
name: "Role ARN",
|
||||
});
|
||||
|
||||
await Promise.race([
|
||||
selectorRadio.waitFor({ state: "visible", timeout: 20000 }),
|
||||
selectorHint.waitFor({ state: "visible", timeout: 20000 }),
|
||||
roleArnInForm.waitFor({ state: "visible", timeout: 20000 }),
|
||||
]);
|
||||
}
|
||||
|
||||
async verifyM365CredentialsPageLoaded(): Promise<void> {
|
||||
@@ -1186,12 +1266,17 @@ export class ProvidersPage extends BasePage {
|
||||
await this.verifyPageHasProwlerTitle();
|
||||
await this.verifyWizardModalOpen();
|
||||
|
||||
// Verify the Launch scan button is visible
|
||||
const launchScanButton = this.page
|
||||
.locator("button")
|
||||
.filter({ hasText: "Launch scan" });
|
||||
// Some providers show "Check connection" before "Launch scan".
|
||||
const launchAction = this.page
|
||||
.getByRole("button", { name: "Launch scan", exact: true })
|
||||
.or(
|
||||
this.page.getByRole("button", {
|
||||
name: "Check connection",
|
||||
exact: true,
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(launchScanButton).toBeVisible();
|
||||
await expect(launchAction).toBeVisible();
|
||||
}
|
||||
|
||||
async verifyLoadProviderPageAfterNewProvider(): Promise<void> {
|
||||
@@ -1235,7 +1320,9 @@ export class ProvidersPage extends BasePage {
|
||||
.click({ force: true });
|
||||
} else if (method === AWS_CREDENTIAL_OPTIONS.AWS_SDK_DEFAULT) {
|
||||
await this.page
|
||||
.getByRole("option", { name: "AWS SDK Default" })
|
||||
.getByRole("option", {
|
||||
name: /AWS SDK Default|Prowler Cloud will assume your IAM role/i,
|
||||
})
|
||||
.click({ force: true });
|
||||
} else {
|
||||
throw new Error(`Invalid authentication method: ${method}`);
|
||||
@@ -1278,9 +1365,7 @@ export class ProvidersPage extends BasePage {
|
||||
}
|
||||
|
||||
async verifyTestConnectionPageLoaded(): Promise<void> {
|
||||
// Verify the test connection page is loaded
|
||||
await this.verifyPageHasProwlerTitle();
|
||||
await this.verifyWizardModalOpen();
|
||||
const testConnectionAction = this.page
|
||||
.getByRole("button", { name: "Launch scan", exact: true })
|
||||
.or(
|
||||
@@ -1289,6 +1374,21 @@ export class ProvidersPage extends BasePage {
|
||||
exact: true,
|
||||
}),
|
||||
);
|
||||
|
||||
// Some update flows return directly to providers list after authenticating.
|
||||
try {
|
||||
await Promise.race([
|
||||
testConnectionAction.waitFor({ state: "visible", timeout: 20000 }),
|
||||
this.providersTable.waitFor({ state: "visible", timeout: 20000 }),
|
||||
]);
|
||||
} catch {
|
||||
// Fall through to explicit assertions below.
|
||||
}
|
||||
|
||||
if (await this.providersTable.isVisible().catch(() => false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await expect(testConnectionAction).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -891,3 +891,60 @@
|
||||
- Requires valid Alibaba Cloud account with RAM Role configured
|
||||
- RAM Role must have sufficient permissions for security scanning
|
||||
- Role ARN must be properly configured and assumable
|
||||
|
||||
---
|
||||
|
||||
## Test Case: `PROVIDER-E2E-016` - Add AWS Organization Using AWS Organizations Flow
|
||||
|
||||
**Priority:** `critical`
|
||||
|
||||
**Tags:**
|
||||
|
||||
- type → @e2e, @serial
|
||||
- feature → @providers
|
||||
- provider → @aws
|
||||
|
||||
**Description/Objective:** Validates the complete flow of adding AWS accounts through AWS Organizations, including organization setup, authentication, account selection, and scan scheduling.
|
||||
|
||||
**Preconditions:**
|
||||
|
||||
- Admin user authentication required (admin.auth.setup setup)
|
||||
- Environment variables configured: E2E_AWS_ORGANIZATION_ID, E2E_AWS_ORGANIZATION_ROLE_ARN
|
||||
- Remove any existing provider with the same Organization ID before starting the test
|
||||
- StackSet must be deployed in AWS Organizations and expose a valid IAM Role ARN for Prowler
|
||||
- This test must be run serially and never in parallel with other tests, as it requires the Organization ID not to be already registered beforehand.
|
||||
|
||||
### Flow Steps:
|
||||
|
||||
1. Navigate to providers page
|
||||
2. Click "Add Provider" button
|
||||
3. Select AWS provider type
|
||||
4. Select "Add Multiple Accounts With AWS Organizations"
|
||||
5. Fill organization details (organization ID and optional name)
|
||||
6. Continue to authentication details and provide role ARN
|
||||
7. Confirm StackSet deployment checkbox and authenticate
|
||||
8. Confirm organization account selection step and continue
|
||||
9. Verify organization launch step, choose single scan schedule, and launch
|
||||
10. Verify redirect to Scans page
|
||||
|
||||
### Expected Result:
|
||||
|
||||
- AWS Organizations flow completes successfully
|
||||
- Accounts are connected and launch step is displayed
|
||||
- Scan scheduling selection is applied
|
||||
- User is redirected to Scans page after launch
|
||||
|
||||
### Key verification points:
|
||||
|
||||
- Connect account page displays AWS option
|
||||
- Organizations method selector is available
|
||||
- Authentication details step loads
|
||||
- Account selection step loads
|
||||
- Accounts connected launch step appears
|
||||
- Successful redirect to Scans page after launching
|
||||
|
||||
### Notes:
|
||||
|
||||
- Organization ID must follow AWS format (e.g., o-abc123def4)
|
||||
- Role ARN must belong to the StackSet deployment for Organizations flow
|
||||
- Provider cleanup is executed before test run to avoid unique constraint conflicts
|
||||
|
||||
@@ -2,6 +2,8 @@ import { test } from "@playwright/test";
|
||||
import {
|
||||
ProvidersPage,
|
||||
AWSProviderData,
|
||||
AWSOrganizationsProviderCredential,
|
||||
AWSOrganizationsProviderData,
|
||||
AWSProviderCredential,
|
||||
AWS_CREDENTIAL_OPTIONS,
|
||||
AZUREProviderData,
|
||||
@@ -36,23 +38,18 @@ test.describe("Add Provider", () => {
|
||||
let providersPage: ProvidersPage;
|
||||
let scansPage: ScansPage;
|
||||
// Test data from environment variables
|
||||
const accountId = process.env.E2E_AWS_PROVIDER_ACCOUNT_ID;
|
||||
const accessKey = process.env.E2E_AWS_PROVIDER_ACCESS_KEY;
|
||||
const secretKey = process.env.E2E_AWS_PROVIDER_SECRET_KEY;
|
||||
const roleArn = process.env.E2E_AWS_PROVIDER_ROLE_ARN;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!accountId) {
|
||||
throw new Error(
|
||||
"E2E_AWS_PROVIDER_ACCOUNT_ID environment variable is not set",
|
||||
);
|
||||
}
|
||||
const accountId = process.env.E2E_AWS_PROVIDER_ACCOUNT_ID ?? "";
|
||||
const accessKey = process.env.E2E_AWS_PROVIDER_ACCESS_KEY ?? "";
|
||||
const secretKey = process.env.E2E_AWS_PROVIDER_SECRET_KEY ?? "";
|
||||
const roleArn = process.env.E2E_AWS_PROVIDER_ROLE_ARN ?? "";
|
||||
const organizationId = process.env.E2E_AWS_ORGANIZATION_ID ?? "";
|
||||
const organizationRoleArn = process.env.E2E_AWS_ORGANIZATION_ROLE_ARN ?? "";
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.skip(!accountId, "E2E_AWS_PROVIDER_ACCOUNT_ID is not set");
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await deleteProviderIfExists(providersPage, accountId);
|
||||
await deleteProviderIfExists(providersPage, accountId!);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
@@ -216,9 +213,8 @@ test.describe("Add Provider", () => {
|
||||
],
|
||||
},
|
||||
async ({ page }) => {
|
||||
|
||||
// Validate required environment variables
|
||||
if (!accountId || !roleArn) {
|
||||
if (!accountId || !roleArn) {
|
||||
throw new Error(
|
||||
"E2E_AWS_PROVIDER_ACCOUNT_ID, and E2E_AWS_PROVIDER_ROLE_ARN environment variables are not set",
|
||||
);
|
||||
@@ -278,6 +274,73 @@ test.describe("Add Provider", () => {
|
||||
await scansPage.verifyScheduledScanStatus(accountId);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"should add multiple AWS accounts using AWS Organizations",
|
||||
{
|
||||
tag: [
|
||||
"@critical",
|
||||
"@e2e",
|
||||
"@providers",
|
||||
"@aws",
|
||||
"@serial",
|
||||
"@PROVIDER-E2E-016",
|
||||
],
|
||||
},
|
||||
async ({ page }) => {
|
||||
if (!organizationId || !organizationRoleArn) {
|
||||
test.skip(
|
||||
true,
|
||||
"E2E_AWS_ORGANIZATION_ID and E2E_AWS_ORGANIZATION_ROLE_ARN environment variables are not set",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const awsOrganizationId = organizationId;
|
||||
const awsOrganizationRoleArn = organizationRoleArn;
|
||||
|
||||
await deleteProviderIfExists(providersPage, awsOrganizationId);
|
||||
|
||||
const awsOrganizationData: AWSOrganizationsProviderData = {
|
||||
organizationId: awsOrganizationId,
|
||||
organizationName: "Test E2E AWS Organization",
|
||||
};
|
||||
const organizationsCredentials: AWSOrganizationsProviderCredential = {
|
||||
roleArn: awsOrganizationRoleArn,
|
||||
stackSetDeployed: true,
|
||||
};
|
||||
|
||||
await providersPage.goto();
|
||||
await providersPage.verifyPageLoaded();
|
||||
|
||||
await providersPage.clickAddProvider();
|
||||
await providersPage.verifyConnectAccountPageLoaded();
|
||||
|
||||
await providersPage.selectAWSProvider();
|
||||
await providersPage.selectAWSOrganizationsMethod();
|
||||
|
||||
await providersPage.fillAWSOrganizationsProviderDetails(
|
||||
awsOrganizationData,
|
||||
);
|
||||
await providersPage.clickNext();
|
||||
|
||||
await providersPage.verifyOrganizationsAuthenticationStepLoaded();
|
||||
await providersPage.fillAWSOrganizationsCredentials(
|
||||
organizationsCredentials,
|
||||
);
|
||||
await providersPage.clickNext();
|
||||
|
||||
await providersPage.verifyOrganizationsAccountSelectionStepLoaded();
|
||||
await providersPage.clickNext();
|
||||
|
||||
await providersPage.verifyOrganizationsLaunchStepLoaded();
|
||||
await providersPage.chooseOrganizationsScanSchedule("single");
|
||||
await providersPage.clickNext();
|
||||
|
||||
scansPage = new ScansPage(page);
|
||||
await scansPage.verifyPageLoaded();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test.describe.serial("Add AZURE Provider", () => {
|
||||
@@ -286,23 +349,19 @@ test.describe("Add Provider", () => {
|
||||
let scansPage: ScansPage;
|
||||
|
||||
// Test data from environment variables
|
||||
const subscriptionId = process.env.E2E_AZURE_SUBSCRIPTION_ID;
|
||||
const clientId = process.env.E2E_AZURE_CLIENT_ID;
|
||||
const clientSecret = process.env.E2E_AZURE_SECRET_ID;
|
||||
const tenantId = process.env.E2E_AZURE_TENANT_ID;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!subscriptionId || !clientId || !clientSecret || !tenantId) {
|
||||
throw new Error(
|
||||
"E2E_AZURE_SUBSCRIPTION_ID, E2E_AZURE_CLIENT_ID, E2E_AZURE_SECRET_ID, and E2E_AZURE_TENANT_ID environment variables are not set",
|
||||
);
|
||||
}
|
||||
const subscriptionId = process.env.E2E_AZURE_SUBSCRIPTION_ID ?? "";
|
||||
const clientId = process.env.E2E_AZURE_CLIENT_ID ?? "";
|
||||
const clientSecret = process.env.E2E_AZURE_SECRET_ID ?? "";
|
||||
const tenantId = process.env.E2E_AZURE_TENANT_ID ?? "";
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.skip(
|
||||
!subscriptionId || !clientId || !clientSecret || !tenantId,
|
||||
"Azure E2E env vars are not set",
|
||||
);
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await deleteProviderIfExists(providersPage, subscriptionId);
|
||||
await deleteProviderIfExists(providersPage, subscriptionId!);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
@@ -374,22 +433,18 @@ test.describe("Add Provider", () => {
|
||||
let scansPage: ScansPage;
|
||||
|
||||
// Test data from environment variables
|
||||
const domainId = process.env.E2E_M365_DOMAIN_ID;
|
||||
const clientId = process.env.E2E_M365_CLIENT_ID;
|
||||
const tenantId = process.env.E2E_M365_TENANT_ID;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!domainId || !clientId || !tenantId) {
|
||||
throw new Error(
|
||||
"E2E_M365_DOMAIN_ID, E2E_M365_CLIENT_ID, and E2E_M365_TENANT_ID environment variables are not set",
|
||||
);
|
||||
}
|
||||
const domainId = process.env.E2E_M365_DOMAIN_ID ?? "";
|
||||
const clientId = process.env.E2E_M365_CLIENT_ID ?? "";
|
||||
const tenantId = process.env.E2E_M365_TENANT_ID ?? "";
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.skip(
|
||||
!domainId || !clientId || !tenantId,
|
||||
"M365 E2E env vars are not set",
|
||||
);
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await deleteProviderIfExists(providersPage, domainId);
|
||||
await deleteProviderIfExists(providersPage, domainId!);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
@@ -409,7 +464,7 @@ test.describe("Add Provider", () => {
|
||||
},
|
||||
async ({ page }) => {
|
||||
// Validate required environment variables
|
||||
const clientSecret = process.env.E2E_M365_SECRET_ID;
|
||||
const clientSecret = process.env.E2E_M365_SECRET_ID ?? "";
|
||||
|
||||
if (!clientSecret) {
|
||||
throw new Error("E2E_M365_SECRET_ID environment variable is not set");
|
||||
@@ -482,7 +537,8 @@ test.describe("Add Provider", () => {
|
||||
},
|
||||
async ({ page }) => {
|
||||
// Validate required environment variables
|
||||
const certificateContent = process.env.E2E_M365_CERTIFICATE_CONTENT;
|
||||
const certificateContent =
|
||||
process.env.E2E_M365_CERTIFICATE_CONTENT ?? "";
|
||||
|
||||
if (!certificateContent) {
|
||||
throw new Error(
|
||||
@@ -551,22 +607,17 @@ test.describe("Add Provider", () => {
|
||||
let scansPage: ScansPage;
|
||||
|
||||
// Test data from environment variables
|
||||
const context = process.env.E2E_KUBERNETES_CONTEXT;
|
||||
const kubeconfigPath = process.env.E2E_KUBERNETES_KUBECONFIG_PATH;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!context || !kubeconfigPath) {
|
||||
throw new Error(
|
||||
"E2E_KUBERNETES_CONTEXT and E2E_KUBERNETES_KUBECONFIG_PATH environment variables are not set",
|
||||
);
|
||||
}
|
||||
|
||||
const context = process.env.E2E_KUBERNETES_CONTEXT ?? "";
|
||||
const kubeconfigPath = process.env.E2E_KUBERNETES_KUBECONFIG_PATH ?? "";
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.skip(
|
||||
!context || !kubeconfigPath,
|
||||
"Kubernetes E2E env vars are not set",
|
||||
);
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await deleteProviderIfExists(providersPage, context);
|
||||
await deleteProviderIfExists(providersPage, context!);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
@@ -656,18 +707,13 @@ test.describe("Add Provider", () => {
|
||||
let scansPage: ScansPage;
|
||||
|
||||
// Test data from environment variables
|
||||
const projectId = process.env.E2E_GCP_PROJECT_ID;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!projectId) {
|
||||
throw new Error("E2E_GCP_PROJECT_ID environment variable is not set");
|
||||
}
|
||||
const projectId = process.env.E2E_GCP_PROJECT_ID ?? "";
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.skip(!projectId, "E2E_GCP_PROJECT_ID is not set");
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await deleteProviderIfExists(providersPage, projectId);
|
||||
await deleteProviderIfExists(providersPage, projectId!);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
@@ -688,7 +734,7 @@ test.describe("Add Provider", () => {
|
||||
async ({ page }) => {
|
||||
// Validate required environment variables
|
||||
const serviceAccountKeyB64 =
|
||||
process.env.E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY;
|
||||
process.env.E2E_GCP_BASE64_SERVICE_ACCOUNT_KEY ?? "";
|
||||
|
||||
// Verify service account key is base64 encoded
|
||||
if (!serviceAccountKeyB64) {
|
||||
@@ -767,19 +813,13 @@ test.describe("Add Provider", () => {
|
||||
let scansPage: ScansPage;
|
||||
|
||||
test.describe("Add GitHub provider with username", () => {
|
||||
// Test data from environment variables
|
||||
const username = process.env.E2E_GITHUB_USERNAME;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!username) {
|
||||
throw new Error("E2E_GITHUB_USERNAME environment variable is not set");
|
||||
}
|
||||
const username = process.env.E2E_GITHUB_USERNAME ?? "";
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.skip(!username, "E2E_GITHUB_USERNAME is not set");
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await deleteProviderIfExists(providersPage, username);
|
||||
await deleteProviderIfExists(providersPage, username!);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
@@ -800,7 +840,7 @@ test.describe("Add Provider", () => {
|
||||
async ({ page }) => {
|
||||
// Validate required environment variables
|
||||
const personalAccessToken =
|
||||
process.env.E2E_GITHUB_PERSONAL_ACCESS_TOKEN;
|
||||
process.env.E2E_GITHUB_PERSONAL_ACCESS_TOKEN ?? "";
|
||||
|
||||
// Verify username and personal access token are set in environment variables
|
||||
if (!personalAccessToken) {
|
||||
@@ -876,10 +916,9 @@ test.describe("Add Provider", () => {
|
||||
},
|
||||
async ({ page }) => {
|
||||
// Validate required environment variables
|
||||
const githubAppId =
|
||||
process.env.E2E_GITHUB_APP_ID;
|
||||
const githubAppId = process.env.E2E_GITHUB_APP_ID ?? "";
|
||||
const githubAppPrivateKeyB64 =
|
||||
process.env.E2E_GITHUB_BASE64_APP_PRIVATE_KEY;
|
||||
process.env.E2E_GITHUB_BASE64_APP_PRIVATE_KEY ?? "";
|
||||
|
||||
// Verify github app id and private key are set in environment variables
|
||||
if (!githubAppId || !githubAppPrivateKeyB64) {
|
||||
@@ -930,9 +969,7 @@ test.describe("Add Provider", () => {
|
||||
await providersPage.verifyGitHubAppPageLoaded();
|
||||
|
||||
// Fill static github app credentials details
|
||||
await providersPage.fillGitHubAppCredentials(
|
||||
githubCredentials,
|
||||
);
|
||||
await providersPage.fillGitHubAppCredentials(githubCredentials);
|
||||
await providersPage.clickNext();
|
||||
|
||||
// Launch scan
|
||||
@@ -949,21 +986,13 @@ test.describe("Add Provider", () => {
|
||||
);
|
||||
});
|
||||
test.describe("Add GitHub provider with organization", () => {
|
||||
// Test data from environment variables
|
||||
const organization = process.env.E2E_GITHUB_ORGANIZATION;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!organization) {
|
||||
throw new Error(
|
||||
"E2E_GITHUB_ORGANIZATION environment variable is not set",
|
||||
);
|
||||
}
|
||||
const organization = process.env.E2E_GITHUB_ORGANIZATION ?? "";
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.skip(!organization, "E2E_GITHUB_ORGANIZATION is not set");
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await deleteProviderIfExists(providersPage, organization);
|
||||
await deleteProviderIfExists(providersPage, organization!);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
@@ -983,7 +1012,7 @@ test.describe("Add Provider", () => {
|
||||
async ({ page }) => {
|
||||
// Validate required environment variables
|
||||
const organizationAccessToken =
|
||||
process.env.E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN;
|
||||
process.env.E2E_GITHUB_ORGANIZATION_ACCESS_TOKEN ?? "";
|
||||
|
||||
// Verify username and personal access token are set in environment variables
|
||||
if (!organizationAccessToken) {
|
||||
@@ -1054,24 +1083,20 @@ test.describe("Add Provider", () => {
|
||||
let scansPage: ScansPage;
|
||||
|
||||
// Test data from environment variables
|
||||
const tenancyId = process.env.E2E_OCI_TENANCY_ID;
|
||||
const userId = process.env.E2E_OCI_USER_ID;
|
||||
const fingerprint = process.env.E2E_OCI_FINGERPRINT;
|
||||
const keyContent = process.env.E2E_OCI_KEY_CONTENT;
|
||||
const region = process.env.E2E_OCI_REGION;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!tenancyId || !userId || !fingerprint || !keyContent || !region) {
|
||||
throw new Error(
|
||||
"E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT, and E2E_OCI_REGION environment variables are not set",
|
||||
);
|
||||
}
|
||||
const tenancyId = process.env.E2E_OCI_TENANCY_ID ?? "";
|
||||
const userId = process.env.E2E_OCI_USER_ID ?? "";
|
||||
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
|
||||
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
|
||||
const region = process.env.E2E_OCI_REGION ?? "";
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.skip(
|
||||
!tenancyId || !userId || !fingerprint || !keyContent || !region,
|
||||
"OCI E2E env vars are not set",
|
||||
);
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await deleteProviderIfExists(providersPage, tenancyId);
|
||||
await deleteProviderIfExists(providersPage, tenancyId!);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
@@ -1148,23 +1173,17 @@ test.describe("Add Provider", () => {
|
||||
let scansPage: ScansPage;
|
||||
|
||||
// Test data from environment variables
|
||||
const accountId = process.env.E2E_ALIBABACLOUD_ACCOUNT_ID;
|
||||
const accessKeyId = process.env.E2E_ALIBABACLOUD_ACCESS_KEY_ID;
|
||||
const accessKeySecret = process.env.E2E_ALIBABACLOUD_ACCESS_KEY_SECRET;
|
||||
const roleArn = process.env.E2E_ALIBABACLOUD_ROLE_ARN;
|
||||
|
||||
// Validate required environment variable for beforeEach
|
||||
if (!accountId) {
|
||||
throw new Error(
|
||||
"E2E_ALIBABACLOUD_ACCOUNT_ID environment variable is not set",
|
||||
);
|
||||
}
|
||||
const accountId = process.env.E2E_ALIBABACLOUD_ACCOUNT_ID ?? "";
|
||||
const accessKeyId = process.env.E2E_ALIBABACLOUD_ACCESS_KEY_ID ?? "";
|
||||
const accessKeySecret =
|
||||
process.env.E2E_ALIBABACLOUD_ACCESS_KEY_SECRET ?? "";
|
||||
const roleArn = process.env.E2E_ALIBABACLOUD_ROLE_ARN ?? "";
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.skip(!accountId, "E2E_ALIBABACLOUD_ACCOUNT_ID is not set");
|
||||
providersPage = new ProvidersPage(page);
|
||||
// Clean up existing provider to ensure clean test state
|
||||
await deleteProviderIfExists(providersPage, accountId);
|
||||
await deleteProviderIfExists(providersPage, accountId!);
|
||||
});
|
||||
|
||||
// Use admin authentication for provider management
|
||||
@@ -1232,7 +1251,9 @@ test.describe("Add Provider", () => {
|
||||
await providersPage.verifyAlibabaCloudStaticCredentialsPageLoaded();
|
||||
|
||||
// Fill static credentials
|
||||
await providersPage.fillAlibabaCloudStaticCredentials(staticCredentials);
|
||||
await providersPage.fillAlibabaCloudStaticCredentials(
|
||||
staticCredentials,
|
||||
);
|
||||
await providersPage.clickNext();
|
||||
|
||||
// Launch scan
|
||||
@@ -1334,21 +1355,18 @@ test.describe("Update Provider Credentials", () => {
|
||||
let providersPage: ProvidersPage;
|
||||
|
||||
// Test data from environment variables (same as add OCI provider test)
|
||||
const tenancyId = process.env.E2E_OCI_TENANCY_ID;
|
||||
const userId = process.env.E2E_OCI_USER_ID;
|
||||
const fingerprint = process.env.E2E_OCI_FINGERPRINT;
|
||||
const keyContent = process.env.E2E_OCI_KEY_CONTENT;
|
||||
const region = process.env.E2E_OCI_REGION;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!tenancyId || !userId || !fingerprint || !keyContent || !region) {
|
||||
throw new Error(
|
||||
"E2E_OCI_TENANCY_ID, E2E_OCI_USER_ID, E2E_OCI_FINGERPRINT, E2E_OCI_KEY_CONTENT, and E2E_OCI_REGION environment variables are not set",
|
||||
);
|
||||
}
|
||||
const tenancyId = process.env.E2E_OCI_TENANCY_ID ?? "";
|
||||
const userId = process.env.E2E_OCI_USER_ID ?? "";
|
||||
const fingerprint = process.env.E2E_OCI_FINGERPRINT ?? "";
|
||||
const keyContent = process.env.E2E_OCI_KEY_CONTENT ?? "";
|
||||
const region = process.env.E2E_OCI_REGION ?? "";
|
||||
|
||||
// Setup before each test
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.skip(
|
||||
!tenancyId || !userId || !fingerprint || !keyContent || !region,
|
||||
"OCI E2E env vars are not set",
|
||||
);
|
||||
providersPage = new ProvidersPage(page);
|
||||
});
|
||||
|
||||
@@ -1358,13 +1376,7 @@ test.describe("Update Provider Credentials", () => {
|
||||
test(
|
||||
"should update OCI provider credentials successfully",
|
||||
{
|
||||
tag: [
|
||||
"@e2e",
|
||||
"@providers",
|
||||
"@oci",
|
||||
"@serial",
|
||||
"@PROVIDER-E2E-013",
|
||||
],
|
||||
tag: ["@e2e", "@providers", "@oci", "@serial", "@PROVIDER-E2E-013"],
|
||||
},
|
||||
async () => {
|
||||
// Prepare updated credentials
|
||||
|
||||
@@ -133,9 +133,7 @@ export class SignUpPage extends BasePage {
|
||||
}
|
||||
|
||||
async verifyRedirectToLogin(): Promise<void> {
|
||||
// Verify redirect to login page
|
||||
|
||||
await expect(this.page).toHaveURL("/sign-in");
|
||||
await expect(this.page).toHaveURL(/\/sign-in/);
|
||||
}
|
||||
|
||||
async verifyRedirectToEmailVerification(): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
|
||||
import { addCredentialsRoleFormSchema } from "./formSchemas";
|
||||
|
||||
const BASE_AWS_ROLE_VALUES = {
|
||||
[ProviderCredentialFields.PROVIDER_ID]: "provider-123",
|
||||
[ProviderCredentialFields.PROVIDER_TYPE]: "aws",
|
||||
[ProviderCredentialFields.ROLE_ARN]:
|
||||
"arn:aws:iam::123456789012:role/ProwlerRole",
|
||||
[ProviderCredentialFields.EXTERNAL_ID]: "tenant-123",
|
||||
[ProviderCredentialFields.CREDENTIALS_TYPE]: "access-secret-key",
|
||||
} as const;
|
||||
|
||||
describe("addCredentialsRoleFormSchema", () => {
|
||||
it("accepts AWS role credentials when access and secret keys are present", () => {
|
||||
const schema = addCredentialsRoleFormSchema("aws");
|
||||
|
||||
const result = schema.safeParse({
|
||||
...BASE_AWS_ROLE_VALUES,
|
||||
[ProviderCredentialFields.AWS_ACCESS_KEY_ID]: "AKIA1234567890EXAMPLE",
|
||||
[ProviderCredentialFields.AWS_SECRET_ACCESS_KEY]:
|
||||
"test/secret+access=key1234567890",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("reports missing AWS secret access key on aws_secret_access_key field", () => {
|
||||
const schema = addCredentialsRoleFormSchema("aws");
|
||||
|
||||
const result = schema.safeParse({
|
||||
...BASE_AWS_ROLE_VALUES,
|
||||
[ProviderCredentialFields.AWS_ACCESS_KEY_ID]: "AKIA1234567890EXAMPLE",
|
||||
[ProviderCredentialFields.AWS_SECRET_ACCESS_KEY]: "",
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
|
||||
expect(result.error.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: [ProviderCredentialFields.AWS_SECRET_ACCESS_KEY],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
+30
-10
@@ -420,17 +420,37 @@ export const addCredentialsRoleFormSchema = (providerType: string) =>
|
||||
[ProviderCredentialFields.ROLE_SESSION_NAME]: z.string().optional(),
|
||||
[ProviderCredentialFields.CREDENTIALS_TYPE]: z.string().optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) =>
|
||||
.superRefine((data, ctx) => {
|
||||
if (
|
||||
data[ProviderCredentialFields.CREDENTIALS_TYPE] !==
|
||||
"access-secret-key" ||
|
||||
(data[ProviderCredentialFields.AWS_ACCESS_KEY_ID] &&
|
||||
data[ProviderCredentialFields.AWS_SECRET_ACCESS_KEY]),
|
||||
{
|
||||
message: "AWS Access Key ID and Secret Access Key are required.",
|
||||
path: [ProviderCredentialFields.AWS_ACCESS_KEY_ID],
|
||||
},
|
||||
)
|
||||
"access-secret-key"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasAccessKey =
|
||||
(data[ProviderCredentialFields.AWS_ACCESS_KEY_ID] || "").trim()
|
||||
.length > 0;
|
||||
const hasSecretAccessKey =
|
||||
(data[ProviderCredentialFields.AWS_SECRET_ACCESS_KEY] || "").trim()
|
||||
.length > 0;
|
||||
|
||||
if (!hasAccessKey) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "AWS Access Key ID is required.",
|
||||
path: [ProviderCredentialFields.AWS_ACCESS_KEY_ID],
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasSecretAccessKey) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "AWS Secret Access Key is required.",
|
||||
path: [ProviderCredentialFields.AWS_SECRET_ACCESS_KEY],
|
||||
});
|
||||
}
|
||||
})
|
||||
: providerType === "alibabacloud"
|
||||
? z.object({
|
||||
[ProviderCredentialFields.PROVIDER_ID]: z.string(),
|
||||
|
||||
Reference in New Issue
Block a user