mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-26 13:50:48 +00:00
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:
@@ -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 {
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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] };
|
||||
}),
|
||||
)
|
||||
|
||||
+1
-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>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
@@ -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");
|
||||
|
||||
@@ -171,7 +171,7 @@ export const AdminEvents = ({ resourcePath }: AdminEventsProps) => {
|
||||
useFetch(
|
||||
() => adminClient.realms.getConfigEvents({ realm }),
|
||||
(events) => {
|
||||
setAdminEventsEnabled(events?.adminEventsEnabled!);
|
||||
setAdminEventsEnabled(events.adminEventsEnabled!);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
}),
|
||||
);
|
||||
|
||||
+7
-6
@@ -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 }[]) => {
|
||||
|
||||
+5
-3
@@ -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")}
|
||||
|
||||
+7
-5
@@ -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
|
||||
|
||||
+2
-2
@@ -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}>
|
||||
|
||||
+6
-6
@@ -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],
|
||||
);
|
||||
|
||||
|
||||
+2
-1
@@ -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>
|
||||
|
||||
+4
-4
@@ -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 = () => {
|
||||
|
||||
@@ -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 />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
() => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user