Compare commits
480
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23de0695d0 | ||
|
|
8d4cb1bfea | ||
|
|
d67f6dab2f | ||
|
|
8ee0e0b92d | ||
|
|
be9b68eeff | ||
|
|
1e20f7a149 | ||
|
|
b413e9d8dd | ||
|
|
fd3c9eb876 | ||
|
|
9979fe8464 | ||
|
|
9049bb2087 | ||
|
|
82135a4b20 | ||
|
|
dd37718c62 | ||
|
|
e6f0166ff0 | ||
|
|
606a48ca49 | ||
|
|
d1e46cb9b1 | ||
|
|
11ceb8b5ca | ||
|
|
3854d32e92 | ||
|
|
bbb703c671 | ||
|
|
f507edae03 | ||
|
|
9231963ac1 | ||
|
|
9bfb136247 | ||
|
|
4af4ee7cc0 | ||
|
|
12a9f72c68 | ||
|
|
bb88672aff | ||
|
|
25e004976e | ||
|
|
60993e455a | ||
|
|
40ee317f47 | ||
|
|
530cd0298a | ||
|
|
374a13cd07 | ||
|
|
6f5da333e0 | ||
|
|
d121bcf204 | ||
|
|
2acd555966 | ||
|
|
820843c51d | ||
|
|
243f165644 | ||
|
|
5036c62740 | ||
|
|
3ffe70cf5d | ||
|
|
c5b0061428 | ||
|
|
fefae2b73d | ||
|
|
196d01aae2 | ||
|
|
96707ebe15 | ||
|
|
79f5db0441 | ||
|
|
d4fee0af34 | ||
|
|
a8c65e73f5 | ||
|
|
13df776c18 | ||
|
|
12588a17ab | ||
|
|
7b2c8b7d58 | ||
|
|
873965d7b7 | ||
|
|
f02f488edd | ||
|
|
a2ee17e918 | ||
|
|
bf8620d75c | ||
|
|
d0eb41e03b | ||
|
|
6e932f8054 | ||
|
|
76291896c3 | ||
|
|
c803adccc1 | ||
|
|
f7854b642c | ||
|
|
a7a6bdedf9 | ||
|
|
4bcd0e7d06 | ||
|
|
895a25218e | ||
|
|
8356e7d47a | ||
|
|
680ae9980b | ||
|
|
e106454bb5 | ||
|
|
aa227a0db8 | ||
|
|
8aca872c66 | ||
|
|
269c56bf83 | ||
|
|
a89b27c813 | ||
|
|
2804b4322b | ||
|
|
66d16d84d0 | ||
|
|
f420d01fb8 | ||
|
|
4bd80a716e | ||
|
|
8098beb450 | ||
|
|
a6672ecef4 | ||
|
|
79b6737aaa | ||
|
|
31afafff4e | ||
|
|
d4a4828b67 | ||
|
|
44fa206619 | ||
|
|
bc10b00874 | ||
|
|
bbd73b36d1 | ||
|
|
3a52b40214 | ||
|
|
c41855ccc4 | ||
|
|
c013b178d7 | ||
|
|
683fffaa0e | ||
|
|
31f67e568c | ||
|
|
9c15d02080 | ||
|
|
c14be070f7 | ||
|
|
7b1410d832 | ||
|
|
06cf112b61 | ||
|
|
95e3410540 | ||
|
|
77d7187116 | ||
|
|
28d18e389c | ||
|
|
d95ae373ab | ||
|
|
a784eeecc2 | ||
|
|
84962c9ed3 | ||
|
|
cfd2e13832 | ||
|
|
880f8f6b39 | ||
|
|
c5b1cc1498 | ||
|
|
343f85f8e7 | ||
|
|
654bc18422 | ||
|
|
81b59af3b4 | ||
|
|
e50d0ac00b | ||
|
|
9fe89c9b18 | ||
|
|
8c20d29806 | ||
|
|
10d93992d4 | ||
|
|
0552d1db5a | ||
|
|
07919409d9 | ||
|
|
d691c1fa11 | ||
|
|
b632df901b | ||
|
|
ecb67ea344 | ||
|
|
4c2e8f7a76 | ||
|
|
22c4581d4d | ||
|
|
34c668b4d9 | ||
|
|
9437edbf8a | ||
|
|
5890b1b2e8 | ||
|
|
5ea26dc7ea | ||
|
|
6afde1fb18 | ||
|
|
708a0836cc | ||
|
|
3fc51ee54a | ||
|
|
e85a75305f | ||
|
|
9f469aec38 | ||
|
|
dd7907da8c | ||
|
|
634c522eaf | ||
|
|
57a924703e | ||
|
|
89285dcfba | ||
|
|
05c14d7bbb | ||
|
|
8c4228016c | ||
|
|
8d00730653 | ||
|
|
38ed272fc2 | ||
|
|
d0a11d6054 | ||
|
|
8022234e84 | ||
|
|
b58bde0461 | ||
|
|
48c7538172 | ||
|
|
cb938d0e33 | ||
|
|
f2544684ec | ||
|
|
131ce44ecc | ||
|
|
b91f8c1661 | ||
|
|
3943a740bc | ||
|
|
d88a4ac103 | ||
|
|
afeaf3a671 | ||
|
|
1a968a7d24 | ||
|
|
ac5f7fa1c9 | ||
|
|
c1cd762da4 | ||
|
|
5f9230c458 | ||
|
|
2381b3372c | ||
|
|
4a07478f1e | ||
|
|
5c4ceb7f0f | ||
|
|
e940ec98d4 | ||
|
|
b7e0f42d47 | ||
|
|
1b300c676b | ||
|
|
a7cc1b9ba2 | ||
|
|
c99449d8e5 | ||
|
|
2038a463db | ||
|
|
4f0e2794ec | ||
|
|
89891a401a | ||
|
|
d572d5157d | ||
|
|
6ffb4a2350 | ||
|
|
1e1ea1c49a | ||
|
|
fb4238b035 | ||
|
|
3d6d7c99c2 | ||
|
|
5bb7d41903 | ||
|
|
47c453e979 | ||
|
|
e16bfdfb03 | ||
|
|
3138aecc37 | ||
|
|
b19583c08d | ||
|
|
3d8a70d676 | ||
|
|
7e6158b847 | ||
|
|
23e82b352e | ||
|
|
2ccd08a8d4 | ||
|
|
6159378a52 | ||
|
|
65039ce820 | ||
|
|
fde633331c | ||
|
|
597b945aca | ||
|
|
d23684c42f | ||
|
|
cb3bcf1b9e | ||
|
|
c8c5c2e7ef | ||
|
|
8886f14996 | ||
|
|
e8c7e81828 | ||
|
|
1f47ca8ea9 | ||
|
|
f61cad986f | ||
|
|
6256bfc3cf | ||
|
|
3133997e78 | ||
|
|
ed9324a0de | ||
|
|
ab668d74eb | ||
|
|
4fa5b924c9 | ||
|
|
7876de8e2d | ||
|
|
e86a4fc27d | ||
|
|
d72b02ad29 | ||
|
|
545e7d5a7f | ||
|
|
4055f3a06e | ||
|
|
4fed7f3d94 | ||
|
|
ee29f3c599 | ||
|
|
06f00ac8f6 | ||
|
|
97ee639d62 | ||
|
|
a1b0481db9 | ||
|
|
314aa62828 | ||
|
|
bcf27ae048 | ||
|
|
685ddeac7e | ||
|
|
ce9503fc5b | ||
|
|
f2f5a46cdf | ||
|
|
1a0650ff7c | ||
|
|
82a0c7c17c | ||
|
|
dead74c825 | ||
|
|
1397df807f | ||
|
|
24e37d3faa | ||
|
|
cc01bddea3 | ||
|
|
385ea34c98 | ||
|
|
9f32b8cdc3 | ||
|
|
ae8e20b8e1 | ||
|
|
e91612b313 | ||
|
|
aab980a7dc | ||
|
|
13146975e2 | ||
|
|
542b469ad8 | ||
|
|
c2c63294f4 | ||
|
|
56936f78b8 | ||
|
|
dcc77153bf | ||
|
|
4b587d35fa | ||
|
|
ce7cdba0bc | ||
|
|
4e13ea56c3 | ||
|
|
3ef7436f3f | ||
|
|
ee748de5c9 | ||
|
|
74501a440f | ||
|
|
828faf2be4 | ||
|
|
dfb9d6e686 | ||
|
|
2be18bcf6f | ||
|
|
8feeaa47dc | ||
|
|
ec9d953bad | ||
|
|
84051e4b72 | ||
|
|
6b93032a78 | ||
|
|
a909ba67df | ||
|
|
cf40b53fd0 | ||
|
|
3425dc89be | ||
|
|
8e1e2b6b36 | ||
|
|
4ace04de7a | ||
|
|
3bef3e4bc5 | ||
|
|
81447e65c8 | ||
|
|
2e71beee81 | ||
|
|
dfbb54a1ee | ||
|
|
0d67b512a2 | ||
|
|
e551ec4008 | ||
|
|
49a85f1a7f | ||
|
|
15d3520408 | ||
|
|
5d7c0180d5 | ||
|
|
053e16dca8 | ||
|
|
3e2ed2dc27 | ||
|
|
faaf7352c1 | ||
|
|
58252ee6f5 | ||
|
|
85ddd7fc4b | ||
|
|
863e81bc01 | ||
|
|
4b212e1ae0 | ||
|
|
343f73bc21 | ||
|
|
cf5f41acbd | ||
|
|
2f12cc91f2 | ||
|
|
99125dbb77 | ||
|
|
3bfae4b33d | ||
|
|
da6b34b337 | ||
|
|
9a909c0ec1 | ||
|
|
42c01100ad | ||
|
|
fe56acd57f | ||
|
|
e4fb2e7c4d | ||
|
|
b1361bcb83 | ||
|
|
30090d82e3 | ||
|
|
fbe8aefcb4 | ||
|
|
fa02fd3f89 | ||
|
|
f021719d8b | ||
|
|
be9ee8f672 | ||
|
|
48ca4c5ac0 | ||
|
|
ac7db55be2 | ||
|
|
dea79d785b | ||
|
|
f597901140 | ||
|
|
0c1b5bf879 | ||
|
|
bab68f4507 | ||
|
|
c6e4a24dab | ||
|
|
0bcb91ede0 | ||
|
|
fbeb764c76 | ||
|
|
67959d7a58 | ||
|
|
c4bb5173d2 | ||
|
|
ea011592a7 | ||
|
|
e761e0e4af | ||
|
|
fa36f9008b | ||
|
|
15d360abc5 | ||
|
|
9914954d08 | ||
|
|
ec184ff4f1 | ||
|
|
bb2d0420d9 | ||
|
|
e3cabe3102 | ||
|
|
7065f462cd | ||
|
|
6e4ab7d881 | ||
|
|
859a3fdd83 | ||
|
|
b1568aa30f | ||
|
|
a46b44b13f | ||
|
|
59dee87141 | ||
|
|
f1a20ea33d | ||
|
|
4a5e72d77b | ||
|
|
87a6d839e7 | ||
|
|
1b449bdea4 | ||
|
|
1ba0c873e9 | ||
|
|
f8e7ec7f5f | ||
|
|
4d4affe6d7 | ||
|
|
b13befaff2 | ||
|
|
3836656042 | ||
|
|
55e3284e3f | ||
|
|
27a416001b | ||
|
|
43a41d6818 | ||
|
|
b17088803f | ||
|
|
5eb9d01b23 | ||
|
|
288a38f6ea | ||
|
|
e7724ab8a5 | ||
|
|
e7c7d2b280 | ||
|
|
80abe240b4 | ||
|
|
6960aaeffc | ||
|
|
deb8871060 | ||
|
|
ba671b4ab3 | ||
|
|
bd3a4935a4 | ||
|
|
13ce61218c | ||
|
|
fa2e1021c5 | ||
|
|
c213ee18b2 | ||
|
|
43c667729f | ||
|
|
26e6cc8a9b | ||
|
|
5d57c8dd25 | ||
|
|
f31a93085a | ||
|
|
1927ef7832 | ||
|
|
fd53f7b282 | ||
|
|
eaaeed6d1b | ||
|
|
8f8566548a | ||
|
|
40e12366e6 | ||
|
|
39ea49ccdf | ||
|
|
a69dfa251a | ||
|
|
906a336acb | ||
|
|
9c9988a305 | ||
|
|
1a901b15d2 | ||
|
|
e894dbc4e8 | ||
|
|
18e95435f5 | ||
|
|
e2154f9698 | ||
|
|
d27ddb1eee | ||
|
|
8d3aea2e23 | ||
|
|
4d47b3cf51 | ||
|
|
b741d3af16 | ||
|
|
92cf57a542 | ||
|
|
aa6d369514 | ||
|
|
1ed685d81f | ||
|
|
586a4dbe23 | ||
|
|
f858da88f2 | ||
|
|
5f342a0ee8 | ||
|
|
3468acce39 | ||
|
|
b0a93e2e2d | ||
|
|
c2b66b74eb | ||
|
|
32ec1f0f70 | ||
|
|
e539a86600 | ||
|
|
efcdd0701e | ||
|
|
9ab9800f7e | ||
|
|
4336fde21b | ||
|
|
4cb7985ac1 | ||
|
|
04726a61da | ||
|
|
53270519cb | ||
|
|
5105d80a9d | ||
|
|
53423739d3 | ||
|
|
f782d90a7e | ||
|
|
721d09899a | ||
|
|
d1303c7bca | ||
|
|
31682ffd85 | ||
|
|
b5a791d618 | ||
|
|
e110f33e40 | ||
|
|
68bf02ee25 | ||
|
|
063d643d76 | ||
|
|
b6818871fc | ||
|
|
1effa79c93 | ||
|
|
6fd075f7e8 | ||
|
|
0d8508cd4e | ||
|
|
f3bd8d0b6f | ||
|
|
8889e86cec | ||
|
|
6500d56273 | ||
|
|
80cffeb48b | ||
|
|
263c8c01e9 | ||
|
|
74531f5d85 | ||
|
|
e0eed2b4ea | ||
|
|
c9df20184a | ||
|
|
afa1deef94 | ||
|
|
5d04dca21e | ||
|
|
746f45b6da | ||
|
|
a62d36af92 | ||
|
|
a41572302a | ||
|
|
2138aa8b01 | ||
|
|
4ea53c9cc7 | ||
|
|
c4e5a4c121 | ||
|
|
e5544415b0 | ||
|
|
ce84d2405c | ||
|
|
b07c90ee53 | ||
|
|
27a28c6db2 | ||
|
|
82eed3c5fe | ||
|
|
3d13d8f412 | ||
|
|
6927008041 | ||
|
|
c978958df2 | ||
|
|
e8b2e1aa6f | ||
|
|
43528c64c2 | ||
|
|
d7f090f348 | ||
|
|
93a9efbd1b | ||
|
|
a50a0735bb | ||
|
|
838567c2d2 | ||
|
|
95e6144e44 | ||
|
|
58c9df272f | ||
|
|
3696e45275 | ||
|
|
73503f98dc | ||
|
|
15f6c408d4 | ||
|
|
0ccc6e7f78 | ||
|
|
69a10f891a | ||
|
|
18d5b60c6d | ||
|
|
f279ac44ca | ||
|
|
29f21e0dfe | ||
|
|
9e588212a4 | ||
|
|
3361aa1800 | ||
|
|
5c3b44b2f6 | ||
|
|
59d345a912 | ||
|
|
2adfdf6ef6 | ||
|
|
8aa9a51c88 | ||
|
|
60b7d0134a | ||
|
|
ae26d588a9 | ||
|
|
abc1827280 | ||
|
|
682bb14037 | ||
|
|
28ec1008a0 | ||
|
|
83ab375829 | ||
|
|
e960c34542 | ||
|
|
4d2cb86a62 | ||
|
|
22131864f8 | ||
|
|
ac8a6054b0 | ||
|
|
22d578620d | ||
|
|
82506d11c8 | ||
|
|
84818a54f5 | ||
|
|
a0b2c6883a | ||
|
|
180b74cc98 | ||
|
|
12f2042f87 | ||
|
|
f096eaf7d7 | ||
|
|
e2ff75f435 | ||
|
|
96ef767e65 | ||
|
|
f387416d6d | ||
|
|
933ade8a16 | ||
|
|
bf4d43cee2 | ||
|
|
6b3b6c4d58 | ||
|
|
4630e130d4 | ||
|
|
42ace44716 | ||
|
|
888b106780 | ||
|
|
6bd994c86b | ||
|
|
6ce09162d7 | ||
|
|
f487c4f0d9 | ||
|
|
2d0afd7b9c | ||
|
|
f406fc3861 | ||
|
|
760710ae23 | ||
|
|
0219b5c99a | ||
|
|
6056b50939 | ||
|
|
9e57f838d5 | ||
|
|
896671ceb1 | ||
|
|
ca9485053e | ||
|
|
7e6c46de2b | ||
|
|
c68c1ba0fb | ||
|
|
5f83751db2 | ||
|
|
8179ab7dec | ||
|
|
d93d48197f | ||
|
|
d010d25934 | ||
|
|
3bb9d66377 | ||
|
|
42fe8f4eca | ||
|
|
45bf901d9e | ||
|
|
a35aacdf5e | ||
|
|
feddb2efad | ||
|
|
47c664eda9 | ||
|
|
9d847348a0 | ||
|
|
9fc2b7a409 | ||
|
|
02642bc5e4 | ||
|
|
8f8e257523 | ||
|
|
e50db68abe | ||
|
|
ec23e3706b | ||
|
|
e9c297b1cf | ||
|
|
4ae5e73def | ||
|
|
78505c720f | ||
|
|
09c8680ff2 | ||
|
|
811d1dbe32 | ||
|
|
dbe7003068 | ||
|
|
05709f7ee5 | ||
|
|
3084d6cf06 | ||
|
|
ac92575b36 | ||
|
|
900f342894 | ||
|
|
2f3478d043 | ||
|
|
af8947dc62 | ||
|
|
a3c12b36e7 | ||
|
|
92640b96aa |
No files matched your search
@@ -1,5 +1,5 @@
|
||||
name: Validate Monorepo
|
||||
on: [push]
|
||||
on: pull_request
|
||||
jobs:
|
||||
|
||||
compliance:
|
||||
@@ -19,26 +19,10 @@ jobs:
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- run: yarn run compliance:licenses
|
||||
|
||||
audit:
|
||||
name: Security Audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v2
|
||||
id: cache
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '12.x'
|
||||
- run: yarn install
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
- run: yarn run compliance:audit
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v2
|
||||
@@ -56,6 +40,7 @@ jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v2
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
name: "Trigger Test Boundary UI release"
|
||||
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: Please use tag-like format n.n.n-someword (1.111.11-beta)
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
trigger-circleci-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Trigger Release job
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
echo "Triggering Test release using BRANCH and TEST_TAG ${{ github.event.inputs.branch }}"
|
||||
|
||||
curl -u ${{ secrets.CIRCLE_TOKEN }}: -X POST --header "Content-Type: application/json" \
|
||||
-d '{ "branch": "main", "parameters": {"TAG": "'${{ github.event.inputs.branch }}'" }}' \
|
||||
https://circleci.com/api/v2/project/github/hashicorp/boundary-ui-releases/pipeline
|
||||
@@ -0,0 +1,6 @@
|
||||
# Contributing to Boundary
|
||||
|
||||
Thank you for contributing to Boundary! Here you can find common questions around reporting issues and opening
|
||||
pull requests to our project.
|
||||
|
||||
When contributing in any way to the Boundary project (new issue, PR, etc), please be aware that our team identifies with many gender pronouns and to use non-binary pronouns when addressing our team. For more reading on our code of conduct, please see the [HashiCorp community guidelines](https://www.hashicorp.com/community-guidelines).
|
||||
@@ -24,6 +24,9 @@ subproject, e.g. running or testing.
|
||||
- [Building Admin UI for Production](#building-admin-ui-for-production)
|
||||
- [Building with a Container](#building-with-a-container)
|
||||
- [Building Desktop UI for Production](#building-desktop-ui-for-production)
|
||||
- [Connect Boundary UI to Boundary local instance](#connect-boundary-ui-to-boundary-local-instance)
|
||||
- [For admin](#for-admin)
|
||||
- [For desktop](#for-desktop)
|
||||
- [Committing](#committing)
|
||||
- [License Checking](#license-checking)
|
||||
|
||||
@@ -35,6 +38,9 @@ You will need the following things properly installed on your computer.
|
||||
|
||||
* [Git][git]
|
||||
* [Node.js][node]
|
||||
* v10–v15
|
||||
* **Apple Silicon Users**: if you experience problems building or running
|
||||
Boundary UIs, try Node v15.
|
||||
* [Yarn][yarn] installed globally
|
||||
* [Google Chrome][chrome]
|
||||
* [Firefox][firefox]
|
||||
@@ -116,11 +122,45 @@ yarn install
|
||||
yarn build:ui:desktop
|
||||
```
|
||||
|
||||
In windows, UI is generated using docker to provide a stable UI across platforms.
|
||||
|
||||
```cmd
|
||||
yarn install
|
||||
docker-compose -f docker-compose-embedding.yml run build-desktop-production
|
||||
yarn build:ui:desktop:app
|
||||
```
|
||||
|
||||
To provide a signing identify for macOS build, use `BOUNDARY_DESKTOP_SIGNING_IDENTITY`
|
||||
environment variable to set signing certificate name (e.g Developer ID Application: * (*)).
|
||||
environment variable to set signing certificate name (e.g Developer ID Application: * (*)) when building desktop app using `yarn build:ui:desktop:app`.
|
||||
|
||||
Assets are saved to `ui/desktop/electron-app/out/make/`.
|
||||
DMG packaged desktop UI is available at asset location as `boundary.dmg`.
|
||||
EXE archived desktop UI is available at asset location under `zip` folder.
|
||||
|
||||
### Connect Boundary UI to Boundary local instance
|
||||
|
||||
This describes how to connect local Boundary UI to your local instance of Boundary.
|
||||
|
||||
This assumes you already have up and running a Boundary dev instance in your local environment with a listener `127.0.0.1:9200` (default behaviour). [Learn how to start a local Boundary dev instance](https://learn.hashicorp.com/tutorials/boundary/getting-started-dev?in=boundary/getting-started#start-a-dev-environment).
|
||||
#### For admin
|
||||
|
||||
This assumes you are within `boundary-ui/ui/admin`.
|
||||
|
||||
You will need to turn `off` mirage, tell the UI where to find Boundary and run it: `$ ENABLE_MIRAGE=false API_HOST=http://localhost:9200 yarn start`.
|
||||
|
||||
Following terminal instructions, open in your browser: `http://localhost:4200/`.
|
||||
|
||||
Once you open the UI you will see the login screen, authenticate using generated admin login name and password.
|
||||
|
||||
#### For desktop
|
||||
|
||||
This assumes you are within `boundary-ui/ui/desktop`.
|
||||
|
||||
You will need to turn `off` mirage and run it: `$ ENABLE_MIRAGE=false yarn start:desktop`.
|
||||
|
||||
In login screen, authenticate using generated admin login name and password.
|
||||
|
||||
Enter the origin of your Boundary dev instance, by default is: `http://localhost:9200`.
|
||||
|
||||
### Committing
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { serializeIntoHash } from '@ember-data/adapter/-private';
|
||||
/* eslint-disable-next-line ember/no-mixins */
|
||||
import AdapterBuildURLMixin from '../mixins/adapter-build-url';
|
||||
import config from 'ember-get-config';
|
||||
import { get, getWithDefault } from '@ember/object';
|
||||
import { get } from '@ember/object';
|
||||
import { InvalidError } from '@ember-data/adapter/error';
|
||||
import { dasherize } from '@ember/string';
|
||||
import { pluralize } from 'ember-inflector';
|
||||
@@ -69,7 +69,7 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
||||
* @return {string}
|
||||
*/
|
||||
urlSuffix(modelName, id, snapshot = {}) {
|
||||
const method = getWithDefault(snapshot, 'adapterOptions.method', '');
|
||||
const method = get(snapshot, 'adapterOptions.method') || '';
|
||||
return method ? `:${method}` : '';
|
||||
}
|
||||
|
||||
@@ -248,7 +248,11 @@ export default class ApplicationAdapter extends RESTAdapter.extend(
|
||||
// Normalize field-specific errors, if any.
|
||||
const errors = fieldErrors.map((error) => ({
|
||||
detail: error.description,
|
||||
source: { pointer: `/data/attributes/${error.name}` },
|
||||
// Ember Data cannot handle errors on nested attributes, so these are
|
||||
// flattened (e.g. `attributes.foobar` becomes `attributes_foobar`).
|
||||
// To capture these errors, add read-only fields on the model
|
||||
// corresponding to the underscored field name.
|
||||
source: { pointer: `/data/attributes/${error.name.replace('.', '_')}` },
|
||||
}));
|
||||
// Return a list of JSON API errors rooted under the `errors` key.
|
||||
return {
|
||||
|
||||
@@ -7,6 +7,12 @@ import { attr } from '@ember-data/model';
|
||||
export default class GeneratedAuthMethodModel extends BaseModel {
|
||||
// =attributes
|
||||
|
||||
@attr('boolean', {
|
||||
description: 'Specifies if this is the primary auth method for its scope.',
|
||||
readOnly: true,
|
||||
})
|
||||
is_primary;
|
||||
|
||||
@attr('string', {
|
||||
description: 'The type of the resource, to help differentiate schemas',
|
||||
})
|
||||
|
||||
@@ -7,6 +7,12 @@ import { attr } from '@ember-data/model';
|
||||
export default class GeneratedScopeModel extends BaseModel {
|
||||
// =attributes
|
||||
|
||||
@attr('string', {
|
||||
description:
|
||||
'The ID of the primary auth method for this scope. A primary auth method\nis allowed to vivify users when new accounts are created and is the source for the users account info.',
|
||||
})
|
||||
primary_auth_method_id;
|
||||
|
||||
@attr('string', {
|
||||
description: 'The type of the resource',
|
||||
})
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// app/initializers/fragment-serializer.js
|
||||
import FragmentAuthMethodAttributesAccountClaimMapSerializer from '../serializers/fragment-auth-method-attributes-account-claim-map';
|
||||
|
||||
export function initialize(application) {
|
||||
application.register(
|
||||
'serializer:fragment-auth-method-attributes-account-claim-map',
|
||||
FragmentAuthMethodAttributesAccountClaimMapSerializer
|
||||
);
|
||||
}
|
||||
|
||||
// Fragment serializers must be manually registered
|
||||
export default {
|
||||
name: 'fragment-auth-method-attributes-account-claim-map',
|
||||
initialize: initialize,
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
// app/initializers/fragment-serializer.js
|
||||
import FragmentStringSerializer from '../serializers/fragment-string';
|
||||
|
||||
export function initialize(application) {
|
||||
application.register('serializer:fragment-string', FragmentStringSerializer);
|
||||
}
|
||||
|
||||
// Fragment serializers must be manually registered
|
||||
export default {
|
||||
name: 'fragment-string',
|
||||
initialize: initialize,
|
||||
};
|
||||
@@ -17,6 +17,11 @@ export default class AccountModel extends GeneratedAccountModel {
|
||||
*/
|
||||
@equal('type', 'password') isPassword;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
@equal('type', 'oidc') isOIDC;
|
||||
|
||||
/**
|
||||
* Convenience for getting username in account.
|
||||
* @type {string}
|
||||
|
||||
@@ -1,3 +1,123 @@
|
||||
import GeneratedAuthMethodModel from '../generated/models/auth-method';
|
||||
import { attr } from '@ember-data/model';
|
||||
import { fragment } from 'ember-data-model-fragments/attributes';
|
||||
import { equal } from '@ember/object/computed';
|
||||
|
||||
export default class AuthMethodModel extends GeneratedAuthMethodModel {}
|
||||
/**
|
||||
* Enum options per auth method type and field.
|
||||
*/
|
||||
export const options = {
|
||||
oidc: {
|
||||
signing_algorithms: [
|
||||
'RS256',
|
||||
'RS384',
|
||||
'RS512',
|
||||
'ES256',
|
||||
'ES384',
|
||||
'ES512',
|
||||
'PS256',
|
||||
'PS384',
|
||||
'PS512',
|
||||
'EdDSA',
|
||||
],
|
||||
state: ['inactive', 'active-private', 'active-public'],
|
||||
account_claim_maps: {
|
||||
to: ['sub', 'name', 'email'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default class AuthMethodModel extends GeneratedAuthMethodModel {
|
||||
// =error attributes
|
||||
// These attributes exist solely to capture errors on nested fields.
|
||||
// See the application adapter's error normalization method for
|
||||
// more information.
|
||||
|
||||
@attr('string', { readOnly: true }) attributes_state;
|
||||
@attr('string', { readOnly: true }) attributes_issuer;
|
||||
@attr('string', { readOnly: true }) attributes_client_id;
|
||||
@attr('string', { readOnly: true }) attributes_client_secret;
|
||||
@attr('string', { readOnly: true }) attributes_max_age;
|
||||
@attr('string', { readOnly: true }) attributes_api_url_prefix;
|
||||
@attr('string', { readOnly: true })
|
||||
attributes_disable_discovered_config_validation;
|
||||
@attr('string', { readOnly: true }) attributes_dry_run;
|
||||
@attr('string', { readOnly: true }) attributes_account_claim_maps;
|
||||
@attr('string', { readOnly: true }) attributes_claims_scopes;
|
||||
@attr('string', { readOnly: true }) attributes_signing_algorithms;
|
||||
@attr('string', { readOnly: true }) attributes_allowed_audiences;
|
||||
@attr('string', { readOnly: true }) attributes_idp_ca_certs;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* Attributes of this resource, if any, represented as a JSON fragment.
|
||||
* @type {FragmentAuthMethodAttributesModel}
|
||||
*/
|
||||
@fragment('fragment-auth-method-attributes', { defaultValue: {} }) attributes;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
@equal('is_primary', true) isPrimary;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isPassword() {
|
||||
return this.type === 'password';
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isOIDC() {
|
||||
return this.type === 'oidc';
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isInactive() {
|
||||
return this.attributes.state === 'inactive';
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isPrivate() {
|
||||
return this.attributes.state === 'active-private';
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isPublic() {
|
||||
return this.attributes.state === 'active-public';
|
||||
}
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Change the active and visibility state of an OIDC auth method
|
||||
* given its ID.
|
||||
* For OIDC auth methods only.
|
||||
* @param {[string]} state
|
||||
* @param {object} options
|
||||
* @param {object} options.adapterOptions
|
||||
* @return {Promise}
|
||||
*/
|
||||
changeState(state, options = { adapterOptions: {} }) {
|
||||
const defaultAdapterOptions = {
|
||||
method: 'change-state',
|
||||
state,
|
||||
};
|
||||
return this.save({
|
||||
...options,
|
||||
adapterOptions: {
|
||||
...defaultAdapterOptions,
|
||||
...options.adapterOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,16 @@ import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
export default class FragmentAccountAttributesModel extends Fragment {
|
||||
// =attributes (password)
|
||||
@attr('string', {
|
||||
for: 'password',
|
||||
description: 'The account login name',
|
||||
})
|
||||
login_name;
|
||||
|
||||
// =attributes (OIDC)
|
||||
@attr('string', { for: 'oidc' }) subject;
|
||||
@attr('string', { for: 'oidc' }) issuer;
|
||||
@attr('string', { for: 'oidc', readOnly: true }) email;
|
||||
@attr('string', { for: 'oidc', readOnly: true }) full_name;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { attr } from '@ember-data/model';
|
||||
|
||||
export default class FragmentAuthMethodAttributesAccountClaimMapModel extends Fragment {
|
||||
// =attributes (OIDC)
|
||||
|
||||
@attr('string') from;
|
||||
@attr('string') to;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import Fragment from 'ember-data-model-fragments/fragment';
|
||||
import { attr } from '@ember-data/model';
|
||||
import { fragmentArray } from 'ember-data-model-fragments/attributes';
|
||||
|
||||
export default class FragmentAuthMethodAttributesModel extends Fragment {
|
||||
// =attributes (OIDC)
|
||||
|
||||
@attr('string', { readOnly: true }) state;
|
||||
@attr('string') issuer;
|
||||
@attr('string') client_id;
|
||||
@attr('string') client_secret;
|
||||
@attr('string', { readOnly: true }) client_secret_hmac;
|
||||
@attr('number') max_age;
|
||||
@attr('string') api_url_prefix;
|
||||
@attr('string', { readOnly: true }) callback_url;
|
||||
@attr('boolean') disable_discovered_config_validation;
|
||||
@attr('boolean') dry_run;
|
||||
|
||||
@fragmentArray('fragment-auth-method-attributes-account-claim-map', {
|
||||
emptyArrayIfMissing: true,
|
||||
})
|
||||
account_claim_maps;
|
||||
|
||||
@fragmentArray('fragment-string', {
|
||||
emptyArrayIfMissing: true,
|
||||
})
|
||||
claims_scopes;
|
||||
|
||||
@fragmentArray('fragment-string', {
|
||||
emptyArrayIfMissing: true,
|
||||
})
|
||||
signing_algorithms;
|
||||
|
||||
@fragmentArray('fragment-string', {
|
||||
emptyArrayIfMissing: true,
|
||||
})
|
||||
allowed_audiences;
|
||||
|
||||
@fragmentArray('fragment-string', { emptyArrayIfMissing: true }) idp_ca_certs;
|
||||
}
|
||||
@@ -20,13 +20,36 @@ export default class AccountSerializer extends ApplicationSerializer {
|
||||
* @return {object}
|
||||
*/
|
||||
serialize(snapshot) {
|
||||
switch (snapshot.record.type) {
|
||||
case 'password':
|
||||
return this.serializePassword(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Password serialization omits `attributes` and handles password-related
|
||||
* custom methods.
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {object}
|
||||
*/
|
||||
serializePassword(snapshot) {
|
||||
const password = snapshot?.adapterOptions?.password;
|
||||
let serialized = super.serialize(...arguments);
|
||||
// Only login_name is serialized into password attributes.
|
||||
const { login_name } = serialized.attributes;
|
||||
if (login_name) {
|
||||
serialized.attributes = { login_name };
|
||||
} else {
|
||||
serialized.attributes = {};
|
||||
}
|
||||
// New record case
|
||||
if (password && snapshot?.record?.isNew)
|
||||
serialized.attributes.password = password;
|
||||
// Set password custom method
|
||||
if (snapshot?.adapterOptions?.method === 'set-password') {
|
||||
serialized = this.serializeForSetPassword(snapshot, password);
|
||||
}
|
||||
// Change password custom method
|
||||
if (snapshot?.adapterOptions?.method === 'change-password') {
|
||||
const { currentPassword, newPassword } = snapshot?.adapterOptions;
|
||||
serialized = this.serializeForChangePassword(
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import ApplicationSerializer from './application';
|
||||
import { copy } from 'ember-copy';
|
||||
|
||||
export default class AuthMethodSerializer extends ApplicationSerializer {
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Delegates to a type-specific serialization, or default.
|
||||
* @override
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {object}
|
||||
*/
|
||||
serialize(snapshot) {
|
||||
switch (snapshot.record.type) {
|
||||
case 'oidc':
|
||||
return this.serializeOIDC(...arguments);
|
||||
default:
|
||||
return this.serializeDefault(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default serialization omits `attributes`.
|
||||
* @return {object}
|
||||
*/
|
||||
serializeDefault() {
|
||||
let serialized = super.serialize(...arguments);
|
||||
delete serialized.attributes;
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `adapterOptions.state` is set, the serialization should
|
||||
* include **only state** and version. Normally, this is not serialized.
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {object}
|
||||
*/
|
||||
serializeOIDC(snapshot) {
|
||||
let serialized = super.serialize(...arguments);
|
||||
const state = snapshot?.adapterOptions?.state;
|
||||
if (state) {
|
||||
serialized = this.serializeOIDCWithState(snapshot, state);
|
||||
} else {
|
||||
delete serialized.attributes.state;
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a payload containing only state and version.
|
||||
* @param {Snapshot} snapshot
|
||||
* @param {string} state
|
||||
* @return {object}
|
||||
*/
|
||||
serializeOIDCWithState(snapshot, state) {
|
||||
return {
|
||||
version: snapshot.attr('version'),
|
||||
attributes: { state },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the array in order of "is_primary" such that the primary method
|
||||
* appears first. The "natural" order of auth methods places primary first.
|
||||
*
|
||||
* @override
|
||||
* @return {object}
|
||||
*/
|
||||
normalizeArrayResponse() {
|
||||
const normalized = super.normalizeArrayResponse(...arguments);
|
||||
normalized.data = normalized.data.sort((a) =>
|
||||
a.attributes.is_primary ? -1 : 1
|
||||
);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets `is_primary` to `false` if it is missing in the response. The API
|
||||
* omits "zero" values per:
|
||||
* https://developers.google.com/protocol-buffers/docs/proto3#default
|
||||
*
|
||||
* TODO: generalize this so that all zero values are reified.
|
||||
* @override
|
||||
* @see FragmentScope
|
||||
* @param {Model} typeClass
|
||||
* @param {Object} hash
|
||||
* @return {Object}
|
||||
*/
|
||||
normalize(typeClass, hash, ...rest) {
|
||||
let normalizedHash = copy(hash, true);
|
||||
// switch (normalizedHash.type) {
|
||||
// case 'oidc':
|
||||
// normalizedHash = this.normalizeOIDC(normalizedHash);
|
||||
// break;
|
||||
// }
|
||||
const normalized = super.normalize(typeClass, normalizedHash, ...rest);
|
||||
if (!normalized.data.attributes.is_primary) {
|
||||
normalized.data.attributes.is_primary = false;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import JSONSerializer from '@ember-data/serializer/json';
|
||||
|
||||
export default class FragmentAccountAttributesSerializer extends JSONSerializer {
|
||||
/**
|
||||
* If an attribute is annotated as readOnly in the model, don't serialize it.
|
||||
* Otherwise delegate to default attribute serializer.
|
||||
*
|
||||
* @override
|
||||
* @method serializeAttribute
|
||||
* @param {Snapshot} snapshot
|
||||
* @param {Object} json
|
||||
* @param {String} key
|
||||
* @param {Object} attribute
|
||||
*/
|
||||
serializeAttribute(snapshot, json, key, attribute) {
|
||||
const { type, options } = attribute;
|
||||
let value = super.serializeAttribute(...arguments);
|
||||
// Convert empty strings to null.
|
||||
if (type === 'string' && json[key] === '') json[key] = null;
|
||||
// Do not serialize read-only attributes.
|
||||
if (options.readOnly) delete json[key];
|
||||
// Client secret cannot be unset. If it's falsy, it must be removed.
|
||||
if (key === 'client_secret' && !json[key]) delete json[key];
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import JSONSerializer from '@ember-data/serializer/json';
|
||||
|
||||
export default class FragmentHostSetSerializer extends JSONSerializer {
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Serializes an account claim to string.
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {string}
|
||||
*/
|
||||
serialize(snapshot) {
|
||||
const from = snapshot.attr('from');
|
||||
const to = snapshot.attr('to');
|
||||
return `${from}=${to}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes account claim strings into JSON API objects.
|
||||
* @param {Model} typeClass
|
||||
* @param {string} value
|
||||
* @return {object}
|
||||
*/
|
||||
normalize(typeClass, value) {
|
||||
const normalizedHash = {
|
||||
from: value.split('=')[0],
|
||||
to: value.split('=')[1],
|
||||
};
|
||||
return super.normalize(typeClass, normalizedHash);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import JSONSerializer from '@ember-data/serializer/json';
|
||||
|
||||
export default class FragmentAuthMethodAttributesSerializer extends JSONSerializer {
|
||||
/**
|
||||
* If an attribute is annotated as readOnly in the model, don't serialize it.
|
||||
* Otherwise delegate to default attribute serializer.
|
||||
*
|
||||
* @override
|
||||
* @method serializeAttribute
|
||||
* @param {Snapshot} snapshot
|
||||
* @param {Object} json
|
||||
* @param {String} key
|
||||
* @param {Object} attribute
|
||||
*/
|
||||
serializeAttribute(snapshot, json, key, attribute) {
|
||||
const { type, options } = attribute;
|
||||
let value = super.serializeAttribute(...arguments);
|
||||
// Convert empty strings to null.
|
||||
if (type === 'string' && json[key] === '') json[key] = null;
|
||||
// Do not serialize read-only attributes.
|
||||
if (options.readOnly) delete json[key];
|
||||
// Client secret cannot be unset. If it's falsy, it must be removed.
|
||||
if (key === 'client_secret' && !json[key]) delete json[key];
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import JSONSerializer from '@ember-data/serializer/json';
|
||||
|
||||
export default class FragmentStringSerializer extends JSONSerializer {
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Serializes a to string.
|
||||
* @param {Snapshot} snapshot
|
||||
* @return {string}
|
||||
*/
|
||||
serialize(snapshot) {
|
||||
return snapshot.attr('value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes from strings.
|
||||
* @param {Model} typeClass
|
||||
* @param {string} value
|
||||
* @return {object}
|
||||
*/
|
||||
normalize(typeClass, value) {
|
||||
return super.normalize(typeClass, { value });
|
||||
}
|
||||
}
|
||||
@@ -37,21 +37,4 @@ export default class HostSetSerializer extends ApplicationSerializer {
|
||||
host_ids: hostIDs,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts string arrays to object arrays for use with `fragment-string`.
|
||||
* E.g. `"foo"` -> `{value: "foo"}`
|
||||
* @override
|
||||
* @param {Model} modelClass
|
||||
* @param {Object} resourceHash
|
||||
* @return {Object}
|
||||
*/
|
||||
normalize(modelClass, resourceHash) {
|
||||
// TODO: can fragment string normalization be handled generically?
|
||||
// TODO: host_ids might actually be attributes.host_ids
|
||||
if (resourceHash.host_ids) {
|
||||
resourceHash.host_ids = resourceHash.host_ids.map((value) => ({ value }));
|
||||
}
|
||||
return super.normalize(modelClass, resourceHash);
|
||||
}
|
||||
}
|
||||
@@ -76,8 +76,9 @@ export default class StorageService extends Service {
|
||||
const inMemory = get(config, 'storage.memory');
|
||||
// Get the global context object (window) via service lookup,
|
||||
// because just reaching for a global is icky
|
||||
const globalContext = getOwner(this).lookup('service:-document')
|
||||
.documentElement.parentNode.defaultView;
|
||||
const globalContext =
|
||||
getOwner(this).lookup('service:-document').documentElement.parentNode
|
||||
.defaultView;
|
||||
// If memory storage enabled, return the memory storage instance,
|
||||
// otherwise return localStorage.
|
||||
return inMemory ? this.#memoryStorage : globalContext.localStorage;
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
default,
|
||||
initialize,
|
||||
} from 'api/initializers/fragment-auth-method-attributes-account-claim-map-serializer';
|
||||
@@ -0,0 +1 @@
|
||||
export { default, initialize } from 'api/initializers/fragment-string';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/models/fragment-auth-method-attributes-account-claim-map';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/models/fragment-auth-method-attributes';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/auth-method';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/fragment-account-attributes';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/fragment-auth-method-attributes';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/fragment-string';
|
||||
@@ -60,7 +60,17 @@ export default function () {
|
||||
this.post('/auth-methods');
|
||||
this.get('/auth-methods/:id');
|
||||
this.patch('/auth-methods/:id');
|
||||
this.del('/auth-methods/:id');
|
||||
this.del('/auth-methods/:id', ({ authMethods }, { params: { id } }) => {
|
||||
const authMethod = authMethods.find(id);
|
||||
const scope = authMethod.scope;
|
||||
authMethod.destroy();
|
||||
// If the primary auth method is deleted, the associated scope's
|
||||
// primary_auth_method_id field should be set to null.
|
||||
if (scope.primaryAuthMethodId === id) {
|
||||
scope.update({ primaryAuthMethodId: null });
|
||||
}
|
||||
return new Response(202);
|
||||
});
|
||||
|
||||
// Auth Method Accounts
|
||||
this.get(
|
||||
@@ -96,8 +106,9 @@ export default function () {
|
||||
}
|
||||
);
|
||||
|
||||
// Authenticate/deauthenticate routes
|
||||
// Authenticate + auth methods custom routes
|
||||
this.post('/auth-methods/:id_method', authHandler);
|
||||
// Deauthenticate route
|
||||
this.post('/scopes/:id_method', deauthHandler);
|
||||
|
||||
// IAM : Users
|
||||
@@ -413,9 +424,12 @@ export default function () {
|
||||
);
|
||||
this.get('/sessions/:id', function ({ sessions }, { params: { id } }) {
|
||||
const session = sessions.find(id);
|
||||
return session.update({
|
||||
status: pickRandomStatusString(),
|
||||
});
|
||||
if (!Ember.testing) {
|
||||
session.update({
|
||||
status: pickRandomStatusString(),
|
||||
});
|
||||
}
|
||||
return session;
|
||||
});
|
||||
this.post(
|
||||
'/sessions/:idMethod',
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import factory from '../generated/factories/auth-method';
|
||||
import { trait } from 'ember-cli-mirage';
|
||||
|
||||
const types = ['password', 'oidc'];
|
||||
|
||||
export default factory.extend({
|
||||
id: (i) => `auth-method-id-${i}`,
|
||||
|
||||
// Cycle through available types
|
||||
type: (i) => types[i % types.length],
|
||||
|
||||
/**
|
||||
* Adds accounts (with associated users) to auth method.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import factory from '../generated/factories/target';
|
||||
import { trait } from 'ember-cli-mirage';
|
||||
import { random } from 'faker';
|
||||
import { random, datatype } from 'faker';
|
||||
|
||||
const randomBoolean = (chance = 0.5) => Math.random() < chance;
|
||||
const hostSetChance = 0.3;
|
||||
@@ -9,7 +9,7 @@ export default factory.extend({
|
||||
/**
|
||||
* -1 means "unlimited" and we want to generate these on occassion.
|
||||
*/
|
||||
session_connection_limit: () => random.arrayElement([-1, random.number()]),
|
||||
session_connection_limit: () => random.arrayElement([-1, datatype.number()]),
|
||||
|
||||
/**
|
||||
* Generates attributes fields by type.
|
||||
@@ -18,7 +18,7 @@ export default factory.extend({
|
||||
if (target.type === 'tcp') {
|
||||
target.update({
|
||||
attributes: {
|
||||
default_port: random.number(),
|
||||
default_port: datatype.number(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { random, date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedAccountFactory
|
||||
@@ -10,6 +10,6 @@ export default Factory.extend({
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
version: () => random.number(),
|
||||
version: () => datatype.number(),
|
||||
attributes: () => ({ login_name: random.words() }),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { random, date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedAuthMethodModelFactory
|
||||
@@ -10,5 +10,30 @@ export default Factory.extend({
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
disabled: () => random.boolean(),
|
||||
disabled: () => datatype.boolean(),
|
||||
|
||||
attributes: function () {
|
||||
let attrs;
|
||||
switch (this.type) {
|
||||
case 'oidc':
|
||||
attrs = {
|
||||
api_url_prefix: 'http://127.0.0.1:9200',
|
||||
callback_url:
|
||||
'http://127.0.0.1:9200/v1/auth-methods/oidc:authenticate:callback',
|
||||
client_id: 'YTwdJBB55LpKnuXb9n65',
|
||||
client_secret_hmac: 'GY7blb4ynM5TEPNi_qnphJ9aDWhW8FpmN3D-eP_L7Zk',
|
||||
issuer: 'http://127.0.0.1:57070',
|
||||
max_age: 1800,
|
||||
disable_discovered_config_validation: false,
|
||||
state: 'active-public',
|
||||
account_claim_maps: ['oid=sub', 'full_name=name'],
|
||||
claims_scopes: ['profile', 'email'],
|
||||
signing_algorithms: ['RS256', 'RS384'],
|
||||
allowed_audiences: ['www.alice.com', 'www.alice.com/admin'],
|
||||
idp_ca_certs: ['certificate-1234', 'certificate-5678'],
|
||||
};
|
||||
break;
|
||||
}
|
||||
return attrs;
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { random, date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedGroupModelFactory
|
||||
@@ -10,5 +10,5 @@ export default Factory.extend({
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
disabled: () => random.boolean(),
|
||||
disabled: () => datatype.boolean(),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { random, date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedHostCatalogModelFactory
|
||||
@@ -10,5 +10,5 @@ export default Factory.extend({
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
disabled: () => random.boolean(),
|
||||
disabled: () => datatype.boolean(),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { random, date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedHostSetModelFactory
|
||||
@@ -10,6 +10,6 @@ export default Factory.extend({
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
version: () => random.number(),
|
||||
size: () => random.number(),
|
||||
version: () => datatype.number(),
|
||||
size: () => datatype.number(),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date, internet } from 'faker';
|
||||
import { random, date, internet, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedHostModelFactory
|
||||
@@ -10,7 +10,7 @@ export default Factory.extend({
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
version: () => random.number(),
|
||||
version: () => datatype.number(),
|
||||
attributes: () => {
|
||||
return { address: internet.ipv6() };
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { random, date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedProjectModelFactory
|
||||
@@ -10,5 +10,5 @@ export default Factory.extend({
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
disabled: () => random.boolean(),
|
||||
disabled: () => datatype.boolean(),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { random, date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedRoleModelFactory
|
||||
@@ -10,6 +10,6 @@ export default Factory.extend({
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
disabled: () => random.boolean(),
|
||||
version: () => random.number(),
|
||||
disabled: () => datatype.boolean(),
|
||||
version: () => datatype.number(),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { random, date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedScopeModelFactory
|
||||
@@ -9,5 +9,5 @@ export default Factory.extend({
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
disabled: () => random.boolean(),
|
||||
disabled: () => datatype.boolean(),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedSessionModelFactory
|
||||
@@ -8,5 +8,5 @@ export default Factory.extend({
|
||||
type: 'tcp',
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
version: () => random.number(),
|
||||
version: () => datatype.number(),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { random, date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedTargetModelFactory
|
||||
@@ -7,11 +7,11 @@ import { random, date } from 'faker';
|
||||
export default Factory.extend({
|
||||
type: 'tcp',
|
||||
name: () => random.words(),
|
||||
session_max_seconds: () => random.number(),
|
||||
session_connection_limit: () => random.number(),
|
||||
session_max_seconds: () => datatype.number(),
|
||||
session_connection_limit: () => datatype.number(),
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
disabled: () => random.boolean(),
|
||||
version: () => random.number(),
|
||||
disabled: () => datatype.boolean(),
|
||||
version: () => datatype.number(),
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date } from 'faker';
|
||||
import { random, date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedUserModelFactory
|
||||
@@ -10,5 +10,5 @@ export default Factory.extend({
|
||||
description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
disabled: () => random.boolean(),
|
||||
disabled: () => datatype.boolean(),
|
||||
});
|
||||
@@ -12,7 +12,7 @@ import { Response } from 'miragejs';
|
||||
// */
|
||||
// export default function authenticateHandler(schema, request) {
|
||||
// const payload = JSON.parse(request.requestBody);
|
||||
// if (payload.credentials.login_name === 'error') {
|
||||
// if (payload.attributes.login_name === 'error') {
|
||||
// return new Response(400);
|
||||
// } else {
|
||||
// const cookieName = config.auth.passwordCookieName;
|
||||
@@ -23,31 +23,117 @@ import { Response } from 'miragejs';
|
||||
// }
|
||||
// }
|
||||
|
||||
// Quick and dirty counter system to simulate the polling behavior
|
||||
// of the API. If the OIDC flow is pending, the route returns 100.
|
||||
// If complete it returns 200 with the token JSON.
|
||||
let oidcAttemptCounter = 0;
|
||||
const oidcRequiredAttempts = 3;
|
||||
|
||||
const commandHandlers = {
|
||||
password: {
|
||||
login: (payload, scopeAttrs) => {
|
||||
if (payload.attributes.login_name === 'error') {
|
||||
return new Response(400);
|
||||
} else {
|
||||
return new Response(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
attributes: {
|
||||
scope: scopeAttrs,
|
||||
id: 'token123',
|
||||
token: 'thetokenstring',
|
||||
account_id: '1',
|
||||
user_id: 'user123',
|
||||
auth_method_id: 'authmethod123',
|
||||
created_time: '',
|
||||
updated_time: '',
|
||||
last_used_time: '',
|
||||
expiration_time: '',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* OIDC authentication is a two-step process:
|
||||
*
|
||||
* 1. `authenticate:start` kicks of authentication by requesting some details
|
||||
* from the Boundary server about the request, including the third-party
|
||||
* URL to which to redirect the user.
|
||||
* 2. `authenticate` accepts the token_id and state parameters in
|
||||
* order to retrieve a Boundary token. This endpoint may be polled until
|
||||
* the authentication flow is completed.
|
||||
*/
|
||||
oidc: {
|
||||
start: () =>
|
||||
new Response(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
attributes: {
|
||||
auth_url: 'https://www.duckduckgo.com',
|
||||
token_id: 'token_1234',
|
||||
},
|
||||
}
|
||||
),
|
||||
token: (_, scopeAttrs) => {
|
||||
oidcAttemptCounter++;
|
||||
if (oidcAttemptCounter < oidcRequiredAttempts) {
|
||||
return new Response(202);
|
||||
} else {
|
||||
return new Response(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
attributes: {
|
||||
scope: scopeAttrs,
|
||||
id: 'token123',
|
||||
token: 'thetokenstring',
|
||||
account_id: '1',
|
||||
user_id: 'user123',
|
||||
auth_method_id: 'authmethod123',
|
||||
created_time: '',
|
||||
updated_time: '',
|
||||
last_used_time: '',
|
||||
expiration_time: '',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Handles all custom methods on /auth-methods/:id route
|
||||
export function authHandler({ scopes, authMethods }, request) {
|
||||
const payload = JSON.parse(request.requestBody);
|
||||
if (payload.credentials.login_name === 'error') {
|
||||
return new Response(400);
|
||||
} else {
|
||||
const id = request.params.id_method.split(':')[0];
|
||||
const authMethod = authMethods.find(id);
|
||||
const [, id, method] = request.params.id_method.match(
|
||||
/(?<id>.[^:]*):(?<method>(.*))/
|
||||
);
|
||||
const authMethod = authMethods.find(id);
|
||||
|
||||
if (method === 'authenticate') {
|
||||
const payload = JSON.parse(request.requestBody);
|
||||
const { command } = payload;
|
||||
const scope = scopes.find(authMethod.scopeId);
|
||||
const scopeAttrs = this.serialize(scopes.find(scope.id));
|
||||
return new Response(
|
||||
200,
|
||||
{},
|
||||
{
|
||||
scope: scopeAttrs,
|
||||
id: 'token123',
|
||||
token: 'thetokenstring',
|
||||
account_id: '1',
|
||||
user_id: 'authenticateduser',
|
||||
auth_method_id: 'authmethod123',
|
||||
created_time: '',
|
||||
updated_time: '',
|
||||
last_used_time: '',
|
||||
expiration_time: '',
|
||||
}
|
||||
);
|
||||
return commandHandlers[authMethod.type][command](payload, scopeAttrs);
|
||||
}
|
||||
|
||||
// TODO: this handler doesn't really belong here, but we already route
|
||||
// POST requests on existing auth methods to this route handler file under the
|
||||
// assumption it would be for authentication. While authentication should
|
||||
// still occur here, we should handle other custom methods in the
|
||||
// mirage config and route here only for auth.
|
||||
if (method === 'change-state') {
|
||||
const attrs = this.normalizedRequestAttrs();
|
||||
return authMethod.update({
|
||||
attributes: {
|
||||
state: attrs.attributes.state,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,18 +20,22 @@ export default function (server) {
|
||||
server.create('user', { id: 'authenticateduser' });
|
||||
|
||||
// Auth
|
||||
server.createList(
|
||||
const globalAuthMethods = server.createList(
|
||||
'auth-method',
|
||||
1,
|
||||
{ scope: globalScope },
|
||||
'withAccountsAndUsers'
|
||||
);
|
||||
server.createList(
|
||||
const orgAuthMethods = server.createList(
|
||||
'auth-method',
|
||||
3,
|
||||
{ scope: orgScope },
|
||||
'withAccountsAndUsers'
|
||||
);
|
||||
// Assign primary auth methods per scope
|
||||
// TODO make this generic
|
||||
globalScope.update({ primaryAuthMethodId: globalAuthMethods[0].id });
|
||||
orgScope.update({ primaryAuthMethodId: orgAuthMethods[1].id });
|
||||
|
||||
// Groups and Users
|
||||
server.createList('group', 1, { scope: globalScope }, 'withMembers');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Ember from 'ember';
|
||||
import config from 'ember-get-config';
|
||||
import { random } from 'faker';
|
||||
import { datatype } from 'faker';
|
||||
|
||||
export default function initializeMockIPC(server) {
|
||||
/**
|
||||
@@ -55,11 +55,33 @@ export default function initializeMockIPC(server) {
|
||||
});
|
||||
return {
|
||||
address: window.location.hostname,
|
||||
port: random.number(),
|
||||
port: datatype.number(),
|
||||
protocol: type,
|
||||
session_id: newSession.id,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for browsers running on mac OS
|
||||
*/
|
||||
isMacOS() {
|
||||
return Boolean(window.navigator.userAgent.match(/(macintosh)/i));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for browsers running on windows OS
|
||||
*/
|
||||
isWindowsOS() {
|
||||
return Boolean(window.navigator.userAgent.match(/(windows)/i));
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing when attempting to minimize, toggle fullscreen,
|
||||
* and close a browser window
|
||||
*/
|
||||
minimizeWindow() {}
|
||||
closeWindow() {}
|
||||
toggleFullScreenWindow() {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,4 +2,17 @@ import ApplicationSerializer from './application';
|
||||
|
||||
export default ApplicationSerializer.extend({
|
||||
modelName: 'auth-method',
|
||||
|
||||
_hashForModel(model) {
|
||||
const json = ApplicationSerializer.prototype._hashForModel.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
// If the scope marks this auth method as primary, designate it
|
||||
// as such with a read-only boolean field (this is how the API behaves).
|
||||
const isPrimary =
|
||||
model.scope && model.scope.primaryAuthMethodId === model.id;
|
||||
json.is_primary = isPrimary;
|
||||
return json;
|
||||
},
|
||||
});
|
||||
@@ -28,7 +28,7 @@
|
||||
"ember-cli-htmlbars": "^5.3.1",
|
||||
"ember-cli-mirage": "^1.1.8",
|
||||
"ember-data": "^3.20.0",
|
||||
"ember-data-model-fragments": "5.0.0-beta.0",
|
||||
"ember-data-model-fragments": "5.0.0-beta.2",
|
||||
"ember-fetch": "^8.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -38,7 +38,7 @@
|
||||
"@glimmer/tracking": "^1.0.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"doctoc": "^1.4.0",
|
||||
"doctoc": "^2.0.0",
|
||||
"ember-auto-import": "^1.10.1",
|
||||
"ember-cli": "~3.24.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
@@ -71,7 +71,7 @@
|
||||
"qunit-dom": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "10.* || >= 12"
|
||||
"node": ">=10 <=15"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import Application from '@ember/application';
|
||||
|
||||
import { initialize } from 'dummy/initializers/fragment-auth-method-attributes-account-claim-map-serializer';
|
||||
import { module, test } from 'qunit';
|
||||
import Resolver from 'ember-resolver';
|
||||
import { run } from '@ember/runloop';
|
||||
|
||||
module(
|
||||
'Unit | Initializer | fragment-auth-method-attributes-account-claim-map-serializer',
|
||||
function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.TestApplication = class TestApplication extends Application {};
|
||||
this.TestApplication.initializer({
|
||||
name: 'initializer under test',
|
||||
initialize,
|
||||
});
|
||||
|
||||
this.application = this.TestApplication.create({
|
||||
autoboot: false,
|
||||
Resolver,
|
||||
});
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
run(this.application, 'destroy');
|
||||
});
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it works', async function (assert) {
|
||||
await this.application.boot();
|
||||
|
||||
assert.ok(true);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,32 @@
|
||||
import Application from '@ember/application';
|
||||
|
||||
import { initialize } from 'dummy/initializers/fragment-string';
|
||||
import { module, test } from 'qunit';
|
||||
import Resolver from 'ember-resolver';
|
||||
import { run } from '@ember/runloop';
|
||||
|
||||
module('Unit | Initializer | fragment-string', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.TestApplication = class TestApplication extends Application {};
|
||||
this.TestApplication.initializer({
|
||||
name: 'initializer under test',
|
||||
initialize,
|
||||
});
|
||||
|
||||
this.application = this.TestApplication.create({
|
||||
autoboot: false,
|
||||
Resolver,
|
||||
});
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
run(this.application, 'destroy');
|
||||
});
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it works', async function (assert) {
|
||||
await this.application.boot();
|
||||
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,49 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
|
||||
module('Unit | Model | auth method', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('auth-method', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
|
||||
test('it has a `changeState` method that targets a specific POST API endpoint and serialization', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.server.post(
|
||||
'/v1/auth-methods/123abc:change-state',
|
||||
(schema, request) => {
|
||||
const body = JSON.parse(request.requestBody);
|
||||
assert.deepEqual(body, {
|
||||
attributes: {
|
||||
state: 'foobar',
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
return { id: '123abc' };
|
||||
}
|
||||
);
|
||||
const store = this.owner.lookup('service:store');
|
||||
store.push({
|
||||
data: {
|
||||
id: '123abc',
|
||||
type: 'auth-method',
|
||||
attributes: {
|
||||
type: 'oidc',
|
||||
state: 'foobar',
|
||||
version: 1,
|
||||
scope: {
|
||||
scope_id: 'o_1',
|
||||
type: 'scope',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const model = store.peekRecord('auth-method', '123abc');
|
||||
await model.changeState('foobar');
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Model | auth token', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('auth-token', {});
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module(
|
||||
'Unit | Model | fragment auth method attributes account claim map',
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord(
|
||||
'fragment-auth-method-attributes-account-claim-map',
|
||||
{}
|
||||
);
|
||||
assert.ok(model);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Model | fragment auth method attributes', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('fragment-auth-method-attributes', {});
|
||||
assert.ok(model);
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Model | fragment host set', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('fragment-host-set', {});
|
||||
|
||||
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Model | fragment principal', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('fragment-principal', {});
|
||||
|
||||
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Model | fragment string', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('fragment-string', {});
|
||||
|
||||
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Model | host', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('host', {});
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
|
||||
module('Unit | Serializer | auth method', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let serializer = store.serializerFor('auth-method');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes password records', function (assert) {
|
||||
assert.expect(2);
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('auth-method', { type: 'password' });
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
assert.notOk(
|
||||
serializedRecord.attributes,
|
||||
'Password should not have attributes'
|
||||
);
|
||||
});
|
||||
|
||||
test('it serializes OIDC records without state', function (assert) {
|
||||
assert.expect(1);
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('auth-method', {
|
||||
type: 'oidc',
|
||||
name: 'OIDC Auth Method',
|
||||
attributes: {
|
||||
state: 'foo',
|
||||
account_claim_maps: [{ from: 'foo', to: 'bar' }],
|
||||
claims_scopes: [{ value: 'profile' }, { value: 'email' }],
|
||||
signing_algorithms: [{ value: 'RS256' }, { value: 'RS384' }],
|
||||
allowed_audiences: [
|
||||
{ value: 'www.alice.com' },
|
||||
{ value: 'www.alice.com/admin' },
|
||||
],
|
||||
idp_ca_certs: [
|
||||
{ value: 'certificate-1234' },
|
||||
{ value: 'certificate-5678' },
|
||||
],
|
||||
api_url_prefix: 'protocol://host:port/foo',
|
||||
client_id: 'id123',
|
||||
client_secret: 'secret456',
|
||||
disable_discovered_config_validation: true,
|
||||
dry_run: true,
|
||||
issuer: 'http://www.example.net',
|
||||
max_age: 500,
|
||||
},
|
||||
});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'oidc',
|
||||
name: 'OIDC Auth Method',
|
||||
description: null,
|
||||
attributes: {
|
||||
account_claim_maps: ['foo=bar'],
|
||||
claims_scopes: ['profile', 'email'],
|
||||
signing_algorithms: ['RS256', 'RS384'],
|
||||
allowed_audiences: ['www.alice.com', 'www.alice.com/admin'],
|
||||
idp_ca_certs: ['certificate-1234', 'certificate-5678'],
|
||||
api_url_prefix: 'protocol://host:port/foo',
|
||||
client_id: 'id123',
|
||||
client_secret: 'secret456',
|
||||
disable_discovered_config_validation: true,
|
||||
dry_run: true,
|
||||
issuer: 'http://www.example.net',
|
||||
max_age: 500,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes OIDC records with only state and version when `adapterOptions.state` is passed', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('auth-method');
|
||||
const record = store.createRecord('auth-method', {
|
||||
type: 'oidc',
|
||||
attributes: {
|
||||
state: 'foo',
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
snapshot.adapterOptions = {
|
||||
state: 'bar',
|
||||
};
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
attributes: {
|
||||
state: 'bar',
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test('it sorts primary first in normalizeArrayResponse', function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('auth-method');
|
||||
const modelClass = store.createRecord('auth-method').constructor;
|
||||
const payload = {
|
||||
items: [{ id: 1 }, { id: 2, is_primary: true }, { id: 3 }],
|
||||
};
|
||||
const normalizedArray = serializer.normalizeArrayResponse(
|
||||
store,
|
||||
modelClass,
|
||||
payload
|
||||
);
|
||||
assert.ok(payload.items[1].is_primary, 'Second payload item is primary');
|
||||
assert.deepEqual(
|
||||
normalizedArray,
|
||||
{
|
||||
included: [],
|
||||
data: [
|
||||
{
|
||||
id: '2',
|
||||
type: 'auth-method',
|
||||
attributes: { is_primary: true },
|
||||
relationships: {},
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
type: 'auth-method',
|
||||
attributes: { is_primary: false },
|
||||
relationships: {},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'auth-method',
|
||||
attributes: { is_primary: false },
|
||||
relationships: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
'First normalized item is primary'
|
||||
);
|
||||
});
|
||||
|
||||
test('it normalizes OIDC records', async function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
this.server.get('/v1/auth-methods/oidc123', () => ({
|
||||
attributes: {
|
||||
account_claim_maps: ['from=to', 'foo=bar'],
|
||||
},
|
||||
version: 1,
|
||||
type: 'oidc',
|
||||
id: 'oidc123',
|
||||
}));
|
||||
const record = await store.findRecord('auth-method', 'oidc123');
|
||||
const { account_claim_maps } = record.attributes;
|
||||
assert.equal(account_claim_maps.firstObject.from, 'from');
|
||||
assert.equal(account_claim_maps.firstObject.to, 'to');
|
||||
assert.equal(account_claim_maps.lastObject.from, 'foo');
|
||||
assert.equal(account_claim_maps.lastObject.to, 'bar');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Serializer | fragment account attributes', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let serializer = store.serializerFor('fragment-account-attributes');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('fragment-account-attributes', {});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.ok(serializedRecord);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Serializer | fragment auth method attributes', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it serializes empty strings to null', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('user', {
|
||||
name: '',
|
||||
description: '',
|
||||
version: null,
|
||||
});
|
||||
let serializedRecord = record.serialize();
|
||||
assert.deepEqual(serializedRecord, {
|
||||
name: null,
|
||||
description: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('it does not serialize client_secret if present but falsy', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('user', {
|
||||
name: '',
|
||||
description: '',
|
||||
version: null,
|
||||
client_secret: false,
|
||||
});
|
||||
let serializedRecord = record.serialize();
|
||||
assert.deepEqual(serializedRecord, {
|
||||
name: null,
|
||||
description: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Serializer | fragment string', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let serializer = store.serializerFor('fragment-string');
|
||||
|
||||
assert.ok(serializer);
|
||||
});
|
||||
|
||||
test('it serializes records', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let record = store.createRecord('fragment-string', {
|
||||
value: 'string value',
|
||||
});
|
||||
|
||||
let serializedRecord = record.serialize();
|
||||
|
||||
assert.equal(serializedRecord, 'string value');
|
||||
});
|
||||
|
||||
test('it normalizes records', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('fragment-string');
|
||||
const modelClass = store.createRecord('fragment-string').constructor;
|
||||
const payload = 'string value';
|
||||
const normalized = serializer.normalizeSingleResponse(
|
||||
store,
|
||||
modelClass,
|
||||
payload
|
||||
);
|
||||
assert.deepEqual(normalized, {
|
||||
included: [],
|
||||
data: {
|
||||
id: null,
|
||||
type: 'fragment-string',
|
||||
attributes: {
|
||||
value: 'string value',
|
||||
},
|
||||
relationships: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Serializer | host', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let serializer = store.serializerFor('host');
|
||||
|
||||
@@ -4,7 +4,6 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Serializer | session', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function (assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let serializer = store.serializerFor('session');
|
||||
|
||||
@@ -4,10 +4,31 @@ import fetch from 'fetch';
|
||||
|
||||
/**
|
||||
* Encapsulates common authenticator functionality.
|
||||
*
|
||||
* Upon session invalidation, deauthentication should be attempted at the URL
|
||||
* generated by `buildDeauthEndpointURL` on a best-effort basis.
|
||||
*
|
||||
* When a session is restored, a call should be made to the endpoint specified
|
||||
* by `buildTokenValidationEndpointURL`. If this endpoint responds with 401 or
|
||||
* 404, then the session is invalid and the restoration should be rejected.
|
||||
* All other responses should resolve the session restoration successfully.
|
||||
*/
|
||||
export default class BaseAuthenticator extends SimpleAuthBaseAuthenticator {
|
||||
// =unimplemented methods
|
||||
|
||||
/**
|
||||
* Generates an auth method URL with which to authenticate.
|
||||
* @override
|
||||
* @param {object} options
|
||||
* @param {string} options.scope.scope_id
|
||||
* @param {string} options.authMethod.id
|
||||
* @return {string}
|
||||
*/
|
||||
buildAuthEndpointURL(/* {
|
||||
scope: { id: scopeID },
|
||||
authMethod: { id: authMethodID },
|
||||
} */) {}
|
||||
|
||||
/**
|
||||
* Generates a scope URL with which to deauthenticate.
|
||||
* @override
|
||||
@@ -51,8 +72,8 @@ export default class BaseAuthenticator extends SimpleAuthBaseAuthenticator {
|
||||
*/
|
||||
async restore(data) {
|
||||
if (!data) return reject();
|
||||
return this.validateToken(data.token, data.id).then(() =>
|
||||
this.normalizeData(data)
|
||||
return this.validateToken(data.attributes.token, data.attributes.id).then(
|
||||
() => this.normalizeData(data)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,6 +86,10 @@ export default class BaseAuthenticator extends SimpleAuthBaseAuthenticator {
|
||||
* @return {object}
|
||||
*/
|
||||
normalizeData(data, username) {
|
||||
// Pull fields up from `data.attributes` for easier access in JavaScript.
|
||||
// The `attributes` field exists on the Go side for its convenience but is
|
||||
// unnecessary here.
|
||||
Object.assign(data, data.attributes);
|
||||
// Add booleans indicated the scope type
|
||||
data.isGlobal = data?.scope?.type === 'global';
|
||||
data.isOrg = data?.scope?.type === 'org';
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import BaseAuthenticator from './base';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { reject } from 'rsvp';
|
||||
import fetch from 'fetch';
|
||||
|
||||
/**
|
||||
* The OIDC base authenticator encapsulates the multistep OIDC flow.
|
||||
*
|
||||
* 1. Start authentication flow: this step is actually a combination of two
|
||||
* sub steps:
|
||||
* 1. Initiate a call via `startAuthentication` to the API at
|
||||
* `buildAuthEndpointURL` using the `start` command to fetch the URL of
|
||||
* the external authentication provider (`auth_url`).
|
||||
* 2. Open `auth_url`. This may be either inline (same tab)
|
||||
* or external in a new tab or browser window.
|
||||
* 2. Requests the token from `buildAuthEndpointURL` using the `token` command.
|
||||
* A token isn't guaranteed to be ready, so the OIDC flow can be in a
|
||||
* "pending" state.
|
||||
*
|
||||
* This authenticator should not be used directly because it does not specify
|
||||
* URL-building endpoints of its own.
|
||||
* To use, generate an application authenticator in your app
|
||||
* `authenticators/application.js` and extend this class.
|
||||
*/
|
||||
export default class OIDCAuthenticator extends BaseAuthenticator {
|
||||
// =services
|
||||
|
||||
@service session;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Kicks-off the OIDC flow: calls the `authenticate:start` action on the
|
||||
* specified auth method, which returns some meta data along with an
|
||||
* authorization request URL. The response data is saved for later and
|
||||
* authentication enters a pending state.
|
||||
*
|
||||
* @param {boolean} requestCookies request cookie tokens (default `true`)
|
||||
* @param {object} options
|
||||
* @return {object}
|
||||
*/
|
||||
async startAuthentication(options) {
|
||||
const url = this.buildAuthEndpointURL(options);
|
||||
const body = JSON.stringify({ command: 'start' });
|
||||
const response = await fetch(url, { method: 'post', body });
|
||||
const json = await response.json();
|
||||
if (response.status < 400) {
|
||||
// Store meta about the pending OIDC flow
|
||||
this.session.set('data.pending', {
|
||||
oidc: json,
|
||||
});
|
||||
return json;
|
||||
} else {
|
||||
return reject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OIDC is a _process_ and does not have a singular "authenticate" step. Thus
|
||||
* this authenticate method simply normalizes the data passed into it once
|
||||
* `attemptFetchToken` completes. Notably, no requests are made by calling
|
||||
* authenticate. A Boundary token must already have been obtained.
|
||||
* @param {object} json
|
||||
* @return {object}
|
||||
*/
|
||||
async authenticate(json) {
|
||||
return this.normalizeData(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* This request attempts to fetch a Boundary token. This method represents a
|
||||
* single request the polling sequence used to wait for authentication.
|
||||
* There are three possible outcomes:
|
||||
*
|
||||
* 1. If the token is ready, it is persisted into the session, the session
|
||||
* enters an authenticated state, and this method returns `true`.
|
||||
* 2. If the token is not yet ready, this method returns false.
|
||||
* 3. If the request errors, this method returns to a rejection,
|
||||
* requiring appropriate error handling.
|
||||
*/
|
||||
async attemptFetchToken(options) {
|
||||
// Get the URL and setup the request body
|
||||
const url = this.buildAuthEndpointURL(options);
|
||||
const body = JSON.stringify({
|
||||
command: 'token',
|
||||
attributes: {
|
||||
//state: this.session.get('data.pending.oidc.attributes.state'),
|
||||
token_id: this.session.get('data.pending.oidc.attributes.token_id'),
|
||||
},
|
||||
});
|
||||
// Fetch the endpoint and get the response JSON
|
||||
const response = await fetch(url, { method: 'post', body });
|
||||
if (response.status === 202) {
|
||||
// The token isn't ready yet, keep trying.
|
||||
return false;
|
||||
} else if (response.status < 400) {
|
||||
// Response was successful, meaning a token was obtained.
|
||||
// Authenticate with the session service using the response JSON.
|
||||
const json = await response.json();
|
||||
await this.session.authenticate('authenticator:oidc', json);
|
||||
return true;
|
||||
} else {
|
||||
// Response errors, return a rejection for further handling.
|
||||
return reject();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,21 +34,6 @@ import fetch from 'fetch';
|
||||
*
|
||||
*/
|
||||
export default class PasswordAuthenticator extends BaseAuthenticator {
|
||||
// =unimplemented methods
|
||||
|
||||
/**
|
||||
* Generates an auth method URL with which to authenticate.
|
||||
* @override
|
||||
* @param {object} options
|
||||
* @param {string} options.scope.scope_id
|
||||
* @param {string} options.authMethod.id
|
||||
* @return {string}
|
||||
*/
|
||||
buildAuthEndpointURL(/* {
|
||||
scope: { id: scopeID },
|
||||
authMethod: { id: authMethodID },
|
||||
} */) {}
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
@@ -71,8 +56,9 @@ export default class PasswordAuthenticator extends BaseAuthenticator {
|
||||
options
|
||||
) {
|
||||
const body = JSON.stringify({
|
||||
command: 'login',
|
||||
token_type: requestCookies ? 'cookie' : null,
|
||||
credentials: { login_name, password },
|
||||
attributes: { login_name, password },
|
||||
});
|
||||
const authEndpointURL = this.buildAuthEndpointURL(options);
|
||||
const response = await fetch(authEndpointURL, { method: 'post', body });
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"babel-eslint": "^10.1.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"core": "*",
|
||||
"doctoc": "^1.4.0",
|
||||
"doctoc": "^2.0.0",
|
||||
"ember-auto-import": "^1.10.1",
|
||||
"ember-cli": "~3.24.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
@@ -65,7 +65,7 @@
|
||||
"qunit-dom": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "10.* || >= 12"
|
||||
"node": ">=10 <=15"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import OIDCAuthenticator from 'auth/authenticators/oidc';
|
||||
|
||||
export default class ApplicationOIDCAuthenticator extends OIDCAuthenticator {
|
||||
buildTokenValidationEndpointURL(id) {
|
||||
return `/validate/${id}`;
|
||||
}
|
||||
|
||||
buildAuthEndpointURL() {
|
||||
return '/authenticate';
|
||||
}
|
||||
|
||||
buildDeauthEndpointURL() {
|
||||
return '/deauthenticate';
|
||||
}
|
||||
}
|
||||
@@ -32,29 +32,25 @@ module('Unit | Authenticator | base', function (hooks) {
|
||||
|
||||
test('it passes token validation on restoration if receiving 200', async function (assert) {
|
||||
assert.expect(1);
|
||||
const mockData = { id: 'token_123' };
|
||||
const id = 'token_123';
|
||||
const mockData = { attributes: { id } };
|
||||
const authenticator = this.owner.lookup('authenticator:base');
|
||||
this.server.get(
|
||||
authenticator.buildTokenValidationEndpointURL(mockData.id),
|
||||
() => {
|
||||
assert.ok(true, 'token validation was requested');
|
||||
return new Response(200);
|
||||
}
|
||||
);
|
||||
this.server.get(authenticator.buildTokenValidationEndpointURL(id), () => {
|
||||
assert.ok(true, 'token validation was requested');
|
||||
return new Response(200);
|
||||
});
|
||||
await authenticator.restore(mockData);
|
||||
});
|
||||
|
||||
test('it fails token validation on restoration if receiving 401', async function (assert) {
|
||||
assert.expect(2);
|
||||
const mockData = { id: 'token_123' };
|
||||
const id = 'token_123';
|
||||
const mockData = { attributes: { id } };
|
||||
const authenticator = this.owner.lookup('authenticator:base');
|
||||
this.server.get(
|
||||
authenticator.buildTokenValidationEndpointURL(mockData.id),
|
||||
() => {
|
||||
assert.ok(true, 'token validation was requested');
|
||||
return new Response(401);
|
||||
}
|
||||
);
|
||||
this.server.get(authenticator.buildTokenValidationEndpointURL(id), () => {
|
||||
assert.ok(true, 'token validation was requested');
|
||||
return new Response(401);
|
||||
});
|
||||
try {
|
||||
await authenticator.restore(mockData);
|
||||
} catch (e) {
|
||||
@@ -64,15 +60,13 @@ module('Unit | Authenticator | base', function (hooks) {
|
||||
|
||||
test('it fails token validation on restoration if receiving 404', async function (assert) {
|
||||
assert.expect(2);
|
||||
const mockData = { id: 'token_123' };
|
||||
const id = 'token_123';
|
||||
const mockData = { attributes: { id } };
|
||||
const authenticator = this.owner.lookup('authenticator:base');
|
||||
this.server.get(
|
||||
authenticator.buildTokenValidationEndpointURL(mockData.id),
|
||||
() => {
|
||||
assert.ok(true, 'token validation was requested');
|
||||
return new Response(404);
|
||||
}
|
||||
);
|
||||
this.server.get(authenticator.buildTokenValidationEndpointURL(id), () => {
|
||||
assert.ok(true, 'token validation was requested');
|
||||
return new Response(404);
|
||||
});
|
||||
try {
|
||||
await authenticator.restore(mockData);
|
||||
} catch (e) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { Response } from 'miragejs';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
|
||||
module('Unit | Authenticator | OIDC', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
test('`startAuthentication` initiates the authentication flow and returns the response', async function (assert) {
|
||||
assert.expect(2);
|
||||
const authenticator = this.owner.lookup('authenticator:oidc');
|
||||
this.server.post(
|
||||
authenticator.buildAuthEndpointURL(),
|
||||
() => new Response(200, {}, { foo: 'bar' })
|
||||
);
|
||||
const response = await authenticator.startAuthentication();
|
||||
assert.ok(true, 'authentication succeeded');
|
||||
assert.deepEqual(response, { foo: 'bar' });
|
||||
});
|
||||
|
||||
test('`startAuthentication` rejects/errors if API returns an error response', async function (assert) {
|
||||
assert.expect(1);
|
||||
const authenticator = this.owner.lookup('authenticator:oidc');
|
||||
this.server.post(
|
||||
authenticator.buildAuthEndpointURL(),
|
||||
() => new Response(400)
|
||||
);
|
||||
// Because assert.throws doesn't work with async errors/promise rejections
|
||||
try {
|
||||
await authenticator.startAuthentication();
|
||||
} catch (e) {
|
||||
assert.ok(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -47,8 +47,9 @@ module('Unit | Authenticator | password', function (hooks) {
|
||||
(schema, request) => {
|
||||
const json = JSON.parse(request.requestBody);
|
||||
assert.deepEqual(json, {
|
||||
command: 'login',
|
||||
token_type: 'cookie',
|
||||
credentials: {
|
||||
attributes: {
|
||||
login_name: 'foo',
|
||||
password: 'bar',
|
||||
},
|
||||
|
||||
@@ -7,6 +7,25 @@ helpers.
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
- [Add core to an App](#add-core-to-an-app)
|
||||
- [Installation](#installation)
|
||||
- [I18n](#i18n)
|
||||
- [Notifications](#notifications)
|
||||
- [Confirmations](#confirmations)
|
||||
- [App and Company Names](#app-and-company-names)
|
||||
- [Documentation URLs](#documentation-urls)
|
||||
- [Loading Helper](#loading-helper)
|
||||
- [Scope Service](#scope-service)
|
||||
- [Yarn Commands](#yarn-commands)
|
||||
- [Linting](#linting)
|
||||
- [Formatting](#formatting)
|
||||
- [Running tests](#running-tests)
|
||||
- [Running the dummy application](#running-the-dummy-application)
|
||||
- [Contributing](#contributing)
|
||||
- [Building ToC](#building-toc)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
- [Add core to an App](#add-core-to-an-app)
|
||||
- [Installation](#installation)
|
||||
- [Yarn Commands](#yarn-commands)
|
||||
|
||||
+11
-23
@@ -1,17 +1,15 @@
|
||||
import BasePasswordAuthenticator from 'auth/authenticators/password';
|
||||
import BaseOIDCAuthenticator from 'auth/authenticators/oidc';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
/**
|
||||
* A username/password authenticator that authenticates with
|
||||
* a scope/auth-method endpoint. Authentication occurs on an auth method
|
||||
* endpoint, while deauthentication occurs on a scope endpoint.
|
||||
* An OIDC authenticator that handles flow kick-off and polling.
|
||||
*/
|
||||
export default class PasswordAuthenticator extends BasePasswordAuthenticator {
|
||||
export default class OIDCAuthenticator extends BaseOIDCAuthenticator {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
|
||||
// =attributes
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Generates an auth method URL with which to authenticate.
|
||||
@@ -30,19 +28,6 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator {
|
||||
return adapter.buildURL('auth-method', authMethodID, options, 'findRecord');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a scope URL with which to deauthenticate.
|
||||
* @override
|
||||
* @param {object} options
|
||||
* @param {string} scopeID
|
||||
* @return {string}
|
||||
*/
|
||||
buildDeauthEndpointURL({ scope: { id: scopeID } }) {
|
||||
const adapter = this.store.adapterFor('application');
|
||||
const options = { adapterOptions: { method: 'deauthenticate' } };
|
||||
return adapter.buildURL('scope', scopeID, options, 'findRecord');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an auth token validation URL used to check tokens on restoration.
|
||||
* @override
|
||||
@@ -62,7 +47,7 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator {
|
||||
*/
|
||||
authenticate() {
|
||||
return super.authenticate(...arguments).then((data) => {
|
||||
const token = data?.token;
|
||||
const token = data?.attributes?.token;
|
||||
this.addTokenToAuthorization(token);
|
||||
return data;
|
||||
});
|
||||
@@ -76,7 +61,7 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator {
|
||||
* @return {object}
|
||||
*/
|
||||
restore(data) {
|
||||
const token = data?.token;
|
||||
const token = data?.attributes?.token;
|
||||
this.addTokenToAuthorization(token);
|
||||
return super.restore(data);
|
||||
}
|
||||
@@ -85,11 +70,14 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator {
|
||||
* Assigns a token string all future requests via the `Authorization` header
|
||||
* on the application adapter prototype. Only application and adapters that
|
||||
* extend the application adapter receive this header (which is most).
|
||||
*
|
||||
* TODO: if cookies were requested, this step should be skipped.
|
||||
*
|
||||
* @param {string} token
|
||||
*/
|
||||
addTokenToAuthorization(token) {
|
||||
const adapterPrototype = this.store.adapterFor('application').constructor
|
||||
.prototype;
|
||||
const adapterPrototype =
|
||||
this.store.adapterFor('application').constructor.prototype;
|
||||
const headers = adapterPrototype?.headers;
|
||||
if (!headers) adapterPrototype.headers = {};
|
||||
adapterPrototype.headers.Authorization = null;
|
||||
@@ -62,7 +62,7 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator {
|
||||
*/
|
||||
authenticate() {
|
||||
return super.authenticate(...arguments).then((data) => {
|
||||
const token = data?.token;
|
||||
const token = data?.attributes?.token;
|
||||
this.addTokenToAuthorization(token);
|
||||
return data;
|
||||
});
|
||||
@@ -76,7 +76,7 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator {
|
||||
* @return {object}
|
||||
*/
|
||||
restore(data) {
|
||||
const token = data?.token;
|
||||
const token = data?.attributes?.token;
|
||||
this.addTokenToAuthorization(token);
|
||||
return super.restore(data);
|
||||
}
|
||||
@@ -91,8 +91,8 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator {
|
||||
* @param {string} token
|
||||
*/
|
||||
addTokenToAuthorization(token) {
|
||||
const adapterPrototype = this.store.adapterFor('application').constructor
|
||||
.prototype;
|
||||
const adapterPrototype =
|
||||
this.store.adapterFor('application').constructor.prototype;
|
||||
const headers = adapterPrototype?.headers;
|
||||
if (!headers) adapterPrototype.headers = {};
|
||||
adapterPrototype.headers.Authorization = null;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<Rose::Form
|
||||
class="full-width"
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{false}}
|
||||
@disabled={{@disabled}}
|
||||
as |form|
|
||||
>
|
||||
|
||||
<form.actions
|
||||
@submitText={{t "actions.authenticate"}}
|
||||
@showCancel={{false}} />
|
||||
|
||||
</Rose::Form>
|
||||
@@ -5,7 +5,7 @@ import { getOwner } from '@ember/application';
|
||||
* confirmation via the confirmation service. If the user accepts, the
|
||||
* original method is executed. If the user denies, it is not.
|
||||
*/
|
||||
export function confirm(confirmationTextKey) {
|
||||
export function confirm(confirmationTextKey, options) {
|
||||
return function (_target, _propertyKey, desc) {
|
||||
const method = desc.value;
|
||||
desc.value = function () {
|
||||
@@ -15,7 +15,7 @@ export function confirm(confirmationTextKey) {
|
||||
const text = intl.t(confirmationTextKey);
|
||||
return (
|
||||
confirmService
|
||||
.confirm(text)
|
||||
.confirm(text, options)
|
||||
// If user confirmed, execute the decorated method
|
||||
.then(() => method.apply(this, arguments))
|
||||
// If the user denied, gracefully handle promise rejection with no-op
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'core/authenticators/oidc';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'core/components/form/authenticate/oidc';
|
||||
@@ -1 +1 @@
|
||||
export { default, appName } from 'core/helpers/app-name';
|
||||
export { default } from 'core/helpers/app-name';
|
||||
@@ -1 +1 @@
|
||||
export { default, companyCopyright } from 'core/helpers/company-copyright';
|
||||
export { default } from 'core/helpers/company-copyright';
|
||||
@@ -1 +1 @@
|
||||
export { default, companyName } from 'core/helpers/company-name';
|
||||
export { default } from 'core/helpers/company-name';
|
||||
@@ -1 +1 @@
|
||||
export { default, docUrl } from 'core/helpers/doc-url';
|
||||
export { default } from 'core/helpers/doc-url';
|
||||
@@ -1,4 +1 @@
|
||||
export {
|
||||
default,
|
||||
formatDateIsoHuman,
|
||||
} from 'core/helpers/format-date-iso-human';
|
||||
export { default } from 'core/helpers/format-date-iso-human';
|
||||
@@ -1 +1 @@
|
||||
export { default, formatDateIso } from 'core/helpers/format-date-iso';
|
||||
export { default } from 'core/helpers/format-date-iso';
|
||||
@@ -1 +1 @@
|
||||
export { default, isLoading } from 'core/helpers/is-loading';
|
||||
export { default } from 'core/helpers/is-loading';
|
||||
@@ -30,8 +30,10 @@
|
||||
"dependencies": {
|
||||
"api": "*",
|
||||
"auth": "*",
|
||||
"ember-browser-services": "^1.1.6",
|
||||
"ember-cli-babel": "^7.23.0",
|
||||
"ember-cli-htmlbars": "^5.3.1",
|
||||
"ember-feature-flags": "^6.0.0",
|
||||
"ember-get-config": "^0.3.0",
|
||||
"ember-intl": "^5.5.0",
|
||||
"ember-loading": "^0.4.0",
|
||||
@@ -46,7 +48,7 @@
|
||||
"@glimmer/tracking": "^1.0.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"doctoc": "^1.4.0",
|
||||
"doctoc": "^2.0.0",
|
||||
"ember-auto-import": "^1.10.1",
|
||||
"ember-cli": "~3.24.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
@@ -71,7 +73,7 @@
|
||||
"eslint-plugin-ember": "^10.1.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"faker": "^5.1.0",
|
||||
"faker": "^5.5.3",
|
||||
"loader.js": "^4.7.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.2.1",
|
||||
@@ -79,7 +81,7 @@
|
||||
"qunit-dom": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "10.* || >= 12"
|
||||
"node": ">=10 <=15"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 377 132">
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M125.45,41.34V35.68h-5.18v5.66h-2.64V27.75h2.64v5.7h5.18v-5.7h2.64V41.34Zm12.32,0h-2.09l-.19-.67a5.69,5.69,0,0,1-3,.87c-1.87,0-2.66-1.23-2.66-2.92,0-2,.9-2.75,3-2.75h2.46v-1c0-1.09-.32-1.47-1.95-1.47a14.85,14.85,0,0,0-2.83.3l-.32-1.87a14,14,0,0,1,3.5-.46c3.21,0,4.15,1.08,4.15,3.54Zm-2.55-3.7h-1.89c-.84,0-1.07.22-1.07,1s.23,1,1,1a4.1,4.1,0,0,0,1.93-.51Zm7.91,3.9a12.53,12.53,0,0,1-3.52-.56L140,39.1a11.5,11.5,0,0,0,3,.43c1.13,0,1.3-.24,1.3-1s-.12-.9-1.78-1.29c-2.49-.58-2.79-1.18-2.79-3.08s.9-2.83,3.82-2.83a14.07,14.07,0,0,1,3.06.34l-.25,2a19,19,0,0,0-2.81-.28c-1.11,0-1.3.24-1.3.84,0,.79.06.85,1.44,1.19,2.85.73,3.13,1.09,3.13,3.1S146.21,41.54,143.13,41.54Zm11.7-.2V34.5c0-.53-.23-.79-.81-.79a7.22,7.22,0,0,0-2.67.83v6.8H148.8V27.56l2.55.38v4.34a9.38,9.38,0,0,1,3.73-.94c1.7,0,2.31,1.14,2.31,2.89v7.11Zm4.71-11.19v-2.4h2.55v2.4Zm0,11.19v-9.8h2.55v9.8Zm4.6-9.72c0-2.46,1.48-3.88,5-3.88a16.63,16.63,0,0,1,3.8.44l-.3,2.19a21.49,21.49,0,0,0-3.41-.34c-1.83,0-2.41.6-2.41,2v5.15c0,1.43.58,2,2.41,2a21.57,21.57,0,0,0,3.41-.35l.3,2.2a16.63,16.63,0,0,1-3.8.44c-3.48,0-5-1.43-5-3.88Zm14.41,9.92c-3.5,0-4.45-1.85-4.45-3.86V35.2c0-2,1-3.86,4.45-3.86S183,33.19,183,35.2v2.48C183,39.69,182.05,41.54,178.55,41.54Zm0-8.11c-1.37,0-1.89.58-1.89,1.69v2.64c0,1.1.52,1.69,1.89,1.69s1.88-.59,1.88-1.69V35.12C180.43,34,179.91,33.43,178.55,33.43Zm11.64.16A20.64,20.64,0,0,0,187.48,35v6.32h-2.55v-9.8h2.16l.16,1.09a11.23,11.23,0,0,1,2.69-1.29Zm10.22,4.49c0,2.17-1,3.46-3.38,3.46a14.74,14.74,0,0,1-2.72-.28v4l-2.56.38V31.54h2l.25.82a5.54,5.54,0,0,1,3.22-1c2.06,0,3.15,1.16,3.15,3.4Zm-6.1,1.11a11.85,11.85,0,0,0,2.26.26c.93,0,1.28-.43,1.28-1.31V34.68c0-.81-.31-1.25-1.26-1.25a3.66,3.66,0,0,0-2.28.89Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M139.64,70.53c3.08-1.33,4.48-3.21,4.48-6.53V59.82c0-5.45-3.09-8.59-10.72-8.59H117.55V91.52h16.94c7.33,0,11-3.56,11-9.25V78C145.51,74,143.33,71.31,139.64,70.53Zm-14.46-13h7.32c2.78,0,4,1,4,3.27v3.57c0,2.36-1.08,3.75-4.06,3.75h-7.25ZM137.89,81.3c0,3-1.46,3.93-4.91,3.93h-7.8v-11h8.47c3,0,4.24,1.33,4.24,4.06Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M161.54,61.45c-10.11,0-12.82,5.57-12.82,11.62v7.44c0,6,2.71,11.62,12.82,11.62s12.83-5.57,12.83-11.62V73.07C174.37,67,171.64,61.45,161.54,61.45ZM167,80.76c0,3.32-1.51,5.08-5.45,5.08s-5.44-1.76-5.44-5.08V72.83c0-3.33,1.51-5.08,5.44-5.08S167,69.5,167,72.83Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M196.51,62.06V82.51c-2.66,1.39-5.87,2.48-7.68,2.48s-2.36-.78-2.36-2.36V62.06h-7.38V83.42c0,5.26,1.75,8.71,6.65,8.71A29.44,29.44,0,0,0,197.54,89l.72,2.48h5.64V62.06Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M228.4,61.45a29.61,29.61,0,0,0-11.8,3.09l-.72-2.48h-5.64V91.52h7.39V71.08c2.66-1.39,5.87-2.48,7.68-2.48s2.36.78,2.36,2.35V91.52h7.38V70.17C235.05,64.9,233.3,61.45,228.4,61.45Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M257.5,50V62.31a40.19,40.19,0,0,0-7.87-.86c-6.83,0-9.74,3.88-9.74,10.41v10c0,6.71,3.15,10.22,9.08,10.22A15.49,15.49,0,0,0,258.29,89l.72,2.48h5.87V49Zm0,33.16a10.43,10.43,0,0,1-6.59,2.66c-2.73,0-3.63-1.33-3.63-3.75V71.68c0-2.66,1-3.93,3.68-3.93a32.33,32.33,0,0,1,6.54.79Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M280.8,61.45a38.34,38.34,0,0,0-10.11,1.4l.91,5.62a41.47,41.47,0,0,1,8.17-.9c4.71,0,5.62,1.15,5.62,4.42v3.08h-7.08c-6,0-8.59,2.3-8.59,8.29,0,5.08,2.3,8.77,7.68,8.77a16.18,16.18,0,0,0,8.78-2.6l.54,2h6.06V72.1C292.78,64.72,290.05,61.45,280.8,61.45Zm4.59,23.3a11.57,11.57,0,0,1-5.56,1.51c-2.3,0-3-.91-3-3,0-2.23.67-2.9,3.09-2.9h5.44Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M313,61.45a33,33,0,0,0-7.74,3.88l-.48-3.27h-6.24V91.52h7.38v-19a57.85,57.85,0,0,1,7.81-4.3Z">
|
||||
</path>
|
||||
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M335.67,62.06,328.9,84.44l-6.72-22.38h-7.56l9.32,29.46h2.59l-3.68,11.92h7l3.93-11.92,9.43-29.46Z">
|
||||
</path>
|
||||
|
||||
<polygon
|
||||
fill="#f04e54"
|
||||
points="44.94 102.47 44.94 97.64 54.96 97.64 54.96 94.67 50.1 94.67 50.1 89.82 73.03 89.82 62.38 71.38 73.03 52.93 49.53 52.93 49.53 78.51 36.88 78.51 36.88 40.29 80.4 40.29 87.67 52.87 76.98 71.38 87.88 90.26 80.84 102.47 44.94 102.47">
|
||||
</polygon>
|
||||
|
||||
<rect
|
||||
fill="#f04e54"
|
||||
x="42.04"
|
||||
y="89.82"
|
||||
width="4.85"
|
||||
height="4.85">
|
||||
</rect>
|
||||
|
||||
<rect
|
||||
fill="#f04e54"
|
||||
x="36.88"
|
||||
y="97.61"
|
||||
width="4.85"
|
||||
height="4.85">
|
||||
</rect>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -5,7 +5,7 @@
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<g id="logomark">
|
||||
<g class="logomark">
|
||||
<path
|
||||
d="M0 7.19579L11.4553 0.58728V5.55192L4.30398 9.68052V25.5537L0 23.069V7.19579Z"
|
||||
fill="currentColor">
|
||||
|
||||
|
Before Width: | Height: | Size: 633 B After Width: | Height: | Size: 636 B |
@@ -26,13 +26,11 @@ module('Unit | Authenticator | password', function (hooks) {
|
||||
|
||||
test('it adds an authorization header to application adapter on restore', async function (assert) {
|
||||
assert.expect(1);
|
||||
const mockData = { id: 'token1234', token: 'token1234' };
|
||||
this.server.get(
|
||||
authenticator.buildTokenValidationEndpointURL(mockData.id),
|
||||
() => {
|
||||
return new Response(200);
|
||||
}
|
||||
);
|
||||
const id = 'token1234';
|
||||
const mockData = { attributes: { id, token: 'token1234' } };
|
||||
this.server.get(authenticator.buildTokenValidationEndpointURL(id), () => {
|
||||
return new Response(200);
|
||||
});
|
||||
authenticator.restore(mockData);
|
||||
assert.equal(applicationAdapter.headers.Authorization, 'Bearer token1234');
|
||||
});
|
||||
|
||||
@@ -16,10 +16,11 @@ back: Back
|
||||
retry: Retry
|
||||
get-topic-help: Get help for this topic
|
||||
copy-to-clipboard: Copy to Clipboard
|
||||
copy-error-detail-to-clipboard: Copy error detail to clipboard
|
||||
authenticate: Authenticate
|
||||
deauthenticate: Deauthenticate
|
||||
choose-org: Choose Scope
|
||||
choose-different-org: Choose a Different Scope
|
||||
choose-different-org: Choose a different scope
|
||||
add-host-set: Create Host Set
|
||||
add-host: Create Host
|
||||
add-host-sets: Add Host Sets
|
||||
@@ -30,3 +31,4 @@ set-password: Set Password
|
||||
change-password: Change Password
|
||||
edit-form: Edit Form
|
||||
change-origin: Change Origin URL
|
||||
change-state: Change State
|
||||
@@ -1,10 +1,14 @@
|
||||
auth:
|
||||
titles:
|
||||
no-scopes: No Scopes Available
|
||||
flow-success: Successful authentication
|
||||
flow-error: Could not authenticate
|
||||
descriptions:
|
||||
no-scopes: No scopes were found. To authenticate, ask your administrator to create an scope and grant you access.
|
||||
choose-org: Choose a scope to authenticate.
|
||||
authenticating-with-org: "You are authenticating with {name}."
|
||||
authenticating-with-org: "Scope: You are authenticating with organization \"{name}\""
|
||||
flow-success: You've successfully authenticated. You may now close this window and return to the client.
|
||||
flow-error: Something went wrong while authenticating. Please close this window and try again. If the problem persists, notify your administrator.
|
||||
origin:
|
||||
connected-to: Connected to
|
||||
titles:
|
||||
@@ -23,6 +27,8 @@ titles:
|
||||
abandon-confirm: Discard unsaved changes?
|
||||
toggle-color-theme: Toggle Color Theme
|
||||
welcome-to-boundary: Welcome to Boundary
|
||||
label: Label
|
||||
value: Value
|
||||
descriptions:
|
||||
empty-set: There are no items to display yet. You may be able to add items or try back later.
|
||||
origin-initialization: To get started, please enter your origin URL
|
||||
|
||||
@@ -51,3 +51,60 @@ origin:
|
||||
description: The Boundary URL to which to connect (e.g. protocol://host:port)
|
||||
proxy:
|
||||
label: Proxy
|
||||
issuer:
|
||||
label: Issuer
|
||||
help: The issuer URL. Boundary expects only the schema, host, and port and will strip off ".well-known/openid-configuration" if present. This will be used for configuration discovery as well as for validation of the "iss" claim.
|
||||
client_id:
|
||||
label: Client ID
|
||||
help: An OAuth 2.0 Client Identifier valid at the Authorization Server.
|
||||
client_secret:
|
||||
label: Client Secret
|
||||
help: The client secret is set-only and may not be unset.
|
||||
client_secret_hmac:
|
||||
label: Client Secret HMAC
|
||||
max_age:
|
||||
label: Maximum Age
|
||||
help: The OIDC "max_age" parameter sent to the Authorization Server indicating a maximum acceptable time in seconds since the user's last authentication before requiring the user to reauthenticate. 0 indicates an immediate need to reauthenticate.
|
||||
api_url_prefix:
|
||||
label: API URL Prefix
|
||||
help: The prefix that should be used for any URLs needed during the authentication flow. This includes the callback URL, the token retrieval URL, and the redirection URL used by the OIDC Authorization Server.
|
||||
disable_discovered_config_validation:
|
||||
label: Disable discovered config validation
|
||||
help: >
|
||||
If the Authorization Server's discovered configuration contains values that do not match the configuration set on this auth method, this can be set to force the local configuration to override the discovered values. This is useful in the case of slow-changing or incorrect values announced by the Authorization Server. NOTE: For safety, not providing this value will be treated the same as "false"; that is, every time the method is updated or the state is changed, this value must be set to "true" or it will be disabled.
|
||||
idp_ca_certs:
|
||||
label: Certificates
|
||||
help: Optional PEM-encoded X.509 CA certificates that can be used as trust anchors when connecting to an OIDC provider.
|
||||
certificate:
|
||||
label: Certificate
|
||||
allowed_audiences:
|
||||
label: Allowed Audiences
|
||||
help: Allowed audience claims for this auth method.
|
||||
claim:
|
||||
label: Claim
|
||||
claims_scopes:
|
||||
label: Claims Scopes
|
||||
help: Optional claims scopes that will be requested during authentication.
|
||||
claims_scope:
|
||||
label: Claims Scope
|
||||
account_claim_maps:
|
||||
label: Account Claim Maps
|
||||
help: >
|
||||
Optional claim maps from custom claims to the standard claims of sub, name and email. These maps are represented as key=value where the key equals the from_claim and the value equals the to_claim. For example "oid=sub".
|
||||
account_claim_map:
|
||||
label: Account Claim Map
|
||||
from_claim:
|
||||
label: From Claim
|
||||
to_claim:
|
||||
label: To Claim
|
||||
signing_algorithms:
|
||||
label: Signing Algorithms
|
||||
help: The signing algorithms allowed for the auth method.
|
||||
algorithm:
|
||||
label: Algorithm
|
||||
subject:
|
||||
label: Subject
|
||||
email:
|
||||
label: Email
|
||||
full_name:
|
||||
label: Full Name
|
||||
@@ -1,5 +1,5 @@
|
||||
create-success: Created succesfully.
|
||||
save-success: Saved succesfully.
|
||||
create-success: Created successfully.
|
||||
save-success: Saved successfully.
|
||||
delete-success: Deleted successfully.
|
||||
add-success: Added successfully.
|
||||
remove-success: Removed successfully.
|
||||
|
||||
@@ -10,13 +10,14 @@ account:
|
||||
description: Set a password for the account.
|
||||
types:
|
||||
password: Password
|
||||
oidc: OIDC
|
||||
actions:
|
||||
create: Create Account
|
||||
delete: Delete Account
|
||||
auth-method:
|
||||
title: Auth Method
|
||||
title_plural: Auth Methods
|
||||
description: Auth methods govern how an org's users can login to Boundary. Boundary’s default auth method is username & passwords.
|
||||
description: Auth methods allow users to authenticate within a scope.
|
||||
messages:
|
||||
welcome:
|
||||
title: Welcome to Auth Methods
|
||||
@@ -24,11 +25,36 @@ auth-method:
|
||||
none:
|
||||
title: No Auth Methods
|
||||
description: No auth methods were found for this scope.
|
||||
no-primary-auth-method:
|
||||
title: "Recommendation: Select a Primary Auth Method"
|
||||
description: A primary auth method auto-creates users when they authenticate for the first time. Scopes without primary auth methods may experience authentication failures.
|
||||
pending:
|
||||
title: Authentication Pending
|
||||
description: Complete authentication in the newly-opened window.
|
||||
titles:
|
||||
new-signing-algorithm: New Signing Algorithm
|
||||
new-allowed-audience: New Allowed Audience
|
||||
new-claims-scope: New Claims Scope
|
||||
new-from-claim: New From Claim
|
||||
new-to-claim: New To Claim
|
||||
new-certificate: New Certificate
|
||||
types:
|
||||
password: Password
|
||||
oidc: OIDC
|
||||
actions:
|
||||
create: Create Auth Method
|
||||
delete: Delete Auth Method
|
||||
make-primary: Make Primary
|
||||
remove-as-primary: Remove as Primary
|
||||
notifications:
|
||||
make-primary-success: Auth method successfully elected to primary.
|
||||
remove-as-primary-success: Auth method successfully removed as primary.
|
||||
questions:
|
||||
make-primary: Make primary auth method?
|
||||
remove-as-primary: Remove primary auth method?
|
||||
make-primary-confirm: "A primary auth method auto-creates users on their first authentication attempt if needed. Note: if you change the primary auth method, the previous primary will no longer auto-create users."
|
||||
remove-as-primary-confirm: Removing the primary auth method may lead to authentication failures for this scope.
|
||||
no-see-window: Don't see the new window?
|
||||
group:
|
||||
title: Group
|
||||
title_plural: Groups
|
||||
|
||||
@@ -7,3 +7,7 @@ copied: Copied
|
||||
connection:
|
||||
success:
|
||||
description: "Local proxy address ({protocol}): {address}:{port}"
|
||||
primary: Primary
|
||||
inactive: Inactive
|
||||
active-private: Private
|
||||
active-public: Public
|
||||
@@ -1,3 +1,5 @@
|
||||
const namedBlockPolyfill = require('ember-named-blocks-polyfill/lib/named-blocks-polyfill-plugin');
|
||||
|
||||
module.exports = {
|
||||
stories: [
|
||||
'../addon/components/**/*.stories.(js|mdx)',
|
||||
@@ -7,5 +9,8 @@ module.exports = {
|
||||
'@storybook/addon-knobs',
|
||||
'@storybook/addon-actions',
|
||||
'@storybook/addon-docs',
|
||||
]
|
||||
],
|
||||
emberOptions: {
|
||||
polyfills: [namedBlockPolyfill],
|
||||
},
|
||||
};
|
||||
@@ -13,6 +13,7 @@ Boundary UIs.
|
||||
- [Linting](#linting)
|
||||
- [Formatting](#formatting)
|
||||
- [Building Components](#building-components)
|
||||
- [Using Components](#using-components)
|
||||
- [Running tests](#running-tests)
|
||||
- [Running the dummy application](#running-the-dummy-application)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
Loaded 100 of 447 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user