Merge branch 'main' into json/fix-pow-deref

Signed-off-by: Jason Cameron <git@jasoncameron.dev>
This commit is contained in:
Jason Cameron
2026-03-18 12:27:55 -04:00
committed by GitHub
52 changed files with 581 additions and 499 deletions

View File

@@ -81,11 +81,11 @@ type Server struct {
func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
// return ED25519 key if HS512 is not set
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
}
} else {
return func(token *jwt.Token) (interface{}, error) {
return func(token *jwt.Token) (any, error) {
return s.hs512Secret, nil
}
}

View File

@@ -38,8 +38,8 @@ func NewTLogWriter(t *testing.T) io.Writer {
// Write splits input on newlines and logs each line separately.
func (w *TLogWriter) Write(p []byte) (n int, err error) {
lines := strings.Split(string(p), "\n")
for _, line := range lines {
lines := strings.SplitSeq(string(p), "\n")
for line := range lines {
if line != "" {
w.t.Log(line)
}

View File

@@ -228,8 +228,8 @@ type ImportStatement struct {
}
func (is *ImportStatement) open() (fs.File, error) {
if strings.HasPrefix(is.Import, "(data)/") {
fname := strings.TrimPrefix(is.Import, "(data)/")
if after, ok := strings.CutPrefix(is.Import, "(data)/"); ok {
fname := after
fin, err := data.BotPolicies.Open(fname)
return fin, err
}
@@ -325,7 +325,7 @@ func (sc StatusCodes) Valid() error {
}
type fileConfig struct {
OpenGraph openGraphFileConfig `json:"openGraph,omitempty"`
OpenGraph openGraphFileConfig `json:"openGraph"`
Impressum *Impressum `json:"impressum,omitempty"`
Store *Store `json:"store"`
Bots []BotOrImport `json:"bots"`

View File

@@ -188,7 +188,6 @@ func TestBotValid(t *testing.T) {
}
for _, cs := range tests {
cs := cs
t.Run(cs.name, func(t *testing.T) {
err := cs.bot.Valid()
if err == nil && cs.err == nil {
@@ -216,7 +215,6 @@ func TestConfigValidKnownGood(t *testing.T) {
}
for _, st := range finfos {
st := st
t.Run(st.Name(), func(t *testing.T) {
fin, err := os.Open(filepath.Join("testdata", "good", st.Name()))
if err != nil {
@@ -303,7 +301,6 @@ func TestConfigValidBad(t *testing.T) {
}
for _, st := range finfos {
st := st
t.Run(st.Name(), func(t *testing.T) {
fin, err := os.Open(filepath.Join("testdata", "bad", st.Name()))
if err != nil {

View File

@@ -24,7 +24,6 @@ func TestBadConfigs(t *testing.T) {
}
for _, st := range finfos {
st := st
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 {
t.Fatal(err)
@@ -42,7 +41,6 @@ func TestGoodConfigs(t *testing.T) {
}
for _, st := range finfos {
st := st
t.Run(st.Name(), func(t *testing.T) {
t.Run("with-thoth", func(t *testing.T) {
ctx := thothmock.WithMockThoth(t)

View File

@@ -182,10 +182,7 @@ func makeCode(err error) string {
enc := base64.StdEncoding.EncodeToString(buf.Bytes())
var builder strings.Builder
for i := 0; i < len(enc); i += width {
end := i + width
if end > len(enc) {
end = len(enc)
}
end := min(i+width, len(enc))
builder.WriteString(enc[i:end])
builder.WriteByte('\n')
}
@@ -341,7 +338,14 @@ func (s *Server) respondWithError(w http.ResponseWriter, r *http.Request, messag
func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg, code string, status int) {
localizer := localization.GetLocalizer(r)
templ.Handler(web.Base(localizer.T("oh_noes"), web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer), s.policy.Impressum, localizer), templ.WithStatus(status)).ServeHTTP(w, r)
component := web.Base(
localizer.T("oh_noes"),
web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer),
s.policy.Impressum,
localizer,
)
handler := internal.NoStoreCache(templ.Handler(component, templ.WithStatus(status)))
handler.ServeHTTP(w, r)
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/TecharoHQ/anubis"
"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/lib/policy"
)
@@ -191,3 +192,34 @@ func TestRenderIndexUnauthorized(t *testing.T) {
t.Errorf("expected body %q, got %q", "Authorization required", body)
}
}
func TestNoCacheOnError(t *testing.T) {
pol := loadPolicies(t, "testdata/useragent.yaml", 0)
srv := spawnAnubis(t, Options{Policy: pol})
ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
defer ts.Close()
for userAgent, expectedCacheControl := range map[string]string{
"DENY": "no-store",
"CHALLENGE": "no-store",
"ALLOW": "",
} {
t.Run(userAgent, func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("User-Agent", userAgent)
resp, err := ts.Client().Do(req)
if err != nil {
t.Fatal(err)
}
if resp.Header.Get("Cache-Control") != expectedCacheControl {
t.Errorf("wanted Cache-Control header %q, got %q", expectedCacheControl, resp.Header.Get("Cache-Control"))
}
})
}
}

View File

@@ -1,66 +1,66 @@
{
"loading": "Chargement...",
"why_am_i_seeing": "Pourquoi je vois ceci ?",
"why_am_i_seeing": "Comment suis-je arrivé·e ici ?",
"protected_by": "Protégé par",
"protected_from": "From",
"protected_from": "de",
"made_with": "Fait avec ❤️ au 🇨🇦",
"mascot_design": "Design de la mascotte par",
"ai_companies_explanation": "Vous voyez ceci car l'administrateur de ce site web a configuré Anubis pour protéger le serveur contre le fléau des entreprises d'IA qui scrapent agressivement les sites web. Cela peut et cause des temps d'arrêt pour les sites web, ce qui rend leurs ressources inaccessibles pour tout le monde.",
"anubis_compromise": "Anubis est un compromis. Anubis utilise un schéma de Preuve de Travail dans la veine de Hashcash, un schéma de preuve de travail proposé pour réduire le spam par email. L'idée est qu'à l'échelle individuelle, la charge supplémentaire est négligeable, mais à l'échelle des scrapers de masse, cela s'accumule et rend le scraping beaucoup plus coûteux.",
"hack_purpose": "En fin de compte, il s'agit d'une solution de substitution afin de consacrer plus de temps à l'identification et à l'empreinte digitale des navigateurs sans tête (par exemple, via leur rendu de police) afin que la page de preuve de travail du défi n'ait pas besoin d'être présentée aux utilisateurs qui sont beaucoup plus susceptibles d'être légitimes.",
"jshelter_note": "Veuillez noter qu'Anubis nécessite l'utilisation de fonctionnalités JavaScript modernes que des plugins comme JShelter désactiveront. Veuillez désactiver JShelter ou d'autres plugins similaires pour ce domaine.",
"version_info": "Ce site web utilise Anubis version",
"ai_companies_explanation": "Vous voyez cette page car l'administrateur·rice de ce site Web a configuré Anubis pour protéger le serveur contre le fléau des entreprises d'IA qui récupèrent agressivement le contenu des sites Web. Cela perturbe leur fonctionnement et rend leurs ressources inaccessibles pour tout le monde.",
"anubis_compromise": "Anubis est un compromis. Anubis utilise un procédé de preuve de travail similaire à Hashcash, un procédé de preuve de travail proposé pour réduire le spam par e-mail. L'idée est qu'à l'échelle individuelle, la charge supplémentaire est négligeable, mais à l'échelle des scrapers de masse, la charge s'accumule et le scraping devient beaucoup plus coûteux.",
"hack_purpose": "En fin de compte, il s'agit d'une solution de substitution permettant de consacrer plus de temps à l'identification et à la prise d'empreintes des navigateurs headless (par exemple, en reconnaissant leur rendu des polices), pour que, à terme, la page de défi utilisant la preuve de travail n'ait plus besoin d'être présentée aux utilisateur·rices qui sont beaucoup plus susceptibles d'être légitimes.",
"jshelter_note": "Veuillez noter qu'Anubis nécessite l'utilisation de fonctionnalités JavaScript modernes qui peuvent être désactivées par des plugins comme JShelter. Veuillez désactiver JShelter ou tout autre plugin similaire pour ce domaine.",
"version_info": "Ce site Web utilise Anubis version",
"try_again": "Réessayer",
"go_home": "Accueil",
"contact_webmaster": "ou si vous pensez que vous ne devriez pas être bloqué, veuillez contacter le webmaster à",
"contact_webmaster": "ou si vous pensez que vous ne devriez pas être bloqué, veuillez contacter le webmaster à l'adresse",
"connection_security": "Veuillez patienter un instant pendant que nous assurons la sécurité de votre connexion.",
"javascript_required": "Malheureusement, vous devez activer JavaScript pour passer ce défi. Ceci est requis car les entreprises d'IA ont changé le contrat social autour du fonctionnement de l'hébergement de sites web. Une solution sans JS est en cours de développement.",
"javascript_required": "Malheureusement, vous devez activer JavaScript pour passer cette page de défi. Cette obligation est imposée par les entreprises d'IA, qui ont décidé de modifier unilatéralement les termes du contrat social régissant l'hébergement de sites Web. Une solution sans JavaScript est en cours de développement.",
"benchmark_requires_js": "L'exécution de l'outil de benchmark nécessite l'activation de JavaScript.",
"difficulty": "Difficulté :",
"algorithm": "Algorithme :",
"compare": "Comparer :",
"difficulty": "Difficulté :",
"algorithm": "Algorithme :",
"compare": "Comparer :",
"time": "Temps",
"iters": "Itérations",
"time_a": "Temps A",
"iters_a": "Itér. A",
"time_b": "Temps B",
"iters_b": "Itér. B",
"static_check_endpoint": "Ceci est juste un point de terminaison de vérification pour votre proxy inverse à utiliser.",
"static_check_endpoint": "Ceci est juste un point de terminaison de vérification à utiliser par votre proxy inverse.",
"authorization_required": "Autorisation requise",
"cookies_disabled": "Votre navigateur est configuré pour désactiver les cookies. Anubis nécessite des cookies pour l'intérêt légitime de s'assurer que vous êtes un client valide. Veuillez activer les cookies pour ce domaine",
"access_denied": "Accès refusé : code d'erreur",
"dronebl_entry": "DroneBL a signalé une entrée",
"cookies_disabled": "Les cookies sont désactivés dans votre navigateur. Anubis a recours aux cookies pour l'intérêt légitime de s'assurer que vous êtes un client valide. Veuillez activer les cookies pour ce domaine.",
"access_denied": "Accès refusé : code d'erreur",
"dronebl_entry": "DroneBL a rapporté une entrée",
"see_dronebl_lookup": "voir",
"internal_server_error": "Erreur interne du serveur : l'administrateur a mal configuré Anubis. Veuillez contacter l'administrateur et lui demander de consulter les logs autour de",
"internal_server_error": "Erreur interne du serveur : l'administrateur·rice a mal configuré Anubis. Veuillez contacter l'administrateur·rice et lui demander de consulter les logs autour de",
"invalid_redirect": "Redirection invalide",
"redirect_not_parseable": "URL de redirection non analysable",
"redirect_domain_not_allowed": "Domaine de redirection non autorisé",
"failed_to_sign_jwt": "échec de la signature JWT",
"failed_to_sign_jwt": "échec de la signature du JWT",
"invalid_invocation": "Invocation invalide de MakeChallenge",
"client_error_browser": "Erreur client : Veuillez vous assurer que votre navigateur est à jour et réessayez plus tard.",
"oh_noes": "Oh non !",
"benchmarking_anubis": "Test de performance d'Anubis !",
"you_are_not_a_bot": "Vous n'êtes pas un robot !",
"making_sure_not_bot": "Vérification que vous n'êtes pas un robot !",
"celphase": "PHASE de CEL",
"js_web_crypto_error": "Votre navigateur n'a pas d'élément web.crypto fonctionnel. Consultez-vous cette page dans un contexte sécurisé ?",
"js_web_workers_error": "Votre navigateur ne prend pas en charge les web workers (Anubis les utilise pour éviter de bloquer votre navigateur). Avez-vous un plugin comme JShelter installé ?",
"js_cookies_error": "Votre navigateur ne stocke pas les cookies. Anubis utilise des cookies pour déterminer quels clients ont réussi les défis en stockant un jeton signé dans un cookie. Veuillez activer le stockage des cookies pour ce domaine. Les noms des cookies qu'Anubis stocke peuvent varier sans préavis. Les noms et valeurs des cookies ne font pas partie de l'API publique.",
"js_context_not_secure": "Votre contexte n'est pas sécurisé !",
"js_context_not_secure_msg": "Essayez de vous connecter via HTTPS ou informez l'administrateur de configurer HTTPS. Pour plus d'informations, voir <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
"client_error_browser": "Erreur client : Veuillez vous assurer que votre navigateur est à jour et réessayez plus tard.",
"oh_noes": "Oh non !",
"benchmarking_anubis": "Je vérifie les performances d'Anubis !",
"you_are_not_a_bot": "Vous n'êtes pas un robot !",
"making_sure_not_bot": "Je m'assure que vous n'êtes pas un robot !",
"celphase": "CELPHASE",
"js_web_crypto_error": "L'élément web.crypto de votre navigateur n'est pas fonctionnel. Consultez-vous bien cette page dans un contexte sécurisé ?",
"js_web_workers_error": "Votre navigateur ne prend pas en charge les web workers (Anubis les utilise pour éviter de bloquer votre navigateur). Avez-vous installé un plugin comme JShelter ?",
"js_cookies_error": "Votre navigateur ne stocke pas les cookies. Anubis a recours aux cookies pour déterminer quels clients ont réussi les défis en stockant un jeton signé dans un cookie. Veuillez activer le stockage des cookies pour ce domaine. Le nom des cookies stockés par Anubis peut varier à tout moment. Le nom et la valeur des cookies ne font pas partie de l'API publique.",
"js_context_not_secure": "Votre contexte n'est pas sécurisé !",
"js_context_not_secure_msg": "Essayez de vous connecter via HTTPS ou demandez à l'administrateur·rice de configurer HTTPS. Pour plus d'informations, consultez <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
"js_calculating": "Calcul en cours...",
"js_missing_feature": "Fonctionnalité manquante",
"js_challenge_error": "Erreur de défi !",
"js_challenge_error": "Erreur de défi !",
"js_challenge_error_msg": "Échec de la résolution de l'algorithme de vérification. Vous pouvez essayer de recharger la page.",
"js_calculating_difficulty": "Calcul en cours...<br/>Difficulté :",
"js_speed": "Vitesse :",
"js_calculating_difficulty": "Calcul en cours...<br/>Difficulté :",
"js_speed": "Vitesse :",
"js_verification_longer": "La vérification prend plus de temps que prévu. Veuillez ne pas actualiser la page.",
"js_success": "Succès !",
"js_done_took": "Terminé ! A pris",
"js_success": "Vérification réussie !",
"js_done_took": "Terminé ! Cela aura nécessité",
"js_iterations": "itérations",
"js_finished_reading": "J'ai fini de lire, continuer →",
"js_calculation_error": "Erreur de calcul !",
"js_calculation_error_msg": "Échec du calcul du défi :",
"missing_required_forwarded_headers": "En-têtes X-Forwarded-* requis manquants",
"simplified_explanation": "Il s'agit d'une mesure contre les robots et les requêtes malveillantes similaire à un CAPTCHA. Cependant, au lieu d'avoir à faire le travail vous-même, votre navigateur se voit confier une tâche de calcul qu'il doit résoudre pour s'assurer qu'il est un client valide. Ce concept s'appelle <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Preuve de travail</a>. La tâche est calculée en quelques secondes et vous avez accès au site Web. Merci de votre compréhension et de votre patience."
"js_finished_reading": "J'ai fini de lire, continuer →",
"js_calculation_error": "Erreur de calcul !",
"js_calculation_error_msg": "Échec du calcul du défi :",
"missing_required_forwarded_headers": "En-têtes X-Forwarded-* manquants",
"simplified_explanation": "Ceci est une mesure contre les robots et les requêtes malveillantes, similaire à un CAPTCHA. Cependant, au lieu d'avoir à faire le travail vous-même, votre navigateur se voit confier une tâche de calcul qu'il doit résoudre pour confirmer qu'il est un client valide. Ce concept est nommé <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Preuve de travail</a>. La tâche s'effectue en quelques secondes, puis vous avez accès au site Web. Merci pour votre compréhension et votre patience."
}

View File

@@ -103,7 +103,7 @@ func TestBotEnvironment(t *testing.T) {
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,
})
if err != nil {
@@ -168,7 +168,7 @@ func TestBotEnvironment(t *testing.T) {
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,
})
if err != nil {
@@ -280,7 +280,7 @@ func TestBotEnvironment(t *testing.T) {
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 {
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)
}
result, _, err := prog.Eval(map[string]interface{}{})
result, _, err := prog.Eval(map[string]any{})
if err != nil {
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)
}
result, _, err := prog.Eval(map[string]interface{}{})
result, _, err := prog.Eval(map[string]any{})
if err != nil {
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)
}
result, _, err := prog.Eval(map[string]interface{}{})
result, _, err := prog.Eval(map[string]any{})
if err != nil {
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)
}
result, _, err := prog.Eval(map[string]interface{}{})
result, _, err := prog.Eval(map[string]any{})
if tt.evalError {
if err == nil {
t.Errorf("%s: expected an evaluation error, but got none", tt.description)
@@ -598,7 +598,7 @@ func TestThresholdEnvironment(t *testing.T) {
}
tests := []struct {
variables map[string]interface{}
variables map[string]any
name string
expression string
description string
@@ -608,7 +608,7 @@ func TestThresholdEnvironment(t *testing.T) {
{
name: "weight-variable-available",
expression: `weight > 100`,
variables: map[string]interface{}{"weight": 150},
variables: map[string]any{"weight": 150},
expected: types.Bool(true),
description: "should support weight variable in expressions",
shouldCompile: true,
@@ -616,7 +616,7 @@ func TestThresholdEnvironment(t *testing.T) {
{
name: "weight-variable-false-case",
expression: `weight > 100`,
variables: map[string]interface{}{"weight": 50},
variables: map[string]any{"weight": 50},
expected: types.Bool(false),
description: "should correctly evaluate weight comparisons",
shouldCompile: true,
@@ -624,7 +624,7 @@ func TestThresholdEnvironment(t *testing.T) {
{
name: "missingHeader-not-available",
expression: `missingHeader(headers, "Test")`,
variables: map[string]interface{}{},
variables: map[string]any{},
expected: types.Bool(false), // not used
description: "should not have missingHeader function available",
shouldCompile: false,
@@ -667,7 +667,7 @@ func TestNewEnvironment(t *testing.T) {
tests := []struct {
name 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
description string
shouldCompile bool
@@ -675,7 +675,7 @@ func TestNewEnvironment(t *testing.T) {
{
name: "randInt-function-compilation",
expression: `randInt(10)`,
variables: map[string]interface{}{},
variables: map[string]any{},
expectBool: nil, // Don't check result, just compilation
description: "should compile randInt function",
shouldCompile: true,
@@ -683,7 +683,7 @@ func TestNewEnvironment(t *testing.T) {
{
name: "randInt-range-validation",
expression: `randInt(10) >= 0 && randInt(10) < 10`,
variables: map[string]interface{}{},
variables: map[string]any{},
expectBool: boolPtr(true),
description: "should return values in correct range",
shouldCompile: true,
@@ -691,7 +691,7 @@ func TestNewEnvironment(t *testing.T) {
{
name: "strings-extension-size",
expression: `"hello".size() == 5`,
variables: map[string]interface{}{},
variables: map[string]any{},
expectBool: boolPtr(true),
description: "should support string extension functions",
shouldCompile: true,
@@ -699,7 +699,7 @@ func TestNewEnvironment(t *testing.T) {
{
name: "strings-extension-contains",
expression: `"hello world".contains("world")`,
variables: map[string]interface{}{},
variables: map[string]any{},
expectBool: boolPtr(true),
description: "should support string contains function",
shouldCompile: true,
@@ -707,7 +707,7 @@ func TestNewEnvironment(t *testing.T) {
{
name: "strings-extension-startsWith",
expression: `"hello world".startsWith("hello")`,
variables: map[string]interface{}{},
variables: map[string]any{},
expectBool: boolPtr(true),
description: "should support string startsWith function",
shouldCompile: true,

View File

@@ -32,7 +32,6 @@ func TestGoodConfigs(t *testing.T) {
}
for _, st := range finfos {
st := st
t.Run(st.Name(), func(t *testing.T) {
t.Run("with-thoth", func(t *testing.T) {
fin, err := os.Open(filepath.Join("..", "config", "testdata", "good", st.Name()))
@@ -71,7 +70,6 @@ func TestBadConfigs(t *testing.T) {
}
for _, st := range finfos {
st := st
t.Run(st.Name(), func(t *testing.T) {
fin, err := os.Open(filepath.Join("..", "config", "testdata", "bad", st.Name()))
if err != nil {

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io"
"maps"
"sync"
"testing"
"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)
if in.Metadata != nil {
m.meta[aws.ToString(in.Key)] = map[string]string{}
for k, v := range in.Metadata {
m.meta[aws.ToString(in.Key)][k] = v
}
maps.Copy(m.meta[aws.ToString(in.Key)], in.Metadata)
}
m.bucket = aws.ToString(in.Bucket)
return &s3.PutObjectOutput{}, nil

View File

@@ -103,7 +103,7 @@ func (s Sentinel) Valid() error {
// redisClient is satisfied by *valkey.Client and *valkey.ClusterClient.
type redisClient interface {
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
Ping(ctx context.Context) *valkey.StatusCmd
}

12
lib/testdata/useragent.yaml vendored Normal file
View File

@@ -0,0 +1,12 @@
bots:
- name: deny
user_agent_regex: DENY
action: DENY
- name: challenge
user_agent_regex: CHALLENGE
action: CHALLENGE
- name: allow
user_agent_regex: ALLOW
action: ALLOW

View File

@@ -11,8 +11,8 @@ func authUnaryClientInterceptor(token string) grpc.UnaryClientInterceptor {
return func(
ctx context.Context,
method string,
req interface{},
reply interface{},
req any,
reply any,
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,