https://github.com/lekovr/showonce
Write text and show it once
https://github.com/lekovr/showonce
inmemory onetimesecret password self-hosted
Last synced: 21 days ago
JSON representation
Write text and show it once
- Host: GitHub
- URL: https://github.com/lekovr/showonce
- Owner: LeKovr
- License: apache-2.0
- Created: 2023-04-02T20:24:44.000Z (about 3 years ago)
- Default Branch: master
- Last Pushed: 2025-08-04T14:31:16.000Z (11 months ago)
- Last Synced: 2025-11-19T23:22:24.772Z (7 months ago)
- Topics: inmemory, onetimesecret, password, self-hosted
- Language: Go
- Homepage:
- Size: 341 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
[![Go Reference][ref1]][ref2]
[![GitHub Release][gr1]][gr2]
[![Test Coverage][cct1]][cct2]
[![Maintainability][ccm1]][ccm2]
[![GoCard][gc1]][gc2]
[![Build Status][bs1]][bs2]
[![GitHub license][gl1]][gl2]
[cct1]: https://api.codeclimate.com/v1/badges/b8061e3ed9faa6819584/test_coverage
[cct2]: https://codeclimate.com/github/LeKovr/showonce/test_coverage
[ccm1]: https://api.codeclimate.com/v1/badges/b8061e3ed9faa6819584/maintainability
[ccm2]: https://codeclimate.com/github/LeKovr/showonce/maintainability
[ref1]: https://pkg.go.dev/badge/github.com/LeKovr/showonce.svg
[ref2]: https://pkg.go.dev/github.com/LeKovr/showonce
[gc1]: https://goreportcard.com/badge/github.com/LeKovr/showonce
[gc2]: https://goreportcard.com/report/github.com/LeKovr/showonce
[bs1]: https://github.com/LeKovr/showonce/actions/workflows/docker-publish.yml/badge.svg
[bs2]: http://github.com/LeKovr/showonce/actions/workflows/docker-publish.yml
[gr1]: https://img.shields.io/github/release/LeKovr/showonce.svg
[gr2]: https://github.com/LeKovr/showonce/releases
[gl1]: https://img.shields.io/github/license/LeKovr/showonce.svg
[gl2]: https://github.com/LeKovr/showonce/blob/master/LICENSE
# Шованс (Show once)
Сервис обмена текстами, которые доступны для чтения только один раз.
## Назначение
* Предоставить **Отправителю** возможность сохранить на сервере некий текстовый **секрет** (например, пароль) с некоторым случайным **идентификатором** и периодом актуальности
* Предоставить **Получателю** возможность однократно прочитать этот секрет при выполнении условий:
* Передан идентификатор
* Период актуальности еще не закончился
* Запрос на доступ предоставляется впервые
В первой версии считается допустимым сценарий, при котором **Получатель** может получить фальшивый URL, пройти по ссылке и увидеть похожий сайт, который запросит информацию с легального, покажет ее пользователю и продублирует у себя в интересах третьих лиц.
## Диаграмма потока запросов
```mermaid
%%{init: {"theme": "neutral","flowchart": {"curve": "linear"}} }%%
flowchart TD
subgraph web[Internet]
subgraph br[Browser]
page[Static page]
js[/Javascript API / Swagger/]
end
grpcc[/GRPC-client/]
end
subgraph srv[Server]
subgraph tr[Traefik+LE]
br-- https -->httpf[HTTPS Frontend]
grpcc-- GRPC -->grpcf[GRPC Frontend]
end
subgraph so[Showonce]
httpf-- http -->http[HTTP Service]
grpcf-- h2c -->pub>GRPC Public service]
http-- public_page_req -->static[Static FS]
http-- public_api_req -->gw_pub[/Public API GRPC Gateway/]
http-- private_req -->oauth[[Oauth2]]
oauth-- page_req -->static
oauth-- api_req -->gw_priv[/Private API GRPC Gateway/]
gw_pub-- GRPC -->pub
gw_priv-- GRPC -->priv>GRPC Private service]
pub ==> st[(Storage)]
priv ==> st
end
end
classDef out stroke:#f00
classDef our stroke:#0f0
classDef ext stroke:#00f
class web out;
class tr ext;
class so our;
```
Обозначения
```mermaid
flowchart LR
a[/Generated from .proto/]
b>Showonce API]
c[(Cache)]
d[[External service]]
```
### API
Для публичной части поддерживается интерфейс GRPC. См [Описание .proto](proto/)
Также доступен [JSON RPC](static/js/service.swagger.json)
## Аргументы сервиса
См. [config.md](config.md)
## Алгоритм
**Отправитель**
* авторизуется
* открывает страницу "Создать"
* вводит атрибуты секрета
* нажимает "Сохранить"
* получает ссылку на доступ к секрету
**Получатель**
* открывает полученную ссылку
* видит название секрета, срок жизни и, если срок не истек и текст не был прочитан, ссылку "Показать"
* после нажатия "Показать" - видит сам текст
* при отправке текста адресату он удаляется на сервере и (в след версиях) формируется уведомление для **Отправителя**
**Отправитель**
* авторизуется
* на открывшейся после авторизации странице видит список своих текстов с атрибутами
* ссылка
* название
* факт показа (в след версиях - IP адресата)
* время показа (до показа - время удаления текста)
* (в след версиях) список может быть отфильтрован по
* факту показа
* значению поля "Группа"
## Карта сайта
Сервис представляет собой сайт со следующими страницами
* главная (/), содержит
* описание сервиса
* ссылку на авторизацию отправителя
* поле ввода идентификатора текста
* метаданные секрета (/?id=XXX), содержит
* все атрибуты текста (кроме контента)
* ссылку "Показать" (если не было показа, иначе - время показа)
* ссылку на показ контента (запрос POST /?id=XXX), которая возвращает
* при первом запросе существующего непрочитанного контента - текст, иначе - 404
* кабинет отправителя (/my), доступен после авторизации и содержит
* ссылку "создать"
* статистику по созданным текстам
* список созданных текстов (/my/items),содержит
* список со ссылками (/?id=XXX) на созданные пользователем тексты
* создание текста (/my/new), содержит
* форму с атрибутами текста
* кнопку "Создать"
## Дополнения
### ID
Для генерации идентификатора используется [ULID](https://github.com/oklog/ulid).
Это (в след версиях) позволит зашивать дату (создания или протухания)
### Уникальность секрета
Осуществляется хранением его sha1 (в след версиях)
### Хранение
Согласно юзкейса сервиса (передача пароля от админа к пользователю), повторное создание текста (генерация нового пароля) дешевле, чем потенциальный ущерб от компрометации этого текста.
Поэтому сам текст хранится только в памяти приложения и удаляется в случае
* показа получателю
* истечения срока хранения
* рестарта сервиса
Т.е. на диск тексты не пишутся, чтобы избежать шифрования, для которого придется где-то хранить ключ, который может утечь. Эта проблема, возможно, решаема с использованием таких техник, как [vault](https://github.com/hashicorp/vault), но для первой версии это принято нецелесообразным.
Вместе с тем, жизненный цикл у секрета и его метаданных разный, по истечении срока актуальности удаляется только сам секрет, а его метаданные хранятся до истечения Срока жизни метаданных, (в след версиях) метаданные будут храниться в персистентном хранилище.
Текущее решение: in-memory KV [zcache](https://zgo.at/zcache/v2).
### Авторизация отправителя
Цели:
* группировка текстов по автору
* исключение нецелевого использования сервиса
Первичное решение:
* gitea. **Отправитель** должен быть членом заданной в настройках организации gitea, обмен с gitea производится по протоколу OAuth2
### Атрибут "Группа"
Задается при создании секрета. Первоначальное значение - `default`, отправитель может заменить это значение.
Назначение атрибута - (в след версиях) фильтрация списка созданных секретов в кабинете.
## См. также
* [Onetime Secret](https://onetimesecret.com/)
* [Password Pusher](https://pwpush.com/)
* [Hemmelig](https://hemmelig.app/)
## TODO
* [ ] js: если после GetMeta вышел срок актуальности, GetData доступна и возвращает `{"code":2,"message":"item not found"}`
* [ ] Подключить автогенерацию тестов для [генерируемых исходников](zdoc/)
* [ ] GRPC: добавить авторизацию пользователя по токену
## Состав проекта
Language|files|blank|comment|code
:-------|-------:|-------:|-------:|-------:
Go|10|114|98|686
JavaScript|2|25|20|377
JSON|1|0|0|365
Markdown|3|129|0|364
YAML|11|36|84|334
HTML|5|1|0|261
Protocol Buffers|1|18|20|116
CSS|2|18|2|83
make|1|37|33|60
Bourne Shell|1|14|11|55
Dockerfile|1|10|0|22
Text|1|0|0|2
--------|--------|--------|--------|--------
SUM:|39|402|268|2725
## История изменений
См. [CHANGELOG](CHANGELOG.md)
## Лицензия
Copyright 2023 Aleksei Kovrizhkin
Исходный код проекта лицензирован под Apache License, Version 2.0 (the "[License](LICENSE)");