diff --git a/History.md b/History.md index 46b9956..f41a95f 100644 --- a/History.md +++ b/History.md @@ -143,7 +143,7 @@ * Only mention tool name in footer (#71) * Replace redis client, move expiry into creation interface -With this release an old migration was removed and in case you are still using the `REDIS_EXPIRY` environment variable you need to switch to `SECRET_EXPIRY`. Also with the new redis client you might need to adjust the username in your `REDIS_URL` to a proper ACL username (or enable legacy auth in Redis) - see the README for the `REDIS_URL` format. +With this release an old migration was removed and in case you are still using the `REDIS_EXPIRY` environment variable you need to switch to `MAX_SECRET_EXPIRY`. Also with the new redis client you might need to adjust the username in your `REDIS_URL` to a proper ACL username (or enable legacy auth in Redis) - see the README for the `REDIS_URL` format. # 1.0.0 / 2023-04-14 diff --git a/README.md b/README.md index 1db58f8..212b68b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ For a better setup you can choose the backend which is used to store the secrets (pre Redis v6 use `auth` as user, afterwards use a user available in your ACLs) - `REDIS_KEY` - Key prefix to store the keys under (Default `io.luzifer.ots`) - Common options - - `SECRET_EXPIRY` - Expiry of the keys in seconds (Default `0` = no expiry) + - `MAX_SECRET_EXPIRY` - Expiry of the keys in seconds (Default `0` = no expiry) ### Customization diff --git a/api.go b/api.go index 82de183..24ff2bd 100644 --- a/api.go +++ b/api.go @@ -62,13 +62,17 @@ func (a apiServer) handleCreate(res http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(res, r.Body, cust.MaxSecretSize*2) //nolint:gomnd } + if cfg.DefaultSecretExpiry == 0 && cfg.MaxSecretExpiry > 0 { + cfg.DefaultSecretExpiry = cfg.MaxSecretExpiry + } + var ( - expiry = cfg.SecretExpiry + expiry = cfg.DefaultSecretExpiry secret string ) if !cust.DisableExpiryOverride { - if ev, err := strconv.ParseInt(r.URL.Query().Get("expire"), 10, 64); err == nil && (ev < expiry || cfg.SecretExpiry == 0) { + if ev, err := strconv.ParseInt(r.URL.Query().Get("expire"), 10, 64); err == nil && (ev <= cfg.MaxSecretExpiry || cfg.MaxSecretExpiry == 0) { expiry = ev } } diff --git a/docker-compose.yml b/docker-compose.yml index 048dca3..87e4140 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: # See README for details REDIS_URL: redis://redis:6379/0 # 168h = 1w - SECRET_EXPIRY: "604800" + MAX_SECRET_EXPIRY: "604800" # "mem" or "redis" (See README) STORAGE_TYPE: redis depends_on: diff --git a/docs/k8s_example.yml b/docs/k8s_example.yml index 78905ad..e221c31 100644 --- a/docs/k8s_example.yml +++ b/docs/k8s_example.yml @@ -118,7 +118,7 @@ spec: value: tcp://ots-redis:6379 - name: REDIS_KEY value: ots - - name: SECRET_EXPIRY + - name: MAX_SECRET_EXPIRY value: "172800" volumeMounts: - mountPath: /custom diff --git a/frontend/index.html b/frontend/index.html index 8e9e77a..3ded167 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -47,6 +47,8 @@ // Template variable from Golang process const maxSecretExpire = {{ .MaxSecretExpiry }} + const defaultSecretExpire = {{ .DefaultSecretExpiry }} + const version = "{{ .Version }}" window.OTSCustomize = JSON.parse('{{ .Customize.ToJSON }}') window.useFormalLanguage = {{ .Customize.UseFormalLanguage | mustToJson }} diff --git a/i18n.yaml b/i18n.yaml index 1bed2a3..f9ba80c 100644 --- a/i18n.yaml +++ b/i18n.yaml @@ -12,7 +12,7 @@ reference: btn-reveal-secret: Show me the secret! btn-reveal-secret-processing: Secret is being decrypted… btn-show-explanation: How does this work? - expire-default: Default Expiry + expire-default: Default Expiry ({duration}) expire-n-days: '{n} day | {n} days' expire-n-hours: '{n} hour | {n} hours' expire-n-minutes: '{n} minute | {n} minutes' @@ -35,6 +35,7 @@ reference: text-invalid-files-selected: At least one of the selected files is not allowed as an attachment. text-max-filesize: 'Maximum size: {maxSize}' text-max-filesize-exceeded: 'The file(s) you chose are too big to attach: {curSize} / {maxSize}' + text-never: never text-powered-by: Powered by text-pre-reveal-hint: To reveal the secret click this button but be aware doing so will destroy the secret. You can only view it once! text-pre-url: 'Your secret was created and stored using this URL:' @@ -57,7 +58,7 @@ translations: btn-reveal-secret: Mostra'm el secret! btn-reveal-secret-processing: El secret s'està desxifrant... btn-show-explanation: Com funciona? - expire-default: Caducitat predeterminada + expire-default: Caducitat predeterminada ({duration}) expire-n-days: '{n} dia | {n} dies' expire-n-hours: '{n} hora | {n} hores' expire-n-minutes: '{n} minut | {n} minuts' @@ -79,6 +80,7 @@ translations: text-hint-burned: Atenció: Només veuràs això una vegada. Quan recarregues la pàgina, el secret desapareixerà, així que copia'l ja… text-max-filesize: 'Mida màxima: {maxSize}' text-max-filesize-exceeded: 'Els arxius seleccionats són massa grans per adjuntar-los: {curSize} / {maxSize}' + text-never: Mai text-powered-by: Funciona amb text-pre-reveal-hint: Per a mostrar el secret prem aquest botó, però tingues en compte que en fer-ho es destruirà. Només pots veure'l una vegada! text-pre-url: 'El teu secret ha sigut creat i emmagatzemat en el següent enllaç:' @@ -101,7 +103,7 @@ translations: btn-reveal-secret: Zeig mir das Secret! btn-reveal-secret-processing: Secret wird entschlüsselt… btn-show-explanation: Wie funktioniert das? - expire-default: Server-Standard + expire-default: Server-Standard ({duration}) expire-n-days: '{n} Tag | {n} Tage' expire-n-hours: '{n} Stunde | {n} Stunden' expire-n-minutes: '{n} Minute | {n} Minuten' @@ -124,6 +126,7 @@ translations: text-invalid-files-selected: Mindestens eine der ausgewählten Dateien ist nicht als Anhang erlaubt. text-max-filesize: 'Maximale Größe: {maxSize}' text-max-filesize-exceeded: 'Die ausgewählten Dateien übersteigen die maximale Größe: {curSize} / {maxSize}' + text-never: Nie text-powered-by: Läuft mit text-pre-reveal-hint: Um das Secret anzuzeigen klicke diesen Button aber denk dran, dass das Secret nur einmal angezeigt und dabei gelöscht wird. text-pre-url: 'Dein Secret wurde angelegt und unter folgender URL gespeichert:' @@ -163,7 +166,7 @@ translations: btn-reveal-secret: ¡Muéstrame el secreto! btn-reveal-secret-processing: El secreto se está descifrando... btn-show-explanation: ¿Cómo funciona? - expire-default: Caducidad predeterminada + expire-default: Caducidad predeterminada ({duration}) expire-n-days: '{n} día | {n} días' expire-n-hours: '{n} hora | {n} horas' expire-n-minutes: '{n} minuto | {n} minutos' @@ -185,6 +188,7 @@ translations: text-hint-burned: Atención: Solo verás esto una vez. En cuanto recargues la página, el secreto desaparecerá, así que cópialo ya… text-max-filesize: 'Tamaño máximo: {maxSize}' text-max-filesize-exceeded: 'Los archivos seleccionados son demasiado grandes para adjuntarlos: {curSize} / {maxSize}' + text-never: Nunca text-powered-by: Funciona con text-pre-reveal-hint: Para mostrar el secreto pulsa este botón, pero ten en cuenta que al hacerlo se destruirá. ¡Solo puedes verlo una vez! text-pre-url: 'Tu secreto ha sido creado y almacenado en el siguiente enlace:' @@ -219,6 +223,7 @@ translations: label-secret-data: 'Données secrètes:' text-burn-hint: Attention de ne pas ouvrir cette URL vous-même, cela détruirait le secret. Fournissez-la à quelqu'un d'autre! text-hint-burned: Attention: Vous ne pouvez consulter ce contenu qu'une fois. Le secret sera détruit dès que vous rechargez la page, donc copiez le maintenant… + text-never: Jamais text-powered-by: Propulsé par text-pre-reveal-hint: Pour afficher le secret, cliquez sur ce bouton, mais soyez conscient que cela le détruira. Vous ne pouvez l'afficher qu'une fois! text-pre-url: 'Votre secret a été créé et stocké à cette URL:' @@ -253,6 +258,7 @@ translations: label-secret-data: 'Ziņa:' text-burn-hint: Lūdzu atceries neatvērt saiti pats, jo tad ziņa tiks dzēsta. Nodod saiti ziņas saņēmējam! text-hint-burned: Uzmanību: Ziņa tiek parādīta tikai vienu reizi. Līdzko lapa tiks pārlādēta, ziņa būs neatgriezeniski zaudēta, tāpēc nepieciešamības gadījumā nokopē to tagad… + text-never: Nekad text-powered-by: Darbina text-pre-reveal-hint: Lai parādītu ziņu nospied šo pogu, bet rēķinies ar to, ka pēc apskates ziņa vairs nebūs pieejama. To var atvērt tikai vienreiz! text-pre-url: 'Ziņa ir nošifrēta un ir atverama šajā adresē:' @@ -274,7 +280,7 @@ translations: btn-reveal-secret: Toon mij de vertrouwelijke info! btn-reveal-secret-processing: Vertrouwelijke info wordt ontsleuteld... btn-show-explanation: Hoe werkt dit? - expire-default: Standaard termijn + expire-default: Standaard termijn ({duration}) expire-n-days: '{n} dag | {n} dagen' expire-n-hours: '{n} uur | {n} uur' expire-n-minutes: '{n} minuut | {n} minuten' @@ -296,6 +302,7 @@ translations: text-hint-burned: Opgelet: Je ziet deze informatie alleen nu. Je kan het niet meer opnieuw opvragen als je de pagina verlaat. text-max-filesize: 'Maximum grootte: {maxSize}' text-max-filesize-exceeded: 'De bestanden die je toevoegde zijn te groot: {curSize} / {maxSize}' + text-never: Nooit text-powered-by: Mogelijk gemaakt door text-pre-reveal-hint: 'Gebruik deze knop om de vertrouwelijke info op te halen. Let op: Je kan dit slechts eenmaal doen!' text-pre-url: 'Je vertrouwelijke informatie kan opgevraagd worden via deze URL:' @@ -318,7 +325,7 @@ translations: btn-reveal-secret: Pokaż mi sekret! btn-reveal-secret-processing: Sekret jest odszyfrowywany... btn-show-explanation: Jak to działa? - expire-default: Domyślne wygasanie + expire-default: Domyślne wygasanie ({duration}) expire-n-days: '{n} dzień | {n} dni' expire-n-hours: '{n} godzina | {n} godzin(y)' expire-n-minutes: '{n} minuta | {n} minut(y)' @@ -341,6 +348,7 @@ translations: text-invalid-files-selected: Co najmniej jeden z załączonych plików nie jest dopuszczalny jako załącznik. text-max-filesize: 'Maksymalny rozmiar: {maxSize}' text-max-filesize-exceeded: 'Wybrane załączniki przekraczają maksymalny rozmiar: {curSize} / {maxSize}' + text-never: Nigdy text-powered-by: Obsługiwane przez text-pre-reveal-hint: Aby odsłonić sekret, naciśnij ten przycisk, jednak wiedz, że to zniszczy sekret. Możesz go zobaczyć tylko raz! text-pre-url: 'Twój sekret został stworzony i zachowany pod tym adresem URL:' @@ -375,6 +383,7 @@ translations: label-secret-data: 'Informação secreta:' text-burn-hint: Importante você lembrar de não acessar esta URL, pois isto irá indisponibilizar o segredo. Apenas encaminhe para outra pessoa! text-hint-burned: 'Atenção: Você está vendo esta informação apenas uma vez. Logo que você recarregar a página o segredo ficará indisponível. É recomendado que você copie a informação agora…' + text-never: Nunca text-powered-by: Powered by text-pre-reveal-hint: Para revelar o segredo clique neste botão, mas lembre-se que esta ação vai destruir o segredo. Você só pode ver uma única vez! text-pre-url: 'Seu segredo foi criado e armazenado na seguinte URL:' @@ -397,7 +406,7 @@ translations: btn-reveal-secret: Показать секрет! btn-reveal-secret-processing: Секрет декодируется ... btn-show-explanation: Как это работает? - expire-default: Значение по умолчанию до исчезновения + expire-default: Значение по умолчанию до исчезновения ({duration}) expire-n-days: '{n} день | {n} дней' expire-n-hours: '{n} час | {n} часов' expire-n-minutes: '{n} минут | {n} минут' @@ -419,6 +428,7 @@ translations: text-hint-burned: Внимание: Секрет будет показан только один раз. Как только вы перезагрузите страницу, секрет исчезнет, скопируйте его незамедлительно… text-max-filesize: 'Максимальный размер: {maxSize}' text-max-filesize-exceeded: 'Ваш файл(ы) слишком большой для прикрепления к секрету: {curSize} / {maxSize}' + text-never: Никогда text-powered-by: Основан text-pre-reveal-hint: Чтобы раскрыть секрет, нажмите эту кнопку, но имейте в виду, что это приведет к уничтожению секрета. Вы можете просмотреть его только один раз! text-pre-url: 'Ваш секрет создан и сохранён, его URL:' @@ -440,7 +450,7 @@ translations: btn-reveal-secret: Visa mig hemligheten! btn-reveal-secret-processing: Hemlighet håller på att dekrypteras.. btn-show-explanation: Hur fungerar detta? - expire-default: Standard utgångstid + expire-default: Standard utgångstid ({duration}) expire-n-days: '{n} dag | {n} dagar' expire-n-hours: '{n} timme | {n} timmar' expire-n-minutes: '{n} minut | {n} minuter' @@ -462,6 +472,7 @@ translations: text-hint-burned: Observera: Du kan endast se denna sida en gång. Så fort du laddar om sidan kommer hemligheten att försvinna så kopiera den nu… text-max-filesize: 'Maximal storlek: {maxSize}' text-max-filesize-exceeded: 'Filerna du valt är för stora för att kunna bifogas: {curSize} / {maxSize}' + text-never: Aldrig text-powered-by: Drivs av text-pre-reveal-hint: För att visa hemligheten klicka på denna knapp. Var medveten om att när du gör det kommer hemligheten att förbrukas, du kan endast se den en gång! text-pre-url: 'Din hemlighet har skapats och lagrats med denna URL:' @@ -496,6 +507,7 @@ translations: label-secret-data: 'Sır bilgisi:' text-burn-hint: Lütfen linki kendiniz acmayın, bu sırrın silinmesine neden olur. Linki sadece alıcıya gönderin! text-hint-burned: 'Dikkat: Bunu sadece bir kez göreceksiniz. Sayfayı güncellediğinizde yada kapattiğınızda sır kaybolacaktır, belkide şimdi sırrı kopyalamanız akıllıca olacaktır…' + text-never: Asla text-powered-by: Tarafından desteklenmektedir text-pre-reveal-hint: Sırrı görmek için bu düğmeye tıklayın, ama bunu yaptıktan sonra sırrın silineceğini unutmayın. Bunu sadece bir kez görebilirsin! text-pre-url: 'Sırrınız oluşturuldu ve bu link kullanılarak kaydedildi:' @@ -517,7 +529,7 @@ translations: btn-reveal-secret: Показати мені секрет! btn-reveal-secret-processing: Секрет розшифровується… btn-show-explanation: Як це працює? - expire-default: Термін дії за замовчуванням + expire-default: Термін дії за замовчуванням ({duration}) expire-n-days: '{n} день | {n} днів' expire-n-hours: '{n} година | {n} годин' expire-n-minutes: '{n} хвилина | {n} хвилин' @@ -539,6 +551,7 @@ translations: text-hint-burned: Увага: Ви бачите це лише раз. Щойно ви перезавантажите сторінку, секрет зникне, тому, можливо, скопіюйте його зараз… text-max-filesize: 'Максимальний розмір: {maxSize}' text-max-filesize-exceeded: 'Вибрані вами файли завеликі, щоб вкласти: {curSize} / {maxSize}' + text-never: Ніколи text-powered-by: Powered by text-pre-reveal-hint: Щоб розкрити секрет, натисніть цю кнопку, але майте на увазі, що це знищить секрет. Ви можете переглянути його лише один раз! text-pre-url: 'Ваш секрет було створено та збережено за допомогою цієї URL-адреси:' @@ -560,7 +573,7 @@ translations: btn-reveal-secret: 向我展示机密 btn-reveal-secret-processing: 机密正在解密中… btn-show-explanation: 它如何运作? - expire-default: 默认过期时间 + expire-default: 默认过期时间 ({duration}) expire-n-days: '{n} 天 | {n} 天' expire-n-hours: '{n} 小时 | {n} 小时' expire-n-minutes: '{n} 分钟 | {n} 分钟' @@ -583,6 +596,7 @@ translations: text-invalid-files-selected: 选中了至少一个不允许被作为附件的文件。 text-max-filesize: 附加文件大小上限:{maxSize} text-max-filesize-exceeded: 您选择的文件过大,无法附加:{curSize} / {maxSize} + text-never: 绝不 text-powered-by: Powered by text-pre-reveal-hint: 点击按钮即可查看机密,但请注意该操作会立即销毁机密,而且您也只能查看一次! text-pre-url: 您的机密已创建,访问链接如下: diff --git a/main.go b/main.go index 404d34d..e92634b 100644 --- a/main.go +++ b/main.go @@ -27,12 +27,13 @@ const scriptNonceSize = 32 var ( cfg struct { - Customize string `flag:"customize" default:"" description:"Customize-File to load"` - Listen string `flag:"listen" default:":3000" description:"IP/Port to listen on"` - LogLevel string `flag:"log-level" default:"info" description:"Set log level (debug, info, warning, error)"` - SecretExpiry int64 `flag:"secret-expiry" default:"0" description:"Maximum expiry of the stored secrets in seconds"` - StorageType string `flag:"storage-type" default:"mem" description:"Storage to use for putting secrets to" validate:"nonzero"` - VersionAndExit bool `flag:"version" default:"false" description:"Print version information and exit"` + Customize string `flag:"customize" default:"" description:"Customize-File to load"` + Listen string `flag:"listen" default:":3000" description:"IP/Port to listen on"` + LogLevel string `flag:"log-level" default:"info" description:"Set log level (debug, info, warning, error)"` + MaxSecretExpiry int64 `flag:"max-secret-expiry" default:"0" description:"Maximum expiry of the stored secrets in seconds"` + DefaultSecretExpiry int64 `flag:"default-secret-expiry" default:"0" description:"Default expiry of the stored secrets in seconds"` + StorageType string `flag:"storage-type" default:"mem" description:"Storage to use for putting secrets to" validate:"nonzero"` + VersionAndExit bool `flag:"version" default:"false" description:"Print version information and exit"` } assets file_helpers.FSStack @@ -154,8 +155,9 @@ func main() { // Start server logrus.WithFields(logrus.Fields{ - "secret_expiry": time.Duration(cfg.SecretExpiry) * time.Second, - "version": version, + "max_secret_expiry": time.Duration(cfg.MaxSecretExpiry) * time.Second, + "default_secret_expiry": time.Duration(cfg.DefaultSecretExpiry) * time.Second, + "version": version, }).Info("ots started") if err = server.ListenAndServe(); err != nil { @@ -209,15 +211,17 @@ func handleIndex(w http.ResponseWriter, _ *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") if err := indexTpl.Execute(w, struct { - Customize customization.Customize - InlineContentNonce string - MaxSecretExpiry int64 - Version string + Customize customization.Customize + InlineContentNonce string + MaxSecretExpiry int64 + DefaultSecretExpiry int64 + Version string }{ - Customize: cust, - InlineContentNonce: inlineContentNonceStr, - MaxSecretExpiry: cfg.SecretExpiry, - Version: version, + Customize: cust, + InlineContentNonce: inlineContentNonceStr, + MaxSecretExpiry: cfg.MaxSecretExpiry, + DefaultSecretExpiry: cfg.DefaultSecretExpiry, + Version: version, }); err != nil { http.Error(w, errors.Wrap(err, "executing template").Error(), http.StatusInternalServerError) return diff --git a/src/components/create.vue b/src/components/create.vue index 1ed40c6..c9af529 100644 --- a/src/components/create.vue +++ b/src/components/create.vue @@ -114,6 +114,14 @@