forked from artirix/ember-simple-auth-auth0
-
Notifications
You must be signed in to change notification settings - Fork 1
/
application-route-mixin.js
196 lines (166 loc) · 5.54 KB
/
application-route-mixin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import Mixin from '@ember/object/mixin';
import { set, getWithDefault, get, computed } from '@ember/object';
import RSVP, { resolve } from 'rsvp';
import { inject as service } from '@ember/service';
import { run } from '@ember/runloop';
import { isEmpty } from '@ember/utils';
import { getOwner } from '@ember/application';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
import { Auth0Error } from '../utils/errors'
import getSessionExpiration from '../utils/get-session-expiration';
import now from '../utils/now';
export default Mixin.create(ApplicationRouteMixin, {
session: service(),
auth0: service(),
inTesting: computed(function() {
let config = getOwner(this).resolveRegistration('config:environment');
return config.environment === 'test';
}),
sessionAuthenticated() {
this._setupFutureEvents();
this._super(...arguments);
},
/**
* Hook that gets called after the jwt has expired
* but before we notify the rest of the system.
* Great place to add cleanup to expire any third-party
* tokens or other cleanup.
*
* IMPORTANT: You must return a promise, else logout
* will not continue.
*
* @return {Promise}
*/
beforeSessionExpired() {
return resolve();
},
/**
* This has to be overridden because the default behavior prevents
* auth0 to logout correctly.
*/
sessionInvalidated() {
this._clearJobs();
return this._super(...arguments);
},
beforeModel() {
this._setupFutureEvents();
let promise = resolve(this._super(...arguments));
promise = promise
.then(this._getUrlHashData.bind(this))
.then(this._authenticateWithUrlHash.bind(this));
return promise;
},
willDestroy() {
this._clearJobs();
},
_authenticateWithUrlHash(urlHashData) {
if (isEmpty(urlHashData) || typeof FastBoot !== 'undefined') {
return;
}
return get(this, 'session').authenticate('authenticator:auth0-url-hash', urlHashData)
.then(this._clearUrlHash.bind(this));
},
_getUrlHashData() {
if (typeof FastBoot !== 'undefined') {
return;
}
const auth0 = get(this, 'auth0')._getAuth0Instance();
const enableImpersonation = get(this, 'auth0.enableImpersonation');
return new RSVP.Promise((resolve, reject) => {
auth0.parseHash({__enableImpersonation: enableImpersonation}, (err, parsedPayload) => {
if (err) {
return reject(new Auth0Error(err));
}
resolve(parsedPayload);
});
});
},
_clearUrlHash() {
if(typeof FastBoot === 'undefined' && !this.get('inTesting') && window.history) {
window.history.pushState('', document.title, window.location.pathname + window.location.search);
}
return RSVP.resolve()
},
_setupFutureEvents() {
// Don't schedule expired events during testing, otherwise acceptance tests will hang.
if (this.get('inTesting') || typeof FastBoot !== 'undefined') {
return;
}
// [XA] only actually schedule events if we're authenticated.
if (get(this, 'session.isAuthenticated')) {
this._scheduleRenew();
this._scheduleExpire();
}
},
_scheduleJob(jobName, jobFn, timeInMilli) {
run.cancel(get(this, jobName));
const job = run.later(this, jobFn, timeInMilli);
set(this, jobName, job);
},
_scheduleRenew() {
const renewInMilli = get(this, 'auth0.silentAuthRenewSeconds') * 1000;
if(renewInMilli) {
this._scheduleJob('_renewJob', this._processSessionRenewed, renewInMilli);
}
},
_scheduleExpire() {
const expireInMilli = get(this, '_jwtRemainingTimeInSeconds') * 1000;
this._scheduleJob('_expireJob', this._processSessionExpired, expireInMilli);
},
/**
* The current JWT's expire time
* @return {Date of expiration}
*/
_expiresAt: computed('session.data.authenticated', {
get() {
if (!get(this, 'session.isAuthenticated')) {
return 0;
}
const sessionData = get(this, 'session.data.authenticated');
return getSessionExpiration(sessionData);
}
}),
/**
* Number of seconds until the JWT expires.
* @return {Number in seconds}
*/
_jwtRemainingTimeInSeconds: computed('_expiresAt', {
get() {
let remaining = getWithDefault(this, '_expiresAt', 0) - now();
return remaining < 0 ? 0 : remaining;
}
}),
_clearJobs() {
run.cancel(get(this, '_renewJob'));
run.cancel(get(this, '_expireJob'));
},
_processSessionRenewed() {
// [XA] need to refactor this a bit. This is kinda bonkers-spaghetti right now.
return this._trySilentAuth()
.then(this._scheduleRenew.bind(this), this._setupFutureEvents.bind(this));
},
_processSessionExpired() {
return this.beforeSessionExpired()
.then(this._trySilentAuth.bind(this))
.then(this._invalidateIfAuthenticated.bind(this), this._scheduleExpire.bind(this)); // reschedule expiration if we re-authenticate.
},
_trySilentAuth() {
const auth0Svc = get(this, 'auth0');
if(get(auth0Svc, 'silentAuthOnSessionExpire')) {
// Try silent auth, but reverse the promise results.
// since a rejecting promise during expiration means
// "don't expire", we want to reject on success and
// resolve on failure. Strange times.
return new RSVP.Promise((resolve, reject) => {
get(this, 'session').authenticate('authenticator:auth0-silent-auth').then(reject, resolve);
});
}
return RSVP.resolve();
},
_invalidateIfAuthenticated() {
let session = get(this, 'session');
if (get(session, 'isAuthenticated')) {
session.invalidate();
}
}
});