Initial project structure with sails.js
This commit is contained in:
55
api/controllers/account/logout.js
Normal file
55
api/controllers/account/logout.js
Normal file
@ -0,0 +1,55 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Logout',
|
||||
|
||||
|
||||
description: 'Log out of this app.',
|
||||
|
||||
|
||||
extendedDescription:
|
||||
`This action deletes the \`req.session.userId\` key from the session of the requesting user agent.
|
||||
Actual garbage collection of session data depends on this app's session store, and
|
||||
potentially also on the [TTL configuration](https://sailsjs.com/docs/reference/configuration/sails-config-session)
|
||||
you provided for it.
|
||||
|
||||
Note that this action does not check to see whether or not the requesting user was
|
||||
actually logged in. (If they weren't, then this action is just a no-op.)`,
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
description: 'The requesting user agent has been successfully logged out.'
|
||||
},
|
||||
|
||||
redirect: {
|
||||
description: 'The requesting user agent looks to be a web browser.',
|
||||
extendedDescription: 'After logging out from a web browser, the user is redirected away.',
|
||||
responseType: 'redirect'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
// Clear the `userId` property from this session.
|
||||
delete this.req.session.userId;
|
||||
|
||||
// Broadcast a message that we can display in other open tabs.
|
||||
if (sails.hooks.sockets) {
|
||||
await sails.helpers.broadcastSessionChange(this.req);
|
||||
}
|
||||
|
||||
// Then finish up, sending an appropriate response.
|
||||
// > Under the covers, this persists the now-logged-out session back
|
||||
// > to the underlying session store.
|
||||
if (!this.req.wantsJSON) {
|
||||
throw {redirect: '/login'};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
79
api/controllers/account/update-billing-card.js
Normal file
79
api/controllers/account/update-billing-card.js
Normal file
@ -0,0 +1,79 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Update billing card',
|
||||
|
||||
|
||||
description: 'Update the credit card for the logged-in user.',
|
||||
|
||||
|
||||
inputs: {
|
||||
|
||||
stripeToken: {
|
||||
type: 'string',
|
||||
example: 'tok_199k3qEXw14QdSnRwmsK99MH',
|
||||
description: 'The single-use Stripe Checkout token identifier representing the user\'s payment source (i.e. credit card.)',
|
||||
extendedDescription: 'Omit this (or use "") to remove this user\'s payment source.',
|
||||
whereToGet: {
|
||||
description: 'This Stripe.js token is provided to the front-end (client-side) code after completing a Stripe Checkout or Stripe Elements flow.'
|
||||
}
|
||||
},
|
||||
|
||||
billingCardLast4: {
|
||||
type: 'string',
|
||||
example: '4242',
|
||||
description: 'Omit if removing card info.',
|
||||
whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
|
||||
},
|
||||
|
||||
billingCardBrand: {
|
||||
type: 'string',
|
||||
example: 'visa',
|
||||
description: 'Omit if removing card info.',
|
||||
whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
|
||||
},
|
||||
|
||||
billingCardExpMonth: {
|
||||
type: 'string',
|
||||
example: '08',
|
||||
description: 'Omit if removing card info.',
|
||||
whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
|
||||
},
|
||||
|
||||
billingCardExpYear: {
|
||||
type: 'string',
|
||||
example: '2023',
|
||||
description: 'Omit if removing card info.',
|
||||
whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({stripeToken, billingCardLast4, billingCardBrand, billingCardExpMonth, billingCardExpYear}) {
|
||||
|
||||
// Add, update, or remove the default payment source for the logged-in user's
|
||||
// customer entry in Stripe.
|
||||
var stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
|
||||
stripeCustomerId: this.req.me.stripeCustomerId,
|
||||
token: stripeToken || '',
|
||||
}).timeout(5000).retry();
|
||||
|
||||
// Update (or clear) the card info we have stored for this user in our database.
|
||||
// > Remember, never store complete card numbers-- only the last 4 digits + expiration!
|
||||
// > Storing (or even receiving) complete, unencrypted card numbers would require PCI
|
||||
// > compliance in the U.S.
|
||||
await User.updateOne({ id: this.req.me.id })
|
||||
.set({
|
||||
stripeCustomerId,
|
||||
hasBillingCard: stripeToken ? true : false,
|
||||
billingCardBrand: stripeToken ? billingCardBrand : '',
|
||||
billingCardLast4: stripeToken ? billingCardLast4 : '',
|
||||
billingCardExpMonth: stripeToken ? billingCardExpMonth : '',
|
||||
billingCardExpYear: stripeToken ? billingCardExpYear : ''
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
35
api/controllers/account/update-password.js
Normal file
35
api/controllers/account/update-password.js
Normal file
@ -0,0 +1,35 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Update password',
|
||||
|
||||
|
||||
description: 'Update the password for the logged-in user.',
|
||||
|
||||
|
||||
inputs: {
|
||||
|
||||
password: {
|
||||
description: 'The new, unencrypted password.',
|
||||
example: 'abc123v2',
|
||||
required: true
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({password}) {
|
||||
|
||||
// Hash the new password.
|
||||
var hashed = await sails.helpers.passwords.hashPassword(password);
|
||||
|
||||
// Update the record for the logged-in user.
|
||||
await User.updateOne({ id: this.req.me.id })
|
||||
.set({
|
||||
password: hashed
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
160
api/controllers/account/update-profile.js
Normal file
160
api/controllers/account/update-profile.js
Normal file
@ -0,0 +1,160 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Update profile',
|
||||
|
||||
|
||||
description: 'Update the profile for the logged-in user.',
|
||||
|
||||
|
||||
inputs: {
|
||||
|
||||
fullName: {
|
||||
type: 'string'
|
||||
},
|
||||
|
||||
emailAddress: {
|
||||
type: 'string'
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
emailAlreadyInUse: {
|
||||
statusCode: 409,
|
||||
description: 'The provided email address is already in use.',
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({fullName, emailAddress}) {
|
||||
|
||||
var newEmailAddress = emailAddress;
|
||||
if (newEmailAddress !== undefined) {
|
||||
newEmailAddress = newEmailAddress.toLowerCase();
|
||||
}
|
||||
|
||||
// Determine if this request wants to change the current user's email address,
|
||||
// revert her pending email address change, modify her pending email address
|
||||
// change, or if the email address won't be affected at all.
|
||||
var desiredEmailEffect;// ('change-immediately', 'begin-change', 'cancel-pending-change', 'modify-pending-change', or '')
|
||||
if (
|
||||
newEmailAddress === undefined ||
|
||||
(this.req.me.emailStatus !== 'change-requested' && newEmailAddress === this.req.me.emailAddress) ||
|
||||
(this.req.me.emailStatus === 'change-requested' && newEmailAddress === this.req.me.emailChangeCandidate)
|
||||
) {
|
||||
desiredEmailEffect = '';
|
||||
} else if (this.req.me.emailStatus === 'change-requested' && newEmailAddress === this.req.me.emailAddress) {
|
||||
desiredEmailEffect = 'cancel-pending-change';
|
||||
} else if (this.req.me.emailStatus === 'change-requested' && newEmailAddress !== this.req.me.emailAddress) {
|
||||
desiredEmailEffect = 'modify-pending-change';
|
||||
} else if (!sails.config.custom.verifyEmailAddresses || this.req.me.emailStatus === 'unconfirmed') {
|
||||
desiredEmailEffect = 'change-immediately';
|
||||
} else {
|
||||
desiredEmailEffect = 'begin-change';
|
||||
}
|
||||
|
||||
|
||||
// If the email address is changing, make sure it is not already being used.
|
||||
if (_.contains(['begin-change', 'change-immediately', 'modify-pending-change'], desiredEmailEffect)) {
|
||||
let conflictingUser = await User.findOne({
|
||||
or: [
|
||||
{ emailAddress: newEmailAddress },
|
||||
{ emailChangeCandidate: newEmailAddress }
|
||||
]
|
||||
});
|
||||
if (conflictingUser) {
|
||||
throw 'emailAlreadyInUse';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Start building the values to set in the db.
|
||||
// (We always set the fullName if provided.)
|
||||
var valuesToSet = {
|
||||
fullName,
|
||||
};
|
||||
|
||||
switch (desiredEmailEffect) {
|
||||
|
||||
// Change now
|
||||
case 'change-immediately':
|
||||
_.extend(valuesToSet, {
|
||||
emailAddress: newEmailAddress,
|
||||
emailChangeCandidate: '',
|
||||
emailProofToken: '',
|
||||
emailProofTokenExpiresAt: 0,
|
||||
emailStatus: this.req.me.emailStatus === 'unconfirmed' ? 'unconfirmed' : 'confirmed'
|
||||
});
|
||||
break;
|
||||
|
||||
// Begin new email change, or modify a pending email change
|
||||
case 'begin-change':
|
||||
case 'modify-pending-change':
|
||||
_.extend(valuesToSet, {
|
||||
emailChangeCandidate: newEmailAddress,
|
||||
emailProofToken: await sails.helpers.strings.random('url-friendly'),
|
||||
emailProofTokenExpiresAt: Date.now() + sails.config.custom.emailProofTokenTTL,
|
||||
emailStatus: 'change-requested'
|
||||
});
|
||||
break;
|
||||
|
||||
// Cancel pending email change
|
||||
case 'cancel-pending-change':
|
||||
_.extend(valuesToSet, {
|
||||
emailChangeCandidate: '',
|
||||
emailProofToken: '',
|
||||
emailProofTokenExpiresAt: 0,
|
||||
emailStatus: 'confirmed'
|
||||
});
|
||||
break;
|
||||
|
||||
// Otherwise, do nothing re: email
|
||||
}
|
||||
|
||||
// Save to the db
|
||||
await User.updateOne({id: this.req.me.id })
|
||||
.set(valuesToSet);
|
||||
|
||||
// If this is an immediate change, and billing features are enabled,
|
||||
// then also update the billing email for this user's linked customer entry
|
||||
// in the Stripe API to make sure they receive email receipts.
|
||||
// > Note: If there was not already a Stripe customer entry for this user,
|
||||
// > then one will be set up implicitly, so we'll need to persist it to our
|
||||
// > database. (This could happen if Stripe credentials were not configured
|
||||
// > at the time this user was originally created.)
|
||||
if(desiredEmailEffect === 'change-immediately' && sails.config.custom.enableBillingFeatures) {
|
||||
let didNotAlreadyHaveCustomerId = (! this.req.me.stripeCustomerId);
|
||||
let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
|
||||
stripeCustomerId: this.req.me.stripeCustomerId,
|
||||
emailAddress: newEmailAddress
|
||||
}).timeout(5000).retry();
|
||||
if (didNotAlreadyHaveCustomerId){
|
||||
await User.updateOne({ id: this.req.me.id })
|
||||
.set({
|
||||
stripeCustomerId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If an email address change was requested, and re-confirmation is required,
|
||||
// send the "confirm account" email.
|
||||
if (desiredEmailEffect === 'begin-change' || desiredEmailEffect === 'modify-pending-change') {
|
||||
await sails.helpers.sendTemplateEmail.with({
|
||||
to: newEmailAddress,
|
||||
subject: 'Your account has been updated',
|
||||
template: 'email-verify-new-email',
|
||||
templateData: {
|
||||
fullName: fullName||this.req.me.fullName,
|
||||
token: valuesToSet.emailProofToken
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
30
api/controllers/account/view-account-overview.js
Normal file
30
api/controllers/account/view-account-overview.js
Normal file
@ -0,0 +1,30 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View account overview',
|
||||
|
||||
|
||||
description: 'Display "Account Overview" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/account/account-overview',
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
// If billing features are enabled, include our configured Stripe.js
|
||||
// public key in the view locals. Otherwise, leave it as undefined.
|
||||
return {
|
||||
stripePublishableKey: sails.config.custom.enableBillingFeatures? sails.config.custom.stripePublishableKey : undefined,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
26
api/controllers/account/view-edit-password.js
Normal file
26
api/controllers/account/view-edit-password.js
Normal file
@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View edit password',
|
||||
|
||||
|
||||
description: 'Display "Edit password" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/account/edit-password'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
26
api/controllers/account/view-edit-profile.js
Normal file
26
api/controllers/account/view-edit-profile.js
Normal file
@ -0,0 +1,26 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View edit profile',
|
||||
|
||||
|
||||
description: 'Display "Edit profile" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/account/edit-profile',
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
27
api/controllers/dashboard/view-welcome.js
Normal file
27
api/controllers/dashboard/view-welcome.js
Normal file
@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View welcome page',
|
||||
|
||||
|
||||
description: 'Display the dashboard "Welcome" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/dashboard/welcome',
|
||||
description: 'Display the welcome page for authenticated users.'
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
79
api/controllers/deliver-contact-form-message.js
Normal file
79
api/controllers/deliver-contact-form-message.js
Normal file
@ -0,0 +1,79 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Deliver contact form message',
|
||||
|
||||
|
||||
description: 'Deliver a contact form message to the appropriate internal channel(s).',
|
||||
|
||||
|
||||
inputs: {
|
||||
|
||||
emailAddress: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'A return email address where we can respond.',
|
||||
example: 'hermione@hogwarts.edu'
|
||||
},
|
||||
|
||||
topic: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'The topic from the contact form.',
|
||||
example: 'I want to buy stuff.'
|
||||
},
|
||||
|
||||
fullName: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'The full name of the human sending this message.',
|
||||
example: 'Hermione Granger'
|
||||
},
|
||||
|
||||
message: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
description: 'The custom message, in plain text.'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
description: 'The message was sent successfully.'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function({emailAddress, topic, fullName, message}) {
|
||||
|
||||
if (!sails.config.custom.internalEmailAddress) {
|
||||
throw new Error(
|
||||
`Cannot deliver incoming message from contact form because there is no internal
|
||||
email address (\`sails.config.custom.internalEmailAddress\`) configured for this
|
||||
app. To enable contact form emails, you'll need to add this missing setting to
|
||||
your custom config -- usually in \`config/custom.js\`, \`config/staging.js\`,
|
||||
\`config/production.js\`, or via system environment variables.`
|
||||
);
|
||||
}
|
||||
|
||||
await sails.helpers.sendTemplateEmail.with({
|
||||
to: sails.config.custom.internalEmailAddress,
|
||||
subject: 'New contact form message',
|
||||
template: 'internal/email-contact-form',
|
||||
layout: false,
|
||||
templateData: {
|
||||
contactName: fullName,
|
||||
contactEmail: emailAddress,
|
||||
topic,
|
||||
message,
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
160
api/controllers/entrance/confirm-email.js
Normal file
160
api/controllers/entrance/confirm-email.js
Normal file
@ -0,0 +1,160 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Confirm email',
|
||||
|
||||
|
||||
description:
|
||||
`Confirm a new user's email address, or an existing user's request for an email address change,
|
||||
then redirect to either a special landing page (for newly-signed up users), or the account page
|
||||
(for existing users who just changed their email address).`,
|
||||
|
||||
|
||||
inputs: {
|
||||
|
||||
token: {
|
||||
description: 'The confirmation token from the email.',
|
||||
example: '4-32fad81jdaf$329'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
description: 'Email address confirmed and requesting user logged in.'
|
||||
},
|
||||
|
||||
redirect: {
|
||||
description: 'Email address confirmed and requesting user logged in. Since this looks like a browser, redirecting...',
|
||||
responseType: 'redirect'
|
||||
},
|
||||
|
||||
invalidOrExpiredToken: {
|
||||
responseType: 'expired',
|
||||
description: 'The provided token is expired, invalid, or already used up.',
|
||||
},
|
||||
|
||||
emailAddressNoLongerAvailable: {
|
||||
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.',
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({token}) {
|
||||
|
||||
// If no token was provided, this is automatically invalid.
|
||||
if (!token) {
|
||||
throw 'invalidOrExpiredToken';
|
||||
}
|
||||
|
||||
// Get the user with the matching email token.
|
||||
var user = await User.findOne({ emailProofToken: token });
|
||||
|
||||
// If no such user exists, or their token is expired, bail.
|
||||
if (!user || user.emailProofTokenExpiresAt <= Date.now()) {
|
||||
throw 'invalidOrExpiredToken';
|
||||
}
|
||||
|
||||
if (user.emailStatus === 'unconfirmed') {
|
||||
// ┌─┐┌─┐┌┐┌┌─┐┬┬─┐┌┬┐┬┌┐┌┌─┐ ╔═╗╦╦═╗╔═╗╔╦╗ ╔╦╗╦╔╦╗╔═╗ ╦ ╦╔═╗╔═╗╦═╗ ┌─┐┌┬┐┌─┐┬┬
|
||||
// │ │ ││││├┤ │├┬┘││││││││ ┬ ╠╣ ║╠╦╝╚═╗ ║───║ ║║║║║╣ ║ ║╚═╗║╣ ╠╦╝ ├┤ │││├─┤││
|
||||
// └─┘└─┘┘└┘└ ┴┴└─┴ ┴┴┘└┘└─┘ ╚ ╩╩╚═╚═╝ ╩ ╩ ╩╩ ╩╚═╝ ╚═╝╚═╝╚═╝╩╚═ └─┘┴ ┴┴ ┴┴┴─┘
|
||||
// If this is a new user confirming their email for the first time,
|
||||
// then just update the state of their user record in the database,
|
||||
// store their user id in the session (just in case they aren't logged
|
||||
// in already), and then redirect them to the "email confirmed" page.
|
||||
await User.updateOne({ id: user.id }).set({
|
||||
emailStatus: 'confirmed',
|
||||
emailProofToken: '',
|
||||
emailProofTokenExpiresAt: 0
|
||||
});
|
||||
this.req.session.userId = user.id;
|
||||
|
||||
// In case there was an existing session, broadcast a message that we can
|
||||
// display in other open tabs.
|
||||
if (sails.hooks.sockets) {
|
||||
await sails.helpers.broadcastSessionChange(this.req);
|
||||
}
|
||||
|
||||
if (this.req.wantsJSON) {
|
||||
return;
|
||||
} else {
|
||||
throw { redirect: '/email/confirmed' };
|
||||
}
|
||||
|
||||
} else if (user.emailStatus === 'change-requested') {
|
||||
// ┌─┐┌─┐┌┐┌┌─┐┬┬─┐┌┬┐┬┌┐┌┌─┐ ╔═╗╦ ╦╔═╗╔╗╔╔═╗╔═╗╔╦╗ ┌─┐┌┬┐┌─┐┬┬
|
||||
// │ │ ││││├┤ │├┬┘││││││││ ┬ ║ ╠═╣╠═╣║║║║ ╦║╣ ║║ ├┤ │││├─┤││
|
||||
// └─┘└─┘┘└┘└ ┴┴└─┴ ┴┴┘└┘└─┘ ╚═╝╩ ╩╩ ╩╝╚╝╚═╝╚═╝═╩╝ └─┘┴ ┴┴ ┴┴┴─┘
|
||||
if (!user.emailChangeCandidate){
|
||||
throw new Error(`Consistency violation: Could not update Stripe customer because this user record's emailChangeCandidate ("${user.emailChangeCandidate}") is missing. (This should never happen.)`);
|
||||
}
|
||||
|
||||
// Last line of defense: since email change candidates are not protected
|
||||
// by a uniqueness constraint in the database, it's important that we make
|
||||
// sure no one else managed to grab this email in the mean time since we
|
||||
// last checked its availability. (This is a relatively rare edge case--
|
||||
// see exit description.)
|
||||
if (await User.count({ emailAddress: user.emailChangeCandidate }) > 0) {
|
||||
throw 'emailAddressNoLongerAvailable';
|
||||
}
|
||||
|
||||
// If billing features are enabled, also update the billing email for this
|
||||
// user's linked customer entry in the Stripe API to make sure they receive
|
||||
// email receipts.
|
||||
// > Note: If there was not already a Stripe customer entry for this user,
|
||||
// > then one will be set up implicitly, so we'll need to persist it to our
|
||||
// > database. (This could happen if Stripe credentials were not configured
|
||||
// > at the time this user was originally created.)
|
||||
if(sails.config.custom.enableBillingFeatures) {
|
||||
let didNotAlreadyHaveCustomerId = (! user.stripeCustomerId);
|
||||
let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
|
||||
stripeCustomerId: user.stripeCustomerId,
|
||||
emailAddress: user.emailChangeCandidate
|
||||
}).timeout(5000).retry();
|
||||
if (didNotAlreadyHaveCustomerId){
|
||||
await User.updateOne({ id: user.id }).set({
|
||||
stripeCustomerId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Finally update the user in the database, store their id in the session
|
||||
// (just in case they aren't logged in already), then redirect them to
|
||||
// their "my account" page so they can see their updated email address.
|
||||
await User.updateOne({ id: user.id })
|
||||
.set({
|
||||
emailStatus: 'confirmed',
|
||||
emailProofToken: '',
|
||||
emailProofTokenExpiresAt: 0,
|
||||
emailAddress: user.emailChangeCandidate,
|
||||
emailChangeCandidate: '',
|
||||
});
|
||||
this.req.session.userId = user.id;
|
||||
|
||||
// In case there was an existing session, broadcast a message that we can
|
||||
// display in other open tabs.
|
||||
if (sails.hooks.sockets) {
|
||||
await sails.helpers.broadcastSessionChange(this.req);
|
||||
}
|
||||
|
||||
if (this.req.wantsJSON) {
|
||||
return;
|
||||
} else {
|
||||
throw { redirect: '/account' };
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error(`Consistency violation: User ${user.id} has an email proof token, but somehow also has an emailStatus of "${user.emailStatus}"! (This should never happen.)`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
119
api/controllers/entrance/login.js
Normal file
119
api/controllers/entrance/login.js
Normal file
@ -0,0 +1,119 @@
|
||||
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: {
|
||||
|
||||
emailAddress: {
|
||||
description: 'The email to try in this attempt, e.g. "irl@example.com".',
|
||||
type: 'string',
|
||||
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.',
|
||||
extendedDescription:
|
||||
`Note that this is NOT SUPPORTED when using virtual requests (e.g. sending
|
||||
requests over WebSockets instead of HTTP).`,
|
||||
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 \`req.me\`.)`
|
||||
},
|
||||
|
||||
badCombo: {
|
||||
description: `The provided email and password combination does not
|
||||
match any user in the database.`,
|
||||
responseType: 'unauthorized'
|
||||
// ^This uses the custom `unauthorized` response located in `api/responses/unauthorized.js`.
|
||||
// To customize the generic "unauthorized" response across this entire app, change that file
|
||||
// (see api/responses/unauthorized).
|
||||
//
|
||||
// To customize the response for _only this_ action, replace `responseType` with
|
||||
// something else. For example, you might set `statusCode: 498` and change the
|
||||
// implementation below accordingly (see http://sailsjs.com/docs/concepts/controllers).
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({emailAddress, password, rememberMe}) {
|
||||
|
||||
// Look up by the email address.
|
||||
// (note that we lowercase it to ensure the lookup is always case-insensitive,
|
||||
// regardless of which database we're using)
|
||||
var userRecord = await User.findOne({
|
||||
emailAddress: emailAddress.toLowerCase(),
|
||||
});
|
||||
|
||||
// If there was no matching user, respond thru the "badCombo" exit.
|
||||
if(!userRecord) {
|
||||
throw 'badCombo';
|
||||
}
|
||||
|
||||
// If the password doesn't match, then also exit thru "badCombo".
|
||||
await sails.helpers.passwords.checkPassword(password, userRecord.password)
|
||||
.intercept('incorrect', 'badCombo');
|
||||
|
||||
// If "Remember Me" was enabled, then keep the session alive for
|
||||
// a longer amount of time. (This causes an updated "Set Cookie"
|
||||
// response header to be sent as the result of this request -- thus
|
||||
// we must be dealing with a traditional HTTP request in order for
|
||||
// this to work.)
|
||||
if (rememberMe) {
|
||||
if (this.req.isSocket) {
|
||||
sails.log.warn(
|
||||
'Received `rememberMe: true` from a virtual request, but it was ignored\n'+
|
||||
'because a browser\'s session cookie cannot be reset over sockets.\n'+
|
||||
'Please use a traditional HTTP request instead.'
|
||||
);
|
||||
} else {
|
||||
this.req.session.cookie.maxAge = sails.config.custom.rememberMeCookieMaxAge;
|
||||
}
|
||||
}//fi
|
||||
|
||||
// Modify the active session instance.
|
||||
// (This will be persisted when the response is sent.)
|
||||
this.req.session.userId = userRecord.id;
|
||||
|
||||
// In case there was an existing session (e.g. if we allow users to go to the login page
|
||||
// when they're already logged in), broadcast a message that we can display in other open tabs.
|
||||
if (sails.hooks.sockets) {
|
||||
await sails.helpers.broadcastSessionChange(this.req);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
66
api/controllers/entrance/send-password-recovery-email.js
Normal file
66
api/controllers/entrance/send-password-recovery-email.js
Normal file
@ -0,0 +1,66 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Send password recovery email',
|
||||
|
||||
|
||||
description: 'Send a password recovery notification to the user with the specified email address.',
|
||||
|
||||
|
||||
inputs: {
|
||||
|
||||
emailAddress: {
|
||||
description: 'The email address of the alleged user who wants to recover their password.',
|
||||
example: 'rydahl@example.com',
|
||||
type: 'string',
|
||||
required: true
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
description: 'The email address might have matched a user in the database. (If so, a recovery email was sent.)'
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({emailAddress}) {
|
||||
|
||||
// Find the record for this user.
|
||||
// (Even if no such user exists, pretend it worked to discourage sniffing.)
|
||||
var userRecord = await User.findOne({ emailAddress });
|
||||
if (!userRecord) {
|
||||
return;
|
||||
}//•
|
||||
|
||||
// Come up with a pseudorandom, probabilistically-unique token for use
|
||||
// in our password recovery email.
|
||||
var token = await sails.helpers.strings.random('url-friendly');
|
||||
|
||||
// Store the token on the user record
|
||||
// (This allows us to look up the user when the link from the email is clicked.)
|
||||
await User.updateOne({ id: userRecord.id })
|
||||
.set({
|
||||
passwordResetToken: token,
|
||||
passwordResetTokenExpiresAt: Date.now() + sails.config.custom.passwordResetTokenTTL,
|
||||
});
|
||||
|
||||
// Send recovery email
|
||||
await sails.helpers.sendTemplateEmail.with({
|
||||
to: emailAddress,
|
||||
subject: 'Password reset instructions',
|
||||
template: 'email-reset-password',
|
||||
templateData: {
|
||||
fullName: userRecord.fullName,
|
||||
token: token
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
127
api/controllers/entrance/signup.js
Normal file
127
api/controllers/entrance/signup.js
Normal file
@ -0,0 +1,127 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Signup',
|
||||
|
||||
|
||||
description: 'Sign up for a new user account.',
|
||||
|
||||
|
||||
extendedDescription:
|
||||
`This creates a new user record in the database, signs in the requesting user agent
|
||||
by modifying its [session](https://sailsjs.com/documentation/concepts/sessions), and
|
||||
(if emailing with Mailgun is enabled) sends an account verification email.
|
||||
|
||||
If a verification email is sent, the new user's account is put in an "unconfirmed" state
|
||||
until they confirm they are using a legitimate email address (by clicking the link in
|
||||
the account verification message.)`,
|
||||
|
||||
|
||||
inputs: {
|
||||
|
||||
emailAddress: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
isEmail: true,
|
||||
description: 'The email address for the new account, e.g. m@example.com.',
|
||||
extendedDescription: 'Must be a valid email address.',
|
||||
},
|
||||
|
||||
password: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
maxLength: 200,
|
||||
example: 'passwordlol',
|
||||
description: 'The unencrypted password to use for the new account.'
|
||||
},
|
||||
|
||||
fullName: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
example: 'Frida Kahlo de Rivera',
|
||||
description: 'The user\'s full name.',
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
description: 'New user account was created successfully.'
|
||||
},
|
||||
|
||||
invalid: {
|
||||
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.'
|
||||
},
|
||||
|
||||
emailAlreadyInUse: {
|
||||
statusCode: 409,
|
||||
description: 'The provided email address is already in use.',
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({emailAddress, password, fullName}) {
|
||||
|
||||
var newEmailAddress = emailAddress.toLowerCase();
|
||||
|
||||
// Build up data for the new user record and save it to the database.
|
||||
// (Also use `fetch` to retrieve the new ID so that we can use it below.)
|
||||
var newUserRecord = await User.create(_.extend({
|
||||
fullName,
|
||||
emailAddress: newEmailAddress,
|
||||
password: await sails.helpers.passwords.hashPassword(password),
|
||||
tosAcceptedByIp: this.req.ip
|
||||
}, sails.config.custom.verifyEmailAddresses? {
|
||||
emailProofToken: await sails.helpers.strings.random('url-friendly'),
|
||||
emailProofTokenExpiresAt: Date.now() + sails.config.custom.emailProofTokenTTL,
|
||||
emailStatus: 'unconfirmed'
|
||||
}:{}))
|
||||
.intercept('E_UNIQUE', 'emailAlreadyInUse')
|
||||
.intercept({name: 'UsageError'}, 'invalid')
|
||||
.fetch();
|
||||
|
||||
// If billing feaures are enabled, save a new customer entry in the Stripe API.
|
||||
// Then persist the Stripe customer id in the database.
|
||||
if (sails.config.custom.enableBillingFeatures) {
|
||||
let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
|
||||
emailAddress: newEmailAddress
|
||||
}).timeout(5000).retry();
|
||||
await User.updateOne({id: newUserRecord.id})
|
||||
.set({
|
||||
stripeCustomerId
|
||||
});
|
||||
}
|
||||
|
||||
// Store the user's new id in their session.
|
||||
this.req.session.userId = newUserRecord.id;
|
||||
|
||||
// In case there was an existing session (e.g. if we allow users to go to the signup page
|
||||
// when they're already logged in), broadcast a message that we can display in other open tabs.
|
||||
if (sails.hooks.sockets) {
|
||||
await sails.helpers.broadcastSessionChange(this.req);
|
||||
}
|
||||
|
||||
if (sails.config.custom.verifyEmailAddresses) {
|
||||
// Send "confirm account" email
|
||||
await sails.helpers.sendTemplateEmail.with({
|
||||
to: newEmailAddress,
|
||||
subject: 'Please confirm your account',
|
||||
template: 'email-verify-account',
|
||||
templateData: {
|
||||
fullName,
|
||||
token: newUserRecord.emailProofToken
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sails.log.info('Skipping new account email verification... (since `verifyEmailAddresses` is disabled)');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
80
api/controllers/entrance/update-password-and-login.js
Normal file
80
api/controllers/entrance/update-password-and-login.js
Normal file
@ -0,0 +1,80 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Update password and login',
|
||||
|
||||
|
||||
description: 'Finish the password recovery flow by setting the new password and '+
|
||||
'logging in the requesting user, based on the authenticity of their token.',
|
||||
|
||||
|
||||
inputs: {
|
||||
|
||||
password: {
|
||||
description: 'The new, unencrypted password.',
|
||||
example: 'abc123v2',
|
||||
required: true
|
||||
},
|
||||
|
||||
token: {
|
||||
description: 'The password token that was generated by the `sendPasswordRecoveryEmail` endpoint.',
|
||||
example: 'gwa8gs8hgw9h2g9hg29hgwh9asdgh9q34$$$$$asdgasdggds',
|
||||
required: true
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
description: 'Password successfully updated, and requesting user agent is now logged in.'
|
||||
},
|
||||
|
||||
invalidToken: {
|
||||
description: 'The provided password token is invalid, expired, or has already been used.',
|
||||
responseType: 'expired'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({password, token}) {
|
||||
|
||||
if(!token) {
|
||||
throw 'invalidToken';
|
||||
}
|
||||
|
||||
// Look up the user with this reset token.
|
||||
var userRecord = await User.findOne({ passwordResetToken: token });
|
||||
|
||||
// If no such user exists, or their token is expired, bail.
|
||||
if (!userRecord || userRecord.passwordResetTokenExpiresAt <= Date.now()) {
|
||||
throw 'invalidToken';
|
||||
}
|
||||
|
||||
// Hash the new password.
|
||||
var hashed = await sails.helpers.passwords.hashPassword(password);
|
||||
|
||||
// Store the user's new password and clear their reset token so it can't be used again.
|
||||
await User.updateOne({ id: userRecord.id })
|
||||
.set({
|
||||
password: hashed,
|
||||
passwordResetToken: '',
|
||||
passwordResetTokenExpiresAt: 0
|
||||
});
|
||||
|
||||
// Log the user in.
|
||||
// (This will be persisted when the response is sent.)
|
||||
this.req.session.userId = userRecord.id;
|
||||
|
||||
// In case there was an existing session, broadcast a message that we can
|
||||
// display in other open tabs.
|
||||
if (sails.hooks.sockets) {
|
||||
await sails.helpers.broadcastSessionChange(this.req);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
27
api/controllers/entrance/view-confirmed-email.js
Normal file
27
api/controllers/entrance/view-confirmed-email.js
Normal file
@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View confirmed email',
|
||||
|
||||
|
||||
description: 'Display "Confirmed email" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/entrance/confirmed-email'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
// Respond with view.
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
36
api/controllers/entrance/view-forgot-password.js
Normal file
36
api/controllers/entrance/view-forgot-password.js
Normal file
@ -0,0 +1,36 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View forgot password',
|
||||
|
||||
|
||||
description: 'Display "Forgot password" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/entrance/forgot-password',
|
||||
},
|
||||
|
||||
redirect: {
|
||||
description: 'The requesting user is already logged in.',
|
||||
extendedDescription: 'Logged-in users should change their password in "Account settings."',
|
||||
responseType: 'redirect',
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
if (this.req.me) {
|
||||
throw {redirect: '/'};
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
35
api/controllers/entrance/view-login.js
Normal file
35
api/controllers/entrance/view-login.js
Normal file
@ -0,0 +1,35 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View login',
|
||||
|
||||
|
||||
description: 'Display "Login" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/entrance/login',
|
||||
},
|
||||
|
||||
redirect: {
|
||||
description: 'The requesting user is already logged in.',
|
||||
responseType: 'redirect'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
if (this.req.me) {
|
||||
throw {redirect: '/'};
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
57
api/controllers/entrance/view-new-password.js
Normal file
57
api/controllers/entrance/view-new-password.js
Normal file
@ -0,0 +1,57 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View new password',
|
||||
|
||||
|
||||
description: 'Display "New password" page.',
|
||||
|
||||
|
||||
inputs: {
|
||||
|
||||
token: {
|
||||
description: 'The password reset token from the email.',
|
||||
example: '4-32fad81jdaf$329'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/entrance/new-password'
|
||||
},
|
||||
|
||||
invalidOrExpiredToken: {
|
||||
responseType: 'expired',
|
||||
description: 'The provided token is expired, invalid, or has already been used.',
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({token}) {
|
||||
|
||||
// If password reset token is missing, display an error page explaining that the link is bad.
|
||||
if (!token) {
|
||||
sails.log.warn('Attempting to view new password (recovery) page, but no reset password token included in request! Displaying error page...');
|
||||
throw 'invalidOrExpiredToken';
|
||||
}//•
|
||||
|
||||
// Look up the user with this reset token.
|
||||
var userRecord = await User.findOne({ passwordResetToken: token });
|
||||
// If no such user exists, or their token is expired, display an error page explaining that the link is bad.
|
||||
if (!userRecord || userRecord.passwordResetTokenExpiresAt <= Date.now()) {
|
||||
throw 'invalidOrExpiredToken';
|
||||
}
|
||||
|
||||
// Grab token and include it in view locals
|
||||
return {
|
||||
token,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
35
api/controllers/entrance/view-signup.js
Normal file
35
api/controllers/entrance/view-signup.js
Normal file
@ -0,0 +1,35 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View signup',
|
||||
|
||||
|
||||
description: 'Display "Signup" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/entrance/signup',
|
||||
},
|
||||
|
||||
redirect: {
|
||||
description: 'The requesting user is already logged in.',
|
||||
responseType: 'redirect'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
if (this.req.me) {
|
||||
throw {redirect: '/'};
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
27
api/controllers/legal/view-privacy.js
Normal file
27
api/controllers/legal/view-privacy.js
Normal file
@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View privacy',
|
||||
|
||||
|
||||
description: 'Display "Privacy policy" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/legal/privacy'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
// All done.
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
27
api/controllers/legal/view-terms.js
Normal file
27
api/controllers/legal/view-terms.js
Normal file
@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View terms',
|
||||
|
||||
|
||||
description: 'Display "Legal terms" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/legal/terms'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
// All done.
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
32
api/controllers/observe-my-session.js
Normal file
32
api/controllers/observe-my-session.js
Normal file
@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Observe my session',
|
||||
|
||||
|
||||
description: 'Subscribe to the logged-in user\'s session so that you receive socket broadcasts when logged out in another tab.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
description: 'The requesting socket is now subscribed to socket broadcasts about the logged-in user\'s session.',
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({}) {
|
||||
|
||||
if (!this.req.isSocket) {
|
||||
throw new Error('This action is designed for use with the virtual request interpreter (over sockets, not traditional HTTP).');
|
||||
}
|
||||
|
||||
let roomName = `session${_.deburr(this.req.sessionID)}`;
|
||||
sails.sockets.join(this.req, roomName);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
27
api/controllers/view-contact.js
Normal file
27
api/controllers/view-contact.js
Normal file
@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View contact',
|
||||
|
||||
|
||||
description: 'Display "Contact" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/contact'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
// Respond with view.
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
27
api/controllers/view-faq.js
Normal file
27
api/controllers/view-faq.js
Normal file
@ -0,0 +1,27 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View faq',
|
||||
|
||||
|
||||
description: 'Display "FAQ" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/faq'
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
// Respond with view.
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
37
api/controllers/view-homepage-or-redirect.js
Normal file
37
api/controllers/view-homepage-or-redirect.js
Normal file
@ -0,0 +1,37 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'View homepage or redirect',
|
||||
|
||||
|
||||
description: 'Display or redirect to the appropriate homepage, depending on login status.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
statusCode: 200,
|
||||
description: 'Requesting user is a guest, so show the public landing page.',
|
||||
viewTemplatePath: 'pages/homepage'
|
||||
},
|
||||
|
||||
redirect: {
|
||||
responseType: 'redirect',
|
||||
description: 'Requesting user is logged in, so redirect to the internal welcome page.'
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
|
||||
fn: async function () {
|
||||
|
||||
if (this.req.me) {
|
||||
throw {redirect:'/welcome'};
|
||||
}
|
||||
|
||||
return {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
Reference in New Issue
Block a user