'use strict'; const oauth2orize = require('@poziworld/oauth2orize'); const passport = require('passport'); const login = require('connect-ensure-login'); const db = require('../db'); const utils = require('../utils'); // Create OAuth 2.0 server const server = oauth2orize.createServer(); // Register serialization 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((client, done) => done(null, client.id)); server.deserializeClient((id, done) => { db.clients.findById(id, (error, client) => { if (error) return done(error); return done(null, client); }); }); // 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((client, redirectUri, user, ares, done) => { const code = utils.getUid(16); db.authorizationCodes.save(code, client.id, redirectUri, user.id, user.username, error => { if (error) return done(error); return done(null, code); }); })); // 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((client, user, ares, done) => { const token = utils.getUid(256); db.accessTokens.save(token, user.id, client.clientId, error => { if (error) return done(error); return done(null, token); }); })); // 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. The issued access token response can include a refresh token and // custom parameters by adding these to the `done()` call server.exchange(oauth2orize.exchange.code((client, code, redirectUri, done) => { db.authorizationCodes.find(code, (error, authCode) => { if (error) return done(error); if (client.id !== authCode.clientId) return done(null, false); if (redirectUri !== authCode.redirectUri) return done(null, false); const token = utils.getUid(256); db.accessTokens.save(token, authCode.userId, authCode.clientId, error => { if (error) return done(error); // Add custom params, e.g. the username let params = { username: authCode.userName }; // Call `done(err, accessToken, [refreshToken], [params])` to issue an access token return done(null, token, null, params); }); }); })); // 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((client, username, password, scope, done) => { // Validate the client db.clients.findByClientId(client.clientId, (error, localClient) => { if (error) return done(error); if (!localClient) return done(null, false); if (localClient.clientSecret !== client.clientSecret) return done(null, false); // Validate the user db.users.findByUsername(username, (error, user) => { if (error) return done(error); if (!user) return done(null, false); if (password !== user.password) return done(null, false); // Everything validated, return the token const token = utils.getUid(256); db.accessTokens.save(token, user.id, client.clientId, error => { if (error) return done(error); // Call `done(err, accessToken, [refreshToken], [params])`, see oauth2orize.exchange.code return done(null, token); }); }); }); })); // 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((client, scope, done) => { // Validate the client db.clients.findByClientId(client.clientId, (error, localClient) => { if (error) return done(error); if (!localClient) return done(null, false); if (localClient.clientSecret !== client.clientSecret) return done(null, false); // Everything validated, return the token const token = utils.getUid(256); // Pass in a null for user id since there is no user with this grant type db.accessTokens.save(token, null, client.clientId, error => { if (error) return done(error); // Call `done(err, accessToken, [refreshToken], [params])`, see oauth2orize.exchange.code return done(null, 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 across // 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((clientId, redirectUri, done) => { db.clients.findByClientId(clientId, (error, client) => { if (error) return done(error); // WARNING: For security purposes, it is highly advisable to check that // redirectUri provided by the client matches one registered with // the server. For simplicity, this example does not. You have // been warned. return done(null, client, redirectUri); }); }, (client, user, done) => { // Check if grant request qualifies for immediate approval // Auto-approve if (client.isTrusted) return done(null, true); db.accessTokens.findByUserIdAndClientId(user.id, client.clientId, (error, token) => { // Auto-approve if (token) return done(null, true); // Otherwise ask user return done(null, false); }); }), (req, res) => { res.render('dialog', { transactionId: req.oauth2.transactionID, user: req.user, client: req.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. module.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. module.exports.token = [ passport.authenticate(['basic', 'oauth2-client-password'], { session: false }), server.token(), server.errorHandler(), ];