UNPKG

express-gateway

Version:

A microservices API gateway built on top of ExpressJS

300 lines (247 loc) 10.6 kB
'use strict'; const oauth2orize = require('oauth2orize'); const passport = require('passport'); const login = require('connect-ensure-login'); const path = require('path'); const services = require('../../services/index'); const tokenService = services.token; const authCodeService = services.authorizationCode; const authService = services.auth; // Create OAuth 2.0 server const server = oauth2orize.createServer(); // Register serialialization and deserialization functions. // // When a client redirects a user to user authorization endpoint, an // authorization transaction is initiated. To complete the transaction, the // user must authenticate and approve the authorization request. Because this // may involve multiple HTTP request/response exchanges, the transaction is // stored in the session. // // An application must supply serialization functions, which determine how the // client object is serialized into the session. Typically this will be a // simple matter of serializing the client's ID, and deserializing by finding // the client by ID from the database. server.serializeClient((consumer, done) => done(null, consumer)); server.deserializeClient((consumer, done) => { const id = consumer.id; return authService.validateConsumer(id) .then(foundConsumer => { if (!foundConsumer) return done(null, false); return done(null, consumer); }) .catch(err => done(err)); }); // Register supported grant types. // // OAuth 2.0 specifies a framework that allows users to grant client // applications limited access to their protected resources. It does this // through a process of the user granting access, and the client exchanging // the grant for an access token. // Grant authorization codes. The callback takes the `client` requesting // authorization, the `redirectUri` (which is used as a verifier in the // subsequent exchange), the authenticated `user` granting access, and // their response, which contains approved scope, duration, etc. as parsed by // the application. The application issues a code, which is bound to these // values, and will be exchanged for an access token. server.grant(oauth2orize.grant.code((consumer, redirectUri, user, ares, done) => { const code = { consumerId: consumer.id, redirectUri: redirectUri, userId: user.id }; if (consumer.authorizedScopes) code.scopes = consumer.authorizedScopes; return authCodeService.save(code) .then((codeObj) => { return done(null, codeObj.id); }) .catch(err => done(err)); })); // Grant implicit authorization. The callback takes the `client` requesting // authorization, the authenticated `user` granting access, and // their response, which contains approved scope, duration, etc. as parsed by // the application. The application issues a token, which is bound to these // values. server.grant(oauth2orize.grant.token((consumer, authenticatedUser, ares, done) => { const tokenCriteria = { consumerId: consumer.id, authenticatedUserId: authenticatedUser.id, redirectUri: consumer.redirectUri, authType: 'oauth2' }; if (consumer.authorizedScopes) tokenCriteria.scopes = consumer.authorizedScopes; return tokenService.findOrSave(tokenCriteria) .then(token => { return done(null, token.access_token); }) .catch(err => done(err)); })); // Exchange authorization codes for access tokens. The callback accepts the // `client`, which is exchanging `code` and any `redirectUri` from the // authorization request for verification. If these values are validated, the // application issues an access token on behalf of the user who authorized the // code. server.exchange(oauth2orize.exchange.code((consumer, code, redirectUri, done) => { const codeCriteria = { id: code, consumerId: consumer.id, redirectUri: redirectUri }; authCodeService.find(codeCriteria) .then(codeObj => { if (!codeObj) { return done(null, false); } const tokenCriteria = { consumerId: consumer.id, authenticatedUserId: codeObj.userId, authType: 'oauth2' }; if (codeObj.scopes) tokenCriteria.scopes = codeObj.scopes; return tokenService.findOrSave(tokenCriteria, { includeRefreshToken: true }) .then(token => { return done(null, token.access_token, token.refresh_token); }); }) .catch(err => done(err)); })); // Exchange user id and password for access tokens. The callback accepts the // `client`, which is exchanging the user's name and password from the // authorization request for verification. If these values are validated, the // application issues an access token on behalf of the user who authorized the code. server.exchange(oauth2orize.exchange.password((consumer, username, password, scopes, done) => { // Validate the consumer return authService.validateConsumer(consumer.id) .then(consumer => { if (!consumer) return done(null, false); return authService.authenticateCredential(username, password, 'oauth2') .then(user => { let scopeAuthorizationPromise; if (!user) return done(null, false); if (scopes) { scopeAuthorizationPromise = authService.authorizeCredential(consumer.id, 'oauth2', scopes); } else scopeAuthorizationPromise = Promise.resolve(true); return scopeAuthorizationPromise .then(authorized => { if (!authorized) return done(null, false); const tokenCriteria = { consumerId: consumer.id, authenticatedUser: user.id }; if (scopes) tokenCriteria.scopes = scopes; return tokenService.findOrSave(tokenCriteria, { includeRefreshToken: true }) .then(token => { return done(null, token.access_token, token.refresh_token); }); }); }) .catch(err => done(err)); }); })); // Exchange the client id and password/secret for an access token. The callback accepts the // `client`, which is exchanging the client's id and password/secret from the // authorization request for verification. If these values are validated, the // application issues an access token on behalf of the client who authorized the code. server.exchange(oauth2orize.exchange.clientCredentials((consumer, scopes, done) => { // Validate the client return authService.validateConsumer(consumer.id) .then(consumer => { let scopeAuthorizationPromise; if (!consumer) return done(null, false); if (scopes) { scopeAuthorizationPromise = authService.authorizeCredential(consumer.id, 'oauth2', scopes); } else scopeAuthorizationPromise = Promise.resolve(true); return scopeAuthorizationPromise .then(authorized => { if (!authorized) return done(null, false); const tokenCriteria = { consumerId: consumer.id, authType: 'oauth2' }; if (scopes) tokenCriteria.scopes = scopes; return tokenService.findOrSave(tokenCriteria) .then(token => { return done(null, token.access_token); }); }); }) .catch(err => done(err)); })); // Exchange Refresh Token server.exchange(oauth2orize.exchange.refreshToken(function (consumer, refreshToken, done) { return authService.validateConsumer(consumer.id) .then(consumer => { if (!consumer) { return done(null, false); } return tokenService.getTokenObject(refreshToken) .then(tokenObj => { if (!tokenObj) { return done(null, false); } return tokenService.findOrSave(tokenObj) .then(token => { return done(null, token.access_token); }); }); }); })); // User authorization endpoint. // // `authorization` middleware accepts a `validate` callback which is // responsible for validating the client making the authorization request. In // doing so, is recommended that the `redirectUri` be checked against a // registered value, although security requirements may vary accross // implementations. Once validated, the `done` callback must be invoked with // a `client` instance, as well as the `redirectUri` to which the user will be // redirected after an authorization decision is obtained. // // This middleware simply initializes a new authorization transaction. It is // the application's responsibility to authenticate the user and render a dialog // to obtain their approval (displaying details about the client requesting // authorization). We accomplish that here by routing through `ensureLoggedIn()` // first, and rendering the `dialog` view. module.exports.authorization = [ login.ensureLoggedIn(), server.authorization((areq, done) => { return authService.validateConsumer(areq.clientID) .then(consumer => { if (!consumer || consumer.redirectUri !== areq.redirectURI) return done(null, false); if (!areq.scope) { return done(null, consumer, areq.redirectURI); } return authService.authorizeCredential(areq.clientID, 'oauth2', areq.scope) .then(authorized => { if (!authorized) { return done(null, false); } consumer.authorizedScopes = areq.scope; return done(null, consumer, areq.redirectURI); }); }); }), (request, response) => { response.set('transaction_id', request.oauth2.transactionID); response.render(path.join(__dirname, 'views/dialog'), { transactionId: request.oauth2.transactionID, user: request.user, client: request.oauth2.client }); } ]; // User decision endpoint. // // `decision` middleware processes a user's decision to allow or deny access // requested by a client application. Based on the grant type requested by the // client, the above grant middleware configured above will be invoked to send // a response. exports.decision = [ login.ensureLoggedIn(), server.decision() ]; // Token endpoint. // // `token` middleware handles client requests to exchange authorization grants // for access tokens. Based on the grant type being exchanged, the above // exchange middleware will be invoked to handle the request. Clients must // authenticate when making requests to this endpoint. exports.token = [ passport.authenticate(['basic', 'oauth2-client-password'], { session: false }), server.token(), server.errorHandler() ];