@miraj181/ipingyou 1.0.0 → 1.0.2
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/package.json +4 -6
- package/src/cli.js +0 -33
- package/src/modes/client.js +1 -1
- package/src/modes/host.js +1 -2
- package/src/server.js +0 -144
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miraj181/ipingyou",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "SecureLink-CLI — Secure peer-to-peer remote access via SSH & Cloudflare Tunnels",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"securelink": "src/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"src/",
|
|
11
|
+
"src/cli.js",
|
|
12
|
+
"src/lib/",
|
|
13
|
+
"src/modes/",
|
|
12
14
|
"README.md",
|
|
13
15
|
"LICENSE"
|
|
14
16
|
],
|
|
15
17
|
"scripts": {
|
|
16
18
|
"start": "node src/cli.js",
|
|
17
|
-
"broker": "node src/server.js",
|
|
18
|
-
"dev:broker": "nodemon src/server.js",
|
|
19
19
|
"dev:cli": "node src/cli.js",
|
|
20
20
|
"prepublishOnly": "node -e \"require('fs').accessSync('LICENSE')\" && node src/cli.js --help"
|
|
21
21
|
},
|
|
@@ -44,9 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"chalk": "^5.3.0",
|
|
46
46
|
"commander": "^12.1.0",
|
|
47
|
-
"dotenv": "^16.4.5",
|
|
48
47
|
"execa": "^9.5.2",
|
|
49
|
-
"express": "^4.21.0",
|
|
50
48
|
"inquirer": "^12.3.2",
|
|
51
49
|
"nanoid": "^5.0.9",
|
|
52
50
|
"ora": "^8.1.1",
|
package/src/cli.js
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* npx ipingyou — Interactive mode
|
|
12
12
|
* npx ipingyou host — Start as host directly
|
|
13
13
|
* npx ipingyou connect — Start as client directly
|
|
14
|
-
* npx ipingyou broker — Start the central broker
|
|
15
14
|
*
|
|
16
15
|
* Security:
|
|
17
16
|
* All tunnel URLs are AES-256-CBC encrypted on the CLI side.
|
|
@@ -19,7 +18,6 @@
|
|
|
19
18
|
* ============================================================
|
|
20
19
|
*/
|
|
21
20
|
|
|
22
|
-
import 'dotenv/config';
|
|
23
21
|
import { Command } from 'commander';
|
|
24
22
|
import inquirer from 'inquirer';
|
|
25
23
|
import chalk from 'chalk';
|
|
@@ -61,7 +59,6 @@ function showRichHelp() {
|
|
|
61
59
|
console.log(` ${chalk.green('host')} : Generates a secure session UID and exposes your local machine.`);
|
|
62
60
|
console.log(` ${chalk.blue('connect')} : Prompts for a UID to connect to a remote host.`);
|
|
63
61
|
console.log(` ${chalk.dim('Supports Interactive SSH Shell & SCP File Transfers')}`);
|
|
64
|
-
console.log(` ${chalk.yellow('broker')} : Start your own relay server (for self-hosting).`);
|
|
65
62
|
console.log('');
|
|
66
63
|
|
|
67
64
|
console.log(chalk.bold.white(' 🔒 Security Architecture:'));
|
|
@@ -74,7 +71,6 @@ function showRichHelp() {
|
|
|
74
71
|
console.log(` $ npx ipingyou ${chalk.dim('# Interactive wizard (Recommended)')}`);
|
|
75
72
|
console.log(` $ npx ipingyou host ${chalk.dim('# Quick start as Host')}`);
|
|
76
73
|
console.log(` $ npx ipingyou connect ${chalk.dim('# Quick start as Client')}`);
|
|
77
|
-
console.log(` $ npx ipingyou broker ${chalk.dim('# Start Relay')}`);
|
|
78
74
|
console.log('');
|
|
79
75
|
}
|
|
80
76
|
|
|
@@ -134,10 +130,6 @@ async function interactiveMode() {
|
|
|
134
130
|
value: 'client',
|
|
135
131
|
},
|
|
136
132
|
new inquirer.Separator(),
|
|
137
|
-
{
|
|
138
|
-
name: `${chalk.yellow('📡 Start Broker Server')} ${chalk.dim('— Run the central relay (for self-hosting)')}`,
|
|
139
|
-
value: 'broker',
|
|
140
|
-
},
|
|
141
133
|
{
|
|
142
134
|
name: `${chalk.magenta('📖 Help / Information')} ${chalk.dim('— Learn how iPingYou works')}`,
|
|
143
135
|
value: 'help',
|
|
@@ -153,23 +145,12 @@ async function interactiveMode() {
|
|
|
153
145
|
case 'client':
|
|
154
146
|
await startClientMode();
|
|
155
147
|
break;
|
|
156
|
-
case 'broker':
|
|
157
|
-
await startBroker();
|
|
158
|
-
break;
|
|
159
148
|
case 'help':
|
|
160
149
|
showRichHelp();
|
|
161
150
|
break;
|
|
162
151
|
}
|
|
163
152
|
}
|
|
164
153
|
|
|
165
|
-
// ─── Broker Start ────────────────────────────────────────────
|
|
166
|
-
async function startBroker() {
|
|
167
|
-
console.log(chalk.cyan(' Starting broker server...'));
|
|
168
|
-
console.log('');
|
|
169
|
-
// Dynamically import the server (it self-starts on import)
|
|
170
|
-
await import('./server.js');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
154
|
// ─── Commander Setup ─────────────────────────────────────────
|
|
174
155
|
const program = new Command();
|
|
175
156
|
|
|
@@ -214,20 +195,6 @@ program
|
|
|
214
195
|
}
|
|
215
196
|
});
|
|
216
197
|
|
|
217
|
-
program
|
|
218
|
-
.command('broker')
|
|
219
|
-
.description('Start the central broker server')
|
|
220
|
-
.option('-p, --port <port>', 'Port to listen on', '4000')
|
|
221
|
-
.action(async (opts) => {
|
|
222
|
-
try {
|
|
223
|
-
if (opts.port) process.env.BROKER_PORT = opts.port;
|
|
224
|
-
showBanner();
|
|
225
|
-
await startBroker();
|
|
226
|
-
} catch (err) {
|
|
227
|
-
fatal('broker', err);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
|
|
231
198
|
// ─── Default: interactive mode ──────────────────────────────
|
|
232
199
|
program.action(async () => {
|
|
233
200
|
try {
|
package/src/modes/client.js
CHANGED
|
@@ -19,7 +19,7 @@ import { decrypt } from '../lib/crypto.js';
|
|
|
19
19
|
import { trackPID, untrackPID } from '../lib/cleanup.js';
|
|
20
20
|
import { createSpinner, sshSpinner, networkSpinner, fileTransferSpinner, showConnectionTrace, animatedSteps, simulateTransferProgress } from '../lib/animations.js';
|
|
21
21
|
|
|
22
|
-
const BROKER_URL = process.env.BROKER_URL || '
|
|
22
|
+
const BROKER_URL = process.env.BROKER_URL || 'https://ipingyou.onrender.com';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Resolve a UID to a tunnel URL via the broker.
|
package/src/modes/host.js
CHANGED
|
@@ -22,7 +22,7 @@ import { trackPID, untrackPID, setRevokeOnExit } from '../lib/cleanup.js';
|
|
|
22
22
|
import { detectOS } from '../lib/platform.js';
|
|
23
23
|
import { createSpinner, cryptoSpinner, tunnelSpinner, networkSpinner, typeText } from '../lib/animations.js';
|
|
24
24
|
|
|
25
|
-
const BROKER_URL = process.env.BROKER_URL || '
|
|
25
|
+
const BROKER_URL = process.env.BROKER_URL || 'https://ipingyou.onrender.com';
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Ensure the local SSH server is running.
|
|
@@ -307,7 +307,6 @@ export async function startHostMode() {
|
|
|
307
307
|
const registered = await registerWithBroker(uid, tunnel.url);
|
|
308
308
|
if (!registered) {
|
|
309
309
|
console.error(chalk.red(`\n ❌ FATAL: Could not register with broker at ${BROKER_URL}`));
|
|
310
|
-
console.log(chalk.dim(' Is the broker running? Try: npx ipingyou broker'));
|
|
311
310
|
process.exit(1);
|
|
312
311
|
}
|
|
313
312
|
|
package/src/server.js
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ============================================================
|
|
3
|
-
* SecureLink-CLI — Central Broker Server
|
|
4
|
-
* ============================================================
|
|
5
|
-
* A lightweight REST relay that pairs hosts and clients via
|
|
6
|
-
* temporary UIDs. The broker is a DUMB PIPE — it stores and
|
|
7
|
-
* returns encrypted blobs without ever seeing plaintext.
|
|
8
|
-
* Encryption/decryption happens ONLY on the CLI side.
|
|
9
|
-
* ============================================================
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import 'dotenv/config';
|
|
13
|
-
import express from 'express';
|
|
14
|
-
|
|
15
|
-
const app = express();
|
|
16
|
-
app.use(express.json());
|
|
17
|
-
|
|
18
|
-
// ─── In-memory store — auto-expire after TTL ─────────────────
|
|
19
|
-
const TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
20
|
-
const store = new Map(); // uid → { iv, ciphertext, createdAt }
|
|
21
|
-
|
|
22
|
-
function pruneExpired() {
|
|
23
|
-
const now = Date.now();
|
|
24
|
-
for (const [uid, entry] of store) {
|
|
25
|
-
if (now - entry.createdAt > TTL_MS) {
|
|
26
|
-
store.delete(uid);
|
|
27
|
-
console.log(`🗑️ Expired UID: ${uid}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Run pruning every 5 minutes
|
|
33
|
-
setInterval(pruneExpired, 5 * 60 * 1000);
|
|
34
|
-
|
|
35
|
-
// ─── Routes ──────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
// Health
|
|
38
|
-
app.get('/health', (_req, res) => {
|
|
39
|
-
res.json({ status: 'ok', uptime: process.uptime(), activeUIDs: store.size });
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* POST /register
|
|
44
|
-
* Body: { uid: string, iv: string, ciphertext: string }
|
|
45
|
-
*
|
|
46
|
-
* The broker receives an ALREADY-ENCRYPTED blob from the host CLI.
|
|
47
|
-
* It never decrypts — just stores the { iv, ciphertext } pair.
|
|
48
|
-
*/
|
|
49
|
-
app.post('/register', (req, res) => {
|
|
50
|
-
try {
|
|
51
|
-
const { uid, iv, ciphertext } = req.body;
|
|
52
|
-
|
|
53
|
-
if (!uid || !iv || !ciphertext) {
|
|
54
|
-
return res.status(400).json({ error: 'Missing uid, iv, or ciphertext' });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (typeof uid !== 'string' || uid.length < 6 || uid.length > 16) {
|
|
58
|
-
return res.status(400).json({ error: 'Invalid UID format (6-16 chars)' });
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Validate IV format — must be 32 hex chars (16 bytes)
|
|
62
|
-
if (!/^[a-f0-9]{32}$/i.test(iv)) {
|
|
63
|
-
return res.status(400).json({ error: 'Invalid IV format (expected 32 hex chars)' });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Validate ciphertext is non-empty base64
|
|
67
|
-
if (typeof ciphertext !== 'string' || ciphertext.length === 0) {
|
|
68
|
-
return res.status(400).json({ error: 'Invalid ciphertext' });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Store the encrypted blob as-is — broker NEVER decrypts
|
|
72
|
-
store.set(uid, {
|
|
73
|
-
iv,
|
|
74
|
-
ciphertext,
|
|
75
|
-
createdAt: Date.now(),
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
console.log(`✅ [${new Date().toLocaleTimeString()}] Registered UID: ${uid} (encrypted, ${ciphertext.length} bytes)`);
|
|
79
|
-
res.json({ status: 'registered', uid, expiresIn: '1 hour' });
|
|
80
|
-
} catch (err) {
|
|
81
|
-
console.error('❌ Register error:', err.message);
|
|
82
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* GET /resolve/:uid
|
|
88
|
-
* Returns the ENCRYPTED blob { iv, ciphertext } for a given UID.
|
|
89
|
-
* The client CLI decrypts it locally — the broker never sees plaintext.
|
|
90
|
-
*/
|
|
91
|
-
app.get('/resolve/:uid', (req, res) => {
|
|
92
|
-
try {
|
|
93
|
-
const { uid } = req.params;
|
|
94
|
-
const entry = store.get(uid);
|
|
95
|
-
|
|
96
|
-
if (!entry) {
|
|
97
|
-
return res.status(404).json({ error: 'UID not found or expired' });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Check TTL
|
|
101
|
-
if (Date.now() - entry.createdAt > TTL_MS) {
|
|
102
|
-
store.delete(uid);
|
|
103
|
-
return res.status(410).json({ error: 'UID expired' });
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
console.log(`🔍 [${new Date().toLocaleTimeString()}] Resolved UID: ${uid} (returning encrypted blob)`);
|
|
107
|
-
|
|
108
|
-
// Return encrypted blob — client decrypts
|
|
109
|
-
res.json({ uid, iv: entry.iv, ciphertext: entry.ciphertext });
|
|
110
|
-
} catch (err) {
|
|
111
|
-
console.error('❌ Resolve error:', err.message);
|
|
112
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* DELETE /revoke/:uid
|
|
118
|
-
* Allows host to explicitly remove their UID before expiry.
|
|
119
|
-
*/
|
|
120
|
-
app.delete('/revoke/:uid', (req, res) => {
|
|
121
|
-
const { uid } = req.params;
|
|
122
|
-
const existed = store.delete(uid);
|
|
123
|
-
console.log(`🚫 [${new Date().toLocaleTimeString()}] Revoked UID: ${uid} (existed: ${existed})`);
|
|
124
|
-
res.json({ status: existed ? 'revoked' : 'not_found' });
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// ─── Launch ──────────────────────────────────────────────────
|
|
128
|
-
// Render injects PORT; locally we use BROKER_PORT; fallback 4000
|
|
129
|
-
const PORT = process.env.PORT || process.env.BROKER_PORT || 4000;
|
|
130
|
-
const HOST = process.env.HOST || '0.0.0.0';
|
|
131
|
-
|
|
132
|
-
app.listen(PORT, HOST, () => {
|
|
133
|
-
console.log('');
|
|
134
|
-
console.log(' ╔══════════════════════════════════════════╗');
|
|
135
|
-
console.log(' ║ 🔗 SecureLink Broker — Active ║');
|
|
136
|
-
console.log(` ║ 📡 Port: ${String(PORT).padEnd(29)}║`);
|
|
137
|
-
console.log(' ║ 🔒 Zero-Knowledge Encrypted Store ║');
|
|
138
|
-
console.log(' ║ ⏱️ TTL: 1 hour per UID ║');
|
|
139
|
-
console.log(' ║ 🚫 Broker NEVER sees plaintext ║');
|
|
140
|
-
console.log(' ╚══════════════════════════════════════════╝');
|
|
141
|
-
console.log('');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
export default app;
|