From 10252c11e5bc8c1da67c9bfc17b008a11ef337fe Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Apr 2026 19:34:35 +0200 Subject: [PATCH 01/12] WebAuth: resolve trustedProxy hostnames via DNS --- CHANGED | 2 + FHEM/98_WebAuth.pm | 131 +++++++++++++++++++++++++++- t/FHEM/98_WebAuth/10_authenticate.t | 20 +++++ 3 files changed, 150 insertions(+), 3 deletions(-) diff --git a/CHANGED b/CHANGED index 139b029..c5b5d4a 100644 --- a/CHANGED +++ b/CHANGED @@ -1,3 +1,5 @@ +2026-04-03 - WebAuth: resolve trustedProxy hostnames via DNS + 2026-04-03 - Merge pull request #8 from fhem/codex/webauth-verbose4-auth-debug WebAuth: add verbose 4 troubleshooting logs for auth debugging diff --git a/FHEM/98_WebAuth.pm b/FHEM/98_WebAuth.pm index 56045e3..45beaa9 100644 --- a/FHEM/98_WebAuth.pm +++ b/FHEM/98_WebAuth.pm @@ -12,6 +12,8 @@ package main; use strict; use warnings; +use Socket (); + use FHEM::Core::Authentication::HeaderPolicy qw( evaluate_header_auth_policy parse_header_auth_policy @@ -130,12 +132,16 @@ sub Authenticate { my $trustedProxy = main::AttrVal($aName, "trustedProxy", undef); if($trustedProxy) { - if(!defined($cl->{PEER}) || $cl->{PEER} !~ m/$trustedProxy/) { + my ($trustedProxyMatched, $peerHostname) = _TrustedProxyMatches($cl->{PEER}, $trustedProxy); + if(!$trustedProxyMatched) { main::Log3 $aName, 5, - "$aName: proxy mismatch for path=$path peer=".(defined($cl->{PEER}) ? $cl->{PEER} : '')." trustedProxy=$trustedProxy"; + "$aName: proxy mismatch for path=$path peer=".(defined($cl->{PEER}) ? $cl->{PEER} : ''). + " peerHostname=".(defined($peerHostname) ? $peerHostname : '')." trustedProxy=$trustedProxy"; return &$doReturn(0); } - main::Log3 $aName, 5, "$aName: trusted proxy matched for path=$path peer=$cl->{PEER}"; + main::Log3 $aName, 5, + "$aName: trusted proxy matched for path=$path peer=$cl->{PEER}". + (defined($peerHostname) ? " peerHostname=$peerHostname" : ''); } my %effectiveHeaders = %{$param}; @@ -301,6 +307,118 @@ sub _ExtractForwardedClientIP { return undef; } +sub _TrustedProxyMatches { + my ($peer, $trustedProxy) = @_; + + return (0, undef) if(!defined($peer) || $peer eq '' || !defined($trustedProxy) || $trustedProxy eq ''); + return (1, undef) if($peer =~ m/$trustedProxy/); + + my $peerHostname = _ResolvePeerHostname($peer); + return (1, $peerHostname) if(defined($peerHostname) && $peerHostname =~ m/$trustedProxy/); + + my $normalizedPeer = _NormalizeIPAddress($peer); + foreach my $hostname (_LiteralTrustedProxyHostnames($trustedProxy)) { + foreach my $resolvedIp (_ResolveHostnameToIPs($hostname)) { + next if(!defined($resolvedIp)); + return (1, $peerHostname) if(defined($normalizedPeer) && $normalizedPeer eq _NormalizeIPAddress($resolvedIp)); + } + } + + return (0, $peerHostname); +} + +sub _ResolvePeerHostname { + my ($peer) = @_; + + my ($family, $packedAddress) = _ParseIPAddress($peer); + return undef if(!defined($family) || !defined($packedAddress)); + + my $hostname = gethostbyaddr($packedAddress, $family); + return undef if(!defined($hostname) || $hostname eq ''); + + return lc($hostname); +} + +sub _LiteralTrustedProxyHostnames { + my ($trustedProxy) = @_; + + return () if(!defined($trustedProxy)); + + my $candidate = $trustedProxy; + $candidate =~ s/\A\^//; + $candidate =~ s/\$\z//; + + return () if($candidate !~ m/\A(?:[A-Za-z0-9-]+\.)*[A-Za-z0-9-]+\z/); + return () if($candidate !~ m/[A-Za-z]/); + + return (lc($candidate)); +} + +sub _ResolveHostnameToIPs { + my ($hostname) = @_; + + return () if(!defined($hostname) || $hostname eq ''); + + my ($err, @results) = Socket::getaddrinfo($hostname, undef); + return () if($err); + + my %seen; + my @ips; + foreach my $result (@results) { + next if(ref($result) ne 'HASH'); + my $ip = _SocketAddressToIP($result->{family}, $result->{addr}); + next if(!defined($ip) || $seen{$ip}++); + push @ips, $ip; + } + + return @ips; +} + +sub _SocketAddressToIP { + my ($family, $socketAddress) = @_; + + return undef if(!defined($family) || !defined($socketAddress)); + + if($family == Socket::AF_INET()) { + my (undef, $packedAddress) = Socket::unpack_sockaddr_in($socketAddress); + return Socket::inet_ntop(Socket::AF_INET(), $packedAddress); + } + + if($family == Socket::AF_INET6()) { + my (undef, $packedAddress) = Socket::unpack_sockaddr_in6($socketAddress); + return Socket::inet_ntop(Socket::AF_INET6(), $packedAddress); + } + + return undef; +} + +sub _ParseIPAddress { + my ($ip) = @_; + + my $normalizedIp = _NormalizeIPAddress($ip); + return (undef, undef) if(!defined($normalizedIp)); + + my $packedIPv4 = Socket::inet_pton(Socket::AF_INET(), $normalizedIp); + return (Socket::AF_INET(), $packedIPv4) if(defined($packedIPv4)); + + my $packedIPv6 = Socket::inet_pton(Socket::AF_INET6(), $normalizedIp); + return (Socket::AF_INET6(), $packedIPv6) if(defined($packedIPv6)); + + return (undef, undef); +} + +sub _NormalizeIPAddress { + my ($ip) = @_; + + return undef if(!defined($ip) || $ip eq ''); + + $ip =~ s/^\[//; + $ip =~ s/\]$//; + $ip =~ s/%[A-Za-z0-9_.-]+\z//; + + return lc($ip); +} + sub _HeaderValue { my ($headers, $wanted) = @_; @@ -463,6 +581,13 @@ sub Attr {
  • trustedProxy
    Regexp of trusted reverse-proxy IP addresses or hostnames.
    The check uses the socket peer address of the TCP connection. + If the regexp does not match the peer IP directly, WebAuth also tries + the reverse-resolved hostname of the peer. For literal hostname + patterns like proxy.example.org or + ^proxy.example.org$, WebAuth additionally resolves the + configured hostname via DNS and compares the resulting IP addresses + with the socket peer.

    + If the peer does not match, WebAuth does not handle the request and lets another authenticator, for example allowed with basicAuth, try next.

    diff --git a/t/FHEM/98_WebAuth/10_authenticate.t b/t/FHEM/98_WebAuth/10_authenticate.t index 3367860..6d2ade9 100644 --- a/t/FHEM/98_WebAuth/10_authenticate.t +++ b/t/FHEM/98_WebAuth/10_authenticate.t @@ -121,6 +121,26 @@ subtest 'noCheckFor still bypasses header auth' => sub { is(fhem('deleteattr webAuthWEB noCheckFor'), U(), 'noCheckFor removed again'); }; +subtest 'trustedProxy accepts literal hostname via DNS resolution' => sub { + is(fhem('attr webAuthWEB trustedProxy localhost'), U(), 'trustedProxy hostname configured'); + + my $client = make_client( + PEER => '127.0.0.1', + ); + my %headers = ( + _Path => '/fhem', + 'X-Forwarded-User' => 'demo-user', + 'X-Auth-Source' => 'oauth2-proxy', + ); + + my $ret = Authenticate($client, \%headers); + + is($ret, 1, 'literal trustedProxy hostname resolves to peer IP'); + is($client->{AuthenticatedBy}, 'webAuthWEB', 'WebAuth authenticated the request via hostname-based trustedProxy'); + + is(fhem('deleteattr webAuthWEB trustedProxy'), U(), 'trustedProxy removed again'); +}; + todo 'known follow-up auth handling regression with the current patch' => sub { subtest 'strict re-checks unauthenticated keep-alive follow-up requests' => sub { is(fhem('attr webAuthWEB strict 0'), U(), 'strict disabled for initial request'); From c659246c3aee953b214ca64f57a5108f2aa159ca Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 3 Apr 2026 17:34:55 +0000 Subject: [PATCH 02/12] Automatic update of controls and CHANGED --- CHANGED | 2 ++ README.md | 4 ++-- controls_WebAuth.txt | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGED b/CHANGED index c5b5d4a..f77b8cc 100644 --- a/CHANGED +++ b/CHANGED @@ -1,5 +1,7 @@ 2026-04-03 - WebAuth: resolve trustedProxy hostnames via DNS +2026-04-03 - WebAuth: resolve trustedProxy hostnames via DNS + 2026-04-03 - Merge pull request #8 from fhem/codex/webauth-verbose4-auth-debug WebAuth: add verbose 4 troubleshooting logs for auth debugging diff --git a/README.md b/README.md index ed3e316..4f3d7f9 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Authenticate FHEMWEB requests based on HTTP headers, typically injected by a tru ] } ``` -- `trustedProxy`: Regexp of trusted reverse-proxy IP addresses or hostnames. The check uses the socket peer address of the TCP connection. If the peer does not match, WebAuth does not handle the request and lets another authenticator, for example `allowed` with `basicAuth`, try next. When the peer matches, WebAuth additionally makes the peer IP and a client IP derived from `Forwarded` or `X-Forwarded-For` available to `headerAuthPolicy` via synthetic internal headers. Example: +- `trustedProxy`: Regexp of trusted reverse-proxy IP addresses or hostnames. The check uses the socket peer address of the TCP connection. If the regexp does not match the peer IP directly, WebAuth also tries the reverse-resolved hostname of the peer. For literal hostname patterns like `proxy.example.org` or `^proxy.example.org$`, WebAuth additionally resolves the configured hostname via DNS and compares the resulting IP addresses with the socket peer. If the peer does not match, WebAuth does not handle the request and lets another authenticator, for example `allowed` with `basicAuth`, try next. When the peer matches, WebAuth additionally makes the peer IP and a client IP derived from `Forwarded` or `X-Forwarded-For` available to `headerAuthPolicy` via synthetic internal headers. Example: ```json { @@ -114,6 +114,6 @@ per branch. To add this branch as an update source in FHEM, use: ```text -update add https://raw.githubusercontent.com/fhem/WebAuth/main/controls_WebAuth.txt +update add https://raw.githubusercontent.com/fhem/WebAuth/codex/trusted-proxy-dns/controls_WebAuth.txt ``` diff --git a/controls_WebAuth.txt b/controls_WebAuth.txt index 31d719d..74450c8 100644 --- a/controls_WebAuth.txt +++ b/controls_WebAuth.txt @@ -1,2 +1,2 @@ -UPD 2026-04-03_19:23:13 16821 FHEM/98_WebAuth.pm +UPD 2026-04-03_19:34:35 20453 FHEM/98_WebAuth.pm UPD 2026-03-29_23:56:53 4139 lib/FHEM/Core/Authentication/HeaderPolicy.pm From ab2a6dfcb1be78f04e7ce544e0fbb2920df32969 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Apr 2026 19:39:00 +0200 Subject: [PATCH 03/12] WebAuth: precompile configured regexes per device --- CHANGED | 2 +- FHEM/98_WebAuth.pm | 36 ++++++++++++++++++++--------- t/FHEM/98_WebAuth/10_authenticate.t | 4 ++++ 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/CHANGED b/CHANGED index f77b8cc..fc80a4c 100644 --- a/CHANGED +++ b/CHANGED @@ -1,6 +1,6 @@ 2026-04-03 - WebAuth: resolve trustedProxy hostnames via DNS -2026-04-03 - WebAuth: resolve trustedProxy hostnames via DNS +2026-04-03 - WebAuth: precompile trustedProxy and noCheckFor regexes per device 2026-04-03 - Merge pull request #8 from fhem/codex/webauth-verbose4-auth-debug diff --git a/FHEM/98_WebAuth.pm b/FHEM/98_WebAuth.pm index 45beaa9..1309883 100644 --- a/FHEM/98_WebAuth.pm +++ b/FHEM/98_WebAuth.pm @@ -124,15 +124,16 @@ sub Authenticate { return &$doReturn(2); } - my $exc = main::AttrVal($aName, "noCheckFor", undef); - if($exc && $param->{_Path} =~ m/$exc/) { + my $exc = $me->{".noCheckFor"}; + if($exc && $param->{_Path} =~ $exc) { main::Log3 $aName, 5, "$aName: bypassing authentication for path=$path due to noCheckFor"; return 3; } - my $trustedProxy = main::AttrVal($aName, "trustedProxy", undef); + my $trustedProxy = $me->{".trustedProxy"}; + my $trustedProxyRe = $me->{".trustedProxyRe"}; if($trustedProxy) { - my ($trustedProxyMatched, $peerHostname) = _TrustedProxyMatches($cl->{PEER}, $trustedProxy); + my ($trustedProxyMatched, $peerHostname) = _TrustedProxyMatches($cl->{PEER}, $trustedProxyRe, $trustedProxy); if(!$trustedProxyMatched) { main::Log3 $aName, 5, "$aName: proxy mismatch for path=$path peer=".(defined($cl->{PEER}) ? $cl->{PEER} : ''). @@ -308,13 +309,13 @@ sub _ExtractForwardedClientIP { } sub _TrustedProxyMatches { - my ($peer, $trustedProxy) = @_; + my ($peer, $trustedProxyRe, $trustedProxy) = @_; - return (0, undef) if(!defined($peer) || $peer eq '' || !defined($trustedProxy) || $trustedProxy eq ''); - return (1, undef) if($peer =~ m/$trustedProxy/); + return (0, undef) if(!defined($peer) || $peer eq '' || !defined($trustedProxyRe) || !defined($trustedProxy) || $trustedProxy eq ''); + return (1, undef) if($peer =~ $trustedProxyRe); my $peerHostname = _ResolvePeerHostname($peer); - return (1, $peerHostname) if(defined($peerHostname) && $peerHostname =~ m/$trustedProxy/); + return (1, $peerHostname) if(defined($peerHostname) && $peerHostname =~ $trustedProxyRe); my $normalizedPeer = _NormalizeIPAddress($peer); foreach my $hostname (_LiteralTrustedProxyHostnames($trustedProxy)) { @@ -432,6 +433,16 @@ sub _HeaderValue { return undef; } +sub _CompileRegex { + my ($raw) = @_; + + return undef if(!defined($raw)); + + my $compiled = eval { qr/$raw/ }; + return undef if($@); + + return $compiled; +} sub Attr { my ($type, $devName, $attrName, @param) = @_; @@ -451,6 +462,7 @@ sub Attr { } } elsif($attrName eq "headerAuthPolicy" || + $attrName eq "noCheckFor" || $attrName eq "trustedProxy" || $attrName eq "validFor") { if($set) { @@ -470,14 +482,16 @@ sub Attr { $hash->{".$attrName"} = $policy; } else { - my $regexOk = eval { '' =~ m/$raw/; 1 }; - return "trustedProxy must be a valid Perl regular expression" - if(!$regexOk); + my $compiled = _CompileRegex($raw); + return "$attrName must be a valid Perl regular expression" + if(!defined($compiled)); $hash->{".$attrName"} = $raw; + $hash->{".$attrName"."Re"} = $compiled; } } } else { delete($hash->{".$attrName"}); + delete($hash->{".$attrName"."Re"}) if($attrName eq "noCheckFor" || $attrName eq "trustedProxy"); } if($attrName eq "validFor") { diff --git a/t/FHEM/98_WebAuth/10_authenticate.t b/t/FHEM/98_WebAuth/10_authenticate.t index 6d2ade9..3e1d29c 100644 --- a/t/FHEM/98_WebAuth/10_authenticate.t +++ b/t/FHEM/98_WebAuth/10_authenticate.t @@ -109,6 +109,7 @@ subtest 'non-matching header policy denies access without basic challenge' => su subtest 'noCheckFor still bypasses header auth' => sub { is(fhem('attr webAuthWEB noCheckFor ^/fhem/icons/favicon$'), U(), 'noCheckFor configured'); + is(ref($defs{webAuthWEB}{'.noCheckForRe'}), 'Regexp', 'noCheckFor regex is precompiled on the device hash'); my $client = make_client(); my %headers = (_Path => '/fhem/icons/favicon'); @@ -119,10 +120,12 @@ subtest 'noCheckFor still bypasses header auth' => sub { is($client->{'.httpAuthHeader'}, U(), 'no auth header is generated for bypass'); is(fhem('deleteattr webAuthWEB noCheckFor'), U(), 'noCheckFor removed again'); + is($defs{webAuthWEB}{'.noCheckForRe'}, U(), 'compiled noCheckFor regex is removed again'); }; subtest 'trustedProxy accepts literal hostname via DNS resolution' => sub { is(fhem('attr webAuthWEB trustedProxy localhost'), U(), 'trustedProxy hostname configured'); + is(ref($defs{webAuthWEB}{'.trustedProxyRe'}), 'Regexp', 'trustedProxy regex is precompiled on the device hash'); my $client = make_client( PEER => '127.0.0.1', @@ -139,6 +142,7 @@ subtest 'trustedProxy accepts literal hostname via DNS resolution' => sub { is($client->{AuthenticatedBy}, 'webAuthWEB', 'WebAuth authenticated the request via hostname-based trustedProxy'); is(fhem('deleteattr webAuthWEB trustedProxy'), U(), 'trustedProxy removed again'); + is($defs{webAuthWEB}{'.trustedProxyRe'}, U(), 'compiled trustedProxy regex is removed again'); }; todo 'known follow-up auth handling regression with the current patch' => sub { From 38bfb4ff13e03d52585e12596159e009de2763f3 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 3 Apr 2026 17:39:48 +0000 Subject: [PATCH 04/12] Automatic update of controls and CHANGED --- CHANGED | 2 ++ controls_WebAuth.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGED b/CHANGED index fc80a4c..4ab31af 100644 --- a/CHANGED +++ b/CHANGED @@ -1,3 +1,5 @@ +2026-04-03 - WebAuth: precompile configured regexes per device + 2026-04-03 - WebAuth: resolve trustedProxy hostnames via DNS 2026-04-03 - WebAuth: precompile trustedProxy and noCheckFor regexes per device diff --git a/controls_WebAuth.txt b/controls_WebAuth.txt index 74450c8..1a36c29 100644 --- a/controls_WebAuth.txt +++ b/controls_WebAuth.txt @@ -1,2 +1,2 @@ -UPD 2026-04-03_19:34:35 20453 FHEM/98_WebAuth.pm +UPD 2026-04-03_19:39:35 20867 FHEM/98_WebAuth.pm UPD 2026-03-29_23:56:53 4139 lib/FHEM/Core/Authentication/HeaderPolicy.pm From 82407e5fca530ecdcd3d234b2cd4293c30ed96a1 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Apr 2026 19:43:00 +0200 Subject: [PATCH 05/12] WebAuth: lazily restore compiled attribute regexes --- FHEM/98_WebAuth.pm | 30 ++++++++++++++++++++++++++--- t/FHEM/98_WebAuth/10_authenticate.t | 24 +++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/FHEM/98_WebAuth.pm b/FHEM/98_WebAuth.pm index 1309883..982114c 100644 --- a/FHEM/98_WebAuth.pm +++ b/FHEM/98_WebAuth.pm @@ -124,14 +124,13 @@ sub Authenticate { return &$doReturn(2); } - my $exc = $me->{".noCheckFor"}; + my ($excRaw, $exc) = _GetStoredRegex($me, $aName, "noCheckFor"); if($exc && $param->{_Path} =~ $exc) { main::Log3 $aName, 5, "$aName: bypassing authentication for path=$path due to noCheckFor"; return 3; } - my $trustedProxy = $me->{".trustedProxy"}; - my $trustedProxyRe = $me->{".trustedProxyRe"}; + my ($trustedProxy, $trustedProxyRe) = _GetStoredRegex($me, $aName, "trustedProxy"); if($trustedProxy) { my ($trustedProxyMatched, $peerHostname) = _TrustedProxyMatches($cl->{PEER}, $trustedProxyRe, $trustedProxy); if(!$trustedProxyMatched) { @@ -444,6 +443,31 @@ sub _CompileRegex { return $compiled; } +sub _GetStoredRegex { + my ($hash, $devName, $attrName) = @_; + + return (undef, undef) if(ref($hash) ne 'HASH' || !defined($attrName)); + + my $raw = $hash->{".$attrName"}; + if(!defined($raw) && defined($devName)) { + $raw = main::AttrVal($devName, $attrName, undef); + $hash->{".$attrName"} = $raw if(defined($raw)); + } + + return (undef, undef) if(!defined($raw) || $raw eq ''); + + my $compiled = $hash->{".$attrName"."Re"}; + if(!defined($compiled)) { + $compiled = _CompileRegex($raw); + if(defined($compiled)) { + $hash->{".$attrName"."Re"} = $compiled; + main::Log3 $devName, 5, "$devName: lazily compiled $attrName regex from stored attribute value"; + } + } + + return ($raw, $compiled); +} + sub Attr { my ($type, $devName, $attrName, @param) = @_; my $hash = $main::defs{$devName}; diff --git a/t/FHEM/98_WebAuth/10_authenticate.t b/t/FHEM/98_WebAuth/10_authenticate.t index 3e1d29c..9975c25 100644 --- a/t/FHEM/98_WebAuth/10_authenticate.t +++ b/t/FHEM/98_WebAuth/10_authenticate.t @@ -145,6 +145,30 @@ subtest 'trustedProxy accepts literal hostname via DNS resolution' => sub { is($defs{webAuthWEB}{'.trustedProxyRe'}, U(), 'compiled trustedProxy regex is removed again'); }; +subtest 'stored trustedProxy is compiled lazily after reload-style state loss' => sub { + is(fhem('attr webAuthWEB trustedProxy ^localhost$'), U(), 'trustedProxy hostname regex configured'); + is(ref($defs{webAuthWEB}{'.trustedProxyRe'}), 'Regexp', 'trustedProxy regex starts precompiled'); + + delete $defs{webAuthWEB}{'.trustedProxyRe'}; + is($defs{webAuthWEB}{'.trustedProxyRe'}, U(), 'compiled trustedProxy regex removed to simulate reload gap'); + + my $client = make_client( + PEER => '127.0.0.1', + ); + my %headers = ( + _Path => '/fhem', + 'X-Forwarded-User' => 'demo-user', + 'X-Auth-Source' => 'oauth2-proxy', + ); + + my $ret = Authenticate($client, \%headers); + + is($ret, 1, 'trustedProxy still matches after lazy compilation'); + is(ref($defs{webAuthWEB}{'.trustedProxyRe'}), 'Regexp', 'compiled trustedProxy regex is restored lazily'); + + is(fhem('deleteattr webAuthWEB trustedProxy'), U(), 'trustedProxy removed again'); +}; + todo 'known follow-up auth handling regression with the current patch' => sub { subtest 'strict re-checks unauthenticated keep-alive follow-up requests' => sub { is(fhem('attr webAuthWEB strict 0'), U(), 'strict disabled for initial request'); From 04e7816a801b45d2ddc055bff81b518e7e5eecf6 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 3 Apr 2026 17:43:29 +0000 Subject: [PATCH 06/12] Automatic update of controls and CHANGED --- CHANGED | 2 ++ controls_WebAuth.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGED b/CHANGED index 4ab31af..e9e0e6b 100644 --- a/CHANGED +++ b/CHANGED @@ -1,3 +1,5 @@ +2026-04-03 - WebAuth: lazily restore compiled attribute regexes + 2026-04-03 - WebAuth: precompile configured regexes per device 2026-04-03 - WebAuth: resolve trustedProxy hostnames via DNS diff --git a/controls_WebAuth.txt b/controls_WebAuth.txt index 1a36c29..5b928c9 100644 --- a/controls_WebAuth.txt +++ b/controls_WebAuth.txt @@ -1,2 +1,2 @@ -UPD 2026-04-03_19:39:35 20867 FHEM/98_WebAuth.pm +UPD 2026-04-03_19:43:16 21608 FHEM/98_WebAuth.pm UPD 2026-03-29_23:56:53 4139 lib/FHEM/Core/Authentication/HeaderPolicy.pm From d910c791906f2851b7a26775f1dd3130c1bc8a1b Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Apr 2026 19:47:51 +0200 Subject: [PATCH 07/12] WebAuth: bump version to 0.3.0 --- FHEM/98_WebAuth.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FHEM/98_WebAuth.pm b/FHEM/98_WebAuth.pm index 982114c..d501b1f 100644 --- a/FHEM/98_WebAuth.pm +++ b/FHEM/98_WebAuth.pm @@ -5,7 +5,7 @@ # authenticate FHEMWEB requests based on HTTP headers # # Author: Sidey -# Version: 0.2.0 +# Version: 0.3.0 # package main; @@ -20,7 +20,7 @@ use FHEM::Core::Authentication::HeaderPolicy qw( validate_header_auth_policy ); -our $VERSION = '0.2.0'; +our $VERSION = '0.3.0'; ##################################### sub WebAuth_Initialize { @@ -707,7 +707,7 @@ sub Attr { "abstract": "authentifiziert FHEMWEB Requests anhand von HTTP Headern" } }, - "x_version": "0.2.0" + "x_version": "0.3.0" } =end :application/json;q=META.json From 99a639a7a279478ce1180576f7526e884bf54d02 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 3 Apr 2026 17:48:11 +0000 Subject: [PATCH 08/12] Automatic update of controls and CHANGED --- CHANGED | 2 ++ README.md | 2 +- controls_WebAuth.txt | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGED b/CHANGED index e9e0e6b..4cdef62 100644 --- a/CHANGED +++ b/CHANGED @@ -1,3 +1,5 @@ +2026-04-03 - WebAuth: bump version to 0.3.0 + 2026-04-03 - WebAuth: lazily restore compiled attribute regexes 2026-04-03 - WebAuth: precompile configured regexes per device diff --git a/README.md b/README.md index 4f3d7f9..00ded55 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Generated from [`FHEM/98_WebAuth.pm`](/home/runner/work/WebAuth/WebAuth/FHEM/98_ - Summary: authenticate FHEMWEB requests based on HTTP headers - Zusammenfassung: authentifiziert FHEMWEB Anfragen anhand von HTTP Headern -- Version: 0.2.0 +- Version: 0.3.0 - Author: Sidey - Keywords: Authentication, Authorization, Header, Reverse Proxy, Trusted Proxy, Forward Auth, SSO, OIDC, Web diff --git a/controls_WebAuth.txt b/controls_WebAuth.txt index 5b928c9..1328da9 100644 --- a/controls_WebAuth.txt +++ b/controls_WebAuth.txt @@ -1,2 +1,2 @@ -UPD 2026-04-03_19:43:16 21608 FHEM/98_WebAuth.pm +UPD 2026-04-03_19:47:56 21608 FHEM/98_WebAuth.pm UPD 2026-03-29_23:56:53 4139 lib/FHEM/Core/Authentication/HeaderPolicy.pm From 1e3cb67079fe91eb3ad1fadc0285cedcd8f74e0d Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Apr 2026 19:57:30 +0200 Subject: [PATCH 09/12] workflow: remove README diff check from test matrix --- .github/workflows/fhem_test.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/fhem_test.yml b/.github/workflows/fhem_test.yml index 757df0a..e004424 100644 --- a/.github/workflows/fhem_test.yml +++ b/.github/workflows/fhem_test.yml @@ -97,10 +97,6 @@ jobs: install-modules-args: --notest - name: Install CPAN dependencies run: cpanm --installdeps . --notest - - name: Verify generated README content - run: | - perl scripts/update-readme-from-module-docs.pl - git diff --exit-code README.md - name: Install FHEM via debian nightly uses: fhem/setup-fhem@v1.0.1 - name: Change ownership of /opt/fhem From 9ec86c93d61457a4794408a12ae7703f49911e7d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 3 Apr 2026 17:57:50 +0000 Subject: [PATCH 10/12] Automatic update of controls and CHANGED --- CHANGED | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGED b/CHANGED index 4cdef62..d7c0b89 100644 --- a/CHANGED +++ b/CHANGED @@ -1,3 +1,5 @@ +2026-04-03 - workflow: remove README diff check from test matrix + 2026-04-03 - WebAuth: bump version to 0.3.0 2026-04-03 - WebAuth: lazily restore compiled attribute regexes From 1553f064c58312df1e013994f947e9fb28bc9b9c Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 3 Apr 2026 20:14:04 +0200 Subject: [PATCH 11/12] workflow: use BOT_PUSH_TOKEN for generated updates --- .github/workflows/fhem_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fhem_test.yml b/.github/workflows/fhem_test.yml index e004424..89e0ecb 100644 --- a/.github/workflows/fhem_test.yml +++ b/.github/workflows/fhem_test.yml @@ -79,7 +79,7 @@ jobs: - name: Push generated files uses: ad-m/github-push-action@v1.0.0 with: - github_token: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.BOT_PUSH_TOKEN }} branch: ${{ steps.branch.outputs.name }} test: From 210b40d275bddc573190505c4675bbe608f584bf Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 3 Apr 2026 18:14:19 +0000 Subject: [PATCH 12/12] Automatic update of controls and CHANGED --- CHANGED | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGED b/CHANGED index d7c0b89..3bc61db 100644 --- a/CHANGED +++ b/CHANGED @@ -1,3 +1,5 @@ +2026-04-03 - workflow: use BOT_PUSH_TOKEN for generated updates + 2026-04-03 - workflow: remove README diff check from test matrix 2026-04-03 - WebAuth: bump version to 0.3.0