mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 21:11:53 +00:00
fix(ui): AWS form selector default values (#8553)
This commit is contained in:
@@ -18,6 +18,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Default value inside credentials form in AWS Provider add workflow properly set [(#8553)](https://github.com/prowler-cloud/prowler/pull/8553)
|
||||
- Auth callback route checking working as expected [(#8556)](https://github.com/prowler-cloud/prowler/pull/8556)
|
||||
- DataTable column headers set to single-line [(#8480)](https://github.com/prowler-cloud/prowler/pull/8480)
|
||||
|
||||
|
||||
@@ -49,6 +49,11 @@ export const S3IntegrationForm = ({
|
||||
const isEditingConfig = editMode === "configuration";
|
||||
const isEditingCredentials = editMode === "credentials";
|
||||
|
||||
const defaultCredentialsType =
|
||||
process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true"
|
||||
? "aws-sdk-default"
|
||||
: "access-secret-key";
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(
|
||||
// For credentials editing, use creation schema (all fields required)
|
||||
@@ -66,7 +71,7 @@ export const S3IntegrationForm = ({
|
||||
providers:
|
||||
integration?.relationships?.providers?.data?.map((p) => p.id) || [],
|
||||
enabled: integration?.attributes.enabled ?? true,
|
||||
credentials_type: "access-secret-key" as const,
|
||||
credentials_type: defaultCredentialsType,
|
||||
aws_access_key_id: "",
|
||||
aws_secret_access_key: "",
|
||||
aws_session_token: "",
|
||||
@@ -81,6 +86,7 @@ export const S3IntegrationForm = ({
|
||||
"",
|
||||
role_session_name: "",
|
||||
session_duration: "",
|
||||
show_role_section: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -116,6 +122,9 @@ export const S3IntegrationForm = ({
|
||||
const buildCredentials = (values: any) => {
|
||||
const credentials: any = {};
|
||||
|
||||
// Don't include credentials_type in the API payload - it's a UI-only field
|
||||
// The backend determines credential type based on which fields are present
|
||||
|
||||
// Only include role-related fields if role_arn is provided
|
||||
if (values.role_arn && values.role_arn.trim() !== "") {
|
||||
credentials.role_arn = values.role_arn;
|
||||
|
||||
+24
-3
@@ -1,5 +1,5 @@
|
||||
import { Chip, Divider, Select, SelectItem, Switch } from "@nextui-org/react";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Control, UseFormSetValue, useWatch } from "react-hook-form";
|
||||
|
||||
import { CredentialsRoleHelper } from "@/components/providers/workflow";
|
||||
@@ -28,17 +28,35 @@ export const AWSRoleCredentialsForm = ({
|
||||
const defaultCredentialsType = isCloudEnv
|
||||
? "aws-sdk-default"
|
||||
: "access-secret-key";
|
||||
|
||||
const credentialsType = useWatch({
|
||||
control,
|
||||
name: ProviderCredentialFields.CREDENTIALS_TYPE,
|
||||
defaultValue: defaultCredentialsType,
|
||||
});
|
||||
|
||||
const [showOptionalRole, setShowOptionalRole] = useState(false);
|
||||
|
||||
const showRoleSection =
|
||||
type === "providers" ||
|
||||
(isCloudEnv && credentialsType === "aws-sdk-default") ||
|
||||
showOptionalRole;
|
||||
|
||||
// Track role section visibility and ensure external_id is set
|
||||
useEffect(() => {
|
||||
// Set show_role_section for validation
|
||||
setValue("show_role_section" as any, showRoleSection);
|
||||
|
||||
// When role section is shown, ensure external_id is set
|
||||
// This handles both initial mount and when the section becomes visible
|
||||
if (showRoleSection && externalId) {
|
||||
setValue(ProviderCredentialFields.EXTERNAL_ID, externalId, {
|
||||
shouldValidate: false,
|
||||
shouldDirty: false,
|
||||
});
|
||||
}
|
||||
}, [showRoleSection, setValue, externalId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
@@ -57,7 +75,7 @@ export const AWSRoleCredentialsForm = ({
|
||||
name={ProviderCredentialFields.CREDENTIALS_TYPE}
|
||||
label="Authentication Method"
|
||||
placeholder="Select credentials type"
|
||||
defaultSelectedKeys={[defaultCredentialsType]}
|
||||
selectedKeys={[credentialsType || defaultCredentialsType]}
|
||||
className="mb-4"
|
||||
variant="bordered"
|
||||
onSelectionChange={(keys) =>
|
||||
@@ -124,6 +142,9 @@ export const AWSRoleCredentialsForm = ({
|
||||
isInvalid={
|
||||
!!control._formState.errors[
|
||||
ProviderCredentialFields.AWS_SECRET_ACCESS_KEY
|
||||
] ||
|
||||
!!control._formState.errors[
|
||||
ProviderCredentialFields.AWS_ACCESS_KEY_ID
|
||||
]
|
||||
}
|
||||
/>
|
||||
@@ -182,7 +203,7 @@ export const AWSRoleCredentialsForm = ({
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Role ARN"
|
||||
variant="bordered"
|
||||
isRequired={type === "providers"}
|
||||
isRequired={showRoleSection}
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.ROLE_ARN]
|
||||
}
|
||||
|
||||
@@ -64,9 +64,13 @@ export const useCredentialsForm = ({
|
||||
|
||||
// AWS Role credentials
|
||||
if (providerType === "aws" && via === "role") {
|
||||
const isCloudEnv = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true";
|
||||
const defaultCredentialsType = isCloudEnv
|
||||
? "aws-sdk-default"
|
||||
: "access-secret-key";
|
||||
return {
|
||||
...baseDefaults,
|
||||
[ProviderCredentialFields.CREDENTIALS_TYPE]: "access-secret-key",
|
||||
[ProviderCredentialFields.CREDENTIALS_TYPE]: defaultCredentialsType,
|
||||
[ProviderCredentialFields.ROLE_ARN]: "",
|
||||
[ProviderCredentialFields.EXTERNAL_ID]: session?.tenantId || "",
|
||||
[ProviderCredentialFields.AWS_ACCESS_KEY_ID]: "",
|
||||
|
||||
+42
-11
@@ -134,25 +134,56 @@ test.describe("Session Persistence", () => {
|
||||
await goToLogin(page);
|
||||
await login(page, TEST_CREDENTIALS.VALID);
|
||||
await verifySuccessfulLogin(page);
|
||||
|
||||
// Logout
|
||||
await logout(page);
|
||||
await verifyLogoutSuccess(page);
|
||||
|
||||
// Verify cannot access protected route after logout
|
||||
await page.goto(URLS.DASHBOARD);
|
||||
await expect(page).toHaveURL(URLS.LOGIN);
|
||||
});
|
||||
|
||||
test("should handle session timeout gracefully", async ({ page }) => {
|
||||
// Login first
|
||||
await goToLogin(page);
|
||||
await login(page, TEST_CREDENTIALS.VALID);
|
||||
await verifySuccessfulLogin(page);
|
||||
// Simulate session timeout by clearing cookies
|
||||
await page.context().clearCookies();
|
||||
// Try to navigate to a protected route
|
||||
await page.goto(URLS.PROFILE);
|
||||
// Should be redirected to login
|
||||
await expect(page).toHaveURL(URLS.LOGIN);
|
||||
test("should handle session timeout gracefully", async ({ browser }) => {
|
||||
// Test approach: Verify that a new browser context without auth cookies
|
||||
// gets redirected to login when accessing protected routes
|
||||
|
||||
// First, login in one context to verify auth works
|
||||
const authContext = await browser.newContext();
|
||||
const authPage = await authContext.newPage();
|
||||
|
||||
await goToLogin(authPage);
|
||||
await login(authPage, TEST_CREDENTIALS.VALID);
|
||||
await verifySuccessfulLogin(authPage);
|
||||
|
||||
// Verify session exists in authenticated context
|
||||
const authResponse = await authPage.request.get("/api/auth/session");
|
||||
const authSession = await authResponse.json();
|
||||
expect(authSession).toBeTruthy();
|
||||
expect(authSession.user).toBeTruthy();
|
||||
|
||||
// Now create a completely separate context without any auth
|
||||
const unauthContext = await browser.newContext();
|
||||
const unauthPage = await unauthContext.newPage();
|
||||
|
||||
// Try to access protected route in unauthenticated context
|
||||
await unauthPage.goto(URLS.PROFILE, {
|
||||
waitUntil: "networkidle",
|
||||
});
|
||||
|
||||
// Should be redirected to login since this context has no auth
|
||||
await expect(unauthPage).toHaveURL(URLS.LOGIN);
|
||||
|
||||
// Verify session is null in unauthenticated context
|
||||
const unauthResponse = await unauthPage.request.get("/api/auth/session");
|
||||
const unauthSessionText = await unauthResponse.text();
|
||||
expect(unauthSessionText).toBe("null");
|
||||
|
||||
// Clean up
|
||||
await authPage.close();
|
||||
await authContext.close();
|
||||
await unauthPage.close();
|
||||
await unauthContext.close();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ const baseS3IntegrationSchema = z.object({
|
||||
external_id: z.string().optional(),
|
||||
role_session_name: z.string().optional(),
|
||||
session_duration: z.string().optional(),
|
||||
// Hidden field to track if role section is shown
|
||||
show_role_section: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const s3IntegrationValidation = (data: any, ctx: z.RefinementCtx) => {
|
||||
@@ -71,12 +73,19 @@ const s3IntegrationValidation = (data: any, ctx: z.RefinementCtx) => {
|
||||
}
|
||||
}
|
||||
|
||||
// If role_arn is provided, external_id is required
|
||||
if (data.role_arn && data.role_arn.trim() !== "") {
|
||||
// When role section is shown, both role_arn and external_id are required
|
||||
if (data.show_role_section === true) {
|
||||
if (!data.role_arn || data.role_arn.trim() === "") {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Role ARN is required",
|
||||
path: ["role_arn"],
|
||||
});
|
||||
}
|
||||
if (!data.external_id || data.external_id.trim() === "") {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "External ID is required when using Role ARN",
|
||||
message: "External ID is required",
|
||||
path: ["external_id"],
|
||||
});
|
||||
}
|
||||
@@ -108,12 +117,19 @@ const s3IntegrationEditValidation = (data: any, ctx: z.RefinementCtx) => {
|
||||
}
|
||||
}
|
||||
|
||||
// If role_arn is provided, external_id is required
|
||||
if (data.role_arn && data.role_arn.trim() !== "") {
|
||||
if (!data.external_id || data.external_id.trim() === "") {
|
||||
// When role section is shown (editing credentials with role), both fields are required
|
||||
if (data.show_role_section === true) {
|
||||
if (data.role_arn && data.role_arn.trim() === "") {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "External ID is required when using Role ARN",
|
||||
message: "Role ARN is required",
|
||||
path: ["role_arn"],
|
||||
});
|
||||
}
|
||||
if (data.external_id && data.external_id.trim() === "") {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "External ID is required",
|
||||
path: ["external_id"],
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user