more changes
This commit is contained in:
		
							parent
							
								
									00ab7c80b5
								
							
						
					
					
						commit
						60be4fbdfb
					
				
							
								
								
									
										30
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										30
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -9,6 +9,9 @@ | |||||||
| 			"version": "0.0.1", | 			"version": "0.0.1", | ||||||
| 			"dependencies": { | 			"dependencies": { | ||||||
| 				"@aws-sdk/client-s3": "^3.828.0", | 				"@aws-sdk/client-s3": "^3.828.0", | ||||||
|  | 				"@fontsource/merriweather": "^5.2.9", | ||||||
|  | 				"@fontsource/roboto": "^5.2.6", | ||||||
|  | 				"@fontsource/roboto-mono": "^5.2.6", | ||||||
| 				"@noble/ed25519": "^2.3.0", | 				"@noble/ed25519": "^2.3.0", | ||||||
| 				"@noble/hashes": "^1.8.0", | 				"@noble/hashes": "^1.8.0", | ||||||
| 				"@sveltejs/adapter-node": "^5.2.12", | 				"@sveltejs/adapter-node": "^5.2.12", | ||||||
| @ -1295,6 +1298,33 @@ | |||||||
| 				"node": ">=18" | 				"node": ">=18" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"node_modules/@fontsource/merriweather": { | ||||||
|  | 			"version": "5.2.9", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@fontsource/merriweather/-/merriweather-5.2.9.tgz", | ||||||
|  | 			"integrity": "sha512-amd5Wp7BM5U0HE/FUgsJmf/dvfqSKYf55HrxR1tMlpT3BqQmVZHB9RJDBpNqqf3/yF6fkryCbx5H9fEMa4mY3g==", | ||||||
|  | 			"license": "OFL-1.1", | ||||||
|  | 			"funding": { | ||||||
|  | 				"url": "https://github.com/sponsors/ayuhito" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"node_modules/@fontsource/roboto": { | ||||||
|  | 			"version": "5.2.6", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.6.tgz", | ||||||
|  | 			"integrity": "sha512-hzarG7yAhMoP418smNgfY4fO7UmuUEm5JUtbxCoCcFHT0hOJB+d/qAEyoNjz7YkPU5OjM2LM8rJnW8hfm0JLaA==", | ||||||
|  | 			"license": "OFL-1.1", | ||||||
|  | 			"funding": { | ||||||
|  | 				"url": "https://github.com/sponsors/ayuhito" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"node_modules/@fontsource/roboto-mono": { | ||||||
|  | 			"version": "5.2.6", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/@fontsource/roboto-mono/-/roboto-mono-5.2.6.tgz", | ||||||
|  | 			"integrity": "sha512-fLCa3zs9XruKE8Fdbq0UWB0wqTi5dzi09QsnW7HgTwwnSVDZ3nH+X7Qg7l0yeIZs+E472cKE3RUD21ZnaXk4Zg==", | ||||||
|  | 			"license": "OFL-1.1", | ||||||
|  | 			"funding": { | ||||||
|  | 				"url": "https://github.com/sponsors/ayuhito" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"node_modules/@isaacs/fs-minipass": { | 		"node_modules/@isaacs/fs-minipass": { | ||||||
| 			"version": "4.0.1", | 			"version": "4.0.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", | 			"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", | ||||||
|  | |||||||
| @ -23,6 +23,9 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@aws-sdk/client-s3": "^3.828.0", | 		"@aws-sdk/client-s3": "^3.828.0", | ||||||
|  | 		"@fontsource/merriweather": "^5.2.9", | ||||||
|  | 		"@fontsource/roboto": "^5.2.6", | ||||||
|  | 		"@fontsource/roboto-mono": "^5.2.6", | ||||||
| 		"@noble/ed25519": "^2.3.0", | 		"@noble/ed25519": "^2.3.0", | ||||||
| 		"@noble/hashes": "^1.8.0", | 		"@noble/hashes": "^1.8.0", | ||||||
| 		"@sveltejs/adapter-node": "^5.2.12", | 		"@sveltejs/adapter-node": "^5.2.12", | ||||||
|  | |||||||
							
								
								
									
										19
									
								
								src/app.css
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								src/app.css
									
									
									
									
									
								
							| @ -1,10 +1,24 @@ | |||||||
| @import "tailwindcss"; | @import "tailwindcss"; | ||||||
| 
 | 
 | ||||||
|  | @import "@fontsource/roboto/400.css"; | ||||||
|  | @import "@fontsource/roboto/700.css"; | ||||||
|  | @import "@fontsource/roboto-mono/400.css"; | ||||||
|  | @import "@fontsource/roboto-mono/700.css"; | ||||||
|  | @import "@fontsource/merriweather/400.css"; | ||||||
|  | @import "@fontsource/merriweather/700.css"; | ||||||
|  | 
 | ||||||
|  | @theme { | ||||||
|  |     --font-sans: 'Roboto', sans-serif; | ||||||
|  |     --font-serif: 'Merriweather', serif; | ||||||
|  |     --font-mono: 'Roboto Mono', monospace; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| html, | html, | ||||||
| body { | body { | ||||||
|     height: 100%; |     height: 100%; | ||||||
|     scroll-behavior: smooth; |     scroll-behavior: smooth; | ||||||
|     background-color: #e7f7df; |     background-color: #e7f7df; | ||||||
|  |     font-family: var(--font-sans); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @keyframes fade-in { | @keyframes fade-in { | ||||||
| @ -71,3 +85,8 @@ body { | |||||||
| .min-rh-screen { | .min-rh-screen { | ||||||
|     min-height: calc(100 * var(--vh) - 1px); |     min-height: calc(100 * var(--vh) - 1px); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | button, a { | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
| @ -9,11 +9,11 @@ const SALT = 'SONRI-KEY-SALT'; | |||||||
| export type KeyVault = Awaited<ReturnType<typeof deriveKeyVault>>; | export type KeyVault = Awaited<ReturnType<typeof deriveKeyVault>>; | ||||||
| 
 | 
 | ||||||
| // Helper: convert string to Uint8Array
 | // Helper: convert string to Uint8Array
 | ||||||
| function strToUint8(str: string) { | export function strToUint8(str: string) { | ||||||
|   return new TextEncoder().encode(str); |   return new TextEncoder().encode(str); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function uint8ToStr(uint8: Uint8Array) { | export function uint8ToStr(uint8: Uint8Array) { | ||||||
|   return new TextDecoder().decode(uint8); |   return new TextDecoder().decode(uint8); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| export function dateDiff(a: Date, b: Date) { | export function dateDiff(a: Date, b: Date) { | ||||||
|     const diff = (a.getTime() - b.getTime()) - 60 * 60 * 1000; |     const diff = (a.getTime() - b.getTime()) - 60 * 60 * 1000; | ||||||
|     return new Date(diff); |     return new Date(diff); | ||||||
| } | } | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ export async function convertVideo(video: Uint8Array) { | |||||||
|         await fs.writeFile(tempInPath, video); |         await fs.writeFile(tempInPath, video); | ||||||
| 
 | 
 | ||||||
|         const status = await new Promise((resolve, reject) => { |         const status = await new Promise((resolve, reject) => { | ||||||
|             const child = exec(`ffmpeg -i "${tempInPath}" -preset ultrafast -vf "scale=320:480,eq=saturation=1.4,unsharp=5:5:1.5:5:5:0.0" -b:v 420k -b:a 64k "${tempOutPath}"`, (err) => { |             const child = exec(`ffmpeg -i "${tempInPath}" -preset ultrafast -vf "scale=320:480,eq=saturation=1.5,unsharp=5:5:1.5:5:5:0.0" -b:v 420k -b:a 48k "${tempOutPath}"`, (err) => { | ||||||
|                 if (err) { |                 if (err) { | ||||||
|                     reject(err); |                     reject(err); | ||||||
|                     return; |                     return; | ||||||
|  | |||||||
| @ -42,6 +42,11 @@ | |||||||
|     }; |     }; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | <svelte:head> | ||||||
|  |     <title>Somri Wormhole - Go back in time!</title> | ||||||
|  | </svelte:head> | ||||||
|  | 
 | ||||||
| <div class="rh-screen p-4 flex flex-col items-center"> | <div class="rh-screen p-4 flex flex-col items-center"> | ||||||
|     <div class="shrink-0"> |     <div class="shrink-0"> | ||||||
|         <input type="date" |         <input type="date" | ||||||
| @ -56,3 +61,7 @@ | |||||||
|         </video> |         </video> | ||||||
|     </div> |     </div> | ||||||
| </div> | </div> | ||||||
|  | 
 | ||||||
|  | <!-- <button class="fixed bg-[#e14c2f] rounded-xl top-3 right-3 p-1 cursor-pointer"> | ||||||
|  |     <svg xmlns="http://www.w3.org/2000/svg" height="22px" viewBox="0 0 24 24" width="22px" fill="white"><path d="M0 0h24v24H0z" fill="none"/><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg> | ||||||
|  | </button> --> | ||||||
|  | |||||||
| @ -20,7 +20,11 @@ | |||||||
|     }); |     }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="h-full grid place-items-center p-6"> | <svelte:head> | ||||||
|  |     <title>Somri Login</title> | ||||||
|  | </svelte:head> | ||||||
|  | 
 | ||||||
|  | <div class="absolute left-0 right-0 m-auto p-6 max-w-[400px]"> | ||||||
|     <form method="POST" class="p-6 rounded-3xl bg-white flex flex-col gap-2" |     <form method="POST" class="p-6 rounded-3xl bg-white flex flex-col gap-2" | ||||||
|         onsubmit={onSubmit} |         onsubmit={onSubmit} | ||||||
|     > |     > | ||||||
| @ -31,4 +35,4 @@ | |||||||
|             Login |             Login | ||||||
|         </button> |         </button> | ||||||
|     </form> |     </form> | ||||||
| </div> | </div> | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								src/routes/(area)/logout/+page.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/routes/(area)/logout/+page.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | <script> | ||||||
|  |     import { logout } from "$lib/auth"; | ||||||
|  |     import { onMount } from "svelte"; | ||||||
|  | 
 | ||||||
|  |     onMount(() => { | ||||||
|  |         if (typeof window !== 'object') return; | ||||||
|  | 
 | ||||||
|  |         logout(); | ||||||
|  |         window.location.href = '/'; | ||||||
|  |     }) | ||||||
|  | </script> | ||||||
| @ -9,4 +9,9 @@ | |||||||
|             window.location.href = '/login'; |             window.location.href = '/login'; | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| </script> | </script> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | <svelte:head> | ||||||
|  |     <title>Somri Settings</title> | ||||||
|  | </svelte:head> | ||||||
| @ -1,9 +1,8 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|     import { onMount } from "svelte"; |     import { onMount } from "svelte"; | ||||||
|     import { hashBuffer } from "$lib/hash"; |  | ||||||
|     import { dateDiff } from "$lib/date"; |     import { dateDiff } from "$lib/date"; | ||||||
|     import { getKeyVault, loggedIn } from "$lib/auth"; |     import { getKeyVault, loggedIn } from "$lib/auth"; | ||||||
|     import { encrypt, sign, uint8ToHex } from "$lib/crypto"; |     import { encrypt, sign, strToUint8, uint8ToHex } from "$lib/crypto"; | ||||||
|     import { lang } from "$lib/lang"; |     import { lang } from "$lib/lang"; | ||||||
| 
 | 
 | ||||||
|     type StatusT = "pending" | "active" | "error"; |     type StatusT = "pending" | "active" | "error"; | ||||||
| @ -11,6 +10,7 @@ | |||||||
|     let errors = $state<string[]>([]); |     let errors = $state<string[]>([]); | ||||||
| 
 | 
 | ||||||
|     let now = $state(new Date()); |     let now = $state(new Date()); | ||||||
|  |     let date = $state(new Date(new Date().getTime() - 4 * 60 * 60 * 1000)); | ||||||
|     let recordingStart = $state<Date | null>(null); |     let recordingStart = $state<Date | null>(null); | ||||||
|     let recordingURL = $state<string | null>(null); |     let recordingURL = $state<string | null>(null); | ||||||
| 
 | 
 | ||||||
| @ -80,11 +80,15 @@ | |||||||
| 
 | 
 | ||||||
|         const listener = () => { |         const listener = () => { | ||||||
|             const container = document.getElementById('container') as HTMLDivElement; |             const container = document.getElementById('container') as HTMLDivElement; | ||||||
|             container.style.height = document.body.offsetHeight + 'px'; |             container.style.height = (document.body.offsetHeight - 1) + 'px'; | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         listener(); |         listener(); | ||||||
|         window.addEventListener('resize', listener); |         window.addEventListener('resize', listener); | ||||||
|  |          | ||||||
|  |         return () => { | ||||||
|  |             window.removeEventListener('resize', listener); | ||||||
|  |         }; | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     const start = () => { |     const start = () => { | ||||||
| @ -100,14 +104,18 @@ | |||||||
|     const submit = async () => { |     const submit = async () => { | ||||||
|         if (!recordingURL) return; |         if (!recordingURL) return; | ||||||
| 
 | 
 | ||||||
|  |         const dateStr = new Date().toISOString().substring(0, 10); | ||||||
|  | 
 | ||||||
|         const blob = await (await fetch(recordingURL)).blob(); |         const blob = await (await fetch(recordingURL)).blob(); | ||||||
|         const buffer = await blob.arrayBuffer(); |         const buffer = await blob.arrayBuffer(); | ||||||
| 
 | 
 | ||||||
|         const vault = getKeyVault()!; |         const vault = getKeyVault()!; | ||||||
|         const encrypted = await encrypt(vault.symmKey, new Uint8Array(buffer)); |         const encrypted = await encrypt(vault.symmKey, new Uint8Array(buffer)); | ||||||
|         const signature = await sign(vault.privKey, encrypted); |  | ||||||
| 
 | 
 | ||||||
|         const res = await fetch(`/api/today?pubkey=${uint8ToHex(vault.pubKey)}&signature=${uint8ToHex(signature)}`, { |         const signatureInput = Uint8Array.from([...strToUint8(dateStr), ...encrypted]); | ||||||
|  |         const signature = await sign(vault.privKey, signatureInput); | ||||||
|  | 
 | ||||||
|  |         const res = await fetch(`/api/submit?pubkey=${uint8ToHex(vault.pubKey)}&signature=${uint8ToHex(signature)}&date=${dateStr}`, { | ||||||
|             method: 'POST', |             method: 'POST', | ||||||
|             body: encrypted, |             body: encrypted, | ||||||
|             headers: { |             headers: { | ||||||
| @ -121,6 +129,10 @@ | |||||||
|     }; |     }; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  | <svelte:head> | ||||||
|  |     <title>Memo - {date.toLocaleDateString(lang(), { dateStyle: 'long' })}</title> | ||||||
|  | </svelte:head> | ||||||
|  | 
 | ||||||
| <div id="container" class="relative w-full"> | <div id="container" class="relative w-full"> | ||||||
|     <div class="absolute left-0 right-0 m-auto h-full w-full max-w-[70vh] p-4"> |     <div class="absolute left-0 right-0 m-auto h-full w-full max-w-[70vh] p-4"> | ||||||
|         <video |         <video | ||||||
| @ -136,8 +148,7 @@ | |||||||
|         class="absolute top-0 left-0 right-0 p-6 m-auto w-fit" |         class="absolute top-0 left-0 right-0 p-6 m-auto w-fit" | ||||||
|     > |     > | ||||||
|         <div class="rounded-3xl bg-white px-3 py-1 font-serif text-center w-fit scale-in" style="animation-delay: 150ms;"> |         <div class="rounded-3xl bg-white px-3 py-1 font-serif text-center w-fit scale-in" style="animation-delay: 150ms;"> | ||||||
|             <h1 class="font-bold text-xl">{now.toLocaleDateString(lang(), { dateStyle: 'long' })}</h1> |             <h1 class="font-bold text-xl">{date.toLocaleDateString(lang(), { dateStyle: 'long' })}</h1> | ||||||
|             <h2 class="text-sm">{now.toLocaleTimeString(lang())}</h2> |  | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         {#if !!recordingStart} |         {#if !!recordingStart} | ||||||
| @ -145,8 +156,7 @@ | |||||||
|             <div class="rounded-3xl bg-white px-3 py-1 font-serif flex gap-1 items-center w-fit m-auto scale-in"> |             <div class="rounded-3xl bg-white px-3 py-1 font-serif flex gap-1 items-center w-fit m-auto scale-in"> | ||||||
|                 <svg class="blink" xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="24px" fill="red"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/></svg> |                 <svg class="blink" xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 0 24 24" width="24px" fill="red"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/></svg> | ||||||
|                 <div> |                 <div> | ||||||
|                     <span class="text-md"> |                     <span class="text-md font-mono"> | ||||||
|                         <!-- <span class="font-bold">Recording</span> --> |  | ||||||
|                         <span>{dateDiff(now, recordingStart).toLocaleTimeString(lang(), { minute: 'numeric', second: 'numeric' })}</span> |                         <span>{dateDiff(now, recordingStart).toLocaleTimeString(lang(), { minute: 'numeric', second: 'numeric' })}</span> | ||||||
|                     </span> |                     </span> | ||||||
|                 </div> |                 </div> | ||||||
|  | |||||||
| @ -11,5 +11,69 @@ | |||||||
|     }); |     }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <h1>Welcome to SvelteKit</h1> | <div class="min-h-screen bg-[#e7f7df] font-serif text-gray-800 flex flex-col"> | ||||||
| <p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p> | 
 | ||||||
|  |     <div class="relative w-full overflow-hidden py-12 px-4 md:py-24 lg:py-32 flex-grow flex items-center justify-center"> | ||||||
|  |         <div class="relative z-10 max-w-5xl mx-auto flex flex-col lg:flex-row items-center justify-between gap-8 md:gap-12"> | ||||||
|  |             <div class="lg:w-1/2 text-center lg:text-left"> | ||||||
|  |                 <h1 class="text-4xl sm:text-5xl lg:text-6xl font-extrabold text-[#388e3c] mb-3 leading-tight"> | ||||||
|  |                     Sonri: Your Private Life, <span class="text-[#4caf50]">Truly Yours.</span> | ||||||
|  |                 </h1> | ||||||
|  |                 <p class="text-lg sm:text-xl lg:text-2xl text-gray-700 mb-6"> | ||||||
|  |                     Capture your moments with <b class="font-semibold text-gray-900">unmatched privacy</b> and <b class="font-semibold text-gray-900">total freedom</b>. Sonri ensures your video diary is for your eyes only, and it's <b class="font-semibold text-gray-900">currently free</b>. | ||||||
|  |                 </p> | ||||||
|  |                 <div class="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start"> | ||||||
|  |                     <a href="/login" class="bg-[#66bb6a] text-white font-bold py-3 px-6 rounded-full text-lg shadow-xl hover:bg-[#5cb85d] focus:outline-none focus:ring-4 focus:ring-[#81c784] focus:ring-opacity-75 transition-all duration-300 ease-in-out"> | ||||||
|  |                         Start Journal Now | ||||||
|  |                     </a> | ||||||
|  |                     <a href="#features" class="inline-flex items-center justify-center text-[#4caf50] font-semibold py-3 px-6 rounded-full text-lg border-2 border-[#4caf50] hover:bg-[#4caf50] hover:text-white transition-colors duration-300 ease-in-out group"> | ||||||
|  |                         Your Privacy | ||||||
|  |                         <svg class="ml-2 w-5 h-5 group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg> | ||||||
|  |                     </a> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             <div class="hidden lg:static lg:w-1/2 mt-8 lg:mt-0 flex justify-center items-center"> | ||||||
|  |                 <div class="w-64 h-64 sm:w-72 sm:h-72 bg-white rounded-full shadow-2xl flex items-center justify-center p-6 sm:p-8 border-4 border-[#aed581] transform rotate-3 hover:rotate-0 transition-transform duration-500 ease-in-out"> | ||||||
|  |                     <svg class="w-40 h-40 sm:w-48 sm:h-48 text-[#4caf50] opacity-70" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div id="features" class="relative z-20 w-full max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8"> | ||||||
|  |         <h2 class="text-3xl sm:text-4xl font-extrabold text-[#388e3c] text-center mb-10">The Sonri Advantage</h2> | ||||||
|  |         <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8"> | ||||||
|  |             <div class="bg-white p-6 sm:p-8 rounded-2xl shadow-lg border border-[#c5e1a5] flex flex-col items-center text-center transition-all duration-300 hover:shadow-xl hover:bg-[#f9fdf8]"> | ||||||
|  |                 <div class="p-3 sm:p-4 rounded-full bg-[#e6ee9c] mb-4 sm:mb-6 shadow-md"> | ||||||
|  |                     <svg class="w-12 h-12 sm:w-16 sm:h-16 text-[#4caf50]" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg> | ||||||
|  |                 </div> | ||||||
|  |                 <h3 class="text-2xl sm:text-3xl font-bold text-[#388e3c] mb-2 sm:mb-3">Anonymous</h3> | ||||||
|  |                 <p class="text-base sm:text-lg text-gray-700">No email, no phone number, nothing else.</p> | ||||||
|  |                 <p class="text-base sm:text-lg text-gray-700 pt-1 sm:pt-2">Just one passphrase.</p> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             <div class="bg-white p-6 sm:p-8 rounded-2xl shadow-lg border border-[#c5e1a5] flex flex-col items-center text-center transition-all duration-300 hover:shadow-xl hover:bg-[#f9fdf8]"> | ||||||
|  |                 <div class="p-3 sm:p-4 rounded-full bg-[#e6ee9c] mb-4 sm:mb-6 shadow-md"> | ||||||
|  |                     <svg class="w-12 h-12 sm:w-16 sm:h-16 text-[#4caf50]" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 7a2 2 0 012 2v5l-3 3H7a2 2 0 01-2-2V9a2 2 0 012-2h1V5a2 2 0 012-2h4a2 2 0 012 2v2h1zM10 11H8v2h2v-2z"></path></svg> | ||||||
|  |                 </div> | ||||||
|  |                 <h3 class="text-2xl sm:text-3xl font-bold text-[#388e3c] mb-2 sm:mb-3">Fully Encrypted</h3> | ||||||
|  |                 <p class="text-base sm:text-lg text-gray-700">Every log entry is encrypted using your unique passphrase.</p> | ||||||
|  |                 <p class="text-base sm:text-lg text-gray-700 pt-1 sm:pt-2">If you forget your passphrase, all logs are lost forever.</p> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |             <div class="bg-white p-6 sm:p-8 rounded-2xl shadow-lg border border-[#c5e1a5] flex flex-col items-center text-center transition-all duration-300 hover:shadow-xl hover:bg-[#f9fdf8]"> | ||||||
|  |                 <div class="p-3 sm:p-4 rounded-full bg-[#e6ee9c] mb-4 sm:mb-6 shadow-md"> | ||||||
|  |                     <svg class="w-12 h-12 sm:w-16 sm:h-16 text-[#4caf50]" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-3.333a2 2 0 01-1.5-.667L12 4z"></path></svg> | ||||||
|  |                 </div> | ||||||
|  |                 <h3 class="text-2xl sm:text-3xl font-bold text-[#388e3c] mb-2 sm:mb-3">Free</h3> | ||||||
|  |                 <p class="text-base sm:text-lg text-gray-700">Currently, Sonri is completely free to use. Experience true privacy and security in your video journaling journey without any cost barriers.</p> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="w-full mt-auto py-4 sm:py-6 text-center text-gray-600 text-xs sm:text-sm border-t border-[#c5e1a5]"> | ||||||
|  |         © 2025 Sonri. All rights reserved. Your story, beautifully told. | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  | </div> | ||||||
							
								
								
									
										35
									
								
								src/routes/api/delete/+server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/routes/api/delete/+server.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | import { error, text } from "@sveltejs/kit"; | ||||||
|  | import type { RequestHandler } from "./$types"; | ||||||
|  | import { DeleteObjectCommand } from "@aws-sdk/client-s3"; | ||||||
|  | import { env } from "$env/dynamic/private"; | ||||||
|  | import { s3 } from "$lib/storage.server"; | ||||||
|  | import { hexToUint8, strToUint8, verify } from "$lib/crypto"; | ||||||
|  | 
 | ||||||
|  | export const POST: RequestHandler = async ({ request, url }) => { | ||||||
|  |     const dateStr = url.searchParams.get('date')?.trim()?.toLowerCase(); | ||||||
|  |     if (!dateStr || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(dateStr)) error(400); | ||||||
|  |     const date = new Date(dateStr); | ||||||
|  | 
 | ||||||
|  |     const msDiff = new Date().getTime() - date.getTime() + 36 * 60 * 60 * 1000; | ||||||
|  |     if (msDiff <= 0) error(400); | ||||||
|  | 
 | ||||||
|  |     const pubkeyHex = url.searchParams.get('pubkey')?.trim()?.toLowerCase(); | ||||||
|  |     if (!pubkeyHex || !/^[0-9a-f]{64}$/.test(pubkeyHex)) error(400); | ||||||
|  |     const pubKey = hexToUint8(pubkeyHex); | ||||||
|  | 
 | ||||||
|  |     const signatureHex = url.searchParams.get('signature')?.trim()?.toLowerCase(); | ||||||
|  |     if (!signatureHex || !/^[0-9a-f]+$/.test(signatureHex)) error(400); | ||||||
|  |     const signature = hexToUint8(signatureHex); | ||||||
|  | 
 | ||||||
|  |     const valid = await verify(pubKey, signature, strToUint8(dateStr)); | ||||||
|  |     if (!valid) error(401); | ||||||
|  | 
 | ||||||
|  |     const command = new DeleteObjectCommand({ | ||||||
|  |         Bucket: env.S3_BUCKET, | ||||||
|  |         Key: `${pubkeyHex}/${dateStr}`, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await s3.send(command); | ||||||
|  | 
 | ||||||
|  |     return text('ok'); | ||||||
|  | } | ||||||
| @ -4,11 +4,17 @@ import type { RequestHandler } from "./$types"; | |||||||
| import { PutObjectCommand } from "@aws-sdk/client-s3"; | import { PutObjectCommand } from "@aws-sdk/client-s3"; | ||||||
| import { env } from "$env/dynamic/private"; | import { env } from "$env/dynamic/private"; | ||||||
| import { s3 } from "$lib/storage.server"; | import { s3 } from "$lib/storage.server"; | ||||||
| import { hexToUint8, verify } from "$lib/crypto"; | import { hexToUint8, strToUint8, verify } from "$lib/crypto"; | ||||||
|  | import { dateDiff } from "$lib/date"; | ||||||
| 
 | 
 | ||||||
| // This route is currently vulnerable against replay attacks across multiple days
 |  | ||||||
| // -- possible fix: prepend date to signature verification payload
 |  | ||||||
| export const POST: RequestHandler = async ({ request, url }) => { | export const POST: RequestHandler = async ({ request, url }) => { | ||||||
|  |     const dateStr = url.searchParams.get('date')?.trim()?.toLowerCase(); | ||||||
|  |     if (!dateStr || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(dateStr)) error(400); | ||||||
|  |     const date = new Date(dateStr); | ||||||
|  |      | ||||||
|  |     const msDiff = Math.abs(new Date().getTime() - date.getTime()); | ||||||
|  |     if (msDiff > 36 * 60 * 60 * 1000) error(403); | ||||||
|  | 
 | ||||||
|     const pubkeyHex = url.searchParams.get('pubkey')?.trim()?.toLowerCase(); |     const pubkeyHex = url.searchParams.get('pubkey')?.trim()?.toLowerCase(); | ||||||
|     if (!pubkeyHex || !/^[0-9a-f]{64}$/.test(pubkeyHex)) error(400); |     if (!pubkeyHex || !/^[0-9a-f]{64}$/.test(pubkeyHex)) error(400); | ||||||
|     const pubKey = hexToUint8(pubkeyHex); |     const pubKey = hexToUint8(pubkeyHex); | ||||||
| @ -17,17 +23,16 @@ export const POST: RequestHandler = async ({ request, url }) => { | |||||||
|     if (!signatureHex || !/^[0-9a-f]+$/.test(signatureHex)) error(400); |     if (!signatureHex || !/^[0-9a-f]+$/.test(signatureHex)) error(400); | ||||||
|     const signature = hexToUint8(signatureHex); |     const signature = hexToUint8(signatureHex); | ||||||
| 
 | 
 | ||||||
|     const today = new Date().toISOString().slice(0, 10); |  | ||||||
| 
 |  | ||||||
|     const bodyBuffer = await request.arrayBuffer(); |     const bodyBuffer = await request.arrayBuffer(); | ||||||
|     const bodyUint8 = new Uint8Array(bodyBuffer); |     const bodyUint8 = new Uint8Array(bodyBuffer); | ||||||
| 
 | 
 | ||||||
|     const valid = await verify(pubKey, signature, bodyUint8); |     const signatureInput = Uint8Array.from([...strToUint8(dateStr), ...bodyUint8]); | ||||||
|  |     const valid = await verify(pubKey, signature, signatureInput); | ||||||
|     if (!valid) error(401); |     if (!valid) error(401); | ||||||
| 
 | 
 | ||||||
|     const command = new PutObjectCommand({ |     const command = new PutObjectCommand({ | ||||||
|         Bucket: env.S3_BUCKET, |         Bucket: env.S3_BUCKET, | ||||||
|         Key: `${pubkeyHex}/${today}`, |         Key: `${pubkeyHex}/${dateStr}`, | ||||||
|         Body: bodyUint8, |         Body: bodyUint8, | ||||||
|         ContentType: 'application/octet-stream', |         ContentType: 'application/octet-stream', | ||||||
|     }); |     }); | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user