Skip to content

Commit

Permalink
[feat] Add mellow react (#114)
Browse files Browse the repository at this point in the history
* chore(mellow-react): update dependencies

* feat(mellow-react): add routes

* feat(mellow-react): add actions

* feat(mellow-react): add helpers

* feat(mellow-react): add custom hook

* feat(mellow-react): add sails mail

* feat(mellow-react): add mail config

* feat(mellow-vue): use integer id

* fix(mellow-react): add url back

* feat(mellow-react): add User model

* feat(mellow-react): add policies

* feat(mellow-react): register policy mappings

* feat(mellow-react): add test

* feat(mellow-react): update jsconfig

* feat(mellow-react): add progressbar color

* feat(mellow-react): add GoogleButton

* feat(mellow-react): add homepage

* feat(mellow-react): setup tailwindcss

* chore(mellow-vue): revert to earliest version of prettier

* feat(mellow-react): add GoogleButton.jsx

* chore(mellow-react): update prettier-config.js

* chore(mellow-react): remove typedoc

* chore(mellow-vue): remove trailing space

* chore(mellow-vue): remove jsdoc

* chore(mellow-vue): prettier

* chore(mellow-react): prettier

* chore(mellow-react): prettier

* feat(mellow-react): work on InputBase component

* feat(mellow-react): add InputText
Co-authored-by: Bayode Emmanuel <[email protected]>

* feat(mellow-react): add InputBase

* feat(mellow-react): add AppLayout

* feat(mellow-react): update homepage

* feat(mellow-react): rename to testing env

* fix(mellow-react): update environment

* fix(mellow-vue): update type in InputEmail

* feat(mellow-react): add InputEmail component

* feat(mellow-react): add InputPassword

* feat(mellow-react): add InputButton

* fix(mellow-react): remove unneeded markups

* fix(mellow-react): use ternary

* fix(mellow-react): passing in props

* fix(mellow-vue): spread props

* fix(mellow-react): remove manual value and onChange props

* fix(mellow-react): fix not passing the input value

* fix(mellow-react): resolve issue with check box

* feat(mellow-react): submit login form

* feat(mellow-react): add signup page

* feat(mellow-react): add check-email page

* feat(mellow-react): add success page

* feat(mellow-react): add link-expired page

* feat(mellow-vue): abstract style for inputs in input base

* feat(mellow-vue): use components in forgot-password page

* feat(mellow-react): add forgot-password page

* fix(mellow-vue): add email error

* feat(mellow-react): add forgot-password page

* fix(mellow-react): fix wrong endpoint

* feat(mellow-vue): proper usage of components

* feat(mellow-react): add and use label prop

* feat(mellow-react): add forms css

* feat(mellow-react): add rest password page

* fix(mellow-react): remove wrong function call

* chore(mellow-react): remove component that no longer exist

* feat(mellow-react): add dashboard

* feat(mellow-vue): move profile to dashboard

* fix(mellow-vue): fix deprecation for back redirect usage

* chore(mellow-react): install sails-hook-organics

* feat(mellow-react): fix depreaction warning for redirect usage

* fix(mellow-react): rename dashboard to index

* feat(mellow-react): add custom configs

* feat(mellow-react): add email templates

* feat(mellow-vue): go to dashboard/profile

* feat(mellow-vue): remove user/profile.vue

* feat(mellow-vue): remove unused config

* feat(mellow-vue): use confirmPassword instead of passwordConfirmation

* fix(mellow-react): use confirmPassword not passwordConfirmation

* feat(mellow-react): use 'dashboard/profile'

* fix(mellow-react): make it easy to merge classes

* feat(mellow-react): add profile page

* feat(mellow-react): fix issues with updating fields

* fix(mellow-react): changed to undefined for forms

* feat(mellow-vue): remove accessing user details with .value
  • Loading branch information
DominusKelvin authored Sep 20, 2024
1 parent a4dd3bc commit 747847c
Show file tree
Hide file tree
Showing 87 changed files with 4,830 additions and 1,663 deletions.
91 changes: 91 additions & 0 deletions templates/mellow-react/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-react/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: '[email protected]',
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-react/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. "[email protected]".',
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-react/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-react/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-react/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 747847c

Please sign in to comment.