Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
hekystyle committed May 1, 2023
0 parents commit 6633e19
Show file tree
Hide file tree
Showing 17 changed files with 6,893 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
TARGET_HOST=api.yourservice.com
USER_FIELD=email
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
node_modules
.env
28 changes: 28 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"root": true,
"extends": [
"eslint:recommended",
"plugin:n/recommended",
"airbnb-base",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"airbnb-typescript/base",
"plugin:prettier/recommended"
],
"parserOptions": {
"project": ["./tsconfig.json"],
"ecmaVersion": 2022,
"sourceType": "module"
},
"env": {
"node": true
},
"rules": {
"import/prefer-default-export": "off",
"node/no-missing-import": "off",
"@typescript-eslint/return-await": ["error", "always"],
"@typescript-eslint/member-ordering": ["error"],
"@typescript-eslint/no-empty-function": ["error", { "allow": ["private-constructors"] }],
"@typescript-eslint/array-type": ["error", { "default": "array-simple", "readonly": "array-simple" }]
}
}
8 changes: 8 additions & 0 deletions .github/codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
coverage:
status:
project:
default:
informational: true
patch:
default:
informational: true
10 changes: 10 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"assigneesFromCodeOwners": true,
"reviewersFromCodeOwners": true,
"labels": ["dependencies"],
"major": {
"dependencyDashboardApproval": false
}
}
70 changes: 70 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: CI

on:
push:
branches:
- master
pull_request:
branches:
- '**'

concurrency:
group: ${{ github.ref }}
cancel-in-progress: false

jobs:
install:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3

- uses: actions/setup-node@v3
with:
node-version: lts/*
cache: "yarn"

- name: Restore node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}

- run: yarn install --immutable

lint:
needs: [install]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3

- uses: actions/setup-node@v3
with:
node-version: lts/*
cache: "yarn"

- name: Restore node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}

- run: yarn lint

build:
needs: [install]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3

- uses: actions/setup-node@v3
with:
node-version: lts/*
cache: "yarn"

- name: Restore node_modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}

- run: yarn build
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
dist
node_modules
.env

# https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
10 changes: 10 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"arrowParens": "avoid",
"endOfLine": "lf"
}
873 changes: 873 additions & 0 deletions .yarn/releases/yarn-3.5.0.cjs

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enableGlobalCache: true

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-3.5.0.cjs
5 changes: 5 additions & 0 deletions nest-cli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}
48 changes: 48 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "hydra-proxy",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"license": "MIT",
"packageManager": "[email protected]",
"engines": {
"node": ">=18.0.0"
},
"scripts": {
"build": "nest build -p ./tsconfig.json",
"dev": "nest start --watch",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"devDependencies": {
"@nestjs/cli": "9.4.2",
"@tsconfig/esm": "1.0.3",
"@tsconfig/node18": "2.0.0",
"@tsconfig/strictest": "2.0.1",
"@types/express": "4.17.17",
"@types/morgan": "1.9.4",
"@types/node": "18.16.3",
"@typescript-eslint/eslint-plugin": "5.59.1",
"@typescript-eslint/parser": "5.59.1",
"eslint": "8.39.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.0.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-n": "15.7.0",
"eslint-plugin-prettier": "4.2.1",
"prettier": "2.8.8"
},
"dependencies": {
"@nestjs/common": "9.4.0",
"@nestjs/config": "2.3.1",
"@nestjs/core": "9.4.0",
"@nestjs/platform-express": "9.4.0",
"express": "4.18.2",
"morgan": "1.10.0",
"node-fetch": "^3.3.1",
"reflect-metadata": "0.1.13",
"typescript": "5.0.4",
"zod": "3.21.4"
}
}
27 changes: 27 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { z } from 'zod';
import { HydraController } from './hydra.controller.js';

const envSchema = z.object({
TARGET_HOST: z.string(),
USER_FIELD: z.string().optional(),
});

export type EnvironmentVariables = z.infer<typeof envSchema>;

export type AppConfigService = ConfigService<EnvironmentVariables>;

@Module({
controllers: [HydraController],
exports: [],
imports: [
ConfigModule.forRoot({
cache: true,
isGlobal: true,
validate: config => envSchema.parse(config),
}),
],
providers: [],
})
export class AppModule {}
75 changes: 75 additions & 0 deletions src/hydra.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
BadRequestException,
Controller,
Inject,
Logger,
Req,
RequestMapping,
RequestMethod,
Res,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import type { Request, Response } from 'express';
import fetch, { type RequestInit } from 'node-fetch';
import type { AppConfigService } from './app.module.js';

@Controller({ path: '*' })
export class HydraController {
private readonly logger = new Logger(HydraController.name);

constructor(
@Inject(ConfigService)
private configService: AppConfigService,
) {}

@RequestMapping({ path: '*', method: RequestMethod.ALL })
async index(@Req() req: Request, @Res() clientResponse: Response): Promise<void> {
const { method, url, headers } = req;

const [type, base64] = headers.authorization?.split(' ') ?? [];

if (!type || !base64) {
throw new BadRequestException(`Missing authorization header`);
}

if (type !== 'Basic') {
throw new BadRequestException(`Unsupported authorization type: ${type}`);
}

const [user, password] = Buffer.from(base64, 'base64').toString().split(':');

if (!user || !password) {
throw new BadRequestException(`Missing user or password`);
}

const userField = this.configService.get('USER_FIELD', { infer: true }) ?? 'username';

const body = JSON.stringify({ [userField]: user, password });

const targetUrl = new URL(url, `https://${this.configService.getOrThrow('TARGET_HOST', { infer: true })}`);

const reqInit: RequestInit = {
method,
body,
headers: { 'content-type': 'application/json' },
};

try {
const targetResponse = await fetch(targetUrl, reqInit);

if (targetResponse.status >= 500) {
this.logger.warn(`Target server responded with ${targetResponse.status}`);
}

targetResponse.headers.forEach((value, key) => {
clientResponse.setHeader(key, value);
});

clientResponse.status(targetResponse.status).send(targetResponse.body);
} catch (err) {
this.logger.error(err);
this.logger.log(reqInit);
throw err;
}
}
}
22 changes: 22 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NestFactory } from '@nestjs/core';
import morgan from 'morgan';
import { Logger } from '@nestjs/common';
import { AppModule } from './app.module.js';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const morganLogger = new Logger('morgan');
app.use(
morgan('[:date[iso]] ":method :url HTTP/:http-version" :status :res[content-length]', {
stream: {
write: msg => morganLogger.debug(msg.trim()),
},
}),
);
await app.listen(80);
}

bootstrap().catch(err =>
// eslint-disable-next-line no-console -- we're in main
console.error(err),
);
Loading

0 comments on commit 6633e19

Please sign in to comment.