Compare commits
26 Commits
v10.0.0-rc
...
v10.0.0-rc
Author | SHA1 | Date | |
---|---|---|---|
f59ee55be5 | |||
bc549af64c | |||
39add772f7 | |||
f19e622d39 | |||
7eb13db467 | |||
ef503e271d | |||
7d24e2716f | |||
9bbcc21f9c | |||
fcfabb0c3f | |||
9ca3cadd10 | |||
f156fec1c3 | |||
e962b37948 | |||
3a8f1a0ed1 | |||
e3a7bb13f5 | |||
29b45497ba | |||
a748e8d8ec | |||
f2fcb553a5 | |||
a9dc11c60d | |||
ee9df31b18 | |||
69d1e86a8a | |||
06761807a3 | |||
a6c1e9bb61 | |||
b70dfe96f6 | |||
5f2b1484b5 | |||
d4595c999f | |||
373850e32a |
10
package.json
10
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "10.0.0-rc.0",
|
||||
"version": "10.0.0-rc.3",
|
||||
"description": "Create Keycloak themes using React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -10,10 +10,10 @@
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"prepare": "yarn generate-i18n-messages",
|
||||
"build": "rimraf dist/ && tsc -p src/bin && tsc -p src && tsc -p src/vite-plugin && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn copy-files dist/ && cp -r src dist/",
|
||||
"build": "tsc -p src/bin && tsc -p src && tsc -p src/vite-plugin && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn run copy-files && cp -r src dist/",
|
||||
"generate:json-schema": "ts-node scripts/generate-json-schema.ts",
|
||||
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
||||
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.java",
|
||||
"grant-exec-perms": "ts-node --skipProject scripts/grant-exec-perms.ts",
|
||||
"copy-files": "copyfiles -u 1 'src/**/*.ftl' dist/",
|
||||
"test": "yarn test:types && vitest run",
|
||||
"test:keycloakify-starter": "ts-node scripts/test-keycloakify-starter",
|
||||
"test:types": "tsc -p test/tsconfig.json --noEmit",
|
||||
@ -23,7 +23,6 @@
|
||||
"generate-i18n-messages": "ts-node --skipProject scripts/generate-i18n-messages.ts",
|
||||
"link-in-app": "ts-node --skipProject scripts/link-in-app.ts",
|
||||
"link-in-starter": "yarn link-in-app keycloakify-starter",
|
||||
"watch-in-starter": "yarn build && yarn link-in-starter && (concurrently \"tsc -p src -w\" \"tsc-alias -p src/tsconfig.json\" \"tsc -p src/bin -w\")",
|
||||
"copy-keycloak-resources-to-storybook-static": "PUBLIC_DIR_PATH=.storybook/static node dist/bin/copy-keycloak-resources-to-public.js",
|
||||
"storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && start-storybook -p 6006",
|
||||
"build-storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && build-storybook"
|
||||
@ -97,7 +96,6 @@
|
||||
"properties-parser": "^0.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"scripting-tools": "^0.19.13",
|
||||
"storybook-dark-mode": "^1.1.2",
|
||||
"ts-node": "^10.9.1",
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { getThisCodebaseRootDirPath } from "./getThisCodebaseRootDirPath";
|
||||
import { join as pathJoin } from "path";
|
||||
import { constants } from "fs";
|
||||
import { chmod, stat } from "fs/promises";
|
||||
|
||||
(async () => {
|
||||
const thisCodebaseRootDirPath = getThisCodebaseRootDirPath();
|
||||
const thisCodebaseRootDirPath = pathJoin(__dirname, "..");
|
||||
|
||||
const { bin } = await import(pathJoin(thisCodebaseRootDirPath, "package.json"));
|
||||
|
@ -15,7 +15,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
|
||||
const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
|
||||
|
||||
const { msg, msgStr, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
|
||||
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
|
||||
|
||||
const { locale, url, features, realm, message, referrer } = kcContext;
|
||||
|
||||
@ -81,10 +81,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
<ul>
|
||||
{locale.supported.map(({ languageTag }) => (
|
||||
<li key={languageTag} className="kc-dropdown-item">
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a href="#" onClick={() => changeLocale(languageTag)}>
|
||||
{labelBySupportedLanguageTag[languageTag]}
|
||||
</a>
|
||||
<a href={getChangeLocalUrl(languageTag)}>{labelBySupportedLanguageTag[languageTag]}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -28,11 +28,10 @@ export type GenericI18n<MessageKey extends string> = {
|
||||
*/
|
||||
currentLanguageTag: string;
|
||||
/**
|
||||
* To call when the user switch language.
|
||||
* This will cause the page to be reloaded,
|
||||
* on next load currentLanguageTag === newLanguageTag
|
||||
* Redirect to this url to change the language.
|
||||
* After reload currentLanguageTag === newLanguageTag
|
||||
*/
|
||||
changeLocale: (newLanguageTag: string) => never;
|
||||
getChangeLocalUrl: (newLanguageTag: string) => string;
|
||||
/**
|
||||
* e.g. "en" => "English", "fr" => "Français", ...
|
||||
*
|
||||
@ -104,7 +103,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
||||
} as any
|
||||
}),
|
||||
currentLanguageTag,
|
||||
"changeLocale": newLanguageTag => {
|
||||
"getChangeLocalUrl": newLanguageTag => {
|
||||
const { locale } = kcContext;
|
||||
|
||||
assert(locale !== undefined, "Internationalization not enabled");
|
||||
@ -113,9 +112,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
||||
|
||||
assert(targetSupportedLocale !== undefined, `${newLanguageTag} need to be enabled in Keycloak admin`);
|
||||
|
||||
window.location.href = targetSupportedLocale.url;
|
||||
|
||||
assert(false, "never");
|
||||
return targetSupportedLocale.url;
|
||||
},
|
||||
"labelBySupportedLanguageTag": Object.fromEntries(
|
||||
(kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])
|
||||
|
@ -164,21 +164,6 @@ export declare namespace KcContext {
|
||||
};
|
||||
mode?: "qr" | "manual" | undefined | null;
|
||||
isAppInitiatedAction: boolean;
|
||||
url: {
|
||||
accountUrl: string;
|
||||
passwordUrl: string;
|
||||
totpUrl: string;
|
||||
socialUrl: string;
|
||||
sessionsUrl: string;
|
||||
applicationsUrl: string;
|
||||
logUrl: string;
|
||||
resourceUrl: string;
|
||||
resourcesCommonPath: string;
|
||||
resourcesPath: string;
|
||||
/** @deprecated, not present in recent keycloak version apparently, use kcContext.referrer instead */
|
||||
referrerURI?: string;
|
||||
getLogoutUrl: () => string;
|
||||
};
|
||||
stateChecker: string;
|
||||
};
|
||||
|
||||
|
@ -29,24 +29,23 @@ export async function buildJar(params: {
|
||||
|
||||
const keycloakifyBuildTmpDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "..", jarFileBasename.replace(".jar", ""));
|
||||
|
||||
{
|
||||
if (!existsSync(buildOptions.keycloakifyBuildDirPath)) {
|
||||
await fs.mkdir(buildOptions.keycloakifyBuildDirPath, { "recursive": true });
|
||||
}
|
||||
|
||||
await fs.writeFile(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8"));
|
||||
if (existsSync(keycloakifyBuildTmpDirPath)) {
|
||||
await fs.rm(keycloakifyBuildTmpDirPath, { "recursive": true });
|
||||
}
|
||||
|
||||
await fs.mkdir(keycloakifyBuildTmpDirPath, { "recursive": true });
|
||||
await fs.writeFile(pathJoin(keycloakifyBuildTmpDirPath, ".gitignore"), Buffer.from("*", "utf8"));
|
||||
|
||||
const srcMainResourcesRelativeDirPath = pathJoin("src", "main", "resources");
|
||||
|
||||
{
|
||||
const keycloakThemesJsonFilePath = pathJoin(srcMainResourcesRelativeDirPath, "META-INF", "keycloak-themes.json");
|
||||
|
||||
const themePropertiesFilePathSet = new Set(
|
||||
...buildOptions.themeNames.map(themeName => pathJoin(srcMainResourcesRelativeDirPath, "themes", themeName, "account", "theme.properties"))
|
||||
...buildOptions.themeNames.map(themeName => pathJoin(srcMainResourcesRelativeDirPath, "theme", themeName, "account", "theme.properties"))
|
||||
);
|
||||
|
||||
const accountV1RelativeDirPath = pathJoin(srcMainResourcesRelativeDirPath, "themes", accountV1ThemeName);
|
||||
const accountV1RelativeDirPath = pathJoin(srcMainResourcesRelativeDirPath, "theme", accountV1ThemeName);
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": buildOptions.keycloakifyBuildDirPath,
|
||||
@ -108,7 +107,7 @@ export async function buildJar(params: {
|
||||
|
||||
(["register.ftl", "login-update-profile.ftl"] as const).forEach(pageId =>
|
||||
buildOptions.themeNames.map(themeName => {
|
||||
const ftlFilePath = pathJoin(srcMainResourcesRelativeDirPath, "themes", themeName, "login", pageId);
|
||||
const ftlFilePath = pathJoin(keycloakifyBuildTmpDirPath, srcMainResourcesRelativeDirPath, "theme", themeName, "login", pageId);
|
||||
|
||||
const ftlFileContent = readFileSync(ftlFilePath).toString("utf8");
|
||||
|
||||
@ -123,7 +122,7 @@ export async function buildJar(params: {
|
||||
})();
|
||||
|
||||
const modifiedFtlFileContent = ftlFileContent.replace(
|
||||
`out["pageId"] = "${pageId}";`,
|
||||
`out["pageId"] = "\${pageId}";`,
|
||||
`out["pageId"] = "${pageId}"; out["realPageId"] = "${realPageId}";`
|
||||
);
|
||||
|
||||
@ -141,12 +140,24 @@ export async function buildJar(params: {
|
||||
keycloakThemeAdditionalInfoExtensionVersion
|
||||
});
|
||||
|
||||
await fs.writeFile(pathJoin(buildOptions.keycloakifyBuildDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
|
||||
await fs.writeFile(pathJoin(keycloakifyBuildTmpDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) =>
|
||||
child_process.exec("mvn clean install", { "cwd": keycloakifyBuildTmpDirPath }, error => {
|
||||
if (error !== null) {
|
||||
console.error(
|
||||
`Build jar failed: ${JSON.stringify(
|
||||
{
|
||||
jarFileBasename,
|
||||
keycloakAccountV1Version,
|
||||
keycloakThemeAdditionalInfoExtensionVersion
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
@ -73,10 +73,10 @@ export function generatePom(params: {
|
||||
` </dependency>`
|
||||
]
|
||||
: []),
|
||||
` </dependencies>`,
|
||||
`</project>`
|
||||
` </dependencies>`
|
||||
]
|
||||
: [])
|
||||
: []),
|
||||
`</project>`
|
||||
].join("\n");
|
||||
|
||||
return { pomFileCode };
|
||||
|
@ -1,280 +1,189 @@
|
||||
<script>const _=
|
||||
<#assign pageId="PAGE_ID_xIgLsPgGId9D8e">
|
||||
(()=>{
|
||||
|
||||
const out = ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
||||
|
||||
out["msg"]= function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); };
|
||||
out["advancedMsg"]= function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); };
|
||||
|
||||
out["messagesPerField"]= {
|
||||
<#assign fieldNames = [ FIELD_NAMES_eKsIY4ZsZ4xeM ]>
|
||||
|
||||
<#attempt>
|
||||
<#if profile?? && profile.attributes?? && profile.attributes?is_enumerable>
|
||||
<#list profile.attributes as attribute>
|
||||
<#if fieldNames?seq_contains(attribute.name)>
|
||||
<#continue>
|
||||
</#if>
|
||||
<#assign fieldNames += [attribute.name]>
|
||||
</#list>
|
||||
</#if>
|
||||
<#recover>
|
||||
</#attempt>
|
||||
|
||||
"printIfExists": function (fieldName, text) {
|
||||
|
||||
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||
throw new Error("You're not supposed to use messagesPerField.printIfExists in this page");
|
||||
<#else>
|
||||
<#list fieldNames as fieldName>
|
||||
if(fieldName === "${fieldName}" ){
|
||||
|
||||
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||
<#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'>
|
||||
|
||||
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||
|
||||
<#attempt>
|
||||
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||
<#recover>
|
||||
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||
</#attempt>
|
||||
|
||||
<#if doExistErrorOnUsernameOrPassword>
|
||||
return text;
|
||||
<#else>
|
||||
|
||||
<#assign doExistMessageForField = "">
|
||||
|
||||
<#attempt>
|
||||
<#assign doExistMessageForField = messagesPerField.exists('${fieldName}')>
|
||||
<#recover>
|
||||
<#assign doExistMessageForField = true>
|
||||
</#attempt>
|
||||
|
||||
return <#if doExistMessageForField>text<#else>undefined</#if>;
|
||||
|
||||
</#if>
|
||||
|
||||
<#assign pageId="PAGE_ID_xIgLsPgGId9D8e">
|
||||
const out = ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
||||
out["msg"]= function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); };
|
||||
out["advancedMsg"]= function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); };
|
||||
out["messagesPerField"]= {
|
||||
<#assign fieldNames = [ FIELD_NAMES_eKsIY4ZsZ4xeM ]>
|
||||
<#attempt>
|
||||
<#if profile?? && profile.attributes?? && profile.attributes?is_enumerable>
|
||||
<#list profile.attributes as attribute>
|
||||
<#if fieldNames?seq_contains(attribute.name)>
|
||||
<#continue>
|
||||
</#if>
|
||||
<#assign fieldNames += [attribute.name]>
|
||||
</#list>
|
||||
</#if>
|
||||
<#recover>
|
||||
</#attempt>
|
||||
"printIfExists": function (fieldName, text) {
|
||||
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||
throw new Error("You're not supposed to use messagesPerField.printIfExists in this page");
|
||||
<#else>
|
||||
<#list fieldNames as fieldName>
|
||||
if(fieldName === "${fieldName}" ){
|
||||
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||
<#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'>
|
||||
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||
<#attempt>
|
||||
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||
<#recover>
|
||||
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||
</#attempt>
|
||||
<#if doExistErrorOnUsernameOrPassword>
|
||||
return text;
|
||||
<#else>
|
||||
|
||||
<#assign doExistMessageForField = "">
|
||||
|
||||
<#attempt>
|
||||
<#assign doExistMessageForField = messagesPerField.exists('${fieldName}')>
|
||||
<#recover>
|
||||
<#assign doExistMessageForField = true>
|
||||
</#attempt>
|
||||
|
||||
return <#if doExistMessageForField>text<#else>undefined</#if>;
|
||||
|
||||
</#if>
|
||||
|
||||
|
||||
}
|
||||
</#list>
|
||||
|
||||
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||
</#if>
|
||||
|
||||
},
|
||||
"existsError": function (){
|
||||
|
||||
function existsError_singleFieldName(fieldName) {
|
||||
|
||||
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||
throw new Error("You're not supposed to use messagesPerField.printIfExists in this page");
|
||||
<#else>
|
||||
<#list fieldNames as fieldName>
|
||||
if(fieldName === "${fieldName}" ){
|
||||
|
||||
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||
<#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'>
|
||||
|
||||
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||
|
||||
<#attempt>
|
||||
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||
<#recover>
|
||||
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||
</#attempt>
|
||||
|
||||
return <#if doExistErrorOnUsernameOrPassword>true<#else>false</#if>;
|
||||
|
||||
<#else>
|
||||
|
||||
<#assign doExistErrorMessageForField = "">
|
||||
|
||||
<#attempt>
|
||||
<#assign doExistErrorMessageForField = messagesPerField.existsError('${fieldName}')>
|
||||
<#recover>
|
||||
<#assign doExistErrorMessageForField = true>
|
||||
</#attempt>
|
||||
|
||||
return <#if doExistErrorMessageForField>true<#else>false</#if>;
|
||||
|
||||
</#if>
|
||||
|
||||
}
|
||||
</#list>
|
||||
|
||||
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||
|
||||
</#if>
|
||||
|
||||
}
|
||||
|
||||
for( let i = 0; i < arguments.length; i++ ){
|
||||
|
||||
if( existsError_singleFieldName(arguments[i]) ){
|
||||
return true;
|
||||
<#else>
|
||||
<#assign doExistMessageForField = "">
|
||||
<#attempt>
|
||||
<#assign doExistMessageForField = messagesPerField.exists('${fieldName}')>
|
||||
<#recover>
|
||||
<#assign doExistMessageForField = true>
|
||||
</#attempt>
|
||||
return <#if doExistMessageForField>text<#else>undefined</#if>;
|
||||
</#if>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
},
|
||||
"get": function (fieldName) {
|
||||
|
||||
|
||||
</#list>
|
||||
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||
</#if>
|
||||
},
|
||||
"existsError": function (){
|
||||
function existsError_singleFieldName(fieldName) {
|
||||
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||
throw new Error("You're not supposed to use messagesPerField.get in this page");
|
||||
throw new Error("You're not supposed to use messagesPerField.printIfExists in this page");
|
||||
<#else>
|
||||
<#list fieldNames as fieldName>
|
||||
if(fieldName === "${fieldName}" ){
|
||||
|
||||
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||
<#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'>
|
||||
|
||||
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||
|
||||
<#attempt>
|
||||
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||
<#recover>
|
||||
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||
</#attempt>
|
||||
|
||||
<#if doExistErrorOnUsernameOrPassword>
|
||||
|
||||
<#attempt>
|
||||
return "${kcSanitize(msg('invalidUserMessage'))?no_esc}";
|
||||
<#recover>
|
||||
return "Invalid username or password.";
|
||||
</#attempt>
|
||||
|
||||
<#else>
|
||||
|
||||
<#attempt>
|
||||
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
||||
<#recover>
|
||||
return "";
|
||||
</#attempt>
|
||||
|
||||
</#if>
|
||||
|
||||
<#else>
|
||||
|
||||
<#attempt>
|
||||
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
||||
<#recover>
|
||||
return "invalid field";
|
||||
</#attempt>
|
||||
|
||||
</#if>
|
||||
|
||||
}
|
||||
</#list>
|
||||
|
||||
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||
|
||||
</#if>
|
||||
|
||||
},
|
||||
"exists": function (fieldName) {
|
||||
|
||||
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||
throw new Error("You're not supposed to use messagesPerField.exists in this page");
|
||||
<#else>
|
||||
<#list fieldNames as fieldName>
|
||||
if(fieldName === "${fieldName}" ){
|
||||
|
||||
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||
<#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'>
|
||||
|
||||
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||
|
||||
<#attempt>
|
||||
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||
<#recover>
|
||||
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||
</#attempt>
|
||||
|
||||
return <#if doExistErrorOnUsernameOrPassword>true<#else>false</#if>;
|
||||
|
||||
<#else>
|
||||
|
||||
<#assign doExistErrorMessageForField = "">
|
||||
|
||||
<#attempt>
|
||||
<#assign doExistErrorMessageForField = messagesPerField.exists('${fieldName}')>
|
||||
<#assign doExistErrorMessageForField = messagesPerField.existsError('${fieldName}')>
|
||||
<#recover>
|
||||
<#assign doExistErrorMessageForField = true>
|
||||
</#attempt>
|
||||
|
||||
return <#if doExistErrorMessageForField>true<#else>false</#if>;
|
||||
|
||||
</#if>
|
||||
|
||||
}
|
||||
</#list>
|
||||
|
||||
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||
</#if>
|
||||
|
||||
},
|
||||
"getFirstError": function () {
|
||||
|
||||
for( let i = 0; i < arguments.length; i++ ){
|
||||
|
||||
const fieldName = arguments[i];
|
||||
|
||||
if( out.messagesPerField.existsError(fieldName) ){
|
||||
return out.messagesPerField.get(fieldName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
<#if account??>
|
||||
out["url"]["getLogoutUrl"] = function () {
|
||||
<#attempt>
|
||||
return "${url.getLogoutUrl()}";
|
||||
<#recover>
|
||||
</#attempt>
|
||||
};
|
||||
</#if>
|
||||
|
||||
out["keycloakifyVersion"] = "KEYCLOAKIFY_VERSION_xEdKd3xEdr";
|
||||
out["themeVersion"] = "KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx";
|
||||
out["themeType"] = "KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr";
|
||||
out["themeName"] = "KEYCLOAKIFY_THEME_NAME_cXxKd3xEer";
|
||||
out["pageId"] = "${pageId}";
|
||||
|
||||
try {
|
||||
|
||||
out["url"]["resourcesCommonPath"] = out["url"]["resourcesPath"] + "/" + "RESOURCES_COMMON_cLsLsMrtDkpVv";
|
||||
|
||||
} catch(error) {
|
||||
|
||||
for( let i = 0; i < arguments.length; i++ ){
|
||||
if( existsError_singleFieldName(arguments[i]) ){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
"get": function (fieldName) {
|
||||
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||
throw new Error("You're not supposed to use messagesPerField.get in this page");
|
||||
<#else>
|
||||
<#list fieldNames as fieldName>
|
||||
if(fieldName === "${fieldName}" ){
|
||||
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||
<#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'>
|
||||
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||
<#attempt>
|
||||
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||
<#recover>
|
||||
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||
</#attempt>
|
||||
<#if doExistErrorOnUsernameOrPassword>
|
||||
<#attempt>
|
||||
return "${kcSanitize(msg('invalidUserMessage'))?no_esc}";
|
||||
<#recover>
|
||||
return "Invalid username or password.";
|
||||
</#attempt>
|
||||
<#else>
|
||||
<#attempt>
|
||||
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
||||
<#recover>
|
||||
return "";
|
||||
</#attempt>
|
||||
</#if>
|
||||
<#else>
|
||||
<#attempt>
|
||||
return "${messagesPerField.get('${fieldName}')?no_esc}";
|
||||
<#recover>
|
||||
return "invalid field";
|
||||
</#attempt>
|
||||
</#if>
|
||||
}
|
||||
</#list>
|
||||
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||
</#if>
|
||||
},
|
||||
"exists": function (fieldName) {
|
||||
<#if !messagesPerField?? || !(messagesPerField?is_hash)>
|
||||
throw new Error("You're not supposed to use messagesPerField.exists in this page");
|
||||
<#else>
|
||||
<#list fieldNames as fieldName>
|
||||
if(fieldName === "${fieldName}" ){
|
||||
<#-- https://github.com/keycloakify/keycloakify/pull/218 -->
|
||||
<#if ('${fieldName}' == 'username' || '${fieldName}' == 'password') && pageId != 'register.ftl' && pageId != 'register-user-profile.ftl'>
|
||||
<#assign doExistErrorOnUsernameOrPassword = "">
|
||||
<#attempt>
|
||||
<#assign doExistErrorOnUsernameOrPassword = messagesPerField.existsError('username', 'password')>
|
||||
<#recover>
|
||||
<#assign doExistErrorOnUsernameOrPassword = true>
|
||||
</#attempt>
|
||||
return <#if doExistErrorOnUsernameOrPassword>true<#else>false</#if>;
|
||||
<#else>
|
||||
<#assign doExistErrorMessageForField = "">
|
||||
<#attempt>
|
||||
<#assign doExistErrorMessageForField = messagesPerField.exists('${fieldName}')>
|
||||
<#recover>
|
||||
<#assign doExistErrorMessageForField = true>
|
||||
</#attempt>
|
||||
return <#if doExistErrorMessageForField>true<#else>false</#if>;
|
||||
</#if>
|
||||
}
|
||||
</#list>
|
||||
throw new Error(fieldName + "is probably runtime generated, see: https://docs.keycloakify.dev/limitations#field-names-cant-be-runtime-generated");
|
||||
</#if>
|
||||
},
|
||||
"getFirstError": function () {
|
||||
for( let i = 0; i < arguments.length; i++ ){
|
||||
const fieldName = arguments[i];
|
||||
if( out.messagesPerField.existsError(fieldName) ){
|
||||
return out.messagesPerField.get(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return out;
|
||||
out["keycloakifyVersion"] = "KEYCLOAKIFY_VERSION_xEdKd3xEdr";
|
||||
out["themeVersion"] = "KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx";
|
||||
out["themeType"] = "KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr";
|
||||
out["themeName"] = "KEYCLOAKIFY_THEME_NAME_cXxKd3xEer";
|
||||
out["pageId"] = "${pageId}";
|
||||
|
||||
})()
|
||||
try {
|
||||
out["url"]["resourcesCommonPath"] = out["url"]["resourcesPath"] + "/" + "RESOURCES_COMMON_cLsLsMrtDkpVv";
|
||||
} catch(error) { }
|
||||
|
||||
return out;
|
||||
|
||||
})();
|
||||
<#function ftl_object_to_js_code_declaring_an_object object path>
|
||||
|
||||
<#local isHash = "">
|
||||
@ -298,7 +207,6 @@
|
||||
<#return "ABORT: We can't list keys on this object">
|
||||
</#attempt>
|
||||
|
||||
|
||||
<#local out_seq = []>
|
||||
|
||||
<#list keys as key>
|
||||
@ -367,23 +275,23 @@
|
||||
key == "realmAttributes"
|
||||
)
|
||||
>
|
||||
<#local out_seq += ["/*If you need '" + path?join(".") + "." + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||
<#local out_seq += ["/*" + path?join(".") + "." + key + " excluded*/"]>
|
||||
<#continue>
|
||||
</#if>
|
||||
|
||||
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
|
||||
<#if (
|
||||
["register.ftl", "info.ftl", "login.ftl", "login-update-password.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
|
||||
["register.ftl", "register-user-profile.ftl", "info.ftl", "login.ftl", "login-update-password.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
|
||||
key == "attemptedUsername" && are_same_path(path, ["auth"])
|
||||
)>
|
||||
<#attempt>
|
||||
<#-- https://github.com/keycloak/keycloak/blob/3a2bf0c04bcde185e497aaa32d0bb7ab7520cf4a/themes/src/main/resources/theme/base/login/template.ftl#L63 -->
|
||||
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
||||
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||
<#local out_seq += ["/*" + path?join(".") + "." + key + " excluded*/"]>
|
||||
<#continue>
|
||||
</#if>
|
||||
<#recover>
|
||||
<#local out_seq += ["/*Testing if attemptedUsername should be skipped throwed an exception */"]>
|
||||
<#local out_seq += ["/*Accessing attemptedUsername throwed an exception */"]>
|
||||
</#attempt>
|
||||
</#if>
|
||||
|
||||
@ -459,6 +367,26 @@
|
||||
</#attempt>
|
||||
</#if>
|
||||
|
||||
<#if are_same_path(path, ["url", "getLogoutUrl"])>
|
||||
<#local returnValue = "">
|
||||
<#attempt>
|
||||
<#local returnValue = url.getLogoutUrl()>
|
||||
<#recover>
|
||||
<#return "ABORT: Couldn't evaluate url.getLogoutUrl()">
|
||||
</#attempt>
|
||||
<#return 'function(){ return "' + returnValue + '"; }'>
|
||||
</#if>
|
||||
|
||||
<#if are_same_path(path, ["totp", "policy", "getAlgorithmKey"])>
|
||||
<#local returnValue = "">
|
||||
<#attempt>
|
||||
<#local returnValue = totp.policy.getAlgorithmKey()>
|
||||
<#recover>
|
||||
<#return "ABORT: Couldn't evaluate totp.policy.getAlgorithmKey()">
|
||||
</#attempt>
|
||||
<#return 'function(){ return "' + returnValue + '"; }'>
|
||||
</#if>
|
||||
|
||||
<#return "ABORT: It's a method">
|
||||
</#if>
|
||||
|
||||
|
@ -108,7 +108,7 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
|
||||
const ftlObjectToJsCodeDeclaringAnObjectPlaceholder = '{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }';
|
||||
|
||||
$("head").prepend(["<script>", ` window.${nameOfTheGlobal}= ${ftlObjectToJsCodeDeclaringAnObjectPlaceholder};`, "</script>"].join("\n"));
|
||||
$("head").prepend(`<script>\nwindow.${nameOfTheGlobal}=${ftlObjectToJsCodeDeclaringAnObjectPlaceholder}</script>`);
|
||||
|
||||
// Remove part of the document marked as ignored.
|
||||
{
|
||||
|
@ -2,9 +2,11 @@ import * as fs from "fs";
|
||||
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { BuildOptions } from "./buildOptions";
|
||||
import { accountV1ThemeName } from "../constants";
|
||||
|
||||
export type BuildOptionsLike = {
|
||||
keycloakifyBuildDirPath: string;
|
||||
themeNames: string[];
|
||||
};
|
||||
|
||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||
@ -15,11 +17,14 @@ const containerName = "keycloak-testing-container";
|
||||
const keycloakVersion = "24.0.4";
|
||||
|
||||
/** Files for being able to run a hot reload keycloak container */
|
||||
export function generateStartKeycloakTestingContainer(params: { jarFilePath: string; buildOptions: BuildOptionsLike }) {
|
||||
const { jarFilePath, buildOptions } = params;
|
||||
export function generateStartKeycloakTestingContainer(params: {
|
||||
jarFilePath: string;
|
||||
doesImplementAccountTheme: boolean;
|
||||
buildOptions: BuildOptionsLike;
|
||||
}) {
|
||||
const { jarFilePath, doesImplementAccountTheme, buildOptions } = params;
|
||||
|
||||
const themeRelativeDirPath = pathJoin("src", "main", "resources", "theme");
|
||||
const themeDirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, themeRelativeDirPath);
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename),
|
||||
@ -40,18 +45,12 @@ export function generateStartKeycloakTestingContainer(params: { jarFilePath: str
|
||||
"$(pwd)",
|
||||
pathRelative(buildOptions.keycloakifyBuildDirPath, jarFilePath)
|
||||
)}":"/opt/keycloak/providers/${pathBasename(jarFilePath)}" \\`,
|
||||
...fs
|
||||
.readdirSync(themeDirPath)
|
||||
.filter(name => fs.lstatSync(pathJoin(themeDirPath, name)).isDirectory())
|
||||
.map(
|
||||
themeName =>
|
||||
` -v "${pathJoin("$(pwd)", themeRelativeDirPath, themeName).replace(
|
||||
/\\/g,
|
||||
"/"
|
||||
)}":"/opt/keycloak/themes/${themeName}":rw \\`
|
||||
),
|
||||
[...(doesImplementAccountTheme ? [accountV1ThemeName] : []), ...buildOptions.themeNames].map(
|
||||
themeName =>
|
||||
` -v "${pathJoin("$(pwd)", themeRelativeDirPath, themeName).replace(/\\/g, "/")}":"/opt/keycloak/themes/${themeName}":rw \\`
|
||||
),
|
||||
` -it quay.io/keycloak/keycloak:${keycloakVersion} \\`,
|
||||
` start-dev --features=declarative-user-profile`,
|
||||
` start-dev`,
|
||||
""
|
||||
].join("\n"),
|
||||
"utf8"
|
||||
|
@ -10,7 +10,7 @@ export function generateThemeVariations(params: { themeName: string; themeVarian
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": mainThemeDirPath,
|
||||
"destDirPath": pathJoin(mainThemeDirPath, themeVariantName),
|
||||
"destDirPath": pathJoin(mainThemeDirPath, "..", themeVariantName),
|
||||
"transformSourceCode": ({ fileRelativePath, sourceCode }) => {
|
||||
if (pathExtname(fileRelativePath) === ".ftl" && fileRelativePath.split(pathSep).length === 2) {
|
||||
const modifiedSourceCode = Buffer.from(
|
||||
|
@ -36,8 +36,6 @@ export function readFieldNameUsage(params: { keycloakifySrcDirPath: string; them
|
||||
return p1;
|
||||
})
|
||||
.map(part => {
|
||||
console.log(part);
|
||||
|
||||
return part
|
||||
.split(",")
|
||||
.map(a => a.trim())
|
||||
|
@ -57,6 +57,7 @@ export async function main() {
|
||||
|
||||
generateStartKeycloakTestingContainer({
|
||||
"jarFilePath": pathJoin(buildOptions.keycloakifyBuildDirPath, lastJarFileBasename),
|
||||
doesImplementAccountTheme,
|
||||
buildOptions
|
||||
});
|
||||
|
||||
|
@ -32,7 +32,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
|
||||
const { getClassName } = useGetClassName({ doUseDefaultCss, classes });
|
||||
|
||||
const { msg, msgStr, changeLocale, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
|
||||
const { msg, msgStr, getChangeLocalUrl, labelBySupportedLanguageTag, currentLanguageTag } = i18n;
|
||||
|
||||
const { realm, locale, auth, url, message, isAppInitiatedAction, authenticationSession, scripts } = kcContext;
|
||||
|
||||
@ -126,31 +126,17 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
|
||||
<div className={getClassName("kcFormCardClass")}>
|
||||
<header className={getClassName("kcFormHeaderClass")}>
|
||||
{realm.internationalizationEnabled && (assert(locale !== undefined), true) && locale.supported.length > 1 && (
|
||||
{realm.internationalizationEnabled && (assert(locale !== undefined), locale.supported.length > 1) && (
|
||||
<div className={getClassName("kcLocaleMainClass")} id="kc-locale">
|
||||
<div id="kc-locale-wrapper" className={getClassName("kcLocaleWrapperClass")}>
|
||||
<div id="kc-locale-dropdown" className={clsx("menu-button-links", getClassName("kcLocaleDropDownClass"))}>
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a href="#" id="kc-current-locale-link">
|
||||
{labelBySupportedLanguageTag[currentLanguageTag]}
|
||||
</a>
|
||||
<ul>
|
||||
{locale.supported.map(({ languageTag }) => (
|
||||
<li key={languageTag} className="kc-dropdown-item">
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a href="#" onClick={() => changeLocale(languageTag)}>
|
||||
{labelBySupportedLanguageTag[languageTag]}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
tabIndex={1}
|
||||
id="kc-current-locale-link"
|
||||
aria-label={msgStr("languages" as any)}
|
||||
aria-haspopup={true}
|
||||
aria-expanded={false}
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-controls="language-switch1"
|
||||
>
|
||||
{labelBySupportedLanguageTag[currentLanguageTag]}
|
||||
@ -165,13 +151,11 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
||||
>
|
||||
{locale.supported.map(({ languageTag }, i) => (
|
||||
<li key={languageTag} className={getClassName("kcLocaleListItemClass")} role="none">
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a
|
||||
role="menuitem"
|
||||
id={`language-${i}`}
|
||||
id={`language-${i + 1}`}
|
||||
className={getClassName("kcLocaleItemClass")}
|
||||
href="#"
|
||||
onClick={() => changeLocale(languageTag)}
|
||||
href={getChangeLocalUrl(languageTag)}
|
||||
>
|
||||
{labelBySupportedLanguageTag[languageTag]}
|
||||
</a>
|
||||
|
@ -28,11 +28,10 @@ export type GenericI18n<MessageKey extends string> = {
|
||||
*/
|
||||
currentLanguageTag: string;
|
||||
/**
|
||||
* To call when the user switch language.
|
||||
* This will cause the page to be reloaded,
|
||||
* on next load currentLanguageTag === newLanguageTag
|
||||
* Redirect to this url to change the language.
|
||||
* After reload currentLanguageTag === newLanguageTag
|
||||
*/
|
||||
changeLocale: (newLanguageTag: string) => never;
|
||||
getChangeLocalUrl: (newLanguageTag: string) => string;
|
||||
/**
|
||||
* e.g. "en" => "English", "fr" => "Français", ...
|
||||
*
|
||||
@ -104,7 +103,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
||||
} as any
|
||||
}),
|
||||
currentLanguageTag,
|
||||
"changeLocale": newLanguageTag => {
|
||||
"getChangeLocalUrl": newLanguageTag => {
|
||||
const { locale } = kcContext;
|
||||
|
||||
assert(locale !== undefined, "Internationalization not enabled");
|
||||
@ -113,9 +112,7 @@ export function createUseI18n<ExtraMessageKey extends string = never>(extraMessa
|
||||
|
||||
assert(targetSupportedLocale !== undefined, `${newLanguageTag} need to be enabled in Keycloak admin`);
|
||||
|
||||
window.location.href = targetSupportedLocale.url;
|
||||
|
||||
assert(false, "never");
|
||||
return targetSupportedLocale.url;
|
||||
},
|
||||
"labelBySupportedLanguageTag": Object.fromEntries(
|
||||
(kcContext.locale?.supported ?? []).map(({ languageTag, label }) => [languageTag, label])
|
||||
|
@ -419,6 +419,7 @@ export declare namespace KcContext {
|
||||
algorithm: "HmacSHA1" | "HmacSHA256" | "HmacSHA512";
|
||||
digits: number;
|
||||
lookAheadWindow: number;
|
||||
getAlgorithmKey: () => string;
|
||||
} & (
|
||||
| {
|
||||
type: "totp";
|
||||
|
@ -443,7 +443,8 @@ export const kcContextMocks = [
|
||||
"digits": 6,
|
||||
"lookAheadWindow": 1,
|
||||
"type": "totp",
|
||||
"period": 30
|
||||
"period": 30,
|
||||
"getAlgorithmKey": () => "SHA1"
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
@ -24,7 +24,7 @@ export default function Code(props: PageProps<Extract<KcContext, { pageId: "code
|
||||
{code.success ? (
|
||||
<>
|
||||
<p>{msg("copyCodeInstruction")}</p>
|
||||
<input id="code" className={getClassName("kcTextareaClass")} value={code.code} />
|
||||
<input id="code" className={getClassName("kcTextareaClass")} defaultValue={code.code} />
|
||||
</>
|
||||
) : (
|
||||
<p id="error">{code.error}</p>
|
||||
|
@ -29,7 +29,7 @@ export default function IdpReviewUserProfile(props: IdpReviewUserProfileProps) {
|
||||
<Template
|
||||
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
||||
displayMessage={messagesPerField.exists("global")}
|
||||
displayRequiredFields={true}
|
||||
displayRequiredFields
|
||||
headerNode={msg("loginIdpReviewProfileTitle")}
|
||||
>
|
||||
<form id="kc-idp-review-profile-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
|
||||
|
@ -104,7 +104,7 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
|
||||
id="username"
|
||||
className={getClassName("kcInputClass")}
|
||||
name="username"
|
||||
value={login.username ?? ""}
|
||||
defaultValue={login.username ?? ""}
|
||||
type="text"
|
||||
autoFocus
|
||||
autoComplete="username"
|
||||
|
@ -17,14 +17,6 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
// NOTE: We should edit the ftl_object_to_js_code_declaring_an_object.ftl
|
||||
// so that we have access to getAlgorithmKey()
|
||||
const algToKeyUriAlg: Record<(typeof kcContext)["totp"]["policy"]["algorithm"], string> = {
|
||||
"HmacSHA1": "SHA1",
|
||||
"HmacSHA256": "SHA256",
|
||||
"HmacSHA512": "SHA512"
|
||||
};
|
||||
|
||||
return (
|
||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("loginTotpTitle")}>
|
||||
<>
|
||||
@ -60,7 +52,7 @@ export default function LoginConfigTotp(props: PageProps<Extract<KcContext, { pa
|
||||
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
|
||||
</li>
|
||||
<li id="kc-totp-algorithm">
|
||||
{msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
|
||||
{msg("loginTotpAlgorithm")}: {totp.policy.getAlgorithmKey()}
|
||||
</li>
|
||||
<li id="kc-totp-digits">
|
||||
{msg("loginTotpDigits")}: {totp.policy.digits}
|
||||
|
@ -26,7 +26,7 @@ export default function LoginUpdateProfile(props: LoginUpdateProfileProps) {
|
||||
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
|
||||
|
||||
return (
|
||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("loginProfileTitle")}>
|
||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayRequiredFields headerNode={msg("loginProfileTitle")}>
|
||||
<form id="kc-update-profile-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
|
||||
<UserProfileFormFields
|
||||
{...{
|
||||
|
@ -101,7 +101,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
|
||||
id="username"
|
||||
className={getClassName("kcInputClass")}
|
||||
name="username"
|
||||
value={login.username ?? ""}
|
||||
defaultValue={login.username ?? ""}
|
||||
type="text"
|
||||
autoFocus
|
||||
autoComplete="off"
|
||||
|
@ -28,7 +28,7 @@ export default function Register(props: RegisterProps) {
|
||||
const [isFormSubmittable, setIsFormSubmittable] = useState(false);
|
||||
|
||||
return (
|
||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("registerTitle")}>
|
||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("registerTitle")} displayRequiredFields>
|
||||
<form id="kc-register-form" className={getClassName("kcFormClass")} action={url.registrationAction} method="post">
|
||||
<UserProfileFormFields
|
||||
{...{
|
||||
@ -85,7 +85,7 @@ export default function Register(props: RegisterProps) {
|
||||
function TermsAcceptance(props: {
|
||||
i18n: I18n;
|
||||
getClassName: ReturnType<typeof useGetClassName>["getClassName"];
|
||||
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
||||
messagesPerField: Pick<KcContext["messagesPerField"], "existsError" | "get">;
|
||||
}) {
|
||||
const { i18n, getClassName, messagesPerField } = props;
|
||||
|
||||
|
@ -29,7 +29,7 @@ export default function UpdateEmail(props: UpdateEmailProps) {
|
||||
<Template
|
||||
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
||||
displayMessage={messagesPerField.exists("global")}
|
||||
displayRequiredFields={true}
|
||||
displayRequiredFields
|
||||
headerNode={msg("updateEmailTitle")}
|
||||
>
|
||||
<form id="kc-update-email-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
|
||||
|
@ -275,7 +275,7 @@ function LogoutOtherSessions(props: { i18n: I18n; getClassName: ReturnType<typeo
|
||||
<div className={getClassName("kcFormOptionsWrapperClass")}>
|
||||
<div className="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" checked />
|
||||
<input type="checkbox" id="logout-sessions" name="logout-sessions" value="on" defaultChecked={true} />
|
||||
{msg("logoutOtherSessions")}
|
||||
</label>
|
||||
</div>
|
||||
|
@ -5,8 +5,8 @@ import { useI18n } from "./i18n";
|
||||
import { useDownloadTerms } from "../../dist/login/lib/useDownloadTerms";
|
||||
import tos_en_url from "./tos_en.md";
|
||||
import tos_fr_url from "./tos_fr.md";
|
||||
|
||||
const DefaultTemplate = lazy(() => import("../../dist/login/Template"));
|
||||
import Template from "../../dist/login/Template";
|
||||
import UserProfileFormFields from "../../dist/login/UserProfileFormFields";
|
||||
|
||||
export default function KcApp(props: { kcContext: KcContext }) {
|
||||
const { kcContext } = props;
|
||||
@ -42,7 +42,7 @@ export default function KcApp(props: { kcContext: KcContext }) {
|
||||
{(() => {
|
||||
switch (kcContext.pageId) {
|
||||
default:
|
||||
return <Fallback {...{ kcContext, i18n }} Template={DefaultTemplate} doUseDefaultCss={true} />;
|
||||
return <Fallback {...{ kcContext, i18n, Template, UserProfileFormFields }} doUseDefaultCss={true} />;
|
||||
}
|
||||
})()}
|
||||
</Suspense>
|
||||
|
@ -1,24 +0,0 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
|
||||
const pageId = "register-user-profile.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
@ -1,24 +0,0 @@
|
||||
import React from "react";
|
||||
import type { ComponentMeta } from "@storybook/react";
|
||||
import { createPageStory } from "../createPageStory";
|
||||
|
||||
const pageId = "update-user-profile.ftl";
|
||||
|
||||
const { PageStory } = createPageStory({ pageId });
|
||||
|
||||
const meta: ComponentMeta<any> = {
|
||||
title: `login/${pageId}`,
|
||||
component: PageStory,
|
||||
parameters: {
|
||||
viewMode: "story",
|
||||
previewTabs: {
|
||||
"storybook/docs/panel": {
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default = () => <PageStory />;
|
Reference in New Issue
Block a user