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