requestCredential(options, credentialRequestCompleteCallback) { // support both (options, callback) and (callback). if (!credentialRequestCompleteCallback && typeof options === 'function') { credentialRequestCompleteCallback = options; options = {}; } const config = ServiceConfiguration.configurations.findOne({ service: this.name }); if (!config) { if (credentialRequestCompleteCallback) { credentialRequestCompleteCallback(new ServiceConfiguration.ConfigError()); } return; } const credentialToken = Random.secret(); const loginStyle = OAuth._loginStyle(this.name, config, options); const separator = this.authorizePath.indexOf('?') !== -1 ? '&' : '?'; const loginUrl = `${ this.authorizePath }${ separator }client_id=${ config.clientId }&redirect_uri=${ OAuth._redirectUri(this.name, config) }&response_type=code` + `&state=${ OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl) }&scope=${ this.scope }`; OAuth.launchLogin({ loginService: this.name, loginStyle, loginUrl, credentialRequestCompleteCallback, credentialToken, popupOptions: { width: 900, height: 450, }, }); }
Accounts.onCreateUser((options, user) => { const shopId = Reaction.getShopId(); // current shop; not primary shop const groupToAddUser = options.groupId; const roles = {}; const additionals = { name: options && options.name, profile: Object.assign({}, options && options.profile) }; if (!user.emails) user.emails = []; // init default user roles // we won't create users unless we have a shop. if (shopId) { // retain language when user has defined a language // perhaps should be treated as additionals // or in onLogin below, or in the anonymous method options if (!(Meteor.users.find().count() === 0)) { // dont set on inital admin if (!user.profile) user.profile = {}; const currentUser = Meteor.user(user); if (currentUser && currentUser.profile) { if (currentUser.profile.lang && !user.profile.lang) { user.profile.lang = currentUser.profile.lang; } if (currentUser.profile.currency && !user.profile.currency) { user.profile.currency = currentUser.profile.currency; } } } // if we don't have user.services we're an anonymous user if (!user.services) { const group = Collections.Groups.findOne({ slug: "guest", shopId }); const defaultGuestRoles = group.permissions; // if no defaultGuestRoles retrieved from DB, use the default Reaction set roles[shopId] = defaultGuestRoles || Reaction.defaultVisitorRoles; additionals.groups = [group._id]; } else { let group; if (groupToAddUser) { group = Collections.Groups.findOne({ _id: groupToAddUser, shopId }); } else { group = Collections.Groups.findOne({ slug: "customer", shopId }); } // if no group or customer permissions retrieved from DB, use the default Reaction customer set roles[shopId] = group.permissions || Reaction.defaultCustomerRoles; additionals.groups = [group._id]; // also add services with email defined to user.emails[] const userServices = user.services; for (const service in userServices) { if ({}.hasOwnProperty.call(userServices, service)) { const serviceObj = userServices[service]; if (serviceObj.email) { const email = { provides: "default", address: serviceObj.email, verified: true }; user.emails.push(email); } if (serviceObj.name) { user.username = serviceObj.name; additionals.profile.name = serviceObj.name; } // TODO: For now we have here instagram, twitter and google avatar cases // need to make complete list if (serviceObj.picture) { additionals.profile.picture = user.services[service].picture; } else if (serviceObj.profile_image_url_https) { additionals.profile.picture = user.services[service].dprofile_image_url_https; } else if (serviceObj.profile_picture) { additionals.profile.picture = user.services[service].profile_picture; } // Correctly map Instagram profile data to Meteor user / Accounts if (userServices.instagram) { user.username = serviceObj.username; user.name = serviceObj.full_name; additionals.name = serviceObj.full_name; additionals.profile.picture = serviceObj.profile_picture; additionals.profile.bio = serviceObj.bio; additionals.profile.name = serviceObj.full_name; additionals.profile.username = serviceObj.username; } } } } // clone before adding roles const account = Object.assign({}, user, additionals); account.userId = user._id; Collections.Accounts.insert(account); Hooks.Events.run("afterAccountsInsert", account.userId, user._id); const userDetails = Collections.Accounts.findOne({ _id: user._id }); // send a welcome email to new users, // but skip the first default admin user and anonymous users // (default admins already get a verification email) if (userDetails.emails && userDetails.emails.length > 0 && (!(Meteor.users.find().count() === 0) && !userDetails.profile.invited)) { const token = Random.secret(); Meteor.call("accounts/sendWelcomeEmail", shopId, user._id, token); const defaultEmail = userDetails.emails.find((email) => email.provides === "default"); const when = new Date(); const tokenObj = { address: defaultEmail.address, token, when }; _.set(user, "services.email.verificationTokens", [tokenObj]); } // assign default user roles user.roles = roles; // run onCreateUser hooks // (the user object must be returned by all callbacks) const userDoc = Hooks.Events.run("onCreateUser", user, options); return userDoc; } });
export async function sendUpdatedVerificationEmail(userId, email) { // Make sure the user exists, and email is one of their addresses. const user = Meteor.users.findOne(userId); if (!user) { Logger.error("sendVerificationEmail - User not found"); throw new Meteor.Error("not-found", "User not found"); } let address = email; // pick the first unverified address if no address provided. if (!email) { const unverifiedEmail = _.find(user.emails || [], (e) => !e.verified) || {}; ({ address } = unverifiedEmail); if (!address) { const msg = "No unverified email addresses found."; Logger.error(msg); throw new Meteor.Error("not-found", msg); } } // make sure we have a valid address if (!address || !user.emails || !(user.emails.map((mailInfo) => mailInfo.address).includes(address))) { const msg = "Email not found for user"; Logger.error(msg); throw new Meteor.Error("not-found", msg); } const token = Random.secret(); const when = new Date(); const tokenObj = { token, address, when }; Meteor.users.update({ _id: userId }, { $push: { "services.email.verificationTokens": tokenObj } }); const shopName = Reaction.getShopName(); const url = Accounts.urls.verifyEmail(token); const copyrightDate = new Date().getFullYear(); const dataForEmail = { // Reaction Information contactEmail: "*****@*****.**", homepage: Meteor.absoluteUrl(), emailLogo: `${Meteor.absoluteUrl()}resources/placeholder.gif`, copyrightDate, legalName: "Reaction Commerce", physicalAddress: { address: "2110 Main Street, Suite 207", city: "Santa Monica", region: "CA", postal: "90405" }, shopName, socialLinks: { facebook: { link: "https://www.facebook.com/reactioncommerce" }, github: { link: "https://github.com/reactioncommerce/reaction" }, instagram: { link: "https://instagram.com/reactioncommerce" }, twitter: { link: "https://www.twitter.com/getreaction" } }, confirmationUrl: url, userEmailAddress: address }; if (!Reaction.Email.getMailUrl()) { Logger.warn(` *************************************************** IMPORTANT! EMAIL VERIFICATION LINK Email sending is not configured. Go to the following URL to verify email: ${address} ${url} *************************************************** `); } const tpl = "accounts/verifyUpdatedEmail"; const subject = "accounts/verifyUpdatedEmail/subject"; SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl)); SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl)); return Reaction.Email.send({ to: address, from: Reaction.getShopEmail(), subject: SSR.render(subject, dataForEmail), html: SSR.render(tpl, dataForEmail) }); }
export async function sendResetPasswordEmail(userId, optionalEmail) { // Make sure the user exists, and email is one of their addresses. const user = Meteor.users.findOne(userId); if (!user) { Logger.error("sendResetPasswordEmail - User not found"); throw new Meteor.Error("not-found", "User not found"); } let email = optionalEmail; // pick the first email if we weren't passed an email. if (!optionalEmail && user.emails && user.emails[0]) { email = user.emails[0].address; } // make sure we have a valid email if (!email || !user.emails || !user.emails.map((mailInfo) => mailInfo.address).includes(email)) { Logger.error("sendResetPasswordEmail - Email not found"); throw new Meteor.Error("not-found", "Email not found"); } // Create token for password reset const token = Random.secret(); const when = new Date(); const tokenObj = { token, email, when }; Meteor.users.update(userId, { $set: { "services.password.reset": tokenObj } }); Meteor._ensure(user, "services", "password").reset = tokenObj; // Get shop data for email display const shop = Shops.findOne(Reaction.getShopId()); const emailLogo = Reaction.Email.getShopLogo(shop); const copyrightDate = new Date().getFullYear(); const dataForEmail = { // Shop Data shop, contactEmail: shop.emails[0].address, homepage: Meteor.absoluteUrl(), emailLogo, copyrightDate, legalName: _.get(shop, "addressBook[0].company"), physicalAddress: { address: `${_.get(shop, "addressBook[0].address1")} ${_.get(shop, "addressBook[0].address2")}`, city: _.get(shop, "addressBook[0].city"), region: _.get(shop, "addressBook[0].region"), postal: _.get(shop, "addressBook[0].postal") }, shopName: shop.name, socialLinks: { display: true, facebook: { display: true, icon: `${Meteor.absoluteUrl()}resources/email-templates/facebook-icon.png`, link: "https://www.facebook.com" }, googlePlus: { display: true, icon: `${Meteor.absoluteUrl()}resources/email-templates/google-plus-icon.png`, link: "https://plus.google.com" }, twitter: { display: true, icon: `${Meteor.absoluteUrl()}resources/email-templates/twitter-icon.png`, link: "https://www.twitter.com" } }, // Account Data passwordResetUrl: Accounts.urls.resetPassword(token), user }; // Compile Email with SSR const tpl = "accounts/resetPassword"; const subject = "accounts/resetPassword/subject"; SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl)); SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl)); return Reaction.Email.send({ to: email, from: Reaction.getShopEmail(), subject: SSR.render(subject, dataForEmail), html: SSR.render(tpl, dataForEmail) }); }
_.each(entry.body.recipients, (recipientId) => { if (!concluded[recipientId]) { var mail = null; var unsubToken = null; try { const user = Meteor.users.findOne(recipientId); if (!user) { throw "User not found for ID '" + recipientId + "'"; } if (user.notifications === false) { throw "User wishes to not receive notifications"; } if (!user.emails || !user.emails[0] || !user.emails[0].address) { throw "Recipient has no email address registered"; } var email = user.emails[0]; var address = email.address; var username = user.username; var userLocale = user.profile && user.profile.locale || 'en'; var siteName = Accounts.emailTemplates.siteName; var subjectPrefix = '['+siteName+'] '; unsubToken = Random.secret(); var vars = model.vars(userLocale, user); const fromAddress = vars.fromAddress || Accounts.emailTemplates.from; vars.unsubLink = Router.url('profile.unsubscribe', { token: unsubToken }); vars.siteName = siteName; vars.locale = userLocale; vars.username = username; vars.logo = logo('mails/logo.png'); var message = SSR.render(model.template, vars); // Template can't handle DOCTYPE header, so we add the thing here. var DOCTYPE = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'; message = DOCTYPE + message; mail = { from: fromAddress , sender: Accounts.emailTemplates.from , to: address , subject: subjectPrefix + vars.subject , html: message , attachments: [ vars.logo.attachement ] }; Email.send(mail); Notification.SendResult.record(entry, unsubToken, true, recipientId, mail, "success"); } catch(e) { var reason = e; if (typeof e == 'object' && 'toJSON' in e) reason = e.toJSON(); Notification.SendResult.record(entry, unsubToken, false, recipientId, mail, reason); } } });
import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { Accounts } from 'meteor/accounts-base'; Meteor.methods({ 'personalAccessTokens:generateToken'({ tokenName }) { if (!Meteor.userId()) { throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'personalAccessTokens:generateToken' }); } if (!RocketChat.authz.hasPermission(Meteor.userId(), 'create-personal-access-tokens')) { throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'personalAccessTokens:generateToken' }); } const token = Random.secret(); const tokenExist = RocketChat.models.Users.findPersonalAccessTokenByTokenNameAndUserId({ userId: Meteor.userId(), tokenName, }); if (tokenExist) { throw new Meteor.Error('error-token-already-exists', 'A token with this name already exists', { method: 'personalAccessTokens:generateToken' }); } RocketChat.models.Users.addPersonalAccessTokenToUser({ userId: Meteor.userId(), loginTokenObject: { hashedToken: Accounts._hashLoginToken(token), type: 'personalAccessToken', createdAt: new Date(), lastTokenPart: token.slice(-6), name: tokenName, },
export function sendResetPasswordEmail(userId, optionalEmail) { // Make sure the user exists, and email is one of their addresses. const user = Meteor.users.findOne(userId); if (!user) { Logger.error("sendResetPasswordEmail - User not found"); throw new Meteor.Error("user-not-found", "User not found"); } let email = optionalEmail; // pick the first email if we weren't passed an email. if (!optionalEmail && user.emails && user.emails[0]) { email = user.emails[0].address; } // make sure we have a valid email if (!email || !user.emails || !user.emails.map((mailInfo) => mailInfo.address).includes(email)) { Logger.error("sendResetPasswordEmail - Email not found"); throw new Meteor.Error("email-not-found", "Email not found"); } // Create token for password reset const token = Random.secret(); const when = new Date(); const tokenObj = { token, email, when }; Meteor.users.update(userId, { $set: { "services.password.reset": tokenObj } }); Meteor._ensure(user, "services", "password").reset = tokenObj; // Get shop data for email display const shop = Shops.findOne(Reaction.getShopId()); // Get shop logo, if available. If not, use default logo from file-system let emailLogo; if (Array.isArray(shop.brandAssets)) { const brandAsset = _.find(shop.brandAssets, (asset) => asset.type === "navbarBrandImage"); const mediaId = Media.findOne(brandAsset.mediaId); emailLogo = path.join(Meteor.absoluteUrl(), mediaId.url()); } else { emailLogo = Meteor.absoluteUrl() + "resources/email-templates/shop-logo.png"; } const dataForEmail = { // Shop Data shop: shop, contactEmail: shop.emails[0].address, homepage: Meteor.absoluteUrl(), emailLogo: emailLogo, copyrightDate: moment().format("YYYY"), legalName: shop.addressBook[0].company, physicalAddress: { address: shop.addressBook[0].address1 + " " + shop.addressBook[0].address2, city: shop.addressBook[0].city, region: shop.addressBook[0].region, postal: shop.addressBook[0].postal }, shopName: shop.name, socialLinks: { display: true, facebook: { display: true, icon: Meteor.absoluteUrl() + "resources/email-templates/facebook-icon.png", link: "https://www.facebook.com" }, googlePlus: { display: true, icon: Meteor.absoluteUrl() + "resources/email-templates/google-plus-icon.png", link: "https://plus.google.com" }, twitter: { display: true, icon: Meteor.absoluteUrl() + "resources/email-templates/twitter-icon.png", link: "https://www.twitter.com" } }, // Account Data passwordResetUrl: Accounts.urls.resetPassword(token), user: user }; // Compile Email with SSR const tpl = "accounts/resetPassword"; const subject = "accounts/resetPassword/subject"; SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl)); SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl)); return Reaction.Email.send({ to: email, from: Reaction.getShopEmail(), subject: SSR.render(subject, dataForEmail), html: SSR.render(tpl, dataForEmail) }); }
Template.sqrlButtons.onCreated(function sqrlButtonsOnCreated() { this.sqrlNonce = Random.secret(); });
let options = {}; options = Hooks.Events.run("beforeCreateDefaultAdminUser", options); // If $REACTION_SECURE_DEFAULT_ADMIN is set to "true" on first run, // a random email/password will be generated instead of using the // default email and password (email: admin@localhost pw: r3@cti0n) // and the new admin user will need to verify their email to log in. // If a random email and password are generated, the console will be // the only place to retrieve them. // If the admin email/password is provided via environment or Meteor settings, // the $REACTION_SECURE_DEFAULT_ADMIN will only enforce the email validation part. const isSecureSetup = process.env.REACTION_SECURE_DEFAULT_ADMIN === "true"; // generate default values to use if none are supplied const defaultEmail = isSecureSetup ? `${Random.id(8).toLowerCase()}@localhost` : "admin@localhost"; const defaultPassword = isSecureSetup ? Random.secret(8) : "r3@cti0n"; const defaultUsername = "******"; const defaultName = "Admin"; // Process environment variables and Meteor settings for initial user config. // If ENV variables are set, they always override Meteor settings (settings.json). // This is to allow for testing environments where we don't want to use users configured in a settings file. const env = process.env; let configureEnv = false; if (env.REACTION_EMAIL && env.REACTION_AUTH) { configureEnv = true; Logger.info("Using environment variables to create admin user"); } // defaults use either env or generated values