aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores/UserStore.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/stores/UserStore.js')
-rw-r--r--src/stores/UserStore.js272
1 files changed, 272 insertions, 0 deletions
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
new file mode 100644
index 000000000..4927d615f
--- /dev/null
+++ b/src/stores/UserStore.js
@@ -0,0 +1,272 @@
1import { observable, computed, action } from 'mobx';
2import moment from 'moment';
3import jwt from 'jsonwebtoken';
4
5import Store from './lib/Store';
6import Request from './lib/Request';
7import CachedRequest from './lib/CachedRequest';
8import { gaEvent } from '../lib/analytics';
9
10// TODO: split stores into UserStore and AuthStore
11export default class UserStore extends Store {
12 BASE_ROUTE = '/auth';
13 WELCOME_ROUTE = `${this.BASE_ROUTE}/welcome`;
14 LOGIN_ROUTE = `${this.BASE_ROUTE}/login`;
15 LOGOUT_ROUTE = `${this.BASE_ROUTE}/logout`;
16 SIGNUP_ROUTE = `${this.BASE_ROUTE}/signup`;
17 PRICING_ROUTE = `${this.BASE_ROUTE}/signup/pricing`;
18 IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`;
19 INVITE_ROUTE = `${this.BASE_ROUTE}/signup/invite`;
20 PASSWORD_ROUTE = `${this.BASE_ROUTE}/password`;
21
22 @observable loginRequest = new Request(this.api.user, 'login');
23 @observable signupRequest = new Request(this.api.user, 'signup');
24 @observable passwordRequest = new Request(this.api.user, 'password');
25 @observable inviteRequest = new Request(this.api.user, 'invite');
26 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo');
27 @observable updateUserInfoRequest = new Request(this.api.user, 'updateInfo');
28 @observable getLegacyServicesRequest = new CachedRequest(this.api.user, 'getLegacyServices');
29
30 @observable isImportLegacyServicesExecuting = false;
31 @observable isImportLegacyServicesCompleted = false;
32
33 @observable id;
34 @observable authToken = localStorage.getItem('authToken') || null;
35 @observable accountType;
36
37 @observable hasCompletedSignup = null;
38
39 @observable userData = {};
40
41 @observable actionStatus = [];
42
43 logoutReasonTypes = {
44 SERVER: 'SERVER',
45 };
46 @observable logoutReason = null;
47
48 constructor(...args) {
49 super(...args);
50
51 // Register action handlers
52 this.actions.user.login.listen(this._login.bind(this));
53 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this));
54 this.actions.user.logout.listen(this._logout.bind(this));
55 this.actions.user.signup.listen(this._signup.bind(this));
56 this.actions.user.invite.listen(this._invite.bind(this));
57 this.actions.user.update.listen(this._update.bind(this));
58 this.actions.user.resetStatus.listen(this._resetStatus.bind(this));
59 this.actions.user.importLegacyServices.listen(this._importLegacyServices.bind(this));
60
61 // Reactions
62 this.registerReactions([
63 this._requireAuthenticatedUser,
64 this._getUserData.bind(this),
65 ]);
66 }
67
68 // Routes
69 get loginRoute() {
70 return this.LOGIN_ROUTE;
71 }
72
73 get logoutRoute() {
74 return this.LOGOUT_ROUTE;
75 }
76
77 get signupRoute() {
78 return this.SIGNUP_ROUTE;
79 }
80
81 get pricingRoute() {
82 return this.PRICING_ROUTE;
83 }
84
85 get inviteRoute() {
86 return this.INVITE_ROUTE;
87 }
88
89 get importRoute() {
90 return this.IMPORT_ROUTE;
91 }
92
93 get passwordRoute() {
94 return this.PASSWORD_ROUTE;
95 }
96
97 // Data
98 @computed get isLoggedIn() {
99 return this.authToken !== null && this.authToken !== undefined;
100 }
101
102 // @computed get isTokenValid() {
103 // return this.authToken !== null && moment(this.tokenExpiry).isAfter(moment());
104 // }
105
106 @computed get isTokenExpired() {
107 if (!this.authToken) return false;
108
109 const { tokenExpiry } = this._parseToken(this.authToken);
110 return this.authToken !== null && moment(tokenExpiry).isBefore(moment());
111 }
112
113 @computed get data() {
114 this.getUserInfoRequest.execute();
115 return this.getUserInfoRequest.result || {};
116 }
117
118 @computed get legacyServices() {
119 this.getLegacyServicesRequest.execute();
120 return this.getLegacyServicesRequest.result || [];
121 }
122
123 // Actions
124 @action async _login({ email, password }) {
125 const authToken = await this.loginRequest.execute(email, password)._promise;
126 this._setUserData(authToken);
127
128 this.stores.router.push('/');
129
130 gaEvent('User', 'login');
131 }
132
133 @action async _signup({ firstname, lastname, email, password, accountType, company }) {
134 const authToken = await this.signupRequest.execute({
135 firstname,
136 lastname,
137 email,
138 password,
139 accountType,
140 company,
141 });
142
143 this.hasCompletedSignup = false;
144
145 this._setUserData(authToken);
146
147 this.stores.router.push(this.PRICING_ROUTE);
148
149 gaEvent('User', 'signup');
150 }
151
152 @action async _retrievePassword({ email }) {
153 const request = this.passwordRequest.execute(email);
154
155 await request._promise;
156 this.actionStatus = request.result.status || [];
157
158 gaEvent('User', 'retrievePassword');
159 }
160
161 @action _invite({ invites }) {
162 const data = invites.filter(invite => invite.email !== '');
163
164 this.inviteRequest.execute(data);
165
166 // we do not wait for a server response before redirecting the user
167 this.stores.router.push('/');
168
169 gaEvent('User', 'inviteUsers');
170 }
171
172 @action async _update({ userData }) {
173 const response = await this.updateUserInfoRequest.execute(userData)._promise;
174
175 this.getUserInfoRequest.patch(() => response.data);
176 this.actionStatus = response.status || [];
177
178 gaEvent('User', 'update');
179 }
180
181 @action _resetStatus() {
182 this.actionStatus = [];
183 }
184
185 @action _logout() {
186 localStorage.removeItem('authToken');
187 this.getUserInfoRequest.invalidate().reset();
188 this.authToken = null;
189 // this.data = {};
190 }
191
192 @action async _importLegacyServices({ services }) {
193 this.isImportLegacyServicesExecuting = true;
194
195 for (const service of services) {
196 this.actions.service.createFromLegacyService({
197 data: service,
198 });
199 await this.stores.services.createServiceRequest._promise; // eslint-disable-line
200 }
201
202 this.isImportLegacyServicesExecuting = false;
203 this.isImportLegacyServicesCompleted = true;
204 }
205
206 // This is a mobx autorun which forces the user to login if not authenticated
207 _requireAuthenticatedUser = () => {
208 if (this.isTokenExpired) {
209 this._logout();
210 }
211
212 const { router } = this.stores;
213 const currentRoute = router.location.pathname;
214 if (!this.isLoggedIn
215 && !currentRoute.includes(this.BASE_ROUTE)) {
216 router.push(this.WELCOME_ROUTE);
217 } else if (this.isLoggedIn
218 && currentRoute === this.LOGOUT_ROUTE) {
219 this.actions.user.logout();
220 router.push(this.LOGIN_ROUTE);
221 } else if (this.isLoggedIn
222 && currentRoute.includes(this.BASE_ROUTE)
223 && (this.hasCompletedSignup
224 || this.hasCompletedSignup === null)) {
225 this.stores.router.push('/');
226 }
227 };
228
229 // Reactions
230 async _getUserData() {
231 if (this.isLoggedIn) {
232 const data = await this.getUserInfoRequest.execute()._promise;
233
234 // We need to set the beta flag for the SettingsStore
235 this.actions.settings.update({
236 settings: {
237 beta: data.beta,
238 },
239 });
240 }
241 }
242
243 // Helpers
244 _parseToken(authToken) {
245 try {
246 const decoded = jwt.decode(authToken);
247
248 return ({
249 id: decoded.userId,
250 tokenExpiry: moment.unix(decoded.exp).toISOString(),
251 authToken,
252 });
253 } catch (err) {
254 console.error('AccessToken Invalid');
255
256 return false;
257 }
258 }
259
260 _setUserData(authToken) {
261 const data = this._parseToken(authToken);
262 if (data.authToken) {
263 localStorage.setItem('authToken', data.authToken);
264
265 this.authToken = data.authToken;
266 this.id = data.id;
267 } else {
268 this.authToken = null;
269 this.id = null;
270 }
271 }
272}