Gitter migration: Setup redirects (rollout pt. 3)
[gitter.git] / server / handlers / settings.js
blobb7e4bf382c5457e1d0325e9c3a11a906cd82341f
1 'use strict';
3 var env = require('gitter-web-env');
4 var logger = env.logger;
5 var stats = env.stats;
6 var config = env.config;
8 var express = require('express');
9 var request = require('request');
10 var StatusError = require('statuserror');
11 var jwt = require('jwt-simple');
12 const asyncHandler = require('express-async-handler');
14 var userSettingsService = require('gitter-web-user-settings');
15 var cdn = require('gitter-web-cdn');
16 var services = require('@gitterhq/services');
17 var debug = require('debug')('gitter:app:settings-route');
18 var userScopes = require('gitter-web-identity/lib/user-scopes');
19 var fonts = require('../web/fonts');
20 var acceptInviteService = require('../services/accept-invite-service');
21 var loginUtils = require('../web/login-utils');
22 var resolveRoomUri = require('../utils/resolve-room-uri');
23 const unsubscribeHashes = require('gitter-web-email-notifications/lib/unsubscribe-hashes');
25 var identifyRoute = env.middlewares.identifyRoute;
26 var ensureLoggedIn = require('../web/middlewares/ensure-logged-in');
27 var uriContextResolverMiddleware = require('./uri-context/uri-context-resolver-middleware');
28 var preventClickjackingMiddleware = require('../web/middlewares/prevent-clickjacking');
29 var preventClickjackingOnlyGitterEmbedMiddleware = require('../web/middlewares/prevent-clickjacking-only-gitter-embed');
31 var supportedServices = [
32 { id: 'github', name: 'GitHub' },
33 { id: 'bitbucket', name: 'BitBucket' },
34 { id: 'trello', name: 'Trello' }
37 var openServices = Object.keys(services).map(function(id) {
38 return {
39 id: id,
40 name: services[id].name
42 });
44 var serviceIdNameMap = supportedServices.concat(openServices).reduce(function(map, service) {
45 map[service.id] = service.name;
46 return map;
47 }, {});
49 function getIntegrations(req, res, next) {
50 debug('Get integrations for %s', req.troupe.url);
52 var url = config.get('webhooks:basepath') + '/troupes/' + req.troupe.id + '/hooks';
53 request.get(
55 url: url,
56 json: true
58 function(err, resp, hooks) {
59 if (err || resp.statusCode !== 200 || !Array.isArray(hooks)) {
60 logger.error('failed to fetch hooks for troupe', {
61 exception: err,
62 resp: resp,
63 hooks: hooks
64 });
65 return next(new StatusError(500, 'Unable to perform request. Please try again later.'));
68 hooks.forEach(function(hook) {
69 hook.serviceDisplayName = serviceIdNameMap[hook.service];
70 });
72 res.render('integrations', {
73 hooks: hooks,
74 troupe: req.troupe,
75 accessToken: req.accessToken,
76 cdnRoot: cdn(''),
77 supportedServices: supportedServices,
78 openServices: openServices,
79 fonts: fonts.getFonts(),
80 hasCachedFonts: fonts.hasCachedFonts(req.cookies)
81 });
86 function deleteIntegration(req, res, next) {
87 debug('Delete integration %s for %s', req.body.id, req.troupe.url);
89 request.del(
91 url: config.get('webhooks:basepath') + '/troupes/' + req.troupe.id + '/hooks/' + req.body.id,
92 json: true
94 function(err, resp) {
95 if (err || resp.statusCode !== 200) {
96 logger.error('failed to delete hook for troupe', { exception: err, resp: resp });
97 return next(new StatusError(500, 'Unable to perform request. Please try again later.'));
100 res.redirect('/settings/integrations/' + req.troupe.uri);
105 function createIntegration(req, res, next) {
106 debug('Create integration for %s', req.body.service, req.troupe.url);
108 request.post(
110 url: config.get('webhooks:basepath') + '/troupes/' + req.troupe.id + '/hooks',
111 json: {
112 service: req.body.service,
113 endpoint: 'gitter'
117 function(err, resp, body) {
118 if (err || resp.statusCode !== 200 || !body) {
119 logger.error('failed to create hook for troupe', { exception: err, resp: resp });
120 return next(new StatusError(500, 'Unable to perform request. Please try again later.'));
123 var encryptedUserToken;
125 // Pass through the token if we have write access
126 // TODO: deal with private repos too
127 if (userScopes.hasGitHubScope(req.user, 'public_repo')) {
128 encryptedUserToken = jwt.encode(
129 userScopes.getGitHubToken(req.user, 'public_repo'),
130 config.get('integrations:secret')
132 } else {
133 encryptedUserToken = '';
136 res.redirect(
137 body.configurationURL +
138 '&rt=' +
139 resp.body.token +
140 '&ut=' +
141 encryptedUserToken +
142 '&returnTo=' +
143 config.get('web:basepath') +
144 req.originalUrl
150 const adminAccessCheck = asyncHandler(async (req, res, next) => {
151 const uriContext = req.uriContext;
152 const policy = uriContext.policy;
154 const access = await policy.canAdmin();
155 if (!access) throw new StatusError(403);
157 next();
160 var router = express.Router({ caseSensitive: true, mergeParams: true });
163 '/integrations/:roomPart1',
164 '/integrations/:roomPart1/:roomPart2',
165 '/integrations/:roomPart1/:roomPart2/:roomPart3'
166 ].forEach(function(uri) {
167 router.use(uri, function(req, res, next) {
168 // Shitty method override because the integrations page
169 // doesn't use javascript and relies on forms aka, the web as of 1996.
170 var _method = req.body && req.body._method ? '' + req.body._method : '';
171 if (req.method === 'POST' && _method.toLowerCase() === 'delete') {
172 req.method = 'DELETE';
174 next();
177 router.get(
178 uri,
179 ensureLoggedIn,
180 preventClickjackingOnlyGitterEmbedMiddleware,
181 identifyRoute('settings-room-get'),
182 uriContextResolverMiddleware,
183 adminAccessCheck,
184 getIntegrations
187 router.delete(
188 uri,
189 ensureLoggedIn,
190 preventClickjackingOnlyGitterEmbedMiddleware,
191 identifyRoute('settings-room-delete'),
192 uriContextResolverMiddleware,
193 adminAccessCheck,
194 deleteIntegration
197 router.post(
198 uri,
199 ensureLoggedIn,
200 preventClickjackingOnlyGitterEmbedMiddleware,
201 identifyRoute('settings-room-create'),
202 uriContextResolverMiddleware,
203 adminAccessCheck,
204 createIntegration
208 router.get(
209 '/accept-invite/:secret',
210 identifyRoute('settings-accept-invite'),
211 ensureLoggedIn,
212 preventClickjackingMiddleware,
213 function(req, res, next) {
214 var secret = req.params.secret;
215 return acceptInviteService
216 .acceptInvite(req.user, secret, { source: req.query.source })
217 .then(function(room) {
218 return resolveRoomUri(room, req.user._id);
220 .then(function(roomUri) {
221 var encodedUri = encodeURI(roomUri);
222 res.relativeRedirect(encodedUri);
224 .catch(StatusError, function(err) {
225 if (err.status >= 500) throw err;
227 if (req.session) {
228 var events = req.session.events;
229 if (!events) {
230 events = [];
231 req.session.events = events;
233 events.push('invite_failed');
235 // TODO: tell the user why they could not get invited
237 logger.error('Unable to use invite', {
238 username: req.user && req.user.username,
239 exception: err
241 return loginUtils.whereToNext(req.user).then(function(next) {
242 res.relativeRedirect(next);
245 .catch(next);
249 router.get(
250 '/unsubscribe/:hash',
251 identifyRoute('settings-unsubscribe'),
252 preventClickjackingMiddleware,
253 function(req, res, next) {
254 let userId;
255 let notificationType;
256 try {
257 ({ userId, notificationType } = unsubscribeHashes.decipherHash(req.params.hash));
258 } catch (err) {
259 return next(new StatusError(400, 'Invalid hash'));
262 debug('User %s opted-out from ', userId, notificationType);
263 stats.event('unsubscribed_unread_notifications', { userId: userId });
265 userSettingsService
266 .setUserSettings(userId, 'unread_notifications_optout', 1)
267 .then(function() {
268 var msg = "Done. You won't receive notifications like that one in the future.";
270 res.render('unsubscribe', { layout: 'generic-layout', title: 'Unsubscribe', msg: msg });
272 .catch(next);
276 router.get(
277 '/badger/opt-out',
278 ensureLoggedIn,
279 preventClickjackingMiddleware,
280 identifyRoute('settings-badger-optout'),
281 function(req, res, next) {
282 var userId = req.user.id;
284 logger.info('User ' + userId + ' opted-out from auto badgers');
285 stats.event('optout_badger', { userId: userId });
287 return userSettingsService
288 .setUserSettings(userId, 'badger_optout', 1)
289 .then(function() {
290 var msg = "Done. We won't send you automatic pull-requests in future.";
292 res.render('unsubscribe', {
293 layout: 'generic-layout',
294 title: 'Opt-out',
295 msg: msg
298 .catch(next);
302 module.exports = router;