Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] handle validation errors in mellow vue #82

Merged
merged 6 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions templates/mellow-vue/api/controllers/auth/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ password attempt.`,
email: {
description: 'The email to try in this attempt, e.g. "[email protected]".',
type: 'string',
isEmail: true,
required: true
},

Expand Down Expand Up @@ -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'
}
},

Expand All @@ -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 =
Expand Down
49 changes: 28 additions & 21 deletions templates/mellow-vue/api/controllers/auth/signup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Expand All @@ -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

Expand Down
49 changes: 27 additions & 22 deletions templates/mellow-vue/api/responses/badRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
}

Expand Down
9 changes: 8 additions & 1 deletion templates/mellow-vue/assets/js/pages/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ const disableLoginButton = computed(() => {
<h1 class="text-2xl">Log into your account</h1>
<p class="text-lg text-gray">Welcome back, please enter your details</p>
</section>
<p
class="b my-4 rounded-sm border-red-400 bg-red-100 p-4 text-red-500"
v-if="form.errors.email || form.errors.login"
>
{{ form.errors?.login || form.errors?.email }}
</p>

<form
@submit.prevent="form.post('/login')"
class="mb-4 flex flex-col space-y-6"
Expand All @@ -74,7 +81,7 @@ const disableLoginButton = computed(() => {
</svg>
</span>
<input
type="email"
type="text"
id="email"
placeholder="Your email"
class="block w-full rounded-lg border border-gray/50 bg-white py-3 pl-10 pr-3 shadow-sm placeholder:text-lg placeholder:text-gray focus:outline-none focus:ring-1 focus:ring-gray-100"
Expand Down
15 changes: 15 additions & 0 deletions templates/mellow-vue/assets/js/pages/signup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ const disableSignupButton = computed(() => {
Welcome! Please enter your details to sign up
</p>
</section>
<p
class="b my-4 rounded-sm border-red-400 bg-red-100 p-4 text-red-500"
v-if="form.errors.signup"
>
{{ form.errors?.signup }}
</p>
<form
@submit.prevent="form.post('/signup')"
class="mb-4 flex flex-col space-y-6"
Expand Down Expand Up @@ -101,6 +107,9 @@ const disableSignupButton = computed(() => {
class="block w-full rounded-lg border border-gray/50 bg-white py-3 pl-10 pr-3 shadow-sm placeholder:text-lg placeholder:text-gray focus:outline-none focus:ring-1 focus:ring-gray-100"
v-model="form.fullName"
/>
<p class="absolute text-red-500" v-if="form.errors.fullName">
{{ form.errors.fullName }}
</p>
</label>
<label for="email" class="relative block"
><span class="block text-lg">Email</span>
Expand All @@ -124,6 +133,9 @@ const disableSignupButton = computed(() => {
class="block w-full rounded-lg border border-gray/50 bg-white py-3 pl-10 pr-3 shadow-sm placeholder:text-lg placeholder:text-gray focus:outline-none focus:ring-1 focus:ring-gray-100"
v-model="form.email"
/>
<p class="absolute text-red-500" v-if="form.errors.email">
{{ form.errors.email }}
</p>
</label>
<label for="password" class="relative block"
><span class="block text-lg">Password</span>
Expand Down Expand Up @@ -214,6 +226,9 @@ const disableSignupButton = computed(() => {
</svg>
</button>
</span>
<p class="absolute text-red-500" v-if="form.errors.password">
{{ form.errors.password }}
</p>
</label>
<ul class="flex justify-between text-sm">
<li
Expand Down
2 changes: 1 addition & 1 deletion templates/mellow-vue/jsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// silences wrong TS error, we don't compile, we only typecheck
"outDir": "./irrelevant/unused",
"allowJs": true,
"checkJs": true,
"checkJs": false,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
Expand Down
28 changes: 23 additions & 5 deletions templates/mellow-vue/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion templates/mellow-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
"@sailshq/connect-redis": "^3.2.1",
"@sailshq/lodash": "^3.10.3",
"@sailshq/socket.io-redis": "^5.2.0",
"inertia-sails": "^0.1.7",
"inertia-sails": "^0.1.8",
"nodemailer": "^6.9.4",
"sails": "^1.5.2",
"sails-flash": "^0.0.1",
"sails-hook-mail": "^0.0.7",
"sails-hook-organics": "^2.2.2",
"sails-hook-orm": "^4.0.0",
Expand Down
Loading