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 Mixin from '@ember/object/mixin';
|
||||
import RSVP from 'rsvp';
|
||||
const INIT = 'vault.cluster.init';
|
||||
const UNSEAL = 'vault.cluster.unseal';
|
||||
const AUTH = 'vault.cluster.auth';
|
||||
const CLUSTER = 'vault.cluster';
|
||||
const CLUSTER_INDEX = 'vault.cluster.index';
|
||||
const OIDC_CALLBACK = 'vault.cluster.oidc-callback';
|
||||
const OIDC_PROVIDER = 'vault.cluster.oidc-provider';
|
||||
const NS_OIDC_PROVIDER = 'vault.cluster.oidc-provider-ns';
|
||||
const DR_REPLICATION_SECONDARY = 'vault.cluster.replication-dr-promote';
|
||||
const DR_REPLICATION_SECONDARY_DETAILS = 'vault.cluster.replication-dr-promote.details';
|
||||
const EXCLUDED_REDIRECT_URLS = ['/vault/logout'];
|
||||
|
||||
export { INIT, UNSEAL, AUTH, CLUSTER, CLUSTER_INDEX, DR_REPLICATION_SECONDARY };
|
||||
import {
|
||||
INIT,
|
||||
UNSEAL,
|
||||
AUTH,
|
||||
CLUSTER,
|
||||
CLUSTER_INDEX,
|
||||
OIDC_CALLBACK,
|
||||
OIDC_PROVIDER,
|
||||
NS_OIDC_PROVIDER,
|
||||
DR_REPLICATION_SECONDARY,
|
||||
DR_REPLICATION_SECONDARY_DETAILS,
|
||||
EXCLUDED_REDIRECT_URLS,
|
||||
REDIRECT,
|
||||
} from 'vault/lib/route-paths';
|
||||
|
||||
export default Mixin.create({
|
||||
auth: service(),
|
||||
|
@ -96,11 +97,14 @@ export default Mixin.create({
|
|||
if (
|
||||
(!cluster.needsInit && this.routeName === INIT) ||
|
||||
(!cluster.sealed && this.routeName === UNSEAL) ||
|
||||
(!cluster?.dr?.isSecondary && this.routeName === DR_REPLICATION_SECONDARY) ||
|
||||
(isAuthed && this.routeName === AUTH)
|
||||
(!cluster?.dr?.isSecondary && this.routeName === DR_REPLICATION_SECONDARY)
|
||||
) {
|
||||
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;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ Router.map(function () {
|
|||
this.route('oidc-provider', { path: '/identity/oidc/provider/:provider_name/authorize' });
|
||||
this.route('oidc-callback', { path: '/auth/*auth_path/oidc/callback' });
|
||||
this.route('auth');
|
||||
this.route('redirect');
|
||||
this.route('init');
|
||||
this.route('logout');
|
||||
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_INDEX,
|
||||
DR_REPLICATION_SECONDARY,
|
||||
} from 'vault/mixins/cluster-route';
|
||||
REDIRECT,
|
||||
} from 'vault/lib/route-paths';
|
||||
import { module, test } from 'qunit';
|
||||
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');
|
||||
|
||||
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;
|
||||
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) {
|
||||
let redirectRouteURL = '/vault/secrets/secret/create';
|
||||
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