Compare commits

..
197 Commits
Author SHA1 Message Date
Susmitha Girumala 6e4ab7d881 Merge pull request #537 from hashicorp/bump-cli-0-2-0
Bump cli to 0.2.0
2021-04-15 09:27:34 -04:00
Susmitha Girumala 859a3fdd83 chore: Bump cli to 0.2.0 2021-04-15 09:13:54 -04:00
Susmitha Girumala b1568aa30f chore: Set current version to 1.0.0 for release 2021-04-15 09:08:21 -04:00
Randall Morey a46b44b13f Merge pull request #536 from hashicorp/bump-changelog
docs: Bump changelog for 1.0.0 - desktop
2021-04-14 19:52:12 -04:00
Susmitha Girumala 59dee87141 Merge branch 'main' into bump-changelog 2021-04-14 17:03:51 -04:00
Susmitha Girumala f1a20ea33d docs: Bump changelog for 1.0.0 2021-04-14 16:59:47 -04:00
Susmitha Girumala 4a5e72d77b Merge pull request #533 from hashicorp/fix-authenticate-read
fix: 🐛 do not load singular auth methods in auth form
2021-04-14 16:53:11 -04:00
Susmitha Girumala 87a6d839e7 Merge pull request #535 from hashicorp/docs-changelog
docs: add OIDC to changelog
2021-04-14 16:43:29 -04:00
Susmitha Girumala 1b449bdea4 Merge pull request #534 from hashicorp/chore-more-error-info
feat: 🎸 show copyable error details in error page
2021-04-14 16:42:19 -04:00
Susmitha Girumala 1ba0c873e9 Merge branch 'main' into fix-authenticate-read 2021-04-14 16:31:49 -04:00
Susmitha Girumala f8e7ec7f5f Merge pull request #532 from hashicorp/check-for-zip-archive-before-update
fix: Check for update archive before prompting download
2021-04-14 16:31:39 -04:00
Randall Morey 4d4affe6d7 docs: add OIDC to changelog 2021-04-14 16:22:25 -04:00
Randall Morey b13befaff2 chore: teak capitalization 2021-04-14 16:16:22 -04:00
Randall Morey 3836656042 feat: 🎸 show copyable error details in error page 2021-04-14 16:11:13 -04:00
Susmitha Girumala 55e3284e3f Merge branch 'main' into fix-authenticate-read 2021-04-14 16:08:27 -04:00
Susmitha Girumala 27a416001b Merge remote-tracking branch 'origin/main' into check-for-zip-archive-before-update 2021-04-14 15:33:09 -04:00
Susmitha Girumala 43a41d6818 Merge pull request #531 from hashicorp/chore-enable-primary-auth-method
chore: 🤖 enable primary auth method feature
2021-04-14 15:32:59 -04:00
Susmitha Girumala b17088803f refactor: Remove ability to hide dialog using suppressPrompt 2021-04-14 15:31:14 -04:00
Susmitha Girumala 5eb9d01b23 Merge branch 'main' into chore-enable-primary-auth-method 2021-04-14 15:15:23 -04:00
Randall Morey 288a38f6ea fix: 🐛 do not load singular auth methods in auth form 2021-04-14 15:13:39 -04:00
Randall Morey e7724ab8a5 fix: 🐛 is_primary should default to false 2021-04-14 14:36:51 -04:00
Susmitha Girumala e7c7d2b280 fix: Check for update archive before prompting download 2021-04-14 13:44:26 -04:00
Randall Morey 80abe240b4 fix: unset mirage flag 2021-04-14 13:27:31 -04:00
Susmitha Girumala 6960aaeffc Merge pull request #530 from hashicorp/fix-error-object-in-desktop-connect
fix: Display api errors in desktop connect
2021-04-14 11:39:40 -04:00
Randall Morey deb8871060 fix: 🐛 API sends is_primary, not primary 2021-04-14 11:28:07 -04:00
Susmitha Girumala ba671b4ab3 Merge branch 'main' into fix-error-object-in-desktop-connect 2021-04-14 11:19:27 -04:00
Susmitha Girumala bd3a4935a4 Merge pull request #529 from hashicorp/fix-typos-in-notifications
fix: Fix misspelling in success notifications
2021-04-14 11:19:13 -04:00
Susmitha Girumala 13ce61218c Merge branch 'main' into fix-typos-in-notifications 2021-04-14 11:05:08 -04:00
Susmitha Girumala fa2e1021c5 Merge pull request #528 from hashicorp/chore-add-feature-flags
chore: 🤖 add disabled feature flags for future work
2021-04-14 11:04:56 -04:00
Randall Morey c213ee18b2 chore: 🤖 enable primary auth method feature 2021-04-14 10:16:34 -04:00
Randall Morey 43c667729f Merge branch 'main' into fix-typos-in-notifications 2021-04-14 10:07:03 -04:00
Susmitha Girumala 26e6cc8a9b fix: Display api errors in desktop connect 2021-04-14 10:03:14 -04:00
Susmitha Girumala 5d57c8dd25 Merge branch 'main' into chore-add-feature-flags 2021-04-14 10:02:33 -04:00
Susmitha Girumala f31a93085a Merge pull request #527 from hashicorp/fix-styles
Fix styles
2021-04-14 10:02:16 -04:00
Susmitha Girumala 1927ef7832 fix: Fix misspelling in success notifications 2021-04-14 09:41:30 -04:00
Randall Morey fd53f7b282 chore: 🤖 add disabled feature flags for future work 2021-04-14 09:40:22 -04:00
Susmitha Girumala eaaeed6d1b Merge branch 'main' into fix-styles 2021-04-14 09:37:35 -04:00
Susmitha Girumala 8f8566548a Merge pull request #526 from hashicorp/fix-hiding-update-dialog
fix: Undefined type issue when not suppressing update dialog
2021-04-14 09:37:23 -04:00
Randall Morey 40e12366e6 fix: use SVG attributes instead of inline styles 2021-04-14 09:12:14 -04:00
Randall Morey 39ea49ccdf Merge branch 'main' into fix-hiding-update-dialog 2021-04-14 08:11:19 -04:00
Randall Morey a69dfa251a Merge pull request #525 from hashicorp/trim-origin-url
fix: Trim origin url in desktop
2021-04-14 08:11:09 -04:00
Susmitha Girumala 906a336acb fix: Undefined type issue when not suppressing update dialog 2021-04-13 18:11:07 -04:00
Randall Morey 9c9988a305 style: cover dialog should have a high z-index and admin OIDC auth button should be block style 2021-04-13 17:25:02 -04:00
Susmitha Girumala 1a901b15d2 fix: Trim first to avoid leaving trailing slashes in origin 2021-04-13 16:57:07 -04:00
Susmitha Girumala e894dbc4e8 fix: Trim origin url in desktop 2021-04-13 16:57:07 -04:00
Susmitha Girumala 18e95435f5 Merge pull request #522 from hashicorp/desktop-update-feature
feat: 🎸 Update desktop through menu option
2021-04-13 16:43:17 -04:00
Susmitha Girumala e2154f9698 Merge branch 'main' into desktop-update-feature 2021-04-13 13:18:52 -04:00
Susmitha Girumala d27ddb1eee Merge pull request #429 from hashicorp/feature-oidc-authenticate
Feature oidc authenticate
2021-04-13 11:42:06 -04:00
Randall Morey 8d3aea2e23 chore: allow insecure localhost external links to open 2021-04-13 11:20:29 -04:00
Randall Morey 4d47b3cf51 Merge branch 'main' into feature-oidc-authenticate 2021-04-13 10:48:29 -04:00
Susmitha Girumala b741d3af16 Merge pull request #523 from hashicorp/fix-copyable-project-id
fix: Show project id to copy in targets list in desktop
2021-04-13 10:40:07 -04:00
Randall Morey 92cf57a542 Merge branch 'main' into fix-copyable-project-id 2021-04-13 10:19:30 -04:00
Randall Morey aa6d369514 Merge pull request #524 from hashicorp/fix-faker-depreciation-warnings
chore: Bump faker and yargs-parser to fix depreciation warns
2021-04-13 10:19:22 -04:00
Randall Morey 1ed685d81f chore: enable OIDC feature flags 2021-04-12 18:12:23 -04:00
Randall Morey 586a4dbe23 feat: 🎸 roll out OIDC to desktop 2021-04-12 18:10:08 -04:00
Susmitha Girumala f858da88f2 chore: Bump faker and yargs-parser to fix depreciation warns 2021-04-12 17:35:57 -04:00
Randall Morey 5f342a0ee8 refactor: 💡 "retry" is a link instead of an action button 2021-04-12 17:10:55 -04:00
Randall Morey 3468acce39 chore: 🤖 OIDC tokens must be added to request auth headers 2021-04-12 16:54:13 -04:00
Susmitha Girumala b0a93e2e2d feat: Check for desktop update on launch 2021-04-12 16:26:46 -04:00
Randall Morey c2b66b74eb feat: 🎸 add oidc-crud feature flag to disable admin list links 2021-04-12 15:31:42 -04:00
Randall Morey 32ec1f0f70 chore: oidc should use 202 2021-04-12 15:16:52 -04:00
Susmitha Girumala e539a86600 fix: Show project id to copy in targets list in desktop 2021-04-12 15:13:10 -04:00
Susmitha Girumala efcdd0701e chore: Use forge config for current version of desktop 2021-04-12 14:52:58 -04:00
Susmitha Girumala 9ab9800f7e docs: Add app updater dev env vars to readme 2021-04-12 14:21:15 -04:00
Susmitha Girumala 4336fde21b fix: Use semver to compare desktop versions for update 2021-04-12 14:10:34 -04:00
Randall Morey 4cb7985ac1 chore: 🤖 token pending status is now 202 2021-04-12 13:47:58 -04:00
Susmitha Girumala 04726a61da docs: Add desktop update feature to changelog 2021-04-12 10:56:12 -04:00
Susmitha Girumala 53270519cb feat: 🎸 Update desktop through menu option 2021-04-12 10:42:34 -04:00
Randall Morey 5105d80a9d fix: 🐛 fix request attributes for OIDC 2021-04-10 12:34:13 -04:00
Randall Morey 53423739d3 chore: 🤖 hide auth form if OIDC and OIDC is disabled 2021-04-09 15:41:32 -04:00
Randall Morey f782d90a7e feat: 🎸 OIDC feature flag (disabled in production) 2021-04-09 14:55:58 -04:00
Randall Morey 721d09899a feat: 🎸 add pending modal with retry and cancel 2021-04-09 14:32:35 -04:00
Randall Morey d1303c7bca Merge branch 'main' into feature-oidc-authenticate 2021-04-09 13:27:38 -04:00
Susmitha Girumala 31682ffd85 Merge pull request #521 from hashicorp/feature-dialog-cover
feat: 🎸 add dialog cover component
2021-04-09 12:24:33 -04:00
Randall Morey b5a791d618 Merge branch 'main' into feature-dialog-cover 2021-04-09 11:45:32 -04:00
Susmitha Girumala e110f33e40 Merge pull request #519 from hashicorp/fix-origin-trailing-slash
fix: 🐛 silently drop trailing slashes from origin input - ICU-1293
2021-04-09 11:43:59 -04:00
Randall Morey 68bf02ee25 chore: 🤖 merge from main 2021-04-09 11:32:20 -04:00
Susmitha Girumala 063d643d76 Merge branch 'main' into fix-origin-trailing-slash 2021-04-09 11:19:47 -04:00
Susmitha Girumala b6818871fc Merge pull request #518 from hashicorp/style-auth-form
Style auth form
2021-04-09 11:19:34 -04:00
Randall Morey 1effa79c93 feat: 🎸 add dialog cover component 2021-04-09 11:07:23 -04:00
Randall Morey 6fd075f7e8 doc: update changelog 2021-04-08 13:58:34 -04:00
Randall Morey 0d8508cd4e fix: 🐛 silently drop trailing slashes from origin input 2021-04-08 13:45:19 -04:00
Randall Morey f3bd8d0b6f test: add missing frame tests 2021-04-08 11:26:29 -04:00
Susmitha Girumala 8889e86cec chore: Add polyfill plugin to storybook config 2021-04-07 14:33:38 -04:00
Randall Morey 6500d56273 style: 💄 add frame component and update admin auth form 2021-04-07 12:53:09 -04:00
99 80cffeb48b Merge pull request #517 from hashicorp/nat/release-test-trigger
Add workflow to trigger test release manually
2021-04-06 08:28:07 -07:00
Randall Morey 263c8c01e9 Merge branch 'main' into feature-oidc-authenticate 2021-04-06 10:37:39 -04:00
Susmitha Girumala 74531f5d85 Merge branch 'main' into nat/release-test-trigger 2021-04-05 09:38:10 -04:00
Susmitha Girumala e0eed2b4ea Merge pull request #516 from hashicorp/feature-oidc-static-callbacks
Add static auth callback pages to admin UI for notifying success or error.
2021-04-02 20:46:10 -04:00
99 c9df20184a Message update 2021-04-02 16:13:24 -07:00
99 afa1deef94 fix messaging 2021-04-02 16:06:31 -07:00
99 5d04dca21e Add workflow to trigger test release 2021-04-02 16:06:21 -07:00
Randall Morey 746f45b6da chore: remove "provider" from auth strings 2021-04-02 18:56:16 -04:00
Randall Morey a62d36af92 feat: 🎸 add auth callback pages 2021-04-02 16:06:38 -04:00
Randall Morey a41572302a Merge pull request #515 from hashicorp/chore-dependabot-alerts
chore: 🤖 add xmldom resolution
2021-04-02 15:33:56 -04:00
Randall Morey 2138aa8b01 chore: 🤖 add xmldom resolution 2021-04-02 15:19:27 -04:00
Randall Morey 4ea53c9cc7 Merge pull request #511 from hashicorp/bump-ember-electron
Bump ember-electron from 3.0.0-beta to 3.0.0
2021-04-02 12:52:13 -04:00
Randall Morey c4e5a4c121 chore: 🤖 merge from main 2021-04-02 12:29:04 -04:00
Randall Morey e5544415b0 Merge pull request #514 from hashicorp/chore-dependabot-alerts
chore: 🤖 yarn install to update lock file
2021-04-02 12:19:24 -04:00
Randall Morey ce84d2405c Merge branch 'main' into bump-ember-electron 2021-04-02 12:00:23 -04:00
Susmitha Girumala b07c90ee53 Merge branch 'main' into chore-dependabot-alerts 2021-04-02 11:59:25 -04:00
Susmitha Girumala 27a28c6db2 Merge pull request #513 from hashicorp/fix-flaky-origin-test
Fix flaky origin test
2021-04-02 11:58:56 -04:00
Randall Morey 82eed3c5fe chore: 🤖 yarn install to update lock file 2021-04-02 11:49:49 -04:00
Randall Morey 3d13d8f412 refactor: 💡 auth payloads now have "attributes" field 2021-04-01 17:41:30 -04:00
Randall Morey 6927008041 refactor: 💡 use command instead of action for auth endpoints 2021-04-01 16:16:46 -04:00
Randall Morey c978958df2 chore: 🤖 merge from main 2021-04-01 15:24:49 -04:00
Randall Morey e8b2e1aa6f Merge branch 'main' into fix-flaky-origin-test 2021-04-01 13:33:02 -04:00
Randall Morey 43528c64c2 test: 💍 build out autoOrigin tests 2021-04-01 13:04:28 -04:00
Randall Morey d7f090f348 test: 💍 dedupe window.location.origin 2021-04-01 10:26:59 -04:00
Susmitha Girumala 93a9efbd1b Merge branch 'main' into bump-ember-electron 2021-04-01 09:29:29 -04:00
Susmitha Girumala a50a0735bb Merge pull request #512 from hashicorp/fix-flaky-origin-test
test: Fix flaky origin test in desktop
2021-04-01 09:29:16 -04:00
Randall Morey 838567c2d2 refactor: 💡 clean up origin error test 2021-03-31 18:00:42 -04:00
Susmitha Girumala 95e6144e44 test: Fix flaky origin test in desktop 2021-03-31 15:02:17 -04:00
Susmitha Girumala 58c9df272f chore: Bump dev electron to latest 12.0.2 2021-03-31 09:50:36 -04:00
Susmitha Girumala 3696e45275 chore: Bump ember-electron to v3.0.0 2021-03-31 09:46:42 -04:00
Randall Morey 73503f98dc Merge pull request #506 from hashicorp/feature-primary-auth-method
Feature primary auth method
2021-03-30 14:14:02 -04:00
Randall Morey 15f6c408d4 fix: typo 2021-03-30 13:21:33 -04:00
Randall Morey 0ccc6e7f78 chore: enable feature in development 2021-03-30 13:21:14 -04:00
Randall Morey 69a10f891a chore: remove empty attribute 2021-03-30 13:12:18 -04:00
Randall Morey 18d5b60c6d chore: remove primary auth method mention for now 2021-03-30 11:27:56 -04:00
Randall Morey f279ac44ca chore: 🤖 cast to boolean explicitly 2021-03-30 10:34:15 -04:00
Randall Morey 29f21e0dfe chore: change to "manage" 2021-03-30 09:10:34 -04:00
Randall Morey 9e588212a4 Merge branch 'feature-primary-auth-method' of https://github.com/hashicorp/boundary-ui into feature-primary-auth-method 2021-03-30 09:04:48 -04:00
Randall Morey 3361aa1800 feat: 🎸 add make/remove primary to auth method list 2021-03-29 19:24:21 -04:00
Randall Morey 5c3b44b2f6 Merge branch 'main' into feature-primary-auth-method 2021-03-29 14:40:24 -04:00
Susmitha Girumala 59d345a912 Merge pull request #505 from hashicorp/zoom-in-shortcut
Specify zoom-in shortcut
2021-03-29 10:49:13 -04:00
Randall Morey 2adfdf6ef6 chore: 🤖 disable primary auth method feature for now 2021-03-26 17:51:33 -04:00
Randall Morey 8aa9a51c88 test: 💍 add primary auth method acceptance tests 2021-03-26 14:55:19 -04:00
Randall Morey 60b7d0134a feat: 🎸 add set/remove primary actions 2021-03-26 12:47:49 -04:00
Randall Morey ae26d588a9 refactor: 💡 primary auth method should be detected on scope
rather than the synthetic `primary` field on auth method
2021-03-26 11:33:43 -04:00
Randall Morey abc1827280 feat: 🎸 add "no primary auth method" warning to index 2021-03-26 09:41:07 -04:00
Susmitha Girumala 682bb14037 docs: Add pr link to changelog 2021-03-25 16:38:32 -04:00
Susmitha Girumala 28ec1008a0 Revert "test: Remove mock in failing origin test in desktop"
This reverts commit 83ab375829.
2021-03-25 16:36:02 -04:00
Susmitha Girumala 83ab375829 test: Remove mock in failing origin test in desktop 2021-03-25 16:19:59 -04:00
Randall Morey e960c34542 feat: 🎸 hide primary auth method behind feature flag 2021-03-25 16:15:32 -04:00
Susmitha Girumala 4d2cb86a62 fix: Trigger zoomin using keyboard shortcut 2021-03-25 16:01:44 -04:00
Randall Morey 22131864f8 feat: 🎸 add primary badge to auth methods UI (listing/form) 2021-03-25 12:48:38 -04:00
Randall Morey ac8a6054b0 feat: 🎸 add API and mock support for primary auth methods 2021-03-24 18:36:34 -04:00
Susmitha Girumala 22d578620d Merge pull request #503 from hashicorp/tests-coverage-desktop
Build all desktop tests in Github action
2021-03-24 10:15:03 -04:00
Susmitha Girumala 82506d11c8 test: Disable random session status updated in testruns 2021-03-23 14:47:07 -04:00
Susmitha Girumala 84818a54f5 chore: Update yarn.lock 2021-03-22 20:37:10 -04:00
Susmitha Girumala a0b2c6883a Merge remote-tracking branch 'origin/main' into tests-coverage-desktop 2021-03-22 20:36:00 -04:00
Susmitha Girumala 180b74cc98 test: 💍 Add scopes test in desktop 2021-03-22 20:32:11 -04:00
Susmitha Girumala 12f2042f87 test: Add targets/sessions test in desktop 2021-03-22 20:25:29 -04:00
Susmitha Girumala f096eaf7d7 test: 💍 Add targets/hosts test
Remove duplicates from origin test
2021-03-22 20:12:25 -04:00
Susmitha Girumala e2ff75f435 Merge pull request #504 from hashicorp/ci-workflow
ci: limit workflow to PRs and specify conservative timeouts
2021-03-22 15:16:13 -04:00
Randall Morey 96ef767e65 test: 💍 add start auth oidc test 2021-03-19 17:03:03 -04:00
Susmitha Girumala f387416d6d test: 💍 Update projects/targets test in desktop 2021-03-19 15:46:34 -04:00
Susmitha Girumala 933ade8a16 test: 💍 Update projects/sessions test in desktop 2021-03-19 14:49:06 -04:00
Susmitha Girumala bf4d43cee2 test: Update projects tests in desktop 2021-03-19 13:16:52 -04:00
Susmitha Girumala 6b3b6c4d58 test: 💍 Update origin test in desktop 2021-03-19 12:58:59 -04:00
Susmitha Girumala 4630e130d4 test: 💍 Update authentication test in desktop 2021-03-19 12:40:12 -04:00
Susmitha Girumala 42ace44716 test: 💍 Add ember-a11y-testing package to desktop 2021-03-19 12:33:36 -04:00
Randall Morey 888b106780 ci: limit workflow to PRs and specify conservative timeouts 2021-03-19 12:10:03 -04:00
Susmitha Girumala 6bd994c86b tests: Update unit tests in desktop 2021-03-19 12:09:06 -04:00
Susmitha Girumala 6ce09162d7 test: Update integration tests in desktop 2021-03-19 11:54:28 -04:00
Jeff Malnick f487c4f0d9 Merge pull request #501 from hashicorp/pronoun-expectations
docs: add contributing guide with pronoun expectations
2021-03-18 16:44:57 -07:00
Jeff Malnick 2d0afd7b9c docs: add contributing guide with pronoun expectations 2021-03-18 08:56:51 -07:00
Randall Morey f406fc3861 chore: 🤖 oidc auth methods are not yet editable in admin ui 2021-03-16 15:41:13 -04:00
Randall Morey 760710ae23 feat: 🎸 add polling error notification and handling 2021-03-16 14:02:34 -04:00
Randall Morey 0219b5c99a fix: move electronegativity comments back to same line 2021-03-15 17:14:23 -04:00
Randall Morey 6056b50939 chore: manual merge from main 2021-03-15 17:08:47 -04:00
Randall Morey 9e57f838d5 chore: 🤖 remove cookies flag from oidc start auth 2021-03-15 17:08:25 -04:00
Randall Morey 896671ceb1 Merge branch 'main' into feature-oidc-authenticate 2021-03-15 16:19:18 -04:00
Randall Morey ca9485053e docs: update doc comments 2021-03-15 16:18:48 -04:00
Randall Morey 7e6c46de2b docs: update doc comments 2021-03-15 16:18:22 -04:00
Susmitha Girumala c68c1ba0fb Merge pull request #500 from hashicorp/docs-cli-version
docs: add CLI versions to changelog
2021-03-15 15:19:59 -04:00
Susmitha Girumala 5f83751db2 Merge branch 'main' into docs-cli-version 2021-03-15 14:44:29 -04:00
Susmitha Girumala 8179ab7dec Merge pull request #499 from hashicorp/chore-dependabot
chore: 🤖 bump deps for dependabot
2021-03-15 14:44:04 -04:00
Susmitha Girumala d93d48197f Merge branch 'main' into chore-dependabot 2021-03-15 14:27:51 -04:00
Susmitha Girumala d010d25934 Merge pull request #498 from hashicorp/chore-remove-getwithdefault
chore: 🤖 remove getwithdefault
2021-03-15 13:23:59 -04:00
Susmitha Girumala 3bb9d66377 Merge branch 'main' into chore-remove-getwithdefault 2021-03-15 11:39:13 -04:00
Susmitha Girumala 42fe8f4eca Merge pull request #494 from hashicorp/chore-electronegativity-audit
chore: 🤖 add electronegativity inline disable flags
2021-03-15 11:38:36 -04:00
Susmitha Girumala 45bf901d9e Merge branch 'main' into chore-electronegativity-audit 2021-03-15 11:27:38 -04:00
Randall Morey a35aacdf5e docs: add CLI versions to changelog 2021-03-15 10:07:26 -04:00
Randall Morey feddb2efad chore: 🤖 bump deps for dependabot 2021-03-12 16:44:15 -05:00
Randall Morey 47c664eda9 Merge branch 'chore-remove-getwithdefault' into feature-oidc-authenticate 2021-03-12 16:07:55 -05:00
Randall Morey 9d847348a0 chore: 🤖 remove getwithdefault 2021-03-12 16:06:08 -05:00
Randall Morey 9fc2b7a409 manual merge from main 2021-03-12 12:34:12 -05:00
Susmitha Girumala 02642bc5e4 Merge pull request #497 from hashicorp/rename-release-commit-var
chore: Rename COMMIT var to RELEASE_COMMIT to match CircleCi
2021-03-10 15:06:10 -05:00
Susmitha Girumala 8f8e257523 chore: Rename COMMIT var to RELEASE_COMMIT to match CircleCi 2021-03-10 14:56:00 -05:00
Randall Morey e50db68abe chore: 🤖 add electronegativity inline disable flags
these occurrences have been manually checked and approved
2021-03-08 17:57:04 -05:00
Randall Morey ec23e3706b Merge branch 'main' into feature-oidc-authenticate 2021-02-27 08:20:59 -05:00
Randall Morey e9c297b1cf chore: 🤖 update token endpoint to match API 2021-02-26 10:42:19 -05:00
Randall Morey 4ae5e73def Merge branch 'main' into feature-oidc-authenticate 2021-02-26 09:59:27 -05:00
Randall Morey 78505c720f Merge branch 'main' into feature-oidc-authenticate 2021-02-26 09:47:25 -05:00
Randall Morey 09c8680ff2 Merge branch 'main' into feature-oidc-authenticate 2021-02-18 15:54:23 -05:00
Randall Morey 811d1dbe32 Merge branch 'main' into feature-oidc-authenticate 2021-02-17 15:33:44 -05:00
Randall Morey dbe7003068 chore: 🤖 manual merge from main 2021-02-16 13:38:48 -05:00
Randall Morey 05709f7ee5 Merge branch 'main' into feature-oidc-authenticate 2021-01-25 15:50:10 -05:00
Randall Morey 3084d6cf06 feature: initial OIDC authentication flow 2021-01-22 17:01:12 -05:00
Randall Morey ac92575b36 chore: setup initial oidc polling route (polling to come) 2021-01-22 10:34:05 -05:00
Randall Morey 900f342894 chore: 🤖 move method template to index to make room for oidc 2021-01-21 18:19:03 -05:00
Randall Morey 2f3478d043 feat: 🎸 initial flow to open new window to 3rd party auth 2021-01-21 14:26:02 -05:00
Randall Morey af8947dc62 chore: add mock authenticate:start endpoint 2021-01-19 15:43:45 -05:00
Randall Morey a3c12b36e7 refactor: 💡 mock authenticate should handle multiple auth types 2021-01-15 17:32:30 -05:00
Randall Morey 92640b96aa wip: OIDC authenticator 2021-01-15 11:10:33 -05:00
141 changed files with 7462 additions and 4091 deletions

