Implement wisp: async encrypted single-retrieval file drop
All checks were successful
Docker / build-and-push (push) Successful in 3m33s
All checks were successful
Docker / build-and-push (push) Successful in 3m33s
Client-side libsodium encryption with the key in the URL fragment, an Express/SQLite server holding ciphertext until a confirm-token round trip proves successful decrypt (avoiding the delete-on-first-byte race), TTL sweep for unclaimed drops, and a password-gated upload UI styled to match flit. Dockerized to match the project family's conventions, with a named volume so the DB/blobs survive redeploys, and a Gitea Actions workflow to build and push the image.
This commit is contained in:
44
client/src/api.ts
Normal file
44
client/src/api.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
export async function checkPassword(password: string): Promise<boolean> {
|
||||
const res = await fetch('/api/auth/check', {
|
||||
method: 'POST',
|
||||
headers: { 'X-Upload-Password': password },
|
||||
})
|
||||
const body = (await res.json()) as { ok: boolean }
|
||||
return body.ok
|
||||
}
|
||||
|
||||
export async function uploadDrop(
|
||||
ciphertext: Uint8Array,
|
||||
password: string,
|
||||
): Promise<{ id: string; ttl: number }> {
|
||||
const res = await fetch('/api/drops', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'X-Upload-Password': password,
|
||||
},
|
||||
body: ciphertext.slice().buffer,
|
||||
})
|
||||
if (!res.ok) {
|
||||
const body = (await res.json().catch(() => ({}))) as { error?: string }
|
||||
throw new Error(body.error ?? `upload failed: ${res.status}`)
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function downloadDrop(
|
||||
id: string,
|
||||
): Promise<{ ciphertext: Uint8Array; confirmToken: string }> {
|
||||
const res = await fetch(`/api/drops/${id}`)
|
||||
if (!res.ok) throw new Error(`download failed: ${res.status}`)
|
||||
const confirmToken = res.headers.get('X-Confirm-Token') ?? ''
|
||||
const ciphertext = new Uint8Array(await res.arrayBuffer())
|
||||
return { ciphertext, confirmToken }
|
||||
}
|
||||
|
||||
export async function confirmDrop(id: string, confirmToken: string): Promise<void> {
|
||||
await fetch(`/api/drops/${id}/confirm`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${confirmToken}` },
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user