mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2024-12-22 16:26: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 inquirer from 'inquirer'
|
||||||
import { access, constants, writeFile } from 'node:fs/promises'
|
import { access, constants, writeFile } from 'node:fs/promises'
|
||||||
import { basename, resolve } from 'node:path'
|
import { basename, resolve } from 'node:path'
|
||||||
import { Hex } from 'occulto'
|
import { AES, Hex } from 'occulto'
|
||||||
import pretty from 'pretty-bytes'
|
import pretty from 'pretty-bytes'
|
||||||
|
|
||||||
import { exit } from './utils'
|
import { exit } from './utils'
|
||||||
@ -10,11 +10,26 @@ import { exit } from './utils'
|
|||||||
export async function download(url: URL) {
|
export async function download(url: URL) {
|
||||||
setBase(url.origin)
|
setBase(url.origin)
|
||||||
const id = url.pathname.split('/')[2]
|
const id = url.pathname.split('/')[2]
|
||||||
await info(id).catch(() => exit('Note does not exist or is expired'))
|
const preview = await info(id).catch(() => exit('Note does not exist or is expired'))
|
||||||
const note = await get(id)
|
|
||||||
|
|
||||||
const password = url.hash.slice(1)
|
// Password
|
||||||
const key = Hex.decode(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')
|
const couldNotDecrypt = () => exit('Could not decrypt note. Probably an invalid password')
|
||||||
switch (note.meta.type) {
|
switch (note.meta.type) {
|
||||||
|
@ -6,7 +6,8 @@ import prettyBytes from 'pretty-bytes'
|
|||||||
|
|
||||||
import { download } from './download.js'
|
import { download } from './download.js'
|
||||||
import { parseFile, parseNumber } from './parsers.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'
|
import { exit } from './utils.js'
|
||||||
|
|
||||||
const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org'
|
const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org'
|
||||||
@ -61,10 +62,12 @@ send
|
|||||||
.addOption(server)
|
.addOption(server)
|
||||||
.addOption(views)
|
.addOption(views)
|
||||||
.addOption(minutes)
|
.addOption(minutes)
|
||||||
|
.addOption(password)
|
||||||
.action(async (files, options) => {
|
.action(async (files, options) => {
|
||||||
setBase(options.server!)
|
setBase(options.server!)
|
||||||
await checkConstrains(options)
|
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
|
send
|
||||||
.command('text')
|
.command('text')
|
||||||
@ -72,10 +75,12 @@ send
|
|||||||
.addOption(server)
|
.addOption(server)
|
||||||
.addOption(views)
|
.addOption(views)
|
||||||
.addOption(minutes)
|
.addOption(minutes)
|
||||||
|
.addOption(password)
|
||||||
.action(async (text, options) => {
|
.action(async (text, options) => {
|
||||||
setBase(options.server!)
|
setBase(options.server!)
|
||||||
await checkConstrains(options)
|
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
|
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,29 +1,28 @@
|
|||||||
import { readFile, stat } from 'node:fs/promises'
|
import { readFile, stat } from 'node:fs/promises'
|
||||||
import { basename } from 'node:path'
|
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 mime from 'mime'
|
||||||
import { AES, Hex, TypedArray } from 'occulto'
|
import { AES, Hex, TypedArray } from 'occulto'
|
||||||
|
|
||||||
import { exit } from './utils.js'
|
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 {
|
try {
|
||||||
const result = await create(note)
|
const { password, ...noteOptions } = options
|
||||||
const password = Hex.encode(key)
|
const derived = options.password ? await AES.derive(options.password) : undefined
|
||||||
const url = `${BASE}/note/${result.id}#${password}`
|
const key = derived ? derived[0] : await AES.generateKey()
|
||||||
console.log(`Note created under:\n\n${url}`)
|
|
||||||
} catch {
|
|
||||||
exit('Could not create note')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function uploadFiles(paths: string[], options: UploadOptions) {
|
let contents: string
|
||||||
const key = await AES.generateKey()
|
let type: NoteMeta['type']
|
||||||
|
if (typeof input === 'string') {
|
||||||
|
contents = await Adapters.Text.encrypt(input, key)
|
||||||
|
type = 'text'
|
||||||
|
} else {
|
||||||
const files: FileDTO[] = await Promise.all(
|
const files: FileDTO[] = await Promise.all(
|
||||||
paths.map(async (path) => {
|
input.map(async (path) => {
|
||||||
const data = new Uint8Array(await readFile(path))
|
const data = new Uint8Array(await readFile(path))
|
||||||
const stats = await stat(path)
|
const stats = await stat(path)
|
||||||
const extension = path.substring(path.indexOf('.') + 1)
|
const extension = path.substring(path.indexOf('.') + 1)
|
||||||
@ -36,13 +35,17 @@ export async function uploadFiles(paths: string[], options: UploadOptions) {
|
|||||||
} satisfies FileDTO
|
} satisfies FileDTO
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
contents = await Adapters.Files.encrypt(files, key)
|
||||||
const contents = await Adapters.Files.encrypt(files, key)
|
type = 'file'
|
||||||
await upload(key, { ...options, contents, meta: { type: 'file' } })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadText(text: string, options: UploadOptions) {
|
// Create the actual note and upload it.
|
||||||
const key = await AES.generateKey()
|
const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } }
|
||||||
const contents = await Adapters.Text.encrypt(text, key)
|
const result = await create(note)
|
||||||
await upload(key, { ...options, contents, meta: { type: 'text' } })
|
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')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user