mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2024-12-23 00:36:28 +00:00
move to packages
This commit is contained in:
parent
6fb7518b6a
commit
4c25ca005e
@ -1,15 +1,15 @@
|
|||||||
*
|
*
|
||||||
|
|
||||||
!/packages/backend/src
|
!/packages
|
||||||
!/packages/backend/Cargo.lock
|
!/package.json
|
||||||
!/packages/backend/Cargo.toml
|
!/pnpm-lock.yaml
|
||||||
|
!/pnpm-workspace.yaml
|
||||||
|
|
||||||
!/packages/frontend/locales
|
**/target
|
||||||
!/packages/frontend/src
|
**/node_modules
|
||||||
!/packages/frontend/static
|
**/dist
|
||||||
!/packages/frontend/.npmrc
|
**/bin
|
||||||
!/packages/frontend/package.json
|
**/*.tsbuildinfo
|
||||||
!/packages/frontend/pnpm-lock.yaml
|
**/build
|
||||||
!/packages/frontend/svelte.config.js
|
**/.svelte
|
||||||
!/packages/frontend/tsconfig.json
|
**/.svelte-kit
|
||||||
!/packages/frontend/vite.config.js
|
|
||||||
|
7
.github/workflows/test.yaml
vendored
7
.github/workflows/test.yaml
vendored
@ -13,10 +13,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@v1
|
- uses: docker/setup-qemu-action@v2
|
||||||
- uses: docker/setup-buildx-action@v1
|
- uses: docker/setup-buildx-action@v2
|
||||||
with:
|
with:
|
||||||
install: true
|
install: true
|
||||||
- name: Build docker image
|
- name: Build docker image
|
||||||
@ -30,7 +30,6 @@ jobs:
|
|||||||
- name: Run your tests
|
- name: Run your tests
|
||||||
run: npm test
|
run: npm test
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: always()
|
|
||||||
with:
|
with:
|
||||||
name: test-results
|
name: test-results
|
||||||
path: test-results
|
path: test-results
|
||||||
|
16
.gitignore
vendored
16
.gitignore
vendored
@ -1,14 +1,10 @@
|
|||||||
|
.env
|
||||||
|
*.tsbuildinfo
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
bin
|
||||||
|
|
||||||
# Backend
|
|
||||||
target
|
target
|
||||||
|
|
||||||
# Client
|
# Testing
|
||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/.svelte
|
|
||||||
/build
|
|
||||||
/functions
|
|
||||||
.env
|
|
||||||
|
|
||||||
General
|
|
||||||
test-results
|
test-results
|
||||||
|
20
Dockerfile
20
Dockerfile
@ -1,31 +1,29 @@
|
|||||||
# FRONTEND
|
# FRONTEND
|
||||||
FROM node:16-alpine as client
|
FROM node:16-alpine as client
|
||||||
WORKDIR /tmp
|
WORKDIR /tmp
|
||||||
RUN npm install -g pnpm@7
|
RUN npm install -g pnpm@8
|
||||||
COPY ./packages/frontend ./
|
COPY . .
|
||||||
RUN pnpm install
|
RUN pnpm install --frozen-lockfile
|
||||||
RUN pnpm exec svelte-kit sync
|
# WORKDIR /tmp/packages/frontend
|
||||||
|
# RUN pnpm exec svelte-kit sync
|
||||||
RUN pnpm run build
|
RUN pnpm run build
|
||||||
|
|
||||||
|
|
||||||
# BACKEND
|
# BACKEND
|
||||||
FROM rust:1.64-alpine as backend
|
FROM rust:1.69-alpine as backend
|
||||||
WORKDIR /tmp
|
WORKDIR /tmp
|
||||||
RUN apk add libc-dev openssl-dev alpine-sdk
|
RUN apk add libc-dev openssl-dev alpine-sdk
|
||||||
COPY ./packages/backend/Cargo.* ./
|
COPY ./packages/backend/Cargo.* ./
|
||||||
# https://blog.rust-lang.org/2022/06/22/sparse-registry-testing.html
|
RUN cargo fetch
|
||||||
RUN rustup update nightly
|
|
||||||
ENV CARGO_UNSTABLE_SPARSE_REGISTRY=true
|
|
||||||
RUN cargo +nightly fetch
|
|
||||||
COPY ./packages/backend ./
|
COPY ./packages/backend ./
|
||||||
RUN cargo +nightly build --release
|
RUN cargo build --release
|
||||||
|
|
||||||
|
|
||||||
# RUNNER
|
# RUNNER
|
||||||
FROM alpine
|
FROM alpine
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=backend /tmp/target/release/cryptgeon .
|
COPY --from=backend /tmp/target/release/cryptgeon .
|
||||||
COPY --from=client /tmp/build ./frontend
|
COPY --from=client /tmp/packages/frontend/build ./frontend
|
||||||
ENV FRONTEND_PATH="./frontend"
|
ENV FRONTEND_PATH="./frontend"
|
||||||
ENV REDIS="redis://redis/"
|
ENV REDIS="redis://redis/"
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
"path": "."
|
"path": "."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "packages/backend"
|
"path": "packages/backend"
|
||||||
}
|
},
|
||||||
],
|
{
|
||||||
|
"path": "packages/frontend"
|
||||||
|
}
|
||||||
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"cSpell.words": ["ciphertext", "cryptgeon"],
|
"i18n-ally.localesPaths": ["packages/frontend/locales"],
|
||||||
"i18n-ally.enabledFrameworks": ["svelte"],
|
"cSpell.words": ["cryptgeon"]
|
||||||
"i18n-ally.keystyle": "nested",
|
|
||||||
"i18n-ally.localesPaths": ["packages/frontend/locales"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ services:
|
|||||||
- 6379:6379
|
- 6379:6379
|
||||||
|
|
||||||
app:
|
app:
|
||||||
image: cupcakearmy/cryptgeon:test
|
|
||||||
build: .
|
build: .
|
||||||
env_file: .dev.env
|
env_file: .dev.env
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -2,17 +2,16 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:docker": "docker-compose -f docker-compose.dev.yaml up redis",
|
"dev:docker": "docker-compose -f docker-compose.dev.yaml up redis",
|
||||||
"dev:packages": "pnpm --parallel run dev",
|
"dev:packages": "pnpm --parallel run dev",
|
||||||
"dev:proxy": "node proxy.mjs",
|
|
||||||
"dev": "run-p dev:*",
|
"dev": "run-p dev:*",
|
||||||
"test": "playwright test --project chrome firefox safari",
|
"test": "playwright test --project chrome firefox safari",
|
||||||
"test:local": "playwright test --project local",
|
"test:local": "playwright test --project local",
|
||||||
"test:server": "docker compose -f docker-compose.dev.yaml up",
|
"test:server": "docker compose -f docker-compose.dev.yaml up",
|
||||||
"test:prepare": "docker compose -f docker-compose.dev.yaml build"
|
"test:prepare": "docker compose -f docker-compose.dev.yaml build",
|
||||||
|
"build": "pnpm run --recursive --filter=!@cryptgeon/backend build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.29.2",
|
"@playwright/test": "^1.32.3",
|
||||||
"@types/node": "^16.18.11",
|
"@types/node": "^16.18.24",
|
||||||
"http-proxy": "^1.18.1",
|
|
||||||
"npm-run-all": "^4.1.5"
|
"npm-run-all": "^4.1.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,9 @@ edition = "2021"
|
|||||||
name = "cryptgeon"
|
name = "cryptgeon"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[registries.crates-io]
|
||||||
|
protocol = "sparse"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "backend",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"name": "@cryptgeon/backend",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cargo watch -x 'run --bin cryptgeon'",
|
"dev": "cargo watch -x 'run --bin cryptgeon'",
|
||||||
"build": "cargo build --release",
|
"build": "cargo build --release",
|
||||||
|
43
packages/cli/package.json
Normal file
43
packages/cli/package.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "@cryptgeon/cli",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "esbuild ./src/index.ts --bundle --platform=node --outfile=dist/index.cjs --watch",
|
||||||
|
"build": "esbuild ./src/index.ts --bundle --platform=node --outfile=dist/index.cjs",
|
||||||
|
"bin": "pkg ."
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"cryptgeon": "./dist/index.cjs"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"pkg": {
|
||||||
|
"scripts": "dist/**/*.js",
|
||||||
|
"targets": [
|
||||||
|
"node18-macos-arm64",
|
||||||
|
"node18-macos-x64",
|
||||||
|
"node18-linux-arm64",
|
||||||
|
"node18-linux-x64",
|
||||||
|
"node18-win-arm64",
|
||||||
|
"node18-win-x64"
|
||||||
|
],
|
||||||
|
"outputPath": "bin"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/inquirer": "^9.0.3",
|
||||||
|
"@types/mime": "^3.0.1",
|
||||||
|
"esbuild": "^0.17.18",
|
||||||
|
"pkg": "^5.8.1",
|
||||||
|
"typescript": "^4.9.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@commander-js/extra-typings": "^9.5.0",
|
||||||
|
"@cryptgeon/shared": "workspace:*",
|
||||||
|
"commander": "^9.5.0",
|
||||||
|
"inquirer": "^9.2.0",
|
||||||
|
"mime": "^3.0.0",
|
||||||
|
"occulto": "^2.0.1",
|
||||||
|
"pretty-bytes": "^6.1.0"
|
||||||
|
}
|
||||||
|
}
|
63
packages/cli/src/download.ts
Normal file
63
packages/cli/src/download.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
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 pretty from 'pretty-bytes'
|
||||||
|
|
||||||
|
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 password = url.hash.slice(1)
|
||||||
|
const key = Hex.decode(password)
|
||||||
|
|
||||||
|
const couldNotDecrypt = () => exit('Could not decrypt note. Probably an invalid password')
|
||||||
|
switch (note.meta.type) {
|
||||||
|
case 'file':
|
||||||
|
const files = await Adapters.Files.decrypt(note.contents, key).catch(couldNotDecrypt)
|
||||||
|
if (!files) {
|
||||||
|
exit('No files found in note')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(files)
|
||||||
|
const { names } = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
message: 'What files should be saved?',
|
||||||
|
name: 'names',
|
||||||
|
choices: files.map((file) => ({
|
||||||
|
value: file.name,
|
||||||
|
name: `${file.name} - ${file.type} - ${pretty(file.size, { binary: true })}`,
|
||||||
|
checked: true,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const selected = files.filter((file) => names.includes(file.name))
|
||||||
|
|
||||||
|
if (!selected.length) exit('No files selected')
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
files.map(async (file) => {
|
||||||
|
let filename = resolve(file.name)
|
||||||
|
try {
|
||||||
|
// If exists -> prepend timestamp to not overwrite the current file
|
||||||
|
await access(filename, constants.R_OK)
|
||||||
|
filename = resolve(`${Date.now()}-${file.name}`)
|
||||||
|
} catch {}
|
||||||
|
await writeFile(filename, new Uint8Array(await file.contents.arrayBuffer()))
|
||||||
|
console.log(`Saved: ${basename(filename)}`)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
break
|
||||||
|
case 'text':
|
||||||
|
const plaintext = await Adapters.Text.decrypt(note.contents, key).catch(couldNotDecrypt)
|
||||||
|
console.log(plaintext)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
82
packages/cli/src/index.ts
Normal file
82
packages/cli/src/index.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { Argument, Option, program } from '@commander-js/extra-typings'
|
||||||
|
import { setBase, status } from '@cryptgeon/shared'
|
||||||
|
|
||||||
|
import { download } from './download.js'
|
||||||
|
import { parseFile, parseNumber } from './parsers.js'
|
||||||
|
import { uploadFiles, uploadText } from './upload.js'
|
||||||
|
import { exit } from './utils.js'
|
||||||
|
|
||||||
|
const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org'
|
||||||
|
const server = new Option('-s --server <url>', 'the cryptgeon server to use').default(defaultServer)
|
||||||
|
const files = new Argument('<file...>', 'Files to be sent').argParser(parseFile)
|
||||||
|
const text = new Argument('<text>', 'Text content of the note')
|
||||||
|
const password = new Option('-p --password <string>', 'manually set a password')
|
||||||
|
const url = new Argument('<url>', 'The url to open')
|
||||||
|
const views = new Option('-v --views <number>', 'Amount of views before getting destroyed').argParser(parseNumber)
|
||||||
|
const minutes = new Option('-m --minutes <number>', 'Minutes before the note expires').argParser(parseNumber)
|
||||||
|
|
||||||
|
async function checkConstrains(constrains: { views?: number; minutes?: number }) {
|
||||||
|
const { views, minutes } = constrains
|
||||||
|
if (views && minutes) exit('cannot set view and minutes constrains simultaneously')
|
||||||
|
if (!views && !minutes) constrains.views = 1
|
||||||
|
|
||||||
|
const response = await status()
|
||||||
|
if (views && views > response.max_views)
|
||||||
|
exit(`Only a maximum of ${response.max_views} views allowed. ${views} given.`)
|
||||||
|
if (minutes && minutes > response.max_expiration)
|
||||||
|
exit(`Only a maximum of ${response.max_expiration} minutes allowed. ${minutes} given.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
program.name('cryptgeon').version('1.0.0').configureHelp({ showGlobalOptions: true })
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('info')
|
||||||
|
.addOption(server)
|
||||||
|
.action(async (options) => {
|
||||||
|
setBase(options.server)
|
||||||
|
const response = await status()
|
||||||
|
for (const key of Object.keys(response)) {
|
||||||
|
if (key.startsWith('theme_')) delete response[key as keyof typeof response]
|
||||||
|
}
|
||||||
|
console.table(response)
|
||||||
|
})
|
||||||
|
|
||||||
|
const send = program.command('send')
|
||||||
|
send
|
||||||
|
.command('file')
|
||||||
|
.addArgument(files)
|
||||||
|
.addOption(server)
|
||||||
|
.addOption(views)
|
||||||
|
.addOption(minutes)
|
||||||
|
.action(async (files, options) => {
|
||||||
|
setBase(options.server!)
|
||||||
|
await checkConstrains(options)
|
||||||
|
await uploadFiles(files, { views: options.views, expiration: options.minutes })
|
||||||
|
})
|
||||||
|
send
|
||||||
|
.command('text')
|
||||||
|
.addArgument(text)
|
||||||
|
.addOption(server)
|
||||||
|
.addOption(views)
|
||||||
|
.addOption(minutes)
|
||||||
|
.action(async (text, options) => {
|
||||||
|
setBase(options.server!)
|
||||||
|
await checkConstrains(options)
|
||||||
|
await uploadText(text, { views: options.views, expiration: options.minutes })
|
||||||
|
})
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('open')
|
||||||
|
.addArgument(url)
|
||||||
|
.action(async (note, options) => {
|
||||||
|
try {
|
||||||
|
const url = new URL(note)
|
||||||
|
await download(url)
|
||||||
|
} catch {
|
||||||
|
exit('Invalid URL')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
program.parse()
|
27
packages/cli/src/parsers.ts
Normal file
27
packages/cli/src/parsers.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { InvalidArgumentError, InvalidOptionArgumentError } from '@commander-js/extra-typings'
|
||||||
|
import { accessSync, constants } from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
export function parseFile(value: string, before: string[] = []) {
|
||||||
|
try {
|
||||||
|
const file = path.resolve(value)
|
||||||
|
accessSync(file, constants.R_OK)
|
||||||
|
return [...before, file]
|
||||||
|
} catch {
|
||||||
|
throw new InvalidArgumentError('cannot access file')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseURL(value: string, _: URL): URL {
|
||||||
|
try {
|
||||||
|
return new URL(value)
|
||||||
|
} catch {
|
||||||
|
throw new InvalidArgumentError('is not a valid url')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseNumber(value: string, _: number): number {
|
||||||
|
const n = parseInt(value, 10)
|
||||||
|
if (isNaN(n)) throw new InvalidOptionArgumentError('invalid number')
|
||||||
|
return n
|
||||||
|
}
|
49
packages/cli/src/upload.ts
Normal file
49
packages/cli/src/upload.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { Blob } from 'node:buffer'
|
||||||
|
import { readFile, stat } from 'node:fs/promises'
|
||||||
|
import { basename } from 'node:path'
|
||||||
|
|
||||||
|
import { Adapters, BASE, create, FileDTO, Note } from '@cryptgeon/shared'
|
||||||
|
import mime from 'mime'
|
||||||
|
import { AES, Hex, TypedArray } from 'occulto'
|
||||||
|
|
||||||
|
import { exit } from './utils.js'
|
||||||
|
|
||||||
|
type UploadOptions = Pick<Note, 'views' | 'expiration'>
|
||||||
|
|
||||||
|
export async function upload(key: TypedArray, note: Note) {
|
||||||
|
try {
|
||||||
|
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}`)
|
||||||
|
} 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: new Blob([data]) as FileDTO['contents'],
|
||||||
|
type,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
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' } })
|
||||||
|
}
|
6
packages/cli/src/utils.ts
Normal file
6
packages/cli/src/utils.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import process from 'node:process'
|
||||||
|
|
||||||
|
export function exit(message: string) {
|
||||||
|
console.error(message)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
110
packages/cli/tsconfig.json
Normal file
110
packages/cli/tsconfig.json
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "es2022" /* Specify what module code is generated. */,
|
||||||
|
// "rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||||
|
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||||
|
// "baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */,
|
||||||
|
// "paths": {
|
||||||
|
// "@shared/*": ["../shared/*"],
|
||||||
|
// "@shared": ["../shared"]
|
||||||
|
// } /* Specify a set of entries that re-map imports to additional lookup locations. */,
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [
|
||||||
|
// "../shared/types.ts",
|
||||||
|
// "node"
|
||||||
|
// ] /* Specify type package names to be included without being referenced in a source file. */,
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
"isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
// "references": [{ "path": "../shared" }]
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
engine-strict=true
|
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"name": "@cryptgeon/web",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
@ -11,29 +12,30 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lokalise/node-api": "^9.5.0",
|
"@lokalise/node-api": "^9.8.0",
|
||||||
"@sveltejs/adapter-static": "^1.0.2",
|
"@sveltejs/adapter-static": "^1.0.6",
|
||||||
"@sveltejs/kit": "^1.0.13",
|
"@sveltejs/kit": "^1.15.8",
|
||||||
"@types/dompurify": "^2.4.0",
|
"@types/dompurify": "^2.4.0",
|
||||||
"@types/file-saver": "^2.0.5",
|
"@types/file-saver": "^2.0.5",
|
||||||
"@zerodevx/svelte-toast": "^0.7.2",
|
"@zerodevx/svelte-toast": "^0.7.2",
|
||||||
"adm-zip": "^0.5.10",
|
"adm-zip": "^0.5.10",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"svelte": "^3.55.1",
|
"svelte": "^3.58.0",
|
||||||
"svelte-check": "^2.10.3",
|
"svelte-check": "^2.10.3",
|
||||||
"svelte-intl-precompile": "^0.10.1",
|
"svelte-intl-precompile": "^0.10.1",
|
||||||
"svelte-preprocess": "^4.10.7",
|
"svelte-preprocess": "^4.10.7",
|
||||||
"tslib": "^2.4.1",
|
"tslib": "^2.5.0",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.5",
|
||||||
"vite": "^4.0.4"
|
"vite": "^4.3.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cryptgeon/shared": "workspace:*",
|
||||||
"@fontsource/fira-mono": "^4.5.10",
|
"@fontsource/fira-mono": "^4.5.10",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"dompurify": "^2.4.3",
|
"dompurify": "^2.4.5",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"occulto": "2.0.0-rc.10",
|
"occulto": "^2.0.0",
|
||||||
"pretty-bytes": "^6.0.0",
|
"pretty-bytes": "^6.1.0",
|
||||||
"qrious": "^4.0.2"
|
"qrious": "^4.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
packages/frontend/src/app.d.ts
vendored
Normal file
9
packages/frontend/src/app.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
// for information about these interfaces
|
||||||
|
// and what to do when importing types
|
||||||
|
declare namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
3
packages/frontend/src/global.d.ts
vendored
3
packages/frontend/src/global.d.ts
vendored
@ -1,3 +0,0 @@
|
|||||||
/// <reference types="@sveltejs/kit" />
|
|
||||||
/// <reference types="svelte" />
|
|
||||||
/// <reference types="vite/client" />
|
|
@ -1,60 +0,0 @@
|
|||||||
import { AES, Bytes, type TypedArray } from 'occulto'
|
|
||||||
import type { EncryptedFileDTO, FileDTO } from './api'
|
|
||||||
|
|
||||||
abstract class CryptAdapter<T> {
|
|
||||||
abstract encrypt(plaintext: T, key: TypedArray): Promise<string>
|
|
||||||
abstract decrypt(ciphertext: string, key: TypedArray): Promise<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
class CryptTextAdapter implements CryptAdapter<string> {
|
|
||||||
async encrypt(plaintext: string, key: TypedArray) {
|
|
||||||
return await AES.encrypt(Bytes.encode(plaintext), key)
|
|
||||||
}
|
|
||||||
async decrypt(ciphertext: string, key: TypedArray) {
|
|
||||||
return Bytes.decode(await AES.decrypt(ciphertext, key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CryptBlobAdapter implements CryptAdapter<Blob> {
|
|
||||||
async encrypt(plaintext: Blob, key: TypedArray) {
|
|
||||||
return await AES.encrypt(new Uint8Array(await plaintext.arrayBuffer()), key)
|
|
||||||
}
|
|
||||||
|
|
||||||
async decrypt(ciphertext: string, key: TypedArray) {
|
|
||||||
const plaintext = await AES.decrypt(ciphertext, key)
|
|
||||||
return new Blob([plaintext], { type: 'application/octet-stream' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CryptFilesAdapter implements CryptAdapter<FileDTO[]> {
|
|
||||||
async encrypt(plaintext: FileDTO[], key: TypedArray) {
|
|
||||||
const adapter = new CryptBlobAdapter()
|
|
||||||
const data: Promise<EncryptedFileDTO>[] = plaintext.map(async (file) => ({
|
|
||||||
name: file.name,
|
|
||||||
size: file.size,
|
|
||||||
type: file.type,
|
|
||||||
contents: await adapter.encrypt(file.contents, key),
|
|
||||||
}))
|
|
||||||
return JSON.stringify(await Promise.all(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
async decrypt(ciphertext: string, key: TypedArray) {
|
|
||||||
const adapter = new CryptBlobAdapter()
|
|
||||||
const data: EncryptedFileDTO[] = JSON.parse(ciphertext)
|
|
||||||
const files: FileDTO[] = await Promise.all(
|
|
||||||
data.map(async (file) => ({
|
|
||||||
name: file.name,
|
|
||||||
size: file.size,
|
|
||||||
type: file.type,
|
|
||||||
contents: await adapter.decrypt(file.contents, key),
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Adapters = {
|
|
||||||
Text: new CryptTextAdapter(),
|
|
||||||
Blob: new CryptBlobAdapter(),
|
|
||||||
Files: new CryptFilesAdapter(),
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
export type NoteMeta = { type: 'text' | 'file' }
|
|
||||||
|
|
||||||
export type Note = {
|
|
||||||
contents: string
|
|
||||||
meta: NoteMeta
|
|
||||||
views?: number
|
|
||||||
expiration?: number
|
|
||||||
}
|
|
||||||
export type NoteInfo = {}
|
|
||||||
export type NotePublic = Pick<Note, 'contents' | 'meta'>
|
|
||||||
export type NoteCreate = Omit<Note, 'meta'> & { meta: string }
|
|
||||||
|
|
||||||
export type FileDTO = Pick<File, 'name' | 'size' | 'type'> & {
|
|
||||||
contents: Blob
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EncryptedFileDTO = Omit<FileDTO, 'contents'> & {
|
|
||||||
contents: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CallOptions = {
|
|
||||||
url: string
|
|
||||||
method: string
|
|
||||||
body?: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PayloadToLargeError extends Error {}
|
|
||||||
|
|
||||||
export async function call(options: CallOptions) {
|
|
||||||
const response = await fetch('/api/' + options.url, {
|
|
||||||
method: options.method,
|
|
||||||
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
||||||
mode: 'cors',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 413) throw new PayloadToLargeError()
|
|
||||||
else throw new Error('API call failed')
|
|
||||||
}
|
|
||||||
return response.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function create(note: Note) {
|
|
||||||
const { meta, ...rest } = note
|
|
||||||
const body: NoteCreate = {
|
|
||||||
...rest,
|
|
||||||
meta: JSON.stringify(meta),
|
|
||||||
}
|
|
||||||
const data = await call({
|
|
||||||
url: 'notes/',
|
|
||||||
method: 'post',
|
|
||||||
body,
|
|
||||||
})
|
|
||||||
return data as { id: string }
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function get(id: string): Promise<NotePublic> {
|
|
||||||
const data = await call({
|
|
||||||
url: `notes/${id}`,
|
|
||||||
method: 'delete',
|
|
||||||
})
|
|
||||||
const { contents, meta } = data
|
|
||||||
return {
|
|
||||||
contents,
|
|
||||||
meta: JSON.parse(meta) as NoteMeta,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function info(id: string): Promise<NoteInfo> {
|
|
||||||
const data = await call({
|
|
||||||
url: `notes/${id}`,
|
|
||||||
method: 'get',
|
|
||||||
})
|
|
||||||
return data
|
|
||||||
}
|
|
@ -1,24 +1,8 @@
|
|||||||
import { call } from '$lib/api'
|
import { status as getStatus, type Status } from '@cryptgeon/shared'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
|
|
||||||
export type Status = {
|
|
||||||
version: string
|
|
||||||
max_size: number
|
|
||||||
max_views: number
|
|
||||||
max_expiration: number
|
|
||||||
allow_advanced: boolean
|
|
||||||
theme_image: string
|
|
||||||
theme_text: string
|
|
||||||
theme_favicon: string
|
|
||||||
theme_page_title: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const status = writable<null | Status>(null)
|
export const status = writable<null | Status>(null)
|
||||||
|
|
||||||
export async function init() {
|
export async function init() {
|
||||||
const data = await call({
|
status.set(await getStatus())
|
||||||
url: 'status/',
|
|
||||||
method: 'get',
|
|
||||||
})
|
|
||||||
status.set(data)
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
|
|
||||||
import type { Note } from '$lib/api'
|
|
||||||
import { status } from '$lib/stores/status'
|
import { status } from '$lib/stores/status'
|
||||||
import Switch from '$lib/ui/Switch.svelte'
|
import Switch from '$lib/ui/Switch.svelte'
|
||||||
import TextInput from '$lib/ui/TextInput.svelte'
|
import TextInput from '$lib/ui/TextInput.svelte'
|
||||||
|
import type { Note } from '@cryptgeon/shared'
|
||||||
|
|
||||||
export let note: Note
|
export let note: Note
|
||||||
export let timeExpiration = false
|
export let timeExpiration = false
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
// @ts-ignore
|
||||||
import QR from 'qrious'
|
import QR from 'qrious'
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
|
|
||||||
import type { FileDTO } from '$lib/api'
|
|
||||||
import Button from '$lib/ui/Button.svelte'
|
import Button from '$lib/ui/Button.svelte'
|
||||||
import MaxSize from '$lib/ui/MaxSize.svelte'
|
import MaxSize from '$lib/ui/MaxSize.svelte'
|
||||||
|
import type { FileDTO } from '@cryptgeon/shared'
|
||||||
|
|
||||||
export let label: string = ''
|
export let label: string = ''
|
||||||
export let files: FileDTO[] = []
|
export let files: FileDTO[] = []
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
|
|
||||||
import type { FileDTO, NotePublic } from '$lib/api'
|
|
||||||
import Button from '$lib/ui/Button.svelte'
|
import Button from '$lib/ui/Button.svelte'
|
||||||
import { copy } from '$lib/utils'
|
import { copy } from '$lib/utils'
|
||||||
|
import type { FileDTO, NotePublic } from '@cryptgeon/shared'
|
||||||
|
|
||||||
export let note: DecryptedNote
|
export let note: DecryptedNote
|
||||||
|
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
import { blur } from 'svelte/transition'
|
import { blur } from 'svelte/transition'
|
||||||
|
|
||||||
import { Adapters } from '$lib/adapters'
|
|
||||||
import type { FileDTO, Note } from '$lib/api'
|
|
||||||
import { create, PayloadToLargeError } from '$lib/api'
|
|
||||||
import { status } from '$lib/stores/status'
|
import { status } from '$lib/stores/status'
|
||||||
import { notify } from '$lib/toast'
|
import { notify } from '$lib/toast'
|
||||||
import AdvancedParameters from '$lib/ui/AdvancedParameters.svelte'
|
import AdvancedParameters from '$lib/ui/AdvancedParameters.svelte'
|
||||||
@ -16,6 +13,8 @@
|
|||||||
import Result, { type NoteResult } from '$lib/ui/NoteResult.svelte'
|
import Result, { type NoteResult } from '$lib/ui/NoteResult.svelte'
|
||||||
import Switch from '$lib/ui/Switch.svelte'
|
import Switch from '$lib/ui/Switch.svelte'
|
||||||
import TextArea from '$lib/ui/TextArea.svelte'
|
import TextArea from '$lib/ui/TextArea.svelte'
|
||||||
|
import type { FileDTO, Note } from '@cryptgeon/shared'
|
||||||
|
import { Adapters, create, PayloadToLargeError } from '@cryptgeon/shared'
|
||||||
|
|
||||||
let note: Note = {
|
let note: Note = {
|
||||||
contents: '',
|
contents: '',
|
||||||
@ -59,7 +58,7 @@
|
|||||||
loading = $t('common.encrypting')
|
loading = $t('common.encrypting')
|
||||||
|
|
||||||
const key = await AES.generateKey()
|
const key = await AES.generateKey()
|
||||||
const password = await Hex.encode(key)
|
const password = Hex.encode(key)
|
||||||
|
|
||||||
const data: Note = {
|
const data: Note = {
|
||||||
contents: '',
|
contents: '',
|
||||||
|
@ -3,11 +3,10 @@
|
|||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { t } from 'svelte-intl-precompile'
|
import { t } from 'svelte-intl-precompile'
|
||||||
|
|
||||||
import { Adapters } from '$lib/adapters'
|
|
||||||
import { get, info } from '$lib/api'
|
|
||||||
import Button from '$lib/ui/Button.svelte'
|
import Button from '$lib/ui/Button.svelte'
|
||||||
import Loader from '$lib/ui/Loader.svelte'
|
import Loader from '$lib/ui/Loader.svelte'
|
||||||
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
|
import ShowNote, { type DecryptedNote } from '$lib/ui/ShowNote.svelte'
|
||||||
|
import { Adapters, get, info } from '@cryptgeon/shared'
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
|
|
||||||
export let data: PageData
|
export let data: PageData
|
||||||
@ -43,7 +42,7 @@
|
|||||||
loading = $t('common.downloading')
|
loading = $t('common.downloading')
|
||||||
const data = await get(id)
|
const data = await get(id)
|
||||||
loading = $t('common.decrypting')
|
loading = $t('common.decrypting')
|
||||||
const key = await Hex.decode(password)
|
const key = Hex.decode(password)
|
||||||
switch (data.meta.type) {
|
switch (data.meta.type) {
|
||||||
case 'text':
|
case 'text':
|
||||||
note = {
|
note = {
|
||||||
|
12
packages/proxy/package.json
Normal file
12
packages/proxy/package.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "@cryptgeon/proxy",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./proxy.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node ."
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"http-proxy": "^1.18.1"
|
||||||
|
}
|
||||||
|
}
|
22
packages/shared/package.json
Normal file
22
packages/shared/package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "@cryptgeon/shared",
|
||||||
|
"type": "module",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsc -w",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^4.9.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"occulto": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
60
packages/shared/src/adapters.ts
Normal file
60
packages/shared/src/adapters.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { AES, Bytes, type TypedArray } from 'occulto'
|
||||||
|
import type { EncryptedFileDTO, FileDTO } from './api'
|
||||||
|
|
||||||
|
abstract class CryptAdapter<T> {
|
||||||
|
abstract encrypt(plaintext: T, key: TypedArray): Promise<string>
|
||||||
|
abstract decrypt(ciphertext: string, key: TypedArray): Promise<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
class CryptTextAdapter implements CryptAdapter<string> {
|
||||||
|
async encrypt(plaintext: string, key: TypedArray) {
|
||||||
|
return await AES.encrypt(Bytes.encode(plaintext), key)
|
||||||
|
}
|
||||||
|
async decrypt(ciphertext: string, key: TypedArray) {
|
||||||
|
return Bytes.decode(await AES.decrypt(ciphertext, key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CryptBlobAdapter implements CryptAdapter<Blob> {
|
||||||
|
async encrypt(plaintext: Blob, key: TypedArray) {
|
||||||
|
return await AES.encrypt(new Uint8Array(await plaintext.arrayBuffer()), key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async decrypt(ciphertext: string, key: TypedArray) {
|
||||||
|
const plaintext = await AES.decrypt(ciphertext, key)
|
||||||
|
return new Blob([plaintext], { type: 'application/octet-stream' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CryptFilesAdapter implements CryptAdapter<FileDTO[]> {
|
||||||
|
async encrypt(plaintext: FileDTO[], key: TypedArray) {
|
||||||
|
const adapter = new CryptBlobAdapter()
|
||||||
|
const data: Promise<EncryptedFileDTO>[] = plaintext.map(async (file) => ({
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
contents: await adapter.encrypt(file.contents, key),
|
||||||
|
}))
|
||||||
|
return JSON.stringify(await Promise.all(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
async decrypt(ciphertext: string, key: TypedArray) {
|
||||||
|
const adapter = new CryptBlobAdapter()
|
||||||
|
const data: EncryptedFileDTO[] = JSON.parse(ciphertext)
|
||||||
|
const files: FileDTO[] = await Promise.all(
|
||||||
|
data.map(async (file) => ({
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
contents: await adapter.decrypt(file.contents, key),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Adapters = {
|
||||||
|
Text: new CryptTextAdapter(),
|
||||||
|
Blob: new CryptBlobAdapter(),
|
||||||
|
Files: new CryptFilesAdapter(),
|
||||||
|
}
|
104
packages/shared/src/api.ts
Normal file
104
packages/shared/src/api.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
export type NoteMeta = { type: 'text' | 'file' }
|
||||||
|
|
||||||
|
export type Note = {
|
||||||
|
contents: string
|
||||||
|
meta: NoteMeta
|
||||||
|
views?: number
|
||||||
|
expiration?: number
|
||||||
|
}
|
||||||
|
export type NoteInfo = {}
|
||||||
|
export type NotePublic = Pick<Note, 'contents' | 'meta'>
|
||||||
|
export type NoteCreate = Omit<Note, 'meta'> & { meta: string }
|
||||||
|
|
||||||
|
export type FileDTO = Pick<File, 'name' | 'size' | 'type'> & {
|
||||||
|
contents: Blob
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EncryptedFileDTO = Omit<FileDTO, 'contents'> & {
|
||||||
|
contents: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallOptions = {
|
||||||
|
url: string
|
||||||
|
method: string
|
||||||
|
body?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PayloadToLargeError extends Error {}
|
||||||
|
|
||||||
|
export let BASE = ''
|
||||||
|
|
||||||
|
export function setBase(url: string) {
|
||||||
|
BASE = url
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function call(options: CallOptions) {
|
||||||
|
const response = await fetch(BASE + '/api/' + options.url, {
|
||||||
|
method: options.method,
|
||||||
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
||||||
|
mode: 'cors',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 413) throw new PayloadToLargeError()
|
||||||
|
else throw new Error('API call failed')
|
||||||
|
}
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function create(note: Note) {
|
||||||
|
const { meta, ...rest } = note
|
||||||
|
const body: NoteCreate = {
|
||||||
|
...rest,
|
||||||
|
meta: JSON.stringify(meta),
|
||||||
|
}
|
||||||
|
const data = await call({
|
||||||
|
url: 'notes/',
|
||||||
|
method: 'post',
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
return data as { id: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function get(id: string): Promise<NotePublic> {
|
||||||
|
const data = await call({
|
||||||
|
url: `notes/${id}`,
|
||||||
|
method: 'delete',
|
||||||
|
})
|
||||||
|
const { contents, meta } = data
|
||||||
|
return {
|
||||||
|
contents,
|
||||||
|
meta: JSON.parse(meta) as NoteMeta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function info(id: string): Promise<NoteInfo> {
|
||||||
|
const data = await call({
|
||||||
|
url: `notes/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Status = {
|
||||||
|
version: string
|
||||||
|
max_size: number
|
||||||
|
max_views: number
|
||||||
|
max_expiration: number
|
||||||
|
allow_advanced: boolean
|
||||||
|
theme_image: string
|
||||||
|
theme_text: string
|
||||||
|
theme_favicon: string
|
||||||
|
theme_page_title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function status() {
|
||||||
|
const data = await call({
|
||||||
|
url: 'status/',
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
return data as Status
|
||||||
|
}
|
2
packages/shared/src/index.ts
Normal file
2
packages/shared/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './adapters.js'
|
||||||
|
export * from './api.js'
|
103
packages/shared/tsconfig.json
Normal file
103
packages/shared/tsconfig.json
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
"incremental": true /* Save .tsbuildinfo files to allow for incremental compilation of projects. */,
|
||||||
|
"composite": true /* Enable constraints that allow a TypeScript project to be used with project references. */,
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "es2022" /* Specify what module code is generated. */,
|
||||||
|
"rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||||
|
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,6 @@ const config: PlaywrightTestConfig = {
|
|||||||
outputDir: './test-results',
|
outputDir: './test-results',
|
||||||
testDir: './test',
|
testDir: './test',
|
||||||
timeout: 60_000,
|
timeout: 60_000,
|
||||||
testIgnore: ['file/too-big.spec.ts'],
|
|
||||||
|
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'docker compose -f docker-compose.dev.yaml up',
|
command: 'docker compose -f docker-compose.dev.yaml up',
|
||||||
@ -25,7 +24,6 @@ const config: PlaywrightTestConfig = {
|
|||||||
{
|
{
|
||||||
name: 'local',
|
name: 'local',
|
||||||
use: { ...devices['Desktop Chrome'] },
|
use: { ...devices['Desktop Chrome'] },
|
||||||
// testMatch: 'file/too-big.spec.ts',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
2233
pnpm-lock.yaml
generated
2233
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@ import { test } from '@playwright/test'
|
|||||||
import { createNote } from '../utils'
|
import { createNote } from '../utils'
|
||||||
import Files from './files'
|
import Files from './files'
|
||||||
|
|
||||||
test('to big zip', async ({ page }) => {
|
test.skip('to big zip', async ({ page }) => {
|
||||||
const files = [Files.Zip]
|
const files = [Files.Zip]
|
||||||
const link = await createNote(page, { files, error: 'note is to big' })
|
const link = await createNote(page, { files, error: 'note is to big' })
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user