diff options
author | Ricardo <ricardo@cino.io> | 2023-10-13 14:12:03 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-13 13:12:03 +0100 |
commit | e503468660a13760010a94ecda5f0625c6f47f87 (patch) | |
tree | fa532f54fc5f091de08d55405ec6339bd2440a02 /app/Controllers/Http/DashboardController.js | |
parent | 1.3.16 [skip ci] (diff) | |
download | ferdium-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.js | 321 |
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 @@ | |||
1 | const { validateAll } = use('Validator'); | ||
2 | |||
3 | const Service = use('App/Models/Service'); | ||
4 | const Workspace = use('App/Models/Workspace'); | ||
5 | const Persona = use('Persona'); | ||
6 | |||
7 | const crypto = require('crypto'); | ||
8 | const { v4: uuid } = require('uuid'); | ||
9 | |||
10 | class 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 | |||
321 | module.exports = DashboardController; | ||