aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/anubis/botPolicies.json12
-rw-r--r--cmd/anubis/internal/config/config.go68
-rw-r--r--cmd/anubis/internal/config/config_test.go42
-rw-r--r--cmd/anubis/js/main.mjs28
-rw-r--r--cmd/anubis/js/proof-of-work-slow.mjs63
-rw-r--r--cmd/anubis/js/proof-of-work.mjs5
-rw-r--r--cmd/anubis/main.go45
-rw-r--r--cmd/anubis/policy.go26
-rw-r--r--cmd/anubis/policy_test.go6
-rw-r--r--cmd/anubis/static/js/main.mjs2
-rw-r--r--cmd/anubis/static/js/main.mjs.brbin992 -> 1216 bytes
-rw-r--r--cmd/anubis/static/js/main.mjs.gzbin1194 -> 1451 bytes
-rw-r--r--cmd/anubis/static/js/main.mjs.map8
-rw-r--r--cmd/anubis/static/js/main.mjs.zstbin1190 -> 1430 bytes
-rw-r--r--docs/docs/CHANGELOG.md17
-rw-r--r--docs/docs/admin/algorithm-selection.mdx12
-rw-r--r--docs/docs/admin/policies.md23
17 files changed, 311 insertions, 46 deletions
diff --git a/cmd/anubis/botPolicies.json b/cmd/anubis/botPolicies.json
index 4a6321b..b602ef7 100644
--- a/cmd/anubis/botPolicies.json
+++ b/cmd/anubis/botPolicies.json
@@ -66,10 +66,20 @@
"action": "DENY"
},
{
+ "name": "generic-bot-catchall",
+ "user_agent_regex": "(?i:bot|crawler)",
+ "action": "CHALLENGE",
+ "challenge": {
+ "difficulty": 16,
+ "report_as": 4,
+ "algorithm": "slow"
+ }
+ },
+ {
"name": "generic-browser",
"user_agent_regex": "Mozilla",
"action": "CHALLENGE"
}
],
"dnsbl": true
-}
+} \ No newline at end of file
diff --git a/cmd/anubis/internal/config/config.go b/cmd/anubis/internal/config/config.go
index ad338ef..efd8496 100644
--- a/cmd/anubis/internal/config/config.go
+++ b/cmd/anubis/internal/config/config.go
@@ -9,17 +9,26 @@ import (
type Rule string
const (
- RuleUnknown = ""
- RuleAllow = "ALLOW"
- RuleDeny = "DENY"
- RuleChallenge = "CHALLENGE"
+ RuleUnknown Rule = ""
+ RuleAllow Rule = "ALLOW"
+ RuleDeny Rule = "DENY"
+ RuleChallenge Rule = "CHALLENGE"
+)
+
+type Algorithm string
+
+const (
+ AlgorithmUnknown Algorithm = ""
+ AlgorithmFast Algorithm = "fast"
+ AlgorithmSlow Algorithm = "slow"
)
type Bot struct {
- Name string `json:"name"`
- UserAgentRegex *string `json:"user_agent_regex"`
- PathRegex *string `json:"path_regex"`
- Action Rule `json:"action"`
+ Name string `json:"name"`
+ UserAgentRegex *string `json:"user_agent_regex"`
+ PathRegex *string `json:"path_regex"`
+ Action Rule `json:"action"`
+ Challenge *ChallengeRules `json:"challenge,omitempty"`
}
var (
@@ -66,6 +75,12 @@ func (b Bot) Valid() error {
errs = append(errs, fmt.Errorf("%w: %q", ErrUnknownAction, b.Action))
}
+ if b.Action == RuleChallenge && b.Challenge != nil {
+ if err := b.Challenge.Valid(); err != nil {
+ errs = append(errs, err)
+ }
+ }
+
if len(errs) != 0 {
return fmt.Errorf("config: bot entry for %q is not valid:\n%w", b.Name, errors.Join(errs...))
}
@@ -73,6 +88,43 @@ func (b Bot) Valid() error {
return nil
}
+type ChallengeRules struct {
+ Difficulty int `json:"difficulty"`
+ ReportAs int `json:"report_as"`
+ Algorithm Algorithm `json:"algorithm"`
+}
+
+var (
+ ErrChallengeRuleHasWrongAlgorithm = errors.New("config.Bot.ChallengeRules: algorithm is invalid")
+ ErrChallengeDifficultyTooLow = errors.New("config.Bot.ChallengeRules: difficulty is too low (must be >= 1)")
+ ErrChallengeDifficultyTooHigh = errors.New("config.Bot.ChallengeRules: difficulty is too high (must be <= 64)")
+)
+
+func (cr ChallengeRules) Valid() error {
+ var errs []error
+
+ if cr.Difficulty < 1 {
+ errs = append(errs, fmt.Errorf("%w, got: %d", ErrChallengeDifficultyTooLow, cr.Difficulty))
+ }
+
+ if cr.Difficulty > 64 {
+ errs = append(errs, fmt.Errorf("%w, got: %d", ErrChallengeDifficultyTooHigh, cr.Difficulty))
+ }
+
+ switch cr.Algorithm {
+ case AlgorithmFast, AlgorithmSlow, AlgorithmUnknown:
+ // do nothing, it's all good
+ default:
+ errs = append(errs, fmt.Errorf("%w: %q", ErrChallengeRuleHasWrongAlgorithm, cr.Algorithm))
+ }
+
+ if len(errs) != 0 {
+ return fmt.Errorf("config: challenge rules entry is not valid:\n%w", errors.Join(errs...))
+ }
+
+ return nil
+}
+
type Config struct {
Bots []Bot `json:"bots"`
DNSBL bool `json:"dnsbl"`
diff --git a/cmd/anubis/internal/config/config_test.go b/cmd/anubis/internal/config/config_test.go
index f362a76..0903fbb 100644
--- a/cmd/anubis/internal/config/config_test.go
+++ b/cmd/anubis/internal/config/config_test.go
@@ -87,6 +87,48 @@ func TestBotValid(t *testing.T) {
},
err: ErrInvalidPathRegex,
},
+ {
+ name: "challenge difficulty too low",
+ bot: Bot{
+ Name: "mozilla-ua",
+ Action: RuleChallenge,
+ PathRegex: p("Mozilla"),
+ Challenge: &ChallengeRules{
+ Difficulty: 0,
+ ReportAs: 4,
+ Algorithm: "fast",
+ },
+ },
+ err: ErrChallengeDifficultyTooLow,
+ },
+ {
+ name: "challenge difficulty too high",
+ bot: Bot{
+ Name: "mozilla-ua",
+ Action: RuleChallenge,
+ PathRegex: p("Mozilla"),
+ Challenge: &ChallengeRules{
+ Difficulty: 420,
+ ReportAs: 4,
+ Algorithm: "fast",
+ },
+ },
+ err: ErrChallengeDifficultyTooHigh,
+ },
+ {
+ name: "challenge wrong algorithm",
+ bot: Bot{
+ Name: "mozilla-ua",
+ Action: RuleChallenge,
+ PathRegex: p("Mozilla"),
+ Challenge: &ChallengeRules{
+ Difficulty: 420,
+ ReportAs: 4,
+ Algorithm: "high quality rips",
+ },
+ },
+ err: ErrChallengeRuleHasWrongAlgorithm,
+ },
}
for _, cs := range tests {
diff --git a/cmd/anubis/js/main.mjs b/cmd/anubis/js/main.mjs
index fc85a44..297f16f 100644
--- a/cmd/anubis/js/main.mjs
+++ b/cmd/anubis/js/main.mjs
@@ -1,5 +1,11 @@
-import { process } from './proof-of-work.mjs';
-import { testVideo } from './video.mjs';
+import processFast from "./proof-of-work.mjs";
+import processSlow from "./proof-of-work-slow.mjs";
+import { testVideo } from "./video.mjs";
+
+const algorithms = {
+ "fast": processFast,
+ "slow": processSlow,
+}
// from Xeact
const u = (url = "", params = {}) => {
@@ -37,7 +43,7 @@ const imageURL = (mood, cacheBuster) =>
status.innerHTML = 'Calculating...';
- const { challenge, difficulty } = await fetch("/.within.website/x/cmd/anubis/api/make-challenge", { method: "POST" })
+ const { challenge, rules } = await fetch("/.within.website/x/cmd/anubis/api/make-challenge", { method: "POST" })
.then(r => {
if (!r.ok) {
throw new Error("Failed to fetch config");
@@ -47,16 +53,26 @@ const imageURL = (mood, cacheBuster) =>
.catch(err => {
title.innerHTML = "Oh no!";
status.innerHTML = `Failed to fetch config: ${err.message}`;
- image.src = imageURL("sad");
+ image.src = imageURL("sad", anubisVersion);
spinner.innerHTML = "";
spinner.style.display = "none";
throw err;
});
- status.innerHTML = `Calculating...<br/>Difficulty: ${difficulty}`;
+ const process = algorithms[rules.algorithm];
+ if (!process) {
+ title.innerHTML = "Oh no!";
+ status.innerHTML = `Failed to resolve check algorithm. You may want to reload the page.`;
+ image.src = imageURL("sad", anubisVersion);
+ spinner.innerHTML = "";
+ spinner.style.display = "none";
+ return;
+ }
+
+ status.innerHTML = `Calculating...<br/>Difficulty: ${rules.report_as}`;
const t0 = Date.now();
- const { hash, nonce } = await process(challenge, difficulty);
+ const { hash, nonce } = await process(challenge, rules.difficulty);
const t1 = Date.now();
console.log({ hash, nonce });
diff --git a/cmd/anubis/js/proof-of-work-slow.mjs b/cmd/anubis/js/proof-of-work-slow.mjs
new file mode 100644
index 0000000..e30dc21
--- /dev/null
+++ b/cmd/anubis/js/proof-of-work-slow.mjs
@@ -0,0 +1,63 @@
+// https://dev.to/ratmd/simple-proof-of-work-in-javascript-3kgm
+
+export default function process(data, difficulty = 5, _threads = 1) {
+ console.debug("slow algo");
+ return new Promise((resolve, reject) => {
+ let webWorkerURL = URL.createObjectURL(new Blob([
+ '(', processTask(), ')()'
+ ], { type: 'application/javascript' }));
+
+ let worker = new Worker(webWorkerURL);
+
+ worker.onmessage = (event) => {
+ worker.terminate();
+ resolve(event.data);
+ };
+
+ worker.onerror = (event) => {
+ worker.terminate();
+ reject();
+ };
+
+ worker.postMessage({
+ data,
+ difficulty
+ });
+
+ URL.revokeObjectURL(webWorkerURL);
+ });
+}
+
+function processTask() {
+ return function () {
+ const sha256 = (text) => {
+ const encoded = new TextEncoder().encode(text);
+ return crypto.subtle.digest("SHA-256", encoded.buffer)
+ .then((result) =>
+ Array.from(new Uint8Array(result))
+ .map((c) => c.toString(16).padStart(2, "0"))
+ .join(""),
+ );
+ };
+
+ addEventListener('message', async (event) => {
+ let data = event.data.data;
+ let difficulty = event.data.difficulty;
+
+ let hash;
+ let nonce = 0;
+ do {
+ hash = await sha256(data + nonce++);
+ } while (hash.substring(0, difficulty) !== Array(difficulty + 1).join('0'));
+
+ nonce -= 1; // last nonce was post-incremented
+
+ postMessage({
+ hash,
+ data,
+ difficulty,
+ nonce,
+ });
+ });
+ }.toString();
+} \ No newline at end of file
diff --git a/cmd/anubis/js/proof-of-work.mjs b/cmd/anubis/js/proof-of-work.mjs
index 3125ae6..b4f9c53 100644
--- a/cmd/anubis/js/proof-of-work.mjs
+++ b/cmd/anubis/js/proof-of-work.mjs
@@ -1,6 +1,5 @@
-// https://dev.to/ratmd/simple-proof-of-work-in-javascript-3kgm
-
-export function process(data, difficulty = 5, threads = (navigator.hardwareConcurrency || 1)) {
+export default function process(data, difficulty = 5, threads = (navigator.hardwareConcurrency || 1)) {
+ console.debug("fast algo");
return new Promise((resolve, reject) => {
let webWorkerURL = URL.createObjectURL(new Blob([
'(', processTask(), ')()'
diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go
index f2f7255..b92b591 100644
--- a/cmd/anubis/main.go
+++ b/cmd/anubis/main.go
@@ -44,7 +44,7 @@ import (
var (
bind = flag.String("bind", ":8923", "network address to bind HTTP to")
bindNetwork = flag.String("bind-network", "tcp", "network family to bind HTTP to, e.g. unix, tcp")
- challengeDifficulty = flag.Int("difficulty", 4, "difficulty of the challenge")
+ challengeDifficulty = flag.Int("difficulty", defaultDifficulty, "difficulty of the challenge")
metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to")
metricsBindNetwork = flag.String("metrics-bind-network", "tcp", "network family for the metrics server to bind to")
socketMode = flag.String("socket-mode", "0770", "socket mode (permissions) for unix domain sockets.")
@@ -85,8 +85,9 @@ var (
)
const (
- cookieName = "within.website-x-cmd-anubis-auth"
- staticPath = "/.within.website/x/cmd/anubis/"
+ cookieName = "within.website-x-cmd-anubis-auth"
+ staticPath = "/.within.website/x/cmd/anubis/"
+ defaultDifficulty = 4
)
//go:generate go tool github.com/a-h/templ/cmd/templ generate
@@ -261,7 +262,7 @@ func sha256sum(text string) (string, error) {
return hex.EncodeToString(hash.Sum(nil)), nil
}
-func (s *Server) challengeFor(r *http.Request) string {
+func (s *Server) challengeFor(r *http.Request, difficulty int) string {
fp := sha256.Sum256(s.priv.Seed())
data := fmt.Sprintf(
@@ -271,7 +272,7 @@ func (s *Server) challengeFor(r *http.Request) string {
r.UserAgent(),
time.Now().UTC().Round(24*7*time.Hour).Format(time.RFC3339),
fp,
- *challengeDifficulty,
+ difficulty,
)
result, _ := sha256sum(data)
return result
@@ -324,7 +325,7 @@ func New(target, policyFname string) (*Server, error) {
defer fin.Close()
- policy, err := parseConfig(fin, policyFname)
+ policy, err := parseConfig(fin, policyFname, *challengeDifficulty)
if err != nil {
return nil, err // parseConfig sets a fancy error for us
}
@@ -485,7 +486,9 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request) {
return
}
- if claims["challenge"] != s.challengeFor(r) {
+ challenge := s.challengeFor(r, rule.Challenge.Difficulty)
+
+ if claims["challenge"] != challenge {
lg.Debug("invalid challenge", "path", r.URL.Path)
clearCookie(w)
s.renderIndex(w, r)
@@ -498,7 +501,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request) {
nonce = int(v)
}
- calcString := fmt.Sprintf("%s%d", s.challengeFor(r), nonce)
+ calcString := fmt.Sprintf("%s%d", challenge, nonce)
calculated, err := sha256sum(calcString)
if err != nil {
lg.Error("failed to calculate sha256sum", "path", r.URL.Path, "err", err)
@@ -527,24 +530,32 @@ func (s *Server) renderIndex(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) makeChallenge(w http.ResponseWriter, r *http.Request) {
- challenge := s.challengeFor(r)
- difficulty := *challengeDifficulty
+ cr, rule := s.check(r)
+ challenge := s.challengeFor(r, rule.Challenge.Difficulty)
lg := slog.With("user_agent", r.UserAgent(), "accept_language", r.Header.Get("Accept-Language"), "priority", r.Header.Get("Priority"), "x-forwarded-for", r.Header.Get("X-Forwarded-For"), "x-real-ip", r.Header.Get("X-Real-Ip"))
json.NewEncoder(w).Encode(struct {
- Challenge string `json:"challenge"`
- Difficulty int `json:"difficulty"`
+ Challenge string `json:"challenge"`
+ Rules *config.ChallengeRules `json:"rules"`
}{
- Challenge: challenge,
- Difficulty: difficulty,
+ Challenge: challenge,
+ Rules: rule.Challenge,
})
- lg.Debug("made challenge", "challenge", challenge, "difficulty", difficulty)
+ lg.Debug("made challenge", "challenge", challenge, "rules", rule.Challenge, "cr", cr)
challengesIssued.Inc()
}
func (s *Server) passChallenge(w http.ResponseWriter, r *http.Request) {
- lg := slog.With("user_agent", r.UserAgent(), "accept_language", r.Header.Get("Accept-Language"), "priority", r.Header.Get("Priority"), "x-forwarded-for", r.Header.Get("X-Forwarded-For"), "x-real-ip", r.Header.Get("X-Real-Ip"))
+ cr, rule := s.check(r)
+ lg := slog.With(
+ "user_agent", r.UserAgent(),
+ "accept_language", r.Header.Get("Accept-Language"),
+ "priority", r.Header.Get("Priority"),
+ "x-forwarded-for", r.Header.Get("X-Forwarded-For"),
+ "x-real-ip", r.Header.Get("X-Real-Ip"),
+ "cr", cr,
+ )
nonceStr := r.FormValue("nonce")
if nonceStr == "" {
@@ -576,7 +587,7 @@ func (s *Server) passChallenge(w http.ResponseWriter, r *http.Request) {
response := r.FormValue("response")
redir := r.FormValue("redir")
- challenge := s.challengeFor(r)
+ challenge := s.challengeFor(r, rule.Challenge.Difficulty)
nonce, err := strconv.Atoi(nonceStr)
if err != nil {
diff --git a/cmd/anubis/policy.go b/cmd/anubis/policy.go
index f636349..a637f09 100644
--- a/cmd/anubis/policy.go
+++ b/cmd/anubis/policy.go
@@ -32,7 +32,8 @@ type Bot struct {
Name string
UserAgent *regexp.Regexp
Path *regexp.Regexp
- Action config.Rule `json:"action"`
+ Action config.Rule
+ Challenge *config.ChallengeRules
}
func (b Bot) Hash() (string, error) {
@@ -48,7 +49,7 @@ func (b Bot) Hash() (string, error) {
return sha256sum(fmt.Sprintf("%s::%s::%s", b.Name, pathRex, userAgentRex))
}
-func parseConfig(fin io.Reader, fname string) (*ParsedConfig, error) {
+func parseConfig(fin io.Reader, fname string, defaultDifficulty int) (*ParsedConfig, error) {
var c config.Config
if err := json.NewDecoder(fin).Decode(&c); err != nil {
return nil, fmt.Errorf("can't parse policy config JSON %s: %w", fname, err)
@@ -96,6 +97,19 @@ func parseConfig(fin io.Reader, fname string) (*ParsedConfig, error) {
}
}
+ if b.Challenge == nil {
+ parsedBot.Challenge = &config.ChallengeRules{
+ Difficulty: defaultDifficulty,
+ ReportAs: defaultDifficulty,
+ Algorithm: config.AlgorithmFast,
+ }
+ } else {
+ parsedBot.Challenge = b.Challenge
+ if parsedBot.Challenge.Algorithm == config.AlgorithmUnknown {
+ parsedBot.Challenge.Algorithm = config.AlgorithmFast
+ }
+ }
+
result.Bots = append(result.Bots, parsedBot)
}
@@ -142,5 +156,11 @@ func (s *Server) check(r *http.Request) (CheckResult, *Bot) {
}
}
- return cr("default/allow", config.RuleAllow), nil
+ return cr("default/allow", config.RuleAllow), &Bot{
+ Challenge: &config.ChallengeRules{
+ Difficulty: defaultDifficulty,
+ ReportAs: defaultDifficulty,
+ Algorithm: config.AlgorithmFast,
+ },
+ }
}
diff --git a/cmd/anubis/policy_test.go b/cmd/anubis/policy_test.go
index 9a5699e..cf8ef2e 100644
--- a/cmd/anubis/policy_test.go
+++ b/cmd/anubis/policy_test.go
@@ -13,7 +13,7 @@ func TestDefaultPolicyMustParse(t *testing.T) {
}
defer fin.Close()
- if _, err := parseConfig(fin, "botPolicies.json"); err != nil {
+ if _, err := parseConfig(fin, "botPolicies.json", defaultDifficulty); err != nil {
t.Fatalf("can't parse config: %v", err)
}
}
@@ -33,7 +33,7 @@ func TestGoodConfigs(t *testing.T) {
}
defer fin.Close()
- if _, err := parseConfig(fin, fin.Name()); err != nil {
+ if _, err := parseConfig(fin, fin.Name(), defaultDifficulty); err != nil {
t.Fatal(err)
}
})
@@ -55,7 +55,7 @@ func TestBadConfigs(t *testing.T) {
}
defer fin.Close()
- if _, err := parseConfig(fin, fin.Name()); err == nil {
+ if _, err := parseConfig(fin, fin.Name(), defaultDifficulty); err == nil {
t.Fatal(err)
} else {
t.Log(err)
diff --git a/cmd/anubis/static/js/main.mjs b/cmd/anubis/static/js/main.mjs
index d1e1b86..a934289 100644
--- a/cmd/anubis/static/js/main.mjs
+++ b/cmd/anubis/static/js/main.mjs
@@ -1,2 +1,2 @@
-(()=>{function m(n,s=5,e=(navigator.hardwareConcurrency||1)){return new Promise((t,l)=>{let a=URL.createObjectURL(new Blob(["(",g(),")()"],{type:"application/javascript"})),r=[];for(let d=0;d<e;d++){let i=new Worker(a);i.onmessage=c=>{r.forEach(u=>u.terminate()),i.terminate(),t(c.data)},i.onerror=c=>{i.terminate(),l()},i.postMessage({data:n,difficulty:s,nonce:d,threads:e}),r.push(i)}URL.revokeObjectURL(a)})}function g(){return function(){let n=e=>{let t=new TextEncoder().encode(e);return crypto.subtle.digest("SHA-256",t.buffer)};function s(e){return Array.from(e).map(t=>t.toString(16).padStart(2,"0")).join("")}addEventListener("message",async e=>{let t=e.data.data,l=e.data.difficulty,a,r=e.data.nonce,d=e.data.threads;for(;;){let i=await n(t+r),c=new Uint8Array(i),u=!0;for(let o=0;o<l;o++){let f=Math.floor(o/2),p=o%2;if((c[f]>>(p===0?4:0)&15)!==0){u=!1;break}}if(u){a=s(c),console.log(a);break}r+=d}postMessage({hash:a,data:t,difficulty:l,nonce:r})})}.toString()}var w=(n="",s={})=>{let e=new URL(n,window.location.href);return Object.entries(s).forEach(t=>{let[l,a]=t;e.searchParams.set(l,a)}),e.toString()},h=(n,s)=>w(`/.within.website/x/cmd/anubis/static/img/${n}.webp`,{cacheBuster:s});(async()=>{let n=document.getElementById("status"),s=document.getElementById("image"),e=document.getElementById("title"),t=document.getElementById("spinner"),l=JSON.parse(document.getElementById("anubis_version").textContent);n.innerHTML="Calculating...";let{challenge:a,difficulty:r}=await fetch("/.within.website/x/cmd/anubis/api/make-challenge",{method:"POST"}).then(o=>{if(!o.ok)throw new Error("Failed to fetch config");return o.json()}).catch(o=>{throw e.innerHTML="Oh no!",n.innerHTML=`Failed to fetch config: ${o.message}`,s.src=h("sad"),t.innerHTML="",t.style.display="none",o});n.innerHTML=`Calculating...<br/>Difficulty: ${r}`;let d=Date.now(),{hash:i,nonce:c}=await m(a,r),u=Date.now();console.log({hash:i,nonce:c}),e.innerHTML="Success!",n.innerHTML=`Done! Took ${u-d}ms, ${c} iterations`,s.src=h("happy",l),t.innerHTML="",t.style.display="none",setTimeout(()=>{let o=window.location.href;window.location.href=w("/.within.website/x/cmd/anubis/api/pass-challenge",{response:i,nonce:c,redir:o,elapsedTime:u-d})},250)})();})();
+(()=>{function p(r,n=5,t=navigator.hardwareConcurrency||1){return console.debug("fast algo"),new Promise((e,o)=>{let s=URL.createObjectURL(new Blob(["(",y(),")()"],{type:"application/javascript"})),a=[];for(let i=0;i<t;i++){let c=new Worker(s);c.onmessage=d=>{a.forEach(u=>u.terminate()),c.terminate(),e(d.data)},c.onerror=d=>{c.terminate(),o()},c.postMessage({data:r,difficulty:n,nonce:i,threads:t}),a.push(c)}URL.revokeObjectURL(s)})}function y(){return function(){let r=t=>{let e=new TextEncoder().encode(t);return crypto.subtle.digest("SHA-256",e.buffer)};function n(t){return Array.from(t).map(e=>e.toString(16).padStart(2,"0")).join("")}addEventListener("message",async t=>{let e=t.data.data,o=t.data.difficulty,s,a=t.data.nonce,i=t.data.threads;for(;;){let c=await r(e+a),d=new Uint8Array(c),u=!0;for(let m=0;m<o;m++){let l=Math.floor(m/2),g=m%2;if((d[l]>>(g===0?4:0)&15)!==0){u=!1;break}}if(u){s=n(d),console.log(s);break}a+=i}postMessage({hash:s,data:e,difficulty:o,nonce:a})})}.toString()}function f(r,n=5,t=1){return console.debug("slow algo"),new Promise((e,o)=>{let s=URL.createObjectURL(new Blob(["(",b(),")()"],{type:"application/javascript"})),a=new Worker(s);a.onmessage=i=>{a.terminate(),e(i.data)},a.onerror=i=>{a.terminate(),o()},a.postMessage({data:r,difficulty:n}),URL.revokeObjectURL(s)})}function b(){return function(){let r=n=>{let t=new TextEncoder().encode(n);return crypto.subtle.digest("SHA-256",t.buffer).then(e=>Array.from(new Uint8Array(e)).map(o=>o.toString(16).padStart(2,"0")).join(""))};addEventListener("message",async n=>{let t=n.data.data,e=n.data.difficulty,o,s=0;do o=await r(t+s++);while(o.substring(0,e)!==Array(e+1).join("0"));s-=1,postMessage({hash:o,data:t,difficulty:e,nonce:s})})}.toString()}var L={fast:p,slow:f},w=(r="",n={})=>{let t=new URL(r,window.location.href);return Object.entries(n).forEach(e=>{let[o,s]=e;t.searchParams.set(o,s)}),t.toString()},h=(r,n)=>w(`/.within.website/x/cmd/anubis/static/img/${r}.webp`,{cacheBuster:n});(async()=>{let r=document.getElementById("status"),n=document.getElementById("image"),t=document.getElementById("title"),e=document.getElementById("spinner"),o=JSON.parse(document.getElementById("anubis_version").textContent);r.innerHTML="Calculating...";let{challenge:s,rules:a}=await fetch("/.within.website/x/cmd/anubis/api/make-challenge",{method:"POST"}).then(l=>{if(!l.ok)throw new Error("Failed to fetch config");return l.json()}).catch(l=>{throw t.innerHTML="Oh no!",r.innerHTML=`Failed to fetch config: ${l.message}`,n.src=h("sad",o),e.innerHTML="",e.style.display="none",l}),i=L[a.algorithm];if(!i){t.innerHTML="Oh no!",r.innerHTML="Failed to resolve check algorithm. You may want to reload the page.",n.src=h("sad",o),e.innerHTML="",e.style.display="none";return}r.innerHTML=`Calculating...<br/>Difficulty: ${a.report_as}`;let c=Date.now(),{hash:d,nonce:u}=await i(s,a.difficulty),m=Date.now();console.log({hash:d,nonce:u}),t.innerHTML="Success!",r.innerHTML=`Done! Took ${m-c}ms, ${u} iterations`,n.src=h("happy",o),e.innerHTML="",e.style.display="none",setTimeout(()=>{let l=window.location.href;window.location.href=w("/.within.website/x/cmd/anubis/api/pass-challenge",{response:d,nonce:u,redir:l,elapsedTime:m-c})},250)})();})();
//# sourceMappingURL=main.mjs.map
diff --git a/cmd/anubis/static/js/main.mjs.br b/cmd/anubis/static/js/main.mjs.br
index a7b35e9..6bfd987 100644
--- a/cmd/anubis/static/js/main.mjs.br
+++ b/cmd/anubis/static/js/main.mjs.br
Binary files differ
diff --git a/cmd/anubis/static/js/main.mjs.gz b/cmd/anubis/static/js/main.mjs.gz
index e0b648c..0e8d735 100644
--- a/cmd/anubis/static/js/main.mjs.gz
+++ b/cmd/anubis/static/js/main.mjs.gz
Binary files differ
diff --git a/cmd/anubis/static/js/main.mjs.map b/cmd/anubis/static/js/main.mjs.map
index 3a0d564..7cad28b 100644
--- a/cmd/anubis/static/js/main.mjs.map
+++ b/cmd/anubis/static/js/main.mjs.map
@@ -1,7 +1,7 @@
{
"version": 3,
- "sources": ["../../js/proof-of-work.mjs", "../../js/main.mjs"],
- "sourcesContent": ["// https://dev.to/ratmd/simple-proof-of-work-in-javascript-3kgm\n\nexport function process(data, difficulty = 5, threads = navigator.hardwareConcurrency) {\n return new Promise((resolve, reject) => {\n let webWorkerURL = URL.createObjectURL(new Blob([\n '(', processTask(), ')()'\n ], { type: 'application/javascript' }));\n\n const workers = [];\n\n for (let i = 0; i < threads; i++) {\n let worker = new Worker(webWorkerURL);\n\n worker.onmessage = (event) => {\n workers.forEach(worker => worker.terminate());\n worker.terminate();\n resolve(event.data);\n };\n\n worker.onerror = (event) => {\n worker.terminate();\n reject();\n };\n\n worker.postMessage({\n data,\n difficulty,\n nonce: i,\n threads,\n });\n\n workers.push(worker);\n }\n\n URL.revokeObjectURL(webWorkerURL);\n });\n}\n\nfunction processTask() {\n return function () {\n const sha256 = (text) => {\n const encoded = new TextEncoder().encode(text);\n return crypto.subtle.digest(\"SHA-256\", encoded.buffer);\n };\n\n function uint8ArrayToHexString(arr) {\n return Array.from(arr)\n .map((c) => c.toString(16).padStart(2, \"0\"))\n .join(\"\");\n }\n\n addEventListener('message', async (event) => {\n let data = event.data.data;\n let difficulty = event.data.difficulty;\n let hash;\n let nonce = event.data.nonce;\n let threads = event.data.threads;\n\n while (true) {\n const currentHash = await sha256(data + nonce);\n const thisHash = new Uint8Array(currentHash);\n let valid = true;\n\n for (let j = 0; j < difficulty; j++) {\n const byteIndex = Math.floor(j / 2); // which byte we are looking at\n const nibbleIndex = j % 2; // which nibble in the byte we are looking at (0 is high, 1 is low)\n\n let nibble = (thisHash[byteIndex] >> (nibbleIndex === 0 ? 4 : 0)) & 0x0F; // Get the nibble\n\n if (nibble !== 0) {\n valid = false;\n break;\n }\n }\n\n if (valid) {\n hash = uint8ArrayToHexString(thisHash);\n console.log(hash);\n break;\n }\n\n nonce += threads;\n }\n\n postMessage({\n hash,\n data,\n difficulty,\n nonce,\n });\n });\n }.toString();\n}\n\n", "import { process } from './proof-of-work.mjs';\nimport { testVideo } from './video.mjs';\n\n// from Xeact\nconst u = (url = \"\", params = {}) => {\n let result = new URL(url, window.location.href);\n Object.entries(params).forEach((kv) => {\n let [k, v] = kv;\n result.searchParams.set(k, v);\n });\n return result.toString();\n};\n\nconst imageURL = (mood, cacheBuster) =>\n u(`/.within.website/x/cmd/anubis/static/img/${mood}.webp`, { cacheBuster });\n\n(async () => {\n const status = document.getElementById('status');\n const image = document.getElementById('image');\n const title = document.getElementById('title');\n const spinner = document.getElementById('spinner');\n const anubisVersion = JSON.parse(document.getElementById('anubis_version').textContent);\n\n // const testarea = document.getElementById('testarea');\n\n // const videoWorks = await testVideo(testarea);\n // console.log(`videoWorks: ${videoWorks}`);\n\n // if (!videoWorks) {\n // title.innerHTML = \"Oh no!\";\n // status.innerHTML = \"Checks failed. Please check your browser's settings and try again.\";\n // image.src = imageURL(\"sad\");\n // spinner.innerHTML = \"\";\n // spinner.style.display = \"none\";\n // return;\n // }\n\n status.innerHTML = 'Calculating...';\n\n const { challenge, difficulty } = await fetch(\"/.within.website/x/cmd/anubis/api/make-challenge\", { method: \"POST\" })\n .then(r => {\n if (!r.ok) {\n throw new Error(\"Failed to fetch config\");\n }\n return r.json();\n })\n .catch(err => {\n title.innerHTML = \"Oh no!\";\n status.innerHTML = `Failed to fetch config: ${err.message}`;\n image.src = imageURL(\"sad\");\n spinner.innerHTML = \"\";\n spinner.style.display = \"none\";\n throw err;\n });\n\n status.innerHTML = `Calculating...<br/>Difficulty: ${difficulty}`;\n\n const t0 = Date.now();\n const { hash, nonce } = await process(challenge, difficulty);\n const t1 = Date.now();\n console.log({ hash, nonce });\n\n title.innerHTML = \"Success!\";\n status.innerHTML = `Done! Took ${t1 - t0}ms, ${nonce} iterations`;\n image.src = imageURL(\"happy\", anubisVersion);\n spinner.innerHTML = \"\";\n spinner.style.display = \"none\";\n\n setTimeout(() => {\n const redir = window.location.href;\n window.location.href = u(\"/.within.website/x/cmd/anubis/api/pass-challenge\", { response: hash, nonce, redir, elapsedTime: t1 - t0 });\n }, 250);\n})();"],
- "mappings": "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",
- "names": ["process", "data", "difficulty", "threads", "resolve", "reject", "webWorkerURL", "processTask", "workers", "i", "worker", "event", "sha256", "text", "encoded", "uint8ArrayToHexString", "arr", "c", "hash", "nonce", "currentHash", "thisHash", "valid", "j", "byteIndex", "nibbleIndex", "u", "url", "params", "result", "kv", "k", "v", "imageURL", "mood", "cacheBuster", "status", "image", "title", "spinner", "anubisVersion", "challenge", "difficulty", "r", "err", "t0", "hash", "nonce", "process", "t1", "redir"]
+ "sources": ["../../js/proof-of-work.mjs", "../../js/proof-of-work-slow.mjs", "../../js/main.mjs"],
+ "sourcesContent": ["export default function process(data, difficulty = 5, threads = (navigator.hardwareConcurrency || 1)) {\n console.debug(\"fast algo\");\n return new Promise((resolve, reject) => {\n let webWorkerURL = URL.createObjectURL(new Blob([\n '(', processTask(), ')()'\n ], { type: 'application/javascript' }));\n\n const workers = [];\n\n for (let i = 0; i < threads; i++) {\n let worker = new Worker(webWorkerURL);\n\n worker.onmessage = (event) => {\n workers.forEach(worker => worker.terminate());\n worker.terminate();\n resolve(event.data);\n };\n\n worker.onerror = (event) => {\n worker.terminate();\n reject();\n };\n\n worker.postMessage({\n data,\n difficulty,\n nonce: i,\n threads,\n });\n\n workers.push(worker);\n }\n\n URL.revokeObjectURL(webWorkerURL);\n });\n}\n\nfunction processTask() {\n return function () {\n const sha256 = (text) => {\n const encoded = new TextEncoder().encode(text);\n return crypto.subtle.digest(\"SHA-256\", encoded.buffer);\n };\n\n function uint8ArrayToHexString(arr) {\n return Array.from(arr)\n .map((c) => c.toString(16).padStart(2, \"0\"))\n .join(\"\");\n }\n\n addEventListener('message', async (event) => {\n let data = event.data.data;\n let difficulty = event.data.difficulty;\n let hash;\n let nonce = event.data.nonce;\n let threads = event.data.threads;\n\n while (true) {\n const currentHash = await sha256(data + nonce);\n const thisHash = new Uint8Array(currentHash);\n let valid = true;\n\n for (let j = 0; j < difficulty; j++) {\n const byteIndex = Math.floor(j / 2); // which byte we are looking at\n const nibbleIndex = j % 2; // which nibble in the byte we are looking at (0 is high, 1 is low)\n\n let nibble = (thisHash[byteIndex] >> (nibbleIndex === 0 ? 4 : 0)) & 0x0F; // Get the nibble\n\n if (nibble !== 0) {\n valid = false;\n break;\n }\n }\n\n if (valid) {\n hash = uint8ArrayToHexString(thisHash);\n console.log(hash);\n break;\n }\n\n nonce += threads;\n }\n\n postMessage({\n hash,\n data,\n difficulty,\n nonce,\n });\n });\n }.toString();\n}\n\n", "// https://dev.to/ratmd/simple-proof-of-work-in-javascript-3kgm\n\nexport default function process(data, difficulty = 5, _threads = 1) {\n console.debug(\"slow algo\");\n return new Promise((resolve, reject) => {\n let webWorkerURL = URL.createObjectURL(new Blob([\n '(', processTask(), ')()'\n ], { type: 'application/javascript' }));\n\n let worker = new Worker(webWorkerURL);\n\n worker.onmessage = (event) => {\n worker.terminate();\n resolve(event.data);\n };\n\n worker.onerror = (event) => {\n worker.terminate();\n reject();\n };\n\n worker.postMessage({\n data,\n difficulty\n });\n\n URL.revokeObjectURL(webWorkerURL);\n });\n}\n\nfunction processTask() {\n return function () {\n const sha256 = (text) => {\n const encoded = new TextEncoder().encode(text);\n return crypto.subtle.digest(\"SHA-256\", encoded.buffer)\n .then((result) =>\n Array.from(new Uint8Array(result))\n .map((c) => c.toString(16).padStart(2, \"0\"))\n .join(