mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-05-08 16:12:52 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 766132209b | |||
| 3154ff5004 | |||
| 5186d7d3ad | |||
| c6d968874d | |||
| 14a8d0c75e | |||
| dd6b44df90 |
@@ -29,3 +29,5 @@ Prolocation
|
|||||||
Necron
|
Necron
|
||||||
Stargate
|
Stargate
|
||||||
FFXIV
|
FFXIV
|
||||||
|
uvensys
|
||||||
|
de
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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