Skip to content

Commit

Permalink
feat(mellow-svelte): add actions
Browse files Browse the repository at this point in the history
  • Loading branch information
DominusKelvin committed Sep 22, 2024
1 parent 1027392 commit 9857406
Show file tree
Hide file tree
Showing 22 changed files with 878 additions and 28 deletions.
91 changes: 91 additions & 0 deletions templates/mellow-svelte/api/controllers/auth/callback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
module.exports = {
friendlyName: 'Callback',

description: 'Callback auth.',

inputs: {
provider: {
isIn: ['google'],
required: true
},
code: {
type: 'string',
required: true
}
},

exits: {
success: {
responseType: 'redirect'
}
},
fn: async function ({ code, provider }, exits) {
const req = this.req
const googleUser = await sails.wish.provider(provider).user(code)

User.findOrCreate(
{ or: [{ googleId: googleUser.id }, { email: googleUser.email }] },
{
googleId: googleUser.id,
email: googleUser.email,
fullName: googleUser.name,
googleAvatarUrl: googleUser.picture,
googleAccessToken: googleUser.accessToken,
googleIdToken: googleUser.idToken,
emailStatus: googleUser.verified_email ? 'verified' : 'unverified'
}
).exec(async (error, user, wasCreated) => {
if (error) throw error

if (!wasCreated && googleUser.verified_email) {
await User.updateOne({ id: user.id }).set({
emailStatus: 'verified'
})
}
if (!wasCreated && user.googleId !== googleUser.id) {
// Checks if the user email has changed since last log in
// And then update the email change candidate which will be used be used to prompt the user to update their email
await User.updateOne({ id: user.id }).set({
emailChangeCandidate: googleUser.email
})
}
if (!wasCreated && user.email !== googleUser.email) {
// Checks if the user email has changed since last log in
// And then update the email change candidate which will be used be used to prompt the user to update their email
await User.updateOne({ id: user.id }).set({
emailChangeCandidate: googleUser.email
})
}

// Checks if the user name has changed since last log in
// And then update the name if changed
if (!wasCreated && user.fullName !== googleUser.name) {
await User.updateOne({ id: user.id }).set({
fullName: googleUser.name
})
}

if (!wasCreated && user.googleAvatarUrl !== googleUser.picture) {
await User.updateOne({ id: user.id }).set({
googleAvatarUrl: googleUser.picture
})
}

if (!wasCreated && user.googleAccessToken !== googleUser.accessToken) {
await User.updateOne({ id: user.id }).set({
googleAccessToken: googleUser.accessToken
})
}

if (!wasCreated && user.googleIdToken !== googleUser.idToken) {
await User.updateOne({ id: user.id }).set({
googleIdToken: googleUser.idToken
})
}

req.session.userId = user.id
const urlToRedirectTo = '/dashboard'
return exits.success(urlToRedirectTo)
})
}
}
53 changes: 53 additions & 0 deletions templates/mellow-svelte/api/controllers/auth/forgot-password.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module.exports = {
friendlyName: 'Forgot password',

description:
'Send a password recovery notification to the user with the specified email address.',

inputs: {
email: {
description:
'The email address of the alleged user who wants to recover their password.',
example: 'kelvin@boringstack.com',
type: 'string',
required: true,
isEmail: true
}
},

exits: {
success: {
description:
'The email address might have matched a user in the database. (If so, a recovery email was sent.)',
responseType: 'redirect'
}
},

fn: async function ({ email }) {
const userExists = await User.count({ email: this.req.session.userEmail })
if (!userExists) {
return '/check-email'
}

const token = await sails.helpers.strings.random('url-friendly')

const user = await User.updateOne({ email }).set({
passwordResetToken: token,
passwordResetTokenExpiresAt:
Date.now() + sails.config.custom.passwordResetTokenTTL
})

await sails.helpers.mail.send.with({
to: user.email,
subject: 'Password reset instructions',
template: 'email-reset-password',
templateData: {
fullName: user.fullName,
token
}
})

this.req.session.userEmail = user.email
return '/check-email'
}
}
81 changes: 81 additions & 0 deletions templates/mellow-svelte/api/controllers/auth/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module.exports = {
friendlyName: 'Login',

description: 'Log in using the provided email and password combination.',

extendedDescription: `This action attempts to look up the user record in the database with the
specified email address. Then, if such a user exists, it uses
bcrypt to compare the hashed password from the database with the provided
password attempt.`,

inputs: {
email: {
description: 'The email to try in this attempt, e.g. "irl@example.com".',
type: 'string',
isEmail: true,
required: true
},

password: {
description:
'The unencrypted password to try in this attempt, e.g. "passwordlol".',
type: 'string',
required: true
},

rememberMe: {
description: "Whether to extend the lifetime of the user's session.",
type: 'boolean'
}
},

exits: {
success: {
description: 'The requesting user agent has been successfully logged in.',
extendedDescription: `Under the covers, this stores the id of the logged-in user in the session
as the \`userId\` key. The next time this user agent sends a request, assuming
it includes a cookie (like a web browser), Sails will automatically make this
user id available as req.session.userId in the corresponding action. (Also note
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'
}
},

fn: async function ({ email, password, rememberMe }) {
const user = await User.findOne({
email: email.toLowerCase()
})

if (!user) {
throw {
badCombo: {
problems: [{ login: 'Wrong email/password.' }]
}
}
}

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 =
sails.config.custom.rememberMeCookieMaxAge
}

this.req.session.userId = user.id
return '/dashboard'
}
}
22 changes: 22 additions & 0 deletions templates/mellow-svelte/api/controllers/auth/redirect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = {
friendlyName: 'Redirect',

description: 'Redirect auth.',

inputs: {
provider: {
isIn: ['google'],
required: true
}
},

exits: {
success: {
responseType: 'redirect'
}
},

fn: async function ({ provider }) {
return sails.wish.provider(provider).redirect()
}
}
44 changes: 44 additions & 0 deletions templates/mellow-svelte/api/controllers/auth/resend-link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module.exports = {
friendlyName: 'Resend link',

description: '',

inputs: {},

exits: {
success: {
responseType: 'redirect'
},
userNotFound: {
responseType: 'notFound'
}
},

fn: async function () {
const userExists = await User.count({ email: this.req.session.userEmail })
if (!userExists) {
return '/check-email'
}
const unverifiedUser = await User.updateOne(this.req.session.userEmail).set(
{
emailStatus: 'unverified',
emailProofToken: sails.helpers.strings.random('url-friendly'),
emailProofTokenExpiresAt:
Date.now() + sails.config.custom.emailProofTokenTTL
}
)

this.req.session.userId = unverifiedUser.id

await sails.helpers.mail.send.with({
subject: 'Verify your email',
template: 'email-verify-account',
to: unverifiedUser.email,
templateData: {
token: unverifiedUser.emailProofToken,
fullName: unverifiedUser.fullName
}
})
return '/check-email'
}
}
58 changes: 58 additions & 0 deletions templates/mellow-svelte/api/controllers/auth/reset-password.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module.exports = {
friendlyName: 'Reset password',

description: '',

inputs: {
token: {
description: 'The verification token from the email.',
example: 'lyCap0N9i8wKYz7rhrEPog'
},
password: {
type: 'string',
required: true,
minLength: 8
}
},

exits: {
success: {
responseType: 'redirect'
},
invalidOrExpiredToken: {
responseType: 'expired',
description: 'The provided token is expired, invalid, or already used up.'
},
badSignupRequest: {
responseType: 'badRequest',
description:
'The provided fullName, password and/or email address are invalid.',
extendedDescription:
'If this request was sent from a graphical user interface, the request ' +
'parameters should have been validated/coerced _before_ they were sent.'
}
},

fn: async function ({ token, password }) {
if (!token) {
throw 'invalidOrExpiredToken'
}

const user = await User.findOne({ passwordResetToken: token })

if (!user || user.passwordResetTokenExpiresAt <= Date.now()) {
throw 'invalidOrExpiredToken'
}
await User.updateOne({ id: user.id }).set({
password,
passwordResetToken: '',
passwordResetTokenExpiresAt: 0
})

this.req.session.userId = user.id

delete this.req.session.userEmail

return '/reset-password/success'
}
}
Loading

0 comments on commit 9857406

Please sign in to comment.