No files matched your search

+3 -1
View File
@@ -1,5 +1,5 @@
name: Validate Monorepo
on: [push]
on: pull_request
jobs:
compliance:
@@ -39,6 +39,7 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
@@ -56,6 +57,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
+6
View File
@@ -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).
+2 -2
View File
@@ -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}` : '';
}
@@ -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',
})
+23 -1
View File
@@ -1,3 +1,25 @@
import GeneratedAuthMethodModel from '../generated/models/auth-method';
import { equal } from '@ember/object/computed';
export default class AuthMethodModel extends GeneratedAuthMethodModel {}
export default class AuthMethodModel extends GeneratedAuthMethodModel {
// =attributes
/**
* @type {boolean}
*/
@equal('is_primary', true) isPrimary;
/**
* @type {boolean}
*/
get isPassword() {
return this.type === 'password';
}
/**
* @type {boolean}
*/
get isOIDC() {
return this.type === 'oidc';
}
}
@@ -0,0 +1,35 @@
import ApplicationSerializer from './application';
export default class AuthMethodSerializer extends ApplicationSerializer {
/**
* 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
* @return {object}
*/
normalize() {
const normalized = super.normalize(...arguments);
if (!normalized.data.attributes.is_primary) {
normalized.data.attributes.is_primary = false;
}
return normalized;
}
}
@@ -0,0 +1 @@
export { default } from 'api/serializers/auth-method';
+17 -4
View File
@@ -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(
@@ -413,9 +423,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.
*/
+3 -3
View File
@@ -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,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';
/**
* 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(),
});
+91 -25
View File
@@ -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,32 +23,98 @@ 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: '',
},
}
);
}
},
},
};
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 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: '',
}
);
}
const [, id] = request.params.id_method.match(/(?<id>.[^:]*)/);
const { command } = payload;
const authMethod = authMethods.find(id);
const scope = scopes.find(authMethod.scopeId);
const scopeAttrs = this.serialize(scopes.find(scope.id));
return commandHandlers[authMethod.type][command](payload, scopeAttrs);
}
export function deauthHandler() {
+6 -2
View File
@@ -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');
+2 -2
View File
@@ -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,7 +55,7 @@ export default function initializeMockIPC(server) {
});
return {
address: window.location.hostname,
port: random.number(),
port: datatype.number(),
protocol: type,
session_id: newSession.id,
};
@@ -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;
},
});
@@ -0,0 +1,66 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Serializer | auth method', 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('auth-method');
assert.ok(serializer);
});
test('it serializes records', function (assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('auth-method', {});
let serializedRecord = record.serialize();
assert.ok(serializedRecord);
});
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'
);
});
});
+29 -3
View File
@@ -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,9 +72,10 @@ 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 +87,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';
+107
View File
@@ -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();
}
}
}
+2 -16
View File
@@ -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 });
@@ -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',
},
@@ -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,6 +70,9 @@ 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) {
+2 -2
View File
@@ -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);
}
@@ -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>
+2 -2
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
export { default } from 'core/authenticators/oidc';
@@ -0,0 +1 @@
export { default } from 'core/components/form/authenticate/oidc';
+2 -1
View File
@@ -37,6 +37,7 @@
"ember-loading": "^0.4.0",
"ember-page-title": "^6.0.3",
"ember-route-action-helper": "^2.0.8",
"ember-feature-flags": "^6.0.0",
"rose": "*"
},
"devDependencies": {
@@ -71,7 +72,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",
+69
View File
@@ -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

+1 -1
View File
@@ -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');
});
+2 -1
View File
@@ -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
+5 -1
View File
@@ -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:
@@ -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.
+19 -1
View File
@@ -16,7 +16,7 @@ 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 +24,29 @@ 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.
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,4 @@ copied: Copied
connection:
success:
description: "Local proxy address ({protocol}): {address}:{port}"
primary: Primary
+6 -1
View File
@@ -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],
},
};
@@ -0,0 +1,21 @@
<div class="rose-dialog-cover-modal-overlay"></div>
<section
...attributes
role="dialog"
class="rose-dialog-cover"
id={{this.id}}
aria-describedby={{concat "dialog-cover-header-" this.id}}
tabindex="0"
{{focus-trap}}
>
<header
class="rose-dialog-cover-header"
id={{concat "dialog-cover-header-" this.id}}
>
{{yield to="header"}}
</header>
<div class="rose-dialog-cover-body">
{{yield to="body"}}
</div>
</section>
@@ -0,0 +1,42 @@
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
import { action } from '@storybook/addon-actions';
<Meta title="Rose|Dialog|Cover" component="Cover" />
# Dialog Cover
A template-only dialog component that covers the viewport and accepts arbitrary
content. Note that this component is unique to Boundary and is not part of the
Structure design system.
Prerequisite: `ember-named-blocks-polyfill` must be installed in your app's
`dependencies` rather than `devDependencies`.
<Preview>
<Story name="Cover" height="15rem">{{
template: hbs`
<Rose::Dialog::Cover>
<:header>
<h1>Cover Header</h1>
</:header>
<:body>
<p>Cover body</p>
</:body>
</Rose::Dialog::Cover>
`
}}</Story>
</Preview>
```htmlbars
<Rose::Dialog::Cover>
<:header>
<h2>Cover Header</h2>
</:header>
<:body>
<p>Cover body</p>
</:body>
</Rose::Dialog::Cover>
```
@@ -0,0 +1,11 @@
<section ...attributes class="rose-frame">
<header class="rose-frame-header">
{{yield to="header"}}
</header>
<div class="rose-frame-body">
{{yield to="body"}}
</div>
</section>
@@ -0,0 +1,42 @@
import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
import { hbs } from 'ember-cli-htmlbars';
import { action } from '@storybook/addon-actions';
<Meta title="Rose|Frame" component="Frame" />
# Frame
A simple frame component with named blocks `:header` and `:body`. Note that
this component is unique to Boundary and is not part of the Structure design
system.
Prerequisite: `ember-named-blocks-polyfill` must be installed in your app's
`dependencies` rather than `devDependencies`.
<Preview>
<Story name="Frame" height="15rem">{{
template: hbs`
<Rose::Frame>
<:header>
<h2>Frame Header</h2>
</:header>
<:body>
<p>Frame body</p>
</:body>
</Rose::Frame>
`
}}</Story>
</Preview>
```htmlbars
<Rose::Frame>
<:header>
<h2>Frame Header</h2>
</:header>
<:body>
<p>Frame body</p>
</:body>
</Rose::Frame>
```
@@ -19,6 +19,7 @@
&.rose-badge-outline {
--border-color: var(--ui-gray-starker-1);
--background-color: none;
}
&.rose-badge-dark {
@@ -40,6 +41,7 @@
&.rose-badge-informational-outline {
--color: var(--info-starker-1);
--border-color: var(--info-starker-1);
--background-color: none;
}
&.rose-badge-alert {
@@ -50,6 +52,7 @@
&.rose-badge-alert-outline {
--color: var(--alert-starker-1);
--border-color: var(--alert-starker-1);
--background-color: none;
}
&.rose-badge-error {
@@ -60,6 +63,7 @@
&.rose-badge-error-outline {
--color: var(--failure-starker-1);
--border-color: var(--failure-starker-1);
--background-color: none;
}
&.rose-badge-success {
@@ -70,6 +74,7 @@
&.rose-badge-success-outline {
--color: var(--success-starker-1);
--border-color: var(--success-starker-1);
--background-color: none;
}
&.rose-badge-warning {
@@ -80,5 +85,6 @@
&.rose-badge-warning-outline {
--color: var(--warning-starker-1);
--border-color: var(--warning-starker-1);
--background-color: none;
}
}
@@ -0,0 +1,46 @@
@use '../variables/sizing';
@use '../utilities/type';
@use '../variables/media';
$l: sizing.rems(l); // 16
$m: sizing.rems(m); // 16
$s: sizing.rems(s); // 12
$xs: sizing.rems(xs); // 8
$xxs: sizing.rems(xxs); // 4
$xxxs: sizing.rems(xxxs); // 2
$xxxxs: sizing.rems(xxxxs); // 1
.rose-frame {
--headerColor: var(--ui-gray-starker-3);
--headerIconColor: var(--headerColor);
--headerBackgroundColor: var(--ui-gray-subtler-5);
--headerBorderColor: var(--ui-gray-subtler-3);
@include type.type(s);
border: $xxxxs solid var(--ui-gray-subtler-3);
border-radius: $xxxs;
//margin: $m;
box-shadow: 0 $xxs $xxs rgba(var(--stark-components), var(--opacity-4));
background-color: var(--background-color);
.rose-frame-header {
padding: ($xs + $xxxs) $l;
min-height: $l;
color: var(--headerColor);
background-color: var(--headerBackgroundColor);
border-bottom: $xxxxs solid var(--headerBorderColor);
border-top-left-radius: $xxxs;
border-top-right-radius: $xxxs;
}
.rose-frame-body {
$bodyPadding: $l;
padding: $bodyPadding;
> .rose-separator {
margin-left: -$bodyPadding;
margin-right: -$bodyPadding;
}
}
}
@@ -2,6 +2,7 @@
@import 'dialog';
@import 'dropdown';
@import 'form';
@import 'frame';
@import 'header';
@import 'footer';
@import 'layout';
@@ -0,0 +1,39 @@
.rose-dialog-cover {
position: fixed;
top: 0;
left: 0;
z-index: 200;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--white);
text-align: center;
a {
color: var(--white);
}
.rose-dialog-cover-header,
.rose-dialog-cover-body {
position: relative;
top: -10vh;
max-width: 650px;
}
}
// overlay
.rose-dialog-cover-modal-overlay {
content: ' ';
background-color: var(--black);
width: 100vw;
height: 100vh;
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 100;
opacity: 0.85;
}
@@ -1,6 +1,6 @@
@use '../variables/sizing';
@use '../utilities/type';
@use '../variables/media';
@use '../../variables/sizing';
@use '../../utilities/type';
@use '../../variables/media';
$m: sizing.rems(m); // 16
$s: sizing.rems(s); // 12
@@ -0,0 +1,2 @@
@import 'cover';
@import 'dialog';
@@ -0,0 +1 @@
export { default } from 'rose/components/rose/dialog/cover';
+1
View File
@@ -0,0 +1 @@
export { default } from 'rose/components/rose/frame';
+3 -1
View File
@@ -33,9 +33,11 @@
"ember-cli-babel": "^7.23.0",
"ember-cli-htmlbars": "^5.3.1",
"ember-focus-trap": "^0.4.0",
"ember-named-blocks-polyfill": "^0.2.4",
"ember-radio-button": "^2.0.1",
"ember-uuid": "^2.1.0",
"emberx-select": "github:adopted-ember-addons/emberx-select#bc5f774"
"emberx-select": "github:adopted-ember-addons/emberx-select#bc5f774",
"ember-named-blocks-polyfill": "^0.2.4"
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
@@ -79,3 +79,15 @@
</Rose::Dropdown>
</form.radioGroup>
</Rose::Form>
<Rose::Dialog::Cover>
<:header>
<h1>Authentication Pending</h1>
</:header>
<:body>
<p>Complete authentication in the newly-opened window.</p>
<p>Don't see the new window? <a href="#">Try again.</a></p>
<p><a href="#">Cancel</a></p>
</:body>
</Rose::Dialog::Cover>
@@ -0,0 +1,29 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | rose/dialog/cover', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
assert.expect(3);
await render(hbs`
<Rose::Dialog::Cover>
<:header>
<h1>Authentication Pending</h1>
</:header>
<:body>
<p>Complete authentication in the newly-opened window.</p>
<p>Don't see the new window? <a href="#">Try again.</a></p>
<p><a href="#">Cancel</a></p>
</:body>
</Rose::Dialog::Cover>
`);
assert.ok(find('.rose-dialog-cover'));
assert.ok(find('.rose-dialog-cover-header'));
assert.ok(find('.rose-dialog-cover-body'));
});
});
@@ -0,0 +1,20 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | rose/frame', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
await render(hbs`
<Rose::Frame>
<:header>Header</:header>
<:body>Body</:body>
</Rose::Frame>
`);
assert.ok(find('section > header'));
assert.ok(find('section > div'));
});
});
+5 -2
View File
@@ -50,13 +50,16 @@
"request": "~2.68.0",
"tunnel-agent": "~0.6.0",
"cryptiles": "~4.1.2",
"prismjs": "~1.21.0",
"prismjs": "~1.23.0",
"npm-user-validate": "^1.0.1",
"caniuse-lite": "^1.0.30001157",
"immer": "^8.0.1",
"node-notifier": "^8.0.1",
"highlight.js": "^10.4.1",
"hoek": "~4.2.0"
"hoek": "~4.2.0",
"elliptic": "~6.5.4",
"react-dev-utils": "~11.0.4",
"yargs-parser": "~20.2.7"
},
"config": {
"commitizen": {
@@ -0,0 +1,13 @@
import Controller from '@ember/controller';
export default class ScopesScopeAuthMethodsIndexController extends Controller {
// =attributes
/**
* True if the current scope has a primary auth method set.
* @type {boolean}
*/
get hasPrimaryAuthMethod() {
return Boolean(this.scopeModel.primary_auth_method_id);
}
}
@@ -0,0 +1,18 @@
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ScopesScopeAuthenticateMethodOidcController extends Controller {
// =services
@service session;
// =attributes
/**
* Authetnication URL for the pending OIDC flow, if any.
* @type {?string}
*/
get authURL() {
return this.session.data.pending.oidc.attributes.auth_url;
}
}
+6 -1
View File
@@ -7,10 +7,15 @@ export default class Router extends EmberRouter {
}
Router.map(function () {
this.route('authentication-complete');
this.route('authentication-error');
this.route('scopes', function () {
this.route('scope', { path: ':scope_id' }, function () {
this.route('authenticate', function () {
this.route('method', { path: ':auth_method_id' });
this.route('method', { path: ':auth_method_id' }, function () {
this.route('oidc');
});
});
this.route('scopes', function () {});
@@ -0,0 +1,3 @@
import Route from '@ember/routing/route';
export default class AuthenticationCompleteRoute extends Route {}
@@ -0,0 +1,12 @@
import Route from '@ember/routing/route';
export default class AuthenticationErrorRoute extends Route {
// =methods
/**
* @return {string}
*/
model() {
return window.location.toString();
}
}
@@ -71,8 +71,67 @@ export default class ScopesScopeAuthMethodsRoute extends Route {
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('notifications.delete-success')
async delete(authMethod) {
const scopeModel = this.modelFor('scopes.scope');
await authMethod.destroyRecord();
// Reload the scope, since this is where the primary_auth_method_id is
// stored. An auth method deletion could affect this field.
await scopeModel.reload();
await this.replaceWith('scopes.scope.auth-methods');
this.refresh();
}
/**
* Elects the specified auth method to primary for the current scope.
* @param {AuthMethodModel} authMethod
* @param {string} authMethod.id
*/
@action
@loading
@confirm('resources.auth-method.questions.make-primary-confirm', {
title: 'resources.auth-method.questions.make-primary',
})
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('resources.auth-method.notifications.make-primary-success')
async makePrimary({ id }) {
const scopeModel = this.modelFor('scopes.scope');
scopeModel.primary_auth_method_id = id;
// Attempt to save the change to the scope. If this operation fails,
// we rollback the change and rethrow the error so the user can be notified.
try {
await scopeModel.save();
} catch (e) {
scopeModel.rollbackAttributes();
throw e;
}
await this.refresh();
}
/**
* Sets the `primary_auth_method_id` field to null for the current scope
* and saves it if and only if the specified auth method is in fact primary
* for the current scope.
* @param {AuthMethodModel} authMethod
*/
@action
@loading
@confirm('resources.auth-method.questions.remove-as-primary-confirm', {
title: 'resources.auth-method.questions.remove-as-primary',
})
@notifyError(({ message }) => message, { catch: true })
@notifySuccess(
'resources.auth-method.notifications.remove-as-primary-success'
)
async removeAsPrimary(authMethod) {
const scopeModel = this.modelFor('scopes.scope');
scopeModel.primary_auth_method_id = null;
// Attempt to save the change to the scope. If this operation fails,
// we rollback the change and rethrow the error so the user can be notified.
try {
await scopeModel.save();
} catch (e) {
scopeModel.rollbackAttributes();
throw e;
}
await authMethod.reload();
}
}
@@ -1,3 +1,15 @@
import Route from '@ember/routing/route';
export default class ScopesScopeAuthMethodsIndexRoute extends Route {}
export default class ScopesScopeAuthMethodsIndexRoute extends Route {
// =methods
/**
* Adds the scope to the controller context.
* @param {Controller} controller
*/
setupController(controller) {
super.setupController(...arguments);
const scopeModel = this.modelFor('scopes.scope');
controller.scopeModel = scopeModel;
}
}
@@ -1,5 +1,6 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { getOwner } from '@ember/application';
import { action } from '@ember/object';
import loading from 'ember-loading/decorator';
import { notifyError } from 'core/decorators/notify';
@@ -20,7 +21,28 @@ export default class ScopesScopeAuthenticateMethodRoute extends Route {
*/
model({ auth_method_id: id }) {
const adapterOptions = { scopeID: this.modelFor('scopes.scope').id };
return this.store.findRecord('auth-method', id, adapterOptions);
return this.store.peekRecord('auth-method', id, adapterOptions);
}
/**
*
*/
async startOIDCAuthentication(authenticatorName, options) {
const oidc = getOwner(this).lookup(authenticatorName);
// TODO: delegate this call from the session service so that we don't have
// to look up the authenticator directly
const json = await oidc.startAuthentication(options);
await this.openExternalOIDCFlow(json.attributes.auth_url);
}
/**
* Opens the specified URL in a new tab or window. By default this uses
* `window.open`, but may be overriden.
* @param {string} url
*/
async openExternalOIDCFlow(url) {
// TODO don't use window directly
await window.open(url);
}
// =actions
@@ -37,10 +59,23 @@ export default class ScopesScopeAuthenticateMethodRoute extends Route {
const authMethod = this.modelFor('scopes.scope.authenticate.method');
const authenticatorName = `authenticator:${authMethod.type}`;
const requestCookies = false;
await this.session.authenticate(authenticatorName, creds, requestCookies, {
scope,
authMethod,
});
this.transitionTo('index');
switch (authMethod.type) {
case 'password':
await this.session.authenticate(
authenticatorName,
creds,
requestCookies,
{ scope, authMethod }
);
this.transitionTo('index');
break;
case 'oidc':
await this.startOIDCAuthentication(authenticatorName, {
scope,
authMethod,
});
this.transitionTo('scopes.scope.authenticate.method.oidc');
break;
}
}
}
@@ -0,0 +1,3 @@
import Route from '@ember/routing/route';
export default class ScopesScopeAuthenticateMethodIndexRoute extends Route {}
@@ -0,0 +1,82 @@
import Route from '@ember/routing/route';
import { getOwner } from '@ember/application';
import { inject as service } from '@ember/service';
import { task, timeout } from 'ember-concurrency';
import { action } from '@ember/object';
import { notifyError } from 'core/decorators/notify';
import config from '../../../../../config/environment';
const POLL_TIMEOUT_SECONDS = config.oidcPollingTimeoutSeconds;
export default class ScopesScopeAuthenticateMethodOidcRoute extends Route {
// =services
@service session;
// =attributes
/**
* A simple Ember Concurrency-based polling task that refreshes the route
* every POLL_TIMEOUT_SECONDS seconds. This is necessary to display changes
* to session `status` that may occur.
*
* NOTE: tasks are sort of attributes and sort of methods, but they are not
* language-level constructs. Thus we annotate this task as if it
* is an attribute.
* @type {Task}
*/
@task(function * () {
while(!this.session.isAuthenticated) {
yield timeout(POLL_TIMEOUT_SECONDS * 1000);
yield this.refresh();
}
/* eslint-disable-next-line prettier/prettier */
}).drop() poller;
// =methods
model() {
const scope = this.modelFor('scopes.scope');
const authMethod = this.modelFor('scopes.scope.authenticate.method');
const authenticatorName = `authenticator:${authMethod.type}`;
const oidc = getOwner(this).lookup(authenticatorName);
// TODO: delegate this call from the session service so that we don't have
// to look up the authenticator directly
return oidc.attemptFetchToken({ scope, authMethod });
}
/**
* If authenticated, redirects to index for further processing.
*/
redirect() {
if (this.session.isAuthenticated) this.replaceWith('index');
}
/**
* When this route is activated (entered), begin polling for changes.
*/
activate() {
this.poller.perform();
}
/**
* When this route is deactivated (exited), stop polling for changes.
*/
deactivate() {
this.poller.cancelAll();
}
/**
* An error in this route indicates authentication failed. The user is
* notified and returned to the index.
*/
@action
@notifyError(() => 'errors.authentication-failed.title', { catch: true })
error(e) {
this.transitionTo('scopes.scope.authenticate.method.index');
// rethrow the error to activate the notifyError decorator
throw e;
}
}
+20
View File
@@ -4,6 +4,12 @@
@import 'rose';
@import 'notify';
.rose-layout-global {
.rose-layout-global-body {
justify-content: center;
}
}
.rose-layout-centered {
> .rose-layout-page {
width: sizing.rems(l) * 18;
@@ -132,6 +138,20 @@
}
}
.rose-frame {
.rose-form-actions {
.rose-button-primary {
display: block;
margin: 0;
width: 100%;
.rose-button-wrapper {
display: inline;
}
}
}
}
.copyable {
white-space: nowrap;
+1 -1
View File
@@ -113,7 +113,7 @@
</Rose::Dialog>
{{else}}
<Rose::Dialog
@heading={{t "actions.confirm"}}
@heading={{if confirmation.options.title (t confirmation.options.title) (t "actions.confirm")}}
@dismissButtonText={{t "actions.cancel"}}
@icon="alert-triangle"
@style="warning"
@@ -0,0 +1,12 @@
{{page-title (t 'auth.titles.flow-success')}}
<main>
<Rose::Layout::Centered>
<Rose::Notification
@style="success"
@heading={{t "auth.titles.flow-success"}}
>
{{t "auth.descriptions.flow-success"}}
</Rose::Notification>
</Rose::Layout::Centered>
</main>
@@ -0,0 +1,20 @@
{{page-title (t "auth.titles.flow-error")}}
<main>
<Rose::Layout::Centered>
<Rose::Notification
@style="error"
@heading={{t "auth.titles.flow-error"}}
>
{{t "auth.descriptions.flow-error"}}
<br>
<Copyable
@text={{@model}}
@buttonText={{t "actions.copy-error-detail-to-clipboard"}}
@acknowledgeText={{t "states.copied"}}
>
{{t "actions.copy-error-detail-to-clipboard"}}
</Copyable>
</Rose::Notification>
</Rose::Layout::Centered>
</main>
@@ -1,4 +1,22 @@
<Rose::Dropdown @text="{{t 'actions.manage'}}" @dropdownRight={{true}} as |dropdown|>
{{#if (feature-flag 'primary-auth-method')}}
{{#unless @model.isPrimary}}
<dropdown.button
@disabled={{@model.canSave}}
{{on "click" (route-action "makePrimary" @model)}}
>
{{t 'resources.auth-method.actions.make-primary'}}
</dropdown.button>
{{/unless}}
{{#if @model.isPrimary}}
<dropdown.button
@disabled={{@model.canSave}}
{{on "click" (route-action "removeAsPrimary" @model)}}
>
{{t 'resources.auth-method.actions.remove-as-primary'}}
</dropdown.button>
{{/if}}
{{/if}}
<dropdown.link
@route="scopes.scope.auth-methods.auth-method.accounts.new"
@model={{@model}}
@@ -1,5 +1,14 @@
<h2>
{{t "resources.auth-method.title"}}
{{#if (feature-flag 'primary-auth-method')}}
{{#if @model.isPrimary}}
&nbsp;
<Rose::Badge @style="success-outline">
{{t 'states.primary'}}
</Rose::Badge>
&nbsp;
{{/if}}
{{/if}}
<DocLink @doc="auth-method" @iconSize="large" />
</h2>
<p>{{t "resources.auth-method.description"}}</p>
@@ -30,12 +30,27 @@
<page.body>
{{#if @model}}
{{#if (feature-flag 'primary-auth-method')}}
{{#unless this.hasPrimaryAuthMethod}}
<Rose::Notification
@style="warning"
@heading={{t "resources.auth-method.messages.no-primary-auth-method.title"}}
>
{{t "resources.auth-method.messages.no-primary-auth-method.description"}}
</Rose::Notification>
{{/unless}}
{{/if}}
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
<row.headerCell>{{t 'form.type.label'}}</row.headerCell>
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
{{#if (feature-flag 'primary-auth-method')}}
<row.cell></row.cell>
<row.headerCell>{{t 'titles.actions'}}</row.headerCell>
{{/if}}
</header.row>
</table.header>
@@ -43,12 +58,30 @@
{{#each @model as |authMethod|}}
<body.row as |row|>
<row.headerCell>
<LinkTo
@route="scopes.scope.auth-methods.auth-method"
@model={{authMethod.id}}
>
{{authMethod.displayName}}
</LinkTo>
{{!
Show link to auth method route if non-OIDC and when
oidc-crud feature is enabled, otherwise show just the name
(when the method is OIDC and oidc-crud is disabled)
}}
{{#if authMethod.isOIDC}}
{{#if (feature-flag 'oidc-crud')}}
<LinkTo
@route="scopes.scope.auth-methods.auth-method"
@model={{authMethod.id}}
>
{{authMethod.displayName}}
</LinkTo>
{{else}}
{{authMethod.displayName}}
{{/if}}
{{else}}
<LinkTo
@route="scopes.scope.auth-methods.auth-method"
@model={{authMethod.id}}
>
{{authMethod.displayName}}
</LinkTo>
{{/if}}
<p>{{authMethod.description}}</p>
</row.headerCell>
<row.cell>
@@ -65,6 +98,39 @@
<code>{{authMethod.id}}</code>
</Copyable>
</row.cell>
{{#if (feature-flag 'primary-auth-method')}}
<row.cell>
{{#if authMethod.isPrimary}}
<Rose::Badge @style="success-outline">
{{t 'states.primary'}}
</Rose::Badge>
{{/if}}
</row.cell>
<row.cell>
<Rose::Dropdown
@text={{t "actions.manage"}}
@dropdownRight={{true}}
@iconOnly={{true}}
@showCaret={{false}}
@icon="more-horizontal"
as |dropdown|>
{{#unless authMethod.isPrimary}}
<dropdown.button
{{on "click" (route-action "makePrimary" authMethod)}}
>
{{t 'resources.auth-method.actions.make-primary'}}
</dropdown.button>
{{/unless}}
{{#if authMethod.isPrimary}}
<dropdown.button
{{on "click" (route-action "removeAsPrimary" authMethod)}}
>
{{t 'resources.auth-method.actions.remove-as-primary'}}
</dropdown.button>
{{/if}}
</Rose::Dropdown>
</row.cell>
{{/if}}
</body.row>
{{/each}}
</table.body>
@@ -3,58 +3,75 @@
<Rose::Layout::Centered>
<Rose::Layout::Page as |page|>
<page.header>
<h2>{{t "actions.authenticate"}}</h2>
<p>{{t "auth.descriptions.authenticating-with-org" name=@model.scope.displayName}}</p>
</page.header>
<page.navigation>
<Rose::Dropdown
@text={{t "actions.choose-different-org"}}
@icon="user-organization"
as |dropdown|>
<dropdown.link @route="scopes.scope.authenticate" @model="global">
{{t "titles.global"}}
</dropdown.link>
{{#each this.sortedScopes as |scope|}}
<dropdown.link @route="scopes.scope.authenticate" @model={{scope.id}}>
{{scope.displayName}}
</dropdown.link>
{{/each}}
</Rose::Dropdown>
<Rose::Separator />
<Rose::Nav::Tabs as |nav|>
{{#each @model.authMethods as |authMethod|}}
<nav.link @route="scopes.scope.authenticate.method" @model={{authMethod.id}}>
{{#if authMethod.name}}
{{authMethod.name}}
{{else}}
{{t (concat "resources.auth-method.types." authMethod.type)}}
{{/if}}
</nav.link>
{{/each}}
</Rose::Nav::Tabs>
</page.navigation>
<page.body>
{{#if @model.authMethods}}
{{outlet}}
{{/if}}
{{#unless @model.authMethods}}
<Rose::Layout::Centered>
<Rose::Message
@title={{t "resources.auth-method.messages.none.title"}}
@icon="alert-circle-outline"
as |message|>
<message.description>
{{t "resources.auth-method.messages.none.description"}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
{{/unless}}
<Rose::Frame>
<:header>
<h2>{{t "actions.authenticate"}}</h2>
</:header>
<:body>
<p>{{t "auth.descriptions.authenticating-with-org" name=@model.scope.displayName}}</p>
<Rose::Dropdown
@text={{t "actions.choose-different-org"}}
@icon="user-organization"
as |dropdown|>
<dropdown.link @route="scopes.scope.authenticate" @model="global">
{{t "titles.global"}}
</dropdown.link>
{{#each this.sortedScopes as |scope|}}
<dropdown.link @route="scopes.scope.authenticate" @model={{scope.id}}>
{{scope.displayName}}
</dropdown.link>
{{/each}}
</Rose::Dropdown>
<Rose::Separator />
<Rose::Nav::Tabs as |nav|>
{{#if (feature-flag 'oidc')}}
{{! OIDC enable: list all auth methods }}
{{#each @model.authMethods as |authMethod|}}
<nav.link @route="scopes.scope.authenticate.method" @model={{authMethod.id}}>
{{#if authMethod.name}}
{{authMethod.name}}
{{else}}
{{t (concat "resources.auth-method.types." authMethod.type)}}
{{/if}}
</nav.link>
{{/each}}
{{else}}
{{! OIDC disabled: filter out OIDC auth methods}}
{{#each @model.authMethods as |authMethod|}}
{{#unless authMethod.isOIDC}}
<nav.link @route="scopes.scope.authenticate.method" @model={{authMethod.id}}>
{{#if authMethod.name}}
{{authMethod.name}}
{{else}}
{{t (concat "resources.auth-method.types." authMethod.type)}}
{{/if}}
</nav.link>
{{/unless}}
{{/each}}
{{/if}}
</Rose::Nav::Tabs>
{{#if @model.authMethods}}
{{outlet}}
{{/if}}
{{#unless @model.authMethods}}
<Rose::Layout::Centered>
<Rose::Message
@title={{t "resources.auth-method.messages.none.title"}}
@icon="alert-circle-outline"
as |message|>
<message.description>
{{t "resources.auth-method.messages.none.description"}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
{{/unless}}
</:body>
</Rose::Frame>
</page.body>
</Rose::Layout::Page>
@@ -1,5 +1 @@
<Form::Authenticate
@model={{@model}}
@submit={{route-action "authenticate"}}
@disabled={{is-loading}}
/>
{{outlet}}
@@ -0,0 +1,17 @@
{{#if (feature-flag 'oidc')}}
{{! OIDC enabled: show form for any auth method}}
<Form::Authenticate
@model={{@model}}
@submit={{route-action "authenticate"}}
@disabled={{is-loading}}
/>
{{else}}
{{! OIDC disabled: hide OIDC auth form}}
{{#unless @model.isOIDC}}
<Form::Authenticate
@model={{@model}}
@submit={{route-action "authenticate"}}
@disabled={{is-loading}}
/>
{{/unless}}
{{/if}}
@@ -0,0 +1,24 @@
<Rose::Dialog::Cover>
<:header>
{{inline-svg "logo-app-full"}}
<h1>{{t 'resources.auth-method.messages.pending.title'}}</h1>
</:header>
<:body>
<p>{{t 'resources.auth-method.messages.pending.description'}}</p>
<p>
{{t 'resources.auth-method.questions.no-see-window'}}
<a
href="{{this.authURL}}"
target="_blank"
rel="noreferrer noopener"
>
{{t 'actions.retry'}}
</a>
</p>
<p>
<LinkTo @route="scopes.scope.authenticate.method">
{{t 'actions.cancel'}}
</LinkTo>
</p>
</:body>
</Rose::Dialog::Cover>
+16
View File
@@ -36,6 +36,7 @@ module.exports = function (environment) {
notifyTimeout: 4000,
sessionPollingTimeoutSeconds: 2.5,
oidcPollingTimeoutSeconds: 1,
documentation: {
baseURL: 'https://boundaryproject.io/help/admin-ui',
@@ -83,6 +84,16 @@ module.exports = function (environment) {
'ember-cli-mirage': {
directory: '../../addons/api/mirage',
},
featureFlags: {
oidc: true,
'oidc-crud': false,
'primary-auth-method': true,
search: false,
filter: false,
capabilities: false,
'credential-store': false,
},
};
// Unsafe policy is necessary in development and test environments, but should
@@ -115,6 +126,11 @@ module.exports = function (environment) {
// rather than automatically include API_HOST. Changes to CSP should
// be explicit.
if (API_HOST) ENV.contentSecurityPolicy['connect-src'].push(API_HOST);
// Enable features in development
ENV.featureFlags['primary-auth-method'] = true;
ENV.featureFlags['oidc'] = true;
//ENV.featureFlags['oidc-crud'] = true;
}
if (environment === 'test') {
+4 -1
View File
@@ -30,6 +30,9 @@
"test:ember": "COVERAGE=true ember exam --random",
"posttest": "ember coverage-merge && yarn clean:coverage"
},
"dependencies": {
"ember-named-blocks-polyfill": "^0.2.4"
},
"devDependencies": {
"@babel/core": "^7.9.6",
"@ember/optional-features": "^2.0.0",
@@ -77,7 +80,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",
@@ -10,6 +10,7 @@ import {
// //currentSession,
// //invalidateSession,
} from 'ember-simple-auth/test-support';
import { enableFeature } from 'ember-feature-flags/test-support';
module('Acceptance | auth methods', function (hooks) {
setupApplicationTest(hooks);
@@ -270,4 +271,118 @@ module('Acceptance | auth methods', function (hooks) {
'Displays field-level errors.'
);
});
test('user can make primary an auth method', async function (assert) {
assert.expect(2);
assert.notOk(
instances.scopes.org.primaryAuthMethodId,
'Primary auth method is not yet set.'
);
enableFeature('primary-auth-method');
await visit(urls.authMethod);
await click(
'.rose-layout-page-actions .rose-dropdown-content [type="button"]:first-child'
);
const scope = this.server.schema.scopes.find(instances.scopes.org.id);
assert.equal(
scope.primaryAuthMethodId,
instances.authMethod.id,
'Primary auth method is set.'
);
});
test('user is notified of error on make primary an auth method', async function (assert) {
assert.expect(2);
this.server.patch('/scopes/:id', () => {
return new Response(
400,
{},
{
status: 400,
code: 'fail',
message: 'Sorry!',
}
);
});
assert.notOk(
instances.scopes.org.primaryAuthMethodId,
'Primary auth method is not yet set.'
);
enableFeature('primary-auth-method');
await visit(urls.authMethod);
await click(
'.rose-layout-page-actions .rose-dropdown-content [type="button"]:first-child'
);
assert.ok(find('.rose-notification'));
});
test('user can remove as primary an auth method', async function (assert) {
assert.expect(2);
instances.scopes.org.update({
primaryAuthMethodId: instances.authMethod.id,
});
assert.ok(
instances.scopes.org.primaryAuthMethodId,
'Primary auth method is set.'
);
enableFeature('primary-auth-method');
await visit(urls.authMethod);
await click(
'.rose-layout-page-actions .rose-dropdown-content [type="button"]:first-child'
);
const scope = this.server.schema.scopes.find(instances.scopes.org.id);
assert.notOk(scope.primaryAuthMethodId, 'Primary auth method is unset.');
});
test('user is notified of error on remove as primary an auth method', async function (assert) {
assert.expect(2);
this.server.patch('/scopes/:id', () => {
return new Response(
400,
{},
{
status: 400,
code: 'fail',
message: 'Sorry!',
}
);
});
instances.scopes.org.update({
primaryAuthMethodId: instances.authMethod.id,
});
assert.ok(
instances.scopes.org.primaryAuthMethodId,
'Primary auth method is set.'
);
enableFeature('primary-auth-method');
await visit(urls.authMethod);
await click(
'.rose-layout-page-actions .rose-dropdown-content [type="button"]:first-child'
);
assert.ok(find('.rose-notification'));
});
test('user can make and remove primary auth methods from index', async function (assert) {
assert.expect(3);
assert.notOk(
instances.scopes.org.primaryAuthMethodId,
'Primary auth method is not yet set.'
);
enableFeature('primary-auth-method');
await visit(urls.authMethods);
await click(
'.rose-table-body .rose-table-row:first-child .rose-dropdown-content [type="button"]:first-child'
);
let scope = this.server.schema.scopes.find(instances.scopes.org.id);
assert.equal(
scope.primaryAuthMethodId,
instances.authMethod.id,
'Primary auth method is set.'
);
await click(
'.rose-table-body .rose-table-row:first-child .rose-dropdown-content [type="button"]:first-child'
);
scope = this.server.schema.scopes.find(instances.scopes.org.id);
assert.notOk(scope.primaryAuthMethodId, 'Primary auth method is unset.');
});
});
@@ -66,9 +66,12 @@ module('Acceptance | authentication', function (hooks) {
scope = { id: orgScope.id, type: orgScope.type };
globalAuthMethod = this.server.create('auth-method', {
scope: globalScope,
type: 'password',
});
authMethod = this.server.create('auth-method', {
scope: orgScope,
type: 'password',
});
this.server.create('user', { scope: globalScope });
authMethod = this.server.create('auth-method', { scope: orgScope });
orgScopeID = orgScope.id;
globalAuthMethodID = globalAuthMethod.id;
authMethodID = authMethod.id;
@@ -0,0 +1,14 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | scopes/scope/auth-methods/index', function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/auth-methods/index'
);
assert.ok(controller);
});
});
@@ -0,0 +1,17 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Controller | scopes/scope/authenticate/method/oidc',
function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/authenticate/method/oidc'
);
assert.ok(controller);
});
}
);
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | authentication-complete', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup('route:authentication-complete');
assert.ok(route);
});
});
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | authentication-error', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup('route:authentication-error');
assert.ok(route);
});
});
@@ -0,0 +1,16 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Route | scopes/scope/authenticate/method/index',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup(
'route:scopes/scope/authenticate/method/index'
);
assert.ok(route);
});
}
);
@@ -0,0 +1,16 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Route | scopes/scope/authenticate/method/oidc',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup(
'route:scopes/scope/authenticate/method/oidc'
);
assert.ok(route);
});
}
);
Loaded 100 of 141 files, more files were not shown because too many files have changed in this diff. Show more