Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ api/.coverage

# TypeScript build info
*.tsbuildinfo

# Environment variables (secrets)
.env
!.env.template
1 change: 1 addition & 0 deletions api/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
POSTHOG_API_KEY=
2 changes: 2 additions & 0 deletions api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ def create_secret():

if posthog:
posthog.capture("server", "secret_created", {"ttl_hours": ttl_hours, "has_alias": alias is not None})
posthog.flush()

return jsonify({"id": secret_id, "alias": alias}), 201

Expand Down Expand Up @@ -174,6 +175,7 @@ def get_secret(secret_id):

if posthog:
posthog.capture("server", "secret_retrieved", {"via": "alias" if alias_used else "uuid"})
posthog.flush()

return jsonify({"ciphertext": ciphertext, "id": actual_id})

Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ services:

api:
build: ./api
env_file:
- ./api/.env
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
Expand Down
1 change: 1 addition & 0 deletions ui/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_POSTHOG_KEY=
52 changes: 26 additions & 26 deletions ui/src/i18n/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,61 @@
"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 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 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 -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 -lo — 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 foi visualizado ou expirou. Segredos 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."
}
}
32 changes: 32 additions & 0 deletions ui/src/lib/posthog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,44 @@ import posthog from "posthog-js";
const POSTHOG_KEY = import.meta.env.VITE_POSTHOG_KEY;
const POSTHOG_HOST = import.meta.env.VITE_POSTHOG_HOST || "https://us.i.posthog.com";

/** Strip URL fragment (#key) from any string that looks like a URL. */
function stripFragment(value: unknown): unknown {
if (typeof value === "string" && value.includes("#")) {
return value.split("#")[0];
}
return value;
}

/** Properties that may contain the encryption key in the URL fragment. */
const URL_PROPS = [
"$current_url",
"$pathname",
"$referrer",
"$initial_current_url",
"$initial_referrer",
"$pageview_id",
];

if (POSTHOG_KEY) {
posthog.init(POSTHOG_KEY, {
api_host: POSTHOG_HOST,
capture_pageview: true,
capture_pageleave: true,
autocapture: true,
sanitize_properties(properties, _event) {
for (const key of URL_PROPS) {
if (key in properties) {
properties[key] = stripFragment(properties[key]);
}
}
// Also strip from any property containing a URL with a fragment
for (const [key, value] of Object.entries(properties)) {
if (typeof value === "string" && value.includes("#") && value.includes("/s/")) {
properties[key] = stripFragment(value);
}
}
return properties;
},
});
}

Expand Down
Loading