@miraj181/ipingyou 2.1.19 → 2.1.23

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/src/server.js CHANGED
@@ -13,7 +13,13 @@ import express from 'express';
13
13
  import rateLimit from 'express-rate-limit';
14
14
  import helmet from 'helmet';
15
15
  import crypto from 'node:crypto';
16
- import { cleanupSessionLog, initSessionLog, logSessionEvent } from './lib/session-log.js';
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ import { cleanupSessionLog, initSessionLog, logSessionEvent } from './lib/mod/session-log.js';
20
+
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+ const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
17
23
 
18
24
  const app = express();
19
25
  const brokerLogPath = initSessionLog('broker');
@@ -359,6 +365,30 @@ app.get('/resolve/:uid', generalLimiter, (req, res) => {
359
365
  if (!approved) {
360
366
  return res.status(423).json({ error: 'Host approval required before resolving this session' });
361
367
  }
368
+
369
+ // Client-specific E2E gating: verify client IP matches approved request IP
370
+ const clientIp = (req.headers['x-forwarded-for'] || req.socket.remoteAddress || '').split(',')[0].trim();
371
+ if (approved.ip && approved.ip !== clientIp) {
372
+ return res.status(403).json({ error: 'Client IP mismatch — access denied' });
373
+ }
374
+
375
+ if (approved.approvedPayload) {
376
+ entry.resolveCount = (entry.resolveCount || 0) + 1;
377
+ console.log(`🔍 [${new Date().toLocaleTimeString()}] Resolved UID (Client Specific): ${uid} (resolve #${entry.resolveCount})`);
378
+ res.json({
379
+ uid,
380
+ iv: approved.approvedPayload.iv,
381
+ ciphertext: approved.approvedPayload.ciphertext,
382
+ salt: approved.approvedPayload.salt,
383
+ isClientSpecific: true,
384
+ ip: clientIp
385
+ });
386
+
387
+ if (entry.oneTime) {
388
+ deleteStoreEntry(uid);
389
+ }
390
+ return;
391
+ }
362
392
  }
363
393
 
364
394
  // Increment resolve counter atomically BEFORE sending response
@@ -400,6 +430,7 @@ app.post('/approval-request/:uid', generalLimiter, (req, res) => {
400
430
 
401
431
  // Generate cryptographically secure request ID
402
432
  const id = crypto.randomBytes(12).toString('hex');
433
+ const ip = (req.headers['x-forwarded-for'] || req.socket.remoteAddress || '').split(',')[0].trim();
403
434
  const request = {
404
435
  id,
405
436
  iv: req.body.iv,
@@ -408,6 +439,7 @@ app.post('/approval-request/:uid', generalLimiter, (req, res) => {
408
439
  status: entry.approvalRequired ? 'pending' : 'approved',
409
440
  createdAt: Date.now(),
410
441
  decidedAt: entry.approvalRequired ? null : Date.now(),
442
+ ip,
411
443
  };
412
444
 
413
445
  const requestBytes = encryptedPayloadBytes(request);
@@ -439,6 +471,7 @@ app.get('/approval-requests/:uid', hostLimiter, (req, res) => {
439
471
  const sanitized = entry.approvals.map(a => ({
440
472
  id: a.id, status: a.status, createdAt: a.createdAt, decidedAt: a.decidedAt,
441
473
  iv: a.iv, ciphertext: a.ciphertext, salt: a.salt,
474
+ ip: a.ip,
442
475
  }));
443
476
  res.json({ approvalRequired: entry.approvalRequired, approvals: sanitized });
444
477
  });
@@ -461,6 +494,14 @@ app.post('/approval-requests/:uid/:requestId/:decision', strictLimiter, (req, re
461
494
 
462
495
  request.status = req.params.decision;
463
496
  request.decidedAt = Date.now();
497
+
498
+ if (req.params.decision === 'approved' && req.body && req.body.ciphertext) {
499
+ request.approvedPayload = {
500
+ iv: req.body.iv,
501
+ ciphertext: req.body.ciphertext,
502
+ salt: req.body.salt
503
+ };
504
+ }
464
505
  res.json({ status: request.status });
465
506
  });
466
507
 
@@ -473,8 +514,13 @@ app.get('/approval-status/:uid/:requestId', generalLimiter, (req, res) => {
473
514
  if (!entry) return res.status(404).json({ error: 'UID not found' });
474
515
  const request = entry.approvals.find(item => item.id === req.params.requestId);
475
516
  if (!request) return res.status(404).json({ error: 'Request not found' });
476
- // Only return status — never leak encrypted approval data
477
- res.json({ status: request.status });
517
+
518
+ const clientIp = (req.headers['x-forwarded-for'] || req.socket.remoteAddress || '').split(',')[0].trim();
519
+ res.json({
520
+ status: request.status,
521
+ ip: clientIp,
522
+ approvedPayload: request.status === 'approved' ? request.approvedPayload : undefined
523
+ });
478
524
  });
479
525
 
480
526
  /**
@@ -571,7 +617,7 @@ const HOST = process.env.HOST || '0.0.0.0';
571
617
  app.listen(PORT, HOST, () => {
572
618
  console.log('');
573
619
  console.log(' ╔══════════════════════════════════════════╗');
574
- console.log(' ║ 🔗 SecureLink Broker — Active ║');
620
+ console.log(` ║ 🔗 SecureLink Broker — Active (v${packageJson.version})`.padEnd(45) + '║');
575
621
  console.log(` ║ 📡 Port: ${String(PORT).padEnd(29)}║`);
576
622
  console.log(' ║ 🔒 Zero-Knowledge Encrypted Store ║');
577
623
  console.log(' ║ ⏱️ TTL: 1 hour per UID ║');
@@ -1,90 +0,0 @@
1
- import { execa } from 'execa';
2
- import chalk from 'chalk';
3
- import os from 'node:os';
4
- import fs from 'node:fs';
5
-
6
- export function detectOS() {
7
- const platform = process.platform;
8
- return {
9
- platform,
10
- isLinux: platform === 'linux',
11
- isMac: platform === 'darwin',
12
- isWindows: platform === 'win32',
13
- distro: null,
14
- arch: os.arch(),
15
- hostname: os.hostname(),
16
- };
17
- }
18
-
19
- export async function detectLinuxDistro() {
20
- try {
21
- const data = await fs.promises.readFile('/etc/os-release', 'utf8');
22
- const lower = data.toLowerCase();
23
- if (/(ubuntu|debian|kali|mint)/.test(lower)) return 'debian';
24
- if (/(arch|manjaro)/.test(lower)) return 'arch';
25
- if (/(fedora|centos|rhel)/.test(lower)) return 'fedora';
26
- } catch {
27
- // Manual instructions fall back to the generic Linux guidance.
28
- }
29
- return 'unknown';
30
- }
31
-
32
- export async function commandExists(command) {
33
- if (!/^[a-zA-Z0-9._+-]{1,64}$/.test(String(command || ''))) return false;
34
- try {
35
- const probe = process.platform === 'win32' ? ['where', command] : ['which', command];
36
- await execa(probe[0], [probe[1]], {
37
- reject: true,
38
- timeout: 5000,
39
- maxBuffer: 64 * 1024,
40
- });
41
- return true;
42
- } catch {
43
- return false;
44
- }
45
- }
46
-
47
- function printInstallGuidance(osInfo, missing) {
48
- console.log(chalk.yellow(' ⚠️ Required system tools are missing.'));
49
- console.log(chalk.dim(' iPingYou does not download or execute native installers automatically.'));
50
- console.log(chalk.dim(' Install the tools through your trusted OS package manager, then rerun iPingYou.'));
51
- console.log('');
52
-
53
- if (missing.includes('cloudflared')) {
54
- if (osInfo.isMac) console.log(chalk.cyan(' brew install cloudflared'));
55
- else if (osInfo.isWindows) console.log(chalk.cyan(' winget install --id Cloudflare.cloudflared -e'));
56
- else console.log(chalk.cyan(' Follow: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/'));
57
- }
58
-
59
- if (missing.includes('ssh')) {
60
- if (osInfo.isMac) console.log(chalk.cyan(' Enable Remote Login in System Settings → General → Sharing'));
61
- else if (osInfo.isWindows) console.log(chalk.cyan(' Add OpenSSH Client/Server from Windows Optional Features'));
62
- else console.log(chalk.cyan(' Debian/Ubuntu: sudo apt-get install openssh-client openssh-server'));
63
- }
64
- console.log('');
65
- }
66
-
67
- export async function checkDependencies() {
68
- const osInfo = detectOS();
69
- const results = {
70
- ssh: await commandExists('ssh'),
71
- cloudflared: await commandExists('cloudflared'),
72
- };
73
-
74
- console.log('');
75
- console.log(chalk.bold(' 🔍 Dependency Check'));
76
- console.log(chalk.dim(' ─────────────────────────────────'));
77
- console.log(` ${results.ssh ? chalk.green('✓') : chalk.red('✗')} ssh ${results.ssh ? chalk.dim('found') : chalk.red('missing')}`);
78
- console.log(` ${results.cloudflared ? chalk.green('✓') : chalk.red('✗')} cloudflared ${results.cloudflared ? chalk.dim('found') : chalk.red('missing')}`);
79
- console.log('');
80
-
81
- const missing = Object.entries(results)
82
- .filter(([, available]) => !available)
83
- .map(([name]) => name);
84
- if (missing.length > 0) {
85
- printInstallGuidance(osInfo, missing);
86
- } else {
87
- console.log(chalk.green(' ✅ All dependencies satisfied!\n'));
88
- }
89
- return results;
90
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes