@cloud-cli/px 3.4.1 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/proxy.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  /// <reference types="node" />
2
- import { IncomingMessage, ServerResponse } from 'http';
2
+ /// <reference types="node" />
3
+ /// <reference types="node" />
4
+ /// <reference types="node" />
5
+ import { IncomingMessage, ClientRequest, ServerResponse } from 'http';
6
+ import { ServerOptions as HttpsServerOptions } from 'https';
7
+ import { SecureContext } from 'tls';
3
8
  import { Resource } from '@cloud-cli/store';
4
9
  export declare class ProxyEntry extends Resource {
5
10
  id: number;
@@ -10,12 +15,8 @@ export declare class ProxyEntry extends Resource {
10
15
  cors: boolean;
11
16
  constructor(p: Partial<ProxyEntry>);
12
17
  }
13
- interface Certificates {
14
- key: string;
15
- cert: string;
16
- }
17
18
  export declare class ProxyServer {
18
- protected certs: Record<string, Certificates>;
19
+ protected certs: Record<string, SecureContext>;
19
20
  protected targets: Record<string, ProxyEntry>;
20
21
  protected servers: any;
21
22
  start(): void;
@@ -23,8 +24,11 @@ export declare class ProxyServer {
23
24
  protected loadCertificates(): Promise<void>;
24
25
  protected loadTargets(): Promise<void>;
25
26
  protected serveRequest(req: IncomingMessage, res: ServerResponse, insecure?: boolean): void;
26
- protected getSslOptions(): {
27
- SNICallback(domain: string, cb: Function): any;
28
- };
27
+ protected getSslOptions(): HttpsServerOptions;
28
+ protected findRootDomain(domain: string): SecureContext;
29
+ protected setHeaders(from: IncomingMessage, to: ServerResponse | ClientRequest): void;
30
+ protected getOrigin(req: IncomingMessage): URL;
31
+ protected getHost(string: string): string;
32
+ protected setCorsHeaders(req: IncomingMessage, res: ServerResponse): void;
33
+ protected handleError(error: any, res: ServerResponse): void;
29
34
  }
30
- export {};
package/dist/proxy.js CHANGED
@@ -4,12 +4,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { createServer as createHttpServer, request as httpRequest } from 'http';
7
+ import { createServer as createHttpServer, request as httpRequest, } from 'http';
8
8
  import { createServer as createHttpsServer, request as httpsRequest } from 'https';
9
9
  import { createSecureContext } from 'tls';
10
10
  import { Model, Primary, Property, Query, Resource } from '@cloud-cli/store';
11
- import { readdir, readFile, stat } from 'fs/promises';
12
- import { existsSync } from 'fs';
11
+ import { readdir, readFile } from 'fs/promises';
13
12
  import { join } from 'path';
14
13
  const certificatesFolder = process.env.PX_CERTS_FOLDER || '/etc/letsencrypt/live';
15
14
  const certificateFile = 'fullchain.pem';
@@ -62,12 +61,9 @@ export class ProxyServer {
62
61
  }
63
62
  async loadCertificates() {
64
63
  const certs = (this.certs = {});
65
- const localCerts = await readdir(certificatesFolder);
66
- const folders = await Promise.all(localCerts.map(async (dir) => {
67
- const status = await stat(join(certificatesFolder, dir));
68
- return status.isDirectory() ? dir : '';
69
- }));
70
- folders.filter(Boolean).forEach(async (rootDomain) => {
64
+ const localCerts = await readdir(certificatesFolder, { withFileTypes: true });
65
+ const folders = localCerts.filter((entry) => entry.isDirectory()).map((dir) => dir.name);
66
+ for (const rootDomain of folders) {
71
67
  if (process.env.DEBUG) {
72
68
  console.log(`Loading certificate for ${rootDomain}`);
73
69
  }
@@ -75,7 +71,7 @@ export class ProxyServer {
75
71
  cert: await readFile(join(certificatesFolder, rootDomain, certificateFile), 'utf8'),
76
72
  key: await readFile(join(certificatesFolder, rootDomain, keyFile), 'utf8'),
77
73
  });
78
- });
74
+ }
79
75
  }
80
76
  async loadTargets() {
81
77
  const targets = await Resource.find(ProxyEntry, new Query());
@@ -83,7 +79,7 @@ export class ProxyServer {
83
79
  targets.forEach((entry) => (this.targets[entry.domain] = entry));
84
80
  }
85
81
  serveRequest(req, res, insecure = false) {
86
- const origin = getOrigin(req);
82
+ const origin = this.getOrigin(req);
87
83
  const proxyEntry = this.targets[origin?.hostname];
88
84
  if (!origin.hostname || !proxyEntry) {
89
85
  res.writeHead(404, 'Not found');
@@ -107,24 +103,25 @@ export class ProxyServer {
107
103
  const url = new URL(req.url, target);
108
104
  const isCorsPreflight = req.method === 'OPTIONS' && proxyEntry.cors;
109
105
  if (isCorsPreflight) {
110
- setCorsHeaders(req, res);
106
+ this.setCorsHeaders(req, res);
111
107
  res.writeHead(204, { 'Content-Length': '0' });
112
108
  res.end();
113
109
  return;
114
110
  }
115
111
  const proxyRequest = (url.protocol === 'https:' ? httpsRequest : httpRequest)(url, { method: req.method });
116
- setHeaders(req, proxyRequest);
117
- proxyRequest.setHeader('host', getHost(target));
112
+ this.setHeaders(req, proxyRequest);
113
+ proxyRequest.setHeader('host', this.getHost(target));
118
114
  proxyRequest.setHeader('x-forwarded-for', req.headers.host);
119
- proxyRequest.setHeader('x-forwarded-proto', insecure ? 'http:' : 'https:');
115
+ proxyRequest.setHeader('x-forwarded-proto', insecure ? 'http' : 'https');
116
+ proxyRequest.setHeader('forwarded', 'host=' + req.headers.host + ';proto=' + insecure ? 'http' : 'https');
120
117
  req.on('data', (chunk) => proxyRequest.write(chunk));
121
118
  req.on('end', () => proxyRequest.end());
122
- proxyRequest.on('error', (error) => handleError(error, res));
119
+ proxyRequest.on('error', (error) => this.handleError(error, res));
123
120
  proxyRequest.on('response', (proxyRes) => {
124
- setHeaders(proxyRes, res);
121
+ this.setHeaders(proxyRes, res);
125
122
  const isCorsSimple = req.method !== 'OPTIONS' && proxyEntry.cors && req.headers.origin;
126
123
  if (isCorsSimple) {
127
- setCorsHeaders(req, res);
124
+ this.setCorsHeaders(req, res);
128
125
  }
129
126
  res.writeHead(proxyRes.statusCode, proxyRes.statusMessage);
130
127
  proxyRes.on('data', (chunk) => res.write(chunk));
@@ -135,59 +132,61 @@ export class ProxyServer {
135
132
  const server = this;
136
133
  return {
137
134
  SNICallback(domain, cb) {
138
- // TODO sanitize string to avoid file navigation!
139
- const rootDomain = findRootDomain(domain);
140
- if (rootDomain in server.certs) {
141
- return cb(null, server.certs[rootDomain]);
135
+ const rootDomain = server.findRootDomain(domain);
136
+ if (rootDomain) {
137
+ return cb(null, rootDomain);
142
138
  }
143
- throw new Error('Not found', { cause: 404 });
139
+ cb(new Error('Not found', { cause: 404 }), null);
144
140
  },
145
141
  };
146
142
  }
147
- }
148
- function findRootDomain(domain) {
149
- let rootDomain = '';
150
- const parts = domain.split('/')[0].split('.');
151
- while (parts.length) {
152
- rootDomain = parts.join('.');
153
- if (existsSync(join(certificatesFolder, rootDomain, certificateFile))) {
154
- return rootDomain;
143
+ findRootDomain(domain) {
144
+ const parts = domain.split('/')[0].split('.');
145
+ const certs = this.certs;
146
+ while (parts.length) {
147
+ const rootDomain = parts.join('.');
148
+ if (certs[rootDomain]) {
149
+ return certs[rootDomain];
150
+ }
151
+ parts.shift();
155
152
  }
156
- parts.shift();
153
+ return null;
157
154
  }
158
- return '';
159
- }
160
- function setHeaders(from, to) {
161
- const headers = Object.entries(from.headers);
162
- for (const header of headers) {
163
- to.setHeader(header[0], header[1]);
155
+ setHeaders(from, to) {
156
+ const headers = Object.entries(from.headers);
157
+ for (const header of headers) {
158
+ to.setHeader(header[0], header[1]);
159
+ }
164
160
  }
165
- }
166
- function handleError(error, res) {
167
- console.error(error);
168
- if (error.code === 'ECONNREFUSED') {
169
- res.writeHead(502);
170
- res.end();
171
- return;
161
+ getOrigin(req) {
162
+ const host = req.headers['x-forwarded-for'] || req.headers.host || '';
163
+ return (host && new URL('http://' + host)) || null;
172
164
  }
173
- if (!res.headersSent) {
174
- res.writeHead(500);
175
- res.end();
165
+ getHost(string) {
166
+ const url = new URL(string);
167
+ return url.hostname + (url.port ? ':' + url.port : '');
168
+ }
169
+ setCorsHeaders(req, res) {
170
+ const headers = req.headers;
171
+ const corsOrigin = new URL(req.headers.origin).origin;
172
+ const allowedMethod = headers['access-control-request-method'] || 'GET,HEAD,PUT,PATCH,POST,DELETE';
173
+ const allowedHeaders = headers['access-control-request-headers'] || '*';
174
+ res.setHeader('Vary', 'Origin');
175
+ res.setHeader('Access-Control-Allow-Origin', corsOrigin);
176
+ res.setHeader('Access-Control-Allow-Headers', allowedHeaders);
177
+ res.setHeader('Access-Control-Allow-Methods', allowedMethod);
178
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
179
+ }
180
+ handleError(error, res) {
181
+ console.error(error);
182
+ if (error.code === 'ECONNREFUSED') {
183
+ res.writeHead(502);
184
+ res.end();
185
+ return;
186
+ }
187
+ if (!res.headersSent) {
188
+ res.writeHead(500);
189
+ res.end();
190
+ }
176
191
  }
177
- }
178
- function getOrigin(req) {
179
- const host = req.headers['x-forwarded-host'] || req.headers.host || '';
180
- return (host && new URL('http://' + host)) || null;
181
- }
182
- function getHost(string) {
183
- const url = new URL(string);
184
- return url.hostname + (url.port ? ':' + url.port : '');
185
- }
186
- function setCorsHeaders(req, res) {
187
- const corsOrigin = new URL(req.headers.origin).origin;
188
- res.setHeader('Vary', 'Origin');
189
- res.setHeader('Access-Control-Allow-Origin', corsOrigin);
190
- res.setHeader('Access-Control-Allow-Headers', '*');
191
- res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE');
192
- res.setHeader('Access-Control-Allow-Credentials', 'true');
193
192
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloud-cli/px",
3
- "version": "3.4.1",
3
+ "version": "3.4.2",
4
4
  "description": "Reverse Proxy",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",