Skip to content

Commit

Permalink
UI - token expiration calculation (#5435)
Browse files Browse the repository at this point in the history
* fix token expiration calculation

* move authenticate to an ember concurrency task

* don't show logged in nav while still on the auth route

* move current tests to integration folder, add unit test for expiration calculation

* fix auth form tests
  • Loading branch information
meirish authored Oct 2, 2018
1 parent db3b646 commit 83d4cef
Show file tree
Hide file tree
Showing 7 changed files with 426 additions and 374 deletions.
44 changes: 24 additions & 20 deletions ui/app/components/auth-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export default Component.extend(DEFAULTS, {
}
}),

showLoading: or('fetchMethods.isRunning', 'unwrapToken.isRunning'),
showLoading: or('authenticate.isRunning', 'fetchMethods.isRunning', 'unwrapToken.isRunning'),

handleError(e) {
this.set('loading', false);
Expand All @@ -165,14 +165,34 @@ export default Component.extend(DEFAULTS, {
this.set('error', `Authentication failed: ${errors.join('.')}`);
},

authenticate: task(function*(backendType, data) {
let clusterId = this.cluster.id;
let targetRoute = this.redirectTo || 'vault.cluster';
try {
let authResponse = yield this.auth.authenticate({ clusterId, backend: backendType, data });

let { isRoot, namespace } = authResponse;
let transition = this.router.transitionTo(targetRoute, { queryParams: { namespace } });
// returning this w/then because if we keep it
// in the task, it will get cancelled when the component in un-rendered
return transition.followRedirects().then(() => {
if (isRoot) {
this.flashMessages.warning(
'You have logged in with a root token. As a security precaution, this root token will not be stored by your browser and you will need to re-authenticate after the window is closed or refreshed.'
);
}
});
} catch (e) {
this.handleError(e);
}
}),

actions: {
doSubmit() {
let data = {};
this.setProperties({
loading: true,
error: null,
});
let targetRoute = this.get('redirectTo') || 'vault.cluster';
let backend = this.get('selectedAuthBackend') || {};
let backendMeta = BACKENDS.find(
b => (get(b, 'type') || '').toLowerCase() === (get(backend, 'type') || '').toLowerCase()
Expand All @@ -183,23 +203,7 @@ export default Component.extend(DEFAULTS, {
if (this.get('customPath') || get(backend, 'id')) {
data.path = this.get('customPath') || get(backend, 'id');
}
const clusterId = this.get('cluster.id');
this.get('auth')
.authenticate({ clusterId, backend: get(backend, 'type'), data })
.then(
({ isRoot, namespace }) => {
this.set('loading', false);
const transition = this.get('router').transitionTo(targetRoute, { queryParams: { namespace } });
if (isRoot) {
transition.followRedirects().then(() => {
this.get('flashMessages').warning(
'You have logged in with a root token. As a security precaution, this root token will not be stored by your browser and you will need to re-authenticate after the window is closed or refreshed.'
);
});
}
},
(...errArgs) => this.handleError(...errArgs)
);
this.authenticate.perform(backend.type, data);
},
},
});
8 changes: 7 additions & 1 deletion ui/app/controllers/vault/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default Controller.extend({
auth: service(),
store: service(),
media: service(),
router: service(),
namespaceService: service('namespace'),

vaultVersion: service('version'),
Expand Down Expand Up @@ -38,6 +39,7 @@ export default Controller.extend({
}),

showNav: computed(
'router.currentRouteName',
'activeClusterName',
'auth.currentToken',
'activeCluster.{dr.isSecondary,needsInit,sealed}',
Expand All @@ -49,7 +51,11 @@ export default Controller.extend({
) {
return false;
}
if (this.get('activeClusterName') && this.get('auth.currentToken')) {
if (
this.activeClusterName &&
this.auth.currentToken &&
this.router.currentRouteName !== 'vault.cluster.auth'
) {
return true;
}
}
Expand Down
39 changes: 22 additions & 17 deletions ui/app/services/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export default Service.extend({
return ENV.environment;
},

now() {
return Date.now();
},

setCluster(clusterId) {
this.set('activeCluster', clusterId);
},
Expand Down Expand Up @@ -95,18 +99,15 @@ export default Service.extend({
return this.ajax(url, 'POST', { namespace });
},

calculateExpiration(resp, creationTime) {
const creationTTL = resp.creation_ttl || resp.lease_duration;
const leaseMilli = creationTTL ? creationTTL * 1e3 : null;
const tokenIssueEpoch = resp.creation_time ? resp.creation_time * 1e3 : creationTime || Date.now();
const tokenExpirationEpoch = tokenIssueEpoch + leaseMilli;
const expirationData = {
tokenIssueEpoch,
calculateExpiration(resp) {
let now = this.now();
const ttl = resp.ttl || resp.lease_duration;
const tokenExpirationEpoch = now + ttl * 1e3;
this.set('expirationCalcTS', now);
return {
ttl,
tokenExpirationEpoch,
leaseMilli,
};
this.set('expirationCalcTS', Date.now());
return expirationData;
},

persistAuthData() {
Expand Down Expand Up @@ -210,17 +211,19 @@ export default Service.extend({

tokenExpired: computed(function() {
const expiration = this.get('tokenExpirationDate');
return expiration ? Date.now() >= expiration : null;
return expiration ? this.now() >= expiration : null;
}).volatile(),

renewAfterEpoch: computed('currentTokenName', 'expirationCalcTS', function() {
const tokenName = this.get('currentTokenName');
let { expirationCalcTS } = this;
const data = this.getTokenData(tokenName);
if (!tokenName || !data) {
return null;
}
const { leaseMilli, tokenIssueEpoch, renewable } = data;
return data && renewable ? Math.floor(leaseMilli / 2) + tokenIssueEpoch : null;
const { ttl, renewable } = data;
// renew after last expirationCalc time + half of the ttl (in ms)
return renewable ? Math.floor((ttl * 1e3) / 2) + expirationCalcTS : null;
}),

renew() {
Expand All @@ -243,7 +246,7 @@ export default Service.extend({
},

shouldRenew: computed(function() {
const now = Date.now();
const now = this.now();
const lastFetch = this.get('lastFetch');
const renewTime = this.get('renewAfterEpoch');
if (this.get('tokenExpired') || this.get('allowExpiration') || !renewTime) {
Expand All @@ -264,9 +267,11 @@ export default Service.extend({
},

getTokensFromStorage(filterFn) {
return this.storage().keys().reject(key => {
return key.indexOf(TOKEN_PREFIX) !== 0 || (filterFn && filterFn(key));
});
return this.storage()
.keys()
.reject(key => {
return key.indexOf(TOKEN_PREFIX) !== 0 || (filterFn && filterFn(key));
});
},

checkForRootToken() {
Expand Down
2 changes: 1 addition & 1 deletion ui/app/templates/components/auth-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
{{/unless}}
</div>
<div class="box is-marginless is-shadowless">
<button data-test-auth-submit=true type="submit" disabled={{loading}} class="button is-primary {{if loading 'is-loading'}}" id="auth-submit">
<button data-test-auth-submit=true type="submit" disabled={{authenticate.isRunning}} class="button is-primary {{if authenticate.isRunning 'is-loading'}}" id="auth-submit">
Sign In
</button>
</div>
Expand Down
10 changes: 9 additions & 1 deletion ui/tests/integration/components/auth-form-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ const workingAuthService = Service.extend({

const routerService = Service.extend({
transitionTo() {
return resolve();
return {
followRedirects() {
return resolve();
},
};
},
replaceWith() {
return resolve();
Expand Down Expand Up @@ -142,6 +146,7 @@ module('Integration | Component | auth form', function(hooks) {
});
});

this.set('cluster', EmberObject.create({}));
await render(hbs`{{auth-form cluster=cluster }}`);
await settled();
assert.equal(component.tabs.length, 2, 'renders a tab for userpass and Other');
Expand All @@ -165,6 +170,7 @@ module('Integration | Component | auth form', function(hooks) {
});
});

this.set('cluster', EmberObject.create({}));
this.set('selectedAuth', 'foo/');
await render(hbs`{{auth-form cluster=cluster selectedAuth=selectedAuth}}`);
await component.login();
Expand All @@ -188,6 +194,7 @@ module('Integration | Component | auth form', function(hooks) {
return [200, { 'Content-Type': 'application/json' }, JSON.stringify({ data: { auth: methods } })];
});
});
this.set('cluster', EmberObject.create({}));
await render(hbs`{{auth-form cluster=cluster}}`);
await settled();
server.shutdown();
Expand All @@ -214,6 +221,7 @@ module('Integration | Component | auth form', function(hooks) {

let wrappedToken = '54321';
this.set('wrappedToken', wrappedToken);
this.set('cluster', EmberObject.create({}));
await render(hbs`{{auth-form cluster=cluster wrappedToken=wrappedToken}}`);
later(() => run.cancelTimers(), 50);
await settled();
Expand Down
Loading

0 comments on commit 83d4cef

Please sign in to comment.