refactor: 💡 only show scopes with auth methods at authenticate (#2129)
* refactor: 💡 only show scopes with auth methods at authenticate ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-2355 * test: 💍 verify scopes with no auth methods are hidden when authenticating ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-2355 * refactor: 💡 update logic for fetching scopes and auth methods ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-2355 * refactor: 💡 PR feedback changes ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-2355 * refactor: 💡 added unless block back to template ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-2355 * refactor: 💡 update admin authenticate route and template ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-2355 * test: 💍 add test for orgs with no auth methods admin ui ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-2355
This commit is contained in:
1 parent
022ac0fffe
commit
d2d332b7d1
6 files changed
+93
-40
No files matched your search
@@ -5,7 +5,6 @@
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
// =services
|
||||
@@ -13,6 +12,7 @@ export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
@service session;
|
||||
@service router;
|
||||
@service resourceFilterStore;
|
||||
|
||||
// =methods
|
||||
|
||||
beforeModel() {
|
||||
@@ -27,16 +27,7 @@ export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
*/
|
||||
async model() {
|
||||
const { id: scope_id } = this.modelFor('scopes.scope');
|
||||
// Fetch auth methods for the current scope
|
||||
const authMethods = await this.resourceFilterStore.queryBy(
|
||||
'auth-method',
|
||||
{
|
||||
authorized_actions: [{ contains: 'authenticate' }],
|
||||
},
|
||||
{
|
||||
scope_id,
|
||||
},
|
||||
);
|
||||
|
||||
// Preload all authenticatable auth methods into the store
|
||||
const authMethodsForAllScopes = await this.resourceFilterStore.queryBy(
|
||||
'auth-method',
|
||||
@@ -48,22 +39,25 @@ export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
recursive: true,
|
||||
},
|
||||
);
|
||||
// Fetch org scopes
|
||||
// and filter out any that have no auth methods
|
||||
const scopes = this.modelFor('scopes').filter(
|
||||
({ id: scope_id, isOrg }) =>
|
||||
isOrg &&
|
||||
authMethodsForAllScopes.filter((i) => i.scopeID === scope_id).length,
|
||||
|
||||
const scopeIDs = new Set(
|
||||
authMethodsForAllScopes.map((authMethod) => authMethod.scopeID),
|
||||
);
|
||||
return hash({
|
||||
|
||||
// Fetch org scopes and filter out any that have no auth methods
|
||||
const scopes = this.modelFor('scopes').filter(({ id: scope_id }) =>
|
||||
scopeIDs.has(scope_id),
|
||||
);
|
||||
|
||||
// Filter out auth methods that are not for the current scope
|
||||
const authMethods = authMethodsForAllScopes.filter(
|
||||
(authMethod) => authMethod.scopeID === scope_id,
|
||||
);
|
||||
|
||||
return {
|
||||
scope: this.modelFor('scopes.scope'),
|
||||
scopes,
|
||||
authMethods,
|
||||
// for integration testing:
|
||||
// authMethods: A([{
|
||||
// id: 'am_1234567890',
|
||||
// type: 'password'
|
||||
// }])
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,19 @@
|
||||
{{#if @model.authMethods}}
|
||||
{{outlet}}
|
||||
{{/if}}
|
||||
{{#unless @model.authMethods}}
|
||||
<Rose::Layout::Centered>
|
||||
<Rose::Message
|
||||
@title={{t 'resources.auth-method.messages.none.title'}}
|
||||
@icon='flight-icons/svg/alert-circle-16'
|
||||
as |message|
|
||||
>
|
||||
<message.description>
|
||||
{{t 'resources.auth-method.messages.none.description'}}
|
||||
</message.description>
|
||||
</Rose::Message>
|
||||
</Rose::Layout::Centered>
|
||||
{{/unless}}
|
||||
</:body>
|
||||
</Rose::Frame>
|
||||
</page.body>
|
||||
|
||||
@@ -76,6 +76,15 @@ module('Acceptance | authentication', function (hooks) {
|
||||
},
|
||||
'withChildren',
|
||||
);
|
||||
// create an emtpy org with no auth methods
|
||||
this.server.create(
|
||||
'scope',
|
||||
{
|
||||
type: 'org',
|
||||
scope: { id: globalScope.id, type: globalScope.type },
|
||||
},
|
||||
'withChildren',
|
||||
);
|
||||
scope = { id: orgScope.id, type: orgScope.type };
|
||||
globalAuthMethod = this.server.create('auth-method', {
|
||||
scope: globalScope,
|
||||
@@ -366,4 +375,12 @@ module('Acceptance | authentication', function (hooks) {
|
||||
});
|
||||
|
||||
// TODO: test OIDC retry and cancel
|
||||
|
||||
test('org scopes with no auth methods are not visible in dropdown', async function (assert) {
|
||||
await visit(authMethodGlobalAuthenticateURL);
|
||||
|
||||
await click('.rose-dropdown-trigger');
|
||||
|
||||
assert.dom('.rose-dropdown-content a').exists({ count: 2 });
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
// =services
|
||||
@@ -14,6 +13,7 @@ export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
@service session;
|
||||
@service clusterUrl;
|
||||
@service router;
|
||||
@service resourceFilterStore;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -28,14 +28,40 @@ export default class ScopesScopeAuthenticateRoute extends Route {
|
||||
* scope and all scopes (for org navigation).
|
||||
* @return {Promise} `{scope, scopes, authMethods}`
|
||||
*/
|
||||
model() {
|
||||
async model() {
|
||||
const { id: scope_id } = this.modelFor('scopes.scope');
|
||||
|
||||
return hash({
|
||||
// Preload all authenticatable auth methods into the store
|
||||
const authMethodsForAllScopes = await this.resourceFilterStore.queryBy(
|
||||
'auth-method',
|
||||
{
|
||||
authorized_actions: [{ contains: 'authenticate' }],
|
||||
},
|
||||
{
|
||||
scope_id: 'global',
|
||||
recursive: true,
|
||||
},
|
||||
);
|
||||
|
||||
const scopeIDs = new Set(
|
||||
authMethodsForAllScopes.map((authMethod) => authMethod.scopeID),
|
||||
);
|
||||
|
||||
// Fetch org scopes and filter out any that have no auth methods
|
||||
const scopes = this.modelFor('scopes').filter(({ id: scope_id }) =>
|
||||
scopeIDs.has(scope_id),
|
||||
);
|
||||
|
||||
// Filter out auth methods that are not for the current scope
|
||||
const authMethods = authMethodsForAllScopes.filter(
|
||||
(authMethod) => authMethod.scopeID === scope_id,
|
||||
);
|
||||
|
||||
return {
|
||||
scope: this.modelFor('scopes.scope'),
|
||||
scopes: this.modelFor('scopes').filter((scope) => scope.isOrg),
|
||||
authMethods: this.store.query('auth-method', { scope_id }),
|
||||
});
|
||||
scopes,
|
||||
authMethods,
|
||||
};
|
||||
}
|
||||
|
||||
redirect() {
|
||||
|
||||
@@ -70,6 +70,5 @@
|
||||
</Rose::Message>
|
||||
</Rose::Layout::Centered>
|
||||
{{/unless}}
|
||||
|
||||
</BrandedCard>
|
||||
</main>
|
||||
@@ -31,6 +31,7 @@ module('Acceptance | authentication', function (hooks) {
|
||||
scopes: {
|
||||
global: null,
|
||||
org: null,
|
||||
emptyOrg: null,
|
||||
project: null,
|
||||
},
|
||||
authMethods: {
|
||||
@@ -92,6 +93,10 @@ module('Acceptance | authentication', function (hooks) {
|
||||
stubs.project = { id: instances.scopes.project.id, type: 'project' };
|
||||
|
||||
// create other resources
|
||||
instances.scopes.emptyOrg = this.server.create('scope', {
|
||||
type: 'org',
|
||||
scope: stubs.global,
|
||||
});
|
||||
instances.authMethods.global = this.server.create('auth-method', {
|
||||
scope: instances.scopes.global,
|
||||
type: 'password',
|
||||
@@ -138,15 +143,6 @@ module('Acceptance | authentication', function (hooks) {
|
||||
assert.strictEqual(currentURL(), urls.authenticate.methods.global);
|
||||
});
|
||||
|
||||
test('visiting authenticate route when there no methods shows a message', async function (assert) {
|
||||
assert.expect(2);
|
||||
instances.authMethods.global.destroy();
|
||||
await visit(urls.authenticate.global);
|
||||
await a11yAudit();
|
||||
assert.strictEqual(currentURL(), urls.authenticate.global);
|
||||
assert.ok(find('.rose-message'));
|
||||
});
|
||||
|
||||
test('visiting authenticate route without clusterUrl redirects to clusterUrl index', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.owner.lookup('service:clusterUrl').rendererClusterUrl = null;
|
||||
@@ -255,4 +251,12 @@ module('Acceptance | authentication', function (hooks) {
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-light'));
|
||||
assert.notOk(getRootElement().classList.contains('rose-theme-dark'));
|
||||
});
|
||||
|
||||
test('org scopes with no auth methods are not visible in dropdown', async function (assert) {
|
||||
await visit(urls.authenticate.methods.global);
|
||||
|
||||
await click('.rose-dropdown-trigger');
|
||||
|
||||
assert.dom('.rose-dropdown-content a').exists({ count: 2 });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user