commit 444156138b00a864aa3ad36d5e14c8bd5d375471 Author: Saahil Date: Fri Sep 13 20:31:36 2024 -0400 feat(main): init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a9941b --- /dev/null +++ b/.gitignore @@ -0,0 +1,176 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store +*.asc \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5edfc89 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# nest-pgp-chat + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run src/index.ts +``` + +This project was created using `bun init` in bun v1.1.9. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..815d6d3 Binary files /dev/null and b/bun.lockb differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..d0de61e --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "nest-pgp-chat", + "module": "src/index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "dotenv": "^16.4.5", + "openpgp": "^5.11.2" + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..6e7cdeb --- /dev/null +++ b/src/index.ts @@ -0,0 +1,229 @@ +import "dotenv/config" +import net from "net"; +import { format } from "util"; +import openpgp from 'openpgp' +// create this sessions PGP key +const { privateKey, publicKey } = await openpgp.generateKey({ + type: 'rsa', // Type of the key + rsaBits: 2048, // RSA key size (defaults to 4096 bits) + userIDs: [{ name: 'Jon Smith', email: 'jon@example.com' }], // you can pass multiple user IDs +format: 'object', + passphrase: 'super long and hard to guess secret' // protects the private key +}); +// console.log(publicKey, privateKey) +console.log(`Generate my key! ${publicKey.getFingerprint()}`) +const Commands = { + IAC: 0xff, + SE: 0xf0, + NOP: 0xf1, + DataMark: 0xf2, + BRK: 0xf3, + IP: 0xf4, + AO: 0xf5, + AYT: 0xf6, + EC: 0xf7, + EL: 0xf8, + GA: 0xf9, + SB: 0xfa, + WILL: 0xfb, + WONT: 0xfc, + DO: 0xfd, + DONT: 0xfe +}; + +const Options = { + ECH: 0x1, + SGA: 0x3, + STS: 0x5, + TimingMark: 0x6, + TerminalType: 0x18, + WindowSize: 0x1f, + TerminalSpeed: 0x20, + RFC: 0x21, + LineMode: 0x22, + EnvVars: 0x24 +} + +const isCommand = buffer => buffer[0] === Commands.IAC; + +const isInterrput = buffer => isCommand(buffer) && buffer[1] === Commands.IP; + +const sendCommand = (socket, command, option) => + socket.write( + Uint8Array.from( + [Commands.IAC, command, option].filter(b => b !== undefined) + ) + ); +//@ts-ignore +const sendEraseLine = socket => sendCommand(socket, Commands.EL); + +const telnet = { + isInterrput, + Commands, + sendCommand, + sendEraseLine, + Options +}; +const Config = { + "server": { + // nests ram COULD not handle more then 20 sadly + "maxConnections": 20, + "host": "127.0.0.1", + "port": process.env.PORT + }, + "chat": { + "messageLength": 150, + "messages": { + "welcome": "Welcome to chat server. Press CTRL+C to leave.\n(note: pgp key name will be used in server. extra metadata such as [email, pgp key id] can be found via whois cmd)", + "nickname": "Enter your public PGP key (in base64, in one line): ", + "logout": "%s has left the chat", + "login": "%s has joined the chat", + "not_allowed": "You may only connect from the nest server. This is not a public server." + }, + "serverNickname": "PGP chat" + } +} + +const Messages = Config.chat.messages; + +const clients = []; + +const messageListener = fn => data => { + console.log('Received data', data, data.toString()); + + data = data + .toString() + .trim() + .replace(/[^\w\d \t]/g, '') + .substring(0, Config.chat.messageLength); + + data && fn(data); +}; + +const exitHandler = socket => { + return data => telnet.isInterrput(data) && socket.end('Bye bye.'); +}; + +const broadcastHandler = (socket, nickname) => { + return messageListener(message => { + fetch(process.env.SLACK_CHAT_WORKFLOW_URL, { + method: 'POST', + headers: {'Content-Type': 'application/json', 'User-Agent': 'insomnia/9.3.2'}, + body: JSON.stringify({ message, name: nickname }) + }) + .then(response => response.json()) + .catch(err => console.error(err)); + return sendBroadcast(socket, nickname, message) + + }); +} + +const endHandler = (socket, nickname) => () => { + clients.splice(clients.indexOf(socket), 1); + sendBroadcast(null, null, format(Messages.logout, nickname)); + + console.log(`${socket.remoteAddress} disconnected`); +}; + +function sendMessageLn(socket, from, message) { + sendMessage(socket, from, message + '\r\n'); +} + +function sendMessage(socket, from, message) { + if (from) message = `<${from}> ${message}`; + socket.write(message); +} + +function sendBroadcast(socket, from, message) { + if (!message) return; + + clients.forEach(client => { + telnet.sendEraseLine(client); + if (client !== socket) sendMessageLn(client, from, message); + }); +} + +function askNickname(socket) { + return new Promise((resolve, reject) => { + let str = ""; + const ask = () => { + socket.on('data', onData); + if(!str.length) sendMessage(socket, null, Messages.nickname); + }; + + const onData = messageListener(data => { + str += data.toString() + if (!data.length) return ask(); +if(!data.toString().includes('END PGP PUBLIC KEY')) return ask() + socket.off('data', onData) + resolve(str); + }); + + ask(); + }); +} + +function createServer() { + const server = net.createServer(async socket => { + try { + console.log(socket.remoteAddress) + if(![`::1`].includes(socket.remoteAddress.trim())) { + sendMessageLn(socket, null, Messages.not_allowed); + socket.end(`Bye`) + return; + } + console.log(`Connection from ${socket.remoteAddress}`); + + sendMessageLn(socket, null, Messages.welcome); + + socket.on('data', exitHandler(socket)); + + const raw_pgp_data = await askNickname(socket); + console.debug(`$afternic`) + let pgp_key=null, nickname= null; +try { + console.log(raw_pgp_data) + // console.log(Buffer.from(raw_pgp_data as string, 'base64').toString()) + pgp_key = await openpgp.readKey({ + armoredKey: raw_pgp_data as string, + }) + // .then(d=>d.users[0]) + console.log(pgp_key.users[0]) + nickname = pgp_key + console.log(`debug`) + debugger; +} catch (e) { + console.error(e.message) + socket.end(`Invalid Key.`) + return; +} + clients.push(socket); + socket.on('data', broadcastHandler(socket, nickname)); + + // telnet.sendCommand(socket, telnet.Commands.NOP); + // telnet.sendCommand(socket, telnet.Commands.WILL, telnet.Options.ECH); + // telnet.sendCommand(socket, telnet.Commands.WILL, telnet.Options.SGA); + + sendBroadcast(null, null, format(Messages.login, nickname)); + console.log( + `${socket.remoteAddress} joined the chat as ${nickname}` + ); + } catch (e) { + console.log('error', e); + socket.end(); + } + }); + + server.maxConnections = Config.server.maxConnections; + server.on('close', () => { + console.log(`Server closed.`); + }); + + return server; +} + + const listner = createServer().listen(parseInt(Config.server.port), () => { + //@ts-ignore + console.log(`Server listening on port ${listner.address().port ? listner.address().port : listner.address()}`); + + }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bc9911b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": false, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}