diff --git a/.gitignore b/.gitignore index 9a9941b..74245dc 100644 --- a/.gitignore +++ b/.gitignore @@ -173,4 +173,6 @@ dist # Finder (MacOS) folder config .DS_Store -*.asc \ No newline at end of file +*.asc +*.key +ssh_keys \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 815d6d3..8380d39 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/host.key.pub b/host.key.pub new file mode 100644 index 0000000..ca51da2 --- /dev/null +++ b/host.key.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOrBXBiOtqX7YEOyLCMag2XFc8unduCPDI3q/pIYBqMD neon@leon diff --git a/package.json b/package.json index d0de61e..6370f84 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,20 @@ { "name": "nest-pgp-chat", "module": "src/index.ts", - "type": "module", "devDependencies": { - "@types/bun": "latest" + "@types/bun": "latest", + "ts-node": "^10.9.2", + "typescript": "^5.6.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "dependencies": { + "blessed": "^0.1.81", "dotenv": "^16.4.5", - "openpgp": "^5.11.2" + "nodemailer": "^6.9.15", + "nodemailer-direct-transport": "^3.3.2", + "openpgp": "^5.11.2", + "ssh2": "^1.15.0" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/_index.ts similarity index 79% rename from src/index.ts rename to src/_index.ts index 6e7cdeb..0fb41f3 100644 --- a/src/index.ts +++ b/src/_index.ts @@ -6,7 +6,7 @@ import openpgp from 'openpgp' 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 + userIDs: [{ name: 'Server', email: 'neon+pgp_server__temp@hackclub.app' }], // you can pass multiple user IDs format: 'object', passphrase: 'super long and hard to guess secret' // protects the private key }); @@ -68,6 +68,7 @@ const Config = { "server": { // nests ram COULD not handle more then 20 sadly "maxConnections": 20, + "noInputTimeout": 55_000 , "host": "127.0.0.1", "port": process.env.PORT }, @@ -75,7 +76,7 @@ const Config = { "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): ", + "nickname": "Enter your public PGP key (in base64, in one line once done write in a new line 'ENDKEY'): ", "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." @@ -85,9 +86,20 @@ const Config = { } const Messages = Config.chat.messages; - +// to prevent my ram from being disposed +const all_connections = []; const clients = []; - +setInterval(() => { + all_connections.forEach(client => { + try { + if(Date.now() - client.lastInput >= 0) { + client.end(`Timed out`) + } + } catch (e) { + console.error(e.message, 'e') + } + }) +}, Config.server.noInputTimeout) const messageListener = fn => data => { console.log('Received data', data, data.toString()); @@ -152,11 +164,16 @@ function askNickname(socket) { }; 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); + if(data.toString().includes('ENDKEY')) { + socket.off('data', onData) + resolve(str); + } else { + str += data.toString() + if (!data.length) return ask(); + if(!data.toString().includes('ENDKEY')) return ask() + + } + }); ask(); @@ -165,6 +182,7 @@ if(!data.toString().includes('END PGP PUBLIC KEY')) return ask() function createServer() { const server = net.createServer(async socket => { + try { console.log(socket.remoteAddress) if(![`::1`].includes(socket.remoteAddress.trim())) { @@ -172,6 +190,13 @@ function createServer() { socket.end(`Bye`) return; } + socket.on('data', () => { + //@ts-ignore +all_connections[all_connections.indexOf(socket)].lastInput = Date.now() + }) + //@ts-ignore + socket.lastInput = Date.now() + all_connections.push(socket) console.log(`Connection from ${socket.remoteAddress}`); sendMessageLn(socket, null, Messages.welcome); @@ -182,10 +207,10 @@ function createServer() { 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()) + // console.log(raw_pgp_data) + console.log(Buffer.from(raw_pgp_data as string, 'base64').toString(), Buffer.from(raw_pgp_data as string, 'base64').toString().length) pgp_key = await openpgp.readKey({ - armoredKey: raw_pgp_data as string, + armoredKey: Buffer.from(raw_pgp_data as string, 'base64').toString(), }) // .then(d=>d.users[0]) console.log(pgp_key.users[0]) diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..56e9ac6 --- /dev/null +++ b/src/index.js @@ -0,0 +1,396 @@ + +const fs = require('fs'); +const openpgp = require('openpgp') +const path = require('path') +const { readFileSync } = fs +const blessed = require('blessed'); +const { utils: { parseKey }, Server } = require('ssh2'); +const RE_SPECIAL = +// eslint-disable-next-line no-control-regex + /[\x00-\x1F\x7F]+|(?:\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K])/g; +const MAX_MSG_LEN = 128; +const MAX_NAME_LEN = 10; +const PROMPT_NAME = `Enter a nickname to use (max ${MAX_NAME_LEN} chars): `; + +const users = []; + +function formatMessage(msg, output) { + output.parseTags = true; + msg = output._parseTags(msg); + output.parseTags = false; + return msg; +} + +function userBroadcast(msg, source) { + const sourceMsg = `> ${msg}`; + const name = `{cyan-fg}{bold}${source.name}{/}`; + msg = `: ${msg}`; + for (const user of users) { + const output = user.output; + if (source === user) + output.add(sourceMsg); + else + output.add(formatMessage(name, output) + msg); + } +} + +function localMessage(msg, source) { + const output = source.output; + output.add(formatMessage(msg, output)); +} +function noop(v) {} +let known_keys = require('fs').readdirSync('./ssh_keys').map(dir => { + const files = fs.readdirSync(path.join(__dirname, 'ssh_keys', dir)) + return { + parsedKey: parseKey(fs.readFileSync(files.find(f => f.endsWith('.sshkey')))), + armoredGPGKey: fs.readFileSync(files.find(f => f.endsWith('.asc'))) + } +}) +new Server({ + hostKeys: [readFileSync('host.key')], +}, (client) => { + let stream; + let name; + + // client.on('authentication', (ctx) => { + // console.log(ctx, ctx.key, ctx.method) + // let nick = ctx.username; + // let prompt = PROMPT_NAME; + // let lowered; + + // // Try to use username as nickname + // if (nick.length > 0 && nick.length <= MAX_NAME_LEN) { + // lowered = nick.toLowerCase(); + // let ok = true; + // for (const user of users) { + // if (user.name.toLowerCase() === lowered) { + // ok = false; + // prompt = `That nickname is already in use.\n${PROMPT_NAME}`; + // break; + // } + // } + // if (ok) { + // name = nick; + // return ctx.accept(); + // } + // } else if (nick.length === 0) { + // prompt = 'A nickname is required.\n' + PROMPT_NAME; + // } else { + // prompt = 'That nickname is too long.\n' + PROMPT_NAME; + // } + + // if (ctx.method !== 'keyboard-interactive') + // return ctx.reject(['keyboard-interactive']); + + // ctx.prompt(prompt, function retryPrompt(answers) { + // if (answers.length === 0) + // return ctx.reject(['keyboard-interactive']); + // nick = answers[0]; + // if (nick.length > MAX_NAME_LEN) { + // return ctx.prompt(`That nickname is too long.\n${PROMPT_NAME}`, + // retryPrompt); + // } else if (nick.length === 0) { + // return ctx.prompt(`A nickname is required.\n${PROMPT_NAME}`, + // retryPrompt); + // } + // lowered = nick.toLowerCase(); + // for (const user of users) { + // if (user.name.toLowerCase() === lowered) { + // return ctx.prompt(`That nickname is already in use.\n${PROMPT_NAME}`, + // retryPrompt); + // } + // } + // name = nick; + // ctx.accept(); + // }); + // }) + let username = null; + let reject_on_connect = false; + client.on('authentication', (ctx) => { + let allowed = false; + username = ctx.username; + if (ctx.username == "gimmie-key") { + allowed = true; + ctx.accept(); +return; + } + + switch (ctx.method) { + case 'password': + // console.log('Not passwor') + ctx.reject(); + break; + case 'publickey': + console.log(`pubkey`) +// let is_allowed = + // console.log(ctx.key.data.toString()) +known_keys.forEach((info) => { + let allowedPubKey = info.parsedKey + if (ctx.key.algo !== allowedPubKey.type + || !checkValue(ctx.key.data, allowedPubKey.getPublicSSH()) + || (ctx.signature && allowedPubKey.verify(ctx.blob, ctx.signature, ctx.hashAlgo) !== true)) { + // do nothing; + } else { + allowed = true; + } +}) + break; + default: + return ctx.reject(); + } + + if (allowed) + ctx.accept(); + else { + console.error(`no`, ctx.prompt) + // ctx.prompt(`Your ssh key was not found/provided please add by sshing with the username gimmie-key`, () => { + // ctx.reject(); + // }) + reject_on_connect = true; + ctx.accept() + } + }) + .on('ready', () => { + let rows; + let cols; + let term; + + client.once('session', (accept, reject) => { + accept().once('pty', (accept, reject, info) => { + rows = info.rows; + cols = info.cols; + term = info.term; + accept && accept(); + }).on('window-change', (accept, reject, info) => { + rows = info.rows; + cols = info.cols; + if (stream) { + stream.rows = rows; + stream.columns = cols; + stream.emit('resize'); + } + accept && accept(); + }).once('shell', (accept, reject) => { + stream = accept(); + stream.name = name; + stream.rows = rows || 24; + stream.columns = cols || 80; + stream.isTTY = true; + stream.setRawMode = noop; + stream.on('error', noop); + if(reject_on_connect) { +stream.write("Your ssh key was not found/provided please add by sshing with the username gimmie-key`\n") +stream.end() + } + if(username == 'gimmie-key') { + const screen = new blessed.screen({ + autoPadding: true, + smartCSR: true, + program: new blessed.program({ + input: stream, + output: stream + }), + terminal: term || 'ansi' + }); + const output = stream.output = new blessed.log({ + screen: screen, + top: 0, + left: 0, + width: '100%', + bottom: 2, + scrollOnInput: true + }); + screen.append(output); + + screen.append(new blessed.box({ + screen: screen, + height: 1, + bottom: 3, + left: 0, + width: '100%', + type: 'line', + ch: '=' + })); + + const input = new blessed.textarea({ + screen: screen, + bottom: 0, + height: 3, + width: '100%', + inputOnFocus: true + }); + const btn = new blessed.button({ + screen, + bottom: 5, + height: 2, + mouse: true, + keys: true, + shrink: true, + padding: { + left: 1, + right: 1 + }, + style: { + bg: 'blue', + focus: { + bg: 'red' + }, + hover: { + bg: 'red' + } + }, + content: "submit", + width: '10%', + }) + screen.append(input); + screen.append(btn) + input.focus(); + output.add('Please send your public GPG key here! ') + output.add(`Once you have pasted it click the red submit button`) + output.add('You will then recive an email shortly pgp-signed with your ssh pirv key') + // console.log(input) + btn.on('press', async () => { + console.debug(`#click`) + let line = input.getValue() + stream.lastInput = Date.now() + // input.clearValue(); + // screen.render(); + if (!input.focused) + input.focus(); + + try { + const key = await openpgp.readKey({ armoredKey: line }) + output.add(`PGP key added.`) + console.log( key.getFingerprint(), key.users[0].userID.name) + + stream.end() + } catch (e) { + output.add(`Error! Your key is an invalid pgp key`) + output.add(`> ${e.message}`) + } + // line = line.replace(RE_SPECIAL, '').trim(); + // if (line.length > MAX_MSG_LEN) + // line = line.substring(0, MAX_MSG_LEN); + // if (line.length > 0) { + // if (line === '/quit' || line === '/exit') + // stream.end(); + // else + // userBroadcast(line, stream); + // } + }); +// stream.end() + } else { + + users.push(stream); + stream.lastInput = Date.now() + + + const screen = new blessed.screen({ + autoPadding: true, + smartCSR: true, + program: new blessed.program({ + input: stream, + output: stream + }), + terminal: term || 'ansi' + }); + + screen.title = 'SSH Chatting as ' + name; + // Disable local echo + screen.program.attr('invisible', true); + + const output = stream.output = new blessed.log({ + screen: screen, + top: 0, + left: 0, + width: '100%', + bottom: 2, + scrollOnInput: true + }); + screen.append(output); + + screen.append(new blessed.box({ + screen: screen, + height: 1, + bottom: 1, + left: 0, + width: '100%', + type: 'line', + ch: '=' + })); + + const input = new blessed.textbox({ + screen: screen, + bottom: 0, + height: 1, + width: '100%', + inputOnFocus: true + }); + screen.append(input); + + input.focus(); + + // Local greetings + localMessage('{blue-bg}{white-fg}{bold}Welcome to SSH Chat!{/}\n' + + 'There are {bold}' + + (users.length - 1) + + '{/} other user(s) connected.\n' + + 'Type /quit or /exit to exit the chat.', + stream); + + // Let everyone else know that this user just joined + for (const user of users) { + const output = user.output; + if (user === stream) + continue; + output.add(formatMessage('{green-fg}*** {bold}', output) + + name + + formatMessage('{/bold} has joined the chat{/}', output)); + } + + screen.render(); + // XXX This fake resize event is needed for some terminals in order to + // have everything display correctly + screen.program.emit('resize'); + setInterval(() => { +if(Date.now() - stream.lastInput >= 60_000) { + return stream.end(); +} + },1000) + // Read a line of input from the user + input.on('submit', (line) => { + stream.lastInput = Date.now() + input.clearValue(); + screen.render(); + if (!input.focused) + input.focus(); + line = line.replace(RE_SPECIAL, '').trim(); + if (line.length > MAX_MSG_LEN) + line = line.substring(0, MAX_MSG_LEN); + if (line.length > 0) { + if (line === '/quit' || line === '/exit') + stream.end(); + else + userBroadcast(line, stream); + } + }); + } + }); + }); + }).on('close', () => { + if (stream !== undefined) { + users.splice(users.indexOf(stream), 1); + // Let everyone else know that this user just left + for (const user of users) { + const output = user.output; + output.add(formatMessage('{magenta-fg}*** {bold}', output) + + name + + formatMessage('{/bold} has left the chat{/}', output)); + } + } + }).on('error', (err) => { + // Ignore errors + }); +}).listen(3001, function() { + console.log('Listening on port ' + this.address().port); +}); \ No newline at end of file diff --git a/src/test_mail.js b/src/test_mail.js new file mode 100644 index 0000000..ef4b36c --- /dev/null +++ b/src/test_mail.js @@ -0,0 +1,31 @@ +const nodemailer = require('nodemailer') +const openpgp = require('openpgp') +var directTransport = require('nodemailer-direct-transport'); +var transporter = nodemailer.createTransport(directTransport()) +const { utils: { generateKeyPair, generateKeyPairSync } } = require('ssh2'); +// transporter.sendMail({ +// to:"neon@saahild.com", +// from: "neon@localhost", +// html: "hello world" +// }) + +;(async () => { +// 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: 'Server', email: 'neon+pgp_server__temp@hackclub.app' }], // you can pass multiple user IDs +format: 'object', + passphrase: 'super long and hard to guess secret' // protects the private key +}); +function sendMail(key) { + const usersSSHKey = generateKeyPairSync('rsa', { bits: 2048, comment: 'Priv key for nest pgp chat - GPG FINGERPRINT: '+ key.getFingerprint() }); +transporter.sendMail({ + from: publicKey.users[0].userID.email, + to: key.users[0].userID.email, +// html: await +}) +} +const clientKey = await openpgp.readKey({ armoredKey: require('fs').readSync('./key.asc').toString() }) +sendMail(clientKey) +})() \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..bb880e7 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,306 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/bun@latest": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@types/bun/-/bun-1.1.9.tgz#2a10783816f178538be72f78e93e2f4f4d73825d" + integrity sha512-SXJRejXpmAc3qxyN/YS4/JGWEzLf4dDBa5fLtRDipQXHqNccuMU4EUYCooXNTsylG0DmwFQsGgEDHxZF+3DqRw== + dependencies: + bun-types "1.1.27" + +"@types/node@*": + version "22.5.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.5.tgz#52f939dd0f65fc552a4ad0b392f3c466cc5d7a44" + integrity sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA== + dependencies: + undici-types "~6.19.2" + +"@types/node@~20.12.8": + version "20.12.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.14.tgz#0c5cf7ef26aedfd64b0539bba9380ed1f57dcc77" + integrity sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg== + dependencies: + undici-types "~5.26.4" + +"@types/ws@~8.5.10": + version "8.5.12" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== + dependencies: + "@types/node" "*" + +acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +acorn@^8.11.0, acorn@^8.4.1: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +asn1.js@^5.0.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + +asn1@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +bcrypt-pbkdf@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + +blessed@^0.1.81: + version "0.1.81" + resolved "https://registry.yarnpkg.com/blessed/-/blessed-0.1.81.tgz#f962d687ec2c369570ae71af843256e6d0ca1129" + integrity sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ== + +bn.js@^4.0.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +buildcheck@~0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238" + integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A== + +bun-types@1.1.27: + version "1.1.27" + resolved "https://registry.yarnpkg.com/bun-types/-/bun-types-1.1.27.tgz#53cbc56d412157fbaf0ba30328208494a6f52cc6" + integrity sha512-rHXAiIDefeMS/fleNM1rRDYqolJGNRdch3+AuCRwcZWaqTa1vjGBNsahH/HVV7Y82frllYhJomCVSEiHzLzkgg== + dependencies: + "@types/node" "~20.12.8" + "@types/ws" "~8.5.10" + +cpu-features@~0.0.9: + version "0.0.10" + resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5" + integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA== + dependencies: + buildcheck "~0.0.6" + nan "^2.19.0" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + +httpntlm@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/httpntlm/-/httpntlm-1.6.1.tgz#ad01527143a2e8773cfae6a96f58656bb52a34b2" + integrity sha512-Tcz3Ct9efvNqw3QdTl3h6IgRRlIQxwKkJELN/aAIGnzi2xvb3pDHdnMs8BrxWLV6OoT4DlVyhzSVhFt/tk0lIw== + dependencies: + httpreq ">=0.4.22" + underscore "~1.7.0" + +httpreq@>=0.4.22: + version "1.1.1" + resolved "https://registry.yarnpkg.com/httpreq/-/httpreq-1.1.1.tgz#b8818316cdfd6b1bfb0f68b822fa1306cd24be68" + integrity sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw== + +inherits@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +nan@^2.18.0, nan@^2.19.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" + integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== + +nodemailer-direct-transport@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/nodemailer-direct-transport/-/nodemailer-direct-transport-3.3.2.tgz#e96fafb90358560947e569017d97e60738a50a86" + integrity sha512-vEMLWdUZP9NpbeabM8VTiB3Ar1R0ixASp/6DdKX372LK4USKB4Lq12/WCp69k/+kWk4RiCWWEGo57CcsXOs/bw== + dependencies: + nodemailer-shared "1.1.0" + smtp-connection "2.12.0" + +nodemailer-fetch@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz#79c4908a1c0f5f375b73fe888da9828f6dc963a4" + integrity sha512-P7S5CEVGAmDrrpn351aXOLYs1R/7fD5NamfMCHyi6WIkbjS2eeZUB/TkuvpOQr0bvRZicVqo59+8wbhR3yrJbQ== + +nodemailer-shared@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz#cf5994e2fd268d00f5cf0fa767a08169edb07ec0" + integrity sha512-68xW5LSyPWv8R0GLm6veAvm7E+XFXkVgvE3FW0FGxNMMZqMkPFeGDVALfR1DPdSfcoO36PnW7q5AAOgFImEZGg== + dependencies: + nodemailer-fetch "1.6.0" + +nodemailer@^6.9.15: + version "6.9.15" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.15.tgz#57b79dc522be27e0e47ac16cc860aa0673e62e04" + integrity sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ== + +openpgp@^5.11.2: + version "5.11.2" + resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.11.2.tgz#2c035a26b13feb3b0bb5180718ec91c8e65cc686" + integrity sha512-f8dJFVLwdkvPvW3VPFs6q9Vs2+HNhdvwls7a/MIFcQUB+XiQzRe7alfa3RtwfGJU7oUDDMAWPZ0nYsHa23Az+A== + dependencies: + asn1.js "^5.0.0" + +safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +smtp-connection@2.12.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/smtp-connection/-/smtp-connection-2.12.0.tgz#d76ef9127cb23c2259edb1e8349c2e8d5e2d74c1" + integrity sha512-UP5jK4s5SGcUcqPN4U9ingqKt9mXYSKa52YhqxPuMecAnUOsVJpOmtgGaOm1urUBJZlzDt1M9WhZZkgbhxQlvg== + dependencies: + httpntlm "1.6.1" + nodemailer-shared "1.1.0" + +ssh2@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.15.0.tgz#2f998455036a7f89e0df5847efb5421748d9871b" + integrity sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw== + dependencies: + asn1 "^0.2.6" + bcrypt-pbkdf "^1.0.2" + optionalDependencies: + cpu-features "~0.0.9" + nan "^2.18.0" + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tweetnacl@^0.14.3: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + +typescript@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== + +underscore@~1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" + integrity sha512-cp0oQQyZhUM1kpJDLdGO1jPZHgS/MpzoWYfe9+CM2h/QGDZlqwT2T3YGukuBdaNJ/CAPoeyAZRRHz8JFo176vA== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==