UNPKG

peanut-stash

Version:

Collaborative command line cloud Stash, Share, Copy & Paste tool.

206 lines (163 loc) 7.2 kB
#! /usr/bin/env node /**** * * Peanut Stash 1.1.1 * * By Roy Massaad * * Collaborative command line cloud Copy & Paste * Quickly stash, pop, send & receive console commands and text with your coding, devops and IT team * * https://github.com/roymasad/peanut-stash * * License: MIT */ import { initializeApp } from 'firebase/app'; import { getDatabase, ref, get, connectDatabaseEmulator, push, set, update, onValue } from 'firebase/database'; import { getAuth, signInWithEmailAndPassword, connectAuthEmulator, } from "firebase/auth"; import fs from 'fs'; import path from 'path'; import os from 'os'; import machinePkg from 'node-machine-id'; const {machineIdSync} = machinePkg; import { config } from 'dotenv'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import color from 'picocolors'; import crypto from 'crypto'; import * as prompts from '@clack/prompts' import { setTimeout } from 'node:timers/promises'; import clipboard from 'clipboardy'; // This project's own libaries import { decryptDataSymmetrical, stateMachine, } from './peanuts/utilities.js'; // Local Emulator flag let localEmulation = false; // Main start function function main() { // Calculating __dirname this way is needed to get the dotenv to load successfully when the cli app is // installed globally from npm using `npm install -g peanut-stash`. Using a relative path wont work. // https://github.com/nodejs/help/issues/2907#issuecomment-757446568 const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const hiddenFolderPath = path.join(os.homedir(), '.peanuts'); // where we save our local config data const serverConfFilePath = path.join(hiddenFolderPath, 'server.json'); let firebaseConfig = {}; // check if a custom server config file exists and load and use it for firebase server credentials if (fs.existsSync(serverConfFilePath)) { const serverData = fs.readFileSync(serverConfFilePath, 'utf8'); const serverJson = JSON.parse(serverData); firebaseConfig = { apiKey: serverJson.apiKey, authDomain: serverJson.authDomain, databaseURL: serverJson.databaseURL, projectId: serverJson.projectId, }; console.log(color.green("Secured: Using custom private server")); } else { // no custom server config file found, so load default firebase public test server from env file config( { path: __dirname + '/config/default-public-server.env' }) // but reload all env for local firebase emulator testing server if flag to do so is set in public env file. if (process.env.useLocalEmulatorServerInstead == 'true') { config( { path: __dirname + '/config/local-testing-server.env', override: true }); localEmulation = true; console.log(color.magenta("Please Note: You are using local emulator server.")); } else { console.log(color.magenta("Please Note: You are using the public testing server.")); } firebaseConfig = { apiKey: process.env.apiKey, authDomain: process.env.authDomain, databaseURL: process.env.databaseURL, projectId: process.env.projectId, }; } // Initialize Firebase const app = initializeApp(firebaseConfig); // Process the actions from the arguments proccessInput(); } // Function to handle terminal input and write data to Firebase function proccessInput() { // Get firebase main objects const db = getDatabase(); const auth = getAuth(); if (localEmulation) { // local testing env for firebase, changing .env loaded doesn seem to work connectAuthEmulator(auth, "http://127.0.0.1:9099"); connectDatabaseEmulator(db, "127.0.0.1", 9000); } // Get command line arguments const args = process.argv.slice(2); // Extract action from first argument const action = args[0]; // Check if user has login information saved previously and load it const hiddenFolderPath = path.join(os.homedir(), '.peanuts'); // Define the path of the hidden folder/file in the user's home directory const authFilePath = path.join(hiddenFolderPath, 'session.json'); // check if cached auth json file exists if (fs.existsSync(authFilePath)) { const sessionData = fs.readFileSync(authFilePath, 'utf8'); const sessionJson = JSON.parse(sessionData); // Decrypt the email and password summetrically using a machine id as key // Half unique to each machine (i had to slice the machine id to 32 bytes from 64) const email = decryptDataSymmetrical(sessionJson.email, machineIdSync().slice(0, 32)); const password = decryptDataSymmetrical(sessionJson.password, machineIdSync().slice(0, 32)); // Sign in user using saved account info // PS: Ideally we would use a custom token, but this will require creating a server endpoint // to generate and verify the tokens. To keep this project working on firebase spark plan // and to make it very easy/fast to use it with private firebase project (just a json file) // i opted out of using custom token for now as it would require deploying a cloud function // or a nodejs endpoint to generate and verify the token // Using a service account or the admin sdk on this project is not possible // as this is a client side public npm console app, we can't have admin access in it // because that would be a builtin security risk design archicture problem. signInWithEmailAndPassword(auth, email, password) .then(async (userCredential) => { const user = userCredential.user; // Load user's public and private keys from firebase // first, make the email compatible with firebase // replacing all . with _ const firebase_email = email.replace(/\./g, '_'); const publicRef = ref(db, 'users/' + firebase_email + '/public/publicKey'); const privateRef = ref(db, 'users/' + firebase_email + '/private/privateKey'); const publicKey = await get(publicRef); const privateKey = await get(privateRef); // Attach the keys to the user's credentials for this session user.privateKey = privateKey.val(); user.publicKey = publicKey.val(); // Run state machine based on args and user state // db is the firebase database // auth is the firebase auth // user is the firebase user // action is the command line argument (first argument) // data is the command line argument concatenated (second argument) // args is the raw array of command line arguments stateMachine(db, auth, user, action, args); }).catch((error) => { const errorMessage = error.message; console.log(`Error loading cached user: ${errorMessage}`); console.log('Try login again or delete ./peanuts/session.json'); }) } else { // User doesn't have login information saved (null) // the statemachine will process only the actions to login or register based on user state let user = null; stateMachine(db, auth, user, action, args); } } // Call Main entry point main();