keycloakify 10.1.3 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/account/KcContext/kcContextMocks.js +18 -6
- package/account/KcContext/kcContextMocks.js.map +1 -1
- package/account/Template.js +8 -26
- package/account/Template.js.map +1 -1
- package/account/Template.useInitialize.d.ts +12 -0
- package/account/Template.useInitialize.js +20 -0
- package/account/Template.useInitialize.js.map +1 -0
- package/account/i18n/index.d.ts +5 -5
- package/account/i18n/index.js +1 -1
- package/account/i18n/index.js.map +1 -1
- package/account/i18n/messages_defaultSet/types.d.ts +3 -0
- package/account/i18n/messages_defaultSet/types.js +26 -0
- package/account/i18n/messages_defaultSet/types.js.map +1 -0
- package/account/i18n/noJsx/GenericI18n_noJsx.d.ts +63 -0
- package/account/i18n/noJsx/GenericI18n_noJsx.js +2 -0
- package/account/i18n/noJsx/GenericI18n_noJsx.js.map +1 -0
- package/account/i18n/noJsx/getI18n.d.ts +41 -0
- package/account/i18n/noJsx/getI18n.js +207 -0
- package/account/i18n/noJsx/getI18n.js.map +1 -0
- package/account/i18n/noJsx/i18nBuilder.d.ts +18 -0
- package/account/i18n/noJsx/i18nBuilder.js +27 -0
- package/account/i18n/noJsx/i18nBuilder.js.map +1 -0
- package/account/i18n/noJsx/index.d.ts +3 -0
- package/account/i18n/noJsx/index.js +2 -0
- package/account/i18n/noJsx/index.js.map +1 -0
- package/account/i18n/withJsx/GenericI18n.d.ts +72 -0
- package/account/i18n/withJsx/GenericI18n.js +5 -0
- package/account/i18n/withJsx/GenericI18n.js.map +1 -0
- package/account/i18n/withJsx/i18nBuilder.d.ts +18 -0
- package/account/i18n/withJsx/i18nBuilder.js +27 -0
- package/account/i18n/withJsx/i18nBuilder.js.map +1 -0
- package/account/i18n/withJsx/index.d.ts +3 -0
- package/account/i18n/withJsx/index.js +2 -0
- package/account/i18n/withJsx/index.js.map +1 -0
- package/account/i18n/withJsx/useI18n.d.ts +27 -0
- package/account/i18n/{useI18n.js → withJsx/useI18n.js} +6 -4
- package/account/i18n/withJsx/useI18n.js.map +1 -0
- package/account/index.d.ts +1 -1
- package/account/index.js +1 -1
- package/account/index.js.map +1 -1
- package/account/pages/Totp.js +3 -2
- package/account/pages/Totp.js.map +1 -1
- package/bin/246.index.js +191 -191
- package/bin/499.index.js +252 -68
- package/bin/main.js +1 -1
- package/lib/kcSanitize/HtmlPolicyBuilder.d.ts +28 -0
- package/lib/kcSanitize/HtmlPolicyBuilder.js +209 -0
- package/lib/kcSanitize/HtmlPolicyBuilder.js.map +1 -0
- package/lib/kcSanitize/KcSanitizer.d.ts +12 -0
- package/lib/kcSanitize/KcSanitizer.js +46 -0
- package/lib/kcSanitize/KcSanitizer.js.map +1 -0
- package/lib/kcSanitize/KcSanitizerPolicy.d.ts +24 -0
- package/lib/kcSanitize/KcSanitizerPolicy.js +149 -0
- package/lib/kcSanitize/KcSanitizerPolicy.js.map +1 -0
- package/lib/kcSanitize/index.d.ts +1 -0
- package/lib/kcSanitize/index.js +5 -0
- package/lib/kcSanitize/index.js.map +1 -0
- package/login/KcContext/kcContextMocks.js +24 -6
- package/login/KcContext/kcContextMocks.js.map +1 -1
- package/login/Template.js +7 -7
- package/login/Template.js.map +1 -1
- package/login/{Template.useStylesAndScripts.d.ts → Template.useInitialize.d.ts} +1 -4
- package/login/{Template.useStylesAndScripts.js → Template.useInitialize.js} +5 -23
- package/login/Template.useInitialize.js.map +1 -0
- package/login/i18n/index.d.ts +5 -5
- package/login/i18n/index.js +1 -1
- package/login/i18n/index.js.map +1 -1
- package/login/i18n/messages_defaultSet/types.d.ts +3 -0
- package/login/i18n/messages_defaultSet/types.js +33 -0
- package/login/i18n/messages_defaultSet/types.js.map +1 -0
- package/login/i18n/noJsx/GenericI18n_noJsx.d.ts +63 -0
- package/login/i18n/noJsx/GenericI18n_noJsx.js +2 -0
- package/login/i18n/noJsx/GenericI18n_noJsx.js.map +1 -0
- package/login/i18n/noJsx/getI18n.d.ts +41 -0
- package/login/i18n/noJsx/getI18n.js +207 -0
- package/login/i18n/noJsx/getI18n.js.map +1 -0
- package/login/i18n/noJsx/i18nBuilder.d.ts +18 -0
- package/login/i18n/noJsx/i18nBuilder.js +27 -0
- package/login/i18n/noJsx/i18nBuilder.js.map +1 -0
- package/login/i18n/noJsx/index.d.ts +3 -0
- package/login/i18n/noJsx/index.js +2 -0
- package/login/i18n/noJsx/index.js.map +1 -0
- package/login/i18n/withJsx/GenericI18n.d.ts +72 -0
- package/login/i18n/withJsx/GenericI18n.js +5 -0
- package/login/i18n/withJsx/GenericI18n.js.map +1 -0
- package/login/i18n/withJsx/i18nBuilder.d.ts +18 -0
- package/login/i18n/withJsx/i18nBuilder.js +27 -0
- package/login/i18n/withJsx/i18nBuilder.js.map +1 -0
- package/login/i18n/withJsx/index.d.ts +3 -0
- package/login/i18n/withJsx/index.js +2 -0
- package/login/i18n/withJsx/index.js.map +1 -0
- package/login/i18n/withJsx/useI18n.d.ts +27 -0
- package/login/i18n/{useI18n.js → withJsx/useI18n.js} +6 -4
- package/login/i18n/withJsx/useI18n.js.map +1 -0
- package/login/index.d.ts +1 -1
- package/login/index.js +1 -1
- package/login/index.js.map +1 -1
- package/login/lib/useUserProfileForm.d.ts +1 -1
- package/login/lib/useUserProfileForm.js +2 -1
- package/login/lib/useUserProfileForm.js.map +1 -1
- package/login/pages/Error.js +2 -1
- package/login/pages/Error.js.map +1 -1
- package/login/pages/Info.js +4 -3
- package/login/pages/Info.js.map +1 -1
- package/login/pages/Login.js +4 -3
- package/login/pages/Login.js.map +1 -1
- package/login/pages/LoginConfigTotp.js +3 -2
- package/login/pages/LoginConfigTotp.js.map +1 -1
- package/login/pages/LoginOtp.js +2 -1
- package/login/pages/LoginOtp.js.map +1 -1
- package/login/pages/LoginPassword.js +2 -1
- package/login/pages/LoginPassword.js.map +1 -1
- package/login/pages/LoginRecoveryAuthnCodeInput.js +2 -1
- package/login/pages/LoginRecoveryAuthnCodeInput.js.map +1 -1
- package/login/pages/LoginResetPassword.js +2 -1
- package/login/pages/LoginResetPassword.js.map +1 -1
- package/login/pages/LoginUpdatePassword.js +3 -2
- package/login/pages/LoginUpdatePassword.js.map +1 -1
- package/login/pages/Register.js +2 -1
- package/login/pages/Register.js.map +1 -1
- package/package.json +110 -31
- package/src/account/KcContext/kcContextMocks.ts +49 -29
- package/src/account/Template.tsx +11 -32
- package/src/account/Template.useInitialize.ts +35 -0
- package/src/account/i18n/index.ts +5 -5
- package/src/account/i18n/messages_defaultSet/types.ts +30 -0
- package/src/account/i18n/noJsx/GenericI18n_noJsx.ts +64 -0
- package/src/account/i18n/noJsx/getI18n.tsx +341 -0
- package/src/account/i18n/noJsx/i18nBuilder.ts +117 -0
- package/src/account/i18n/noJsx/index.ts +3 -0
- package/src/account/i18n/withJsx/GenericI18n.tsx +81 -0
- package/src/account/i18n/withJsx/i18nBuilder.ts +117 -0
- package/src/account/i18n/withJsx/index.ts +3 -0
- package/src/{login/i18n → account/i18n/withJsx}/useI18n.tsx +43 -11
- package/src/account/index.ts +1 -1
- package/src/account/pages/Totp.tsx +3 -2
- package/src/bin/initialize-account-theme/src/multi-page/i18n.ts +10 -3
- package/src/bin/keycloakify/generateFtl/kcContextDeclarationTemplate.ftl +15 -0
- package/src/bin/keycloakify/generateResources/generateMessageProperties.ts +371 -121
- package/src/bin/keycloakify/generateResources/generateResources.ts +8 -6
- package/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts +61 -29
- package/src/bin/keycloakify/generateResources/generateResourcesForThemeVariant.ts +15 -9
- package/src/lib/kcSanitize/HtmlPolicyBuilder.ts +252 -0
- package/src/lib/kcSanitize/KcSanitizer.ts +60 -0
- package/src/lib/kcSanitize/KcSanitizerPolicy.ts +294 -0
- package/src/lib/kcSanitize/index.ts +5 -0
- package/src/login/KcContext/kcContextMocks.ts +54 -29
- package/src/login/Template.tsx +11 -17
- package/src/login/{Template.useStylesAndScripts.ts → Template.useInitialize.ts} +5 -29
- package/src/login/i18n/index.ts +5 -5
- package/src/login/i18n/messages_defaultSet/types.ts +37 -0
- package/src/login/i18n/noJsx/GenericI18n_noJsx.ts +64 -0
- package/src/login/i18n/noJsx/getI18n.tsx +341 -0
- package/src/login/i18n/noJsx/i18nBuilder.ts +117 -0
- package/src/login/i18n/noJsx/index.ts +3 -0
- package/src/login/i18n/withJsx/GenericI18n.tsx +81 -0
- package/src/login/i18n/withJsx/i18nBuilder.ts +117 -0
- package/src/login/i18n/withJsx/index.ts +3 -0
- package/src/{account/i18n → login/i18n/withJsx}/useI18n.tsx +43 -11
- package/src/login/index.ts +1 -1
- package/src/login/lib/useUserProfileForm.tsx +3 -2
- package/src/login/pages/Error.tsx +2 -1
- package/src/login/pages/Info.tsx +13 -10
- package/src/login/pages/Login.tsx +4 -3
- package/src/login/pages/LoginConfigTotp.tsx +3 -2
- package/src/login/pages/LoginOtp.tsx +2 -1
- package/src/login/pages/LoginPassword.tsx +2 -1
- package/src/login/pages/LoginRecoveryAuthnCodeInput.tsx +2 -1
- package/src/login/pages/LoginResetPassword.tsx +2 -1
- package/src/login/pages/LoginUpdatePassword.tsx +3 -2
- package/src/login/pages/Register.tsx +2 -1
- package/src/tools/useInsertScriptTags.ts +1 -1
- package/src/tools/vendor/dompurify.ts +3 -0
- package/stories/intro/intro.stories.tsx +0 -1
- package/tools/useInsertScriptTags.d.ts +1 -1
- package/tools/vendor/dompurify.d.ts +2 -0
- package/tools/vendor/dompurify.js +2 -0
- package/account/i18n/GenericI18n.d.ts +0 -6
- package/account/i18n/GenericI18n.js +0 -2
- package/account/i18n/GenericI18n.js.map +0 -1
- package/account/i18n/i18n.d.ts +0 -87
- package/account/i18n/i18n.js +0 -111
- package/account/i18n/i18n.js.map +0 -1
- package/account/i18n/useI18n.d.ts +0 -14
- package/account/i18n/useI18n.js.map +0 -1
- package/login/Template.useStylesAndScripts.js.map +0 -1
- package/login/i18n/GenericI18n.d.ts +0 -6
- package/login/i18n/GenericI18n.js +0 -2
- package/login/i18n/GenericI18n.js.map +0 -1
- package/login/i18n/i18n.d.ts +0 -87
- package/login/i18n/i18n.js +0 -113
- package/login/i18n/i18n.js.map +0 -1
- package/login/i18n/useI18n.d.ts +0 -14
- package/login/i18n/useI18n.js.map +0 -1
- package/src/account/i18n/GenericI18n.tsx +0 -6
- package/src/account/i18n/i18n.tsx +0 -250
- package/src/login/i18n/GenericI18n.tsx +0 -6
- package/src/login/i18n/i18n.tsx +0 -252
@@ -0,0 +1,294 @@
|
|
1
|
+
import { HtmlPolicyBuilder } from "./HtmlPolicyBuilder";
|
2
|
+
import type { DOMPurify as ofTypeDomPurify } from "keycloakify/tools/vendor/dompurify";
|
3
|
+
|
4
|
+
//implementation of java Sanitizer policy ( KeycloakSanitizerPolicy )
|
5
|
+
// All regex directly copied from the keycloak source but some of them changed slightly to work with typescript(ONSITE_URL and OFFSITE_URL)
|
6
|
+
// Also replaced ?i with "i" tag as second parameter of RegExp
|
7
|
+
//https://github.com/keycloak/keycloak/blob/8ce8a4ba089eef25a0e01f58e09890399477b9ef/services/src/main/java/org/keycloak/theme/KeycloakSanitizerPolicy.java#L29
|
8
|
+
export class KcSanitizerPolicy {
|
9
|
+
public static readonly COLOR_NAME = new RegExp(
|
10
|
+
"(?:aqua|black|blue|fuchsia|gray|grey|green|lime|maroon|navy|olive|purple|red|silver|teal|white|yellow)"
|
11
|
+
);
|
12
|
+
|
13
|
+
public static readonly COLOR_CODE = new RegExp(
|
14
|
+
"(?:#(?:[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?))"
|
15
|
+
);
|
16
|
+
|
17
|
+
public static readonly NUMBER_OR_PERCENT = new RegExp("[0-9]+%?");
|
18
|
+
|
19
|
+
public static readonly PARAGRAPH = new RegExp(
|
20
|
+
"(?:[\\p{L}\\p{N},'\\.\\s\\-_\\(\\)]|&[0-9]{2};)*",
|
21
|
+
"u" // Unicode flag for \p{L} and \p{N} in the pattern
|
22
|
+
);
|
23
|
+
|
24
|
+
public static readonly HTML_ID = new RegExp("[a-zA-Z0-9\\:\\-_\\.]+");
|
25
|
+
|
26
|
+
public static readonly HTML_TITLE = new RegExp(
|
27
|
+
"[\\p{L}\\p{N}\\s\\-_',:\\[\\]!\\./\\\\\\(\\)&]*",
|
28
|
+
"u" // Unicode flag for \p{L} and \p{N} in the pattern
|
29
|
+
);
|
30
|
+
|
31
|
+
public static readonly HTML_CLASS = new RegExp("[a-zA-Z0-9\\s,\\-_]+");
|
32
|
+
|
33
|
+
public static readonly ONSITE_URL = new RegExp(
|
34
|
+
"(?:[\\p{L}\\p{N}.#@\\$%+&;\\-_~,?=/!]+|#(\\w)+)",
|
35
|
+
"u" // Unicode flag for \p{L} and \p{N} in the pattern
|
36
|
+
);
|
37
|
+
|
38
|
+
public static readonly OFFSITE_URL = new RegExp(
|
39
|
+
"\\s*(?:(?:ht|f)tps?://|mailto:)[\\p{L}\\p{N}]+" +
|
40
|
+
"[\\p{L}\\p{N}\\p{Zs}.#@\\$%+&;:\\-_~,?=/!()]*\\s*",
|
41
|
+
"u" // Unicode flag for \p{L} and \p{N} in the pattern
|
42
|
+
);
|
43
|
+
|
44
|
+
public static readonly NUMBER = new RegExp(
|
45
|
+
"[+-]?(?:(?:[0-9]+(?:\\.[0-9]*)?)|\\.[0-9]+)"
|
46
|
+
);
|
47
|
+
public static readonly NAME = new RegExp("[a-zA-Z0-9\\-_\\$]+");
|
48
|
+
|
49
|
+
public static readonly ALIGN = new RegExp(
|
50
|
+
"\\b(center|left|right|justify|char)\\b",
|
51
|
+
"i" // Case-insensitive flag
|
52
|
+
);
|
53
|
+
|
54
|
+
public static readonly VALIGN = new RegExp(
|
55
|
+
"\\b(baseline|bottom|middle|top)\\b",
|
56
|
+
"i" // Case-insensitive flag
|
57
|
+
);
|
58
|
+
|
59
|
+
public static readonly HISTORY_BACK = new RegExp(
|
60
|
+
"(?:javascript:)?\\Qhistory.go(-1)\\E"
|
61
|
+
);
|
62
|
+
|
63
|
+
public static readonly ONE_CHAR = new RegExp(
|
64
|
+
".?",
|
65
|
+
"s" // Dotall flag for . to match newlines
|
66
|
+
);
|
67
|
+
|
68
|
+
private static COLOR_NAME_OR_COLOR_CODE(s: string): boolean {
|
69
|
+
return (
|
70
|
+
KcSanitizerPolicy.COLOR_NAME.test(s) || KcSanitizerPolicy.COLOR_CODE.test(s)
|
71
|
+
);
|
72
|
+
}
|
73
|
+
|
74
|
+
private static ONSITE_OR_OFFSITE_URL(s: string): boolean {
|
75
|
+
return (
|
76
|
+
KcSanitizerPolicy.ONSITE_URL.test(s) || KcSanitizerPolicy.OFFSITE_URL.test(s)
|
77
|
+
);
|
78
|
+
}
|
79
|
+
|
80
|
+
public static sanitize(
|
81
|
+
html: string,
|
82
|
+
dependencyInjections: Partial<{
|
83
|
+
DOMPurify: typeof ofTypeDomPurify;
|
84
|
+
}>
|
85
|
+
): string {
|
86
|
+
return new HtmlPolicyBuilder(dependencyInjections)
|
87
|
+
.allowWithoutAttributes("span")
|
88
|
+
|
89
|
+
.allowAttributes("id")
|
90
|
+
.matching(this.HTML_ID)
|
91
|
+
.globally()
|
92
|
+
|
93
|
+
.allowAttributes("class")
|
94
|
+
.matching(this.HTML_CLASS)
|
95
|
+
.globally()
|
96
|
+
|
97
|
+
.allowAttributes("lang")
|
98
|
+
.matching(/[a-zA-Z]{2,20}/)
|
99
|
+
.globally()
|
100
|
+
|
101
|
+
.allowAttributes("title")
|
102
|
+
.matching(this.HTML_TITLE)
|
103
|
+
.globally()
|
104
|
+
|
105
|
+
.allowStyling()
|
106
|
+
|
107
|
+
.allowAttributes("align")
|
108
|
+
.matching(this.ALIGN)
|
109
|
+
.onElements("p")
|
110
|
+
|
111
|
+
.allowAttributes("for")
|
112
|
+
.matching(this.HTML_ID)
|
113
|
+
.onElements("label")
|
114
|
+
|
115
|
+
.allowAttributes("color")
|
116
|
+
.matching(this.COLOR_NAME_OR_COLOR_CODE)
|
117
|
+
.onElements("font")
|
118
|
+
|
119
|
+
.allowAttributes("face")
|
120
|
+
.matching(/[\w;, \-]+/)
|
121
|
+
.onElements("font")
|
122
|
+
|
123
|
+
.allowAttributes("size")
|
124
|
+
.matching(this.NUMBER)
|
125
|
+
.onElements("font")
|
126
|
+
|
127
|
+
.allowAttributes("href")
|
128
|
+
.matching(this.ONSITE_OR_OFFSITE_URL)
|
129
|
+
.onElements("a")
|
130
|
+
|
131
|
+
.allowStandardUrlProtocols()
|
132
|
+
.allowAttributes("nohref")
|
133
|
+
.onElements("a")
|
134
|
+
|
135
|
+
.allowAttributes("name")
|
136
|
+
.matching(this.NAME)
|
137
|
+
.onElements("a")
|
138
|
+
|
139
|
+
.allowAttributes("onfocus", "onblur", "onclick", "onmousedown", "onmouseup")
|
140
|
+
.matching(this.HISTORY_BACK)
|
141
|
+
.onElements("a")
|
142
|
+
|
143
|
+
.requireRelNofollowOnLinks()
|
144
|
+
.allowAttributes("src")
|
145
|
+
.matching(this.ONSITE_OR_OFFSITE_URL)
|
146
|
+
.onElements("img")
|
147
|
+
|
148
|
+
.allowAttributes("name")
|
149
|
+
.matching(this.NAME)
|
150
|
+
.onElements("img")
|
151
|
+
|
152
|
+
.allowAttributes("alt")
|
153
|
+
.matching(this.PARAGRAPH)
|
154
|
+
.onElements("img")
|
155
|
+
|
156
|
+
.allowAttributes("border", "hspace", "vspace")
|
157
|
+
.matching(this.NUMBER)
|
158
|
+
.onElements("img")
|
159
|
+
|
160
|
+
.allowAttributes("border", "cellpadding", "cellspacing")
|
161
|
+
.matching(this.NUMBER)
|
162
|
+
.onElements("table")
|
163
|
+
|
164
|
+
.allowAttributes("bgcolor")
|
165
|
+
.matching(this.COLOR_NAME_OR_COLOR_CODE)
|
166
|
+
.onElements("table")
|
167
|
+
|
168
|
+
.allowAttributes("background")
|
169
|
+
.matching(this.ONSITE_URL)
|
170
|
+
.onElements("table")
|
171
|
+
|
172
|
+
.allowAttributes("align")
|
173
|
+
.matching(this.ALIGN)
|
174
|
+
.onElements("table")
|
175
|
+
|
176
|
+
.allowAttributes("noresize")
|
177
|
+
.matching(new RegExp("noresize", "i"))
|
178
|
+
.onElements("table")
|
179
|
+
|
180
|
+
.allowAttributes("background")
|
181
|
+
.matching(this.ONSITE_URL)
|
182
|
+
.onElements("td", "th", "tr")
|
183
|
+
|
184
|
+
.allowAttributes("bgcolor")
|
185
|
+
.matching(this.COLOR_NAME_OR_COLOR_CODE)
|
186
|
+
.onElements("td", "th")
|
187
|
+
|
188
|
+
.allowAttributes("abbr")
|
189
|
+
.matching(this.PARAGRAPH)
|
190
|
+
.onElements("td", "th")
|
191
|
+
|
192
|
+
.allowAttributes("axis", "headers")
|
193
|
+
.matching(this.NAME)
|
194
|
+
.onElements("td", "th")
|
195
|
+
|
196
|
+
.allowAttributes("scope")
|
197
|
+
.matching(new RegExp("(?:row|col)(?:group)?", "i"))
|
198
|
+
.onElements("td", "th")
|
199
|
+
|
200
|
+
.allowAttributes("nowrap")
|
201
|
+
.onElements("td", "th")
|
202
|
+
|
203
|
+
.allowAttributes("height", "width")
|
204
|
+
.matching(this.NUMBER_OR_PERCENT)
|
205
|
+
.onElements("table", "td", "th", "tr", "img")
|
206
|
+
|
207
|
+
.allowAttributes("align")
|
208
|
+
.matching(this.ALIGN)
|
209
|
+
.onElements(
|
210
|
+
"thead",
|
211
|
+
"tbody",
|
212
|
+
"tfoot",
|
213
|
+
"img",
|
214
|
+
"td",
|
215
|
+
"th",
|
216
|
+
"tr",
|
217
|
+
"colgroup",
|
218
|
+
"col"
|
219
|
+
)
|
220
|
+
|
221
|
+
.allowAttributes("valign")
|
222
|
+
.matching(this.VALIGN)
|
223
|
+
.onElements("thead", "tbody", "tfoot", "td", "th", "tr", "colgroup", "col")
|
224
|
+
|
225
|
+
.allowAttributes("charoff")
|
226
|
+
.matching(this.NUMBER_OR_PERCENT)
|
227
|
+
.onElements("td", "th", "tr", "colgroup", "col", "thead", "tbody", "tfoot")
|
228
|
+
|
229
|
+
.allowAttributes("char")
|
230
|
+
.matching(this.ONE_CHAR)
|
231
|
+
.onElements("td", "th", "tr", "colgroup", "col", "thead", "tbody", "tfoot")
|
232
|
+
|
233
|
+
.allowAttributes("colspan", "rowspan")
|
234
|
+
.matching(this.NUMBER)
|
235
|
+
.onElements("td", "th")
|
236
|
+
|
237
|
+
.allowAttributes("span", "width")
|
238
|
+
.matching(this.NUMBER_OR_PERCENT)
|
239
|
+
.onElements("colgroup", "col")
|
240
|
+
.allowElements(
|
241
|
+
"a",
|
242
|
+
"label",
|
243
|
+
"noscript",
|
244
|
+
"h1",
|
245
|
+
"h2",
|
246
|
+
"h3",
|
247
|
+
"h4",
|
248
|
+
"h5",
|
249
|
+
"h6",
|
250
|
+
"p",
|
251
|
+
"i",
|
252
|
+
"b",
|
253
|
+
"u",
|
254
|
+
"strong",
|
255
|
+
"em",
|
256
|
+
"small",
|
257
|
+
"big",
|
258
|
+
"pre",
|
259
|
+
"code",
|
260
|
+
"cite",
|
261
|
+
"samp",
|
262
|
+
"sub",
|
263
|
+
"sup",
|
264
|
+
"strike",
|
265
|
+
"center",
|
266
|
+
"blockquote",
|
267
|
+
"hr",
|
268
|
+
"br",
|
269
|
+
"col",
|
270
|
+
"font",
|
271
|
+
"map",
|
272
|
+
"span",
|
273
|
+
"div",
|
274
|
+
"img",
|
275
|
+
"ul",
|
276
|
+
"ol",
|
277
|
+
"li",
|
278
|
+
"dd",
|
279
|
+
"dt",
|
280
|
+
"dl",
|
281
|
+
"tbody",
|
282
|
+
"thead",
|
283
|
+
"tfoot",
|
284
|
+
"table",
|
285
|
+
"td",
|
286
|
+
"th",
|
287
|
+
"tr",
|
288
|
+
"colgroup",
|
289
|
+
"fieldset",
|
290
|
+
"legend"
|
291
|
+
)
|
292
|
+
.apply(html);
|
293
|
+
}
|
294
|
+
}
|
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
import { id } from "tsafe/id";
|
8
8
|
import { assert, type Equals } from "tsafe/assert";
|
9
9
|
import { BASE_URL } from "keycloakify/lib/BASE_URL";
|
10
|
+
import type { LanguageTag } from "keycloakify/login/i18n/messages_defaultSet/types";
|
10
11
|
|
11
12
|
const attributesByName = Object.fromEntries(
|
12
13
|
id<Attribute[]>([
|
@@ -116,35 +117,59 @@ export const kcContextCommonMock: KcContext.Common = {
|
|
116
117
|
}
|
117
118
|
},
|
118
119
|
locale: {
|
119
|
-
supported:
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
120
|
+
supported: (
|
121
|
+
[
|
122
|
+
/* spell-checker: disable */
|
123
|
+
["de", "Deutsch"],
|
124
|
+
["no", "Norsk"],
|
125
|
+
["ru", "Русский"],
|
126
|
+
["sv", "Svenska"],
|
127
|
+
["pt-BR", "Português (Brasil)"],
|
128
|
+
["lt", "Lietuvių"],
|
129
|
+
["en", "English"],
|
130
|
+
["it", "Italiano"],
|
131
|
+
["fr", "Français"],
|
132
|
+
["zh-CN", "中文简体"],
|
133
|
+
["es", "Español"],
|
134
|
+
["cs", "Čeština"],
|
135
|
+
["ja", "日本語"],
|
136
|
+
["sk", "Slovenčina"],
|
137
|
+
["pl", "Polski"],
|
138
|
+
["ca", "Català"],
|
139
|
+
["nl", "Nederlands"],
|
140
|
+
["tr", "Türkçe"],
|
141
|
+
["ar", "العربية"],
|
142
|
+
["da", "Dansk"],
|
143
|
+
["el", "Ελληνικά"],
|
144
|
+
["fa", "فارسی"],
|
145
|
+
["fi", "Suomi"],
|
146
|
+
["hu", "Magyar"],
|
147
|
+
["ka", "ქართული"],
|
148
|
+
["lv", "Latviešu"],
|
149
|
+
["pt", "Português"],
|
150
|
+
["th", "ไทย"],
|
151
|
+
["uk", "Українська"],
|
152
|
+
["zh-TW", "中文繁體"]
|
153
|
+
/* spell-checker: enable */
|
154
|
+
] as const
|
155
|
+
).map(([languageTag, label]) => {
|
156
|
+
{
|
157
|
+
type Got = typeof languageTag;
|
158
|
+
type Expected = LanguageTag;
|
159
|
+
|
160
|
+
type Missing = Exclude<Expected, Got>;
|
161
|
+
type Unexpected = Exclude<Got, Expected>;
|
162
|
+
|
163
|
+
assert<Equals<Missing, never>>;
|
164
|
+
assert<Equals<Unexpected, never>>;
|
165
|
+
}
|
166
|
+
|
167
|
+
return {
|
168
|
+
languageTag,
|
169
|
+
label,
|
170
|
+
url: "https://gist.github.com/garronej/52baaca1bb925f2296ab32741e062b8e"
|
171
|
+
} as const;
|
172
|
+
}),
|
148
173
|
|
149
174
|
currentLanguageTag: "en"
|
150
175
|
},
|
package/src/login/Template.tsx
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
import { useEffect } from "react";
|
2
|
-
import { assert } from "keycloakify/tools/assert";
|
3
2
|
import { clsx } from "keycloakify/tools/clsx";
|
3
|
+
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
4
4
|
import type { TemplateProps } from "keycloakify/login/TemplateProps";
|
5
5
|
import { getKcClsx } from "keycloakify/login/lib/kcClsx";
|
6
6
|
import { useSetClassName } from "keycloakify/tools/useSetClassName";
|
7
|
-
import {
|
7
|
+
import { useInitialize } from "keycloakify/login/Template.useInitialize";
|
8
8
|
import type { I18n } from "./i18n";
|
9
9
|
import type { KcContext } from "./KcContext";
|
10
10
|
|
@@ -27,9 +27,9 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|
27
27
|
|
28
28
|
const { kcClsx } = getKcClsx({ doUseDefaultCss, classes });
|
29
29
|
|
30
|
-
const { msg, msgStr,
|
30
|
+
const { msg, msgStr, currentLanguage, enabledLanguages } = i18n;
|
31
31
|
|
32
|
-
const { realm,
|
32
|
+
const { realm, auth, url, message, isAppInitiatedAction } = kcContext;
|
33
33
|
|
34
34
|
useEffect(() => {
|
35
35
|
document.title = documentTitle ?? msgStr("loginTitle", kcContext.realm.displayName);
|
@@ -45,7 +45,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|
45
45
|
className: bodyClassName ?? kcClsx("kcBodyClass")
|
46
46
|
});
|
47
47
|
|
48
|
-
const { isReadyToRender } =
|
48
|
+
const { isReadyToRender } = useInitialize({ kcContext, doUseDefaultCss });
|
49
49
|
|
50
50
|
if (!isReadyToRender) {
|
51
51
|
return null;
|
@@ -58,10 +58,9 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|
58
58
|
{msg("loginTitleHtml", realm.displayNameHtml)}
|
59
59
|
</div>
|
60
60
|
</div>
|
61
|
-
|
62
61
|
<div className={kcClsx("kcFormCardClass")}>
|
63
62
|
<header className={kcClsx("kcFormHeaderClass")}>
|
64
|
-
{
|
63
|
+
{enabledLanguages.length > 1 && (
|
65
64
|
<div className={kcClsx("kcLocaleMainClass")} id="kc-locale">
|
66
65
|
<div id="kc-locale-wrapper" className={kcClsx("kcLocaleWrapperClass")}>
|
67
66
|
<div id="kc-locale-dropdown" className={clsx("menu-button-links", kcClsx("kcLocaleDropDownClass"))}>
|
@@ -73,7 +72,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|
73
72
|
aria-expanded="false"
|
74
73
|
aria-controls="language-switch1"
|
75
74
|
>
|
76
|
-
{
|
75
|
+
{currentLanguage.label}
|
77
76
|
</button>
|
78
77
|
<ul
|
79
78
|
role="menu"
|
@@ -83,15 +82,10 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|
83
82
|
id="language-switch1"
|
84
83
|
className={kcClsx("kcLocaleListClass")}
|
85
84
|
>
|
86
|
-
{
|
85
|
+
{enabledLanguages.map(({ languageTag, label, href }, i) => (
|
87
86
|
<li key={languageTag} className={kcClsx("kcLocaleListItemClass")} role="none">
|
88
|
-
<a
|
89
|
-
|
90
|
-
id={`language-${i + 1}`}
|
91
|
-
className={kcClsx("kcLocaleItemClass")}
|
92
|
-
href={getChangeLocaleUrl(languageTag)}
|
93
|
-
>
|
94
|
-
{labelBySupportedLanguageTag[languageTag]}
|
87
|
+
<a role="menuitem" id={`language-${i + 1}`} className={kcClsx("kcLocaleItemClass")} href={href}>
|
88
|
+
{label}
|
95
89
|
</a>
|
96
90
|
</li>
|
97
91
|
))}
|
@@ -152,7 +146,7 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|
152
146
|
<span
|
153
147
|
className={kcClsx("kcAlertTitleClass")}
|
154
148
|
dangerouslySetInnerHTML={{
|
155
|
-
__html: message.summary
|
149
|
+
__html: kcSanitize(message.summary)
|
156
150
|
}}
|
157
151
|
/>
|
158
152
|
</div>
|
@@ -2,7 +2,7 @@ import { useEffect } from "react";
|
|
2
2
|
import { assert } from "keycloakify/tools/assert";
|
3
3
|
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
4
4
|
import { useInsertLinkTags } from "keycloakify/tools/useInsertLinkTags";
|
5
|
-
import { KcContext } from "keycloakify/login/KcContext
|
5
|
+
import type { KcContext } from "keycloakify/login/KcContext";
|
6
6
|
|
7
7
|
export type KcContextLike = {
|
8
8
|
url: {
|
@@ -10,34 +10,19 @@ export type KcContextLike = {
|
|
10
10
|
resourcesPath: string;
|
11
11
|
ssoLoginInOtherTabsUrl: string;
|
12
12
|
};
|
13
|
-
locale?: {
|
14
|
-
currentLanguageTag: string;
|
15
|
-
};
|
16
13
|
scripts: string[];
|
17
14
|
};
|
18
15
|
|
19
16
|
assert<keyof KcContextLike extends keyof KcContext ? true : false>();
|
20
17
|
assert<KcContext extends KcContextLike ? true : false>();
|
21
18
|
|
22
|
-
export function
|
19
|
+
export function useInitialize(params: {
|
23
20
|
kcContext: KcContextLike;
|
24
21
|
doUseDefaultCss: boolean;
|
25
22
|
}) {
|
26
23
|
const { kcContext, doUseDefaultCss } = params;
|
27
24
|
|
28
|
-
const { url,
|
29
|
-
|
30
|
-
useEffect(() => {
|
31
|
-
const { currentLanguageTag } = locale ?? {};
|
32
|
-
|
33
|
-
if (currentLanguageTag === undefined) {
|
34
|
-
return;
|
35
|
-
}
|
36
|
-
|
37
|
-
const html = document.querySelector("html");
|
38
|
-
assert(html !== null);
|
39
|
-
html.lang = currentLanguageTag;
|
40
|
-
}, []);
|
25
|
+
const { url, scripts } = kcContext;
|
41
26
|
|
42
27
|
const { areAllStyleSheetsLoaded } = useInsertLinkTags({
|
43
28
|
componentOrHookName: "Template",
|
@@ -55,14 +40,7 @@ export function useStylesAndScripts(params: {
|
|
55
40
|
const { insertScriptTags } = useInsertScriptTags({
|
56
41
|
componentOrHookName: "Template",
|
57
42
|
scriptTags: [
|
58
|
-
|
59
|
-
type: "importmap",
|
60
|
-
textContent: JSON.stringify({
|
61
|
-
imports: {
|
62
|
-
rfc4648: `${url.resourcesCommonPath}/node_modules/rfc4648/lib/rfc4648.js`
|
63
|
-
}
|
64
|
-
})
|
65
|
-
},
|
43
|
+
// NOTE: The importmap is added in by the FTL script because it's too late to add it here.
|
66
44
|
{
|
67
45
|
type: "module",
|
68
46
|
src: `${url.resourcesPath}/js/menu-button-links.js`
|
@@ -76,9 +54,7 @@ export function useStylesAndScripts(params: {
|
|
76
54
|
textContent: `
|
77
55
|
import { checkCookiesAndSetTimer } from "${url.resourcesPath}/js/authChecker.js";
|
78
56
|
|
79
|
-
checkCookiesAndSetTimer(
|
80
|
-
"${url.ssoLoginInOtherTabsUrl}"
|
81
|
-
);
|
57
|
+
checkCookiesAndSetTimer("${url.ssoLoginInOtherTabsUrl}");
|
82
58
|
`
|
83
59
|
}
|
84
60
|
]
|
package/src/login/i18n/index.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
import type {
|
3
|
-
|
4
|
-
|
5
|
-
export
|
1
|
+
export * from "./withJsx";
|
2
|
+
import type { GenericI18n } from "./withJsx/GenericI18n";
|
3
|
+
import type { MessageKey as MessageKey_defaultSet } from "./messages_defaultSet/types";
|
4
|
+
/** INTERNAL: DO NOT IMPORT THIS */
|
5
|
+
export type I18n = GenericI18n<MessageKey_defaultSet, string>;
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
export const languageTags = [
|
3
|
+
"ar",
|
4
|
+
"ca",
|
5
|
+
"cs",
|
6
|
+
"da",
|
7
|
+
"de",
|
8
|
+
"el",
|
9
|
+
"en",
|
10
|
+
"es",
|
11
|
+
"fa",
|
12
|
+
"fi",
|
13
|
+
"fr",
|
14
|
+
"hu",
|
15
|
+
"it",
|
16
|
+
"ja",
|
17
|
+
"ka",
|
18
|
+
"lt",
|
19
|
+
"lv",
|
20
|
+
"nl",
|
21
|
+
"no",
|
22
|
+
"pl",
|
23
|
+
"pt",
|
24
|
+
"pt-BR",
|
25
|
+
"ru",
|
26
|
+
"sk",
|
27
|
+
"sv",
|
28
|
+
"th",
|
29
|
+
"tr",
|
30
|
+
"uk",
|
31
|
+
"zh-CN",
|
32
|
+
"zh-TW"
|
33
|
+
] as const;
|
34
|
+
|
35
|
+
export type LanguageTag = typeof languageTags[number];
|
36
|
+
|
37
|
+
export type MessageKey = keyof typeof import("./en")["default"];
|
@@ -0,0 +1,64 @@
|
|
1
|
+
export type GenericI18n_noJsx<MessageKey extends string, LanguageTag extends string> = {
|
2
|
+
currentLanguage: {
|
3
|
+
/**
|
4
|
+
* e.g: "en", "fr", "zh-CN"
|
5
|
+
*
|
6
|
+
* The current language
|
7
|
+
*/
|
8
|
+
languageTag: LanguageTag;
|
9
|
+
/**
|
10
|
+
* e.g: "English", "Français", "中文(简体)"
|
11
|
+
*
|
12
|
+
* The current language
|
13
|
+
*/
|
14
|
+
label: string;
|
15
|
+
};
|
16
|
+
/**
|
17
|
+
* Array of languages enabled on the realm.
|
18
|
+
*/
|
19
|
+
enabledLanguages: {
|
20
|
+
languageTag: LanguageTag;
|
21
|
+
label: string;
|
22
|
+
href: string;
|
23
|
+
}[];
|
24
|
+
/**
|
25
|
+
*
|
26
|
+
* Examples assuming currentLanguageTag === "en"
|
27
|
+
* {
|
28
|
+
* en: {
|
29
|
+
* "access-denied": "Access denied",
|
30
|
+
* "impersonateTitleHtml": "<strong>{0}</strong> Impersonate User",
|
31
|
+
* "bar": "Bar {0}"
|
32
|
+
* }
|
33
|
+
* }
|
34
|
+
*
|
35
|
+
* msgStr("access-denied") === "Access denied"
|
36
|
+
* msgStr("not-a-message-key") Throws an error
|
37
|
+
* msgStr("impersonateTitleHtml", "Foo") === "<strong>Foo</strong> Impersonate User"
|
38
|
+
* msgStr("${bar}", "<strong>c</strong>") === "Bar <strong>XXX</strong>"
|
39
|
+
* The html in the arg is partially escaped for security reasons, it might come from an untrusted source, it's not safe to render it as html.
|
40
|
+
*/
|
41
|
+
msgStr: (key: MessageKey, ...args: (string | undefined)[]) => string;
|
42
|
+
/**
|
43
|
+
* This is meant to be used when the key argument is variable, something that might have been configured by the user
|
44
|
+
* in the Keycloak admin for example.
|
45
|
+
*
|
46
|
+
* Examples assuming currentLanguageTag === "en"
|
47
|
+
* {
|
48
|
+
* en: {
|
49
|
+
* "access-denied": "Access denied",
|
50
|
+
* }
|
51
|
+
* }
|
52
|
+
*
|
53
|
+
* advancedMsgStr("${access-denied}") === advancedMsgStr("access-denied") === msgStr("access-denied") === "Access denied"
|
54
|
+
* advancedMsgStr("${not-a-message-key}") === advancedMsgStr("not-a-message-key") === "not-a-message-key"
|
55
|
+
*/
|
56
|
+
advancedMsgStr: (key: string, ...args: (string | undefined)[]) => string;
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Initially the messages are in english (fallback language).
|
60
|
+
* The translations in the current language are being fetched dynamically.
|
61
|
+
* This property is true while the translations are being fetched.
|
62
|
+
*/
|
63
|
+
isFetchingTranslations: boolean;
|
64
|
+
};
|