From e8bf52dee75d6e12e7972f01e8c70e6891e6325b Mon Sep 17 00:00:00 2001
From: andrej romanov <50377758+auumgn@users.noreply.github.com>
Date: Tue, 26 Mar 2024 14:56:57 +0200
Subject: [PATCH 1/3] add landing page content
---
ui/package-lock.json | 152 ++++++----
ui/package.json | 3 +
ui/src/app/app.module.ts | 3 -
.../landing-page/landing-page.component.html | 71 +++++
.../landing-page.component.spec.ts | 21 ++
.../landing-page/landing-page.component.ts | 269 ++++++++++++++++++
.../app/landing-page/landing-page.module.ts | 25 ++
ui/src/app/landing-page/landing-page.route.ts | 11 +
.../app/landing-page/landing-page.service.ts | 48 ++++
9 files changed, 546 insertions(+), 57 deletions(-)
create mode 100644 ui/src/app/landing-page/landing-page.component.html
create mode 100644 ui/src/app/landing-page/landing-page.component.spec.ts
create mode 100644 ui/src/app/landing-page/landing-page.component.ts
create mode 100644 ui/src/app/landing-page/landing-page.module.ts
create mode 100644 ui/src/app/landing-page/landing-page.route.ts
create mode 100644 ui/src/app/landing-page/landing-page.service.ts
diff --git a/ui/package-lock.json b/ui/package-lock.json
index d74369555..744e83d18 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -22,6 +22,8 @@
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@ng-bootstrap/ng-bootstrap": "^15.1.1",
"bootstrap": "^5.3.2",
+ "jsrsasign": "^11.1.0",
+ "jsrsasign-util": "^1.0.5",
"moment": "^2.29.4",
"ngx-clipboard": "^16.0.0",
"ngx-webstorage": "^12.0.0",
@@ -39,6 +41,7 @@
"@angular/cli": "~16.2.6",
"@angular/compiler-cli": "^16.2.9",
"@types/jasmine": "~4.0.0",
+ "@types/jsrsasign": "^10.5.13",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"eslint": "^8.49.0",
@@ -4864,6 +4867,12 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
+ "node_modules/@types/jsrsasign": {
+ "version": "10.5.13",
+ "resolved": "https://registry.npmjs.org/@types/jsrsasign/-/jsrsasign-10.5.13.tgz",
+ "integrity": "sha512-vvVHLrXxoUZgBWTcJnTMSC4FAQcG2loK7N1Uy20I3nr/aUhetbGdfuwSzXkrMoll2RoYKW0IcMIN0I0bwMwVMQ==",
+ "dev": true
+ },
"node_modules/@types/mime": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
@@ -6015,13 +6024,13 @@
}
},
"node_modules/body-parser": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
- "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dev": true,
"dependencies": {
"bytes": "3.1.2",
- "content-type": "~1.0.4",
+ "content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@@ -6029,7 +6038,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
- "raw-body": "2.5.1",
+ "raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@@ -6600,9 +6609,9 @@
}
},
"node_modules/content-type": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true,
"engines": {
"node": ">= 0.6"
@@ -8071,17 +8080,17 @@
"dev": true
},
"node_modules/express": {
- "version": "4.18.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
- "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+ "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dev": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.20.1",
+ "body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.5.0",
+ "cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -8119,9 +8128,9 @@
"dev": true
},
"node_modules/express/node_modules/cookie": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
- "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true,
"engines": {
"node": ">= 0.6"
@@ -8406,9 +8415,9 @@
"dev": true
},
"node_modules/follow-redirects": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
- "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true,
"funding": [
{
@@ -9924,8 +9933,7 @@
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
- "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
- "dev": true
+ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
},
"node_modules/jsonfile": {
"version": "4.0.0",
@@ -9945,6 +9953,23 @@
"node >= 0.2.0"
]
},
+ "node_modules/jsrsasign": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-11.1.0.tgz",
+ "integrity": "sha512-Ov74K9GihaK9/9WncTe1mPmvrO7Py665TUfUKvraXBpu+xcTWitrtuOwcjf4KMU9maPaYn0OuaWy0HOzy/GBXg==",
+ "funding": {
+ "url": "https://github.com/kjur/jsrsasign#donations"
+ }
+ },
+ "node_modules/jsrsasign-util": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/jsrsasign-util/-/jsrsasign-util-1.0.5.tgz",
+ "integrity": "sha512-e5Kp8aaT5GH2c5X8j4uaJruYmT4GcnaGb47nw8m60YqPywtnOtTISZ9hZgtZ3a+jh7B27bU2LCf3Y32wZyfhtQ==",
+ "dependencies": {
+ "jsonc-parser": ">= 0.0.1",
+ "jsrsasign": ">= 4.8.2"
+ }
+ },
"node_modules/karma": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz",
@@ -12753,9 +12778,9 @@
}
},
"node_modules/raw-body": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
- "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dev": true,
"dependencies": {
"bytes": "3.1.2",
@@ -14913,9 +14938,9 @@
}
},
"node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz",
- "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==",
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
+ "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
"dev": true,
"dependencies": {
"colorette": "^2.0.10",
@@ -18855,6 +18880,12 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
+ "@types/jsrsasign": {
+ "version": "10.5.13",
+ "resolved": "https://registry.npmjs.org/@types/jsrsasign/-/jsrsasign-10.5.13.tgz",
+ "integrity": "sha512-vvVHLrXxoUZgBWTcJnTMSC4FAQcG2loK7N1Uy20I3nr/aUhetbGdfuwSzXkrMoll2RoYKW0IcMIN0I0bwMwVMQ==",
+ "dev": true
+ },
"@types/mime": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
@@ -19730,13 +19761,13 @@
}
},
"body-parser": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
- "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dev": true,
"requires": {
"bytes": "3.1.2",
- "content-type": "~1.0.4",
+ "content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@@ -19744,7 +19775,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
- "raw-body": "2.5.1",
+ "raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@@ -20170,9 +20201,9 @@
}
},
"content-type": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true
},
"convert-source-map": {
@@ -21262,17 +21293,17 @@
"dev": true
},
"express": {
- "version": "4.18.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
- "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+ "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dev": true,
"requires": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
- "body-parser": "1.20.1",
+ "body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.5.0",
+ "cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -21307,9 +21338,9 @@
"dev": true
},
"cookie": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
- "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true
},
"debug": {
@@ -21546,9 +21577,9 @@
"dev": true
},
"follow-redirects": {
- "version": "1.15.5",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
- "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true
},
"foreground-child": {
@@ -22655,8 +22686,7 @@
"jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
- "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
- "dev": true
+ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
},
"jsonfile": {
"version": "4.0.0",
@@ -22673,6 +22703,20 @@
"integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==",
"dev": true
},
+ "jsrsasign": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-11.1.0.tgz",
+ "integrity": "sha512-Ov74K9GihaK9/9WncTe1mPmvrO7Py665TUfUKvraXBpu+xcTWitrtuOwcjf4KMU9maPaYn0OuaWy0HOzy/GBXg=="
+ },
+ "jsrsasign-util": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/jsrsasign-util/-/jsrsasign-util-1.0.5.tgz",
+ "integrity": "sha512-e5Kp8aaT5GH2c5X8j4uaJruYmT4GcnaGb47nw8m60YqPywtnOtTISZ9hZgtZ3a+jh7B27bU2LCf3Y32wZyfhtQ==",
+ "requires": {
+ "jsonc-parser": ">= 0.0.1",
+ "jsrsasign": ">= 4.8.2"
+ }
+ },
"karma": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz",
@@ -24743,9 +24787,9 @@
"dev": true
},
"raw-body": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
- "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dev": true,
"requires": {
"bytes": "3.1.2",
@@ -26350,9 +26394,9 @@
},
"dependencies": {
"webpack-dev-middleware": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz",
- "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==",
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz",
+ "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==",
"dev": true,
"requires": {
"colorette": "^2.0.10",
diff --git a/ui/package.json b/ui/package.json
index 8eae3dc40..a3ddf84ac 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -28,6 +28,8 @@
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@ng-bootstrap/ng-bootstrap": "^15.1.1",
"bootstrap": "^5.3.2",
+ "jsrsasign": "^11.1.0",
+ "jsrsasign-util": "^1.0.5",
"moment": "^2.29.4",
"ngx-clipboard": "^16.0.0",
"ngx-webstorage": "^12.0.0",
@@ -45,6 +47,7 @@
"@angular/cli": "~16.2.6",
"@angular/compiler-cli": "^16.2.9",
"@types/jasmine": "~4.0.0",
+ "@types/jsrsasign": "^10.5.13",
"@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0",
"eslint": "^8.49.0",
diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts
index 7f416c348..70605bbe8 100644
--- a/ui/src/app/app.module.ts
+++ b/ui/src/app/app.module.ts
@@ -6,10 +6,8 @@ import { AppComponent } from './app.component'
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'
import { AccountModule } from './account/account.module'
import { NgxWebstorageModule } from 'ngx-webstorage'
-import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
import { NavbarComponent } from './layout/navbar/navbar.component'
import { CommonModule } from '@angular/common'
-import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { HomeModule } from './home/home.module'
import { FooterComponent } from './layout/footer/footer.component'
import { SharedModule } from './shared/shared.module'
@@ -18,7 +16,6 @@ import { ErrorService } from './error/service/error.service'
import { ErrorComponent } from './error/error.component'
import { FormsModule } from '@angular/forms'
import { UserModule } from './user/user.module'
-import { AffiliationsComponent } from './affiliation/affiliations.component'
import { AffiliationModule } from './affiliation/affiliation.module'
@NgModule({
diff --git a/ui/src/app/landing-page/landing-page.component.html b/ui/src/app/landing-page/landing-page.component.html
new file mode 100644
index 000000000..3d654fb5a
--- /dev/null
+++ b/ui/src/app/landing-page/landing-page.component.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+ {{ linkAlreadyUsedMessage }}
+
+
+
+
+ Oops, something went wrong and we were not able to fetch your ORCID iD
+
+
+
+
+ Oops, you have denied access.
+ {{ clientName }}
+ will not be able to update your ORCID record.
+
+
+ If this was a mistake, click the button below to grant access.
+
+
+ {{ allowToUpdateRecordMessage }}
+
+
+
+
+
+
+
+
diff --git a/ui/src/app/landing-page/landing-page.component.spec.ts b/ui/src/app/landing-page/landing-page.component.spec.ts
new file mode 100644
index 000000000..5b7ad0b17
--- /dev/null
+++ b/ui/src/app/landing-page/landing-page.component.spec.ts
@@ -0,0 +1,21 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LandingPageComponent } from './landing-page.component';
+
+describe('LandingPageComponent', () => {
+ let component: LandingPageComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [LandingPageComponent]
+ });
+ fixture = TestBed.createComponent(LandingPageComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/ui/src/app/landing-page/landing-page.component.ts b/ui/src/app/landing-page/landing-page.component.ts
new file mode 100644
index 000000000..04a427fb1
--- /dev/null
+++ b/ui/src/app/landing-page/landing-page.component.ts
@@ -0,0 +1,269 @@
+import { Component, OnInit } from '@angular/core'
+import { HttpErrorResponse, HttpResponse } from '@angular/common/http'
+import { interval } from 'rxjs'
+import { KEYUTIL, KJUR, RSAKey } from 'jsrsasign'
+import { LandingPageService } from './landing-page.service'
+import { MemberService } from '../member/service/member.service'
+import { IMember } from '../member/model/member.model'
+import { ORCID_BASE_URL } from '../app.constants'
+
+@Component({
+ selector: 'landing-page',
+ templateUrl: './landing-page.component.html',
+})
+export class LandingPageComponent implements OnInit {
+ issuer: string = ORCID_BASE_URL
+ oauthBaseUrl: string = ORCID_BASE_URL + '/oauth/authorize'
+ redirectUri: string = '/landing-page'
+
+ loading: Boolean = true
+ showConnectionExists: Boolean = false
+ showConnectionExistsDifferentUser: Boolean = false
+ showDenied: Boolean = false
+ showError: Boolean = false
+ showSuccess: Boolean = false
+ key: any
+ clientName: string | undefined
+ salesforceId: string | undefined
+ clientId: string | undefined
+ orcidId: string | undefined
+ oauthUrl: string | undefined
+ orcidRecord: any
+ signedInIdToken: any
+ givenName: string | undefined
+ familyName: string | undefined
+ progressbarValue = 100
+ curSec = 0
+ incorrectDataMessage = ''
+ linkAlreadyUsedMessage = ''
+ allowToUpdateRecordMessage = ''
+ successfullyGrantedMessage = ''
+ thanksMessage = ''
+
+ constructor(
+ private landingPageService: LandingPageService,
+ protected memberService: MemberService
+ ) {}
+
+ ngOnInit() {
+ const id_token_fragment = this.getFragmentParameterByName('id_token')
+ const access_token_fragment = this.getFragmentParameterByName('access_token')
+ const state_param = this.getQueryParameterByName('state')
+
+ if (state_param) {
+ this.landingPageService.getOrcidConnectionRecord(state_param).subscribe({
+ next: (result: HttpResponse) => {
+ this.orcidRecord = result.body
+ this.landingPageService.getMemberInfo(state_param).subscribe({
+ next: (res: IMember) => {
+ this.clientName = res.clientName
+ this.clientId = res.clientId
+ this.salesforceId = res.salesforceId
+ this.oauthUrl =
+ this.oauthBaseUrl +
+ '?response_type=token&redirect_uri=' +
+ this.redirectUri +
+ '&client_id=' +
+ this.clientId +
+ '&scope=/read-limited /activities/update /person/update openid&prompt=login&state=' +
+ state_param
+
+ this.incorrectDataMessage = $localize`:@@landingPage.success.ifYouFind:If you find that data added to your ORCID record is incorrect, please contact ${this.clientName}`
+ this.linkAlreadyUsedMessage = $localize`:@@landingPage.connectionExists.differentUser.string:This authorization link has already been used. Please contact ${this.clientName} for a new authorization link.`
+ this.allowToUpdateRecordMessage = $localize`:@@landingPage.denied.grantAccess.string:Allow ${this.clientName} to update my ORCID record.`
+ this.successfullyGrantedMessage = $localize`:@@landingPage.success.youHaveSuccessfully.string:You have successfully granted ${this.clientName} permission to update your ORCID record, and your record has been updated with affiliation information.`
+
+ // Check if id token exists in URL (user just granted permission)
+ if (id_token_fragment != null && id_token_fragment !== '') {
+ this.checkSubmitToken(id_token_fragment, state_param, access_token_fragment)
+ } else {
+ const error = this.getFragmentParameterByName('error')
+ // Check if user denied permission
+ if (error != null && error !== '') {
+ if (error === 'access_denied') {
+ this.submitUserDenied(state_param)
+ } else {
+ this.showErrorElement()
+ }
+ } else {
+ window.location.replace(this.oauthUrl)
+ }
+ }
+
+ this.startTimer(600)
+ },
+ error: (res: HttpErrorResponse) => {
+ console.log('error')
+ },
+ })
+ },
+ error: (res: HttpErrorResponse) => {
+ console.log('error')
+ },
+ })
+ }
+ }
+
+ getFragmentParameterByName(name: string): string {
+ name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]')
+ const regex = new RegExp('[\\#&]' + name + '=([^]*)'),
+ results = regex.exec(window.location.hash)
+ return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '))
+ }
+
+ getQueryParameterByName(name: string): string | null {
+ name = name.replace(/[\[\]]/g, '\\$&')
+ const regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)'),
+ results = regex.exec(window.location.href)
+ if (!results) {
+ return null
+ }
+ if (!results[2]) {
+ return ''
+ }
+ return decodeURIComponent(results[2].replace(/\+/g, ' '))
+ }
+
+ checkSubmitToken(id_token: string, state: string, access_token: string) {
+ this.landingPageService.getPublicKey().subscribe(
+ (res) => {
+ const pubKey = KEYUTIL.getKey(res.keys[0]) as RSAKey
+ const response = KJUR.jws.JWS.verifyJWT(id_token, pubKey, {
+ alg: ['RS256'],
+ iss: [this.issuer],
+ aud: [this.clientId || ''],
+ gracePeriod: 15 * 60, // 15 mins skew allowed
+ })
+ if (response === true) {
+ // check if existing token belongs to a different user
+
+ this.landingPageService.submitUserResponse({ id_token, state, salesforce_id: this.salesforceId }).subscribe({
+ next: (res) => {
+ const data = res
+ if (data) {
+ if (data.isDifferentUser) {
+ this.showConnectionExistsDifferentUserElement()
+ return
+ }
+ if (data.isSameUserThatAlreadyGranted) {
+ this.showConnectionExistsElement()
+ return
+ }
+ }
+ this.landingPageService.getUserInfo(access_token).subscribe({
+ next: (result: HttpResponse) => {
+ this.signedInIdToken = result
+ this.givenName = ''
+ if (this.signedInIdToken.given_name) {
+ this.givenName = this.signedInIdToken.given_name
+ }
+ this.familyName = ''
+ if (this.signedInIdToken.family_name) {
+ this.familyName = this.signedInIdToken.family_name
+ }
+ this.thanksMessage = $localize`:@@landingPage.success.thanks.string:Thanks, ${this.givenName} ${this.familyName}!`
+
+ this.showSuccessElement()
+ },
+ error: () => {
+ this.showErrorElement()
+ },
+ })
+ },
+ error: () => {
+ this.showErrorElement()
+ },
+ })
+ } else {
+ this.showErrorElement()
+ }
+ },
+ () => {
+ this.showErrorElement()
+ }
+ )
+ }
+
+ submitIdTokenData(id_token: string, state: string, access_token: string) {
+ this.landingPageService.submitUserResponse({ id_token, state }).subscribe({
+ next: () => {
+ this.landingPageService.getUserInfo(access_token).subscribe({
+ next: (res: HttpResponse) => {
+ this.signedInIdToken = res
+ this.showSuccessElement()
+ },
+ error: () => {
+ this.showErrorElement()
+ },
+ })
+ },
+ error: () => {
+ this.showErrorElement()
+ },
+ })
+ }
+
+ submitUserDenied(state: string) {
+ this.landingPageService.submitUserResponse({ denied: true, state }).subscribe(
+ () => {
+ this.showDeniedElement()
+ },
+ () => {
+ this.showErrorElement()
+ }
+ )
+ }
+
+ startTimer(seconds: number) {
+ const timer = interval(100)
+ const sub = timer.subscribe((sec) => {
+ this.progressbarValue = (sec * 100) / seconds
+ this.curSec = sec
+ if (this.curSec === seconds) {
+ sub.unsubscribe()
+ }
+ })
+ }
+
+ showConnectionExistsElement(): void {
+ this.showDenied = false
+ this.showError = false
+ this.showSuccess = false
+ this.showConnectionExists = true
+ this.loading = false
+ this.showConnectionExistsDifferentUser = false
+ }
+
+ showConnectionExistsDifferentUserElement(): void {
+ this.showDenied = false
+ this.showError = false
+ this.showSuccess = false
+ this.showConnectionExists = false
+ this.loading = false
+ this.showConnectionExistsDifferentUser = true
+ }
+
+ showErrorElement(): void {
+ this.showDenied = false
+ this.showError = true
+ this.showSuccess = false
+ this.loading = false
+ this.showConnectionExistsDifferentUser = false
+ }
+
+ showDeniedElement(): void {
+ this.showDenied = true
+ this.showError = false
+ this.showSuccess = false
+ this.loading = false
+ this.showConnectionExistsDifferentUser = false
+ }
+
+ showSuccessElement(): void {
+ this.showDenied = false
+ this.showError = false
+ this.showSuccess = true
+ this.loading = false
+ this.showConnectionExistsDifferentUser = false
+ }
+}
diff --git a/ui/src/app/landing-page/landing-page.module.ts b/ui/src/app/landing-page/landing-page.module.ts
new file mode 100644
index 000000000..1f98b5429
--- /dev/null
+++ b/ui/src/app/landing-page/landing-page.module.ts
@@ -0,0 +1,25 @@
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
+import { RouterModule } from '@angular/router'
+
+import { BrowserModule } from '@angular/platform-browser'
+
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
+
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'
+import { MatProgressBarModule } from '@angular/material/progress-bar'
+import { LandingPageComponent } from './landing-page.component'
+import { LANDING_PAGE_ROUTE } from './landing-page.route'
+
+@NgModule({
+ imports: [
+ RouterModule.forChild([LANDING_PAGE_ROUTE]),
+ BrowserModule,
+ BrowserAnimationsModule,
+
+ MatProgressSpinnerModule,
+ MatProgressBarModule,
+ ],
+ declarations: [LandingPageComponent],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
+})
+export class GatewayLandingPageModule {}
diff --git a/ui/src/app/landing-page/landing-page.route.ts b/ui/src/app/landing-page/landing-page.route.ts
new file mode 100644
index 000000000..9ffdc5843
--- /dev/null
+++ b/ui/src/app/landing-page/landing-page.route.ts
@@ -0,0 +1,11 @@
+import { Route } from '@angular/router'
+import { LandingPageComponent } from './landing-page.component'
+
+export const LANDING_PAGE_ROUTE: Route = {
+ path: 'landing-page',
+ component: LandingPageComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'landingPage.title.string',
+ },
+}
diff --git a/ui/src/app/landing-page/landing-page.service.ts b/ui/src/app/landing-page/landing-page.service.ts
new file mode 100644
index 000000000..6758e7289
--- /dev/null
+++ b/ui/src/app/landing-page/landing-page.service.ts
@@ -0,0 +1,48 @@
+import { Injectable } from '@angular/core'
+import { HttpClient, HttpClientModule, HttpHeaders } from '@angular/common/http'
+import { Observable } from 'rxjs'
+import { ORCID_BASE_URL } from '../app.constants'
+
+@Injectable({ providedIn: 'root' })
+export class LandingPageService {
+ private headers: HttpHeaders
+
+ idTokenUri: string = '/services/assertionservice/api/id-token'
+ recordConnectionUri: string = '/services/assertionservice/api/assertion/record/'
+ memberInfoUri: string = '/services/memberservice/api/members/authorized/'
+ userInfoUri: string = ORCID_BASE_URL + '/oauth/userinfo'
+ publicKeyUri: string = ORCID_BASE_URL + '/oauth/jwks'
+
+ constructor(private http: HttpClient) {
+ this.headers = new HttpHeaders({
+ 'Access-Control-Allow-Origin': '*',
+ 'Content-Type': 'application/json',
+ })
+ }
+
+ submitUserResponse(data: any): Observable {
+ return this.http.post(this.idTokenUri, JSON.stringify(data), { headers: this.headers })
+ }
+
+ getOrcidConnectionRecord(state: String): Observable {
+ const requestUrl = this.recordConnectionUri + state
+ return this.http.get(requestUrl, { observe: 'response' })
+ }
+
+ getMemberInfo(state: String): Observable {
+ const requestUrl = this.memberInfoUri + state
+ return this.http.get(requestUrl)
+ }
+
+ getUserInfo(access_token: String): Observable {
+ const headers = new HttpHeaders({
+ Authorization: 'Bearer ' + access_token,
+ 'Content-Type': 'application/json',
+ })
+ return this.http.post(this.userInfoUri, {}, { headers })
+ }
+
+ getPublicKey(): Observable {
+ return this.http.get(this.publicKeyUri)
+ }
+}
From 61bdf5ea92c60493a5099018e68436cf0f064f37 Mon Sep 17 00:00:00 2001
From: andrej romanov <50377758+auumgn@users.noreply.github.com>
Date: Wed, 27 Mar 2024 15:40:19 +0200
Subject: [PATCH 2/3] make the landing page work
also correct all route paths
---
ui/src/app/affiliation/affiliation.route.ts | 4 ++--
ui/src/app/app-routing.module.ts | 8 +++++---
.../app/landing-page/landing-page.component.ts | 2 +-
ui/src/app/landing-page/landing-page.module.ts | 13 ++-----------
ui/src/app/landing-page/landing-page.route.ts | 18 ++++++++++--------
ui/src/app/shared/shared.module.ts | 2 +-
ui/src/app/user/user.route.ts | 8 ++++----
ui/src/app/user/users.component.spec.ts | 6 +++++-
8 files changed, 30 insertions(+), 31 deletions(-)
diff --git a/ui/src/app/affiliation/affiliation.route.ts b/ui/src/app/affiliation/affiliation.route.ts
index d186e48d2..b3fae9ab4 100644
--- a/ui/src/app/affiliation/affiliation.route.ts
+++ b/ui/src/app/affiliation/affiliation.route.ts
@@ -28,7 +28,7 @@ export const AffiliationResolver: ResolveFn = (
export const affiliationRoutes: Routes = [
{
- path: 'affiliations',
+ path: '',
component: AffiliationsComponent,
data: {
authorities: ['ASSERTION_SERVICE_ENABLED'],
@@ -76,7 +76,7 @@ export const affiliationRoutes: Routes = [
],
},
{
- path: 'affiliations/:id/view',
+ path: ':id/view',
component: AffiliationDetailComponent,
resolve: {
affiliation: AffiliationResolver,
diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts
index e97de382e..b3c2fb2da 100644
--- a/ui/src/app/app-routing.module.ts
+++ b/ui/src/app/app-routing.module.ts
@@ -1,9 +1,7 @@
import { NgModule } from '@angular/core'
-import { ActivatedRouteSnapshot, Resolve, Route, RouterModule, RouterStateSnapshot, Routes } from '@angular/router'
+import { RouterModule, Routes } from '@angular/router'
import { navbarRoute } from './layout/navbar/navbar.route'
import { errorRoutes } from './error/error.route'
-import { AuthGuard } from './account/auth.guard'
-import { UsersComponent } from './user/users.component'
const routes: Routes = [
{
@@ -22,6 +20,10 @@ const routes: Routes = [
path: 'affiliations',
loadChildren: () => import('./affiliation/affiliation.module').then((m) => m.AffiliationModule),
},
+ {
+ path: 'landing-page',
+ loadChildren: () => import('./landing-page/landing-page.module').then((m) => m.LandingPageModule),
+ },
]
@NgModule({
diff --git a/ui/src/app/landing-page/landing-page.component.ts b/ui/src/app/landing-page/landing-page.component.ts
index 04a427fb1..7f0d21dae 100644
--- a/ui/src/app/landing-page/landing-page.component.ts
+++ b/ui/src/app/landing-page/landing-page.component.ts
@@ -8,7 +8,7 @@ import { IMember } from '../member/model/member.model'
import { ORCID_BASE_URL } from '../app.constants'
@Component({
- selector: 'landing-page',
+ selector: 'app-landing-page',
templateUrl: './landing-page.component.html',
})
export class LandingPageComponent implements OnInit {
diff --git a/ui/src/app/landing-page/landing-page.module.ts b/ui/src/app/landing-page/landing-page.module.ts
index 1f98b5429..6af3902e6 100644
--- a/ui/src/app/landing-page/landing-page.module.ts
+++ b/ui/src/app/landing-page/landing-page.module.ts
@@ -1,8 +1,6 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { RouterModule } from '@angular/router'
-import { BrowserModule } from '@angular/platform-browser'
-
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'
@@ -11,15 +9,8 @@ import { LandingPageComponent } from './landing-page.component'
import { LANDING_PAGE_ROUTE } from './landing-page.route'
@NgModule({
- imports: [
- RouterModule.forChild([LANDING_PAGE_ROUTE]),
- BrowserModule,
- BrowserAnimationsModule,
-
- MatProgressSpinnerModule,
- MatProgressBarModule,
- ],
+ imports: [MatProgressSpinnerModule, MatProgressBarModule, RouterModule.forChild(LANDING_PAGE_ROUTE)],
declarations: [LandingPageComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
-export class GatewayLandingPageModule {}
+export class LandingPageModule {}
diff --git a/ui/src/app/landing-page/landing-page.route.ts b/ui/src/app/landing-page/landing-page.route.ts
index 9ffdc5843..cd7099ccf 100644
--- a/ui/src/app/landing-page/landing-page.route.ts
+++ b/ui/src/app/landing-page/landing-page.route.ts
@@ -1,11 +1,13 @@
-import { Route } from '@angular/router'
+import { Routes } from '@angular/router'
import { LandingPageComponent } from './landing-page.component'
-export const LANDING_PAGE_ROUTE: Route = {
- path: 'landing-page',
- component: LandingPageComponent,
- data: {
- authorities: [],
- pageTitle: 'landingPage.title.string',
+export const LANDING_PAGE_ROUTE: Routes = [
+ {
+ path: '',
+ component: LandingPageComponent,
+ data: {
+ authorities: [],
+ pageTitle: 'landingPage.title.string',
+ },
},
-}
+]
diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts
index 078ba4194..e3c1cf79b 100644
--- a/ui/src/app/shared/shared.module.ts
+++ b/ui/src/app/shared/shared.module.ts
@@ -9,7 +9,7 @@ import { AlertComponent } from './alert/alert.component'
import { LocalizePipe } from './pipe/localize'
@NgModule({
- imports: [CommonModule, NgbModule, FontAwesomeModule],
+ imports: [NgbModule, FontAwesomeModule],
declarations: [FindLanguageFromKeyPipe, LocalizePipe, ErrorAlertComponent, HasAnyAuthorityDirective, AlertComponent],
exports: [
FindLanguageFromKeyPipe,
diff --git a/ui/src/app/user/user.route.ts b/ui/src/app/user/user.route.ts
index ddb774385..ee53eb104 100644
--- a/ui/src/app/user/user.route.ts
+++ b/ui/src/app/user/user.route.ts
@@ -27,7 +27,7 @@ export const UserResolver: ResolveFn = (
export const routes: Routes = [
{
- path: 'users',
+ path: '',
component: UsersComponent,
data: {
authorities: ['ROLE_ADMIN', 'ROLE_ORG_OWNER', 'ROLE_CONSORTIUM_LEAD'],
@@ -66,7 +66,7 @@ export const routes: Routes = [
],
},
{
- path: 'users/:id/view',
+ path: ':id/view',
component: UserDetailComponent,
resolve: {
user: UserResolver,
@@ -78,7 +78,7 @@ export const routes: Routes = [
canActivate: [AuthGuard],
},
{
- path: 'users/new',
+ path: 'new',
component: UserUpdateComponent,
resolve: {
user: UserResolver,
@@ -90,7 +90,7 @@ export const routes: Routes = [
canActivate: [AuthGuard],
},
{
- path: 'users/:id/edit',
+ path: ':id/edit',
component: UserUpdateComponent,
resolve: {
user: UserResolver,
diff --git a/ui/src/app/user/users.component.spec.ts b/ui/src/app/user/users.component.spec.ts
index a3a19957c..e33bfa34b 100644
--- a/ui/src/app/user/users.component.spec.ts
+++ b/ui/src/app/user/users.component.spec.ts
@@ -42,7 +42,11 @@ describe('UsersComponent', () => {
TestBed.configureTestingModule({
declarations: [UsersComponent, HasAnyAuthorityDirective, LocalizePipe],
- imports: [ReactiveFormsModule, RouterModule.forRoot([{ path: 'users', component: UsersComponent }]), FormsModule],
+ imports: [
+ ReactiveFormsModule,
+ RouterModule.forChild([{ path: 'users', component: UsersComponent }]),
+ FormsModule,
+ ],
providers: [
{ provide: UserService, useValue: userServiceSpy },
{ provide: AccountService, useValue: accountServiceSpy },
From ca669077758a3c45fa3c812032979c4b5be99f17 Mon Sep 17 00:00:00 2001
From: andrej romanov <50377758+auumgn@users.noreply.github.com>
Date: Fri, 29 Mar 2024 13:21:23 +0200
Subject: [PATCH 3/3] add unit tests and final tweaks
---
.../landing-page/landing-page.component.html | 4 +-
.../landing-page.component.spec.ts | 153 ++++++++++++++++--
.../landing-page/landing-page.component.ts | 151 ++++++++---------
.../app/landing-page/landing-page.module.ts | 3 +-
.../app/landing-page/landing-page.service.ts | 21 +--
ui/src/app/shared/model/orcid-record.model.ts | 15 ++
.../shared/service/window-location.service.ts | 4 +
ui/src/app/user/users.component.spec.ts | 1 +
8 files changed, 244 insertions(+), 108 deletions(-)
create mode 100644 ui/src/app/shared/model/orcid-record.model.ts
diff --git a/ui/src/app/landing-page/landing-page.component.html b/ui/src/app/landing-page/landing-page.component.html
index 3d654fb5a..03715968c 100644
--- a/ui/src/app/landing-page/landing-page.component.html
+++ b/ui/src/app/landing-page/landing-page.component.html
@@ -17,7 +17,7 @@
>
-
+
{{ issuer }}/{{ orcidRecord.orcid }}
@@ -57,7 +57,7 @@
{{ successfullyGrantedMessage }}
-
+
{{ issuer }}/{{ signedInIdToken.sub }}
diff --git a/ui/src/app/landing-page/landing-page.component.spec.ts b/ui/src/app/landing-page/landing-page.component.spec.ts
index 5b7ad0b17..42e67adaa 100644
--- a/ui/src/app/landing-page/landing-page.component.spec.ts
+++ b/ui/src/app/landing-page/landing-page.component.spec.ts
@@ -1,21 +1,146 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { LandingPageComponent } from './landing-page.component';
+import { ComponentFixture, TestBed } from '@angular/core/testing'
+import { HttpClientModule } from '@angular/common/http'
+import { LandingPageComponent } from './landing-page.component'
+import { LandingPageService } from './landing-page.service'
+import { OrcidRecord } from '../shared/model/orcid-record.model'
+import { of } from 'rxjs'
+import { Member } from '../member/model/member.model'
+import { WindowLocationService } from '../shared/service/window-location.service'
+import * as KEYUTIL from 'jsrsasign'
describe('LandingPageComponent', () => {
- let component: LandingPageComponent;
- let fixture: ComponentFixture;
+ let component: LandingPageComponent
+ let fixture: ComponentFixture
+ let landingPageService: jasmine.SpyObj
+ let windowLocationService: jasmine.SpyObj
beforeEach(() => {
TestBed.configureTestingModule({
- declarations: [LandingPageComponent]
- });
- fixture = TestBed.createComponent(LandingPageComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
+ declarations: [LandingPageComponent],
+ imports: [HttpClientModule],
+ providers: [
+ {
+ provide: LandingPageService,
+ useValue: jasmine.createSpyObj('LandingPageService', [
+ 'getOrcidConnectionRecord',
+ 'getMemberInfo',
+ 'getPublicKey',
+ 'submitUserResponse',
+ 'getUserInfo',
+ 'submitUserResponse',
+ ]),
+ },
+ {
+ provide: WindowLocationService,
+ useValue: jasmine.createSpyObj('WindowLocationService', ['updateWindowLocation', 'getWindowLocationHash']),
+ },
+ ],
+ })
+ fixture = TestBed.createComponent(LandingPageComponent)
+ component = fixture.componentInstance
+ landingPageService = TestBed.inject(LandingPageService) as jasmine.SpyObj
+ windowLocationService = TestBed.inject(WindowLocationService) as jasmine.SpyObj
+ })
it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
+ expect(component).toBeTruthy()
+ })
+
+ it('New record connection should redirect to the registry', () => {
+ windowLocationService.updateWindowLocation.and.returnValue()
+ landingPageService.getOrcidConnectionRecord.and.returnValue(of(new OrcidRecord('email', 'orcid')))
+ landingPageService.getMemberInfo.and.returnValue(
+ of(new Member('id', 'name', 'email', 'orcid', 'salesforceId', 'clientId'))
+ )
+ component.processRequest('someState', '', '')
+ expect(landingPageService.getOrcidConnectionRecord).toHaveBeenCalled()
+ expect(component.oauthUrl).toBe(
+ 'localhost.orcid.org/oauth/authorize?response_type=token&redirect_uri=/landing-page&client_id=name&scope=/read-limited /activities/update /person/update openid&prompt=login&state=someState'
+ )
+ expect(landingPageService.getPublicKey).toHaveBeenCalledTimes(0)
+ expect(windowLocationService.updateWindowLocation).toHaveBeenCalled()
+ })
+
+ it('New record connection should fail (user denied permission)', () => {
+ windowLocationService.updateWindowLocation.and.returnValue()
+ windowLocationService.getWindowLocationHash.and.returnValue('#error=access_denied')
+ landingPageService.getOrcidConnectionRecord.and.returnValue(of(new OrcidRecord('email', 'orcid')))
+ landingPageService.getMemberInfo.and.returnValue(
+ of(new Member('id', 'name', 'email', 'orcid', 'salesforceId', 'clientId'))
+ )
+ landingPageService.submitUserResponse.and.returnValue(of(''))
+ component.processRequest('someState', '', '')
+ expect(landingPageService.getOrcidConnectionRecord).toHaveBeenCalled()
+ expect(component.oauthUrl).toBe(
+ 'localhost.orcid.org/oauth/authorize?response_type=token&redirect_uri=/landing-page&client_id=name&scope=/read-limited /activities/update /person/update openid&prompt=login&state=someState'
+ )
+ expect(landingPageService.getPublicKey).toHaveBeenCalledTimes(0)
+ expect(windowLocationService.updateWindowLocation).toHaveBeenCalledTimes(0)
+ expect(landingPageService.submitUserResponse).toHaveBeenCalled()
+ expect(component.showError).toBeFalsy()
+ expect(component.showDenied).toBeTruthy()
+ })
+
+ it('New record connection should fail (generic error)', () => {
+ windowLocationService.updateWindowLocation.and.returnValue()
+ windowLocationService.getWindowLocationHash.and.returnValue('#error=123')
+ landingPageService.getOrcidConnectionRecord.and.returnValue(of(new OrcidRecord('email', 'orcid')))
+ landingPageService.getMemberInfo.and.returnValue(
+ of(new Member('id', 'name', 'email', 'orcid', 'salesforceId', 'clientId'))
+ )
+ landingPageService.submitUserResponse.and.returnValue(of(''))
+ component.processRequest('someState', '', '')
+ expect(landingPageService.getOrcidConnectionRecord).toHaveBeenCalled()
+ expect(component.oauthUrl).toBe(
+ 'localhost.orcid.org/oauth/authorize?response_type=token&redirect_uri=/landing-page&client_id=name&scope=/read-limited /activities/update /person/update openid&prompt=login&state=someState'
+ )
+ expect(landingPageService.getPublicKey).toHaveBeenCalledTimes(0)
+ expect(windowLocationService.updateWindowLocation).toHaveBeenCalledTimes(0)
+ expect(landingPageService.submitUserResponse).toHaveBeenCalledTimes(0)
+ expect(component.showError).toBeTruthy()
+ expect(component.showDenied).toBeFalsy()
+ })
+
+ it('Existing record connection should be identified', () => {
+ windowLocationService.updateWindowLocation.and.returnValue()
+ landingPageService.getOrcidConnectionRecord.and.returnValue(of(new OrcidRecord('email', 'orcid')))
+ landingPageService.getMemberInfo.and.returnValue(
+ of(new Member('id', 'name', 'email', 'orcid', 'salesforceId', 'clientId'))
+ )
+ landingPageService.getPublicKey.and.returnValue(of(['publicKey']))
+ landingPageService.submitUserResponse.and.returnValue(of(''))
+ landingPageService.getUserInfo.and.returnValue(of({ givenName: 'givenName', familyName: 'familyName' }))
+ spyOn(KEYUTIL.KEYUTIL, 'getKey').and.returnValue(new KEYUTIL.RSAKey())
+ spyOn(KEYUTIL.KJUR.jws.JWS, 'verifyJWT').and.returnValue(true)
+
+ component.processRequest('someState', 'it_token', '')
+
+ expect(landingPageService.getOrcidConnectionRecord).toHaveBeenCalled()
+ expect(landingPageService.getPublicKey).toHaveBeenCalled()
+ expect(windowLocationService.updateWindowLocation).toHaveBeenCalledTimes(0)
+ })
+
+ it('Check for wrong user', () => {
+ landingPageService.submitUserResponse.and.returnValue(of({ isDifferentUser: true }))
+ landingPageService.getPublicKey.and.returnValue(of(['publicKey']))
+ landingPageService.getUserInfo.and.returnValue(of({ givenName: 'givenName', familyName: 'familyName' }))
+ spyOn(KEYUTIL.KEYUTIL, 'getKey').and.returnValue(new KEYUTIL.RSAKey())
+ spyOn(KEYUTIL.KJUR.jws.JWS, 'verifyJWT').and.returnValue(true)
+ component.checkSubmitToken('token', 'state', 'access_token')
+ expect(landingPageService.submitUserResponse).toHaveBeenCalled()
+ expect(component.showConnectionExists).toBeFalsy()
+ expect(component.showConnectionExistsDifferentUser).toBeTruthy()
+ })
+
+ it('Check for existing connection', () => {
+ landingPageService.submitUserResponse.and.returnValue(of({ isSameUserThatAlreadyGranted: true }))
+ landingPageService.getPublicKey.and.returnValue(of(['publicKey']))
+ landingPageService.getUserInfo.and.returnValue(of({ givenName: 'givenName', familyName: 'familyName' }))
+ spyOn(KEYUTIL.KEYUTIL, 'getKey').and.returnValue(new KEYUTIL.RSAKey())
+ spyOn(KEYUTIL.KJUR.jws.JWS, 'verifyJWT').and.returnValue(true)
+ component.checkSubmitToken('token', 'state', 'access_token')
+ expect(landingPageService.submitUserResponse).toHaveBeenCalled()
+ expect(component.showConnectionExists).toBeTruthy()
+ expect(component.showConnectionExistsDifferentUser).toBeFalsy()
+ })
+})
diff --git a/ui/src/app/landing-page/landing-page.component.ts b/ui/src/app/landing-page/landing-page.component.ts
index 7f0d21dae..e2294c31a 100644
--- a/ui/src/app/landing-page/landing-page.component.ts
+++ b/ui/src/app/landing-page/landing-page.component.ts
@@ -6,22 +6,23 @@ import { LandingPageService } from './landing-page.service'
import { MemberService } from '../member/service/member.service'
import { IMember } from '../member/model/member.model'
import { ORCID_BASE_URL } from '../app.constants'
+import { WindowLocationService } from '../shared/service/window-location.service'
@Component({
selector: 'app-landing-page',
templateUrl: './landing-page.component.html',
})
export class LandingPageComponent implements OnInit {
- issuer: string = ORCID_BASE_URL
- oauthBaseUrl: string = ORCID_BASE_URL + '/oauth/authorize'
- redirectUri: string = '/landing-page'
+ issuer = ORCID_BASE_URL
+ oauthBaseUrl = ORCID_BASE_URL + '/oauth/authorize'
+ redirectUri = '/landing-page'
- loading: Boolean = true
- showConnectionExists: Boolean = false
- showConnectionExistsDifferentUser: Boolean = false
- showDenied: Boolean = false
- showError: Boolean = false
- showSuccess: Boolean = false
+ loading = true
+ showConnectionExists = false
+ showConnectionExistsDifferentUser = false
+ showDenied = false
+ showError = false
+ showSuccess = false
key: any
clientName: string | undefined
salesforceId: string | undefined
@@ -42,6 +43,7 @@ export class LandingPageComponent implements OnInit {
constructor(
private landingPageService: LandingPageService,
+ private windowLocationService: WindowLocationService,
protected memberService: MemberService
) {}
@@ -51,67 +53,73 @@ export class LandingPageComponent implements OnInit {
const state_param = this.getQueryParameterByName('state')
if (state_param) {
- this.landingPageService.getOrcidConnectionRecord(state_param).subscribe({
- next: (result: HttpResponse) => {
- this.orcidRecord = result.body
- this.landingPageService.getMemberInfo(state_param).subscribe({
- next: (res: IMember) => {
- this.clientName = res.clientName
- this.clientId = res.clientId
- this.salesforceId = res.salesforceId
- this.oauthUrl =
- this.oauthBaseUrl +
- '?response_type=token&redirect_uri=' +
- this.redirectUri +
- '&client_id=' +
- this.clientId +
- '&scope=/read-limited /activities/update /person/update openid&prompt=login&state=' +
- state_param
+ this.processRequest(state_param, id_token_fragment, access_token_fragment)
+ }
+ }
- this.incorrectDataMessage = $localize`:@@landingPage.success.ifYouFind:If you find that data added to your ORCID record is incorrect, please contact ${this.clientName}`
- this.linkAlreadyUsedMessage = $localize`:@@landingPage.connectionExists.differentUser.string:This authorization link has already been used. Please contact ${this.clientName} for a new authorization link.`
- this.allowToUpdateRecordMessage = $localize`:@@landingPage.denied.grantAccess.string:Allow ${this.clientName} to update my ORCID record.`
- this.successfullyGrantedMessage = $localize`:@@landingPage.success.youHaveSuccessfully.string:You have successfully granted ${this.clientName} permission to update your ORCID record, and your record has been updated with affiliation information.`
+ processRequest(state_param: string, id_token_fragment: string, access_token_fragment: string) {
+ this.landingPageService.getOrcidConnectionRecord(state_param).subscribe({
+ next: (result) => {
+ this.orcidRecord = result
+ this.landingPageService.getMemberInfo(state_param).subscribe({
+ next: (res: IMember) => {
+ this.clientName = res.clientName
+ this.clientId = res.clientId
+ this.salesforceId = res.salesforceId
+ this.oauthUrl =
+ this.oauthBaseUrl +
+ '?response_type=token&redirect_uri=' +
+ this.redirectUri +
+ '&client_id=' +
+ this.clientId +
+ '&scope=/read-limited /activities/update /person/update openid&prompt=login&state=' +
+ state_param
- // Check if id token exists in URL (user just granted permission)
- if (id_token_fragment != null && id_token_fragment !== '') {
- this.checkSubmitToken(id_token_fragment, state_param, access_token_fragment)
- } else {
- const error = this.getFragmentParameterByName('error')
- // Check if user denied permission
- if (error != null && error !== '') {
- if (error === 'access_denied') {
- this.submitUserDenied(state_param)
- } else {
- this.showErrorElement()
- }
+ this.incorrectDataMessage = $localize`:@@landingPage.success.ifYouFind:If you find that data added to your ORCID record is incorrect, please contact ${this.clientName}`
+ this.linkAlreadyUsedMessage = $localize`:@@landingPage.connectionExists.differentUser.string:This authorization link has already been used. Please contact ${this.clientName} for a new authorization link.`
+ this.allowToUpdateRecordMessage = $localize`:@@landingPage.denied.grantAccess.string:Allow ${this.clientName} to update my ORCID record.`
+ this.successfullyGrantedMessage = $localize`:@@landingPage.success.youHaveSuccessfully.string:You have successfully granted ${this.clientName} permission to update your ORCID record, and your record has been updated with affiliation information.`
+
+ // Check if id token exists in URL (user just granted permission)
+ if (id_token_fragment != null && id_token_fragment !== '') {
+ this.checkSubmitToken(id_token_fragment, state_param, access_token_fragment)
+ } else {
+ const error = this.getFragmentParameterByName('error')
+ // Check if user denied permission
+ if (error != null && error !== '') {
+ if (error === 'access_denied') {
+ this.submitUserDenied(state_param)
} else {
- window.location.replace(this.oauthUrl)
+ this.showErrorElement()
}
+ } else {
+ this.windowLocationService.updateWindowLocation(this.oauthUrl)
}
+ }
- this.startTimer(600)
- },
- error: (res: HttpErrorResponse) => {
- console.log('error')
- },
- })
- },
- error: (res: HttpErrorResponse) => {
- console.log('error')
- },
- })
- }
+ this.startTimer(600)
+ },
+ error: (res: HttpErrorResponse) => {
+ console.log('error')
+ },
+ })
+ },
+ error: (res: HttpErrorResponse) => {
+ console.log('error')
+ },
+ })
}
getFragmentParameterByName(name: string): string {
+ // eslint-disable-next-line
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]')
const regex = new RegExp('[\\#&]' + name + '=([^]*)'),
- results = regex.exec(window.location.hash)
+ results = regex.exec(this.windowLocationService.getWindowLocationHash())
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '))
}
getQueryParameterByName(name: string): string | null {
+ // eslint-disable-next-line
name = name.replace(/[\[\]]/g, '\\$&')
const regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)'),
results = regex.exec(window.location.href)
@@ -125,8 +133,8 @@ export class LandingPageComponent implements OnInit {
}
checkSubmitToken(id_token: string, state: string, access_token: string) {
- this.landingPageService.getPublicKey().subscribe(
- (res) => {
+ this.landingPageService.getPublicKey().subscribe({
+ next: (res) => {
const pubKey = KEYUTIL.getKey(res.keys[0]) as RSAKey
const response = KJUR.jws.JWS.verifyJWT(id_token, pubKey, {
alg: ['RS256'],
@@ -178,25 +186,6 @@ export class LandingPageComponent implements OnInit {
this.showErrorElement()
}
},
- () => {
- this.showErrorElement()
- }
- )
- }
-
- submitIdTokenData(id_token: string, state: string, access_token: string) {
- this.landingPageService.submitUserResponse({ id_token, state }).subscribe({
- next: () => {
- this.landingPageService.getUserInfo(access_token).subscribe({
- next: (res: HttpResponse) => {
- this.signedInIdToken = res
- this.showSuccessElement()
- },
- error: () => {
- this.showErrorElement()
- },
- })
- },
error: () => {
this.showErrorElement()
},
@@ -204,14 +193,14 @@ export class LandingPageComponent implements OnInit {
}
submitUserDenied(state: string) {
- this.landingPageService.submitUserResponse({ denied: true, state }).subscribe(
- () => {
+ this.landingPageService.submitUserResponse({ denied: true, state }).subscribe({
+ next: () => {
this.showDeniedElement()
},
- () => {
+ error: () => {
this.showErrorElement()
- }
- )
+ },
+ })
}
startTimer(seconds: number) {
diff --git a/ui/src/app/landing-page/landing-page.module.ts b/ui/src/app/landing-page/landing-page.module.ts
index 6af3902e6..53fc89b5d 100644
--- a/ui/src/app/landing-page/landing-page.module.ts
+++ b/ui/src/app/landing-page/landing-page.module.ts
@@ -7,9 +7,10 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'
import { MatProgressBarModule } from '@angular/material/progress-bar'
import { LandingPageComponent } from './landing-page.component'
import { LANDING_PAGE_ROUTE } from './landing-page.route'
+import { CommonModule } from '@angular/common'
@NgModule({
- imports: [MatProgressSpinnerModule, MatProgressBarModule, RouterModule.forChild(LANDING_PAGE_ROUTE)],
+ imports: [CommonModule, MatProgressSpinnerModule, MatProgressBarModule, RouterModule.forChild(LANDING_PAGE_ROUTE)],
declarations: [LandingPageComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
diff --git a/ui/src/app/landing-page/landing-page.service.ts b/ui/src/app/landing-page/landing-page.service.ts
index 6758e7289..e18311a9e 100644
--- a/ui/src/app/landing-page/landing-page.service.ts
+++ b/ui/src/app/landing-page/landing-page.service.ts
@@ -1,17 +1,18 @@
import { Injectable } from '@angular/core'
import { HttpClient, HttpClientModule, HttpHeaders } from '@angular/common/http'
-import { Observable } from 'rxjs'
+import { Observable, map } from 'rxjs'
import { ORCID_BASE_URL } from '../app.constants'
+import { OrcidRecord } from '../shared/model/orcid-record.model'
@Injectable({ providedIn: 'root' })
export class LandingPageService {
private headers: HttpHeaders
- idTokenUri: string = '/services/assertionservice/api/id-token'
- recordConnectionUri: string = '/services/assertionservice/api/assertion/record/'
- memberInfoUri: string = '/services/memberservice/api/members/authorized/'
- userInfoUri: string = ORCID_BASE_URL + '/oauth/userinfo'
- publicKeyUri: string = ORCID_BASE_URL + '/oauth/jwks'
+ idTokenUri = '/services/assertionservice/api/id-token'
+ recordConnectionUri = '/services/assertionservice/api/assertion/record/'
+ memberInfoUri = '/services/memberservice/api/members/authorized/'
+ userInfoUri = ORCID_BASE_URL + '/oauth/userinfo'
+ publicKeyUri = ORCID_BASE_URL + '/oauth/jwks'
constructor(private http: HttpClient) {
this.headers = new HttpHeaders({
@@ -24,17 +25,17 @@ export class LandingPageService {
return this.http.post(this.idTokenUri, JSON.stringify(data), { headers: this.headers })
}
- getOrcidConnectionRecord(state: String): Observable {
+ getOrcidConnectionRecord(state: string): Observable {
const requestUrl = this.recordConnectionUri + state
- return this.http.get(requestUrl, { observe: 'response' })
+ return this.http.get(requestUrl).pipe(map((response: any) => response.body))
}
- getMemberInfo(state: String): Observable {
+ getMemberInfo(state: string): Observable {
const requestUrl = this.memberInfoUri + state
return this.http.get(requestUrl)
}
- getUserInfo(access_token: String): Observable {
+ getUserInfo(access_token: string): Observable {
const headers = new HttpHeaders({
Authorization: 'Bearer ' + access_token,
'Content-Type': 'application/json',
diff --git a/ui/src/app/shared/model/orcid-record.model.ts b/ui/src/app/shared/model/orcid-record.model.ts
new file mode 100644
index 000000000..a21fe3feb
--- /dev/null
+++ b/ui/src/app/shared/model/orcid-record.model.ts
@@ -0,0 +1,15 @@
+import { Moment } from 'moment'
+import { EventType } from 'src/app/app.constants'
+
+export class OrcidRecord {
+ constructor(
+ public email: string,
+ public orcid: string,
+ public tokens?: any,
+ public last_notified?: Moment,
+ public revoke_notification_sent_date?: Moment,
+ public eminder_notification_sent_date?: Moment,
+ public created?: Moment,
+ public modified?: Moment
+ ) {}
+}
diff --git a/ui/src/app/shared/service/window-location.service.ts b/ui/src/app/shared/service/window-location.service.ts
index b7c93ca2f..35d980d8b 100644
--- a/ui/src/app/shared/service/window-location.service.ts
+++ b/ui/src/app/shared/service/window-location.service.ts
@@ -17,4 +17,8 @@ export class WindowLocationService {
getWindowLocationHref(): string {
return window.location.href
}
+
+ getWindowLocationHash(): string {
+ return window.location.hash
+ }
}
diff --git a/ui/src/app/user/users.component.spec.ts b/ui/src/app/user/users.component.spec.ts
index e33bfa34b..3b0a1c70d 100644
--- a/ui/src/app/user/users.component.spec.ts
+++ b/ui/src/app/user/users.component.spec.ts
@@ -44,6 +44,7 @@ describe('UsersComponent', () => {
declarations: [UsersComponent, HasAnyAuthorityDirective, LocalizePipe],
imports: [
ReactiveFormsModule,
+ RouterTestingModule,
RouterModule.forChild([{ path: 'users', component: UsersComponent }]),
FormsModule,
],