Compare commits

...

137 Commits

Author SHA1 Message Date
f9e5286603 Enable vion branch 2022-09-11 01:47:53 +02:00
68ab37e6a8 Bump version 2022-09-11 01:47:19 +02:00
ff328e9d56 #164 2022-09-11 01:46:53 +02:00
ba432f8841 Update garronej_modules_update 2022-09-04 19:09:53 +00:00
27aedbbc7f Create FUNDING.yml 2022-09-02 18:41:02 +02:00
360ed3dc4b Update garronej_modules_update 2022-08-26 16:39:19 +00:00
53fd8bd0ee Fmt (changelog ignore) 2022-08-26 16:12:55 +07:00
7e20ce4efa Remove warning about website beeing down (changelog ignore) 2022-08-26 15:52:28 +07:00
220d9d4d5e Update README.md 2022-08-04 14:15:23 +07:00
697171be1e Update ci.yaml 2022-08-02 14:23:47 +02:00
cf91c6fb8a Bump version (changelog ignore) 2022-08-02 09:07:11 +02:00
18a2e4ae04 Add extra field for u/loki344, in v6 such fields will have to be declared in the package.json #40 2022-08-02 09:06:29 +02:00
020823e933 Update garronej_modules_update 2022-07-28 04:44:01 +00:00
5879972924 Fix readme error (changelog ignore) 2022-07-28 04:38:04 +02:00
8031294230 Update ts-ci, remove changelog, use github release generated body instead 2022-07-28 04:32:27 +02:00
e0a6935c49 Bump version (changelog ignore) 2022-07-28 04:27:38 +02:00
3d581a5454 Support for React.lazy() #141 🎉 2022-07-28 04:24:10 +02:00
317ad8386c Attempt to activate renovate on project's landingpage (changelog ignore) 2022-07-23 17:45:04 +02:00
7ad5011280 Merge pull request #142 from InseeFrLab/renovate/configure
Configure Renovate
2022-07-23 16:10:40 +02:00
76990702f0 Add renovate.json
update
2022-07-23 16:09:14 +02:00
9a27824fe9 Only run the unit test in the CI pipeline (changelog ignore) 2022-07-22 18:25:09 +02:00
d877d90bf3 Writing tests for #141 2022-07-22 18:20:50 +02:00
0b790c47e6 fmt (changelog ignore) 2022-07-22 17:34:52 +02:00
6b49c8dd95 complete exhausive unit testing of replacer (changelog ignore) 2022-07-22 17:33:53 +02:00
e56f9b144e Improve unit tests (changelog ignore) 2022-07-22 17:22:23 +02:00
3c82944daf improve replacer unit test (changelog ignore) 2022-07-22 16:38:44 +02:00
5e3070a6c4 Improve unit test (changelog ignore) 2022-07-22 15:35:23 +02:00
a3a0e9eebe Add closest thing to a migration guide from v4 to v5 (changelog ignore) 2022-07-21 19:49:10 +02:00
c6593f03bc Update changelog v5.7.3 2022-07-17 01:34:02 +00:00
54dc4f650c Merge branch 'main' of https://github.com/InseeFrLab/keycloakify 2022-07-17 03:30:10 +02:00
dec7af0381 Bump version (changelog ignore) 2022-07-17 03:29:17 +02:00
ae001eea54 update evt and powerhooks (changelog ignore) 2022-07-17 03:28:35 +02:00
4d0e17a11e Update changelog v5.7.1 2022-07-17 03:28:34 +02:00
c708e619e9 Update changelog v5.7.2 2022-07-13 01:30:23 +00:00
2cceb3c929 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify 2022-07-13 03:27:38 +02:00
aee8704691 Bump version (changelog ignore) 2022-07-13 03:27:29 +02:00
43af60237b #135 2022-07-13 03:26:46 +02:00
e615479b41 Update changelog v5.7.1 2022-07-11 17:25:30 +00:00
973fb4d2d5 Bump version (changelog ignore) 2022-07-11 19:18:42 +02:00
964feae846 #134 2022-07-11 19:18:15 +02:00
ea3d8a5634 Update changelog v5.7.0 2022-07-07 11:56:00 +00:00
48aab2d92a Bump version (changelog ignore) 2022-07-07 13:53:20 +02:00
00eab73954 Merge pull request #120 from revolunet/logout
feat: add logout-confirm
2022-07-07 13:40:17 +02:00
5f6f8b12bd fix: use kcMessages 2022-07-07 01:05:18 +02:00
c2d4d6fd49 fix: add translations FR 2022-07-07 01:05:18 +02:00
04b660ff9b feat: add logout-confirm 2022-07-07 01:05:15 +02:00
c292d926be Update changelog v5.6.5 2022-07-06 10:01:52 +00:00
23b83ceef7 Bump version (changelog ignore) 2022-07-06 11:58:43 +02:00
1324648db6 Merge pull request #133 from bardius/fix/Issue-131-include-all-nested-folders-in-artifact-unzip
fix: Issue-131: include all nested folders in artifact unzip
2022-07-06 11:56:00 +02:00
735bff3348 Merge pull request #132 from bardius/fix/Issue-130-fix-equality-detection-of-nested-ftl-object-properties
fix: Issue-130: fix equality detection of nested ftl object property …
2022-07-06 11:55:18 +02:00
05a6aee782 fix: Issue-131: include all nested folders in artifact unzip 2022-07-06 11:44:25 +03:00
c7349c2556 fix: Issue-130: fix equality detection of nested ftl object property paths 2022-07-06 11:38:47 +03:00
51f3d06752 Update changelog v5.6.4 2022-07-06 00:08:51 +00:00
31759d86ab Bump version (changelog ignore) 2022-07-06 02:05:45 +02:00
7c6eed99d2 Fix login-register-email.ftl 2022-07-06 02:05:08 +02:00
bc4b0ec17d Update to Keycloak 18.0.2 for the test container 2022-07-06 01:30:30 +02:00
f766348b87 Update changelog v5.6.3 2022-07-03 22:05:43 +00:00
82281303d0 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify 2022-07-04 00:02:47 +02:00
1caa17beb0 Bump version (changelog ignore) 2022-07-04 00:02:39 +02:00
1c4d346f9f update powerhooks 2022-07-04 00:02:19 +02:00
4320efb049 Update changelog v5.6.2 2022-07-03 18:07:07 +00:00
a756423768 Bump version (changelog ignore) 2022-07-03 20:03:35 +02:00
8525fc74c0 Update powerhooks and EVT 2022-07-03 20:03:19 +02:00
30c0cc5aa8 Update changelog v5.6.1 2022-07-03 14:01:24 +00:00
b3bbd7c07d Bump version (changelog ignore) 2022-07-03 15:58:41 +02:00
09d4ba2bb0 Refactor (avoid using else) changelog ignore 2022-07-03 15:58:00 +02:00
30315027c1 Merge pull request #128 from Ann2827/pull
Fix bugs on error.ftl template
2022-07-03 15:52:42 +02:00
05acefe70e fix: bugs on error.ftl template 2022-07-02 11:01:14 +03:00
6c14758e33 Merge pull request #52 from InseeFrLab/main
Update fork
2022-07-02 10:39:39 +03:00
b93ec20119 Update changelog v5.6.0 2022-06-28 21:39:13 +00:00
ce04646576 Update React (changelog ignore) #127 2022-06-28 23:36:16 +02:00
9282dfe491 Bump version (changelog ignore) 2022-06-28 21:53:02 +02:00
fca6280bcc Merge pull request #127 from aidangilmore/add-totp-support
feat: add login-config-totp.ftl page
2022-06-28 21:49:04 +02:00
cdeb575ec6 Fix unknown algorithm name lookup in LoginConfigTotp 2022-06-28 15:21:09 -04:00
271dbe4fb7 Add totp config support 2022-06-28 14:37:17 -04:00
9a0337114d Update changelog v5.5.0 2022-06-28 04:54:43 +00:00
2d28f4eb55 Attempt to fix ci (changelog ignore) 2022-06-28 06:51:49 +02:00
f673927e16 [CI]: update npm-install (changelog ignore) 2022-06-28 06:45:59 +02:00
52896b82a9 Update yarn.lock (changelog ignore) 2022-06-28 06:40:51 +02:00
9d53ecb0cd Bump version (changelog ignore) 2022-06-28 06:37:05 +02:00
aec3ac32e5 Make it possible to redirect to login by repacing the url (should be default in most case) 2022-06-28 06:36:30 +02:00
f150f1568e Update changelog v5.4.7 2022-06-19 21:33:05 +00:00
309189c55d Bump version (changelog ignore) 2022-06-19 23:30:38 +02:00
f68c54cd3a #121 2022-06-19 23:30:05 +02:00
bef8545161 Merge pull request #48 from InseeFrLab/main
Update fork
2022-06-18 17:17:08 +03:00
c21cd14ac2 fmt 2022-06-17 18:25:54 +02:00
275d7f0072 Create CONTRIBUTING.md 2022-06-17 17:00:55 +02:00
58c8306cf4 Enable users to link keycloak in their own app 2022-06-17 16:32:20 +02:00
f782b684ad Update changelog v5.4.6 2022-06-16 23:51:09 +00:00
092b2a5f52 Bump version (changelog ignore) 2022-06-17 01:45:32 +02:00
42b2d40ad6 Update powerhooks (changelog ignore) 2022-06-17 01:45:17 +02:00
3f6fe6cfc0 Use keycloak 18.0.1 i18n resources #120 2022-06-17 01:43:14 +02:00
1abf542a74 Update changelog v5.4.5 2022-06-14 21:07:14 +00:00
c4720ca03d Bump version (changelog ignore) 2022-06-14 23:04:59 +02:00
4316878cce Merge pull request #119 from dro-sh/fix-locale-on-useFormValidationSlice
pass locale to getGetErrors to get correct messages
2022-06-14 23:04:28 +02:00
c180d75a83 pass locale to getGetErrors to get correct messages 2022-06-14 21:52:18 +03:00
4a040b32c0 Display downloads by month (changelog ignore) 2022-06-11 03:36:03 +02:00
ea330a1eef Update changelog v5.4.4 2022-06-05 03:49:23 +00:00
2451ba0a77 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify 2022-06-05 05:46:26 +02:00
2c276a56e5 Bump version (changelog ignore) 2022-06-05 05:46:20 +02:00
708030b8b5 Update powerhooks (changelog ignore) 2022-06-05 05:46:03 +02:00
d5fc0582bc Update changelog v5.4.3 2022-06-01 23:37:41 +00:00
f9dce82c83 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify 2022-06-02 01:32:57 +02:00
e82602f994 Bump version (changelog ignore) 2022-06-02 01:32:49 +02:00
1d36395e5a Update EVT and powerhook (changelog ignore) 2022-06-02 01:32:26 +02:00
8f8857bc22 Update changelog v5.4.2 2022-06-01 22:21:23 +00:00
226247b3b6 Bump version (changelog ignore) 2022-06-02 00:15:25 +02:00
b2ea5014f3 Update evt (changelog ignore) 2022-06-02 00:15:10 +02:00
48bc416aa7 Update powerhooks (changelog ignore) 2022-06-02 00:14:36 +02:00
386e7203b2 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify 2022-06-01 05:51:41 +02:00
9bdb224631 Prevent rate limite in CI by authenticating 2022-06-01 05:51:33 +02:00
dd36aacbee Update changelog v5.4.1 2022-06-01 03:45:04 +00:00
6b57b1c720 Bump version (changelog ignore) 2022-06-01 05:42:23 +02:00
9e9e6d41ff Update dependencies (changelog ignore) 2022-06-01 05:38:12 +02:00
5140389502 Update changelog v5.4.0 2022-05-23 14:59:15 +00:00
fc6328131f Bump version (changelog ignore) 2022-05-23 16:51:58 +02:00
9de0083ca6 #109 2022-05-23 16:51:18 +02:00
f5231b840d Update changelog v5.3.2 2022-05-04 10:16:04 +00:00
afb6596c4b Bump version (changelog ignore) 2022-05-04 12:13:10 +02:00
dde9afef92 Merge pull request #101 from Romcol/bugfix/99
Issue #99 - Make replace less greedy in remplaceImportFromStatic
2022-05-04 12:12:06 +02:00
6595e9c3cb [IMP] Issue #99 - Make replace less greedy in remplaceImportFromStatic 2022-05-04 11:22:58 +02:00
c0e3b5fe06 Update changelog v5.3.1 2022-04-29 16:41:12 +00:00
6b8f3bbc51 Bump version (changelog ignore) 2022-04-29 18:35:39 +02:00
9a5a021e64 Comment out missleading informations 2022-04-29 18:35:07 +02:00
14c05fec8c Update changelog v5.3.0 2022-04-28 07:52:08 +00:00
eaf7a455cd Fix name of npx script for generating email dir (changelog ignore) 2022-04-28 09:46:49 +02:00
55bb21f3ee Merge branch 'main' of https://github.com/InseeFrLab/keycloakify 2022-04-28 09:36:41 +02:00
f123bc0912 Bump version (changelog ignore) 2022-04-28 09:35:53 +02:00
572eb7b1c0 Rename keycloak_theme_email to keycloak_email (it's shorter) 2022-04-28 09:34:01 +02:00
2befaff8a8 Update changelog sumup in readme (changelog ignore) 2022-04-27 23:32:47 +02:00
437a9ce2d3 Update changelog v5.2.0 2022-04-27 19:27:37 +00:00
1b967b250a Bump version (changelog ignore) 2022-04-27 21:23:10 +02:00
e221f39e07 Export KcApp 2022-04-27 21:22:55 +02:00
21a8838a24 Update changelog v5.1.0 2022-04-27 19:20:36 +00:00
fad91ccae0 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify 2022-04-27 21:17:40 +02:00
825914aa4b Export kcLanguageTags 2022-04-27 21:17:26 +02:00
f15c0ecbb0 Merge pull request #46 from InseeFrLab/main
Update fork
2022-04-04 17:41:06 +03:00
41 changed files with 13713 additions and 1361 deletions

5
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# These are supported funding model platforms
github: [garronej]
open_collective: keycloakify
custom: ['https://www.ringerhq.com/experts/garronej']

25
.github/release.yaml vendored Normal file
View File

@ -0,0 +1,25 @@
changelog:
exclude:
labels:
- ignore-for-release
authors:
- octocat
categories:
- title: Breaking Changes 🛠
labels:
- breaking
- title: Exciting New Features 🎉
labels:
- feature
- title: Fixes 🔧
labels:
- fix
- title: Documentation 🔧
labels:
- docs
- title: CI 👷
labels:
- ci
- title: Other Changes
labels:
- '*'

View File

@ -2,20 +2,21 @@ name: ci
on:
push:
branches:
- main
- v5
pull_request:
branches:
- main
- v5
jobs:
test_formatting:
test_lint:
runs-on: ubuntu-latest
if: ${{ !github.event.created && github.repository != 'garronej/ts-ci' }}
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/setup-node@v2.1.3
- uses: bahmutov/npm-install@v1
- name: If this step fails run 'yarn format' then commit again.
- name: If this step fails run 'npm run lint' and 'npm run format' then commit again.
run: |
PACKAGE_MANAGER=npm
if [ -f "./yarn.lock" ]; then
@ -24,7 +25,9 @@ jobs:
$PACKAGE_MANAGER run format:check
test:
runs-on: macos-10.15
needs: test_formatting
needs: test_lint
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strategy:
matrix:
node: [ '15', '14' ]
@ -32,7 +35,7 @@ jobs:
steps:
- name: Tell if project is using npm or yarn
id: step1
uses: garronej/ts-ci@v1.1.6
uses: garronej/ts-ci@v1.1.4
with:
action_name: tell_if_project_uses_npm_or_yarn
- uses: actions/checkout@v2.3.4
@ -64,54 +67,40 @@ jobs:
is_upgraded_version: ${{ steps.step1.outputs.is_upgraded_version }}
is_release_beta: ${{steps.step1.outputs.is_release_beta }}
steps:
- uses: garronej/ts-ci@v1.1.6
- uses: garronej/ts-ci@v1.1.8
id: step1
with:
action_name: is_package_json_version_upgraded
branch: ${{ github.head_ref || github.ref }}
update_changelog:
runs-on: ubuntu-latest
needs: check_if_version_upgraded
if: needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true'
steps:
- uses: garronej/ts-ci@v1.1.6
with:
action_name: update_changelog
branch: ${{ github.head_ref || github.ref }}
create_github_release:
runs-on: ubuntu-latest
# We create a release only if the version have been upgraded and we are on a default branch
# PR on the default branch can release beta but not real release
if: |
needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true' &&
(
github.event_name == 'push' ||
needs.check_if_version_upgraded.outputs.is_release_beta == 'true'
)
needs:
- update_changelog
- check_if_version_upgraded
steps:
- name: Build GitHub release body
id: step1
run: |
if [ "$FROM_VERSION" = "0.0.0" ]; then
echo "::set-output name=body::🚀"
else
echo "::set-output name=body::📋 [CHANGELOG](https://github.com/$GITHUB_REPOSITORY/blob/v$TO_VERSION/CHANGELOG.md)"
fi
env:
FROM_VERSION: ${{ needs.check_if_version_upgraded.outputs.from_version }}
TO_VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
- uses: garronej/action-gh-release@v0.2.0
- uses: softprops/action-gh-release@v1
with:
name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
tag_name: v${{ needs.check_if_version_upgraded.outputs.to_version }}
target_commitish: ${{ github.head_ref || github.ref }}
body: ${{ steps.step1.outputs.body }}
generate_release_notes: true
draft: false
prerelease: ${{ needs.check_if_version_upgraded.outputs.is_release_beta == 'true' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_on_npm:
runs-on: macos-10.15
runs-on: ubuntu-latest
needs:
- update_changelog
- create_github_release
- check_if_version_upgraded
steps:
- uses: actions/checkout@v2.3.4

2
.gitignore vendored
View File

@ -47,5 +47,5 @@ jspm_packages
.idea
/keycloak_theme_email
/keycloak_email
/build_keycloak

View File

@ -4,4 +4,5 @@ node_modules/
/.yarn_home/
/src/test/apps/
/src/tools/types/
/sample_react_project
/sample_react_project
/build_keycloak/

View File

@ -1,893 +0,0 @@
# **5.0.0** (2022-04-27)
- i18n rebuild from the ground up
## **4.10.0** (2022-04-26)
- Merge pull request #92 from Tasyp/add-login-idp-link-email
feat: add login-idp-link-email page
- feat: add mock data for login-idp-link-email page
- feat: supply broker context with context
## **4.9.0** (2022-04-25)
- Test by default with kc 18. Update instructions to use quay.io/keycloak/keycloak instead of jboss/keycloak #93
### **4.8.7** (2022-04-25)
- Update instructions to test on Keycloak 18 https://github.com/keycloak/keycloak-web/issues/306 #93
- Move the documentation form the readme to docs.keycloakify.dev
- Update README.md
- Update demo video
### **4.8.6** (2022-04-22)
- always offer to download v11.0.3
### **4.8.5** (2022-04-22)
- #91
### **4.8.4** (2022-04-22)
- #90
### **4.8.3** (2022-04-20)
### **4.8.2** (2022-04-20)
- Tell pepoles they can test with different keycloak version
### **4.8.1** (2022-04-20)
- Add missing shebang
- Add video demo for npx download-builtin-keycloak-theme
## **4.8.0** (2022-04-20)
- Document email template customization feature #9
- Add mention of download-builtin-keycloak-theme
- Let the choice of kc version be auto in GH Action
- Only test on node v15 and v14 (bellow is no longer supported (rmSync)
- Feature email customization #9
### **4.7.6** (2022-04-12)
- Fix bugs with language switch #85
### **4.7.5** (2022-04-09)
- Fix #85
### **4.7.4** (2022-04-09)
- M1 Mac compat (for real this time)
### **4.7.3** (2022-04-08)
- Mention that there is still problems with M1 Mac
### **4.7.2** (2022-04-06)
- #43: M1 Mac support
### **4.7.1** (2022-03-30)
- Improve browser autofill
- factorization
## **4.7.0** (2022-03-17)
- Add support for options validator
- remove duplicate dependency
## **4.6.0** (2022-03-07)
- Remove powerhooks as dev dependency
### **4.5.5** (2022-03-07)
- Update tss-react
### **4.5.4** (2022-03-06)
- Remove tss-react from peerDependencies (it becomes a dependency)
- (dev script) Use tsconfig.json to tell we are at the root of the project
### **4.5.3** (2022-01-26)
- Themes no longer have to break on minor Keycloakify update
### **4.5.2** (2022-01-20)
- Test container uses Keycloak 16.1.0
- Merge pull request #78 from InseeFrLab/Ann2827/pull
Ann2827/pull
- Refactor #78
- Compat with Keycloak 16 (and probably 17, 18) #79
- Warning about compat issues with Keycloak 16
- fix: changes
- fix: Errors on pages login-idp-link-confirm and login-idp-link-email
ref: https://github.com/InseeFrLab/keycloakify/issues/75
### **4.5.1** (2022-01-18)
- fix previous version
## **4.5.0** (2022-01-18)
- Read public/CNAME for domain name in --externel-assets mode
## **4.4.0** (2022-01-01)
- Merge pull request #73 from lazToum/main
(feature) added login-page-expired.ftl
- added login-page-expired.ftl
- Add update instruction for 4.3.0
## **4.3.0** (2021-12-27)
- Merge pull request #72 from praiz/main
feat(*): added login-update-password
- feat(*): added login-update-password
### **4.2.21** (2021-12-27)
- update dependencies
### **4.2.19** (2021-12-21)
- Merge pull request #70 from VBustamante/patch-1
- Added realm name field to KcContext mocks object
- Merge pull request #69 from VBustamante/patch-1
Adding name field to realm in KcContext type
- Adding name field to realm in KcContext type
### **4.2.18** (2021-12-17)
- Improve css url() import (fix CRA 5)
### **4.2.17** (2021-12-16)
- Fix path.join polyfill
### **4.2.16** (2021-12-16)
### **4.2.15** (2021-12-16)
- use custom polyfill for path.join (fix webpack 5 build)
### **4.2.14** (2021-12-12)
- Merge pull request #65 from InseeFrLab/doge_ftl_errors
Prevent ftl errors in Keycloak log
- Encourage users to report errors in logs
- Fix ftl error related to url.loginAction in saml-post-form.ftl
- Ftl prevent error with updateProfileCtx
- Ftl prevent error with auth.attemptedUsername
- Fix ftl error as comment formatting
- Merge remote-tracking branch 'origin/main' into doge_ftl_errors
- Update README, remove all instruction about errors in logs
- Avoid error in Keycloak logs, fix long template loading time
- Add missing collon in README sample code
Add miss ','
### **4.2.13** (2021-12-08)
- Fix broken link about how to import fonts #62
- Add a video to show how to test the theme in a local container
### **4.2.12** (2021-12-08)
- Update post build instructions
### **4.2.11** (2021-12-07)
### **4.2.10** (2021-11-12)
- Export an exaustive list of KcLanguageTag
### **4.2.9** (2021-11-11)
- Fix useAdvancedMsg
### **4.2.8** (2021-11-10)
- Update doc about pattern that can be used for user attributes #50
- Bring back Safari compat
### **4.2.7** (2021-11-09)
- Fix useFormValidationSlice
### **4.2.6** (2021-11-08)
- Fix deepClone so we can overwrite with undefined in when we mock kcContext
### **4.2.5** (2021-11-07)
- Better debugging experience with user profile
### **4.2.4** (2021-11-01)
- Better autoComplete typings
### **4.2.3** (2021-11-01)
- Make it more easy to understand that error in the log are expected
### **4.2.2** (2021-10-27)
- Replace 'path' by 'browserify-path' #47
### **4.2.1** (2021-10-26)
- useFormValidationSlice: update when params have changed
- Explains that the password can't be validated
## **4.2.0** (2021-10-26)
- Export types definitions for Attribue and Validator
## **4.1.0** (2021-10-26)
- Document what's new in v4
# **4.0.0** (2021-10-26)
- fix RegisterUserProfile password confirmation field
- Much better support for frontend field validation
- Fix css injection order
- Makes the download output predictable. This fixes the case where GitHub redirects and wget was trying to download a filename called "15.0.2", and then unzip wouldn't pick it up.
Changes wget to curl because curl is awesome. -L is to follow the GitHub redirects.
- Remove duplicates
### **3.0.2** (2021-10-18)
- Scan deeper to retreive user attribute
### **3.0.1** (2021-10-17)
- Add client.description in type kcContext type def
# **3.0.0** (2021-10-16)
### **2.5.3** (2021-10-16)
### **2.5.2** (2021-10-13)
### **2.5.1** (2021-10-13)
- Update tss-react
## **2.5.0** (2021-10-12)
- register-user-profile.ftl tested working
- Make kcMessage more easily hackable
- fix useKcMessage
- Implement and type validators
- Remove syntax error in ftl and make it more directly debugable
- Support register-user-profile.ftl
## **2.4.0** (2021-10-08)
- #38: Implement messagesPerField existsError and get
## **2.3.0** (2021-10-07)
- #20: Support advancedMsg
## **2.2.0** (2021-10-07)
- Feat scrip: download-builtin-keycloak-theme for downloading any version of the builtin themes
- Use the latest version of keycloak for testing
- Test locally with 15.0.2 instead of 11.0.3
## **2.1.0** (2021-10-06)
- Support Hungarian and Danish (use Keycloak 15 language resources)
### **2.0.20** (2021-10-05)
- Update README.md
### **2.0.19** (2021-09-17)
- Fix kcContext type definitions
### **2.0.18** (2021-09-14)
### **2.0.17** (2021-09-14)
### **2.0.16** (2021-09-12)
- Add explaination about errors in logs
### **2.0.15** (2021-08-31)
- Update tss-react
### **2.0.14** (2021-08-20)
- Update tss-react
### **2.0.13** (2021-08-04)
- Merge pull request #28 from marcmrf/main
fix(mvn): scoped packages compatibility
- fix(mvn): scoped packages compatibility
### **2.0.12** (2021-07-28)
- Merge pull request #27 from jchn-codes/patch-1
add maven to requirements
- add maven to requirements
- Add #bluehats in the keyworks
### **2.0.11** (2021-07-21)
- Spaces in file path #22
- uptdate dependnecies
- Inport specific powerhooks files to reduce bundle size
### **2.0.10** (2021-07-16)
- Update dependencies
### **2.0.9** (2021-07-14)
- Fix #21
### **2.0.8** (2021-07-12)
- Fix previous release
- #20: Add def for clientId and name on kcContext.client
### **2.0.6** (2021-07-08)
- Merge pull request #18 from asashay/add-custom-props-to-theme-properties
Add possibility to add custom properties to theme.properties file
- add possibility to add custom properties to theme.properties file
### **2.0.5** (2021-07-05)
- Fix broken url for big stylesheet #16
### **2.0.4** (2021-07-03)
- Fix: #7
### **2.0.3** (2021-06-30)
- Escape double quote in ftl to js conversion #15
- Update readme
### **2.0.2** (2021-06-28)
- Updagte README for implementing non incuded pages
### **2.0.1** (2021-06-28)
- Update documentation for v2
# **2.0.0** (2021-06-28)
- Fix last bugs before relasing v2
- Implement a mechanism to overload kcContext
- Give the option in template to pull the default assets or not
- Enable possiblity to support custom pages (without forking keycloakify)
- Implement a getter for kcContext
- Update README.md
# **2.0.0** (2021-06-28)
- Fix last bugs before relasing v2
- Implement a mechanism to overload kcContext
- Give the option in template to pull the default assets or not
- Enable possiblity to support custom pages (without forking keycloakify)
- Implement a getter for kcContext
- Update README.md
### **1.2.1** (2021-06-22)
- Remove unessesary log
## **1.2.0** (2021-06-22)
- Generate kcContext automatically :rocket:
### **1.1.6** (2021-06-21)
- Fix: Alert messages sometimes includes HTML that is not rendered
- Update dist
### **1.1.5** (2021-06-15)
- #11: Provide socials in the register
### **1.1.4** (2021-06-15)
- Merge pull request #12 from InseeFrLab/email-typo
Fix typo on email
- Fix typo on email
### **1.1.3** (2021-06-14)
- Add missing key in Login for providers
### **1.1.2** (2021-06-14)
### **1.1.1** (2021-06-14)
## **1.1.0** (2021-06-14)
- Add login-idp-link-confirm.ftl
- Fix login-update-profile.ftl
- Add login-update-profile.ftl page
- Fix default background bug
- Remove unused 'markdown' dependency
- Fix warning related to powerhooks_useGlobalState_kcLanguageTag
- Update README.md
### **1.0.4** (2021-05-28)
- Instructions for custom themes with custom components
### **1.0.3** (2021-05-23)
- Instuction about how to integrate with non CRA projects
- Add mention to awesome list
### **1.0.2** (2021-05-01)
### **1.0.1** (2021-05-01)
- Fix: LoginOtp (and not otc)
# **1.0.0** (2021-05-01)
- #4: Guide for implementing a missing page
- Support OTP #4
### **0.4.4** (2021-04-29)
- Fix previous release
### **0.4.3** (2021-04-29)
- Add infos about the plugin that defines authorizedMailDomains
### **0.4.2** (2021-04-29)
- Client side validation of allowed email domains
- Support email whitlisting
- Restore kickstart video in the readme
- Update README.md
- Update README.md
- Important readme update
### **0.4.1** (2021-04-11)
- Quietly re-introduce --external-assets
- Give example of customization
## **0.4.0** (2021-04-09)
- Acual support of Therms of services
### **0.3.24** (2021-04-08)
- Add missing dependency: markdown
### **0.3.23** (2021-04-08)
- Allow to lazily load therms
### **0.3.22** (2021-04-08)
- update powerhooks
- Support terms and condition
- Fix info.ftl
- For useKcMessage we prefer returning callbacks with a changing references
### **0.3.21** (2021-04-04)
- Update powerhooks
### **0.3.20** (2021-04-01)
- Always catch freemarker errors
### **0.3.19** (2021-04-01)
- Fix previous release
### **0.3.18** (2021-04-01)
- Fix error.ftt, Adopt best effort strategy to convert ftl values into JS
### **0.3.17** (2021-03-29)
- Use push instead of replace in keycloak-js adapter to enable going back
### **0.3.15** (2021-03-28)
- Remove all reference to --external-assets, broken feature
### **0.3.14** (2021-03-28)
- Fix standalone mode: imports from js
### **0.3.13** (2021-03-26)
### **0.3.12** (2021-03-26)
- Fix mocksContext
### **0.3.11** (2021-03-26)
- Fix previous build, improve README
### **0.3.10** (2021-03-26)
- Handle <style> tag, improve documentation
### **0.3.9** (2021-03-25)
- Update readme
- Document --external-assets
- Update README.md
- Update README.md
- Update README.md
### **0.3.8** (2021-03-22)
- Make standalone mode the default
### **0.3.7** (2021-03-22)
- (test) external asset mode by default
### **0.3.6** (2021-03-22)
- Fix previous release
### **0.3.5** (2021-03-22)
- support homepage with urlPath
### **0.3.4** (2021-03-22)
- Bugfix: Import assets from CSS
### **0.3.3** (2021-03-22)
- Fix submit not receving correct text
### **0.3.2** (2021-03-21)
- Fix broken previous release
### **0.3.1** (2021-03-21)
- kcHeaderClass can be updated after initial mount
## **0.3.0** (2021-03-20)
- Bump version
- Feat: Cary over states using URL search params
- Bugfix: with kcHtmlClass
### **0.2.10** (2021-03-19)
- Remove dependency to denoify
### **0.2.9** (2021-03-19)
- Update deps and CI workflow
### **0.2.8** (2021-03-19)
- Bugfix: keycloak_build that grow and grow in size
- Add disclaimer about maitainment strategy
- Add a note for tested version support
### **0.2.7** (2021-03-13)
- Bump version
- Update README.md
- Update README.md
### **0.2.6** (2021-03-10)
- Fix generated gitignore
### **0.2.5** (2021-03-10)
- Fix generated .gitignore
### **0.2.4** (2021-03-10)
- Update README.md
### **0.2.3** (2021-03-09)
- fix gitignore generation
### **0.2.2** (2021-03-08)
- Add table of content
- Update README.md
- Update README.md
## **0.2.1** (2021-03-08)
- Update ci.yaml
- Update readme
- Update readme
- update deps
- Update readme
- Add all mocks for testing
- many small fixes
### **0.1.6** (2021-03-07)
- Fix Turkish
### **0.1.5** (2021-03-07)
- Fix getKcLanguageLabel
### **0.1.4** (2021-03-07)
### **0.1.3** (2021-03-07)
- Implement LoginVerifyEmail
- Implement login-reset-password.ftl
### **0.1.2** (2021-03-07)
- Fix build
- Fix build
### **0.1.1** (2021-03-06)
- Implement Error page
- rename pageBasename by pageId
- Implement reactive programing for language switching
- Add Info page, refactor
## **0.1.0** (2021-03-05)
- Rename keycloakify
### **0.0.33** (2021-03-05)
- Fix syncronization with non react pages
### **0.0.32** (2021-03-05)
- bump version
- Add log to tell when we are using react
- Fix missing parentesis
### **0.0.31** (2021-03-05)
- Fix typo
- Fix register page 500
### **0.0.30** (2021-03-05)
- Edit language statistique
### **0.0.30** (2021-03-05)
- avoid escaping urls
- Use default value instead of value
- Fix double single quote problem in messages
- Fix typo
- Fix non editable username
- Fix some bugs
- Fix Object.deepAssign
- Make the dongle to download smaller
- Split kcContext among pages
- Implement register
### **0.0.29** (2021-03-04)
- Fix build
- Fix i18n
- Login appear to be working now
- closer but not there yet
### **0.0.28** (2021-03-03)
- fix build
- There is no reason not to let use translations outside of keycloak
### **0.0.27** (2021-03-02)
- Implement entrypoint
### **0.0.26** (2021-03-02)
- Login page implemented
- Implement login
- remove unesseary log
### **0.0.25** (2021-03-02)
- Fix build and reduce size
- Implement the template
### **0.0.24** (2021-03-01)
- update
- update
- update
### **0.0.23** (2021-03-01)
- update
### **0.0.23** (2021-03-01)
- update
- update
### **0.0.23** (2021-03-01)
- update
- update
### **0.0.23** (2021-03-01)
- update
- Handle formatting in translation function
### **0.0.22** (2021-02-28)
- Split page messages
### **0.0.21** (2021-02-28)
- Restore yarn file
- Multiple fixes
- Update deps
- Update deps
- includes translations
- Update README.md
- improve docs
- update
- Update README.md
- update
- update
- update
- update
### **0.0.20** (2021-02-27)
- update
- update
### **0.0.19** (2021-02-27)
- update
- update
### **0.0.18** (2021-02-23)
- Bump version number
- Moving on with implementation of the lib
- Update readme
- Readme eddit
- Fixing video link
### **0.0.16** (2021-02-23)
- Bump version
- Give test container credentials
### **0.0.14** (2021-02-23)
- Bump version number
- enable the docker container to be run from the root of the react project
### **0.0.13** (2021-02-23)
- bump version
### **0.0.12** (2021-02-23)
- update readme
### **0.0.11** (2021-02-23)
- Add documentation
### **0.0.10** (2021-02-23)
- Remove extra closing bracket
### **0.0.9** (2021-02-22)
- fix container startup script
- minor update
### **0.0.8** (2021-02-21)
- Include theme properties
### **0.0.7** (2021-02-21)
- fix build
- Fix bundle
### **0.0.6** (2021-02-21)
- Include missing files in the release bundle
### **0.0.5** (2021-02-21)
- Bump version number
- Make the install faster
### **0.0.4** (2021-02-21)
- Fix script visibility
### **0.0.3** (2021-02-21)
- Do not run tests on window
- Add script for downloading base themes
- Generate debug files to be able to test the container
- Fix many little bugs
- refactor
- Almoste there
- Things are starting to take form
- Seems to be working
- First draft
- Remove eslint and prettyer
### **0.0.2** (2021-02-20)
- Update package.json

3
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,3 @@
Looking to contribute? Thank you! PR are more than welcome.
Please refers to [this documentation page](https://docs.keycloakify.dev/contributing) that will help you get started.

View File

@ -12,7 +12,7 @@
<img src="https://img.shields.io/bundlephobia/minzip/keycloakify">
</a>
<a href="https://www.npmjs.com/package/keycloakify">
<img src="https://img.shields.io/npm/dw/keycloakify">
<img src="https://img.shields.io/npm/dm/keycloakify">
</a>
<a href="https://github.com/garronej/keycloakify/blob/main/LICENSE">
<img src="https://img.shields.io/npm/l/keycloakify">
@ -36,8 +36,38 @@
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
</p>
> 🗣 Beloved contributors: [Keycloakify v6](https://docs.keycloakify.dev/v/v6/) is just around the corner, please stop
> submitting PRs against `main` but work on [the `v6` branch](https://github.com/InseeFrLab/keycloakify/tree/v6) instead.
# Changelog highlights
## 5.8.0
- [React.lazy()](https://reactjs.org/docs/code-splitting.html#reactlazy) support 🎉. [#141](https://github.com/InseeFrLab/keycloakify/issues/141)
## 5.7.0
- Feat `logout-confirm.ftl`. [PR](https://github.com/InseeFrLab/keycloakify/pull/120)
## 5.6.4
Fix `login-verify-email.ftl` page. [Before](https://user-images.githubusercontent.com/6702424/177436014-0bad22c4-5bfb-45bb-8fc9-dad65143cd0c.png) - [After](https://user-images.githubusercontent.com/6702424/177435797-ec5d7db3-84cf-49cb-8efc-3427a81f744e.png)
## v5.6.0
Add support for `login-config-totp.ftl` page [#127](https://github.com/InseeFrLab/keycloakify/pull/127).
## v5.3.0
Rename `keycloak_theme_email` to `keycloak_email`.
If you already had a `keycloak_theme_email` you should rename it `keycloak_email`.
## v5.0.0
[Migration guide](https://github.com/garronej/keycloakify-demo-app/blob/a5b6a50f24bc25e082931f5ad9ebf47492acd12a/src/index.tsx#L46-L63)
New i18n system.
Import of terms and services have changed. [See example](https://github.com/garronej/keycloakify-demo-app/blob/a5b6a50f24bc25e082931f5ad9ebf47492acd12a/src/index.tsx#L46-L63).
## v4.10.0
Add `login-idp-link-email.ftl` page [See PR](https://github.com/InseeFrLab/keycloakify/pull/92).

View File

@ -1,6 +1,6 @@
{
"name": "keycloakify",
"version": "5.0.0",
"version": "5.9.3",
"description": "Keycloak theme generator for Reacts app",
"repository": {
"type": "git",
@ -12,7 +12,7 @@
"clean": "rimraf dist/",
"build": "yarn clean && tsc && yarn grant-exec-perms && yarn copy-files",
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
"test": "node dist/test/bin/main && node dist/test/lib",
"test": "node dist/test/bin && node dist/test/lib",
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/",
"generate-messages": "node dist/bin/generate-i18n-messages.js",
"link_in_test_app": "node dist/bin/link_in_test_app.js",
@ -22,7 +22,7 @@
},
"bin": {
"build-keycloak-theme": "dist/bin/build-keycloak-theme/index.js",
"create-keycloak-theme-email-directory": "dist/bin/create-keycloak-theme-email-directory.js",
"create-keycloak-email-directory": "dist/bin/create-keycloak-email-directory.js",
"download-builtin-keycloak-theme": "dist/bin/download-builtin-keycloak-theme.js"
},
"lint-staged": {
@ -57,19 +57,19 @@
"homepage": "https://github.com/garronej/keycloakify",
"peerDependencies": {
"@emotion/react": "^11.4.1",
"react": "^16.8.0 || ^17.0.0"
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"devDependencies": {
"@emotion/react": "^11.4.1",
"@types/memoizee": "^0.4.7",
"@types/node": "^17.0.25",
"@types/react": "^17.0.0",
"@types/react": "18.0.9",
"copyfiles": "^2.4.1",
"husky": "^4.3.8",
"lint-staged": "^11.0.0",
"prettier": "^2.3.0",
"properties-parser": "^0.3.1",
"react": "^17.0.1",
"react": "18.1.0",
"rimraf": "^3.0.2",
"typescript": "^4.2.3"
},
@ -77,14 +77,14 @@
"@octokit/rest": "^18.12.0",
"cheerio": "^1.0.0-rc.5",
"cli-select": "^1.1.2",
"evt": "2.0.0-beta.39",
"evt": "^2.4.1",
"memoizee": "^0.4.15",
"minimal-polyfills": "^2.2.1",
"path-browserify": "^1.0.1",
"powerhooks": "^0.14.0",
"powerhooks": "^0.20.16",
"react-markdown": "^5.0.3",
"scripting-tools": "^0.19.13",
"tsafe": "^0.9.0",
"tss-react": "^3.5.2"
"tsafe": "^0.10.1",
"tss-react": "^3.7.1"
}
}

27
renovate.json Normal file
View File

@ -0,0 +1,27 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"baseBranches": ["main", "landingpage"],
"extends": ["config:base"],
"dependencyDashboard": false,
"bumpVersion": "patch",
"rangeStrategy": "bump",
"ignorePaths": [".github/**"],
"branchPrefix": "renovate_",
"vulnerabilityAlerts": {
"enabled": false
},
"packageRules": [
{
"packagePatterns": ["*"],
"excludePackagePatterns": ["tss-react", "powerhooks", "tsafe", "evt"],
"enabled": false
},
{
"packagePatterns": ["tss-react", "powerhooks", "tsafe", "evt"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"automergeType": "branch",
"groupName": "garronej_modules_update"
}
]
}

View File

@ -19,7 +19,7 @@ const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-asset
const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json"));
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
export const keycloakThemeEmailDirPath = pathJoin(keycloakThemeBuildingDirPath, "..", "keycloak_theme_email");
export const keycloakThemeEmailDirPath = pathJoin(keycloakThemeBuildingDirPath, "..", "keycloak_email");
function sanitizeThemeName(name: string) {
return name
@ -91,8 +91,8 @@ export function main() {
"cwd": keycloakThemeBuildingDirPath,
});
//We want, however to test in a container running the latest Keycloak version
const containerKeycloakVersion = "18.0.0";
//We want, however, to test in a container running the latest Keycloak version
const containerKeycloakVersion = "18.0.2";
generateStartKeycloakTestingContainer({
keycloakThemeBuildingDirPath,
@ -106,7 +106,8 @@ export function main() {
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`,
`It is to be placed in "/opt/keycloak/providers" in the container running a quay.io/keycloak/keycloak Docker image.`,
"",
"Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:",
//TODO: Restore when we find a good Helm chart for Keycloak.
//"Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:",
"",
"value.yaml: ",
" extraInitContainers: |",

View File

@ -14,7 +14,8 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
"totp", "totpSecret", "SAMLRequest", "SAMLResponse", "relayState", "device_user_code", "code",
"password-new", "rememberMe", "login", "authenticationExecution", "cancel-aia", "clientDataJSON",
"authenticatorData", "signature", "credentialId", "userHandle", "error", "authn_use_chk", "authenticationExecution",
"isSetRetry", "try-again", "attestationObject", "publicKeyCredentialId", "authenticatorLabel"
"isSetRetry", "try-again", "attestationObject", "publicKeyCredentialId", "authenticatorLabel",
"user.attributes.dateOfBirth", "user.attributes.country", "user.attributes.acceptedTermsAndConditions"
]>
<#attempt>
@ -30,6 +31,9 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
</#attempt>
"printIfExists": function (fieldName, x) {
<#if !messagesPerField?? >
return undefined;
</#if>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
@ -41,6 +45,9 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
throw new Error("There is no " + fieldName + " field");
},
"existsError": function (fieldName) {
<#if !messagesPerField?? >
return false;
</#if>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
@ -52,6 +59,9 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
throw new Error("There is no " + fieldName + " field");
},
"get": function (fieldName) {
<#if !messagesPerField?? >
return '';
</#if>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
@ -65,6 +75,9 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
throw new Error("There is no " + fieldName + " field");
},
"exists": function (fieldName) {
<#if !messagesPerField?? >
return false;
</#if>
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
@ -124,9 +137,11 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
) || (
<#-- https://github.com/InseeFrLab/keycloakify/pull/65#issuecomment-991896344 (reports with saml-post-form.ftl) -->
<#-- https://github.com/InseeFrLab/keycloakify/issues/91#issue-1212319466 (reports with error.ftl and Kc18) -->
<#-- https://github.com/InseeFrLab/keycloakify/issues/109#issuecomment-1134610163 -->
key == "loginAction" &&
are_same_path(path, ["url"]) &&
["saml-post-form.ftl", "error.ftl"]?seq_contains(pageId)
["saml-post-form.ftl", "error.ftl", "info.ftl"]?seq_contains(pageId) &&
!(auth?has_content && auth.showTryAnotherWayLink())
) || (
["contextData", "idpConfig", "idp", "authenticationSession"]?seq_contains(key) &&
are_same_path(path, ["brokerContext"]) &&
@ -135,6 +150,9 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
key == "identityProviderBrokerCtx" &&
are_same_path(path, []) &&
["login-idp-link-confirm.ftl", "login-idp-link-email.ftl" ]?seq_contains(pageId)
) || (
["masterAdminClient", "delegateForUpdate", "defaultRole"]?seq_contains(key) &&
are_same_path(path, ["realm"])
)
>
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
@ -152,7 +170,7 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
</#attempt>
</#if>
<#attempt>
<#if !object[key]??>
<#continue>
@ -200,6 +218,31 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
</#attempt>
<#if isMethod>
<#if are_same_path(path, ["auth", "showUsername"])>
<#attempt>
<#return auth.showUsername()?c>
<#recover>
<#return "ABORT: Couldn't evaluate auth.showUsername()">
</#attempt>
</#if>
<#if are_same_path(path, ["auth", "showResetCredentials"])>
<#attempt>
<#return auth.showResetCredentials()?c>
<#recover>
<#return "ABORT: Couldn't evaluate auth.showResetCredentials()">
</#attempt>
</#if>
<#if are_same_path(path, ["auth", "showTryAnotherWayLink"])>
<#attempt>
<#return auth.showTryAnotherWayLink()?c>
<#recover>
<#return "ABORT: Couldn't evaluate auth.showTryAnotherWayLink()">
</#attempt>
</#if>
<#return "ABORT: It's a method">
</#if>
@ -230,7 +273,12 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
<#list object as array_item>
<#local rec_out = ftl_object_to_js_code_declaring_an_object(array_item, path + [ i ])>
<#local rec_out = "">
<#attempt>
<#local rec_out = ftl_object_to_js_code_declaring_an_object(array_item, path + [ i ])>
<#recover>
<#return "ABORT: Unable to convert recursive array item in enumerable">
</#attempt>
<#local i = i + 1>
@ -263,7 +311,7 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
</#function>
<#function are_same_path path searchedPath>
<#if path?size != path?size>
<#if path?size != searchedPath?size>
<#return false>
</#if>

View File

@ -5,6 +5,7 @@ import { join as pathJoin } from "path";
import { objectKeys } from "tsafe/objectKeys";
import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
// https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/forms/login/freemarker/Templates.java
export const pageIds = [
"login.ftl",
"register.ftl",
@ -20,6 +21,8 @@ export const pageIds = [
"login-idp-link-confirm.ftl",
"login-idp-link-email.ftl",
"login-page-expired.ftl",
"login-config-totp.ftl",
"logout-confirm.ftl",
] as const;
export type PageId = typeof pageIds[number];

View File

@ -88,7 +88,7 @@ export function generateKeycloakThemeResources(params: {
console.log(
[
`Not bundling email template because ${pathBasename(keycloakThemeEmailDirPath)} does not exist`,
`To start customizing the email template, run: 👉 npx create-keycloak-theme-email-directory 👈`,
`To start customizing the email template, run: 👉 npx create-keycloak-email-directory 👈`,
].join("\n"),
);
doBundleEmailTemplate = false;

View File

@ -17,12 +17,36 @@ export function replaceImportsFromStaticInJsCode(params: { jsCode: string; urlOr
const { jsCode, urlOrigin } = params;
const fixedJsCode = jsCode
.replace(/(\w+\.\w+)\+"static\//g, (...[, group]) =>
.replace(
/([a-zA-Z]+)\.([a-zA-Z]+)=function\(([a-zA-Z]+)\){return"static\/js\/"/g,
(...[, n, u, e]) => `
${n}[(function(){
${
urlOrigin === undefined
? `
Object.defineProperty(${n}, "p", {
get: function() { return window.${ftlValuesGlobalName}.url.resourcesPath; },
set: function (){}
});
`
: `
var p= "";
Object.defineProperty(${n}, "p", {
get: function() { return ("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + p; },
set: function (value){ p = value;}
});
`
}
return "${u}";
})()] = function(${e}) { return "${urlOrigin === undefined ? "/build/" : ""}static/js/"`,
)
.replace(/([a-zA-Z]+\.[a-zA-Z]+)\+"static\//g, (...[, group]) =>
urlOrigin === undefined
? `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
: `("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group} + "static/`,
)
.replace(/".chunk.css",(\w)+=(\w+\.\w+)\+(\w+),/, (...[, group1, group2, group3]) =>
//TODO: Write a test case for this
.replace(/".chunk.css",([a-zA-Z])+=([a-zA-Z]+\.[a-zA-Z]+)\+([a-zA-Z]+),/, (...[, group1, group2, group3]) =>
urlOrigin === undefined
? `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},`
: `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group2} + ${group3},`,

View File

@ -9,7 +9,7 @@ import { rm_rf, rm_r } from "./tools/rm";
//@ts-ignore
const propertiesParser = require("properties-parser");
for (const keycloakVersion of ["11.0.3", "15.0.2", "16.1.0"]) {
for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
console.log({ keycloakVersion });
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");

View File

@ -60,7 +60,7 @@ const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => {
});
};
const testAppNames = ["keycloakify-demo-app"] as const;
const testAppNames = [process.argv[2] ?? "keycloakify-demo-app"] as const;
const getTestAppPath = (testAppName: typeof testAppNames[number]) => pathJoin(keycloakifyDirPath, "..", testAppName);

View File

@ -1,7 +0,0 @@
import { Octokit } from "@octokit/rest";
export function createOctokit(params: { github_token: string }) {
const { github_token } = params;
return new Octokit({ ...(github_token !== "" ? { "auth": github_token } : {}) });
}

View File

@ -17,7 +17,7 @@ export function downloadAndUnzip(params: { url: string; destDirPath: string; pat
execSync(`curl -L ${url} -o ${zipFilePath}`, { "cwd": tmpDirPath });
execSync(`unzip ${zipFilePath}${pathOfDirToExtractInArchive === undefined ? "" : ` "${pathOfDirToExtractInArchive}/*"`}`, {
execSync(`unzip -o ${zipFilePath}${pathOfDirToExtractInArchive === undefined ? "" : ` "${pathOfDirToExtractInArchive}/**/*"`}`, {
"cwd": tmpDirPath,
});

View File

@ -6,7 +6,7 @@ import type { KcContextBase } from "../getKcContext/KcContextBase";
import { getMsg } from "../i18n";
export const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Info } & KcProps) => {
const { msg } = getMsg(kcContext);
const { msg, msgStr } = getMsg(kcContext);
assert(kcContext.message !== undefined);
@ -24,7 +24,7 @@ export const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.In
{message.summary}
{requiredActions !== undefined && (
<b>{requiredActions.map(requiredAction => msg(`requiredAction.${requiredAction}` as const)).join(",")}</b>
<b>{requiredActions.map(requiredAction => msgStr(`requiredAction.${requiredAction}` as const)).join(",")}</b>
)}
</p>
{!skipLink && pageRedirectUri !== undefined ? (

View File

@ -15,6 +15,8 @@ import { LoginUpdateProfile } from "./LoginUpdateProfile";
import { LoginIdpLinkConfirm } from "./LoginIdpLinkConfirm";
import { LoginPageExpired } from "./LoginPageExpired";
import { LoginIdpLinkEmail } from "./LoginIdpLinkEmail";
import { LoginConfigTotp } from "./LoginConfigTotp";
import { LogoutConfirm } from "./LogoutConfirm";
export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase } & KcProps) => {
switch (kcContext.pageId) {
@ -46,5 +48,9 @@ export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase }
return <LoginIdpLinkEmail {...{ kcContext, ...props }} />;
case "login-page-expired.ftl":
return <LoginPageExpired {...{ kcContext, ...props }} />;
case "login-config-totp.ftl":
return <LoginConfigTotp {...{ kcContext, ...props }} />;
case "logout-confirm.ftl":
return <LogoutConfirm {...{ kcContext, ...props }} />;
}
});

View File

@ -0,0 +1,183 @@
import { memo } from "react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { getMsg } from "../i18n";
import { useCssAndCx } from "tss-react";
export const LoginConfigTotp = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginConfigTotp } & KcProps) => {
const { url, isAppInitiatedAction, totp, mode, messagesPerField } = kcContext;
const { cx } = useCssAndCx();
const { msg, msgStr } = getMsg(kcContext);
const algToKeyUriAlg: Record<KcContextBase.LoginConfigTotp["totp"]["policy"]["algorithm"], string> = {
HmacSHA1: "SHA1",
HmacSHA256: "SHA256",
HmacSHA512: "SHA512",
};
return (
<Template
{...{ kcContext, ...props }}
doFetchDefaultThemeResources={true}
headerNode={msg("loginTotpTitle")}
formNode={
<>
<ol id="kc-totp-settings">
<li>
<p>{msg("loginTotpStep1")}</p>
<ul id="kc-totp-supported-apps">
{totp.policy.supportedApplications.map(app => (
<li>{app}</li>
))}
</ul>
</li>
{mode && mode == "manual" ? (
<>
<li>
<p>{msg("loginTotpManualStep2")}</p>
<p>
<span id="kc-totp-secret-key">{totp.totpSecretEncoded}</span>
</p>
<p>
<a href={totp.qrUrl} id="mode-barcode">
{msg("loginTotpScanBarcode")}
</a>
</p>
</li>
<li>
<p>{msg("loginTotpManualStep3")}</p>
<p>
<ul>
<li id="kc-totp-type">
{msg("loginTotpType")}: {msg(`loginTotp.${totp.policy.type}`)}
</li>
<li id="kc-totp-algorithm">
{msg("loginTotpAlgorithm")}: {algToKeyUriAlg?.[totp.policy.algorithm] ?? totp.policy.algorithm}
</li>
<li id="kc-totp-digits">
{msg("loginTotpDigits")}: {totp.policy.digits}
</li>
{totp.policy.type === "totp" ? (
<li id="kc-totp-period">
{msg("loginTotpInterval")}: {totp.policy.period}
</li>
) : (
<li id="kc-totp-counter">
{msg("loginTotpCounter")}: {totp.policy.initialCounter}
</li>
)}
</ul>
</p>
</li>
</>
) : (
<li>
<p>{msg("loginTotpStep2")}</p>
<img id="kc-totp-secret-qr-code" src={`data:image/png;base64, ${totp.totpSecretQrCode}`} alt="Figure: Barcode" />
<br />
<p>
<a href={totp.manualUrl} id="mode-manual">
{msg("loginTotpUnableToScan")}
</a>
</p>
</li>
)}
<li>
<p>{msg("loginTotpStep3")}</p>
<p>{msg("loginTotpStep3DeviceName")}</p>
</li>
</ol>
<form action={url.loginAction} className={cx(props.kcFormClass)} id="kc-totp-settings-form" method="post">
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}>
<label htmlFor="totp" className={cx(props.kcLabelClass)}>
{msg("authenticatorCode")}
</label>{" "}
<span className="required">*</span>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="totp"
name="totp"
autoComplete="off"
className={cx(props.kcInputClass)}
aria-invalid={messagesPerField.existsError("totp")}
/>
{messagesPerField.existsError("totp") && (
<span id="input-error-otp-code" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("totp")}
</span>
)}
</div>
<input type="hidden" id="totpSecret" name="totpSecret" value={totp.totpSecret} />
{mode && <input type="hidden" id="mode" value={mode} />}
</div>
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}>
<label htmlFor="userLabel" className={cx(props.kcLabelClass)}>
{msg("loginTotpDeviceName")}
</label>{" "}
{totp.otpCredentials.length >= 1 && <span className="required">*</span>}
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="userLabel"
name="userLabel"
autoComplete="off"
className={cx(props.kcInputClass)}
aria-invalid={messagesPerField.existsError("userLabel")}
/>
{messagesPerField.existsError("userLabel") && (
<span id="input-error-otp-label" className={cx(props.kcInputErrorMessageClass)} aria-live="polite">
{messagesPerField.get("userLabel")}
</span>
)}
</div>
</div>
{isAppInitiatedAction ? (
<>
<input
type="submit"
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>
<button
type="submit"
className={cx(
props.kcButtonClass,
props.kcButtonDefaultClass,
props.kcButtonLargeClass,
props.kcButtonLargeClass,
)}
id="cancelTOTPBtn"
name="cancel-aia"
value="true"
>
${msg("doCancel")}
</button>
</>
) : (
<input
type="submit"
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonLargeClass)}
id="saveTOTPBtn"
value={msgStr("doSubmit")}
/>
)}
</form>
</>
}
/>
);
});

View File

@ -7,7 +7,7 @@ import { getMsg } from "../i18n";
export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginVerifyEmail } & KcProps) => {
const { msg } = getMsg(kcContext);
const { url } = kcContext;
const { url, user } = kcContext;
return (
<Template
@ -17,10 +17,12 @@ export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcCo
headerNode={msg("emailVerifyTitle")}
formNode={
<>
<p className="instruction">{msg("emailVerifyInstruction1")}</p>
<p className="instruction">{msg("emailVerifyInstruction1", user?.email)}</p>
<p className="instruction">
{msg("emailVerifyInstruction2")}
<br />
<a href={url.loginAction}>{msg("doClickHere")}</a>
&nbsp;
{msg("emailVerifyInstruction3")}
</p>
</>

View File

@ -0,0 +1,61 @@
import { memo } from "react";
import { useCssAndCx } from "tss-react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { getMsg } from "../i18n";
export const LogoutConfirm = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LogoutConfirm } & KcProps) => {
const { url, client, logoutConfirm } = kcContext;
const { cx } = useCssAndCx();
const { msg, msgStr } = getMsg(kcContext);
return (
<Template
{...{ kcContext, ...props }}
doFetchDefaultThemeResources={true}
displayMessage={false}
headerNode={msg("logoutConfirmTitle")}
formNode={
<>
<div id="kc-logout-confirm" className="content-area">
<p className="instruction">{msg("logoutConfirmHeader")}</p>
<form className="form-actions" action={url.logoutConfirmAction} method="POST">
<input type="hidden" name="session_code" value={logoutConfirm.code} />
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options">
<div className={cx(props.kcFormOptionsWrapperClass)}></div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
<input
tabIndex={4}
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass,
)}
name="confirmLogout"
id="kc-logout"
type="submit"
value={msgStr("doLogout")}
/>
</div>
</div>
</form>
<div id="kc-info-message">
{!logoutConfirm.skipLink && client.baseUrl && (
<p>
<a href={client.baseUrl} dangerouslySetInnerHTML={{ __html: msgStr("backToApplication") }} />
</p>
)}
</div>
</div>
</>
}
/>
);
});

View File

@ -24,7 +24,9 @@ export type KcContextBase =
| KcContextBase.LoginUpdateProfile
| KcContextBase.LoginIdpLinkConfirm
| KcContextBase.LoginIdpLinkEmail
| KcContextBase.LoginPageExpired;
| KcContextBase.LoginPageExpired
| KcContextBase.LoginConfigTotp
| KcContextBase.LogoutConfirm;
export declare namespace KcContextBase {
export type Common = {
@ -52,9 +54,9 @@ export declare namespace KcContextBase {
currentLanguageTag: KcLanguageTag;
};
auth?: {
showUsername: boolean;
showResetCredentials: boolean;
showTryAnotherWayLink: boolean;
showUsername?: boolean;
showResetCredentials?: boolean;
showTryAnotherWayLink?: boolean;
attemptedUsername?: string;
};
scripts: string[];
@ -178,6 +180,10 @@ export declare namespace KcContextBase {
export type LoginVerifyEmail = Common & {
pageId: "login-verify-email.ftl";
//NOTE: Optional because maybe it wasn't defined in older keycloak versions.
user?: {
email: string;
};
};
export type Terms = Common & {
@ -223,6 +229,48 @@ export declare namespace KcContextBase {
export type LoginPageExpired = Common & {
pageId: "login-page-expired.ftl";
};
export type LoginConfigTotp = Common & {
pageId: "login-config-totp.ftl";
mode?: "qr" | "manual" | undefined | null;
totp: {
totpSecretEncoded: string;
qrUrl: string;
policy: {
supportedApplications: string[];
algorithm: "HmacSHA1" | "HmacSHA256" | "HmacSHA512";
digits: number;
lookAheadWindow: number;
} & (
| {
type: "totp";
period: number;
}
| {
type: "hotp";
initialCounter: number;
}
);
totpSecretQrCode: string;
manualUrl: string;
totpSecret: string;
otpCredentials: { id: string; userLabel: string }[];
};
};
export type LogoutConfirm = Common & {
pageId: "logout-confirm.ftl";
url: {
logoutConfirmAction: string;
};
client: {
baseUrl?: string;
};
logoutConfirm: {
code: string;
skipLink?: boolean;
};
};
}
export type Attribute = {

View File

@ -337,6 +337,9 @@ export const kcContextMocks: KcContextBase[] = [
id<KcContextBase.LoginVerifyEmail>({
...kcContextCommonMock,
"pageId": "login-verify-email.ftl",
"user": {
"email": "john.doe@gmail.com",
},
}),
id<KcContextBase.Terms>({
...kcContextCommonMock,
@ -387,4 +390,38 @@ export const kcContextMocks: KcContextBase[] = [
"username": "anUsername",
},
}),
id<KcContextBase.LoginConfigTotp>({
...kcContextCommonMock,
"pageId": "login-config-totp.ftl",
totp: {
totpSecretEncoded: "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
qrUrl: "#",
totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACM0lEQVR4Xu3OIZJgOQwDUDFd2UxiurLAVnnbHw4YGDKtSiWOn4Gxf81//7r/+q8b4HfLGBZDK9d85NmNR+sB42sXvOYrN5P1DcgYYFTGfOlbzE8gzwy3euweGizw7cfdl34/GRhlkxjKNV+5AebPXPORX1JuB9x8ZfbyyD2y1krWAKsbMq1HnqQDaLfa77p4+MqvzEGSqvSAD/2IHW2yHaigR9tX3m8dDIYGcNf3f+gDpVBZbZU77zyJ6Rlcy+qoTMG887KAPD9hsh6a1Sv3gJUHGHUAxSMzj7zqDDe7Phmt2eG+8UsMxjRGm816MAO+8VMl1R1jGHOrZB/5Zo/WXAPgxixm9Mo96vDGrM1eOto8c4Ax4wF437mifOXlpiPzCnN7Y9l95NnEMxgMY9AAGA8fucH14Y1aVb6N/cqrmyh0BVht7k1e+bU8LK0Cg5vmVq9c5vHIjOfqxDIfeTraNVTwewa4wVe+SW5N+uP1qACeudUZbqGOfA6VZV750Noq2Xx3kpveV44ZelSV1V7KFHzkWyVrrlUwG0Pl9pWnoy3vsQoME6vKI69i5osVgwWzHT7zjmJtMcNUSVn1oYMd7ZodbgowZl45VG0uVuLPUr1yc79uaQBag/mqR34xhlWyHm1prplHboCWdZ4TeZjsK8+dI+jbz1C5hl65mcpgB5dhcj8+dGO+0Ko68+lD37JDD83dpDLzzK+TrQyaVwGj6pUboGV+7+AyN8An/pf84/7rv/4/1l4OCc/1BYMAAAAASUVORK5CYII=",
manualUrl: "#",
totpSecret: "G4nsI8lQagRMUchH8jEG",
otpCredentials: [],
policy: {
supportedApplications: ["FreeOTP", "Google Authenticator"],
algorithm: "HmacSHA1",
digits: 6,
lookAheadWindow: 1,
type: "totp",
period: 30,
},
},
}),
id<KcContextBase.LogoutConfirm>({
...kcContextCommonMock,
"pageId": "logout-confirm.ftl",
"url": {
...kcContextCommonMock.url,
"logoutConfirmAction": "Continuer?",
},
"client": {
"clientId": "myApp",
"baseUrl": "#",
},
"logoutConfirm": { "code": "123", skipLink: false },
}),
];

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,283 @@
//This code was automatically generated by running dist/bin/generate-i18n-messages.js
//PLEASE DO NOT EDIT MANUALLY
/* spell-checker: disable */
export const kcMessages = {
"ca": {
"invalidPasswordHistoryMessage": "Contrasenya incorrecta: no pot ser igual a cap de les últimes {0} contrasenyes.",
"invalidPasswordMinDigitsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.",
"invalidPasswordMinLengthMessage": "Contrasenya incorrecta: longitud mínima {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} lletres minúscules.",
"invalidPasswordMinSpecialCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} caràcters especials.",
"invalidPasswordMinUpperCaseCharsMessage": "Contrasenya incorrecta: ha de contenir almenys {0} lletres majúscules.",
"invalidPasswordNotUsernameMessage": "Contrasenya incorrecta: no pot ser igual al nom d'usuari.",
"invalidPasswordRegexPatternMessage": "Contrasenya incorrecta: no compleix l'expressió regular.",
},
"de": {
"invalidPasswordMinLengthMessage": "Ungültiges Passwort: muss mindestens {0} Zeichen beinhalten.",
"invalidPasswordMinLowerCaseCharsMessage": "Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.",
"invalidPasswordMinDigitsMessage": "Ungültiges Passwort: muss mindestens {0} Ziffern beinhalten.",
"invalidPasswordMinUpperCaseCharsMessage": "Ungültiges Passwort: muss mindestens {0} Großbuchstaben beinhalten.",
"invalidPasswordMinSpecialCharsMessage": "Ungültiges Passwort: muss mindestens {0} Sonderzeichen beinhalten.",
"invalidPasswordNotUsernameMessage": "Ungültiges Passwort: darf nicht identisch mit dem Benutzernamen sein.",
"invalidPasswordNotEmailMessage": "Ungültiges Passwort: darf nicht identisch mit der E-Mail-Adresse sein.",
"invalidPasswordRegexPatternMessage": "Ungültiges Passwort: stimmt nicht mit Regex-Muster überein.",
"invalidPasswordHistoryMessage": "Ungültiges Passwort: darf nicht identisch mit einem der letzten {0} Passwörter sein.",
"invalidPasswordBlacklistedMessage": "Ungültiges Passwort: Passwort ist zu bekannt und auf der schwarzen Liste.",
"invalidPasswordGenericMessage": "Ungültiges Passwort: neues Passwort erfüllt die Passwort-Anforderungen nicht.",
},
"en": {
"invalidPasswordMinLengthMessage": "Invalid password: minimum length {0}.",
"invalidPasswordMaxLengthMessage": "Invalid password: maximum length {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Invalid password: must contain at least {0} lower case characters.",
"invalidPasswordMinDigitsMessage": "Invalid password: must contain at least {0} numerical digits.",
"invalidPasswordMinUpperCaseCharsMessage": "Invalid password: must contain at least {0} upper case characters.",
"invalidPasswordMinSpecialCharsMessage": "Invalid password: must contain at least {0} special characters.",
"invalidPasswordNotUsernameMessage": "Invalid password: must not be equal to the username.",
"invalidPasswordNotEmailMessage": "Invalid password: must not be equal to the email.",
"invalidPasswordRegexPatternMessage": "Invalid password: fails to match regex pattern(s).",
"invalidPasswordHistoryMessage": "Invalid password: must not be equal to any of last {0} passwords.",
"invalidPasswordBlacklistedMessage": "Invalid password: password is blacklisted.",
"invalidPasswordGenericMessage": "Invalid password: new password does not match password policies.",
"ldapErrorEditModeMandatory": "Edit Mode is mandatory",
"ldapErrorInvalidCustomFilter": 'Custom configured LDAP filter does not start with "(" or does not end with ")".',
"ldapErrorConnectionTimeoutNotNumber": "Connection Timeout must be a number",
"ldapErrorReadTimeoutNotNumber": "Read Timeout must be a number",
"ldapErrorMissingClientId": "Client ID needs to be provided in config when Realm Roles Mapping is not used.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"Not possible to preserve group inheritance and use UID membership type together.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Can not set write only when LDAP provider mode is not WRITABLE",
"ldapErrorCantWriteOnlyAndReadOnly": "Can not set write-only and read-only together",
"ldapErrorCantEnableStartTlsAndConnectionPooling": "Can not enable both StartTLS and connection pooling.",
"ldapErrorCantEnableUnsyncedAndImportOff": "Can not disable Importing users when LDAP provider mode is UNSYNCED",
"ldapErrorMissingGroupsPathGroup": "Groups path group does not exist - please create the group on specified path first",
"ldapErrorValidatePasswordPolicyAvailableForWritableOnly": "Validate Password Policy is applicable only with WRITABLE edit mode",
"clientRedirectURIsFragmentError": "Redirect URIs must not contain an URI fragment",
"clientRootURLFragmentError": "Root URL must not contain an URL fragment",
"clientRootURLIllegalSchemeError": "Root URL uses an illegal scheme",
"clientBaseURLIllegalSchemeError": "Base URL uses an illegal scheme",
"backchannelLogoutUrlIllegalSchemeError": "Backchannel logout URL uses an illegal scheme",
"clientRedirectURIsIllegalSchemeError": "A redirect URI uses an illegal scheme",
"clientBaseURLInvalid": "Base URL is not a valid URL",
"clientRootURLInvalid": "Root URL is not a valid URL",
"clientRedirectURIsInvalid": "A redirect URI is not a valid URI",
"backchannelLogoutUrlIsInvalid": "Backchannel logout URL is not a valid URL",
"pairwiseMalformedClientRedirectURI": "Client contained an invalid redirect URI.",
"pairwiseClientRedirectURIsMissingHost": "Client redirect URIs must contain a valid host component.",
"pairwiseClientRedirectURIsMultipleHosts":
"Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.",
"pairwiseMalformedSectorIdentifierURI": "Malformed Sector Identifier URI.",
"pairwiseFailedToGetRedirectURIs": "Failed to get redirect URIs from the Sector Identifier URI.",
"pairwiseRedirectURIsMismatch": "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.",
"duplicatedJwksSettings": 'The "Use JWKS" switch and the switch "Use JWKS URL" cannot be ON at the same time.',
"error-invalid-value": "Invalid value.",
"error-invalid-blank": "Please specify value.",
"error-empty": "Please specify value.",
"error-invalid-length": "Attribute {0} must have a length between {1} and {2}.",
"error-invalid-length-too-short": "Attribute {0} must have minimal length of {1}.",
"error-invalid-length-too-long": "Attribute {0} must have maximal length of {2}.",
"error-invalid-email": "Invalid email address.",
"error-invalid-number": "Invalid number.",
"error-number-out-of-range": "Attribute {0} must be a number between {1} and {2}.",
"error-number-out-of-range-too-small": "Attribute {0} must have minimal value of {1}.",
"error-number-out-of-range-too-big": "Attribute {0} must have maximal value of {2}.",
"error-pattern-no-match": "Invalid value.",
"error-invalid-uri": "Invalid URL.",
"error-invalid-uri-scheme": "Invalid URL scheme.",
"error-invalid-uri-fragment": "Invalid URL fragment.",
"error-user-attribute-required": "Please specify attribute {0}.",
"error-invalid-date": "Attribute {0} is invalid date.",
"error-user-attribute-read-only": "Attribute {0} is read only.",
"error-username-invalid-character": "{0} contains invalid character.",
"error-person-name-invalid-character": "{0} contains invalid character.",
},
"es": {
"invalidPasswordMinLengthMessage": "Contraseña incorrecta: longitud mínima {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Contraseña incorrecta: debe contener al menos {0} letras minúsculas.",
"invalidPasswordMinDigitsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres numéricos.",
"invalidPasswordMinUpperCaseCharsMessage": "Contraseña incorrecta: debe contener al menos {0} letras mayúsculas.",
"invalidPasswordMinSpecialCharsMessage": "Contraseña incorrecta: debe contener al menos {0} caracteres especiales.",
"invalidPasswordNotUsernameMessage": "Contraseña incorrecta: no puede ser igual al nombre de usuario.",
"invalidPasswordRegexPatternMessage": "Contraseña incorrecta: no cumple la expresión regular.",
"invalidPasswordHistoryMessage": "Contraseña incorrecta: no puede ser igual a ninguna de las últimas {0} contraseñas.",
},
"fi": {},
"fr": {
"invalidPasswordMinLengthMessage": "Mot de passe invalide : longueur minimale requise de {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Mot de passe invalide : doit contenir au moins {0} lettre(s) en minuscule.",
"invalidPasswordMinDigitsMessage": "Mot de passe invalide : doit contenir au moins {0} chiffre(s).",
"invalidPasswordMinUpperCaseCharsMessage": "Mot de passe invalide : doit contenir au moins {0} lettre(s) en majuscule.",
"invalidPasswordMinSpecialCharsMessage": "Mot de passe invalide : doit contenir au moins {0} caractère(s) spéciaux.",
"invalidPasswordNotUsernameMessage": "Mot de passe invalide : ne doit pas être identique au nom d'utilisateur.",
"invalidPasswordRegexPatternMessage": "Mot de passe invalide : ne valide pas l'expression rationnelle.",
"invalidPasswordHistoryMessage": "Mot de passe invalide : ne doit pas être égal aux {0} derniers mot de passe.",
},
"it": {},
"ja": {
"invalidPasswordMinLengthMessage": "無効なパスワード: 最小{0}の長さが必要です。",
"invalidPasswordMinLowerCaseCharsMessage": "無効なパスワード: 少なくとも{0}文字の小文字を含む必要があります。",
"invalidPasswordMinDigitsMessage": "無効なパスワード: 少なくとも{0}文字の数字を含む必要があります。",
"invalidPasswordMinUpperCaseCharsMessage": "無効なパスワード: 少なくとも{0}文字の大文字を含む必要があります。",
"invalidPasswordMinSpecialCharsMessage": "無効なパスワード: 少なくとも{0}文字の特殊文字を含む必要があります。",
"invalidPasswordNotUsernameMessage": "無効なパスワード: ユーザー名と同じパスワードは禁止されています。",
"invalidPasswordRegexPatternMessage": "無効なパスワード: 正規表現パターンと一致しません。",
"invalidPasswordHistoryMessage": "無効なパスワード: 最近の{0}パスワードのいずれかと同じパスワードは禁止されています。",
"invalidPasswordBlacklistedMessage": "無効なパスワード: パスワードがブラックリストに含まれています。",
"invalidPasswordGenericMessage": "無効なパスワード: 新しいパスワードはパスワード・ポリシーと一致しません。",
"ldapErrorInvalidCustomFilter": "LDAPフィルターのカスタム設定が、「(」から開始または「)」で終了となっていません。",
"ldapErrorConnectionTimeoutNotNumber": "接続タイムアウトは数字でなければなりません",
"ldapErrorReadTimeoutNotNumber": "読み取りタイムアウトは数字でなければなりません",
"ldapErrorMissingClientId": "レルムロール・マッピングを使用しない場合は、クライアントIDは設定内で提供される必要があります。",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"グループの継承を維持することと、UIDメンバーシップ・タイプを使用することは同時にできません。",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "LDAPプロバイダー・モードがWRITABLEではない場合は、write onlyを設定することはできません。",
"ldapErrorCantWriteOnlyAndReadOnly": "write-onlyとread-onlyを一緒に設定することはできません。",
"ldapErrorCantEnableStartTlsAndConnectionPooling": "StartTLSと接続プーリングの両方を有効にできません。",
"clientRedirectURIsFragmentError": "リダイレクトURIにURIフラグメントを含めることはできません。",
"clientRootURLFragmentError": "ルートURLにURLフラグメントを含めることはできません。",
"pairwiseMalformedClientRedirectURI": "クライアントに無効なリダイレクトURIが含まれていました。",
"pairwiseClientRedirectURIsMissingHost": "クライアントのリダイレクトURIには有効なホスト・コンポーネントが含まれている必要があります。",
"pairwiseClientRedirectURIsMultipleHosts":
"設定されたセレクター識別子URIがない場合は、クライアントのリダイレクトURIは複数のホスト・コンポーネントを含むことはできません。",
"pairwiseMalformedSectorIdentifierURI": "不正なセレクター識別子URIです。",
"pairwiseFailedToGetRedirectURIs": "セクター識別子URIからリダイレクトURIを取得できませんでした。",
"pairwiseRedirectURIsMismatch": "クライアントのリダイレクトURIは、セクター識別子URIからフェッチされたリダイレクトURIと一致しません。",
},
"lt": {
"invalidPasswordMinLengthMessage": "Per trumpas slaptažodis: mažiausias ilgis {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} mažąją raidę.",
"invalidPasswordMinDigitsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} skaitmenį.",
"invalidPasswordMinUpperCaseCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} didžiąją raidę.",
"invalidPasswordMinSpecialCharsMessage": "Neteisingas slaptažodis: privaloma įvesti {0} specialų simbolį.",
"invalidPasswordNotUsernameMessage": "Neteisingas slaptažodis: slaptažodis negali sutapti su naudotojo vardu.",
"invalidPasswordRegexPatternMessage": "Neteisingas slaptažodis: slaptažodis netenkina regex taisyklės(ių).",
"invalidPasswordHistoryMessage": "Neteisingas slaptažodis: slaptažodis negali sutapti su prieš tai buvusiais {0} slaptažodžiais.",
"ldapErrorInvalidCustomFilter": 'Sukonfigūruotas LDAP filtras neprasideda "(" ir nesibaigia ")" simboliais.',
"ldapErrorMissingClientId": "Privaloma nurodyti kliento ID kai srities rolių susiejimas nėra nenaudojamas.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Grupių paveldėjimo ir UID narystės tipas kartu negali būti naudojami.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Negalima nustatyti rašymo rėžimo kuomet LDAP teikėjo rėžimas ne WRITABLE",
"ldapErrorCantWriteOnlyAndReadOnly": "Negalima nustatyti tik rašyti ir tik skaityti kartu",
"clientRedirectURIsFragmentError": "Nurodykite URI fragmentą, kurio negali būti peradresuojamuose URI adresuose",
"clientRootURLFragmentError": "Nurodykite URL fragmentą, kurio negali būti šakniniame URL adrese",
"pairwiseMalformedClientRedirectURI": "Klientas pateikė neteisingą nukreipimo nuorodą.",
"pairwiseClientRedirectURIsMissingHost": "Kliento nukreipimo nuorodos privalo būti nurodytos su serverio vardo komponentu.",
"pairwiseClientRedirectURIsMultipleHosts":
"Kuomet nesukonfigūruotas sektoriaus identifikatoriaus URL, kliento nukreipimo nuorodos privalo talpinti ne daugiau kaip vieną skirtingą serverio vardo komponentą.",
"pairwiseMalformedSectorIdentifierURI": "Neteisinga sektoriaus identifikatoriaus URI.",
"pairwiseFailedToGetRedirectURIs": "Nepavyko gauti nukreipimo nuorodų iš sektoriaus identifikatoriaus URI.",
"pairwiseRedirectURIsMismatch": "Kliento nukreipimo nuoroda neatitinka nukreipimo nuorodų iš sektoriaus identifikatoriaus URI.",
},
"lv": {},
"nl": {
"invalidPasswordMinLengthMessage": "Ongeldig wachtwoord: de minimale lengte is {0} karakters.",
"invalidPasswordMinLowerCaseCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} kleine letters bevatten.",
"invalidPasswordMinDigitsMessage": "Ongeldig wachtwoord: het moet minstens {0} getallen bevatten.",
"invalidPasswordMinUpperCaseCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} hoofdletters bevatten.",
"invalidPasswordMinSpecialCharsMessage": "Ongeldig wachtwoord: het moet minstens {0} speciale karakters bevatten.",
"invalidPasswordNotUsernameMessage": "Ongeldig wachtwoord: het mag niet overeenkomen met de gebruikersnaam.",
"invalidPasswordRegexPatternMessage": "Ongeldig wachtwoord: het voldoet niet aan het door de beheerder ingestelde patroon.",
"invalidPasswordHistoryMessage": "Ongeldig wachtwoord: het mag niet overeen komen met een van de laatste {0} wachtwoorden.",
"invalidPasswordGenericMessage": "Ongeldig wachtwoord: het nieuwe wachtwoord voldoet niet aan het wachtwoordbeleid.",
"ldapErrorInvalidCustomFilter": 'LDAP filter met aangepaste configuratie start niet met "(" of eindigt niet met ")".',
"ldapErrorConnectionTimeoutNotNumber": "Verbindingstimeout moet een getal zijn",
"ldapErrorReadTimeoutNotNumber": "Lees-timeout moet een getal zijn",
"ldapErrorMissingClientId": "Client ID moet ingesteld zijn als Realm Roles Mapping niet gebruikt wordt.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Kan groepsovererving niet behouden bij UID-lidmaatschapstype.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Alleen-schrijven niet mogelijk als LDAP provider mode niet WRITABLE is",
"ldapErrorCantWriteOnlyAndReadOnly": "Alleen-schrijven en alleen-lezen mogen niet tegelijk ingesteld zijn",
"clientRedirectURIsFragmentError": "Redirect URIs mogen geen URI fragment bevatten",
"clientRootURLFragmentError": "Root URL mag geen URL fragment bevatten",
"pairwiseMalformedClientRedirectURI": "Client heeft een ongeldige redirect URI.",
"pairwiseClientRedirectURIsMissingHost": "Client redirect URIs moeten een geldige host-component bevatten.",
"pairwiseClientRedirectURIsMultipleHosts":
"Zonder een geconfigureerde Sector Identifier URI mogen client redirect URIs niet meerdere host componenten hebben.",
"pairwiseMalformedSectorIdentifierURI": "Onjuist notatie in Sector Identifier URI.",
"pairwiseFailedToGetRedirectURIs": "Kon geen redirect URIs verkrijgen van de Sector Identifier URI.",
"pairwiseRedirectURIsMismatch": "Client redirect URIs komen niet overeen met redict URIs ontvangen van de Sector Identifier URI.",
},
"no": {
"invalidPasswordMinLengthMessage": "Ugyldig passord: minimum lengde {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "Ugyldig passord: må inneholde minst {0} små bokstaver.",
"invalidPasswordMinDigitsMessage": "Ugyldig passord: må inneholde minst {0} sifre.",
"invalidPasswordMinUpperCaseCharsMessage": "Ugyldig passord: må inneholde minst {0} store bokstaver.",
"invalidPasswordMinSpecialCharsMessage": "Ugyldig passord: må inneholde minst {0} spesialtegn.",
"invalidPasswordNotUsernameMessage": "Ugyldig passord: kan ikke være likt brukernavn.",
"invalidPasswordRegexPatternMessage": "Ugyldig passord: tilfredsstiller ikke kravene for passord-mønster.",
"invalidPasswordHistoryMessage": "Ugyldig passord: kan ikke være likt noen av de {0} foregående passordene.",
"ldapErrorInvalidCustomFilter": 'Tilpasset konfigurasjon av LDAP-filter starter ikke med "(" eller slutter ikke med ")".',
"ldapErrorMissingClientId": "KlientID må være tilgjengelig i config når sikkerhetsdomenerollemapping ikke brukes.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Ikke mulig å bevare gruppearv og samtidig bruke UID medlemskapstype.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Kan ikke sette write-only når LDAP leverandør-modus ikke er WRITABLE",
"ldapErrorCantWriteOnlyAndReadOnly": "Kan ikke sette både write-only og read-only",
},
"pl": {},
"pt-BR": {
"invalidPasswordMinLengthMessage": "Senha inválida: deve conter ao menos {0} caracteres.",
"invalidPasswordMinLowerCaseCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres minúsculos.",
"invalidPasswordMinDigitsMessage": "Senha inválida: deve conter ao menos {0} digitos numéricos.",
"invalidPasswordMinUpperCaseCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres maiúsculos.",
"invalidPasswordMinSpecialCharsMessage": "Senha inválida: deve conter ao menos {0} caracteres especiais.",
"invalidPasswordNotUsernameMessage": "Senha inválida: não deve ser igual ao nome de usuário.",
"invalidPasswordRegexPatternMessage": "Senha inválida: falha ao passar por padrões.",
"invalidPasswordHistoryMessage": "Senha inválida: não deve ser igual às últimas {0} senhas.",
"ldapErrorInvalidCustomFilter": 'Filtro LDAP não inicia com "(" ou não termina com ")".',
"ldapErrorMissingClientId": "ID do cliente precisa ser definido na configuração quando mapeamentos de Roles do Realm não é utilizado.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType":
"Não é possível preservar herança de grupos e usar tipo de associação de UID ao mesmo tempo.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "Não é possível definir modo de somente escrita quando o provedor LDAP não suporta escrita",
"ldapErrorCantWriteOnlyAndReadOnly": "Não é possível definir somente escrita e somente leitura ao mesmo tempo",
"clientRedirectURIsFragmentError": "URIs de redirecionamento não podem conter fragmentos",
"clientRootURLFragmentError": "URL raiz não pode conter fragmentos",
},
"ru": {
"invalidPasswordMinLengthMessage": "Некорректный пароль: длина пароля должна быть не менее {0} символов(а).",
"invalidPasswordMinDigitsMessage": "Некорректный пароль: должен содержать не менее {0} цифр(ы).",
"invalidPasswordMinLowerCaseCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в нижнем регистре.",
"invalidPasswordMinUpperCaseCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} символов(а) в верхнем регистре.",
"invalidPasswordMinSpecialCharsMessage": "Некорректный пароль: пароль должен содержать не менее {0} спецсимволов(а).",
"invalidPasswordNotUsernameMessage": "Некорректный пароль: пароль не должен совпадать с именем пользователя.",
"invalidPasswordRegexPatternMessage": "Некорректный пароль: пароль не прошел проверку по регулярному выражению.",
"invalidPasswordHistoryMessage": "Некорректный пароль: пароль не должен совпадать с последним(и) {0} паролем(ями).",
"invalidPasswordGenericMessage": "Некорректный пароль: новый пароль не соответствует правилам пароля.",
"ldapErrorInvalidCustomFilter": 'Сконфигурированный пользователем фильтр LDAP не должен начинаться с "(" или заканчиваться на ")".',
"ldapErrorMissingClientId": "Client ID должен быть настроен в конфигурации, если не используется сопоставление ролей в realm.",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "Не удалось унаследовать группу и использовать членство UID типа вместе.",
"ldapErrorCantWriteOnlyForReadOnlyLdap": 'Невозможно установить режим "только на запись", когда LDAP провайдер не в режиме WRITABLE',
"ldapErrorCantWriteOnlyAndReadOnly": 'Невозможно одновременно установить режимы "только на чтение" и "только на запись"',
"clientRedirectURIsFragmentError": "URI перенаправления не должен содержать фрагмент URI",
"clientRootURLFragmentError": "Корневой URL не должен содержать фрагмент URL ",
"pairwiseMalformedClientRedirectURI": "Клиент содержит некорректный URI перенаправления.",
"pairwiseClientRedirectURIsMissingHost": "URI перенаправления клиента должен содержать корректный компонент хоста.",
"pairwiseClientRedirectURIsMultipleHosts":
"Без конфигурации по части идентификатора URI, URI перенаправления клиента не может содержать несколько компонентов хоста.",
"pairwiseMalformedSectorIdentifierURI": "Искаженная часть идентификатора URI.",
"pairwiseFailedToGetRedirectURIs": "Не удалось получить идентификаторы URI перенаправления из части идентификатора URI.",
"pairwiseRedirectURIsMismatch": "Клиент URI переадресации не соответствует URI переадресации, полученной из части идентификатора URI.",
},
"zh-CN": {
"invalidPasswordMinLengthMessage": "无效的密码:最短长度 {0}.",
"invalidPasswordMinLowerCaseCharsMessage": "无效的密码:至少包含 {0} 小写字母",
"invalidPasswordMinDigitsMessage": "无效的密码:至少包含 {0} 个数字",
"invalidPasswordMinUpperCaseCharsMessage": "无效的密码:最短长度 {0} 大写字母",
"invalidPasswordMinSpecialCharsMessage": "无效的密码:最短长度 {0} 特殊字符",
"invalidPasswordNotUsernameMessage": "无效的密码: 不可以与用户名相同",
"invalidPasswordRegexPatternMessage": "无效的密码: 无法与正则表达式匹配",
"invalidPasswordHistoryMessage": "无效的密码:不能与最后使用的 {0} 个密码相同",
"ldapErrorInvalidCustomFilter": '定制的 LDAP过滤器不是以 "(" 开头或以 ")"结尾.',
"ldapErrorConnectionTimeoutNotNumber": "Connection Timeout 必须是个数字",
"ldapErrorMissingClientId": "当域角色映射未启用时,客户端 ID 需要指定。",
"ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType": "无法在使用UID成员类型的同时维护组继承属性。",
"ldapErrorCantWriteOnlyForReadOnlyLdap": "当LDAP提供方不是可写模式时无法设置只写",
"ldapErrorCantWriteOnlyAndReadOnly": "无法同时设置只读和只写",
"clientRedirectURIsFragmentError": "重定向URL不应包含URI片段",
"clientRootURLFragmentError": "根URL 不应包含 URL 片段",
"pairwiseMalformedClientRedirectURI": "客户端包含一个无效的重定向URL",
"pairwiseClientRedirectURIsMissingHost": "客户端重定向URL需要有一个有效的主机",
"pairwiseClientRedirectURIsMultipleHosts":
"Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.",
"pairwiseMalformedSectorIdentifierURI": "Malformed Sector Identifier URI.",
"pairwiseFailedToGetRedirectURIs": "无法从服务器获得重定向URL",
"pairwiseRedirectURIsMismatch": "客户端的重定向URI与服务器端获取的URI配置不匹配。",
},
};
/* spell-checker: enable */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,9 @@ import "minimal-polyfills/Object.fromEntries";
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
import ReactMarkdown from "react-markdown";
import memoize from "memoizee";
import { kcMessages as kcMessagesBase } from "./generated_kcMessages/15.0.2/login";
import { kcMessages as kcMessagesBase } from "./generated_kcMessages/18.0.1/login";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
export const kcMessages = {
...kcMessagesBase,
@ -19,17 +20,47 @@ export const kcMessages = {
"fr": {
...kcMessagesBase["fr"],
/* spell-checker: disable */
"shouldBeEqual": "{0} doit être egale à {1}",
"shouldBeEqual": "{0} doit être égal à {1}",
"shouldBeDifferent": "{0} doit être différent de {1}",
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
"mustBeAnInteger": "Doit être un nombre entiers",
"mustBeAnInteger": "Doit être un nombre entier",
"notAValidOption": "N'est pas une option valide",
"logoutConfirmTitle": "Déconnexion",
"logoutConfirmHeader": "Êtes-vous sûr(e) de vouloir vous déconnecter ?",
"doLogout": "Se déconnecter",
/* spell-checker: enable */
},
};
export type KcLanguageTag = keyof typeof kcMessages;
export const kcLanguageTags = [
"en",
"fr",
"ca",
"cs",
"da",
"de",
"es",
"hu",
"it",
"ja",
"lt",
"nl",
"no",
"pl",
"pt-BR",
"ru",
"sk",
"sv",
"tr",
"zh-CN",
"fi",
"lv",
] as const;
assert<Equals<KcLanguageTag, typeof kcLanguageTags[number]>>();
type KcContextLike = { locale?: { currentLanguageTag: KcLanguageTag } };
export function getCurrentKcLanguageTag(kcContext: KcContextLike) {

View File

@ -4,6 +4,7 @@ export * from "./i18n";
export { useDownloadTerms } from "./components/Terms";
export * from "./components/KcApp";
export * from "./components/KcProps";
export * from "./keycloakJsAdapter";
export * from "./useFormValidationSlice";

View File

@ -44,8 +44,9 @@ export declare namespace keycloak_js {
export function createKeycloakAdapter(params: {
keycloakInstance: keycloak_js.KeycloakInstance;
transformUrlBeforeRedirect(url: string): string;
getRedirectMethod?: () => "overwrite location.href" | "location.replace";
}): keycloak_js.KeycloakAdapter {
const { keycloakInstance, transformUrlBeforeRedirect } = params;
const { keycloakInstance, transformUrlBeforeRedirect, getRedirectMethod = () => "overwrite location.href" } = params;
const neverResolvingPromise: keycloak_js.KeycloakPromise<void, void> = Object.defineProperties(new Promise(() => {}), {
"success": { "value": () => {} },
@ -54,25 +55,50 @@ export function createKeycloakAdapter(params: {
return {
"login": options => {
window.location.href = transformUrlBeforeRedirect(keycloakInstance.createLoginUrl(options));
const newHref = transformUrlBeforeRedirect(keycloakInstance.createLoginUrl(options));
switch (getRedirectMethod()) {
case "location.replace":
window.location.replace(newHref);
break;
case "overwrite location.href":
window.location.href = newHref;
break;
}
return neverResolvingPromise;
},
"register": options => {
const newHref = transformUrlBeforeRedirect(keycloakInstance.createRegisterUrl(options));
switch (getRedirectMethod()) {
case "location.replace":
window.location.replace(newHref);
break;
case "overwrite location.href":
window.location.href = newHref;
break;
}
return neverResolvingPromise;
},
"logout": options => {
window.location.replace(transformUrlBeforeRedirect(keycloakInstance.createLogoutUrl(options)));
return neverResolvingPromise;
},
"register": options => {
window.location.href = transformUrlBeforeRedirect(keycloakInstance.createRegisterUrl(options));
return neverResolvingPromise;
},
"accountManagement": () => {
var accountUrl = transformUrlBeforeRedirect(keycloakInstance.createAccountUrl());
if (typeof accountUrl !== "undefined") {
window.location.href = accountUrl;
} else {
const accountUrl = transformUrlBeforeRedirect(keycloakInstance.createAccountUrl());
if (accountUrl === "undefined") {
throw new Error("Not supported by the OIDC server");
}
switch (getRedirectMethod()) {
case "location.replace":
window.location.replace(accountUrl);
break;
case "overwrite location.href":
window.location.href = accountUrl;
break;
}
return neverResolvingPromise;
},
"redirectUri": options => {

View File

@ -313,6 +313,9 @@ export function useFormValidationSlice(params: {
};
passwordRequired: boolean;
realm: { registrationEmailAsUsername: boolean };
locale?: {
currentLanguageTag: KcLanguageTag;
};
};
/** NOTE: Try to avoid passing a new ref every render for better performances. */
passwordValidators?: Validators;
@ -382,6 +385,7 @@ export function useFormValidationSlice(params: {
"profile": {
"attributes": attributesWithPassword,
},
"locale": kcContext.locale,
},
});

View File

@ -8,7 +8,7 @@ generateKeycloakThemeResources({
"themeName": "keycloakify-demo-app",
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
"keycloakThemeEmailDirPath": pathJoin(sampleReactProjectDirPath, "keycloak_theme_email"),
"keycloakThemeEmailDirPath": pathJoin(sampleReactProjectDirPath, "keycloak_email"),
"urlPathname": "/keycloakify-demo-app/",
"urlOrigin": undefined,
"extraPagesId": ["my-custom-page.ftl"],

1
src/test/bin/index.ts Normal file
View File

@ -0,0 +1 @@
import "./replaceImportFromStatic";

View File

@ -1,3 +1,4 @@
import "./replaceImportFromStatic";
import { setupSampleReactProject, sampleReactProjectDirPath } from "./setupSampleReactProject";
import * as st from "scripting-tools";
import { join as pathJoin } from "path";

View File

@ -1,67 +1,472 @@
import {
replaceImportsFromStaticInJsCode,
replaceImportsInInlineCssCode,
replaceImportsInCssCode,
generateCssCodeToDefineGlobals,
} from "../../bin/build-keycloak-theme/replaceImportFromStatic";
import { assert } from "tsafe/assert";
import { same } from "evt/tools/inDepth/same";
import { assetIsSameCode } from "../tools/assertIsSameCode";
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": `
/*
NOTES:
When not compiled with --external-assets urlOrigin will always be undefined regardless of the "homepage" field.
When compiled with --external-assets and we have a home page filed like "https://example.com" or "https://example.com/x/y/z" urlOrigin will be "https://example.com"
Regardless of if it's compiled with --external-assets or not, if "homepage" is like "https://example.com/x/y/z" urlPathname will be "/x/y/z/"
*/
{
const jsCodeUntransformed = `
function f() {
return a.p+"static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
function f2() {
return a.p+"static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
`,
"urlOrigin": undefined,
});
const { fixedJsCode: fixedJsCodeExternal } = replaceImportsFromStaticInJsCode({
"jsCode": `
function f() {
function sameAsF() {
return a.p+"static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
function f2() {
return a.p+"static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
n.u=function(e){return"static/js/" + e + "." + {
147: "6c5cee76",
787: "8da10fcf",
922: "be170a73"
} [e] + ".chunk.js"
}
`,
"urlOrigin": "https://www.example.com",
});
`;
console.log({ fixedJsCode, fixedJsCodeExternal });
{
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": jsCodeUntransformed,
"urlOrigin": undefined,
});
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
"cssCode": `
const fixedJsCodeExpected = `
function f() {
return window.kcContext.url.resourcesPath + "/build/static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
.my-div {
background: url(/logo192.png) no-repeat center center;
function sameAsF() {
return window.kcContext.url.resourcesPath + "/build/static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
n[(function (){
Object.defineProperty(n, "p", {
get: function() { return window.kcContext.url.resourcesPath; },
set: function (){}
});
return "u";
})()] = function(e) {
return "/build/static/js/" + e + "." + {
147: "6c5cee76",
787: "8da10fcf",
922: "be170a73"
} [e] + ".chunk.js"
}
`;
assetIsSameCode(fixedJsCode, fixedJsCodeExpected);
}
.my-div2 {
background: url(/logo192.png) no-repeat center center;
{
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": jsCodeUntransformed,
"urlOrigin": "https://demo-app.keycloakify.dev",
});
const fixedJsCodeExpected = `
function f() {
return ("kcContext" in window ? "https://demo-app.keycloakify.dev" : "") + a.p + "static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
function sameAsF() {
return ("kcContext" in window ? "https://demo-app.keycloakify.dev" : "") + a.p + "static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
n[(function (){
var p= "";
Object.defineProperty(n, "p", {
get: function() { return ("kcContext" in window ? "https://demo-app.keycloakify.dev" : "") + p; },
set: function (value){ p = value; }
});
return "u";
})()] = function(e) {
return "static/js/" + e + "." + {
147: "6c5cee76",
787: "8da10fcf",
922: "be170a73"
} [e] + ".chunk.js"
}
`;
assetIsSameCode(fixedJsCode, fixedJsCodeExpected);
}
}
{
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
"cssCode": `
.my-div {
background: url(/logo192.png) no-repeat center center;
}
.my-div2 {
background: url(/logo192.png) no-repeat center center;
}
.my-div {
background-image: url(/static/media/something.svg);
}
`,
});
const fixedCssCodeExpected = `
.my-div {
background: var(--url1f9ef5a892c104c);
}
.my-div2 {
background: var(--url1f9ef5a892c104c);
}
.my-div {
background-image: var(--urldd75cab58377c19);
}
`;
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
const cssGlobalsToDefineExpected = {
"url1f9ef5a892c104c": "url(/logo192.png) no-repeat center center",
"urldd75cab58377c19": "url(/static/media/something.svg)",
};
assert(same(cssGlobalsToDefine, cssGlobalsToDefineExpected));
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
"urlPathname": "/",
});
const cssCodeToPrependInHeadExpected = `
:root {
--url1f9ef5a892c104c: url(\${url.resourcesPath}/build/logo192.png) no-repeat center center;
--urldd75cab58377c19: url(\${url.resourcesPath}/build/static/media/something.svg);
}
`;
assetIsSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected);
}
{
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
"cssCode": `
.my-div {
background: url(/x/y/z/logo192.png) no-repeat center center;
}
.my-div2 {
background: url(/x/y/z/logo192.png) no-repeat center center;
}
.my-div {
background-image: url(/x/y/z/static/media/something.svg);
}
`,
});
const fixedCssCodeExpected = `
.my-div {
background: var(--urlf8277cddaa2be78);
}
.my-div2 {
background: var(--urlf8277cddaa2be78);
}
.my-div {
background-image: var(--url8bdc0887b97ac9a);
}
`;
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
const cssGlobalsToDefineExpected = {
"urlf8277cddaa2be78": "url(/x/y/z/logo192.png) no-repeat center center",
"url8bdc0887b97ac9a": "url(/x/y/z/static/media/something.svg)",
};
assert(same(cssGlobalsToDefine, cssGlobalsToDefineExpected));
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
"urlPathname": "/x/y/z/",
});
const cssCodeToPrependInHeadExpected = `
:root {
--urlf8277cddaa2be78: url(\${url.resourcesPath}/build/logo192.png) no-repeat center center;
--url8bdc0887b97ac9a: url(\${url.resourcesPath}/build/static/media/something.svg);
}
`;
assetIsSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected);
}
{
const cssCode = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("/fonts/WorkSans/worksans-regular-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("/fonts/WorkSans/worksans-medium-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/fonts/WorkSans/worksans-semibold-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
}
`;
{
const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode,
"urlOrigin": undefined,
"urlPathname": "/",
});
const fixedCssCodeExpected = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-regular-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-medium-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-semibold-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-bold-webfont.woff2)
format("woff2");
}
`;
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
}
.my-div {
background-image: url(/static/media/something.svg);
{
const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode,
"urlOrigin": "https://demo-app.keycloakify.dev",
"urlPathname": "/",
});
const fixedCssCodeExpected = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/fonts/WorkSans/worksans-regular-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/fonts/WorkSans/worksans-medium-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/fonts/WorkSans/worksans-semibold-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/fonts/WorkSans/worksans-bold-webfont.woff2)
format("woff2");
}
`;
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
}
`,
});
}
console.log({ fixedCssCode, cssGlobalsToDefine });
{
const cssCode = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url("/x/y/z/fonts/WorkSans/worksans-regular-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("/x/y/z/fonts/WorkSans/worksans-medium-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/x/y/z/fonts/WorkSans/worksans-semibold-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("/x/y/z/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
}
`;
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
"urlPathname": "/",
});
{
const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode,
"urlOrigin": undefined,
"urlPathname": "/x/y/z/",
});
console.log({ cssCodeToPrependInHead });
const fixedCssCodeExpected = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-regular-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-medium-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-semibold-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-bold-webfont.woff2)
format("woff2");
}
`;
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
}
{
const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode,
"urlOrigin": "https://demo-app.keycloakify.dev",
"urlPathname": "/x/y/z/",
});
const fixedCssCodeExpected = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/x/y/z/fonts/WorkSans/worksans-regular-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/x/y/z/fonts/WorkSans/worksans-medium-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/x/y/z/fonts/WorkSans/worksans-semibold-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/x/y/z/fonts/WorkSans/worksans-bold-webfont.woff2)
format("woff2");
}
`;
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
}
}
console.log("PASS replace import from static");

View File

@ -0,0 +1,7 @@
import { assert } from "tsafe/assert";
export function assetIsSameCode(code1: string, code2: string, message?: string): void {
const removeSpacesAndNewLines = (code: string) => code.replace(/\s/g, "").replace(/\n/g, "");
assert(removeSpacesAndNewLines(code1) === removeSpacesAndNewLines(code2), message);
}

819
yarn.lock

File diff suppressed because it is too large Load Diff