* fix: 🐛 properly show username for oidc auth method in user menu * test: 💍 fix admin ui tests * test: 💍 fix desktop ui tests * refactor: 💡 admin to not store username in authenticated object * refactor: 💡 use getter in app controller * test: 💍 POC for how we could update our tests to use auth account * refactor: 💡 remove unused service * test: 💍 fix all tests in ui/admin * test: 💍 revert one change * refactor: 💡 update ui/desktop w/ feedback + fix tests * refactor: 💡 add in error handling * refactor: 💡 cleanup unused service * test: 💍 fix e2e test selector ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17231
137 lines
4.4 KiB
JavaScript
137 lines
4.4 KiB
JavaScript
/**
|
|
* Copyright (c) HashiCorp, Inc.
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
import SimpleAuthBaseAuthenticator from 'ember-simple-auth/authenticators/base';
|
|
import { resolve, reject } from 'rsvp';
|
|
import { waitForPromise } from '@ember/test-waiters';
|
|
|
|
/**
|
|
* 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
|
|
* @param {object} options
|
|
* @param {string} scopeID
|
|
* @return {string}
|
|
*/
|
|
buildDeauthEndpointURL(/* { scope: { id: scopeID } } */) {}
|
|
|
|
/**
|
|
* Generates an auth token validation URL used to check tokens on restoration.
|
|
* @override
|
|
* @param {string} tokenID
|
|
* @return {string}
|
|
*/
|
|
buildTokenValidationEndpointURL(/* tokenID */) {}
|
|
|
|
// =methods
|
|
|
|
/**
|
|
* Checks that the given token is valid and resolves, otherwise rejects.
|
|
*/
|
|
async validateToken(token, tokenID) {
|
|
const tokenValidationURL = this.buildTokenValidationEndpointURL(tokenID);
|
|
|
|
// Note: waitForPromise is needed to provide the necessary integration with @ember/test-helpers
|
|
// visit https://www.npmjs.com/package/@ember/test-waiters for more info.
|
|
const response = await waitForPromise(
|
|
fetch(tokenValidationURL, {
|
|
method: 'get',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
}),
|
|
);
|
|
// Note: Always consume response body in order to avoid memory leaks
|
|
// visit https://undici.nodejs.org/#/?id=garbage-collection for more info.
|
|
// We do not use the undici package but the link informs us that garbage
|
|
// collection is undefined when response body is not consumed.
|
|
await response.json();
|
|
|
|
// 401 and 404 responses mean the token is invalid, whereas other types of
|
|
// error responses do not tell us about the validity of the token.
|
|
if (response.status === 401 || response.status === 404) return reject();
|
|
return resolve();
|
|
}
|
|
|
|
/**
|
|
* Restores the session if data is present and token validation succeeds
|
|
* (any response other than 401 or 404 === success). Otherwise rejects.
|
|
* @override
|
|
* @param {object} data
|
|
* @return {Promise}
|
|
*/
|
|
async restore(data) {
|
|
if (!data) return reject();
|
|
return this.validateToken(data.attributes.token, data.attributes.id).then(
|
|
() => this.normalizeData(data),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Normalizes the auth data. Adds convenience booleans depending on the
|
|
* scope within which the session is authenticated: isGlobal, isOrg.
|
|
* If a `username` is provided, appends this to the data.
|
|
* @param {object} data
|
|
* @param {string} username
|
|
* @return {object}
|
|
*/
|
|
normalizeData(data) {
|
|
// 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';
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Posts to the `deauthEndpoint` on a best-effort basis and then returns.
|
|
* Deauthentication with the server is not guaranteed and request failures
|
|
* are ignored.
|
|
*
|
|
* @override
|
|
* @return {Promise}
|
|
*/
|
|
async invalidate(options) {
|
|
const { token } = options;
|
|
const deauthEndpointURL = this.buildDeauthEndpointURL(options);
|
|
await waitForPromise(
|
|
fetch(deauthEndpointURL, {
|
|
method: 'delete',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
}).catch(() => {
|
|
/* no op */
|
|
}),
|
|
);
|
|
return super.invalidate(...arguments);
|
|
}
|
|
}
|