Skip to content
This repository was archived by the owner on Oct 11, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions api/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,11 @@ const init = () => {
const githubUsername =
profile.username || profile._json.login || fallbackUsername;

const existingUserWithProviderId = await getUserByIndex(
'githubProviderId',
profile.id
);

if (req.user) {
// if a user exists in the request body, it means the user is already
// authed and is trying to connect a github account. Before we do so
Expand All @@ -302,7 +307,6 @@ const init = () => {
// 2. The providerId returned from GitHub isnt' being used by another user

// 1
// if the user already has a githubProviderId, don't override it
if (req.user.githubProviderId) {
/*
Update the cached content of the github profile that we store
Expand Down Expand Up @@ -333,11 +337,6 @@ const init = () => {
return done(null, req.user);
}

const existingUserWithProviderId = await getUserByIndex(
'githubProviderId',
profile.id
);

// 2
// if no user exists with this provider id, it's safe to save on the req.user's object
if (!existingUserWithProviderId) {
Expand All @@ -359,7 +358,10 @@ const init = () => {

// if a user exists with this provider id, don't do anything and return
if (existingUserWithProviderId) {
return done(null, req.user);
return done(null, req.user, {
message:
'Your GitHub account is already linked to another Spectrum profile.',
});
}
}

Expand Down
133 changes: 66 additions & 67 deletions api/routes/api/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@ import {
} from '../../models/community';
import { getChannelsByCommunity } from '../../models/channel';

const rootRedirect = IS_PROD
? `https://spectrum.chat`
: `http://localhost:3000`;

// $FlowIssue
emailRouter.get('/unsubscribe', (req, res) => {
const { token } = req.query;

// if no token was provided
if (!token) return res.status(400).send('No token provided to unsubscribe.');
if (!token)
return res.redirect(
`${rootRedirect}/me/settings?toastType=error&toastMessage=No token provided to unsubscribe.`
);

// verify that the token signature matches our env signature
let decoded;
Expand All @@ -38,19 +45,19 @@ emailRouter.get('/unsubscribe', (req, res) => {
errMessage =
'This unsubscribe token is invalid. You can unsubscribe from this email type in your user settings.';
}
return res.status(400).send(errMessage);
return res.redirect(
`${rootRedirect}/me/settings?toastType=error&toastMessage=${errMessage}`
);
}

// once the token is verified, we can decode it to get the userId and type
const { userId, type, dataId } = jwt.decode(token);

// if the token doesn't have the necessary info
if (!userId || !type) {
return res
.status(400)
.send(
'We were not able to verify this request to unsubscribe. You can unsubscribe from this email type in your users settings.'
);
return res.redirect(
`${rootRedirect}/me/settings?toastType=error&toastMessage=We were not able to verify this request to unsubscribe. You can unsubscribe from this email type in your users settings.`
);
}

// and send a database request to unsubscribe from a particular email type
Expand All @@ -62,17 +69,15 @@ emailRouter.get('/unsubscribe', (req, res) => {
case 'newMessageInThreads':
case 'newDirectMessage':
return unsubscribeUserFromEmailNotification(userId, type).then(() =>
res
.status(200)
.send('You have been successfully unsubscribed from this email.')
res.redirect(
`${rootRedirect}/me/settings?toastType=success&toastMessage=You have been successfully unsubscribed from this email.`
)
);
case 'muteChannel':
return toggleUserChannelNotifications(userId, dataId, false).then(() =>
res
.status(200)
.send(
'You will no longer receive new thread emails from this channel.'
)
res.redirect(
`${rootRedirect}/me/settings?toastType=success&toastMessage=You will no longer receive new thread emails from this channel.`
)
);
case 'muteCommunity':
return getChannelsByCommunity(dataId)
Expand All @@ -81,49 +86,41 @@ emailRouter.get('/unsubscribe', (req, res) => {
channels.map(c => toggleUserChannelNotifications(userId, c, false))
)
.then(() =>
res
.status(200)
.send(
'You will no longer receive new thread emails from this community.'
)
res.redirect(
`${rootRedirect}/me/settings?toastType=success&toastMessage=You will no longer receive new thread emails from this community.`
)
);
case 'muteThread':
return updateThreadNotificationStatusForUser(
dataId,
userId,
false
).then(() =>
res
.status(200)
.send(
'You will no longer receive emails about new messages in this thread.'
)
res.redirect(
`${rootRedirect}/me/settings?toastType=success&toastMessage=You will no longer receive emails about new messages in this thread.`
)
);
case 'muteDirectMessageThread':
return updateDirectMessageThreadNotificationStatusForUser(
dataId,
userId,
false
).then(() =>
res
.status(200)
.send(
'You will no longer receive emails about new messages in this direct message conversation.'
)
res.redirect(
`${rootRedirect}/me/settings?toastType=success&toastMessage=You will no longer receive emails about new messages in this direct message conversation.`
)
);
default: {
return res
.status(400)
.send("We couldn't identify this type of email to unsubscribe.");
return res.redirect(
`${rootRedirect}/me/settings?toastType=error&toastMessage=We couldn't identify this type of email to unsubscribe.`
);
}
}
} catch (err) {
console.error(err);
return res
.status(400)
.send(
'We ran into an issue unsubscribing you from this email. You can always unsubscribe from this email type in your user settings, or get in touch with us at hi@spectrum.chat.'
);
return res.redirect(
`${rootRedirect}/me/settings?toastType=error&toastMessage=We ran into an issue unsubscribing you from this email. You can always unsubscribe from this email type in your user settings, or get in touch with us at hi@spectrum.chat.`
);
}
});

Expand All @@ -133,7 +130,9 @@ emailRouter.get('/validate', (req, res) => {

// if no token was provided
if (!token)
return res.status(400).send('No token provided to validate this email.');
return res.redirect(
`${rootRedirect}?toastType=error&toastMessage=No token provided to validate this email.`
);

// verify that the token signature matches our env signature
let decoded;
Expand All @@ -149,19 +148,19 @@ emailRouter.get('/validate', (req, res) => {
errMessage =
'This unsubscribe token is invalid. You can re-enter your email address in your user settings to resend a confirmation email.';
}
return res.status(400).send(errMessage);
return res.redirect(
`${rootRedirect}/me/settings?toastType=error&toastMessage=${errMessage}`
);
}

// once the token is verified, we can decode it to get the userId and email
const { userId, email, communityId } = jwt.decode(token);

// if the token doesn't have the necessary info
if (!userId || !email) {
return res
.status(400)
.send(
'We were not able to verify this email validation. You can re-enter your email address in your user settings to resend a confirmation email.'
);
return res.redirect(
`${rootRedirect}/me/settings?toastType=error&toastMessage=We were not able to verify this email validation. You can re-enter your email address in your user settings to resend a confirmation email.`
);
}

// if there is a community id present in the token, the user is trying to
Expand All @@ -171,46 +170,46 @@ emailRouter.get('/validate', (req, res) => {
return updateCommunityAdministratorEmail(communityId, email, userId).then(
community =>
IS_PROD
? res.redirect(`https://spectrum.chat/${community.slug}/settings`)
: res.redirect(`http://localhost:3000/${community.slug}/settings`)
? res.redirect(
`https://spectrum.chat/${
community.slug
}/settings?toastType=success&toastMessage=Your email address has been validated!`
)
: res.redirect(
`http://localhost:3000/${
community.slug
}/settings?toastType=success&toastMessage=Your email address has been validated!`
)
);
} catch (err) {
console.error(err);
return res
.status(400)
.send(
'We ran into an issue validating this email address. You can re-enter your email address in your community settings to resend a confirmation email, or get in touch with us at hi@spectrum.chat.'
);
return res.redirect(
`${rootRedirect}/me/settings?toastType=error&toastMessage=We ran into an issue validating this email address. You can re-enter your email address in your community settings to resend a confirmation email, or get in touch with us at hi@spectrum.chat.`
);
}
}

// and send a database request to update the user record with this email
try {
return updateUserEmail(userId, email).then(user => {
const rootRedirect = IS_PROD
? `https://spectrum.chat`
: `http://localhost:3000`;

req.login(user, err => {
if (err) {
return res
.status(400)
.send(
'We ran into an issue validating this email address. You can re-enter your email address in your community settings to resend a confirmation email, or get in touch with us at hi@spectrum.chat.'
);
return res.redirect(
`${rootRedirect}/me/settings?toastType=error&toastMessage=We ran into an issue validating this email address. You can re-enter your email address in your community settings to resend a confirmation email, or get in touch with us at hi@spectrum.chat.`
);
}

if (!user.username) return res.redirect(rootRedirect);
return res.redirect(`${rootRedirect}/users/${user.username}/settings`);
return res.redirect(
`${rootRedirect}/me/settings?toastType=success&toastMessage=Email updated!`
);
});
});
} catch (err) {
console.error(err);
return res
.status(400)
.send(
'We ran into an issue validating this email address. You can re-enter your email address in your user settings to resend a confirmation email, or get in touch with us at hi@spectrum.chat.'
);
return res.redirect(
`${rootRedirect}/me/settings?toastType=error&toastMessage=We ran into an issue validating this email address. You can re-enter your email address in your user settings to resend a confirmation email, or get in touch with us at hi@spectrum.chat.`
);
}
});

Expand Down
8 changes: 8 additions & 0 deletions api/routes/auth/create-signin-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ export const createSigninRoutes = (
? new URL(req.session.redirectUrl)
: new URL(FALLBACK_URL);
redirectUrl.searchParams.append('authed', 'true');
if (req.authInfo && req.authInfo.message) {
redirectUrl.searchParams.append(
'toastMessage',
// $FlowIssue
req.authInfo.message
);
redirectUrl.searchParams.append('toastType', 'error');
}

// Delete the redirectURL from the session again so we don't redirect
// to the old URL the next time around
Expand Down
78 changes: 78 additions & 0 deletions cypress/integration/toasts_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import data from '../../shared/testing/data';
const user = data.users[0];

const toastMessage = 'This is a long toast message';

describe('Toasts and url query paramaters', () => {
beforeEach(() => {
cy.auth(user.id);
});

it('should show toast', () => {
const url = new URL('http://localhost:3000/me/settings');
url.searchParams.append('toastType', 'success');
url.searchParams.append('toastMessage', toastMessage);
cy.visit(url.toString());
cy.get('[data-cy="toast-success"]').contains(toastMessage);
cy.url().should('eq', 'http://localhost:3000/users/mxstbr/settings');
});

it('should not show toast if no toastType', () => {
const url = new URL('http://localhost:3000/me/settings');
url.searchParams.append('toastMessage', toastMessage);
cy.visit(url.toString());
cy.get('[data-cy="toast-success"]').should('have.length', 0);
cy.url().should('eq', 'http://localhost:3000/users/mxstbr/settings');
});

it('should not show toast if no toastMessage', () => {
const url = new URL('http://localhost:3000/me/settings');
url.searchParams.append('toastType', 'success');
cy.visit(url.toString());
cy.get('[data-cy="toast-success"]', { timeout: 1 }).should(
'have.length',
0
);
cy.url().should('eq', 'http://localhost:3000/users/mxstbr/settings');
});

it('should not show toast if invalid toastType', () => {
const url = new URL('http://localhost:3000/me/settings');
url.searchParams.append('toastType', 'foo');
cy.visit(url.toString());
cy.get('[data-cy="toast-success"]', { timeout: 1 }).should(
'have.length',
0
);
cy.url().should('eq', 'http://localhost:3000/users/mxstbr/settings');
});

it('should preserve existing query parameters', () => {
const url = new URL('http://localhost:3000/?t=thread-9');
url.searchParams.append('toastType', 'success');
url.searchParams.append('toastMessage', toastMessage);
cy.visit(url.toString());
cy.get('[data-cy="toast-success"]', { timeout: 100 }).should(
'have.length',
1
);
cy.url().should('eq', 'http://localhost:3000/?t=thread-9');
});

it.only('should preserve many existing query parameters', () => {
const url = new URL(
'http://localhost:3000/spectrum/general/another-thread~thread-2?m=MTQ4MzIyNTIwMDAwMg=='
);
url.searchParams.append('toastType', 'success');
url.searchParams.append('toastMessage', toastMessage);
cy.visit(url.toString());
cy.get('[data-cy="toast-success"]', { timeout: 100 }).should(
'have.length',
1
);
cy.url().should(
'eq',
'http://localhost:3000/spectrum/general/another-thread~thread-2?m=MTQ4MzIyNTIwMDAwMg=='
);
});
});
5 changes: 3 additions & 2 deletions src/actions/toasts.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ let nextToastId = 0;
export const addToastWithTimeout = (kind: Toasts, message: string) => (
dispatch: Dispatch<Object>
) => {
const timeout = kind === 'success' ? 2000 : 4000;
const id = nextToastId++;
const timeout = kind === 'success' ? 3000 : 6000;
let id = nextToastId++;
dispatch(addToast(id, kind, message, timeout));

setTimeout(() => {
dispatch(removeToast(id));
id = nextToastId--;
}, timeout);
};
Loading