aboutsummaryrefslogtreecommitdiffstats
path: root/app/Controllers/Http/DashboardController.js
diff options
context:
space:
mode:
authorLibravatar Ricardo <ricardo@cino.io>2023-10-13 14:12:03 +0200
committerLibravatar GitHub <noreply@github.com>2023-10-13 13:12:03 +0100
commite503468660a13760010a94ecda5f0625c6f47f87 (patch)
treefa532f54fc5f091de08d55405ec6339bd2440a02 /app/Controllers/Http/DashboardController.js
parent1.3.16 [skip ci] (diff)
downloadferdium-server-e503468660a13760010a94ecda5f0625c6f47f87.tar.gz
ferdium-server-e503468660a13760010a94ecda5f0625c6f47f87.tar.zst
ferdium-server-e503468660a13760010a94ecda5f0625c6f47f87.zip
Server re-build with latest AdonisJS framework & Typescript (#47)
* chore: setup first basis structure * chore: ensure styling is loaded correctly * chore: comply to new routing syntax by replace . with / in routes/resource locations * chore: add login controller * chore: correctly use views with slash instead of dot * chore: working login + tests * chore: clean up tests * chore: add password-forgot endpoint and matching test * chore: add delete page test * chore: add logout test * chore: add reset-password route and tests * chore: remove obsolete comment * chore: add account-page and tests * chore: add data page & first step of the test * chore: add transfer/import data feature and tests * chore: add export and basic test * chore: add all static api routes with tests * Regenerate 'pnpm-lock.json' and fix bad merge conflict WIP: - Tests have been commented out since they dont work - Server doesn't start * easier dev and test runs * - remove --require-pragma from reformat-files so formatting works properly - run pnpm reformat-files over codebase - remove .json files from .eslintignore - add invalid.json file to .eslintignore - configure prettier properly in eslint config - add type jsdoc to prettier config - run adonis generate:manifest command to regenerate ace-manifest.json - specify volta in package.json - introduce typecheck npm script - remove unused .mjs extension from npm scripts - install missing type definition dependencies - add pnpm.allowedDeprecatedVersions to package.json - fix invalid extends in tsconfig.json causing TS issues throughout codebase - remove @ts-ignore throughout codebase which is not relevant anymore - enable some of the tsconfig options - remove outdated eslint-disable from codebase - change deprecated faker.company.companyName() to faker.company.name() - fix TS issues inside transfer.spec.ts * - update to latest node and pnpm versions - upgrade all non-major dependencies to latest - install missing @types/luxon dependency - add cuid to pnpm.allowedDeprecatedVersions - add esModuleInterop config option to tsconfig - migrate more deprecated faker methods to new ones - add more temporary ts-ignore to code * - update eslint config - remove trailingComma: all since default in prettier v3 - add typecheck command to prepare-code npm script - upgrade various dependencies to latest major version - update tsconfig to include only useful config options - disable some lint issues and fix others * - add test command to prepare-code - disable strictPropertyInitialization flag in tsconfig which creates issues with adonis models - update precommit hook to excute pnpm prepare-code - remove ts-ignore statements from all models * fix node and pnpm dependency update * add cross env (so that we can develop on windows) * add signup endpoint (TODO: JWT auth) * Add login endpoint * Add me and updateMe endpoints * Add service endpoint * refactor: change endpoints to use jwt * add recipes endpoint * add workspaces endpoint * fix web controllors for login and post import * Update node deps * Change auth middleware (for web) and exempt api from CSRF * Add import endpoint (franz import) * Fix export/import logic * Fix service and workspace data in user/data * Fix partial lint * chore: workaround lint issues * fix: migration naming had two . * Sync back node with recipes repo * Temporarily ignore typescript * Fix adonisrc to handle public folder static assets * Fix issue with production database * add Legacy Password Provider * Fix lint errors * Fix issue on login errors frontend * add Legacy Password Provider * Fix issue with customIcons * Fix issue with auth tokens * Update 'node' to '18.18.0' * make docker work * improve docker entrypoint (test api performance) * Add migration database script * NODE_ENV on recipes * prefer @ts-expect-error over @ts-ignore * small fixes * Update 'pnpm' to '8.7.6' * fix error catch * Automatically generate JWT Public and Private keys * Use custom Adonis5-jwt * Update code to use secret (old way, no breaking changes) * Normalize appKey * Trick to make JWT tokens on client work with new version * Fix error with new JWT logic * Change migration and how we store JWT * Fix 500 response code (needs to be 401) * Improve logic and fix bugs * Fix build and entrypoint logic * Catch error if appKey changes * Add newToken logic * Fix lint (ignore any errors) * Add build for PRs * pnpm reformat-files result * Fix some tests * Fix reset password not working (test failing) * Restore csrfTokens (disabled by accident) * Fix pnpm start command with .env * Disable failing tests on the transfer endpoint (TODO) * Add tests to PR build * Fix build * Remove unnecessary assertStatus * Add typecheck * hash password on UserFactory (fix build) * Add JWT_USE_PEM true by default (increase security) * fix name of github action --------- Co-authored-by: Vijay A <vraravam@users.noreply.github.com> Co-authored-by: Balaji Vijayakumar <kuttibalaji.v6@gmail.com> Co-authored-by: MCMXC <16797721+mcmxcdev@users.noreply.github.com> Co-authored-by: André Oliveira <oliveira.andrerodrigues95@gmail.com>
Diffstat (limited to 'app/Controllers/Http/DashboardController.js')
-rw-r--r--app/Controllers/Http/DashboardController.js321
1 files changed, 0 insertions, 321 deletions
diff --git a/app/Controllers/Http/DashboardController.js b/app/Controllers/Http/DashboardController.js
deleted file mode 100644
index 9b3b08e..0000000
--- a/app/Controllers/Http/DashboardController.js
+++ /dev/null
@@ -1,321 +0,0 @@
1const { validateAll } = use('Validator');
2
3const Service = use('App/Models/Service');
4const Workspace = use('App/Models/Workspace');
5const Persona = use('Persona');
6
7const crypto = require('crypto');
8const { v4: uuid } = require('uuid');
9
10class DashboardController {
11 async login({ request, response, auth, session }) {
12 const validation = await validateAll(request.all(), {
13 mail: 'required|email',
14 password: 'required',
15 });
16 if (validation.fails()) {
17 session
18 .withErrors({
19 type: 'danger',
20 message: 'Invalid mail or password',
21 })
22 .flashExcept(['password']);
23 return response.redirect('back');
24 }
25
26 const { mail, password } = request.all();
27
28 const hashedPassword = crypto
29 .createHash('sha256')
30 .update(password)
31 .digest('base64');
32
33 try {
34 await auth.authenticator('session').attempt(mail, hashedPassword);
35 } catch (error) {
36 session.flash({
37 type: 'danger',
38 message: 'Invalid mail or password',
39 });
40 return response.redirect('back');
41 }
42 return response.redirect('/user/account');
43 }
44
45 async forgotPassword({ request, view }) {
46 const validation = await validateAll(request.all(), {
47 mail: 'required|email',
48 });
49 if (validation.fails()) {
50 return view.render('others.message', {
51 heading: 'Cannot reset your password',
52 text: 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.',
53 });
54 }
55 try {
56 await Persona.forgotPassword(request.input('mail'));
57 // eslint-disable-next-line no-empty
58 } catch (e) {}
59
60 return view.render('others.message', {
61 heading: 'Reset password',
62 text: 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.',
63 });
64 }
65
66 async resetPassword({ request, view }) {
67 const validation = await validateAll(request.all(), {
68 password: 'required',
69 password_confirmation: 'required',
70 token: 'required',
71 });
72 if (validation.fails()) {
73 session.withErrors({
74 type: 'danger',
75 message: 'Passwords do not match',
76 });
77 return response.redirect('back');
78 }
79
80 const payload = {
81 password: crypto
82 .createHash('sha256')
83 .update(request.input('password'))
84 .digest('base64'),
85 password_confirmation: crypto
86 .createHash('sha256')
87 .update(request.input('password_confirmation'))
88 .digest('base64'),
89 };
90
91 try {
92 await Persona.updatePasswordByToken(request.input('token'), payload);
93 } catch (e) {
94 return view.render('others.message', {
95 heading: 'Cannot reset your password',
96 text: 'Please make sure you are using a valid and recent link to reset your password and that your passwords entered match.',
97 });
98 }
99
100 return view.render('others.message', {
101 heading: 'Reset password',
102 text: 'Successfully reset your password. You can now login to your account using your new password.',
103 });
104 }
105
106 async account({ auth, view, response }) {
107 try {
108 await auth.check();
109 } catch (error) {
110 return response.redirect('/user/login');
111 }
112
113 return view.render('dashboard.account', {
114 username: auth.user.username,
115 email: auth.user.email,
116 lastname: auth.user.lastname,
117 });
118 }
119
120 async edit({ auth, request, session, view, response }) {
121 let validation = await validateAll(request.all(), {
122 username: 'required',
123 email: 'required',
124 lastname: 'required',
125 });
126 if (validation.fails()) {
127 session.withErrors(validation.messages()).flashExcept(['password']);
128 return response.redirect('back');
129 }
130
131 // Check new username
132 if (request.input('username') !== auth.user.username) {
133 validation = await validateAll(request.all(), {
134 username: 'required|unique:users,username',
135 email: 'required',
136 });
137 if (validation.fails()) {
138 session.withErrors(validation.messages()).flashExcept(['password']);
139 return response.redirect('back');
140 }
141 }
142
143 // Check new email
144 if (request.input('email') !== auth.user.email) {
145 validation = await validateAll(request.all(), {
146 username: 'required',
147 email: 'required|email|unique:users,email',
148 });
149 if (validation.fails()) {
150 session.withErrors(validation.messages()).flashExcept(['password']);
151 return response.redirect('back');
152 }
153 }
154
155 // Update user account
156 const { user } = auth;
157 user.username = request.input('username');
158 user.lastname = request.input('lastname');
159 user.email = request.input('email');
160 if (request.input('password')) {
161 const hashedPassword = crypto
162 .createHash('sha256')
163 .update(request.input('password'))
164 .digest('base64');
165 user.password = hashedPassword;
166 }
167 user.save();
168
169 return view.render('dashboard.account', {
170 username: user.username,
171 email: user.email,
172 success: true,
173 });
174 }
175
176 async data({ auth, view }) {
177 const general = auth.user;
178 const services = (await auth.user.services().fetch()).toJSON();
179 const workspaces = (await auth.user.workspaces().fetch()).toJSON();
180
181 return view.render('dashboard.data', {
182 username: general.username,
183 lastname: general.lastname,
184 mail: general.email,
185 created: general.created_at,
186 updated: general.updated_at,
187 stringify: JSON.stringify,
188 services,
189 workspaces,
190 });
191 }
192
193 async export({ auth, response }) {
194 const general = auth.user;
195 const services = (await auth.user.services().fetch()).toJSON();
196 const workspaces = (await auth.user.workspaces().fetch()).toJSON();
197
198 const exportData = {
199 username: general.username,
200 lastname: general.lastname,
201 mail: general.email,
202 services,
203 workspaces,
204 };
205
206 return response
207 .header('Content-Type', 'application/force-download')
208 .header('Content-disposition', 'attachment; filename=export.ferdium-data')
209 .send(exportData);
210 }
211
212 async import({ auth, request, session, response, view }) {
213 const validation = await validateAll(request.all(), {
214 file: 'required',
215 });
216 if (validation.fails()) {
217 session.withErrors(validation.messages()).flashExcept(['password']);
218 return response.redirect('back');
219 }
220
221 let file;
222 try {
223 file = JSON.parse(request.input('file'));
224 } catch (e) {
225 session.flash({ type: 'danger', message: 'Invalid Ferdium account file' });
226 return response.redirect('back');
227 }
228
229 if (!file || !file.services || !file.workspaces) {
230 session.flash({
231 type: 'danger',
232 message: 'Invalid Ferdium account file (2)',
233 });
234 return response.redirect('back');
235 }
236
237 const serviceIdTranslation = {};
238
239 // Import services
240 try {
241 for (const service of file.services) {
242 // Get new, unused uuid
243 let serviceId;
244 do {
245 serviceId = uuid();
246 } while (
247 (await Service.query().where('serviceId', serviceId).fetch()).rows
248 .length > 0
249 ); // eslint-disable-line no-await-in-loop
250
251 await Service.create({
252 // eslint-disable-line no-await-in-loop
253 userId: auth.user.id,
254 serviceId,
255 name: service.name,
256 recipeId: service.recipeId,
257 settings: JSON.stringify(service.settings),
258 });
259
260 serviceIdTranslation[service.serviceId] = serviceId;
261 }
262 } catch (e) {
263 const errorMessage = `Could not import your services into our system.\nError: ${e}`;
264 return view.render('others.message', {
265 heading: 'Error while importing',
266 text: errorMessage,
267 });
268 }
269
270 // Import workspaces
271 try {
272 for (const workspace of file.workspaces) {
273 let workspaceId;
274 do {
275 workspaceId = uuid();
276 } while (
277 (await Workspace.query().where('workspaceId', workspaceId).fetch())
278 .rows.length > 0
279 ); // eslint-disable-line no-await-in-loop
280
281 const services = workspace.services.map(
282 service => serviceIdTranslation[service],
283 );
284
285 await Workspace.create({
286 // eslint-disable-line no-await-in-loop
287 userId: auth.user.id,
288 workspaceId,
289 name: workspace.name,
290 order: workspace.order,
291 services: JSON.stringify(services),
292 data: JSON.stringify(workspace.data),
293 });
294 }
295 } catch (e) {
296 const errorMessage = `Could not import your workspaces into our system.\nError: ${e}`;
297 return view.render('others.message', {
298 heading: 'Error while importing',
299 text: errorMessage,
300 });
301 }
302
303 return view.render('others.message', {
304 heading: 'Successfully imported',
305 text: 'Your account has been imported, you can now login as usual!',
306 });
307 }
308
309 logout({ auth, response }) {
310 auth.authenticator('session').logout();
311 return response.redirect('/user/login');
312 }
313
314 delete({ auth, response }) {
315 auth.user.delete();
316 auth.authenticator('session').logout();
317 return response.redirect('/user/login');
318 }
319}
320
321module.exports = DashboardController;