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()