@cloud-cli/px 1.1.1 → 2.0.0

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/README.md CHANGED
@@ -4,51 +4,48 @@ Reverse proxy to map local services to http(s) subdomains
4
4
 
5
5
  #### Usage
6
6
 
7
+ Use it with [Cloudy](https://github.com/cloud-cli/cli)
8
+
7
9
  ```ts
8
10
  // cloudy.conf.mjs
9
11
 
10
12
  import proxy from '@cloud-cli/px';
11
13
 
12
14
  export default { proxy };
13
-
14
15
  ```
15
16
 
16
17
  #### API
17
18
 
18
- **Add a certificate**
19
-
20
- ```
21
- cy proxy.addCertificate --domain="example.com" --certificate @path/to/cert --key @path/to/key
22
- ```
23
-
24
- **Remove a certificate**
19
+ **Add a proxy to a local service**
25
20
 
26
21
  ```
27
- cy proxy.removeCertificate --domain example.com
22
+ cy proxy.add --domain "foo.example.com" --target "localhost:1234"
23
+ cy proxy.add --domain "bar.example.com/api" --target "localhost:3000"
28
24
  ```
29
25
 
30
- **Add a proxy to a local service**
26
+ **Remove a proxy**
31
27
 
32
28
  ```
33
- cy proxy.addProxy --domain "foo.example.com" --target "localhost:1234"
29
+ cy proxy.remove --domain "foo.example.com" --target "localhost:1234"
30
+ cy proxy.remove --domain "bar.example.com/api" --target "localhost:1234"
34
31
  ```
35
32
 
36
- **Remove a proxy**
33
+ **Get details of a proxy**
37
34
 
38
35
  ```
39
- cy proxy.removeProxy --domain "foo.example.com"
36
+ cy proxy.get --domain "foo.example.com"
40
37
  ```
41
38
 
42
39
  **List proxies**
43
40
 
44
41
  ```
45
- cy proxy.getProxyList
42
+ cy proxy.list
46
43
  ```
47
44
 
48
45
  **List registered domains**
49
46
 
50
47
  ```
51
- cy proxy.getDomainList
48
+ cy proxy.domains
52
49
  ```
53
50
 
54
51
  **Reload all configurations**
@@ -56,3 +53,19 @@ cy proxy.getDomainList
56
53
  ```
57
54
  cy proxy.reload
58
55
  ```
56
+
57
+ ## Certificate file resolution
58
+
59
+ ```
60
+ certificatesFolder = '/etc/letsencrypt/live' or process.env.PX_CERTS_FOLDER
61
+ rootDomain = 'example.com' in 'xyz.example.com'
62
+ cert = certificatesFolder + '/' + rootDomain + '/' + certificateFile
63
+ key = certificatesFolder + '/' + rootDomain + '/' + keyFile
64
+ ```
65
+
66
+ So xyz.example.com points to:
67
+
68
+ ```
69
+ /etc/letsencrypt/live/example.com/fullchain.pem for certificate
70
+ /etc/letsencrypt/live/example.com/privkey.pem for private key
71
+ ```
package/dist/index.d.ts CHANGED
@@ -1,12 +1,10 @@
1
- import { Proxy, DomainOption, Certificate } from './proxy-manager.js';
1
+ import { HttpProxy, Domain } from './proxy-manager.js';
2
2
  declare const _default: {
3
- addProxy(options: Proxy): void;
4
- removeProxy(options: DomainOption): void;
5
- getProxy(options: DomainOption): Proxy;
6
- addCertificate(options: Certificate): void;
7
- removeCertificate(options: DomainOption): void;
3
+ add(options: HttpProxy): Promise<unknown>;
4
+ remove(options: HttpProxy): Promise<unknown>;
5
+ get(options: Domain): HttpProxy[];
8
6
  reload(): void;
9
- getDomainList(): string[];
10
- getProxyList(): [string, Proxy][];
7
+ domains(): string[];
8
+ list(): string[];
11
9
  };
12
10
  export default _default;
package/dist/index.js CHANGED
@@ -1,28 +1,22 @@
1
1
  import { ProxyManager } from './proxy-manager.js';
2
2
  const manager = new ProxyManager();
3
3
  export default {
4
- addProxy(options) {
4
+ add(options) {
5
5
  return manager.addProxy(options);
6
6
  },
7
- removeProxy(options) {
7
+ remove(options) {
8
8
  return manager.removeProxy(options);
9
9
  },
10
- getProxy(options) {
11
- return manager.getProxyForDomain(options);
12
- },
13
- addCertificate(options) {
14
- return manager.addCertificate(options);
15
- },
16
- removeCertificate(options) {
17
- return manager.removeCertificate(options);
10
+ get(options) {
11
+ return manager.getProxyListForDomain(options);
18
12
  },
19
13
  reload() {
20
14
  return manager.reloadProxies();
21
15
  },
22
- getDomainList() {
16
+ domains() {
23
17
  return manager.getDomainList();
24
18
  },
25
- getProxyList() {
19
+ list() {
26
20
  return manager.getProxyList();
27
- }
21
+ },
28
22
  };
@@ -1,30 +1,23 @@
1
- export interface DomainOption {
1
+ export interface Domain {
2
2
  domain: string;
3
3
  }
4
- export interface Proxy {
5
- domain: string;
4
+ export interface HttpProxy extends Domain {
6
5
  target: string;
7
6
  }
8
- export interface Certificate {
9
- domain: string;
10
- certificate: string;
11
- key: string;
12
- }
13
7
  export declare class ProxyManager {
14
- private proxyConfiguration;
8
+ private proxyList;
15
9
  private _proxy;
16
10
  private get proxy();
17
- constructor();
18
- addProxy({ domain, target }: Proxy): void;
19
- removeProxy(options: DomainOption): void;
20
- addCertificate({ domain, certificate, key }: Certificate): void;
21
- removeCertificate(options: DomainOption): void;
11
+ addProxy(proxy: HttpProxy): Promise<unknown>;
12
+ removeProxy(options: HttpProxy): Promise<unknown>;
22
13
  getDomainList(): string[];
23
- getProxyForDomain(options: DomainOption): Proxy;
14
+ getProxyList(): string[];
15
+ getProxyListForDomain(options: Domain): HttpProxy[];
24
16
  reloadProxies(): void;
25
- getProxyList(): [string, Proxy][];
17
+ private readProxyList;
18
+ private disconnectDomainAndTarget;
26
19
  private connectDomainToTarget;
27
- private writeFile;
20
+ private findRootDomain;
28
21
  private saveProxies;
29
22
  private closeProxy;
30
23
  }
@@ -1,16 +1,17 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
- import { join } from "path";
3
- import redbird from "redbird";
4
- const certificatesFolder = join(process.cwd(), "certs");
5
- const configurationFile = join(process.cwd(), "data", "px.json");
6
- const keyExtension = ".key";
7
- const certificateExtension = ".cert";
1
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import redbird from 'redbird';
4
+ const certificatesFolder = process.env.PX_CERTS_FOLDER || '/etc/letsencrypt/live';
5
+ const certificateFile = 'fullchain.pem';
6
+ const keyFile = 'privkey.pem';
7
+ const configurationFile = join(process.cwd(), 'data', 'px.json');
8
+ const domainNotSpecifiedError = new Error('Domain not specified');
9
+ const targetNotSpecifiedError = new Error('Target not specified');
10
+ const notFoundError = new Error('Proxy not found');
11
+ const proxyKey = ({ domain, target }) => `${domain} => ${target}`;
8
12
  export class ProxyManager {
9
13
  constructor() {
10
- this.proxyConfiguration = new Map();
11
- if (!existsSync(certificatesFolder)) {
12
- mkdirSync(certificatesFolder, { recursive: true });
13
- }
14
+ this.proxyList = [];
14
15
  }
15
16
  get proxy() {
16
17
  if (!this._proxy) {
@@ -24,79 +25,91 @@ export class ProxyManager {
24
25
  }
25
26
  return this._proxy;
26
27
  }
27
- addProxy({ domain, target }) {
28
- this.connectDomainToTarget(domain, target);
29
- this.proxyConfiguration.set(domain, { domain, target });
30
- this.saveProxies();
28
+ addProxy(proxy) {
29
+ return new Promise((resolve, reject) => {
30
+ if (!proxy.domain) {
31
+ return reject(domainNotSpecifiedError);
32
+ }
33
+ if (!proxy.target) {
34
+ return reject(targetNotSpecifiedError);
35
+ }
36
+ this.connectDomainToTarget(proxy);
37
+ this.saveProxies();
38
+ resolve(null);
39
+ });
31
40
  }
32
41
  removeProxy(options) {
33
- const { domain } = options;
34
- if (!this.proxyConfiguration.has(domain)) {
35
- return;
36
- }
37
- const { target } = this.proxyConfiguration.get(domain);
38
- this.proxy.unregister(domain, target);
39
- this.proxyConfiguration.delete(domain);
40
- this.saveProxies();
41
- }
42
- addCertificate({ domain, certificate, key }) {
43
- const basePath = join(certificatesFolder, domain);
44
- this.writeFile(basePath + certificateExtension, certificate);
45
- this.writeFile(basePath + keyExtension, key);
46
- }
47
- removeCertificate(options) {
48
- const basePath = join(certificatesFolder, options.domain);
49
- rmSync(basePath + certificateExtension, { force: true });
50
- rmSync(basePath + keyExtension, { force: true });
42
+ return new Promise((resolve, reject) => {
43
+ if (!options.domain) {
44
+ return reject(domainNotSpecifiedError);
45
+ }
46
+ if (!options.target) {
47
+ return reject(targetNotSpecifiedError);
48
+ }
49
+ const proxy = this.proxyList.find((p) => proxyKey(p) === proxyKey(options));
50
+ if (!proxy) {
51
+ return reject(notFoundError);
52
+ }
53
+ this.disconnectDomainAndTarget(proxy);
54
+ this.saveProxies();
55
+ resolve(true);
56
+ });
51
57
  }
52
58
  getDomainList() {
53
- return Array.from(this.proxyConfiguration.keys() || []);
59
+ return this.proxyList.map((p) => p.domain);
54
60
  }
55
- getProxyForDomain(options) {
56
- return this.proxyConfiguration.get(options.domain);
61
+ getProxyList() {
62
+ return this.proxyList.map(proxyKey);
63
+ }
64
+ getProxyListForDomain(options) {
65
+ return this.proxyList.filter((p) => p.domain === options.domain);
57
66
  }
58
67
  reloadProxies() {
59
68
  this.closeProxy();
60
- this.proxyConfiguration.clear();
61
- const proxyList = this.getProxyList();
62
- proxyList.forEach(([key, proxy]) => {
63
- const { domain, target } = proxy;
64
- this.proxyConfiguration.set(key, proxy);
65
- this.connectDomainToTarget(domain, target);
66
- });
69
+ this.proxyList = [];
70
+ this.readProxyList().forEach((proxy) => this.connectDomainToTarget(proxy));
67
71
  }
68
- getProxyList() {
69
- if (!existsSync(configurationFile))
72
+ readProxyList() {
73
+ if (!existsSync(configurationFile)) {
70
74
  return [];
71
- const json = readFileSync(configurationFile, "utf8") || "[]";
72
- const entries = JSON.parse(json);
73
- return entries;
75
+ }
76
+ const json = readFileSync(configurationFile, 'utf8') || '[]';
77
+ return JSON.parse(json);
78
+ }
79
+ disconnectDomainAndTarget(proxy) {
80
+ const { domain, target } = proxy;
81
+ this.proxy.unregister(domain, target);
82
+ this.proxyList = this.proxyList.filter((p) => proxyKey(p) !== proxyKey(proxy));
83
+ }
84
+ connectDomainToTarget(proxy) {
85
+ const { domain, target } = proxy;
86
+ const rootDomain = this.findRootDomain(domain);
87
+ const ssl = { redirect: false };
88
+ if (rootDomain) {
89
+ Object.assign(ssl, {
90
+ redirect: true,
91
+ cert: join(certificatesFolder, rootDomain, certificateFile),
92
+ key: join(certificatesFolder, rootDomain, keyFile),
93
+ });
94
+ }
95
+ this.proxy.register(domain, target, { ssl });
96
+ this.proxyList = this.proxyList.filter((p) => proxyKey(p) !== proxyKey(proxy)).concat(proxy);
74
97
  }
75
- connectDomainToTarget(domain, target) {
76
- const parts = domain.split(".");
77
- let rootDomain = "";
98
+ findRootDomain(domain) {
99
+ let rootDomain = '';
100
+ const parts = domain.split('/')[0].split('.');
78
101
  while (parts.length) {
79
- rootDomain = parts.join(".");
80
- if (existsSync(join(certificatesFolder, rootDomain) + keyExtension))
81
- break;
102
+ rootDomain = parts.join('.');
103
+ if (existsSync(join(certificatesFolder, rootDomain, certificateFile))) {
104
+ return rootDomain;
105
+ }
82
106
  parts.shift();
83
107
  }
84
- if (!rootDomain) {
85
- throw new Error("Certificate not found for " + domain);
86
- }
87
- this.proxy.register(domain, target, {
88
- ssl: {
89
- cert: join(certificatesFolder, rootDomain) + certificateExtension,
90
- key: join(certificatesFolder, rootDomain) + keyExtension,
91
- },
92
- });
93
- }
94
- writeFile(path, content) {
95
- writeFileSync(path, content);
108
+ return '';
96
109
  }
97
110
  saveProxies() {
98
- const proxies = Array.from(this.proxyConfiguration.entries());
99
- this.writeFile(configurationFile, JSON.stringify(proxies));
111
+ const proxies = Array.from(this.proxyList.entries());
112
+ writeFileSync(configurationFile, JSON.stringify(proxies));
100
113
  }
101
114
  closeProxy() {
102
115
  this.proxy.close();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloud-cli/px",
3
- "version": "1.1.1",
4
- "description": "Reverse Proxy API/module",
3
+ "version": "2.0.0",
4
+ "description": "Reverse Proxy",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "types": "./dist/index.d.ts",
@@ -9,8 +9,7 @@
9
9
  "test": "echo true",
10
10
  "xtest": "jest",
11
11
  "tdd": "jest --watchAll",
12
- "build": "rm -rf ./dist/* && tsc --project ./tsconfig.json",
13
- "start": "node dist/server.js"
12
+ "build": "rm -rf ./dist/* && tsc --project ./tsconfig.json"
14
13
  },
15
14
  "keywords": [
16
15
  "cloudy"
@@ -37,7 +36,6 @@
37
36
  "access": "public"
38
37
  },
39
38
  "dependencies": {
40
- "@cloud-cli/gw": "^1.1.2",
41
39
  "redbird": "^0.10.0"
42
40
  }
43
41
  }