From e503468660a13760010a94ecda5f0625c6f47f87 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Fri, 13 Oct 2023 14:12:03 +0200 Subject: Server re-build with latest AdonisJS framework & Typescript (#47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: Balaji Vijayakumar Co-authored-by: MCMXC <16797721+mcmxcdev@users.noreply.github.com> Co-authored-by: André Oliveira --- app/Controllers/Http/DashboardController.js | 321 ---------------------------- 1 file changed, 321 deletions(-) delete mode 100644 app/Controllers/Http/DashboardController.js (limited to 'app/Controllers/Http/DashboardController.js') 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 @@ -const { validateAll } = use('Validator'); - -const Service = use('App/Models/Service'); -const Workspace = use('App/Models/Workspace'); -const Persona = use('Persona'); - -const crypto = require('crypto'); -const { v4: uuid } = require('uuid'); - -class DashboardController { - async login({ request, response, auth, session }) { - const validation = await validateAll(request.all(), { - mail: 'required|email', - password: 'required', - }); - if (validation.fails()) { - session - .withErrors({ - type: 'danger', - message: 'Invalid mail or password', - }) - .flashExcept(['password']); - return response.redirect('back'); - } - - const { mail, password } = request.all(); - - const hashedPassword = crypto - .createHash('sha256') - .update(password) - .digest('base64'); - - try { - await auth.authenticator('session').attempt(mail, hashedPassword); - } catch (error) { - session.flash({ - type: 'danger', - message: 'Invalid mail or password', - }); - return response.redirect('back'); - } - return response.redirect('/user/account'); - } - - async forgotPassword({ request, view }) { - const validation = await validateAll(request.all(), { - mail: 'required|email', - }); - if (validation.fails()) { - return view.render('others.message', { - heading: 'Cannot reset your password', - text: 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.', - }); - } - try { - await Persona.forgotPassword(request.input('mail')); - // eslint-disable-next-line no-empty - } catch (e) {} - - return view.render('others.message', { - heading: 'Reset password', - text: 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.', - }); - } - - async resetPassword({ request, view }) { - const validation = await validateAll(request.all(), { - password: 'required', - password_confirmation: 'required', - token: 'required', - }); - if (validation.fails()) { - session.withErrors({ - type: 'danger', - message: 'Passwords do not match', - }); - return response.redirect('back'); - } - - const payload = { - password: crypto - .createHash('sha256') - .update(request.input('password')) - .digest('base64'), - password_confirmation: crypto - .createHash('sha256') - .update(request.input('password_confirmation')) - .digest('base64'), - }; - - try { - await Persona.updatePasswordByToken(request.input('token'), payload); - } catch (e) { - return view.render('others.message', { - heading: 'Cannot reset your password', - text: 'Please make sure you are using a valid and recent link to reset your password and that your passwords entered match.', - }); - } - - return view.render('others.message', { - heading: 'Reset password', - text: 'Successfully reset your password. You can now login to your account using your new password.', - }); - } - - async account({ auth, view, response }) { - try { - await auth.check(); - } catch (error) { - return response.redirect('/user/login'); - } - - return view.render('dashboard.account', { - username: auth.user.username, - email: auth.user.email, - lastname: auth.user.lastname, - }); - } - - async edit({ auth, request, session, view, response }) { - let validation = await validateAll(request.all(), { - username: 'required', - email: 'required', - lastname: 'required', - }); - if (validation.fails()) { - session.withErrors(validation.messages()).flashExcept(['password']); - return response.redirect('back'); - } - - // Check new username - if (request.input('username') !== auth.user.username) { - validation = await validateAll(request.all(), { - username: 'required|unique:users,username', - email: 'required', - }); - if (validation.fails()) { - session.withErrors(validation.messages()).flashExcept(['password']); - return response.redirect('back'); - } - } - - // Check new email - if (request.input('email') !== auth.user.email) { - validation = await validateAll(request.all(), { - username: 'required', - email: 'required|email|unique:users,email', - }); - if (validation.fails()) { - session.withErrors(validation.messages()).flashExcept(['password']); - return response.redirect('back'); - } - } - - // Update user account - const { user } = auth; - user.username = request.input('username'); - user.lastname = request.input('lastname'); - user.email = request.input('email'); - if (request.input('password')) { - const hashedPassword = crypto - .createHash('sha256') - .update(request.input('password')) - .digest('base64'); - user.password = hashedPassword; - } - user.save(); - - return view.render('dashboard.account', { - username: user.username, - email: user.email, - success: true, - }); - } - - async data({ auth, view }) { - const general = auth.user; - const services = (await auth.user.services().fetch()).toJSON(); - const workspaces = (await auth.user.workspaces().fetch()).toJSON(); - - return view.render('dashboard.data', { - username: general.username, - lastname: general.lastname, - mail: general.email, - created: general.created_at, - updated: general.updated_at, - stringify: JSON.stringify, - services, - workspaces, - }); - } - - async export({ auth, response }) { - const general = auth.user; - const services = (await auth.user.services().fetch()).toJSON(); - const workspaces = (await auth.user.workspaces().fetch()).toJSON(); - - const exportData = { - username: general.username, - lastname: general.lastname, - mail: general.email, - services, - workspaces, - }; - - return response - .header('Content-Type', 'application/force-download') - .header('Content-disposition', 'attachment; filename=export.ferdium-data') - .send(exportData); - } - - async import({ auth, request, session, response, view }) { - const validation = await validateAll(request.all(), { - file: 'required', - }); - if (validation.fails()) { - session.withErrors(validation.messages()).flashExcept(['password']); - return response.redirect('back'); - } - - let file; - try { - file = JSON.parse(request.input('file')); - } catch (e) { - session.flash({ type: 'danger', message: 'Invalid Ferdium account file' }); - return response.redirect('back'); - } - - if (!file || !file.services || !file.workspaces) { - session.flash({ - type: 'danger', - message: 'Invalid Ferdium account file (2)', - }); - return response.redirect('back'); - } - - const serviceIdTranslation = {}; - - // Import services - try { - for (const service of file.services) { - // Get new, unused uuid - let serviceId; - do { - serviceId = uuid(); - } while ( - (await Service.query().where('serviceId', serviceId).fetch()).rows - .length > 0 - ); // eslint-disable-line no-await-in-loop - - await Service.create({ - // eslint-disable-line no-await-in-loop - userId: auth.user.id, - serviceId, - name: service.name, - recipeId: service.recipeId, - settings: JSON.stringify(service.settings), - }); - - serviceIdTranslation[service.serviceId] = serviceId; - } - } catch (e) { - const errorMessage = `Could not import your services into our system.\nError: ${e}`; - return view.render('others.message', { - heading: 'Error while importing', - text: errorMessage, - }); - } - - // Import workspaces - try { - for (const workspace of file.workspaces) { - let workspaceId; - do { - workspaceId = uuid(); - } while ( - (await Workspace.query().where('workspaceId', workspaceId).fetch()) - .rows.length > 0 - ); // eslint-disable-line no-await-in-loop - - const services = workspace.services.map( - service => serviceIdTranslation[service], - ); - - await Workspace.create({ - // eslint-disable-line no-await-in-loop - userId: auth.user.id, - workspaceId, - name: workspace.name, - order: workspace.order, - services: JSON.stringify(services), - data: JSON.stringify(workspace.data), - }); - } - } catch (e) { - const errorMessage = `Could not import your workspaces into our system.\nError: ${e}`; - return view.render('others.message', { - heading: 'Error while importing', - text: errorMessage, - }); - } - - return view.render('others.message', { - heading: 'Successfully imported', - text: 'Your account has been imported, you can now login as usual!', - }); - } - - logout({ auth, response }) { - auth.authenticator('session').logout(); - return response.redirect('/user/login'); - } - - delete({ auth, response }) { - auth.user.delete(); - auth.authenticator('session').logout(); - return response.redirect('/user/login'); - } -} - -module.exports = DashboardController; -- cgit v1.2.3-70-g09d2