Merge pull request #506 from hashicorp/feature-primary-auth-method
Feature primary auth method
This commit is contained in:
25 files changed
+1641
-931
No files matched your search
@@ -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,
|
||||
})
|
||||
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',
|
||||
})
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
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('primary', true) isPrimary;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import ApplicationSerializer from './application';
|
||||
|
||||
export default class AuthMethodSerializer extends ApplicationSerializer {
|
||||
/**
|
||||
* Sorts the array in order of "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.primary ? -1 : 1
|
||||
);
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/auth-method';
|
||||
@@ -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(204);
|
||||
});
|
||||
|
||||
// Auth Method Accounts
|
||||
this.get(
|
||||
|
||||
@@ -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,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.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, primary: true }, { id: 3 }],
|
||||
};
|
||||
const normalizedArray = serializer.normalizeArrayResponse(
|
||||
store,
|
||||
modelClass,
|
||||
payload
|
||||
);
|
||||
assert.ok(payload.items[1].primary, 'Second payload item is primary');
|
||||
assert.deepEqual(
|
||||
normalizedArray,
|
||||
{
|
||||
included: [],
|
||||
data: [
|
||||
{
|
||||
id: '2',
|
||||
type: 'auth-method',
|
||||
attributes: { primary: true },
|
||||
relationships: {},
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
type: 'auth-method',
|
||||
attributes: {},
|
||||
relationships: {},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'auth-method',
|
||||
attributes: {},
|
||||
relationships: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
'First normalized item is primary'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -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,24 @@ 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.
|
||||
types:
|
||||
password: Password
|
||||
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.
|
||||
group:
|
||||
title: Group
|
||||
title_plural: Groups
|
||||
|
||||
@@ -7,3 +7,4 @@ copied: Copied
|
||||
connection:
|
||||
success:
|
||||
description: "Local proxy address ({protocol}): {address}:{port}"
|
||||
primary: Primary
|
||||
@@ -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,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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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}}
|
||||
|
||||
<Rose::Badge @style="success-outline">
|
||||
{{t 'states.primary'}}
|
||||
</Rose::Badge>
|
||||
|
||||
{{/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>
|
||||
|
||||
@@ -65,6 +80,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>
|
||||
|
||||
@@ -83,6 +83,10 @@ module.exports = function (environment) {
|
||||
'ember-cli-mirage': {
|
||||
directory: '../../addons/api/mirage',
|
||||
},
|
||||
|
||||
featureFlags: {
|
||||
'primary-auth-method': false,
|
||||
},
|
||||
};
|
||||
|
||||
// Unsafe policy is necessary in development and test environments, but should
|
||||
@@ -115,6 +119,9 @@ 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;
|
||||
}
|
||||
|
||||
if (environment === 'test') {
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"ember-concurrency": "^1.3.0",
|
||||
"ember-exam": "^6.0.1",
|
||||
"ember-export-application-global": "^2.0.1",
|
||||
"ember-feature-flags": "^6.0.0",
|
||||
"ember-inline-svg": "^1.0.0",
|
||||
"ember-load-initializers": "^2.1.2",
|
||||
"ember-maybe-import-regenerator": "^0.1.6",
|
||||
|
||||
@@ -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.');
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user