@jsenv/https-local 2.0.0 → 3.0.1

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
@@ -61,13 +61,13 @@ node ./install_certificate_authority.mjs
61
61
  *
62
62
  * > node ./install_certificate_authority.mjs
63
63
  *
64
- * Read more in https://github.com/jsenv/https-local#requestCertificateForLocalhost
64
+ * Read more in https://github.com/jsenv/https-local#requestCertificate
65
65
  */
66
66
 
67
67
  import { createServer } from "node:https"
68
- import { requestCertificateForLocalhost } from "@jsenv/https-local"
68
+ import { requestCertificate } from "@jsenv/https-local"
69
69
 
70
- const { certificate, privateKey } = requestCertificateForLocalhost()
70
+ const { certificate, privateKey } = requestCertificate()
71
71
 
72
72
  const server = createServer(
73
73
  {
@@ -99,10 +99,10 @@ The rest of the documentation goes into details.
99
99
 
100
100
  # Certificate expiration
101
101
 
102
- | Certificate | Expires after | How to renew? |
103
- | ----------- | ------------- | --------------------------------------- |
104
- | server | 1 year | Re-run _requestCertificateForLocalhost_ |
105
- | authority | 20 year | Re-run _installCertificateAuthority_ |
102
+ | Certificate | Expires after | How to renew? |
103
+ | ----------- | ------------- | ------------------------------------ |
104
+ | server | 1 year | Re-run _requestCertificate_ |
105
+ | authority | 20 year | Re-run _installCertificateAuthority_ |
106
106
 
107
107
  The **server** certificate expires after one year which is the maximum duration allowed by web browsers. In the unlikely scenario where your local server is running for more than a year without interruption, restart it and you're good for one more year.
108
108
 
@@ -352,15 +352,15 @@ Check if certificate is trusted by firefox...
352
352
 
353
353
  </details>
354
354
 
355
- # requestCertificateForLocalhost
355
+ # requestCertificate
356
356
 
357
- _requestCertificateForLocalhost_ function returns a certificate and private key that can be used to start a server in HTTPS.
357
+ _requestCertificate_ function returns a certificate and private key that can be used to start a server in HTTPS.
358
358
 
359
359
  ```js
360
360
  import { createServer } from "node:https"
361
- import { requestCertificateForLocalhost } from "@jsenv/https-local"
361
+ import { requestCertificate } from "@jsenv/https-local"
362
362
 
363
- const { certificate, privateKey } = requestCertificateForLocalhost({
363
+ const { certificate, privateKey } = requestCertificate({
364
364
  altNames: ["localhost", "local.example"],
365
365
  })
366
366
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/https-local",
3
- "version": "2.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "A programmatic way to generate locally trusted certificates",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -13,8 +13,7 @@
13
13
  "url": "https://github.com/jsenv/https-local"
14
14
  },
15
15
  "publishConfig": {
16
- "access": "public",
17
- "registry": "https://registry.npmjs.org"
16
+ "access": "public"
18
17
  },
19
18
  "engines": {
20
19
  "node": ">=16.13.0"
@@ -48,9 +47,9 @@
48
47
  "playwright-install": "npx playwright install-deps && npx playwright install"
49
48
  },
50
49
  "dependencies": {
51
- "@jsenv/filesystem": "4.1.0",
52
- "@jsenv/log": "2.0.1",
53
- "@jsenv/urls": "1.2.6",
50
+ "@jsenv/filesystem": "4.1.2",
51
+ "@jsenv/log": "3.1.0",
52
+ "@jsenv/urls": "1.2.7",
54
53
  "command-exists": "1.2.9",
55
54
  "node-forge": "1.3.1",
56
55
  "sudo-prompt": "9.2.1",
@@ -58,15 +57,15 @@
58
57
  },
59
58
  "devDependencies": {
60
59
  "@jsenv/assert": "2.6.0",
61
- "@jsenv/core": "27.0.0-alpha.92",
62
- "@jsenv/eslint-config": "16.0.9",
60
+ "@jsenv/core": "28.0.2",
61
+ "@jsenv/eslint-config": "16.2.1",
63
62
  "@jsenv/eslint-import-resolver": "0.3.0",
64
- "@jsenv/github-release-package": "1.4.0",
65
- "@jsenv/package-publish": "1.7.5",
63
+ "@jsenv/github-release-package": "1.5.0",
64
+ "@jsenv/package-publish": "1.10.0",
66
65
  "@jsenv/performance-impact": "2.3.0",
67
- "eslint": "8.18.0",
66
+ "eslint": "8.21.0",
68
67
  "eslint-plugin-import": "2.26.0",
69
- "playwright": "1.22.2",
68
+ "playwright": "1.24.2",
70
69
  "prettier": "2.7.1"
71
70
  }
72
- }
71
+ }
@@ -231,13 +231,12 @@ export const installCertificateAuthority = async ({
231
231
  const rootCertificatePrivateKeyForgeObject = pki.privateKeyFromPem(
232
232
  rootCertificatePrivateKey,
233
233
  )
234
-
235
234
  const trustInfo = await platformMethods.executeTrustQuery({
236
235
  logger,
237
236
  certificateCommonName,
238
237
  certificateFileUrl: rootCertificateFileInfo.url,
239
238
  certificate: rootCertificate,
240
- verb: tryToTrust ? "ADD_TRUST" : "CHECK_TRUST",
239
+ verb: tryToTrust ? "ENSURE_TRUST" : "CHECK_TRUST",
241
240
  NSSDynamicInstall,
242
241
  })
243
242
 
@@ -11,7 +11,7 @@ import { getAuthorityFileInfos } from "./internal/authority_file_infos.js"
11
11
  import { requestCertificateFromAuthority } from "./internal/certificate_generator.js"
12
12
  import { formatDuration } from "./internal/validity_formatting.js"
13
13
 
14
- export const requestCertificateForLocalhost = ({
14
+ export const requestCertificate = ({
15
15
  logLevel,
16
16
  logger = createLogger({ logLevel }), // to be able to catch logs during unit tests
17
17
 
@@ -47,7 +47,7 @@ export const requestCertificateForLocalhost = ({
47
47
  } = getAuthorityFileInfos()
48
48
  if (!rootCertificateFileInfo.exists) {
49
49
  throw new Error(
50
- `Certificate authority not found, "installCertificateAuthority" must be called before "requestCertificateForLocalhost"`,
50
+ `Certificate authority not found, "installCertificateAuthority" must be called before "requestServerCertificate"`,
51
51
  )
52
52
  }
53
53
  if (!rootCertificatePrivateKeyFileInfo.exists) {
@@ -81,10 +81,6 @@ export const requestCertificateForLocalhost = ({
81
81
  JSON.stringify({ serialNumber: serverCertificateSerialNumber }, null, " "),
82
82
  )
83
83
 
84
- if (!altNames.includes("localhost")) {
85
- altNames.push("localhost")
86
- }
87
-
88
84
  logger.debug(`Generating server certificate...`)
89
85
  const { certificateForgeObject, certificatePrivateKeyForgeObject } =
90
86
  requestCertificateFromAuthority({
@@ -10,6 +10,7 @@ import { createDetailedMessage, UNICODE } from "@jsenv/log"
10
10
  import {
11
11
  VERB_CHECK_TRUST,
12
12
  VERB_ADD_TRUST,
13
+ VERB_ENSURE_TRUST,
13
14
  VERB_REMOVE_TRUST,
14
15
  } from "../trust_query.js"
15
16
  import { exec } from "../exec.js"
@@ -100,7 +101,11 @@ export const executeTrustQueryOnLinux = async ({
100
101
  }
101
102
 
102
103
  logger.info(`${UNICODE.OK} certificate found in linux`)
103
- if (verb === VERB_CHECK_TRUST || verb === VERB_ADD_TRUST) {
104
+ if (
105
+ verb === VERB_CHECK_TRUST ||
106
+ verb === VERB_ADD_TRUST ||
107
+ verb === VERB_ENSURE_TRUST
108
+ ) {
104
109
  return {
105
110
  status: "trusted",
106
111
  reason: REASON_FOUND_IN_LINUX,
@@ -78,5 +78,6 @@ export const executeTrustQueryOnFirefox = ({
78
78
  }
79
79
 
80
80
  const isFirefoxOpen = () => {
81
- return execSync("ps aux").includes("firefox")
81
+ const psAux = execSync("ps aux")
82
+ return psAux.includes("Firefox.app")
82
83
  }
@@ -8,6 +8,7 @@ import { searchCertificateInCommandOutput } from "../search_certificate_in_comma
8
8
  import {
9
9
  VERB_CHECK_TRUST,
10
10
  VERB_ADD_TRUST,
11
+ VERB_ENSURE_TRUST,
11
12
  VERB_REMOVE_TRUST,
12
13
  } from "../trust_query.js"
13
14
 
@@ -52,6 +53,35 @@ export const executeTrustQueryOnMacKeychain = async ({
52
53
  certificate,
53
54
  )
54
55
 
56
+ const removeCert = async () => {
57
+ // https://ss64.com/osx/security-delete-cert.html
58
+ const removeTrustedCertCommand = `sudo security delete-certificate -c "${certificateCommonName}"`
59
+ logger.info(`Removing certificate from mac keychain...`)
60
+ logger.info(`${UNICODE.COMMAND} ${removeTrustedCertCommand}`)
61
+ try {
62
+ await exec(removeTrustedCertCommand)
63
+ logger.info(`${UNICODE.OK} certificate removed from mac keychain`)
64
+ return {
65
+ status: "not_trusted",
66
+ reason: REASON_REMOVE_FROM_KEYCHAIN_COMMAND_COMPLETED,
67
+ }
68
+ } catch (e) {
69
+ logger.error(
70
+ createDetailedMessage(
71
+ `${UNICODE.FAILURE} failed to remove certificate from mac keychain`,
72
+ {
73
+ "error stack": e.stack,
74
+ "certificate file url": certificateFileUrl,
75
+ },
76
+ ),
77
+ )
78
+ return {
79
+ status: "not_trusted",
80
+ reason: REASON_REMOVE_FROM_KEYCHAIN_COMMAND_FAILED,
81
+ }
82
+ }
83
+ }
84
+
55
85
  if (!certificateFoundInCommandOutput) {
56
86
  logger.info(`${UNICODE.INFO} certificate not found in mac keychain`)
57
87
  if (verb === VERB_CHECK_TRUST || verb === VERB_REMOVE_TRUST) {
@@ -60,7 +90,13 @@ export const executeTrustQueryOnMacKeychain = async ({
60
90
  reason: REASON_NOT_IN_KEYCHAIN,
61
91
  }
62
92
  }
63
-
93
+ if (verb === VERB_ENSURE_TRUST) {
94
+ // It seems possible for certificate PEM representation to be different
95
+ // in mackeychain and in the one we have written on the filesystem
96
+ // When it happens the certificate is not found but actually exists on mackeychain
97
+ // and must be deleted first
98
+ await removeCert()
99
+ }
64
100
  const certificateFilePath = fileURLToPath(certificateFileUrl)
65
101
  // https://ss64.com/osx/security-cert.html
66
102
  const addTrustedCertCommand = `sudo security add-trusted-cert -d -r trustRoot -k ${systemKeychainPath} "${certificateFilePath}"`
@@ -95,37 +131,16 @@ export const executeTrustQueryOnMacKeychain = async ({
95
131
  // but they shouldn't and I couldn't find an API to know if the cert is trusted or not
96
132
  // just if it's in the keychain
97
133
  logger.info(`${UNICODE.OK} certificate found in mac keychain`)
98
- if (verb === VERB_CHECK_TRUST || verb === VERB_ADD_TRUST) {
134
+ if (
135
+ verb === VERB_CHECK_TRUST ||
136
+ verb === VERB_ADD_TRUST ||
137
+ verb === VERB_ENSURE_TRUST
138
+ ) {
99
139
  return {
100
140
  status: "trusted",
101
141
  reason: REASON_IN_KEYCHAIN,
102
142
  }
103
143
  }
104
144
 
105
- // https://ss64.com/osx/security-delete-cert.html
106
- const removeTrustedCertCommand = `sudo security delete-certificate -c "${certificateCommonName}"`
107
- logger.info(`Removing certificate from mac keychain...`)
108
- logger.info(`${UNICODE.COMMAND} ${removeTrustedCertCommand}`)
109
- try {
110
- await exec(removeTrustedCertCommand)
111
- logger.info(`${UNICODE.OK} certificate removed from mac keychain`)
112
- return {
113
- status: "not_trusted",
114
- reason: REASON_REMOVE_FROM_KEYCHAIN_COMMAND_COMPLETED,
115
- }
116
- } catch (e) {
117
- logger.error(
118
- createDetailedMessage(
119
- `${UNICODE.FAILURE} failed to remove certificate from mac keychain`,
120
- {
121
- "error stack": e.stack,
122
- "certificate file url": certificateFileUrl,
123
- },
124
- ),
125
- )
126
- return {
127
- status: "not_trusted",
128
- reason: REASON_REMOVE_FROM_KEYCHAIN_COMMAND_FAILED,
129
- }
130
- }
145
+ return removeCert()
131
146
  }
@@ -11,7 +11,11 @@ import { assertAndNormalizeDirectoryUrl, collectFiles } from "@jsenv/filesystem"
11
11
 
12
12
  import { exec } from "./exec.js"
13
13
  import { searchCertificateInCommandOutput } from "./search_certificate_in_command_output.js"
14
- import { VERB_CHECK_TRUST, VERB_ADD_TRUST } from "./trust_query.js"
14
+ import {
15
+ VERB_CHECK_TRUST,
16
+ VERB_ADD_TRUST,
17
+ VERB_ENSURE_TRUST,
18
+ } from "./trust_query.js"
15
19
 
16
20
  export const executeTrustQueryOnBrowserNSSDB = async ({
17
21
  logger,
@@ -52,7 +56,7 @@ export const executeTrustQueryOnBrowserNSSDB = async ({
52
56
  const nssIsInstalled = await detectIfNSSIsInstalled({ logger })
53
57
  const cannotCheckMessage = `${UNICODE.FAILURE} cannot check if certificate is in ${browserName}`
54
58
  if (!nssIsInstalled) {
55
- if (verb === VERB_ADD_TRUST) {
59
+ if (verb === VERB_ADD_TRUST || verb === VERB_ENSURE_TRUST) {
56
60
  const nssDynamicInstallInfo = await getNSSDynamicInstallInfo({ logger })
57
61
  if (!nssDynamicInstallInfo.isInstallable) {
58
62
  const reason = `"${nssCommandName}" is not installed and not cannot be installed`
@@ -209,7 +213,7 @@ export const executeTrustQueryOnBrowserNSSDB = async ({
209
213
  }
210
214
  }
211
215
 
212
- if (verb === VERB_ADD_TRUST) {
216
+ if (verb === VERB_ADD_TRUST || verb === VERB_ENSURE_TRUST) {
213
217
  if (missingCount === 0 && outdatedCount === 0) {
214
218
  logger.info(`${UNICODE.OK} certificate found in ${browserName}`)
215
219
  return {
@@ -1,5 +1,4 @@
1
1
  export const VERB_CHECK_TRUST = "CHECK_TRUST"
2
-
3
2
  export const VERB_ADD_TRUST = "ADD_TRUST"
4
-
3
+ export const VERB_ENSURE_TRUST = "ENSURE_TRUST"
5
4
  export const VERB_REMOVE_TRUST = "REMOVE_TRUST"
@@ -10,6 +10,7 @@ import { exec } from "../exec.js"
10
10
  import {
11
11
  VERB_CHECK_TRUST,
12
12
  VERB_ADD_TRUST,
13
+ VERB_ENSURE_TRUST,
13
14
  VERB_REMOVE_TRUST,
14
15
  } from "../trust_query.js"
15
16
 
@@ -93,7 +94,11 @@ export const executeTrustQueryOnWindows = async ({
93
94
  }
94
95
 
95
96
  logger.info(`${UNICODE.OK} certificate found in windows`)
96
- if (verb === VERB_CHECK_TRUST || verb === VERB_ADD_TRUST) {
97
+ if (
98
+ verb === VERB_CHECK_TRUST ||
99
+ verb === VERB_ADD_TRUST ||
100
+ verb === VERB_ENSURE_TRUST
101
+ ) {
97
102
  return {
98
103
  status: "trusted",
99
104
  reason: REASON_FOUND_IN_WINDOWS,
package/src/main.js CHANGED
@@ -16,4 +16,4 @@ export {
16
16
 
17
17
  export { verifyHostsFile } from "./hosts_file_verif.js"
18
18
 
19
- export { requestCertificateForLocalhost } from "./certificate_for_localhost.js"
19
+ export { requestCertificate } from "./certificate_request.js"