Skip to content

Commit a500a97

Browse files
committed
feat: persist and reuse generated https certificates
1 parent df3e604 commit a500a97

File tree

3 files changed

+43
-0
lines changed

3 files changed

+43
-0
lines changed

src/_cert.ts

+36
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
import { promisify } from "node:util";
33
import nodeOS from "node:os";
44
import { promises as fs } from "node:fs";
5+
import { mkdir, writeFile } from "node:fs/promises";
56
import type nodeForge from "node-forge";
67
import forge from "node-forge";
78
import ipRegex from "ip-regex";
89
import { defu } from "defu";
10+
import { resolve } from "pathe";
911
import type { Certificate, HTTPSOptions } from "./types";
1012

13+
const certificateDirectory = "node_modules/.listhen/certs/";
14+
1115
export interface CertificateOptions {
1216
validityDays: number;
1317
subject: nodeForge.pki.CertificateField[];
@@ -40,10 +44,34 @@ export interface TLSCertOptions
4044
passphrase?: string;
4145
}
4246

47+
function pathExists(path: string) {
48+
return new Promise((resolve) => {
49+
fs.access(path, fs.constants.F_OK)
50+
.then(() => resolve(true))
51+
.catch(() => resolve(false));
52+
});
53+
}
54+
4355
export async function resolveCertificate(
4456
options: HTTPSOptions,
4557
): Promise<Certificate> {
4658
let https: Certificate;
59+
60+
if (typeof options === "object" && options.reuse) {
61+
// Reuse previously autogenerated certificates if exists
62+
const certExists = await pathExists(certificateDirectory + "cert.pem");
63+
const keyExists = await pathExists(certificateDirectory + "cert-key.pem");
64+
65+
if (certExists && keyExists) {
66+
const cert = await fs.readFile(certificateDirectory + "cert.pem");
67+
const key = await fs.readFile(certificateDirectory + "cert-key.pem");
68+
return {
69+
cert: cert.toString("utf8"),
70+
key: key.toString("utf8"),
71+
};
72+
}
73+
}
74+
4775
if (typeof options === "object" && options.key && options.cert) {
4876
// Resolve actual certificate and cert
4977
https = await resolveCert(options);
@@ -95,6 +123,10 @@ async function generateCertificates(
95123
caOptions.passphrase = options.signingKeyPassphrase;
96124
const ca = await generateCACert(caOptions);
97125

126+
await mkdir(resolve(certificateDirectory), { recursive: true });
127+
await writeFile(resolve(certificateDirectory + "ca.pem"), ca.cert);
128+
await writeFile(resolve(certificateDirectory + "ca-key.pem"), ca.key);
129+
98130
const domains = Array.isArray(options.domains)
99131
? options.domains
100132
: ["localhost", "127.0.0.1", "::1"];
@@ -106,6 +138,10 @@ async function generateCertificates(
106138
signingKey: ca.key,
107139
domains,
108140
});
141+
142+
await writeFile(resolve(certificateDirectory + "cert.pem"), cert.cert);
143+
await writeFile(resolve(certificateDirectory + "cert-key.pem"), cert.key);
144+
109145
return { ca, cert };
110146
}
111147

src/cli.ts

+6
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ export function getArgs() {
8989
type: "boolean",
9090
description: "Enable HTTPS",
9191
},
92+
"https.reuse": {
93+
type: "boolean",
94+
description: "Reuse previously autogenerated certificates",
95+
default: false,
96+
},
9297
"https.cert": {
9398
type: "string",
9499
description: "Path to TLS certificate used with HTTPS in PEM format",
@@ -155,6 +160,7 @@ export function parseArgs(args: ParsedListhenArgs): Partial<ListenOptions> {
155160
tunnel: args.tunnel,
156161
https: args.https
157162
? <HTTPSOptions>{
163+
reuse: args["https.reuse"],
158164
cert: args["https.cert"],
159165
key: args["https.key"],
160166
pfx: args["https.pfx"],

src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface Certificate {
1010
}
1111

1212
export interface HTTPSOptions {
13+
reuse?: boolean;
1314
cert?: string;
1415
key?: string;
1516
pfx?: string;

0 commit comments

Comments
 (0)