From 65cf080af7e395322b2f9faf4e118a322dcf9971 Mon Sep 17 00:00:00 2001 From: MickaelK Date: Thu, 6 Feb 2025 21:06:52 +1100 Subject: [PATCH] fix (admin): report validation error I've seen someone who mistakenly had updated their config secret key, not sure if it was done by a weird password manager or a manual action but that did corrupt their entire setup. We now make sure value like secret key get validated before saving. Didn't realise this would be necessary to start with as the appraoch has always been "with great power come great responsability" but if it can prevent catastrophic failure mode hapening by mistake, then we will be making an exception to the moto --- public/assets/components/form.js | 4 +++- public/assets/lib/rx.d.ts | 2 +- public/assets/pages/adminpage/ctrl_settings.js | 2 +- public/assets/pages/adminpage/helper_form.js | 7 +++++++ public/assets/pages/filespage/ctrl_filesystem.js | 4 ++-- server/common/config.go | 3 ++- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/public/assets/components/form.js b/public/assets/components/form.js index 9e0c95ec..ca87fc2a 100644 --- a/public/assets/components/form.js +++ b/public/assets/components/form.js @@ -54,7 +54,8 @@ export function $renderInput(options = {}) { readonly = false, path = [], datalist = null, - options = null + options = null, + pattern = null, } = props; const attrs = []; @@ -67,6 +68,7 @@ export function $renderInput(options = {}) { $node.setAttribute("autocapitalize", "off"); $node.setAttribute("spellcheck", "off"); }); + if (pattern) attrs.push(($node) => $node.setAttribute("pattern", pattern)); if (required) attrs.push(($node) => $node.setAttribute("required", "")); if (readonly) attrs.push(($node) => $node.setAttribute("disabled", "")); diff --git a/public/assets/lib/rx.d.ts b/public/assets/lib/rx.d.ts index 7623e4eb..b7ba1de6 100644 --- a/public/assets/lib/rx.d.ts +++ b/public/assets/lib/rx.d.ts @@ -74,6 +74,6 @@ export function stateMutation($node: HTMLElement, attr: string); export function preventDefault(): typeof tap; -export function onClick($node: HTMLElement): ReturnType; +export function onClick($node: HTMLElement, opts?: { preventDefault?: boolean }): ReturnType; export function onLoad($node: HTMLElement): void; \ No newline at end of file diff --git a/public/assets/pages/adminpage/ctrl_settings.js b/public/assets/pages/adminpage/ctrl_settings.js index 66cd0e2b..d4e34da8 100644 --- a/public/assets/pages/adminpage/ctrl_settings.js +++ b/public/assets/pages/adminpage/ctrl_settings.js @@ -47,7 +47,7 @@ export default AdminHOC(async function(render) { const init$ = config$.pipe( rxjs.mergeMap((formSpec) => createForm(formSpec, tmpl)), rxjs.map(($form) => [$form]), - applyMutation(qs($container, "[data-bind=\"form\"]"), "replaceChildren"), + applyMutation(qs($container, `[data-bind="form"]`), "replaceChildren"), rxjs.share(), ); effect(init$); diff --git a/public/assets/pages/adminpage/helper_form.js b/public/assets/pages/adminpage/helper_form.js index ec1931e0..958d2f27 100644 --- a/public/assets/pages/adminpage/helper_form.js +++ b/public/assets/pages/adminpage/helper_form.js @@ -33,6 +33,13 @@ export function useForm$($inputNodeList) { return rxjs.pipe( rxjs.mergeMap(() => $inputNodeList()), rxjs.mergeMap(($el) => rxjs.fromEvent($el, "input")), + rxjs.mergeMap(($el) => { + if ($el.target.checkValidity() === false) { + $el.target.reportValidity(); + return rxjs.EMPTY; + } + return rxjs.of($el); + }), rxjs.map((e) => ({ name: e.target.getAttribute("name"), value: (function() { diff --git a/public/assets/pages/filespage/ctrl_filesystem.js b/public/assets/pages/filespage/ctrl_filesystem.js index 303c75f0..8955a3ce 100644 --- a/public/assets/pages/filespage/ctrl_filesystem.js +++ b/public/assets/pages/filespage/ctrl_filesystem.js @@ -61,9 +61,9 @@ export default async function(render) { rxjs.startWith(null), rxjs.map(() => { if ($list.getAttribute("data-type") === "grid") { - return gridSize($list.clientWidth, document.body.clientWidth) + return gridSize($list.clientWidth, document.body.clientWidth); } - return -1; + return 0; }), rxjs.distinctUntilChanged(), rxjs.skip(1), diff --git a/server/common/config.go b/server/common/config.go index 82f910b1..b99ed05f 100644 --- a/server/common/config.go +++ b/server/common/config.go @@ -38,6 +38,7 @@ type FormElement struct { Type string `json:"type"` Description string `json:"description,omitempty"` Placeholder string `json:"placeholder,omitempty"` + Pattern string `json:"pattern,omitempty"` Opts []string `json:"options,omitempty"` Target []string `json:"target,omitempty"` ReadOnly bool `json:"readonly"` @@ -67,7 +68,7 @@ func NewConfiguration() Configuration { FormElement{Name: "name", Type: "text", Default: "Filestash", Description: "Name has shown in the UI", Placeholder: "Default: \"Filestash\""}, FormElement{Name: "port", Type: "number", Default: 8334, Description: "Port on which the application is available.", Placeholder: "Default: 8334"}, FormElement{Name: "host", Type: "text", Description: "The host people need to use to access this server", Placeholder: "Eg: \"demo.filestash.app\""}, - FormElement{Name: "secret_key", Type: "password", Description: "The key that's used to encrypt and decrypt content. Update this settings will invalidate existing user sessions and shared links, use with caution!"}, + FormElement{Name: "secret_key", Type: "password", Required: true, Pattern: "[a-zA-Z0-9]{16}", Description: "The key that's used to encrypt and decrypt content. Update this settings will invalidate existing user sessions and shared links, use with caution!"}, FormElement{Name: "force_ssl", Type: "boolean", Description: "Enable the web security mechanism called 'Strict Transport Security'"}, FormElement{Name: "editor", Type: "select", Default: "emacs", Opts: []string{"base", "emacs", "vim"}, Description: "Keybinding to be use in the editor. Default: \"emacs\""}, FormElement{Name: "fork_button", Type: "boolean", Default: true, Description: "Display the fork button in the login screen"},