feat: Addition of Application Account page
This commit is contained in:
parent
f49d20e47c
commit
de47525d7c
@ -8,6 +8,7 @@ const Password = lazy(() => import("keycloakify/account/pages/Password"));
|
|||||||
const Account = lazy(() => import("keycloakify/account/pages/Account"));
|
const Account = lazy(() => import("keycloakify/account/pages/Account"));
|
||||||
const Sessions = lazy(() => import("keycloakify/account/pages/Sessions"));
|
const Sessions = lazy(() => import("keycloakify/account/pages/Sessions"));
|
||||||
const Totp = lazy(() => import("keycloakify/account/pages/Totp"));
|
const Totp = lazy(() => import("keycloakify/account/pages/Totp"));
|
||||||
|
const Applications = lazy(() => import("keycloakify/account/pages/Applications"));
|
||||||
|
|
||||||
export default function Fallback(props: PageProps<KcContext, I18n>) {
|
export default function Fallback(props: PageProps<KcContext, I18n>) {
|
||||||
const { kcContext, ...rest } = props;
|
const { kcContext, ...rest } = props;
|
||||||
@ -24,6 +25,8 @@ export default function Fallback(props: PageProps<KcContext, I18n>) {
|
|||||||
return <Account kcContext={kcContext} {...rest} />;
|
return <Account kcContext={kcContext} {...rest} />;
|
||||||
case "totp.ftl":
|
case "totp.ftl":
|
||||||
return <Totp kcContext={kcContext} {...rest} />;
|
return <Totp kcContext={kcContext} {...rest} />;
|
||||||
|
case "applications.ftl":
|
||||||
|
return <Applications kcContext={kcContext} {...rest} />;
|
||||||
}
|
}
|
||||||
assert<Equals<typeof kcContext, never>>(false);
|
assert<Equals<typeof kcContext, never>>(false);
|
||||||
})()}
|
})()}
|
||||||
|
@ -3,7 +3,7 @@ import { assert } from "tsafe/assert";
|
|||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
import { type ThemeType } from "keycloakify/bin/constants";
|
import { type ThemeType } from "keycloakify/bin/constants";
|
||||||
|
|
||||||
export type KcContext = KcContext.Password | KcContext.Account | KcContext.Sessions | KcContext.Totp;
|
export type KcContext = KcContext.Password | KcContext.Account | KcContext.Sessions | KcContext.Totp | KcContext.Applications;
|
||||||
|
|
||||||
export declare namespace KcContext {
|
export declare namespace KcContext {
|
||||||
export type Common = {
|
export type Common = {
|
||||||
@ -180,6 +180,71 @@ export declare namespace KcContext {
|
|||||||
};
|
};
|
||||||
stateChecker: string;
|
stateChecker: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Applications = Common & {
|
||||||
|
pageId: "applications.ftl";
|
||||||
|
features: {
|
||||||
|
log: boolean;
|
||||||
|
identityFederation: boolean;
|
||||||
|
authorization: boolean;
|
||||||
|
passwordUpdateSupported: boolean;
|
||||||
|
};
|
||||||
|
stateChecker: string;
|
||||||
|
applications: {
|
||||||
|
applications: {
|
||||||
|
realmRolesAvailable: { name: string; description: string }[];
|
||||||
|
resourceRolesAvailable: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
roleName: string;
|
||||||
|
roleDescription: string;
|
||||||
|
clientName: string;
|
||||||
|
clientId: string;
|
||||||
|
}[]
|
||||||
|
>;
|
||||||
|
additionalGrants: string[];
|
||||||
|
clientScopesGranted: string[];
|
||||||
|
effectiveUrl?: string;
|
||||||
|
client: {
|
||||||
|
consentScreenText: string;
|
||||||
|
surrogateAuthRequired: boolean;
|
||||||
|
bearerOnly: boolean;
|
||||||
|
id: string;
|
||||||
|
protocolMappersStream: Record<string, unknown>;
|
||||||
|
includeInTokenScope: boolean;
|
||||||
|
redirectUris: string[];
|
||||||
|
fullScopeAllowed: boolean;
|
||||||
|
registeredNodes: Record<string, unknown>;
|
||||||
|
enabled: boolean;
|
||||||
|
clientAuthenticatorType: string;
|
||||||
|
realmScopeMappingsStream: Record<string, unknown>;
|
||||||
|
scopeMappingsStream: Record<string, unknown>;
|
||||||
|
displayOnConsentScreen: boolean;
|
||||||
|
clientId: string;
|
||||||
|
rootUrl: string;
|
||||||
|
authenticationFlowBindingOverrides: Record<string, unknown>;
|
||||||
|
standardFlowEnabled: boolean;
|
||||||
|
attributes: Record<string, unknown>;
|
||||||
|
publicClient: boolean;
|
||||||
|
alwaysDisplayInConsole: boolean;
|
||||||
|
consentRequired: boolean;
|
||||||
|
notBefore: string;
|
||||||
|
rolesStream: Record<string, unknown>;
|
||||||
|
protocol: string;
|
||||||
|
dynamicScope: boolean;
|
||||||
|
directAccessGrantsEnabled: boolean;
|
||||||
|
name: string;
|
||||||
|
serviceAccountsEnabled: boolean;
|
||||||
|
frontchannelLogout: boolean;
|
||||||
|
nodeReRegistrationTimeout: string;
|
||||||
|
implicitFlowEnabled: boolean;
|
||||||
|
baseUrl: string;
|
||||||
|
webOrigins: string[];
|
||||||
|
realm: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -198,7 +198,7 @@ export const kcContextMocks: KcContext[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stateChecker": ""
|
"stateChecker": "g6WB1FaYnKotTkiy7ZrlxvFztSqS0U8jvHsOOOb2z4g"
|
||||||
}),
|
}),
|
||||||
id<KcContext.Totp>({
|
id<KcContext.Totp>({
|
||||||
...kcContextCommonMock,
|
...kcContextCommonMock,
|
||||||
|
138
src/account/pages/Applications.tsx
Normal file
138
src/account/pages/Applications.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
|
import type { PageProps } from "keycloakify/account/pages/PageProps";
|
||||||
|
import { useGetClassName } from "keycloakify/account/lib/useGetClassName";
|
||||||
|
import type { KcContext } from "../kcContext";
|
||||||
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
|
function isArrayWithEmptyObject(variable: any): boolean {
|
||||||
|
return Array.isArray(variable) && variable.length === 1 && typeof variable[0] === "object" && Object.keys(variable[0]).length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Applications(props: PageProps<Extract<KcContext, { pageId: "applications.ftl" }>, I18n>) {
|
||||||
|
const { kcContext, i18n, doUseDefaultCss, classes, Template } = props;
|
||||||
|
|
||||||
|
const { getClassName } = useGetClassName({
|
||||||
|
doUseDefaultCss,
|
||||||
|
classes
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
url,
|
||||||
|
applications: { applications },
|
||||||
|
stateChecker
|
||||||
|
} = kcContext;
|
||||||
|
|
||||||
|
const { msg, advancedMsg } = i18n;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} active="applications">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-10">
|
||||||
|
<h2>{msg("applicationsHtmlTitle")}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action={url.applicationsUrl} method="post">
|
||||||
|
<input type="hidden" id="stateChecker" name="stateChecker" value={stateChecker} />
|
||||||
|
<input type="hidden" id="referrer" name="referrer" value={stateChecker} />
|
||||||
|
|
||||||
|
<table className="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>{msg("application")}</td>
|
||||||
|
<td>{msg("availableRoles")}</td>
|
||||||
|
<td>{msg("grantedPermissions")}</td>
|
||||||
|
<td>{msg("additionalGrants")}</td>
|
||||||
|
<td>{msg("action")}</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{applications.map(application => (
|
||||||
|
<tr key={application.client.clientId}>
|
||||||
|
<td>
|
||||||
|
{application.effectiveUrl && (
|
||||||
|
<a href={application.effectiveUrl}>
|
||||||
|
{(application.client.name && advancedMsg(application.client.name)) || application.client.clientId}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{!application.effectiveUrl &&
|
||||||
|
((application.client.name && advancedMsg(application.client.name)) || application.client.clientId)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{!isArrayWithEmptyObject(application.realmRolesAvailable) &&
|
||||||
|
application.realmRolesAvailable.map(role => (
|
||||||
|
<span key={role.name}>
|
||||||
|
{role.description ? advancedMsg(role.description) : advancedMsg(role.name)}
|
||||||
|
{role !== application.realmRolesAvailable[application.realmRolesAvailable.length - 1] && ", "}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{!isArrayWithEmptyObject(application.realmRolesAvailable) && application.resourceRolesAvailable && ", "}
|
||||||
|
{application.resourceRolesAvailable &&
|
||||||
|
Object.keys(application.resourceRolesAvailable).map(resource => (
|
||||||
|
<span key={resource}>
|
||||||
|
{!isArrayWithEmptyObject(application.realmRolesAvailable) && ", "}
|
||||||
|
{application.resourceRolesAvailable[resource].map(clientRole => (
|
||||||
|
<span key={clientRole.roleName}>
|
||||||
|
{clientRole.roleDescription
|
||||||
|
? advancedMsg(clientRole.roleDescription)
|
||||||
|
: advancedMsg(clientRole.roleName)}
|
||||||
|
{msg("inResource")}{" "}
|
||||||
|
<strong>
|
||||||
|
{clientRole.clientName ? advancedMsg(clientRole.clientName) : clientRole.clientId}
|
||||||
|
</strong>
|
||||||
|
{clientRole !==
|
||||||
|
application.resourceRolesAvailable[resource][
|
||||||
|
application.resourceRolesAvailable[resource].length - 1
|
||||||
|
] && ", "}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{application.client.consentRequired ? (
|
||||||
|
application.clientScopesGranted.map(claim => (
|
||||||
|
<span key={claim}>
|
||||||
|
{advancedMsg(claim)}
|
||||||
|
{claim !== application.clientScopesGranted[application.clientScopesGranted.length - 1] && ", "}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<strong>{msg("fullAccess")}</strong>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{application.additionalGrants.map(grant => (
|
||||||
|
<span key={grant}>
|
||||||
|
{advancedMsg(grant)}
|
||||||
|
{grant !== application.additionalGrants[application.additionalGrants.length - 1] && ", "}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{(application.client.consentRequired && application.clientScopesGranted.length > 0) ||
|
||||||
|
application.additionalGrants.length > 0 ? (
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className={clsx(getClassName("kcButtonPrimaryClass"), getClassName("kcButtonClass"))}
|
||||||
|
id={`revoke-${application.client.clientId}`}
|
||||||
|
name="clientId"
|
||||||
|
value={application.client.id}
|
||||||
|
>
|
||||||
|
{msg("revoke")}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</Template>
|
||||||
|
);
|
||||||
|
}
|
@ -27,7 +27,7 @@ export const loginThemePageIds = [
|
|||||||
"saml-post-form.ftl"
|
"saml-post-form.ftl"
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const accountThemePageIds = ["password.ftl", "account.ftl", "sessions.ftl", "totp.ftl"] as const;
|
export const accountThemePageIds = ["password.ftl", "account.ftl", "sessions.ftl", "totp.ftl", "applications.ftl"] as const;
|
||||||
|
|
||||||
export type LoginThemePageId = (typeof loginThemePageIds)[number];
|
export type LoginThemePageId = (typeof loginThemePageIds)[number];
|
||||||
export type AccountThemePageId = (typeof accountThemePageIds)[number];
|
export type AccountThemePageId = (typeof accountThemePageIds)[number];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user