6 var env = require('gitter-web-env');
7 var logger = env.logger;
8 var errorReporter = env.errorReporter;
10 const url = require('url');
12 var oauth2orize = require('oauth2orize');
13 var passport = require('passport');
14 var oauthService = require('gitter-web-oauth');
15 var random = require('gitter-web-oauth/lib/random');
16 var ensureLoggedIn = require('./middlewares/ensure-logged-in');
18 const OauthAuthorizationError = require('./oauth-authorization-error');
20 // create OAuth 2.0 server
21 var server = oauth2orize.createServer();
23 // Register serialialization and deserialization functions.
25 // When a client redirects a user to user authorization endpoint, an
26 // authorization transaction is initiated. To complete the transaction, the
27 // user must authenticate and approve the authorization request. Because this
28 // may involve multiple HTTP request/response exchanges, the transaction is
29 // stored in the session.
31 // An application must supply serialization functions, which determine how the
32 // client object is serialized into the session. Typically this will be a
33 // simple matter of serializing the client's Id, and deserializing by finding
34 // the client by Id from the database.
36 server.serializeClient(function(client, done) {
37 return done(null, client.id);
40 server.deserializeClient(function(id, done) {
41 oauthService.findClientById(id, done);
44 // Register supported grant types.
46 // OAuth 2.0 specifies a framework that allows users to grant client
47 // applications limited access to their protected resources. It does this
48 // through a process of the user granting access, and the client exchanging
49 // the grant for an access token.
51 // Grant authorization codes. The callback takes the `client` requesting
52 // authorization, the `redirectUri` (which is used as a verifier in the
53 // subsequent exchange), the authenticated `user` granting access, and
54 // their response, which contains approved scope, duration, etc. as parsed by
55 // the application. The application issues a code, which is bound to these
56 // values, and will be exchanged for an access token.
59 oauth2orize.grant.code(function(client, redirectUri, user, ares, done) {
60 logger.info('Granted access to ' + client.name + ' for ' + user.displayName);
62 random.generateToken(function(err, token) {
67 oauthService.saveAuthorizationCode(token, client, redirectUri, user, function(err) {
77 // Exchange authorization codes for access tokens. The callback accepts the
78 // `client`, which is exchanging `code` and any `redirectUri` from the
79 // authorization request for verification. If these values are validated, the
80 // application issues an access token on behalf of the user who authorized the
84 oauth2orize.exchange.code(async function(client, code, redirectUri, done) {
86 const authCode = await oauthService.findAuthorizationCode(code);
87 if (!authCode) return done();
89 if (!client._id.equals(authCode.clientId)) {
92 if (redirectUri !== authCode.redirectUri) {
96 const token = await oauthService.findOrCreateToken(authCode.userId, authCode.clientId);
97 // > The client MUST NOT use the authorization code more than once.
98 // > https://tools.ietf.org/html/rfc6749#section-4.1.2
99 await oauthService.deleteAuthorizationCode(code);
102 if (err) return done(err);
107 // user authorization endpoint
109 // `authorization` middleware accepts a `validate` callback which is
110 // responsible for validating the client making the authorization request. In
111 // doing so, is recommended that the `redirectUri` be checked against a
112 // registered value, although security requirements may vary across
113 // implementations. Once validated, the `done` callback must be invoked with
114 // a `client` instance, as well as the `redirectUri` to which the user will be
115 // redirected after an authorization decision is obtained.
117 // This middleware simply initializes a new authorization transaction. It is
118 // the application's responsibility to authenticate the user and render a dialog
119 // to obtain their approval (displaying details about the client requesting
120 // authorization). We accomplish that here by routing through `ensureLoggedIn()`
121 // first, and rendering the `dialog` view.
123 exports.authorization = [
125 server.authorization(async function(clientKey, redirectUri, done) {
127 stats.event('oauth.authorize');
128 const client = await oauthService.findClientByClientKey(clientKey);
131 return done(new OauthAuthorizationError('Provided clientKey does not exist.'));
135 !client.registeredRedirectUri ||
137 client.registeredRedirectUri !== redirectUri
139 logger.warn('Provided redirectUri does not match registered URI for client_id/clientKey ', {
140 redirectUri: redirectUri,
141 registeredUri: client.registeredRedirectUri,
146 new OauthAuthorizationError(
147 'Provided redirectUri does not match registered URI for client_id/clientKey'
152 const urlData = url.parse(client.registeredRedirectUri);
153 const hasBadProtocol =
154 !urlData.protocol || urlData.protocol === 'javascript:' || urlData.protocol === 'data:';
155 if (hasBadProtocol) {
156 logger.warn('Provided redirectUri is using disallowed bad protocol ', {
157 redirectUri: redirectUri,
158 registeredUri: client.registeredRedirectUri,
163 new OauthAuthorizationError(
164 'Provided redirectUri is using disallowed bad protocol (no javascript:// or data://)'
169 return done(null, client, redirectUri);
171 errorReporter(err, { clientKey, redirectUri }, { module: 'oauth.authorize' });
172 done(new OauthAuthorizationError('Error occured while oauth.authorize'));
175 function(req, res, next) {
176 /* Is this client allowed to skip the authorization page? */
177 if (req.oauth2.client.canSkipAuthorization) {
178 return server.decision({ loadTransaction: false })(req, res, next);
181 stats.event('oauth.authorize.dialog');
183 /* Non-trusted Client */
184 res.render('oauth_authorize_dialog', {
185 transactionId: req.oauth2.transactionID,
187 client: req.oauth2.client
190 function(err, req, res, next) {
191 stats.event('oauth.authorize.failed');
192 errorReporter(err, { oauthAuthorizationDialog: 'failed' }, { module: 'oauth2' });
194 var missingParams = ['response_type', 'redirect_uri', 'client_id'].filter(function(param) {
195 return !req.query[param];
198 var incorrectResponseType = req.query.response_type && req.query.response_type !== 'code';
200 if (err instanceof OauthAuthorizationError || missingParams.length || incorrectResponseType) {
202 res.render('oauth_authorize_failed', {
203 errorMessage: err.message,
204 missingParams: missingParams.length && missingParams,
205 incorrectResponseType: incorrectResponseType
208 /* Let the main error handler deal with this */
214 // user decision endpoint
216 // `decision` middleware processes a user's decision to allow or deny access
217 // requested by a client application. Based on the grant type requested by the
218 // client, the above grant middleware configured above will be invoked to send
221 exports.decision = [ensureLoggedIn, server.decision()];
225 // `token` middleware handles client requests to exchange authorization grants
226 // for access tokens. Based on the grant type being exchanged, the above
227 // exchange middleware will be invoked to handle the request. Clients must
228 // authenticate when making requests to this endpoint.
231 function(req, res, next) {
232 stats.event('oauth.exchange');
235 passport.authenticate([/*'basic', */ 'oauth2-client-password'], {
240 server.errorHandler()