diff --git a/.gitignore b/.gitignore index b345356..303c324 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,14 @@ target/ # Ralph backup directories (created by migration) .ralph_backup_* + +# Coverage reports +ui/coverage/ +api/.coverage + +# TypeScript build info +*.tsbuildinfo + +# Environment variables (secrets) +.env +!.env.template diff --git a/ui/index.html b/ui/index.html index fa01d2f..36d826b 100644 --- a/ui/index.html +++ b/ui/index.html @@ -5,11 +5,63 @@ + + + + + + + + + + + + + + + + + + + + + + Only Once Share — Secure One-Time Secret Sharing + + +
diff --git a/ui/nginx.conf b/ui/nginx.conf index f749e02..c9c544c 100644 --- a/ui/nginx.conf +++ b/ui/nginx.conf @@ -3,6 +3,22 @@ server { root /usr/share/nginx/html; index index.html; + # Static assets — long cache + location ~* \.(js|css|png|jpg|jpeg|svg|ico|woff2?)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + } + + # robots.txt and sitemap — short cache + location = /robots.txt { + expires 7d; + add_header Cache-Control "public"; + } + location = /sitemap.xml { + expires 7d; + add_header Cache-Control "public"; + } + location / { try_files $uri $uri/ /index.html; } diff --git a/ui/public/og-image.png b/ui/public/og-image.png new file mode 100644 index 0000000..f74e912 Binary files /dev/null and b/ui/public/og-image.png differ diff --git a/ui/public/og-image.svg b/ui/public/og-image.svg new file mode 100644 index 0000000..ebe44fc --- /dev/null +++ b/ui/public/og-image.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + Only Once Share + + Secure One-Time Secret Sharing + + End-to-End Encrypted · Zero-Knowledge · Self-Destructing Links + + ooshare.io + diff --git a/ui/public/robots.txt b/ui/public/robots.txt new file mode 100644 index 0000000..0345e3e --- /dev/null +++ b/ui/public/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / +Disallow: /s/ + +Sitemap: https://ooshare.io/sitemap.xml diff --git a/ui/public/sitemap.xml b/ui/public/sitemap.xml new file mode 100644 index 0000000..368ecfb --- /dev/null +++ b/ui/public/sitemap.xml @@ -0,0 +1,8 @@ + + + + https://ooshare.io/ + monthly + 1.0 + + diff --git a/ui/src/components/Layout.tsx b/ui/src/components/Layout.tsx index b22ad4e..ba1b6ed 100644 --- a/ui/src/components/Layout.tsx +++ b/ui/src/components/Layout.tsx @@ -1,10 +1,19 @@ +import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Shield, Lock, Eye, Trash2 } from "lucide-react"; import SecurityModal from "./SecurityModal"; import LanguageSelector from "./LanguageSelector"; export default function Layout({ children }: { children: React.ReactNode }) { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); + + useEffect(() => { + document.documentElement.lang = i18n.language; + document.title = t("meta.title"); + document + .querySelector('meta[name="description"]') + ?.setAttribute("content", t("meta.description")); + }, [i18n.language, t]); return (
diff --git a/ui/src/i18n/locales/ar.json b/ui/src/i18n/locales/ar.json index 907662a..f8ed7ce 100644 --- a/ui/src/i18n/locales/ar.json +++ b/ui/src/i18n/locales/ar.json @@ -1,4 +1,8 @@ { + "meta": { + "title": "Only Once Share — مشاركة الأسرار الآمنة لمرة واحدة", + "description": "شارك كلمات المرور ومفاتيح API والبيانات الحساسة بتشفير من طرف إلى طرف. معرفة صفرية، روابط تُستخدم مرة واحدة وتتلف ذاتياً بعد المشاهدة." + }, "header": { "title": "Only Once Share" }, diff --git a/ui/src/i18n/locales/en.json b/ui/src/i18n/locales/en.json index 5f14ab5..9eee21d 100644 --- a/ui/src/i18n/locales/en.json +++ b/ui/src/i18n/locales/en.json @@ -1,4 +1,8 @@ { + "meta": { + "title": "Only Once Share — Secure One-Time Secret Sharing", + "description": "Share passwords, API keys, and sensitive data with end-to-end encryption. Zero-knowledge, single-use links that self-destruct after viewing." + }, "header": { "title": "Only Once Share" }, diff --git a/ui/src/i18n/locales/es.json b/ui/src/i18n/locales/es.json index 01dcef7..a003240 100644 --- a/ui/src/i18n/locales/es.json +++ b/ui/src/i18n/locales/es.json @@ -1,4 +1,8 @@ { + "meta": { + "title": "Only Once Share — Compartir secretos de forma segura", + "description": "Comparte contraseñas, claves API y datos sensibles con cifrado de extremo a extremo. Conocimiento cero, enlaces de un solo uso que se autodestruyen después de verlos." + }, "header": { "title": "Only Once Share" }, diff --git a/ui/src/i18n/locales/hi.json b/ui/src/i18n/locales/hi.json index dd744a2..1cb06b7 100644 --- a/ui/src/i18n/locales/hi.json +++ b/ui/src/i18n/locales/hi.json @@ -1,4 +1,8 @@ { + "meta": { + "title": "Only Once Share — सुरक्षित एक बार का गोपनीय साझाकरण", + "description": "एंड-टू-एंड एन्क्रिप्शन के साथ पासवर्ड, API कुंजियाँ और संवेदनशील डेटा साझा करें। शून्य ज्ञान, एक बार उपयोग लिंक जो देखने के बाद स्वतः नष्ट हो जाते हैं।" + }, "header": { "title": "Only Once Share" }, diff --git a/ui/src/i18n/locales/pt.json b/ui/src/i18n/locales/pt.json index 28527b7..6dbcb27 100644 --- a/ui/src/i18n/locales/pt.json +++ b/ui/src/i18n/locales/pt.json @@ -1,63 +1,67 @@ { + "meta": { + "title": "Only Once Share — Compartilhamento seguro de segredos", + "description": "Compartilhe senhas, chaves de API e dados sensíveis com criptografia ponta a ponta. Conhecimento zero, links de uso único que se autodestroem após a visualização." + }, "header": { "title": "Only Once Share" }, "hero": { - "title": "Compartilhe segredos com seguranca", - "subtitle": "Criptografia ponta a ponta no seu navegador. O servidor nunca ve seus dados. Links se autodestroem apos uma visualizacao." + "title": "Compartilhe segredos com segurança", + "subtitle": "Criptografia ponta a ponta no seu navegador. O servidor nunca vê seus dados. Links se autodestroem após uma visualização." }, "create": { - "label": "Conteudo secreto", + "label": "Conteúdo secreto", "placeholder": "Cole sua senha, chave de API ou mensagem privada...", "charCount": "{{count}} / 50,000", "expiresIn": "Expira em", "encrypting": "Criptografando...", "submit": "Criar link secreto", "linkCreated": "Link secreto criado", - "linkInfo": "Compartilhe este link com seu destinatario. Ele so pode ser aberto uma vez, depois e permanentemente destruido.", + "linkInfo": "Compartilhe este link com seu destinatário. Ele só pode ser aberto uma vez, depois é permanentemente destruído.", "shareVia": "Compartilhar via", "copy": "Copiar", "copied": "Copiado", "whatsapp": "WhatsApp", "email": "E-mail", "createAnother": "Criar outro", - "whatsappMsg": "Estou compartilhando um segredo com voce. Abra este link para ve-lo (apenas uma vez):\n\n{{link}}", - "emailSubject": "Aqui esta um segredo para voce", - "emailBody": "Estou compartilhando um segredo com voce. Abra este link para ve-lo — so pode ser aberto uma vez:\n\n{{link}}" + "whatsappMsg": "Estou compartilhando um segredo com você. Abra este link para vê-lo (apenas uma vez):\n\n{{link}}", + "emailSubject": "Aqui está um segredo para você", + "emailBody": "Estou compartilhando um segredo com você. Abra este link para vê-lo — só pode ser aberto uma vez:\n\n{{link}}" }, "view": { "loading": "Recuperando e descriptografando segredo...", - "destroyed": "Este segredo foi permanentemente destruido. Nao pode ser visualizado novamente.", + "destroyed": "Este segredo foi permanentemente destruído. Não pode ser visualizado novamente.", "copySecret": "Copiar segredo", - "copiedClipboard": "Copiado para a area de transferencia", - "notFoundTitle": "Segredo nao disponivel", - "notFoundMsg": "Este segredo ja foi visualizado ou expirou. Segredos so podem ser acessados uma vez.", + "copiedClipboard": "Copiado para a área de transferência", + "notFoundTitle": "Segredo não disponível", + "notFoundMsg": "Este segredo já foi visualizado ou expirou. Segredos só podem ser acessados uma vez.", "errorTitle": "Algo deu errado", - "errorMsg": "Nao foi possivel descriptografar o segredo. O link pode ser invalido.", - "invalidLink": "Link invalido — chave de descriptografia ausente", + "errorMsg": "Não foi possível descriptografar o segredo. O link pode ser inválido.", + "invalidLink": "Link inválido — chave de descriptografia ausente", "newSecret": "Compartilhar novo segredo", - "backHome": "Voltar ao inicio" + "backHome": "Voltar ao início" }, "footer": { "encryption": "AES-256-GCM", "zeroKnowledge": "Conhecimento zero", - "autoDelete": "Auto-exclusao" + "autoDelete": "Auto-exclusão" }, "security": { "title": "Como funciona", "e2eTitle": "Criptografia ponta a ponta", - "e2eDesc": "Seu segredo e criptografado no seu navegador usando AES-256-GCM com um IV aleatorio de 96 bits antes de sair do seu dispositivo. A chave de criptografia nunca e enviada ao nosso servidor.", - "hkdfTitle": "Derivacao de chave HKDF", - "hkdfDesc": "Uma chave de criptografia unica e derivada para cada segredo usando HKDF-SHA-256 com o ID do segredo como contexto. Mesmo com a chave mestra, cada segredo tem sua propria chave criptograficamente independente.", - "aadTitle": "Vinculacao de dados autenticados", - "aadDesc": "O ID do segredo e vinculado como Dados Autenticados Adicionais (AAD) durante a criptografia. Se alguem adulterar o ID ou trocar o texto cifrado entre segredos, a descriptografia falhara.", + "e2eDesc": "Seu segredo é criptografado no seu navegador usando AES-256-GCM com um IV aleatório de 96 bits antes de sair do seu dispositivo. A chave de criptografia nunca é enviada ao nosso servidor.", + "hkdfTitle": "Derivação de chave HKDF", + "hkdfDesc": "Uma chave de criptografia única é derivada para cada segredo usando HKDF-SHA-256 com o ID do segredo como contexto. Mesmo com a chave mestra, cada segredo tem sua própria chave criptograficamente independente.", + "aadTitle": "Vinculação de dados autenticados", + "aadDesc": "O ID do segredo é vinculado como Dados Autenticados Adicionais (AAD) durante a criptografia. Se alguém adulterar o ID ou trocar o texto cifrado entre segredos, a descriptografia falhará.", "zkTitle": "Conhecimento zero", - "zkDesc": "O servidor armazena apenas dados criptografados. Nao podemos ler, descriptografar ou acessar seus segredos de nenhuma forma.", + "zkDesc": "O servidor armazena apenas dados criptografados. Não podemos ler, descriptografar ou acessar seus segredos de nenhuma forma.", "keyTitle": "A chave nunca sai do navegador", - "keyDesc": "A chave de descriptografia e colocada apos o # na URL. Fragmentos de URL do navegador nunca sao enviados aos servidores.", - "oneTimeTitle": "Visualizacao unica", - "oneTimeDesc": "Quando um segredo e recuperado, ele e atomicamente excluido do armazenamento na mesma operacao.", - "expiryTitle": "Expiracao automatica", - "expiryDesc": "Segredos expiram automaticamente apos o TTL escolhido (1-72 horas), mesmo se nunca forem visualizados." + "keyDesc": "A chave de descriptografia é colocada após o # na URL. Fragmentos de URL do navegador nunca são enviados aos servidores.", + "oneTimeTitle": "Visualização única", + "oneTimeDesc": "Quando um segredo é recuperado, ele é atomicamente excluído do armazenamento na mesma operação.", + "expiryTitle": "Expiração automática", + "expiryDesc": "Segredos expiram automaticamente após o TTL escolhido (1-72 horas), mesmo se nunca forem visualizados." } } diff --git a/ui/src/i18n/locales/zh.json b/ui/src/i18n/locales/zh.json index 07b1bec..8ff82f8 100644 --- a/ui/src/i18n/locales/zh.json +++ b/ui/src/i18n/locales/zh.json @@ -1,4 +1,8 @@ { + "meta": { + "title": "Only Once Share — 安全一次性秘密分享", + "description": "通过端到端加密分享密码、API密钥和敏感数据。零知识架构,一次性自毁链接。" + }, "header": { "title": "Only Once Share" },