UI: Forward to `redirect_to` param to when auth'd (#16821)
* Pull route paths out of cluster-route mixin * Add redirect route and point there if authed and desired path is auth * Cleanup test * Use replaceWith instead of transitionTo * Update tests * Fix controller accessed by redirect route * Add changelog * Fix tests
This commit is contained in:
parent
b3e8098685
commit
c6bc8db441
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
ui: redirect_to param forwards from auth route when authenticated
|
||||||
|
```
|
|
@ -0,0 +1,12 @@
|
||||||
|
export const INIT = 'vault.cluster.init';
|
||||||
|
export const UNSEAL = 'vault.cluster.unseal';
|
||||||
|
export const AUTH = 'vault.cluster.auth';
|
||||||
|
export const REDIRECT = 'vault.cluster.redirect';
|
||||||
|
export const CLUSTER = 'vault.cluster';
|
||||||
|
export const CLUSTER_INDEX = 'vault.cluster.index';
|
||||||
|
export const OIDC_CALLBACK = 'vault.cluster.oidc-callback';
|
||||||
|
export const OIDC_PROVIDER = 'vault.cluster.oidc-provider';
|
||||||
|
export const NS_OIDC_PROVIDER = 'vault.cluster.oidc-provider-ns';
|
||||||
|
export const DR_REPLICATION_SECONDARY = 'vault.cluster.replication-dr-promote';
|
||||||
|
export const DR_REPLICATION_SECONDARY_DETAILS = 'vault.cluster.replication-dr-promote.details';
|
||||||
|
export const EXCLUDED_REDIRECT_URLS = ['/vault/logout'];
|
|
@ -1,19 +1,20 @@
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import Mixin from '@ember/object/mixin';
|
import Mixin from '@ember/object/mixin';
|
||||||
import RSVP from 'rsvp';
|
import RSVP from 'rsvp';
|
||||||
const INIT = 'vault.cluster.init';
|
import {
|
||||||
const UNSEAL = 'vault.cluster.unseal';
|
INIT,
|
||||||
const AUTH = 'vault.cluster.auth';
|
UNSEAL,
|
||||||
const CLUSTER = 'vault.cluster';
|
AUTH,
|
||||||
const CLUSTER_INDEX = 'vault.cluster.index';
|
CLUSTER,
|
||||||
const OIDC_CALLBACK = 'vault.cluster.oidc-callback';
|
CLUSTER_INDEX,
|
||||||
const OIDC_PROVIDER = 'vault.cluster.oidc-provider';
|
OIDC_CALLBACK,
|
||||||
const NS_OIDC_PROVIDER = 'vault.cluster.oidc-provider-ns';
|
OIDC_PROVIDER,
|
||||||
const DR_REPLICATION_SECONDARY = 'vault.cluster.replication-dr-promote';
|
NS_OIDC_PROVIDER,
|
||||||
const DR_REPLICATION_SECONDARY_DETAILS = 'vault.cluster.replication-dr-promote.details';
|
DR_REPLICATION_SECONDARY,
|
||||||
const EXCLUDED_REDIRECT_URLS = ['/vault/logout'];
|
DR_REPLICATION_SECONDARY_DETAILS,
|
||||||
|
EXCLUDED_REDIRECT_URLS,
|
||||||
export { INIT, UNSEAL, AUTH, CLUSTER, CLUSTER_INDEX, DR_REPLICATION_SECONDARY };
|
REDIRECT,
|
||||||
|
} from 'vault/lib/route-paths';
|
||||||
|
|
||||||
export default Mixin.create({
|
export default Mixin.create({
|
||||||
auth: service(),
|
auth: service(),
|
||||||
|
@ -96,11 +97,14 @@ export default Mixin.create({
|
||||||
if (
|
if (
|
||||||
(!cluster.needsInit && this.routeName === INIT) ||
|
(!cluster.needsInit && this.routeName === INIT) ||
|
||||||
(!cluster.sealed && this.routeName === UNSEAL) ||
|
(!cluster.sealed && this.routeName === UNSEAL) ||
|
||||||
(!cluster?.dr?.isSecondary && this.routeName === DR_REPLICATION_SECONDARY) ||
|
(!cluster?.dr?.isSecondary && this.routeName === DR_REPLICATION_SECONDARY)
|
||||||
(isAuthed && this.routeName === AUTH)
|
|
||||||
) {
|
) {
|
||||||
return CLUSTER;
|
return CLUSTER;
|
||||||
}
|
}
|
||||||
|
if (isAuthed && this.routeName === AUTH) {
|
||||||
|
// if you're already authed and you wanna go to auth, you probably want to redirect
|
||||||
|
return REDIRECT;
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,7 @@ Router.map(function () {
|
||||||
this.route('oidc-provider', { path: '/identity/oidc/provider/:provider_name/authorize' });
|
this.route('oidc-provider', { path: '/identity/oidc/provider/:provider_name/authorize' });
|
||||||
this.route('oidc-callback', { path: '/auth/*auth_path/oidc/callback' });
|
this.route('oidc-callback', { path: '/auth/*auth_path/oidc/callback' });
|
||||||
this.route('auth');
|
this.route('auth');
|
||||||
|
this.route('redirect');
|
||||||
this.route('init');
|
this.route('init');
|
||||||
this.route('logout');
|
this.route('logout');
|
||||||
this.mount('open-api-explorer', { path: '/api-explorer' });
|
this.mount('open-api-explorer', { path: '/api-explorer' });
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import Route from '@ember/routing/route';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import { AUTH, CLUSTER } from 'vault/lib/route-paths';
|
||||||
|
|
||||||
|
export default class VaultClusterRedirectRoute extends Route {
|
||||||
|
@service auth;
|
||||||
|
@service router;
|
||||||
|
|
||||||
|
beforeModel({ to: { queryParams } }) {
|
||||||
|
let transition;
|
||||||
|
const isAuthed = this.auth.currentToken;
|
||||||
|
// eslint-disable-next-line ember/no-controller-access-in-routes
|
||||||
|
const controller = this.controllerFor('vault');
|
||||||
|
const { redirect_to, ...otherParams } = queryParams;
|
||||||
|
|
||||||
|
if (isAuthed && redirect_to) {
|
||||||
|
// if authenticated and redirect exists, redirect to that place and strip other params
|
||||||
|
transition = this.router.replaceWith(redirect_to);
|
||||||
|
} else if (isAuthed) {
|
||||||
|
// if authed no redirect, go to cluster
|
||||||
|
transition = this.router.replaceWith(CLUSTER, { queryParams: otherParams });
|
||||||
|
} else {
|
||||||
|
// default go to Auth
|
||||||
|
transition = this.router.replaceWith(AUTH, { queryParams: otherParams });
|
||||||
|
}
|
||||||
|
transition.followRedirects().then(() => {
|
||||||
|
controller.set('redirectTo', '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<LogoSplash />
|
|
@ -8,7 +8,8 @@ import {
|
||||||
CLUSTER,
|
CLUSTER,
|
||||||
CLUSTER_INDEX,
|
CLUSTER_INDEX,
|
||||||
DR_REPLICATION_SECONDARY,
|
DR_REPLICATION_SECONDARY,
|
||||||
} from 'vault/mixins/cluster-route';
|
REDIRECT,
|
||||||
|
} from 'vault/lib/route-paths';
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
@ -79,7 +80,7 @@ module('Unit | Mixin | cluster route', function () {
|
||||||
assert.equal(subject.targetRouteName(), CLUSTER, 'forwards when unsealed and navigating to UNSEAL');
|
assert.equal(subject.targetRouteName(), CLUSTER, 'forwards when unsealed and navigating to UNSEAL');
|
||||||
|
|
||||||
subject.routeName = AUTH;
|
subject.routeName = AUTH;
|
||||||
assert.equal(subject.targetRouteName(), CLUSTER, 'forwards when authenticated and navigating to AUTH');
|
assert.equal(subject.targetRouteName(), REDIRECT, 'forwards when authenticated and navigating to AUTH');
|
||||||
|
|
||||||
subject.routeName = DR_REPLICATION_SECONDARY;
|
subject.routeName = DR_REPLICATION_SECONDARY;
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
@ -89,6 +90,28 @@ module('Unit | Mixin | cluster route', function () {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#targetRouteName happy path when not authed forwards to AUTH', function (assert) {
|
||||||
|
let subject = createClusterRoute(
|
||||||
|
{ needsInit: false, sealed: false, dr: { isSecondary: false } },
|
||||||
|
{ hasKeyData: () => false, authToken: () => null }
|
||||||
|
);
|
||||||
|
subject.routeName = INIT;
|
||||||
|
assert.equal(subject.targetRouteName(), AUTH, 'forwards when inited and navigating to INIT');
|
||||||
|
|
||||||
|
subject.routeName = UNSEAL;
|
||||||
|
assert.equal(subject.targetRouteName(), AUTH, 'forwards when unsealed and navigating to UNSEAL');
|
||||||
|
|
||||||
|
subject.routeName = AUTH;
|
||||||
|
assert.equal(subject.targetRouteName(), AUTH, 'forwards when non-authenticated and navigating to AUTH');
|
||||||
|
|
||||||
|
subject.routeName = DR_REPLICATION_SECONDARY;
|
||||||
|
assert.equal(
|
||||||
|
subject.targetRouteName(),
|
||||||
|
AUTH,
|
||||||
|
'forwards when not a DR secondary and navigating to DR_REPLICATION_SECONDARY'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('#transitionToTargetRoute', function (assert) {
|
test('#transitionToTargetRoute', function (assert) {
|
||||||
let redirectRouteURL = '/vault/secrets/secret/create';
|
let redirectRouteURL = '/vault/secrets/secret/create';
|
||||||
let subject = createClusterRoute({ needsInit: false, sealed: false });
|
let subject = createClusterRoute({ needsInit: false, sealed: false });
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupTest } from 'ember-qunit';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
module('Unit | Route | vault/cluster/redirect', function (hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
this.router = this.owner.lookup('service:router');
|
||||||
|
this.originalTransition = this.router.replaceWith;
|
||||||
|
this.router.replaceWith = sinon.stub().returns({
|
||||||
|
followRedirects: function () {
|
||||||
|
return {
|
||||||
|
then: function (callback) {
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
hooks.afterEach(function () {
|
||||||
|
this.router.replaceWith = this.originalTransition;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it calls route', function (assert) {
|
||||||
|
let route = this.owner.lookup('route:vault/cluster/redirect');
|
||||||
|
assert.ok(route);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it redirects to auth when unauthenticated', function (assert) {
|
||||||
|
let route = this.owner.lookup('route:vault/cluster/redirect');
|
||||||
|
const auth = this.owner.lookup('service:auth');
|
||||||
|
const originalToken = auth.currentToken;
|
||||||
|
|
||||||
|
auth.currentToken = null;
|
||||||
|
|
||||||
|
route.beforeModel({ to: { queryParams: { redirect_to: 'vault/cluster/tools', namespace: 'admin' } } });
|
||||||
|
|
||||||
|
assert.true(
|
||||||
|
this.router.replaceWith.calledWithExactly('vault.cluster.auth', {
|
||||||
|
queryParams: { namespace: 'admin' },
|
||||||
|
}),
|
||||||
|
'transitions to auth when not authenticated'
|
||||||
|
);
|
||||||
|
auth.currentToken = originalToken;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it redirects to cluster when authenticated without redirect param', function (assert) {
|
||||||
|
let route = this.owner.lookup('route:vault/cluster/redirect');
|
||||||
|
const auth = this.owner.lookup('service:auth');
|
||||||
|
const originalToken = auth.currentToken;
|
||||||
|
|
||||||
|
auth.currentToken = 's.xxxxxxxxx';
|
||||||
|
|
||||||
|
route.beforeModel({ to: { queryParams: { foo: 'bar' } } });
|
||||||
|
assert.true(
|
||||||
|
this.router.replaceWith.calledWithExactly('vault.cluster', { queryParams: { foo: 'bar' } }),
|
||||||
|
'transitions to cluster when authenticated but no redirect param'
|
||||||
|
);
|
||||||
|
auth.currentToken = originalToken;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it redirects to desired path when authenticated with redirect param', function (assert) {
|
||||||
|
let route = this.owner.lookup('route:vault/cluster/redirect');
|
||||||
|
const auth = this.owner.lookup('service:auth');
|
||||||
|
const originalToken = auth.currentToken;
|
||||||
|
|
||||||
|
auth.currentToken = 's.xxxxxxxxx';
|
||||||
|
|
||||||
|
route.beforeModel({
|
||||||
|
to: {
|
||||||
|
queryParams: { redirect_to: 'vault/cluster/tools?namespace=admin', namespace: 'ns1', foo: 'bar' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.true(
|
||||||
|
this.router.replaceWith.calledWithExactly('vault/cluster/tools?namespace=admin'),
|
||||||
|
'transitions to redirect_to path when authenticated and removes other params'
|
||||||
|
);
|
||||||
|
auth.currentToken = originalToken;
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue