From 40677cedfabb71c5e610300d60a72fda2e61cb11 Mon Sep 17 00:00:00 2001 From: Bhark Date: Sat, 26 Oct 2024 16:10:38 +0200 Subject: [PATCH] friendship ended with no-storage, no-trust is new best friend --- README.md | 4 +- package-lock.json | 192 +++++++++++++++++- package.json | 3 + src/lib/server/supabase.js | 4 + src/routes/+error.svelte | 2 + src/routes/[[encrypted]]/+page.js | 17 -- src/routes/[[uuid]]/+page.server.js | 51 +++++ .../{[[encrypted]] => [[uuid]]}/+page.svelte | 160 +++++++++++++-- src/routes/api/+server.js | 42 ---- 9 files changed, 390 insertions(+), 85 deletions(-) create mode 100644 src/lib/server/supabase.js delete mode 100644 src/routes/[[encrypted]]/+page.js create mode 100644 src/routes/[[uuid]]/+page.server.js rename src/routes/{[[encrypted]] => [[uuid]]}/+page.svelte (50%) delete mode 100644 src/routes/api/+server.js diff --git a/README.md b/README.md index d591496..2c77788 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Lyra -Simple no-storage password sharing with expiring links. No fancy - everything is kept as minimal as possible, as we aim to add a layer of security to secret sharing done by clients who would not bother to use complete suites. +Simple zero-trust password sharing with expiring and self-destructing links. No fancy - everything is kept as minimal as possible, as we aim to add a layer of security to secret sharing done by clients who would not bother to use complete suites. -**NB**: The server has to be trusted, as it could log links and holds the secret. Please self-host this for internal use. +Encrypted values are stored by the server, while all encryption happens client-side. Secrets never leave the client machine, until they decide to share the link. [![Lyra](https://lyra.tetrabit.coop/banner.jpg)](https://lyra.tetrabit.coop) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d90468d..74aa0a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "lyra", "version": "0.0.1", "dependencies": { + "@supabase/supabase-js": "^2.45.6", + "base64url": "^3.0.1", + "buffer": "^6.0.3", "sass": "^1.80.4", "scss": "^0.2.4", "svelte-preprocess": "^6.0.3" @@ -969,6 +972,73 @@ "win32" ] }, + "node_modules/@supabase/auth-js": { + "version": "2.65.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.65.1.tgz", + "integrity": "sha512-IA7i2Xq2SWNCNMKxwmPlHafBQda0qtnFr8QnyyBr+KaSxoXXqEzFCnQ1dGTy6bsZjVBgXu++o3qrDypTspaAPw==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.3.tgz", + "integrity": "sha512-sOLXy+mWRyu4LLv1onYydq+10mNRQ4rzqQxNhbrKLTLTcdcmS9hbWif0bGz/NavmiQfPs4ZcmQJp4WqOXlR4AQ==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.16.3.tgz", + "integrity": "sha512-HI6dsbW68AKlOPofUjDTaosiDBCtW4XAm0D18pPwxoW3zKOE2Ru13Z69Wuys9fd6iTpfDViNco5sgrtnP0666A==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.10.7.tgz", + "integrity": "sha512-OLI0hiSAqQSqRpGMTUwoIWo51eUivSYlaNBgxsXZE7PSoWh12wPRdVt0psUMaUzEonSB85K21wGc7W5jHnT6uA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.14.2" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.45.6", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.45.6.tgz", + "integrity": "sha512-qVXSSUhhIqdFnF2VUGgeecPvw1cDW6+avcTbRgur4LaGnzrJCbM3Rx7g81/SSZjjeqYOtmHuKWhiHzV/EN8Ktw==", + "dependencies": { + "@supabase/auth-js": "2.65.1", + "@supabase/functions-js": "2.4.3", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.16.3", + "@supabase/realtime-js": "2.10.7", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@sveltejs/adapter-auto": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.3.1.tgz", @@ -1075,6 +1145,27 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, + "node_modules/@types/node": { + "version": "22.8.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", + "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.5.tgz", + "integrity": "sha512-xegpDuR+z0UqG9fwHqNoy3rI7JDlvaPh2TY47Fl80oq6g+hXT+c/LEuE43X48clZ6lOfANl5WrPur9fYO1RJ/w==" + }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vercel/nft": { "version": "0.27.5", "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.27.5.tgz", @@ -1204,6 +1295,33 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -1234,6 +1352,29 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-builder": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", @@ -1561,6 +1702,25 @@ "node": ">= 6" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/immutable": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", @@ -2711,8 +2871,7 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tslib": { "version": "2.8.0", @@ -2720,6 +2879,11 @@ "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", "dev": true }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2808,14 +2972,12 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -2836,6 +2998,26 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 6cade5c..ccccd3d 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,9 @@ "vite": "^5.0.3" }, "dependencies": { + "@supabase/supabase-js": "^2.45.6", + "base64url": "^3.0.1", + "buffer": "^6.0.3", "sass": "^1.80.4", "scss": "^0.2.4", "svelte-preprocess": "^6.0.3" diff --git a/src/lib/server/supabase.js b/src/lib/server/supabase.js new file mode 100644 index 0000000..48f8c54 --- /dev/null +++ b/src/lib/server/supabase.js @@ -0,0 +1,4 @@ +import { createClient } from '@supabase/supabase-js' +import { SUPABASE_URL, SUPABASE_KEY } from '$env/static/private' + +export const sb = createClient(SUPABASE_URL, SUPABASE_KEY) \ No newline at end of file diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte index 6279a9b..a06687b 100644 --- a/src/routes/+error.svelte +++ b/src/routes/+error.svelte @@ -28,6 +28,8 @@ align-items: center; flex-direction: column; gap: 10px; + padding: 20px; + text-align: center; a { text-decoration: none; diff --git a/src/routes/[[encrypted]]/+page.js b/src/routes/[[encrypted]]/+page.js deleted file mode 100644 index 6345f6a..0000000 --- a/src/routes/[[encrypted]]/+page.js +++ /dev/null @@ -1,17 +0,0 @@ -import { redirect } from '@sveltejs/kit' - -export const load = async ({ params, fetch }) => { - if (params.encrypted) { - const decrypted = await fetch('/api', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ action: 'decrypt', value: params.encrypted }) - }).then(r => r.json()).then(r => r.decrypted) - - if (!decrypted) redirect(301, '/') - - return { decrypted } - } -} \ No newline at end of file diff --git a/src/routes/[[uuid]]/+page.server.js b/src/routes/[[uuid]]/+page.server.js new file mode 100644 index 0000000..e4dfd71 --- /dev/null +++ b/src/routes/[[uuid]]/+page.server.js @@ -0,0 +1,51 @@ +import { error, json } from '@sveltejs/kit' +import { sb } from '$lib/server/supabase' + +export const load = async ({ params, fetch }) => { + if (params.uuid) { + + const deleteEncryptedPassword = async () => { + const { error } = await sb.from('encrypted_passwords').delete().eq('id', params.uuid) + if (error) error(500, error.message || 'Something went wrong when trying to delete the encrypted password') + } + + // get the encrypted password from the database + const { data, error: err } = await sb + .from('encrypted_passwords') + .select('ciphertext, created_at') + .eq('id', params.uuid) + .maybeSingle() + + if (err) error(500, err.message || 'Something went wrong when trying to retrieve the encrypted password') + if (!data) error(404, 'The encrypted password does not exist') + + console.log(data) + + const { ciphertext, created_at } = data + + await deleteEncryptedPassword() + + // check if it's older than 1 week + if (Date.now() - new Date(created_at).getTime() > 604800000) return { expired: true } + + return { ciphertext } + } +} + +export const actions = { + default: async ({ request }) => { + const data = await request.formData() + const { ciphertext } = Object.fromEntries(data) + + // insert the encrypted password into the database + const { data: res, error: err } = await sb + .from('encrypted_passwords') + .insert({ ciphertext }) + .select('id') + .single() + + if (err) error(500, err.message || 'Something went wrong when trying to save the encrypted password') + + return { id: res.id } + } +} \ No newline at end of file diff --git a/src/routes/[[encrypted]]/+page.svelte b/src/routes/[[uuid]]/+page.svelte similarity index 50% rename from src/routes/[[encrypted]]/+page.svelte rename to src/routes/[[uuid]]/+page.svelte index 831d0ce..ea35284 100644 --- a/src/routes/[[encrypted]]/+page.svelte +++ b/src/routes/[[uuid]]/+page.svelte @@ -4,21 +4,106 @@ import { fly } from 'svelte/transition' import { page } from '$app/stores' import { browser } from '$app/environment' + import { Buffer } from 'buffer' - let { decrypted } = $derived($page.data) - let value = $state(decrypted || '') - let link = $state(null) + // fixed values + const saltLength = 32 + const secretLength = 32 + const ivLength = 16 + + // reactive values + let link = $state('') + let generatingLink = $state(false) + let decrypted = $state('') + let inputValue = $state('') + + // derivatives + let { ciphertext } = $derived($page.data) let lang = $derived(browser ? navigator.language.split('-')[0] : 'en') + let product = $derived($page.url.searchParams.get('product')) + + const deriveKey = async (password, salt) => { + const encoder = new TextEncoder() + return await crypto.subtle.importKey('raw', encoder.encode(password), { name: 'PBKDF2' }, false, ['deriveKey']) + .then(key => crypto.subtle.deriveKey({ name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' }, key, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'])) + } - const go = async () => { - const { encrypted } = await fetch('/api', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ value, action: 'encrypt' }) - }).then(r => r.json()) + const encode = (uint8array) => { + return Buffer.from(uint8array).toString('base64') + } - link = encrypted - navigator.clipboard.writeText(`${$page.url.origin}/${encodeURIComponent(encrypted)}`) + const decode = (base64) => { + if (!base64) return + return Uint8Array.from(Buffer.from(base64, 'base64')) + } + + const encrypt = async (secret) => { + const encoder = new TextEncoder() + + const salt = crypto.getRandomValues(new Uint8Array(saltLength)) + const iv = crypto.getRandomValues(new Uint8Array(ivLength)) + const decryptionSecret = crypto.getRandomValues(new Uint8Array(secretLength)) + + const key = await deriveKey(decryptionSecret, salt) + const ciphertext = encode(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoder.encode(secret))) + + let product = new Uint8Array([...salt, ...iv, ...decryptionSecret]) + product = encode(product) + + return { + product, + ciphertext + } + } + + const decrypt = async (product) => { + + if (!product || product == '') return + + const decoder = new TextDecoder() + + product = decode(product) + const decodedCiphertext = decode(ciphertext) + + // extract salt, iv and decryption secret + const salt = product.slice(0, saltLength) + const iv = product.slice(saltLength, saltLength + ivLength) + const decryptionSecret = product.slice(saltLength + ivLength) + + const key = await deriveKey(decryptionSecret, salt) + + decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, decodedCiphertext) + .then(buffer => decoder.decode(buffer)) + } + + const generateLink = async (secret) => { + + try { + generatingLink = true + + // encrypt the secret, retrieve ciphertext and product + const { ciphertext, product } = await encrypt(secret) + + // store ciphertext on server + const formData = new FormData() + formData.append('ciphertext', ciphertext) + + const id = await fetch('/', { + method: 'POST', + body: formData + }).then(res => res.json()).then(data => JSON.parse(data.data)[1]) + + // set link + link = `${$page.url.origin}/${id}?product=${encodeURIComponent(product)}` + + // copy link to clipboard + navigator.clipboard.writeText(link) + + } catch (error) { + console.error(error) + } finally { + generatingLink = false + } } const texts = { @@ -27,18 +112,22 @@ copied: 'Kopieret', find: 'Find din adgangskode her', write: 'Skriv en adgangskode her', + get: 'Hent din adgangskode her', + getPass: 'Hent adgangskode', share: 'Sikker deling af adgangskoder', - infoDecrypted: 'Lyra opbevarer aldrig delte adgangskoder. Ingen andre end de personer der har linket, kan tilgå adgangskoden - selv ikke Tetrabit. Linket virker i én uge. Du finder adgangskoden du har modtaget under "Find din adgangskode her".', - info: 'Lyra opbevarer aldrig delte adgangskoder. Ingen andre end de personer der har linket, kan tilgå adgangskoden - selv ikke Tetrabit. Linket virker i én uge. Skriv en adgangskode du vil dele, og tryk "fortsæt" for at få et sikkert link.' + infoDecrypted: 'Din adgangskode forlader aldrig din enhed. Alt kryptering foregår offline, og Lyra opbevarer kun sikkert krypterede værdier. Adgangskoder slettes efter en uge, eller når de er blevet åbnet første gang. Du finder adgangskoden du har modtaget under "Find din adgangskode her".', + info: 'Din adgangskode forlader aldrig din enhed. Alt kryptering foregår offline, og Lyra opbevarer kun sikkert krypterede værdier. Adgangskoder slettes efter en uge, eller når de er blevet åbnet første gang. Skriv en adgangskode du vil dele, og tryk "fortsæt" for at få et sikkert link.' }, en: { continue: 'Continue', copied: 'Copied', find: 'Find your password here', write: 'Write a password here', + get: 'Get your password here', + getPass: 'Get password', share: 'Secure sharing of passwords', - infoDecrypted: 'Lyra never stores shared passwords. No one but the people who have the link can access the password - not even Tetrabit. The link is valid for one week. You will find the password you received under "Find your password here".', - info: 'Lyra never stores shared passwords. No one but the people who have the link can access the password - not even Tetrabit. The link is valid for one week. Write a password you want to share, and press "continue" to get a secure link.' + infoDecrypted: 'Your password never leaves your device. All encryption happens offline, and Lyra only stores securely encrypted values. Passwords are deleted after a week, or when they are opened for the first time. You will find the password you received under "Find your password here".', + info: 'Your password never leaves your device. All encryption happens offline, and Lyra only stores securely encrypted values. Passwords are deleted after a week, or when they are opened for the first time. Write a password you want to share, and press "continue" to get a secure link.' } } @@ -58,7 +147,7 @@ {/each} - + {:else if decrypted} + + {:else} + + {/if}
@@ -135,6 +238,7 @@ .section { padding: $standard-padding; flex-basis: 50%; + position: relative; h3 { display: flex; @@ -147,7 +251,25 @@ padding: 0 $standard-padding; padding-bottom: 10px; text-transform: uppercase; - margin-top: -7px; + margin-top: -20px; + padding-top: 12px; + position: relative; + z-index: 2; + background-color: $sand; + } + + button.get { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: $black; + color: $sand; + font-size: 3rem; + text-transform: uppercase; + cursor: pointer; + padding-top: 40px; } &:last-child { border-left: solid 1px $black; } diff --git a/src/routes/api/+server.js b/src/routes/api/+server.js deleted file mode 100644 index 2ad2c73..0000000 --- a/src/routes/api/+server.js +++ /dev/null @@ -1,42 +0,0 @@ -import { SECRET } from '$env/static/private' -import crypto from 'crypto' -import { json } from '@sveltejs/kit' - -export const POST = async ({ request }) => { - const body = await request.json() - const { action, value } = body - - if (action === 'encrypt') return json({ encrypted: encrypt(value) }) - if (action === 'decrypt') return json({ decrypted: decrypt(value) }) -} - -const encrypt = (value) => { - const key = Buffer.alloc(32) - Buffer.from(SECRET).copy(key) - const iv = crypto.randomBytes(16) - - const today = new Date() - - const cipher = crypto.createCipheriv('aes-256-cbc', key, iv) - let encrypted = cipher.update(`${value}:${today.toISOString().split('T')[0]}`, 'utf8', 'hex') - encrypted += cipher.final('hex') - - return `${iv.toString('hex')}:${encrypted}` -} - -const decrypt = (value) => { - const key = Buffer.alloc(32) - Buffer.from(SECRET).copy(key) - const [iv, encrypted] = value.split(':').map(v => Buffer.from(v, 'hex')) - - const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv) - let decrypted = decipher.update(encrypted, 'hex', 'utf8') - decrypted += decipher.final('utf8') - - const [decryptedValue, date] = decrypted.split(':') - - const daysAgo = Math.abs(Math.floor((new Date() - new Date(date)) / (1000 * 60 * 60 * 24))) - if (daysAgo > 7) return null - - return decryptedValue -} \ No newline at end of file