mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-05-09 08:32:51 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0e8a9044d | |||
| 11c4adc6b4 | |||
| edbfd180b8 | |||
| efde4f0dc7 | |||
| 24857f430f | |||
| e0ece7d333 | |||
| 3eab1d873d | |||
| c7b31d0ca9 | |||
| 3154ff5004 | |||
| 5186d7d3ad | |||
| c6d968874d | |||
| 14a8d0c75e | |||
| e9969ba22a | |||
| 7db2c9ebb5 |
@@ -29,3 +29,8 @@ Prolocation
|
|||||||
Necron
|
Necron
|
||||||
Stargate
|
Stargate
|
||||||
FFXIV
|
FFXIV
|
||||||
|
uvensys
|
||||||
|
de
|
||||||
|
resourced
|
||||||
|
envoyproxy
|
||||||
|
unipromos
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ oci
|
|||||||
OCOB
|
OCOB
|
||||||
ogtag
|
ogtag
|
||||||
oklch
|
oklch
|
||||||
|
oldstable
|
||||||
omgili
|
omgili
|
||||||
omgilibot
|
omgilibot
|
||||||
openai
|
openai
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
go_tests:
|
go_tests:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go_version:
|
||||||
|
- oldstable
|
||||||
|
- stable
|
||||||
#runs-on: alrest-techarohq
|
#runs-on: alrest-techarohq
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
@@ -26,10 +31,11 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "latest"
|
||||||
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
|
||||||
|
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: ${{ matrix.go_version }}
|
||||||
|
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ Anubis is brought to you by sponsors and donors like:
|
|||||||
|
|
||||||
### Gold Tier
|
### Gold Tier
|
||||||
|
|
||||||
|
<a href="https://www.unipromos.com/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="./docs/static/img/sponsors/unipromos.webp" alt="Unipromos" height="64" />
|
||||||
|
</a>
|
||||||
|
<a href="https://uvensys.de/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="./docs/static/img/sponsors/uvensys.webp" alt="Uvensys" height="64">
|
||||||
|
</a>
|
||||||
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
<img src="./docs/static/img/sponsors/distrust-logo.webp" alt="Distrust" height="64">
|
<img src="./docs/static/img/sponsors/distrust-logo.webp" alt="Distrust" height="64">
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
+2
-2
@@ -418,8 +418,8 @@ func main() {
|
|||||||
|
|
||||||
var redirectDomainsList []string
|
var redirectDomainsList []string
|
||||||
if *redirectDomains != "" {
|
if *redirectDomains != "" {
|
||||||
domains := strings.Split(*redirectDomains, ",")
|
domains := strings.SplitSeq(*redirectDomains, ",")
|
||||||
for _, domain := range domains {
|
for domain := range domains {
|
||||||
_, err = url.Parse(domain)
|
_, err = url.Parse(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("cannot parse redirect-domain %q: %s", domain, err.Error())
|
log.Fatalf("cannot parse redirect-domain %q: %s", domain, err.Error())
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis/lib/config"
|
"github.com/TecharoHQ/anubis/lib/config"
|
||||||
@@ -210,11 +211,8 @@ func parseRobotsTxt(input io.Reader) ([]RobotsRule, error) {
|
|||||||
|
|
||||||
// Mark blacklisted user agents (those with "Disallow: /")
|
// Mark blacklisted user agents (those with "Disallow: /")
|
||||||
for i := range rules {
|
for i := range rules {
|
||||||
for _, disallow := range rules[i].Disallows {
|
if slices.Contains(rules[i].Disallows, "/") {
|
||||||
if disallow == "/" {
|
|
||||||
rules[i].IsBlacklist = true
|
rules[i].IsBlacklist = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,8 +158,8 @@ func TestDataFileConversion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(*outputFormat) == "yaml" {
|
if strings.ToLower(*outputFormat) == "yaml" {
|
||||||
var actualData []interface{}
|
var actualData []any
|
||||||
var expectedData []interface{}
|
var expectedData []any
|
||||||
|
|
||||||
err = yaml.Unmarshal(actualOutput, &actualData)
|
err = yaml.Unmarshal(actualOutput, &actualData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -178,8 +178,8 @@ func TestDataFileConversion(t *testing.T) {
|
|||||||
t.Errorf("Output mismatch for %s\nExpected:\n%s\n\nActual:\n%s", tc.name, expectedStr, actualStr)
|
t.Errorf("Output mismatch for %s\nExpected:\n%s\n\nActual:\n%s", tc.name, expectedStr, actualStr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var actualData []interface{}
|
var actualData []any
|
||||||
var expectedData []interface{}
|
var expectedData []any
|
||||||
|
|
||||||
err = json.Unmarshal(actualOutput, &actualData)
|
err = json.Unmarshal(actualOutput, &actualData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -419,6 +419,6 @@ Disallow: /`
|
|||||||
|
|
||||||
// compareData performs a deep comparison of two data structures,
|
// compareData performs a deep comparison of two data structures,
|
||||||
// ignoring differences that are semantically equivalent in YAML/JSON
|
// ignoring differences that are semantically equivalent in YAML/JSON
|
||||||
func compareData(actual, expected interface{}) bool {
|
func compareData(actual, expected any) bool {
|
||||||
return reflect.DeepEqual(actual, expected)
|
return reflect.DeepEqual(actual, expected)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,5 @@
|
|||||||
- import: (data)/crawlers/marginalia.yaml
|
- import: (data)/crawlers/marginalia.yaml
|
||||||
- import: (data)/crawlers/mojeekbot.yaml
|
- import: (data)/crawlers/mojeekbot.yaml
|
||||||
- import: (data)/crawlers/commoncrawl.yaml
|
- import: (data)/crawlers/commoncrawl.yaml
|
||||||
|
- import: (data)/crawlers/wikimedia-citoid.yaml
|
||||||
- import: (data)/crawlers/yandexbot.yaml
|
- import: (data)/crawlers/yandexbot.yaml
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Wikimedia Foundation citation services
|
||||||
|
# https://www.mediawiki.org/wiki/Citoid
|
||||||
|
|
||||||
|
- name: wikimedia-citoid
|
||||||
|
user_agent_regex: "Citoid/WMF"
|
||||||
|
action: ALLOW
|
||||||
|
remote_addresses: [
|
||||||
|
"208.80.152.0/22",
|
||||||
|
"2620:0:860::/46",
|
||||||
|
]
|
||||||
|
|
||||||
|
- name: wikimedia-zotero-translation-server
|
||||||
|
user_agent_regex: "ZoteroTranslationServer/WMF"
|
||||||
|
action: ALLOW
|
||||||
|
remote_addresses: [
|
||||||
|
"208.80.152.0/22",
|
||||||
|
"2620:0:860::/46",
|
||||||
|
]
|
||||||
@@ -11,10 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
- fix: prevent nil pointer panic in challenge validation when threshold rules match during PassChallenge (#1463)
|
||||||
- Instruct reverse proxies to not cache error pages.
|
- Instruct reverse proxies to not cache error pages.
|
||||||
- Fixed mixed tab/space indentation in Caddy documentation code block
|
- Fixed mixed tab/space indentation in Caddy documentation code block
|
||||||
|
|
||||||
<!-- This changes the project to: -->
|
<!-- This changes the project to: -->
|
||||||
|
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)).
|
||||||
|
|
||||||
## v1.25.0: Necron
|
## v1.25.0: Necron
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ This simply enables SSL offloading, sets some useful and required headers and ro
|
|||||||
|
|
||||||
Due to the fact that HAProxy can decode JWT, we are able to verify the Anubis token directly in HAProxy and route the traffic to the specific backends ourselves.
|
Due to the fact that HAProxy can decode JWT, we are able to verify the Anubis token directly in HAProxy and route the traffic to the specific backends ourselves.
|
||||||
|
|
||||||
|
Mind that rule logic to allow Git HTTP and other legit bot traffic to bypass is delegated from Anubis to HAProxy then. If required, you should implement any whitelisting in HAProxy using `acl_anubis_ignore` yourself.
|
||||||
|
|
||||||
In this example are three applications behind one HAProxy frontend. Only App1 and App2 are secured via Anubis; App3 is open for everyone. The path `/excluded/path` can also be accessed by anyone.
|
In this example are three applications behind one HAProxy frontend. Only App1 and App2 are secured via Anubis; App3 is open for everyone. The path `/excluded/path` can also be accessed by anyone.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|||||||
@@ -130,3 +130,52 @@ Then point your Ingress to the Anubis port:
|
|||||||
# diff-add
|
# diff-add
|
||||||
name: anubis
|
name: anubis
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Envoy Gateway
|
||||||
|
|
||||||
|
If you are using envoy-gateway, the `X-Real-Ip` header is not set by default, but Anubis does require it. You can resolve this by adding the header, either on the specific `HTTPRoute` where Anubis is listening, or on the `ClientTrafficPolicy` to apply it to any number of Gateways:
|
||||||
|
|
||||||
|
HTTPRoute:
|
||||||
|
```yaml
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: app-route
|
||||||
|
spec:
|
||||||
|
hostnames: ["app.domain.tld"]
|
||||||
|
parentRefs:
|
||||||
|
- name: envoy-external
|
||||||
|
namespace: network
|
||||||
|
sectionName: https
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- identifier: *app
|
||||||
|
port: anubis
|
||||||
|
filters:
|
||||||
|
- type: RequestHeaderModifier
|
||||||
|
requestHeaderModifier:
|
||||||
|
set:
|
||||||
|
- name: X-Real-Ip
|
||||||
|
value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
|
||||||
|
```
|
||||||
|
|
||||||
|
Applying to any number of Gateways:
|
||||||
|
```yaml
|
||||||
|
apiVersion: gateway.envoyproxy.io/v1alpha1
|
||||||
|
kind: ClientTrafficPolicy
|
||||||
|
metadata:
|
||||||
|
name: envoy
|
||||||
|
spec:
|
||||||
|
headers:
|
||||||
|
earlyRequestHeaders:
|
||||||
|
set:
|
||||||
|
- name: X-Real-Ip
|
||||||
|
value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
|
||||||
|
clientIPDetection:
|
||||||
|
xForwardedFor:
|
||||||
|
trustedCIDRs:
|
||||||
|
- 10.96.0.0/16 # Cluster pod CIDR
|
||||||
|
targetSelectors: # These will apply to all Gateways
|
||||||
|
- group: gateway.networking.k8s.io
|
||||||
|
kind: Gateway
|
||||||
|
```
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ Anubis is brought to you by sponsors and donors like:
|
|||||||
|
|
||||||
### Gold Tier
|
### Gold Tier
|
||||||
|
|
||||||
|
<a href="https://www.unipromos.com/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="/img/sponsors/unipromos.webp" alt="Uvensys" height="64" />
|
||||||
|
</a>
|
||||||
|
<a href="https://uvensys.de/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="/img/sponsors/uvensys.webp" alt="Uvensys" height="64" />
|
||||||
|
</a>
|
||||||
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
<img src="/img/sponsors/distrust-logo.webp" alt="Distrust" height="64" />
|
<img src="/img/sponsors/distrust-logo.webp" alt="Distrust" height="64" />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -22,3 +22,13 @@ If you use a browser extension such as [JShelter](https://jshelter.org/), you wi
|
|||||||
## Does Anubis mine Bitcoin?
|
## Does Anubis mine Bitcoin?
|
||||||
|
|
||||||
No. Anubis does not mine Bitcoin or any other cryptocurrency.
|
No. Anubis does not mine Bitcoin or any other cryptocurrency.
|
||||||
|
|
||||||
|
## I disabled Just-in-time compilation in my browser. Why is Anubis slow?
|
||||||
|
|
||||||
|
Anubis proof-of-work checks run an open source JavaScript program in your browser. These checks do a lot of complicated math and aim to be done quickly, so the execution speed depends on [Just-in-time (JIT) compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation). JIT compiles JavaScript from the Internet into native machine code at runtime. The code produced by the JIT engine is almost as good as if it was written in a native programming language and compiled for your computer in particular. Without JIT, all JavaScript programs on every website you visit run through a slow interpreter.
|
||||||
|
|
||||||
|
This interpreter is much slower than native code because it has to translate each low level JavaScript operation into many dozens of calls to execute. This means that using the interpreter incurs a massive performance hit by its very nature; it takes longer to add numbers than if the CPU just added the numbers directly.
|
||||||
|
|
||||||
|
Some users choose to disable JIT as a hardening measure against theoretical browser exploits. This is a reasonable choice if you face targeted attacks from well-resourced adversaries (such as nation-state actors), but it comes with real performance costs.
|
||||||
|
|
||||||
|
If you've disabled JIT and find Anubis checks slow, re-enabling JIT is the fix. There is no way for Anubis to work around this on our end.
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
@@ -36,7 +36,7 @@ func Glob(pattern, subj string) bool {
|
|||||||
end := len(parts) - 1
|
end := len(parts) - 1
|
||||||
|
|
||||||
// Go over the leading parts and ensure they match.
|
// Go over the leading parts and ensure they match.
|
||||||
for i := 0; i < end; i++ {
|
for i := range end {
|
||||||
idx := strings.Index(subj, parts[i])
|
idx := strings.Index(subj, parts[i])
|
||||||
|
|
||||||
switch i {
|
switch i {
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func TestHashCollisions(t *testing.T) {
|
|||||||
for _, prefix := range prefixes {
|
for _, prefix := range prefixes {
|
||||||
for _, suffix := range suffixes {
|
for _, suffix := range suffixes {
|
||||||
for _, variation := range variations {
|
for _, variation := range variations {
|
||||||
for i := 0; i < 100; i++ {
|
for i := range 100 {
|
||||||
input := fmt.Sprintf("%s%s%s-%d", prefix, suffix, variation, i)
|
input := fmt.Sprintf("%s%s%s-%d", prefix, suffix, variation, i)
|
||||||
hash := XXHash64sum(input)
|
hash := XXHash64sum(input)
|
||||||
if existing, exists := xxhashHashes[hash]; exists {
|
if existing, exists := xxhashHashes[hash]; exists {
|
||||||
@@ -211,7 +211,7 @@ func TestHashCollisions(t *testing.T) {
|
|||||||
|
|
||||||
seqCount := 0
|
seqCount := 0
|
||||||
for _, pattern := range patterns {
|
for _, pattern := range patterns {
|
||||||
for i := 0; i < 10000; i++ {
|
for i := range 10000 {
|
||||||
input := fmt.Sprintf(pattern, i)
|
input := fmt.Sprintf(pattern, i)
|
||||||
hash := XXHash64sum(input)
|
hash := XXHash64sum(input)
|
||||||
if existing, exists := xxhashHashes[hash]; exists {
|
if existing, exists := xxhashHashes[hash]; exists {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func (i *Impl) makeAffirmations() []string {
|
|||||||
count := rand.IntN(5) + 1
|
count := rand.IntN(5) + 1
|
||||||
|
|
||||||
var result []string
|
var result []string
|
||||||
for j := 0; j < count; j++ {
|
for range count {
|
||||||
result = append(result, i.affirmation.Spin())
|
result = append(result, i.affirmation.Spin())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ func (i *Impl) makeSpins() []string {
|
|||||||
count := rand.IntN(5) + 1
|
count := rand.IntN(5) + 1
|
||||||
|
|
||||||
var result []string
|
var result []string
|
||||||
for j := 0; j < count; j++ {
|
for range count {
|
||||||
result = append(result, i.body.Spin())
|
result = append(result, i.body.Spin())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ func (lo *ListOr[T]) UnmarshalJSON(data []byte) error {
|
|||||||
|
|
||||||
// Check if first non-whitespace character is '['
|
// Check if first non-whitespace character is '['
|
||||||
firstChar := data[0]
|
firstChar := data[0]
|
||||||
for i := 0; i < len(data); i++ {
|
for i := range data {
|
||||||
if data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
if data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||||
firstChar = data[i]
|
firstChar = data[i]
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func TestMemoryUsage(t *testing.T) {
|
|||||||
|
|
||||||
// Run getTarget many times
|
// Run getTarget many times
|
||||||
u, _ := url.Parse("/path/to/resource?query=1&foo=bar&baz=qux")
|
u, _ := url.Parse("/path/to/resource?query=1&foo=bar&baz=qux")
|
||||||
for i := 0; i < 10000; i++ {
|
for range 10000 {
|
||||||
_ = cache.getTarget(u)
|
_ = cache.getTarget(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ func TestMemoryUsage(t *testing.T) {
|
|||||||
runtime.GC()
|
runtime.GC()
|
||||||
runtime.ReadMemStats(&m1)
|
runtime.ReadMemStats(&m1)
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
for range 1000 {
|
||||||
_ = cache.extractOGTags(doc)
|
_ = cache.extractOGTags(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ogtags
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -78,7 +79,7 @@ func FuzzGetTarget(f *testing.F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure no memory corruption by calling multiple times
|
// Ensure no memory corruption by calling multiple times
|
||||||
for i := 0; i < 3; i++ {
|
for range 3 {
|
||||||
result2 := cache.getTarget(u)
|
result2 := cache.getTarget(u)
|
||||||
if result != result2 {
|
if result != result2 {
|
||||||
t.Errorf("getTarget not deterministic: %q != %q", result, result2)
|
t.Errorf("getTarget not deterministic: %q != %q", result, result2)
|
||||||
@@ -148,11 +149,8 @@ func FuzzExtractOGTags(f *testing.F) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
for _, tag := range cache.approvedTags {
|
if slices.Contains(cache.approvedTags, property) {
|
||||||
if property == tag {
|
|
||||||
approved = true
|
approved = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
@@ -260,11 +258,8 @@ func FuzzExtractMetaTagInfo(f *testing.F) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
for _, tag := range cache.approvedTags {
|
if slices.Contains(cache.approvedTags, property) {
|
||||||
if property == tag {
|
|
||||||
approved = true
|
approved = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ogtags
|
package ogtags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
@@ -65,11 +66,9 @@ func (c *OGTagCache) extractMetaTagInfo(n *html.Node) (property, content string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check exact matches
|
// Check exact matches
|
||||||
for _, tag := range c.approvedTags {
|
if slices.Contains(c.approvedTags, propertyKey) {
|
||||||
if propertyKey == tag {
|
|
||||||
return propertyKey, content
|
return propertyKey, content
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return "", content
|
return "", content
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ func TestPlaywrightBrowser(t *testing.T) {
|
|||||||
|
|
||||||
var performedAction action
|
var performedAction action
|
||||||
var err error
|
var err error
|
||||||
for i := 0; i < 5; i++ {
|
for i := range 5 {
|
||||||
performedAction, err = executeTestCase(t, tc, typ, anubisURL)
|
performedAction, err = executeTestCase(t, tc, typ, anubisURL)
|
||||||
if performedAction == tc.action {
|
if performedAction == tc.action {
|
||||||
break
|
break
|
||||||
|
|||||||
+23
-4
@@ -81,11 +81,11 @@ type Server struct {
|
|||||||
func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
|
func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
|
||||||
// return ED25519 key if HS512 is not set
|
// return ED25519 key if HS512 is not set
|
||||||
if len(s.hs512Secret) == 0 {
|
if len(s.hs512Secret) == 0 {
|
||||||
return func(token *jwt.Token) (interface{}, error) {
|
return func(token *jwt.Token) (any, error) {
|
||||||
return s.ed25519Priv.Public().(ed25519.PublicKey), nil
|
return s.ed25519Priv.Public().(ed25519.PublicKey), nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return func(token *jwt.Token) (interface{}, error) {
|
return func(token *jwt.Token) (any, error) {
|
||||||
return s.hs512Secret, nil
|
return s.hs512Secret, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +106,13 @@ func (s *Server) issueChallenge(ctx context.Context, r *http.Request, lg *slog.L
|
|||||||
//return nil, errors.New("[unexpected] this codepath should be impossible, asked to issue a challenge for a non-challenge rule")
|
//return nil, errors.New("[unexpected] this codepath should be impossible, asked to issue a challenge for a non-challenge rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rule.Challenge == nil {
|
||||||
|
rule.Challenge = &config.ChallengeRules{
|
||||||
|
Difficulty: s.policy.DefaultDifficulty,
|
||||||
|
Algorithm: config.DefaultAlgorithm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
id, err := uuid.NewV7()
|
id, err := uuid.NewV7()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -491,7 +498,11 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
chall, err := s.getChallenge(r)
|
chall, err := s.getChallenge(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Error("getChallenge failed", "err", err)
|
lg.Error("getChallenge failed", "err", err)
|
||||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err))
|
algorithm := "unknown"
|
||||||
|
if rule.Challenge != nil {
|
||||||
|
algorithm = rule.Challenge.Algorithm
|
||||||
|
}
|
||||||
|
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,8 +649,16 @@ func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *p
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matches {
|
if matches {
|
||||||
|
challRules := t.Challenge
|
||||||
|
if challRules == nil {
|
||||||
|
// Non-CHALLENGE thresholds (ALLOW/DENY) don't have challenge config.
|
||||||
|
// Use an empty struct so hydrateChallengeRule can fill from stored
|
||||||
|
// challenge data during validation, rather than baking in defaults
|
||||||
|
// that could mismatch the difficulty the client actually solved for.
|
||||||
|
challRules = &config.ChallengeRules{}
|
||||||
|
}
|
||||||
return cr("threshold/"+t.Name, t.Action, weight), &policy.Bot{
|
return cr("threshold/"+t.Name, t.Action, weight), &policy.Bot{
|
||||||
Challenge: t.Challenge,
|
Challenge: challRules,
|
||||||
Rules: &checker.List{},
|
Rules: &checker.List{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -38,8 +38,8 @@ func NewTLogWriter(t *testing.T) io.Writer {
|
|||||||
|
|
||||||
// Write splits input on newlines and logs each line separately.
|
// Write splits input on newlines and logs each line separately.
|
||||||
func (w *TLogWriter) Write(p []byte) (n int, err error) {
|
func (w *TLogWriter) Write(p []byte) (n int, err error) {
|
||||||
lines := strings.Split(string(p), "\n")
|
lines := strings.SplitSeq(string(p), "\n")
|
||||||
for _, line := range lines {
|
for line := range lines {
|
||||||
if line != "" {
|
if line != "" {
|
||||||
w.t.Log(line)
|
w.t.Log(line)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ var (
|
|||||||
ErrFailed = errors.New("challenge: user failed challenge")
|
ErrFailed = errors.New("challenge: user failed challenge")
|
||||||
ErrMissingField = errors.New("challenge: missing field")
|
ErrMissingField = errors.New("challenge: missing field")
|
||||||
ErrInvalidFormat = errors.New("challenge: field has invalid format")
|
ErrInvalidFormat = errors.New("challenge: field has invalid format")
|
||||||
|
ErrInvalidInput = errors.New("challenge: input is nil or missing required fields")
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewError(verb, publicReason string, privateReason error) *Error {
|
func NewError(verb, publicReason string, privateReason error) *Error {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package challenge
|
package challenge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -50,12 +51,44 @@ type IssueInput struct {
|
|||||||
Store store.Interface
|
Store store.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (in *IssueInput) Valid() error {
|
||||||
|
if in == nil {
|
||||||
|
return fmt.Errorf("%w: IssueInput is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule == nil {
|
||||||
|
return fmt.Errorf("%w: Rule is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Rule.Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ValidateInput struct {
|
type ValidateInput struct {
|
||||||
Rule *policy.Bot
|
Rule *policy.Bot
|
||||||
Challenge *Challenge
|
Challenge *Challenge
|
||||||
Store store.Interface
|
Store store.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (in *ValidateInput) Valid() error {
|
||||||
|
if in == nil {
|
||||||
|
return fmt.Errorf("%w: ValidateInput is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule == nil {
|
||||||
|
return fmt.Errorf("%w: Rule is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Rule.Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Impl interface {
|
type Impl interface {
|
||||||
// Setup registers any additional routes with the Impl for assets or API routes.
|
// Setup registers any additional routes with the Impl for assets or API routes.
|
||||||
Setup(mux *http.ServeMux)
|
Setup(mux *http.ServeMux)
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ type Impl struct{}
|
|||||||
func (i *Impl) Setup(mux *http.ServeMux) {}
|
func (i *Impl) Setup(mux *http.ServeMux) {}
|
||||||
|
|
||||||
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't render page: %w", err)
|
return nil, fmt.Errorf("can't render page: %w", err)
|
||||||
@@ -49,6 +53,10 @@ func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return challenge.NewError("validate", "invalid input", err)
|
||||||
|
}
|
||||||
|
|
||||||
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 800 * time.Millisecond)
|
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 800 * time.Millisecond)
|
||||||
|
|
||||||
if time.Now().Before(wantTime) {
|
if time.Now().Before(wantTime) {
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ type impl struct{}
|
|||||||
func (i *impl) Setup(mux *http.ServeMux) {}
|
func (i *impl) Setup(mux *http.ServeMux) {}
|
||||||
|
|
||||||
func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't render page: %w", err)
|
return nil, fmt.Errorf("can't render page: %w", err)
|
||||||
@@ -57,6 +61,10 @@ func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return challenge.NewError("validate", "invalid input", err)
|
||||||
|
}
|
||||||
|
|
||||||
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 80 * time.Millisecond)
|
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 80 * time.Millisecond)
|
||||||
|
|
||||||
if time.Now().Before(wantTime) {
|
if time.Now().Before(wantTime) {
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
|
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return chall.NewError("validate", "invalid input", err)
|
||||||
|
}
|
||||||
|
|
||||||
rule := in.Rule
|
rule := in.Rule
|
||||||
challenge := in.Challenge.RandomData
|
challenge := in.Challenge.RandomData
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,62 @@ func mkRequest(t *testing.T, values map[string]string) *http.Request {
|
|||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestValidateNilRuleChallenge reproduces the panic from
|
||||||
|
// https://github.com/TecharoHQ/anubis/issues/1463
|
||||||
|
//
|
||||||
|
// When a threshold rule matches during PassChallenge, check() can return
|
||||||
|
// a policy.Bot with Challenge == nil. After hydrateChallengeRule fails to
|
||||||
|
// run (or the error path hits before it), Validate dereferences
|
||||||
|
// rule.Challenge.Difficulty and panics.
|
||||||
|
func TestValidateNilRuleChallenge(t *testing.T) {
|
||||||
|
i := &Impl{Algorithm: "fast"}
|
||||||
|
lg := slog.With()
|
||||||
|
|
||||||
|
// This is the exact response for SHA256("hunter" + "0") with 0 leading zeros required.
|
||||||
|
const challengeStr = "hunter"
|
||||||
|
const response = "2652bdba8fb4d2ab39ef28d8534d7694c557a4ae146c1e9237bd8d950280500e"
|
||||||
|
|
||||||
|
req := mkRequest(t, map[string]string{
|
||||||
|
"nonce": "0",
|
||||||
|
"elapsedTime": "69",
|
||||||
|
"response": response,
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
input *challenge.ValidateInput
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil-rule-challenge",
|
||||||
|
input: &challenge.ValidateInput{
|
||||||
|
Rule: &policy.Bot{},
|
||||||
|
Challenge: &challenge.Challenge{RandomData: challengeStr},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil-rule",
|
||||||
|
input: &challenge.ValidateInput{
|
||||||
|
Challenge: &challenge.Challenge{RandomData: challengeStr},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil-challenge",
|
||||||
|
input: &challenge.ValidateInput{Rule: &policy.Bot{Challenge: &config.ChallengeRules{Algorithm: "fast"}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil-input",
|
||||||
|
input: nil,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := i.Validate(req, lg, tc.input)
|
||||||
|
if !errors.Is(err, challenge.ErrInvalidInput) {
|
||||||
|
t.Fatalf("expected ErrInvalidInput, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBasic(t *testing.T) {
|
func TestBasic(t *testing.T) {
|
||||||
i := &Impl{Algorithm: "fast"}
|
i := &Impl{Algorithm: "fast"}
|
||||||
bot := &policy.Bot{
|
bot := &policy.Bot{
|
||||||
|
|||||||
@@ -228,8 +228,8 @@ type ImportStatement struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (is *ImportStatement) open() (fs.File, error) {
|
func (is *ImportStatement) open() (fs.File, error) {
|
||||||
if strings.HasPrefix(is.Import, "(data)/") {
|
if after, ok := strings.CutPrefix(is.Import, "(data)/"); ok {
|
||||||
fname := strings.TrimPrefix(is.Import, "(data)/")
|
fname := after
|
||||||
fin, err := data.BotPolicies.Open(fname)
|
fin, err := data.BotPolicies.Open(fname)
|
||||||
return fin, err
|
return fin, err
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@ func (sc StatusCodes) Valid() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fileConfig struct {
|
type fileConfig struct {
|
||||||
OpenGraph openGraphFileConfig `json:"openGraph,omitempty"`
|
OpenGraph openGraphFileConfig `json:"openGraph"`
|
||||||
Impressum *Impressum `json:"impressum,omitempty"`
|
Impressum *Impressum `json:"impressum,omitempty"`
|
||||||
Store *Store `json:"store"`
|
Store *Store `json:"store"`
|
||||||
Bots []BotOrImport `json:"bots"`
|
Bots []BotOrImport `json:"bots"`
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ func TestBotValid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, cs := range tests {
|
for _, cs := range tests {
|
||||||
cs := cs
|
|
||||||
t.Run(cs.name, func(t *testing.T) {
|
t.Run(cs.name, func(t *testing.T) {
|
||||||
err := cs.bot.Valid()
|
err := cs.bot.Valid()
|
||||||
if err == nil && cs.err == nil {
|
if err == nil && cs.err == nil {
|
||||||
@@ -216,7 +215,6 @@ func TestConfigValidKnownGood(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
fin, err := os.Open(filepath.Join("testdata", "good", st.Name()))
|
fin, err := os.Open(filepath.Join("testdata", "good", st.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -303,7 +301,6 @@ func TestConfigValidBad(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
fin, err := os.Open(filepath.Join("testdata", "bad", st.Name()))
|
fin, err := os.Open(filepath.Join("testdata", "bad", st.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ func TestBadConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "bad", st.Name()), anubis.DefaultDifficulty, "info"); err == nil {
|
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "bad", st.Name()), anubis.DefaultDifficulty, "info"); err == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -42,7 +41,6 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
t.Run("with-thoth", func(t *testing.T) {
|
t.Run("with-thoth", func(t *testing.T) {
|
||||||
ctx := thothmock.WithMockThoth(t)
|
ctx := thothmock.WithMockThoth(t)
|
||||||
|
|||||||
+12
-7
@@ -182,10 +182,7 @@ func makeCode(err error) string {
|
|||||||
enc := base64.StdEncoding.EncodeToString(buf.Bytes())
|
enc := base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
for i := 0; i < len(enc); i += width {
|
for i := 0; i < len(enc); i += width {
|
||||||
end := i + width
|
end := min(i+width, len(enc))
|
||||||
if end > len(enc) {
|
|
||||||
end = len(enc)
|
|
||||||
}
|
|
||||||
builder.WriteString(enc[i:end])
|
builder.WriteString(enc[i:end])
|
||||||
builder.WriteByte('\n')
|
builder.WriteByte('\n')
|
||||||
}
|
}
|
||||||
@@ -222,8 +219,12 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
chall, err := s.issueChallenge(r.Context(), r, lg, cr, rule)
|
chall, err := s.issueChallenge(r.Context(), r, lg, cr, rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Error("can't get challenge", "err", err)
|
lg.Error("can't get challenge", "err", err)
|
||||||
|
algorithm := "unknown"
|
||||||
|
if rule.Challenge != nil {
|
||||||
|
algorithm = rule.Challenge.Algorithm
|
||||||
|
}
|
||||||
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
||||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err))
|
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,9 +249,13 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
|
|
||||||
impl, ok := challenge.Get(chall.Method)
|
impl, ok := challenge.Get(chall.Method)
|
||||||
if !ok {
|
if !ok {
|
||||||
lg.Error("check failed", "err", "can't get algorithm", "algorithm", rule.Challenge.Algorithm)
|
algorithm := "unknown"
|
||||||
|
if rule.Challenge != nil {
|
||||||
|
algorithm = rule.Challenge.Algorithm
|
||||||
|
}
|
||||||
|
lg.Error("check failed", "err", "can't get algorithm", "algorithm", algorithm)
|
||||||
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
||||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err))
|
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"anubis_compromise": "Anubisは妥協策です。AnubisはHashcashのようなProof-of-Work方式を採用しており、これは元々メールスパムを減らすために提案された仕組みです。個人レベルでは追加の負荷は無視できる程度ですが、大規模なスクレイピングでは負荷が積み重なり、スクレイピングのコストが大幅に増加します。",
|
"anubis_compromise": "Anubisは妥協策です。AnubisはHashcashのようなProof-of-Work方式を採用しており、これは元々メールスパムを減らすために提案された仕組みです。個人レベルでは追加の負荷は無視できる程度ですが、大規模なスクレイピングでは負荷が積み重なり、スクレイピングのコストが大幅に増加します。",
|
||||||
"hack_purpose": "最終的に、これはヘッドレスブラウザのフィンガープリントと識別に時間を費やすためのプレースホルダーソリューションです(例:フォントレンダリングの方法による)。これにより、正当なユーザーにはチャレンジのプルーフオブワークページを提示する必要がなくなります。",
|
"hack_purpose": "最終的に、これはヘッドレスブラウザのフィンガープリントと識別に時間を費やすためのプレースホルダーソリューションです(例:フォントレンダリングの方法による)。これにより、正当なユーザーにはチャレンジのプルーフオブワークページを提示する必要がなくなります。",
|
||||||
"jshelter_note": "Anubisは、JShelterのようなプラグインが無効化する最新のJavaScript機能を必要とします。このドメインではJShelterや同様のプラグインを無効にしてください。",
|
"jshelter_note": "Anubisは、JShelterのようなプラグインが無効化する最新のJavaScript機能を必要とします。このドメインではJShelterや同様のプラグインを無効にしてください。",
|
||||||
"version_info": "このウェブサイトはAnubisバージョンで動作しています",
|
"version_info": "このウェブサイトはAnubisで動作しています バージョン",
|
||||||
"try_again": "再試行",
|
"try_again": "再試行",
|
||||||
"go_home": "ホームに戻る",
|
"go_home": "ホームに戻る",
|
||||||
"contact_webmaster": "もしブロックされるべきでないと思われる場合は、ウェブマスターにご連絡ください:",
|
"contact_webmaster": "もしブロックされるべきでないと思われる場合は、ウェブマスターにご連絡ください:",
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package policy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/TecharoHQ/anubis/internal/dns"
|
||||||
|
"github.com/TecharoHQ/anubis/lib/config"
|
||||||
|
"github.com/TecharoHQ/anubis/lib/store/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestDNS(t *testing.T) *dns.Dns {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ctx := t.Context()
|
||||||
|
memStore := memory.New(ctx)
|
||||||
|
cache := dns.NewDNSCache(300, 300, memStore)
|
||||||
|
return dns.New(ctx, cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCELChecker_MapIterationWrappers(t *testing.T) {
|
||||||
|
cfg := &config.ExpressionOrList{
|
||||||
|
Expression: `headers.exists(k, k == "Accept") && query.exists(k, k == "format")`,
|
||||||
|
}
|
||||||
|
|
||||||
|
checker, err := NewCELChecker(cfg, newTestDNS(t))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("creating CEL checker failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "https://example.com/?format=json", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("making request failed: %v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
got, err := checker.Check(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("checking expression failed: %v", err)
|
||||||
|
}
|
||||||
|
if !got {
|
||||||
|
t.Fatal("expected expression to evaluate true")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -103,7 +103,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{
|
result, _, err := prog.Eval(map[string]any{
|
||||||
"headers": tt.headers,
|
"headers": tt.headers,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -168,7 +168,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{
|
result, _, err := prog.Eval(map[string]any{
|
||||||
"path": tt.path,
|
"path": tt.path,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -280,7 +280,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{})
|
result, _, err := prog.Eval(map[string]any{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
@@ -359,7 +359,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{})
|
result, _, err := prog.Eval(map[string]any{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
@@ -421,7 +421,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{})
|
result, _, err := prog.Eval(map[string]any{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
@@ -514,7 +514,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{})
|
result, _, err := prog.Eval(map[string]any{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
@@ -572,7 +572,7 @@ func TestBotEnvironment(t *testing.T) {
|
|||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{})
|
result, _, err := prog.Eval(map[string]any{})
|
||||||
if tt.evalError {
|
if tt.evalError {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("%s: expected an evaluation error, but got none", tt.description)
|
t.Errorf("%s: expected an evaluation error, but got none", tt.description)
|
||||||
@@ -598,7 +598,7 @@ func TestThresholdEnvironment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
variables map[string]interface{}
|
variables map[string]any
|
||||||
name string
|
name string
|
||||||
expression string
|
expression string
|
||||||
description string
|
description string
|
||||||
@@ -608,7 +608,7 @@ func TestThresholdEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "weight-variable-available",
|
name: "weight-variable-available",
|
||||||
expression: `weight > 100`,
|
expression: `weight > 100`,
|
||||||
variables: map[string]interface{}{"weight": 150},
|
variables: map[string]any{"weight": 150},
|
||||||
expected: types.Bool(true),
|
expected: types.Bool(true),
|
||||||
description: "should support weight variable in expressions",
|
description: "should support weight variable in expressions",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -616,7 +616,7 @@ func TestThresholdEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "weight-variable-false-case",
|
name: "weight-variable-false-case",
|
||||||
expression: `weight > 100`,
|
expression: `weight > 100`,
|
||||||
variables: map[string]interface{}{"weight": 50},
|
variables: map[string]any{"weight": 50},
|
||||||
expected: types.Bool(false),
|
expected: types.Bool(false),
|
||||||
description: "should correctly evaluate weight comparisons",
|
description: "should correctly evaluate weight comparisons",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -624,7 +624,7 @@ func TestThresholdEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missingHeader-not-available",
|
name: "missingHeader-not-available",
|
||||||
expression: `missingHeader(headers, "Test")`,
|
expression: `missingHeader(headers, "Test")`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expected: types.Bool(false), // not used
|
expected: types.Bool(false), // not used
|
||||||
description: "should not have missingHeader function available",
|
description: "should not have missingHeader function available",
|
||||||
shouldCompile: false,
|
shouldCompile: false,
|
||||||
@@ -667,7 +667,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
expression string
|
expression string
|
||||||
variables map[string]interface{}
|
variables map[string]any
|
||||||
expectBool *bool // nil if we just want to test compilation or non-bool result
|
expectBool *bool // nil if we just want to test compilation or non-bool result
|
||||||
description string
|
description string
|
||||||
shouldCompile bool
|
shouldCompile bool
|
||||||
@@ -675,7 +675,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "randInt-function-compilation",
|
name: "randInt-function-compilation",
|
||||||
expression: `randInt(10)`,
|
expression: `randInt(10)`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expectBool: nil, // Don't check result, just compilation
|
expectBool: nil, // Don't check result, just compilation
|
||||||
description: "should compile randInt function",
|
description: "should compile randInt function",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -683,7 +683,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "randInt-range-validation",
|
name: "randInt-range-validation",
|
||||||
expression: `randInt(10) >= 0 && randInt(10) < 10`,
|
expression: `randInt(10) >= 0 && randInt(10) < 10`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expectBool: boolPtr(true),
|
expectBool: boolPtr(true),
|
||||||
description: "should return values in correct range",
|
description: "should return values in correct range",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -691,7 +691,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "strings-extension-size",
|
name: "strings-extension-size",
|
||||||
expression: `"hello".size() == 5`,
|
expression: `"hello".size() == 5`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expectBool: boolPtr(true),
|
expectBool: boolPtr(true),
|
||||||
description: "should support string extension functions",
|
description: "should support string extension functions",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -699,7 +699,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "strings-extension-contains",
|
name: "strings-extension-contains",
|
||||||
expression: `"hello world".contains("world")`,
|
expression: `"hello world".contains("world")`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expectBool: boolPtr(true),
|
expectBool: boolPtr(true),
|
||||||
description: "should support string contains function",
|
description: "should support string contains function",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
@@ -707,7 +707,7 @@ func TestNewEnvironment(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "strings-extension-startsWith",
|
name: "strings-extension-startsWith",
|
||||||
expression: `"hello world".startsWith("hello")`,
|
expression: `"hello world".startsWith("hello")`,
|
||||||
variables: map[string]interface{}{},
|
variables: map[string]any{},
|
||||||
expectBool: boolPtr(true),
|
expectBool: boolPtr(true),
|
||||||
description: "should support string startsWith function",
|
description: "should support string startsWith function",
|
||||||
shouldCompile: true,
|
shouldCompile: true,
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ func (h HTTPHeaders) Get(key ref.Val) ref.Val {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HTTPHeaders) Iterator() traits.Iterator { panic("TODO(Xe): implement me") }
|
func (h HTTPHeaders) Iterator() traits.Iterator {
|
||||||
|
return newMapIterator(h.Header)
|
||||||
|
}
|
||||||
|
|
||||||
func (h HTTPHeaders) IsZeroValue() bool {
|
func (h HTTPHeaders) IsZeroValue() bool {
|
||||||
return len(h.Header) == 0
|
return len(h.Header) == 0
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package expressions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"maps"
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/google/cel-go/common/types"
|
||||||
|
"github.com/google/cel-go/common/types/ref"
|
||||||
|
"github.com/google/cel-go/common/types/traits"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotImplemented = errors.New("expressions: not implemented")
|
||||||
|
|
||||||
|
type stringSliceIterator struct {
|
||||||
|
keys []string
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringSliceIterator) Value() any {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringSliceIterator) ConvertToNative(typeDesc reflect.Type) (any, error) {
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringSliceIterator) ConvertToType(typeValue ref.Type) ref.Val {
|
||||||
|
return types.NewErr("can't convert from %q to %q", types.IteratorType, typeValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringSliceIterator) Equal(other ref.Val) ref.Val {
|
||||||
|
return types.NewErr("can't compare %q to %q", types.IteratorType, other.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringSliceIterator) Type() ref.Type {
|
||||||
|
return types.IteratorType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringSliceIterator) HasNext() ref.Val {
|
||||||
|
return types.Bool(s.idx < len(s.keys))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringSliceIterator) Next() ref.Val {
|
||||||
|
if s.HasNext() != types.True {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
val := s.keys[s.idx]
|
||||||
|
s.idx++
|
||||||
|
return types.String(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMapIterator(m map[string][]string) traits.Iterator {
|
||||||
|
return &stringSliceIterator{
|
||||||
|
keys: slices.Collect(maps.Keys(m)),
|
||||||
|
idx: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package expressions
|
package expressions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -11,8 +10,6 @@ import (
|
|||||||
"github.com/google/cel-go/common/types/traits"
|
"github.com/google/cel-go/common/types/traits"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrNotImplemented = errors.New("expressions: not implemented")
|
|
||||||
|
|
||||||
// URLValues is a type wrapper to expose url.Values into CEL programs.
|
// URLValues is a type wrapper to expose url.Values into CEL programs.
|
||||||
type URLValues struct {
|
type URLValues struct {
|
||||||
url.Values
|
url.Values
|
||||||
@@ -69,7 +66,9 @@ func (u URLValues) Get(key ref.Val) ref.Val {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u URLValues) Iterator() traits.Iterator { panic("TODO(Xe): implement me") }
|
func (u URLValues) Iterator() traits.Iterator {
|
||||||
|
return newMapIterator(u.Values)
|
||||||
|
}
|
||||||
|
|
||||||
func (u URLValues) IsZeroValue() bool {
|
func (u URLValues) IsZeroValue() bool {
|
||||||
return len(u.Values) == 0
|
return len(u.Values) == 0
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
t.Run("with-thoth", func(t *testing.T) {
|
t.Run("with-thoth", func(t *testing.T) {
|
||||||
fin, err := os.Open(filepath.Join("..", "config", "testdata", "good", st.Name()))
|
fin, err := os.Open(filepath.Join("..", "config", "testdata", "good", st.Name()))
|
||||||
@@ -71,7 +70,6 @@ func TestBadConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
fin, err := os.Open(filepath.Join("..", "config", "testdata", "bad", st.Name()))
|
fin, err := os.Open(filepath.Join("..", "config", "testdata", "bad", st.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -36,9 +37,7 @@ func (m *mockS3) PutObject(ctx context.Context, in *s3.PutObjectInput, _ ...func
|
|||||||
m.data[aws.ToString(in.Key)] = bytes.Clone(b)
|
m.data[aws.ToString(in.Key)] = bytes.Clone(b)
|
||||||
if in.Metadata != nil {
|
if in.Metadata != nil {
|
||||||
m.meta[aws.ToString(in.Key)] = map[string]string{}
|
m.meta[aws.ToString(in.Key)] = map[string]string{}
|
||||||
for k, v := range in.Metadata {
|
maps.Copy(m.meta[aws.ToString(in.Key)], in.Metadata)
|
||||||
m.meta[aws.ToString(in.Key)][k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
m.bucket = aws.ToString(in.Bucket)
|
m.bucket = aws.ToString(in.Bucket)
|
||||||
return &s3.PutObjectOutput{}, nil
|
return &s3.PutObjectOutput{}, nil
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func (s Sentinel) Valid() error {
|
|||||||
// redisClient is satisfied by *valkey.Client and *valkey.ClusterClient.
|
// redisClient is satisfied by *valkey.Client and *valkey.ClusterClient.
|
||||||
type redisClient interface {
|
type redisClient interface {
|
||||||
Get(ctx context.Context, key string) *valkey.StringCmd
|
Get(ctx context.Context, key string) *valkey.StringCmd
|
||||||
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *valkey.StatusCmd
|
Set(ctx context.Context, key string, value any, expiration time.Duration) *valkey.StatusCmd
|
||||||
Del(ctx context.Context, keys ...string) *valkey.IntCmd
|
Del(ctx context.Context, keys ...string) *valkey.IntCmd
|
||||||
Ping(ctx context.Context) *valkey.StatusCmd
|
Ping(ctx context.Context) *valkey.StatusCmd
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -11,8 +11,8 @@ func authUnaryClientInterceptor(token string) grpc.UnaryClientInterceptor {
|
|||||||
return func(
|
return func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
method string,
|
method string,
|
||||||
req interface{},
|
req any,
|
||||||
reply interface{},
|
reply any,
|
||||||
cc *grpc.ClientConn,
|
cc *grpc.ClientConn,
|
||||||
invoker grpc.UnaryInvoker,
|
invoker grpc.UnaryInvoker,
|
||||||
opts ...grpc.CallOption,
|
opts ...grpc.CallOption,
|
||||||
|
|||||||
Reference in New Issue
Block a user