Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI - token expiration calculation #5435

Merged
merged 5 commits into from
Oct 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't set this anymore now? Do we just calculate the expiration on the fly every time now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aha!

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