fix(ui): AWS form selector default values (#8553)

This commit is contained in:
Alejandro Bailo
2025-08-25 12:30:02 +02:00
committed by GitHub
parent 88f38b2d2a
commit d457166a0c
6 changed files with 105 additions and 23 deletions
+1
View File
@@ -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;
@@ -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]
}
+5 -1
View File
@@ -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
View File
@@ -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();
});
});
+23 -7
View File
@@ -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"],
});
}