Compare commits
228 Commits
Author | SHA1 | Date | |
---|---|---|---|
049e52d2cc | |||
10d6fb96cd | |||
05d4d1b5f5 | |||
f9e5286603 | |||
68ab37e6a8 | |||
ff328e9d56 | |||
ba432f8841 | |||
27aedbbc7f | |||
360ed3dc4b | |||
53fd8bd0ee | |||
7e20ce4efa | |||
220d9d4d5e | |||
697171be1e | |||
cf91c6fb8a | |||
18a2e4ae04 | |||
020823e933 | |||
5879972924 | |||
8031294230 | |||
e0a6935c49 | |||
3d581a5454 | |||
317ad8386c | |||
7ad5011280 | |||
76990702f0 | |||
9a27824fe9 | |||
d877d90bf3 | |||
0b790c47e6 | |||
6b49c8dd95 | |||
e56f9b144e | |||
3c82944daf | |||
5e3070a6c4 | |||
a3a0e9eebe | |||
c6593f03bc | |||
54dc4f650c | |||
dec7af0381 | |||
ae001eea54 | |||
4d0e17a11e | |||
c708e619e9 | |||
2cceb3c929 | |||
aee8704691 | |||
43af60237b | |||
e615479b41 | |||
973fb4d2d5 | |||
964feae846 | |||
ea3d8a5634 | |||
48aab2d92a | |||
00eab73954 | |||
5f6f8b12bd | |||
c2d4d6fd49 | |||
04b660ff9b | |||
c292d926be | |||
23b83ceef7 | |||
1324648db6 | |||
735bff3348 | |||
05a6aee782 | |||
c7349c2556 | |||
51f3d06752 | |||
31759d86ab | |||
7c6eed99d2 | |||
bc4b0ec17d | |||
f766348b87 | |||
82281303d0 | |||
1caa17beb0 | |||
1c4d346f9f | |||
4320efb049 | |||
a756423768 | |||
8525fc74c0 | |||
30c0cc5aa8 | |||
b3bbd7c07d | |||
09d4ba2bb0 | |||
30315027c1 | |||
05acefe70e | |||
6c14758e33 | |||
b93ec20119 | |||
ce04646576 | |||
9282dfe491 | |||
fca6280bcc | |||
cdeb575ec6 | |||
271dbe4fb7 | |||
9a0337114d | |||
2d28f4eb55 | |||
f673927e16 | |||
52896b82a9 | |||
9d53ecb0cd | |||
aec3ac32e5 | |||
f150f1568e | |||
309189c55d | |||
f68c54cd3a | |||
bef8545161 | |||
c21cd14ac2 | |||
275d7f0072 | |||
58c8306cf4 | |||
f782b684ad | |||
092b2a5f52 | |||
42b2d40ad6 | |||
3f6fe6cfc0 | |||
1abf542a74 | |||
c4720ca03d | |||
4316878cce | |||
c180d75a83 | |||
4a040b32c0 | |||
ea330a1eef | |||
2451ba0a77 | |||
2c276a56e5 | |||
708030b8b5 | |||
d5fc0582bc | |||
f9dce82c83 | |||
e82602f994 | |||
1d36395e5a | |||
8f8857bc22 | |||
226247b3b6 | |||
b2ea5014f3 | |||
48bc416aa7 | |||
386e7203b2 | |||
9bdb224631 | |||
dd36aacbee | |||
6b57b1c720 | |||
9e9e6d41ff | |||
5140389502 | |||
fc6328131f | |||
9de0083ca6 | |||
f5231b840d | |||
afb6596c4b | |||
dde9afef92 | |||
6595e9c3cb | |||
c0e3b5fe06 | |||
6b8f3bbc51 | |||
9a5a021e64 | |||
14c05fec8c | |||
eaf7a455cd | |||
55bb21f3ee | |||
f123bc0912 | |||
572eb7b1c0 | |||
2befaff8a8 | |||
437a9ce2d3 | |||
1b967b250a | |||
e221f39e07 | |||
21a8838a24 | |||
fad91ccae0 | |||
825914aa4b | |||
a8246d12ee | |||
abb8bf2ebb | |||
7e7071305f | |||
cc8b2e72c1 | |||
a3d6ee44a1 | |||
ac99e2f41f | |||
bf1839c061 | |||
fd5c132a40 | |||
4dfa268eb3 | |||
332ca084f5 | |||
01cbb8680a | |||
bbdaaf30bc | |||
0550b9ff8b | |||
b1a4c5cca5 | |||
785080e14a | |||
3c7e093a3c | |||
89be9f3a86 | |||
6f2ffa7861 | |||
7091f283f2 | |||
2d28003451 | |||
f0ba7d3c0d | |||
cd5f346895 | |||
66cd5aef0c | |||
6f8ec53e8b | |||
622504ff72 | |||
c9d47c483c | |||
07098b89a5 | |||
c583b83cbb | |||
1670e1fe42 | |||
de8809608c | |||
0e194ee045 | |||
4205f6ecbe | |||
4d90ec60e2 | |||
d126a6563b | |||
aecb6ae79c | |||
a65c826717 | |||
66c3705f2b | |||
d18ebb45f8 | |||
d8e01f2c5d | |||
4abbaa3841 | |||
42a463b348 | |||
8e15cf1d45 | |||
2468b4108e | |||
528b1bb607 | |||
b4449bb289 | |||
737e00b490 | |||
55d4c7f4ab | |||
7afb078efd | |||
2c04f6c1e9 | |||
2ad5ed7e73 | |||
f2b7fe46a2 | |||
1a1af62f62 | |||
98f715e652 | |||
fa5f1c230a | |||
c92ae9cfa9 | |||
3dcb3a1a5b | |||
efde71d07c | |||
bff8cf2f32 | |||
72730135f1 | |||
50cf27b686 | |||
b293abffa4 | |||
be84ea299c | |||
d54586426a | |||
6ccf72c707 | |||
5817118461 | |||
ebac1de111 | |||
0d2f841b27 | |||
780ca383c9 | |||
a652a0f4f3 | |||
5bdc812c43 | |||
357bc8d19d | |||
85b54ac011 | |||
17f888019c | |||
947fd0564e | |||
bd51d02902 | |||
36d75c8641 | |||
c75f158b48 | |||
bb37ce9cef | |||
77ff33570d | |||
20383d60a9 | |||
f15c0ecbb0 | |||
79aa5ac5f2 | |||
8be6c0d1d2 | |||
7f5a9e77de | |||
ff19ab8b08 | |||
63dcb2ad39 | |||
795e8ed0e5 | |||
bccb56ed61 | |||
02e2ad89ec |
5
.github/FUNDING.yml
vendored
Normal file
5
.github/FUNDING.yml
vendored
Normal 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
25
.github/release.yaml
vendored
Normal 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:
|
||||
- '*'
|
62
.github/workflows/ci.yaml
vendored
62
.github/workflows/ci.yaml
vendored
@ -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,15 +25,17 @@ 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', '13' ]
|
||||
node: [ '15', '14' ]
|
||||
name: Test with Node v${{ matrix.node }}
|
||||
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
|
||||
@ -141,10 +130,7 @@ jobs:
|
||||
echo "Can't publish on NPM, You must first create a secret called NPM_TOKEN that contains your NPM auth token. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets"
|
||||
false
|
||||
fi
|
||||
EXTRA_ARGS=""
|
||||
if [ "$IS_BETA" = "true" ]; then
|
||||
EXTRA_ARGS="--tag beta"
|
||||
fi
|
||||
EXTRA_ARGS="--tag v5"
|
||||
npm publish $EXTRA_ARGS
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -46,3 +46,6 @@ jspm_packages
|
||||
/.yarn_home/
|
||||
|
||||
.idea
|
||||
|
||||
/keycloak_email
|
||||
/build_keycloak
|
@ -4,4 +4,5 @@ node_modules/
|
||||
/.yarn_home/
|
||||
/src/test/apps/
|
||||
/src/tools/types/
|
||||
/sample_react_project
|
||||
/sample_react_project
|
||||
/build_keycloak/
|
807
CHANGELOG.md
807
CHANGELOG.md
@ -1,807 +0,0 @@
|
||||
## **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
3
CONTRIBUTING.md
Normal 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.
|
505
README.md
505
README.md
@ -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">
|
||||
@ -23,458 +23,75 @@
|
||||
<a href="https://github.com/thomasdarimont/awesome-keycloak">
|
||||
<img src="https://awesome.re/mentioned-badge.svg"/>
|
||||
</a>
|
||||
<p align="center">
|
||||
<a href="https://www.keycloakify.dev">Home</a>
|
||||
-
|
||||
<a href="https://docs.keycloakify.dev">Documentation</a>
|
||||
</p>
|
||||
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<i>Ultimately this build tool generates a Keycloak theme</i>
|
||||
<i>Ultimately this build tool generates a Keycloak theme <a href="https://www.keycloakify.dev">Learn more</a></i>
|
||||
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
|
||||
</p>
|
||||
|
||||
# Motivations
|
||||
|
||||
Keycloak provides [theme support](https://www.keycloak.org/docs/latest/server_development/#_themes) for web pages. This allows customizing the look and feel of end-user facing pages so they can be integrated with your applications.
|
||||
It involves, however, a lot of raw JS/CSS/[FTL]() hacking, and bundling the theme is not exactly straightforward.
|
||||
|
||||
Beyond that, if you use Keycloak for a specific app you want your login page to be tightly integrated with it.
|
||||
Ideally, you don't want the user to notice when he is being redirected away.
|
||||
|
||||
Trying to reproduce the look and feel of a specific app in another stack is not an easy task not to mention
|
||||
the cheer amount of maintenance that it involves.
|
||||
|
||||
<p align="center">
|
||||
<i>Without keycloakify, users suffers from a harsh context switch, no fronted form pre-validation</i><br>
|
||||
<img src="https://user-images.githubusercontent.com/6702424/134997335-a28b4a57-0884-47ec-9341-a0e49f835c4d.gif">
|
||||
</p>
|
||||
|
||||
Wouldn't it be great if we could just design the login and register pages as if they were part of our app?
|
||||
Here is `keycloakify` for you 🍸
|
||||
|
||||
<p align="center">
|
||||
<i> <a href="https://datalab.sspcloud.fr">With keycloakify:</a> </i>
|
||||
<br>
|
||||
<img src="https://user-images.githubusercontent.com/6702424/114332075-c5e37900-9b45-11eb-910b-48a05b3d90d9.gif">
|
||||
</p>
|
||||
|
||||
**TL;DR**: [Here](https://github.com/garronej/keycloakify-demo-app) is a Hello World React project with Keycloakify set up.
|
||||
|
||||
If you already have a Keycloak custom theme, it can be easily ported to Keycloakify.
|
||||
|
||||
---
|
||||
|
||||
- [Motivations](#motivations)
|
||||
- [Requirements](#requirements)
|
||||
- [My framework doesn’t seem to be supported, what can I do?](#my-framework-doesnt-seem-to-be-supported-what-can-i-do)
|
||||
- [How to use](#how-to-use)
|
||||
- [Setting up the build tool](#setting-up-the-build-tool)
|
||||
- [Changing just the look of the default Keycloak theme](#changing-just-the-look-of-the-default-keycloak-theme)
|
||||
- [Advanced pages configuration](#advanced-pages-configuration)
|
||||
- [Hot reload](#hot-reload)
|
||||
- [Enable loading in a blink of an eye of login pages ⚡ (--external-assets)](#enable-loading-in-a-blink-of-an-eye-of-login-pages----external-assets)
|
||||
- [User profile and frontend form validation](#user-profile-and-frontend-form-validation)
|
||||
- [Support for Terms and conditions](#support-for-terms-and-conditions)
|
||||
- [Some pages still have the default theme. Why?](#some-pages-still-have-the-default-theme-why)
|
||||
- [GitHub Actions](#github-actions)
|
||||
- [Limitations](#limitations)
|
||||
- [`process.env.PUBLIC_URL` not supported.](#processenvpublic_url-not-supported)
|
||||
- [`@font-face` importing fonts from the `src/` dir](#font-face-importing-fonts-from-the-src-dir)
|
||||
- [Example of setup that **won't** work](#example-of-setup-that-wont-work)
|
||||
- [Possible workarounds](#possible-workarounds)
|
||||
- [Implement context persistence (optional)](#implement-context-persistence-optional)
|
||||
- [Kickstart video](#kickstart-video)
|
||||
- [FTL errors related to `ftl_object_to_js_code_declaring_an_object` in Keycloak logs.](#ftl-errors-related-to-ftl_object_to_js_code_declaring_an_object-in-keycloak-logs)
|
||||
- [Adding custom message (to `i18n/useKcMessage.tsx`)](#adding-custom-message-to-i18nusekcmessagetsx)
|
||||
- [Email domain whitelist](#email-domain-whitelist)
|
||||
- [Changelog highlights](#changelog-highlights)
|
||||
- [v4](#v4)
|
||||
- [v3](#v3)
|
||||
- [v2.5](#v25)
|
||||
- [v2](#v2)
|
||||
|
||||
# Requirements
|
||||
|
||||
On Windows OS you'll have to use [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10). More info [here](https://github.com/InseeFrLab/keycloakify/issues/54%23issuecomment-984834217)
|
||||
|
||||
Tested with the following Keycloak versions:
|
||||
|
||||
- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore)
|
||||
- [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore)
|
||||
- [15.0.2](https://hub.docker.com/layers/jboss/keycloak/15.0.2/images/sha256-d8ed1ee5df42a178c341f924377da75db49eab08ea9f058ff39a8ed7ee05ec93?context=explore)
|
||||
- [16.1.0](https://hub.docker.com/layers/jboss/keycloak/16.1.0/images/sha256-6ecb9492224c6cfbb55d43f64a5ab634145d8cc1eba14eae8c37e3afde89546e?context=explore)
|
||||
|
||||
This tool will be maintained to stay compatible with Keycloak v11 and up, however, the default pages you will get
|
||||
(before you customize it) will always be the ones of Keycloak v11.
|
||||
|
||||
This tool assumes you are bundling your app with Webpack (tested with the versions that ships with CRA v4.44.2 and v5.0.0) .
|
||||
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
|
||||
and a `build/static/` directory generated by webpack.
|
||||
For more information see [this issue](https://github.com/InseeFrLab/keycloakify/issues/5#issuecomment-832296432)
|
||||
|
||||
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3)
|
||||
|
||||
- `mvn` ([Maven](https://maven.apache.org/)), `rm`, `mkdir`, `curl`, `unzip` are assumed to be available.
|
||||
- `docker` must be up and running when running `start_keycloak_testing_container.sh` (Instructions provided after running `yarn keycloak`).
|
||||
|
||||
## My framework doesn’t seem to be supported, what can I do?
|
||||
|
||||
Currently Keycloakify is only compatible with `create-react-app` apps.
|
||||
It doesn’t mean that you can't use Keycloakify if you are using Next.js, Express or any other
|
||||
framework that involves SSR but your Keycloak theme will need to be a standalone project.
|
||||
Find specific instructions about how to get started [**here**](https://github.com/garronej/keycloakify-demo-app#keycloak-theme-only).
|
||||
|
||||
To share your styles between your main app and your login pages you will need to externalize your design system by making it a
|
||||
separate module. Checkout [ts_ci](https://github.com/garronej/ts_ci), it can help with that.
|
||||
|
||||
# How to use
|
||||
|
||||
## Setting up the build tool
|
||||
|
||||
```bash
|
||||
yarn add keycloakify @emotion/react
|
||||
```
|
||||
|
||||
[`package.json`](https://github.com/garronej/keycloakify-demo-app/blob/main/package.json)
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"keycloak": "yarn build && build-keycloak-theme",
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn keycloak # generates keycloak-theme.jar
|
||||
```
|
||||
|
||||
On the console will be printed all the instructions about how to load the generated theme in Keycloak
|
||||
|
||||
### Changing just the look of the default Keycloak theme
|
||||
|
||||
The first approach is to only customize the style of the default Keycloak login by providing
|
||||
your own class names.
|
||||
|
||||
If you have created a new React project specifically to create a Keycloak theme and nothing else then
|
||||
your index should look something like:
|
||||
|
||||
`src/index.tsx`
|
||||
|
||||
```tsx
|
||||
import { App } from "./<wherever>/App";
|
||||
import { KcApp, defaultKcProps, getKcContext } from "keycloakify";
|
||||
import { css } from "tss-react/@emotion/css";
|
||||
|
||||
const { kcContext } = getKcContext();
|
||||
|
||||
const myClassName = css({ "color": "red" });
|
||||
|
||||
reactDom.render(
|
||||
<KcApp
|
||||
kcContext={kcContext}
|
||||
{...{
|
||||
...defaultKcProps,
|
||||
"kcHeaderWrapperClass": myClassName,
|
||||
}}
|
||||
/>,
|
||||
document.getElementById("root"),
|
||||
);
|
||||
```
|
||||
|
||||
If you share a unique project for your app and the Keycloak theme, your index should look
|
||||
more like this:
|
||||
|
||||
`src/index.tsx`
|
||||
|
||||
```tsx
|
||||
import { App } from "./<wherever>/App";
|
||||
import { KcApp, defaultKcProps, getKcContext } from "keycloakify";
|
||||
import { css } from "tss-react/@emotion/css";
|
||||
|
||||
const { kcContext } = getKcContext();
|
||||
|
||||
const myClassName = css({ "color": "red" });
|
||||
|
||||
reactDom.render(
|
||||
// Unless the app is currently being served by Keycloak
|
||||
// kcContext is undefined.
|
||||
kcContext !== undefined ? (
|
||||
<KcApp
|
||||
kcContext={kcContext}
|
||||
{...{
|
||||
...defaultKcProps,
|
||||
"kcHeaderWrapperClass": myClassName,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<App />
|
||||
), // Your actual app
|
||||
document.getElementById("root"),
|
||||
);
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<i>result:</i></br>
|
||||
<img src="https://user-images.githubusercontent.com/6702424/114326299-6892fc00-9b34-11eb-8d75-85696e55458f.png">
|
||||
</p>
|
||||
|
||||
Example of a customization using only CSS: [here](https://github.com/InseeFrLab/onyxia-web/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/components/KcApp.tsx)
|
||||
(the [index.tsx](https://github.com/InseeFrLab/onyxia-web/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/index.tsx#L89-L94) )
|
||||
and the result you can expect:
|
||||
|
||||
<p align="center">
|
||||
<i> <a href="https://datalab.sspcloud.fr">Customization using only CSS:</a> </i>
|
||||
<br>
|
||||
<img src="https://github.com/InseeFrLab/keycloakify/releases/download/v0.3.8/keycloakify_after.gif">
|
||||
</p>
|
||||
|
||||
### Advanced pages configuration
|
||||
|
||||
If you want to go beyond only customizing the CSS you can re-implement some of the
|
||||
pages or even add new ones.
|
||||
|
||||
If you want to go this way checkout the demo setup provided [here](https://github.com/garronej/keycloakify-demo-app/tree/look_and_feel).
|
||||
If you prefer a real life example you can checkout [onyxia-web's source](https://github.com/InseeFrLab/onyxia-web/tree/main/src/ui/components/KcApp).
|
||||
The web app is in production [here](https://datalab.sspcloud.fr).
|
||||
|
||||
Main takeaways are:
|
||||
|
||||
- You must declare your custom pages in the package.json. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/package.json#L17-L22)
|
||||
- (TS only) You must declare theses page in the type argument of the getter
|
||||
function for the `kcContext` in order to have the correct typings. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L16-L21)
|
||||
- (TS only) If you use Keycloak plugins that defines non standard `.ftl` values
|
||||
(Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting)
|
||||
that define `authorizedMailDomains` in `register.ftl`) you should
|
||||
declare theses value to get the type. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L6-L13)
|
||||
- You should provide sample data for all the non standard value if you want to be able
|
||||
to debug the page outside of keycloak. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L28-L43)
|
||||
|
||||
WARNING: If you chose to go this way use:
|
||||
|
||||
```json
|
||||
"dependencies": {
|
||||
"keycloakify": "~X.Y.Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Hot reload
|
||||
|
||||
Rebuild the theme each time you make a change to see the result is not practical.
|
||||
If you want to test your login screens outside of Keycloak you can mock a given `kcContext`:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
KcApp,
|
||||
defaultKcProps,
|
||||
getKcContext
|
||||
} from "keycloakify";
|
||||
|
||||
const { kcContext } = getKcContext({
|
||||
"mockPageId": "login.ftl"
|
||||
});
|
||||
|
||||
reactDom.render(
|
||||
<KcApp
|
||||
kcContext={kcContextMocks.kcLoginContext}
|
||||
{...defaultKcProps}
|
||||
/>
|
||||
document.getElementById("root")
|
||||
);
|
||||
```
|
||||
|
||||
Then `yarn start`, you will see your login page.
|
||||
|
||||
Checkout [this concrete example](https://github.com/garronej/keycloakify-demo-app/blob/main/src/index.tsx)
|
||||
|
||||
## Enable loading in a blink of an eye of login pages ⚡ (--external-assets)
|
||||
|
||||
By default the theme generated is standalone. Meaning that when your users
|
||||
reach the login pages all scripts, images and stylesheet are downloaded from the Keycloak server.
|
||||
If you are specifically building a theme to integrate with an app or a website that allows users
|
||||
to first browse unauthenticated before logging in, you will get a significant
|
||||
performance boost if you jump through those hoops:
|
||||
|
||||
- Provide the url of your app in the `homepage` field of package.json. [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/package.json#L2) or in a `public/CNAME` file. [ex](https://github.com/garronej/keycloakify-demo-app/blob/main/public/CNAME).
|
||||
- Build the theme using `npx build-keycloak-theme --external-assets` [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/.github/workflows/ci.yaml#L21)
|
||||
- Enable [long-term assets caching](https://create-react-app.dev/docs/production-build/#static-file-caching) on the server hosting your app.
|
||||
- Make sure not to build your app and the keycloak theme separately
|
||||
and remember to update the Keycloak theme every time you update your app.
|
||||
- Be mindful that if your app is down your login pages are down as well.
|
||||
|
||||
Checkout a complete setup [here](https://github.com/garronej/keycloakify-demo-app#about-keycloakify)
|
||||
|
||||
# User profile and frontend form validation
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/InseeFrLab/keycloakify/releases/download/v0.0.1/keycloakify_fontend_validation.mp4">
|
||||
<img src="https://user-images.githubusercontent.com/6702424/138880146-6fef3280-c4a5-46d2-bbb3-8b9598c057a5.gif">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
NOTE: In reality the regexp used in this gif doesn't work server side, the regexp pattern should be `^[^@]@gmail\.com$` 😬.
|
||||
|
||||
User Profile is a Keycloak feature that enables to
|
||||
[define, from the admin console](https://user-images.githubusercontent.com/6702424/136872461-1f5b64ef-d2ef-4c6b-bb8d-07d4729552b3.png),
|
||||
what information you want to collect on your users in the register page and to validate inputs
|
||||
[**on the frontend**, in realtime](https://github.com/InseeFrLab/keycloakify/blob/6dca6a93d8cfe634ee4d8574ad0c091641220092/src/lib/getKcContext/KcContextBase.ts#L225-L261)!
|
||||
|
||||
NOTE: User profile is only available in Keycloak 15 and it's a beta feature that
|
||||
[needs to be enabled when launching keycloak](https://github.com/InseeFrLab/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/bin/build-keycloak-theme/build-keycloak-theme.ts#L116-L117)
|
||||
and [enabled in the console](https://user-images.githubusercontent.com/6702424/136874428-b071d614-c7f7-440d-9b2e-670faadc0871.png).
|
||||
|
||||
Keycloakify, in [`register-user-profile.ftl`](https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/components/RegisterUserProfile.tsx),
|
||||
provides frontend validation out of the box.
|
||||
|
||||
For implementing your own `register-user-profile.ftl` page, you can use [`import { useFormValidationSlice } from "keycloakify";`](https://github.com/InseeFrLab/keycloakify/blob/main/src/lib/useFormValidationSlice.tsx).
|
||||
Find usage example [`here`](https://github.com/InseeFrLab/keycloakify/blob/d3a07edfcb3739e30032dc96fc2a55944dfc3387/src/lib/components/RegisterUserProfile.tsx#L79-L112).
|
||||
|
||||
As for right now [it's not possible to define a pattern for the password](https://keycloak.discourse.group/t/make-password-policies-available-to-freemarker/11632)
|
||||
from the admin console. You can however pass validators for it to the `useFormValidationSlice` function.
|
||||
|
||||
# Support for Terms and conditions
|
||||
|
||||
[Many organizations have a requirement that when a new user logs in for the first time, they need to agree to the terms and conditions of the website.](https://www.keycloak.org/docs/4.8/server_admin/#terms-and-conditions).
|
||||
|
||||
First you need to enable the required action on the Keycloak server admin console:
|
||||

|
||||
|
||||
Then to load your own therms of services using [like this](https://github.com/garronej/keycloakify-demo-app/blob/8168c928a66605f2464f9bd28a4dc85fb0a231f9/src/index.tsx#L42-L66).
|
||||
|
||||
# Some pages still have the default theme. Why?
|
||||
|
||||
This project only support out of the box the most common user facing pages of Keycloak login.
|
||||
[Here](https://user-images.githubusercontent.com/6702424/116787906-227fe700-aaa7-11eb-92ee-22e7673717c2.png) is the complete list of pages (you get them after running `yarn test`)
|
||||
and [here](https://github.com/InseeFrLab/keycloakify/tree/main/src/lib/components) are the pages currently implemented by this module.
|
||||
If you need to customize pages that are not supported yet or if you need to implement some non standard `.ftl` pages please refer to [Advanced pages configuration](#advanced-pages-configuration).
|
||||
|
||||
# GitHub Actions
|
||||
|
||||

|
||||
|
||||
[Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate
|
||||
the building and publishing of the theme (the .jar file).
|
||||
|
||||
# Limitations
|
||||
|
||||
## `process.env.PUBLIC_URL` not supported.
|
||||
|
||||
You won't be able to [import things from your public directory **in your JavaScript code**](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system).
|
||||
(This isn't recommended anyway).
|
||||
|
||||
## `@font-face` importing fonts from the `src/` dir
|
||||
|
||||
If you are building the theme with [--external-assets](#enable-loading-in-a-blink-of-a-eye-of-login-pages-)
|
||||
this limitation doesn't apply, you can import fonts however you see fit.
|
||||
|
||||
### Example of setup that **won't** work
|
||||
|
||||
- We have a `fonts/` directory in `src/`
|
||||
- We import the font like this [`src: url("/fonts/my-font.woff2") format("woff2");`](https://github.com/garronej/keycloakify-demo-app/blob/07d54a3012ef354ee12b1374c6f7ad1cb125d56b/src/fonts.scss#L4) in a `.scss` a file.
|
||||
|
||||
### Possible workarounds
|
||||
|
||||
- Use [`--external-assets`](#enable-loading-in-a-blink-of-a-eye-of-login-pages-).
|
||||
- If it is possible, use Google Fonts or any other font provider.
|
||||
- If you want to host your font recommended approach is to move your fonts into the `public`
|
||||
directory and to place your `@font-face` statements in the `public/index.html`.
|
||||
Example [here](https://github.com/garronej/keycloakify-demo-app/blob/9aa2dbaec28a7786d6b2983c9a59d393dec1b2d6/public/index.html#L27-L73)
|
||||
(and the font are [here](https://github.com/garronej/keycloakify-demo-app/tree/main/public/fonts/WorkSans)).
|
||||
- You can also [use non relative url](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/src/fonts.scss#L16) but don't forget [`Access-Control-Allow-Origin`](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/nginx.conf#L17-L19).
|
||||
|
||||
# Implement context persistence (optional)
|
||||
|
||||
If, before logging in, a user has selected a specific language
|
||||
you don't want it to be reset to default when the user gets redirected to
|
||||
the login or register pages.
|
||||
|
||||
Same goes for the dark mode, you don't want, if the user had it enabled
|
||||
to show the login page with light themes.
|
||||
|
||||
The problem is that you are probably using `localStorage` to persist theses values across
|
||||
reload but, as the Keycloak pages are not served on the same domain that the rest of your
|
||||
app you won't be able to carry over states using `localStorage`.
|
||||
|
||||
The only reliable solution is to inject parameters into the URL before
|
||||
redirecting to Keycloak. We integrate with
|
||||
[`keycloak-js`](https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/javascript-adapter.adoc),
|
||||
by providing you a way to tell `keycloak-js` that you would like to inject
|
||||
some search parameters before redirecting.
|
||||
|
||||
The method also works with [`@react-keycloak/web`](https://www.npmjs.com/package/@react-keycloak/web) (use the `initOptions`).
|
||||
|
||||
You can implement your own mechanism to pass the states in the URL and
|
||||
restore it on the other side but we recommend using `powerhooks/useGlobalState`
|
||||
from the library [`powerhooks`](https://www.powerhooks.dev) that provide an elegant
|
||||
way to handle states such as `isDarkModeEnabled` or `selectedLanguage`.
|
||||
|
||||
Let's modify [the example](https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/javascript-adapter.adoc) from the official `keycloak-js` documentation to
|
||||
enables the states of `useGlobalStates` to be injected in the URL before redirecting.
|
||||
Note that the states are automatically restored on the other side by `powerhooks`
|
||||
|
||||
```typescript
|
||||
import keycloak_js from "keycloak-js";
|
||||
import { injectGlobalStatesInSearchParams } from "powerhooks/useGlobalState";
|
||||
import { createKeycloakAdapter } from "keycloakify";
|
||||
|
||||
//...
|
||||
|
||||
const keycloakInstance = keycloak_js({
|
||||
"url": "http://keycloak-server/auth",
|
||||
"realm": "myrealm",
|
||||
"clientId": "myapp",
|
||||
});
|
||||
|
||||
keycloakInstance.init({
|
||||
"onLoad": "check-sso",
|
||||
"silentCheckSsoRedirectUri": window.location.origin + "/silent-check-sso.html",
|
||||
"adapter": createKeycloakAdapter({
|
||||
"transformUrlBeforeRedirect": injectGlobalStatesInSearchParams,
|
||||
keycloakInstance,
|
||||
}),
|
||||
});
|
||||
|
||||
//...
|
||||
```
|
||||
|
||||
If you really want to go the extra miles and avoid having the white
|
||||
flash of the blank html before the js bundle have been evaluated
|
||||
[here is a snippet](https://github.com/InseeFrLab/onyxia-web/blob/e1c1f309aaa3d5f860df39ba0b75cce89c88a9de/public/index.html#L117-L166) that you can place in your `public/index.html` if you are using `powerhooks/useGlobalState`.
|
||||
|
||||
# Kickstart video
|
||||
|
||||
_NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded_
|
||||
[](https://youtu.be/xTz0Rj7i2v8)
|
||||
|
||||
# FTL errors related to `ftl_object_to_js_code_declaring_an_object` in Keycloak logs.
|
||||
|
||||
If you ever encounter one of these errors:
|
||||
|
||||
```log
|
||||
FTL stack trace ("~" means nesting-related):
|
||||
- Failed at: #local value = object[key] [in template "login.ftl" in macro "ftl_object_to_js_code_declaring_an_object" at line 70, column 21]
|
||||
- Reached through: @compress [in template "login.ftl" in macro "ftl_object_to_js_code_declaring_an_object" at line 36, column 5]
|
||||
- Reached through: @ftl_object_to_js_code_declaring_an_object object=value depth=(dep... [in template "login.ftl" in macro "ftl_object_to_js_code_declaring_an_object" at line 81, column 27]
|
||||
- Reached through: @compress [in template "login.ftl" in macro "ftl_object_to_js_code_declaring_an_object" at line 36, column 5]
|
||||
- Reached through: @ftl_object_to_js_code_declaring_an_object object=(.data_model) de... [in template "login.ftl" at line 163, column 43]
|
||||
```
|
||||
|
||||
It's just noise, they can be safely ignored.
|
||||
You can, however, and are encouraged to, report any that you would spot.
|
||||
Just open an issue about it and I will release a patched version of Keycloakify in the better delays.
|
||||
|
||||
# Adding custom message (to `i18n/useKcMessage.tsx`)
|
||||
|
||||
You can reproduce [this approach](https://github.com/garronej/keycloakify-demo-app/blob/main/src/kcMessagesExtension.ts)
|
||||
( don't forget to [evaluate the code](https://github.com/garronej/keycloakify-demo-app/blob/0a6d349dba89a5702f98ba48bca6c76ac7265e1f/src/index.tsx#L15) ).
|
||||
This approach is a bit hacky as it doesn't provide type safety but it works.
|
||||
|
||||
# Email domain whitelist
|
||||
|
||||
NOTE: This have been kind of deprecated by [user attribute](#user-profile-and-frontend-form-validation) you could
|
||||
use a pattern [like this one](https://github.com/InseeFrLab/onyxia-web/blob/f1206e0329b3b8d401ca7bffa95ca9c213cb190a/src/app/components/KcApp/kcContext.ts#L106) to whitelist email domains.
|
||||
|
||||
If you want to restrict the emails domain that can register, you can use [this plugin](https://github.com/micedre/keycloak-mail-whitelisting)
|
||||
and `kcRegisterContext["authorizedMailDomains"]` to validate on.
|
||||
> 🗣 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
|
||||
|
||||
# v4.6.0
|
||||
## 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).
|
||||
|
||||
## v4.8.0
|
||||
|
||||
[Email template customization.](#email-template-customization)
|
||||
|
||||
## v4.7.4
|
||||
|
||||
**M1 Mac** support (for testing locally with a dockerized Keycloak).
|
||||
|
||||
## v4.7.2
|
||||
|
||||
> WARNING: This is broken.
|
||||
> Testing with local Keycloak container working with M1 Mac. Thanks to [@eduardosanzb](https://github.com/InseeFrLab/keycloakify/issues/43#issuecomment-975699658).
|
||||
> Be aware: When running M1s you are testing with Keycloak v15 else the local container spun will be a Keycloak v16.1.0.
|
||||
|
||||
## v4.7.0
|
||||
|
||||
Register with user profile enabled: Out of the box `options` validator support.
|
||||
[Example](https://user-images.githubusercontent.com/6702424/158911163-81e6bbe8-feb0-4dc8-abff-de199d7a678e.mov)
|
||||
|
||||
## v4.6.0
|
||||
|
||||
`tss-react` and `powerhooks` are no longer peer dependencies of `keycloakify`.
|
||||
After updating Keycloakify you can remove `tss-react` and `powerhooks` from your dependencies if you don't use them explicitly.
|
||||
|
28
package.json
28
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "4.6.0",
|
||||
"version": "5.9.4",
|
||||
"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,6 +22,7 @@
|
||||
},
|
||||
"bin": {
|
||||
"build-keycloak-theme": "dist/bin/build-keycloak-theme/index.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": {
|
||||
@ -56,31 +57,34 @@
|
||||
"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/node": "^10.0.0",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/memoizee": "^0.4.7",
|
||||
"@types/node": "^17.0.25",
|
||||
"@types/react": "18.0.9",
|
||||
"copyfiles": "^2.4.1",
|
||||
"husky": "^4.3.8",
|
||||
"lint-staged": "^11.0.0",
|
||||
"powerhooks": "^0.11.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"
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0-rc.5",
|
||||
"evt": "2.0.0-beta.39",
|
||||
"@octokit/rest": "^18.12.0",
|
||||
"cheerio": "1.0.0-rc.5",
|
||||
"cli-select": "^1.1.2",
|
||||
"evt": "^2.4.1",
|
||||
"memoizee": "^0.4.15",
|
||||
"minimal-polyfills": "^2.2.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"powerhooks": "^0.20.16",
|
||||
"react-markdown": "^5.0.3",
|
||||
"scripting-tools": "^0.19.13",
|
||||
"tsafe": "^0.9.0",
|
||||
"tss-react": "^3.5.2",
|
||||
"powerhooks": "^0.14.0"
|
||||
"tsafe": "^0.10.1",
|
||||
"tss-react": "^3.7.1"
|
||||
}
|
||||
}
|
||||
|
27
renovate.json
Normal file
27
renovate.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export const keycloakVersions = ["11.0.3", "15.0.2", "16.1.0"] as const;
|
||||
|
||||
export type KeycloakVersion = typeof keycloakVersions[number];
|
@ -2,7 +2,7 @@ import { generateKeycloakThemeResources } from "./generateKeycloakThemeResources
|
||||
import { generateJavaStackFiles } from "./generateJavaStackFiles";
|
||||
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
|
||||
import * as child_process from "child_process";
|
||||
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
|
||||
import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
|
||||
import { URL } from "url";
|
||||
import * as fs from "fs";
|
||||
|
||||
@ -19,6 +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_email");
|
||||
|
||||
function sanitizeThemeName(name: string) {
|
||||
return name
|
||||
@ -34,8 +35,9 @@ export function main() {
|
||||
const extraThemeProperties: string[] = (parsedPackageJson as any)["keycloakify"]?.["extraThemeProperties"] ?? [];
|
||||
const themeName = sanitizeThemeName(parsedPackageJson.name);
|
||||
|
||||
generateKeycloakThemeResources({
|
||||
const { doBundleEmailTemplate } = generateKeycloakThemeResources({
|
||||
keycloakThemeBuildingDirPath,
|
||||
keycloakThemeEmailDirPath,
|
||||
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
|
||||
themeName,
|
||||
...(() => {
|
||||
@ -78,30 +80,34 @@ export function main() {
|
||||
});
|
||||
|
||||
const { jarFilePath } = generateJavaStackFiles({
|
||||
version: parsedPackageJson.version,
|
||||
"version": parsedPackageJson.version,
|
||||
themeName,
|
||||
homepage: parsedPackageJson.homepage,
|
||||
"homepage": parsedPackageJson.homepage,
|
||||
keycloakThemeBuildingDirPath,
|
||||
doBundleEmailTemplate,
|
||||
});
|
||||
|
||||
child_process.execSync("mvn package", {
|
||||
"cwd": keycloakThemeBuildingDirPath,
|
||||
});
|
||||
|
||||
generateDebugFiles({
|
||||
//We want, however, to test in a container running the latest Keycloak version
|
||||
const containerKeycloakVersion = "18.0.2";
|
||||
|
||||
generateStartKeycloakTestingContainer({
|
||||
keycloakThemeBuildingDirPath,
|
||||
themeName,
|
||||
//We want, however to test in a container running the latest Keycloak version
|
||||
"keycloakVersion": "16.1.0",
|
||||
"keycloakVersion": containerKeycloakVersion,
|
||||
});
|
||||
|
||||
console.log(
|
||||
[
|
||||
"",
|
||||
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`,
|
||||
`It is to be placed in "/opt/jboss/keycloak/standalone/deployments" in the container running a jboss/keycloak Docker image.`,
|
||||
`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: |",
|
||||
@ -119,7 +125,7 @@ export function main() {
|
||||
" ",
|
||||
" extraVolumeMounts: |",
|
||||
" - name: extensions",
|
||||
" mountPath: /opt/jboss/keycloak/standalone/deployments",
|
||||
" mountPath: /opt/keycloak/providers",
|
||||
" extraEnv: |",
|
||||
" - name: KEYCLOAK_USER",
|
||||
" value: admin",
|
||||
@ -129,14 +135,16 @@ export function main() {
|
||||
" value: -Dkeycloak.profile=preview",
|
||||
"",
|
||||
"",
|
||||
"To test your theme locally, with hot reloading, you can spin up a Keycloak container image with the theme loaded by running:",
|
||||
`To test your theme locally you can spin up a Keycloak ${containerKeycloakVersion} container image with the theme pre loaded by running:`,
|
||||
"",
|
||||
`👉 $ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename))} 👈`,
|
||||
`👉 $ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, generateStartKeycloakTestingContainer.basename))} 👈`,
|
||||
"",
|
||||
"Test with different Keycloak versions by editing the .sh file. see available versions here: https://quay.io/repository/keycloak/keycloak?tab=tags",
|
||||
"",
|
||||
"Once your container is up and running: ",
|
||||
"- Log into the admin console 👉 http://localhost:8080 username: admin, password: admin 👈",
|
||||
"- Log into the admin console 👉 http://localhost:8080/admin username: admin, password: admin 👈",
|
||||
'- Create a realm named "myrealm"',
|
||||
'- Create a client with id "myclient" and root url: "https://www.keycloak.org/app/"',
|
||||
'- Create a client with ID: "myclient", "Root URL": "https://www.keycloak.org/app/" and "Valid redirect URIs": "https://www.keycloak.org/app/*"',
|
||||
`- Select Login Theme: ${themeName} (don't forget to save at the bottom of the page)`,
|
||||
`- Go to 👉 https://www.keycloak.org/app/ 👈 Click "Save" then "Sign in". You should see your login page`,
|
||||
"",
|
||||
|
@ -1,85 +0,0 @@
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
||||
import type { KeycloakVersion } from "../../KeycloakVersion";
|
||||
|
||||
export const containerLaunchScriptBasename = "start_keycloak_testing_container.sh";
|
||||
|
||||
/** Files for being able to run a hot reload keycloak container */
|
||||
export function generateDebugFiles(params: { keycloakVersion: KeycloakVersion; themeName: string; keycloakThemeBuildingDirPath: string }) {
|
||||
const { themeName, keycloakThemeBuildingDirPath, keycloakVersion } = params;
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(keycloakThemeBuildingDirPath, "Dockerfile"),
|
||||
Buffer.from(
|
||||
[
|
||||
`FROM jboss/keycloak:${keycloakVersion}`,
|
||||
"",
|
||||
"USER root",
|
||||
"",
|
||||
"WORKDIR /",
|
||||
"",
|
||||
"ADD configuration /opt/jboss/keycloak/standalone/configuration/",
|
||||
"",
|
||||
'ENTRYPOINT [ "/opt/jboss/tools/docker-entrypoint.sh" ]',
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
),
|
||||
);
|
||||
|
||||
const dockerImage = `${themeName}/keycloak-hot-reload`;
|
||||
const containerName = "keycloak-testing-container";
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename),
|
||||
Buffer.from(
|
||||
[
|
||||
"#!/bin/bash",
|
||||
"",
|
||||
`cd ${keycloakThemeBuildingDirPath}`,
|
||||
"",
|
||||
`docker rm ${containerName} || true`,
|
||||
"",
|
||||
`docker build . -t ${dockerImage}`,
|
||||
"",
|
||||
"docker run \\",
|
||||
" -p 8080:8080 \\",
|
||||
` --name ${containerName} \\`,
|
||||
" -e KEYCLOAK_USER=admin \\",
|
||||
" -e KEYCLOAK_PASSWORD=admin \\",
|
||||
" -e JAVA_OPTS=-Dkeycloak.profile=preview \\",
|
||||
` -v ${pathJoin(
|
||||
keycloakThemeBuildingDirPath,
|
||||
"src",
|
||||
"main",
|
||||
"resources",
|
||||
"theme",
|
||||
themeName,
|
||||
)}:/opt/jboss/keycloak/themes/${themeName}:rw \\`,
|
||||
` -it ${dockerImage}:latest`,
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
),
|
||||
{ "mode": 0o755 },
|
||||
);
|
||||
|
||||
const standaloneHaFilePath = pathJoin(keycloakThemeBuildingDirPath, "configuration", `standalone-ha.xml`);
|
||||
|
||||
try {
|
||||
fs.mkdirSync(pathDirname(standaloneHaFilePath));
|
||||
} catch {}
|
||||
|
||||
fs.writeFileSync(
|
||||
standaloneHaFilePath,
|
||||
fs
|
||||
.readFileSync(pathJoin(__dirname, `standalone-ha_${keycloakVersion}.xml`))
|
||||
.toString("utf8")
|
||||
.replace(
|
||||
new RegExp(
|
||||
["<staticMaxAge>2592000</staticMaxAge>", "<cacheThemes>true</cacheThemes>", "<cacheTemplates>true</cacheTemplates>"].join("\\s*"),
|
||||
"g",
|
||||
),
|
||||
["<staticMaxAge>-1</staticMaxAge>", "<cacheThemes>false</cacheThemes>", "<cacheTemplates>false</cacheTemplates>"].join("\n"),
|
||||
),
|
||||
);
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from "./generateDebugFiles";
|
@ -1,666 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
|
||||
<server xmlns="urn:jboss:domain:13.0">
|
||||
<extensions>
|
||||
<extension module="org.jboss.as.clustering.infinispan"/>
|
||||
<extension module="org.jboss.as.clustering.jgroups"/>
|
||||
<extension module="org.jboss.as.connector"/>
|
||||
<extension module="org.jboss.as.deployment-scanner"/>
|
||||
<extension module="org.jboss.as.ee"/>
|
||||
<extension module="org.jboss.as.ejb3"/>
|
||||
<extension module="org.jboss.as.jaxrs"/>
|
||||
<extension module="org.jboss.as.jmx"/>
|
||||
<extension module="org.jboss.as.jpa"/>
|
||||
<extension module="org.jboss.as.logging"/>
|
||||
<extension module="org.jboss.as.mail"/>
|
||||
<extension module="org.jboss.as.modcluster"/>
|
||||
<extension module="org.jboss.as.naming"/>
|
||||
<extension module="org.jboss.as.remoting"/>
|
||||
<extension module="org.jboss.as.security"/>
|
||||
<extension module="org.jboss.as.transactions"/>
|
||||
<extension module="org.jboss.as.weld"/>
|
||||
<extension module="org.keycloak.keycloak-server-subsystem"/>
|
||||
<extension module="org.wildfly.extension.bean-validation"/>
|
||||
<extension module="org.wildfly.extension.core-management"/>
|
||||
<extension module="org.wildfly.extension.elytron"/>
|
||||
<extension module="org.wildfly.extension.io"/>
|
||||
<extension module="org.wildfly.extension.microprofile.config-smallrye"/>
|
||||
<extension module="org.wildfly.extension.microprofile.health-smallrye"/>
|
||||
<extension module="org.wildfly.extension.microprofile.metrics-smallrye"/>
|
||||
<extension module="org.wildfly.extension.request-controller"/>
|
||||
<extension module="org.wildfly.extension.security.manager"/>
|
||||
<extension module="org.wildfly.extension.undertow"/>
|
||||
</extensions>
|
||||
<management>
|
||||
<security-realms>
|
||||
<security-realm name="ManagementRealm">
|
||||
<authentication>
|
||||
<local default-user="$local" skip-group-loading="true"/>
|
||||
<properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
|
||||
</authentication>
|
||||
<authorization map-groups-to-roles="false">
|
||||
<properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
|
||||
</authorization>
|
||||
</security-realm>
|
||||
<security-realm name="ApplicationRealm">
|
||||
<server-identities>
|
||||
<ssl>
|
||||
<keystore path="application.keystore" relative-to="jboss.server.config.dir" keystore-password="password" alias="server" key-password="password" generate-self-signed-certificate-host="localhost"/>
|
||||
</ssl>
|
||||
</server-identities>
|
||||
<authentication>
|
||||
<local default-user="$local" allowed-users="*" skip-group-loading="true"/>
|
||||
<properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
|
||||
</authentication>
|
||||
<authorization>
|
||||
<properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
|
||||
</authorization>
|
||||
</security-realm>
|
||||
</security-realms>
|
||||
<audit-log>
|
||||
<formatters>
|
||||
<json-formatter name="json-formatter"/>
|
||||
</formatters>
|
||||
<handlers>
|
||||
<file-handler name="file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
|
||||
</handlers>
|
||||
<logger log-boot="true" log-read-only="false" enabled="false">
|
||||
<handlers>
|
||||
<handler name="file"/>
|
||||
</handlers>
|
||||
</logger>
|
||||
</audit-log>
|
||||
<management-interfaces>
|
||||
<http-interface security-realm="ManagementRealm">
|
||||
<http-upgrade enabled="true"/>
|
||||
<socket-binding http="management-http"/>
|
||||
</http-interface>
|
||||
</management-interfaces>
|
||||
<access-control provider="simple">
|
||||
<role-mapping>
|
||||
<role name="SuperUser">
|
||||
<include>
|
||||
<user name="$local"/>
|
||||
</include>
|
||||
</role>
|
||||
</role-mapping>
|
||||
</access-control>
|
||||
</management>
|
||||
<profile>
|
||||
<subsystem xmlns="urn:jboss:domain:logging:8.0">
|
||||
<console-handler name="CONSOLE">
|
||||
<formatter>
|
||||
<named-formatter name="COLOR-PATTERN"/>
|
||||
</formatter>
|
||||
</console-handler>
|
||||
<logger category="com.arjuna">
|
||||
<level name="WARN"/>
|
||||
</logger>
|
||||
<logger category="io.jaegertracing.Configuration">
|
||||
<level name="WARN"/>
|
||||
</logger>
|
||||
<logger category="org.jboss.as.config">
|
||||
<level name="DEBUG"/>
|
||||
</logger>
|
||||
<logger category="sun.rmi">
|
||||
<level name="WARN"/>
|
||||
</logger>
|
||||
<logger category="org.keycloak">
|
||||
<level name="${env.KEYCLOAK_LOGLEVEL:INFO}"/>
|
||||
</logger>
|
||||
<root-logger>
|
||||
<level name="${env.ROOT_LOGLEVEL:INFO}"/>
|
||||
<handlers>
|
||||
<handler name="CONSOLE"/>
|
||||
</handlers>
|
||||
</root-logger>
|
||||
<formatter name="PATTERN">
|
||||
<pattern-formatter pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
|
||||
</formatter>
|
||||
<formatter name="COLOR-PATTERN">
|
||||
<pattern-formatter pattern="%K{level}%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
|
||||
</formatter>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:bean-validation:1.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:core-management:1.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:datasources:6.0">
|
||||
<datasources>
|
||||
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
|
||||
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
|
||||
<driver>h2</driver>
|
||||
<security>
|
||||
<user-name>sa</user-name>
|
||||
<password>sa</password>
|
||||
</security>
|
||||
</datasource>
|
||||
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
|
||||
<connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
|
||||
<driver>h2</driver>
|
||||
<pool>
|
||||
<max-pool-size>100</max-pool-size>
|
||||
</pool>
|
||||
<security>
|
||||
<user-name>sa</user-name>
|
||||
<password>sa</password>
|
||||
</security>
|
||||
</datasource>
|
||||
<drivers>
|
||||
<driver name="h2" module="com.h2database.h2">
|
||||
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
|
||||
</driver>
|
||||
</drivers>
|
||||
</datasources>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:deployment-scanner:2.0">
|
||||
<deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" runtime-failure-causes-rollback="${jboss.deployment.scanner.rollback.on.failure:false}"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:ee:5.0">
|
||||
<spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
|
||||
<concurrent>
|
||||
<context-services>
|
||||
<context-service name="default" jndi-name="java:jboss/ee/concurrency/context/default" use-transaction-setup-provider="true"/>
|
||||
</context-services>
|
||||
<managed-thread-factories>
|
||||
<managed-thread-factory name="default" jndi-name="java:jboss/ee/concurrency/factory/default" context-service="default"/>
|
||||
</managed-thread-factories>
|
||||
<managed-executor-services>
|
||||
<managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default" context-service="default" hung-task-threshold="60000" keepalive-time="5000"/>
|
||||
</managed-executor-services>
|
||||
<managed-scheduled-executor-services>
|
||||
<managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" context-service="default" hung-task-threshold="60000" keepalive-time="3000"/>
|
||||
</managed-scheduled-executor-services>
|
||||
</concurrent>
|
||||
<default-bindings context-service="java:jboss/ee/concurrency/context/default" datasource="java:jboss/datasources/ExampleDS" managed-executor-service="java:jboss/ee/concurrency/executor/default" managed-scheduled-executor-service="java:jboss/ee/concurrency/scheduler/default" managed-thread-factory="java:jboss/ee/concurrency/factory/default"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:ejb3:7.0">
|
||||
<session-bean>
|
||||
<stateless>
|
||||
<bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
|
||||
</stateless>
|
||||
<stateful default-access-timeout="5000" cache-ref="distributable" passivation-disabled-cache-ref="simple"/>
|
||||
<singleton default-access-timeout="5000"/>
|
||||
</session-bean>
|
||||
<pools>
|
||||
<bean-instance-pools>
|
||||
<strict-max-pool name="mdb-strict-max-pool" derive-size="from-cpu-count" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
|
||||
<strict-max-pool name="slsb-strict-max-pool" derive-size="from-worker-pools" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
|
||||
</bean-instance-pools>
|
||||
</pools>
|
||||
<caches>
|
||||
<cache name="simple"/>
|
||||
<cache name="distributable" passivation-store-ref="infinispan" aliases="passivating clustered"/>
|
||||
</caches>
|
||||
<passivation-stores>
|
||||
<passivation-store name="infinispan" cache-container="ejb" max-size="10000"/>
|
||||
</passivation-stores>
|
||||
<async thread-pool-name="default"/>
|
||||
<timer-service thread-pool-name="default" default-data-store="default-file-store">
|
||||
<data-stores>
|
||||
<file-data-store name="default-file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
|
||||
</data-stores>
|
||||
</timer-service>
|
||||
<remote connector-ref="http-remoting-connector" thread-pool-name="default">
|
||||
<channel-creation-options>
|
||||
<option name="MAX_OUTBOUND_MESSAGES" value="1234" type="remoting"/>
|
||||
</channel-creation-options>
|
||||
</remote>
|
||||
<thread-pools>
|
||||
<thread-pool name="default">
|
||||
<max-threads count="10"/>
|
||||
<keepalive-time time="60" unit="seconds"/>
|
||||
</thread-pool>
|
||||
</thread-pools>
|
||||
<default-security-domain value="other"/>
|
||||
<default-missing-method-permissions-deny-access value="true"/>
|
||||
<statistics enabled="${wildfly.ejb3.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
|
||||
<log-system-exceptions value="true"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:wildfly:elytron:10.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
|
||||
<providers>
|
||||
<aggregate-providers name="combined-providers">
|
||||
<providers name="elytron"/>
|
||||
<providers name="openssl"/>
|
||||
</aggregate-providers>
|
||||
<provider-loader name="elytron" module="org.wildfly.security.elytron"/>
|
||||
<provider-loader name="openssl" module="org.wildfly.openssl"/>
|
||||
</providers>
|
||||
<audit-logging>
|
||||
<file-audit-log name="local-audit" path="audit.log" relative-to="jboss.server.log.dir" format="JSON"/>
|
||||
</audit-logging>
|
||||
<security-domains>
|
||||
<security-domain name="ApplicationDomain" default-realm="ApplicationRealm" permission-mapper="default-permission-mapper">
|
||||
<realm name="ApplicationRealm" role-decoder="groups-to-roles"/>
|
||||
<realm name="local"/>
|
||||
</security-domain>
|
||||
<security-domain name="ManagementDomain" default-realm="ManagementRealm" permission-mapper="default-permission-mapper">
|
||||
<realm name="ManagementRealm" role-decoder="groups-to-roles"/>
|
||||
<realm name="local" role-mapper="super-user-mapper"/>
|
||||
</security-domain>
|
||||
</security-domains>
|
||||
<security-realms>
|
||||
<identity-realm name="local" identity="$local"/>
|
||||
<properties-realm name="ApplicationRealm">
|
||||
<users-properties path="application-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ApplicationRealm"/>
|
||||
<groups-properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
|
||||
</properties-realm>
|
||||
<properties-realm name="ManagementRealm">
|
||||
<users-properties path="mgmt-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ManagementRealm"/>
|
||||
<groups-properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
|
||||
</properties-realm>
|
||||
</security-realms>
|
||||
<mappers>
|
||||
<simple-permission-mapper name="default-permission-mapper" mapping-mode="first">
|
||||
<permission-mapping>
|
||||
<principal name="anonymous"/>
|
||||
<permission-set name="default-permissions"/>
|
||||
</permission-mapping>
|
||||
<permission-mapping match-all="true">
|
||||
<permission-set name="login-permission"/>
|
||||
<permission-set name="default-permissions"/>
|
||||
</permission-mapping>
|
||||
</simple-permission-mapper>
|
||||
<constant-realm-mapper name="local" realm-name="local"/>
|
||||
<simple-role-decoder name="groups-to-roles" attribute="groups"/>
|
||||
<constant-role-mapper name="super-user-mapper">
|
||||
<role name="SuperUser"/>
|
||||
</constant-role-mapper>
|
||||
</mappers>
|
||||
<permission-sets>
|
||||
<permission-set name="login-permission">
|
||||
<permission class-name="org.wildfly.security.auth.permission.LoginPermission"/>
|
||||
</permission-set>
|
||||
<permission-set name="default-permissions">
|
||||
<permission class-name="org.wildfly.extension.batch.jberet.deployment.BatchPermission" module="org.wildfly.extension.batch.jberet" target-name="*"/>
|
||||
<permission class-name="org.wildfly.transaction.client.RemoteTransactionPermission" module="org.wildfly.transaction.client"/>
|
||||
<permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
|
||||
</permission-set>
|
||||
</permission-sets>
|
||||
<http>
|
||||
<http-authentication-factory name="management-http-authentication" security-domain="ManagementDomain" http-server-mechanism-factory="global">
|
||||
<mechanism-configuration>
|
||||
<mechanism mechanism-name="DIGEST">
|
||||
<mechanism-realm realm-name="ManagementRealm"/>
|
||||
</mechanism>
|
||||
</mechanism-configuration>
|
||||
</http-authentication-factory>
|
||||
<provider-http-server-mechanism-factory name="global"/>
|
||||
</http>
|
||||
<sasl>
|
||||
<sasl-authentication-factory name="application-sasl-authentication" sasl-server-factory="configured" security-domain="ApplicationDomain">
|
||||
<mechanism-configuration>
|
||||
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
|
||||
<mechanism mechanism-name="DIGEST-MD5">
|
||||
<mechanism-realm realm-name="ApplicationRealm"/>
|
||||
</mechanism>
|
||||
</mechanism-configuration>
|
||||
</sasl-authentication-factory>
|
||||
<sasl-authentication-factory name="management-sasl-authentication" sasl-server-factory="configured" security-domain="ManagementDomain">
|
||||
<mechanism-configuration>
|
||||
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
|
||||
<mechanism mechanism-name="DIGEST-MD5">
|
||||
<mechanism-realm realm-name="ManagementRealm"/>
|
||||
</mechanism>
|
||||
</mechanism-configuration>
|
||||
</sasl-authentication-factory>
|
||||
<configurable-sasl-server-factory name="configured" sasl-server-factory="elytron">
|
||||
<properties>
|
||||
<property name="wildfly.sasl.local-user.default-user" value="$local"/>
|
||||
</properties>
|
||||
</configurable-sasl-server-factory>
|
||||
<mechanism-provider-filtering-sasl-server-factory name="elytron" sasl-server-factory="global">
|
||||
<filters>
|
||||
<filter provider-name="WildFlyElytron"/>
|
||||
</filters>
|
||||
</mechanism-provider-filtering-sasl-server-factory>
|
||||
<provider-sasl-server-factory name="global"/>
|
||||
</sasl>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:infinispan:10.0">
|
||||
<cache-container name="keycloak" module="org.keycloak.keycloak-model-infinispan">
|
||||
<transport lock-timeout="60000"/>
|
||||
<local-cache name="realms">
|
||||
<object-memory size="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="users">
|
||||
<object-memory size="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="authorization">
|
||||
<object-memory size="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="keys">
|
||||
<object-memory size="1000"/>
|
||||
<expiration max-idle="3600000"/>
|
||||
</local-cache>
|
||||
<replicated-cache name="work"/>
|
||||
<distributed-cache name="sessions" owners="1"/>
|
||||
<distributed-cache name="authenticationSessions" owners="1"/>
|
||||
<distributed-cache name="offlineSessions" owners="1"/>
|
||||
<distributed-cache name="clientSessions" owners="1"/>
|
||||
<distributed-cache name="offlineClientSessions" owners="1"/>
|
||||
<distributed-cache name="loginFailures" owners="1"/>
|
||||
<distributed-cache name="actionTokens" owners="2">
|
||||
<object-memory size="-1"/>
|
||||
<expiration interval="300000" max-idle="-1"/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
<cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
|
||||
<transport lock-timeout="60000"/>
|
||||
<replicated-cache name="default">
|
||||
<transaction mode="BATCH"/>
|
||||
</replicated-cache>
|
||||
</cache-container>
|
||||
<cache-container name="web" default-cache="dist" module="org.wildfly.clustering.web.infinispan">
|
||||
<transport lock-timeout="60000"/>
|
||||
<replicated-cache name="sso">
|
||||
<locking isolation="REPEATABLE_READ"/>
|
||||
<transaction mode="BATCH"/>
|
||||
</replicated-cache>
|
||||
<distributed-cache name="dist">
|
||||
<locking isolation="REPEATABLE_READ"/>
|
||||
<transaction mode="BATCH"/>
|
||||
<file-store/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="routing"/>
|
||||
</cache-container>
|
||||
<cache-container name="ejb" aliases="sfsb" default-cache="dist" module="org.wildfly.clustering.ejb.infinispan">
|
||||
<transport lock-timeout="60000"/>
|
||||
<distributed-cache name="dist">
|
||||
<locking isolation="REPEATABLE_READ"/>
|
||||
<transaction mode="BATCH"/>
|
||||
<file-store/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
<cache-container name="hibernate" module="org.infinispan.hibernate-cache">
|
||||
<transport lock-timeout="60000"/>
|
||||
<local-cache name="local-query">
|
||||
<object-memory size="10000"/>
|
||||
<expiration max-idle="100000"/>
|
||||
</local-cache>
|
||||
<invalidation-cache name="entity">
|
||||
<transaction mode="NON_XA"/>
|
||||
<object-memory size="10000"/>
|
||||
<expiration max-idle="100000"/>
|
||||
</invalidation-cache>
|
||||
<replicated-cache name="timestamps"/>
|
||||
</cache-container>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:io:3.0">
|
||||
<worker name="default"/>
|
||||
<buffer-pool name="default"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jaxrs:2.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:jca:5.0">
|
||||
<archive-validation enabled="true" fail-on-error="true" fail-on-warn="false"/>
|
||||
<bean-validation enabled="true"/>
|
||||
<default-workmanager>
|
||||
<short-running-threads>
|
||||
<core-threads count="50"/>
|
||||
<queue-length count="50"/>
|
||||
<max-threads count="50"/>
|
||||
<keepalive-time time="10" unit="seconds"/>
|
||||
</short-running-threads>
|
||||
<long-running-threads>
|
||||
<core-threads count="50"/>
|
||||
<queue-length count="50"/>
|
||||
<max-threads count="50"/>
|
||||
<keepalive-time time="10" unit="seconds"/>
|
||||
</long-running-threads>
|
||||
</default-workmanager>
|
||||
<cached-connection-manager/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jgroups:8.0">
|
||||
<channels default="ee">
|
||||
<channel name="ee" stack="udp" cluster="ejb"/>
|
||||
</channels>
|
||||
<stacks>
|
||||
<stack name="udp">
|
||||
<transport type="UDP" socket-binding="jgroups-udp"/>
|
||||
<protocol type="PING"/>
|
||||
<protocol type="MERGE3"/>
|
||||
<socket-protocol type="FD_SOCK" socket-binding="jgroups-udp-fd"/>
|
||||
<protocol type="FD_ALL"/>
|
||||
<protocol type="VERIFY_SUSPECT"/>
|
||||
<protocol type="pbcast.NAKACK2"/>
|
||||
<protocol type="UNICAST3"/>
|
||||
<protocol type="pbcast.STABLE"/>
|
||||
<protocol type="pbcast.GMS"/>
|
||||
<protocol type="UFC"/>
|
||||
<protocol type="MFC"/>
|
||||
<protocol type="FRAG3"/>
|
||||
</stack>
|
||||
<stack name="tcp">
|
||||
<transport type="TCP" socket-binding="jgroups-tcp"/>
|
||||
<socket-protocol type="MPING" socket-binding="jgroups-mping"/>
|
||||
<protocol type="MERGE3"/>
|
||||
<socket-protocol type="FD_SOCK" socket-binding="jgroups-tcp-fd"/>
|
||||
<protocol type="FD_ALL"/>
|
||||
<protocol type="VERIFY_SUSPECT"/>
|
||||
<protocol type="pbcast.NAKACK2"/>
|
||||
<protocol type="UNICAST3"/>
|
||||
<protocol type="pbcast.STABLE"/>
|
||||
<protocol type="pbcast.GMS"/>
|
||||
<protocol type="MFC"/>
|
||||
<protocol type="FRAG3"/>
|
||||
</stack>
|
||||
</stacks>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jmx:1.3">
|
||||
<expose-resolved-model/>
|
||||
<expose-expression-model/>
|
||||
<remoting-connector/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jpa:1.1">
|
||||
<jpa default-datasource="" default-extended-persistence-inheritance="DEEP"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
|
||||
<web-context>auth</web-context>
|
||||
<providers>
|
||||
<provider>
|
||||
classpath:${jboss.home.dir}/providers/*
|
||||
</provider>
|
||||
</providers>
|
||||
<master-realm-name>master</master-realm-name>
|
||||
<scheduled-task-interval>900</scheduled-task-interval>
|
||||
<theme>
|
||||
<staticMaxAge>2592000</staticMaxAge>
|
||||
<cacheThemes>true</cacheThemes>
|
||||
<cacheTemplates>true</cacheTemplates>
|
||||
<welcomeTheme>${env.KEYCLOAK_WELCOME_THEME:keycloak}</welcomeTheme>
|
||||
<default>${env.KEYCLOAK_DEFAULT_THEME:keycloak}</default>
|
||||
<dir>${jboss.home.dir}/themes</dir>
|
||||
</theme>
|
||||
<spi name="eventsStore">
|
||||
<provider name="jpa" enabled="true">
|
||||
<properties>
|
||||
<property name="exclude-events" value="["REFRESH_TOKEN"]"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="userCache">
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="userSessionPersister">
|
||||
<default-provider>jpa</default-provider>
|
||||
</spi>
|
||||
<spi name="timer">
|
||||
<default-provider>basic</default-provider>
|
||||
</spi>
|
||||
<spi name="connectionsHttpClient">
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="connectionsJpa">
|
||||
<provider name="default" enabled="true">
|
||||
<properties>
|
||||
<property name="dataSource" value="java:jboss/datasources/KeycloakDS"/>
|
||||
<property name="initializeEmpty" value="true"/>
|
||||
<property name="migrationStrategy" value="update"/>
|
||||
<property name="migrationExport" value="${jboss.home.dir}/keycloak-database-update.sql"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="realmCache">
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="connectionsInfinispan">
|
||||
<default-provider>default</default-provider>
|
||||
<provider name="default" enabled="true">
|
||||
<properties>
|
||||
<property name="cacheContainer" value="java:jboss/infinispan/container/keycloak"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="jta-lookup">
|
||||
<default-provider>${keycloak.jta.lookup.provider:jboss}</default-provider>
|
||||
<provider name="jboss" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="publicKeyStorage">
|
||||
<provider name="infinispan" enabled="true">
|
||||
<properties>
|
||||
<property name="minTimeBetweenRequests" value="10"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="x509cert-lookup">
|
||||
<default-provider>${keycloak.x509cert.lookup.provider:default}</default-provider>
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="hostname">
|
||||
<default-provider>${keycloak.hostname.provider:default}</default-provider>
|
||||
<provider name="default" enabled="true">
|
||||
<properties>
|
||||
<property name="frontendUrl" value="${keycloak.frontendUrl:}"/>
|
||||
<property name="forceBackendUrlToFrontendUrl" value="false"/>
|
||||
</properties>
|
||||
</provider>
|
||||
<provider name="fixed" enabled="true">
|
||||
<properties>
|
||||
<property name="hostname" value="${keycloak.hostname.fixed.hostname:localhost}"/>
|
||||
<property name="httpPort" value="${keycloak.hostname.fixed.httpPort:-1}"/>
|
||||
<property name="httpsPort" value="${keycloak.hostname.fixed.httpsPort:-1}"/>
|
||||
<property name="alwaysHttps" value="${keycloak.hostname.fixed.alwaysHttps:false}"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:mail:4.0">
|
||||
<mail-session name="default" jndi-name="java:jboss/mail/Default">
|
||||
<smtp-server outbound-socket-binding-ref="mail-smtp"/>
|
||||
</mail-session>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:wildfly:microprofile-config-smallrye:1.0"/>
|
||||
<subsystem xmlns="urn:wildfly:microprofile-health-smallrye:2.0" security-enabled="false" empty-liveness-checks-status="${env.MP_HEALTH_EMPTY_LIVENESS_CHECKS_STATUS:UP}" empty-readiness-checks-status="${env.MP_HEALTH_EMPTY_READINESS_CHECKS_STATUS:UP}"/>
|
||||
<subsystem xmlns="urn:wildfly:microprofile-metrics-smallrye:2.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:wildfly}"/>
|
||||
<subsystem xmlns="urn:jboss:domain:modcluster:5.0">
|
||||
<proxy name="default" advertise-socket="modcluster" listener="ajp">
|
||||
<dynamic-load-provider>
|
||||
<load-metric type="cpu"/>
|
||||
</dynamic-load-provider>
|
||||
</proxy>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:naming:2.0">
|
||||
<remote-naming/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:remoting:4.0">
|
||||
<http-connector name="http-remoting-connector" connector-ref="default" security-realm="ApplicationRealm"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:request-controller:1.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:security:2.0">
|
||||
<security-domains>
|
||||
<security-domain name="other" cache-type="default">
|
||||
<authentication>
|
||||
<login-module code="Remoting" flag="optional">
|
||||
<module-option name="password-stacking" value="useFirstPass"/>
|
||||
</login-module>
|
||||
<login-module code="RealmDirect" flag="required">
|
||||
<module-option name="password-stacking" value="useFirstPass"/>
|
||||
</login-module>
|
||||
</authentication>
|
||||
</security-domain>
|
||||
<security-domain name="jboss-web-policy" cache-type="default">
|
||||
<authorization>
|
||||
<policy-module code="Delegating" flag="required"/>
|
||||
</authorization>
|
||||
</security-domain>
|
||||
<security-domain name="jaspitest" cache-type="default">
|
||||
<authentication-jaspi>
|
||||
<login-module-stack name="dummy">
|
||||
<login-module code="Dummy" flag="optional"/>
|
||||
</login-module-stack>
|
||||
<auth-module code="Dummy"/>
|
||||
</authentication-jaspi>
|
||||
</security-domain>
|
||||
<security-domain name="jboss-ejb-policy" cache-type="default">
|
||||
<authorization>
|
||||
<policy-module code="Delegating" flag="required"/>
|
||||
</authorization>
|
||||
</security-domain>
|
||||
</security-domains>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:security-manager:1.0">
|
||||
<deployment-permissions>
|
||||
<maximum-set>
|
||||
<permission class="java.security.AllPermission"/>
|
||||
</maximum-set>
|
||||
</deployment-permissions>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:transactions:5.0">
|
||||
<core-environment node-identifier="${jboss.tx.node.id:1}">
|
||||
<process-id>
|
||||
<uuid/>
|
||||
</process-id>
|
||||
</core-environment>
|
||||
<recovery-environment socket-binding="txn-recovery-environment" status-socket-binding="txn-status-manager"/>
|
||||
<coordinator-environment statistics-enabled="${wildfly.transactions.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
|
||||
<object-store path="tx-object-store" relative-to="jboss.server.data.dir"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:undertow:11.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other" statistics-enabled="${wildfly.undertow.statistics-enabled:${wildfly.statistics-enabled:false}}">
|
||||
<buffer-cache name="default"/>
|
||||
<server name="default-server">
|
||||
<ajp-listener name="ajp" socket-binding="ajp"/>
|
||||
<http-listener name="default" read-timeout="30000" socket-binding="http" redirect-socket="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" enable-http2="true"/>
|
||||
<https-listener name="https" read-timeout="30000" socket-binding="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" security-realm="ApplicationRealm" enable-http2="true"/>
|
||||
<host name="default-host" alias="localhost">
|
||||
<location name="/" handler="welcome-content"/>
|
||||
<http-invoker security-realm="ApplicationRealm"/>
|
||||
</host>
|
||||
</server>
|
||||
<servlet-container name="default">
|
||||
<jsp-config/>
|
||||
<websockets/>
|
||||
</servlet-container>
|
||||
<handlers>
|
||||
<file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
|
||||
</handlers>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:weld:4.0"/>
|
||||
</profile>
|
||||
<interfaces>
|
||||
<interface name="management">
|
||||
<inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
|
||||
</interface>
|
||||
<interface name="private">
|
||||
<inet-address value="${jboss.bind.address.private:127.0.0.1}"/>
|
||||
</interface>
|
||||
<interface name="public">
|
||||
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
|
||||
</interface>
|
||||
</interfaces>
|
||||
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
|
||||
<socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
|
||||
<socket-binding name="http" port="${jboss.http.port:8080}"/>
|
||||
<socket-binding name="https" port="${jboss.https.port:8443}"/>
|
||||
<socket-binding name="jgroups-mping" interface="private" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45700"/>
|
||||
<socket-binding name="jgroups-tcp" interface="private" port="7600"/>
|
||||
<socket-binding name="jgroups-tcp-fd" interface="private" port="57600"/>
|
||||
<socket-binding name="jgroups-udp" interface="private" port="55200" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45688"/>
|
||||
<socket-binding name="jgroups-udp-fd" interface="private" port="54200"/>
|
||||
<socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
|
||||
<socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
|
||||
<socket-binding name="modcluster" multicast-address="${jboss.modcluster.multicast.address:224.0.1.105}" multicast-port="23364"/>
|
||||
<socket-binding name="txn-recovery-environment" port="4712"/>
|
||||
<socket-binding name="txn-status-manager" port="4713"/>
|
||||
<outbound-socket-binding name="mail-smtp">
|
||||
<remote-destination host="localhost" port="25"/>
|
||||
</outbound-socket-binding>
|
||||
</socket-binding-group>
|
||||
</server>
|
@ -1,693 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
|
||||
<server xmlns="urn:jboss:domain:16.0">
|
||||
<extensions>
|
||||
<extension module="org.jboss.as.clustering.infinispan"/>
|
||||
<extension module="org.jboss.as.clustering.jgroups"/>
|
||||
<extension module="org.jboss.as.connector"/>
|
||||
<extension module="org.jboss.as.deployment-scanner"/>
|
||||
<extension module="org.jboss.as.ee"/>
|
||||
<extension module="org.jboss.as.ejb3"/>
|
||||
<extension module="org.jboss.as.jaxrs"/>
|
||||
<extension module="org.jboss.as.jmx"/>
|
||||
<extension module="org.jboss.as.jpa"/>
|
||||
<extension module="org.jboss.as.logging"/>
|
||||
<extension module="org.jboss.as.mail"/>
|
||||
<extension module="org.jboss.as.modcluster"/>
|
||||
<extension module="org.jboss.as.naming"/>
|
||||
<extension module="org.jboss.as.remoting"/>
|
||||
<extension module="org.jboss.as.security"/>
|
||||
<extension module="org.jboss.as.transactions"/>
|
||||
<extension module="org.jboss.as.weld"/>
|
||||
<extension module="org.keycloak.keycloak-server-subsystem"/>
|
||||
<extension module="org.wildfly.extension.bean-validation"/>
|
||||
<extension module="org.wildfly.extension.core-management"/>
|
||||
<extension module="org.wildfly.extension.elytron"/>
|
||||
<extension module="org.wildfly.extension.health"/>
|
||||
<extension module="org.wildfly.extension.io"/>
|
||||
<extension module="org.wildfly.extension.metrics"/>
|
||||
<extension module="org.wildfly.extension.request-controller"/>
|
||||
<extension module="org.wildfly.extension.security.manager"/>
|
||||
<extension module="org.wildfly.extension.undertow"/>
|
||||
</extensions>
|
||||
<management>
|
||||
<security-realms>
|
||||
<security-realm name="ManagementRealm">
|
||||
<authentication>
|
||||
<local default-user="$local" skip-group-loading="true"/>
|
||||
<properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
|
||||
</authentication>
|
||||
<authorization map-groups-to-roles="false">
|
||||
<properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
|
||||
</authorization>
|
||||
</security-realm>
|
||||
<security-realm name="ApplicationRealm">
|
||||
<server-identities>
|
||||
<ssl>
|
||||
<keystore path="application.keystore" relative-to="jboss.server.config.dir" keystore-password="password" alias="server" key-password="password" generate-self-signed-certificate-host="localhost"/>
|
||||
</ssl>
|
||||
</server-identities>
|
||||
<authentication>
|
||||
<local default-user="$local" allowed-users="*" skip-group-loading="true"/>
|
||||
<properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
|
||||
</authentication>
|
||||
<authorization>
|
||||
<properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
|
||||
</authorization>
|
||||
</security-realm>
|
||||
</security-realms>
|
||||
<audit-log>
|
||||
<formatters>
|
||||
<json-formatter name="json-formatter"/>
|
||||
</formatters>
|
||||
<handlers>
|
||||
<file-handler name="file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
|
||||
</handlers>
|
||||
<logger log-boot="true" log-read-only="false" enabled="false">
|
||||
<handlers>
|
||||
<handler name="file"/>
|
||||
</handlers>
|
||||
</logger>
|
||||
</audit-log>
|
||||
<management-interfaces>
|
||||
<http-interface security-realm="ManagementRealm">
|
||||
<http-upgrade enabled="true"/>
|
||||
<socket-binding http="management-http"/>
|
||||
</http-interface>
|
||||
</management-interfaces>
|
||||
<access-control provider="simple">
|
||||
<role-mapping>
|
||||
<role name="SuperUser">
|
||||
<include>
|
||||
<user name="$local"/>
|
||||
</include>
|
||||
</role>
|
||||
</role-mapping>
|
||||
</access-control>
|
||||
</management>
|
||||
<profile>
|
||||
<subsystem xmlns="urn:jboss:domain:logging:8.0">
|
||||
<console-handler name="CONSOLE">
|
||||
<formatter>
|
||||
<named-formatter name="COLOR-PATTERN"/>
|
||||
</formatter>
|
||||
</console-handler>
|
||||
<logger category="com.arjuna">
|
||||
<level name="WARN"/>
|
||||
</logger>
|
||||
<logger category="io.jaegertracing.Configuration">
|
||||
<level name="WARN"/>
|
||||
</logger>
|
||||
<logger category="org.jboss.as.config">
|
||||
<level name="DEBUG"/>
|
||||
</logger>
|
||||
<logger category="sun.rmi">
|
||||
<level name="WARN"/>
|
||||
</logger>
|
||||
<logger category="org.keycloak">
|
||||
<level name="${env.KEYCLOAK_LOGLEVEL:INFO}"/>
|
||||
</logger>
|
||||
<root-logger>
|
||||
<level name="${env.ROOT_LOGLEVEL:INFO}"/>
|
||||
<handlers>
|
||||
<handler name="CONSOLE"/>
|
||||
</handlers>
|
||||
</root-logger>
|
||||
<formatter name="PATTERN">
|
||||
<pattern-formatter pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
|
||||
</formatter>
|
||||
<formatter name="COLOR-PATTERN">
|
||||
<pattern-formatter pattern="%K{level}%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
|
||||
</formatter>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:bean-validation:1.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:core-management:1.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:datasources:6.0">
|
||||
<datasources>
|
||||
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
|
||||
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
|
||||
<driver>h2</driver>
|
||||
<security>
|
||||
<user-name>sa</user-name>
|
||||
<password>sa</password>
|
||||
</security>
|
||||
</datasource>
|
||||
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
|
||||
<connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
|
||||
<driver>h2</driver>
|
||||
<security>
|
||||
<user-name>sa</user-name>
|
||||
<password>sa</password>
|
||||
</security>
|
||||
</datasource>
|
||||
<drivers>
|
||||
<driver name="h2" module="com.h2database.h2">
|
||||
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
|
||||
</driver>
|
||||
</drivers>
|
||||
</datasources>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:deployment-scanner:2.0">
|
||||
<deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" runtime-failure-causes-rollback="${jboss.deployment.scanner.rollback.on.failure:false}"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:ee:6.0">
|
||||
<spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
|
||||
<concurrent>
|
||||
<context-services>
|
||||
<context-service name="default" jndi-name="java:jboss/ee/concurrency/context/default" use-transaction-setup-provider="true"/>
|
||||
</context-services>
|
||||
<managed-thread-factories>
|
||||
<managed-thread-factory name="default" jndi-name="java:jboss/ee/concurrency/factory/default" context-service="default"/>
|
||||
</managed-thread-factories>
|
||||
<managed-executor-services>
|
||||
<managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default" context-service="default" hung-task-termination-period="0" hung-task-threshold="60000" keepalive-time="5000"/>
|
||||
</managed-executor-services>
|
||||
<managed-scheduled-executor-services>
|
||||
<managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" context-service="default" hung-task-termination-period="0" hung-task-threshold="60000" keepalive-time="3000"/>
|
||||
</managed-scheduled-executor-services>
|
||||
</concurrent>
|
||||
<default-bindings context-service="java:jboss/ee/concurrency/context/default" datasource="java:jboss/datasources/ExampleDS" managed-executor-service="java:jboss/ee/concurrency/executor/default" managed-scheduled-executor-service="java:jboss/ee/concurrency/scheduler/default" managed-thread-factory="java:jboss/ee/concurrency/factory/default"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:ejb3:9.0">
|
||||
<session-bean>
|
||||
<stateless>
|
||||
<bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
|
||||
</stateless>
|
||||
<stateful default-access-timeout="5000" cache-ref="distributable" passivation-disabled-cache-ref="simple"/>
|
||||
<singleton default-access-timeout="5000"/>
|
||||
</session-bean>
|
||||
<pools>
|
||||
<bean-instance-pools>
|
||||
<strict-max-pool name="mdb-strict-max-pool" derive-size="from-cpu-count" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
|
||||
<strict-max-pool name="slsb-strict-max-pool" derive-size="from-worker-pools" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
|
||||
</bean-instance-pools>
|
||||
</pools>
|
||||
<caches>
|
||||
<cache name="simple"/>
|
||||
<cache name="distributable" passivation-store-ref="infinispan" aliases="passivating clustered"/>
|
||||
</caches>
|
||||
<passivation-stores>
|
||||
<passivation-store name="infinispan" cache-container="ejb" max-size="10000"/>
|
||||
</passivation-stores>
|
||||
<async thread-pool-name="default"/>
|
||||
<timer-service thread-pool-name="default" default-data-store="default-file-store">
|
||||
<data-stores>
|
||||
<file-data-store name="default-file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
|
||||
</data-stores>
|
||||
</timer-service>
|
||||
<remote cluster="ejb" connectors="http-remoting-connector" thread-pool-name="default">
|
||||
<channel-creation-options>
|
||||
<option name="MAX_OUTBOUND_MESSAGES" value="1234" type="remoting"/>
|
||||
</channel-creation-options>
|
||||
</remote>
|
||||
<thread-pools>
|
||||
<thread-pool name="default">
|
||||
<max-threads count="10"/>
|
||||
<keepalive-time time="60" unit="seconds"/>
|
||||
</thread-pool>
|
||||
</thread-pools>
|
||||
<default-security-domain value="other"/>
|
||||
<default-missing-method-permissions-deny-access value="true"/>
|
||||
<statistics enabled="${wildfly.ejb3.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
|
||||
<log-system-exceptions value="true"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:wildfly:elytron:13.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
|
||||
<providers>
|
||||
<aggregate-providers name="combined-providers">
|
||||
<providers name="elytron"/>
|
||||
<providers name="openssl"/>
|
||||
</aggregate-providers>
|
||||
<provider-loader name="elytron" module="org.wildfly.security.elytron"/>
|
||||
<provider-loader name="openssl" module="org.wildfly.openssl"/>
|
||||
</providers>
|
||||
<audit-logging>
|
||||
<file-audit-log name="local-audit" path="audit.log" relative-to="jboss.server.log.dir" format="JSON"/>
|
||||
</audit-logging>
|
||||
<security-domains>
|
||||
<security-domain name="ApplicationDomain" default-realm="ApplicationRealm" permission-mapper="default-permission-mapper">
|
||||
<realm name="ApplicationRealm" role-decoder="groups-to-roles"/>
|
||||
<realm name="local"/>
|
||||
</security-domain>
|
||||
<security-domain name="ManagementDomain" default-realm="ManagementRealm" permission-mapper="default-permission-mapper">
|
||||
<realm name="ManagementRealm" role-decoder="groups-to-roles"/>
|
||||
<realm name="local" role-mapper="super-user-mapper"/>
|
||||
</security-domain>
|
||||
</security-domains>
|
||||
<security-realms>
|
||||
<identity-realm name="local" identity="$local"/>
|
||||
<properties-realm name="ApplicationRealm">
|
||||
<users-properties path="application-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ApplicationRealm"/>
|
||||
<groups-properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
|
||||
</properties-realm>
|
||||
<properties-realm name="ManagementRealm">
|
||||
<users-properties path="mgmt-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ManagementRealm"/>
|
||||
<groups-properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
|
||||
</properties-realm>
|
||||
</security-realms>
|
||||
<mappers>
|
||||
<simple-permission-mapper name="default-permission-mapper" mapping-mode="first">
|
||||
<permission-mapping>
|
||||
<principal name="anonymous"/>
|
||||
<permission-set name="default-permissions"/>
|
||||
</permission-mapping>
|
||||
<permission-mapping match-all="true">
|
||||
<permission-set name="login-permission"/>
|
||||
<permission-set name="default-permissions"/>
|
||||
</permission-mapping>
|
||||
</simple-permission-mapper>
|
||||
<constant-realm-mapper name="local" realm-name="local"/>
|
||||
<simple-role-decoder name="groups-to-roles" attribute="groups"/>
|
||||
<constant-role-mapper name="super-user-mapper">
|
||||
<role name="SuperUser"/>
|
||||
</constant-role-mapper>
|
||||
</mappers>
|
||||
<permission-sets>
|
||||
<permission-set name="login-permission">
|
||||
<permission class-name="org.wildfly.security.auth.permission.LoginPermission"/>
|
||||
</permission-set>
|
||||
<permission-set name="default-permissions">
|
||||
<permission class-name="org.wildfly.extension.batch.jberet.deployment.BatchPermission" module="org.wildfly.extension.batch.jberet" target-name="*"/>
|
||||
<permission class-name="org.wildfly.transaction.client.RemoteTransactionPermission" module="org.wildfly.transaction.client"/>
|
||||
<permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
|
||||
<permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
|
||||
</permission-set>
|
||||
</permission-sets>
|
||||
<http>
|
||||
<http-authentication-factory name="management-http-authentication" security-domain="ManagementDomain" http-server-mechanism-factory="global">
|
||||
<mechanism-configuration>
|
||||
<mechanism mechanism-name="DIGEST">
|
||||
<mechanism-realm realm-name="ManagementRealm"/>
|
||||
</mechanism>
|
||||
</mechanism-configuration>
|
||||
</http-authentication-factory>
|
||||
<provider-http-server-mechanism-factory name="global"/>
|
||||
</http>
|
||||
<sasl>
|
||||
<sasl-authentication-factory name="application-sasl-authentication" sasl-server-factory="configured" security-domain="ApplicationDomain">
|
||||
<mechanism-configuration>
|
||||
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
|
||||
<mechanism mechanism-name="DIGEST-MD5">
|
||||
<mechanism-realm realm-name="ApplicationRealm"/>
|
||||
</mechanism>
|
||||
</mechanism-configuration>
|
||||
</sasl-authentication-factory>
|
||||
<sasl-authentication-factory name="management-sasl-authentication" sasl-server-factory="configured" security-domain="ManagementDomain">
|
||||
<mechanism-configuration>
|
||||
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
|
||||
<mechanism mechanism-name="DIGEST-MD5">
|
||||
<mechanism-realm realm-name="ManagementRealm"/>
|
||||
</mechanism>
|
||||
</mechanism-configuration>
|
||||
</sasl-authentication-factory>
|
||||
<configurable-sasl-server-factory name="configured" sasl-server-factory="elytron">
|
||||
<properties>
|
||||
<property name="wildfly.sasl.local-user.default-user" value="$local"/>
|
||||
</properties>
|
||||
</configurable-sasl-server-factory>
|
||||
<mechanism-provider-filtering-sasl-server-factory name="elytron" sasl-server-factory="global">
|
||||
<filters>
|
||||
<filter provider-name="WildFlyElytron"/>
|
||||
</filters>
|
||||
</mechanism-provider-filtering-sasl-server-factory>
|
||||
<provider-sasl-server-factory name="global"/>
|
||||
</sasl>
|
||||
<tls>
|
||||
<key-stores>
|
||||
<key-store name="applicationKS">
|
||||
<credential-reference clear-text="password"/>
|
||||
<implementation type="JKS"/>
|
||||
<file path="application.keystore" relative-to="jboss.server.config.dir"/>
|
||||
</key-store>
|
||||
</key-stores>
|
||||
<key-managers>
|
||||
<key-manager name="applicationKM" key-store="applicationKS" generate-self-signed-certificate-host="localhost">
|
||||
<credential-reference clear-text="password"/>
|
||||
</key-manager>
|
||||
</key-managers>
|
||||
<server-ssl-contexts>
|
||||
<server-ssl-context name="applicationSSC" key-manager="applicationKM"/>
|
||||
</server-ssl-contexts>
|
||||
</tls>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:wildfly:health:1.0" security-enabled="false"/>
|
||||
<subsystem xmlns="urn:jboss:domain:infinispan:12.0">
|
||||
<cache-container name="ejb" default-cache="dist" aliases="sfsb" modules="org.wildfly.clustering.ejb.infinispan">
|
||||
<transport lock-timeout="60000"/>
|
||||
<distributed-cache name="dist">
|
||||
<locking isolation="REPEATABLE_READ"/>
|
||||
<transaction mode="BATCH"/>
|
||||
<file-store/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
<cache-container name="keycloak" modules="org.keycloak.keycloak-model-infinispan">
|
||||
<transport lock-timeout="60000"/>
|
||||
<local-cache name="realms">
|
||||
<heap-memory size="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="users">
|
||||
<heap-memory size="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="authorization">
|
||||
<heap-memory size="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="keys">
|
||||
<heap-memory size="1000"/>
|
||||
<expiration max-idle="3600000"/>
|
||||
</local-cache>
|
||||
<replicated-cache name="work">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</replicated-cache>
|
||||
<distributed-cache name="sessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="authenticationSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="offlineSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="clientSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="offlineClientSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="loginFailures" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="actionTokens" owners="2">
|
||||
<heap-memory size="-1"/>
|
||||
<expiration interval="300000" lifespan="900000000000000000" max-idle="-1"/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
<cache-container name="server" default-cache="default" aliases="singleton cluster" modules="org.wildfly.clustering.server">
|
||||
<transport lock-timeout="60000"/>
|
||||
<replicated-cache name="default">
|
||||
<transaction mode="BATCH"/>
|
||||
</replicated-cache>
|
||||
</cache-container>
|
||||
<cache-container name="web" default-cache="dist" modules="org.wildfly.clustering.web.infinispan">
|
||||
<transport lock-timeout="60000"/>
|
||||
<replicated-cache name="sso">
|
||||
<locking isolation="REPEATABLE_READ"/>
|
||||
<transaction mode="BATCH"/>
|
||||
</replicated-cache>
|
||||
<distributed-cache name="dist">
|
||||
<locking isolation="REPEATABLE_READ"/>
|
||||
<transaction mode="BATCH"/>
|
||||
<file-store/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="routing"/>
|
||||
</cache-container>
|
||||
<cache-container name="hibernate" modules="org.infinispan.hibernate-cache">
|
||||
<transport lock-timeout="60000"/>
|
||||
<local-cache name="local-query">
|
||||
<heap-memory size="10000"/>
|
||||
<expiration max-idle="100000"/>
|
||||
</local-cache>
|
||||
<invalidation-cache name="entity">
|
||||
<transaction mode="NON_XA"/>
|
||||
<heap-memory size="10000"/>
|
||||
<expiration max-idle="100000"/>
|
||||
</invalidation-cache>
|
||||
<replicated-cache name="timestamps"/>
|
||||
</cache-container>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:io:3.0">
|
||||
<worker name="default"/>
|
||||
<buffer-pool name="default"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jaxrs:2.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:jca:5.0">
|
||||
<archive-validation enabled="true" fail-on-error="true" fail-on-warn="false"/>
|
||||
<bean-validation enabled="true"/>
|
||||
<default-workmanager>
|
||||
<short-running-threads>
|
||||
<core-threads count="50"/>
|
||||
<queue-length count="50"/>
|
||||
<max-threads count="50"/>
|
||||
<keepalive-time time="10" unit="seconds"/>
|
||||
</short-running-threads>
|
||||
<long-running-threads>
|
||||
<core-threads count="50"/>
|
||||
<queue-length count="50"/>
|
||||
<max-threads count="50"/>
|
||||
<keepalive-time time="10" unit="seconds"/>
|
||||
</long-running-threads>
|
||||
</default-workmanager>
|
||||
<cached-connection-manager/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jgroups:8.0">
|
||||
<channels default="ee">
|
||||
<channel name="ee" stack="udp" cluster="ejb"/>
|
||||
</channels>
|
||||
<stacks>
|
||||
<stack name="udp">
|
||||
<transport type="UDP" socket-binding="jgroups-udp"/>
|
||||
<protocol type="PING"/>
|
||||
<protocol type="MERGE3"/>
|
||||
<socket-protocol type="FD_SOCK" socket-binding="jgroups-udp-fd"/>
|
||||
<protocol type="FD_ALL"/>
|
||||
<protocol type="VERIFY_SUSPECT"/>
|
||||
<protocol type="pbcast.NAKACK2"/>
|
||||
<protocol type="UNICAST3"/>
|
||||
<protocol type="pbcast.STABLE"/>
|
||||
<protocol type="pbcast.GMS"/>
|
||||
<protocol type="UFC"/>
|
||||
<protocol type="MFC"/>
|
||||
<protocol type="FRAG3"/>
|
||||
</stack>
|
||||
<stack name="tcp">
|
||||
<transport type="TCP" socket-binding="jgroups-tcp"/>
|
||||
<socket-protocol type="MPING" socket-binding="jgroups-mping"/>
|
||||
<protocol type="MERGE3"/>
|
||||
<socket-protocol type="FD_SOCK" socket-binding="jgroups-tcp-fd"/>
|
||||
<protocol type="FD_ALL"/>
|
||||
<protocol type="VERIFY_SUSPECT"/>
|
||||
<protocol type="pbcast.NAKACK2"/>
|
||||
<protocol type="UNICAST3"/>
|
||||
<protocol type="pbcast.STABLE"/>
|
||||
<protocol type="pbcast.GMS"/>
|
||||
<protocol type="MFC"/>
|
||||
<protocol type="FRAG3"/>
|
||||
</stack>
|
||||
</stacks>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jmx:1.3">
|
||||
<expose-resolved-model/>
|
||||
<expose-expression-model/>
|
||||
<remoting-connector/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jpa:1.1">
|
||||
<jpa default-extended-persistence-inheritance="DEEP"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
|
||||
<web-context>auth</web-context>
|
||||
<providers>
|
||||
<provider>
|
||||
classpath:${jboss.home.dir}/providers/*
|
||||
</provider>
|
||||
</providers>
|
||||
<master-realm-name>master</master-realm-name>
|
||||
<scheduled-task-interval>900</scheduled-task-interval>
|
||||
<theme>
|
||||
<staticMaxAge>2592000</staticMaxAge>
|
||||
<cacheThemes>true</cacheThemes>
|
||||
<cacheTemplates>true</cacheTemplates>
|
||||
<welcomeTheme>${env.KEYCLOAK_WELCOME_THEME:keycloak}</welcomeTheme>
|
||||
<default>${env.KEYCLOAK_DEFAULT_THEME:keycloak}</default>
|
||||
<dir>${jboss.home.dir}/themes</dir>
|
||||
</theme>
|
||||
<spi name="eventsStore">
|
||||
<provider name="jpa" enabled="true">
|
||||
<properties>
|
||||
<property name="exclude-events" value="["REFRESH_TOKEN"]"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="userCache">
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="userSessionPersister">
|
||||
<default-provider>jpa</default-provider>
|
||||
</spi>
|
||||
<spi name="timer">
|
||||
<default-provider>basic</default-provider>
|
||||
</spi>
|
||||
<spi name="connectionsHttpClient">
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="connectionsJpa">
|
||||
<provider name="default" enabled="true">
|
||||
<properties>
|
||||
<property name="dataSource" value="java:jboss/datasources/KeycloakDS"/>
|
||||
<property name="initializeEmpty" value="true"/>
|
||||
<property name="migrationStrategy" value="update"/>
|
||||
<property name="migrationExport" value="${jboss.home.dir}/keycloak-database-update.sql"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="realmCache">
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="connectionsInfinispan">
|
||||
<default-provider>default</default-provider>
|
||||
<provider name="default" enabled="true">
|
||||
<properties>
|
||||
<property name="cacheContainer" value="java:jboss/infinispan/container/keycloak"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="jta-lookup">
|
||||
<default-provider>${keycloak.jta.lookup.provider:jboss}</default-provider>
|
||||
<provider name="jboss" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="publicKeyStorage">
|
||||
<provider name="infinispan" enabled="true">
|
||||
<properties>
|
||||
<property name="minTimeBetweenRequests" value="10"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="x509cert-lookup">
|
||||
<default-provider>${keycloak.x509cert.lookup.provider:default}</default-provider>
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="hostname">
|
||||
<default-provider>${keycloak.hostname.provider:default}</default-provider>
|
||||
<provider name="default" enabled="true">
|
||||
<properties>
|
||||
<property name="frontendUrl" value="${keycloak.frontendUrl:}"/>
|
||||
<property name="forceBackendUrlToFrontendUrl" value="false"/>
|
||||
</properties>
|
||||
</provider>
|
||||
<provider name="fixed" enabled="true">
|
||||
<properties>
|
||||
<property name="hostname" value="${keycloak.hostname.fixed.hostname:localhost}"/>
|
||||
<property name="httpPort" value="${keycloak.hostname.fixed.httpPort:-1}"/>
|
||||
<property name="httpsPort" value="${keycloak.hostname.fixed.httpsPort:-1}"/>
|
||||
<property name="alwaysHttps" value="${keycloak.hostname.fixed.alwaysHttps:false}"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:mail:4.0">
|
||||
<mail-session name="default" jndi-name="java:jboss/mail/Default">
|
||||
<smtp-server outbound-socket-binding-ref="mail-smtp"/>
|
||||
</mail-session>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:wildfly:metrics:1.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:wildfly}"/>
|
||||
<subsystem xmlns="urn:jboss:domain:modcluster:5.0">
|
||||
<proxy name="default" advertise-socket="modcluster" listener="ajp">
|
||||
<dynamic-load-provider>
|
||||
<load-metric type="cpu"/>
|
||||
</dynamic-load-provider>
|
||||
</proxy>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:naming:2.0">
|
||||
<remote-naming/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:remoting:4.0">
|
||||
<http-connector name="http-remoting-connector" connector-ref="default" security-realm="ApplicationRealm"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:request-controller:1.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:security:2.0">
|
||||
<security-domains>
|
||||
<security-domain name="other" cache-type="default">
|
||||
<authentication>
|
||||
<login-module code="Remoting" flag="optional">
|
||||
<module-option name="password-stacking" value="useFirstPass"/>
|
||||
</login-module>
|
||||
<login-module code="RealmDirect" flag="required">
|
||||
<module-option name="password-stacking" value="useFirstPass"/>
|
||||
</login-module>
|
||||
</authentication>
|
||||
</security-domain>
|
||||
<security-domain name="jboss-web-policy" cache-type="default">
|
||||
<authorization>
|
||||
<policy-module code="Delegating" flag="required"/>
|
||||
</authorization>
|
||||
</security-domain>
|
||||
<security-domain name="jaspitest" cache-type="default">
|
||||
<authentication-jaspi>
|
||||
<login-module-stack name="dummy">
|
||||
<login-module code="Dummy" flag="optional"/>
|
||||
</login-module-stack>
|
||||
<auth-module code="Dummy"/>
|
||||
</authentication-jaspi>
|
||||
</security-domain>
|
||||
<security-domain name="jboss-ejb-policy" cache-type="default">
|
||||
<authorization>
|
||||
<policy-module code="Delegating" flag="required"/>
|
||||
</authorization>
|
||||
</security-domain>
|
||||
</security-domains>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:security-manager:1.0">
|
||||
<deployment-permissions>
|
||||
<maximum-set>
|
||||
<permission class="java.security.AllPermission"/>
|
||||
</maximum-set>
|
||||
</deployment-permissions>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:transactions:6.0">
|
||||
<core-environment node-identifier="${jboss.tx.node.id:1}">
|
||||
<process-id>
|
||||
<uuid/>
|
||||
</process-id>
|
||||
</core-environment>
|
||||
<recovery-environment socket-binding="txn-recovery-environment" status-socket-binding="txn-status-manager"/>
|
||||
<coordinator-environment statistics-enabled="${wildfly.transactions.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
|
||||
<object-store path="tx-object-store" relative-to="jboss.server.data.dir"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:undertow:12.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other" statistics-enabled="${wildfly.undertow.statistics-enabled:${wildfly.statistics-enabled:false}}">
|
||||
<buffer-cache name="default"/>
|
||||
<server name="default-server">
|
||||
<ajp-listener name="ajp" socket-binding="ajp"/>
|
||||
<http-listener name="default" socket-binding="http" redirect-socket="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" enable-http2="true"/>
|
||||
<https-listener name="https" socket-binding="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" security-realm="ApplicationRealm" enable-http2="true"/>
|
||||
<host name="default-host" alias="localhost">
|
||||
<location name="/" handler="welcome-content"/>
|
||||
<http-invoker security-realm="ApplicationRealm"/>
|
||||
</host>
|
||||
</server>
|
||||
<servlet-container name="default">
|
||||
<jsp-config/>
|
||||
<websockets/>
|
||||
</servlet-container>
|
||||
<handlers>
|
||||
<file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
|
||||
</handlers>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:weld:4.0"/>
|
||||
</profile>
|
||||
<interfaces>
|
||||
<interface name="management">
|
||||
<inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
|
||||
</interface>
|
||||
<interface name="private">
|
||||
<inet-address value="${jboss.bind.address.private:127.0.0.1}"/>
|
||||
</interface>
|
||||
<interface name="public">
|
||||
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
|
||||
</interface>
|
||||
</interfaces>
|
||||
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
|
||||
<socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
|
||||
<socket-binding name="http" port="${jboss.http.port:8080}"/>
|
||||
<socket-binding name="https" port="${jboss.https.port:8443}"/>
|
||||
<socket-binding name="jgroups-mping" interface="private" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45700"/>
|
||||
<socket-binding name="jgroups-tcp" interface="private" port="7600"/>
|
||||
<socket-binding name="jgroups-tcp-fd" interface="private" port="57600"/>
|
||||
<socket-binding name="jgroups-udp" interface="private" port="55200" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45688"/>
|
||||
<socket-binding name="jgroups-udp-fd" interface="private" port="54200"/>
|
||||
<socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
|
||||
<socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
|
||||
<socket-binding name="modcluster" multicast-address="${jboss.modcluster.multicast.address:224.0.1.105}" multicast-port="23364"/>
|
||||
<socket-binding name="txn-recovery-environment" port="4712"/>
|
||||
<socket-binding name="txn-status-manager" port="4713"/>
|
||||
<outbound-socket-binding name="mail-smtp">
|
||||
<remote-destination host="${jboss.mail.server.host:localhost}" port="${jboss.mail.server.port:25}"/>
|
||||
</outbound-socket-binding>
|
||||
</socket-binding-group>
|
||||
</server>
|
@ -1,652 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<server xmlns="urn:jboss:domain:19.0">
|
||||
<extensions>
|
||||
<extension module="org.jboss.as.clustering.infinispan"/>
|
||||
<extension module="org.jboss.as.clustering.jgroups"/>
|
||||
<extension module="org.jboss.as.connector"/>
|
||||
<extension module="org.jboss.as.deployment-scanner"/>
|
||||
<extension module="org.jboss.as.ee"/>
|
||||
<extension module="org.jboss.as.ejb3"/>
|
||||
<extension module="org.jboss.as.jaxrs"/>
|
||||
<extension module="org.jboss.as.jmx"/>
|
||||
<extension module="org.jboss.as.jpa"/>
|
||||
<extension module="org.jboss.as.logging"/>
|
||||
<extension module="org.jboss.as.mail"/>
|
||||
<extension module="org.jboss.as.modcluster"/>
|
||||
<extension module="org.jboss.as.naming"/>
|
||||
<extension module="org.jboss.as.remoting"/>
|
||||
<extension module="org.jboss.as.transactions"/>
|
||||
<extension module="org.jboss.as.weld"/>
|
||||
<extension module="org.keycloak.keycloak-server-subsystem"/>
|
||||
<extension module="org.wildfly.extension.bean-validation"/>
|
||||
<extension module="org.wildfly.extension.core-management"/>
|
||||
<extension module="org.wildfly.extension.elytron"/>
|
||||
<extension module="org.wildfly.extension.health"/>
|
||||
<extension module="org.wildfly.extension.io"/>
|
||||
<extension module="org.wildfly.extension.metrics"/>
|
||||
<extension module="org.wildfly.extension.request-controller"/>
|
||||
<extension module="org.wildfly.extension.security.manager"/>
|
||||
<extension module="org.wildfly.extension.undertow"/>
|
||||
</extensions>
|
||||
<management>
|
||||
<audit-log>
|
||||
<formatters>
|
||||
<json-formatter name="json-formatter"/>
|
||||
</formatters>
|
||||
<handlers>
|
||||
<file-handler name="file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
|
||||
</handlers>
|
||||
<logger log-boot="true" log-read-only="false" enabled="false">
|
||||
<handlers>
|
||||
<handler name="file"/>
|
||||
</handlers>
|
||||
</logger>
|
||||
</audit-log>
|
||||
<management-interfaces>
|
||||
<http-interface http-authentication-factory="management-http-authentication">
|
||||
<http-upgrade enabled="true" sasl-authentication-factory="management-sasl-authentication"/>
|
||||
<socket-binding http="management-http"/>
|
||||
</http-interface>
|
||||
</management-interfaces>
|
||||
<access-control provider="simple">
|
||||
<role-mapping>
|
||||
<role name="SuperUser">
|
||||
<include>
|
||||
<user name="$local"/>
|
||||
</include>
|
||||
</role>
|
||||
</role-mapping>
|
||||
</access-control>
|
||||
</management>
|
||||
<profile>
|
||||
<subsystem xmlns="urn:jboss:domain:logging:8.0">
|
||||
<console-handler name="CONSOLE">
|
||||
<formatter>
|
||||
<named-formatter name="COLOR-PATTERN"/>
|
||||
</formatter>
|
||||
</console-handler>
|
||||
<logger category="com.arjuna">
|
||||
<level name="WARN"/>
|
||||
</logger>
|
||||
<logger category="io.jaegertracing.Configuration">
|
||||
<level name="WARN"/>
|
||||
</logger>
|
||||
<logger category="org.jboss.as.config">
|
||||
<level name="DEBUG"/>
|
||||
</logger>
|
||||
<logger category="sun.rmi">
|
||||
<level name="WARN"/>
|
||||
</logger>
|
||||
<logger category="org.keycloak">
|
||||
<level name="${env.KEYCLOAK_LOGLEVEL:INFO}"/>
|
||||
</logger>
|
||||
<root-logger>
|
||||
<level name="${env.ROOT_LOGLEVEL:INFO}"/>
|
||||
<handlers>
|
||||
<handler name="CONSOLE"/>
|
||||
</handlers>
|
||||
</root-logger>
|
||||
<formatter name="PATTERN">
|
||||
<pattern-formatter pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
|
||||
</formatter>
|
||||
<formatter name="COLOR-PATTERN">
|
||||
<pattern-formatter pattern="%K{level}%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
|
||||
</formatter>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:bean-validation:1.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:core-management:1.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:datasources:6.0">
|
||||
<datasources>
|
||||
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
|
||||
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
|
||||
<driver>h2</driver>
|
||||
<security>
|
||||
<user-name>sa</user-name>
|
||||
<password>sa</password>
|
||||
</security>
|
||||
</datasource>
|
||||
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
|
||||
<connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
|
||||
<driver>h2</driver>
|
||||
<security>
|
||||
<user-name>sa</user-name>
|
||||
<password>sa</password>
|
||||
</security>
|
||||
</datasource>
|
||||
<drivers>
|
||||
<driver name="h2" module="com.h2database.h2">
|
||||
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
|
||||
</driver>
|
||||
</drivers>
|
||||
</datasources>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:deployment-scanner:2.0">
|
||||
<deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" runtime-failure-causes-rollback="${jboss.deployment.scanner.rollback.on.failure:false}"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:ee:6.0">
|
||||
<spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
|
||||
<concurrent>
|
||||
<context-services>
|
||||
<context-service name="default" jndi-name="java:jboss/ee/concurrency/context/default" use-transaction-setup-provider="true"/>
|
||||
</context-services>
|
||||
<managed-thread-factories>
|
||||
<managed-thread-factory name="default" jndi-name="java:jboss/ee/concurrency/factory/default" context-service="default"/>
|
||||
</managed-thread-factories>
|
||||
<managed-executor-services>
|
||||
<managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default" context-service="default" hung-task-termination-period="0" hung-task-threshold="60000" keepalive-time="5000"/>
|
||||
</managed-executor-services>
|
||||
<managed-scheduled-executor-services>
|
||||
<managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" context-service="default" hung-task-termination-period="0" hung-task-threshold="60000" keepalive-time="3000"/>
|
||||
</managed-scheduled-executor-services>
|
||||
</concurrent>
|
||||
<default-bindings context-service="java:jboss/ee/concurrency/context/default" datasource="java:jboss/datasources/ExampleDS" managed-executor-service="java:jboss/ee/concurrency/executor/default" managed-scheduled-executor-service="java:jboss/ee/concurrency/scheduler/default" managed-thread-factory="java:jboss/ee/concurrency/factory/default"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:ejb3:9.0">
|
||||
<session-bean>
|
||||
<stateless>
|
||||
<bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
|
||||
</stateless>
|
||||
<stateful default-access-timeout="5000" cache-ref="distributable" passivation-disabled-cache-ref="simple"/>
|
||||
<singleton default-access-timeout="5000"/>
|
||||
</session-bean>
|
||||
<pools>
|
||||
<bean-instance-pools>
|
||||
<strict-max-pool name="mdb-strict-max-pool" derive-size="from-cpu-count" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
|
||||
<strict-max-pool name="slsb-strict-max-pool" derive-size="from-worker-pools" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
|
||||
</bean-instance-pools>
|
||||
</pools>
|
||||
<caches>
|
||||
<cache name="simple"/>
|
||||
<cache name="distributable" passivation-store-ref="infinispan" aliases="passivating clustered"/>
|
||||
</caches>
|
||||
<passivation-stores>
|
||||
<passivation-store name="infinispan" cache-container="ejb" max-size="10000"/>
|
||||
</passivation-stores>
|
||||
<async thread-pool-name="default"/>
|
||||
<timer-service thread-pool-name="default" default-data-store="default-file-store">
|
||||
<data-stores>
|
||||
<file-data-store name="default-file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
|
||||
</data-stores>
|
||||
</timer-service>
|
||||
<remote cluster="ejb" connectors="http-remoting-connector" thread-pool-name="default">
|
||||
<channel-creation-options>
|
||||
<option name="MAX_OUTBOUND_MESSAGES" value="1234" type="remoting"/>
|
||||
</channel-creation-options>
|
||||
</remote>
|
||||
<thread-pools>
|
||||
<thread-pool name="default">
|
||||
<max-threads count="10"/>
|
||||
<keepalive-time time="60" unit="seconds"/>
|
||||
</thread-pool>
|
||||
</thread-pools>
|
||||
<default-security-domain value="other"/>
|
||||
<application-security-domains>
|
||||
<application-security-domain name="other" security-domain="ApplicationDomain"/>
|
||||
</application-security-domains>
|
||||
<default-missing-method-permissions-deny-access value="true"/>
|
||||
<statistics enabled="${wildfly.ejb3.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
|
||||
<log-system-exceptions value="true"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:wildfly:elytron:15.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
|
||||
<providers>
|
||||
<aggregate-providers name="combined-providers">
|
||||
<providers name="elytron"/>
|
||||
<providers name="openssl"/>
|
||||
</aggregate-providers>
|
||||
<provider-loader name="elytron" module="org.wildfly.security.elytron"/>
|
||||
<provider-loader name="openssl" module="org.wildfly.openssl"/>
|
||||
</providers>
|
||||
<audit-logging>
|
||||
<file-audit-log name="local-audit" path="audit.log" relative-to="jboss.server.log.dir" format="JSON"/>
|
||||
</audit-logging>
|
||||
<security-domains>
|
||||
<security-domain name="ManagementDomain" default-realm="ManagementRealm" permission-mapper="default-permission-mapper">
|
||||
<realm name="ManagementRealm" role-decoder="groups-to-roles"/>
|
||||
<realm name="local" role-mapper="super-user-mapper"/>
|
||||
</security-domain>
|
||||
<security-domain name="ApplicationDomain" default-realm="ApplicationRealm" permission-mapper="default-permission-mapper">
|
||||
<realm name="ApplicationRealm" role-decoder="groups-to-roles"/>
|
||||
<realm name="local"/>
|
||||
</security-domain>
|
||||
</security-domains>
|
||||
<security-realms>
|
||||
<identity-realm name="local" identity="$local"/>
|
||||
<properties-realm name="ApplicationRealm">
|
||||
<users-properties path="application-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ApplicationRealm"/>
|
||||
<groups-properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
|
||||
</properties-realm>
|
||||
<properties-realm name="ManagementRealm">
|
||||
<users-properties path="mgmt-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ManagementRealm"/>
|
||||
<groups-properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
|
||||
</properties-realm>
|
||||
</security-realms>
|
||||
<mappers>
|
||||
<simple-permission-mapper name="default-permission-mapper" mapping-mode="first">
|
||||
<permission-mapping>
|
||||
<principal name="anonymous"/>
|
||||
<permission-set name="default-permissions"/>
|
||||
</permission-mapping>
|
||||
<permission-mapping match-all="true">
|
||||
<permission-set name="login-permission"/>
|
||||
<permission-set name="default-permissions"/>
|
||||
</permission-mapping>
|
||||
</simple-permission-mapper>
|
||||
<constant-realm-mapper name="local" realm-name="local"/>
|
||||
<simple-role-decoder name="groups-to-roles" attribute="groups"/>
|
||||
<constant-role-mapper name="super-user-mapper">
|
||||
<role name="SuperUser"/>
|
||||
</constant-role-mapper>
|
||||
</mappers>
|
||||
<permission-sets>
|
||||
<permission-set name="login-permission">
|
||||
<permission class-name="org.wildfly.security.auth.permission.LoginPermission"/>
|
||||
</permission-set>
|
||||
<permission-set name="default-permissions">
|
||||
<permission class-name="org.wildfly.extension.batch.jberet.deployment.BatchPermission" module="org.wildfly.extension.batch.jberet" target-name="*"/>
|
||||
<permission class-name="org.wildfly.transaction.client.RemoteTransactionPermission" module="org.wildfly.transaction.client"/>
|
||||
<permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
|
||||
<permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
|
||||
</permission-set>
|
||||
</permission-sets>
|
||||
<http>
|
||||
<http-authentication-factory name="management-http-authentication" security-domain="ManagementDomain" http-server-mechanism-factory="global">
|
||||
<mechanism-configuration>
|
||||
<mechanism mechanism-name="DIGEST">
|
||||
<mechanism-realm realm-name="ManagementRealm"/>
|
||||
</mechanism>
|
||||
</mechanism-configuration>
|
||||
</http-authentication-factory>
|
||||
<http-authentication-factory name="application-http-authentication" security-domain="ApplicationDomain" http-server-mechanism-factory="global">
|
||||
<mechanism-configuration>
|
||||
<mechanism mechanism-name="BASIC">
|
||||
<mechanism-realm realm-name="ApplicationRealm"/>
|
||||
</mechanism>
|
||||
</mechanism-configuration>
|
||||
</http-authentication-factory>
|
||||
<provider-http-server-mechanism-factory name="global"/>
|
||||
</http>
|
||||
<sasl>
|
||||
<sasl-authentication-factory name="management-sasl-authentication" sasl-server-factory="configured" security-domain="ManagementDomain">
|
||||
<mechanism-configuration>
|
||||
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
|
||||
<mechanism mechanism-name="DIGEST-MD5">
|
||||
<mechanism-realm realm-name="ManagementRealm"/>
|
||||
</mechanism>
|
||||
</mechanism-configuration>
|
||||
</sasl-authentication-factory>
|
||||
<sasl-authentication-factory name="application-sasl-authentication" sasl-server-factory="configured" security-domain="ApplicationDomain">
|
||||
<mechanism-configuration>
|
||||
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
|
||||
<mechanism mechanism-name="DIGEST-MD5">
|
||||
<mechanism-realm realm-name="ApplicationRealm"/>
|
||||
</mechanism>
|
||||
</mechanism-configuration>
|
||||
</sasl-authentication-factory>
|
||||
<configurable-sasl-server-factory name="configured" sasl-server-factory="elytron">
|
||||
<properties>
|
||||
<property name="wildfly.sasl.local-user.default-user" value="$local"/>
|
||||
<property name="wildfly.sasl.local-user.challenge-path" value="${jboss.server.temp.dir}/auth"/>
|
||||
</properties>
|
||||
</configurable-sasl-server-factory>
|
||||
<mechanism-provider-filtering-sasl-server-factory name="elytron" sasl-server-factory="global">
|
||||
<filters>
|
||||
<filter provider-name="WildFlyElytron"/>
|
||||
</filters>
|
||||
</mechanism-provider-filtering-sasl-server-factory>
|
||||
<provider-sasl-server-factory name="global"/>
|
||||
</sasl>
|
||||
<tls>
|
||||
<key-stores>
|
||||
<key-store name="applicationKS">
|
||||
<credential-reference clear-text="password"/>
|
||||
<implementation type="JKS"/>
|
||||
<file path="application.keystore" relative-to="jboss.server.config.dir"/>
|
||||
</key-store>
|
||||
</key-stores>
|
||||
<key-managers>
|
||||
<key-manager name="applicationKM" key-store="applicationKS" generate-self-signed-certificate-host="localhost">
|
||||
<credential-reference clear-text="password"/>
|
||||
</key-manager>
|
||||
</key-managers>
|
||||
<server-ssl-contexts>
|
||||
<server-ssl-context name="applicationSSC" key-manager="applicationKM"/>
|
||||
</server-ssl-contexts>
|
||||
</tls>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:wildfly:health:1.0" security-enabled="false"/>
|
||||
<subsystem xmlns="urn:jboss:domain:infinispan:13.0">
|
||||
<cache-container name="ejb" default-cache="dist" marshaller="PROTOSTREAM" aliases="sfsb" modules="org.wildfly.clustering.ejb.infinispan">
|
||||
<transport lock-timeout="60000"/>
|
||||
<distributed-cache name="dist">
|
||||
<locking isolation="REPEATABLE_READ"/>
|
||||
<transaction mode="BATCH"/>
|
||||
<file-store/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
<cache-container name="keycloak" marshaller="JBOSS" modules="org.keycloak.keycloak-model-infinispan">
|
||||
<transport lock-timeout="60000"/>
|
||||
<local-cache name="realms">
|
||||
<heap-memory size="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="users">
|
||||
<heap-memory size="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="authorization">
|
||||
<heap-memory size="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="keys">
|
||||
<heap-memory size="1000"/>
|
||||
<expiration max-idle="3600000"/>
|
||||
</local-cache>
|
||||
<replicated-cache name="work">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</replicated-cache>
|
||||
<distributed-cache name="sessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="authenticationSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="offlineSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="clientSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="offlineClientSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="loginFailures" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="actionTokens" owners="2">
|
||||
<heap-memory size="-1"/>
|
||||
<expiration interval="300000" lifespan="900000000000000000" max-idle="-1"/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
<cache-container name="server" default-cache="default" marshaller="PROTOSTREAM" aliases="singleton cluster" modules="org.wildfly.clustering.server">
|
||||
<transport lock-timeout="60000"/>
|
||||
<replicated-cache name="default">
|
||||
<transaction mode="BATCH"/>
|
||||
</replicated-cache>
|
||||
</cache-container>
|
||||
<cache-container name="web" default-cache="dist" marshaller="PROTOSTREAM" modules="org.wildfly.clustering.web.infinispan">
|
||||
<transport lock-timeout="60000"/>
|
||||
<replicated-cache name="sso">
|
||||
<locking isolation="REPEATABLE_READ"/>
|
||||
<transaction mode="BATCH"/>
|
||||
</replicated-cache>
|
||||
<distributed-cache name="dist">
|
||||
<locking isolation="REPEATABLE_READ"/>
|
||||
<transaction mode="BATCH"/>
|
||||
<file-store/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="routing"/>
|
||||
</cache-container>
|
||||
<cache-container name="hibernate" marshaller="JBOSS" modules="org.infinispan.hibernate-cache">
|
||||
<transport lock-timeout="60000"/>
|
||||
<local-cache name="local-query">
|
||||
<heap-memory size="10000"/>
|
||||
<expiration max-idle="100000"/>
|
||||
</local-cache>
|
||||
<local-cache name="pending-puts">
|
||||
<expiration max-idle="60000"/>
|
||||
</local-cache>
|
||||
<invalidation-cache name="entity">
|
||||
<transaction mode="NON_XA"/>
|
||||
<heap-memory size="10000"/>
|
||||
<expiration max-idle="100000"/>
|
||||
</invalidation-cache>
|
||||
<replicated-cache name="timestamps"/>
|
||||
</cache-container>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:io:3.0">
|
||||
<worker name="default"/>
|
||||
<buffer-pool name="default"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jaxrs:2.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:jca:5.0">
|
||||
<archive-validation enabled="true" fail-on-error="true" fail-on-warn="false"/>
|
||||
<bean-validation enabled="true"/>
|
||||
<default-workmanager>
|
||||
<short-running-threads>
|
||||
<core-threads count="50"/>
|
||||
<queue-length count="50"/>
|
||||
<max-threads count="50"/>
|
||||
<keepalive-time time="10" unit="seconds"/>
|
||||
</short-running-threads>
|
||||
<long-running-threads>
|
||||
<core-threads count="50"/>
|
||||
<queue-length count="50"/>
|
||||
<max-threads count="50"/>
|
||||
<keepalive-time time="10" unit="seconds"/>
|
||||
</long-running-threads>
|
||||
</default-workmanager>
|
||||
<cached-connection-manager/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jgroups:8.0">
|
||||
<channels default="ee">
|
||||
<channel name="ee" stack="udp" cluster="ejb"/>
|
||||
</channels>
|
||||
<stacks>
|
||||
<stack name="udp">
|
||||
<transport type="UDP" socket-binding="jgroups-udp"/>
|
||||
<protocol type="PING"/>
|
||||
<protocol type="MERGE3"/>
|
||||
<socket-protocol type="FD_SOCK" socket-binding="jgroups-udp-fd"/>
|
||||
<protocol type="FD_ALL"/>
|
||||
<protocol type="VERIFY_SUSPECT"/>
|
||||
<protocol type="pbcast.NAKACK2"/>
|
||||
<protocol type="UNICAST3"/>
|
||||
<protocol type="pbcast.STABLE"/>
|
||||
<protocol type="pbcast.GMS"/>
|
||||
<protocol type="UFC"/>
|
||||
<protocol type="MFC"/>
|
||||
<protocol type="FRAG3"/>
|
||||
</stack>
|
||||
<stack name="tcp">
|
||||
<transport type="TCP" socket-binding="jgroups-tcp"/>
|
||||
<socket-protocol type="MPING" socket-binding="jgroups-mping"/>
|
||||
<protocol type="MERGE3"/>
|
||||
<socket-protocol type="FD_SOCK" socket-binding="jgroups-tcp-fd"/>
|
||||
<protocol type="FD_ALL"/>
|
||||
<protocol type="VERIFY_SUSPECT"/>
|
||||
<protocol type="pbcast.NAKACK2"/>
|
||||
<protocol type="UNICAST3"/>
|
||||
<protocol type="pbcast.STABLE"/>
|
||||
<protocol type="pbcast.GMS"/>
|
||||
<protocol type="MFC"/>
|
||||
<protocol type="FRAG3"/>
|
||||
</stack>
|
||||
</stacks>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jmx:1.3">
|
||||
<expose-resolved-model/>
|
||||
<expose-expression-model/>
|
||||
<remoting-connector/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:jpa:1.1">
|
||||
<jpa default-extended-persistence-inheritance="DEEP"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
|
||||
<web-context>auth</web-context>
|
||||
<providers>
|
||||
<provider>
|
||||
classpath:${jboss.home.dir}/providers/*
|
||||
</provider>
|
||||
</providers>
|
||||
<master-realm-name>master</master-realm-name>
|
||||
<scheduled-task-interval>900</scheduled-task-interval>
|
||||
<theme>
|
||||
<staticMaxAge>2592000</staticMaxAge>
|
||||
<cacheThemes>true</cacheThemes>
|
||||
<cacheTemplates>true</cacheTemplates>
|
||||
<welcomeTheme>${env.KEYCLOAK_WELCOME_THEME:keycloak}</welcomeTheme>
|
||||
<default>${env.KEYCLOAK_DEFAULT_THEME:keycloak}</default>
|
||||
<dir>${jboss.home.dir}/themes</dir>
|
||||
</theme>
|
||||
<spi name="eventsStore">
|
||||
<provider name="jpa" enabled="true">
|
||||
<properties>
|
||||
<property name="exclude-events" value="["REFRESH_TOKEN"]"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="userCache">
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="userSessionPersister">
|
||||
<default-provider>jpa</default-provider>
|
||||
</spi>
|
||||
<spi name="timer">
|
||||
<default-provider>basic</default-provider>
|
||||
</spi>
|
||||
<spi name="connectionsHttpClient">
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="connectionsJpa">
|
||||
<provider name="default" enabled="true">
|
||||
<properties>
|
||||
<property name="dataSource" value="java:jboss/datasources/KeycloakDS"/>
|
||||
<property name="initializeEmpty" value="true"/>
|
||||
<property name="migrationStrategy" value="update"/>
|
||||
<property name="migrationExport" value="${jboss.home.dir}/keycloak-database-update.sql"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="realmCache">
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="connectionsInfinispan">
|
||||
<default-provider>default</default-provider>
|
||||
<provider name="default" enabled="true">
|
||||
<properties>
|
||||
<property name="cacheContainer" value="java:jboss/infinispan/container/keycloak"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="jta-lookup">
|
||||
<default-provider>${keycloak.jta.lookup.provider:jboss}</default-provider>
|
||||
<provider name="jboss" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="publicKeyStorage">
|
||||
<provider name="infinispan" enabled="true">
|
||||
<properties>
|
||||
<property name="minTimeBetweenRequests" value="10"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
<spi name="x509cert-lookup">
|
||||
<default-provider>${keycloak.x509cert.lookup.provider:default}</default-provider>
|
||||
<provider name="default" enabled="true"/>
|
||||
</spi>
|
||||
<spi name="hostname">
|
||||
<default-provider>${keycloak.hostname.provider:default}</default-provider>
|
||||
<provider name="default" enabled="true">
|
||||
<properties>
|
||||
<property name="frontendUrl" value="${keycloak.frontendUrl:}"/>
|
||||
<property name="forceBackendUrlToFrontendUrl" value="false"/>
|
||||
</properties>
|
||||
</provider>
|
||||
<provider name="fixed" enabled="true">
|
||||
<properties>
|
||||
<property name="hostname" value="${keycloak.hostname.fixed.hostname:localhost}"/>
|
||||
<property name="httpPort" value="${keycloak.hostname.fixed.httpPort:-1}"/>
|
||||
<property name="httpsPort" value="${keycloak.hostname.fixed.httpsPort:-1}"/>
|
||||
<property name="alwaysHttps" value="${keycloak.hostname.fixed.alwaysHttps:false}"/>
|
||||
</properties>
|
||||
</provider>
|
||||
</spi>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:mail:4.0">
|
||||
<mail-session name="default" jndi-name="java:jboss/mail/Default">
|
||||
<smtp-server outbound-socket-binding-ref="mail-smtp"/>
|
||||
</mail-session>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:wildfly:metrics:1.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:wildfly}"/>
|
||||
<subsystem xmlns="urn:jboss:domain:modcluster:5.0">
|
||||
<proxy name="default" advertise-socket="modcluster" listener="ajp">
|
||||
<dynamic-load-provider>
|
||||
<load-metric type="cpu"/>
|
||||
</dynamic-load-provider>
|
||||
</proxy>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:naming:2.0">
|
||||
<remote-naming/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:remoting:4.0">
|
||||
<http-connector name="http-remoting-connector" connector-ref="default" sasl-authentication-factory="application-sasl-authentication"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:request-controller:1.0"/>
|
||||
<subsystem xmlns="urn:jboss:domain:security-manager:1.0">
|
||||
<deployment-permissions>
|
||||
<maximum-set>
|
||||
<permission class="java.security.AllPermission"/>
|
||||
</maximum-set>
|
||||
</deployment-permissions>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:transactions:6.0">
|
||||
<core-environment node-identifier="${jboss.tx.node.id:1}">
|
||||
<process-id>
|
||||
<uuid/>
|
||||
</process-id>
|
||||
</core-environment>
|
||||
<recovery-environment socket-binding="txn-recovery-environment" status-socket-binding="txn-status-manager"/>
|
||||
<coordinator-environment statistics-enabled="${wildfly.transactions.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
|
||||
<object-store path="tx-object-store" relative-to="jboss.server.data.dir"/>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:undertow:12.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other" statistics-enabled="${wildfly.undertow.statistics-enabled:${wildfly.statistics-enabled:false}}">
|
||||
<buffer-cache name="default"/>
|
||||
<server name="default-server">
|
||||
<ajp-listener name="ajp" socket-binding="ajp"/>
|
||||
<http-listener name="default" socket-binding="http" redirect-socket="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" enable-http2="true"/>
|
||||
<https-listener name="https" socket-binding="https" ssl-context="applicationSSC" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" enable-http2="true"/>
|
||||
<host name="default-host" alias="localhost">
|
||||
<location name="/" handler="welcome-content"/>
|
||||
<http-invoker http-authentication-factory="application-http-authentication"/>
|
||||
</host>
|
||||
</server>
|
||||
<servlet-container name="default">
|
||||
<jsp-config/>
|
||||
<websockets/>
|
||||
</servlet-container>
|
||||
<handlers>
|
||||
<file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
|
||||
</handlers>
|
||||
<application-security-domains>
|
||||
<application-security-domain name="other" security-domain="ApplicationDomain"/>
|
||||
</application-security-domains>
|
||||
</subsystem>
|
||||
<subsystem xmlns="urn:jboss:domain:weld:4.0"/>
|
||||
</profile>
|
||||
<interfaces>
|
||||
<interface name="management">
|
||||
<inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
|
||||
</interface>
|
||||
<interface name="private">
|
||||
<inet-address value="${jboss.bind.address.private:127.0.0.1}"/>
|
||||
</interface>
|
||||
<interface name="public">
|
||||
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
|
||||
</interface>
|
||||
</interfaces>
|
||||
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
|
||||
<socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
|
||||
<socket-binding name="http" port="${jboss.http.port:8080}"/>
|
||||
<socket-binding name="https" port="${jboss.https.port:8443}"/>
|
||||
<socket-binding name="jgroups-mping" interface="private" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45700"/>
|
||||
<socket-binding name="jgroups-tcp" interface="private" port="7600"/>
|
||||
<socket-binding name="jgroups-tcp-fd" interface="private" port="57600"/>
|
||||
<socket-binding name="jgroups-udp" interface="private" port="55200" multicast-address="${jboss.default.multicast.address:230.0.0.4}" multicast-port="45688"/>
|
||||
<socket-binding name="jgroups-udp-fd" interface="private" port="54200"/>
|
||||
<socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
|
||||
<socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
|
||||
<socket-binding name="modcluster" multicast-address="${jboss.modcluster.multicast.address:224.0.1.105}" multicast-port="23364"/>
|
||||
<socket-binding name="txn-recovery-environment" port="4712"/>
|
||||
<socket-binding name="txn-status-manager" port="4713"/>
|
||||
<outbound-socket-binding name="mail-smtp">
|
||||
<remote-destination host="${jboss.mail.server.host:localhost}" port="${jboss.mail.server.port:25}"/>
|
||||
</outbound-socket-binding>
|
||||
</socket-binding-group>
|
||||
</server>
|
@ -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>
|
||||
@ -122,10 +135,13 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
||||
key == "updateProfileCtx" &&
|
||||
are_same_path(path, [])
|
||||
) || (
|
||||
<#-- https://github.com/InseeFrLab/keycloakify/pull/65#issuecomment-991896344 -->
|
||||
<#-- 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"]) &&
|
||||
pageId == "saml-post-form.ftl"
|
||||
["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"]) &&
|
||||
@ -134,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*/"]>
|
||||
@ -151,7 +170,7 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
||||
</#attempt>
|
||||
|
||||
</#if>
|
||||
|
||||
|
||||
<#attempt>
|
||||
<#if !object[key]??>
|
||||
<#continue>
|
||||
@ -199,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>
|
||||
|
||||
@ -229,6 +273,11 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
||||
|
||||
<#list object as array_item>
|
||||
|
||||
<#if !array_item??>
|
||||
<#local out_seq += ["null,"]>
|
||||
<#continue>
|
||||
</#if>
|
||||
|
||||
<#local rec_out = ftl_object_to_js_code_declaring_an_object(array_item, path + [ i ])>
|
||||
|
||||
<#local i = i + 1>
|
||||
@ -262,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>
|
||||
|
||||
@ -295,4 +344,4 @@ ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
||||
<#return true>
|
||||
|
||||
</#function>
|
||||
</script>
|
||||
</script>
|
||||
|
@ -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",
|
||||
@ -18,7 +19,10 @@ export const pageIds = [
|
||||
"login-update-profile.ftl",
|
||||
"login-update-password.ftl",
|
||||
"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];
|
||||
|
@ -2,10 +2,16 @@ import * as url from "url";
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
||||
|
||||
export function generateJavaStackFiles(params: { version: string; themeName: string; homepage?: string; keycloakThemeBuildingDirPath: string }): {
|
||||
export function generateJavaStackFiles(params: {
|
||||
version: string;
|
||||
themeName: string;
|
||||
homepage?: string;
|
||||
keycloakThemeBuildingDirPath: string;
|
||||
doBundleEmailTemplate: boolean;
|
||||
}): {
|
||||
jarFilePath: string;
|
||||
} {
|
||||
const { themeName, version, homepage, keycloakThemeBuildingDirPath } = params;
|
||||
const { themeName, version, homepage, keycloakThemeBuildingDirPath, doBundleEmailTemplate } = params;
|
||||
|
||||
{
|
||||
const { pomFileCode } = (function generatePomFileCode(): {
|
||||
@ -63,7 +69,7 @@ export function generateJavaStackFiles(params: { version: string; themeName: str
|
||||
"themes": [
|
||||
{
|
||||
"name": themeName,
|
||||
"types": ["login"],
|
||||
"types": ["login", ...(doBundleEmailTemplate ? ["email"] : [])],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -7,23 +7,24 @@ import { downloadBuiltinKeycloakTheme } from "../download-builtin-keycloak-theme
|
||||
import * as child_process from "child_process";
|
||||
import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/getKcContext/kcContextMocks/urlResourcesPath";
|
||||
import { isInside } from "../tools/isInside";
|
||||
import type { KeycloakVersion } from "../KeycloakVersion";
|
||||
|
||||
export function generateKeycloakThemeResources(params: {
|
||||
themeName: string;
|
||||
reactAppBuildDirPath: string;
|
||||
keycloakThemeBuildingDirPath: string;
|
||||
keycloakThemeEmailDirPath: string;
|
||||
urlPathname: string;
|
||||
//If urlOrigin is not undefined then it means --externals-assets
|
||||
urlOrigin: undefined | string;
|
||||
extraPagesId: string[];
|
||||
extraThemeProperties: string[];
|
||||
keycloakVersion: KeycloakVersion;
|
||||
}) {
|
||||
keycloakVersion: string;
|
||||
}): { doBundleEmailTemplate: boolean } {
|
||||
const {
|
||||
themeName,
|
||||
reactAppBuildDirPath,
|
||||
keycloakThemeBuildingDirPath,
|
||||
keycloakThemeEmailDirPath,
|
||||
urlPathname,
|
||||
urlOrigin,
|
||||
extraPagesId,
|
||||
@ -80,6 +81,28 @@ export function generateKeycloakThemeResources(params: {
|
||||
},
|
||||
});
|
||||
|
||||
let doBundleEmailTemplate: boolean;
|
||||
|
||||
email: {
|
||||
if (!fs.existsSync(keycloakThemeEmailDirPath)) {
|
||||
console.log(
|
||||
[
|
||||
`Not bundling email template because ${pathBasename(keycloakThemeEmailDirPath)} does not exist`,
|
||||
`To start customizing the email template, run: 👉 npx create-keycloak-email-directory 👈`,
|
||||
].join("\n"),
|
||||
);
|
||||
doBundleEmailTemplate = false;
|
||||
break email;
|
||||
}
|
||||
|
||||
doBundleEmailTemplate = true;
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": keycloakThemeEmailDirPath,
|
||||
"destDirPath": pathJoin(themeDirPath, "..", "email"),
|
||||
});
|
||||
}
|
||||
|
||||
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
|
||||
"cssGlobalsToDefine": allCssGlobalsToDefine,
|
||||
"indexHtmlCode": fs.readFileSync(pathJoin(reactAppBuildDirPath, "index.html")).toString("utf8"),
|
||||
@ -140,4 +163,6 @@ export function generateKeycloakThemeResources(params: {
|
||||
pathJoin(themeDirPath, "theme.properties"),
|
||||
Buffer.from("parent=keycloak".concat("\n\n", extraThemeProperties.join("\n\n")), "utf8"),
|
||||
);
|
||||
|
||||
return { doBundleEmailTemplate };
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
|
||||
generateStartKeycloakTestingContainer.basename = "start_keycloak_testing_container.sh";
|
||||
|
||||
const containerName = "keycloak-testing-container";
|
||||
|
||||
/** Files for being able to run a hot reload keycloak container */
|
||||
export function generateStartKeycloakTestingContainer(params: { keycloakVersion: string; themeName: string; keycloakThemeBuildingDirPath: string }) {
|
||||
const { themeName, keycloakThemeBuildingDirPath, keycloakVersion } = params;
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(keycloakThemeBuildingDirPath, generateStartKeycloakTestingContainer.basename),
|
||||
Buffer.from(
|
||||
[
|
||||
"#!/bin/bash",
|
||||
"",
|
||||
`docker rm ${containerName} || true`,
|
||||
"",
|
||||
`cd ${keycloakThemeBuildingDirPath}`,
|
||||
"",
|
||||
"docker run \\",
|
||||
" -p 8080:8080 \\",
|
||||
` --name ${containerName} \\`,
|
||||
" -e KEYCLOAK_ADMIN=admin \\",
|
||||
" -e KEYCLOAK_ADMIN_PASSWORD=admin \\",
|
||||
" -e JAVA_OPTS=-Dkeycloak.profile=preview \\",
|
||||
` -v ${pathJoin(
|
||||
keycloakThemeBuildingDirPath,
|
||||
"src",
|
||||
"main",
|
||||
"resources",
|
||||
"theme",
|
||||
themeName,
|
||||
)}:/opt/keycloak/themes/${themeName}:rw \\`,
|
||||
` -it quay.io/keycloak/keycloak:${keycloakVersion} \\`,
|
||||
` start-dev`,
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
),
|
||||
{ "mode": 0o755 },
|
||||
);
|
||||
}
|
@ -17,12 +17,36 @@ export function replaceImportsFromStaticInJsCode(params: { jsCode: string; urlOr
|
||||
const { jsCode, urlOrigin } = params;
|
||||
|
||||
const fixedJsCode = jsCode
|
||||
.replace(/([a-z]+\.[a-z]+)\+"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",([a-z])+=([a-z]+\.[a-z]+)\+([a-z]+),/, (...[, 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},`,
|
||||
|
36
src/bin/create-keycloak-email-directory.ts
Normal file
36
src/bin/create-keycloak-email-directory.ts
Normal file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
|
||||
import { keycloakThemeEmailDirPath } from "./build-keycloak-theme";
|
||||
import { join as pathJoin, basename as pathBasename } from "path";
|
||||
import { transformCodebase } from "./tools/transformCodebase";
|
||||
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
||||
import * as fs from "fs";
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
if (fs.existsSync(keycloakThemeEmailDirPath)) {
|
||||
console.log(`There is already a ./${pathBasename(keycloakThemeEmailDirPath)} directory in your project. Aborting.`);
|
||||
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
const { keycloakVersion } = await promptKeycloakVersion();
|
||||
|
||||
const builtinKeycloakThemeTmpDirPath = pathJoin(keycloakThemeEmailDirPath, "..", "tmp_xIdP3_builtin_keycloak_theme");
|
||||
|
||||
downloadBuiltinKeycloakTheme({
|
||||
keycloakVersion,
|
||||
"destDirPath": builtinKeycloakThemeTmpDirPath,
|
||||
});
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "email"),
|
||||
"destDirPath": keycloakThemeEmailDirPath,
|
||||
});
|
||||
|
||||
console.log(`./${pathBasename(keycloakThemeEmailDirPath)} ready to be customized`);
|
||||
|
||||
fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
|
||||
})();
|
||||
}
|
@ -3,9 +3,9 @@
|
||||
import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme";
|
||||
import { join as pathJoin } from "path";
|
||||
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
||||
import type { KeycloakVersion } from "./KeycloakVersion";
|
||||
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
||||
|
||||
export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: KeycloakVersion; destDirPath: string }) {
|
||||
export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string }) {
|
||||
const { keycloakVersion, destDirPath } = params;
|
||||
|
||||
for (const ext of ["", "-community"]) {
|
||||
@ -18,22 +18,16 @@ export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: Keycloak
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const keycloakVersion = (() => {
|
||||
const keycloakVersion = process.argv[2] as KeycloakVersion | undefined;
|
||||
(async () => {
|
||||
const { keycloakVersion } = await promptKeycloakVersion();
|
||||
|
||||
if (keycloakVersion === undefined) {
|
||||
return "11.0.3";
|
||||
}
|
||||
const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme");
|
||||
|
||||
return keycloakVersion;
|
||||
console.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
|
||||
|
||||
downloadBuiltinKeycloakTheme({
|
||||
keycloakVersion,
|
||||
destDirPath,
|
||||
});
|
||||
})();
|
||||
|
||||
const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme");
|
||||
|
||||
console.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
|
||||
|
||||
downloadBuiltinKeycloakTheme({
|
||||
keycloakVersion,
|
||||
destDirPath,
|
||||
});
|
||||
}
|
||||
|
@ -5,12 +5,11 @@ import { crawl } from "./tools/crawl";
|
||||
import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
|
||||
import { getProjectRoot } from "./tools/getProjectRoot";
|
||||
import { rm_rf, rm_r } from "./tools/rm";
|
||||
import { keycloakVersions } from "./KeycloakVersion";
|
||||
|
||||
//@ts-ignore
|
||||
const propertiesParser = require("properties-parser");
|
||||
|
||||
for (const keycloakVersion of keycloakVersions) {
|
||||
for (const keycloakVersion of ["11.0.3", "15.0.2", "18.0.1"]) {
|
||||
console.log({ keycloakVersion });
|
||||
|
||||
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
|
||||
|
@ -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);
|
||||
|
||||
|
47
src/bin/promptKeycloakVersion.ts
Normal file
47
src/bin/promptKeycloakVersion.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { getLatestsSemVersionedTagFactory } from "./tools/octokit-addons/getLatestsSemVersionedTag";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import cliSelect from "cli-select";
|
||||
|
||||
export async function promptKeycloakVersion() {
|
||||
const { getLatestsSemVersionedTag } = (() => {
|
||||
const { octokit } = (() => {
|
||||
const githubToken = process.env.GITHUB_TOKEN;
|
||||
|
||||
const octokit = new Octokit(githubToken === undefined ? undefined : { "auth": githubToken });
|
||||
|
||||
return { octokit };
|
||||
})();
|
||||
|
||||
const { getLatestsSemVersionedTag } = getLatestsSemVersionedTagFactory({ octokit });
|
||||
|
||||
return { getLatestsSemVersionedTag };
|
||||
})();
|
||||
|
||||
console.log("Initialize the directory with email template from which keycloak version?");
|
||||
|
||||
const tags = [
|
||||
...(await getLatestsSemVersionedTag({
|
||||
"count": 10,
|
||||
"doIgnoreBeta": true,
|
||||
"owner": "keycloak",
|
||||
"repo": "keycloak",
|
||||
}).then(arr => arr.map(({ tag }) => tag))),
|
||||
"11.0.3",
|
||||
];
|
||||
|
||||
if (process.env["GITHUB_ACTIONS"] === "true") {
|
||||
return { "keycloakVersion": tags[0] };
|
||||
}
|
||||
|
||||
const { value: keycloakVersion } = await cliSelect<string>({
|
||||
"values": tags,
|
||||
}).catch(() => {
|
||||
console.log("Aborting");
|
||||
|
||||
process.exit(-1);
|
||||
});
|
||||
|
||||
console.log(keycloakVersion);
|
||||
|
||||
return { keycloakVersion };
|
||||
}
|
73
src/bin/tools/NpmModuleVersion.ts
Normal file
73
src/bin/tools/NpmModuleVersion.ts
Normal file
@ -0,0 +1,73 @@
|
||||
export type NpmModuleVersion = {
|
||||
major: number;
|
||||
minor: number;
|
||||
patch: number;
|
||||
betaPreRelease?: number;
|
||||
};
|
||||
|
||||
export namespace NpmModuleVersion {
|
||||
export function parse(versionStr: string): NpmModuleVersion {
|
||||
const match = versionStr.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-beta.([0-9]+))?/);
|
||||
|
||||
if (!match) {
|
||||
throw new Error(`${versionStr} is not a valid NPM version`);
|
||||
}
|
||||
|
||||
return {
|
||||
"major": parseInt(match[1]),
|
||||
"minor": parseInt(match[2]),
|
||||
"patch": parseInt(match[3]),
|
||||
...(() => {
|
||||
const str = match[4];
|
||||
return str === undefined ? {} : { "betaPreRelease": parseInt(str) };
|
||||
})(),
|
||||
};
|
||||
}
|
||||
|
||||
export function stringify(v: NpmModuleVersion) {
|
||||
return `${v.major}.${v.minor}.${v.patch}${v.betaPreRelease === undefined ? "" : `-beta.${v.betaPreRelease}`}`;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* v1 < v2 => -1
|
||||
* v1 === v2 => 0
|
||||
* v1 > v2 => 1
|
||||
*
|
||||
*/
|
||||
export function compare(v1: NpmModuleVersion, v2: NpmModuleVersion): -1 | 0 | 1 {
|
||||
const sign = (diff: number): -1 | 0 | 1 => (diff === 0 ? 0 : diff < 0 ? -1 : 1);
|
||||
const noUndefined = (n: number | undefined) => n ?? Infinity;
|
||||
|
||||
for (const level of ["major", "minor", "patch", "betaPreRelease"] as const) {
|
||||
if (noUndefined(v1[level]) !== noUndefined(v2[level])) {
|
||||
return sign(noUndefined(v1[level]) - noUndefined(v2[level]));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
console.log(compare(parse("3.0.0-beta.3"), parse("3.0.0")) === -1 )
|
||||
console.log(compare(parse("3.0.0-beta.3"), parse("3.0.0-beta.4")) === -1 )
|
||||
console.log(compare(parse("3.0.0-beta.3"), parse("4.0.0")) === -1 )
|
||||
*/
|
||||
|
||||
export function bumpType(params: { versionBehindStr: string; versionAheadStr: string }): "major" | "minor" | "patch" | "betaPreRelease" | "same" {
|
||||
const versionAhead = parse(params.versionAheadStr);
|
||||
const versionBehind = parse(params.versionBehindStr);
|
||||
|
||||
if (compare(versionBehind, versionAhead) === 1) {
|
||||
throw new Error(`Version regression ${versionBehind} -> ${versionAhead}`);
|
||||
}
|
||||
|
||||
for (const level of ["major", "minor", "patch", "betaPreRelease"] as const) {
|
||||
if (versionBehind[level] !== versionAhead[level]) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
|
||||
return "same";
|
||||
}
|
||||
}
|
@ -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,
|
||||
});
|
||||
|
||||
|
40
src/bin/tools/octokit-addons/getLatestsSemVersionedTag.ts
Normal file
40
src/bin/tools/octokit-addons/getLatestsSemVersionedTag.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { listTagsFactory } from "./listTags";
|
||||
import type { Octokit } from "@octokit/rest";
|
||||
import { NpmModuleVersion } from "../NpmModuleVersion";
|
||||
|
||||
export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
|
||||
const { octokit } = params;
|
||||
|
||||
async function getLatestsSemVersionedTag(params: { owner: string; repo: string; doIgnoreBeta: boolean; count: number }): Promise<
|
||||
{
|
||||
tag: string;
|
||||
version: NpmModuleVersion;
|
||||
}[]
|
||||
> {
|
||||
const { owner, repo, doIgnoreBeta, count } = params;
|
||||
|
||||
const semVersionedTags: { tag: string; version: NpmModuleVersion }[] = [];
|
||||
|
||||
const { listTags } = listTagsFactory({ octokit });
|
||||
|
||||
for await (const tag of listTags({ owner, repo })) {
|
||||
let version: NpmModuleVersion;
|
||||
|
||||
try {
|
||||
version = NpmModuleVersion.parse(tag.replace(/^[vV]?/, ""));
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (doIgnoreBeta && version.betaPreRelease !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
semVersionedTags.push({ tag, version });
|
||||
}
|
||||
|
||||
return semVersionedTags.sort(({ version: vX }, { version: vY }) => NpmModuleVersion.compare(vY, vX)).slice(0, count);
|
||||
}
|
||||
|
||||
return { getLatestsSemVersionedTag };
|
||||
}
|
49
src/bin/tools/octokit-addons/listTags.ts
Normal file
49
src/bin/tools/octokit-addons/listTags.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import type { Octokit } from "@octokit/rest";
|
||||
|
||||
const per_page = 99;
|
||||
|
||||
export function listTagsFactory(params: { octokit: Octokit }) {
|
||||
const { octokit } = params;
|
||||
|
||||
const octokit_repo_listTags = async (params: { owner: string; repo: string; per_page: number; page: number }) => {
|
||||
return octokit.repos.listTags(params);
|
||||
};
|
||||
|
||||
async function* listTags(params: { owner: string; repo: string }): AsyncGenerator<string> {
|
||||
const { owner, repo } = params;
|
||||
|
||||
let page = 1;
|
||||
|
||||
while (true) {
|
||||
const resp = await octokit_repo_listTags({
|
||||
owner,
|
||||
repo,
|
||||
per_page,
|
||||
"page": page++,
|
||||
});
|
||||
|
||||
for (const branch of resp.data.map(({ name }) => name)) {
|
||||
yield branch;
|
||||
}
|
||||
|
||||
if (resp.data.length < 99) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the same "latest" tag as deno.land/x, not actually the latest though */
|
||||
async function getLatestTag(params: { owner: string; repo: string }): Promise<string | undefined> {
|
||||
const { owner, repo } = params;
|
||||
|
||||
const itRes = await listTags({ owner, repo }).next();
|
||||
|
||||
if (itRes.done) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return itRes.value;
|
||||
}
|
||||
|
||||
return { listTags, getLatestTag };
|
||||
}
|
@ -2,10 +2,10 @@ import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
|
||||
export const Error = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Error } & KcProps) => {
|
||||
const { msg } = useKcMessage();
|
||||
const { msg } = getMsg(kcContext);
|
||||
|
||||
const { message, client } = kcContext;
|
||||
|
||||
|
@ -3,10 +3,10 @@ import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import { assert } from "../tools/assert";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
|
||||
export const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Info } & KcProps) => {
|
||||
const { msg } = useKcMessage();
|
||||
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 ? (
|
||||
|
@ -14,6 +14,9 @@ import { LoginUpdatePassword } from "./LoginUpdatePassword";
|
||||
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) {
|
||||
@ -41,7 +44,13 @@ export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase }
|
||||
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
|
||||
case "login-idp-link-confirm.ftl":
|
||||
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
|
||||
case "login-idp-link-email.ftl":
|
||||
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 }} />;
|
||||
}
|
||||
});
|
||||
|
@ -2,20 +2,33 @@ import { useState, memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import type { FormEventHandler } from "react";
|
||||
|
||||
export const Login = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Login } & KcProps) => {
|
||||
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
|
||||
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
||||
|
||||
const onSubmit = useConstCallback(() => (setIsLoginButtonDisabled(true), true));
|
||||
const onSubmit = useConstCallback<FormEventHandler<HTMLFormElement>>(e => {
|
||||
e.preventDefault();
|
||||
|
||||
setIsLoginButtonDisabled(true);
|
||||
|
||||
const formElement = e.target as HTMLFormElement;
|
||||
|
||||
//NOTE: Even if we login with email Keycloak expect username and password in
|
||||
//the POST request.
|
||||
formElement.querySelector("input[name='email']")?.setAttribute("name", "username");
|
||||
|
||||
formElement.submit();
|
||||
});
|
||||
|
||||
return (
|
||||
<Template
|
||||
@ -33,27 +46,40 @@ export const Login = memo(({ kcContext, ...props }: { kcContext: KcContextBase.L
|
||||
{realm.password && (
|
||||
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<label htmlFor="username" className={cx(props.kcLabelClass)}>
|
||||
{!realm.loginWithEmailAllowed
|
||||
? msg("username")
|
||||
: !realm.registrationEmailAsUsername
|
||||
? msg("usernameOrEmail")
|
||||
: msg("email")}
|
||||
</label>
|
||||
<input
|
||||
tabIndex={1}
|
||||
id="username"
|
||||
className={cx(props.kcInputClass)}
|
||||
name="username"
|
||||
defaultValue={login.username ?? ""}
|
||||
type="text"
|
||||
{...(usernameEditDisabled
|
||||
? { "disabled": true }
|
||||
: {
|
||||
"autoFocus": true,
|
||||
"autoComplete": "off",
|
||||
})}
|
||||
/>
|
||||
{(() => {
|
||||
const label = !realm.loginWithEmailAllowed
|
||||
? "username"
|
||||
: realm.registrationEmailAsUsername
|
||||
? "email"
|
||||
: "usernameOrEmail";
|
||||
|
||||
const autoCompleteHelper: typeof label = label === "usernameOrEmail" ? "username" : label;
|
||||
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={autoCompleteHelper} className={cx(props.kcLabelClass)}>
|
||||
{msg(label)}
|
||||
</label>
|
||||
<input
|
||||
tabIndex={1}
|
||||
id={autoCompleteHelper}
|
||||
className={cx(props.kcInputClass)}
|
||||
//NOTE: This is used by Google Chrome auto fill so we use it to tell
|
||||
//the browser how to pre fill the form but before submit we put it back
|
||||
//to username because it is what keycloak expects.
|
||||
name={autoCompleteHelper}
|
||||
defaultValue={login.username ?? ""}
|
||||
type="text"
|
||||
{...(usernameEditDisabled
|
||||
? { "disabled": true }
|
||||
: {
|
||||
"autoFocus": true,
|
||||
"autoComplete": "off",
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className={cx(props.kcFormGroupClass)}>
|
||||
<label htmlFor="password" className={cx(props.kcLabelClass)}>
|
||||
|
183
src/lib/components/LoginConfigTotp.tsx
Normal file
183
src/lib/components/LoginConfigTotp.tsx
Normal 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>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
@ -2,13 +2,13 @@ import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm } & KcProps) => {
|
||||
const { url, idpAlias } = kcContext;
|
||||
|
||||
const { msg } = useKcMessage();
|
||||
const { msg } = getMsg(kcContext);
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
|
32
src/lib/components/LoginIdpLinkEmail.tsx
Normal file
32
src/lib/components/LoginIdpLinkEmail.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { getMsg } from "../i18n";
|
||||
|
||||
export const LoginIdpLinkEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkEmail } & KcProps) => {
|
||||
const { url, realm, brokerContext, idpAlias } = kcContext;
|
||||
|
||||
const { msg } = getMsg(kcContext);
|
||||
|
||||
return (
|
||||
<Template
|
||||
{...{ kcContext, ...props }}
|
||||
doFetchDefaultThemeResources={true}
|
||||
headerNode={msg("emailLinkIdpTitle", idpAlias)}
|
||||
formNode={
|
||||
<>
|
||||
<p id="instruction1" className="instruction">
|
||||
{msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.displayName)}
|
||||
</p>
|
||||
<p id="instruction2" className="instruction">
|
||||
{msg("emailLinkIdp2")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp3")}
|
||||
</p>
|
||||
<p id="instruction3" className="instruction">
|
||||
{msg("emailLinkIdp4")} <a href={url.loginAction}>{msg("doClickHere")}</a> {msg("emailLinkIdp5")}
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
@ -2,7 +2,7 @@ import { useEffect, memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
import { headInsert } from "../tools/headInsert";
|
||||
import { pathJoin } from "../tools/pathJoin";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
@ -12,7 +12,7 @@ export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBas
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
|
||||
useEffect(() => {
|
||||
let isCleanedUp = false;
|
||||
|
@ -2,12 +2,12 @@ import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
|
||||
export const LoginPageExpired = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginPageExpired } & KcProps) => {
|
||||
const { url } = kcContext;
|
||||
|
||||
const { msg } = useKcMessage();
|
||||
const { msg } = getMsg(kcContext);
|
||||
|
||||
return (
|
||||
<Template
|
||||
|
@ -2,13 +2,13 @@ import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginResetPassword } & KcProps) => {
|
||||
const { url, realm, auth } = kcContext;
|
||||
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
|
@ -2,13 +2,13 @@ import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
export const LoginUpdatePassword = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdatePassword } & KcProps) => {
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
|
||||
const { url, messagesPerField, isAppInitiatedAction, username } = kcContext;
|
||||
|
||||
|
@ -2,13 +2,13 @@ import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
export const LoginUpdateProfile = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginUpdateProfile } & KcProps) => {
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
|
||||
const { url, user, messagesPerField, isAppInitiatedAction } = kcContext;
|
||||
|
||||
|
@ -2,12 +2,12 @@ import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
|
||||
export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginVerifyEmail } & KcProps) => {
|
||||
const { msg } = useKcMessage();
|
||||
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>
|
||||
|
||||
{msg("emailVerifyInstruction3")}
|
||||
</p>
|
||||
</>
|
||||
|
61
src/lib/components/LogoutConfirm.tsx
Normal file
61
src/lib/components/LogoutConfirm.tsx
Normal 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>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
@ -2,13 +2,13 @@ import { memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
|
||||
export const Register = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Register } & KcProps) => {
|
||||
const { url, messagesPerField, register, realm, passwordRequired, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { useMemo, memo, useEffect, useState, Fragment } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase, Attribute } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import type { ReactComponent } from "../tools/ReactComponent";
|
||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||
@ -11,7 +11,7 @@ import { useFormValidationSlice } from "../useFormValidationSlice";
|
||||
export const RegisterUserProfile = memo(({ kcContext, ...props_ }: { kcContext: KcContextBase.RegisterUserProfile } & KcProps) => {
|
||||
const { url, messagesPerField, recaptchaRequired, recaptchaSiteKey } = kcContext;
|
||||
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
|
||||
const { cx, css } = useCssAndCx();
|
||||
|
||||
@ -74,7 +74,7 @@ type UserProfileFormFieldsProps = { kcContext: KcContextBase.RegisterUserProfile
|
||||
const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange, ...props }: UserProfileFormFieldsProps) => {
|
||||
const { cx, css } = useCssAndCx();
|
||||
|
||||
const { advancedMsg } = useKcMessage();
|
||||
const { advancedMsg } = getMsg(kcContext);
|
||||
|
||||
const {
|
||||
formValidationState: { fieldStateByAttributeName, isFormSubmittable },
|
||||
@ -95,7 +95,7 @@ const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange,
|
||||
{
|
||||
target: { value },
|
||||
},
|
||||
]: [React.ChangeEvent<HTMLInputElement>],
|
||||
]: [React.ChangeEvent<HTMLInputElement | HTMLSelectElement>],
|
||||
) =>
|
||||
formValidationReducer({
|
||||
"action": "update value",
|
||||
@ -148,26 +148,50 @@ const UserProfileFormFields = memo(({ kcContext, onIsFormSubmittableValueChange,
|
||||
{attribute.required && <>*</>}
|
||||
</div>
|
||||
<div className={cx(props.kcInputWrapperClass)}>
|
||||
<input
|
||||
type={(() => {
|
||||
switch (attribute.name) {
|
||||
case "password-confirm":
|
||||
case "password":
|
||||
return "password";
|
||||
default:
|
||||
return "text";
|
||||
}
|
||||
})()}
|
||||
id={attribute.name}
|
||||
name={attribute.name}
|
||||
value={value}
|
||||
onChange={onChangeFactory(attribute.name)}
|
||||
className={cx(props.kcInputClass)}
|
||||
aria-invalid={displayableErrors.length !== 0}
|
||||
disabled={attribute.readOnly}
|
||||
autoComplete={attribute.autocomplete}
|
||||
onBlur={onBlurFactory(attribute.name)}
|
||||
/>
|
||||
{(() => {
|
||||
const { options } = attribute.validators;
|
||||
|
||||
if (options !== undefined) {
|
||||
return (
|
||||
<select
|
||||
id={attribute.name}
|
||||
name={attribute.name}
|
||||
onChange={onChangeFactory(attribute.name)}
|
||||
onBlur={onBlurFactory(attribute.name)}
|
||||
value={value}
|
||||
>
|
||||
{options.options.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
type={(() => {
|
||||
switch (attribute.name) {
|
||||
case "password-confirm":
|
||||
case "password":
|
||||
return "password";
|
||||
default:
|
||||
return "text";
|
||||
}
|
||||
})()}
|
||||
id={attribute.name}
|
||||
name={attribute.name}
|
||||
value={value}
|
||||
onChange={onChangeFactory(attribute.name)}
|
||||
className={cx(props.kcInputClass)}
|
||||
aria-invalid={displayableErrors.length !== 0}
|
||||
disabled={attribute.readOnly}
|
||||
autoComplete={attribute.autocomplete}
|
||||
onBlur={onBlurFactory(attribute.name)}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
{displayableErrors.length !== 0 && (
|
||||
<span
|
||||
id={`input-error-${attribute.name}`}
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { useReducer, useEffect, memo } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
|
||||
import { getMsg, getCurrentKcLanguageTag, changeLocale, getTagLabel } from "../i18n";
|
||||
import type { KcLanguageTag } from "../i18n";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { assert } from "../tools/assert";
|
||||
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
|
||||
import { getBestMatchAmongKcLanguageTag } from "../i18n/KcLanguageTag";
|
||||
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
|
||||
import { useCallbackFactory } from "powerhooks/useCallbackFactory";
|
||||
import { headInsert } from "../tools/headInsert";
|
||||
import { pathJoin } from "../tools/pathJoin";
|
||||
@ -51,30 +48,19 @@ export const Template = memo((props: TemplateProps) => {
|
||||
console.log("Rendering this page with react using keycloakify");
|
||||
}, []);
|
||||
|
||||
const { msg } = useKcMessage();
|
||||
const { msg } = getMsg(kcContext);
|
||||
|
||||
const { kcLanguageTag, setKcLanguageTag } = useKcLanguageTag();
|
||||
|
||||
const onChangeLanguageClickFactory = useCallbackFactory(([languageTag]: [KcLanguageTag]) => setKcLanguageTag(languageTag));
|
||||
const onChangeLanguageClickFactory = useCallbackFactory(([kcLanguageTag]: [KcLanguageTag]) =>
|
||||
changeLocale({
|
||||
kcContext,
|
||||
kcLanguageTag,
|
||||
}),
|
||||
);
|
||||
|
||||
const onTryAnotherWayClick = useConstCallback(() => (document.forms["kc-select-try-another-way-form" as never].submit(), false));
|
||||
|
||||
const { realm, locale, auth, url, message, isAppInitiatedAction } = kcContext;
|
||||
|
||||
useEffect(() => {
|
||||
if (!realm.internationalizationEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(locale !== undefined);
|
||||
|
||||
if (kcLanguageTag === getBestMatchAmongKcLanguageTag(locale.current)) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url;
|
||||
}, [kcLanguageTag]);
|
||||
|
||||
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -152,13 +138,13 @@ export const Template = memo((props: TemplateProps) => {
|
||||
<div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}>
|
||||
<div className="kc-dropdown" id="kc-locale-dropdown">
|
||||
<a href="#" id="kc-current-locale-link">
|
||||
{getKcLanguageTagLabel(kcLanguageTag)}
|
||||
{getTagLabel({ "kcLanguageTag": getCurrentKcLanguageTag(kcContext), kcContext })}
|
||||
</a>
|
||||
<ul>
|
||||
{locale.supported.map(({ languageTag }) => (
|
||||
<li key={languageTag} className="kc-dropdown-item">
|
||||
<a href="#" onClick={onChangeLanguageClickFactory(languageTag)}>
|
||||
{getKcLanguageTagLabel(languageTag)}
|
||||
{getTagLabel({ "kcLanguageTag": languageTag, kcContext })}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
|
@ -1,12 +1,33 @@
|
||||
import { memo } from "react";
|
||||
import { useReducer, useEffect, memo } from "react";
|
||||
import { Template } from "./Template";
|
||||
import type { KcProps } from "./KcProps";
|
||||
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "../i18n/useKcMessage";
|
||||
import { getMsg } from "../i18n";
|
||||
import { useCssAndCx } from "tss-react";
|
||||
import { kcMessages, getCurrentKcLanguageTag } from "../i18n";
|
||||
import type { KcLanguageTag } from "../i18n";
|
||||
|
||||
/** Allow to avoid bundling the terms and download it on demand*/
|
||||
export function useDownloadTerms(params: {
|
||||
kcContext: KcContextBase;
|
||||
downloadTermMarkdown: (params: { currentKcLanguageTag: KcLanguageTag }) => Promise<string>;
|
||||
}) {
|
||||
const { kcContext, downloadTermMarkdown } = params;
|
||||
|
||||
const [, forceUpdate] = useReducer(x => x + 1, 0);
|
||||
|
||||
useEffect(() => {
|
||||
const currentKcLanguageTag = getCurrentKcLanguageTag(kcContext);
|
||||
|
||||
downloadTermMarkdown({ currentKcLanguageTag }).then(thermMarkdown => {
|
||||
kcMessages[currentKcLanguageTag].termsText = thermMarkdown;
|
||||
forceUpdate();
|
||||
});
|
||||
}, []);
|
||||
}
|
||||
|
||||
export const Terms = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Terms } & KcProps) => {
|
||||
const { msg, msgStr } = useKcMessage();
|
||||
const { msg, msgStr } = getMsg(kcContext);
|
||||
|
||||
const { cx } = useCssAndCx();
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
import type { PageId } from "../../bin/build-keycloak-theme/generateFtl";
|
||||
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
|
||||
import type { KcLanguageTag } from "../i18n";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { Equals } from "tsafe";
|
||||
import type { MessageKey } from "../i18n/useKcMessage";
|
||||
import type { LanguageLabel } from "../i18n/KcLanguageTag";
|
||||
import type { MessageKey } from "../i18n";
|
||||
|
||||
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
||||
|
||||
@ -24,7 +23,10 @@ export type KcContextBase =
|
||||
| KcContextBase.LoginUpdatePassword
|
||||
| KcContextBase.LoginUpdateProfile
|
||||
| KcContextBase.LoginIdpLinkConfirm
|
||||
| KcContextBase.LoginPageExpired;
|
||||
| KcContextBase.LoginIdpLinkEmail
|
||||
| KcContextBase.LoginPageExpired
|
||||
| KcContextBase.LoginConfigTotp
|
||||
| KcContextBase.LogoutConfirm;
|
||||
|
||||
export declare namespace KcContextBase {
|
||||
export type Common = {
|
||||
@ -46,18 +48,15 @@ export declare namespace KcContextBase {
|
||||
locale?: {
|
||||
supported: {
|
||||
url: string;
|
||||
label: string;
|
||||
languageTag: KcLanguageTag;
|
||||
/** Is determined by languageTag. Ex: languageTag === "en" => label === "English"
|
||||
* or getLanguageLabel(languageTag) === label
|
||||
*/
|
||||
//label: LanguageLabel;
|
||||
}[];
|
||||
current: LanguageLabel;
|
||||
currentLanguageTag: KcLanguageTag;
|
||||
};
|
||||
auth?: {
|
||||
showUsername: boolean;
|
||||
showResetCredentials: boolean;
|
||||
showTryAnotherWayLink: boolean;
|
||||
showUsername?: boolean;
|
||||
showResetCredentials?: boolean;
|
||||
showTryAnotherWayLink?: boolean;
|
||||
attemptedUsername?: string;
|
||||
};
|
||||
scripts: string[];
|
||||
@ -181,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 & {
|
||||
@ -215,9 +218,59 @@ export declare namespace KcContextBase {
|
||||
idpAlias: string;
|
||||
};
|
||||
|
||||
export type LoginIdpLinkEmail = Common & {
|
||||
pageId: "login-idp-link-email.ftl";
|
||||
brokerContext: {
|
||||
username: string;
|
||||
};
|
||||
idpAlias: string;
|
||||
};
|
||||
|
||||
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 = {
|
||||
@ -315,6 +368,7 @@ export type Validators = Partial<{
|
||||
name: string;
|
||||
shouldBe: "equal" | "different";
|
||||
};
|
||||
options: Validators.Options;
|
||||
}>;
|
||||
|
||||
export declare namespace Validators {
|
||||
@ -331,6 +385,9 @@ export declare namespace Validators {
|
||||
min?: `${number}`;
|
||||
max?: `${number}`;
|
||||
};
|
||||
export type Options = {
|
||||
options: string[];
|
||||
};
|
||||
}
|
||||
|
||||
assert<Equals<KcContextBase["pageId"], PageId>>();
|
||||
|
@ -1,7 +1,5 @@
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
import type { KcContextBase, Attribute } from "../KcContextBase";
|
||||
import { getEvtKcLanguage } from "../../i18n/useKcLanguageTag";
|
||||
import { getKcLanguageTagLabel } from "../../i18n/KcLanguageTag";
|
||||
//NOTE: Aside because we want to be able to import them from node
|
||||
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
|
||||
import { id } from "tsafe/id";
|
||||
@ -32,81 +30,100 @@ export const kcContextCommonMock: KcContextBase.Common = {
|
||||
},
|
||||
"locale": {
|
||||
"supported": [
|
||||
/* spell-checker: disable */
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
|
||||
"label": "Deutsch",
|
||||
"languageTag": "de",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
|
||||
"label": "Norsk",
|
||||
"languageTag": "no",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
|
||||
"label": "Русский",
|
||||
"languageTag": "ru",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
|
||||
"label": "Svenska",
|
||||
"languageTag": "sv",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
|
||||
"label": "Português (Brasil)",
|
||||
"languageTag": "pt-BR",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
|
||||
"label": "Lietuvių",
|
||||
"languageTag": "lt",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
|
||||
"label": "English",
|
||||
"languageTag": "en",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
|
||||
"label": "Italiano",
|
||||
"languageTag": "it",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
|
||||
"label": "Français",
|
||||
"languageTag": "fr",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
|
||||
"label": "中文简体",
|
||||
"languageTag": "zh-CN",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
|
||||
"label": "Español",
|
||||
"languageTag": "es",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
|
||||
"label": "Čeština",
|
||||
"languageTag": "cs",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
|
||||
"label": "日本語",
|
||||
"languageTag": "ja",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
|
||||
"label": "Slovenčina",
|
||||
"languageTag": "sk",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
|
||||
"label": "Polski",
|
||||
"languageTag": "pl",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
|
||||
"label": "Català",
|
||||
"languageTag": "ca",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
|
||||
"label": "Nederlands",
|
||||
"languageTag": "nl",
|
||||
},
|
||||
{
|
||||
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
|
||||
"label": "Türkçe",
|
||||
"languageTag": "tr",
|
||||
},
|
||||
/* spell-checker: enable */
|
||||
],
|
||||
//"current": null as any
|
||||
"current": "English",
|
||||
"currentLanguageTag": "en",
|
||||
},
|
||||
"auth": {
|
||||
"showUsername": false,
|
||||
@ -124,11 +141,6 @@ export const kcContextCommonMock: KcContextBase.Common = {
|
||||
"isAppInitiatedAction": false,
|
||||
};
|
||||
|
||||
Object.defineProperty(kcContextCommonMock.locale!, "current", {
|
||||
"get": () => getKcLanguageTagLabel(getEvtKcLanguage().state),
|
||||
"enumerable": true,
|
||||
});
|
||||
|
||||
const loginUrl = {
|
||||
...kcContextCommonMock.url,
|
||||
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
|
||||
@ -325,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,
|
||||
@ -367,4 +382,46 @@ export const kcContextMocks: KcContextBase[] = [
|
||||
"pageId": "login-idp-link-confirm.ftl",
|
||||
"idpAlias": "FranceConnect",
|
||||
}),
|
||||
id<KcContextBase.LoginIdpLinkEmail>({
|
||||
...kcContextCommonMock,
|
||||
"pageId": "login-idp-link-email.ftl",
|
||||
"idpAlias": "FranceConnect",
|
||||
"brokerContext": {
|
||||
"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 },
|
||||
}),
|
||||
];
|
||||
|
@ -1,63 +0,0 @@
|
||||
import { objectKeys } from "tsafe/objectKeys";
|
||||
import { kcMessages } from "./kcMessages/login";
|
||||
|
||||
export type KcLanguageTag = keyof typeof kcMessages;
|
||||
|
||||
const kcLanguageByTagLabel = {
|
||||
/* spell-checker: disable */
|
||||
"es": "Español",
|
||||
"it": "Italiano",
|
||||
"fr": "Français",
|
||||
"ca": "Català",
|
||||
"en": "English",
|
||||
"de": "Deutsch",
|
||||
"no": "Norsk",
|
||||
"pt-BR": "Português (Brasil)",
|
||||
"ru": "Русский",
|
||||
"sk": "Slovenčina",
|
||||
"ja": "日本語",
|
||||
"pl": "Polski",
|
||||
"zh-CN": "中文简体",
|
||||
"sv": "Svenska",
|
||||
"lt": "Lietuvių",
|
||||
"cs": "Čeština",
|
||||
"nl": "Nederlands",
|
||||
"tr": "Türkçe",
|
||||
"da": "Dansk",
|
||||
"hu": "Magyar",
|
||||
/* spell-checker: enable */
|
||||
} as const;
|
||||
|
||||
export type LanguageLabel = typeof kcLanguageByTagLabel[keyof typeof kcLanguageByTagLabel];
|
||||
|
||||
export function getKcLanguageTagLabel(language: KcLanguageTag): LanguageLabel {
|
||||
return kcLanguageByTagLabel[language] ?? language;
|
||||
}
|
||||
|
||||
export const kcLanguageTags = objectKeys(kcMessages);
|
||||
|
||||
/**
|
||||
* Pass in "fr-FR" or "français" for example, it will return the AvailableLanguage
|
||||
* it corresponds to: "fr".
|
||||
* If there is no reasonable match it's guessed from navigator.language.
|
||||
* If still no matches "en" is returned.
|
||||
*/
|
||||
export function getBestMatchAmongKcLanguageTag(languageLike: string): KcLanguageTag {
|
||||
const iso2LanguageLike = languageLike.split("-")[0].toLowerCase();
|
||||
|
||||
const kcLanguageTag = kcLanguageTags.find(
|
||||
language =>
|
||||
language.toLowerCase().includes(iso2LanguageLike) ||
|
||||
getKcLanguageTagLabel(language).toLocaleLowerCase() === languageLike.toLocaleLowerCase(),
|
||||
);
|
||||
|
||||
if (kcLanguageTag !== undefined) {
|
||||
return kcLanguageTag;
|
||||
}
|
||||
|
||||
if (languageLike !== navigator.language) {
|
||||
return getBestMatchAmongKcLanguageTag(navigator.language);
|
||||
}
|
||||
|
||||
return "en";
|
||||
}
|
4732
src/lib/i18n/generated_kcMessages/18.0.1/account.ts
Normal file
4732
src/lib/i18n/generated_kcMessages/18.0.1/account.ts
Normal file
File diff suppressed because it is too large
Load Diff
283
src/lib/i18n/generated_kcMessages/18.0.1/admin.ts
Normal file
283
src/lib/i18n/generated_kcMessages/18.0.1/admin.ts
Normal 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 */
|
1062
src/lib/i18n/generated_kcMessages/18.0.1/email.ts
Normal file
1062
src/lib/i18n/generated_kcMessages/18.0.1/email.ts
Normal file
File diff suppressed because it is too large
Load Diff
6063
src/lib/i18n/generated_kcMessages/18.0.1/login.ts
Normal file
6063
src/lib/i18n/generated_kcMessages/18.0.1/login.ts
Normal file
File diff suppressed because it is too large
Load Diff
211
src/lib/i18n/index.tsx
Normal file
211
src/lib/i18n/index.tsx
Normal file
@ -0,0 +1,211 @@
|
||||
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/18.0.1/login";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { Equals } from "tsafe";
|
||||
|
||||
export const kcMessages = {
|
||||
...kcMessagesBase,
|
||||
"en": {
|
||||
...kcMessagesBase["en"],
|
||||
"termsText": "⏳",
|
||||
"shouldBeEqual": "{0} should be equal to {1}",
|
||||
"shouldBeDifferent": "{0} should be different to {1}",
|
||||
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
||||
"mustBeAnInteger": "Must be an integer",
|
||||
"notAValidOption": "Not a valid option",
|
||||
},
|
||||
"fr": {
|
||||
...kcMessagesBase["fr"],
|
||||
/* spell-checker: disable */
|
||||
"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 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) {
|
||||
return kcContext.locale?.currentLanguageTag ?? "en";
|
||||
}
|
||||
|
||||
export function getTagLabel(params: {
|
||||
kcContext: {
|
||||
locale?: {
|
||||
supported: { languageTag: KcLanguageTag; label: string }[];
|
||||
};
|
||||
};
|
||||
kcLanguageTag: KcLanguageTag;
|
||||
}): string {
|
||||
const { kcContext, kcLanguageTag } = params;
|
||||
|
||||
const { locale } = kcContext;
|
||||
|
||||
assert(locale !== undefined, "Internationalization not enabled");
|
||||
|
||||
const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag);
|
||||
|
||||
assert(targetSupportedLocale !== undefined, `${kcLanguageTag} need to be enabled in Keycloak admin`);
|
||||
|
||||
return targetSupportedLocale.label;
|
||||
}
|
||||
|
||||
export function changeLocale(params: {
|
||||
kcContext: {
|
||||
locale?: {
|
||||
supported: { languageTag: KcLanguageTag; url: string }[];
|
||||
};
|
||||
};
|
||||
kcLanguageTag: KcLanguageTag;
|
||||
}): never {
|
||||
const { kcContext, kcLanguageTag } = params;
|
||||
|
||||
const { locale } = kcContext;
|
||||
|
||||
assert(locale !== undefined, "Internationalization not enabled");
|
||||
|
||||
const targetSupportedLocale = locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag);
|
||||
|
||||
assert(targetSupportedLocale !== undefined, `${kcLanguageTag} need to be enabled in Keycloak admin`);
|
||||
|
||||
window.location.href = targetSupportedLocale.url;
|
||||
|
||||
assert(false, "never");
|
||||
}
|
||||
|
||||
export type MessageKey = keyof typeof kcMessages["en"];
|
||||
|
||||
function resolveMsg<Key extends string, DoRenderMarkdown extends boolean>(props: {
|
||||
key: Key;
|
||||
args: (string | undefined)[];
|
||||
kcLanguageTag: string;
|
||||
doRenderMarkdown: DoRenderMarkdown;
|
||||
}): Key extends MessageKey ? (DoRenderMarkdown extends true ? JSX.Element : string) : undefined {
|
||||
const { key, args, kcLanguageTag, doRenderMarkdown } = props;
|
||||
|
||||
let str = kcMessages[kcLanguageTag as any as "en"][key as MessageKey] ?? kcMessages["en"][key as MessageKey];
|
||||
|
||||
if (str === undefined) {
|
||||
return undefined as any;
|
||||
}
|
||||
|
||||
str = (() => {
|
||||
const startIndex = str
|
||||
.match(/{[0-9]+}/g)
|
||||
?.map(g => g.match(/{([0-9]+)}/)![1])
|
||||
.map(indexStr => parseInt(indexStr))
|
||||
.sort((a, b) => a - b)[0];
|
||||
|
||||
if (startIndex === undefined) {
|
||||
return str;
|
||||
}
|
||||
|
||||
args.forEach((arg, i) => {
|
||||
if (arg === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
str = str.replace(new RegExp(`\\{${i + startIndex}\\}`, "g"), arg);
|
||||
});
|
||||
|
||||
return str;
|
||||
})();
|
||||
|
||||
return (
|
||||
doRenderMarkdown ? (
|
||||
<ReactMarkdown allowDangerousHtml renderers={key === "termsText" ? undefined : { "paragraph": "span" }}>
|
||||
{str}
|
||||
</ReactMarkdown>
|
||||
) : (
|
||||
str
|
||||
)
|
||||
) as any;
|
||||
}
|
||||
|
||||
function resolveMsgAdvanced<Key extends string, DoRenderMarkdown extends boolean>(props: {
|
||||
key: Key;
|
||||
args: (string | undefined)[];
|
||||
kcLanguageTag: string;
|
||||
doRenderMarkdown: DoRenderMarkdown;
|
||||
}): DoRenderMarkdown extends true ? JSX.Element : string {
|
||||
const { key, args, kcLanguageTag, doRenderMarkdown } = props;
|
||||
|
||||
const match = key.match(/^\$\{([^{]+)\}$/);
|
||||
|
||||
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
||||
|
||||
const out = resolveMsg({
|
||||
"key": keyUnwrappedFromCurlyBraces,
|
||||
args,
|
||||
kcLanguageTag,
|
||||
doRenderMarkdown,
|
||||
});
|
||||
|
||||
return (out !== undefined ? out : doRenderMarkdown ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the language is switched the page is reloaded, this may appear
|
||||
* as a bug as you might notice that the language successfully switch before
|
||||
* reload.
|
||||
* However we need to tell Keycloak that the user have changed the language
|
||||
* during login so we can retrieve the "local" field of the JWT encoded accessToken.
|
||||
* https://user-images.githubusercontent.com/6702424/138096682-351bb61f-f24e-4caf-91b7-cca8cfa2cb58.mov
|
||||
*
|
||||
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied")
|
||||
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === "not-a-message-key"
|
||||
*
|
||||
*
|
||||
* NOTE: This function is memoized, it always returns the same object for a given kcContext)
|
||||
*
|
||||
*/
|
||||
export const getMsg = memoize((kcContext: KcContextLike) => {
|
||||
const kcLanguageTag = getCurrentKcLanguageTag(kcContext);
|
||||
|
||||
return {
|
||||
"msgStr": (key: MessageKey, ...args: (string | undefined)[]): string => resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": false }),
|
||||
"msg": (key: MessageKey, ...args: (string | undefined)[]): JSX.Element => resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": true }),
|
||||
"advancedMsg": <Key extends string>(key: Key, ...args: (string | undefined)[]): JSX.Element =>
|
||||
resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": true }),
|
||||
"advancedMsgStr": <Key extends string>(key: Key, ...args: (string | undefined)[]): string =>
|
||||
resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": false }),
|
||||
};
|
||||
});
|
@ -1,48 +0,0 @@
|
||||
import { kcMessages as kcMessagesBase } from "../generated_kcMessages/15.0.2/login";
|
||||
import { Evt } from "evt";
|
||||
import { objectKeys } from "tsafe/objectKeys";
|
||||
|
||||
const kcMessages = {
|
||||
...kcMessagesBase,
|
||||
"en": {
|
||||
...kcMessagesBase["en"],
|
||||
"shouldBeEqual": "{0} should be equal to {1}",
|
||||
"shouldBeDifferent": "{0} should be different to {1}",
|
||||
"shouldMatchPattern": "Pattern should match: `/{0}/`",
|
||||
"mustBeAnInteger": "Must be an integer",
|
||||
},
|
||||
"fr": {
|
||||
...kcMessagesBase["fr"],
|
||||
/* spell-checker: disable */
|
||||
"shouldBeEqual": "{0} doit être egale à {1}",
|
||||
"shouldBeDifferent": "{0} doit être différent de {1}",
|
||||
"shouldMatchPattern": "Dois respecter le schéma: `/{0}/`",
|
||||
"mustBeAnInteger": "Doit être un nombre entiers",
|
||||
/* spell-checker: enable */
|
||||
},
|
||||
};
|
||||
|
||||
export const evtTermsUpdated = Evt.asNonPostable(Evt.create<void>());
|
||||
|
||||
(["termsText", "doAccept", "doDecline", "termsTitle"] as const).forEach(key =>
|
||||
objectKeys(kcMessages).forEach(kcLanguage =>
|
||||
Object.defineProperty(
|
||||
kcMessages[kcLanguage],
|
||||
key,
|
||||
(() => {
|
||||
let value = key === "termsText" ? "⏳" : kcMessages[kcLanguage][key];
|
||||
|
||||
return {
|
||||
"enumerable": true,
|
||||
"get": () => value,
|
||||
"set": (newValue: string) => {
|
||||
value = newValue;
|
||||
Evt.asPostable(evtTermsUpdated).post();
|
||||
},
|
||||
};
|
||||
})(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
export { kcMessages };
|
@ -1,28 +0,0 @@
|
||||
import { createUseGlobalState } from "powerhooks/useGlobalState";
|
||||
import { getKcContextFromWindow } from "../getKcContext/getKcContextFromWindow";
|
||||
import { getBestMatchAmongKcLanguageTag } from "./KcLanguageTag";
|
||||
import type { StatefulEvt } from "powerhooks";
|
||||
import { KcLanguageTag } from "./KcLanguageTag";
|
||||
|
||||
//export const { useKcLanguageTag, evtKcLanguageTag } = createUseGlobalState(
|
||||
const wrap = createUseGlobalState(
|
||||
"kcLanguageTag",
|
||||
() => {
|
||||
const kcContext = getKcContextFromWindow();
|
||||
|
||||
const languageLike = kcContext?.locale?.current ?? (typeof navigator === "undefined" ? undefined : navigator.language);
|
||||
|
||||
if (languageLike === undefined) {
|
||||
return "en";
|
||||
}
|
||||
|
||||
return getBestMatchAmongKcLanguageTag(languageLike);
|
||||
},
|
||||
{ "persistance": "localStorage" },
|
||||
);
|
||||
|
||||
export const { useKcLanguageTag } = wrap;
|
||||
|
||||
export function getEvtKcLanguage(): StatefulEvt<KcLanguageTag> {
|
||||
return wrap.evtKcLanguageTag;
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
import "minimal-polyfills/Object.fromEntries";
|
||||
import { useReducer } from "react";
|
||||
import { useKcLanguageTag } from "./useKcLanguageTag";
|
||||
import { kcMessages, evtTermsUpdated } from "./kcMessages/login";
|
||||
import { useEvt } from "evt/hooks";
|
||||
//NOTE for later: https://github.com/remarkjs/react-markdown/blob/236182ecf30bd89c1e5a7652acaf8d0bf81e6170/src/renderers.js#L7-L35
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { useGuaranteedMemo } from "powerhooks/useGuaranteedMemo";
|
||||
|
||||
export { kcMessages };
|
||||
|
||||
export type MessageKey = keyof typeof kcMessages["en"];
|
||||
|
||||
function resolveMsg<Key extends string, DoRenderMarkdown extends boolean>(props: {
|
||||
key: Key;
|
||||
args: (string | undefined)[];
|
||||
kcLanguageTag: string;
|
||||
doRenderMarkdown: DoRenderMarkdown;
|
||||
}): Key extends MessageKey ? (DoRenderMarkdown extends true ? JSX.Element : string) : undefined {
|
||||
const { key, args, kcLanguageTag, doRenderMarkdown } = props;
|
||||
|
||||
let str = kcMessages[kcLanguageTag as any as "en"][key as MessageKey] ?? kcMessages["en"][key as MessageKey];
|
||||
|
||||
if (str === undefined) {
|
||||
return undefined as any;
|
||||
}
|
||||
|
||||
str = (() => {
|
||||
const startIndex = str
|
||||
.match(/{[0-9]+}/g)
|
||||
?.map(g => g.match(/{([0-9]+)}/)![1])
|
||||
.map(indexStr => parseInt(indexStr))
|
||||
.sort((a, b) => a - b)[0];
|
||||
|
||||
if (startIndex === undefined) {
|
||||
return str;
|
||||
}
|
||||
|
||||
args.forEach((arg, i) => {
|
||||
if (arg === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
str = str.replace(new RegExp(`\\{${i + startIndex}\\}`, "g"), arg);
|
||||
});
|
||||
|
||||
return str;
|
||||
})();
|
||||
|
||||
return (
|
||||
doRenderMarkdown ? (
|
||||
<ReactMarkdown allowDangerousHtml renderers={key === "termsText" ? undefined : { "paragraph": "span" }}>
|
||||
{str}
|
||||
</ReactMarkdown>
|
||||
) : (
|
||||
str
|
||||
)
|
||||
) as any;
|
||||
}
|
||||
|
||||
function resolveMsgAdvanced<Key extends string, DoRenderMarkdown extends boolean>(props: {
|
||||
key: Key;
|
||||
args: (string | undefined)[];
|
||||
kcLanguageTag: string;
|
||||
doRenderMarkdown: DoRenderMarkdown;
|
||||
}): DoRenderMarkdown extends true ? JSX.Element : string {
|
||||
const { key, args, kcLanguageTag, doRenderMarkdown } = props;
|
||||
|
||||
const match = key.match(/^\$\{([^{]+)\}$/);
|
||||
|
||||
const keyUnwrappedFromCurlyBraces = match === null ? key : match[1];
|
||||
|
||||
const out = resolveMsg({
|
||||
"key": keyUnwrappedFromCurlyBraces,
|
||||
args,
|
||||
kcLanguageTag,
|
||||
doRenderMarkdown,
|
||||
});
|
||||
|
||||
return (out !== undefined ? out : doRenderMarkdown ? <span>{keyUnwrappedFromCurlyBraces}</span> : keyUnwrappedFromCurlyBraces) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the language is switched the page is reloaded, this may appear
|
||||
* as a bug as you might notice that the language successfully switch before
|
||||
* reload.
|
||||
* However we need to tell Keycloak that the user have changed the language
|
||||
* during login so we can retrieve the "local" field of the JWT encoded accessToken.
|
||||
* https://user-images.githubusercontent.com/6702424/138096682-351bb61f-f24e-4caf-91b7-cca8cfa2cb58.mov
|
||||
*
|
||||
* advancedMsg("${access-denied}") === advancedMsg("access-denied") === msg("access-denied")
|
||||
* advancedMsg("${not-a-message-key}") === advancedMsg(not-a-message-key") === "not-a-message-key"
|
||||
*
|
||||
*/
|
||||
export function useKcMessage() {
|
||||
const { kcLanguageTag } = useKcLanguageTag();
|
||||
|
||||
const [trigger, forceUpdate] = useReducer((counter: number) => counter + 1, 0);
|
||||
|
||||
useEvt(ctx => evtTermsUpdated.attach(ctx, forceUpdate), []);
|
||||
|
||||
return useGuaranteedMemo(
|
||||
() => ({
|
||||
"msgStr": (key: MessageKey, ...args: (string | undefined)[]): string =>
|
||||
resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": false }),
|
||||
"msg": (key: MessageKey, ...args: (string | undefined)[]): JSX.Element =>
|
||||
resolveMsg({ key, args, kcLanguageTag, "doRenderMarkdown": true }),
|
||||
"advancedMsg": <Key extends string>(key: Key, ...args: (string | undefined)[]): JSX.Element =>
|
||||
resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": true }),
|
||||
"advancedMsgStr": <Key extends string>(key: Key, ...args: (string | undefined)[]): string =>
|
||||
resolveMsgAdvanced({ key, args, kcLanguageTag, "doRenderMarkdown": false }),
|
||||
}),
|
||||
[kcLanguageTag, trigger],
|
||||
);
|
||||
}
|
@ -1,18 +1,11 @@
|
||||
export * from "./getKcContext";
|
||||
|
||||
export * from "./i18n/KcLanguageTag";
|
||||
export * from "./i18n/useKcLanguageTag";
|
||||
export * from "./i18n/useKcMessage";
|
||||
export * from "./i18n/kcMessages/login";
|
||||
export * from "./i18n";
|
||||
|
||||
export { useDownloadTerms } from "./components/Terms";
|
||||
|
||||
export * from "./components/KcProps";
|
||||
export * from "./components/Login";
|
||||
export * from "./components/Template";
|
||||
export * from "./components/KcApp";
|
||||
export * from "./components/Info";
|
||||
export * from "./components/Error";
|
||||
export * from "./components/LoginResetPassword";
|
||||
export * from "./components/LoginVerifyEmail";
|
||||
export * from "./components/KcProps";
|
||||
export * from "./keycloakJsAdapter";
|
||||
export * from "./useFormValidationSlice";
|
||||
|
||||
|
@ -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 => {
|
||||
|
@ -1,35 +1,30 @@
|
||||
import "./tools/Array.prototype.every";
|
||||
import { useMemo, useReducer, Fragment } from "react";
|
||||
import type { KcContextBase, Validators, Attribute } from "./getKcContext/KcContextBase";
|
||||
import { useKcMessage } from "./i18n/useKcMessage";
|
||||
import { getMsg } from "./i18n";
|
||||
import type { KcLanguageTag } from "./i18n";
|
||||
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||
import { id } from "tsafe/id";
|
||||
import type { MessageKey } from "./i18n/useKcMessage";
|
||||
import type { MessageKey } from "./i18n";
|
||||
import { emailRegexp } from "./tools/emailRegExp";
|
||||
|
||||
export type KcContextLike = {
|
||||
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
||||
attributes: { name: string; value?: string; validators: Validators }[];
|
||||
passwordRequired: boolean;
|
||||
realm: { registrationEmailAsUsername: boolean };
|
||||
};
|
||||
|
||||
export function useGetErrors(params: {
|
||||
kcContext: {
|
||||
messagesPerField: Pick<KcContextBase.Common["messagesPerField"], "existsError" | "get">;
|
||||
profile: {
|
||||
attributes: { name: string; value?: string; validators: Validators }[];
|
||||
};
|
||||
locale?: { currentLanguageTag: KcLanguageTag };
|
||||
};
|
||||
}) {
|
||||
const {
|
||||
kcContext: {
|
||||
messagesPerField,
|
||||
profile: { attributes },
|
||||
},
|
||||
} = params;
|
||||
const { kcContext } = params;
|
||||
|
||||
const { msg, msgStr, advancedMsg, advancedMsgStr } = useKcMessage();
|
||||
const {
|
||||
messagesPerField,
|
||||
profile: { attributes },
|
||||
} = kcContext;
|
||||
|
||||
const { msg, msgStr, advancedMsg, advancedMsgStr } = getMsg(kcContext);
|
||||
|
||||
const getErrors = useConstCallback((params: { name: string; fieldValueByAttributeName: Record<string, { value: string }> }) => {
|
||||
const { name, fieldValueByAttributeName } = params;
|
||||
@ -213,7 +208,7 @@ export function useGetErrors(params: {
|
||||
break scope;
|
||||
}
|
||||
|
||||
const msgArgs = ["invalidEmailMessage"] as const;
|
||||
const msgArgs = [id<MessageKey>("invalidEmailMessage")] as const;
|
||||
|
||||
errors.push({
|
||||
validatorName,
|
||||
@ -276,6 +271,32 @@ export function useGetErrors(params: {
|
||||
}
|
||||
}
|
||||
|
||||
scope: {
|
||||
const validatorName = "options";
|
||||
|
||||
const validator = validators[validatorName];
|
||||
|
||||
if (validator === undefined) {
|
||||
break scope;
|
||||
}
|
||||
|
||||
if (value === "") {
|
||||
break scope;
|
||||
}
|
||||
|
||||
if (validator.options.indexOf(value) >= 0) {
|
||||
break scope;
|
||||
}
|
||||
|
||||
const msgArgs = [id<MessageKey>("notAValidOption")] as const;
|
||||
|
||||
errors.push({
|
||||
validatorName,
|
||||
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
|
||||
"errorMessageStr": advancedMsgStr(...msgArgs),
|
||||
});
|
||||
}
|
||||
|
||||
//TODO: Implement missing validators.
|
||||
|
||||
return errors;
|
||||
@ -292,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;
|
||||
@ -361,6 +385,7 @@ export function useFormValidationSlice(params: {
|
||||
"profile": {
|
||||
"attributes": attributesWithPassword,
|
||||
},
|
||||
"locale": kcContext.locale,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -8,6 +8,7 @@ generateKeycloakThemeResources({
|
||||
"themeName": "keycloakify-demo-app",
|
||||
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
|
||||
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
|
||||
"keycloakThemeEmailDirPath": pathJoin(sampleReactProjectDirPath, "keycloak_email"),
|
||||
"urlPathname": "/keycloakify-demo-app/",
|
||||
"urlOrigin": undefined,
|
||||
"extraPagesId": ["my-custom-page.ftl"],
|
||||
|
1
src/test/bin/index.ts
Normal file
1
src/test/bin/index.ts
Normal file
@ -0,0 +1 @@
|
||||
import "./replaceImportFromStatic";
|
@ -1,3 +1,4 @@
|
||||
import "./replaceImportFromStatic";
|
||||
import { setupSampleReactProject, sampleReactProjectDirPath } from "./setupSampleReactProject";
|
||||
import * as st from "scripting-tools";
|
||||
import { join as pathJoin } from "path";
|
||||
|
@ -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");
|
||||
|
7
src/test/tools/assertIsSameCode.ts
Normal file
7
src/test/tools/assertIsSameCode.ts
Normal 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);
|
||||
}
|
Reference in New Issue
Block a user