From fff1304709a65599e4d4409b1aa809c050f3d22a Mon Sep 17 00:00:00 2001
From: Kelvin Oghenerhoro Omereshone
Date: Thu, 22 Feb 2024 07:39:00 +0100
Subject: [PATCH] [feat] handle validation errors in mellow vue (#82)
* feat(mellow-vue): update badRequest response
* feat(mellow-vue): update inertia-sails and install sails-flash
* chore(mellow-vue): turn of typechecking js files
* feat(mellow-vue): update login action and page to display validation errors
* feat(mellow-vue): modify signup action to handle validation errors
* feat(mellow-vue): display validation errors
---
.../mellow-vue/api/controllers/auth/login.js | 23 +++++++--
.../mellow-vue/api/controllers/auth/signup.js | 49 +++++++++++--------
.../mellow-vue/api/responses/badRequest.js | 49 ++++++++++---------
.../mellow-vue/assets/js/pages/login.vue | 9 +++-
.../mellow-vue/assets/js/pages/signup.vue | 15 ++++++
templates/mellow-vue/jsconfig.json | 2 +-
templates/mellow-vue/package-lock.json | 28 +++++++++--
templates/mellow-vue/package.json | 3 +-
8 files changed, 123 insertions(+), 55 deletions(-)
diff --git a/templates/mellow-vue/api/controllers/auth/login.js b/templates/mellow-vue/api/controllers/auth/login.js
index 5541f661..cd30be41 100644
--- a/templates/mellow-vue/api/controllers/auth/login.js
+++ b/templates/mellow-vue/api/controllers/auth/login.js
@@ -12,6 +12,7 @@ password attempt.`,
email: {
description: 'The email to try in this attempt, e.g. "irl@example.com".',
type: 'string',
+ isEmail: true,
required: true
},
@@ -39,6 +40,9 @@ that, thanks to the included "custom" hook, when a relevant request is received
from a logged-in user, that user's entire record from the database will be fetched
and exposed as a shared data via loggedInUser prop.)`,
responseType: 'redirect'
+ },
+ badCombo: {
+ responseType: 'badRequest'
}
},
@@ -48,12 +52,23 @@ and exposed as a shared data via loggedInUser prop.)`,
})
if (!user) {
- throw 'badCombo'
+ throw {
+ badCombo: {
+ problems: [{ login: 'Wrong email/password.' }]
+ }
+ }
}
- await sails.helpers.passwords
- .checkPassword(password, user.password)
- .intercept('incorrect', 'badCombo')
+ try {
+ await sails.helpers.passwords.checkPassword(password, user.password)
+ } catch (e) {
+ sails.log.error(e.message)
+ throw {
+ badCombo: {
+ problems: [{ login: 'Wrong email/password.' }]
+ }
+ }
+ }
if (rememberMe) {
this.req.session.cookie.maxAge =
diff --git a/templates/mellow-vue/api/controllers/auth/signup.js b/templates/mellow-vue/api/controllers/auth/signup.js
index 95f6e5d1..9bf4d8ae 100644
--- a/templates/mellow-vue/api/controllers/auth/signup.js
+++ b/templates/mellow-vue/api/controllers/auth/signup.js
@@ -30,13 +30,6 @@ module.exports = {
'If this request was sent from a graphical user interface, the request ' +
'parameters should have been validated/coerced _before_ they were sent.'
},
- emailAlreadyInUse: {
- statusCode: 409,
- viewTemplatePath: '500',
- description: 'The email address is no longer available.',
- extendedDescription:
- 'This is an edge case that is not always anticipated by websites and APIs. Since it is pretty rare, the 500 server error page is used as a simple catch-all. If this becomes important in the future, this could easily be expanded into a custom error page or resolution flow. But for context: this behavior of showing the 500 server error page mimics how popular apps like Slack behave under the same circumstances.'
- },
success: {
responseType: 'redirect'
}
@@ -45,26 +38,40 @@ module.exports = {
fn: async function ({ fullName, email: userEmail, password }) {
const email = userEmail.toLowerCase()
- const unverifiedUser = await User.create({
- email,
- password,
- fullName,
- tosAcceptedByIp: this.req.ip,
- emailProofToken: sails.helpers.strings.random('url-friendly'),
- emailProofTokenExpiresAt:
- Date.now() + sails.config.custom.emailProofTokenTTL
- })
- .intercept('E_UNIQUE', 'emailAlreadyInUse')
- .intercept({ name: 'UsageError' }, () => {
+ try {
+ unverifiedUser = await User.create({
+ email,
+ password,
+ fullName,
+ tosAcceptedByIp: this.req.ip,
+ emailProofToken: sails.helpers.strings.random('url-friendly'),
+ emailProofTokenExpiresAt:
+ Date.now() + sails.config.custom.emailProofTokenTTL
+ }).fetch()
+ } catch (error) {
+ if (error.code === 'E_UNIQUE') {
throw {
badSignupRequest: {
problems: [
- `"signup" Apologies, but something went wrong with signing you up. Please try again.`
+ {
+ email: 'An account with this email address already exists.'
+ }
]
}
}
- })
- .fetch()
+ } else if (error.name === 'UsageError') {
+ throw {
+ badSignupRequest: {
+ problems: [
+ {
+ signup:
+ 'Apologies, but something went wrong with signing you up. Please try again.'
+ }
+ ]
+ }
+ }
+ }
+ }
this.req.session.userEmail = unverifiedUser.email
diff --git a/templates/mellow-vue/api/responses/badRequest.js b/templates/mellow-vue/api/responses/badRequest.js
index 06ce0117..142fcd6b 100644
--- a/templates/mellow-vue/api/responses/badRequest.js
+++ b/templates/mellow-vue/api/responses/badRequest.js
@@ -36,34 +36,39 @@ module.exports = function badRequest(optionalData) {
// Check if it's an Inertia request
if (req.header('X-Inertia')) {
- console.log(optionalData)
- if (
- optionalData &&
- optionalData.code &&
- (optionalData.code === 'E_MISSING_OR_INVALID_PARAMS' ||
- optionalData.name === 'UsageError')
- ) {
+ if (optionalData && optionalData.problems) {
const errors = {}
optionalData.problems.forEach((problem) => {
- const regex = /"(.*?)"/
- const matches = problem.match(regex)
+ if (typeof problem === 'object') {
+ Object.keys(problem).forEach((propertyName) => {
+ const sanitizedProblem = problem[propertyName].replace(/\.$/, '') // Trim trailing dot
+ if (!errors[propertyName]) {
+ errors[propertyName] = [sanitizedProblem]
+ } else {
+ errors[propertyName].push(sanitizedProblem)
+ }
+ })
+ } else {
+ const regex = /"(.*?)"/
+ const matches = problem.match(regex)
- if (matches && matches.length > 1) {
- const propertyName = matches[1]
- const sanitizedProblem = problem.replace(/"([^"]+)"/, '$1')
- if (!errors[propertyName]) {
- errors[propertyName] = [sanitizedProblem]
- } else {
- errors[propertyName].push(sanitizedProblem)
+ if (matches && matches.length > 1) {
+ const propertyName = matches[1]
+ const sanitizedProblem = problem
+ .replace(/"([^"]+)"/, '$1')
+ .replace('\n', '')
+ .replace('ยท', '')
+ .trim()
+ if (!errors[propertyName]) {
+ errors[propertyName] = [sanitizedProblem]
+ } else {
+ errors[propertyName].push(sanitizedProblem)
+ }
}
}
})
-
- if (req.session) {
- req.session.errors = errors
- }
-
- return res.status(statusCodeToSet).redirect('back')
+ req.session.errors = errors
+ return res.redirect(303, 'back')
}
}
diff --git a/templates/mellow-vue/assets/js/pages/login.vue b/templates/mellow-vue/assets/js/pages/login.vue
index 20444370..5ac7ec24 100644
--- a/templates/mellow-vue/assets/js/pages/login.vue
+++ b/templates/mellow-vue/assets/js/pages/login.vue
@@ -54,6 +54,13 @@ const disableLoginButton = computed(() => {
Log into your account
Welcome back, please enter your details
+
+ {{ form.errors?.login || form.errors?.email }}
+
+
+
+ {{ form.errors?.signup }}
+