From 476b3e290db3dbadae12f5905816d72a8dd0ea6f Mon Sep 17 00:00:00 2001 From: William Hilton Date: Wed, 29 May 2019 17:07:22 -0400 Subject: [PATCH] feat: allow injecting an authorization middleware (#6) --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++- middleware.js | 39 +++++++++++++++++++++++++++----- 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 08c077f..f9240b6 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,73 @@ Kill the process with the PID specified in `$PWD/cors-proxy.pid`: cors-proxy stop ``` -## Configuration +### CLI configuration Environment variables: - `PORT` the port to listen to (if run with `npm start`) - `ALLOW_ORIGIN` the value for the 'Access-Control-Allow-Origin' CORS header - `INSECURE_HTTP_ORIGINS` comma separated list of origins for which HTTP should be used instead of HTTPS (added to make developing against locally running git servers easier) + +## Middleware usage + +You can also use the `cors-proxy` as a middleware in your own server. + +```js +const express = require('express') +const corsProxy = require('@isomorphic-git/cors-proxy/middleware.js') + +const app = express() +const options = {} + +app.use(corsProxy(options)) + +``` + +### Middleware configuration + +*The middleware doesn't use the environment variables.* The options object supports the following properties: + +- `origin`: _string_. The value for the 'Access-Control-Allow-Origin' CORS header +- `insecure_origins`: _string[]_. Array of origins for which HTTP should be used instead of HTTPS (added to make developing against locally running git servers easier) +- `authorization`: _(req, res, next) => void_. A middleware function you can use to handle custom authorization. Is run after filtering for git-like requests and handling CORS but before the request is proxied. + +_Example:_ +```ts +app.use( + corsProxy({ + authorization: (req: Request, res: Response, next: NextFunction) => { + // proxied git HTTP requests already use the Authorization header for git credentials, + // so their [Company] credentials are inserted in the X-Authorization header instead. + if (getAuthorizedUser(req, 'X-Authorization')) { + return next(); + } else { + return res.status(401).send("Unable to authenticate you with [Company]'s git proxy"); + } + }, + }) +); + +// Only requests with a valid JSON Web Token will be proxied +function getAuthorizedUser(req: Request, header: string = 'Authorization') { + const Authorization = req.get(header); + + if (Authorization) { + const token = Authorization.replace('Bearer ', ''); + try { + const verifiedToken = verify(token, env.APP_SECRET) as IToken; + if (verifiedToken) { + return { + id: verifiedToken.userId, + }; + } + } catch (e) { + // noop + } + } +} +``` + ## License This work is released under [The MIT License](https://opensource.org/licenses/MIT) diff --git a/middleware.js b/middleware.js index 2a5fb8a..530024f 100644 --- a/middleware.js +++ b/middleware.js @@ -19,6 +19,7 @@ const allowHeaders = [ 'range', 'referer', 'user-agent', + 'x-authorization', 'x-http-method-override', 'x-requested-with', ] @@ -58,19 +59,47 @@ const filter = (predicate, middleware) => { return corsProxyMiddleware } -module.exports = ({ origin, insecure_origins = [] } = {}) => { +const compose = (...handlers) => { + const composeTwo = (handler1, handler2) => { + function composed (req, res, next) { + handler1(req, res, (err) => { + if (err) { + return next(err) + } else { + return handler2(req, res, next) + } + }) + } + return composed + } + let result = handlers.pop() + while(handlers.length) { + result = composeTwo(handlers.pop(), result) + } + return result +} + +function noop (_req, _res, next) { + next() +} + +module.exports = ({ origin, insecure_origins = [], authorization = noop } = {}) => { function predicate (req) { let u = url.parse(req.url, true) // Not a git request, skip return allow(req, u) } - function middleware (req, res) { - let u = url.parse(req.url, true) - + function sendCorsOK (req, res, next) { // Handle CORS preflight request if (req.method === 'OPTIONS') { return send(res, 200, '') + } else { + next() } + } + function middleware (req, res) { + let u = url.parse(req.url, true) + let headers = {} for (let h of allowHeaders) { @@ -110,5 +139,5 @@ module.exports = ({ origin, insecure_origins = [] } = {}) => { allowCredentials: false, origin }) - return filter(predicate, cors(middleware)) + return filter(predicate, cors(compose(sendCorsOK, authorization, middleware))) }