Added react-oidc (use demo/demo)

Added an example of an authentication protected page (tenders)
Added an example with the built in proxy (to combat CORS) (tendersguru)
This commit is contained in:
Rick van Lieshout 2022-07-26 11:15:36 +02:00
parent 8496f5cfbe
commit b9d3025163
24 changed files with 400 additions and 33 deletions

View File

@ -5,13 +5,16 @@
"deepmerge", "deepmerge",
"flexbugs", "flexbugs",
"Immer", "Immer",
"Keycloak",
"languagedetector", "languagedetector",
"luxon", "luxon",
"oidc",
"pmmmwh", "pmmmwh",
"preinstall", "preinstall",
"reduxjs", "reduxjs",
"SVGR", "SVGR",
"tailwindcss", "tailwindcss",
"tendersguru",
"testid", "testid",
"typeahead", "typeahead",
"uncompiled" "uncompiled"

View File

@ -68,5 +68,9 @@
"", "",
"export default ${1:${TM_FILENAME_BASE}}Slice.reducer;" "export default ${1:${TM_FILENAME_BASE}}Slice.reducer;"
] ]
},
"react-i18next useTranslate hook": {
"prefix": ["useTranslation", "translate", "i18-trans"],
"body": ["const [translate] = useTranslation();"]
} }
} }

View File

@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.5.0] - 2022-06-26
- Added react-oidc (use demo/demo)
- Added an example of an authentication protected page (tenders)
- Added an example with the built in proxy (to combat CORS) (tendersguru)
## [0.4.1] - 2022-06-26 ## [0.4.1] - 2022-06-26
- Moved examples into example directory - Moved examples into example directory

138
package-lock.json generated
View File

