mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2024-12-22 08:16:28 +00:00
add password to CLI
This commit is contained in:
parent
6000553b95
commit
e6d1e0f44a
@ -2,7 +2,7 @@ import { Adapters, get, info, setBase } from '@cryptgeon/shared'
|
||||
import inquirer from 'inquirer'
|
||||
import { access, constants, writeFile } from 'node:fs/promises'
|
||||
import { basename, resolve } from 'node:path'
|
||||
import { Hex } from 'occulto'
|
||||
import { AES, Hex } from 'occulto'
|
||||
import pretty from 'pretty-bytes'
|
||||
|
||||
import { exit } from './utils'
|
||||
@ -10,11 +10,26 @@ import { exit } from './utils'
|
||||
export async function download(url: URL) {
|
||||
setBase(url.origin)
|
||||
const id = url.pathname.split('/')[2]
|
||||
await info(id).catch(() => exit('Note does not exist or is expired'))
|
||||
const note = await get(id)
|
||||
const preview = await info(id).catch(() => exit('Note does not exist or is expired'))
|
||||
|
||||
const password = url.hash.slice(1)
|
||||
const key = Hex.decode(password)
|
||||
// Password
|
||||
let password: string
|
||||
const derivation = preview?.meta.derivation
|
||||
if (derivation) {
|
||||
const response = await inquirer.prompt([
|
||||
{
|
||||
type: 'password',
|
||||
message: 'Note password',
|
||||
name: 'password',
|
||||
},
|
||||
])
|
||||
password = response.password
|
||||
} else {
|
||||
password = url.hash.slice(1)
|
||||
}
|
||||
|
||||
const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password)
|
||||
const note = await get(id)
|
||||
|
||||
const couldNotDecrypt = () => exit('Could not decrypt note. Probably an invalid password')
|
||||
switch (note.meta.type) {
|
||||
|
@ -6,7 +6,8 @@ import prettyBytes from 'pretty-bytes'
|
||||
|
||||
import { download } from './download.js'
|
||||
import { parseFile, parseNumber } from './parsers.js'
|
||||
import { uploadFiles, uploadText } from './upload.js'
|
||||
import { getStdin } from './stdin.js'
|
||||
import { upload } from './upload.js'
|
||||
import { exit } from './utils.js'
|
||||
|
||||
const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org'
|
||||
@ -61,10 +62,12 @@ send
|
||||
.addOption(server)
|
||||
.addOption(views)
|
||||
.addOption(minutes)
|
||||
.addOption(password)
|
||||
.action(async (files, options) => {
|
||||
setBase(options.server!)
|
||||
await checkConstrains(options)
|
||||
await uploadFiles(files, { views: options.views, expiration: options.minutes })
|
||||
options.password ||= await getStdin()
|
||||
await upload(files, { views: options.views, expiration: options.minutes, password: options.password })
|
||||
})
|
||||
send
|
||||
.command('text')
|
||||
@ -72,10 +75,12 @@ send
|
||||
.addOption(server)
|
||||
.addOption(views)
|
||||
.addOption(minutes)
|
||||
.addOption(password)
|
||||
.action(async (text, options) => {
|
||||
setBase(options.server!)
|
||||
await checkConstrains(options)
|
||||
await uploadText(text, { views: options.views, expiration: options.minutes })
|
||||
options.password ||= await getStdin()
|
||||
await upload(text, { views: options.views, expiration: options.minutes, password: options.password })
|
||||
})
|
||||
|
||||
program
|
||||
|
20
packages/cli/src/stdin.ts
Normal file
20
packages/cli/src/stdin.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export function getStdin(timeout: number = 10): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
// Store the data from stdin in a buffer
|
||||
let buffer = ''
|
||||
process.stdin.on('data', (d) => (buffer += d.toString()))
|
||||
|
||||
// Stop listening for data after the timeout, otherwise hangs indefinitely
|
||||
const t = setTimeout(() => {
|
||||
process.stdin.destroy()
|
||||
resolve('')
|
||||
}, timeout)
|
||||
|
||||
// Listen for end and error events
|
||||
process.stdin.on('end', () => {
|
||||
clearTimeout(t)
|
||||
resolve(buffer.trim())
|
||||
})
|
||||
process.stdin.on('error', reject)
|
||||
})
|
||||
}
|
@ -1,48 +1,51 @@
|
||||
import { readFile, stat } from 'node:fs/promises'
|
||||
import { basename } from 'node:path'
|
||||
|
||||
import { Adapters, BASE, create, FileDTO, Note } from '@cryptgeon/shared'
|
||||
import { Adapters, BASE, create, FileDTO, Note, NoteMeta } from '@cryptgeon/shared'
|
||||
import mime from 'mime'
|
||||
import { AES, Hex, TypedArray } from 'occulto'
|
||||
|
||||
import { exit } from './utils.js'
|
||||
|
||||
type UploadOptions = Pick<Note, 'views' | 'expiration'>
|
||||
type UploadOptions = Pick<Note, 'views' | 'expiration'> & { password?: string }
|
||||
|
||||
export async function upload(key: TypedArray, note: Note) {
|
||||
export async function upload(input: string | string[], options: UploadOptions) {
|
||||
try {
|
||||
const { password, ...noteOptions } = options
|
||||
const derived = options.password ? await AES.derive(options.password) : undefined
|
||||
const key = derived ? derived[0] : await AES.generateKey()
|
||||
|
||||
let contents: string
|
||||
let type: NoteMeta['type']
|
||||
if (typeof input === 'string') {
|
||||
contents = await Adapters.Text.encrypt(input, key)
|
||||
type = 'text'
|
||||
} else {
|
||||
const files: FileDTO[] = await Promise.all(
|
||||
input.map(async (path) => {
|
||||
const data = new Uint8Array(await readFile(path))
|
||||
const stats = await stat(path)
|
||||
const extension = path.substring(path.indexOf('.') + 1)
|
||||
const type = mime.getType(extension) ?? 'application/octet-stream'
|
||||
return {
|
||||
name: basename(path),
|
||||
size: stats.size,
|
||||
contents: data,
|
||||
type,
|
||||
} satisfies FileDTO
|
||||
})
|
||||
)
|
||||
contents = await Adapters.Files.encrypt(files, key)
|
||||
type = 'file'
|
||||
}
|
||||
|
||||
// Create the actual note and upload it.
|
||||
const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } }
|
||||
const result = await create(note)
|
||||
const password = Hex.encode(key)
|
||||
const url = `${BASE}/note/${result.id}#${password}`
|
||||
console.log(`Note created under:\n\n${url}`)
|
||||
let url = `${BASE}/note/${result.id}`
|
||||
if (!derived) url += `#${Hex.encode(key)}`
|
||||
console.log(`Note created:\n\n${url}`)
|
||||
} catch {
|
||||
exit('Could not create note')
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadFiles(paths: string[], options: UploadOptions) {
|
||||
const key = await AES.generateKey()
|
||||
const files: FileDTO[] = await Promise.all(
|
||||
paths.map(async (path) => {
|
||||
const data = new Uint8Array(await readFile(path))
|
||||
const stats = await stat(path)
|
||||
const extension = path.substring(path.indexOf('.') + 1)
|
||||
const type = mime.getType(extension) ?? 'application/octet-stream'
|
||||
return {
|
||||
name: basename(path),
|
||||
size: stats.size,
|
||||
contents: data,
|
||||
type,
|
||||
} satisfies FileDTO
|
||||
})
|
||||
)
|
||||
|
||||
const contents = await Adapters.Files.encrypt(files, key)
|
||||
await upload(key, { ...options, contents, meta: { type: 'file' } })
|
||||
}
|
||||
|
||||
export async function uploadText(text: string, options: UploadOptions) {
|
||||
const key = await AES.generateKey()
|
||||
const contents = await Adapters.Text.encrypt(text, key)
|
||||
await upload(key, { ...options, contents, meta: { type: 'text' } })
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user