localhost-public
Version:
Make your localhost publicly accessible with optional custom authentication through tunneling
243 lines (214 loc) • 7.62 kB
JavaScript
const { Command } = require("commander");
const LocalhostPublic = require("../index");
const chalk = require("chalk");
const crypto = require("crypto");
const program = new Command();
program
.name("localhost-public")
.description(
"Make your localhost publicly accessible with optional custom authentication"
)
.version("1.0.0");
program
.option("-p, --port <number>", "local port to tunnel", "3000")
.option("-h, --host <string>", "tunnel host service", "https://loca.lt")
.option(
"-e, --expiry <string>",
"time to live for the tunnel (s: seconds, m: minutes, h: hours)",
"24h"
)
.option(
"--auth",
"enable authentication (requires login before accessing tunnel)"
)
.option(
"-u, --username <string>",
"username for authentication (default: admin)"
)
.option(
"-w, --password <string>",
"password for authentication (auto-generated if not provided)"
)
.option(
"--auth-port <number>",
"port for authentication server (default: original port + 1000)"
)
.action(async (options) => {
const port = parseInt(options.port);
// Validate port
if (isNaN(port) || port < 1 || port > 65535) {
console.error(
chalk.red("Invalid port number. Please provide a port between 1-65535")
);
process.exit(1);
}
// Validate TTL
if (!/^\d+[smh]$/.test(options.expiry) && options.expiry !== "24h") {
console.error(
chalk.red(
"Invalid expiry format. Please use a format like '10s', '5m', '1h', or '24h'."
)
);
process.exit(1);
}
// Validate auth port if provided
let authPort = options.authPort ? parseInt(options.authPort) : port + 1000;
if (
options.authPort &&
(isNaN(authPort) || authPort < 1 || authPort > 65535)
) {
console.error(
chalk.red(
"Invalid auth port number. Please provide a port between 1-65535"
)
);
process.exit(1);
}
// Check for port conflicts
if (options.auth && authPort === port) {
console.error(
chalk.red(
"Auth port cannot be the same as the main port. Please choose a different auth port."
)
);
process.exit(1);
}
console.log(chalk.blue.bold("🌐 Localhost Public Tunnel"));
if (options.auth) {
console.log(chalk.yellow("Authentication enabled"));
} else {
console.log(chalk.gray("No authentication (public access)"));
}
console.log(chalk.gray("Press Ctrl+C to stop the tunnel\n"));
// Setup tunnel configuration
const tunnelConfig = {
port,
host: options.host,
expiry: options.expiry,
};
// Configure authentication if enabled
if (options.auth) {
tunnelConfig.authPort = authPort;
// Set username (default to 'admin' if not provided)
const username = options.username || "admin";
// Generate password if not provided
let password = options.password;
if (!password) {
password = crypto.randomBytes(8).toString("hex");
}
tunnelConfig.credentials = {
username,
password,
};
// Generate a secure session secret
tunnelConfig.sessionSecret = crypto.randomBytes(32).toString("hex");
}
const tunnel = new LocalhostPublic(tunnelConfig);
try {
const url = await tunnel.start();
// Display additional info for authenticated tunnels
if (options.auth) {
console.log(chalk.blue("\n" + "=".repeat(50)));
console.log(chalk.blue.bold("AUTHENTICATION DETAILS"));
console.log(chalk.blue("=".repeat(50)));
console.log(chalk.yellow("Login Credentials:"));
console.log(
chalk.white(
` Username: ${chalk.cyan.bold(tunnelConfig.credentials.username)}`
)
);
console.log(
chalk.white(
` Password: ${chalk.cyan.bold(tunnelConfig.credentials.password)}`
)
);
console.log(chalk.yellow("\nAccess Instructions:"));
console.log(chalk.white(` 1. Visit: ${chalk.cyan(url)}`));
console.log(chalk.white(" 2. You'll be redirected to login page"));
console.log(chalk.white(" 3. Enter above credentials"));
console.log(chalk.white(" 4. Access your application"));
console.log(chalk.yellow("\nSecurity Notes:"));
console.log(chalk.gray(` • Session expires in ${options.expiry}`));
console.log(chalk.gray(" • Keep credentials secure"));
console.log(chalk.blue("=".repeat(50) + "\n"));
if (!options.password) {
console.log(chalk.yellow.bold("PASSWORD WAS AUTO-GENERATED"));
console.log(
chalk.yellow(" Save it now - it won't be shown again!")
);
console.log(
chalk.gray(" Next time use: --password your-password\n")
);
}
}
// Keep the process running
process.stdin.resume();
} catch (error) {
console.error(chalk.red("Failed to start tunnel:"), error.message);
// Provide helpful error messages
if (error.message.includes("EADDRINUSE")) {
console.log(chalk.yellow("\nTips to fix port conflicts:"));
console.log(chalk.gray(` • Check if port ${port} is already in use`));
console.log(
chalk.gray(` • Try a different port: --port ${port + 1}`)
);
if (options.auth) {
console.log(
chalk.gray(
` • Try different auth port: --auth-port ${authPort + 1}`
)
);
}
} else if (error.message.includes("No server detected")) {
console.log(chalk.yellow("\nMake sure your application is running:"));
console.log(chalk.gray(` • Start your app on port ${port}`));
console.log(
chalk.gray(" • Verify it's accessible at http://localhost:" + port)
);
}
process.exit(1);
}
});
// Add help examples
program.on("--help", () => {
console.log("");
console.log(chalk.blue.bold("Examples:"));
console.log("");
console.log(chalk.gray(" # Basic tunnel (no auth)"));
console.log(" $ localhost-public");
console.log("");
console.log(chalk.gray(" # Custom port"));
console.log(" $ localhost-public --port 8080");
console.log("");
console.log(chalk.gray(" # With authentication"));
console.log(" $ localhost-public --auth");
console.log("");
console.log(chalk.gray(" # Custom credentials"));
console.log(
" $ localhost-public --auth --username myuser --password mypass"
);
console.log("");
console.log(chalk.gray(" # Short TTL with auth"));
console.log(" $ localhost-public --auth --ttl 30m");
console.log("");
console.log(chalk.blue.bold("Authentication:"));
console.log("");
console.log(chalk.white(" When --auth is enabled:"));
console.log(chalk.gray(" • Users must login before accessing your app"));
console.log(chalk.gray(" • Sessions are secure and expire automatically"));
console.log(chalk.gray(" • Password is auto-generated if not provided"));
console.log(chalk.gray(" • Auth server runs on port + 1000 by default"));
console.log("");
});
// Handle unknown commands
program.on("command:*", function (operands) {
console.error(chalk.red(`Unknown command: ${operands[0]}`));
console.log(chalk.gray("Use --help to see available commands"));
process.exit(1);
});
// Show help if no arguments provided
if (process.argv.length === 2) {
program.help();
}
program.parse(process.argv);