security hardening, team invites, granular locking, view counts, board subscriptions, scheduled changelog, mentions, recovery codes, accessibility and hover states

This commit is contained in:
2026-03-21 17:37:01 +02:00
parent f07eddf29e
commit 5ba25fb956
142 changed files with 30397 additions and 2287 deletions

View File

@@ -0,0 +1,34 @@
import { api } from './api'
interface Challenge {
algorithm: string
challenge: string
maxnumber: number
salt: string
signature: string
}
async function hashHex(algorithm: string, data: string): Promise<string> {
const alg = algorithm.replace('-', '').toUpperCase() === 'SHA256' ? 'SHA-256' : algorithm
const buf = await crypto.subtle.digest(alg, new TextEncoder().encode(data))
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('')
}
export async function solveAltcha(difficulty: 'normal' | 'light' = 'normal'): Promise<string> {
const ch = await api.get<Challenge>(`/altcha/challenge?difficulty=${difficulty}`)
for (let n = 0; n <= ch.maxnumber; n++) {
const hash = await hashHex(ch.algorithm, ch.salt + n)
if (hash === ch.challenge) {
return btoa(JSON.stringify({
algorithm: ch.algorithm,
challenge: ch.challenge,
number: n,
salt: ch.salt,
signature: ch.signature,
}))
}
}
throw new Error('Failed to solve challenge')
}