Slave/upload ui #8

Merged
nobswebdev merged 15 commits from slave/upload-ui into master 2026-10-25 22:43:57 +00:00
6 changed files with 152 additions and 1 deletions
Showing only changes of commit fe643d9831 - Show all commits
+3 -1
View File
@@ -20,7 +20,7 @@ CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
ALTCHA_ENABLED=true
ALTCHA_HMAC_KEY=change-me-use-long-random-secret
ALTCHA_MAX_NUMBER=50000
ALTCHA_MAX_NUMBER=250000
ALTCHA_CHALLENGE_TTL_MS=600000
THROTTLE_TTL_MS=60000
@@ -35,6 +35,8 @@ VALIDATION_UPLOAD_JSON_BODY_PADDING_BYTES=2097152 # 2MiB
VITE_API_BASE_URL=http://localhost:3000/api
VITE_CLEARNET_DOMAIN=localhost:5173
VITE_ONION_HOSTNAME=
VITE_ALTCHA_ENABLED=true
VITE_ALTCHA_MAX_NUMBER=250000
VITE_UPLOAD_MAX_QUEUE_SIZE=10
VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX=1000000
VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES=10485760 # 10MiB
+70
View File
@@ -11,6 +11,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.3.3",
"@uswriting/exiftool": "1.0.9",
"altcha-lib": "^2.7.0",
"axios": "^1.20.0",
"dayjs": "^1.11.23",
"hash-wasm": "^4.12.0",
@@ -2071,6 +2072,75 @@
"dev": true,
"license": "MIT"
},
"node_modules/altcha-lib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/altcha-lib/-/altcha-lib-2.7.0.tgz",
"integrity": "sha512-gnysjfIpeT6k3RLaWRd7xVvfs8U0a5DHIS95Xgwn8znKq5uIj6c/oTH8DtcdyJS/ZV3I5kYpyurvmJFb0eFWvA==",
"license": "MIT",
"bin": {
"altcha-lib": "bin/cli.mjs"
},
"peerDependencies": {
"@fastify/cookie": "^11.0.0",
"@fastify/cors": "^11.0.0",
"@fastify/formbody": "^8.0.0 || ^9.0.0",
"@nestjs/common": "^10.0.0 || ^11.0.0 || ^12.0.0",
"@nestjs/core": "^10.0.0 || ^11.0.0 || ^12.0.0",
"@nestjs/platform-express": "^10.0.0 || ^11.0.0 || ^12.0.0",
"@nestjs/platform-fastify": "^10.0.0 || ^11.0.0 || ^12.0.0",
"@sveltejs/kit": "^2.0.0 || ^3.0.0",
"cookie-parser": "^1.0.0",
"cors": "^2.0.0",
"express": "^4.0.0 || ^5.0.0",
"fastify": "^5.0.0",
"h3": "^2.0.0",
"hono": "^4.0.0"
},
"peerDependenciesMeta": {
"@fastify/cookie": {
"optional": true
},
"@fastify/cors": {
"optional": true
},
"@fastify/formbody": {
"optional": true
},
"@nestjs/common": {
"optional": true
},
"@nestjs/core": {
"optional": true
},
"@nestjs/platform-express": {
"optional": true
},
"@nestjs/platform-fastify": {
"optional": true
},
"@sveltejs/kit": {
"optional": true
},
"cookie-parser": {
"optional": true
},
"cors": {
"optional": true
},
"express": {
"optional": true
},
"fastify": {
"optional": true
},
"h3": {
"optional": true
},
"hono": {
"optional": true
}
}
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+1
View File
@@ -13,6 +13,7 @@
"dependencies": {
"@tailwindcss/vite": "^4.3.3",
"@uswriting/exiftool": "1.0.9",
"altcha-lib": "^2.7.0",
"axios": "^1.20.0",
"dayjs": "^1.11.23",
"hash-wasm": "^4.12.0",
+6
View File
@@ -10,10 +10,16 @@ const env = (key: string): string => {
const envInt = (key: string): number => parseInt(env(key), 10);
const isEnabled = (key: string): boolean => env(key) === 'true';
export const config = {
baseUrl: env('VITE_API_BASE_URL'),
clearnetDomain: env('VITE_CLEARNET_DOMAIN'),
onionHostname: env('VITE_ONION_HOSTNAME'),
altcha: {
enabled: isEnabled('VITE_ALTCHA_ENABLED'),
maxNumber: envInt('VITE_ALTCHA_MAX_NUMBER')
},
validation: {
upload: {
maxQueueSize: envInt('VITE_UPLOAD_MAX_QUEUE_SIZE'),
+70
View File
@@ -0,0 +1,70 @@
import { solveChallengeWorkers } from 'altcha-lib/v1';
import type { Challenge, Payload } from 'altcha-lib/v1/types';
import { defineStore } from 'pinia';
import { config } from '@/config';
import { api } from '@/plugins/axios';
import { bytesToBase64 } from '@/utils/encoding/bytesToBase64';
import AltchaSolveWorker from 'altcha-lib/v1/worker?worker';
export const useAltchaStore = defineStore('altcha', () => {
const runProofOfWork = async (): Promise<string | undefined> => {
if (!config.altcha.enabled) {
return undefined;
}
const challenge = await fetchChallenge();
return encodeSolvedChallenge(challenge);
};
const fetchChallenge = async (): Promise<Challenge> => {
const { data } = await api.get<Challenge>('/altcha/challenge');
return data;
};
const encodeSolvedChallenge = async ({
algorithm,
challenge: challengeValue,
salt,
signature,
maxnumber
}: Challenge): Promise<string> => {
const { maxNumber: fallbackMaxNumber } = config.altcha;
const maxNumber = maxnumber ?? fallbackMaxNumber;
const cuncurrency = Math.min(8, Math.max(1, navigator.hardwareConcurrency ?? 4));
const solution = await solveChallengeWorkers(
() => new AltchaSolveWorker(),
cuncurrency,
challengeValue,
salt,
algorithm,
maxNumber
);
if (!solution) {
throw new Error('ALTCHA proof of work could not be solved');
}
const payload: Payload = {
algorithm,
challenge: challengeValue,
number: solution.number,
salt,
signature
};
const json = JSON.stringify(payload);
const textEncoder = new TextEncoder();
const bytes = textEncoder.encode(json);
return bytesToBase64(bytes);
};
return {
runProofOfWork
};
});
+2
View File
@@ -4,6 +4,8 @@ interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string;
readonly VITE_CLEARNET_DOMAIN: string;
readonly VITE_ONION_HOSTNAME: string;
readonly VITE_ALTCHA_ENABLED: string;
readonly VITE_ALTCHA_MAX_NUMBER: string;
readonly VITE_UPLOAD_MAX_QUEUE_SIZE: string;
readonly VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX: string;
readonly VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES: string;