Add altcha captcha to rahvatarkus, adjust some wordings in rahvatarkus
This commit is contained in:
parent
4363f56c23
commit
c6ec335b04
11 changed files with 207 additions and 20 deletions
|
@ -5,6 +5,9 @@ CLIENT_SECRET=<spotifyAPISecret>
|
|||
# Session token salt
|
||||
SESH_SECRET=<longRandomString>
|
||||
|
||||
# Secret used to generate ALTCHA captchas
|
||||
ALTCHA_HMAC=<longRandomString>
|
||||
|
||||
# SQLite DB location
|
||||
DATABASE_URL=local.db
|
||||
|
||||
|
|
|
@ -59,6 +59,8 @@
|
|||
"@fontsource-variable/smooch-sans": "^5.1.1",
|
||||
"@upstash/ratelimit": "^2.0.5",
|
||||
"@upstash/redis": "^1.34.4",
|
||||
"altcha": "^1.1.1",
|
||||
"altcha-lib": "^1.2.0",
|
||||
"better-sqlite3": "^11.8.0",
|
||||
"drizzle-orm": "^0.38.4",
|
||||
"nanoid": "^5.0.9",
|
||||
|
|
33
pnpm-lock.yaml
generated
33
pnpm-lock.yaml
generated
|
@ -20,6 +20,12 @@ importers:
|
|||
'@upstash/redis':
|
||||
specifier: ^1.34.4
|
||||
version: 1.34.4
|
||||
altcha:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
altcha-lib:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
better-sqlite3:
|
||||
specifier: ^11.8.0
|
||||
version: 11.8.1
|
||||
|
@ -157,6 +163,9 @@ packages:
|
|||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@altcha/crypto@0.0.1':
|
||||
resolution: {integrity: sha512-qZMdnoD3lAyvfSUMNtC2adRi666Pxdcw9zqfMU5qBOaJWqpN9K+eqQGWqeiKDMqL0SF+EytNG4kR/Pr/99GJ6g==}
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
@ -959,6 +968,11 @@ packages:
|
|||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.18.0':
|
||||
resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.31.0':
|
||||
resolution: {integrity: sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==}
|
||||
cpu: [x64]
|
||||
|
@ -1172,6 +1186,12 @@ packages:
|
|||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
|
||||
altcha-lib@1.2.0:
|
||||
resolution: {integrity: sha512-S5WF8QLNRaM1hvK24XPhOLfu9is2EBCvH7+nv50sM5CaIdUCqQCd0WV/qm/ZZFGTdSoKLuDp+IapZxBLvC+SNg==}
|
||||
|
||||
altcha@1.1.1:
|
||||
resolution: {integrity: sha512-BPqLHiCAcVuF+dwshPyVBtpAXvgcSOO5DA3KfMLfQO3I5gxytE2bUrclYK5E8vWAzzrkiiz6OhFrZ69nUAOOzg==}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -2748,6 +2768,8 @@ snapshots:
|
|||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@altcha/crypto@0.0.1': {}
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.8
|
||||
|
@ -3292,6 +3314,9 @@ snapshots:
|
|||
'@rollup/rollup-linux-s390x-gnu@4.31.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.18.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.31.0':
|
||||
optional: true
|
||||
|
||||
|
@ -3554,6 +3579,14 @@ snapshots:
|
|||
json-schema-traverse: 0.4.1
|
||||
uri-js: 4.4.1
|
||||
|
||||
altcha-lib@1.2.0: {}
|
||||
|
||||
altcha@1.1.1:
|
||||
dependencies:
|
||||
'@altcha/crypto': 0.0.1
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-linux-x64-gnu': 4.18.0
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-regex@6.1.0: {}
|
||||
|
|
19
src/app.css
19
src/app.css
|
@ -32,6 +32,16 @@
|
|||
--sidebar-accent-foreground: 240 5.9% 10%;
|
||||
--sidebar-border: 220 13% 91%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
|
||||
--altcha-border-width: 2px;
|
||||
--altcha-border-radius: var(--radius);
|
||||
--altcha-color-base: #ffffff;
|
||||
--altcha-color-border: #f5f5f4;
|
||||
--altcha-color-text: #0c0a09;
|
||||
--altcha-color-border-focus: #78716c;
|
||||
--altcha-color-error-text: #f23939;
|
||||
--altcha-color-footer-bg: #f5f5f4;
|
||||
--altcha-max-width: 260px;
|
||||
}
|
||||
|
||||
.dark {
|
||||
|
@ -51,7 +61,7 @@
|
|||
--secondary-foreground: 60 9.1% 94%;
|
||||
--accent: 12 6.5% 15.1%;
|
||||
--accent-foreground: 60 9.1% 94%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive: 0 70% 63.9%;
|
||||
--destructive-foreground: 60 9.1% 94%;
|
||||
--ring: 24 5.7% 82.9%;
|
||||
--sidebar-background: 240 5.9% 10%;
|
||||
|
@ -62,6 +72,13 @@
|
|||
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
||||
--sidebar-border: 240 3.7% 15.9%;
|
||||
--sidebar-ring: 217.2 91.2% 59.8%;
|
||||
|
||||
--altcha-color-base: #0c0a09;
|
||||
--altcha-color-text: #f1f1ee;
|
||||
--altcha-color-border: #292524;
|
||||
--altcha-color-border-focus: #a8a29e;
|
||||
--altcha-color-error-text: #f23939;
|
||||
--altcha-color-footer-bg: #292524;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
41
src/lib/components/Altcha.svelte
Normal file
41
src/lib/components/Altcha.svelte
Normal file
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Input from './ui/input/input.svelte';
|
||||
// Importing altcha package will introduce a new element <altcha-widget>
|
||||
|
||||
onMount(async () => {
|
||||
await import('altcha');
|
||||
});
|
||||
|
||||
let { value = $bindable(), ...props } = $props();
|
||||
|
||||
const estonianStrings = {
|
||||
ariaLinkLabel: 'Külasta Altcha.org',
|
||||
error: 'Kinnitus nurjus. Proovi hiljem uuesti.',
|
||||
expired: 'Kinnitus aegus. Proovi uuesti.',
|
||||
footer:
|
||||
'Turvab <a href="https://altcha.org/" target="_blank" aria-label="Külasta Altcha.org">ALTCHA</a>',
|
||||
label: 'Ma ei ole robot',
|
||||
verified: 'Kõik ok!',
|
||||
verifying: 'Kinntan...',
|
||||
waitAlert: 'Kinnitan... palun oota.'
|
||||
};
|
||||
</script>
|
||||
|
||||
<Input type="hidden" bind:value {...props} />
|
||||
|
||||
<!-- Configure your `challengeurl` and remove the `test` attribute, see docs: https://altcha.org/docs/website-integration/#using-altcha-widget -->
|
||||
<altcha-widget
|
||||
strings={JSON.stringify(estonianStrings)}
|
||||
debug
|
||||
challengeurl="/api/altcha"
|
||||
spamfilter
|
||||
blockspam
|
||||
hidefooter
|
||||
expire={180000}
|
||||
onverified={(ev) => {
|
||||
if (ev.detail.payload) {
|
||||
value = ev.detail.payload;
|
||||
}
|
||||
}}
|
||||
></altcha-widget>
|
22
src/routes/api/altcha/+server.ts
Normal file
22
src/routes/api/altcha/+server.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { ALTCHA_HMAC } from '$env/static/private';
|
||||
import { json } from '@sveltejs/kit';
|
||||
|
||||
import { createChallenge, verifySolution } from 'altcha-lib';
|
||||
|
||||
export async function GET() {
|
||||
const challenge = await createChallenge({
|
||||
hmacKey: ALTCHA_HMAC,
|
||||
maxNumber: 100000 // the maximum random number
|
||||
});
|
||||
console.log('challange get');
|
||||
|
||||
return json(challenge);
|
||||
}
|
||||
|
||||
export async function POST({ request }) {
|
||||
const { payload }: { payload: string } = await request.json();
|
||||
console.log('challange done');
|
||||
const ok = await verifySolution(payload, ALTCHA_HMAC);
|
||||
|
||||
return json({ ok });
|
||||
}
|
|
@ -87,6 +87,29 @@ export const actions: Actions = {
|
|||
});
|
||||
}
|
||||
|
||||
const altchaValid = await event
|
||||
.fetch('/api/altcha', { method: 'POST', body: JSON.stringify({ payload: form.data.altcha }) })
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
return data;
|
||||
});
|
||||
|
||||
if (!altchaValid) {
|
||||
const message =
|
||||
'Altchale ei meeldinud see. Sa oled kas liiga boti laadse käitumisega või minu implementatsioon on kohutav.';
|
||||
|
||||
if (form.errors.answer) {
|
||||
form.errors.answer.push(message);
|
||||
} else {
|
||||
form.errors.answer = [message];
|
||||
}
|
||||
return fail(429, {
|
||||
form
|
||||
});
|
||||
}
|
||||
|
||||
const response = await event
|
||||
.fetch('/api/rahvatarkus/answer', {
|
||||
method: 'POST',
|
||||
|
@ -180,6 +203,29 @@ export const actions: Actions = {
|
|||
});
|
||||
}
|
||||
|
||||
const altchaValid = await event
|
||||
.fetch('/api/altcha', { method: 'POST', body: JSON.stringify({ payload: form.data.altcha }) })
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
return data;
|
||||
});
|
||||
|
||||
if (!altchaValid) {
|
||||
const message =
|
||||
'Altchale ei meeldinud see. Sa oled kas liiga boti laadse käitumisega või minu implementatsioon on kohutav.';
|
||||
|
||||
if (form.errors.question) {
|
||||
form.errors.question.push(message);
|
||||
} else {
|
||||
form.errors.question = [message];
|
||||
}
|
||||
return fail(429, {
|
||||
form
|
||||
});
|
||||
}
|
||||
|
||||
const response = await event
|
||||
.fetch('/api/rahvatarkus/question', {
|
||||
method: 'POST',
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import * as Form from '$lib/components/ui/form/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Textarea } from '$lib/components/ui/textarea/index.js';
|
||||
import Altcha from '$lib/components/Altcha.svelte';
|
||||
|
||||
let {
|
||||
data
|
||||
|
@ -22,9 +23,9 @@
|
|||
invalidateAll: 'force',
|
||||
onUpdated: ({ form: f }) => {
|
||||
if (f.valid) {
|
||||
toast.success(`You submitted ${JSON.stringify(f.data, null, 2)}`);
|
||||
toast.success('Vastus saadetud.');
|
||||
} else {
|
||||
toast.error('Please fix the errors in the form.');
|
||||
toast.error('Vastamine nurjus, palun paranda vead.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -56,11 +57,20 @@
|
|||
<Form.Field {form} name="answer">
|
||||
<Form.Control>
|
||||
{#snippet children({ props })}
|
||||
<Form.Label>{data.question.content}?</Form.Label>
|
||||
<Textarea {...props} bind:value={$formData.answer} class="resize-none" />
|
||||
<Form.Label class="transition-colors">{data.question.content}?</Form.Label>
|
||||
<Textarea
|
||||
{...props}
|
||||
bind:value={$formData.answer}
|
||||
class="resize-none transition-colors"
|
||||
/>
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
<div class="flex justify-between">
|
||||
<Form.FieldErrors />
|
||||
<Form.Description>
|
||||
{$formData.answer.length}/{$constraints.answer?.maxlength}
|
||||
</Form.Description>
|
||||
</div>
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="questionId">
|
||||
<Form.Control>
|
||||
|
@ -68,10 +78,13 @@
|
|||
<Input type="hidden" {...props} bind:value={$formData.questionId} />
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
<Form.Description class="text-right">
|
||||
{$formData.answer.length}/{$constraints.answer?.maxlength}
|
||||
</Form.Description>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="altcha" class="mx-auto mt-3 w-[var(--altcha-max-width)]">
|
||||
<Form.Control>
|
||||
{#snippet children({ props })}
|
||||
<Altcha {...props} bind:value={$formData.altcha} />
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
</Card.Content>
|
||||
<Card.Footer class="justify-center">
|
||||
|
|
|
@ -5,7 +5,8 @@ export const formSchema = z.object({
|
|||
answer: z
|
||||
.string()
|
||||
.min(2, 'Vastus peab olema vähemalt 2 tähemärki.')
|
||||
.max(150, 'Vastus ei või olla pikem kui 150 tähemärki.')
|
||||
.max(150, 'Vastus ei või olla pikem kui 150 tähemärki.'),
|
||||
altcha: z.string()
|
||||
});
|
||||
|
||||
export type FormSchema = typeof formSchema;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import * as Form from '$lib/components/ui/form/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import Altcha from '$lib/components/Altcha.svelte';
|
||||
|
||||
let {
|
||||
data
|
||||
|
@ -29,9 +30,9 @@
|
|||
resetForm: true,
|
||||
onUpdated: ({ form: f }) => {
|
||||
if (f.valid) {
|
||||
toast.success(`You submitted ${JSON.stringify(f.data, null, 2)}`);
|
||||
toast.success('Küsimus esitatud.');
|
||||
} else {
|
||||
toast.error('Please fix the errors in the form.');
|
||||
toast.error('Küsimine nurjus, palun paranda vead.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -42,7 +43,9 @@
|
|||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Küsi rahvalt</Card.Title>
|
||||
<Card.Description>Iga vastus annab võimaluse küsida ühe küsimuse.</Card.Description>
|
||||
<Card.Description
|
||||
>Sul on alles {data.user.balance > 0 ? data.user.balance : data.poolSize > 0 ? 0 : 1} küsimust.</Card.Description
|
||||
>
|
||||
</Card.Header>
|
||||
{#if data.user.balance === 0 && (!data.question || data.poolSize > 0)}
|
||||
<Card.Content>
|
||||
|
@ -54,16 +57,21 @@
|
|||
<Form.Field {form} name="question">
|
||||
<Form.Control>
|
||||
{#snippet children({ props })}
|
||||
<Form.Label>Küsimus rahvale</Form.Label>
|
||||
<Input {...props} bind:value={$formData.question} />
|
||||
<Form.Label class="transition-colors">Küsimus rahvale</Form.Label>
|
||||
<Input {...props} bind:value={$formData.question} class="transition-colors" />
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
<Form.Description class="flex justify-between">
|
||||
<p>
|
||||
Sul on alles {data.user.balance > 0 ? data.user.balance : data.poolSize > 0 ? 0 : 1} küsimust.
|
||||
</p>
|
||||
<Form.FieldErrors />
|
||||
<p>{$formData.question.length}/{$constraints.question?.maxlength}</p>
|
||||
</Form.Description>
|
||||
</Form.Field>
|
||||
<Form.Field {form} name="altcha" class="mx-auto mt-3 w-[var(--altcha-max-width)]">
|
||||
<Form.Control>
|
||||
{#snippet children({ props })}
|
||||
<Altcha {...props} bind:value={$formData.altcha} />
|
||||
{/snippet}
|
||||
</Form.Control>
|
||||
<Form.FieldErrors />
|
||||
</Form.Field>
|
||||
</Card.Content>
|
||||
|
|
|
@ -4,7 +4,8 @@ export const formSchema = z.object({
|
|||
question: z
|
||||
.string()
|
||||
.min(2, 'Küsimus peab olema vähemalt 2 tähemärki.')
|
||||
.max(50, 'Küsimus ei või olla pikem kui 50 tähemärki.')
|
||||
.max(50, 'Küsimus ei või olla pikem kui 50 tähemärki.'),
|
||||
altcha: z.string()
|
||||
});
|
||||
|
||||
export type FormSchema = typeof formSchema;
|
||||
|
|
Loading…
Add table
Reference in a new issue