Skip to content

Commit 7290600

Browse files
committed
[#915] Add flow to unlink OAuth accounts
1 parent 919d15c commit 7290600

File tree

6 files changed

+110
-45
lines changed

6 files changed

+110
-45
lines changed

client/modules/User/actions.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,20 @@ export function removeApiKey(keyId) {
270270
Promise.reject(new Error(response.data.error));
271271
});
272272
}
273+
274+
export function unlinkService(service) {
275+
return (dispatch) => {
276+
if (!['github', 'google'].includes(service)) return;
277+
apiClient.delete(`/auth/${service}`)
278+
.then((response) => {
279+
dispatch({
280+
type: ActionTypes.AUTH_USER,
281+
user: response.data
282+
});
283+
}).catch((error) => {
284+
const { response } = error;
285+
const message = response.message || response.data.error;
286+
dispatch(authError(message));
287+
});
288+
};
289+
}

client/modules/User/components/SocialAuthButton.jsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import PropTypes from 'prop-types';
22
import React from 'react';
33
import styled from 'styled-components';
44
import { withTranslation } from 'react-i18next';
5+
import { useDispatch } from 'react-redux';
56

67
import { remSize } from '../../../theme';
7-
88
import { GithubIcon, GoogleIcon } from '../../../common/icons';
99
import Button from '../../../common/Button';
10+
import { unlinkService } from '../actions';
1011

1112
const authUrls = {
1213
github: '/auth/github',
@@ -16,11 +17,11 @@ const authUrls = {
1617
const linkLabels = {
1718
github: {
1819
connect: 'Connect GitHub Account',
19-
connected: 'Re-link GitHub Account'
20+
unlink: 'Unlink GitHub Account'
2021
},
2122
google: {
2223
connect: 'Connect Google Account',
23-
connected: 'Re-link Google Account'
24+
unlink: 'Unlink Google Account'
2425
}
2526
};
2627

@@ -46,18 +47,33 @@ function SocialAuthButton({
4647
github: t('SocialAuthButton.Github'),
4748
google: t('SocialAuthButton.Google')
4849
};
49-
let label;
50+
const dispatch = useDispatch();
5051
if (linkStyle) {
51-
label = isConnected ? linkLabels[service].connected : linkLabels[service].connect;
52-
} else {
53-
label = labels[service];
52+
if (isConnected) {
53+
return (
54+
<StyledButton
55+
iconBefore={<ServiceIcon aria-label={t('SocialAuthButton.LogoARIA', { serviceauth: service })} />}
56+
onClick={() => { dispatch(unlinkService(service)); }}
57+
>
58+
{linkLabels[service].unlink}
59+
</StyledButton>
60+
);
61+
}
62+
return (
63+
<StyledButton
64+
iconBefore={<ServiceIcon aria-label={t('SocialAuthButton.LogoARIA', { serviceauth: service })} />}
65+
href={authUrls[service]}
66+
>
67+
{linkLabels[service].connect}
68+
</StyledButton>
69+
);
5470
}
5571
return (
5672
<StyledButton
5773
iconBefore={<ServiceIcon aria-label={t('SocialAuthButton.LogoARIA', { serviceauth: service })} />}
5874
href={authUrls[service]}
5975
>
60-
{label}
76+
{labels[service]}
6177
</StyledButton>
6278
);
6379
}

server/controllers/user.controller.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,23 @@ export function updateSettings(req, res) {
337337
});
338338
}
339339

340+
export function unlinkGithub(req, res) {
341+
if (req.user) {
342+
req.user.github = undefined;
343+
req.user.tokens = req.user.tokens.filter(token => token.kind !== 'github');
344+
saveUser(res, req.user);
345+
return;
346+
}
347+
res.status(404).json({ success: false, message: 'You must be logged in to complete this action.' });
348+
}
349+
350+
export function unlinkGoogle(req, res) {
351+
if (req.user) {
352+
req.user.google = undefined;
353+
req.user.tokens = req.user.tokens.filter(token => token.kind !== 'google');
354+
saveUser(res, req.user);
355+
return;
356+
}
357+
res.status(404).json({ success: false, message: 'You must be logged in to complete this action.' });
358+
}
359+

server/routes/passport.routes.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Router from 'express';
2+
import passport from 'passport';
3+
4+
const router = new Router();
5+
6+
router.get('/auth/github', passport.authenticate('github'));
7+
router.get('/auth/github/callback', (req, res, next) => {
8+
passport.authenticate('github', { failureRedirect: '/login' }, (err, user) => {
9+
if (err) {
10+
// use query string param to show error;
11+
res.redirect('/account?error=github');
12+
return;
13+
}
14+
15+
req.logIn(user, (loginErr) => {
16+
if (loginErr) {
17+
next(loginErr);
18+
return;
19+
}
20+
res.redirect('/');
21+
});
22+
})(req, res, next);
23+
});
24+
25+
router.get('/auth/google', passport.authenticate('google'));
26+
router.get('/auth/google/callback', (req, res, next) => {
27+
passport.authenticate('google', { failureRedirect: '/login' }, (err, user) => {
28+
if (err) {
29+
// use query string param to show error;
30+
res.redirect('/account?error=google');
31+
return;
32+
}
33+
34+
req.logIn(user, (loginErr) => {
35+
if (loginErr) {
36+
next(loginErr);
37+
return;
38+
}
39+
res.redirect('/');
40+
});
41+
})(req, res, next);
42+
});
43+
44+
export default router;

server/routes/user.routes.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ router.post('/verify/send', UserController.emailVerificationInitiate);
2626

2727
router.get('/verify', UserController.verifyEmail);
2828

29+
router.delete('/auth/github', UserController.unlinkGithub);
30+
router.delete('/auth/google', UserController.unlinkGoogle);
31+
2932
export default router;

server/server.js

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import aws from './routes/aws.routes';
2626
import serverRoutes from './routes/server.routes';
2727
import embedRoutes from './routes/embed.routes';
2828
import assetRoutes from './routes/asset.routes';
29+
import passportRoutes from './routes/passport.routes';
2930
import { requestsOfTypeJSON } from './utils/requestsOfType';
3031

3132
import { renderIndex } from './views/index';
@@ -135,43 +136,7 @@ app.use('/', serverRoutes);
135136
app.use(assetRoutes);
136137

137138
app.use('/', embedRoutes);
138-
app.get('/auth/github', passport.authenticate('github'));
139-
app.get('/auth/github/callback', (req, res, next) => {
140-
passport.authenticate('github', { failureRedirect: '/login' }, (err, user) => {
141-
if (err) {
142-
// use query string param to show error;
143-
res.redirect('/account?error=github');
144-
return;
145-
}
146-
147-
req.logIn(user, (loginErr) => {
148-
if (loginErr) {
149-
next(loginErr);
150-
return;
151-
}
152-
res.redirect('/');
153-
});
154-
})(req, res, next);
155-
});
156-
157-
app.get('/auth/google', passport.authenticate('google'));
158-
app.get('/auth/google/callback', (req, res, next) => {
159-
passport.authenticate('google', { failureRedirect: '/login' }, (err, user) => {
160-
if (err) {
161-
// use query string param to show error;
162-
res.redirect('/account?error=google');
163-
return;
164-
}
165-
166-
req.logIn(user, (loginErr) => {
167-
if (loginErr) {
168-
next(loginErr);
169-
return;
170-
}
171-
res.redirect('/');
172-
});
173-
})(req, res, next);
174-
});
139+
app.use('/', passportRoutes);
175140

176141
// configure passport
177142
require('./config/passport');

0 commit comments

Comments
 (0)