aboutsummaryrefslogtreecommitdiffstats
path: root/src/internal-server/app/Controllers/Http/UserController.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/internal-server/app/Controllers/Http/UserController.js')
-rw-r--r--src/internal-server/app/Controllers/Http/UserController.js370
1 files changed, 370 insertions, 0 deletions
diff --git a/src/internal-server/app/Controllers/Http/UserController.js b/src/internal-server/app/Controllers/Http/UserController.js
new file mode 100644
index 000000000..a3ad736fa
--- /dev/null
+++ b/src/internal-server/app/Controllers/Http/UserController.js
@@ -0,0 +1,370 @@
1const User = use('App/Models/User');
2const Service = use('App/Models/Service');
3const Workspace = use('App/Models/Workspace');
4const {
5 validateAll,
6} = use('Validator');
7
8const btoa = require('btoa');
9const fetch = require('node-fetch');
10const uuid = require('uuid/v4');
11const crypto = require('crypto');
12const { DEFAULT_APP_SETTINGS } = require('../../../../environment');
13
14const apiRequest = (url, route, method, auth) => new Promise((resolve, reject) => {
15 const base = `${url}/v1/`;
16 const user = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Ferdi/5.3.0-beta.1 Chrome/69.0.3497.128 Electron/4.2.4 Safari/537.36';
17
18 try {
19 fetch(base + route, {
20 method,
21 headers: {
22 Authorization: `Bearer ${auth}`,
23 'User-Agent': user,
24 },
25 })
26 .then(data => data.json())
27 .then(json => resolve(json));
28 } catch (e) {
29 reject();
30 }
31});
32
33class UserController {
34 // Register a new user
35 async signup({
36 request,
37 response,
38 }) {
39 // Validate user input
40 const validation = await validateAll(request.all(), {
41 firstname: 'required',
42 email: 'required|email',
43 password: 'required',
44 });
45 if (validation.fails()) {
46 return response.status(401).send({
47 message: 'Invalid POST arguments',
48 messages: validation.messages(),
49 status: 401,
50 });
51 }
52
53 return response.send({
54 message: 'Successfully created account',
55 token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJGZXJkaSBJbnRlcm5hbCBTZXJ2ZXIiLCJpYXQiOjE1NzEwNDAyMTUsImV4cCI6MjUzMzk1NDE3ODQ0LCJhdWQiOiJnZXRmZXJkaS5jb20iLCJzdWIiOiJmZXJkaUBsb2NhbGhvc3QiLCJ1c2VySWQiOiIxIn0.9_TWFGp6HROv8Yg82Rt6i1-95jqWym40a-HmgrdMC6M',
56 });
57 }
58
59 // Login using an existing user
60 async login({
61 request,
62 response,
63 }) {
64 if (!request.header('Authorization')) {
65 return response.status(401).send({
66 message: 'Please provide authorization',
67 status: 401,
68 });
69 }
70
71 return response.send({
72 message: 'Successfully logged in',
73 token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJGZXJkaSBJbnRlcm5hbCBTZXJ2ZXIiLCJpYXQiOjE1NzEwNDAyMTUsImV4cCI6MjUzMzk1NDE3ODQ0LCJhdWQiOiJnZXRmZXJkaS5jb20iLCJzdWIiOiJmZXJkaUBsb2NhbGhvc3QiLCJ1c2VySWQiOiIxIn0.9_TWFGp6HROv8Yg82Rt6i1-95jqWym40a-HmgrdMC6M',
74 });
75 }
76
77 // Return information about the current user
78 async me({
79 response,
80 }) {
81 const user = await User.find(1);
82
83 const settings = typeof user.settings === 'string' ? JSON.parse(user.settings) : user.settings;
84
85 return response.send({
86 accountType: 'individual',
87 beta: false,
88 donor: {},
89 email: '',
90 emailValidated: true,
91 features: {},
92 firstname: 'Ferdi',
93 id: '82c1cf9d-ab58-4da2-b55e-aaa41d2142d8',
94 isSubscriptionOwner: true,
95 lastname: 'Application',
96 locale: DEFAULT_APP_SETTINGS.fallbackLocale,
97 ...settings || {},
98 });
99 }
100
101 async updateMe({
102 request,
103 response,
104 }) {
105 const user = await User.find(1);
106
107 let settings = user.settings || {};
108 if (typeof settings === 'string') {
109 settings = JSON.parse(settings);
110 }
111
112 const newSettings = {
113 ...settings,
114 ...request.all(),
115 };
116
117 user.settings = JSON.stringify(newSettings);
118 await user.save();
119
120 return response.send({
121 data: {
122 accountType: 'individual',
123 beta: false,
124 donor: {},
125 email: '',
126 emailValidated: true,
127 features: {},
128 firstname: 'Ferdi',
129 id: '82c1cf9d-ab58-4da2-b55e-aaa41d2142d8',
130 isSubscriptionOwner: true,
131 lastname: 'Application',
132 locale: DEFAULT_APP_SETTINGS.fallbackLocale,
133 ...newSettings,
134 },
135 status: [
136 'data-updated',
137 ],
138 });
139 }
140
141 async import({
142 request,
143 response,
144 }) {
145 // Validate user input
146 const validation = await validateAll(request.all(), {
147 email: 'required|email',
148 password: 'required',
149 server: 'required',
150 });
151 if (validation.fails()) {
152 let errorMessage = 'There was an error while trying to import your account:\n';
153 for (const message of validation.messages()) {
154 if (message.validation === 'required') {
155 errorMessage += `- Please make sure to supply your ${message.field}\n`;
156 } else if (message.validation === 'unique') {
157 errorMessage += '- There is already a user with this email.\n';
158 } else {
159 errorMessage += `${message.message}\n`;
160 }
161 }
162 return response.status(401).send(errorMessage);
163 }
164
165 const {
166 email,
167 password,
168 server,
169 } = request.all();
170
171 const hashedPassword = crypto.createHash('sha256').update(password).digest('base64');
172
173 const base = `${server}/v1/`;
174 const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Ferdi/5.3.0-beta.1 Chrome/69.0.3497.128 Electron/4.2.4 Safari/537.36';
175
176 // Try to get an authentication token
177 let token;
178 try {
179 const basicToken = btoa(`${email}:${hashedPassword}`);
180
181 const rawResponse = await fetch(`${base}auth/login`, {
182 method: 'POST',
183 headers: {
184 Authorization: `Basic ${basicToken}`,
185 'User-Agent': userAgent,
186 },
187 });
188 const content = await rawResponse.json();
189
190 if (!content.message || content.message !== 'Successfully logged in') {
191 const errorMessage = 'Could not login into Franz with your supplied credentials. Please check and try again';
192 return response.status(401).send(errorMessage);
193 }
194
195 // eslint-disable-next-line prefer-destructuring
196 token = content.token;
197 } catch (e) {
198 return response.status(401).send({
199 message: 'Cannot login to Franz',
200 error: e,
201 });
202 }
203
204 // Get user information
205 let userInf = false;
206 try {
207 userInf = await apiRequest(server, 'me', 'GET', token);
208 } catch (e) {
209 const errorMessage = `Could not get your user info from Franz. Please check your credentials or try again later.\nError: ${e}`;
210 return response.status(401).send(errorMessage);
211 }
212 if (!userInf) {
213 const errorMessage = 'Could not get your user info from Franz. Please check your credentials or try again later';
214 return response.status(401).send(errorMessage);
215 }
216
217 const serviceIdTranslation = {};
218
219 // Import services
220 try {
221 const services = await apiRequest(server, 'me/services', 'GET', token);
222
223 for (const service of services) {
224 // Get new, unused uuid
225 let serviceId;
226 do {
227 serviceId = uuid();
228 } while ((await Service.query().where('serviceId', serviceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop
229
230 await Service.create({ // eslint-disable-line no-await-in-loop
231 serviceId,
232 name: service.name,
233 recipeId: service.recipeId,
234 settings: JSON.stringify(service),
235 });
236
237 serviceIdTranslation[service.id] = serviceId;
238 }
239 } catch (e) {
240 const errorMessage = `Could not import your services into our system.\nError: ${e}`;
241 return response.status(401).send(errorMessage);
242 }
243
244 // Import workspaces
245 try {
246 const workspaces = await apiRequest(server, 'workspace', 'GET', token);
247
248 for (const workspace of workspaces) {
249 let workspaceId;
250 do {
251 workspaceId = uuid();
252 } while ((await Workspace.query().where('workspaceId', workspaceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop
253
254 const services = workspace.services.map(service => serviceIdTranslation[service]);
255
256 await Workspace.create({ // eslint-disable-line no-await-in-loop
257 workspaceId,
258 name: workspace.name,
259 order: workspace.order,
260 services: JSON.stringify(services),
261 data: JSON.stringify({}),
262 });
263 }
264 } catch (e) {
265 const errorMessage = `Could not import your workspaces into our system.\nError: ${e}`;
266 return response.status(401).send(errorMessage);
267 }
268
269 return response.send('Your account has been imported. You can now use your Franz account in Ferdi.');
270 }
271
272 // Account import/export
273 async export({
274 // eslint-disable-next-line no-unused-vars
275 auth,
276 response,
277 }) {
278 const services = (await Service.all()).toJSON();
279 const workspaces = (await Workspace.all()).toJSON();
280
281 const exportData = {
282 username: 'Ferdi',
283 mail: 'internal@getferdi.com',
284 services,
285 workspaces,
286 };
287
288 return response
289 .header('Content-Type', 'application/force-download')
290 .header('Content-disposition', 'attachment; filename=export.ferdi-data')
291 .send(exportData);
292 }
293
294 async importFerdi({
295 request,
296 response,
297 }) {
298 const validation = await validateAll(request.all(), {
299 file: 'required',
300 });
301 if (validation.fails()) {
302 return response.send(validation.messages());
303 }
304
305 let file;
306 try {
307 file = JSON.parse(request.input('file'));
308 } catch (e) {
309 return response.send('Could not import: Invalid file, could not read file');
310 }
311
312 if (!file || !file.services || !file.workspaces) {
313 return response.send('Could not import: Invalid file (2)');
314 }
315
316 const serviceIdTranslation = {};
317
318 // Import services
319 try {
320 for (const service of file.services) {
321 // Get new, unused uuid
322 let serviceId;
323 do {
324 serviceId = uuid();
325 } while ((await Service.query().where('serviceId', serviceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop
326
327 await Service.create({ // eslint-disable-line no-await-in-loop
328 serviceId,
329 name: service.name,
330 recipeId: service.recipeId,
331 settings: JSON.stringify(service.settings),
332 });
333
334 serviceIdTranslation[service.id] = serviceId;
335 }
336 } catch (e) {
337 const errorMessage = `Could not import your services into our system.\nError: ${e}`;
338 return response.send(errorMessage);
339 }
340
341 // Import workspaces
342 try {
343 for (const workspace of file.workspaces) {
344 let workspaceId;
345 do {
346 workspaceId = uuid();
347 } while ((await Workspace.query().where('workspaceId', workspaceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop
348
349 const services = (workspace.services && typeof (workspace.services) === 'object') ?
350 workspace.services.map((service) => serviceIdTranslation[service]) :
351 [];
352
353 await Workspace.create({ // eslint-disable-line no-await-in-loop
354 workspaceId,
355 name: workspace.name,
356 order: workspace.order,
357 services: JSON.stringify(services),
358 data: JSON.stringify(workspace.data),
359 });
360 }
361 } catch (e) {
362 const errorMessage = `Could not import your workspaces into our system.\nError: ${e}`;
363 return response.status(401).send(errorMessage);
364 }
365
366 return response.send('Your account has been imported.');
367 }
368}
369
370module.exports = UserController;