|
| 1 | +<script lang="ts"> |
| 2 | + import { OctagonAlert } from '@lucide/svelte'; |
| 3 | + import { goto } from '$app/navigation'; |
| 4 | + import { apiTokensApi } from '$lib/api'; |
| 5 | + import Turnstile from '$lib/components/Turnstile.svelte'; |
| 6 | + import { Button } from '$lib/components/ui/button'; |
| 7 | + import * as Card from '$lib/components/ui/card'; |
| 8 | + import { Checkbox } from '$lib/components/ui/checkbox'; |
| 9 | + import { Label } from '$lib/components/ui/label'; |
| 10 | + import ScrollArea from '$lib/components/ui/scroll-area/scroll-area.svelte'; |
| 11 | + import { handleApiError } from '$lib/errorhandling/apiErrorHandling'; |
| 12 | + import { toast } from 'svelte-sonner'; |
| 13 | +
|
| 14 | + function isValid(str: string): boolean { |
| 15 | + return /^[0-9a-zA-Z]{32,64}$/i.test(str); |
| 16 | + } |
| 17 | +
|
| 18 | + let secrets = $state<string[]>([]); |
| 19 | + let turnstileResponse = $state<string | null>(null); |
| 20 | + let acknowledgement = $state(false); |
| 21 | + let isAllValid = $derived(secrets.every(isValid)); |
| 22 | + let canSubmit = $derived( |
| 23 | + secrets.length > 0 && isAllValid && turnstileResponse !== null && acknowledgement |
| 24 | + ); |
| 25 | +
|
| 26 | + async function handleSubmit() { |
| 27 | + if (!canSubmit || !turnstileResponse) return; |
| 28 | +
|
| 29 | + try { |
| 30 | + await apiTokensApi.tokensReportTokens({ turnstileResponse, secrets }); |
| 31 | + goto('/login'); |
| 32 | + } catch (err) { |
| 33 | + handleApiError(err); |
| 34 | + } |
| 35 | + } |
| 36 | +
|
| 37 | + async function pasteFromClipboard() { |
| 38 | + try { |
| 39 | + const text = await navigator.clipboard.readText(); |
| 40 | + secrets = text |
| 41 | + .split(/\s|,/) |
| 42 | + .map((s) => s.trim()) |
| 43 | + .filter((s) => s.length > 0); |
| 44 | + } catch (err) { |
| 45 | + toast.error(`Failed to read clipboard: ${err}`); |
| 46 | + } |
| 47 | + } |
| 48 | +</script> |
| 49 | + |
| 50 | +<div class="max-w-3xl mx-auto my-10 space-y-6 px-4"> |
| 51 | + <Card.Header> |
| 52 | + <Card.Title class="text-3xl font-semibold flex justify-between items-center"> |
| 53 | + Report Leaked API Tokens |
| 54 | + <Button onclick={pasteFromClipboard} size="sm" variant="outline">Paste from clipboard</Button> |
| 55 | + </Card.Title> |
| 56 | + </Card.Header> |
| 57 | + |
| 58 | + <Card.Content class="space-y-5"> |
| 59 | + <!-- Warning Message --> |
| 60 | + <div class="flex items-start gap-3 p-4 border-l-4 border-red-500 bg-red-50 rounded-md"> |
| 61 | + <OctagonAlert class="text-red-600" /> |
| 62 | + <p class="text-sm text-red-800 leading-snug"> |
| 63 | + <strong>This form is only for reporting accidentally leaked API tokens.</strong><br /> |
| 64 | + <u>Intentional abuse will result in bans or severe endpoint restrictions.</u> |
| 65 | + </p> |
| 66 | + </div> |
| 67 | + |
| 68 | + <!-- Token Preview --> |
| 69 | + <span class="block text-sm font-medium text-gray-700 mb-2">Detected Tokens</span> |
| 70 | + <ScrollArea class="h-48 border rounded-md p-3 bg-gray-50"> |
| 71 | + {#each secrets as secret} |
| 72 | + <p |
| 73 | + class="text-sm font-mono break-all px-2 py-1 mb-1 rounded |
| 74 | + {isValid(secret) ? 'bg-green-200 text-gray-800' : 'bg-red-200 text-red-700'}" |
| 75 | + > |
| 76 | + {secret} |
| 77 | + </p> |
| 78 | + {/each} |
| 79 | + </ScrollArea> |
| 80 | + {#if !isAllValid} |
| 81 | + <div |
| 82 | + class="mt-2 flex items-start gap-2 text-sm text-red-700 bg-red-50 border border-red-200 p-3 rounded-md" |
| 83 | + > |
| 84 | + <span> One or more tokens appear to be invalid. Please check for formatting issues. </span> |
| 85 | + </div> |
| 86 | + {/if} |
| 87 | + |
| 88 | + <!-- Turnstile + Acknowledgement --> |
| 89 | + <div class="space-y-3"> |
| 90 | + <Turnstile action="report-token" bind:response={turnstileResponse} /> |
| 91 | + <div class="flex items-center space-x-2"> |
| 92 | + <Checkbox |
| 93 | + id="acknowledgement" |
| 94 | + bind:checked={acknowledgement} |
| 95 | + aria-labelledby="acknowledgement-label" |
| 96 | + /> |
| 97 | + <Label |
| 98 | + id="acknowledgement-label" |
| 99 | + for="acknowledgement" |
| 100 | + class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" |
| 101 | + > |
| 102 | + I confirm I understand what this feature is for and accept responsibility. |
| 103 | + </Label> |
| 104 | + </div> |
| 105 | + </div> |
| 106 | + |
| 107 | + <!-- Submit --> |
| 108 | + <Button onclick={handleSubmit} disabled={!canSubmit} class="w-full">Submit Report</Button> |
| 109 | + </Card.Content> |
| 110 | +</div> |
0 commit comments