localssl-cli 0.1.4 → 0.1.6

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
@@ -146,6 +146,13 @@ It never stores CA private keys.
146
146
  - Team file validation blocks private-key content in `localssl.json`
147
147
  - Never share root CA private key files
148
148
 
149
+ ## Windows permissions behavior
150
+
151
+ - localssl-cli first trusts certs in `CurrentUser\\Root` (no admin expected)
152
+ - if needed, it prompts: `Admin access needed for machine-wide trust. Continue? (y/N)`
153
+ - choosing `No` keeps safe mode and skips machine-wide trust
154
+ - rerunning `localssl-cli init` repairs trust if CA already exists
155
+
149
156
  ---
150
157
 
151
158
  ## Troubleshooting
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "localssl-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "One-command local HTTPS setup for teams",
5
5
  "type": "commonjs",
6
6
  "main": "src/index.js",
package/src/bootstrap.js CHANGED
@@ -99,8 +99,8 @@ async function trustSystem(certPath) {
99
99
  }
100
100
 
101
101
  if (process.platform === 'win32') {
102
- await trustWindows(certPath);
103
- return 'Windows Root store';
102
+ const scope = await trustWindows(certPath);
103
+ return scope;
104
104
  }
105
105
 
106
106
  await trustLinux(certPath);
@@ -138,8 +138,21 @@ async function initMachine({ quiet = false } = {}) {
138
138
 
139
139
  const hasCA = await fs.pathExists(LOCALSSL_CA_PUBLIC);
140
140
  if (hasCA) {
141
+ let repairSummary = 'already configured';
142
+ try {
143
+ const systemResult = await trustSystem(LOCALSSL_CA_PUBLIC);
144
+ const firefoxResult = await trustInFirefox(LOCALSSL_CA_PUBLIC);
145
+ const chromiumResult = await trustInChromium(LOCALSSL_CA_PUBLIC);
146
+ const nodeResult = await configureNodeExtraCACerts();
147
+ repairSummary = `${systemResult}; ${nodeResult}; Firefox ${firefoxResult.trusted ? 'ok' : 'skipped'}; Chrome/Edge ${chromiumResult.trusted ? 'ok' : 'skipped'}`;
148
+ } catch (error) {
149
+ if (!quiet) {
150
+ warn(`Trust repair skipped: ${error.message}`);
151
+ }
152
+ }
153
+
141
154
  if (!quiet) {
142
- step(1, 1, 'Machine CA setup', 'skip', '(already configured)');
155
+ step(1, 1, 'Machine CA setup', 'skip', `(${repairSummary})`);
143
156
  }
144
157
  return { mkcertPath, initialized: false };
145
158
  }
package/src/prompt.js ADDED
@@ -0,0 +1,22 @@
1
+ const readline = require('readline');
2
+
3
+ async function askYesNo(question, defaultNo = true) {
4
+ if (process.env.LOCALSSL_ASSUME_YES === '1') {
5
+ return true;
6
+ }
7
+
8
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
9
+ return !defaultNo;
10
+ }
11
+
12
+ return new Promise((resolve) => {
13
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
14
+ rl.question(question, (answer) => {
15
+ rl.close();
16
+ const normalized = String(answer || '').trim().toLowerCase();
17
+ resolve(normalized === 'y' || normalized === 'yes');
18
+ });
19
+ });
20
+ }
21
+
22
+ module.exports = { askYesNo };
@@ -1,20 +1,57 @@
1
1
  const { execFile } = require('child_process');
2
+ const { askYesNo } = require('../prompt');
3
+
4
+ function run(file, args) {
5
+ return new Promise((resolve) => {
6
+ execFile(file, args, { windowsHide: true }, (error) => resolve(!error));
7
+ });
8
+ }
9
+
10
+ async function trustCurrentUserWithPowerShell(certPath) {
11
+ const command = `Import-Certificate -FilePath \"${certPath}\" -CertStoreLocation Cert:\\CurrentUser\\Root | Out-Null`;
12
+ return run('powershell', ['-NoProfile', '-NonInteractive', '-Command', command]);
13
+ }
14
+
15
+ async function trustMachineWithElevation(certPath) {
16
+ const command = `$p = Start-Process certutil -ArgumentList '-addstore','-f','ROOT','${certPath}' -Verb RunAs -Wait -PassThru; if ($p.ExitCode -eq 0) { exit 0 } else { exit 1 }`;
17
+ return run('powershell', ['-NoProfile', '-NonInteractive', '-Command', command]);
18
+ }
2
19
 
3
20
  function trustCertificate(certPath) {
4
- return new Promise((resolve, reject) => {
5
- execFile('certutil', ['-addstore', '-f', 'ROOT', certPath], { windowsHide: true }, (error) => {
6
- if (error) {
7
- reject(new Error('Admin privileges required to install CA on Windows. Re-run terminal as Administrator.'));
8
- return;
9
- }
10
- resolve();
11
- });
21
+ return new Promise(async (resolve, reject) => {
22
+ const userViaCertutil = await run('certutil', ['-user', '-addstore', '-f', 'ROOT', certPath]);
23
+ if (userViaCertutil) {
24
+ resolve('Windows CurrentUser Root');
25
+ return;
26
+ }
27
+
28
+ const userViaPowerShell = await trustCurrentUserWithPowerShell(certPath);
29
+ if (userViaPowerShell) {
30
+ resolve('Windows CurrentUser Root (PowerShell)');
31
+ return;
32
+ }
33
+
34
+ const consent = await askYesNo(' Admin access needed for machine-wide trust. Continue? (y/N): ');
35
+ if (!consent) {
36
+ resolve('Windows trust skipped (user declined admin prompt)');
37
+ return;
38
+ }
39
+
40
+ const machineStore = await trustMachineWithElevation(certPath);
41
+ if (machineStore) {
42
+ resolve('Windows LocalMachine Root (elevated)');
43
+ return;
44
+ }
45
+
46
+ reject(new Error('Could not install CA in Windows trust stores. Safe mode preserved (no insecure fallback used).'));
12
47
  });
13
48
  }
14
49
 
15
50
  function untrustCertificate(certPath) {
16
51
  return new Promise((resolve) => {
17
- execFile('certutil', ['-delstore', 'ROOT', certPath], { windowsHide: true }, () => resolve());
52
+ execFile('certutil', ['-user', '-delstore', 'ROOT', certPath], { windowsHide: true }, () => {
53
+ execFile('certutil', ['-delstore', 'ROOT', certPath], { windowsHide: true }, () => resolve());
54
+ });
18
55
  });
19
56
  }
20
57