Enable @typescript-eslint/no-unnecessary-condition ESLint rule (#48218)

Enable the rule (previously "off") and fix all violations across the JS
workspace. The core change makes RealmContext.realmRepresentation
non-optional — guarded by the existing KeycloakSpinner in the provider —
allowing ~30 downstream consumers to drop redundant optional chains.

Where TypeScript types declare a value as non-nullable but runtime
behaviour can still produce undefined/null (API responses, react-hook-form
dynamic values, route params present on one route but not another, array
index access, DOM queries), the existing guards are preserved with
eslint-disable-next-line comments explaining the rationale.

Additional fixes surfaced during this work:
- PermissionConfigurationDetails: fix spinner blocking the "create
  permission" form by distinguishing "loading" (null) from "new" ({})
- ResourceType: restore resourceIds?.some() guard — form.getValues
  returns undefined when the field has no default value
- clients.ts createOrUpdatePolicy: narrow catch to 404 NetworkError
  only instead of swallowing all errors

Closes #17770

Signed-off-by: Pierluigi Lenoci <pierluigilenoci@gmail.com>
This commit is contained in:
Pierluigi Lenoci
2026-05-12 09:42:52 +02:00
committed by GitHub
parent 77bf9a1053
commit d896684126
102 changed files with 309 additions and 334 deletions
@@ -40,10 +40,10 @@ export interface CredentialMetadataRepresentationMessage {
}
export interface CredentialMetadataRepresentation {
infoMessage: CredentialMetadataRepresentationMessage;
infoProperties: CredentialMetadataRepresentationMessage[];
warningMessageTitle: CredentialMetadataRepresentationMessage;
warningMessageDescription: CredentialMetadataRepresentationMessage;
infoMessage?: CredentialMetadataRepresentationMessage;
infoProperties?: CredentialMetadataRepresentationMessage[];
warningMessageTitle?: CredentialMetadataRepresentationMessage;
warningMessageDescription?: CredentialMetadataRepresentationMessage;
credential: CredentialRepresentation;
iconLight?: string;
iconDark?: string;
@@ -17,7 +17,7 @@ export const Organizations = () => {
const { t } = useTranslation();
const context = useEnvironment<AccountEnvironment>();
const [userOrgs, setUserOrgs] = useState<OrganizationRepresentation[]>([]);
const [userOrgs, setUserOrgs] = useState<OrganizationRepresentation[]>();
usePromise(
(signal) => getUserOrganizations({ signal, context }),
@@ -95,8 +95,8 @@ export const PersonalInfo = () => {
}
const allFieldsReadOnly = () =>
userProfileMetadata?.attributes
?.map((a) => a.readOnly)
userProfileMetadata.attributes
.map((a) => a.readOnly)
.reduce((p, c) => p && c, true);
const {
+3 -3
View File
@@ -115,7 +115,7 @@ export const PageNav = () => {
style={{ wordWrap: "break-word" }}
>
<span data-testid="currentRealm">
{resolveDisplayName(t, realmRepresentation?.displayName, realm)}
{resolveDisplayName(t, realmRepresentation.displayName, realm)}
</span>{" "}
<Label color="blue">{t("currentRealm")}</Label>
</h2>
@@ -127,7 +127,7 @@ export const PageNav = () => {
{showManage && (
<NavGroup aria-label={t("manage")} title={t("manage")}>
{isFeatureEnabled(Feature.Organizations) &&
realmRepresentation?.organizationsEnabled && (
realmRepresentation.organizationsEnabled && (
<LeftNav title="organizations" path="/organizations" />
)}
<LeftNav title="clients" path="/clients" />
@@ -145,7 +145,7 @@ export const PageNav = () => {
<LeftNav title="realmSettings" path="/realm-settings" />
<LeftNav title="authentication" path="/authentication" />
{isFeatureEnabled(Feature.AdminFineGrainedAuthzV2) &&
realmRepresentation?.adminPermissionsEnabled && (
realmRepresentation.adminPermissionsEnabled && (
<LeftNav title="permissions" path="/permissions" />
)}
<LeftNav title="identityProviders" path="/identity-providers" />
@@ -1,7 +1,6 @@
import { fetchWithError } from "@keycloak/keycloak-admin-client";
import {
KeycloakDataTable,
KeycloakSpinner,
ListEmptyState,
useAlerts,
} from "@keycloak/keycloak-ui-shared";
@@ -67,7 +66,7 @@ const AliasRenderer = ({ id, alias, usedBy, builtIn }: AuthenticationType) => {
export default function AuthenticationSection() {
const { adminClient } = useAdminClient();
const { t } = useTranslation();
const { realm: realmName, realmRepresentation: realm } = useRealm();
const { realm: realmName } = useRealm();
const [key, setKey] = useState(0);
const refresh = () => setKey(key + 1);
const { addAlert, addError } = useAlerts();
@@ -128,8 +127,6 @@ export default function AuthenticationSection() {
},
});
if (!realm) return <KeycloakSpinner />;
return (
<>
<DeleteConfirm />
@@ -1,7 +1,6 @@
import { Tab, Tabs, TabTitleText } from "@patternfly/react-core";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { KeycloakSpinner } from "@keycloak/keycloak-ui-shared";
import { useRealm } from "../../context/realm-context/RealmContext";
import { CibaPolicy } from "./CibaPolicy";
import { OtpPolicy } from "./OtpPolicy";
@@ -13,10 +12,6 @@ export const Policies = () => {
const [subTab, setSubTab] = useState(1);
const { realmRepresentation: realm, refresh } = useRealm();
if (!realm) {
return <KeycloakSpinner />;
}
return (
<Tabs
activeKey={subTab}
@@ -202,7 +202,7 @@ export const WebauthnPolicy = ({
labelPrefix="attestationPreference"
validate={(value) => {
const hasValidAAGUIDs = acceptableAAGUIDs.some(
(guid: string) => guid?.trim().length > 0,
(guid: string) => guid.trim().length > 0,
);
if (
@@ -288,7 +288,7 @@ export default function EditClientScope() {
save={assignRoles}
/>
</Tab>
{realmRepresentation?.adminEventsEnabled &&
{realmRepresentation.adminEventsEnabled &&
hasAccess("view-events") && (
<Tab
data-testid="admin-events-tab"
@@ -83,7 +83,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
const isDynamicScopesEnabled = isFeatureEnabled(Feature.DynamicScopes);
// Get available hash algorithms from server info
const hashAlgorithms = serverInfo?.providers?.hash?.providers
const hashAlgorithms = serverInfo.providers?.hash.providers
? Object.keys(serverInfo.providers.hash.providers).map((alg) =>
alg.toLowerCase(),
)
@@ -91,7 +91,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
// Get available asymmetric signature algorithms from server info
const asymmetricAlgorithms = useMemo(
() => serverInfo?.cryptoInfo?.clientSignatureAsymmetricAlgorithms ?? [],
() => serverInfo.cryptoInfo?.clientSignatureAsymmetricAlgorithms ?? [],
[serverInfo],
);
@@ -120,7 +120,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
// Filter only active keys suitable for signing credentials AND using asymmetric algorithms
const keyOptions = useMemo(() => {
const options = [{ key: "", value: t("useDefaultKey") }];
if (realmKeys && realmKeys.length > 0) {
if (realmKeys.length > 0) {
const keyOptions = realmKeys
.filter(
(key) =>
@@ -178,7 +178,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
const isOid4vcProtocol = selectedProtocol === OID4VC_PROTOCOL;
const isOid4vcEnabled =
isFeatureEnabled(Feature.OpenId4VCI) &&
realmRepresentation?.verifiableCredentialsEnabled;
realmRepresentation.verifiableCredentialsEnabled;
const isNotSaml = selectedProtocol != "saml";
const recommendedTokenJwsType =
selectedFormat === VC_FORMAT_SD_JWT
@@ -456,7 +456,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
</HelperText>
</FormHelperText>
)}
{realmKeys && realmKeys.length > 0 && (
{realmKeys.length > 0 && (
<SelectControl
id="kc-signing-key-id"
name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
@@ -44,7 +44,7 @@ export const removeEmptyOid4vcAttributes = (
for (const fieldName of fieldNames) {
const attrKey = fieldName.replace(/^attributes\./, "");
if (isEmptyValue(cleanedAttributes?.[attrKey])) {
if (isEmptyValue(cleanedAttributes[attrKey])) {
delete cleanedAttributes[attrKey];
}
}
+1 -1
View File
@@ -211,7 +211,7 @@ export const AdvancedTab = ({ save, client }: AdvancedProps) => {
isHidden:
(protocol !== PROTOCOL_OIDC && protocol !== PROTOCOL_OID4VC) ||
!isFeatureEnabled(Feature.OpenId4VCI) ||
!realmRepresentation?.verifiableCredentialsEnabled,
!realmRepresentation.verifiableCredentialsEnabled,
panel: (
<>
<Text className="pf-v5-u-pb-lg">
@@ -12,7 +12,7 @@ export const GeneralSettings = () => {
const { realmRepresentation } = useRealm();
const providers = useLoginProviders();
const filteredProviders = realmRepresentation?.verifiableCredentialsEnabled
const filteredProviders = realmRepresentation.verifiableCredentialsEnabled
? providers
: providers.filter((provider) => provider !== PROTOCOL_OID4VC);
@@ -32,7 +32,7 @@ const NewClientFooter = (newClientForm: any) => {
if (!(await trigger())) {
return;
}
onNext?.();
onNext();
};
return (
@@ -41,7 +41,7 @@ export const AdvancedSettings = ({
const { control, watch, register } = useFormContext();
const acrUriMapRealm = realm?.attributes?.["acr.uri.map"]
const acrUriMapRealm = realm.attributes?.["acr.uri.map"]
? Object.values(JSON.parse(realm.attributes["acr.uri.map"]))
: [];
@@ -151,7 +151,7 @@ export const AdvancedSettings = ({
name={convertAttributeNameToForm(
"attributes.access.token.lifespan",
)}
defaultValue={realm?.accessTokenLifespan}
defaultValue={realm.accessTokenLifespan}
units={["minute", "day", "hour"]}
/>
<TokenLifespan
@@ -159,7 +159,7 @@ export const AdvancedSettings = ({
name={convertAttributeNameToForm(
"attributes.client.session.idle.timeout",
)}
defaultValue={realm?.clientSessionIdleTimeout}
defaultValue={realm.clientSessionIdleTimeout}
units={["minute", "day", "hour"]}
/>
<TokenLifespan
@@ -167,7 +167,7 @@ export const AdvancedSettings = ({
name={convertAttributeNameToForm(
"attributes.client.session.max.lifespan",
)}
defaultValue={realm?.clientSessionMaxLifespan}
defaultValue={realm.clientSessionMaxLifespan}
units={["minute", "day", "hour"]}
/>
<TokenLifespan
@@ -175,21 +175,17 @@ export const AdvancedSettings = ({
name={convertAttributeNameToForm(
"attributes.client.offline.session.idle.timeout",
)}
defaultValue={realm?.offlineSessionIdleTimeout}
defaultValue={realm.offlineSessionIdleTimeout}
units={["minute", "day", "hour"]}
/>
{realm?.offlineSessionMaxLifespanEnabled && (
{realm.offlineSessionMaxLifespanEnabled && (
<TokenLifespan
id="clientOfflineSessionMax"
name={convertAttributeNameToForm(
"attributes.client.offline.session.max.lifespan",
)}
defaultValue={
realm?.offlineSessionMaxLifespanEnabled
? realm.offlineSessionMaxLifespan
: undefined
}
defaultValue={realm.offlineSessionMaxLifespan}
units={["minute", "day", "hour"]}
/>
)}
@@ -128,7 +128,7 @@ export const ClusteringPanel = ({
ariaLabelKey="registeredClusterNodes"
loader={() =>
Promise.resolve<Node[]>(
Object.entries(nodes || {}).map((entry) => {
Object.entries(nodes).map((entry) => {
return { host: entry[0], registration: entry[1] };
}),
)
@@ -71,7 +71,7 @@ export const AuthorizationEvaluateResourcePolicies = ({
<Td>
{outerPolicy.status === DecisionEffect.Deny &&
resource.policies?.[rowIndex]?.scopes?.length
? resource.policies[rowIndex].scopes?.join(", ")
? resource.policies[rowIndex].scopes.join(", ")
: "-"}
</Td>
</Tr>
@@ -219,12 +219,12 @@ export const ResourcesPolicySelect = ({
) => {
return (
<ChipGroup>
{selected?.map((item) => (
{selected.map((item) => (
<Chip
key={item.id}
onClick={() => {
field.onChange(field.value?.filter((id) => id !== item.id) || []);
setSelected(selected?.filter((p) => p.id !== item.id) || []);
setSelected(selected.filter((p) => p.id !== item.id));
}}
>
{!isAdminPermissionsClient ? (
@@ -44,9 +44,6 @@ export default function ScopeDetails() {
id,
scopeId,
});
if (!scope) {
throw new Error(t("notFound"));
}
return scope;
}
},
@@ -33,8 +33,8 @@ export const ClientScope = () => {
useFetch(
() => adminClient.clientScopes.find(),
(scopes = []) => {
const clientScopes = getValues("clientScopes") || [];
(scopes) => {
const clientScopes = getValues("clientScopes");
setSelectedScopes(
clientScopes.map((s) => scopes.find((c) => c.id === s.id)!),
);
@@ -82,11 +82,13 @@ export default function PolicyDetails() {
if (policyId) {
const result = await Promise.all([
adminClient.clients.findOnePolicyWithType({
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- permissionClientId is undefined when navigating from client authorization tab
id: permissionClientId ?? id,
type: policyType!,
policyId,
}) as PolicyRepresentation | undefined,
adminClient.clients.getAssociatedPolicies({
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- permissionClientId is undefined when navigating from client authorization tab
id: permissionClientId ?? id,
permissionId: policyId,
}),
@@ -103,6 +105,7 @@ export default function PolicyDetails() {
}
if (!isValidComponentType(policyType!)) {
const providers = await adminClient.clients.listPolicyProviders({
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- permissionClientId is undefined when navigating from client authorization tab
id: permissionClientId ?? id,
});
const provider = providers.find((p) => p.type === policyType);
@@ -66,7 +66,7 @@ export default function DetailProvider() {
const updatedComponent = {
...component,
subType: subTab,
parentId: realmRepresentation?.id,
parentId: realmRepresentation.id,
providerType:
"org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy",
providerId,
@@ -110,9 +110,7 @@ export const MultiValuedListComponent = ({
newValue = values;
}
field.onChange(
stringify && variant === SelectVariant.typeaheadMulti
? toStringValue(newValue)
: newValue,
stringify ? toStringValue(newValue) : newValue,
);
} else {
field.onChange(option);
@@ -236,15 +236,14 @@ export const GroupPickerDialog = ({
setFirst(0);
}}
type={type}
isSearching={filter !== ""}
setIsSearching={(boolean) => setFilter(boolean ? "" : filter)}
isSearching={false}
selectedRows={selectedRows}
setSelectedRows={setSelectedRows}
canBrowse={canBrowse}
/>
))
: groups
?.map((g) => deepGroup([g]))
.map((g) => deepGroup([g]))
.flat()
.map((g) => (
<GroupRow
@@ -13,7 +13,7 @@ import { useTranslation } from "react-i18next";
import { FormErrorText } from "@keycloak/keycloak-ui-shared";
function stringToMultiline(value?: string): string[] {
return typeof value === "string" ? value.split("##") : [value || ""];
return typeof value === "string" ? value.split("##") : [""];
}
function toStringValue(formValue: string[]): string {
@@ -135,7 +135,7 @@ export const RolesList = ({
onRowClick: (role) => {
setSelectedRole(role);
if (
realm?.defaultRole &&
realm.defaultRole &&
role.name === realm!.defaultRole!.name
) {
addAlert(
@@ -154,7 +154,7 @@ export const RolesList = ({
cellRenderer: (row) => (
<RoleDetailLink
{...row}
defaultRoleName={realm?.defaultRole?.name}
defaultRoleName={realm.defaultRole?.name}
toDetail={toDetail}
messageBundle={messageBundle}
/>
@@ -143,7 +143,7 @@ export function UserDataTable() {
adminClient.users.getProfile(),
]);
} catch (error) {
if (error instanceof NetworkError && error?.response?.status === 403) {
if (error instanceof NetworkError && error.response.status === 403) {
// "User Profile" attributes not available for Users Attribute search, when admin user does not have view- or manage-realm realm-management role
return [{}, {}] as [UiRealmInfo, UserProfileConfig];
} else {
@@ -230,7 +230,7 @@ export function UserDataTable() {
const goToCreate = () => navigate(toAddUser({ realm: realmName }));
if (uiRealmInfo.userProfileProvidersEnabled === undefined || !realm) {
if (uiRealmInfo.userProfileProvidersEnabled === undefined) {
return <KeycloakSpinner />;
}
@@ -140,6 +140,7 @@ export function UserDataTableAttributeSearchForm({
};
const createAttributeKeyInputField = () => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- may be undefined at runtime despite the type
if (profile) {
return (
<KeycloakSelect
@@ -180,7 +180,7 @@ export const UserSelect = ({
}}
>
{
users.find((u) => u?.id === selection)
users.find((u) => u.id === selection)
?.username
}
</Chip>
@@ -197,7 +197,7 @@ export const UserSelect = ({
setInputValue("");
setSearch("");
field.onChange([]);
textInputRef?.current?.focus();
textInputRef.current?.focus();
}}
aria-label="Clear input value"
>
@@ -224,7 +224,7 @@ export const UserSelect = ({
setInputValue(
removed
? ""
: users.find((u) => u?.id === option)?.username || "",
: users.find((u) => u.id === option)?.username || "",
);
setOpen(false);
} else {
@@ -59,14 +59,11 @@ export const RecentRealmsProvider = ({ children }: PropsWithChildren) => {
}),
),
(realms) => setStoredRealms(realms.filter((r) => !!r)),
[realm?.realm === "master"],
[realm.realm === "master"],
);
useEffect(() => {
if (
storedRealms.map((r) => r.name).includes(realm?.realm || "") ||
!realm
) {
if (storedRealms.map((r) => r.name).includes(realm.realm || "")) {
return;
}
setStoredRealms(
@@ -13,7 +13,7 @@ import { useTranslation } from "react-i18next";
type RealmContextType = {
realm: string;
realmRepresentation?: RealmRepresentation;
realmRepresentation: RealmRepresentation;
refresh: () => void;
};
@@ -32,7 +32,7 @@ export const RealmContextProvider = ({ children }: PropsWithChildren) => {
useState<RealmRepresentation>();
const locationRealm = useHash();
const realm = locationRealm?.split("/")[1] ?? environment.realm;
const realm = locationRealm.split("/")[1] ?? environment.realm;
// Configure admin client to use selected realm when it changes.
useEffect(() => {
@@ -54,7 +54,9 @@ export const RealmContextProvider = ({ children }: PropsWithChildren) => {
}
return (
<RealmContext.Provider value={{ realm, realmRepresentation, refresh }}>
<RealmContext.Provider
value={{ realm, realmRepresentation: realmRepresentation!, refresh }}
>
{children}
</RealmContext.Provider>
);
+5 -7
View File
@@ -54,7 +54,7 @@ const EmptyDashboard = () => {
const { t } = useTranslation();
const { realm, realmRepresentation: realmInfo } = useRealm();
const brandImage = environment.logo ? environment.logo : "/icon.svg";
const realmDisplayInfo = resolveDisplayName(t, realmInfo?.displayName, realm);
const realmDisplayInfo = resolveDisplayName(t, realmInfo.displayName, realm);
return (
<PageSection variant="light">
@@ -87,9 +87,7 @@ const FeatureItem = ({ feature }: FeatureItemProps) => {
? "blue"
: feature.type === FeatureType.Experimental
? "orange"
: feature.type === FeatureType.Deprecated
? "grey"
: "red";
: "grey";
return (
<ListItem className="pf-v5-u-mb-sm">
{feature.name}&nbsp;
@@ -116,12 +114,12 @@ const Dashboard = () => {
);
const disabledFeatures = useMemo(
() => sortedFeatures.filter((f) => !f.enabled) || [],
() => sortedFeatures.filter((f) => !f.enabled),
[serverInfo.features],
);
const enabledFeatures = useMemo(
() => sortedFeatures.filter((f) => f.enabled) || [],
() => sortedFeatures.filter((f) => f.enabled),
[serverInfo.features],
);
@@ -133,7 +131,7 @@ const Dashboard = () => {
}),
);
const realmDisplayInfo = resolveDisplayName(t, realmInfo?.displayName, realm);
const realmDisplayInfo = resolveDisplayName(t, realmInfo.displayName, realm);
const welcomeTab = useTab("welcome");
const infoTab = useTab("info");
+1 -1
View File
@@ -171,7 +171,7 @@ export const AdminEvents = ({ resourcePath }: AdminEventsProps) => {
useFetch(
() => adminClient.realms.getConfigEvents({ realm }),
(events) => {
setAdminEventsEnabled(events?.adminEventsEnabled!);
setAdminEventsEnabled(events.adminEventsEnabled!);
},
[],
);
+2 -2
View File
@@ -167,8 +167,8 @@ export const UserEvents = ({ user, client }: UserEventsProps) => {
useFetch(
() => adminClient.realms.getConfigEvents({ realm }),
(events) => {
setUserEventsEnabled(events?.eventsEnabled!);
setEvents(localeSort(events?.enabledEventTypes || [], (e) => e));
setUserEventsEnabled(events.eventsEnabled!);
setEvents(localeSort(events.enabledEventTypes || [], (e) => e));
},
[],
);
+3 -3
View File
@@ -148,9 +148,9 @@ export const GroupsModal = ({
!groups.isOrgGroups() &&
isFeatureEnabled(Feature.AdminFineGrainedAuthz)
) {
const permissions = await groups.listPermissions({
const permissions = (await groups.listPermissions({
id: sourceGroup.id!,
});
})) as Awaited<ReturnType<typeof groups.listPermissions>> | undefined;
if (permissions) {
await groups.updatePermission({ id: createdGroup.id }, permissions);
@@ -171,7 +171,7 @@ export const GroupsModal = ({
);
const clientRolesPayload: RoleMappingPayload[] =
clientRoleMappings?.flatMap((clientRoleMapping) =>
clientRoleMappings.flatMap((clientRoleMapping) =>
clientRoleMapping.roles.map((role) => ({
id: role.id!,
name: role.name!,
@@ -78,7 +78,7 @@ const OrganizationLink = (identityProvider: IdentityProviderRepresentation) => {
const { t } = useTranslation();
const { realm } = useRealm();
if (!identityProvider?.organizationId) {
if (!identityProvider.organizationId) {
return "—";
}
@@ -228,7 +228,7 @@ export default function AddMapper() {
<GroupResourceContext
value={
idp?.organizationId
? adminClient.organizations.groups(idp?.organizationId)
? adminClient.organizations.groups(idp.organizationId)
: adminClient.groups
}
>
@@ -441,7 +441,7 @@ export default function DetailSettings() {
const isSocial = !isOIDC && !isSAML && !isOAuth2;
const isJWTAuthorizationGrantSupported =
(isOAuth2 || isOIDC) &&
!!provider?.types?.includes(IdentityProviderType.JWT_AUTHORIZATION_GRANT) &&
!!provider.types?.includes(IdentityProviderType.JWT_AUTHORIZATION_GRANT) &&
isFeatureEnabled(Feature.JWTAuthorizationGrant);
const groupResource = provider.organizationId
? adminClient.organizations.groups(provider.organizationId)
@@ -769,7 +769,7 @@ export default function DetailSettings() {
<PermissionsTab id={alias} type="identityProviders" />
</Tab>
)}
{realmRepresentation?.adminEventsEnabled &&
{realmRepresentation.adminEventsEnabled &&
hasAccess("view-events") && (
<Tab
data-testid="admin-events-tab"
@@ -62,7 +62,11 @@ export default function DetailOrganization() {
};
useFetch(
() => adminClient.organizations.findOne({ id }),
() =>
adminClient.organizations.findOne({ id }) as Promise<
| Awaited<ReturnType<typeof adminClient.organizations.findOne>>
| undefined
>,
(org) => {
if (!org) {
throw new Error(t("notFound"));
@@ -179,7 +183,7 @@ export default function DetailOrganization() {
>
<IdentityProviders />
</Tab>
{realmRepresentation?.adminEventsEnabled &&
{realmRepresentation.adminEventsEnabled &&
hasAccess("view-events") && (
<Tab
data-testid="admin-events-tab"
@@ -25,7 +25,6 @@ import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../admin-client";
import { ComponentProps } from "../components/dynamic/components";
import { KeycloakSpinner } from "@keycloak/keycloak-ui-shared";
import useToggle from "../utils/useToggle";
type IdentityProviderSelectProps = Omit<ComponentProps, "convertToName"> & {
@@ -96,9 +95,6 @@ export const IdentityProviderSelect = ({
return options;
};
if (!idps) {
return <KeycloakSpinner />;
}
return (
<FormGroup
label={t(label!)}
@@ -116,9 +112,7 @@ export const IdentityProviderSelect = ({
control={control}
rules={{
validate: (value: string[]) =>
isRequired && value.filter((i) => i !== undefined).length === 0
? t("required")
: undefined,
isRequired && value.length === 0 ? t("required") : undefined,
}}
render={({ field }) => (
<Select
@@ -185,7 +179,7 @@ export const IdentityProviderSelect = ({
setInputValue("");
setSearch("");
field.onChange([]);
textInputRef?.current?.focus();
textInputRef.current?.focus();
}}
aria-label={t("clear")}
>
@@ -81,7 +81,7 @@ export const LinkIdentityProviderModal = ({
...foundIdentityProvider.config,
...config,
};
foundIdentityProvider.hideOnLogin = data.hideOnLogin ?? true;
foundIdentityProvider.hideOnLogin = data.hideOnLogin;
await adminClient.identityProviders.update(
{ alias: data.alias[0] },
foundIdentityProvider,
@@ -75,7 +75,7 @@ export const OrganizationForm = ({
aria-label={t("domain")}
addButtonLabel="addDomain"
/>
{errors?.["domains"]?.message && (
{errors["domains"]?.message && (
<FormErrorText message={errors["domains"].message.toString()} />
)}
</FormGroup>
+6 -8
View File
@@ -57,19 +57,17 @@ export const PageHandler = ({
);
const onSubmit = async (component: ComponentRepresentation) => {
if (component.config || params) {
component.config = Object.assign(component.config || {}, params);
Object.entries(component.config).forEach(
([key, value]) =>
(component.config![key] = Array.isArray(value) ? value : [value]),
);
}
component.config = Object.assign(component.config || {}, params);
Object.entries(component.config).forEach(
([key, value]) =>
(component.config![key] = Array.isArray(value) ? value : [value]),
);
try {
const updatedComponent = {
...component,
providerId,
providerType,
parentId: realm?.id,
parentId: realm.id,
};
if (id) {
await adminClient.components.update({ id }, updatedComponent);
+1 -1
View File
@@ -62,7 +62,7 @@ export default function PageList() {
signal: { providerId },
loader: async () => {
const params: ComponentQuery = {
parent: realm?.id,
parent: realm.id,
type: PAGE_PROVIDER,
providerId,
};
@@ -50,21 +50,21 @@ export default function PermissionsConfigurationSection() {
const permissionsResourcesTab = useRoutableTab(
toPermissionsConfigurationTabs({
realm,
permissionClientId: realmRepresentation?.adminPermissionsClient?.id!,
permissionClientId: realmRepresentation.adminPermissionsClient?.id!,
tab: "permissions",
}),
);
const permissionsPoliciesTab = useRoutableTab(
toPermissionsConfigurationTabs({
realm,
permissionClientId: realmRepresentation?.adminPermissionsClient?.id!,
permissionClientId: realmRepresentation.adminPermissionsClient?.id!,
tab: "policies",
}),
);
const permissionsEvaluateTab = useRoutableTab(
toPermissionsConfigurationTabs({
realm,
permissionClientId: realmRepresentation?.adminPermissionsClient?.id!,
permissionClientId: realmRepresentation.adminPermissionsClient?.id!,
tab: "evaluation",
}),
);
@@ -78,18 +78,19 @@ export const AssignedPolicies = ({
return Promise.resolve([]);
},
(policies) => {
const filteredPolicy = policies.filter((p) => p) as [];
setSelectedPolicies(filteredPolicy);
setSelectedPolicies(
(policies as (PolicyRepresentation | undefined)[]).filter(
(p): p is PolicyRepresentation => p !== undefined,
),
);
},
[policies],
);
const sortedProviders = sortBy(
providers
? providers
.filter((p) => p.type !== "resource" && p.type !== "scope")
.map((provider) => provider.name)
: [],
.filter((p) => p.type !== "resource" && p.type !== "scope")
.map((provider) => provider.name),
);
const assign = async (policies: { policy: PolicyRepresentation }[]) => {
@@ -106,7 +106,7 @@ export const NewPermissionPolicyDialog = ({
});
const { addAlert, addError } = useAlerts();
const { handleSubmit, reset } = form;
const isPermissionClient = realmRepresentation?.adminPermissionsEnabled;
const isPermissionClient = realmRepresentation.adminPermissionsEnabled;
const policyTypeSelector = useWatch({
control: form.control,
@@ -144,10 +144,12 @@ export const NewPermissionPolicyDialog = ({
...(groups && groups.length > 0 && { groups }),
...(roles && roles.length > 0 && { roles }),
...(policies && policies.length > 0 && { policies }),
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- clients may be undefined at runtime despite the type
...(clients && clients.length > 0 && { clients }),
...(rest.type === "group" &&
(!groups || groups.length === 0) && { groups: [] }),
...(rest.type === "client" &&
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- clients may be undefined at runtime despite the type
(!clients || clients.length === 0) && { clients: [] }),
};
@@ -192,7 +194,7 @@ export const NewPermissionPolicyDialog = ({
rules={{ required: t("required") }}
/>
<TextControl name="description" label={t("description")} />
{providers && providers.length > 0 && (
{providers.length > 0 && (
<SelectControl
name="type"
label={t("policyType")}
@@ -218,7 +220,7 @@ export const NewPermissionPolicyDialog = ({
type="submit"
data-testid="save"
isDisabled={
policies?.length === 0 && policyTypeSelector === "aggregate"
policies.length === 0 && policyTypeSelector === "aggregate"
}
>
{t("save")}
@@ -40,7 +40,9 @@ export default function PermissionConfigurationDetails() {
const form = useForm();
const { handleSubmit, reset } = form;
const { addAlert, addError } = useAlerts();
const [permission, setPermission] = useState<PolicyRepresentation>();
const [permission, setPermission] = useState<PolicyRepresentation | null>(
permissionId ? null : {},
);
const [providers, setProviders] = useState<PolicyProviderRepresentation[]>();
const [policies, setPolicies] = useState<PolicyRepresentation[]>();
const resourceTypes = useSortedResourceTypes({
@@ -213,7 +215,7 @@ export default function PermissionConfigurationDetails() {
},
});
if (!permission) {
if (permission === null || !providers) {
return <KeycloakSpinner />;
}
@@ -221,10 +223,10 @@ export default function PermissionConfigurationDetails() {
<>
<DeleteConfirm />
<ViewHeader
titleKey={permissionId ? permission?.name! : t("createPermission")}
titleKey={permissionId ? permission.name! : t("createPermission")}
subKey={
permissionId
? permission?.description!
? permission.description!
: t("createPermissionOfType", { resourceType })
}
dropdownItems={
@@ -247,7 +249,7 @@ export default function PermissionConfigurationDetails() {
<NameDescription clientId={permissionClientId} />
<ScopePicker
clientId={permissionClientId}
resourceTypeScopes={resourceTypeScopes ?? []}
resourceTypeScopes={resourceTypeScopes}
/>
<ResourceType resourceType={resourceType} />
<AssignedPolicies
@@ -79,7 +79,7 @@ export const PermissionsConfigurationTab = ({
});
const processedPermissions = await Promise.all(
(permissions || []).map(async (permission) => {
permissions.map(async (permission) => {
const policies = await adminClient.clients.getAssociatedPolicies({
id: clientId,
permissionId: permission.id!,
@@ -282,7 +282,7 @@ export const PermissionsConfigurationTab = ({
<>
<Th>{t("resources")}</Th>
{permission.resources &&
permission.resources?.length > 0 ? (
permission.resources.length > 0 ? (
permission.resources!.map(
(resource: ResourceRepresentation, index) => (
<Td key={index}>
@@ -12,23 +12,23 @@ export const PermissionEvaluationResult = ({
evaluateResult,
}: PermissionEvaluationResultProps) => {
const { t } = useTranslation();
const evaluatedResults = evaluateResult?.results || [];
const evaluatedResults = evaluateResult.results || [];
const evaluatedResult = evaluatedResults[0] || {};
const alertTitle =
evaluatedResult?.resource?.name ?? t("permissionEvaluationAlertTitle");
evaluatedResult.resource?.name ?? t("permissionEvaluationAlertTitle");
const alertVariant =
evaluateResult?.status === "PERMIT" ? "success" : "warning";
evaluateResult.status === "PERMIT" ? "success" : "warning";
const evaluatedAllowedScopes = useMemo(
() => sortBy(evaluatedResult?.allowedScopes || [], "name"),
() => sortBy(evaluatedResult.allowedScopes || [], "name"),
[evaluatedResult],
);
const evaluatedDeniedScopes = useMemo(
() => sortBy(evaluatedResult?.deniedScopes || [], "name"),
() => sortBy(evaluatedResult.deniedScopes || [], "name"),
[evaluatedResult],
);
const evaluatedPolicies = useMemo(
() => sortBy(evaluatedResult?.policies || [], "name"),
() => sortBy(evaluatedResult.policies || [], "name"),
[evaluatedResult],
);
@@ -101,7 +101,7 @@ const PermissionEvaluateContent = ({ client }: Props) => {
const formValues = getValues();
const getSingleValue = (source: string | string[]) => {
return Array.isArray(source) ? source?.[0] : source;
return Array.isArray(source) ? source[0] : source;
};
const getResourceName = (resourceType: string) => {
@@ -192,6 +192,7 @@ const PermissionEvaluateContent = ({ client }: Props) => {
}}
options={resourceTypes.map((resource) => resource.type!)}
/>
{/* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- undefined when selectedResourceType not in COMPONENTS */}
{ResourceTypeComponent && (
<ResourceTypeComponent
name={selectedResourceType?.toLowerCase()}
@@ -46,7 +46,7 @@ export const GroupSelect = ({
useFetch(
() => {
if (values && values.length > 0) {
if (values.length > 0) {
return Promise.all(
(values as string[]).map((id) => adminClient.groups.findOne({ id })),
);
@@ -139,7 +139,7 @@ export const GroupSelect = ({
onClick={() => {
setValue(name!, [
...convertGroups(
(groups || []).filter(({ id }) => id !== group.id),
groups.filter(({ id }) => id !== group.id),
),
]);
setGroups([
@@ -34,6 +34,7 @@ export const ResourceType = ({
const normalizedResourceType = resourceType.toLowerCase();
const [isSpecificResources, setIsSpecificResources] = useState(
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- resourceIds is undefined when form field "resources" has no default value
resourceIds?.some((id) => id !== resourceType) || !withEnforceAccessTo,
);
@@ -30,7 +30,7 @@ export const RoleSelect = ({ name, isRadio = false }: RoleSelectorProps) => {
setValue,
formState: { errors },
} = useFormContext<{ [key: string]: string[] }>();
const values = getValues(name) || [];
const values = getValues(name);
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedRoles, setSelectedRoles] = useState<Row[]>([]);
const [filterType, setFilterType] = useState<FilterType>("clients");
@@ -65,7 +65,7 @@ export const SearchDropdown = ({
};
useEffect(() => {
const type = types?.find((item) => item.type === selectedType);
const type = types.find((item) => item.type === selectedType);
setResourceScopes(type?.scopes || []);
}, [selectedType, types]);
@@ -130,7 +130,7 @@ export const SearchDropdown = ({
defaultValue: "",
}}
options={[
...(resourceScopes || []).map((resourceScope) => ({
...resourceScopes.map((resourceScope) => ({
key: resourceScope!,
value: resourceScope!,
})),
@@ -1,9 +1,5 @@
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import {
KeycloakSpinner,
useAlerts,
useFetch,
} from "@keycloak/keycloak-ui-shared";
import { useAlerts, useFetch } from "@keycloak/keycloak-ui-shared";
import {
AlertVariant,
ButtonVariant,
@@ -238,11 +234,7 @@ export default function RealmRoleTabs() {
};
const isDefaultRole = (name: string | undefined) =>
realm?.defaultRole && realm.defaultRole!.name === name;
if (!realm) {
return <KeycloakSpinner />;
}
realm.defaultRole && realm.defaultRole!.name === name;
return (
<>
@@ -350,45 +350,43 @@ export const RealmSettingsEmailTab = ({
/>
)}
/>
{currentUser && (
<FormGroup id="descriptionTestConnection">
{currentUser.email ? (
<Alert
variant="info"
component="h2"
isInline
title={t("testConnectionHint.withEmail", {
email: currentUser.email,
})}
/>
) : (
<Alert
variant="warning"
component="h2"
isInline
title={t("testConnectionHint.withoutEmail", {
userName: currentUser.username,
})}
actionLinks={
<AlertActionLink
component={(props) => (
<Link
{...props}
to={toUser({
realm: currentUser.realm!,
id: currentUser.id!,
tab: "settings",
})}
/>
)}
>
{t("testConnectionHint.withoutEmailAction")}
</AlertActionLink>
}
/>
)}
</FormGroup>
)}
<FormGroup id="descriptionTestConnection">
{currentUser.email ? (
<Alert
variant="info"
component="h2"
isInline
title={t("testConnectionHint.withEmail", {
email: currentUser.email,
})}
/>
) : (
<Alert
variant="warning"
component="h2"
isInline
title={t("testConnectionHint.withoutEmail", {
userName: currentUser.username,
})}
actionLinks={
<AlertActionLink
component={(props) => (
<Link
{...props}
to={toUser({
realm: currentUser.realm!,
id: currentUser.id!,
tab: "settings",
})}
/>
)}
>
{t("testConnectionHint.withoutEmailAction")}
</AlertActionLink>
}
/>
)}
</FormGroup>
<ActionGroup>
<ActionListItem>
<Button
@@ -407,7 +405,7 @@ export const RealmSettingsEmailTab = ({
isDisabled={
!(
emailRegexPattern.test(watchFromValue) && watchHostValue
) || !currentUser?.email
) || !currentUser.email
}
aria-describedby="descriptionTestConnection"
isLoading={isTesting}
@@ -133,9 +133,9 @@ function RealmSettingsGeneralTabForm({
JSON.parse(realm.attributes["acr.loa.map"]),
).flatMap(([acr, loa]) => ({ acr, loa }) as RealmLoAMappingType);
if (isStepUpAuthenticationSaml && realm.attributes?.["acr.uri.map"]) {
if (isStepUpAuthenticationSaml && realm.attributes["acr.uri.map"]) {
const acrUriMap = JSON.parse(realm.attributes["acr.uri.map"]);
acrLoaMap.forEach((row) => (row.uri = acrUriMap?.[row?.acr]));
acrLoaMap.forEach((row) => (row.uri = acrUriMap?.[row.acr]));
}
setValue(
@@ -276,6 +276,7 @@ export default function NewAttributeSettings() {
realm: realmName,
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- translation is undefined when user hasn't interacted with translation dialogs
if (formFields.translation) {
try {
await saveTranslations({
@@ -234,7 +234,7 @@ export default function NewClientPolicyCondition() {
<SelectOption
data-testid={condition.id}
selected={condition.id === field.value}
description={condition?.helpText}
description={condition.helpText}
key={condition.id}
value={condition}
>
@@ -254,7 +254,7 @@ export default function ProfilesTab() {
<CodeEditor
value={code}
language="json"
onChange={(value) => setCode(value ?? "")}
onChange={(value) => setCode(value)}
height={480}
/>
</div>
@@ -206,10 +206,10 @@ export const RealmSettingsTabs = () => {
combinedLocales.map(async (locale) => {
try {
const response =
await adminClient.realms.getRealmLocalizationTexts({
(await adminClient.realms.getRealmLocalizationTexts({
realm: realmName,
selectedLocale: locale,
});
})) as Record<string, string> | undefined;
if (response) {
setTableData([response]);
@@ -278,7 +278,7 @@ export const RealmSettingsTabs = () => {
addError("realmSaveError", error);
}
const isRealmRenamed = realmName !== (r.realm || realm?.realm);
const isRealmRenamed = realmName !== (r.realm || realm.realm);
if (isRealmRenamed) {
navigate(toRealmSettings({ realm: r.realm!, tab: "general" }));
}
@@ -328,7 +328,7 @@ export const RealmSettingsTabs = () => {
<RealmSettingsHeader
value={field.value}
onChange={field.onChange}
realmName={resolveDisplayName(t, realm?.displayName, realmName)}
realmName={resolveDisplayName(t, realm.displayName, realmName)}
refresh={refreshHeader}
save={() => save(getValues())}
/>
@@ -209,12 +209,10 @@ export const RealmSettingsTokensTab = ({
id="oAuthDevicePollingInterval"
value={field.value}
min={0}
onPlus={() => field.onChange(Number(field?.value) + 1)}
onPlus={() => field.onChange(Number(field.value) + 1)}
onMinus={() =>
field.onChange(
Number(field?.value) > 0
? Number(field?.value) - 1
: 0,
Number(field.value) > 0 ? Number(field.value) - 1 : 0,
)
}
onChange={(event) => {
@@ -24,7 +24,7 @@ export const UserRegistration = () => {
try {
await adminClient.roles.createComposite(
{ roleId: realm?.defaultRole!.id!, realm: realmName },
{ roleId: realm.defaultRole!.id!, realm: realmName },
compositeArray,
);
setKey(key + 1);
@@ -47,8 +47,8 @@ export const UserRegistration = () => {
data-testid="default-roles-tab"
>
<RoleMapping
name={realm?.defaultRole!.name!}
id={realm?.defaultRole!.id!}
name={realm.defaultRole!.name!}
id={realm.defaultRole!.id!}
type="roles"
isManager
save={(rows) => addComposites(rows.map((r) => r.role))}
@@ -95,7 +95,7 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => {
const { realm } = useRealm();
const [keyData, setKeyData] = useState<KeyData[]>([]);
const [keyData, setKeyData] = useState<KeyData[] | undefined>();
const [filter, setFilter] = useState<string>(FILTER_OPTIONS[0]);
@@ -146,7 +146,7 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => {
<KeycloakDataTable
isNotCompact
className="kc-keys-list"
loader={filteredKeyData}
loader={filteredKeyData ?? []}
ariaLabelKey="keysList"
searchPlaceholderKey="searchKey"
searchTypeComponent={
@@ -35,7 +35,7 @@ export const FileNameDialog = ({ onSave, onClose }: FileNameDialogProps) => {
// Auto-update fileName when themeName changes
useEffect(() => {
setValue("fileName", `${themeName?.trim()}.jar`);
setValue("fileName", `${themeName.trim()}.jar`);
}, [themeName, setValue]);
const save = ({ themeName, fileName, themeDescription }: FormValues) =>
@@ -201,10 +201,7 @@ const flattenVariables = (theme: ThemeType): FlattenedVariable[] => {
return result;
};
export const lightTheme = (): FlattenedVariable[] =>
flattenVariables("light").filter(
(v) => v.defaultValue !== undefined || v.parentName !== undefined,
);
export const lightTheme = (): FlattenedVariable[] => flattenVariables("light");
export const darkTheme = (): FlattenedVariable[] => flattenVariables("dark");
@@ -31,9 +31,9 @@ export const QuickTheme = ({ realm, theme }: QuickThemeProps) => {
const { favicon, logo, bgimage, fileName } = realm;
const logoName =
"img/logo" + logo?.name?.substring(logo?.name?.lastIndexOf("."));
"img/logo" + logo?.name.substring(logo.name.lastIndexOf("."));
const bgimageName =
"img/bgimage" + bgimage?.name?.substring(bgimage?.name?.lastIndexOf("."));
"img/bgimage" + bgimage?.name.substring(bgimage.name.lastIndexOf("."));
const themeNameClean =
(realm.themeName ?? "quick-theme")
@@ -115,7 +115,7 @@ export const ThemeColors = ({
// Recalculate derived colors when any parent color changes
useEffect(() => {
if (!parentColorValues || parentColorValues.length === 0) return;
if (parentColorValues.length === 0) return;
// Find which parent colors changed and update their dependents
parentColors.forEach((parent, index) => {
@@ -326,14 +326,14 @@ export const ThemeColors = ({
<Tab title={t("loginPagePreview")} eventKey={0}>
<LoginPreviewWindow
cssVars={{
...(style?.[theme] || {}),
logoWidth: style?.["logoWidth"],
logoHeight: style?.["logoHeight"],
...(style[theme] || {}),
logoWidth: style["logoWidth"],
logoHeight: style["logoHeight"],
}}
/>
</Tab>
<Tab title={t("adminConsolePreview")} eventKey={1}>
<PreviewWindow cssVars={style?.[theme] || {}} />
<PreviewWindow cssVars={style[theme] || {}} />
</Tab>
</Tabs>
</FlexItem>
@@ -13,9 +13,7 @@ export const UploadJar = ({ onUpload }: UploadJarProps) => {
const triggerUpload = () => {
const input = document.getElementById("jarUpload") as HTMLInputElement;
if (input) {
input.click();
}
input.click();
};
const handleAcceptedFiles = async (files: ChangeEvent<HTMLInputElement>) => {
@@ -106,7 +106,7 @@ export default function AttributesGroupForm() {
const success = await save({ ...config, groups });
if (success) {
if (realm?.internationalizationEnabled) {
if (realm.internationalizationEnabled) {
try {
await saveTranslations({
adminClient,
@@ -60,44 +60,41 @@ export const AttributesGroupTab = ({
const translationsForDisplayHeaderToDelete =
groupToDelete?.displayHeader?.substring(
2,
groupToDelete?.displayHeader.length - 1,
groupToDelete.displayHeader.length - 1,
);
const translationsForDisplayDescriptionToDelete =
groupToDelete?.displayDescription?.substring(
2,
groupToDelete?.displayDescription.length - 1,
groupToDelete.displayDescription.length - 1,
);
try {
await Promise.all(
combinedLocales.map(async (locale) => {
try {
const response =
await adminClient.realms.getRealmLocalizationTexts({
realm,
selectedLocale: locale,
});
await adminClient.realms.deleteRealmLocalizationTexts({
realm,
selectedLocale: locale,
key: translationsForDisplayHeaderToDelete,
});
await adminClient.realms.deleteRealmLocalizationTexts({
realm,
selectedLocale: locale,
key: translationsForDisplayDescriptionToDelete,
});
const updatedData =
await adminClient.realms.getRealmLocalizationTexts({
realm,
selectedLocale: locale,
});
if (response) {
await adminClient.realms.deleteRealmLocalizationTexts({
realm,
selectedLocale: locale,
key: translationsForDisplayHeaderToDelete,
});
await adminClient.realms.deleteRealmLocalizationTexts({
realm,
selectedLocale: locale,
key: translationsForDisplayDescriptionToDelete,
});
const updatedData =
await adminClient.realms.getRealmLocalizationTexts({
realm,
selectedLocale: locale,
});
setTableData([updatedData]);
}
setTableData([updatedData]);
} catch {
console.error(`Error removing translations for ${locale}`);
}
@@ -73,10 +73,10 @@ export const AttributesTab = ({ setTableData }: AttributesTabProps) => {
combinedLocales.map(async (locale) => {
try {
const response =
await adminClient.realms.getRealmLocalizationTexts({
(await adminClient.realms.getRealmLocalizationTexts({
realm,
selectedLocale: locale,
});
})) as Record<string, string> | undefined;
if (response) {
await adminClient.realms.deleteRealmLocalizationTexts({
@@ -36,7 +36,7 @@ export const JsonEditorTab = () => {
<CodeEditor
language="json"
value={code}
onChange={(value) => setCode(value ?? "")}
onChange={(value) => setCode(value)}
height={480}
/>
<Form>
@@ -80,8 +80,8 @@ export const AddTranslationsDialog = ({
const selectedLocales = combinedLocales
.filter((l) =>
localeToDisplayName(l, whoAmI.locale)
?.toLocaleLowerCase(realm?.defaultLocale)
?.includes(filter.toLocaleLowerCase(realm?.defaultLocale)),
?.toLocaleLowerCase(realm.defaultLocale)
.includes(filter.toLocaleLowerCase(realm.defaultLocale)),
)
.slice(first, first + max + 1);
@@ -223,7 +223,7 @@ export const AddTranslationsDialog = ({
translation.locale,
whoAmI.locale,
)}
{translation.locale === realm?.defaultLocale && (
{translation.locale === realm.defaultLocale && (
<Label className="pf-v5-u-ml-xs" color="blue">
{t("defaultLanguage")}
</Label>
@@ -236,7 +236,7 @@ export const AddTranslationsDialog = ({
{...register(`${prefix}.${index}.value`, {
required: {
value:
translation.locale === realm?.defaultLocale,
translation.locale === realm.defaultLocale,
message: t("required"),
},
})}
@@ -93,7 +93,7 @@ export const TranslatableField = ({
if (predefinedAttributes?.includes(value)) {
return;
}
if (realm?.internationalizationEnabled && value) {
if (realm.internationalizationEnabled && value) {
setValue(fieldName, `\${${prefix}.${value}}`);
}
}, [value]);
@@ -131,11 +131,11 @@ export const TranslatableField = ({
<TextInput
id={`kc-attribute-${fieldName}`}
data-testid={`attributes-${fieldName}`}
isDisabled={realm?.internationalizationEnabled}
isDisabled={realm.internationalizationEnabled}
{...register(fieldName)}
/>
</InputGroupItem>
{realm?.internationalizationEnabled && (
{realm.internationalizationEnabled && (
<InputGroupItem>
<Button
variant="link"
@@ -148,7 +148,7 @@ export const TranslatableField = ({
</InputGroupItem>
)}
</InputGroup>
{realm?.internationalizationEnabled && (
{realm.internationalizationEnabled && (
<FormHelperText>
<Alert
variant="info"
@@ -178,9 +178,9 @@ export const RevocationModal = ({
autoFocus
readOnly
value={
realm?.notBefore === 0
realm.notBefore === 0
? (t("none") as string)
: new Date(realm?.notBefore! * 1000).toString()
: new Date(realm.notBefore! * 1000).toString()
}
type="text"
id="not-before"
@@ -85,7 +85,7 @@ export default function CustomProviderSettings() {
),
providerId,
providerType: "org.keycloak.storage.UserStorageProvider",
parentId: realm?.id,
parentId: realm.id,
});
try {
@@ -36,7 +36,7 @@ export const KerberosSettingsRequired = ({
name: "config.allowPasswordAuthentication",
});
useEffect(() => form.setValue("parentId", realmRepresentation?.id), []);
useEffect(() => form.setValue("parentId", realmRepresentation.id), []);
return (
<FormProvider {...form}>
@@ -29,7 +29,7 @@ export const LdapSettingsGeneral = ({
const { t } = useTranslation();
const { realm, realmRepresentation } = useRealm();
useEffect(() => form.setValue("parentId", realmRepresentation?.id), []);
useEffect(() => form.setValue("parentId", realmRepresentation.id), []);
const [isVendorDropdownOpen, setIsVendorDropdownOpen] = useState(false);
const setVendorDefaultValues = () => {
+1 -5
View File
@@ -37,10 +37,6 @@ export default function CreateUser() {
useFetch(
() => adminClient.users.getProfileMetadata({ realm: realmName }),
(userProfileMetadata) => {
if (!userProfileMetadata) {
throw new Error(t("notFound"));
}
setUserProfileMetadata(userProfileMetadata);
},
[],
@@ -68,7 +64,7 @@ export default function CreateUser() {
}
};
if (!realm || !userProfileMetadata) {
if (!userProfileMetadata) {
return <KeycloakSpinner />;
}
+13 -11
View File
@@ -97,7 +97,7 @@ export default function EditUser() {
const [realmHasOrganizations, setRealmHasOrganizations] = useState(false);
const isFeatureEnabled = useIsFeatureEnabled();
const showOrganizations =
isFeatureEnabled(Feature.Organizations) && realm?.organizationsEnabled;
isFeatureEnabled(Feature.Organizations) && realm.organizationsEnabled;
const toTab = (tab: UserTab) =>
toUser({
@@ -143,7 +143,7 @@ export default function EditUser() {
upConfig,
organizations,
]) => {
if (!userData || !realm || !attackDetection) {
if (!userData || !attackDetection) {
throw new Error(t("notFound"));
}
@@ -196,16 +196,17 @@ export default function EditUser() {
(field, params) => {
if (field.startsWith("attributes.")) {
const attributeName = field.substring("attributes.".length);
const unmanagedAttrs =
data.unmanagedAttributes as KeyValueType[];
let isUnmanagedAttribute = false;
(data.unmanagedAttributes as KeyValueType[]).forEach(
(attr, index) => {
if (attr.key === attributeName) {
unmanagedAttributeErrors[index] = params;
someUnmanagedAttributeError = true;
isUnmanagedAttribute = true;
}
},
);
unmanagedAttrs.forEach((attr, index) => {
if (attr.key === attributeName) {
unmanagedAttributeErrors[index] = params;
someUnmanagedAttributeError = true;
isUnmanagedAttribute = true;
}
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- mutated in forEach above
if (!isUnmanagedAttribute) {
form.setError(field, params);
}
@@ -215,6 +216,7 @@ export default function EditUser() {
},
((key, param) => t(key as string, param as any)) as TFunction,
);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- mutated in callback above
if (someUnmanagedAttributeError) {
form.setError(
"unmanagedAttributes",
@@ -362,8 +362,8 @@ export const UserCredentials = ({ user, setUser }: UserCredentialsProps) => {
const useFederatedCredentials = user.federationLink;
const [credentialTypes, setCredentialTypes] = useState<
CredentialRepresentation[]
>([]);
CredentialRepresentation[] | undefined
>();
useFetch(
() => {
+2 -2
View File
@@ -152,8 +152,8 @@ export const UserForm = ({
const allFieldsReadOnly = () =>
user?.userProfileMetadata?.attributes &&
!user?.userProfileMetadata?.attributes
?.map((a) => a.readOnly)
!user.userProfileMetadata.attributes
.map((a) => a.readOnly)
.reduce((p, c) => p && c, true);
const handleEmailVerificationReset = async () => {
+1 -1
View File
@@ -45,7 +45,7 @@ export function toUserRepresentation(
: data.unmanagedAttributes;
for (const key in unmanagedAttributes) {
if (attributes && Object.hasOwn(attributes, key)) {
if (Object.hasOwn(attributes, key)) {
throw Error(
`Attribute ${key} is a managed attribute and is already available from the user details.`,
);
@@ -30,7 +30,7 @@ export const ResetCredentialDialog = ({
const form = useForm<CredentialResetForm>({
defaultValues: {
actions: [],
lifespan: realm?.actionTokenGeneratedByAdminLifespan,
lifespan: realm.actionTokenGeneratedByAdminLifespan,
},
});
const { handleSubmit, control } = form;
@@ -159,10 +159,7 @@ export const ResetPasswordDialog = ({
id="password"
onChange={async (e) => {
await onChange(e);
if (
e.currentTarget &&
passwordConfirmation !== e.currentTarget.value
) {
if (passwordConfirmation !== e.currentTarget.value) {
setError("passwordConfirmation", {
message: t("confirmPasswordDoesNotMatch"),
});
@@ -7,7 +7,7 @@ export function useIsAdminPermissionsClient(selectedClientId: string) {
useState<boolean>(false);
useEffect(() => {
if (realmRepresentation?.adminPermissionsClient) {
if (realmRepresentation.adminPermissionsClient) {
setIsAdminPermissionsClient(
selectedClientId === realmRepresentation.adminPermissionsClient.id,
);
+2 -2
View File
@@ -6,13 +6,13 @@ export default function useLocale() {
const { realmRepresentation: realm } = useRealm();
const defaultSupportedLocales = useMemo(() => {
return realm?.supportedLocales?.length
return realm.supportedLocales?.length
? realm.supportedLocales
: [DEFAULT_LOCALE];
}, [realm]);
const defaultLocales = useMemo(() => {
return realm?.defaultLocale?.length ? [realm.defaultLocale] : [];
return realm.defaultLocale?.length ? [realm.defaultLocale] : [];
}, [realm]);
const combinedLocales = useMemo(() => {
@@ -91,9 +91,7 @@ test.describe("OID4VCI Protocol Mapper Configuration", () => {
});
test.afterEach(async () => {
if (testBed) {
await testBed[Symbol.asyncDispose]();
}
await testBed[Symbol.asyncDispose]();
});
test("should display mandatory claim toggle and claim display fields", async ({
@@ -18,25 +18,25 @@ export async function fillHardwareAttributeMapper(
if (data.config?.["user.model.attribute"])
await page
.getByTestId("config.user🍺model🍺attribute")
.fill(data.config?.["user.model.attribute"][0] || "");
.fill(data.config["user.model.attribute"][0] || "");
if (data.config?.["attribute.value"])
await page
.getByTestId("attribute.value")
.fill(data.config?.["attribute.value"][0] || "");
.fill(data.config["attribute.value"][0] || "");
if (data.config?.group)
await page.getByTestId("group").fill(data.config?.group[0] || "");
await page.getByTestId("group").fill(data.config.group[0] || "");
if (data.config?.["ldap.attribute.name"])
await page
.getByTestId("ldap.attribute.name")
.fill(data.config?.["ldap.attribute.name"][0] || "");
.fill(data.config["ldap.attribute.name"][0] || "");
if (data.config?.["ldap.attribute.value"])
await page
.getByTestId("ldap.attribute.value")
.fill(data.config?.["ldap.attribute.value"][0] || "");
.fill(data.config["ldap.attribute.value"][0] || "");
if (data.config?.role) {
await pickRoleType(page, "roles");
@@ -45,7 +45,7 @@ export async function fillHardwareAttributeMapper(
}
if (data.config?.roleDn)
await page.getByTestId("roles.dn").fill(data.config?.roleDn[0] || "");
await page.getByTestId("roles.dn").fill(data.config.roleDn[0] || "");
}
export async function saveMapper(page: Page) {
@@ -69,6 +69,7 @@ class AdminClient {
await this.#client.clients.find({ clientId: clientName })
)[0];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- find()[0] is undefined when client does not exist
if (client) {
await this.#client.clients.del({ id: client.id! });
}
@@ -604,6 +605,7 @@ class AdminClient {
const client = (await this.#client.clients.find({ clientId, realm }))[0];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- find()[0] is undefined when client does not exist
if (!client?.id) {
throw new Error(`Client ${clientId} not found in realm ${realm}`);
}
@@ -623,6 +625,7 @@ class AdminClient {
const client = (await this.#client.clients.find({ clientId, realm }))[0];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- find()[0] is undefined when client does not exist
if (!client?.id) {
throw new Error(`Client ${clientId} not found in realm ${realm}`);
}
+2 -1
View File
@@ -14,6 +14,7 @@ const compat = new FlatCompat({
baseDirectory: import.meta.dirname,
});
// eslint-disable-next-line @typescript-eslint/no-deprecated -- defineConfig() not yet available in this version
export default tseslint.config(
{
ignores: [
@@ -73,7 +74,7 @@ export default tseslint.config(
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-redundant-type-constituents": "off",
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "off",
"@typescript-eslint/no-unnecessary-condition": "off",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-unnecessary-type-arguments": "off",
"@typescript-eslint/no-unnecessary-type-assertion": "off",
"@typescript-eslint/no-unnecessary-type-parameters": "off",
@@ -148,9 +148,10 @@ export class Agent {
const baseParams = this.#getBaseParams?.() ?? {};
// Filter query parameters by queryParamKeys
const queryParams = queryParamKeys
? (pick(query, queryParamKeys) as any)
: undefined;
const queryParams =
queryParamKeys.length > 0
? (pick(query, queryParamKeys) as any)
: undefined;
// Add filtered query parameters to base parameters
const allUrlParamKeys = [...Object.keys(baseParams), ...urlParamKeys];
@@ -307,9 +308,9 @@ export class Agent {
return;
}
Object.keys(keyMapping).some((key) => {
Object.keys(keyMapping).forEach((key) => {
if (typeof payload[key] === "undefined") {
return false;
return;
}
const newKey = keyMapping[key];
payload[newKey] = payload[key];
@@ -1,4 +1,5 @@
import type { KeycloakAdminClient } from "../client.js";
import { NetworkError } from "../utils/fetchWithError.js";
import type CertificateRepresentation from "../defs/certificateRepresentation.js";
import type ClientRepresentation from "../defs/clientRepresentation.js";
import type ClientScopeRepresentation from "../defs/clientScopeRepresentation.js";
@@ -718,11 +719,11 @@ export class Clients extends Resource<{ realm?: string }> {
policyName: string;
policy: PolicyRepresentation;
}): Promise<PolicyRepresentation> {
const policyFound = await this.findPolicyByName({
id: payload.id,
name: payload.policyName,
});
if (policyFound) {
try {
const policyFound = await this.findPolicyByName({
id: payload.id,
name: payload.policyName,
});
await this.updatePolicy(
{
id: payload.id,
@@ -731,15 +732,18 @@ export class Clients extends Resource<{ realm?: string }> {
},
payload.policy,
);
return this.findPolicyByName({
return await this.findPolicyByName({
id: payload.id,
name: payload.policyName,
});
} else {
return this.createPolicy(
{ id: payload.id, type: payload.policy.type! },
payload.policy,
);
} catch (error) {
if (error instanceof NetworkError && error.response.status === 404) {
return this.createPolicy(
{ id: payload.id, type: payload.policy.type! },
payload.policy,
);
}
throw error;
}
}
@@ -84,7 +84,8 @@ export const getToken = async (settings: Settings): Promise<TokenResponse> => {
// Prepare credentials for openid-connect token request
// ref: http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
const credentials = settings.credentials || ({} as any);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- credentials may be undefined at runtime despite the type
const credentials = settings.credentials ?? ({} as Credentials);
const payload = stringifyQueryParams({
username: credentials.username,
password: credentials.password,
@@ -3,7 +3,7 @@ export interface DecodedToken {
}
export function decodeToken(token: string): DecodedToken {
const [, payload] = token?.split(".") || [];
const [, payload] = token.split(".");
if (typeof payload !== "string") {
return {};
@@ -35,8 +35,7 @@ export const FormSubmitButton = ({
<Button
variant="primary"
isDisabled={
(formState && !isSubmittable(formState, allowNonDirty, allowInvalid)) ||
isDisabled
!isSubmittable(formState, allowNonDirty, allowInvalid) || isDisabled
}
{...rest}
type="submit"
@@ -27,7 +27,9 @@ let KeycloakEnvContext: any;
export const useEnvironment = <
T extends BaseEnvironment = BaseEnvironment,
>() => {
const context = useContext<KeycloakContext<T>>(KeycloakEnvContext);
const context = useContext<KeycloakContext<T> | undefined>(
KeycloakEnvContext,
);
if (!context)
throw Error(
"no environment provider in the hierarchy make sure to add the provider",
@@ -273,7 +273,7 @@ export const TypeaheadSelectControl = <
onClick={() => {
setFilterValue("");
field.onChange(isTypeaheadMulti ? [] : "");
textInputRef?.current?.focus();
textInputRef.current?.focus();
}}
aria-label="Clear input value"
>
@@ -141,6 +141,7 @@ function DataTable<T>({
useEffect(() => {
if (canSelectAll) {
const selectAllCheckbox = document.getElementsByName("check-all").item(0);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- DOM query can return null at runtime
if (selectAllCheckbox) {
const checkbox = selectAllCheckbox as HTMLInputElement;
checkbox.indeterminate =
@@ -441,6 +442,7 @@ export function KeycloakDataTable<T>({
data: value,
disableSelection: disabledRow,
disableActions: disabledRow,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- lodash get performs dynamic property access at runtime
selected: !!selected.find((v) => get(v, "id") === get(value, "id")),
isOpen: isDetailColumnsEnabled(value) ? false : undefined,
cells: renderCell(columns, value),

Some files were not shown because too many files have changed in this diff Show More