@ -10,6 +10,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@axa-fr/react-oidc": "^6.0.0-beta10",
"@reduxjs/toolkit": "^1.8.3", "@reduxjs/toolkit": "^1.8.3",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"i18next-http-backend": "^1.4.1", "i18next-http-backend": "^1.4.1",
@ -60,6 +61,7 @@
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"http-proxy-middleware": "^2.0.6",
"husky": "^8.0.1", "husky": "^8.0.1",
"i18next": "^21.8.14", "i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4", "i18next-browser-languagedetector": "^6.1.4",
@ -112,6 +114,18 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@axa-fr/react-oidc": {
"version": "6.0.0-beta10",
"resolved": "https://registry.npmjs.org/@axa-fr/react-oidc/-/react-oidc-6.0.0-beta10.tgz",
"integrity": "sha512-BWHuIn9b+5O35It+V3m/8MWAV6pOjoMy2SihKfeDlsQs2Bp4HMBb4x46zygZ1IGs/u43jtTsnZkrPhwJU6VdqQ==",
"dependencies": {
"@openid/appauth": "1.3.1"
},
"peerDependencies": {
"react": "x",
"react-dom": "x"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.18.6", "version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@ -3309,6 +3323,32 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@openid/appauth": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@openid/appauth/-/appauth-1.3.1.tgz",
"integrity": "sha512-e54kpi219wES2ijPzeHe1kMnT8VKH8YeTd1GAn9BzVBmutz3tBgcG1y8a4pziNr4vNjFnuD4W446Ua7ELnNDiA==",
"dependencies": {
"@types/base64-js": "^1.3.0",
"@types/jquery": "^3.5.5",
"base64-js": "^1.5.1",
"follow-redirects": "^1.13.3",
"form-data": "^4.0.0",
"opener": "^1.5.2"
}
},
"node_modules/@openid/appauth/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@pmmmwh/react-refresh-webpack-plugin": { "node_modules/@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.5.7", "version": "0.5.7",
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz",
@ -4019,6 +4059,11 @@
"@babel/types": "^7.3.0" "@babel/types": "^7.3.0"
} }
}, },
"node_modules/@types/base64-js": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/base64-js/-/base64-js-1.3.0.tgz",
"integrity": "sha512-ZmI0sZGAUNXUfMWboWwi4LcfpoVUYldyN6Oe0oJ5cCsHDU/LlRq8nQKPXhYLOx36QYSW9bNIb1vvRrD6K7Llgw=="
},
"node_modules/@types/body-parser": { "node_modules/@types/body-parser": {
"version": "1.19.2", "version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@ -4203,6 +4248,14 @@
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
"dev": true "dev": true
}, },
"node_modules/@types/jquery": {
"version": "3.5.14",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz",
"integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==",
"dependencies": {
"@types/sizzle": "*"
}
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.11", "version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -4338,8 +4391,7 @@
"node_modules/@types/sizzle": { "node_modules/@types/sizzle": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ=="
"dev": true
}, },
"node_modules/@types/sockjs": { "node_modules/@types/sockjs": {
"version": "0.3.33", "version": "0.3.33",
@ -5252,8 +5304,7 @@
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
"dev": true
}, },
"node_modules/at-least-node": { "node_modules/at-least-node": {
"version": "1.0.0", "version": "1.0.0",
@ -5666,7 +5717,6 @@
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -6354,7 +6404,6 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"dependencies": { "dependencies": {
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
}, },
@ -7540,7 +7589,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"engines": { "engines": {
"node": ">=0.4.0" "node": ">=0.4.0"
} }
@ -9636,7 +9684,6 @@
"version": "1.15.1", "version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -15653,6 +15700,14 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"bin": {
"opener": "bin/opener-bin.js"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.1", "version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@ -21337,6 +21392,14 @@
"@jridgewell/trace-mapping": "^0.3.9" "@jridgewell/trace-mapping": "^0.3.9"
} }
}, },
"@axa-fr/react-oidc": {
"version": "6.0.0-beta10",
"resolved": "https://registry.npmjs.org/@axa-fr/react-oidc/-/react-oidc-6.0.0-beta10.tgz",
"integrity": "sha512-BWHuIn9b+5O35It+V3m/8MWAV6pOjoMy2SihKfeDlsQs2Bp4HMBb4x46zygZ1IGs/u43jtTsnZkrPhwJU6VdqQ==",
"requires": {
"@openid/appauth": "1.3.1"
}
},
"@babel/code-frame": { "@babel/code-frame": {
"version": "7.18.6", "version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@ -23588,6 +23651,31 @@
"fastq": "^1.6.0" "fastq": "^1.6.0"
} }
}, },
"@openid/appauth": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@openid/appauth/-/appauth-1.3.1.tgz",
"integrity": "sha512-e54kpi219wES2ijPzeHe1kMnT8VKH8YeTd1GAn9BzVBmutz3tBgcG1y8a4pziNr4vNjFnuD4W446Ua7ELnNDiA==",
"requires": {
"@types/base64-js": "^1.3.0",
"@types/jquery": "^3.5.5",
"base64-js": "^1.5.1",
"follow-redirects": "^1.13.3",
"form-data": "^4.0.0",
"opener": "^1.5.2"
},
"dependencies": {
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
}
}
},
"@pmmmwh/react-refresh-webpack-plugin": { "@pmmmwh/react-refresh-webpack-plugin": {
"version": "0.5.7", "version": "0.5.7",
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz",
@ -24067,6 +24155,11 @@
"@babel/types": "^7.3.0" "@babel/types": "^7.3.0"
} }
}, },
"@types/base64-js": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/base64-js/-/base64-js-1.3.0.tgz",
"integrity": "sha512-ZmI0sZGAUNXUfMWboWwi4LcfpoVUYldyN6Oe0oJ5cCsHDU/LlRq8nQKPXhYLOx36QYSW9bNIb1vvRrD6K7Llgw=="
},
"@types/body-parser": { "@types/body-parser": {
"version": "1.19.2", "version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@ -24244,6 +24337,14 @@
} }
} }
}, },
"@types/jquery": {
"version": "3.5.14",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz",
"integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==",
"requires": {
"@types/sizzle": "*"
}
},
"@types/json-schema": { "@types/json-schema": {
"version": "7.0.11", "version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -24379,8 +24480,7 @@
"@types/sizzle": { "@types/sizzle": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ=="
"dev": true
}, },
"@types/sockjs": { "@types/sockjs": {
"version": "0.3.33", "version": "0.3.33",
@ -25058,8 +25158,7 @@
"asynckit": { "asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
"dev": true
}, },
"at-least-node": { "at-least-node": {
"version": "1.0.0", "version": "1.0.0",
@ -25369,8 +25468,7 @@
"base64-js": { "base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
"dev": true
}, },
"batch": { "batch": {
"version": "0.6.1", "version": "0.6.1",
@ -25874,7 +25972,6 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"requires": { "requires": {
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
} }
@ -26742,8 +26839,7 @@
"delayed-stream": { "delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
"dev": true
}, },
"depd": { "depd": {
"version": "2.0.0", "version": "2.0.0",
@ -28331,8 +28427,7 @@
"follow-redirects": { "follow-redirects": {
"version": "1.15.1", "version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
"dev": true
}, },
"forever-agent": { "forever-agent": {
"version": "0.6.1", "version": "0.6.1",
@ -32760,6 +32855,11 @@
"is-wsl": "^2.2.0" "is-wsl": "^2.2.0"
} }
}, },
"opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="
},
"optionator": { "optionator": {
"version": "0.9.1", "version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "react-starter-kit", "name": "react-starter-kit",
"version": "0.4.1", "version": "0.5.0",
"description": "A modern, create-react-app-based, starter kit for React projects", "description": "A modern, create-react-app-based, starter kit for React projects",
"keywords": [ "keywords": [
"react", "react",
@ -45,6 +45,7 @@
"*.{ts,js,jsx,tsx,css,scss,json,md}": "prettier --write" "*.{ts,js,jsx,tsx,css,scss,json,md}": "prettier --write"
}, },
"dependencies": { "dependencies": {
"@axa-fr/react-oidc": "^6.0.0-beta10",
"@reduxjs/toolkit": "^1.8.3", "@reduxjs/toolkit": "^1.8.3",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"i18next-http-backend": "^1.4.1", "i18next-http-backend": "^1.4.1",
@ -95,6 +96,7 @@
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"http-proxy-middleware": "^2.0.6",
"husky": "^8.0.1", "husky": "^8.0.1",
"i18next": "^21.8.14", "i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4", "i18next-browser-languagedetector": "^6.1.4",

View File

@ -1,6 +1,24 @@
const defaultConfig = { const defaultConfig = {
version: "0.1.0", version: "0.1.0",
name: "React-starter-kit", name: "React-starter-kit",
// oidc: {
// url: "private_url",
// realm: "inforit",
// clientId: "react-base",
// scope: "openid profile email",
// redirectUri: "http://localhost:3000/authentication/callback",
// silentRedirectUri: "http://localhost:3000/authentication/silent-callback",
// serviceWorkerOnly: false,
// },
oidc: {
url: "https://sso.mastermindzh.tech/realms/public-tests",
realm: "public-tests",
clientId: "react-starter-kit",
scope: "openid profile email",
redirectUri: "http://localhost:3000/authentication/callback",
silentRedirectUri: "http://localhost:3000/authentication/silent-callback",
serviceWorkerOnly: false,
},
}; };
// ignore this :) // ignore this :)

View File

@ -10,7 +10,8 @@
"nav": { "nav": {
"home": "home", "home": "home",
"about": "about", "about": "about",
"counter": "counter" "counter": "counter",
"tenders": "tenders (with auth)"
}, },
"about": { "about": {
"title": "About" "title": "About"

View File

@ -10,7 +10,8 @@
"nav": { "nav": {
"home": "home", "home": "home",
"about": "over ons", "about": "over ons",
"counter": "teller" "counter": "teller",
"tenders": "aanbestedingen (met auth)"
}, },
"about": { "about": {
"title": "Over ons" "title": "Over ons"

View File

@ -1,7 +1,9 @@
import { OidcSecure } from "@axa-fr/react-oidc";
import { FunctionComponent } from "react"; import { FunctionComponent } from "react";
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import { AboutContainer } from "./features/about/About"; import { AboutContainer } from "./features/about/About";
import { CounterContainer } from "./features/examples/counter/Counter"; import { CounterContainer } from "./features/examples/counter/Counter";
import { Tenders } from "./features/examples/tenders/Tenders";
import { HomeContainer } from "./features/home/Home"; import { HomeContainer } from "./features/home/Home";
type Props = {}; type Props = {};
@ -9,15 +11,25 @@ export const ROUTE_KEYS = {
home: "", home: "",
about: "about", about: "about",
counter: "counter", counter: "counter",
tenders: "tenders",
}; };
export const AppRoutes: FunctionComponent<Props> = () => { export const AppRoutes: FunctionComponent<Props> = () => {
const { home, about, counter } = ROUTE_KEYS; const { home, about, counter, tenders } = ROUTE_KEYS;
return ( return (
<Routes> <Routes>
<Route path={home} element={<HomeContainer />} /> <Route path={home} element={<HomeContainer />} />
<Route path={about} element={<AboutContainer />} /> <Route path={about} element={<AboutContainer />} />
<Route path={counter} element={<CounterContainer />} /> <Route path={counter} element={<CounterContainer />} />
{/* a route with authentication */}
<Route
path={tenders}
element={
<OidcSecure>
<Tenders />
</OidcSecure>
}
/>
{/* <Route index element={<Home />} /> */} {/* <Route index element={<Home />} /> */}
{/* <Route path="teams" element={<Teams />}> {/* <Route path="teams" element={<Teams />}>
<Route path=":teamId" element={<Team />} /> <Route path=":teamId" element={<Team />} />

View File

@ -0,0 +1,33 @@
import { useOidcAccessToken } from "@axa-fr/react-oidc";
import { FunctionComponent, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
type Props = {};
export const Tenders: FunctionComponent<Props> = () => {
const [tenders, setTenders] = useState(null);
const [translate] = useTranslation();
const { accessToken } = useOidcAccessToken();
useEffect(() => {
// this uses the CORS proxy in our webpack setup
// tendersguru is mapped to "https://tenders.guru" in setupProxy.js
// it will strip the tendersguru part, so the full url will be: https://tenders.guru/api/ro/tenders
fetch("tendersguru/api/ro/tenders", {
// fetch's way of adding headers. Not required to access the api... but :shrug:
headers: new Headers({
Authorization: `Bearer ${accessToken}`,
}),
})
.then((response) => response.json())
.then((data) => {
setTenders(data);
});
}, [accessToken]);
return (
<>
<h1>{translate("nav.tenders")}</h1>
{tenders ? <pre>{JSON.stringify(tenders, null, 2)}</pre> : <h2>loading...</h2>}
</>
);
};

View File

@ -5,6 +5,7 @@ import App from "./App";
import { store } from "./app/store"; import { store } from "./app/store";
import "./index.css"; import "./index.css";
import "./infrastructure/i18n/init"; import "./infrastructure/i18n/init";
import { OidcProvider } from "./infrastructure/sso/OidcProvider";
import { Loader } from "./infrastructure/wrappers/WithPageSuspense"; import { Loader } from "./infrastructure/wrappers/WithPageSuspense";
import reportWebVitals from "./reportWebVitals"; import reportWebVitals from "./reportWebVitals";
@ -14,9 +15,11 @@ const root = createRoot(container);
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<Provider store={store}> <Provider store={store}>
<OidcProvider>
<Loader> <Loader>
<App /> <App />
</Loader> </Loader>
</OidcProvider>
</Provider> </Provider>
</React.StrictMode>, </React.StrictMode>,
); );

View File

@ -1,4 +1,11 @@
import { OIDCConfig } from "../sso/models/OIDCConfig";
export interface RunTimeConfig { export interface RunTimeConfig {
version: number; version: number;
name: string; name: string;
/**
* Settings for the OIDC connection
*/
oidc: OIDCConfig;
} }

View File

@ -1,18 +1,25 @@
import { render, screen } from "@testing-library/react"; import { render, screen, waitFor } from "@testing-library/react";
import { WithTestTranslations } from "../../app/tests/mocks/i18n/WithTestTranslations"; import { WithTestTranslations } from "../../app/tests/mocks/i18n/WithTestTranslations";
import { OidcProvider } from "../sso/OidcProvider";
import { WithRouter } from "../wrappers/WithRouter"; import { WithRouter } from "../wrappers/WithRouter";
import { Navbar } from "./Navbar"; import { Navbar } from "./Navbar";
describe("Navbar container", () => { describe("Navbar container", () => {
it.only("renders a navigation section identified by the nav test-id", () => { it.only("renders a navigation section identified by the nav test-id", async () => {
render( render(
<WithTestTranslations> <WithTestTranslations>
{/* for simple tests where we don't need auth we don't actually have to mock responses */}
<OidcProvider>
<WithRouter> <WithRouter>
<Navbar /> <Navbar />
</WithRouter> </WithRouter>
</OidcProvider>
</WithTestTranslations>, </WithTestTranslations>,
); );
// because of the extra loaders we wait for a result just to be sure
// see: https://testing-library.com/docs/guide-disappearance/
await waitFor(() => {
expect(screen.getAllByTestId("nav")?.length).toBeGreaterThan(0); expect(screen.getAllByTestId("nav")?.length).toBeGreaterThan(0);
}); });
}); });
});

View File

@ -1,3 +1,4 @@
import { useOidc, useOidcAccessToken } from "@axa-fr/react-oidc";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { FunctionComponent } from "react"; import { FunctionComponent } from "react";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
@ -9,6 +10,8 @@ type Props = {};
export const Navbar: FunctionComponent<Props> = () => { export const Navbar: FunctionComponent<Props> = () => {
const [translate, i18n] = useTranslation(); const [translate, i18n] = useTranslation();
const { login, logout, isAuthenticated } = useOidc();
const { accessTokenPayload } = useOidcAccessToken();
const { home, about, counter } = ROUTE_KEYS; const { home, about, counter } = ROUTE_KEYS;
return ( return (
<> <>
@ -17,6 +20,13 @@ export const Navbar: FunctionComponent<Props> = () => {
{/* trans can also be used to translate */} {/* trans can also be used to translate */}
{Config.name} <Trans i18nKey="navBar.version">version:</Trans> {Config.name} <Trans i18nKey="navBar.version">version:</Trans>
{JSON.stringify(Config.version)} {JSON.stringify(Config.version)}
<button
onClick={() => {
isAuthenticated ? logout() : login("/");
}}
>
{isAuthenticated ? `logout (${accessTokenPayload.email})` : "login"}
</button>
</p> </p>
{/* This translation uses a formatter in the translation files */} {/* This translation uses a formatter in the translation files */}
@ -31,6 +41,9 @@ export const Navbar: FunctionComponent<Props> = () => {
<Link to={counter} data-testid="nav.counter"> <Link to={counter} data-testid="nav.counter">
{translate("nav.counter")} {translate("nav.counter")}
</Link> </Link>
<Link to="/tenders" data-testid="nav.tenders">
{translate("nav.tenders")}
</Link>
<button onClick={() => i18n.changeLanguage("en")}>en</button> <button onClick={() => i18n.changeLanguage("en")}>en</button>
<button onClick={() => i18n.changeLanguage("nl")}>nl</button> <button onClick={() => i18n.changeLanguage("nl")}>nl</button>
<hr /> <hr />

View File

@ -0,0 +1,14 @@
import { OidcConfiguration } from "@axa-fr/react-oidc/dist/vanilla/oidc";
import { Config } from "../config";
const { clientId, redirectUri, silentRedirectUri, scope, url } = Config.oidc;
/* eslint-disable camelcase */
export const SSOConfiguration: OidcConfiguration = {
client_id: clientId,
redirect_uri: redirectUri,
silent_redirect_uri: silentRedirectUri,
scope,
authority: url,
service_worker_only: false,
};

View File

@ -0,0 +1,28 @@
import { OidcProvider as ReactOidcProvider } from "@axa-fr/react-oidc";
import { FunctionComponent, ReactNode } from "react";
import { AppLoader } from "../loader/appLoader";
import { SSOConfiguration } from "./Configuration";
import { Authenticating } from "./overrides/Authenticating";
import { AuthenticatingError } from "./overrides/AuthenticatingError";
import { CallBackSuccess } from "./overrides/CallBackSuccess";
import { ServiceWorkerNotSupported } from "./overrides/ServiceWorkerNotSupported";
import { SessionLost } from "./overrides/SessionLost";
type Props = { children?: ReactNode; [x: string]: any };
export const OidcProvider: FunctionComponent<Props> = ({ children, ...rest }) => {
return (
<ReactOidcProvider
loadingComponent={AppLoader}
authenticatingErrorComponent={AuthenticatingError}
authenticatingComponent={Authenticating}
sessionLostComponent={SessionLost}
serviceWorkerNotSupportedComponent={ServiceWorkerNotSupported}
callbackSuccessComponent={CallBackSuccess}
configuration={SSOConfiguration}
{...rest}
>
{children}
</ReactOidcProvider>
);
};

View File

@ -0,0 +1,31 @@
export interface OIDCConfig {
/**
* Authority URL
*/
url: string;
/**
* Realm to authenticate against
*/
realm: string;
/**
* Id of this client
*/
clientId: string;
/**
* Scope(s) that you want to request
*/
scope: string;
/**
* URI to redirect to after successful login
*/
redirectUri: string;
/**
* redirect uri for the silent refresh
*/
silentRedirectUri: string;
}

View File

@ -0,0 +1,3 @@
export interface SSOResult {
configurationName: string;
}

View File

@ -0,0 +1,9 @@
import { FunctionComponent } from "react";
import { SSOResult } from "../models/SSOResult";
export const Authenticating: FunctionComponent<SSOResult> = ({ configurationName }) => (
<>
<h1>Authentication in progress for {configurationName}</h1>
<p>You will be redirected to the login page.</p>
</>
);

View File

@ -0,0 +1,9 @@
import { FunctionComponent } from "react";
import { SSOResult } from "../models/SSOResult";
export const AuthenticatingError: FunctionComponent<SSOResult> = ({ configurationName }) => (
<>
<h1>Error for {configurationName}</h1>
<p>An error occurred during authentication.</p>
</>
);

View File

@ -0,0 +1,9 @@
import { FunctionComponent } from "react";
import { SSOResult } from "../models/SSOResult";
export const CallBackSuccess: FunctionComponent<SSOResult> = ({ configurationName }) => (
<>
<h1>Authentication complete for {configurationName}</h1>
<p>You will be redirected...</p>
</>
);

View File

@ -0,0 +1,9 @@
import { FunctionComponent } from "react";
import { SSOResult } from "../models/SSOResult";
export const ServiceWorkerNotSupported: FunctionComponent<SSOResult> = ({ configurationName }) => (
<>
<h1>Unable to authenticate on this browser for {configurationName}</h1>
<p>Your browser is not configured to support Service Workers.</p>
</>
);

View File

@ -0,0 +1,17 @@
import { useOidc } from "@axa-fr/react-oidc";
import { FunctionComponent } from "react";
import { SSOResult } from "../models/SSOResult";
export const SessionLost: FunctionComponent<SSOResult> = ({ configurationName }) => {
const { login } = useOidc(configurationName);
return (
<>
<h1>Session timed out for {configurationName}</h1>
<p>Your session has expired. Please re-authenticate.</p>
<button type="button" onClick={() => login("/")}>
Login
</button>
</>
);
};

28
src/setupProxy.js Normal file
View File

@ -0,0 +1,28 @@
const { createProxyMiddleware } = require("http-proxy-middleware");
/**
* Create a simple proxy that automatically removes the prefix endpoint
* @param {*} app app to configure proxy on
* @param {*} endpoint endpoint you want to use for the proxy
* @param {*} target proxy target
*/
const createSimpleProxy = (app, endpoint, target) => {
app.use(
endpoint,
createProxyMiddleware({
target,
changeOrigin: true,
pathRewrite: {
[`^${endpoint}`]: "",
},
}),
);
};
/**
* Actual proxy configuration
* @param {*} app app to configure proxy on
*/
module.exports = function (app) {
createSimpleProxy(app, "/tendersguru", "https://tenders.guru");
};