From d4dedecfd13a6c294afaaa6393238f084cf30ed7 Mon Sep 17 00:00:00 2001 From: trendschau Date: Mon, 18 Dec 2023 12:58:55 +0100 Subject: [PATCH 01/20] V2.1.0 - New reference feature in meta tabs --- .gitignore | 7 ++-- content/00-welcome/00-setup-your-website.yaml | 1 + content/00-welcome/01-manage-access.yaml | 16 -------- ...2-write-content.md => 01-write-content.md} | 0 ...1-manage-access.md => 02-manage-access.md} | 0 content/00-welcome/02-write-content.yaml | 10 ----- content/00-welcome/03-get-help.yaml | 2 +- content/00-welcome/04-markdown-test.yaml | 2 +- data/navigation/navi-draft.txt | 2 +- data/navigation/navi-extended.txt | 14 +++---- .../Controllers/ControllerWebFrontend.php | 38 +++++++++++++++++++ system/typemill/settings/metatabs.yaml | 31 ++++++++------- 12 files changed, 70 insertions(+), 53 deletions(-) delete mode 100644 content/00-welcome/01-manage-access.yaml rename content/00-welcome/{02-write-content.md => 01-write-content.md} (100%) rename content/00-welcome/{01-manage-access.md => 02-manage-access.md} (100%) delete mode 100644 content/00-welcome/02-write-content.yaml diff --git a/.gitignore b/.gitignore index 51f80f8..bb3ebe2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ cache/sitemap.xml content/index.yaml content/00-welcome/index.yaml -content/00-welcome/00-setup.yaml +content/00-welcome/00-setup-your-website.yaml content/00-welcome/01-write-content.yaml -content/00-welcome/02-get-help.yaml -content/00-welcome/03-markdown-test.yaml +content/00-welcome/02-manage-access.yaml +content/00-welcome/03-get-help.yaml +content/00-welcome/04-markdown-test.yaml content/01-cyanine-theme/index.yaml content/01-cyanine-theme/00-landingpage.yaml content/01-cyanine-theme/01-colors-and-fonts.yaml diff --git a/content/00-welcome/00-setup-your-website.yaml b/content/00-welcome/00-setup-your-website.yaml index 38ccecb..8940a3d 100644 --- a/content/00-welcome/00-setup-your-website.yaml +++ b/content/00-welcome/00-setup-your-website.yaml @@ -9,3 +9,4 @@ meta: time: 15-18-48 hide: false noindex: false + author: Sebastian diff --git a/content/00-welcome/01-manage-access.yaml b/content/00-welcome/01-manage-access.yaml deleted file mode 100644 index d11743e..0000000 --- a/content/00-welcome/01-manage-access.yaml +++ /dev/null @@ -1,16 +0,0 @@ -meta: - navtitle: 'manage access' - title: 'Manage access' - description: ' Restrict Access for the Website' - heroimage: null - heroimagealt: null - owner: Sebastian - author: null - allowedrole: null - alloweduser: null - manualdate: null - modified: '2023-05-06' - created: '2023-06-12' - time: 22-36-36 - hide: false - noindex: false diff --git a/content/00-welcome/02-write-content.md b/content/00-welcome/01-write-content.md similarity index 100% rename from content/00-welcome/02-write-content.md rename to content/00-welcome/01-write-content.md diff --git a/content/00-welcome/01-manage-access.md b/content/00-welcome/02-manage-access.md similarity index 100% rename from content/00-welcome/01-manage-access.md rename to content/00-welcome/02-manage-access.md diff --git a/content/00-welcome/02-write-content.yaml b/content/00-welcome/02-write-content.yaml deleted file mode 100644 index 05a410b..0000000 --- a/content/00-welcome/02-write-content.yaml +++ /dev/null @@ -1,10 +0,0 @@ -meta: - navtitle: 'write content' - title: 'Write Content' - description: 'Typemill provides easy and intuitive authoring tools and we work hard to create a good author experience. With the interactive navigation you can create pages' - owner: Sebastian - modified: '2023-05-11' - created: '2023-06-12' - time: 22-09-48 - hide: false - noindex: false diff --git a/content/00-welcome/03-get-help.yaml b/content/00-welcome/03-get-help.yaml index efc891b..b6ee53a 100644 --- a/content/00-welcome/03-get-help.yaml +++ b/content/00-welcome/03-get-help.yaml @@ -5,7 +5,7 @@ meta: heroimage: null heroimagealt: null owner: trendschau - author: null + author: Sebastian allowedrole: null alloweduser: null manualdate: null diff --git a/content/00-welcome/04-markdown-test.yaml b/content/00-welcome/04-markdown-test.yaml index 043ea59..d8f6279 100644 --- a/content/00-welcome/04-markdown-test.yaml +++ b/content/00-welcome/04-markdown-test.yaml @@ -5,7 +5,7 @@ meta: heroimage: null heroimagealt: null owner: Sebastian - author: null + author: Sebastian allowedrole: null alloweduser: null manualdate: null diff --git a/data/navigation/navi-draft.txt b/data/navigation/navi-draft.txt index 82a3b37..84fcabc 100644 --- a/data/navigation/navi-draft.txt +++ b/data/navigation/navi-draft.txt @@ -1 +1 @@ -a:2:{i:0;O:8:"stdClass":22:{s:12:"originalName";s:10:"00-welcome";s:11:"elementType";s:6:"folder";s:8:"contains";s:5:"pages";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:7:"welcome";s:4:"slug";s:7:"welcome";s:4:"path";s:11:"/00-welcome";s:15:"pathWithoutType";s:17:"/00-welcome/index";s:9:"urlRelWoF";s:8:"/welcome";s:6:"urlRel";s:17:"/typemill/welcome";s:6:"urlAbs";s:33:"http://localhost/typemill/welcome";s:3:"key";i:0;s:7:"keyPath";i:0;s:12:"keyPathArray";a:1:{i:0;s:1:"0";}s:7:"chapter";i:1;s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:13:"folderContent";a:5:{i:0;O:8:"stdClass":20:{s:12:"originalName";s:24:"00-setup-your-website.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:18:"setup your website";s:4:"slug";s:18:"setup-your-website";s:4:"path";s:36:"/00-welcome/00-setup-your-website.md";s:15:"pathWithoutType";s:33:"/00-welcome/00-setup-your-website";s:3:"key";i:0;s:7:"keyPath";s:3:"0.0";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"0";}s:7:"chapter";s:3:"1.1";s:9:"urlRelWoF";s:27:"/welcome/setup-your-website";s:6:"urlRel";s:36:"/typemill/welcome/setup-your-website";s:6:"urlAbs";s:52:"http://localhost/typemill/welcome/setup-your-website";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:1;O:8:"stdClass":20:{s:12:"originalName";s:19:"01-manage-access.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:13:"manage access";s:4:"slug";s:13:"manage-access";s:4:"path";s:31:"/00-welcome/01-manage-access.md";s:15:"pathWithoutType";s:28:"/00-welcome/01-manage-access";s:3:"key";i:1;s:7:"keyPath";s:3:"0.1";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"1";}s:7:"chapter";s:3:"1.2";s:9:"urlRelWoF";s:22:"/welcome/manage-access";s:6:"urlRel";s:31:"/typemill/welcome/manage-access";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/manage-access";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:2;O:8:"stdClass":20:{s:12:"originalName";s:19:"02-write-content.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"02";s:4:"name";s:13:"write content";s:4:"slug";s:13:"write-content";s:4:"path";s:31:"/00-welcome/02-write-content.md";s:15:"pathWithoutType";s:28:"/00-welcome/02-write-content";s:3:"key";i:2;s:7:"keyPath";s:3:"0.2";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"2";}s:7:"chapter";s:3:"1.3";s:9:"urlRelWoF";s:22:"/welcome/write-content";s:6:"urlRel";s:31:"/typemill/welcome/write-content";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/write-content";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:3;O:8:"stdClass":20:{s:12:"originalName";s:14:"03-get-help.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"03";s:4:"name";s:8:"get help";s:4:"slug";s:8:"get-help";s:4:"path";s:26:"/00-welcome/03-get-help.md";s:15:"pathWithoutType";s:23:"/00-welcome/03-get-help";s:3:"key";i:3;s:7:"keyPath";s:3:"0.3";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"3";}s:7:"chapter";s:3:"1.4";s:9:"urlRelWoF";s:17:"/welcome/get-help";s:6:"urlRel";s:26:"/typemill/welcome/get-help";s:6:"urlAbs";s:42:"http://localhost/typemill/welcome/get-help";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:4;O:8:"stdClass":20:{s:12:"originalName";s:19:"04-markdown-test.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"04";s:4:"name";s:13:"markdown test";s:4:"slug";s:13:"markdown-test";s:4:"path";s:31:"/00-welcome/04-markdown-test.md";s:15:"pathWithoutType";s:28:"/00-welcome/04-markdown-test";s:3:"key";i:4;s:7:"keyPath";s:3:"0.4";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"4";}s:7:"chapter";s:3:"1.5";s:9:"urlRelWoF";s:22:"/welcome/markdown-test";s:6:"urlRel";s:31:"/typemill/welcome/markdown-test";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/markdown-test";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}}s:7:"noindex";b:0;}i:1;O:8:"stdClass":22:{s:12:"originalName";s:16:"01-cyanine-theme";s:11:"elementType";s:6:"folder";s:8:"contains";s:5:"pages";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:13:"cyanine theme";s:4:"slug";s:13:"cyanine-theme";s:4:"path";s:17:"/01-cyanine-theme";s:15:"pathWithoutType";s:23:"/01-cyanine-theme/index";s:9:"urlRelWoF";s:14:"/cyanine-theme";s:6:"urlRel";s:23:"/typemill/cyanine-theme";s:6:"urlAbs";s:39:"http://localhost/typemill/cyanine-theme";s:3:"key";i:1;s:7:"keyPath";i:1;s:12:"keyPathArray";a:1:{i:0;s:1:"1";}s:7:"chapter";i:2;s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:13:"folderContent";a:4:{i:0;O:8:"stdClass":20:{s:12:"originalName";s:17:"00-landingpage.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:11:"landingpage";s:4:"slug";s:11:"landingpage";s:4:"path";s:35:"/01-cyanine-theme/00-landingpage.md";s:15:"pathWithoutType";s:32:"/01-cyanine-theme/00-landingpage";s:3:"key";i:0;s:7:"keyPath";s:3:"1.0";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"0";}s:7:"chapter";s:3:"2.1";s:9:"urlRelWoF";s:26:"/cyanine-theme/landingpage";s:6:"urlRel";s:35:"/typemill/cyanine-theme/landingpage";s:6:"urlAbs";s:51:"http://localhost/typemill/cyanine-theme/landingpage";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:1;}i:1;O:8:"stdClass":20:{s:12:"originalName";s:22:"01-colors-and-fonts.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:16:"colors and fonts";s:4:"slug";s:16:"colors-and-fonts";s:4:"path";s:40:"/01-cyanine-theme/01-colors-and-fonts.md";s:15:"pathWithoutType";s:37:"/01-cyanine-theme/01-colors-and-fonts";s:3:"key";i:1;s:7:"keyPath";s:3:"1.1";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"1";}s:7:"chapter";s:3:"2.2";s:9:"urlRelWoF";s:31:"/cyanine-theme/colors-and-fonts";s:6:"urlRel";s:40:"/typemill/cyanine-theme/colors-and-fonts";s:6:"urlAbs";s:56:"http://localhost/typemill/cyanine-theme/colors-and-fonts";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:2;O:8:"stdClass":20:{s:12:"originalName";s:12:"02-footer.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"02";s:4:"name";s:6:"footer";s:4:"slug";s:6:"footer";s:4:"path";s:30:"/01-cyanine-theme/02-footer.md";s:15:"pathWithoutType";s:27:"/01-cyanine-theme/02-footer";s:3:"key";i:2;s:7:"keyPath";s:3:"1.2";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"2";}s:7:"chapter";s:3:"2.3";s:9:"urlRelWoF";s:21:"/cyanine-theme/footer";s:6:"urlRel";s:30:"/typemill/cyanine-theme/footer";s:6:"urlAbs";s:46:"http://localhost/typemill/cyanine-theme/footer";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:3;O:8:"stdClass":20:{s:12:"originalName";s:22:"03-content-elements.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"03";s:4:"name";s:16:"content elements";s:4:"slug";s:16:"content-elements";s:4:"path";s:40:"/01-cyanine-theme/03-content-elements.md";s:15:"pathWithoutType";s:37:"/01-cyanine-theme/03-content-elements";s:3:"key";i:3;s:7:"keyPath";s:3:"1.3";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"3";}s:7:"chapter";s:3:"2.4";s:9:"urlRelWoF";s:31:"/cyanine-theme/content-elements";s:6:"urlRel";s:40:"/typemill/cyanine-theme/content-elements";s:6:"urlAbs";s:56:"http://localhost/typemill/cyanine-theme/content-elements";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}}s:7:"noindex";b:0;}} \ No newline at end of file +a:2:{i:0;O:8:"stdClass":22:{s:12:"originalName";s:10:"00-welcome";s:11:"elementType";s:6:"folder";s:8:"contains";s:5:"pages";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:7:"welcome";s:4:"slug";s:7:"welcome";s:4:"path";s:11:"/00-welcome";s:15:"pathWithoutType";s:17:"/00-welcome/index";s:9:"urlRelWoF";s:8:"/welcome";s:6:"urlRel";s:17:"/typemill/welcome";s:6:"urlAbs";s:33:"http://localhost/typemill/welcome";s:3:"key";i:0;s:7:"keyPath";i:0;s:12:"keyPathArray";a:1:{i:0;s:1:"0";}s:7:"chapter";i:1;s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:13:"folderContent";a:5:{i:0;O:8:"stdClass":20:{s:12:"originalName";s:24:"00-setup-your-website.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:18:"setup your website";s:4:"slug";s:18:"setup-your-website";s:4:"path";s:36:"/00-welcome/00-setup-your-website.md";s:15:"pathWithoutType";s:33:"/00-welcome/00-setup-your-website";s:3:"key";i:0;s:7:"keyPath";s:3:"0.0";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"0";}s:7:"chapter";s:3:"1.1";s:9:"urlRelWoF";s:27:"/welcome/setup-your-website";s:6:"urlRel";s:36:"/typemill/welcome/setup-your-website";s:6:"urlAbs";s:52:"http://localhost/typemill/welcome/setup-your-website";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:1;O:8:"stdClass":20:{s:12:"originalName";s:19:"01-write-content.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:13:"write content";s:4:"slug";s:13:"write-content";s:4:"path";s:31:"/00-welcome/01-write-content.md";s:15:"pathWithoutType";s:28:"/00-welcome/01-write-content";s:3:"key";i:1;s:7:"keyPath";s:3:"0.1";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"1";}s:7:"chapter";s:3:"1.2";s:9:"urlRelWoF";s:22:"/welcome/write-content";s:6:"urlRel";s:31:"/typemill/welcome/write-content";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/write-content";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:2;O:8:"stdClass":20:{s:12:"originalName";s:19:"02-manage-access.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"02";s:4:"name";s:13:"manage access";s:4:"slug";s:13:"manage-access";s:4:"path";s:31:"/00-welcome/02-manage-access.md";s:15:"pathWithoutType";s:28:"/00-welcome/02-manage-access";s:3:"key";i:2;s:7:"keyPath";s:3:"0.2";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"2";}s:7:"chapter";s:3:"1.3";s:9:"urlRelWoF";s:22:"/welcome/manage-access";s:6:"urlRel";s:31:"/typemill/welcome/manage-access";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/manage-access";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:3;O:8:"stdClass":20:{s:12:"originalName";s:14:"03-get-help.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"03";s:4:"name";s:8:"get help";s:4:"slug";s:8:"get-help";s:4:"path";s:26:"/00-welcome/03-get-help.md";s:15:"pathWithoutType";s:23:"/00-welcome/03-get-help";s:3:"key";i:3;s:7:"keyPath";s:3:"0.3";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"3";}s:7:"chapter";s:3:"1.4";s:9:"urlRelWoF";s:17:"/welcome/get-help";s:6:"urlRel";s:26:"/typemill/welcome/get-help";s:6:"urlAbs";s:42:"http://localhost/typemill/welcome/get-help";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:4;O:8:"stdClass":20:{s:12:"originalName";s:19:"04-markdown-test.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"04";s:4:"name";s:13:"markdown test";s:4:"slug";s:13:"markdown-test";s:4:"path";s:31:"/00-welcome/04-markdown-test.md";s:15:"pathWithoutType";s:28:"/00-welcome/04-markdown-test";s:3:"key";i:4;s:7:"keyPath";s:3:"0.4";s:12:"keyPathArray";a:2:{i:0;s:1:"0";i:1;s:1:"4";}s:7:"chapter";s:3:"1.5";s:9:"urlRelWoF";s:22:"/welcome/markdown-test";s:6:"urlRel";s:31:"/typemill/welcome/markdown-test";s:6:"urlAbs";s:47:"http://localhost/typemill/welcome/markdown-test";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}}s:7:"noindex";b:0;}i:1;O:8:"stdClass":22:{s:12:"originalName";s:16:"01-cyanine-theme";s:11:"elementType";s:6:"folder";s:8:"contains";s:5:"pages";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:13:"cyanine theme";s:4:"slug";s:13:"cyanine-theme";s:4:"path";s:17:"/01-cyanine-theme";s:15:"pathWithoutType";s:23:"/01-cyanine-theme/index";s:9:"urlRelWoF";s:14:"/cyanine-theme";s:6:"urlRel";s:23:"/typemill/cyanine-theme";s:6:"urlAbs";s:39:"http://localhost/typemill/cyanine-theme";s:3:"key";i:1;s:7:"keyPath";i:1;s:12:"keyPathArray";a:1:{i:0;s:1:"1";}s:7:"chapter";i:2;s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:13:"folderContent";a:4:{i:0;O:8:"stdClass":20:{s:12:"originalName";s:17:"00-landingpage.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"00";s:4:"name";s:11:"landingpage";s:4:"slug";s:11:"landingpage";s:4:"path";s:35:"/01-cyanine-theme/00-landingpage.md";s:15:"pathWithoutType";s:32:"/01-cyanine-theme/00-landingpage";s:3:"key";i:0;s:7:"keyPath";s:3:"1.0";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"0";}s:7:"chapter";s:3:"2.1";s:9:"urlRelWoF";s:26:"/cyanine-theme/landingpage";s:6:"urlRel";s:35:"/typemill/cyanine-theme/landingpage";s:6:"urlAbs";s:51:"http://localhost/typemill/cyanine-theme/landingpage";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:1;}i:1;O:8:"stdClass":20:{s:12:"originalName";s:22:"01-colors-and-fonts.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"01";s:4:"name";s:16:"colors and fonts";s:4:"slug";s:16:"colors-and-fonts";s:4:"path";s:40:"/01-cyanine-theme/01-colors-and-fonts.md";s:15:"pathWithoutType";s:37:"/01-cyanine-theme/01-colors-and-fonts";s:3:"key";i:1;s:7:"keyPath";s:3:"1.1";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"1";}s:7:"chapter";s:3:"2.2";s:9:"urlRelWoF";s:31:"/cyanine-theme/colors-and-fonts";s:6:"urlRel";s:40:"/typemill/cyanine-theme/colors-and-fonts";s:6:"urlAbs";s:56:"http://localhost/typemill/cyanine-theme/colors-and-fonts";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:2;O:8:"stdClass":20:{s:12:"originalName";s:12:"02-footer.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"02";s:4:"name";s:6:"footer";s:4:"slug";s:6:"footer";s:4:"path";s:30:"/01-cyanine-theme/02-footer.md";s:15:"pathWithoutType";s:27:"/01-cyanine-theme/02-footer";s:3:"key";i:2;s:7:"keyPath";s:3:"1.2";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"2";}s:7:"chapter";s:3:"2.3";s:9:"urlRelWoF";s:21:"/cyanine-theme/footer";s:6:"urlRel";s:30:"/typemill/cyanine-theme/footer";s:6:"urlAbs";s:46:"http://localhost/typemill/cyanine-theme/footer";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}i:3;O:8:"stdClass":20:{s:12:"originalName";s:22:"03-content-elements.md";s:11:"elementType";s:4:"file";s:6:"status";s:9:"published";s:8:"fileType";s:2:"md";s:5:"order";s:2:"03";s:4:"name";s:16:"content elements";s:4:"slug";s:16:"content-elements";s:4:"path";s:40:"/01-cyanine-theme/03-content-elements.md";s:15:"pathWithoutType";s:37:"/01-cyanine-theme/03-content-elements";s:3:"key";i:3;s:7:"keyPath";s:3:"1.3";s:12:"keyPathArray";a:2:{i:0;s:1:"1";i:1;s:1:"3";}s:7:"chapter";s:3:"2.4";s:9:"urlRelWoF";s:31:"/cyanine-theme/content-elements";s:6:"urlRel";s:40:"/typemill/cyanine-theme/content-elements";s:6:"urlAbs";s:56:"http://localhost/typemill/cyanine-theme/content-elements";s:6:"active";b:0;s:12:"activeParent";b:0;s:4:"hide";b:0;s:7:"noindex";b:0;}}s:7:"noindex";b:0;}} \ No newline at end of file diff --git a/data/navigation/navi-extended.txt b/data/navigation/navi-extended.txt index 6914183..69087af 100644 --- a/data/navigation/navi-extended.txt +++ b/data/navigation/navi-extended.txt @@ -10,17 +10,17 @@ noindex: false path: /00-welcome/00-setup-your-website.md keyPath: '0.0' -/welcome/manage-access: - navtitle: 'manage access' - hide: false - noindex: false - path: /00-welcome/01-manage-access.md - keyPath: '0.1' /welcome/write-content: navtitle: 'write content' hide: false noindex: false - path: /00-welcome/02-write-content.md + path: /00-welcome/01-write-content.md + keyPath: '0.1' +/welcome/manage-access: + navtitle: 'manage access' + hide: false + noindex: false + path: /00-welcome/02-manage-access.md keyPath: '0.2' /welcome/get-help: navtitle: 'get help' diff --git a/system/typemill/Controllers/ControllerWebFrontend.php b/system/typemill/Controllers/ControllerWebFrontend.php index c58f730..3facbac 100644 --- a/system/typemill/Controllers/ControllerWebFrontend.php +++ b/system/typemill/Controllers/ControllerWebFrontend.php @@ -134,6 +134,44 @@ class ControllerWebFrontend extends Controller $metadata = $this->c->get('dispatcher')->dispatch(new OnMetaLoaded($metadata),'onMetaLoaded')->getData(); + # REFERENCE FEATURE + if(isset($metadata['meta']['referencetype']) && $metadata['meta']['referencetype'] != 'disable') + { + $referenceurl = rtrim($urlinfo['baseurl'], '/') . '/' . trim($metadata['meta']['reference'], '/'); + + switch ($metadata['meta']['referencetype']) { + case 'redirect301': + return $response->withHeader('Location', $referenceurl)->withStatus(301); + break; + case 'redirect302': + return $response->withHeader('Location', $referenceurl)->withStatus(302); + break; + case 'outlink': + return $response->withHeader('Location', $metadata['meta']['reference'])->withStatus(301); + break; + case 'copy': + $refpageinfo = $extendedNavigation[$metadata['meta']['reference']] ?? false; + if(!$refpageinfo) + { + return $this->c->get('view')->render($response->withStatus(404), '404.twig', [ + 'title' => 'Referenced page not found', + 'description' => 'We did not find the page that has been referenced. Please inform the website owner to fix it in meta reference.' + ]); + } + + $refKeyPathArray = explode(".", $refpageinfo['keyPath']); + $refItem = $navigation->getItemWithKeyPath($draftNavigation, $refKeyPathArray); + + # GET THE CONTENT FROM REFENCED PAGE + $liveMarkdown = $content->getLiveMarkdown($refItem); + $liveMarkdown = $this->c->get('dispatcher')->dispatch(new OnMarkdownLoaded($liveMarkdown), 'onMarkdownLoaded')->getData(); + $markdownArray = $content->markdownTextToArray($liveMarkdown); + + break; + } + } + + # CHECK ACCESS RESTRICTIONS $restricted = $this->checkRestrictions($metadata['meta'], $username, $userrole); if($restricted) diff --git a/system/typemill/settings/metatabs.yaml b/system/typemill/settings/metatabs.yaml index be5ee00..32f6eea 100644 --- a/system/typemill/settings/metatabs.yaml +++ b/system/typemill/settings/metatabs.yaml @@ -78,20 +78,23 @@ meta: hidden: true css: hidden pattern: '[0-9][0-9]-[0-9][0-9]-[0-9][0-9]' -# fieldsetreference: -# type: fieldset -# legend: Reference -# fields: -# reference: -# type: text -# label: Reference to page -# maxlength: 60 -# referencetype: -# type: radio -# label: Type of reference -# options: -# copy: Copy (copy the content of the referenced page) -# redirect: Redirect (redirect the user to the referenced page) + fieldsetreference: + type: fieldset + legend: Reference + fields: + reference: + type: text + label: Reference to page + maxlength: 200 + referencetype: + type: radio + label: Type of reference + options: + disable: Disable + redirect301: PERMANENT REDIRECT (301) the user to the referenced internal page + redirect302: TEMPORARY REDIRECT (302) the user to the referenced internal page + copy: COPY the content of the referenced internal page + outlink: LINK to an external page fieldsetvisibility: type: fieldset legend: Visibility From 997863826671f1e6ca4df2359beb97f8e0c9fd72 Mon Sep 17 00:00:00 2001 From: trendschau Date: Tue, 26 Dec 2023 04:39:17 +0100 Subject: [PATCH 02/20] Add auth code feature, add simpleMail model and refactor system settings --- data/security/securitylog.txt | 10 + .../Controllers/ControllerWebAuth.php | 342 +++++++++++++++--- .../Controllers/ControllerWebRecover.php | 21 +- .../Controllers/ControllerWebSetup.php | 6 +- system/typemill/Models/Settings.php | 14 +- system/typemill/Models/SimpleMail.php | 63 ++++ system/typemill/Models/Validation.php | 26 ++ system/typemill/Plugin.php | 17 - system/typemill/Static/Helpers.php | 1 - system/typemill/author/auth/authcode.twig | 103 ++++++ system/typemill/routes/web.php | 6 + system/typemill/settings/metatabs.yaml | 1 + system/typemill/settings/system.yaml | 146 +++++--- 13 files changed, 616 insertions(+), 140 deletions(-) create mode 100644 data/security/securitylog.txt create mode 100644 system/typemill/Models/SimpleMail.php create mode 100644 system/typemill/author/auth/authcode.twig diff --git a/data/security/securitylog.txt b/data/security/securitylog.txt new file mode 100644 index 0000000..d146afb --- /dev/null +++ b/data/security/securitylog.txt @@ -0,0 +1,10 @@ +127.0.0.1;2023-12-24 10:46:32;login: invalid data +127.0.0.1;2023-12-24 10:47:01;login: invalid data +127.0.0.1;2023-12-24 10:51:05;login: invalid data +127.0.0.1;2023-12-24 10:57:31;login: authcode wrong or outdated. +127.0.0.1;2023-12-24 10:59:47;login: authcode wrong or outdated. +127.0.0.1;2023-12-24 10:59:51;login: wrong password +127.0.0.1;2023-12-24 10:59:59;login: authcode wrong or outdated. +127.0.0.1;2023-12-25 06:20:18;login: authcode wrong or outdated. +127.0.0.1;2023-12-25 06:20:35;login: user not found +127.0.0.1;2023-12-25 09:12:05;login: wrong password diff --git a/system/typemill/Controllers/ControllerWebAuth.php b/system/typemill/Controllers/ControllerWebAuth.php index 07af29a..6727498 100644 --- a/system/typemill/Controllers/ControllerWebAuth.php +++ b/system/typemill/Controllers/ControllerWebAuth.php @@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface as Response; use Slim\Routing\RouteContext; use Typemill\Models\Validation; use Typemill\Models\User; +use Typemill\Models\SimpleMail; use Typemill\Static\Translations; class ControllerWebAuth extends Controller @@ -21,68 +22,215 @@ class ControllerWebAuth extends Controller public function login(Request $request, Response $response) { - /* - if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) ) - { - $this->c->flash->addMessage('error', 'The form has a timeout, please try again.'); - - return $response->withHeader('Location', $this->routeParser->urlFor('auth.show')); - } - */ - $input = $request->getParsedBody(); $validation = new Validation(); -# $settings = $this->c->get('settings'); + $securitylog = $this->settings['securitylog'] ?? false; + $authcodeactive = $this->settings['authcode'] ?? false; - if($validation->signin($input) === true) + if($validation->signin($input) !== true) { - $user = new User(); - - if(!$user->setUserWithPassword($input['username'])) + if($securitylog) { - $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); - - return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + \Typemill\Static\Helpers::addLogEntry('login: invalid data'); } - $userdata = $user->getUserData(); + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); - if($userdata && password_verify($input['password'], $userdata['password'])) - { - # check if user has confirmed the account - if(isset($userdata['optintoken']) && $userdata['optintoken']) - { - $this->c->get('flash')->addMessage('error', Translations::translate('Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.')); - return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); - } - - $user->login(); - -# return $response->withHeader('Location', $this->routeParser->urlFor('settings.show'))->withStatus(302); - - # if user is allowed to view content-area - $acl = $this->c->get('acl'); - if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view')) - { - $editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw'; - - return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302); - } - - return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302); - } + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } - if(isset($this->settings['securitylog']) && $this->settings['securitylog']) + $user = new User(); + + if(!$user->setUserWithPassword($input['username'])) { - \Typemill\Static\Helpers::addLogEntry('wrong login'); + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: user not found'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } - $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + $userdata = $user->getUserData(); + $authcodedata = $this->checkAuthcode($userdata); - return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + if($userdata && !password_verify($input['password'], $userdata['password'])) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: wrong password'); + } + + # always show authcode page, so attacker does not know if email or password was wrong or mail was send. + if($authcodeactive && !$authcodedata['valid']) + { + # a bit slower because send mail takes some time usually + usleep(rand(100000, 200000)); + + # show authcode page + return $this->c->get('view')->render($response, 'auth/authcode.twig', [ + 'username' => $userdata['username'], + ]); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + # check device fingerprint + if($authcodeactive) + { + $fingerprint = $this->generateDeviceFingerprint(); + if(!$this->findDeviceFingerprint($fingerprint, $userdata)) + { + # invalidate authcodedata so user has to use a new authcode again + $authcodedata['valid'] = false; + $authcodedata['validated'] = 12345; + } + } + + if($authcodeactive && !$authcodedata['valid'] ) + { + # generate new authcode + $authcodevalue = rand(10000, 99999); + + $mail = new SimpleMail($settings); + + $subject = Translations::translate('Your authentication code for Typemill'); + $message = Translations::translate('Use the following authentication code to login into Typemill cms') . ': ' . $authcodevalue; + + $send = $mail->send($userdata['email'], $subject, $message); + + $send = true; + + if(!$send) + { + $title = Translations::translate('Error sending email'); + $message = Translations::translate('Dear ') . $userdata['username'] . ', ' . Translations::translate('we could not send the email with the authentication code to your address. Reason: ') . $mail->error; + } + else + { + # store authcode + $user->setValue('authcodedata', $authcodevalue . ':' . time() . ':' . $authcodedata['validated']); + $user->updateUser(); + } + + # show authcode page + return $this->c->get('view')->render($response, 'auth/authcode.twig', [ + 'username' => $userdata['username'], + ]); + } + + # check if user has confirmed the account + if(isset($userdata['optintoken']) && $userdata['optintoken']) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: user not confirmed yet.'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $user->login(); + +# return $response->withHeader('Location', $this->routeParser->urlFor('settings.show'))->withStatus(302); + + # if user is allowed to view content-area + $acl = $this->c->get('acl'); + if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view')) + { + $editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw'; + + return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302); } + + # login user with valid authcode + public function loginWithAuthcode(Request $request, Response $response) + { + $input = $request->getParsedBody(); + $validation = new Validation(); + $securitylog = $this->settings['securitylog'] ?? false; + + if($validation->authcode($input) !== true) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: invalid authcode format'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('Invalid authcode format, please try again.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $user = new User(); + + if(!$user->setUserWithPassword($input['username'])) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: user not found'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $userdata = $user->getUserData(); + $authcodevalue = $input['code-1'] . $input['code-2'] . $input['code-3'] . $input['code-4'] . $input['code-5']; + $authcodedata = $this->checkAuthcode($userdata); + + if(!$this->validateAuthcode($authcodevalue, $authcodedata)) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: authcode wrong or outdated.'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('The authcode was wrong or outdated, please start again.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + # add the device fingerprint if not set yet + $fingerprints = $userdata['fingerprints'] ?? []; + $fingerprint = $this->generateDeviceFingerprint(); + if(!$this->findDeviceFingerprint($fingerprint, $fingerprints)) + { + $fingerprints[] = $fingerprint; + $user->setValue('fingerprints', $fingerprints); + } + + # update authcode lastValidation and store + $user->setValue('authcodedata', $authcodevalue . ':' . $authcodedata['generated'] . ':' . time()); + $user->updateUser(); + + $user->login(); + + # if user is allowed to view content-area + $acl = $this->c->get('acl'); + if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view')) + { + $editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw'; + + return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302); + } + + /** * log out a user * @@ -97,4 +245,106 @@ class ControllerWebAuth extends Controller return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } + + + # check if the stored authcode in userdata is valid and/or fresh + private function checkAuthcode($userdata) + { + # format: 12345:time(generated):time(validated) + + $authcodedata = $userdata['authcodedata'] ?? false; + + if(!$authcodedata) + { + return $authcode = [ + 'value' => 12345, + 'generated' => 12345, + 'validated' => 12345, + 'valid' => false, + 'fresh' => false + ]; + } + + $validation = new Validation(); + $authcodedata = explode(":", $authcodedata); + + # validate format here, do we need it? + + $now = time(); + $lastValidation = 60 * 60 * 24; + $lastGeneration = 60 * 5; + $valid = false; + $fresh = false; + + # if last validation is less than 24 hours old + if($now - $lastValidation < $authcodedata[2]) + { + $valid = true; + } + + # if last generation is less than 5 minutes old + if($now - $lastGeneration < $authcodedata[1]) + { + $fresh = true; + } + + $authcode = [ + 'value' => $authcodedata[0], + 'generated' => $authcodedata[1], + 'validated' => $authcodedata[2], + 'valid' => $valid, + 'fresh' => $fresh + ]; + + return $authcode; + } + + # check if the submitted authcode is the same as the stored authcode + private function validateAuthcode($authcodevalue, $authcodedata) + { + if($authcodedata['valid'] === true) + { + return true; + } + + if($authcodedata['fresh'] === false) + { + return false; + } + + if($authcodevalue == $authcodedata['value']) + { + return true; + } + + return false; + } + + # create a simple device fingerprint + private function generateDeviceFingerprint() + { + $userAgent = $_SERVER['HTTP_USER_AGENT']; + $ipAddress = $_SERVER['REMOTE_ADDR']; + $acceptLanguage = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : ''; + + $fingerprint = md5($userAgent . $ipAddress . $acceptLanguage); + + return $fingerprint; + } + + # create a simple device fingerprint + private function findDeviceFingerprint($fingerprint, $userdata) + { + if(!isset($userdata['fingerprints']) or empty($userdata['fingerprints'])) + { + return false; + } + + if(!in_array($fingerprint, $userdata['fingerprints'])) + { + return false; + } + + return true; + } } \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebRecover.php b/system/typemill/Controllers/ControllerWebRecover.php index 905d068..8a52c4e 100644 --- a/system/typemill/Controllers/ControllerWebRecover.php +++ b/system/typemill/Controllers/ControllerWebRecover.php @@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface as Response; use Slim\Routing\RouteContext; use Typemill\Models\User; use Typemill\Models\Validation; +use Typemill\Models\SimpleMail; use Typemill\Static\Translations; use Typemill\Extensions\ParsedownExtension; @@ -50,15 +51,9 @@ class ControllerWebRecover extends Controller $link = '' . $url . ''; # define the headers - $headers = 'Content-Type: text/html; charset=utf-8' . "\r\n"; - $headers .= 'Content-Transfer-Encoding: base64' . "\r\n"; - if(isset($settings['recoverfrom']) && $settings['recoverfrom'] != '') - { - $headers .= 'From: ' . $settings['recoverfrom']; - } + $mail = new SimpleMail($settings); - $subjectline = (isset($settings['recoversubject']) && ($settings['recoversubject'] != '') ) ? $settings['recoversubject'] : 'Recover your password'; - $subject = '=?UTF-8?B?' . base64_encode($subjectline) . '?='; + $subject = (isset($settings['recoversubject']) && ($settings['recoversubject'] != '') ) ? $settings['recoversubject'] : 'Recover your password'; $messagetext = Translations::translate('Dear user'); $messagetext .= ",

"; @@ -72,16 +67,14 @@ class ControllerWebRecover extends Controller $messagetext = $parsedown->markup($contentArray); } - $message = base64_encode($messagetext . "

" . $link); + $message = $messagetext . "

" . $link; - # $send = mail($requiredUser['email'], $subject, $message, $headers); + $send = $mail->send($requiredUser['email'], $subject, $message); - $send = false; - - if($send == 'delete') + if(!$send) { $title = Translations::translate('Error sending email'); - $message = Translations::translate('Dear ') . $requiredUser['username'] . ', ' . Translations::translate('we could not send the email with the password instructions to your address. Please contact the website owner and ask for help.'); + $message = Translations::translate('Dear ') . $requiredUser['username'] . ', ' . Translations::translate('we could not send the email with the password instructions to your address. Reason: ') . $mail->error; } else { diff --git a/system/typemill/Controllers/ControllerWebSetup.php b/system/typemill/Controllers/ControllerWebSetup.php index f9cfa4a..f90e2b3 100644 --- a/system/typemill/Controllers/ControllerWebSetup.php +++ b/system/typemill/Controllers/ControllerWebSetup.php @@ -95,7 +95,11 @@ class ControllerWebSetup extends Controller # create initial settings file $settingsModel = new Settings(); - $settingsModel->createSettings(); + $settingsModel->createSettings([ + 'author' => $params['username'], + 'mailfrom' => $params['email'], + 'mailfromname' => $params['username'] + ]); $urlinfo = $this->c->get('urlinfo'); $route = $urlinfo['baseurl'] . '/tm/system'; diff --git a/system/typemill/Models/Settings.php b/system/typemill/Models/Settings.php index 8fdab5d..2f7bac7 100644 --- a/system/typemill/Models/Settings.php +++ b/system/typemill/Models/Settings.php @@ -224,18 +224,26 @@ class Settings return $settingsDefinitions; } - public function createSettings() + public function createSettings(array $defaultSettings = NULL) { - $language = Translations::whichLanguage(); + $defaults = [ + 'language' => Translations::whichLanguage() + ]; + + if($defaultSettings) + { + $defaults = array_merge($defaults, $defaultSettings); + } $initialSettings = $this->storage->updateYaml('settingsFolder', '', 'settings.yaml', [ - 'language' => $language + $defaults ]); if($initialSettings) { return true; } + return false; } diff --git a/system/typemill/Models/SimpleMail.php b/system/typemill/Models/SimpleMail.php new file mode 100644 index 0000000..d86bb36 --- /dev/null +++ b/system/typemill/Models/SimpleMail.php @@ -0,0 +1,63 @@ +from = trim($settings['mailfrom']); + + if(isset($settings['mailfromname']) && $settings['mailfromname'] != '') + { + $this->from = '=?UTF-8?B?' . base64_encode($settings['mailfromname']) . '?= <' . trim($settings['mailfrom']) . '>'; + } + } + + if(isset($settings['mailreply']) && $settings['mailreply'] != '') + { + $this->reply = trim($settings['mailreply']); + } + } + + public function sendEmail(string $to, string $subject, string $message) + { + if(!$this->from) + { + $this->error = 'You need to add a email address into the settings.'; + + return false; + } + + # 'Reply-To: webmaster@example.com' . "\r\n" . + + $headers = 'Content-Type: text/html; charset=utf-8' . "\r\n"; + $headers .= 'Content-Transfer-Encoding: base64' . "\r\n"; + $headers .= 'From: ' . $this->from . "\r\n"; + if($this->$reply) + { + $headers .= 'Reply-To: base64' . $this->reply . "\r\n"; + } + $headers .= 'X-Mailer: PHP/' . phpversion(); + + $subject = '=?UTF-8?B?' . base64_encode($subject) . '?='; + $message = base64_encode($message); + + $send = mail($to, $subject, $message, $headers); + + if($send !== true) + { + $this->error = error_get_last()['message']; + } + + return $send; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Validation.php b/system/typemill/Models/Validation.php index 5cd7d94..e1be426 100644 --- a/system/typemill/Models/Validation.php +++ b/system/typemill/Models/Validation.php @@ -274,6 +274,32 @@ class Validation return false; } + /** + * validation for authcode confirmation + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function authcode(array $params) + { + $v = new Validator($params); + $v->rule('required', ['username', 'code-1', 'code-2', 'code-3', 'code-4', 'code-5'])->message("Required"); + $v->rule('alphaNum', 'username')->message("Invalid characters"); + $v->rule('regex', 'code-1', '/^[0-9]{1}$/')->message("Must be 1-9"); + $v->rule('regex', 'code-2', '/^[0-9]{1}$/')->message("Must be 1-9"); + $v->rule('regex', 'code-3', '/^[0-9]{1}$/')->message("Must be 1-9"); + $v->rule('regex', 'code-4', '/^[0-9]{1}$/')->message("Must be 1-9"); + $v->rule('regex', 'code-5', '/^[0-9]{1}$/')->message("Must be 1-9"); + + if($v->validate()) + { + return true; + } + + return false; + } + /** * validation for setup user (in backoffice) diff --git a/system/typemill/Plugin.php b/system/typemill/Plugin.php index 68421ca..6102586 100644 --- a/system/typemill/Plugin.php +++ b/system/typemill/Plugin.php @@ -194,23 +194,6 @@ abstract class Plugin implements EventSubscriberInterface $this->container->get('assets')->addJS($JS); } -/* - protected function addEditorJS($JS) - { - $this->container->get('assets')->addEditorJS($JS); - } - - protected function addEditorInlineJS($JS) - { - $this->container->get('assets')->addEditorInlineJS($JS); - } - - protected function addEditorCSS($CSS) - { - $this->container->get('assets')->addEditorCSS($CSS); - } -*/ - protected function addInlineJS($JS) { $this->container->get('assets')->addInlineJS($JS); diff --git a/system/typemill/Static/Helpers.php b/system/typemill/Static/Helpers.php index f3c443d..4adb2c4 100644 --- a/system/typemill/Static/Helpers.php +++ b/system/typemill/Static/Helpers.php @@ -51,7 +51,6 @@ class Helpers{ return $ip; } - public static function addLogEntry($action) { $line = self::getUserIP(); diff --git a/system/typemill/author/auth/authcode.twig b/system/typemill/author/auth/authcode.twig new file mode 100644 index 0000000..44372ec --- /dev/null +++ b/system/typemill/author/auth/authcode.twig @@ -0,0 +1,103 @@ +{% extends 'layouts/layoutAuth.twig' %} + +{% block title %}Login Authentication Code{% endblock %} + +{% block content %} + +
+ +
+
+ +

Authentication Code

+ +

Enter the auth code from the e-mail you got: + +

+ +
+ +
+ + + + + +
+ + + + + + + +
+ +
+
+
+ +
+
+

{{ translate('Auth code missing?') }}

+

{{ translate('If you did not receive an email with an authentication code, then the username or password you entered was wrong. Please try again.') }}

+
+
+ +
+{% endblock %} \ No newline at end of file diff --git a/system/typemill/routes/web.php b/system/typemill/routes/web.php index 18e1c89..4024b0f 100644 --- a/system/typemill/routes/web.php +++ b/system/typemill/routes/web.php @@ -18,6 +18,11 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($settings) { $group->get('/login', ControllerWebAuth::class . ':show')->setName('auth.show'); $group->post('/login', ControllerWebAuth::class . ':login')->setName('auth.login'); + if(isset($settings['authcode']) && $settings['authcode']) + { + $group->post('/authcode', ControllerWebAuth::class . ':loginWithAuthcode')->setName('auth.authcode'); + } + if(isset($settings['recoverpw']) && $settings['recoverpw']) { $group->get('/recover', ControllerWebRecover::class . ':showRecoverForm')->setName('auth.recoverform'); @@ -50,6 +55,7 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl) $app->redirect('/tm', $routeParser->urlFor('auth.show'), 302); $app->redirect('/tm/', $routeParser->urlFor('auth.show'), 302); +$app->redirect('/tm/authcode', $routeParser->urlFor('auth.show'), 302); # downloads $app->get('/media/files[/{params:.*}]', ControllerWebDownload::class . ':download')->setName('download.file'); diff --git a/system/typemill/settings/metatabs.yaml b/system/typemill/settings/metatabs.yaml index 32f6eea..722bc48 100644 --- a/system/typemill/settings/metatabs.yaml +++ b/system/typemill/settings/metatabs.yaml @@ -85,6 +85,7 @@ meta: reference: type: text label: Reference to page + placeholder: '/path/to/internal/page or https://exgernal-page.org' maxlength: 200 referencetype: type: radio diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml index 81a3e5b..739222a 100644 --- a/system/typemill/settings/system.yaml +++ b/system/typemill/settings/system.yaml @@ -4,19 +4,20 @@ fieldsetsystem: fields: title: type: text - label: Website title + label: 'Website title' maxlength: 60 css: lg:w-half author: type: text - label: Website owner + label: 'Website owner' css: lg:w-half maxlength: 60 copyright: type: select - label: Copyright + label: 'Copyright' css: lg:w-half maxlength: 60 + description: 'Used for copyright and year in footer.' options: '©': '©' 'CC-BY': 'CC-BY' @@ -30,11 +31,13 @@ fieldsetsystem: label: Year css: lg:w-half maxlength: 4 + description: 'Used for copyright and year in footer.' language: type: select - label: Language (author area) + label: 'Language (author area)' css: lg:w-half maxlength: 60 + description: 'Used for translations in author area, themes, and plugins.' options: 'en': 'English' 'ru': 'Russian' @@ -44,15 +47,16 @@ fieldsetsystem: 'fr': 'French' langattr: type: text - label: Language attribute (website) + label: 'Language attribute (website)' css: lg:w-half maxlength: 5 - description: Please use ISO 639-1 codes like "en" + description: 'Used for frontend language attribute. Please use ISO 639-1 codes like "en".' sitemap: type: text - label: Google sitemap (readonly) + label: 'Google sitemap (readonly)' css: lg:w-half disabled: true + description: 'Submit the url above in google search console to support indexing.' fieldsetmedia: type: fieldset legend: Media @@ -63,48 +67,48 @@ fieldsetmedia: favicon: type: image label: Favicon - description: Only PNG format will work. + description: 'Only PNG format will work.' liveimagewidth: type: number - label: Standard width for live pictures + label: 'Standard width for live pictures' placeholder: 820 - description: Default width of live images is 820px. Changes will apply to future uploads. + description: 'Default width of live images is 820px. Changes will apply to future uploads.' css: lg:w-half liveimageheight: type: number - label: Standard height for live pictures - description: If you add a value for the height, then the image will be cropped. + label: 'Standard height for live pictures' + description: 'If you add a value for the height, then the image will be cropped.' css: lg:w-half maximageuploads: type: number - label: Maximum size for image uploads in MB - description: The maximum image size might be limited by your server settings. + label: 'Maximum size for image uploads in MB' + description: 'The maximum image size might be limited by your server settings.' allowsvg: type: checkbox label: Allow svg - checkboxlabel: Allow the upload of svg images + checkboxlabel: 'Allow the upload of svg images' convertwebp: type: checkbox - label: Convert to webp - checkboxlabel: Try to convert uploaded images into the webp-format + label: 'Convert to webp' + checkboxlabel: 'Try to convert uploaded images into the webp-format for better performance.' maxfileuploads: type: number - label: Maximum size for file uploads in MB - description: The maximum file size might be limited by your server settings. + label: 'Maximum size for file uploads in MB' + description: 'The maximum file size might be limited by your server settings.' fieldsetwriting: type: fieldset legend: Writing fields: editor: type: radio - label: Standard editor mode + label: 'Standard editor mode' css: lg:w-half options: 'visual': 'visual editor' 'raw': 'raw editor' formats: type: checkboxlist - label: Format options for visual editor + label: 'Format options for visual editor' css: lg:w-half options: 'markdown': 'markdown' @@ -124,12 +128,12 @@ fieldsetwriting: 'shortcode': 'shortcode' headlineanchors: type: checkbox - label: Headline anchors - checkboxlabel: Show anchors next to headline in frontend + label: 'Headline anchors' + checkboxlabel: 'Show anchors next to headline in frontend' urlschemes: type: text - label: Url schemes - description: Add more url schemes for external links e.g. like dict:// (comma separated list) + label: 'Url schemes' + description: 'Add more url schemes for external links e.g. like dict:// (comma separated list)' maxlength: 60 fieldsetaccess: type: fieldset @@ -137,57 +141,83 @@ fieldsetaccess: fields: access: type: checkbox - label: Website restriction - checkboxlabel: Show the website only to authenticated users and redirect all other users to the login page. + label: 'Website restriction' + checkboxlabel: 'Show the website only to authenticated users and redirect all other users to the login page.' pageaccess: type: checkbox - label: Page restriction - checkboxlabel: Activate individual restrictions for pages in the meta-tab of each page. + label: 'Page restriction' + checkboxlabel: 'Activate individual restrictions for pages in the meta-tab of each page.' hrdelimiter: type: checkbox - label: Content break - checkboxlabel: Cut restricted content after the first hr-element on a page (per default content will be cut after title). + label: 'Content break' + checkboxlabel: 'Cut restricted content after the first hr-element on a page (per default content will be cut after title).' restrictionnotice: type: textarea - label: Restriction notice (use markdown) + label: 'Restriction notice (use markdown)' maxlength: 2000 wraprestrictionnotice: type: checkbox - label: Wrap restriction notice - checkboxlabel: Wrap the restriction notice above into a notice-4 element (which can be designed as special box) + label: 'Wrap restriction notice' + checkboxlabel: 'Wrap the restriction notice above into a notice-4 element (which can be designed as special box)' +fieldsetmail: + type: fieldset + legend: Email + fields: + mailfrom: + type: text + label: 'Mail From (required)' + placeholder: sender@yourmail.org + maxlength: 60 + description: 'Enter an email address that should send emails (sender).' + mailfromname: + type: text + label: 'Mail From Name (optional)' + placeholder: sender name + maxlength: 60 + description: 'Optionally enter a name for the sender address. If not set, the from-address will be visible.' + replyto: + type: text + label: 'Reply To (optional)' + placeholder: noreply@yourmail.org + maxlength: 60 + description: 'Optionally enter a "reply to" address for answers from the receiver. If not set, answers will go to the from-address.' +fieldsetrecover: + type: fieldset + legend: Recover + fields: + recoverpw: + type: checkbox + label: 'Recover password' + checkboxlabel: 'Activate a password recovery in the login form.' + recoversubject: + type: text + label: 'Email subject' + placeholder: 'Recover your password' + maxlength: 60 + recovermessage: + type: textarea + label: 'Text before recover link in email message' + maxlength: 2000 fieldsetsecurity: type: fieldset legend: Security fields: securitylog: type: checkbox - label: Security log - checkboxlabel: Track spam and suspicious actions in a logfile + label: 'Security log' + checkboxlabel: 'Track spam and suspicious actions in a logfile' + authcode: + type: checkbox + label: 'Authentication code (recommended)' + checkboxlabel: 'Send a 5-digit authentication code by email to confirm login.' + description: 'The authentication code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts. Make sure this complies with privacy legislation in your country.' authcaptcha: type: radio - label: Use captcha in authentication forms + label: 'Use captcha in authentication forms' options: - disabled: Disable - standard: Always show - aftererror: Show after first wrong input - recoverpw: - type: checkbox - label: Recover password - checkboxlabel: Activate the password recovery. - recoverfrom: - type: text - label: Sender email - placeholder: your@email.org - maxlength: 60 - recoversubject: - type: text - label: Email subject - placeholder: Recover your password - maxlength: 60 - recovermessage: - type: textarea - label: Text before recover link in email message - maxlength: 2000 + disabled: 'Disable' + standard: 'Always show' + aftererror: 'Show after first wrong input' fieldsetdeveloper: type: fieldset legend: Developer From f674d4b6f94c7e920b281643aed27bfce07b8e65 Mon Sep 17 00:00:00 2001 From: trendschau Date: Wed, 27 Dec 2023 12:33:13 +0100 Subject: [PATCH 03/20] v2.1.0 Finish authentication code and login --- data/security/securitylog.txt | 8 +++++ .../Controllers/ControllerWebAuth.php | 35 +++++++++---------- system/typemill/Models/SimpleMail.php | 6 ++-- system/typemill/author/auth/authcode.twig | 19 +++++----- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/data/security/securitylog.txt b/data/security/securitylog.txt index d146afb..2eee11f 100644 --- a/data/security/securitylog.txt +++ b/data/security/securitylog.txt @@ -8,3 +8,11 @@ 127.0.0.1;2023-12-25 06:20:18;login: authcode wrong or outdated. 127.0.0.1;2023-12-25 06:20:35;login: user not found 127.0.0.1;2023-12-25 09:12:05;login: wrong password +127.0.0.1;2023-12-27 11:17:43;login: authcode wrong or outdated. +127.0.0.1;2023-12-27 11:21:21;login: authcode wrong or outdated. +127.0.0.1;2023-12-27 11:24:01;login: authcode wrong or outdated. +127.0.0.1;2023-12-27 11:25:07;login: authcode wrong or outdated. +127.0.0.1;2023-12-27 11:30:28;login: authcode wrong or outdated. +127.0.0.1;2023-12-27 11:31:36;login: authcode wrong or outdated. +127.0.0.1;2023-12-27 11:31:52;login: authcode wrong or outdated. +127.0.0.1;2023-12-27 11:32:10;login: authcode wrong or outdated. diff --git a/system/typemill/Controllers/ControllerWebAuth.php b/system/typemill/Controllers/ControllerWebAuth.php index 6727498..1851e43 100644 --- a/system/typemill/Controllers/ControllerWebAuth.php +++ b/system/typemill/Controllers/ControllerWebAuth.php @@ -26,7 +26,9 @@ class ControllerWebAuth extends Controller $validation = new Validation(); $securitylog = $this->settings['securitylog'] ?? false; $authcodeactive = $this->settings['authcode'] ?? false; - + $authtitle = Translations::translate('Auth code missing?'); + $authtext = Translations::translate('If you did not receive an email with an authentication code, then the username or password you entered was wrong. Please try again.'); + if($validation->signin($input) !== true) { if($securitylog) @@ -72,6 +74,8 @@ class ControllerWebAuth extends Controller # show authcode page return $this->c->get('view')->render($response, 'auth/authcode.twig', [ 'username' => $userdata['username'], + 'authtitle' => $authtitle, + 'authtext' => $authtext ]); } @@ -100,7 +104,7 @@ class ControllerWebAuth extends Controller $mail = new SimpleMail($settings); $subject = Translations::translate('Your authentication code for Typemill'); - $message = Translations::translate('Use the following authentication code to login into Typemill cms') . ': ' . $authcodevalue; + $message = Translations::translate('Use the following authentication code to login into Typemill') . ': ' . $authcodevalue; $send = $mail->send($userdata['email'], $subject, $message); @@ -108,8 +112,8 @@ class ControllerWebAuth extends Controller if(!$send) { - $title = Translations::translate('Error sending email'); - $message = Translations::translate('Dear ') . $userdata['username'] . ', ' . Translations::translate('we could not send the email with the authentication code to your address. Reason: ') . $mail->error; + $authtitle = Translations::translate('Error sending email'); + $authtext = Translations::translate('We could not send the email with the authentication code to your address. Reason: ') . $mail->error; } else { @@ -120,7 +124,9 @@ class ControllerWebAuth extends Controller # show authcode page return $this->c->get('view')->render($response, 'auth/authcode.twig', [ - 'username' => $userdata['username'], + 'username' => $userdata['username'], + 'authtitle' => $authtitle, + 'authtext' => $authtext ]); } @@ -139,8 +145,6 @@ class ControllerWebAuth extends Controller $user->login(); -# return $response->withHeader('Location', $this->routeParser->urlFor('settings.show'))->withStatus(302); - # if user is allowed to view content-area $acl = $this->c->get('acl'); if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view')) @@ -154,7 +158,7 @@ class ControllerWebAuth extends Controller } - # login user with valid authcode + # login a user with valid authcode public function loginWithAuthcode(Request $request, Response $response) { $input = $request->getParsedBody(); @@ -204,9 +208,9 @@ class ControllerWebAuth extends Controller } # add the device fingerprint if not set yet - $fingerprints = $userdata['fingerprints'] ?? []; - $fingerprint = $this->generateDeviceFingerprint(); - if(!$this->findDeviceFingerprint($fingerprint, $fingerprints)) + $fingerprints = $userdata['fingerprints'] ?? []; + $fingerprint = $this->generateDeviceFingerprint(); + if(!$this->findDeviceFingerprint($fingerprint, $userdata)) { $fingerprints[] = $fingerprint; $user->setValue('fingerprints', $fingerprints); @@ -231,14 +235,7 @@ class ControllerWebAuth extends Controller } - /** - * log out a user - * - * @param obj $request the slim request object - * @param obj $response the slim response object - * @return obje $response with redirect to route - */ - + # log out a user public function logout(Request $request, Response $response) { \Typemill\Static\Session::stopSession(); diff --git a/system/typemill/Models/SimpleMail.php b/system/typemill/Models/SimpleMail.php index d86bb36..ed2b092 100644 --- a/system/typemill/Models/SimpleMail.php +++ b/system/typemill/Models/SimpleMail.php @@ -2,6 +2,8 @@ namespace Typemill\Models; +use Typemill\Static\Translations; + class SimpleMail { private $from = false; @@ -28,11 +30,11 @@ class SimpleMail } } - public function sendEmail(string $to, string $subject, string $message) + public function send(string $to, string $subject, string $message) { if(!$this->from) { - $this->error = 'You need to add a email address into the settings.'; + $this->error = Translations::translate('Email address in system settings is missing.'); return false; } diff --git a/system/typemill/author/auth/authcode.twig b/system/typemill/author/auth/authcode.twig index 44372ec..f44f97f 100644 --- a/system/typemill/author/auth/authcode.twig +++ b/system/typemill/author/auth/authcode.twig @@ -11,7 +11,7 @@

Authentication Code

-

Enter the auth code from the e-mail you got: +

{{ translate('Enter the authentication code from your email:') }}

@@ -24,7 +24,7 @@ pattern="[0-9]" maxlength="1" oninput="moveToNextField(this)" - class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non" + class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-bold text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non" required> @@ -88,14 +88,15 @@ } } -
+
-

{{ translate('Auth code missing?') }}

-

{{ translate('If you did not receive an email with an authentication code, then the username or password you entered was wrong. Please try again.') }}

+

{{ authtitle }}

+

{{ authtext }}

+ → {{ translate('Back to login') }}
From 8a19620201c862d3d8f30e26d4d7f74293428190 Mon Sep 17 00:00:00 2001 From: trendschau Date: Wed, 27 Dec 2023 21:54:28 +0100 Subject: [PATCH 04/20] Custom header middleware to improve security --- .../Middleware/CustomHeadersMiddleware.php | 46 +++++++++++++++++++ system/typemill/settings/system.yaml | 6 ++- system/typemill/system.php | 3 ++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 system/typemill/Middleware/CustomHeadersMiddleware.php diff --git a/system/typemill/Middleware/CustomHeadersMiddleware.php b/system/typemill/Middleware/CustomHeadersMiddleware.php new file mode 100644 index 0000000..185cae6 --- /dev/null +++ b/system/typemill/Middleware/CustomHeadersMiddleware.php @@ -0,0 +1,46 @@ +settings = $settings; + } + + public function process(Request $request, RequestHandler $handler) :response + { + $scheme = $request->getUri()->getScheme(); + + $response = $handler->handle($request); + + $response = $response->withoutHeader('Server'); + $response = $response->withHeader('X-Powered-By', 'Typemill'); + + $headersOff = $this->settings['headersoff'] ?? false; + + if(!$headersOff) + { + $response = $response + ->withHeader('X-Content-Type-Options', 'nosniff') + ->withHeader('X-Frame-Options', 'SAMEORIGIN') + ->withHeader('X-XSS-Protection', '1;mode=block') + ->withHeader('Referrer-Policy', 'no-referrer-when-downgrade'); + + if($scheme == 'https') + { + $response = $response->withHeader('Strict-Transport-Security', 'max-age=63072000'); + } + } + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml index 739222a..aa9ed44 100644 --- a/system/typemill/settings/system.yaml +++ b/system/typemill/settings/system.yaml @@ -236,4 +236,8 @@ fieldsetdeveloper: checkboxlabel: Use x-forwarded-header. trustedproxies: type: text - label: Trusted IPs for proxies (comma separated) \ No newline at end of file + label: Trusted IPs for proxies (comma separated) + headersoff: + type: checkbox + label: Disable Custom Headers + checkboxlabel: Disable all custom headers of Typemill and send your own headers instead. \ No newline at end of file diff --git a/system/typemill/system.php b/system/typemill/system.php index 157b64a..2fa40c0 100644 --- a/system/typemill/system.php +++ b/system/typemill/system.php @@ -28,6 +28,7 @@ use Typemill\Middleware\JsonBodyParser; use Typemill\Middleware\FlashMessages; use Typemill\Middleware\AssetMiddleware; use Typemill\Middleware\SecurityMiddleware; +use Typemill\Middleware\CustomHeadersMiddleware; use Typemill\Extensions\TwigCsrfExtension; use Typemill\Extensions\TwigUrlExtension; use Typemill\Extensions\TwigUserExtension; @@ -304,6 +305,8 @@ foreach($middleware as $pluginMiddleware) } } +$app->add(new CustomHeadersMiddleware($settings)); + $app->add(new AssetMiddleware($assets, $container->get('view'))); $app->add(new ValidationErrorsMiddleware($container->get('view'))); From fb3f5f46736a514d6de0b1d4039235d0c7c7a166 Mon Sep 17 00:00:00 2001 From: trendschau Date: Sat, 30 Dec 2023 00:10:05 +0100 Subject: [PATCH 05/20] Remove Credentials Middleware and Add Custom Headers and Cors Headers --- .../Controllers/ControllerApiGlobals.php | 9 ++- .../Controllers/ControllerWebAuth.php | 2 +- .../typemill/Middleware/ApiAuthentication.php | 3 + .../Middleware/CustomHeadersMiddleware.php | 44 ++++++++++- .../RemoveCredentialsMiddleware.php | 28 +++++++ .../typemill/Middleware/SessionMiddleware.php | 76 +++++++++---------- system/typemill/Static/Helpers.php | 2 + system/typemill/settings/system.yaml | 7 +- system/typemill/system.php | 24 +++++- 9 files changed, 147 insertions(+), 48 deletions(-) create mode 100644 system/typemill/Middleware/RemoveCredentialsMiddleware.php diff --git a/system/typemill/Controllers/ControllerApiGlobals.php b/system/typemill/Controllers/ControllerApiGlobals.php index b66f4ac..a154450 100644 --- a/system/typemill/Controllers/ControllerApiGlobals.php +++ b/system/typemill/Controllers/ControllerApiGlobals.php @@ -12,10 +12,11 @@ class ControllerApiGlobals extends Controller { $navigation = new Navigation(); $systemNavigation = $navigation->getSystemNavigation( - $userrole = $request->getAttribute('c_userrole'), - $acl = $this->c->get('acl'), - $urlinfo = $this->c->get('urlinfo'), - $dispatcher = $this->c->get('dispatcher') + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser ); # won't work because api has no session, instead you have to pass user diff --git a/system/typemill/Controllers/ControllerWebAuth.php b/system/typemill/Controllers/ControllerWebAuth.php index 1851e43..4065880 100644 --- a/system/typemill/Controllers/ControllerWebAuth.php +++ b/system/typemill/Controllers/ControllerWebAuth.php @@ -101,7 +101,7 @@ class ControllerWebAuth extends Controller # generate new authcode $authcodevalue = rand(10000, 99999); - $mail = new SimpleMail($settings); + $mail = new SimpleMail($this->settings); $subject = Translations::translate('Your authentication code for Typemill'); $message = Translations::translate('Use the following authentication code to login into Typemill') . ': ' . $authcodevalue; diff --git a/system/typemill/Middleware/ApiAuthentication.php b/system/typemill/Middleware/ApiAuthentication.php index c38c2b5..7f7c3c1 100644 --- a/system/typemill/Middleware/ApiAuthentication.php +++ b/system/typemill/Middleware/ApiAuthentication.php @@ -63,6 +63,9 @@ class ApiAuthentication } */ + +########### WHY NOT USE BASIC AUTH PARAMS FROM URI ? + $params = []; if (preg_match("/Basic\s+(.*)$/i", $request->getHeaderLine("Authorization"), $matches)) diff --git a/system/typemill/Middleware/CustomHeadersMiddleware.php b/system/typemill/Middleware/CustomHeadersMiddleware.php index 185cae6..9a19135 100644 --- a/system/typemill/Middleware/CustomHeadersMiddleware.php +++ b/system/typemill/Middleware/CustomHeadersMiddleware.php @@ -10,23 +10,33 @@ use Slim\Psr7\Response; class CustomHeadersMiddleware implements MiddlewareInterface { protected $settings; + + protected $urlinfo; - public function __construct($settings) + public function __construct($settings, $urlinfo) { $this->settings = $settings; + + $this->urlinfo = $urlinfo; } public function process(Request $request, RequestHandler $handler) :response { $scheme = $request->getUri()->getScheme(); + # add the custom headers to the response after everything is processed $response = $handler->handle($request); $response = $response->withoutHeader('Server'); $response = $response->withHeader('X-Powered-By', 'Typemill'); $headersOff = $this->settings['headersoff'] ?? false; + + ################### + # SECURITY HEADER # + ################### + if(!$headersOff) { $response = $response @@ -41,6 +51,38 @@ class CustomHeadersMiddleware implements MiddlewareInterface } } + ################### + # CORS HEADER # + ################### + + $origin = $request->getHeaderLine('Origin'); + $corsdomains = isset($this->settings['corsdomains']) ? trim($this->settings['corsdomains']) : false; + $whitelist = []; + + if($corsdomains && $corsdomains != '') + { + $corsdomains = explode(",", $corsdomains); + foreach($corsdomains as $domain) + { + $domain = trim($domain); + if($domain != '') + { + $whitelist[] = $domain; + } + } + } + + if(!$origin OR $origin == '' OR !isset($whitelist[$origin])) + { + # set current website as default origin and block all cross origin calls + $origin = $this->urlinfo['baseurl']; + } + + $response = $response->withHeader('Access-Control-Allow-Origin', $origin) + ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization') + ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS') + ->withHeader('Access-Control-Allow-Credentials', 'true'); + return $response; } } \ No newline at end of file diff --git a/system/typemill/Middleware/RemoveCredentialsMiddleware.php b/system/typemill/Middleware/RemoveCredentialsMiddleware.php new file mode 100644 index 0000000..a0fe634 --- /dev/null +++ b/system/typemill/Middleware/RemoveCredentialsMiddleware.php @@ -0,0 +1,28 @@ +getUri(); + + # Remove user information (username:password) from the URI + $uri = $uri->withUserInfo(''); + + # Create a new request with the modified URI + $request = $request->withUri($uri); + + # we could add basic auth credentials to request for later usage + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/SessionMiddleware.php b/system/typemill/Middleware/SessionMiddleware.php index bb21251..14afca3 100644 --- a/system/typemill/Middleware/SessionMiddleware.php +++ b/system/typemill/Middleware/SessionMiddleware.php @@ -11,54 +11,52 @@ use Typemill\Models\User; class SessionMiddleware implements MiddlewareInterface { - protected $segments; + protected $segments; - protected $route; + protected $route; + + public function __construct($segments, $route) + { + $this->segments = $segments; - protected $uri; - - public function __construct($segments, $route, $uri) - { - $this->segments = $segments; - - $this->route = $route; - - $this->uri = $uri; - } - + $this->route = $route; + } + public function process(Request $request, RequestHandler $handler) :response - { - $scheme = $request->getUri()->getScheme(); - - # start session - Session::startSessionForSegments($this->segments, $this->route, $scheme); + { + $uri = $request->getUri(); - $authenticated = ( - (isset($_SESSION['username'])) && - (isset($_SESSION['login'])) - ) - ? true : false; + $scheme = $request->getUri()->getScheme(); + + # start session + Session::startSessionForSegments($this->segments, $this->route, $scheme); - if($authenticated) - { - # add userdata to the request for later use - $user = new User(); + $authenticated = ( + (isset($_SESSION['username'])) && + (isset($_SESSION['login'])) + ) + ? true : false; - if($user->setUser($_SESSION['username'])) - { - $userdata = $user->getUserData(); + if($authenticated) + { + # add userdata to the request for later use + $user = new User(); - $request = $request->withAttribute('c_username', $userdata['username']); - $request = $request->withAttribute('c_userrole', $userdata['userrole']); - if(isset($userdata['darkmode'])) - { - $request = $request->withAttribute('c_darkmode', $userdata['darkmode']); - } - } - } + if($user->setUser($_SESSION['username'])) + { + $userdata = $user->getUserData(); + + $request = $request->withAttribute('c_username', $userdata['username']); + $request = $request->withAttribute('c_userrole', $userdata['userrole']); + if(isset($userdata['darkmode'])) + { + $request = $request->withAttribute('c_darkmode', $userdata['darkmode']); + } + } + } $response = $handler->handle($request); return $response; - } + } } \ No newline at end of file diff --git a/system/typemill/Static/Helpers.php b/system/typemill/Static/Helpers.php index 4adb2c4..564d85b 100644 --- a/system/typemill/Static/Helpers.php +++ b/system/typemill/Static/Helpers.php @@ -8,6 +8,8 @@ class Helpers{ public static function urlInfo($uri) { + $uri = $uri->withUserInfo(''); + $basepath = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']); $currentpath = $uri->getPath(); $route = str_replace($basepath, '', $currentpath); diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml index aa9ed44..cdce570 100644 --- a/system/typemill/settings/system.yaml +++ b/system/typemill/settings/system.yaml @@ -240,4 +240,9 @@ fieldsetdeveloper: headersoff: type: checkbox label: Disable Custom Headers - checkboxlabel: Disable all custom headers of Typemill and send your own headers instead. \ No newline at end of file + checkboxlabel: Disable all custom headers of Typemill and send your own headers instead. + corsdomains: + type: textarea + label: Allowed Domains for API-Access (CORS) + placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org' + description: Add all domains separated with comma, that should have access to the API. Domains will be added to the cors-header. \ No newline at end of file diff --git a/system/typemill/system.php b/system/typemill/system.php index 2fa40c0..3319999 100644 --- a/system/typemill/system.php +++ b/system/typemill/system.php @@ -21,6 +21,7 @@ use Typemill\Events\OnPluginsLoaded; use Typemill\Events\OnSessionSegmentsLoaded; use Typemill\Events\OnRolesPermissionsLoaded; use Typemill\Events\OnResourcesLoaded; +use Typemill\Middleware\RemoveCredentialsMiddleware; use Typemill\Middleware\SessionMiddleware; use Typemill\Middleware\OldInputMiddleware; use Typemill\Middleware\ValidationErrorsMiddleware; @@ -75,6 +76,23 @@ $uriFactory = new UriFactory(); $uri = $uriFactory->createFromGlobals($_SERVER); $urlinfo = Helpers::urlInfo($uri); +/* PROBLEM WITH URLINFO + +* it contains basic authentication like + + ["basepath"]=> "/typemill" + ["currentpath"]=> "/typemill/api/v1/mainnavi" + ["route"]=> "/api/v1/mainnavi" + ["scheme"]=> "http" + ["authority"]=> "trendschau:password@localhost" + ["protocol"]=> "http://trendschau:password@localhost" + ["baseurl"] => "http://trendschau:password@localhost/typemill" + ["currenturl"]=> "http://trendschau:password@localhost/typemill/api/v1/mainnavi" + +* It probably contains wrong scheme when used with proxy + +*/ + $timer['settings'] = microtime(true); /**************************** @@ -305,7 +323,7 @@ foreach($middleware as $pluginMiddleware) } } -$app->add(new CustomHeadersMiddleware($settings)); +$app->add(new CustomHeadersMiddleware($settings, $urlinfo)); $app->add(new AssetMiddleware($assets, $container->get('view'))); @@ -346,7 +364,9 @@ $errorMiddleware->setErrorHandler(HttpNotFoundException::class, function ($reque $app->add($errorMiddleware); -$app->add(new SessionMiddleware($session_segments, $urlinfo['route'], $uri)); +$app->add(new SessionMiddleware($session_segments, $urlinfo['route'])); + +$app->add(new RemoveCredentialsMiddleware()); if(isset($settings['proxy']) && $settings['proxy']) { From c3517fde58420fe0c777fd3695f643cf3a53732e Mon Sep 17 00:00:00 2001 From: trendschau Date: Sat, 30 Dec 2023 20:53:51 +0100 Subject: [PATCH 06/20] Add separate cors header middleware to all api endpoints --- .../Middleware/CorsHeadersMiddleware.php | 62 +++++++++++++++++ .../Middleware/CspHeadersMiddleware.php | 67 +++++++++++++++++++ .../Middleware/CustomHeadersMiddleware.php | 43 +----------- system/typemill/routes/api.php | 7 +- system/typemill/system.php | 2 +- 5 files changed, 135 insertions(+), 46 deletions(-) create mode 100644 system/typemill/Middleware/CorsHeadersMiddleware.php create mode 100644 system/typemill/Middleware/CspHeadersMiddleware.php diff --git a/system/typemill/Middleware/CorsHeadersMiddleware.php b/system/typemill/Middleware/CorsHeadersMiddleware.php new file mode 100644 index 0000000..062db4a --- /dev/null +++ b/system/typemill/Middleware/CorsHeadersMiddleware.php @@ -0,0 +1,62 @@ +settings = $settings; + + $this->urlinfo = $urlinfo; + } + + public function process(Request $request, RequestHandler $handler) :response + { + # add the custom headers to the response after everything is processed + $response = $handler->handle($request); + + ################### + # CORS HEADER # + ################### + + $origin = $request->getHeaderLine('Origin'); + $corsdomains = isset($this->settings['corsdomains']) ? trim($this->settings['corsdomains']) : false; + $whitelist = []; + + if($corsdomains && $corsdomains != '') + { + $corsdomains = explode(",", $corsdomains); + foreach($corsdomains as $domain) + { + $domain = trim($domain); + if($domain != '') + { + $whitelist[] = $domain; + } + } + } + + if(!$origin OR $origin == '' OR !isset($whitelist[$origin])) + { + # set current website as default origin and block all cross origin calls + $origin = $this->urlinfo['baseurl']; + } + + $response = $response->withHeader('Access-Control-Allow-Origin', $origin) + ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization') + ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS') + ->withHeader('Access-Control-Allow-Credentials', 'true'); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/CspHeadersMiddleware.php b/system/typemill/Middleware/CspHeadersMiddleware.php new file mode 100644 index 0000000..9957c3e --- /dev/null +++ b/system/typemill/Middleware/CspHeadersMiddleware.php @@ -0,0 +1,67 @@ +settings = $settings; + + $this->urlinfo = $urlinfo; + } + + public function process(Request $request, RequestHandler $handler) :response + { + $scheme = $request->getUri()->getScheme(); + + # add the custom headers to the response after everything is processed + $response = $handler->handle($request); + + ################### + # CSP HEADER # + ################### + + $cspdomains = isset($this->settings['cspdomains']) ? trim($this->settings['cspdomains']) : false; + $defaultsrc = "default-src 'unsafe-inline' 'unsafe-eval' 'self'"; + + if($cspdomains && $cspdomains != '') + { + $cspdomains = explode(",", $cspdomains); + foreach($cspdomains as $cspdomain) + { + $cspdomain = trim($cspdomain); + if($cspdomain != '') + { + $defaultsrc .= ' ' . $cspdomain; + } + } + } + + # dispatch to get from plugins + # get yaml from current theme + + # Define the Content Security Policy header + $cspHeader = $defaultsrc . ";"; // Default source is restricted to 'self' +# $cspHeader .= "frame-src 'self' https://www.youtube.com;"; // Allowing embedding YouTube videos +# $cspHeader .= "img-src *"; +# $cspHeader .= "media-src *"; +# $cspHeader .= "script-src *"; +# $cspHeader .= "style-src *"; +# $cspHeader .= "object-src 'none'"; + + # Add the Content Security Policy header to the response + $response = $response->withHeader('Content-Security-Policy', $cspHeader); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/CustomHeadersMiddleware.php b/system/typemill/Middleware/CustomHeadersMiddleware.php index 9a19135..98a993f 100644 --- a/system/typemill/Middleware/CustomHeadersMiddleware.php +++ b/system/typemill/Middleware/CustomHeadersMiddleware.php @@ -10,14 +10,10 @@ use Slim\Psr7\Response; class CustomHeadersMiddleware implements MiddlewareInterface { protected $settings; - - protected $urlinfo; - public function __construct($settings, $urlinfo) + public function __construct($settings) { $this->settings = $settings; - - $this->urlinfo = $urlinfo; } public function process(Request $request, RequestHandler $handler) :response @@ -32,11 +28,6 @@ class CustomHeadersMiddleware implements MiddlewareInterface $headersOff = $this->settings['headersoff'] ?? false; - - ################### - # SECURITY HEADER # - ################### - if(!$headersOff) { $response = $response @@ -51,38 +42,6 @@ class CustomHeadersMiddleware implements MiddlewareInterface } } - ################### - # CORS HEADER # - ################### - - $origin = $request->getHeaderLine('Origin'); - $corsdomains = isset($this->settings['corsdomains']) ? trim($this->settings['corsdomains']) : false; - $whitelist = []; - - if($corsdomains && $corsdomains != '') - { - $corsdomains = explode(",", $corsdomains); - foreach($corsdomains as $domain) - { - $domain = trim($domain); - if($domain != '') - { - $whitelist[] = $domain; - } - } - } - - if(!$origin OR $origin == '' OR !isset($whitelist[$origin])) - { - # set current website as default origin and block all cross origin calls - $origin = $this->urlinfo['baseurl']; - } - - $response = $response->withHeader('Access-Control-Allow-Origin', $origin) - ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization') - ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS') - ->withHeader('Access-Control-Allow-Credentials', 'true'); - return $response; } } \ No newline at end of file diff --git a/system/typemill/routes/api.php b/system/typemill/routes/api.php index 05bd1bb..da573f3 100644 --- a/system/typemill/routes/api.php +++ b/system/typemill/routes/api.php @@ -3,6 +3,7 @@ use Slim\Routing\RouteCollectorProxy; use Typemill\Middleware\ApiAuthentication; use Typemill\Middleware\ApiAuthorization; +use Typemill\Middleware\CorsHeadersMiddleware; use Typemill\Controllers\ControllerApiGlobals; use Typemill\Controllers\ControllerApiMedia; use Typemill\Controllers\ControllerApiSystemSettings; @@ -85,7 +86,7 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) { $group->get('/meta', ControllerApiAuthorMeta::class . ':getMeta')->setName('api.meta.get')->add(new ApiAuthorization($acl, 'mycontent', 'view')); $group->post('/meta', ControllerApiAuthorMeta::class . ':updateMeta')->setName('api.metadata.update')->add(new ApiAuthorization($acl, 'mycontent', 'update')); -})->add(new ApiAuthentication()); +})->add(new CorsHeadersMiddleware($settings, $urlinfo))->add(new ApiAuthentication()); # api-routes from plugins if(isset($routes['api']) && !empty($routes['api'])) @@ -102,12 +103,12 @@ if(isset($routes['api']) && !empty($routes['api'])) if($resources && $privilege) { # protected api requires authentication and authorization - $app->{$method}($route, $class)->setName($name)->add(new ApiAuthorization($acl, $resource, $privilege))->add(new ApiAuthentication()); + $app->{$method}($route, $class)->setName($name)->add(new ApiAuthorization($acl, $resource, $privilege))->add(new CorsHeadersMiddleware($settings, $urlinfo))->add(new ApiAuthentication()); } else { # public api routes - $app->{$method}($route, $class)->setName($name); + $app->{$method}($route, $class)->setName($name)->add(new CorsHeadersMiddleware($settings, $urlinfo)); } } } \ No newline at end of file diff --git a/system/typemill/system.php b/system/typemill/system.php index 3319999..4602df9 100644 --- a/system/typemill/system.php +++ b/system/typemill/system.php @@ -323,7 +323,7 @@ foreach($middleware as $pluginMiddleware) } } -$app->add(new CustomHeadersMiddleware($settings, $urlinfo)); +$app->add(new CustomHeadersMiddleware($settings)); $app->add(new AssetMiddleware($assets, $container->get('view'))); From 79b3be361931bfad27ddb775339edaff8a5bcea8 Mon Sep 17 00:00:00 2001 From: trendschau Date: Mon, 1 Jan 2024 19:49:46 +0100 Subject: [PATCH 07/20] Version 2.1.0 CSP Middleware --- system/typemill/Events/OnCspLoaded.php | 14 ++++++ .../Middleware/CspHeadersMiddleware.php | 49 ++++++++++--------- system/typemill/routes/api.php | 2 +- system/typemill/routes/web.php | 13 ++--- system/typemill/system.php | 18 +++++++ 5 files changed, 67 insertions(+), 29 deletions(-) create mode 100644 system/typemill/Events/OnCspLoaded.php diff --git a/system/typemill/Events/OnCspLoaded.php b/system/typemill/Events/OnCspLoaded.php new file mode 100644 index 0000000..cdae7ec --- /dev/null +++ b/system/typemill/Events/OnCspLoaded.php @@ -0,0 +1,14 @@ +settings = $settings; - $this->urlinfo = $urlinfo; + $this->$cspFromPlugins = $cspFromPlugins; + + $this->$cspFromTheme = $cspFromTheme; } public function process(Request $request, RequestHandler $handler) :response - { - $scheme = $request->getUri()->getScheme(); - + { # add the custom headers to the response after everything is processed $response = $handler->handle($request); - ################### - # CSP HEADER # - ################### + $whitelist = ["'unsafe-inline'", "'unsafe-eval'", "'self'", "*.youtube-nocookie.com", "*.youtube.com"]; $cspdomains = isset($this->settings['cspdomains']) ? trim($this->settings['cspdomains']) : false; - $defaultsrc = "default-src 'unsafe-inline' 'unsafe-eval' 'self'"; if($cspdomains && $cspdomains != '') { @@ -42,23 +41,29 @@ class CspHeadersMiddleware implements MiddlewareInterface $cspdomain = trim($cspdomain); if($cspdomain != '') { - $defaultsrc .= ' ' . $cspdomain; + $whitelist[] = $cspdomain; } } } - # dispatch to get from plugins - # get yaml from current theme + # add csp from plugins + if($this->cspFromPlugins && is_array($this->cspFromPlugins) && !empty($this->cspFromPlugins)) + { + $whitelist = array_merge($whitelist, $this->cspFromPlugins); + } + + # add csp from current theme + if($this->cspFromTheme && is_array($this->cspFromTheme) && !empty($this->cspFromTheme)) + { + $whitelist = array_merge($whitelist, $this->cspFromTheme); + } + + $whitelist = array_unique($whitelist); + $whitelist = implode(' ', $whitelist); # Define the Content Security Policy header - $cspHeader = $defaultsrc . ";"; // Default source is restricted to 'self' -# $cspHeader .= "frame-src 'self' https://www.youtube.com;"; // Allowing embedding YouTube videos -# $cspHeader .= "img-src *"; -# $cspHeader .= "media-src *"; -# $cspHeader .= "script-src *"; -# $cspHeader .= "style-src *"; -# $cspHeader .= "object-src 'none'"; - + $cspHeader = "default-src " . $whitelist . ";"; + # Add the Content Security Policy header to the response $response = $response->withHeader('Content-Security-Policy', $cspHeader); diff --git a/system/typemill/routes/api.php b/system/typemill/routes/api.php index da573f3..f47f867 100644 --- a/system/typemill/routes/api.php +++ b/system/typemill/routes/api.php @@ -92,7 +92,7 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) { if(isset($routes['api']) && !empty($routes['api'])) { foreach($routes['api'] as $pluginRoute) - { + { $method = $pluginRoute['httpMethod'] ?? false; $route = $pluginRoute['route'] ?? false; $class = $pluginRoute['class'] ?? false; diff --git a/system/typemill/routes/web.php b/system/typemill/routes/web.php index 4024b0f..037269d 100644 --- a/system/typemill/routes/web.php +++ b/system/typemill/routes/web.php @@ -4,6 +4,7 @@ use Slim\Routing\RouteCollectorProxy; use Typemill\Middleware\WebRedirectIfAuthenticated; use Typemill\Middleware\WebRedirectIfUnauthenticated; use Typemill\Middleware\WebAuthorization; +use Typemill\Middleware\CspHeadersMiddleware; use Typemill\Controllers\ControllerWebSetup; use Typemill\Controllers\ControllerWebAuth; use Typemill\Controllers\ControllerWebRecover; @@ -31,7 +32,7 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($settings) { $group->post('/reset', ControllerWebRecover::class . ':resetPassword')->setName('auth.reset'); } -})->add(new WebRedirectIfAuthenticated($routeParser, $settings)); +})->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebRedirectIfAuthenticated($routeParser, $settings)); # AUTHOR AREA (requires authentication) $app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl) { @@ -51,7 +52,7 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl) $group->get('/content/visual[/{route:.*}]', ControllerWebAuthor::class . ':showBlox')->setName('content.visual')->add(new WebAuthorization($routeParser, $acl, 'mycontent', 'view')); $group->get('/content/raw[/{route:.*}]', ControllerWebAuthor::class . ':showRaw')->setName('content.raw')->add(new WebAuthorization($routeParser, $acl, 'mycontent', 'view')); -})->add(new WebRedirectIfUnauthenticated($routeParser)); +})->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebRedirectIfUnauthenticated($routeParser)); $app->redirect('/tm', $routeParser->urlFor('auth.show'), 302); $app->redirect('/tm/', $routeParser->urlFor('auth.show'), 302); @@ -74,11 +75,11 @@ if(isset($routes['web']) && !empty($routes['web'])) if($resources && $privilege) { - $app->{$method}($route, $class)->setName($name)->add(new WebAuthorization($routeParser, $acl, $resource, $privilege))->add(new WebRedirectIfUnauthenticated($routeParser)); + $app->{$method}($route, $class)->setName($name)->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebAuthorization($routeParser, $acl, $resource, $privilege))->add(new WebRedirectIfUnauthenticated($routeParser)); } else { - $app->{$method}($route, $class)->setName($name); + $app->{$method}($route, $class)->setName($name)->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme)); } } } @@ -86,10 +87,10 @@ if(isset($routes['web']) && !empty($routes['web'])) if(isset($settings['access']) && $settings['access'] != '') { # if access for website is restricted - $app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new WebAuthorization($routeParser, $acl, 'account', 'view')); + $app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebAuthorization($routeParser, $acl, 'account', 'view')); } else { # if access is not restricted - $app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home'); + $app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme)); } diff --git a/system/typemill/system.php b/system/typemill/system.php index 4602df9..70e30a8 100644 --- a/system/typemill/system.php +++ b/system/typemill/system.php @@ -21,6 +21,7 @@ use Typemill\Events\OnPluginsLoaded; use Typemill\Events\OnSessionSegmentsLoaded; use Typemill\Events\OnRolesPermissionsLoaded; use Typemill\Events\OnResourcesLoaded; +use Typemill\Events\OnCspLoaded; use Typemill\Middleware\RemoveCredentialsMiddleware; use Typemill\Middleware\SessionMiddleware; use Typemill\Middleware\OldInputMiddleware; @@ -378,9 +379,25 @@ if(isset($settings['proxy']) && $settings['proxy']) $timer['middleware'] = microtime(true); +/****************************** +* GET CSP FROM PLUGINS/THEMES * +******************************/ + +# get additional csp headers from plugins +$cspFromPlugins = $dispatcher->dispatch(new OnCspLoaded([]), 'onCspLoaded')->getData(); + +# get additional csp headers from theme +$cspFromTheme = []; +$themeSettings = $settingsModel->getObjectSettings('themes', $settings['theme']); +if(isset($themeSettings['csp']) && is_array($themeSettings['csp']) && !empty($themeSettings['csp']) ) +{ + $cspFromTheme = $themeSettings['csp']; +} + /************************ * ADD ROUTES * ************************/ + if(isset($settings['setup']) && $settings['setup'] == true) { require __DIR__ . '/routes/setup.php'; @@ -393,6 +410,7 @@ else $timer['routes'] = microtime(true); + /************************ * RUN APP * ************************/ From a8a271d0915532d3a9be6122ce8e0889aa41357a Mon Sep 17 00:00:00 2001 From: trendschau Date: Wed, 3 Jan 2024 13:23:57 +0100 Subject: [PATCH 08/20] v2.1.0 updated developer fields, fixed license, fixed cspheadermiddleware --- composer.lock | 12 ++++---- .../Middleware/CspHeadersMiddleware.php | 8 ++--- system/typemill/author/js/vue-license.js | 29 ------------------- system/typemill/author/system/license.twig | 7 ----- system/typemill/settings/defaults.yaml | 2 +- system/typemill/settings/system.yaml | 29 +++++++++++-------- system/typemill/system.php | 19 +----------- 7 files changed, 29 insertions(+), 77 deletions(-) diff --git a/composer.lock b/composer.lock index f970e95..e7f012d 100644 --- a/composer.lock +++ b/composer.lock @@ -1582,16 +1582,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v5.4.26", + "version": "v5.4.34", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac" + "reference": "e3bca343efeb613f843c254e7718ef17c9bdf7a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5dcc00e03413f05c1e7900090927bb7247cb0aac", - "reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e3bca343efeb613f843c254e7718ef17c9bdf7a3", + "reference": "e3bca343efeb613f843c254e7718ef17c9bdf7a3", "shasum": "" }, "require": { @@ -1647,7 +1647,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.26" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.34" }, "funding": [ { @@ -1663,7 +1663,7 @@ "type": "tidelift" } ], - "time": "2023-07-06T06:34:20+00:00" + "time": "2023-12-27T21:12:56+00:00" }, { "name": "symfony/event-dispatcher-contracts", diff --git a/system/typemill/Middleware/CspHeadersMiddleware.php b/system/typemill/Middleware/CspHeadersMiddleware.php index 49df947..c0767c1 100644 --- a/system/typemill/Middleware/CspHeadersMiddleware.php +++ b/system/typemill/Middleware/CspHeadersMiddleware.php @@ -11,17 +11,17 @@ class CspHeadersMiddleware implements MiddlewareInterface { protected $settings; - protected $cspFromPlugins = false; + protected $cspFromPlugins; - protected $cspFromTheme = false; + protected $cspFromTheme; public function __construct($settings, $cspFromPlugins, $cspFromTheme) { $this->settings = $settings; - $this->$cspFromPlugins = $cspFromPlugins; + $this->cspFromPlugins = $cspFromPlugins; - $this->$cspFromTheme = $cspFromTheme; + $this->cspFromTheme = $cspFromTheme; } public function process(Request $request, RequestHandler $handler) :response diff --git a/system/typemill/author/js/vue-license.js b/system/typemill/author/js/vue-license.js index 6b077e4..4b178d0 100644 --- a/system/typemill/author/js/vue-license.js +++ b/system/typemill/author/js/vue-license.js @@ -43,35 +43,6 @@ const app = Vue.createApp({ - `, data() { diff --git a/system/typemill/author/system/license.twig b/system/typemill/author/system/license.twig index c381395..f92624d 100644 --- a/system/typemill/author/system/license.twig +++ b/system/typemill/author/system/license.twig @@ -17,11 +17,4 @@ app.config.globalProperties.$filters = translatefilter; app.mount('#license'); - - {% endblock %} \ No newline at end of file diff --git a/system/typemill/settings/defaults.yaml b/system/typemill/settings/defaults.yaml index 6123000..3fd0912 100644 --- a/system/typemill/settings/defaults.yaml +++ b/system/typemill/settings/defaults.yaml @@ -1,4 +1,4 @@ -version: '2.0.3' +version: '2.1.0' title: 'Typemill' author: 'Unknown' copyright: false diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml index cdce570..dce332b 100644 --- a/system/typemill/settings/system.yaml +++ b/system/typemill/settings/system.yaml @@ -220,29 +220,34 @@ fieldsetsecurity: aftererror: 'Show after first wrong input' fieldsetdeveloper: type: fieldset - legend: Developer + legend: "Developer" fields: displayErrorDetails: type: checkbox - label: Error reporting - checkboxlabel: Display application errors + label: "Error reporting" + checkboxlabel: "Display application errors" twigcache: type: checkbox - label: Twig cache - checkboxlabel: Activate the cache for twig templates + label: "Twig cache" + checkboxlabel: "Activate the cache for twig templates" proxy: type: checkbox - label: Proxy - checkboxlabel: Use x-forwarded-header. + label: "Proxy" + checkboxlabel: "Use x-forwarded-header" trustedproxies: type: text - label: Trusted IPs for proxies (comma separated) + label: "Trusted IPs for proxies (comma separated)" headersoff: type: checkbox - label: Disable Custom Headers - checkboxlabel: Disable all custom headers of Typemill and send your own headers instead. + label: "Disable Custom Headers" + checkboxlabel: "Disable all custom headers of Typemill and send your own headers instead." corsdomains: type: textarea - label: Allowed Domains for API-Access (CORS) + label: "Allowed Domains for API-Access (CORS)" placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org' - description: Add all domains separated with comma, that should have access to the API. Domains will be added to the cors-header. \ No newline at end of file + description: "Add all domains separated with comma, that should have access to the Typemill API. Domains will be added to the cors-header." + cspdomains: + type: textarea + label: "Allowed Domains for Content on Typemill (CSP)" + placeholder: 'https://www.google.com,*google.com' + description: "Add all domains separated with comma, that you want to integrate on your Typemill website. Domains will be added to the csp-header. Usually done with plugins and themes, but add manually if something is blocked." \ No newline at end of file diff --git a/system/typemill/system.php b/system/typemill/system.php index 70e30a8..7739797 100644 --- a/system/typemill/system.php +++ b/system/typemill/system.php @@ -52,7 +52,7 @@ ini_set('display_startup_errors', 0); error_reporting(E_ALL); /**************************** -* LOAD SETTINGS * +* LOAD SETTINGS * ****************************/ $settingsModel = new Settings(); @@ -77,23 +77,6 @@ $uriFactory = new UriFactory(); $uri = $uriFactory->createFromGlobals($_SERVER); $urlinfo = Helpers::urlInfo($uri); -/* PROBLEM WITH URLINFO - -* it contains basic authentication like - - ["basepath"]=> "/typemill" - ["currentpath"]=> "/typemill/api/v1/mainnavi" - ["route"]=> "/api/v1/mainnavi" - ["scheme"]=> "http" - ["authority"]=> "trendschau:password@localhost" - ["protocol"]=> "http://trendschau:password@localhost" - ["baseurl"] => "http://trendschau:password@localhost/typemill" - ["currenturl"]=> "http://trendschau:password@localhost/typemill/api/v1/mainnavi" - -* It probably contains wrong scheme when used with proxy - -*/ - $timer['settings'] = microtime(true); /**************************** From b7fb6bcdd9e961dbc0269f8fd02211764bf293b4 Mon Sep 17 00:00:00 2001 From: trendschau Date: Wed, 3 Jan 2024 14:19:05 +0100 Subject: [PATCH 09/20] v 2.1 add more info to security features into system.yaml --- system/typemill/settings/system.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml index dce332b..46e215e 100644 --- a/system/typemill/settings/system.yaml +++ b/system/typemill/settings/system.yaml @@ -189,6 +189,7 @@ fieldsetrecover: type: checkbox label: 'Recover password' checkboxlabel: 'Activate a password recovery in the login form.' + description: "Be aware that some providers might reject emails send with this feature (php-mail)." recoversubject: type: text label: 'Email subject' @@ -197,6 +198,7 @@ fieldsetrecover: recovermessage: type: textarea label: 'Text before recover link in email message' + descriptiion: 'The recover-link will be active for 24 hours.' maxlength: 2000 fieldsetsecurity: type: fieldset @@ -210,7 +212,7 @@ fieldsetsecurity: type: checkbox label: 'Authentication code (recommended)' checkboxlabel: 'Send a 5-digit authentication code by email to confirm login.' - description: 'The authentication code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts. Make sure this complies with privacy legislation in your country.' + description: 'Please test this with your account first. If you do not get an email, make sure you have ftp-access to the server to disable the feature in the settings.yaml manually. The authentication code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts. Make sure this complies with privacy legislation in your country.' authcaptcha: type: radio label: 'Use captcha in authentication forms' From 766f799f4459c9e682ca8d1a3cc4a4894cccc9a2 Mon Sep 17 00:00:00 2001 From: trendschau Date: Wed, 3 Jan 2024 15:43:19 +0100 Subject: [PATCH 10/20] v2.1 delete port from uri --- system/typemill/Static/Helpers.php | 1 + 1 file changed, 1 insertion(+) diff --git a/system/typemill/Static/Helpers.php b/system/typemill/Static/Helpers.php index 564d85b..8aa9f31 100644 --- a/system/typemill/Static/Helpers.php +++ b/system/typemill/Static/Helpers.php @@ -9,6 +9,7 @@ class Helpers{ public static function urlInfo($uri) { $uri = $uri->withUserInfo(''); + $uri = $uri->withPort(null); $basepath = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']); $currentpath = $uri->getPath(); From af5f633aa727f36bfca0708aae786d82c2d8760a Mon Sep 17 00:00:00 2001 From: trendschau Date: Thu, 4 Jan 2024 21:01:40 +0100 Subject: [PATCH 11/20] v2.1.0 add data to csp --- system/typemill/Middleware/CspHeadersMiddleware.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/typemill/Middleware/CspHeadersMiddleware.php b/system/typemill/Middleware/CspHeadersMiddleware.php index c0767c1..79a1064 100644 --- a/system/typemill/Middleware/CspHeadersMiddleware.php +++ b/system/typemill/Middleware/CspHeadersMiddleware.php @@ -29,7 +29,7 @@ class CspHeadersMiddleware implements MiddlewareInterface # add the custom headers to the response after everything is processed $response = $handler->handle($request); - $whitelist = ["'unsafe-inline'", "'unsafe-eval'", "'self'", "*.youtube-nocookie.com", "*.youtube.com"]; + $whitelist = ["'unsafe-inline'", "'unsafe-eval'", "'self'", "data:", "*.youtube-nocookie.com", "*.youtube.com"]; $cspdomains = isset($this->settings['cspdomains']) ? trim($this->settings['cspdomains']) : false; From caf2b989214e4823f54d68ba943825733a8fd82d Mon Sep 17 00:00:00 2001 From: trendschau Date: Fri, 5 Jan 2024 19:49:44 +0100 Subject: [PATCH 12/20] v2.1.0 fix download response if no file found --- .../typemill/Controllers/ControllerWebDownload.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/system/typemill/Controllers/ControllerWebDownload.php b/system/typemill/Controllers/ControllerWebDownload.php index 4ed8a84..a81d75a 100644 --- a/system/typemill/Controllers/ControllerWebDownload.php +++ b/system/typemill/Controllers/ControllerWebDownload.php @@ -2,18 +2,20 @@ namespace Typemill\Controllers; +use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ResponseInterface as Response; use Typemill\Models\StorageWrapper; use Typemill\Static\Translations; class ControllerWebDownload extends Controller { - public function download($request, $response, $args) + public function download(Request $request, Response $response, $args) { $filename = isset($args['params']) ? $args['params'] : false; if(!$filename) { - $response->getBody()->write(Translations::translate('the requested file does not exist.'))->withStatus(404); - return $response; + $response->getBody()->write(Translations::translate('the requested file does not exist.')); + return $response->withStatus(404); } $storage = new StorageWrapper('\Typemill\Models\Storage'); @@ -26,8 +28,8 @@ class ControllerWebDownload extends Controller $allowedFiletypes = []; if(!$this->validate($filepath, $filename, $allowedFiletypes)) { - $response->getBody()->write(Translations::translate('the requested filetype does not exist.'))->withStatus(404); - return $response; + $response->getBody()->write(Translations::translate('the requested filetype does not exist.')); + return $response->withStatus(404); } if($restrictions && isset($restrictions[$filefolder . $filename])) From 0141995679565785ce69404ab58139ca6a486134 Mon Sep 17 00:00:00 2001 From: trendschau Date: Sun, 7 Jan 2024 19:00:15 +0100 Subject: [PATCH 13/20] V2.1.0 Rename auth code to login verification code --- .../typemill/Controllers/ControllerWebAuth.php | 18 +++++++++--------- system/typemill/author/auth/authcode.twig | 6 +++--- system/typemill/settings/system.yaml | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/system/typemill/Controllers/ControllerWebAuth.php b/system/typemill/Controllers/ControllerWebAuth.php index 4065880..36c2671 100644 --- a/system/typemill/Controllers/ControllerWebAuth.php +++ b/system/typemill/Controllers/ControllerWebAuth.php @@ -26,8 +26,8 @@ class ControllerWebAuth extends Controller $validation = new Validation(); $securitylog = $this->settings['securitylog'] ?? false; $authcodeactive = $this->settings['authcode'] ?? false; - $authtitle = Translations::translate('Auth code missing?'); - $authtext = Translations::translate('If you did not receive an email with an authentication code, then the username or password you entered was wrong. Please try again.'); + $authtitle = Translations::translate('Verification code missing?'); + $authtext = Translations::translate('If you did not receive an email with the verification code, then the username or password you entered was wrong. Please try again.'); if($validation->signin($input) !== true) { @@ -103,8 +103,8 @@ class ControllerWebAuth extends Controller $mail = new SimpleMail($this->settings); - $subject = Translations::translate('Your authentication code for Typemill'); - $message = Translations::translate('Use the following authentication code to login into Typemill') . ': ' . $authcodevalue; + $subject = Translations::translate('Your verification code for Typemill'); + $message = Translations::translate('Use the following verification code to login into Typemill') . ': ' . $authcodevalue; $send = $mail->send($userdata['email'], $subject, $message); @@ -113,7 +113,7 @@ class ControllerWebAuth extends Controller if(!$send) { $authtitle = Translations::translate('Error sending email'); - $authtext = Translations::translate('We could not send the email with the authentication code to your address. Reason: ') . $mail->error; + $authtext = Translations::translate('We could not send the email with the verification code to your address. Reason: ') . $mail->error; } else { @@ -169,10 +169,10 @@ class ControllerWebAuth extends Controller { if($securitylog) { - \Typemill\Static\Helpers::addLogEntry('login: invalid authcode format'); + \Typemill\Static\Helpers::addLogEntry('login: invalid verification code format'); } - $this->c->get('flash')->addMessage('error', Translations::translate('Invalid authcode format, please try again.')); + $this->c->get('flash')->addMessage('error', Translations::translate('Invalid verification code format, please try again.')); return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } @@ -199,10 +199,10 @@ class ControllerWebAuth extends Controller { if($securitylog) { - \Typemill\Static\Helpers::addLogEntry('login: authcode wrong or outdated.'); + \Typemill\Static\Helpers::addLogEntry('login: verification code wrong or outdated.'); } - $this->c->get('flash')->addMessage('error', Translations::translate('The authcode was wrong or outdated, please start again.')); + $this->c->get('flash')->addMessage('error', Translations::translate('The verification was wrong or outdated, please start again.')); return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } diff --git a/system/typemill/author/auth/authcode.twig b/system/typemill/author/auth/authcode.twig index f44f97f..90dc73f 100644 --- a/system/typemill/author/auth/authcode.twig +++ b/system/typemill/author/auth/authcode.twig @@ -1,6 +1,6 @@ {% extends 'layouts/layoutAuth.twig' %} -{% block title %}Login Authentication Code{% endblock %} +{% block title %}Login Verification Code{% endblock %} {% block content %} @@ -9,9 +9,9 @@
-

Authentication Code

+

Login Verification Code

-

{{ translate('Enter the authentication code from your email:') }} +

{{ translate('Enter the verification code from your email:') }}

diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml index 46e215e..3f06009 100644 --- a/system/typemill/settings/system.yaml +++ b/system/typemill/settings/system.yaml @@ -210,9 +210,9 @@ fieldsetsecurity: checkboxlabel: 'Track spam and suspicious actions in a logfile' authcode: type: checkbox - label: 'Authentication code (recommended)' - checkboxlabel: 'Send a 5-digit authentication code by email to confirm login.' - description: 'Please test this with your account first. If you do not get an email, make sure you have ftp-access to the server to disable the feature in the settings.yaml manually. The authentication code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts. Make sure this complies with privacy legislation in your country.' + label: 'Login Verification (recommended)' + checkboxlabel: 'Verify your login with a 5-digit code send by email.' + description: 'Please test this with your account. If you do not get an email, make sure you have ftp-access to disable the feature in the settings.yaml manually. The verification code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts. Make sure this complies with privacy legislation in your country.' authcaptcha: type: radio label: 'Use captcha in authentication forms' From a47e45719e9d61ed4f8766abb22930e2ea171ce4 Mon Sep 17 00:00:00 2001 From: trendschau Date: Sun, 7 Jan 2024 21:36:38 +0100 Subject: [PATCH 14/20] V2.1.0 add optiion to disable csp headers on single page or whole website --- system/typemill/Middleware/CspHeadersMiddleware.php | 12 ++++++++++++ system/typemill/settings/system.yaml | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/system/typemill/Middleware/CspHeadersMiddleware.php b/system/typemill/Middleware/CspHeadersMiddleware.php index 79a1064..661fc9d 100644 --- a/system/typemill/Middleware/CspHeadersMiddleware.php +++ b/system/typemill/Middleware/CspHeadersMiddleware.php @@ -29,6 +29,11 @@ class CspHeadersMiddleware implements MiddlewareInterface # add the custom headers to the response after everything is processed $response = $handler->handle($request); + if(isset($this->settings['cspdisabled']) && $this->settings['cspdisabled']) + { + return $response; + } + $whitelist = ["'unsafe-inline'", "'unsafe-eval'", "'self'", "data:", "*.youtube-nocookie.com", "*.youtube.com"]; $cspdomains = isset($this->settings['cspdomains']) ? trim($this->settings['cspdomains']) : false; @@ -59,6 +64,13 @@ class CspHeadersMiddleware implements MiddlewareInterface } $whitelist = array_unique($whitelist); + + # do not add csp header if disabled-flag is found + if(in_array("disable", $whitelist)) + { + return $response; + } + $whitelist = implode(' ', $whitelist); # Define the Content Security Policy header diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml index 3f06009..324e6e6 100644 --- a/system/typemill/settings/system.yaml +++ b/system/typemill/settings/system.yaml @@ -252,4 +252,8 @@ fieldsetdeveloper: type: textarea label: "Allowed Domains for Content on Typemill (CSP)" placeholder: 'https://www.google.com,*google.com' - description: "Add all domains separated with comma, that you want to integrate on your Typemill website. Domains will be added to the csp-header. Usually done with plugins and themes, but add manually if something is blocked." \ No newline at end of file + description: "Add all domains separated with comma, that you want to integrate on your Typemill website. Domains will be added to the csp-header. Usually done with plugins and themes, but add manually if something is blocked." + cspdisabled: + type: checkbox + label: "Disable CSP Headers" + checkboxlabel: "Disable all csp (content security policy) headers for this website." \ No newline at end of file From df5a58df0b6846a3b984cc58aabafd5fd2d6482f Mon Sep 17 00:00:00 2001 From: trendschau Date: Mon, 8 Jan 2024 14:53:25 +0100 Subject: [PATCH 15/20] V2.1.0 Fix fileupload error mtype check --- ...eenshot-2023-06-09-at-16-53-35-typemill.png | Bin 122333 -> 0 bytes .../typemill/Controllers/ControllerApiFile.php | 8 +++++--- system/typemill/Models/Media.php | 10 ++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) delete mode 100644 media/files/screenshot-2023-06-09-at-16-53-35-typemill.png diff --git a/media/files/screenshot-2023-06-09-at-16-53-35-typemill.png b/media/files/screenshot-2023-06-09-at-16-53-35-typemill.png deleted file mode 100644 index c8a20daad3214c820c365a2f176b65daad4de6e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122333 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 z 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 Csettings['rootPath'] . $filePath; + $filePath = str_replace('media/files', 'media/tmp', $fileinfo['url']); + $filePath = str_replace('/', DIRECTORY_SEPARATOR, $filePath); + $fullPath = $this->settings['rootPath'] . DIRECTORY_SEPARATOR . $filePath; $finfo = finfo_open( FILEINFO_MIME_TYPE ); $mtype = @finfo_file( $finfo, $fullPath ); finfo_close($finfo); if(!$mtype OR !$this->checkAllowedMimeTypes($mtype, $extension)) { - $media->clearTempFolder(); + $media->clearTempFolder($immediate = true); $response->getBody()->write(json_encode([ - 'message' => Translations::translate('The mime-type is missing, not allowed or does not fit to the file extension.') + 'message' => Translations::translate('The mime-type is missing, not allowed or does not fit to the file extension.') . ' mtype: ' . $mtype . ', ext: ' . $extension ])); return $response->withHeader('Content-Type', 'application/json')->withStatus(400); diff --git a/system/typemill/Models/Media.php b/system/typemill/Models/Media.php index d9d6f1c..75a8505 100644 --- a/system/typemill/Models/Media.php +++ b/system/typemill/Models/Media.php @@ -41,7 +41,7 @@ class Media $this->tmpFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; } - public function clearTempFolder() + public function clearTempFolder($immediate = NULL) { $files = scandir($this->tmpFolder); $now = time(); @@ -54,13 +54,15 @@ class Media $filelink = $this->tmpFolder . $file; if(file_exists($filelink)) { - $filetime = filemtime($filelink); - if($now - $filetime > 1800) + $filetime = filemtime($filelink); + $delete = $immediate ? $immediate : ($now - $filetime > 1800); + + if($delete) { if(!unlink($filelink)) { $result = false; - } + } } } } From 2290c87df983932c18fb74f4c475c582f9007562 Mon Sep 17 00:00:00 2001 From: trendschau Date: Mon, 8 Jan 2024 21:15:40 +0100 Subject: [PATCH 16/20] v2.1.0 increase chars of text field to 1000 --- system/typemill/Models/Validation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/typemill/Models/Validation.php b/system/typemill/Models/Validation.php index 27c59d9..04b4fe9 100644 --- a/system/typemill/Models/Validation.php +++ b/system/typemill/Models/Validation.php @@ -812,7 +812,7 @@ class Validation break; case "text": $v->rule('noHTML', $fieldName); - $v->rule('lengthMax', $fieldName, 500); + $v->rule('lengthMax', $fieldName, 1000); # $v->rule('regex', $fieldName, '/^[\pL0-9_ \-\.\?\!\/\:]*$/u'); break; case "textarea": From 1593bed754c9110bc57519cf0a9ab1a91f3be761 Mon Sep 17 00:00:00 2001 From: trendschau Date: Thu, 11 Jan 2024 16:19:37 +0100 Subject: [PATCH 17/20] Version 2.1.0 fix urlinfo --- system/typemill/Static/Helpers.php | 26 ------- system/typemill/Static/Urlinfo.php | 106 +++++++++++++++++++++++++++++ system/typemill/system.php | 52 ++++++++++---- 3 files changed, 144 insertions(+), 40 deletions(-) create mode 100644 system/typemill/Static/Urlinfo.php diff --git a/system/typemill/Static/Helpers.php b/system/typemill/Static/Helpers.php index 8aa9f31..818602f 100644 --- a/system/typemill/Static/Helpers.php +++ b/system/typemill/Static/Helpers.php @@ -6,32 +6,6 @@ use Typemill\Models\StorageWrapper; class Helpers{ - public static function urlInfo($uri) - { - $uri = $uri->withUserInfo(''); - $uri = $uri->withPort(null); - - $basepath = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']); - $currentpath = $uri->getPath(); - $route = str_replace($basepath, '', $currentpath); - $scheme = $uri->getScheme(); - $authority = $uri->getAuthority(); - $protocol = ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : ''); - $baseurl = $protocol . $basepath; - $currenturl = $protocol . $currentpath; - - return [ - 'basepath' => $basepath, - 'currentpath' => $currentpath, - 'route' => $route, - 'scheme' => $scheme, - 'authority' => $authority, - 'protocol' => $protocol, - 'baseurl' => $baseurl, - 'currenturl' => $currenturl - ]; - } - public static function getUserIP() { $client = @$_SERVER['HTTP_CLIENT_IP']; diff --git a/system/typemill/Static/Urlinfo.php b/system/typemill/Static/Urlinfo.php new file mode 100644 index 0000000..c704dfa --- /dev/null +++ b/system/typemill/Static/Urlinfo.php @@ -0,0 +1,106 @@ +withUserInfo(''); + $uri = $uri->withPort(null); + + $currentpath = $uri->getPath(); + $route = str_replace($basepath, '', $currentpath); + + $query = $uri->getQuery(); + parse_str($query, $params); + + # proxy detection + if(isset($settings['proxy']) && $settings['proxy'] && isset($_SERVER['HTTP_X_FORWARDED_HOST'])) + { + $trustedProxies = ( isset($settings['trustedproxies']) && !empty($settings['trustedproxies']) ) ? explode(",", $settings['trustedproxies']) : []; + + $proxyuri = self::updateUri($uri, $trustedProxies); + + if($proxyuri) + { + # use uri from proxy + $uri = $proxyuri; + + # standard basepath is empty + $basepath = ""; + + # if proxy has basepath, then + if (isset($_SERVER['HTTP_X_FORWARDED_PREFIX'])) + { + # Use X-Forwarded-Prefix if available + $basepath = rtrim($_SERVER['HTTP_X_FORWARDED_PREFIX'], '/') . '/'; + } + } + } + + $scheme = $uri->getScheme(); + $authority = $uri->getAuthority(); + $protocol = ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : ''); + $baseurl = $protocol . $basepath; + $currenturl = $baseurl . $route; + + return [ + 'basepath' => $basepath, + 'currentpath' => $currentpath, + 'route' => $route, + 'scheme' => $scheme, + 'authority' => $authority, + 'protocol' => $protocol, + 'baseurl' => $baseurl, + 'baseurlWithoutProxy' => false, # add the base url without proxy maybe needed for license? + 'currenturl' => $currenturl, + 'params' => $params + ]; + } + + private static function updateUri($uri, $trustedProxies) + { + # optionally check trusted proxies + $ipAddress = $_SERVER['REMOTE_ADDR'] ?? null; + if ( + $ipAddress + && !empty($trustedProxies) + && !in_array($ipAddress, $trustedProxies) + ) + { + return false; + } + + # get scheme from proxy + $scheme = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null; + if ( + $scheme + && in_array($scheme, ['http', 'https']) + ) + { + $uri = $uri->withScheme($scheme); + } + + # get host from proxy + $host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? null; + if ( + $host + ) + { + $host = trim(current(explode(',', $host))); + + $pos = strpos($host, ':'); + if ($pos !== false) + { + $host = strstr($host, ':', true); + } + $uri = $uri->withHost($host); + } + + return $uri; + } +} \ No newline at end of file diff --git a/system/typemill/system.php b/system/typemill/system.php index 7739797..8b07114 100644 --- a/system/typemill/system.php +++ b/system/typemill/system.php @@ -1,3 +1,4 @@ + loadSettings(); + /**************************** * HANDLE DISPLAY ERRORS * ****************************/ @@ -68,19 +73,9 @@ if(isset($settings['displayErrorDetails']) && $settings['displayErrorDetails']) # ini_set('display_startup_errors', 1); } -/**************************** -* ADD PATH-INFOS FOR LATER * -****************************/ - -# ADD THEM TO THE SETTINGS AND YOU HAVE THEM EVERYWHERE?? -$uriFactory = new UriFactory(); -$uri = $uriFactory->createFromGlobals($_SERVER); -$urlinfo = Helpers::urlInfo($uri); - -$timer['settings'] = microtime(true); /**************************** -* CREATE CONTAINER * +* CREATE CONTAINER + APP * ****************************/ # https://www.slimframework.com/docs/v4/start/upgrade.html#changes-to-container @@ -96,11 +91,40 @@ $routeParser = $app->getRouteCollector()->getRouteParser(); # add route parser to container to use named routes in controller $container->set('routeParser', $routeParser); -# set urlinfo -$container->set('urlinfo', $urlinfo); + +/******************************* + * Basepath * + ******************************/ # in slim 4 you alsways have to set application basepath -$app->setBasePath($urlinfo['basepath']); +$basepath = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']); +$app->setBasePath($basepath); + + +/**************** +* URLINFO * +****************/ + +# WE DO NOT NEED IT HERE? +# WE CAN ADD IT TO CONTAINER IN MIDDLEWARE AFTER PROXY DETECTION + +# WE NEED FOR +# - LICENSE (base url) +# - Each plugin to add container +# - SESSION SEGMEŃTS (route) +# - TRANSLATIONS (route) +# - ASSETS (route) +# - TWIG URL EXTENSION +# - SESSION MIDDLEWARE + +$uriFactory = new UriFactory(); +$uri = $uriFactory->createFromGlobals($_SERVER); +$urlinfo = Urlinfo::getUrlInfo($basepath, $uri, $settings); + +$timer['settings'] = microtime(true); + +# set urlinfo +$container->set('urlinfo', $urlinfo); $timer['container'] = microtime(true); From c5437b9bfe8aadb34af825ee7def5839b08f7a83 Mon Sep 17 00:00:00 2001 From: trendschau Date: Thu, 11 Jan 2024 21:40:26 +0100 Subject: [PATCH 18/20] Version 2.1.0 add testmail feature --- .../Controllers/ControllerApiTestmail.php | 61 +++++++++++++++++++ .../Controllers/ControllerWebAuth.php | 19 +++++- system/typemill/author/js/vue-forms.js | 26 ++++---- system/typemill/author/js/vue-system.js | 35 ++++++++++- system/typemill/routes/api.php | 2 + system/typemill/settings/system.yaml | 47 +++++++------- 6 files changed, 149 insertions(+), 41 deletions(-) create mode 100644 system/typemill/Controllers/ControllerApiTestmail.php diff --git a/system/typemill/Controllers/ControllerApiTestmail.php b/system/typemill/Controllers/ControllerApiTestmail.php new file mode 100644 index 0000000..e68c1b1 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiTestmail.php @@ -0,0 +1,61 @@ +settings['mailfrom']) or !filter_var($this->settings['mailfrom'], FILTER_VALIDATE_EMAIL)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('The from mail is missing or it is not a valid e-mail address.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $user = new User(); + $username = $request->getAttribute('c_username'); + + if(!$user->setUser($username)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We did not find the a user or usermail.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $userdata = $user->getUserData(); + + $mail = new SimpleMail($this->settings); + + $subject = Translations::translate('Testmail from Typemill'); + $message = Translations::translate('This is a testmail from Typemill and if you read this e-mail, then everything works fine.'); + + $send = $mail->send($userdata['email'], $subject, $message); + + if(!$send) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not send the testmail to your e-mail address. Reason: ') . $mail->error + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('The testmail has been send, please check your inbox and your spam-folder to varify that you received the mail.') + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebAuth.php b/system/typemill/Controllers/ControllerWebAuth.php index 36c2671..6359fa8 100644 --- a/system/typemill/Controllers/ControllerWebAuth.php +++ b/system/typemill/Controllers/ControllerWebAuth.php @@ -25,7 +25,7 @@ class ControllerWebAuth extends Controller $input = $request->getParsedBody(); $validation = new Validation(); $securitylog = $this->settings['securitylog'] ?? false; - $authcodeactive = $this->settings['authcode'] ?? false; + $authcodeactive = $this->isAuthcodeActive($this->settings); $authtitle = Translations::translate('Verification code missing?'); $authtext = Translations::translate('If you did not receive an email with the verification code, then the username or password you entered was wrong. Please try again.'); @@ -108,8 +108,6 @@ class ControllerWebAuth extends Controller $send = $mail->send($userdata['email'], $subject, $message); - $send = true; - if(!$send) { $authtitle = Translations::translate('Error sending email'); @@ -158,6 +156,21 @@ class ControllerWebAuth extends Controller } + private function isAuthcodeActive($settings) + { + if( + isset($settings['authcode']) && + $settings['authcode'] && + isset($settings['mailfrom']) && + filter_var($settings['mailfrom'], FILTER_VALIDATE_EMAIL) + ) + { + return true; + } + + return false; + } + # login a user with valid authcode public function loginWithAuthcode(Request $request, Response $response) { diff --git a/system/typemill/author/js/vue-forms.js b/system/typemill/author/js/vue-forms.js index 5e4765f..b686dd3 100644 --- a/system/typemill/author/js/vue-forms.js +++ b/system/typemill/author/js/vue-forms.js @@ -32,14 +32,13 @@ app.component('component-textarea', { + @input="update($event, name)">

{{ errors[name] }}

{{ $filters.translate(description) }}

`, @@ -81,8 +80,7 @@ app.component('component-codearea', { :name="name" :placeholder="placeholder" :value="value" - @input="update($event, name)"> - + @input="update($event, name)">

{{ errors[name] }}

@@ -141,7 +139,7 @@ app.component('component-select', { @change="update($event,name)"> - +

{{ errors[name] }}

{{ $filters.translate(description) }}

`, @@ -173,7 +171,7 @@ app.component('component-checkbox', { v-model="checked" @change="update(checked, name)"> {{ $filters.translate(checkboxlabel) }} - +

{{ errors[name] }}

{{ $filters.translate(description) }}

`, @@ -209,7 +207,7 @@ app.component('component-checkboxlist', { v-model="checkedoptions" @change="update(checkedoptions, name)"> {{ $filters.translate(option) }} - +

{{ errors[name] }}

{{ $filters.translate(description) }}

`, @@ -249,7 +247,7 @@ app.component('component-radio', { v-model="picked" @change="update(picked, name)"> {{ $filters.translate(option) }} - +

{{ errors[name] }}

{{ $filters.translate(description) }}

`, @@ -277,7 +275,7 @@ app.component('component-number', { :name="name" :placeholder="placeholder" :value="value" - @input="update($event, name)"> + @input="update($event, name)">

{{ errors[name] }}

{{ $filters.translate(description) }}

`, @@ -308,7 +306,7 @@ app.component('component-date', { :name="name" :placeholder="placeholder" :value="value" - @input="update($event, name)"> + @input="update($event, name)">

{{ errors[name] }}

{{ $filters.translate(description) }}

@@ -342,7 +340,7 @@ app.component('component-email', { :name="name" :placeholder="placeholder" :value="value" - @input="update($event, name)"> + @input="update($event, name)">

{{ errors[name] }}

{{ $filters.translate(description) }}

@@ -375,7 +373,7 @@ app.component('component-tel', { :name="name" :placeholder="placeholder" :value="value" - @input="update($event, name)"> + @input="update($event, name)">

{{ errors[name] }}

{{ $filters.translate(description) }}

@@ -409,7 +407,7 @@ app.component('component-url', { :name="name" :placeholder="placeholder" :value="value" - @input="update($event, name)"> + @input="update($event, name)">

{{ errors[name] }}

{{ $filters.translate(description) }}

@@ -442,7 +440,7 @@ app.component('component-color', { :name="name" :placeholder="placeholder" :value="value" - @input="update($event, name)"> + @input="update($event, name)">

{{ errors[name] }}

{{ $filters.translate(description) }}

diff --git a/system/typemill/author/js/vue-system.js b/system/typemill/author/js/vue-system.js index da87e5f..7a5b276 100644 --- a/system/typemill/author/js/vue-system.js +++ b/system/typemill/author/js/vue-system.js @@ -17,6 +17,13 @@ const app = Vue.createApp({ :userroles="userroles" :value="formData[fieldname]" v-bind="subfieldDefinition"> + + + @@ -85,6 +92,32 @@ const app = Vue.createApp({ this.currentTab = tab; this.reset(); }, + testmail: function() + { + this.reset(); + var self = this; + + tmaxios.post('/api/v1/testmail',{ + 'url': data.urlinfo.route, + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + }) + .catch(function (error) + { + if(error.response) + { + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, save: function() { this.reset(); @@ -109,7 +142,7 @@ const app = Vue.createApp({ self.errors = error.response.data.errors; } } - }); + }); }, reset: function() { diff --git a/system/typemill/routes/api.php b/system/typemill/routes/api.php index f47f867..7abf57c 100644 --- a/system/typemill/routes/api.php +++ b/system/typemill/routes/api.php @@ -19,6 +19,7 @@ use Typemill\Controllers\ControllerApiAuthorArticle; use Typemill\Controllers\ControllerApiAuthorBlock; use Typemill\Controllers\ControllerApiAuthorMeta; use Typemill\Controllers\ControllerApiAuthorShortcode; +use Typemill\Controllers\ControllerApiTestmail; $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) { @@ -35,6 +36,7 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) { $group->post('/plugin', ControllerApiSystemPlugins::class . ':updatePlugin')->setName('api.plugin.set')->add(new ApiAuthorization($acl, 'system', 'update')); # admin $group->post('/extensions', ControllerApiSystemExtensions::class . ':activateExtension')->setName('api.extension.activate')->add(new ApiAuthorization($acl, 'system', 'update')); # admin $group->post('/versioncheck', ControllerApiSystemVersions::class . ':checkVersions')->setName('api.versioncheck')->add(new ApiAuthorization($acl, 'system', 'update')); # admin + $group->post('/testmail', ControllerApiTestmail::class . ':send')->setName('api.testmail')->add(new ApiAuthorization($acl, 'system', 'update')); # admin $group->get('/users/getbynames', ControllerApiSystemUsers::class . ':getUsersByNames')->setName('api.usersbynames')->add(new ApiAuthorization($acl, 'user', 'update')); # admin $group->get('/users/getbyemail', ControllerApiSystemUsers::class . ':getUsersByEmail')->setName('api.usersbyemail')->add(new ApiAuthorization($acl, 'user', 'update')); # admin $group->get('/users/getbyrole', ControllerApiSystemUsers::class . ':getUsersByRole')->setName('api.usersbyrole')->add(new ApiAuthorization($acl, 'user', 'update')); # admin diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml index 324e6e6..f495185 100644 --- a/system/typemill/settings/system.yaml +++ b/system/typemill/settings/system.yaml @@ -164,22 +164,22 @@ fieldsetmail: legend: Email fields: mailfrom: - type: text + type: email label: 'Mail From (required)' placeholder: sender@yourmail.org - maxlength: 60 - description: 'Enter an email address that should send emails (sender).' + maxlength: 100 + description: 'Enter an email address that sends the e-mails (sender). The e-mail-feature will be used for recovery and verification e-mails. Send a testmail to your user-account to verify that you receive the e-mails.' mailfromname: type: text label: 'Mail From Name (optional)' placeholder: sender name - maxlength: 60 + maxlength: 100 description: 'Optionally enter a name for the sender address. If not set, the from-address will be visible.' replyto: type: text label: 'Reply To (optional)' placeholder: noreply@yourmail.org - maxlength: 60 + maxlength: 100 description: 'Optionally enter a "reply to" address for answers from the receiver. If not set, answers will go to the from-address.' fieldsetrecover: type: fieldset @@ -189,7 +189,7 @@ fieldsetrecover: type: checkbox label: 'Recover password' checkboxlabel: 'Activate a password recovery in the login form.' - description: "Be aware that some providers might reject emails send with this feature (php-mail)." + description: "From mail is required for this feature. Send a testmail before you use this feature." recoversubject: type: text label: 'Email subject' @@ -204,15 +204,11 @@ fieldsetsecurity: type: fieldset legend: Security fields: - securitylog: - type: checkbox - label: 'Security log' - checkboxlabel: 'Track spam and suspicious actions in a logfile' authcode: type: checkbox label: 'Login Verification (recommended)' checkboxlabel: 'Verify your login with a 5-digit code send by email.' - description: 'Please test this with your account. If you do not get an email, make sure you have ftp-access to disable the feature in the settings.yaml manually. The verification code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts. Make sure this complies with privacy legislation in your country.' + description: 'From mail is required for this feature. Send a testmail before you use this feature. Make sure you have ftp-access to disable the feature in settings.yaml on failure. The verification code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts.' authcaptcha: type: radio label: 'Use captcha in authentication forms' @@ -220,6 +216,10 @@ fieldsetsecurity: disabled: 'Disable' standard: 'Always show' aftererror: 'Show after first wrong input' + securitylog: + type: checkbox + label: 'Security log' + checkboxlabel: 'Track spam and suspicious actions in a logfile' fieldsetdeveloper: type: fieldset legend: "Developer" @@ -242,18 +242,19 @@ fieldsetdeveloper: headersoff: type: checkbox label: "Disable Custom Headers" - checkboxlabel: "Disable all custom headers of Typemill and send your own headers instead." - corsdomains: - type: textarea - label: "Allowed Domains for API-Access (CORS)" - placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org' - description: "Add all domains separated with comma, that should have access to the Typemill API. Domains will be added to the cors-header." - cspdomains: - type: textarea - label: "Allowed Domains for Content on Typemill (CSP)" - placeholder: 'https://www.google.com,*google.com' - description: "Add all domains separated with comma, that you want to integrate on your Typemill website. Domains will be added to the csp-header. Usually done with plugins and themes, but add manually if something is blocked." + checkboxlabel: "Disable all custom headers of Typemill (except cors) and send your own headers instead." cspdisabled: type: checkbox label: "Disable CSP Headers" - checkboxlabel: "Disable all csp (content security policy) headers for this website." \ No newline at end of file + checkboxlabel: "Disable all csp headers (content security policy) for this website." + cspdomains: + type: textarea + label: "Allowed Domains for Content on Typemill (CSP-Headers)" + placeholder: 'https://www.google.com,*google.com' + description: "List all domains, separated by commas, to allow content integration, such as iframes, on your Typemill website. Domains will be added to the csp-header. Usually done with plugins and themes, but add manually if something is blocked." + corsdomains: + type: textarea + label: "Allowed Domains for API-Access (CORS-Headers)" + placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org' + description: "List all domains, separated by comma, that should have access to the Typemill API. Domains will be added to the cors-header." + \ No newline at end of file From f5faf67c5d717eddb84223ac7b48ff044ede66c3 Mon Sep 17 00:00:00 2001 From: trendschau Date: Mon, 15 Jan 2024 10:51:56 +0100 Subject: [PATCH 19/20] v2.1.0 add author line to translation files --- system/typemill/author/translations/de.yaml | 2 ++ system/typemill/author/translations/en.yaml | 2 ++ system/typemill/author/translations/fr.yaml | 2 ++ system/typemill/author/translations/it.yaml | 2 ++ 4 files changed, 8 insertions(+) diff --git a/system/typemill/author/translations/de.yaml b/system/typemill/author/translations/de.yaml index 9c6b71b..a2854b7 100644 --- a/system/typemill/author/translations/de.yaml +++ b/system/typemill/author/translations/de.yaml @@ -1,3 +1,5 @@ +# German (Deutsche) +# Author: Sebastian Schürmanns '-LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE.': '-Lizenz und die Webseite muss unter der Domain von der Lizenz laufen.' 'ACCESS_&_RIGHTS': 'Zugang & Rechte' ACCESS_FOR: 'Zugang für' diff --git a/system/typemill/author/translations/en.yaml b/system/typemill/author/translations/en.yaml index f48e9b6..cdef239 100644 --- a/system/typemill/author/translations/en.yaml +++ b/system/typemill/author/translations/en.yaml @@ -1,3 +1,5 @@ +# English +# Author: Sebastian Schuermanns '-LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE.': '' 'ACCESS_&_RIGHTS': '' ACCESS_FOR: '' diff --git a/system/typemill/author/translations/fr.yaml b/system/typemill/author/translations/fr.yaml index 927aaa2..a3cbb34 100644 --- a/system/typemill/author/translations/fr.yaml +++ b/system/typemill/author/translations/fr.yaml @@ -1,3 +1,5 @@ +# French (Français) +# Author: '-LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE.': '' 'ACCESS_&_RIGHTS': '' ACCESS_FOR: '' diff --git a/system/typemill/author/translations/it.yaml b/system/typemill/author/translations/it.yaml index b940d41..bc42361 100644 --- a/system/typemill/author/translations/it.yaml +++ b/system/typemill/author/translations/it.yaml @@ -1,3 +1,5 @@ +# Italian (Italiano) pls ignore autoupdates +# Author: Severo Iuliano (https://github.com/iusvar) '-LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE.': '' 'ACCESS_&_RIGHTS': '' ACCESS_FOR: '' From e7f311558f215f6025164a58fc6935a43dfb8c92 Mon Sep 17 00:00:00 2001 From: trendschau Date: Mon, 15 Jan 2024 17:15:20 +0100 Subject: [PATCH 20/20] V2.1.0 Fixed simplemail and empty line in template --- data/security/securitylog.txt | 1 + .../Controllers/ControllerWebAuth.php | 42 +++++++++++++++---- system/typemill/Middleware/FlashMessages.php | 2 +- system/typemill/Models/SimpleMail.php | 2 +- system/typemill/Static/License.php | 4 +- system/typemill/system.php | 6 +-- 6 files changed, 39 insertions(+), 18 deletions(-) diff --git a/data/security/securitylog.txt b/data/security/securitylog.txt index 2eee11f..b123e63 100644 --- a/data/security/securitylog.txt +++ b/data/security/securitylog.txt @@ -16,3 +16,4 @@ 127.0.0.1;2023-12-27 11:31:36;login: authcode wrong or outdated. 127.0.0.1;2023-12-27 11:31:52;login: authcode wrong or outdated. 127.0.0.1;2023-12-27 11:32:10;login: authcode wrong or outdated. +127.0.0.1;2024-01-15 13:11:20;login: invalid data diff --git a/system/typemill/Controllers/ControllerWebAuth.php b/system/typemill/Controllers/ControllerWebAuth.php index 6359fa8..2815b46 100644 --- a/system/typemill/Controllers/ControllerWebAuth.php +++ b/system/typemill/Controllers/ControllerWebAuth.php @@ -36,7 +36,10 @@ class ControllerWebAuth extends Controller \Typemill\Static\Helpers::addLogEntry('login: invalid data'); } - $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } @@ -50,7 +53,10 @@ class ControllerWebAuth extends Controller \Typemill\Static\Helpers::addLogEntry('login: user not found'); } - $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } @@ -79,7 +85,10 @@ class ControllerWebAuth extends Controller ]); } - $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } @@ -103,8 +112,11 @@ class ControllerWebAuth extends Controller $mail = new SimpleMail($this->settings); - $subject = Translations::translate('Your verification code for Typemill'); - $message = Translations::translate('Use the following verification code to login into Typemill') . ': ' . $authcodevalue; + $subject = Translations::translate('Your Typemill verification code'); + $message = Translations::translate('Dear user') . ',

'; + $message .= Translations::translate('Someone tried to log in to your Typemill website and we want to make sure it is you. Enter the following verification code to finish your login. The code will be valid for 5 minutes.'); + $message .= '

' . $authcodevalue . '

'; + $message .= Translations::translate('If you did not make this login attempt, please reset your password immediately.'); $send = $mail->send($userdata['email'], $subject, $message); @@ -136,7 +148,10 @@ class ControllerWebAuth extends Controller \Typemill\Static\Helpers::addLogEntry('login: user not confirmed yet.'); } - $this->c->get('flash')->addMessage('error', Translations::translate('Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.')); + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.')); + } return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } @@ -185,7 +200,10 @@ class ControllerWebAuth extends Controller \Typemill\Static\Helpers::addLogEntry('login: invalid verification code format'); } - $this->c->get('flash')->addMessage('error', Translations::translate('Invalid verification code format, please try again.')); + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Invalid verification code format, please try again.')); + } return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } @@ -199,7 +217,10 @@ class ControllerWebAuth extends Controller \Typemill\Static\Helpers::addLogEntry('login: user not found'); } - $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } @@ -215,7 +236,10 @@ class ControllerWebAuth extends Controller \Typemill\Static\Helpers::addLogEntry('login: verification code wrong or outdated.'); } - $this->c->get('flash')->addMessage('error', Translations::translate('The verification was wrong or outdated, please start again.')); + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('The verification was wrong or outdated, please start again.')); + } return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); } diff --git a/system/typemill/Middleware/FlashMessages.php b/system/typemill/Middleware/FlashMessages.php index 9bcb7bc..65eb88e 100644 --- a/system/typemill/Middleware/FlashMessages.php +++ b/system/typemill/Middleware/FlashMessages.php @@ -8,7 +8,7 @@ use Slim\Flash\Messages; class FlashMessages { - private $container; + public $container; public function __construct($container) { diff --git a/system/typemill/Models/SimpleMail.php b/system/typemill/Models/SimpleMail.php index ed2b092..ae96a58 100644 --- a/system/typemill/Models/SimpleMail.php +++ b/system/typemill/Models/SimpleMail.php @@ -44,7 +44,7 @@ class SimpleMail $headers = 'Content-Type: text/html; charset=utf-8' . "\r\n"; $headers .= 'Content-Transfer-Encoding: base64' . "\r\n"; $headers .= 'From: ' . $this->from . "\r\n"; - if($this->$reply) + if($this->reply) { $headers .= 'Reply-To: base64' . $this->reply . "\r\n"; } diff --git a/system/typemill/Static/License.php b/system/typemill/Static/License.php index 7676ce6..0a9f050 100644 --- a/system/typemill/Static/License.php +++ b/system/typemill/Static/License.php @@ -43,9 +43,7 @@ class License } elseif($licenseStatus === true) { - echo '
';
-			print_r($licensedata);
-			die();
+			die('Static License licenceStatus is true');
 		}
 		else
 		{
diff --git a/system/typemill/system.php b/system/typemill/system.php
index 8b07114..aa06a0c 100644
--- a/system/typemill/system.php
+++ b/system/typemill/system.php
@@ -1,4 +1,3 @@
-
 add(new SecurityMiddleware($routeParser, $container->get('settings')));
 
 $app->add(new OldInputMiddleware($container->get('view')));
 
-$app->add(new FlashMessages($container));
-
 # Add Twig-View Middleware
 $app->add(TwigMiddleware::createFromContainer($app));
 
+$app->add(new FlashMessages($container));
+
 # add JsonBodyParser Middleware
 $app->add(new JsonBodyParser());