Add client ALTCHA proof-of-work and Vite env flags for uploads.
Expose enabled/max-number settings and wire altcha-lib for per-upload challenges.
This commit is contained in:
+3
-1
@@ -20,7 +20,7 @@ CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
|||||||
|
|
||||||
ALTCHA_ENABLED=true
|
ALTCHA_ENABLED=true
|
||||||
ALTCHA_HMAC_KEY=change-me-use-long-random-secret
|
ALTCHA_HMAC_KEY=change-me-use-long-random-secret
|
||||||
ALTCHA_MAX_NUMBER=50000
|
ALTCHA_MAX_NUMBER=250000
|
||||||
ALTCHA_CHALLENGE_TTL_MS=600000
|
ALTCHA_CHALLENGE_TTL_MS=600000
|
||||||
|
|
||||||
THROTTLE_TTL_MS=60000
|
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_API_BASE_URL=http://localhost:3000/api
|
||||||
VITE_CLEARNET_DOMAIN=localhost:5173
|
VITE_CLEARNET_DOMAIN=localhost:5173
|
||||||
VITE_ONION_HOSTNAME=
|
VITE_ONION_HOSTNAME=
|
||||||
|
VITE_ALTCHA_ENABLED=true
|
||||||
|
VITE_ALTCHA_MAX_NUMBER=250000
|
||||||
VITE_UPLOAD_MAX_QUEUE_SIZE=10
|
VITE_UPLOAD_MAX_QUEUE_SIZE=10
|
||||||
VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX=1000000
|
VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX=1000000
|
||||||
VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES=10485760 # 10MiB
|
VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES=10485760 # 10MiB
|
||||||
|
|||||||
Generated
+70
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.3.3",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"@uswriting/exiftool": "1.0.9",
|
"@uswriting/exiftool": "1.0.9",
|
||||||
|
"altcha-lib": "^2.7.0",
|
||||||
"axios": "^1.20.0",
|
"axios": "^1.20.0",
|
||||||
"dayjs": "^1.11.23",
|
"dayjs": "^1.11.23",
|
||||||
"hash-wasm": "^4.12.0",
|
"hash-wasm": "^4.12.0",
|
||||||
@@ -2071,6 +2072,75 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/assertion-error": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.3.3",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"@uswriting/exiftool": "1.0.9",
|
"@uswriting/exiftool": "1.0.9",
|
||||||
|
"altcha-lib": "^2.7.0",
|
||||||
"axios": "^1.20.0",
|
"axios": "^1.20.0",
|
||||||
"dayjs": "^1.11.23",
|
"dayjs": "^1.11.23",
|
||||||
"hash-wasm": "^4.12.0",
|
"hash-wasm": "^4.12.0",
|
||||||
|
|||||||
@@ -10,10 +10,16 @@ const env = (key: string): string => {
|
|||||||
|
|
||||||
const envInt = (key: string): number => parseInt(env(key), 10);
|
const envInt = (key: string): number => parseInt(env(key), 10);
|
||||||
|
|
||||||
|
const isEnabled = (key: string): boolean => env(key) === 'true';
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
baseUrl: env('VITE_API_BASE_URL'),
|
baseUrl: env('VITE_API_BASE_URL'),
|
||||||
clearnetDomain: env('VITE_CLEARNET_DOMAIN'),
|
clearnetDomain: env('VITE_CLEARNET_DOMAIN'),
|
||||||
onionHostname: env('VITE_ONION_HOSTNAME'),
|
onionHostname: env('VITE_ONION_HOSTNAME'),
|
||||||
|
altcha: {
|
||||||
|
enabled: isEnabled('VITE_ALTCHA_ENABLED'),
|
||||||
|
maxNumber: envInt('VITE_ALTCHA_MAX_NUMBER')
|
||||||
|
},
|
||||||
validation: {
|
validation: {
|
||||||
upload: {
|
upload: {
|
||||||
maxQueueSize: envInt('VITE_UPLOAD_MAX_QUEUE_SIZE'),
|
maxQueueSize: envInt('VITE_UPLOAD_MAX_QUEUE_SIZE'),
|
||||||
|
|||||||
@@ -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
|
||||||
|
};
|
||||||
|
});
|
||||||
Vendored
+2
@@ -4,6 +4,8 @@ interface ImportMetaEnv {
|
|||||||
readonly VITE_API_BASE_URL: string;
|
readonly VITE_API_BASE_URL: string;
|
||||||
readonly VITE_CLEARNET_DOMAIN: string;
|
readonly VITE_CLEARNET_DOMAIN: string;
|
||||||
readonly VITE_ONION_HOSTNAME: 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_UPLOAD_MAX_QUEUE_SIZE: string;
|
||||||
readonly VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX: string;
|
readonly VITE_VALIDATION_UPLOAD_MAX_VIEWS_MAX: string;
|
||||||
readonly VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES: string;
|
readonly VITE_VALIDATION_UPLOAD_MAX_IMAGE_BYTES: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user