Added refresh button to challenges and deny pages where reasonable, ensure no open redirect or other
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>{{ .Title }}</title>
|
<title>{{ .Title }}</title>
|
||||||
<link rel="stylesheet" href="{{ .Path }}/embed/assets/static/anubis/style.css?cacheBust={{ .Random }}"/>
|
<link rel="stylesheet" href="{{ .Path }}/assets/static/anubis/style.css?cacheBust={{ .Random }}"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
{{ range $key, $value := .Meta }}
|
{{ range $key, $value := .Meta }}
|
||||||
{{ if eq $key "refresh"}}
|
{{ if eq $key "refresh"}}
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
<img
|
<img
|
||||||
id="image"
|
id="image"
|
||||||
style="width:100%;max-width:256px;"
|
style="width:100%;max-width:256px;"
|
||||||
src="{{ .Path }}/embed/assets/static/logo.png?cacheBust={{ .Random }}"
|
src="{{ .Path }}/assets/static/logo.png?cacheBust={{ .Random }}"
|
||||||
/>
|
/>
|
||||||
{{if .Challenge }}
|
{{if .Challenge }}
|
||||||
<p id="status">Loading challenge <em>{{ .Challenge }}</em>...</p>
|
<p id="status">Loading challenge <em>{{ .Challenge }}</em>...</p>
|
||||||
@@ -173,18 +173,24 @@
|
|||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<details>
|
<details style="padding-bottom: 2em;">
|
||||||
<summary>Why am I seeing this?</summary>
|
<summary>Why am I seeing this?</summary>
|
||||||
<p>You are seeing this because the administrator of this website has set up <a href="https://git.gammaspectra.live/git/go-away">go-away</a> to protect the server against the scourge of <a href="https://thelibre.news/foss-infrastructure-is-under-attack-by-ai-companies/">AI companies aggressively scraping websites</a>. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.</p>
|
<p>You are seeing this because the administrator of this website has set up <a href="https://git.gammaspectra.live/git/go-away">go-away</a> to protect the server against the scourge of <a href="https://thelibre.news/foss-infrastructure-is-under-attack-by-ai-companies/">AI companies aggressively scraping websites</a>. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.</p>
|
||||||
<p>Please note that some challenges requires the use of modern JavaScript features and some plugins may will disable. Please disable such plugins for this domain (for example, JShelter).</p>
|
<p>Please note that some challenges requires the use of modern JavaScript features and some plugins may will disable. Please disable such plugins for this domain (for example, JShelter).</p>
|
||||||
<p>If you have any issues contact the administrator and provide this request id: <em>{{ .Id }}</em></p>
|
<p>If you have any issues contact the administrator and provide this Request Id: <em>{{ .Id }}</em></p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<noscript>
|
<noscript>
|
||||||
<p>
|
<p>
|
||||||
Sadly, you may need to enable JavaScript to get past this challenge. This is required because AI companies have changed
|
Sadly, you may need to enable JavaScript to get past this challenge. This is required because AI companies have changed
|
||||||
the social contract around how website hosting works.
|
the social contract around how website hosting works.
|
||||||
</p>
|
</p>
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
||||||
|
{{if .Redirect }}
|
||||||
|
<a role="button" href="{{ .Redirect }}">Refresh page</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<div id="testarea"></div>
|
<div id="testarea"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -70,13 +70,26 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
<div id="spinner"></div>
|
<div id="spinner"></div>
|
||||||
|
|
||||||
<details>
|
<details style="padding-bottom: 2em;">
|
||||||
<summary>Why am I seeing this?</summary>
|
<summary>Why am I seeing this?</summary>
|
||||||
<p>You are seeing this because the administrator of this website has set up <a href="https://git.gammaspectra.live/git/go-away">go-away</a> to protect the server against the scourge of <a href="https://thelibre.news/foss-infrastructure-is-under-attack-by-ai-companies/">AI companies aggressively scraping websites</a>. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.</p>
|
<p>You are seeing this because the administrator of this website has set up <a href="https://git.gammaspectra.live/git/go-away">go-away</a> to protect the server against the scourge of <a href="https://thelibre.news/foss-infrastructure-is-under-attack-by-ai-companies/">AI companies aggressively scraping websites</a>. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.</p>
|
||||||
<p>Please note that some challenges requires the use of modern JavaScript features and some plugins may will disable. Please disable such plugins for this domain (for example, JShelter).</p>
|
<p>Please note that some challenges requires the use of modern JavaScript features and some plugins may will disable. Please disable such plugins for this domain (for example, JShelter).</p>
|
||||||
<p>If you have any issues contact the administrator and provide the request id: <em>{{ .Id }}</em></p>
|
<p>If you have any issues contact the administrator and provide the Request Id: <em>{{ .Id }}</em></p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<p>
|
||||||
|
Sadly, you may need to enable JavaScript to get past this challenge. This is required because AI companies have changed
|
||||||
|
the social contract around how website hosting works.
|
||||||
|
</p>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
{{if .Redirect }}
|
||||||
|
<div class="button-row">
|
||||||
|
<a role="button" class="ui small primary button" href="{{ .Redirect }}">Refresh page</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
|
||||||
<div id="testarea"></div>
|
<div id="testarea"></div>
|
||||||
</div>
|
</div>
|
||||||
|
20
lib/http.go
20
lib/http.go
@@ -362,7 +362,13 @@ func (state *State) setupRoutes() error {
|
|||||||
state.Mux.Handle(fmt.Sprintf("GET %s/verify-challenge", c.Path), c.ServeVerifyChallenge)
|
state.Mux.Handle(fmt.Sprintf("GET %s/verify-challenge", c.Path), c.ServeVerifyChallenge)
|
||||||
} else if c.Verify != nil {
|
} else if c.Verify != nil {
|
||||||
state.Mux.HandleFunc(fmt.Sprintf("GET %s/verify-challenge", c.Path), func(w http.ResponseWriter, r *http.Request) {
|
state.Mux.HandleFunc(fmt.Sprintf("GET %s/verify-challenge", c.Path), func(w http.ResponseWriter, r *http.Request) {
|
||||||
err := func() (err error) {
|
redirect, err := utils.EnsureNoOpenRedirect(r.FormValue("redirect"))
|
||||||
|
if redirect == "" {
|
||||||
|
_ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = func() (err error) {
|
||||||
|
|
||||||
data := RequestDataFromContext(r.Context())
|
data := RequestDataFromContext(r.Context())
|
||||||
|
|
||||||
@@ -380,15 +386,15 @@ func (state *State) setupRoutes() error {
|
|||||||
state.addTiming(w, "challenge-verify", "Verify client challenge", time.Since(start))
|
state.addTiming(w, "challenge-verify", "Verify client challenge", time.Since(start))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
state.logger(r).Error(fmt.Errorf("challenge error: %w", err).Error(), "challenge", c.Name, "redirect", r.FormValue("redirect"))
|
state.logger(r).Error(fmt.Errorf("challenge error: %w", err).Error(), "challenge", c.Name, "redirect", redirect)
|
||||||
return err
|
return err
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
state.logger(r).Warn("challenge failed", "challenge", c.Name, "redirect", r.FormValue("redirect"))
|
state.logger(r).Warn("challenge failed", "challenge", c.Name, "redirect", redirect)
|
||||||
utils.ClearCookie(utils.CookiePrefix+c.Name, w)
|
utils.ClearCookie(utils.CookiePrefix+c.Name, w)
|
||||||
_ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusForbidden, fmt.Errorf("access denied: failed challenge %s", c.Name), r.FormValue("redirect"))
|
_ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusForbidden, fmt.Errorf("access denied: failed challenge %s", c.Name), redirect)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
state.logger(r).Info("challenge passed", "challenge", c.Name, "redirect", r.FormValue("redirect"))
|
state.logger(r).Info("challenge passed", "challenge", c.Name, "redirect", redirect)
|
||||||
|
|
||||||
token, err := c.IssueChallengeToken(state.privateKey, key, []byte(result), data.Expires)
|
token, err := c.IssueChallengeToken(state.privateKey, key, []byte(result), data.Expires)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -398,12 +404,12 @@ func (state *State) setupRoutes() error {
|
|||||||
}
|
}
|
||||||
data.Challenges[c.Id] = challenge.VerifyResultPASS
|
data.Challenges[c.Id] = challenge.VerifyResultPASS
|
||||||
|
|
||||||
http.Redirect(w, r, r.FormValue("redirect"), http.StatusTemporaryRedirect)
|
http.Redirect(w, r, redirect, http.StatusTemporaryRedirect)
|
||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.ClearCookie(utils.CookiePrefix+c.Name, w)
|
utils.ClearCookie(utils.CookiePrefix+c.Name, w)
|
||||||
_ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, r.FormValue("redirect"))
|
_ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, redirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
19
lib/state.go
19
lib/state.go
@@ -435,7 +435,14 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
|
|
||||||
c.ServeVerifyChallenge = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
c.ServeVerifyChallenge = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
err := func() (err error) {
|
redirect, err := utils.EnsureNoOpenRedirect(r.FormValue("redirect"))
|
||||||
|
if err != nil {
|
||||||
|
_ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = func() (err error) {
|
||||||
|
|
||||||
data := RequestDataFromContext(r.Context())
|
data := RequestDataFromContext(r.Context())
|
||||||
|
|
||||||
key := state.GetChallengeKeyForRequest(challengeName, data.Expires, r)
|
key := state.GetChallengeKeyForRequest(challengeName, data.Expires, r)
|
||||||
@@ -449,13 +456,13 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
if ok, err := c.Verify(key, result, r); err != nil {
|
if ok, err := c.Verify(key, result, r); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
state.logger(r).Warn("challenge failed", "challenge", challengeName, "redirect", r.FormValue("redirect"))
|
state.logger(r).Warn("challenge failed", "challenge", challengeName, "redirect", redirect)
|
||||||
utils.ClearCookie(utils.CookiePrefix+challengeName, w)
|
utils.ClearCookie(utils.CookiePrefix+challengeName, w)
|
||||||
_ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusForbidden, fmt.Errorf("access denied: failed challenge %s", challengeName), r.FormValue("redirect"))
|
_ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusForbidden, fmt.Errorf("access denied: failed challenge %s", challengeName), redirect)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
state.logger(r).Warn("challenge passed", "challenge", challengeName, "redirect", r.FormValue("redirect"))
|
state.logger(r).Warn("challenge passed", "challenge", challengeName, "redirect", redirect)
|
||||||
|
|
||||||
token, err := c.IssueChallengeToken(state.privateKey, key, []byte(result), data.Expires)
|
token, err := c.IssueChallengeToken(state.privateKey, key, []byte(result), data.Expires)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -467,7 +474,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
|
|
||||||
switch httpCode {
|
switch httpCode {
|
||||||
case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect:
|
case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect:
|
||||||
http.Redirect(w, r, r.FormValue("redirect"), httpCode)
|
http.Redirect(w, r, redirect, httpCode)
|
||||||
default:
|
default:
|
||||||
w.Header().Set("Content-Type", mimeType)
|
w.Header().Set("Content-Type", mimeType)
|
||||||
w.WriteHeader(httpCode)
|
w.WriteHeader(httpCode)
|
||||||
@@ -480,7 +487,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
|
|||||||
}()
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.ClearCookie(utils.CookiePrefix+challengeName, w)
|
utils.ClearCookie(utils.CookiePrefix+challengeName, w)
|
||||||
_ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, r.FormValue("redirect"))
|
_ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, redirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -2,13 +2,33 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func EnsureNoOpenRedirect(redirect string) (string, error) {
|
||||||
|
uri, err := url.Parse(redirect)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
uri.Scheme = ""
|
||||||
|
uri.Host = ""
|
||||||
|
uri.User = nil
|
||||||
|
uri.Opaque = ""
|
||||||
|
uri.OmitHost = true
|
||||||
|
|
||||||
|
if uri.Path != "" && !strings.HasPrefix(uri.Path, "/") {
|
||||||
|
return "", errors.New("invalid redirect path")
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func MakeReverseProxy(target string) (*httputil.ReverseProxy, error) {
|
func MakeReverseProxy(target string) (*httputil.ReverseProxy, error) {
|
||||||
u, err := url.Parse(target)
|
u, err := url.Parse(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Reference in New Issue
Block a user