From 0290a42d07f3dcd01975ff73cc05e5bd3dd62226 Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Wed, 22 Nov 2023 17:28:46 +0100 Subject: [PATCH] Add new debug option for http --- go.mod | 4 +- go.sum | 29 ++------ internal/config/config.yml | 5 ++ internal/config/http.go | 4 ++ internal/dis/component/auth/next/next.go | 4 +- internal/dis/component/auth/panel/panel.go | 26 ++++--- internal/dis/component/auth/panel/password.go | 16 ++--- internal/dis/component/auth/panel/ssh.go | 17 ++--- internal/dis/component/auth/panel/tokens.go | 33 +++++---- internal/dis/component/auth/panel/totp.go | 48 +++++++------ internal/dis/component/auth/panel/user.go | 2 +- internal/dis/component/auth/session.go | 18 ++--- internal/dis/component/resolver/resolver.go | 5 +- internal/dis/component/server.go | 21 +++++- internal/dis/component/server/admin/admin.go | 4 +- internal/dis/component/server/admin/index.go | 4 +- .../dis/component/server/admin/instance.go | 2 +- .../component/server/admin/instance_data.go | 2 +- .../component/server/admin/instance_drupal.go | 2 +- .../server/admin/instance_provision.go | 2 +- .../component/server/admin/instance_purge.go | 2 +- .../server/admin/instance_rebuild.go | 2 +- .../server/admin/instance_snapshots.go | 2 +- .../component/server/admin/instance_ssh.go | 2 +- .../component/server/admin/instance_stats.go | 2 +- .../component/server/admin/instance_users.go | 4 +- .../server/admin/socket/proto/proto.go | 7 +- .../component/server/admin/socket/socket.go | 6 +- internal/dis/component/server/admin/users.go | 17 ++--- .../dis/component/server/assets/assets.go | 10 +++ .../dis/component/server/handling/handling.go | 60 ++++++++++++++++ internal/dis/component/server/home/home.go | 2 + internal/dis/component/server/home/public.go | 2 +- internal/dis/component/server/legal/legal.go | 4 +- internal/dis/component/server/news/news.go | 4 +- internal/dis/component/server/server.go | 70 +++++++++---------- .../dis/component/server/templating/base.go | 34 +++++---- .../dis/component/server/templating/menu.go | 2 +- internal/dis/distillery.go | 2 + 39 files changed, 293 insertions(+), 189 deletions(-) create mode 100644 internal/dis/component/server/handling/handling.go diff --git a/go.mod b/go.mod index d0d8938..74f3e43 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,12 @@ require ( github.com/gliderlabs/ssh v0.3.5 github.com/gorilla/csrf v1.7.2 github.com/gorilla/sessions v1.2.2 - github.com/gorilla/websocket v1.5.1 github.com/julienschmidt/httprouter v1.3.0 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 github.com/rs/zerolog v1.31.0 github.com/tkw1536/goprogram v0.5.0 - github.com/tkw1536/pkglib v0.0.0-20231114141909-8837d3186025 + github.com/tkw1536/pkglib v0.0.0-20231122155813-969c635025aa github.com/yuin/goldmark v1.6.0 github.com/yuin/goldmark-meta v1.1.0 golang.org/x/crypto v0.15.0 @@ -42,6 +41,7 @@ require ( github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/websocket v1.5.1 // indirect github.com/gosuri/uilive v0.0.4 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/jessevdk/go-flags v1.5.0 // indirect diff --git a/go.sum b/go.sum index a5d4588..ad9fbfa 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuW github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/compose-spec/compose-go v1.20.0 h1:h4ZKOst1EF/DwZp7dWkb+wbTVE4nEyT9Lc89to84Ol4= -github.com/compose-spec/compose-go v1.20.0/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM= github.com/compose-spec/compose-go v1.20.1 h1:I6gCMGLl96kEf8XZwaozeTwnNfxA2eVsO46W+5ciTEg= github.com/compose-spec/compose-go v1.20.1/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -48,11 +46,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= @@ -124,10 +119,10 @@ github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM= github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tkw1536/goprogram v0.5.0 h1:7vcIjmMdcZPJyRhgdlCaGfHAoOG3oYlFrno1pWXy1Bs= github.com/tkw1536/goprogram v0.5.0/go.mod h1:MDCwqLmvcc2QryMm6oSC9h/QAdE9PewZ2Mp2Lm7MmAg= -github.com/tkw1536/pkglib v0.0.0-20231110192201-b920fd9f7764 h1:rJStxc6PoFUQcsRRqoUNtaM80GZWT69WfWeA+EDbvXM= -github.com/tkw1536/pkglib v0.0.0-20231110192201-b920fd9f7764/go.mod h1:Qi/vpuxuxo5D40O9jLUSmcUF01B5LmJqDxs8o8Lc6bg= -github.com/tkw1536/pkglib v0.0.0-20231114141909-8837d3186025 h1:t3ewoi0rdqQo0a8zFFpmtsUi+O4C+kCYoOM/QkXV7b0= -github.com/tkw1536/pkglib v0.0.0-20231114141909-8837d3186025/go.mod h1:Qi/vpuxuxo5D40O9jLUSmcUF01B5LmJqDxs8o8Lc6bg= +github.com/tkw1536/pkglib v0.0.0-20231121123232-879eff5cea2d h1:J8EvqdNY/qJpn8qoYkxtHx+Uag6sbHxgXNClt6S3Zqc= +github.com/tkw1536/pkglib v0.0.0-20231121123232-879eff5cea2d/go.mod h1:Qi/vpuxuxo5D40O9jLUSmcUF01B5LmJqDxs8o8Lc6bg= +github.com/tkw1536/pkglib v0.0.0-20231122155813-969c635025aa h1:HQxorKzWcH3D0In/G6Y24IT9KVVcGjeJGcZzN0T3b30= +github.com/tkw1536/pkglib v0.0.0-20231122155813-969c635025aa/go.mod h1:Qi/vpuxuxo5D40O9jLUSmcUF01B5LmJqDxs8o8Lc6bg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -145,18 +140,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= -golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -165,15 +154,11 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -194,16 +179,12 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -212,8 +193,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/config/config.yml b/internal/config/config.yml index 8cfdb6a..8cac841 100644 --- a/internal/config/config.yml +++ b/internal/config/config.yml @@ -39,6 +39,11 @@ http: # This email address can be configured here. certbot_email: null + # Debug determines if error messages should be written as html pages with stack traces to http clients. + # This potentially exposes sensitive information and may cause certain API responses to be of content type 'text/html' unexpectedly. + # It is not recommended to use this on production systems. + debug: null + # Serve the panel also on the toplevel domain, and not only on the "panel" domain. # Enabled by default. panel: null diff --git a/internal/config/http.go b/internal/config/http.go index a9896ef..a9e7ca1 100644 --- a/internal/config/http.go +++ b/internal/config/http.go @@ -25,6 +25,10 @@ type HTTPConfig struct { // This email address can be configured here. CertbotEmail string `yaml:"certbot_email" validate:"email"` + // Debug determines if error messages should be written as html pages with stack traces to http clients. + // This potentially exposes sensitive information and may cause certain API responses to be of content type 'text/html' unexpectedly. + Debug validators.NullableBool `yaml:"debug" validate:"bool" default:"false"` + // Also serve the panel on the toplevel domain. // Note that the panel is *always* servered under the "panel" domain. // Disabling this is not recommended. diff --git a/internal/dis/component/auth/next/next.go b/internal/dis/component/auth/next/next.go index 4be8367..b00628e 100644 --- a/internal/dis/component/auth/next/next.go +++ b/internal/dis/component/auth/next/next.go @@ -10,6 +10,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling" "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php/users" "github.com/tkw1536/pkglib/httpx" @@ -21,6 +22,7 @@ type Next struct { Auth *auth.Auth Policy *policy.Policy Instances *instances.Instances + Handleing *handling.Handling } } @@ -72,7 +74,7 @@ func (next *Next) getInstance(r *http.Request) (wisski *wisski.WissKI, path stri } func (next *Next) HandleRoute(ctx context.Context, path string) (http.Handler, error) { - return httpx.RedirectHandler(func(r *http.Request) (string, int, error) { + return next.dependencies.Handleing.Redirect(func(r *http.Request) (string, int, error) { // get the instance and the path instance, path, err := next.getInstance(r) if err != nil { diff --git a/internal/dis/component/auth/panel/panel.go b/internal/dis/component/auth/panel/panel.go index 3d61186..1f7cfd7 100644 --- a/internal/dis/component/auth/panel/panel.go +++ b/internal/dis/component/auth/panel/panel.go @@ -11,25 +11,29 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/tokens" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh2" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh2/sshkeys" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/julienschmidt/httprouter" - "github.com/tkw1536/pkglib/httpx" + "github.com/tkw1536/pkglib/httpx/form" ) type UserPanel struct { component.Base dependencies struct { - Auth *auth.Auth + Auth *auth.Auth + + Handling *handling.Handling Templating *templating.Templating - Policy *policy.Policy - Tokens *tokens.Tokens - Instances *instances.Instances - Next *next.Next - Keys *sshkeys.SSHKeys - SSH2 *ssh2.SSH2 + + Policy *policy.Policy + Tokens *tokens.Tokens + Instances *instances.Instances + Next *next.Next + Keys *sshkeys.SSHKeys + SSH2 *ssh2.SSH2 } } @@ -142,12 +146,12 @@ func (panel *UserPanel) HandleRoute(ctx context.Context, route string) (http.Han type userFormContext struct { templating.RuntimeFlags - httpx.FormContext + form.FormContext User *models.User } -func (panel *UserPanel) UserFormContext(tpl *templating.Template[userFormContext], last component.MenuItem, funcs ...templating.FlagFunc) func(ctx httpx.FormContext, r *http.Request) any { +func (panel *UserPanel) UserFormContext(tpl *templating.Template[userFormContext], last component.MenuItem, funcs ...templating.FlagFunc) func(ctx form.FormContext, r *http.Request) any { funcs = append(funcs, func(flags templating.Flags, r *http.Request) templating.Flags { // append the last menu item, and prepend the menuUser one! flags.Crumbs = append(flags.Crumbs, last, last) @@ -156,7 +160,7 @@ func (panel *UserPanel) UserFormContext(tpl *templating.Template[userFormContext return flags }) - return func(ctx httpx.FormContext, r *http.Request) any { + return func(ctx form.FormContext, r *http.Request) any { uctx := userFormContext{FormContext: ctx} if user, err := panel.dependencies.Auth.UserOfSession(r); err == nil { uctx.User = &user.User diff --git a/internal/dis/component/auth/panel/password.go b/internal/dis/component/auth/panel/password.go index e78b5ea..1863877 100644 --- a/internal/dis/component/auth/panel/password.go +++ b/internal/dis/component/auth/panel/password.go @@ -9,14 +9,14 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" - "github.com/tkw1536/pkglib/httpx" - "github.com/tkw1536/pkglib/httpx/field" + "github.com/tkw1536/pkglib/httpx/form" + "github.com/tkw1536/pkglib/httpx/form/field" ) //go:embed "templates/password.html" var passwordHTML []byte var passwordTemplate = templating.Parse[userFormContext]( - "password.html", passwordHTML, httpx.FormTemplate, + "password.html", passwordHTML, form.FormTemplate, templating.Title("Change Password"), templating.Assets(assets.AssetsUser), @@ -34,17 +34,17 @@ var ( func (panel *UserPanel) routePassword(ctx context.Context) http.Handler { tpl := passwordTemplate.Prepare(panel.dependencies.Templating) - return &httpx.Form[struct{}]{ + return &form.Form[struct{}]{ Fields: []field.Field{ {Name: "old", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Current Password"}, {Name: "otp", Type: field.Text, Autocomplete: field.OneTimeCode, EmptyOnError: true, Label: "Current Passcode (optional)"}, {Name: "new", Type: field.Password, Autocomplete: field.NewPassword, EmptyOnError: true, Label: "New Password"}, {Name: "new2", Type: field.Password, Autocomplete: field.NewPassword, EmptyOnError: true, Label: "New Password (again)"}, }, - FieldTemplate: field.PureCSSFieldTemplate, + FieldTemplate: assets.PureCSSFieldTemplate, - RenderTemplate: tpl.Template(), - RenderTemplateContext: panel.UserFormContext(tpl, menuChangePassword), + Template: tpl.Template(), + TemplateContext: panel.UserFormContext(tpl, menuChangePassword), Validate: func(r *http.Request, values map[string]string) (struct{}, error) { old, passcode, new, new2 := values["old"], values["otp"], values["new"], values["new2"] @@ -82,7 +82,7 @@ func (panel *UserPanel) routePassword(ctx context.Context) http.Handler { return struct{}{}, nil }, - RenderSuccess: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error { + Success: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error { return errPasswordSet }, } diff --git a/internal/dis/component/auth/panel/ssh.go b/internal/dis/component/auth/panel/ssh.go index 25bb9d9..1d867e0 100644 --- a/internal/dis/component/auth/panel/ssh.go +++ b/internal/dis/component/auth/panel/ssh.go @@ -13,7 +13,8 @@ import ( "github.com/gliderlabs/ssh" "github.com/rs/zerolog" "github.com/tkw1536/pkglib/httpx" - "github.com/tkw1536/pkglib/httpx/field" + "github.com/tkw1536/pkglib/httpx/form" + "github.com/tkw1536/pkglib/httpx/form/field" gossh "golang.org/x/crypto/ssh" @@ -57,7 +58,7 @@ func (panel *UserPanel) sshRoute(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandler(func(r *http.Request) (sc SSHTemplateContext, err error) { + return tpl.HTMLHandler(panel.dependencies.Handling, func(r *http.Request) (sc SSHTemplateContext, err error) { user, err := panel.dependencies.Auth.UserOfSession(r) if err != nil { return sc, err @@ -129,7 +130,7 @@ func (panel *UserPanel) sshDeleteRoute(ctx context.Context) http.Handler { //go:embed "templates/ssh_add.html" var sshAddHTML []byte var sshAddTemplate = templating.ParseForm( - "ssh_add.html", sshAddHTML, httpx.FormTemplate, + "ssh_add.html", sshAddHTML, form.FormTemplate, templating.Title("Add SSH Key"), templating.Assets(assets.AssetsUser), ) @@ -150,15 +151,15 @@ func (panel *UserPanel) sshAddRoute(ctx context.Context) http.Handler { ), ) - return &httpx.Form[addKeyResult]{ + return &form.Form[addKeyResult]{ Fields: []field.Field{ {Name: "comment", Type: field.Text, Label: "Comment"}, {Name: "key", Type: field.Textarea, Label: "Key in authorized_keys format"}, // has hacked css! }, - FieldTemplate: field.PureCSSFieldTemplate, + FieldTemplate: assets.PureCSSFieldTemplate, - RenderTemplate: tpl.Template(), - RenderTemplateContext: templating.FormTemplateContext(tpl), + Template: tpl.Template(), + TemplateContext: templating.FormTemplateContext(tpl), Validate: func(r *http.Request, values map[string]string) (ak addKeyResult, err error) { ak.User, err = panel.dependencies.Auth.UserOfSession(r) @@ -181,7 +182,7 @@ func (panel *UserPanel) sshAddRoute(ctx context.Context) http.Handler { return ak, nil }, - RenderSuccess: func(ak addKeyResult, values map[string]string, w http.ResponseWriter, r *http.Request) error { + Success: func(ak addKeyResult, values map[string]string, w http.ResponseWriter, r *http.Request) error { // add the key to the user if err := panel.dependencies.Keys.Add(r.Context(), ak.User.User.User, ak.Comment, ak.Key); err != nil { return errAddKey diff --git a/internal/dis/component/auth/panel/tokens.go b/internal/dis/component/auth/panel/tokens.go index 728f8b9..e9fc2d3 100644 --- a/internal/dis/component/auth/panel/tokens.go +++ b/internal/dis/component/auth/panel/tokens.go @@ -11,7 +11,8 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/rs/zerolog" "github.com/tkw1536/pkglib/httpx" - "github.com/tkw1536/pkglib/httpx/field" + "github.com/tkw1536/pkglib/httpx/form" + "github.com/tkw1536/pkglib/httpx/form/field" _ "embed" ) @@ -44,7 +45,7 @@ func (panel *UserPanel) tokensRoute(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandler(func(r *http.Request) (tc TokenTemplateContext, err error) { + return tpl.HTMLHandler(panel.dependencies.Handling, func(r *http.Request) (tc TokenTemplateContext, err error) { // list the user user, err := panel.dependencies.Auth.UserOfSession(r) if err != nil || user == nil { @@ -94,7 +95,7 @@ func (panel *UserPanel) tokensDeleteRoute(ctx context.Context) http.Handler { //go:embed "templates/tokens_add.html" var tokensAddHTML []byte var tokensAddTemplate = templating.ParseForm( - "tokens_add.html", tokensAddHTML, httpx.FormTemplate, + "tokens_add.html", tokensAddHTML, form.FormTemplate, templating.Title("Add Token"), templating.Assets(assets.AssetsUser), ) @@ -108,7 +109,7 @@ type addTokenResult struct { //go:embed "templates/token_created.html" var tokenCreatedHTML []byte var tokenCreateTemplate = templating.Parse[TokenCreateContext]( - "token_created.html", tokenCreatedHTML, httpx.FormTemplate, + "token_created.html", tokenCreatedHTML, form.FormTemplate, templating.Title("Add Token"), templating.Assets(assets.AssetsUser), ) @@ -139,14 +140,14 @@ func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler { ), ) - return &httpx.Form[addTokenResult]{ + return &form.Form[addTokenResult]{ Fields: []field.Field{ {Name: "description", Type: field.Text, Label: "Description"}, }, - FieldTemplate: field.PureCSSFieldTemplate, + FieldTemplate: assets.PureCSSFieldTemplate, - RenderTemplate: tplForm.Template(), - RenderTemplateContext: templating.FormTemplateContext(tplForm), + Template: tplForm.Template(), + TemplateContext: templating.FormTemplateContext(tplForm), Validate: func(r *http.Request, values map[string]string) (at addTokenResult, err error) { at.User, err = panel.dependencies.Auth.UserOfSession(r) @@ -164,7 +165,7 @@ func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler { return at, nil }, - RenderSuccess: func(at addTokenResult, values map[string]string, w http.ResponseWriter, r *http.Request) error { + Success: func(at addTokenResult, values map[string]string, w http.ResponseWriter, r *http.Request) error { // add the key to the user tok, err := panel.dependencies.Tokens.Add(r.Context(), at.User.User.User, at.Description, at.Scopes) if err != nil { @@ -175,10 +176,16 @@ func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler { } // render the created context - return httpx.WriteHTML(tplDone.Context(r, TokenCreateContext{ - Domain: template.URL(panel.Config.HTTP.JoinPath().String()), - Token: tok, - }), nil, tplDone.Template(), "", w, r) + return panel.dependencies.Handling.WriteHTML( + tplDone.Context(r, TokenCreateContext{ + Domain: template.URL(panel.Config.HTTP.JoinPath().String()), + Token: tok, + }), + nil, + tplDone.Template(), + w, + r, + ) }, } } diff --git a/internal/dis/component/auth/panel/totp.go b/internal/dis/component/auth/panel/totp.go index 8f0dfa5..aac14ed 100644 --- a/internal/dis/component/auth/panel/totp.go +++ b/internal/dis/component/auth/panel/totp.go @@ -8,8 +8,8 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" - "github.com/tkw1536/pkglib/httpx" - "github.com/tkw1536/pkglib/httpx/field" + "github.com/tkw1536/pkglib/httpx/form" + "github.com/tkw1536/pkglib/httpx/form/field" _ "embed" ) @@ -17,7 +17,7 @@ import ( //go:embed "templates/totp_enable.html" var totpEnableHTML []byte var totpEnable = templating.Parse[userFormContext]( - "totp_enable.html", totpEnableHTML, httpx.FormTemplate, + "totp_enable.html", totpEnableHTML, form.FormTemplate, templating.Title("Enable TOTP"), templating.Assets(assets.AssetsUser), @@ -26,19 +26,19 @@ var totpEnable = templating.Parse[userFormContext]( func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler { tpl := totpEnable.Prepare(panel.dependencies.Templating) - return &httpx.Form[struct{}]{ + return &form.Form[struct{}]{ Fields: []field.Field{ {Name: "password", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Current Password"}, }, - FieldTemplate: field.PureCSSFieldTemplate, + FieldTemplate: assets.PureCSSFieldTemplate, - SkipForm: func(r *http.Request) (data struct{}, skip bool) { + Skip: func(r *http.Request) (data struct{}, skip bool) { user, err := panel.dependencies.Auth.UserOfSession(r) return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled() }, - RenderTemplate: tpl.Template(), - RenderTemplateContext: panel.UserFormContext(tpl, menuTOTPEnable), + Template: tpl.Template(), + TemplateContext: panel.UserFormContext(tpl, menuTOTPEnable), Validate: func(r *http.Request, values map[string]string) (struct{}, error) { password := values["password"] @@ -64,7 +64,7 @@ func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler { return struct{}{}, nil }, - RenderSuccess: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error { + Success: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error { http.Redirect(w, r, "/user/totp/enroll", http.StatusSeeOther) return nil }, @@ -74,7 +74,7 @@ func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler { //go:embed "templates/totp_enroll.html" var totpEnrollHTML []byte var totpEnrollTemplate = templating.Parse[totpEnrollContext]( - "totp_enroll.html", totpEnrollHTML, httpx.FormTemplate, + "totp_enroll.html", totpEnrollHTML, form.FormTemplate, templating.Title("Enable TOTP"), templating.Assets(assets.AssetsUser), @@ -97,18 +97,20 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler { ), ) - return &httpx.Form[struct{}]{ + return &form.Form[struct{}]{ Fields: []field.Field{ {Name: "password", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Current Password"}, {Name: "otp", Type: field.Text, Autocomplete: field.OneTimeCode, EmptyOnError: true, Label: "Passcode"}, }, - FieldTemplate: field.PureCSSFieldTemplate, + FieldTemplate: assets.PureCSSFieldTemplate, - SkipForm: func(r *http.Request) (data struct{}, skip bool) { + Skip: func(r *http.Request) (data struct{}, skip bool) { user, err := panel.dependencies.Auth.UserOfSession(r) return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled() }, - RenderTemplateContext: func(context httpx.FormContext, r *http.Request) any { + + Template: tpl.Template(), + TemplateContext: func(context form.FormContext, r *http.Request) any { user, err := panel.dependencies.Auth.UserOfSession(r) ctx := totpEnrollContext{ @@ -131,7 +133,6 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler { return tpl.Context(r, ctx) }, - RenderTemplate: tpl.Template(), Validate: func(r *http.Request, values map[string]string) (struct{}, error) { password, otp := values["password"], values["otp"] @@ -157,7 +158,7 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler { return struct{}{}, nil }, - RenderSuccess: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error { + Success: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error { http.Redirect(w, r, "/user/", http.StatusSeeOther) return nil }, @@ -167,7 +168,7 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler { //go:embed "templates/totp_disable.html" var totpDisableHTML []byte var totpDisableTemplate = templating.Parse[userFormContext]( - "totp_disable.html", totpDisableHTML, httpx.FormTemplate, + "totp_disable.html", totpDisableHTML, form.FormTemplate, templating.Title("Disable TOTP"), templating.Assets(assets.AssetsUser), @@ -176,19 +177,20 @@ var totpDisableTemplate = templating.Parse[userFormContext]( func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler { tpl := totpDisableTemplate.Prepare(panel.dependencies.Templating) - return &httpx.Form[struct{}]{ + return &form.Form[struct{}]{ Fields: []field.Field{ {Name: "password", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Current Password"}, {Name: "otp", Type: field.Text, Autocomplete: field.OneTimeCode, EmptyOnError: true, Label: "Current Passcode"}, }, - FieldTemplate: field.PureCSSFieldTemplate, + FieldTemplate: assets.PureCSSFieldTemplate, - SkipForm: func(r *http.Request) (data struct{}, skip bool) { + Skip: func(r *http.Request) (data struct{}, skip bool) { user, err := panel.dependencies.Auth.UserOfSession(r) return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled() }, - RenderTemplate: tpl.Template(), - RenderTemplateContext: panel.UserFormContext(tpl, menuTOTPDisable), + + Template: tpl.Template(), + TemplateContext: panel.UserFormContext(tpl, menuTOTPDisable), Validate: func(r *http.Request, values map[string]string) (struct{}, error) { password, otp := values["password"], values["otp"] @@ -214,7 +216,7 @@ func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler { return struct{}{}, nil }, - RenderSuccess: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error { + Success: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error { http.Redirect(w, r, "/user/", http.StatusSeeOther) return nil }, diff --git a/internal/dis/component/auth/panel/user.go b/internal/dis/component/auth/panel/user.go index 00d4345..d444dfb 100644 --- a/internal/dis/component/auth/panel/user.go +++ b/internal/dis/component/auth/panel/user.go @@ -59,7 +59,7 @@ func (panel *UserPanel) routeUser(ctx context.Context) http.Handler { templating.Actions(actions...), ) - return tpl.HTMLHandlerWithFlags(func(r *http.Request) (uc userContext, funcs []templating.FlagFunc, err error) { + return tpl.HTMLHandlerWithFlags(panel.dependencies.Handling, func(r *http.Request) (uc userContext, funcs []templating.FlagFunc, err error) { // find the user uc.AuthUser, err = panel.dependencies.Auth.UserOfSession(r) if err != nil || uc.AuthUser == nil { diff --git a/internal/dis/component/auth/session.go b/internal/dis/component/auth/session.go index 7543343..09c6ac2 100644 --- a/internal/dis/component/auth/session.go +++ b/internal/dis/component/auth/session.go @@ -10,8 +10,8 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" - "github.com/tkw1536/pkglib/httpx" - "github.com/tkw1536/pkglib/httpx/field" + "github.com/tkw1536/pkglib/httpx/form" + "github.com/tkw1536/pkglib/httpx/form/field" "github.com/gorilla/sessions" @@ -162,7 +162,7 @@ func (auth *Auth) Logout(w http.ResponseWriter, r *http.Request) error { //go:embed "login.html" var loginHTML []byte var loginTemplate = templating.ParseForm( - "login.html", loginHTML, httpx.FormTemplate, + "login.html", loginHTML, form.FormTemplate, templating.Title("Login Required"), templating.Assets(assets.AssetsUser), @@ -182,21 +182,21 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler { }, ) - return &httpx.Form[*AuthUser]{ + return &form.Form[*AuthUser]{ Fields: []field.Field{ {Name: "username", Type: field.Text, Autocomplete: field.Username, Label: "Username"}, {Name: "password", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Password"}, {Name: "otp", Type: field.Text, Autocomplete: field.OneTimeCode, EmptyOnError: true, Label: "Passcode (optional)"}, }, - FieldTemplate: field.PureCSSFieldTemplate, + FieldTemplate: assets.PureCSSFieldTemplate, - RenderTemplateContext: func(ctx httpx.FormContext, r *http.Request) any { + Template: tpl.Template(), + TemplateContext: func(ctx form.FormContext, r *http.Request) any { if ctx.Err != nil { ctx.Err = errLoginFailed } return tpl.Context(r, templating.NewFormContext(ctx)) }, - RenderTemplate: tpl.Template(), Validate: func(r *http.Request, values map[string]string) (*AuthUser, error) { username, password, passcode := values["username"], values["password"], values["otp"] @@ -215,12 +215,12 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler { return user, nil }, - SkipForm: func(r *http.Request) (user *AuthUser, skip bool) { + Skip: func(r *http.Request) (user *AuthUser, skip bool) { user, err := auth.UserOfSession(r) return user, err == nil && user != nil }, - RenderSuccess: func(user *AuthUser, _ map[string]string, w http.ResponseWriter, r *http.Request) error { + Success: func(user *AuthUser, _ map[string]string, w http.ResponseWriter, r *http.Request) error { if err := auth.Login(w, r, user); err != nil { return err } diff --git a/internal/dis/component/resolver/resolver.go b/internal/dis/component/resolver/resolver.go index 6b07e69..70f319f 100644 --- a/internal/dis/component/resolver/resolver.go +++ b/internal/dis/component/resolver/resolver.go @@ -14,9 +14,9 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" "github.com/rs/zerolog" - "github.com/tkw1536/pkglib/httpx" "github.com/tkw1536/pkglib/lazy" _ "embed" @@ -27,6 +27,7 @@ type Resolver struct { dependencies struct { Instances *instances.Instances Templating *templating.Templating + Handling *handling.Handling Auth *auth.Auth } @@ -106,7 +107,7 @@ func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.H if resolver.dependencies.Auth.CheckScope("", scopes.ScopeUserValid, r) != nil { ctx.IndexContext.Prefixes = nil } - httpx.WriteHTML(tpl.Context(r, ctx), nil, t, "", w, r) + resolver.dependencies.Handling.WriteHTML(tpl.Context(r, ctx), nil, t, w, r) }, Resolver: resolvers.InOrder{ diff --git a/internal/dis/component/server.go b/internal/dis/component/server.go index 9a7a79c..e1aa3c1 100644 --- a/internal/dis/component/server.go +++ b/internal/dis/component/server.go @@ -4,7 +4,7 @@ import ( "context" "net/http" - "github.com/tkw1536/pkglib/mux" + "github.com/tkw1536/pkglib/httpx/mux" ) // Routeable is a component that is servable @@ -51,10 +51,29 @@ type Routes struct { Decorator func(http.Handler) http.Handler } +type routeContextTyp int + +const routeContextKey routeContextTyp = 0 + +// RouteContext represents the context passed to a given route type RouteContext struct { DefaultDomain bool } +// WithRouteContext adds the given RouteContext to the context +func WithRouteContext(parent context.Context, value RouteContext) context.Context { + return context.WithValue(parent, routeContextKey, value) +} + +// RouteContextOf returns the route context of the given context +func RouteContextOf(context context.Context) RouteContext { + ctx, ok := context.Value(routeContextKey).(RouteContext) + if !ok { + return RouteContext{} + } + return ctx +} + // Predicate returns the predicate corresponding to the given route func (routes Routes) Predicate(context func(*http.Request) RouteContext) mux.Predicate { if routes.MatchAllDomains || routes.Internal { diff --git a/internal/dis/component/server/admin/admin.go b/internal/dis/component/server/admin/admin.go index 4f18cf7..7d4e2f0 100644 --- a/internal/dis/component/server/admin/admin.go +++ b/internal/dis/component/server/admin/admin.go @@ -9,6 +9,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" "github.com/julienschmidt/httprouter" "github.com/rs/zerolog" @@ -20,6 +21,7 @@ import ( type Admin struct { component.Base dependencies struct { + Handling *handling.Handling Fetchers []component.DistilleryFetcher Instances *instances.Instances @@ -178,7 +180,7 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http func (admin *Admin) loginHandler(ctx context.Context) http.Handler { logger := zerolog.Ctx(ctx) - return httpx.RedirectHandler(func(r *http.Request) (string, int, error) { + return admin.dependencies.Handling.Redirect(func(r *http.Request) (string, int, error) { // parse the form if err := r.ParseForm(); err != nil { logger.Err(err).Msg("failed to parse admin login") diff --git a/internal/dis/component/server/admin/index.go b/internal/dis/component/server/admin/index.go index 34e84c2..7c85be4 100644 --- a/internal/dis/component/server/admin/index.go +++ b/internal/dis/component/server/admin/index.go @@ -120,7 +120,7 @@ func (admin *Admin) index(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandler(func(r *http.Request) (idx indexContext, err error) { + return tpl.HTMLHandler(admin.dependencies.Handling, func(r *http.Request) (idx indexContext, err error) { idx.Distillery, idx.Instances, err = admin.Status(r.Context(), false) return }) @@ -138,7 +138,7 @@ func (admin *Admin) instances(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandler(func(r *http.Request) (idx indexContext, err error) { + return tpl.HTMLHandler(admin.dependencies.Handling, func(r *http.Request) (idx indexContext, err error) { idx.Distillery, idx.Instances, err = admin.Status(r.Context(), true) return }) diff --git a/internal/dis/component/server/admin/instance.go b/internal/dis/component/server/admin/instance.go index 9940c14..fa6afcf 100644 --- a/internal/dis/component/server/admin/instance.go +++ b/internal/dis/component/server/admin/instance.go @@ -42,7 +42,7 @@ func (admin *Admin) instance(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ic instanceContext, funcs []templating.FlagFunc, err error) { + return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ic instanceContext, funcs []templating.FlagFunc, err error) { slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") // find the instance itself! diff --git a/internal/dis/component/server/admin/instance_data.go b/internal/dis/component/server/admin/instance_data.go index 35da680..5008e6b 100644 --- a/internal/dis/component/server/admin/instance_data.go +++ b/internal/dis/component/server/admin/instance_data.go @@ -43,7 +43,7 @@ func (admin *Admin) instanceData(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instanceDataContext, funcs []templating.FlagFunc, err error) { + return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instanceDataContext, funcs []templating.FlagFunc, err error) { slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") // setup the context with just the instance diff --git a/internal/dis/component/server/admin/instance_drupal.go b/internal/dis/component/server/admin/instance_drupal.go index c5768e2..f3b8d92 100644 --- a/internal/dis/component/server/admin/instance_drupal.go +++ b/internal/dis/component/server/admin/instance_drupal.go @@ -50,7 +50,7 @@ func (admin *Admin) instanceDrupal(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instanceDrupalContext, funcs []templating.FlagFunc, err error) { + return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instanceDrupalContext, funcs []templating.FlagFunc, err error) { slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") // setup the context with just the instance diff --git a/internal/dis/component/server/admin/instance_provision.go b/internal/dis/component/server/admin/instance_provision.go index 2f33d05..57a910e 100644 --- a/internal/dis/component/server/admin/instance_provision.go +++ b/internal/dis/component/server/admin/instance_provision.go @@ -26,7 +26,7 @@ func (admin *Admin) instanceProvision(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandler(func(r *http.Request) (ipc instanceSystemContext, err error) { + return tpl.HTMLHandler(admin.dependencies.Handling, func(r *http.Request) (ipc instanceSystemContext, err error) { ipc.prepare(false) ipc.DefaultProfile = manager.DefaultProfile() ipc.Profiles = collection.MapValues(manager.Profiles(), func(_ string, profile manager.Profile) string { return profile.Description }) diff --git a/internal/dis/component/server/admin/instance_purge.go b/internal/dis/component/server/admin/instance_purge.go index 93da21b..083c5b3 100644 --- a/internal/dis/component/server/admin/instance_purge.go +++ b/internal/dis/component/server/admin/instance_purge.go @@ -40,7 +40,7 @@ func (admin *Admin) instancePurge(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instancePurgeContext, funcs []templating.FlagFunc, err error) { + return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instancePurgeContext, funcs []templating.FlagFunc, err error) { slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") // setup the context with just the instance diff --git a/internal/dis/component/server/admin/instance_rebuild.go b/internal/dis/component/server/admin/instance_rebuild.go index ad9b226..fd78f1d 100644 --- a/internal/dis/component/server/admin/instance_rebuild.go +++ b/internal/dis/component/server/admin/instance_rebuild.go @@ -65,7 +65,7 @@ func (admin *Admin) instanceRebuild(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandlerWithFlags(func(r *http.Request) (isc instanceSystemContext, funcs []templating.FlagFunc, err error) { + return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (isc instanceSystemContext, funcs []templating.FlagFunc, err error) { slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") var instance *wisski.WissKI diff --git a/internal/dis/component/server/admin/instance_snapshots.go b/internal/dis/component/server/admin/instance_snapshots.go index a655fc0..2d14623 100644 --- a/internal/dis/component/server/admin/instance_snapshots.go +++ b/internal/dis/component/server/admin/instance_snapshots.go @@ -42,7 +42,7 @@ func (admin *Admin) instanceSnapshots(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instanceSnapshotsContext, funcs []templating.FlagFunc, err error) { + return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instanceSnapshotsContext, funcs []templating.FlagFunc, err error) { slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") // setup the context with just the instance diff --git a/internal/dis/component/server/admin/instance_ssh.go b/internal/dis/component/server/admin/instance_ssh.go index 5e1be64..e727761 100644 --- a/internal/dis/component/server/admin/instance_ssh.go +++ b/internal/dis/component/server/admin/instance_ssh.go @@ -47,7 +47,7 @@ func (admin *Admin) instanceSSH(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instanceSSHContext, funcs []templating.FlagFunc, err error) { + return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instanceSSHContext, funcs []templating.FlagFunc, err error) { slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") // setup the context with just the instance diff --git a/internal/dis/component/server/admin/instance_stats.go b/internal/dis/component/server/admin/instance_stats.go index 447672c..1ccaeb1 100644 --- a/internal/dis/component/server/admin/instance_stats.go +++ b/internal/dis/component/server/admin/instance_stats.go @@ -42,7 +42,7 @@ func (admin *Admin) instanceStats(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instanceStatsContext, funcs []templating.FlagFunc, err error) { + return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instanceStatsContext, funcs []templating.FlagFunc, err error) { slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") // setup the context with just the instance diff --git a/internal/dis/component/server/admin/instance_users.go b/internal/dis/component/server/admin/instance_users.go index e07c440..163e70c 100644 --- a/internal/dis/component/server/admin/instance_users.go +++ b/internal/dis/component/server/admin/instance_users.go @@ -15,7 +15,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/status" "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/tkw1536/pkglib/httpx" - "github.com/tkw1536/pkglib/httpx/field" + "github.com/tkw1536/pkglib/httpx/form/field" "github.com/julienschmidt/httprouter" "golang.org/x/exp/maps" @@ -55,7 +55,7 @@ func (admin *Admin) instanceUsers(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandlerWithFlags(func(r *http.Request) (instanceUsersContext, []templating.FlagFunc, error) { + return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (instanceUsersContext, []templating.FlagFunc, error) { if r.Method == http.MethodGet { return admin.getGrantsUsers(r) } else { diff --git a/internal/dis/component/server/admin/socket/proto/proto.go b/internal/dis/component/server/admin/socket/proto/proto.go index 852fb64..b6cda86 100644 --- a/internal/dis/component/server/admin/socket/proto/proto.go +++ b/internal/dis/component/server/admin/socket/proto/proto.go @@ -10,8 +10,7 @@ import ( "time" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth" - "github.com/gorilla/websocket" - "github.com/tkw1536/pkglib/httpx" + "github.com/tkw1536/pkglib/httpx/websocket" ) // ActionMap handles a set of WebSocket actions @@ -40,7 +39,7 @@ func (err errPanic) Error() string { // Finally it will send a ResultMessage once handling is complete. // // A corresponding client implementation of this can be found in ..../remote/proto.ts -func (am ActionMap) Handle(auth *auth.Auth, conn httpx.WebSocketConnection) (name string, err error) { +func (am ActionMap) Handle(auth *auth.Auth, conn *websocket.Connection) (name string, err error) { var wg sync.WaitGroup // once we have finished executing send a binary message (indicating success) to the client. @@ -67,7 +66,7 @@ func (am ActionMap) Handle(auth *auth.Auth, conn httpx.WebSocketConnection) (nam } // encode the result message to json! - var message httpx.WebSocketMessage + var message websocket.Message message.Type = websocket.BinaryMessage message.Bytes, err = json.Marshal(result) diff --git a/internal/dis/component/server/admin/socket/socket.go b/internal/dis/component/server/admin/socket/socket.go index c620e49..c8a5a78 100644 --- a/internal/dis/component/server/admin/socket/socket.go +++ b/internal/dis/component/server/admin/socket/socket.go @@ -14,7 +14,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket/actions" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket/proto" "github.com/rs/zerolog" - "github.com/tkw1536/pkglib/httpx" + "github.com/tkw1536/pkglib/httpx/websocket" "github.com/tkw1536/pkglib/lazy" ) @@ -48,14 +48,14 @@ func (socket *Sockets) Routes() component.Routes { } func (sockets *Sockets) HandleRoute(ctx context.Context, path string) (http.Handler, error) { - return &httpx.WebSocket{ + return &websocket.Server{ Context: ctx, Handler: sockets.Serve, }, nil } // Serve handles a connection to the websocket api -func (socket *Sockets) Serve(conn httpx.WebSocketConnection) { +func (socket *Sockets) Serve(conn *websocket.Connection) { // handle the websocket connection! name, err := socket.actions.Get(func() proto.ActionMap { return socket.Actions(conn.Context()) }).Handle(socket.dependencies.Auth, conn) if err != nil { diff --git a/internal/dis/component/server/admin/users.go b/internal/dis/component/server/admin/users.go index bcf21bb..d4e1e0b 100644 --- a/internal/dis/component/server/admin/users.go +++ b/internal/dis/component/server/admin/users.go @@ -11,7 +11,8 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" "github.com/rs/zerolog" "github.com/tkw1536/pkglib/httpx" - "github.com/tkw1536/pkglib/httpx/field" + "github.com/tkw1536/pkglib/httpx/form" + "github.com/tkw1536/pkglib/httpx/form/field" _ "embed" ) @@ -43,7 +44,7 @@ func (admin *Admin) users(ctx context.Context) http.Handler { ), ) - return tpl.HTMLHandler(func(r *http.Request) (uc usersContext, err error) { + return tpl.HTMLHandler(admin.dependencies.Handling, func(r *http.Request) (uc usersContext, err error) { uc.Error = r.URL.Query().Get("error") uc.Users, err = admin.dependencies.Auth.Users(r.Context()) return @@ -53,7 +54,7 @@ func (admin *Admin) users(ctx context.Context) http.Handler { //go:embed "html/user_create.html" var userCreateHTML []byte var userCreateTemplate = templating.ParseForm( - "user_create.html", userCreateHTML, httpx.FormTemplate, + "user_create.html", userCreateHTML, form.FormTemplate, templating.Title("Create User"), templating.Assets(assets.AssetsAdmin), @@ -80,16 +81,16 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler { ), ) - return &httpx.Form[createUserResult]{ + return &form.Form[createUserResult]{ Fields: []field.Field{ {Name: "username", Type: field.Text, Autocomplete: field.Username, Label: "Username"}, {Name: "password", Type: field.Password, Autocomplete: field.NewPassword, Label: "Password"}, {Name: "admin", Type: field.Checkbox, Label: "Distillery Administrator"}, }, - FieldTemplate: field.PureCSSFieldTemplate, + FieldTemplate: assets.PureCSSFieldTemplate, - RenderTemplate: tpl.Template(), - RenderTemplateContext: templating.FormTemplateContext(tpl), + Template: tpl.Template(), + TemplateContext: templating.FormTemplateContext(tpl), Validate: func(r *http.Request, values map[string]string) (cu createUserResult, err error) { cu.User, cu.Passsword, cu.Admin = values["username"], values["password"], values["admin"] == field.CheckboxChecked @@ -110,7 +111,7 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler { return cu, nil }, - RenderSuccess: func(cu createUserResult, values map[string]string, w http.ResponseWriter, r *http.Request) error { + Success: func(cu createUserResult, values map[string]string, w http.ResponseWriter, r *http.Request) error { // create the user user, err := admin.dependencies.Auth.CreateUser(r.Context(), cu.User) if err != nil { diff --git a/internal/dis/component/server/assets/assets.go b/internal/dis/component/server/assets/assets.go index 89bb22e..a9d5ae7 100644 --- a/internal/dis/component/server/assets/assets.go +++ b/internal/dis/component/server/assets/assets.go @@ -21,4 +21,14 @@ type Assets struct { Styles template.HTML // tags inserted by the asset } +var PureCSSFieldTemplate = template.Must(template.New("").Parse(` +
+ +{{ if (eq .Type "textarea" )}} + +{{ else }} + +{{ end }} +
`)) + //go:generate node build.mjs Default User Admin AdminProvision AdminRebuild diff --git a/internal/dis/component/server/handling/handling.go b/internal/dis/component/server/handling/handling.go new file mode 100644 index 0000000..e9cf3df --- /dev/null +++ b/internal/dis/component/server/handling/handling.go @@ -0,0 +1,60 @@ +package handling + +import ( + "html/template" + "net/http" + + "github.com/FAU-CDI/wisski-distillery/internal/dis/component" + "github.com/rs/zerolog" + "github.com/tkw1536/pkglib/httpx" + "github.com/tkw1536/pkglib/httpx/content" + "github.com/tkw1536/pkglib/lazy" +) + +type Handling struct { + component.Base + + text lazy.Lazy[httpx.ErrInterceptor] + html lazy.Lazy[httpx.ErrInterceptor] +} + +func (h *Handling) TextInterceptor() httpx.ErrInterceptor { + return h.text.Get(func() httpx.ErrInterceptor { + return h.interceptor(httpx.TextInterceptor) + }) +} + +func (h *Handling) HTMLInterceptor() httpx.ErrInterceptor { + return h.html.Get(func() httpx.ErrInterceptor { + return h.interceptor(httpx.TextInterceptor) + }) +} + +// Interceptor returns a copy of the parent interceptor with global distillery interceptor options enabled. +func (h *Handling) interceptor(parent httpx.ErrInterceptor) httpx.ErrInterceptor { + pf := parent.OnFallback + if pf == nil { + pf = func(r *http.Request, err error) {} + } + + parent.RenderError = h.Config.HTTP.Debug.Set && h.Config.HTTP.Debug.Value + parent.OnFallback = func(r *http.Request, err error) { + pf(r, err) + + zerolog.Ctx(r.Context()). + Err(err). + Str("path", r.URL.Path). + Msg("unknown error") + } + return parent +} + +func (h *Handling) Redirect(Handler content.RedirectFunc) http.Handler { + r := content.Redirect(Handler) + r.Interceptor = h.TextInterceptor() + return r +} + +func (h *Handling) WriteHTML(context any, err error, template *template.Template, w http.ResponseWriter, r *http.Request) error { + return content.WriteHTMLI(context, err, template, h.HTMLInterceptor(), w, r) +} diff --git a/internal/dis/component/server/home/home.go b/internal/dis/component/server/home/home.go index e168843..599bc61 100644 --- a/internal/dis/component/server/home/home.go +++ b/internal/dis/component/server/home/home.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/list" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" ) @@ -15,6 +16,7 @@ type Home struct { dependencies struct { ListInstances *list.ListInstances Templating *templating.Templating + Handling *handling.Handling } } diff --git a/internal/dis/component/server/home/public.go b/internal/dis/component/server/home/public.go index 30cf4f3..9c78075 100644 --- a/internal/dis/component/server/home/public.go +++ b/internal/dis/component/server/home/public.go @@ -61,7 +61,7 @@ func (home *Home) publicHandler(ctx context.Context) http.Handler { about := home.dependencies.Templating.GetCustomizable(aboutTemplate) - return tpl.HTMLHandler(func(r *http.Request) (pc publicContext, err error) { + return tpl.HTMLHandler(home.dependencies.Handling, func(r *http.Request) (pc publicContext, err error) { // only act on the root path! if strings.TrimSuffix(r.URL.Path, "/") != "" { return pc, httpx.ErrNotFound diff --git a/internal/dis/component/server/legal/legal.go b/internal/dis/component/server/legal/legal.go index ec1b450..5548fe7 100644 --- a/internal/dis/component/server/legal/legal.go +++ b/internal/dis/component/server/legal/legal.go @@ -8,6 +8,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" _ "embed" @@ -18,6 +19,7 @@ type Legal struct { dependencies struct { Static *assets.Static Templating *templating.Templating + Handling *handling.Handling } } @@ -65,7 +67,7 @@ func (legal *Legal) HandleRoute(ctx context.Context, route string) (http.Handler ), ) - return tpl.HTMLHandler(func(r *http.Request) (lc legalContext, err error) { + return tpl.HTMLHandler(legal.dependencies.Handling, func(r *http.Request) (lc legalContext, err error) { lc.LegalNotices = cli.LegalNotices lc.CSRFCookie = server.CSRFCookie diff --git a/internal/dis/component/server/news/news.go b/internal/dis/component/server/news/news.go index 1656ff0..86f0a92 100644 --- a/internal/dis/component/server/news/news.go +++ b/internal/dis/component/server/news/news.go @@ -11,6 +11,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" "github.com/rs/zerolog" "github.com/yuin/goldmark" @@ -23,6 +24,7 @@ type News struct { component.Base dependencies struct { Templating *templating.Templating + Handling *handling.Handling } } @@ -143,7 +145,7 @@ func (news *News) HandleRoute(ctx context.Context, path string) (http.Handler, e zerolog.Ctx(ctx).Err(itemsErr).Msg("Unable to load news items") } - return tpl.HTMLHandler(func(r *http.Request) (nc newsContext, err error) { + return tpl.HTMLHandler(news.dependencies.Handling, func(r *http.Request) (nc newsContext, err error) { nc.Items, err = items, itemsErr return }), nil diff --git a/internal/dis/component/server/server.go b/internal/dis/component/server/server.go index 9b980b5..c513e38 100644 --- a/internal/dis/component/server/server.go +++ b/internal/dis/component/server/server.go @@ -2,18 +2,17 @@ package server import ( "context" - "fmt" "io" "net/http" - "runtime/debug" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/tkw1536/pkglib/contextx" - "github.com/tkw1536/pkglib/httpx" - "github.com/tkw1536/pkglib/httpx/timewrap" - "github.com/tkw1536/pkglib/mux" + "github.com/tkw1536/pkglib/httpx/mux" + "github.com/tkw1536/pkglib/httpx/wrap" + "github.com/tkw1536/pkglib/recovery" "github.com/gorilla/csrf" "github.com/rs/zerolog" @@ -27,6 +26,7 @@ type Server struct { Cronables []component.Cronable Templating *templating.Templating + Handleing *handling.Handling } } @@ -39,30 +39,33 @@ var ( // // Logging messages are directed to progress func (server *Server) Server(ctx context.Context, progress io.Writer) (public http.Handler, internal http.Handler, err error) { - logger := zerolog.Ctx(ctx) + interceptor := server.dependencies.Handleing.TextInterceptor() - var publicM, internalM mux.Mux[component.RouteContext] - publicM.Context = func(r *http.Request) component.RouteContext { - slug, ok := server.Config.HTTP.NormSlugFromHost(r.Host) - return component.RouteContext{ - DefaultDomain: slug == "" && ok, - } - } - publicM.Panic = func(p any, stack []byte, w http.ResponseWriter, r *http.Request) { - // log the panic - logger.Error(). - Str("panic", fmt.Sprint(p)). - Str("stack", string(stack)). - Str("path", r.URL.Path). - Msg("panic serving handler") + // wrapHandler wraps individual handlers for errors + wrapHandler := func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // handle any panic()s that occur + defer func() { + // intercept any panic() that wasn't caught + if err := recovery.Recover(recover()); err != nil { + interceptor.Intercept(w, r, err) + } + }() - // and send an internal server error - httpx.TextInterceptor.Fallback.ServeHTTP(w, r) + // determine if we are on a slug from a host + slug, ok := server.Config.HTTP.NormSlugFromHost(r.Host) + + rctx := component.WithRouteContext(r.Context(), component.RouteContext{ + DefaultDomain: slug == "" && ok, + }) + ctx := contextx.WithValuesOf(rctx, ctx) + + // serve with the next context + h.ServeHTTP(w, r.WithContext(ctx)) + }) } - // setup the internal server identically - internalM.Panic = publicM.Panic - internalM.Context = publicM.Context + var publicM, internalM mux.Mux // create a csrf protector csrfProtector := server.csrf() @@ -95,7 +98,7 @@ func (server *Server) Server(ctx context.Context, progress io.Writer) (public ht handler = routes.Decorate(handler, csrfProtector) // determine the predicate - predicate := routes.Predicate(publicM.ContextOf) + predicate := routes.Predicate(func(r *http.Request) component.RouteContext { return component.RouteContextOf(r.Context()) }) // and add all the prefixes for _, prefix := range append([]string{routes.Prefix}, routes.Aliases...) { @@ -107,15 +110,15 @@ func (server *Server) Server(ctx context.Context, progress io.Writer) (public ht } } - // apply the given context function - public = httpx.WithContextWrapper(&publicM, func(rcontext context.Context) context.Context { return contextx.WithValuesOf(rcontext, ctx) }) - internal = httpx.WithContextWrapper(&internalM, func(rcontext context.Context) context.Context { return contextx.WithValuesOf(rcontext, ctx) }) + // wrap the handlers + public = wrapHandler(&publicM) + internal = wrapHandler(&internalM) // Add Content-Security-Policy public = WithCSP(public, models.ContentSecurityPolicyDistilery) internal = WithCSP(internal, models.ContentSecurityPolicyNothing) - public = timewrap.Wrap(public) + public = wrap.Time(public) err = nil return @@ -141,10 +144,3 @@ func WithCSP(handler http.Handler, policy string) http.Handler { handler.ServeHTTP(w, r) }) } - -func init() { - httpx.InterceptorOnFallback = func(req *http.Request, err error) { - stack := debug.Stack() - zerolog.Ctx(req.Context()).Err(err).Str("stack", string(stack)).Msg("unknown error intercepted") - } -} diff --git a/internal/dis/component/server/templating/base.go b/internal/dis/component/server/templating/base.go index 5799eba..668b2e6 100644 --- a/internal/dis/component/server/templating/base.go +++ b/internal/dis/component/server/templating/base.go @@ -11,10 +11,12 @@ import ( "strings" "time" + "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling" "github.com/gorilla/csrf" "github.com/rs/zerolog" - "github.com/tkw1536/pkglib/httpx" - "github.com/tkw1536/pkglib/httpx/timewrap" + "github.com/tkw1536/pkglib/httpx/content" + "github.com/tkw1536/pkglib/httpx/form" + "github.com/tkw1536/pkglib/httpx/wrap" ) //go:embed "src/base.html" @@ -46,7 +48,7 @@ func (tpl *Template[C]) context(r *http.Request, funcs ...FlagFunc) (ctx *tConte // setup the basic properties ctx.ctx = r.Context() ctx.Runtime.RequestURI = r.URL.RequestURI() - ctx.Runtime.StartedAt = timewrap.Start(r).UTC() + ctx.Runtime.StartedAt = wrap.TimeStart(r).UTC() ctx.Runtime.GeneratedAt = time.Now().UTC() ctx.Runtime.CSRF = csrf.TemplateField(r) ctx.Runtime.Menu = tpl.templating.buildMenu(r) @@ -70,19 +72,19 @@ func (tpl *Template[C]) context(r *http.Request, funcs ...FlagFunc) (ctx *tConte var ParseForm = Parse[FormContext] type FormContext struct { - httpx.FormContext + form.FormContext RuntimeFlags } // NewFormContext returns a new FormContext from an underlying context -func NewFormContext(context httpx.FormContext) FormContext { +func NewFormContext(context form.FormContext) FormContext { return FormContext{FormContext: context} } // FormTemplateContext returns a new handler for a form with the given base context -func FormTemplateContext(tw *Template[FormContext]) func(ctx httpx.FormContext, r *http.Request) any { +func FormTemplateContext(tw *Template[FormContext]) func(ctx form.FormContext, r *http.Request) any { // TODO: Is this needed? - return func(ctx httpx.FormContext, r *http.Request) any { + return func(ctx form.FormContext, r *http.Request) any { return tw.Context(r, FormContext{FormContext: ctx}) } } @@ -113,19 +115,21 @@ func (tw *Template[C]) HandlerWithFlags(worker func(r *http.Request) (C, []FlagF // HTMLHandler creates a new httpx.HTMLHandler that calls tw.Handler(worker) and tw.Template. // See also Handler. -func (tw *Template[C]) HTMLHandler(worker func(r *http.Request) (C, error)) httpx.HTMLHandler[any] { - return httpx.HTMLHandler[any]{ - Handler: tw.Handler(worker), - Template: tw.Template(), +func (tw *Template[C]) HTMLHandler(handling *handling.Handling, worker func(r *http.Request) (C, error)) content.HTMLHandler[any] { + return content.HTMLHandler[any]{ + Handler: tw.Handler(worker), + Template: tw.Template(), + Interceptor: handling.HTMLInterceptor(), } } // HTMLHandlerWithFlags creates a new httpx.HTMLHandler that calls tw.HandlerWithFlags(worker) and tw.Template. // See also HandlerWithFlags. -func (tw *Template[C]) HTMLHandlerWithFlags(worker func(r *http.Request) (C, []FlagFunc, error)) httpx.HTMLHandler[any] { - return httpx.HTMLHandler[any]{ - Handler: tw.HandlerWithFlags(worker), - Template: tw.Template(), +func (tw *Template[C]) HTMLHandlerWithFlags(handling *handling.Handling, worker func(r *http.Request) (C, []FlagFunc, error)) content.HTMLHandler[any] { + return content.HTMLHandler[any]{ + Handler: tw.HandlerWithFlags(worker), + Template: tw.Template(), + Interceptor: handling.HTMLInterceptor(), } } diff --git a/internal/dis/component/server/templating/menu.go b/internal/dis/component/server/templating/menu.go index 821a12c..1e4bd67 100644 --- a/internal/dis/component/server/templating/menu.go +++ b/internal/dis/component/server/templating/menu.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" - "github.com/tkw1536/pkglib/mux" + "github.com/tkw1536/pkglib/httpx/mux" "golang.org/x/exp/slices" ) diff --git a/internal/dis/distillery.go b/internal/dis/distillery.go index e712a7b..38f98ee 100644 --- a/internal/dis/distillery.go +++ b/internal/dis/distillery.go @@ -30,6 +30,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket/actions" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/cron" + handleing "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/home" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/legal" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/list" @@ -203,6 +204,7 @@ func (dis *Distillery) allComponents(context *lifetime.Registry[component.Compon // Control server lifetime.Place[*server.Server](context) + lifetime.Place[*handleing.Handling](context) lifetime.Place[*home.Home](context) lifetime.Place[*list.ListInstances](context)