open-vault/ui/app/components/auth-jwt.js

166 lines
5.0 KiB
JavaScript
Raw Normal View History

UI - jwt auth (#6188) * fix default rendering of svg and allow plugins access to mount tune form * add auth-jwt component * add callback route, and allow it to be navigated to on load * add jwt as a supported auth method * use auth-jwt component and implement intial oidc flow * allow wrapping un-authed requests * pass redirect_url and properly redirect with the wrapped token * popup for login * center popup window and move to localStorage events for cross window communication because of IE11 * access window via a getter on the auth-form component * show OIDC provider name on the button * fetch default role on render of the auth-jwt component * simplify auth-form template * style callback page * refetch auth_url when path changes for auth-jwt component * fix glimmer error on alias metadata, and add back popup-metadata component * fix link in metadata page * add logo-edition component and remove use of partial for logo svg * render oidc callback template on the loading page if we're going there * add docs icon and change timeout on the auth form * move OIDC auth specific things to auth-jwt component * start to add branded buttons for OIDC providers * add google button * finish branded buttons * update glyph for error messages * update tests for auth screen not showing tabs, add adapter tests and new auth jwt tests * start auth-jwt tests * simplify auth-jwt * remove negative top margin on AlertInline * only preventDefault if there's an event * fill out tests * sort out some naming * feedback on templates and styles * clear error when starting OIDC auth and call for new auth_url * also allow 'oidc' as the auth method type * handle namespaces with OIDC auth * review feedback * use new getters in popup-metadata
2019-02-14 15:39:19 +00:00
import Ember from 'ember';
import { inject as service } from '@ember/service';
import Component from './outer-html';
import { next, later } from '@ember/runloop';
import { task, timeout, waitForEvent } from 'ember-concurrency';
import { computed } from '@ember/object';
const WAIT_TIME = Ember.testing ? 0 : 500;
const ERROR_WINDOW_CLOSED =
'The provider window was closed before authentication was complete. Please click Sign In to try again.';
const ERROR_MISSING_PARAMS =
'The callback from the provider did not supply all of the required parameters. Please click Sign In to try again. If the problem persists, you may want to contact your administrator.';
export { ERROR_WINDOW_CLOSED, ERROR_MISSING_PARAMS };
export default Component.extend({
store: service(),
selectedAuthPath: null,
roleName: null,
role: null,
onRoleName() {},
onLoading() {},
onError() {},
onToken() {},
onNamespace() {},
didReceiveAttrs() {
next(() => {
let { oldSelectedAuthPath, selectedAuthPath } = this;
if (oldSelectedAuthPath !== selectedAuthPath) {
this.set('role', null);
this.onRoleName(null);
this.fetchRole.perform(null, { debounce: false });
}
this.set('oldSelectedAuthPath', selectedAuthPath);
});
},
// OIDC roles in the JWT/OIDC backend are those with an authUrl,
// those that are JWT type will 400 when trying to fetch the role
isOIDC: computed('role', 'role.authUrl', function() {
return this.role && this.role.authUrl;
}),
getWindow() {
return this.window || window;
},
fetchRole: task(function*(roleName, options = { debounce: true }) {
if (options.debounce) {
this.onRoleName(roleName);
// debounce
yield timeout(WAIT_TIME);
}
let path = this.selectedAuthPath || 'jwt';
let id = JSON.stringify([path, roleName]);
let role = null;
try {
role = yield this.store.findRecord('role-jwt', id, { adapterOptions: { namespace: this.namespace } });
} catch (e) {
if (!e.httpStatus || e.httpStatus !== 400) {
throw e;
}
}
this.set('role', role);
}).restartable(),
handleOIDCError(err) {
this.onLoading(false);
this.prepareForOIDC.cancelAll();
this.onError(err);
},
prepareForOIDC: task(function*(oidcWindow) {
// show the loading animation in the parent
this.onLoading(true);
// start watching the popup window and the current one
this.watchPopup.perform(oidcWindow);
this.watchCurrent.perform(oidcWindow);
// and then wait for storage event to be fired from the popup
// window setting a value in localStorage when the callback route is loaded
let storageEvent = yield waitForEvent(this.getWindow(), 'storage');
this.exchangeOIDC.perform(storageEvent, oidcWindow);
}),
watchPopup: task(function*(oidcWindow) {
while (true) {
yield timeout(WAIT_TIME);
if (!oidcWindow || oidcWindow.closed) {
return this.handleOIDCError(ERROR_WINDOW_CLOSED);
}
}
}),
watchCurrent: task(function*(oidcWindow) {
yield waitForEvent(this.getWindow(), 'beforeunload');
oidcWindow.close();
}),
closeWindow(oidcWindow) {
this.watchPopup.cancelAll();
this.watchCurrent.cancelAll();
oidcWindow.close();
},
exchangeOIDC: task(function*(event, oidcWindow) {
if (event.key !== 'oidcState') {
return;
}
this.onLoading(true);
// get the info from the event fired by the other window and
// then remove it from localStorage
let { namespace, path, state, code } = JSON.parse(event.newValue);
this.getWindow().localStorage.removeItem('oidcState');
// defer closing of the window, but continue executing the task
later(() => {
this.closeWindow(oidcWindow);
}, WAIT_TIME);
if (!path || !state || !code) {
return this.handleOIDCError(ERROR_MISSING_PARAMS);
}
let adapter = this.store.adapterFor('auth-method');
this.onNamespace(namespace);
let resp;
// do the OIDC exchange, set the token on the parent component
// and submit auth form
try {
resp = yield adapter.exchangeOIDC(path, state, code);
} catch (e) {
return this.handleOIDCError(e);
}
let token = resp.auth.client_token;
this.onSelectedAuth('token');
this.onToken(token);
yield this.onSubmit();
}),
actions: {
async startOIDCAuth(data, e) {
this.onError(null);
if (e && e.preventDefault) {
e.preventDefault();
}
if (!this.isOIDC) {
return;
}
await this.fetchRole.perform(this.roleName, { debounce: false });
let win = this.getWindow();
const POPUP_WIDTH = 500;
const POPUP_HEIGHT = 600;
let left = win.screen.width / 2 - POPUP_WIDTH / 2;
let top = win.screen.height / 2 - POPUP_HEIGHT / 2;
let oidcWindow = win.open(
this.role.authUrl,
'vaultOIDCWindow',
`width=${POPUP_WIDTH},height=${POPUP_HEIGHT},resizable,scrollbars=yes,top=${top},left=${left}`
UI - jwt auth (#6188) * fix default rendering of svg and allow plugins access to mount tune form * add auth-jwt component * add callback route, and allow it to be navigated to on load * add jwt as a supported auth method * use auth-jwt component and implement intial oidc flow * allow wrapping un-authed requests * pass redirect_url and properly redirect with the wrapped token * popup for login * center popup window and move to localStorage events for cross window communication because of IE11 * access window via a getter on the auth-form component * show OIDC provider name on the button * fetch default role on render of the auth-jwt component * simplify auth-form template * style callback page * refetch auth_url when path changes for auth-jwt component * fix glimmer error on alias metadata, and add back popup-metadata component * fix link in metadata page * add logo-edition component and remove use of partial for logo svg * render oidc callback template on the loading page if we're going there * add docs icon and change timeout on the auth form * move OIDC auth specific things to auth-jwt component * start to add branded buttons for OIDC providers * add google button * finish branded buttons * update glyph for error messages * update tests for auth screen not showing tabs, add adapter tests and new auth jwt tests * start auth-jwt tests * simplify auth-jwt * remove negative top margin on AlertInline * only preventDefault if there's an event * fill out tests * sort out some naming * feedback on templates and styles * clear error when starting OIDC auth and call for new auth_url * also allow 'oidc' as the auth method type * handle namespaces with OIDC auth * review feedback * use new getters in popup-metadata
2019-02-14 15:39:19 +00:00
);
this.prepareForOIDC.perform(oidcWindow);
},
},
});