diff options
author | 2023-10-13 14:12:03 +0200 | |
---|---|---|
committer | 2023-10-13 13:12:03 +0100 | |
commit | e503468660a13760010a94ecda5f0625c6f47f87 (patch) | |
tree | fa532f54fc5f091de08d55405ec6339bd2440a02 /app/Controllers/Http/DashboardController.ts | |
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.ts')
-rw-r--r-- | app/Controllers/Http/DashboardController.ts | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/app/Controllers/Http/DashboardController.ts b/app/Controllers/Http/DashboardController.ts new file mode 100644 index 0000000..a6f5b44 --- /dev/null +++ b/app/Controllers/Http/DashboardController.ts | |||
@@ -0,0 +1,327 @@ | |||
1 | // import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' | ||
2 | |||
3 | export default class DashboardController {} | ||
4 | |||
5 | /* | ||
6 | const { validateAll } = use('Validator'); | ||
7 | |||
8 | const Service = use('App/Models/Service'); | ||
9 | const Workspace = use('App/Models/Workspace'); | ||
10 | const Persona = use('Persona'); | ||
11 | |||
12 | const crypto = require('crypto'); | ||
13 | const { v4: uuid } = require('uuid'); | ||
14 | |||
15 | class DashboardController { | ||
16 | async login({ request, response, auth, session }) { | ||
17 | const validation = await validateAll(request.all(), { | ||
18 | mail: 'required|email', | ||
19 | password: 'required', | ||
20 | }); | ||
21 | if (validation.fails()) { | ||
22 | session | ||
23 | .withErrors({ | ||
24 | type: 'danger', | ||
25 | message: 'Invalid mail or password', | ||
26 | }) | ||
27 | .flashExcept(['password']); | ||
28 | return response.redirect('back'); | ||
29 | } | ||
30 | |||
31 | const { mail, password } = request.all(); | ||
32 | |||
33 | const hashedPassword = crypto | ||
34 | .createHash('sha256') | ||
35 | .update(password) | ||
36 | .digest('base64'); | ||
37 | |||
38 | try { | ||
39 | await auth.authenticator('session').attempt(mail, hashedPassword); | ||
40 | } catch (error) { | ||
41 | session.flash({ | ||
42 | type: 'danger', | ||
43 | message: 'Invalid mail or password', | ||
44 | }); | ||
45 | return response.redirect('back'); | ||
46 | } | ||
47 | return response.redirect('/user/account'); | ||
48 | } | ||
49 | |||
50 | async forgotPassword({ request, view }) { | ||
51 | const validation = await validateAll(request.all(), { | ||
52 | mail: 'required|email', | ||
53 | }); | ||
54 | if (validation.fails()) { | ||
55 | return view.render('others.message', { | ||
56 | heading: 'Cannot reset your password', | ||
57 | text: 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.', | ||
58 | }); | ||
59 | } | ||
60 | try { | ||
61 | await Persona.forgotPassword(request.input('mail')); | ||
62 | // eslint-disable-next-line no-empty | ||
63 | } catch (e) {} | ||
64 | |||
65 | return view.render('others.message', { | ||
66 | heading: 'Reset password', | ||
67 | text: 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.', | ||
68 | }); | ||
69 | } | ||
70 | |||
71 | async resetPassword({ request, view }) { | ||
72 | const validation = await validateAll(request.all(), { | ||
73 | password: 'required', | ||
74 | password_confirmation: 'required', | ||
75 | token: 'required', | ||
76 | }); | ||
77 | if (validation.fails()) { | ||
78 | session.withErrors({ | ||
79 | type: 'danger', | ||
80 | message: 'Passwords do not match', | ||
81 | }); | ||
82 | return response.redirect('back'); | ||
83 | } | ||
84 | |||
85 | const payload = { | ||
86 | password: crypto | ||
87 | .createHash('sha256') | ||
88 | .update(request.input('password')) | ||
89 | .digest('base64'), | ||
90 | password_confirmation: crypto | ||
91 | .createHash('sha256') | ||
92 | .update(request.input('password_confirmation')) | ||
93 | .digest('base64'), | ||
94 | }; | ||
95 | |||
96 | try { | ||
97 | await Persona.updatePasswordByToken(request.input('token'), payload); | ||
98 | } catch (e) { | ||
99 | return view.render('others.message', { | ||
100 | heading: 'Cannot reset your password', | ||
101 | text: 'Please make sure you are using a valid and recent link to reset your password and that your passwords entered match.', | ||
102 | }); | ||
103 | } | ||
104 | |||
105 | return view.render('others.message', { | ||
106 | heading: 'Reset password', | ||
107 | text: 'Successfully reset your password. You can now login to your account using your new password.', | ||
108 | }); | ||
109 | } | ||
110 | |||
111 | async account({ auth, view, response }) { | ||
112 | try { | ||
113 | await auth.check(); | ||
114 | } catch (error) { | ||
115 | return response.redirect('/user/login'); | ||
116 | } | ||
117 | |||
118 | return view.render('dashboard.account', { | ||
119 | username: auth.user.username, | ||
120 | email: auth.user.email, | ||
121 | lastname: auth.user.lastname, | ||
122 | }); | ||
123 | } | ||
124 | |||
125 | async edit({ auth, request, session, view, response }) { | ||
126 | let validation = await validateAll(request.all(), { | ||
127 | username: 'required', | ||
128 | email: 'required', | ||
129 | lastname: 'required', | ||
130 | }); | ||
131 | if (validation.fails()) { | ||
132 | session.withErrors(validation.messages()).flashExcept(['password']); | ||
133 | return response.redirect('back'); | ||
134 | } | ||
135 | |||
136 | // Check new username | ||
137 | if (request.input('username') !== auth.user.username) { | ||
138 | validation = await validateAll(request.all(), { | ||
139 | username: 'required|unique:users,username', | ||
140 | email: 'required', | ||
141 | }); | ||
142 | if (validation.fails()) { | ||
143 | session.withErrors(validation.messages()).flashExcept(['password']); | ||
144 | return response.redirect('back'); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | // Check new email | ||
149 | if (request.input('email') !== auth.user.email) { | ||
150 | validation = await validateAll(request.all(), { | ||
151 | username: 'required', | ||
152 | email: 'required|email|unique:users,email', | ||
153 | }); | ||
154 | if (validation.fails()) { | ||
155 | session.withErrors(validation.messages()).flashExcept(['password']); | ||
156 | return response.redirect('back'); | ||
157 | } | ||
158 | } | ||
159 | |||
160 | // Update user account | ||
161 | const { user } = auth; | ||
162 | user.username = request.input('username'); | ||
163 | user.lastname = request.input('lastname'); | ||
164 | user.email = request.input('email'); | ||
165 | if (request.input('password')) { | ||
166 | const hashedPassword = crypto | ||
167 | .createHash('sha256') | ||
168 | .update(request.input('password')) | ||
169 | .digest('base64'); | ||
170 | user.password = hashedPassword; | ||
171 | } | ||
172 | user.save(); | ||
173 | |||
174 | return view.render('dashboard.account', { | ||
175 | username: user.username, | ||
176 | email: user.email, | ||
177 | success: true, | ||
178 | }); | ||
179 | } | ||
180 | |||
181 | async data({ auth, view }) { | ||
182 | const general = auth.user; | ||
183 | const services = (await auth.user.services().fetch()).toJSON(); | ||
184 | const workspaces = (await auth.user.workspaces().fetch()).toJSON(); | ||
185 | |||
186 | return view.render('dashboard.data', { | ||
187 | username: general.username, | ||
188 | lastname: general.lastname, | ||
189 | mail: general.email, | ||
190 | created: general.created_at, | ||
191 | updated: general.updated_at, | ||
192 | stringify: JSON.stringify, | ||
193 | services, | ||
194 | workspaces, | ||
195 | }); | ||
196 | } | ||
197 | |||
198 | async export({ auth, response }) { | ||
199 | const general = auth.user; | ||
200 | const services = (await auth.user.services().fetch()).toJSON(); | ||
201 | const workspaces = (await auth.user.workspaces().fetch()).toJSON(); | ||
202 | |||
203 | const exportData = { | ||
204 | username: general.username, | ||
205 | lastname: general.lastname, | ||
206 | mail: general.email, | ||
207 | services, | ||
208 | workspaces, | ||
209 | }; | ||
210 | |||
211 | return response | ||
212 | .header('Content-Type', 'application/force-download') | ||
213 | .header('Content-disposition', 'attachment; filename=export.ferdium-data') | ||
214 | .send(exportData); | ||
215 | } | ||
216 | |||
217 | async import({ auth, request, session, response, view }) { | ||
218 | const validation = await validateAll(request.all(), { | ||
219 | file: 'required', | ||
220 | }); | ||
221 | if (validation.fails()) { | ||
222 | session.withErrors(validation.messages()).flashExcept(['password']); | ||
223 | return response.redirect('back'); | ||
224 | } | ||
225 | |||
226 | let file; | ||
227 | try { | ||
228 | file = JSON.parse(request.input('file')); | ||
229 | } catch (e) { | ||
230 | session.flash({ type: 'danger', message: 'Invalid Ferdium account file' }); | ||
231 | return response.redirect('back'); | ||
232 | } | ||
233 | |||
234 | if (!file || !file.services || !file.workspaces) { | ||
235 | session.flash({ | ||
236 | type: 'danger', | ||
237 | message: 'Invalid Ferdium account file (2)', | ||
238 | }); | ||
239 | return response.redirect('back'); | ||
240 | } | ||
241 | |||
242 | const serviceIdTranslation = {}; | ||
243 | |||
244 | // Import services | ||
245 | try { | ||
246 | for (const service of file.services) { | ||
247 | // Get new, unused uuid | ||
248 | let serviceId; | ||
249 | do { | ||
250 | serviceId = uuid(); | ||
251 | } while ( | ||
252 | (await Service.query().where('serviceId', serviceId).fetch()).rows | ||
253 | .length > 0 | ||
254 | ); // eslint-disable-line no-await-in-loop | ||
255 | |||
256 | await Service.create({ | ||
257 | // eslint-disable-line no-await-in-loop | ||
258 | userId: auth.user.id, | ||
259 | serviceId, | ||
260 | name: service.name, | ||
261 | recipeId: service.recipeId, | ||
262 | settings: JSON.stringify(service.settings), | ||
263 | }); | ||
264 | |||
265 | serviceIdTranslation[service.serviceId] = serviceId; | ||
266 | } | ||
267 | } catch (e) { | ||
268 | const errorMessage = `Could not import your services into our system.\nError: ${e}`; | ||
269 | return view.render('others.message', { | ||
270 | heading: 'Error while importing', | ||
271 | text: errorMessage, | ||
272 | }); | ||
273 | } | ||
274 | |||
275 | // Import workspaces | ||
276 | try { | ||
277 | for (const workspace of file.workspaces) { | ||
278 | let workspaceId; | ||
279 | do { | ||
280 | workspaceId = uuid(); | ||
281 | } while ( | ||
282 | (await Workspace.query().where('workspaceId', workspaceId).fetch()) | ||
283 | .rows.length > 0 | ||
284 | ); // eslint-disable-line no-await-in-loop | ||
285 | |||
286 | const services = workspace.services.map( | ||
287 | service => serviceIdTranslation[service], | ||
288 | ); | ||
289 | |||
290 | await Workspace.create({ | ||
291 | // eslint-disable-line no-await-in-loop | ||
292 | userId: auth.user.id, | ||
293 | workspaceId, | ||
294 | name: workspace.name, | ||
295 | order: workspace.order, | ||
296 | services: JSON.stringify(services), | ||
297 | data: JSON.stringify(workspace.data), | ||
298 | }); | ||
299 | } | ||
300 | } catch (e) { | ||
301 | const errorMessage = `Could not import your workspaces into our system.\nError: ${e}`; | ||
302 | return view.render('others.message', { | ||
303 | heading: 'Error while importing', | ||
304 | text: errorMessage, | ||
305 | }); | ||
306 | } | ||
307 | |||
308 | return view.render('others.message', { | ||
309 | heading: 'Successfully imported', | ||
310 | text: 'Your account has been imported, you can now login as usual!', | ||
311 | }); | ||
312 | } | ||
313 | |||
314 | logout({ auth, response }) { | ||
315 | auth.authenticator('session').logout(); | ||
316 | return response.redirect('/user/login'); | ||
317 | } | ||
318 | |||
319 | delete({ auth, response }) { | ||
320 | auth.user.delete(); | ||
321 | auth.authenticator('session').logout(); | ||
322 | return response.redirect('/user/login'); | ||
323 | } | ||
324 | } | ||
325 | |||
326 | module.exports = DashboardController; | ||
327 | */ | ||