isomorphic-git / isomorphic-git/isomorphic-git

Proxy middleware - Koa

Open
#1,078 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
JavaScript
Stars
8.4k
Forks
491
Avg merge
2h 53m
Merged PRs (30d)
7

Description

### Problem to solve
There is a middleware, but only for express. I needed one for Koa.

### Intended users
Developers with a Koa (based) backend.

### Further details
I needed one quickly, so I have created a simple middleware with the help of your `middleware.js`.
Do keep in mind, this is without the CORS or Authorization, and with the path prefix `/proxy`.
Also, I made this quite quickly, so please don't be harsh :blush:.

### Proposed code

```typescript
import { ParameterizedContext } from 'koa';
import KoaRouter from 'koa-router';
import fetch from 'node-fetch';
import rawBody from 'raw-body';

const allowHeaders = [
'accept-encoding',
'accept-language',
'accept',
'access-control-allow-origin',
'authorization',
'cache-control',
'connection',
'content-length',
'content-type',
'dnt',
'pragma',
'range',
'referer',
'user-agent',
'x-authorization',
'x-http-method-override',
'x-requested-with'
];
const exposeHeaders = [
'accept-ranges',
'age',
'cache-control',
'content-length',
'content-language',
'content-type',
'date',
'etag',
'expires',
'last-modified',
'pragma',
'server',
'transfer-encoding',
'vary',
'x-github-request-id',
'x-redirected-url'
];
const allowMethods = [
'POST',
'GET',
'OPTIONS'
];

const INSECURE_ORIGINS: string[] = [];

type Context = ParameterizedContext>;

export default async function (ctx: Context, next: () => Promise) {

if (!allow(ctx)) return next();

const { url, method } = ctx.request;

const headers: Record = {};
for (const headername of allowHeaders) {
if (ctx.request.headers[headername]) {
headers[headername] = ctx.request.headers[headername];
}
}

// GitHub uses user-agent sniffing for git/* and changes its behavior which is frustrating
if (!headers['user-agent'] || !headers['user-agent'].startsWith('git/')) {
headers['user-agent'] = 'git/@isomorphic-git/cors-proxy';
}

const [_, pathdomain, remainingpath] = url.match(/\/proxy\/([^\/]*)\/(.*)/)!;
const protocol = INSECURE_ORIGINS.includes(pathdomain) ? 'http' : 'https';
const body: any = (method !== 'GET' && method !== 'HEAD' && !!rawBody)
? await rawBody(ctx.req) : undefined;

const response = await fetch(`${protocol}://${pathdomain}/${remainingpath}`, {
method, headers, body
});

ctx.status = response.status;
for (const headername of exposeHeaders) {
if (headername === 'content-length') continue;
if (response.headers.has(headername)) {
ctx.header[headername] = response.headers.get(headername);
}
}

if (response.redirected) ctx.header['x-redirected-url'] = response.url;
ctx.body = response.body;
}

function allow(context: Context) {
const { method, headers, path, query } = context.request;
if (method === 'OPTIONS' && path.endsWith('/info/refs') && (query.service === 'git-upload-pack' || query.service === 'git-receive-pack')) return true;
if (method === 'GET' && path.endsWith('/info/refs') && (query.service === 'git-upload-pack' || query.service === 'git-receive-pack')) return true;
if (method === 'OPTIONS' && headers['access-control-request-headers'].includes('content-type') && path.endsWith('git-upload-pack')) return true;
if (method === 'POST' && headers['content-type'] === 'application/x-git-upload-pack-request' && path.endsWith('git-upload-pack')) return true;
if (method === 'OPTIONS' && headers['access-control-request-headers'].includes('content-type') && path.endsWith('git-receive-pack')) return true;
if (method === 'POST' && headers['content-type'] === 'application/x-git-receive-pack-request' && path.endsWith('git-receive-pack')) return true;
return false;
}
```

Updated (2020-09-03) to have `raw-body` util for parsing the body.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.