diff --git a/website/src/controllers/api-v3/auth.js b/website/src/controllers/api-v3/auth.js index c66b254e9b..673a87927f 100644 --- a/website/src/controllers/api-v3/auth.js +++ b/website/src/controllers/api-v3/auth.js @@ -1,18 +1,50 @@ import validator from 'validator'; +import moment from 'moment'; import passport from 'passport'; import { authWithHeaders } from '../../middlewares/api-v3/auth'; import cron from '../../middlewares/api-v3/cron'; import { NotAuthorized, + NotFound, } from '../../libs/api-v3/errors'; import Q from 'q'; import * as passwordUtils from '../../libs/api-v3/password'; import { model as User } from '../../models/user'; +import { model as Group } from '../../models/group'; import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email'; +import { decrypt } from '../../libs/api-v3/encryption'; + let api = {}; +// When the user signed up after having been invited to a group, invite them automatically to the group +async function _handleGroupInvitation (user, invite) { + // wrapping the code in a try because we don't want it to prevent the user from signing up + // that's why errors are not translated + try { + let {sentAt, id: groupId, inviter} = JSON.parse(decrypt(invite)); + + // check that the invite has not expired (after 7 days) + if (sentAt && moment().subtract(7, 'days').isAfter(sentAt)) { + let err = new Error('Invite expired'); + err.privateData = invite; + throw err; + } + + let group = await Group.getGroup({user, optionalMembership: true, groupId, fields: 'name type'}); + if (!group) throw new NotFound('Group not found.'); + + if (group.type === 'party') { + user.invitations.party = {id: group._id, name: group.name, inviter}; + } else { + user.invitations.guilds.push({id: group._id, name: group.name, inviter}); + } + } catch (err) { + // TODO log errors + } +} + /** * @api {post} /user/auth/local/register Register a new user with email, username and password or attach local auth to a social user * @apiVersion 3.0.0 @@ -84,25 +116,29 @@ api.registerLocal = { }, }; - let savedUser; - if (fbUser) { if (!fbUser.auth.facebook.id) throw new NotAuthorized(res.t('onlySocialAttachLocal')); fbUser.auth.local = newUser; - savedUser = await fbUser.save(); + newUser = fbUser; } else { newUser = new User(newUser); newUser.registeredThrough = req.headers['x-client']; // TODO is this saved somewhere? - savedUser = await newUser.save(); } + // we check for partyInvite for backward compatibility + if (req.query.groupInvite || req.query.partyInvite) { + await _handleGroupInvitation(newUser, req.query.groupInvite || req.query.partyInvite); + } + + let savedUser = await newUser.save(); + if (savedUser.auth.facebook.id) { - res.respond(200, savedUser.auth.local); // TODO make sure this used .toJSON and removes private fields + res.respond(200, savedUser.toJSON().auth.local); // We convert to toJSON to hide private fields } else { res.respond(201, savedUser); } - // Clean previous email preferences + // Clean previous email preferences and send welcome email EmailUnsubscription .remove({email: savedUser.auth.local.email}) .then(() => sendTxnEmail(savedUser, 'welcome')); diff --git a/website/src/controllers/api-v3/groups.js b/website/src/controllers/api-v3/groups.js index 05925d5730..a078092a7e 100644 --- a/website/src/controllers/api-v3/groups.js +++ b/website/src/controllers/api-v3/groups.js @@ -459,7 +459,6 @@ api.removeGroupMember = { }; async function _inviteByUUID (uuid, group, inviter, req, res) { - // TODO: Add Push Notifications let userToInvite = await User.findById(uuid).exec(); if (!userToInvite) { @@ -493,7 +492,6 @@ async function _inviteByUUID (uuid, group, inviter, req, res) { if (userToInvite.preferences.emailNotifications[`invited${groupLabel}`] !== false) { let emailVars = [ {name: 'INVITER', content: inviter.profile.name}, - {name: 'REPLY_TO_ADDRESS', content: inviter.email}, ]; if (group.type === 'guild') { @@ -541,16 +539,16 @@ async function _inviteByEmail (invite, group, inviter, req, res) { userReturnInfo = await _inviteByUUID(userToContact._id, group, inviter, req, res); } else { userReturnInfo = invite.email; - // yeah, it supports guild too but for backward compatibility we'll use partyInvite as query - // TODO absolutely refactor this horrible code - const partyQueryString = JSON.stringify({id: group._id, inviter, name: group.name}); - const encryptedPartyqueryString = encrypt(partyQueryString); - let link = `?partyInvite=${encryptedPartyqueryString}`; + const groupQueryString = JSON.stringify({ + id: group._id, + inviter: inviter._id, + sentAt: Date.now(), // so we can let it expire + }); + let link = `?groupInvite=${encrypt(groupQueryString)}`; let variables = [ {name: 'LINK', content: link}, - {name: 'INVITER', content: inviter || inviter.profile.name}, - {name: 'REPLY_TO_ADDRESS', content: inviter.email}, + {name: 'INVITER', content: inviter.profile.name}, ]; if (group.type === 'guild') { diff --git a/website/src/middlewares/api-v3/locals.js b/website/src/middlewares/api-v3/locals.js index ccb4fc003b..43b4f5f00a 100644 --- a/website/src/middlewares/api-v3/locals.js +++ b/website/src/middlewares/api-v3/locals.js @@ -9,7 +9,6 @@ import { import forceRefresh from './../forceRefresh'; import { tavernQuest } from '../../models/group'; import { mods } from '../../models/user'; -import { decrypt } from '../../libs/api-v3/encryption'; // To avoid stringifying more data then we need, // items from `env` used on the client will have to be specified in this array @@ -59,15 +58,5 @@ export default function locals (req, res, next) { worldDmg: tavernQuest && tavernQuest.extra && tavernQuest.extra.worldDmg || {}, }); - // Put query-string party (& guild but use partyInvite for backward compatibility) - // invitations into session to be handled later - if (req.query.partyInvite) { - try { - req.session.partyInvite = JSON.parse(decrypt(req.query.partyInvite)); - } catch (e) { - // TODO logs - } - } - next(); }