@loginguards/loginguards-win 0.1.1 → 0.1.3

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
@@ -13,12 +13,14 @@ Enterprise-grade password breach prevention for Windows domains.
13
13
  ## Install (development)
14
14
 
15
15
  ```bash
16
- npm install -g .
16
+ npm i -g @loginguards/loginguards-win
17
17
  loginguards-win configure
18
18
  loginguards-win install
19
19
  loginguards-win test
20
+ loginguards-win --help
20
21
  ```
21
22
 
23
+
22
24
  ## Configuration
23
25
 
24
26
  - API base: `https://api.loginguards.com/v1`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loginguards/loginguards-win",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "LoginGuards Active Directory Password Protection for Windows",
5
5
  "private": false,
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ const storage = require('../../storage');
3
+ const apiClient = require('../../apiClient');
4
+ const { logger } = require('../../logger');
5
+
6
+ module.exports = {
7
+ command: 'check',
8
+ describe: 'Check one or more passwords against LoginGuards (no passwords are logged or stored)',
9
+ builder: {
10
+ password: { type: 'string', describe: 'Password to check (avoid history; prefer --prompt or --stdin)' },
11
+ prompt: { type: 'boolean', default: false, describe: 'Securely prompt for a password' },
12
+ stdin: { type: 'boolean', default: false, describe: 'Read newline-separated passwords from stdin' },
13
+ debug: { type: 'boolean', default: false, describe: 'Print raw API response (sanitized)' }
14
+ },
15
+ handler: async (args) => {
16
+ const apiKey = await storage.getApiKey();
17
+ if (!apiKey) {
18
+ console.log('✖ API key not configured. Run "loginguards-win configure".');
19
+ process.exit(1);
20
+ }
21
+
22
+ async function evalOne(pwd) {
23
+ try {
24
+ const res = await apiClient.checkPlain(pwd, apiKey);
25
+ if (args.debug) {
26
+ try { console.log('debug response:', JSON.stringify(res)); } catch {}
27
+ }
28
+ const compromised = (typeof res.breached !== 'undefined') ? !!res.breached
29
+ : (typeof res.compromised !== 'undefined') ? !!res.compromised
30
+ : (typeof res.is_compromised !== 'undefined') ? !!res.is_compromised
31
+ : (typeof res.isCompromised !== 'undefined') ? !!res.isCompromised
32
+ : (typeof res.count === 'number') ? res.count > 0
33
+ : false;
34
+ if (compromised) {
35
+ console.log('✖ COMPROMISED');
36
+ } else {
37
+ console.log('✔ NOT COMPROMISED');
38
+ }
39
+ } catch (e) {
40
+ const status = e && e.response && e.response.status;
41
+ if (status === 401) console.log('✖ Error: Unauthorized (invalid API key)');
42
+ else if (status === 429) console.log('✖ Error: Rate limited');
43
+ else console.log('✖ Error:', e.message || String(e));
44
+ logger.warn(`check command API error: ${status || e.code || e.message}`);
45
+ process.exitCode = 1;
46
+ }
47
+ }
48
+
49
+ if (args.stdin) {
50
+ // batch mode from stdin
51
+ const chunks = [];
52
+ process.stdin.setEncoding('utf8');
53
+ process.stdin.on('data', (d) => chunks.push(d));
54
+ process.stdin.on('end', async () => {
55
+ const text = chunks.join('');
56
+ const lines = text.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
57
+ if (!lines.length) { console.log('No input passwords.'); return; }
58
+ for (const line of lines) {
59
+ await evalOne(line);
60
+ }
61
+ });
62
+ if (process.stdin.readableEnded) {
63
+ // no stdin piped
64
+ console.log('No stdin provided. Use --password or --prompt instead.');
65
+ }
66
+ return;
67
+ }
68
+
69
+ if (typeof args.password === 'string' && args.password.length > 0) {
70
+ await evalOne(args.password);
71
+ return;
72
+ }
73
+
74
+ // prompt safely
75
+ try {
76
+ const inquirer = (await import('inquirer')).default;
77
+ const ans = await inquirer.prompt([{ type: 'password', name: 'pwd', message: 'Enter password to check', mask: '*', validate: v => v ? true : 'Required' }]);
78
+ await evalOne(ans.pwd);
79
+ } catch (e) {
80
+ console.error('✖ Prompt failed:', e.message || String(e));
81
+ process.exit(1);
82
+ }
83
+ }
84
+ };
package/src/cli/index.js CHANGED
@@ -11,6 +11,7 @@ async function run(argvInput) {
11
11
  .usage('$0 <cmd> [args]')
12
12
  .command(require('./commands/install'))
13
13
  .command(require('./commands/configure'))
14
+ .command(require('./commands/check'))
14
15
  .command(require('./commands/test'))
15
16
  .command(require('./commands/pipe-test'))
16
17
  .command(require('./commands/uninstall'))
@@ -23,7 +23,12 @@ async function evaluatePassword(password) {
23
23
  }
24
24
  try {
25
25
  const res = await apiClient.checkPlain(password, apiKey);
26
- const compromised = !!res.compromised;
26
+ const compromised = (typeof res.breached !== 'undefined') ? !!res.breached
27
+ : (typeof res.compromised !== 'undefined') ? !!res.compromised
28
+ : (typeof res.is_compromised !== 'undefined') ? !!res.is_compromised
29
+ : (typeof res.isCompromised !== 'undefined') ? !!res.isCompromised
30
+ : (typeof res.count === 'number') ? res.count > 0
31
+ : false;
27
32
  return { allow: !compromised, reason: compromised ? 'compromised' : 'ok' };
28
33
  } catch (e) {
29
34
  logger.warn(`API error during evaluation: ${e.status || e.code || e.message}`);
package/src/storage.js CHANGED
@@ -62,16 +62,40 @@ async function dpapiProtect(plain) {
62
62
  $plain = @'
63
63
  ${plain.replace(/'/g, "''")}
64
64
  '@
65
- $bytes = [System.Text.Encoding]::UTF8.GetBytes($plain)
66
- $enc = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
67
- [Convert]::ToBase64String($enc)
65
+ try { Add-Type -AssemblyName 'System.Security' -ErrorAction SilentlyContinue } catch {}
66
+ try {
67
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes($plain)
68
+ $enc = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
69
+ [Convert]::ToBase64String($enc)
70
+ } catch {
71
+ try {
72
+ $secure = ConvertTo-SecureString $plain -AsPlainText -Force
73
+ $hasScope = (Get-Command ConvertFrom-SecureString).Parameters.ContainsKey('Scope')
74
+ if ($hasScope) { $encStr = ConvertFrom-SecureString $secure -Scope LocalMachine } else { $encStr = ConvertFrom-SecureString $secure }
75
+ 'SS:' + $encStr
76
+ } catch { throw }
77
+ }
68
78
  `;
69
79
  const { stdout } = await runPS(script);
70
80
  return stdout.trim();
71
81
  }
72
82
 
73
83
  async function dpapiUnprotect(b64) {
84
+ if (b64.startsWith('SS:')) {
85
+ const encStr = b64.slice(3);
86
+ const scriptSS = `
87
+ $encStr = @'
88
+ ${encStr.replace(/'/g, "''")}
89
+ '@
90
+ $secure = ConvertTo-SecureString $encStr
91
+ $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
92
+ [Runtime.InteropServices.Marshal]::PtrToStringUni($bstr)
93
+ `;
94
+ const { stdout } = await runPS(scriptSS);
95
+ return stdout.replace(/\r?\n$/, '');
96
+ }
74
97
  const script = `
98
+ try { Add-Type -AssemblyName 'System.Security' -ErrorAction SilentlyContinue } catch {}
75
99
  $b64 = '${b64.replace(/'/g, "''")}'
76
100
  $bytes = [Convert]::FromBase64String($b64)
77
101
  $dec = [System.Security.Cryptography.ProtectedData]::Unprotect($bytes, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)