import { setTitle } from '../../functions/Route' import HeaderMessage from '../layout/HeaderMessage' import InputButton from '../inputs/Button' import InputButtonRow from '../inputs/ButtonRow' import InputLabel from '../inputs/Label' import InputDescription from '../inputs/Description' import './styles/Settings.css' export default function ViewSettings() { const GIFUU_PREFIX = '_|GIFUU_BACKUP:DO_NOT_SHARE|_' async function onDataBackup() { const password = prompt('Please enter a password for this backup (Leave empty for none):') if (password === null) return // Generate Key const enc = new TextEncoder() const keyMaterial = await crypto.subtle.importKey('raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']) const salt = crypto.getRandomValues(new Uint8Array(16)) const key = await crypto.subtle.deriveKey( { name: 'PBKDF2', salt, iterations: 100_000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt'], ) // Encrypt String const data = JSON.stringify(localStorage) const nonce = crypto.getRandomValues(new Uint8Array(12)) const cipher = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: nonce }, key, enc.encode(data)) // Package Contents const bundle = new Uint8Array(salt.byteLength + nonce.byteLength + cipher.byteLength) bundle.set(salt, 0) bundle.set(nonce, 16) bundle.set(new Uint8Array(cipher), 28) const string = GIFUU_PREFIX + btoa(String.fromCharCode(...bundle)) navigator.clipboard.writeText(string) alert('Data has been copied to clipboard.') } async function onDataRestore() { // Retrieve User Data const blob = await navigator.clipboard.readText() if (!blob.startsWith(GIFUU_PREFIX)) { alert('Contents stored in clipboard is not a backup.') return } const password = prompt('Please enter the password for this backup:') if (password === null) return // Unpackage Contents const bundle = Uint8Array.from(atob(blob.slice(GIFUU_PREFIX.length)), (c) => c.charCodeAt(0)) const salt = bundle.slice(0, 16) const iv = bundle.slice(16, 28) const cipher = bundle.slice(28) // Generate Key const enc = new TextEncoder() const keyMaterial = await crypto.subtle.importKey('raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']) const key = await crypto.subtle.deriveKey( { name: 'PBKDF2', salt, iterations: 100_000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['decrypt'], ) // Decrypt String let raw: string try { const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, cipher) raw = new TextDecoder().decode(plain) } catch (err) { console.error('Backup Error:', err) alert('Incorrect Password') return } // Parse String try { const data = JSON.parse(raw) if (typeof data !== 'object' || Array.isArray(data)) { throw 'Invalid Object' } Object.entries(data).forEach(([k, v]) => { if (typeof k === 'string' && typeof v === 'string') { localStorage.setItem(k, v) } }) } catch (err) { console.error('Backup Error:', err) alert('Invalid data found in backup.') } alert('Data restored, the webpage will now refresh.') window.location.reload() } setTitle('Settings') return ( <>
Export your local preferences and tokens for transfer or backup purposes to your devices clipboard. Do not share these with anybody!
) }