mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-09 10:08:45 +00:00
Compare commits
13 Commits
json/requi
...
Xe/decayma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0067659ac | ||
|
|
406732fe7e | ||
|
|
f79d36d21e | ||
|
|
f5b5243b5e | ||
|
|
2011b83a44 | ||
|
|
8ed89a6c6e | ||
|
|
9430d0e6a5 | ||
|
|
8b9dafac51 | ||
|
|
9997130a7c | ||
|
|
e239083944 | ||
|
|
abf6c8de57 | ||
|
|
7e1b5d9951 | ||
|
|
98945fb56f |
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,3 +9,4 @@ Checklist:
|
|||||||
- [ ] Added a description of the changes to the `[Unreleased]` section of docs/docs/CHANGELOG.md
|
- [ ] Added a description of the changes to the `[Unreleased]` section of docs/docs/CHANGELOG.md
|
||||||
- [ ] Added test cases to [the relevant parts of the codebase](https://anubis.techaro.lol/docs/developer/code-quality)
|
- [ ] Added test cases to [the relevant parts of the codebase](https://anubis.techaro.lol/docs/developer/code-quality)
|
||||||
- [ ] Ran integration tests `npm run test:integration` (unsupported on Windows, please use WSL)
|
- [ ] Ran integration tests `npm run test:integration` (unsupported on Windows, please use WSL)
|
||||||
|
- [ ] All of my commits have [verified signatures](https://anubis.techaro.lol/docs/developer/signed-commits)
|
||||||
|
|||||||
1
.github/actions/spelling/allow.txt
vendored
1
.github/actions/spelling/allow.txt
vendored
@@ -6,3 +6,4 @@ workarounds
|
|||||||
rjack
|
rjack
|
||||||
msgbox
|
msgbox
|
||||||
xeact
|
xeact
|
||||||
|
ABee
|
||||||
|
|||||||
7
.github/actions/spelling/expect.txt
vendored
7
.github/actions/spelling/expect.txt
vendored
@@ -140,6 +140,7 @@ headermap
|
|||||||
healthcheck
|
healthcheck
|
||||||
healthz
|
healthz
|
||||||
hec
|
hec
|
||||||
|
Hetzner
|
||||||
hmc
|
hmc
|
||||||
homelab
|
homelab
|
||||||
hostable
|
hostable
|
||||||
@@ -213,6 +214,7 @@ nicksnyder
|
|||||||
nobots
|
nobots
|
||||||
NONINFRINGEMENT
|
NONINFRINGEMENT
|
||||||
nosleep
|
nosleep
|
||||||
|
nullglob
|
||||||
OCOB
|
OCOB
|
||||||
ogtag
|
ogtag
|
||||||
oklch
|
oklch
|
||||||
@@ -237,7 +239,6 @@ pki
|
|||||||
podkova
|
podkova
|
||||||
podman
|
podman
|
||||||
poststart
|
poststart
|
||||||
poxied
|
|
||||||
prebaked
|
prebaked
|
||||||
privkey
|
privkey
|
||||||
promauto
|
promauto
|
||||||
@@ -250,7 +251,6 @@ pwuser
|
|||||||
qualys
|
qualys
|
||||||
qwant
|
qwant
|
||||||
qwantbot
|
qwantbot
|
||||||
QWEN
|
|
||||||
rac
|
rac
|
||||||
rawler
|
rawler
|
||||||
rcvar
|
rcvar
|
||||||
@@ -279,10 +279,10 @@ Seo
|
|||||||
setsebool
|
setsebool
|
||||||
shellcheck
|
shellcheck
|
||||||
shirou
|
shirou
|
||||||
|
shopt
|
||||||
Sidetrade
|
Sidetrade
|
||||||
simprint
|
simprint
|
||||||
sitemap
|
sitemap
|
||||||
Slackware
|
|
||||||
sls
|
sls
|
||||||
Smartphone
|
Smartphone
|
||||||
sni
|
sni
|
||||||
@@ -360,6 +360,7 @@ XOriginal
|
|||||||
XReal
|
XReal
|
||||||
yae
|
yae
|
||||||
YAMLTo
|
YAMLTo
|
||||||
|
Yda
|
||||||
yeet
|
yeet
|
||||||
yeetfile
|
yeetfile
|
||||||
yourdomain
|
yourdomain
|
||||||
|
|||||||
4
.github/workflows/smoke-tests.yml
vendored
4
.github/workflows/smoke-tests.yml
vendored
@@ -28,11 +28,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version: latest
|
node-version: latest
|
||||||
|
|
||||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||||
with:
|
with:
|
||||||
go-version: stable
|
go-version: stable
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/ssh-ci.yml
vendored
2
.github/workflows/ssh-ci.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
|||||||
name: id_rsa
|
name: id_rsa
|
||||||
known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }}
|
known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }}
|
||||||
|
|
||||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||||
with:
|
with:
|
||||||
go-version: stable
|
go-version: stable
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/zizmor.yml
vendored
4
.github/workflows/zizmor.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||||
|
|
||||||
- name: Run zizmor 🌈
|
- name: Run zizmor 🌈
|
||||||
run: uvx zizmor --format sarif . > results.sarif
|
run: uvx zizmor --format sarif . > results.sarif
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11
|
uses: github/codeql-action/upload-sarif@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
category: zizmor
|
category: zizmor
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ var (
|
|||||||
cookieExpiration = flag.Duration("cookie-expiration-time", anubis.CookieDefaultExpirationTime, "The amount of time the authorization cookie is valid for")
|
cookieExpiration = flag.Duration("cookie-expiration-time", anubis.CookieDefaultExpirationTime, "The amount of time the authorization cookie is valid for")
|
||||||
cookiePrefix = flag.String("cookie-prefix", anubis.CookieName, "prefix for browser cookies created by Anubis")
|
cookiePrefix = flag.String("cookie-prefix", anubis.CookieName, "prefix for browser cookies created by Anubis")
|
||||||
cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
|
cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
|
||||||
|
difficultyInJWT = flag.Bool("difficulty-in-jwt", false, "if true, adds a difficulty field in the JWT claims")
|
||||||
useSimplifiedExplanation = flag.Bool("use-simplified-explanation", false, "if true, replaces the text when clicking \"Why am I seeing this?\" with a more simplified text for a non-tech-savvy audience.")
|
useSimplifiedExplanation = flag.Bool("use-simplified-explanation", false, "if true, replaces the text when clicking \"Why am I seeing this?\" with a more simplified text for a non-tech-savvy audience.")
|
||||||
forcedLanguage = flag.String("forced-language", "", "if set, this language is being used instead of the one from the request's Accept-Language header")
|
forcedLanguage = flag.String("forced-language", "", "if set, this language is being used instead of the one from the request's Accept-Language header")
|
||||||
hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set")
|
hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set")
|
||||||
@@ -433,6 +434,7 @@ func main() {
|
|||||||
CookieSecure: *cookieSecure,
|
CookieSecure: *cookieSecure,
|
||||||
PublicUrl: *publicUrl,
|
PublicUrl: *publicUrl,
|
||||||
JWTRestrictionHeader: *jwtRestrictionHeader,
|
JWTRestrictionHeader: *jwtRestrictionHeader,
|
||||||
|
DifficultyInJWT: *difficultyInJWT,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("can't construct libanubis.Server: %v", err)
|
log.Fatalf("can't construct libanubis.Server: %v", err)
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ func main() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.Contains(*dockerTags, ",") {
|
||||||
|
newTags := strings.Join(strings.Split(*dockerTags, ","), "\n")
|
||||||
|
dockerTags = &newTags
|
||||||
|
}
|
||||||
|
|
||||||
setOutput("docker_image", strings.SplitN(*dockerTags, "\n", 2)[0])
|
setOutput("docker_image", strings.SplitN(*dockerTags, "\n", 2)[0])
|
||||||
|
|
||||||
version, err := run("git describe --tags --always --dirty")
|
version, err := run("git describe --tags --always --dirty")
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ func Zilch[T any]() T {
|
|||||||
type Impl[K comparable, V any] struct {
|
type Impl[K comparable, V any] struct {
|
||||||
data map[K]decayMapEntry[V]
|
data map[K]decayMapEntry[V]
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
|
||||||
|
// deleteCh receives decay-deletion requests from readers.
|
||||||
|
deleteCh chan deleteReq[K]
|
||||||
|
// stopCh stops the background cleanup worker.
|
||||||
|
stopCh chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
type decayMapEntry[V any] struct {
|
type decayMapEntry[V any] struct {
|
||||||
@@ -21,30 +27,38 @@ type decayMapEntry[V any] struct {
|
|||||||
expiry time.Time
|
expiry time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteReq is a request to remove a key if its expiry timestamp still matches
|
||||||
|
// the observed one. This prevents racing with concurrent Set updates.
|
||||||
|
type deleteReq[K comparable] struct {
|
||||||
|
key K
|
||||||
|
expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// New creates a new DecayMap of key type K and value type V.
|
// New creates a new DecayMap of key type K and value type V.
|
||||||
//
|
//
|
||||||
// Key types must be comparable to work with maps.
|
// Key types must be comparable to work with maps.
|
||||||
func New[K comparable, V any]() *Impl[K, V] {
|
func New[K comparable, V any]() *Impl[K, V] {
|
||||||
return &Impl[K, V]{
|
m := &Impl[K, V]{
|
||||||
data: make(map[K]decayMapEntry[V]),
|
data: make(map[K]decayMapEntry[V]),
|
||||||
|
deleteCh: make(chan deleteReq[K], 1024),
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
m.wg.Add(1)
|
||||||
|
go m.cleanupWorker()
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// expire forcibly expires a key by setting its time-to-live one second in the past.
|
// expire forcibly expires a key by setting its time-to-live one second in the past.
|
||||||
func (m *Impl[K, V]) expire(key K) bool {
|
func (m *Impl[K, V]) expire(key K) bool {
|
||||||
m.lock.RLock()
|
// Use a single write lock to avoid RUnlock->Lock convoy.
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
val, ok := m.data[key]
|
val, ok := m.data[key]
|
||||||
m.lock.RUnlock()
|
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
m.lock.Lock()
|
|
||||||
val.expiry = time.Now().Add(-1 * time.Second)
|
val.expiry = time.Now().Add(-1 * time.Second)
|
||||||
m.data[key] = val
|
m.data[key] = val
|
||||||
m.lock.Unlock()
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,19 +67,14 @@ func (m *Impl[K, V]) expire(key K) bool {
|
|||||||
// If the value does not exist, return false. Return true after
|
// If the value does not exist, return false. Return true after
|
||||||
// deletion.
|
// deletion.
|
||||||
func (m *Impl[K, V]) Delete(key K) bool {
|
func (m *Impl[K, V]) Delete(key K) bool {
|
||||||
m.lock.RLock()
|
// Use a single write lock to avoid RUnlock->Lock convoy.
|
||||||
_, ok := m.data[key]
|
|
||||||
m.lock.RUnlock()
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
delete(m.data, key)
|
defer m.lock.Unlock()
|
||||||
m.lock.Unlock()
|
_, ok := m.data[key]
|
||||||
|
if ok {
|
||||||
return true
|
delete(m.data, key)
|
||||||
|
}
|
||||||
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets a value from the DecayMap by key.
|
// Get gets a value from the DecayMap by key.
|
||||||
@@ -81,13 +90,12 @@ func (m *Impl[K, V]) Get(key K) (V, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().After(value.expiry) {
|
if time.Now().After(value.expiry) {
|
||||||
m.lock.Lock()
|
// Defer decay deletion to the background worker to avoid convoy.
|
||||||
// Since previously reading m.data[key], the value may have been updated.
|
select {
|
||||||
// Delete the entry only if the expiry time is still the same.
|
case m.deleteCh <- deleteReq[K]{key: key, expiry: value.expiry}:
|
||||||
if m.data[key].expiry.Equal(value.expiry) {
|
default:
|
||||||
delete(m.data, key)
|
// Channel full: drop request; a future Cleanup() or Get will retry.
|
||||||
}
|
}
|
||||||
m.lock.Unlock()
|
|
||||||
|
|
||||||
return Zilch[V](), false
|
return Zilch[V](), false
|
||||||
}
|
}
|
||||||
@@ -125,3 +133,64 @@ func (m *Impl[K, V]) Len() int {
|
|||||||
defer m.lock.RUnlock()
|
defer m.lock.RUnlock()
|
||||||
return len(m.data)
|
return len(m.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close stops the background cleanup worker. It's optional to call; maps live
|
||||||
|
// for the process lifetime in many cases. Call in tests or when you know you no
|
||||||
|
// longer need the map to avoid goroutine leaks.
|
||||||
|
func (m *Impl[K, V]) Close() {
|
||||||
|
close(m.stopCh)
|
||||||
|
m.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupWorker batches decay deletions to minimize lock contention.
|
||||||
|
func (m *Impl[K, V]) cleanupWorker() {
|
||||||
|
defer m.wg.Done()
|
||||||
|
batch := make([]deleteReq[K], 0, 64)
|
||||||
|
ticker := time.NewTicker(10 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
flush := func() {
|
||||||
|
if len(batch) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.applyDeletes(batch)
|
||||||
|
// reset batch without reallocating
|
||||||
|
batch = batch[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case req := <-m.deleteCh:
|
||||||
|
batch = append(batch, req)
|
||||||
|
case <-ticker.C:
|
||||||
|
flush()
|
||||||
|
case <-m.stopCh:
|
||||||
|
// Drain any remaining requests then exit
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case req := <-m.deleteCh:
|
||||||
|
batch = append(batch, req)
|
||||||
|
default:
|
||||||
|
flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Impl[K, V]) applyDeletes(batch []deleteReq[K]) {
|
||||||
|
now := time.Now()
|
||||||
|
m.lock.Lock()
|
||||||
|
for _, req := range batch {
|
||||||
|
entry, ok := m.data[req.key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Only delete if the expiry is unchanged and already past.
|
||||||
|
if entry.expiry.Equal(req.expiry) && now.After(entry.expiry) {
|
||||||
|
delete(m.data, req.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.lock.Unlock()
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
func TestImpl(t *testing.T) {
|
func TestImpl(t *testing.T) {
|
||||||
dm := New[string, string]()
|
dm := New[string, string]()
|
||||||
|
t.Cleanup(dm.Close)
|
||||||
|
|
||||||
dm.Set("test", "hi", 5*time.Minute)
|
dm.Set("test", "hi", 5*time.Minute)
|
||||||
|
|
||||||
@@ -28,10 +29,24 @@ func TestImpl(t *testing.T) {
|
|||||||
if ok {
|
if ok {
|
||||||
t.Error("got value even though it was supposed to be expired")
|
t.Error("got value even though it was supposed to be expired")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deletion of expired entries after Get is deferred to a background worker.
|
||||||
|
// Assert it eventually disappears from the map.
|
||||||
|
deadline := time.Now().Add(200 * time.Millisecond)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
if dm.Len() == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if dm.Len() != 0 {
|
||||||
|
t.Fatalf("expected background cleanup to remove expired key; len=%d", dm.Len())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCleanup(t *testing.T) {
|
func TestCleanup(t *testing.T) {
|
||||||
dm := New[string, string]()
|
dm := New[string, string]()
|
||||||
|
t.Cleanup(dm.Close)
|
||||||
|
|
||||||
dm.Set("test1", "hi1", 1*time.Second)
|
dm.Set("test1", "hi1", 1*time.Second)
|
||||||
dm.Set("test2", "hi2", 2*time.Second)
|
dm.Set("test2", "hi2", 2*time.Second)
|
||||||
|
|||||||
@@ -11,11 +11,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
<!-- This changes the project to: -->
|
||||||
|
|
||||||
|
- Fix lock convoy problem in decaymap ([#1103](https://github.com/TecharoHQ/anubis/issues/1103))
|
||||||
- Document missing environment variables in installation guide: `SLOG_LEVEL`, `COOKIE_PREFIX`, `FORCED_LANGUAGE`, and `TARGET_DISABLE_KEEPALIVE` ([#1086](https://github.com/TecharoHQ/anubis/pull/1086))
|
- Document missing environment variables in installation guide: `SLOG_LEVEL`, `COOKIE_PREFIX`, `FORCED_LANGUAGE`, and `TARGET_DISABLE_KEEPALIVE` ([#1086](https://github.com/TecharoHQ/anubis/pull/1086))
|
||||||
- Add validation warning when persistent storage is used without setting signing keys
|
- Add validation warning when persistent storage is used without setting signing keys
|
||||||
- Fixed `robots2policy` to properly group consecutive user agents into `any:` instead of only processing the last one ([#925](https://github.com/TecharoHQ/anubis/pull/925))
|
- Fixed `robots2policy` to properly group consecutive user agents into `any:` instead of only processing the last one ([#925](https://github.com/TecharoHQ/anubis/pull/925))
|
||||||
|
- Add the [`s3api` storage backend](./admin/policies.mdx#s3api) to allow Anubis to use S3 API compatible object storage as its storage backend.
|
||||||
|
- Make `cmd/containerbuild` support commas for separating elements of the `--docker-tags` argument as well as newlines.
|
||||||
|
- Add the `DIFFICULTY_IN_JWT` option, which allows one to add the `difficulty` field in the JWT claims which indicates the difficulty of the token ([#1063](https://github.com/TecharoHQ/anubis/pull/1063))
|
||||||
|
- Ported the client-side JS to TypeScript to avoid egregious errors in the future.
|
||||||
|
- Fixes concurrency problems with very old browsers ([#1082](https://github.com/TecharoHQ/anubis/issues/1082)).
|
||||||
|
|
||||||
<!-- This changes the project to: -->
|
### Bug Fixes
|
||||||
|
|
||||||
|
Sometimes the enhanced temporal assurance in [#1038](https://github.com/TecharoHQ/anubis/pull/1038) and [#1068](https://github.com/TecharoHQ/anubis/pull/1068) could backfire because Chromium and its ilk randomize the amount of time they wait in order to avoid a timing side channel attack. This has been fixed by both increasing the amount of time a client has to wait for the metarefresh and preact challenges as well as making the server side logic more permissive.
|
||||||
|
|
||||||
## v1.22.0: Yda Hext
|
## v1.22.0: Yda Hext
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ Currently the following settings are configurable via the policy file:
|
|||||||
Anubis uses these environment variables for configuration:
|
Anubis uses these environment variables for configuration:
|
||||||
|
|
||||||
| Environment Variable | Default value | Explanation |
|
| Environment Variable | Default value | Explanation |
|
||||||
|:-------------------------------|:------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
| :----------------------------- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| `BASE_PREFIX` | unset | If set, adds a global prefix to all Anubis endpoints (everything starting with `/.within.website/x/anubis/`). For example, setting this to `/myapp` would make Anubis accessible at `/myapp/` instead of `/`. This is useful when running Anubis behind a reverse proxy that routes based on path prefixes. |
|
| `BASE_PREFIX` | unset | If set, adds a global prefix to all Anubis endpoints (everything starting with `/.within.website/x/anubis/`). For example, setting this to `/myapp` would make Anubis accessible at `/myapp/` instead of `/`. This is useful when running Anubis behind a reverse proxy that routes based on path prefixes. |
|
||||||
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
|
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
|
||||||
| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
|
| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
|
||||||
@@ -70,6 +70,7 @@ Anubis uses these environment variables for configuration:
|
|||||||
| `COOKIE_PREFIX` | `anubis-cookie` | The prefix used for browser cookies created by Anubis. Useful for customization or avoiding conflicts with other applications. |
|
| `COOKIE_PREFIX` | `anubis-cookie` | The prefix used for browser cookies created by Anubis. Useful for customization or avoiding conflicts with other applications. |
|
||||||
| `COOKIE_SECURE` | `true` | If set to `true`, enables the [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies), meaning that the cookies will only be transmitted over HTTPS. If Anubis is used in an unsecure context (plain HTTP), this will be need to be set to false |
|
| `COOKIE_SECURE` | `true` | If set to `true`, enables the [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies), meaning that the cookies will only be transmitted over HTTPS. If Anubis is used in an unsecure context (plain HTTP), this will be need to be set to false |
|
||||||
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
||||||
|
| `DIFFICULTY_IN_JWT` | `false` | If set to `true`, adds the `difficulty` field into JWT claims, which indicates the difficulty the token has been generated. This may be useful for statistics and debugging. |
|
||||||
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. See below for details. |
|
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. See below for details. |
|
||||||
| `ED25519_PRIVATE_KEY_HEX_FILE` | unset | Path to a file containing the hex-encoded ed25519 private key. Only one of this or its sister option may be set. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. |
|
| `ED25519_PRIVATE_KEY_HEX_FILE` | unset | Path to a file containing the hex-encoded ed25519 private key. Only one of this or its sister option may be set. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. |
|
||||||
| `JWT_RESTRICTION_HEADER` | `X-Real-IP` | If set, the JWT is only valid if the current value of this header matches the value when the JWT was created. You can use it e.g. to restrict a JWT to the source IP of the user using `X-Real-IP`. |
|
| `JWT_RESTRICTION_HEADER` | `X-Real-IP` | If set, the JWT is only valid if the current value of this header matches the value when the JWT was created. You can use it e.g. to restrict a JWT to the source IP of the user using `X-Real-IP`. |
|
||||||
@@ -100,14 +101,14 @@ If you don't know or understand what these settings mean, ignore them. These are
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
| Environment Variable | Default value | Explanation |
|
| Environment Variable | Default value | Explanation |
|
||||||
| :---------------------------- | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| :---------------------------- | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `FORCED_LANGUAGE` | unset | If set, forces Anubis to display challenge pages in the specified language instead of using the browser's Accept-Language header. Use ISO 639-1 language codes (e.g., `de` for German, `fr` for French). |
|
| `FORCED_LANGUAGE` | unset | If set, forces Anubis to display challenge pages in the specified language instead of using the browser's Accept-Language header. Use ISO 639-1 language codes (e.g., `de` for German, `fr` for French). |
|
||||||
| `HS512_SECRET` | unset | Secret string for JWT HS512 algorithm. If this is not set, Anubis will use ED25519 as defined via the variables above. The longer the better; 128 chars should suffice. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. |
|
| `HS512_SECRET` | unset | Secret string for JWT HS512 algorithm. If this is not set, Anubis will use ED25519 as defined via the variables above. The longer the better; 128 chars should suffice. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. |
|
||||||
| `TARGET_DISABLE_KEEPALIVE` | `false` | If `true`, disables HTTP keep-alive for connections to the target backend. Useful for backends that don't handle keep-alive properly. |
|
| `TARGET_DISABLE_KEEPALIVE` | `false` | If `true`, disables HTTP keep-alive for connections to the target backend. Useful for backends that don't handle keep-alive properly. |
|
||||||
| `TARGET_HOST` | unset | If set, overrides the Host header in requests forwarded to `TARGET`. |
|
| `TARGET_HOST` | unset | If set, overrides the Host header in requests forwarded to `TARGET`. |
|
||||||
| `TARGET_INSECURE_SKIP_VERIFY` | `false` | If `true`, skip TLS certificate validation for targets that listen over `https`. If your backend does not listen over `https`, ignore this setting. |
|
| `TARGET_INSECURE_SKIP_VERIFY` | `false` | If `true`, skip TLS certificate validation for targets that listen over `https`. If your backend does not listen over `https`, ignore this setting. |
|
||||||
| `TARGET_SNI` | unset | If set, overrides the TLS handshake hostname in requests forwarded to `TARGET`. |
|
| `TARGET_SNI` | unset | If set, overrides the TLS handshake hostname in requests forwarded to `TARGET`. |
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|||||||
@@ -196,6 +196,83 @@ store:
|
|||||||
path: /data/anubis.bdb
|
path: /data/anubis.bdb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `s3api`
|
||||||
|
|
||||||
|
A network-backed storage layer backed by [object storage](https://en.wikipedia.org/wiki/Object_storage), specifically using the [S3 API](https://docs.aws.amazon.com/AmazonS3/latest/API/Type_API_Reference.html). This can be backed by any S3-compatible object storage service such as:
|
||||||
|
|
||||||
|
- [AWS S3](https://aws.amazon.com/s3/)
|
||||||
|
- [Cloudflare R2](https://www.cloudflare.com/developer-platform/products/r2/)
|
||||||
|
- [Hetzner Object Storage](https://www.hetzner.com/storage/object-storage/)
|
||||||
|
- [Minio](https://www.min.io/)
|
||||||
|
- [Tigris](https://www.tigrisdata.com/)
|
||||||
|
|
||||||
|
If you are using a cloud platform, they likely provide an S3 compatible object storage service. If not, you may want to choose [one of the fastest options](https://www.tigrisdata.com/blog/benchmark-small-objects/).
|
||||||
|
|
||||||
|
| Should I use this backend? | Yes/no |
|
||||||
|
| :------------------------------------------------------------ | :----- |
|
||||||
|
| Are you running only one instance of Anubis for this service? | 🚫 No |
|
||||||
|
| Does your service get a lot of traffic? | ✅ Yes |
|
||||||
|
| Do you want to store data persistently when Anubis restarts? | ✅ Yes |
|
||||||
|
| Do you run Anubis without mutable filesystem storage? | ✅ Yes |
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
Using this backend will cause a lot of S3 operations, at least one for creating challenges, one for invalidating challenges, one for updating challenges to prevent double-spends, and one for removing challenges.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
#### Configuration
|
||||||
|
|
||||||
|
The `s3api` backend takes the following configuration options:
|
||||||
|
|
||||||
|
| Name | Type | Example | Description |
|
||||||
|
| :----------- | :------ | :------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| `bucketName` | string | The name of the dedicated bucket for Anubis to store information in. |
|
||||||
|
| `pathStyle` | boolean | `false` | If true, use path-style S3 API operations. Please consult your storage provider's documentation if you don't know what you should put here. |
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
You should probably enable a lifecycle expiration rule for buckets containing Anubis data. Here is an example policy:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Rules": [
|
||||||
|
{
|
||||||
|
"Status": "Enabled",
|
||||||
|
"Expiration": {
|
||||||
|
"Days": 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Adjust this as facts and circumstances demand, but 7 days should be enough for anyone.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
Assuming your environment looks like this:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# All of the following are fake credentials that look like real ones.
|
||||||
|
AWS_ACCESS_KEY_ID=accordingToAllKnownRulesOfAviation
|
||||||
|
AWS_SECRET_ACCESS_KEY=thereIsNoWayABeeShouldBeAbleToFly
|
||||||
|
AWS_REGION=yow
|
||||||
|
AWS_ENDPOINT_URL_S3=https://yow.s3.probably-not-malware.lol
|
||||||
|
```
|
||||||
|
|
||||||
|
Then your configuration would look like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
store:
|
||||||
|
backend: s3api
|
||||||
|
parameters:
|
||||||
|
bucketName: techaro-prod-anubis
|
||||||
|
pathStyle: false
|
||||||
|
```
|
||||||
|
|
||||||
### `valkey`
|
### `valkey`
|
||||||
|
|
||||||
[Valkey](https://valkey.io/) is an in-memory key/value store that clients access over the network. This allows multiple instances of Anubis to share information and does not require each instance of Anubis to have persistent filesystem storage.
|
[Valkey](https://valkey.io/) is an in-memory key/value store that clients access over the network. This allows multiple instances of Anubis to share information and does not require each instance of Anubis to have persistent filesystem storage.
|
||||||
|
|||||||
677
docs/package-lock.json
generated
677
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@
|
|||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "^3.8.1",
|
"@docusaurus/module-type-aliases": "^3.0.1",
|
||||||
"@docusaurus/tsconfig": "^3.8.1",
|
"@docusaurus/tsconfig": "^3.8.1",
|
||||||
"@docusaurus/types": "^3.8.1",
|
"@docusaurus/types": "^3.8.1",
|
||||||
"typescript": "~5.6.2"
|
"typescript": "~5.6.2"
|
||||||
|
|||||||
18
go.mod
18
go.mod
@@ -5,6 +5,9 @@ go 1.24.2
|
|||||||
require (
|
require (
|
||||||
github.com/TecharoHQ/thoth-proto v0.4.0
|
github.com/TecharoHQ/thoth-proto v0.4.0
|
||||||
github.com/a-h/templ v0.3.924
|
github.com/a-h/templ v0.3.924
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.38.3
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.31.6
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3
|
||||||
github.com/cespare/xxhash/v2 v2.3.0
|
github.com/cespare/xxhash/v2 v2.3.0
|
||||||
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
|
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
|
||||||
github.com/gaissmai/bart v0.23.0
|
github.com/gaissmai/bart v0.23.0
|
||||||
@@ -49,6 +52,21 @@ require (
|
|||||||
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
|
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
|
||||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 // indirect
|
||||||
|
github.com/aws/smithy-go v1.23.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||||
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
||||||
|
|||||||
36
go.sum
36
go.sum
@@ -51,6 +51,42 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW
|
|||||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.38.3 h1:B6cV4oxnMs45fql4yRH+/Po/YU+597zgWqvDpYMturk=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.38.3/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.31.6 h1:a1t8fXY4GT4xjyJExz4knbuoxSCacB5hT/WgtfPyLjo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.31.6/go.mod h1:5ByscNi7R+ztvOGzeUaIu49vkMk2soq5NaH5PYe33MQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 h1:xdJnXCouCx8Y0NncgoptztUocIYLKeQxrCgN6x9sdhg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.10/go.mod h1:7tQk08ntj914F/5i9jC4+2HQTAuJirq7m1vZVIhEkWs=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 h1:wbjnrrMnKew78/juW7I2BtKQwa1qlf6EjQgS69uYY14=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6/go.mod h1:AtiqqNrDioJXuUgz3+3T0mBWN7Hro2n9wll2zRUc0ww=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 h1:uF68eJA6+S9iVr9WgX1NaRGyQ/6MdIyc4JNUo6TN1FA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6/go.mod h1:qlPeVZCGPiobx8wb1ft0GHT5l+dc6ldnwInDFaMvC7Y=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 h1:pa1DEC6JoI0zduhZePp3zmhWvk/xxm4NB8Hy/Tlsgos=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6/go.mod h1:gxEjPebnhWGJoaDdtDkA0JX46VRg1wcTHYe63OfX5pE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6 h1:R0tNFJqfjHL3900cqhXuwQ+1K4G0xc9Yf8EDbFXCKEw=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6/go.mod h1:y/7sDdu+aJvPtGXr4xYosdpq9a6T9Z0jkXfugmti0rI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6 h1:hncKj/4gR+TPauZgTAsxOxNcvBayhUlYZ6LO/BYiQ30=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6/go.mod h1:OiIh45tp6HdJDDJGnja0mw8ihQGz3VGrUflLqSL0SmM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 h1:LHS1YAIJXJ4K9zS+1d/xa9JAA9sL2QyXIQCQFQW/X08=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6/go.mod h1:c9PCiTEuh0wQID5/KqA32J+HAgZxN9tOGXKCiYJjTZI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6 h1:nEXUSAwyUfLTgnc9cxlDWy637qsq4UWwp3sNAfl0Z3Y=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6/go.mod h1:HGzIULx4Ge3Do2V0FaiYKcyKzOqwrhUZgCI77NisswQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3 h1:ETkfWcXP2KNPLecaDa++5bsQhCRa5M5sLUJa5DWYIIg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3/go.mod h1:+/3ZTqoYb3Ur7DObD00tarKMLMuKg8iqz5CHEanqTnw=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 h1:8OLZnVJPvjnrxEwHFg9hVUof/P4sibH+Ea4KKuqAGSg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1/go.mod h1:27M3BpVi0C02UiQh1w9nsBEit6pLhlaH3NHna6WUbDE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 h1:gKWSTnqudpo8dAxqBqZnDoDWCiEh/40FziUjr/mo6uA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2/go.mod h1:x7+rkNmRoEN1U13A6JE2fXne9EWyJy54o3n6d4mGaXQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 h1:YZPjhyaGzhDQEvsffDEcpycq49nl7fiGcfJTIo8BszI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2/go.mod h1:2dIN8qhQfv37BdUYGgEC8Q3tteM3zFxTI1MLO2O3J3c=
|
||||||
|
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
|
||||||
|
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||||
|
|||||||
@@ -501,6 +501,12 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
var tokenString string
|
var tokenString string
|
||||||
|
|
||||||
// check if JWTRestrictionHeader is set and header is in request
|
// check if JWTRestrictionHeader is set and header is in request
|
||||||
|
claims := jwt.MapClaims{
|
||||||
|
"challenge": chall.ID,
|
||||||
|
"method": rule.Challenge.Algorithm,
|
||||||
|
"policyRule": rule.Hash(),
|
||||||
|
"action": string(cr.Rule),
|
||||||
|
}
|
||||||
if s.opts.JWTRestrictionHeader != "" {
|
if s.opts.JWTRestrictionHeader != "" {
|
||||||
if r.Header.Get(s.opts.JWTRestrictionHeader) == "" {
|
if r.Header.Get(s.opts.JWTRestrictionHeader) == "" {
|
||||||
lg.Error("JWTRestrictionHeader is set in config but not found in request, please check your reverse proxy config.")
|
lg.Error("JWTRestrictionHeader is set in config but not found in request, please check your reverse proxy config.")
|
||||||
@@ -508,22 +514,13 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
s.respondWithError(w, r, "failed to sign JWT")
|
s.respondWithError(w, r, "failed to sign JWT")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
tokenString, err = s.signJWT(jwt.MapClaims{
|
claims["restriction"] = internal.SHA256sum(r.Header.Get(s.opts.JWTRestrictionHeader))
|
||||||
"challenge": chall.ID,
|
|
||||||
"method": rule.Challenge.Algorithm,
|
|
||||||
"policyRule": rule.Hash(),
|
|
||||||
"action": string(cr.Rule),
|
|
||||||
"restriction": internal.SHA256sum(r.Header.Get(s.opts.JWTRestrictionHeader)),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tokenString, err = s.signJWT(jwt.MapClaims{
|
|
||||||
"challenge": chall.ID,
|
|
||||||
"method": rule.Challenge.Algorithm,
|
|
||||||
"policyRule": rule.Hash(),
|
|
||||||
"action": string(cr.Rule),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
if s.opts.DifficultyInJWT {
|
||||||
|
claims["difficulty"] = rule.Challenge.Difficulty
|
||||||
|
}
|
||||||
|
tokenString, err = s.signJWT(claims)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Error("failed to sign JWT", "err", err)
|
lg.Error("failed to sign JWT", "err", err)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func (i *Impl) Issue(r *http.Request, lg *slog.Logger, in *challenge.IssueInput)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 950 * 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) {
|
||||||
return challenge.NewError("validate", "insufficent time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339)))
|
return challenge.NewError("validate", "insufficent time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339)))
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ templ page(redir string, difficulty int, loc *localization.SimpleLocalizer) {
|
|||||||
<img style="display:none;" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version }/>
|
<img style="display:none;" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version }/>
|
||||||
<p id="status">{ loc.T("loading") }</p>
|
<p id="status">{ loc.T("loading") }</p>
|
||||||
<p>{ loc.T("connection_security") }</p>
|
<p>{ loc.T("connection_security") }</p>
|
||||||
<meta http-equiv="refresh" content={ fmt.Sprintf("%d; url=%s", difficulty, redir) }/>
|
<meta http-equiv="refresh" content={ fmt.Sprintf("%d; url=%s", difficulty+1, redir) }/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
4
lib/challenge/metarefresh/metarefresh_templ.go
generated
4
lib/challenge/metarefresh/metarefresh_templ.go
generated
@@ -93,9 +93,9 @@ func page(redir string, difficulty int, loc *localization.SimpleLocalizer) templ
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
var templ_7745c5c3_Var6 string
|
var templ_7745c5c3_Var6 string
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d; url=%s", difficulty, redir))
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d; url=%s", difficulty+1, redir))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 16, Col: 83}
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metarefresh.templ`, Line: 16, Col: 85}
|
||||||
}
|
}
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ for the JavaScript code in this page.
|
|||||||
|
|
||||||
mkdir -p static/js
|
mkdir -p static/js
|
||||||
|
|
||||||
for file in js/*.jsx; do
|
for file in js/*.tsx; do
|
||||||
filename="${file##*/}" # Extracts "app.jsx" from "./js/app.jsx"
|
filename="${file##*/}" # Extracts "app.jsx" from "./js/app.jsx"
|
||||||
output="${filename%.jsx}.js" # Changes "app.jsx" to "app.js"
|
output="${filename%.tsx}.js" # Changes "app.jsx" to "app.js"
|
||||||
echo $output
|
echo $output
|
||||||
|
|
||||||
esbuild "${file}" --minify --bundle --outfile=static/"${output}" --banner:js="${LICENSE}"
|
esbuild "${file}" --minify --bundle --outfile=static/"${output}" --banner:js="${LICENSE}"
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import { render, h, Fragment } from 'preact';
|
|
||||||
import { useState, useEffect } from 'preact/hooks';
|
|
||||||
import { g, j, u, x } from "./xeact.js";
|
|
||||||
import { Sha256 } from '@aws-crypto/sha256-js';
|
|
||||||
|
|
||||||
/** @jsx h */
|
|
||||||
/** @jsxFrag Fragment */
|
|
||||||
|
|
||||||
function toHexString(arr) {
|
|
||||||
return Array.from(arr)
|
|
||||||
.map((c) => c.toString(16).padStart(2, "0"))
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
const App = () => {
|
|
||||||
const [state, setState] = useState(null);
|
|
||||||
const [imageURL, setImageURL] = useState(null);
|
|
||||||
const [passed, setPassed] = useState(false);
|
|
||||||
const [challenge, setChallenge] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setState(j("preact_info"));
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setImageURL(state.pensive_url);
|
|
||||||
const hash = new Sha256('');
|
|
||||||
hash.update(state.challenge);
|
|
||||||
setChallenge(toHexString(hash.digestSync()));
|
|
||||||
}, [state]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
setPassed(true);
|
|
||||||
}, state.difficulty * 100);
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, [challenge]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.location.href = u(state.redir, {
|
|
||||||
result: challenge,
|
|
||||||
});
|
|
||||||
}, [passed]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{imageURL !== null && (
|
|
||||||
<img src={imageURL} style="width:100%;max-width:256px;" />
|
|
||||||
)}
|
|
||||||
{state !== null && (
|
|
||||||
<>
|
|
||||||
<p id="status">{state.loading_message}</p>
|
|
||||||
<p>{state.connection_security_message}</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
x(g("app"));
|
|
||||||
render(<App />, g("app"));
|
|
||||||
87
lib/challenge/preact/js/app.tsx
Normal file
87
lib/challenge/preact/js/app.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { render, h, Fragment } from "preact";
|
||||||
|
import { useState, useEffect } from "preact/hooks";
|
||||||
|
import { g, j, r, u, x } from "./xeact.js";
|
||||||
|
import { Sha256 } from "@aws-crypto/sha256-js";
|
||||||
|
|
||||||
|
/** @jsx h */
|
||||||
|
/** @jsxFrag Fragment */
|
||||||
|
|
||||||
|
function toHexString(arr: Uint8Array) {
|
||||||
|
return Array.from(arr)
|
||||||
|
.map((c) => c.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PreactInfo {
|
||||||
|
redir: string;
|
||||||
|
challenge: string;
|
||||||
|
difficulty: number;
|
||||||
|
connection_security_message: string;
|
||||||
|
loading_message: string;
|
||||||
|
pensive_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const [state, setState] = useState<PreactInfo>();
|
||||||
|
const [imageURL, setImageURL] = useState<string | null>(null);
|
||||||
|
const [passed, setPassed] = useState<boolean>(false);
|
||||||
|
const [challenge, setChallenge] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setState(j("preact_info"));
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImageURL(state?.pensive_url);
|
||||||
|
const hash = new Sha256("");
|
||||||
|
hash.update(state.challenge);
|
||||||
|
setChallenge(toHexString(hash.digestSync()));
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setPassed(true);
|
||||||
|
}, state?.difficulty * 125);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [challenge]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (challenge === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = u(state.redir, {
|
||||||
|
result: challenge,
|
||||||
|
});
|
||||||
|
}, [passed]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{imageURL !== null && (
|
||||||
|
<img src={imageURL} style={{ width: "100%", maxWidth: "256px" }} />
|
||||||
|
)}
|
||||||
|
{state !== undefined && (
|
||||||
|
<>
|
||||||
|
<p id="status">{state.loading_message}</p>
|
||||||
|
<p>{state.connection_security_message}</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
x(g("app"));
|
||||||
|
render(<App />, g("app"));
|
||||||
@@ -57,7 +57,7 @@ func (i *impl) Issue(r *http.Request, lg *slog.Logger, in *challenge.IssueInput)
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 95 * 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) {
|
||||||
return challenge.NewError("validate", "insufficent time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339)))
|
return challenge.NewError("validate", "insufficent time", fmt.Errorf("%w: wanted user to wait until at least %s", challenge.ErrFailed, wantTime.Format(time.RFC3339)))
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ type Options struct {
|
|||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
PublicUrl string
|
PublicUrl string
|
||||||
JWTRestrictionHeader string
|
JWTRestrictionHeader string
|
||||||
|
DifficultyInJWT bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
|
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
|
||||||
|
|||||||
@@ -62,5 +62,6 @@
|
|||||||
"js_iterations": "iteracijų",
|
"js_iterations": "iteracijų",
|
||||||
"js_finished_reading": "Viską perskaičiau, tęskime →",
|
"js_finished_reading": "Viską perskaičiau, tęskime →",
|
||||||
"js_calculation_error": "Skaičiavimo klaida!",
|
"js_calculation_error": "Skaičiavimo klaida!",
|
||||||
"js_calculation_error_msg": "Nepavyko įveikti iššūkio:"
|
"js_calculation_error_msg": "Nepavyko įveikti iššūkio:",
|
||||||
|
"missing_required_forwarded_headers": "Trūksta privalomų X-Forwarded-* antraščių"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ package all
|
|||||||
import (
|
import (
|
||||||
_ "github.com/TecharoHQ/anubis/lib/store/bbolt"
|
_ "github.com/TecharoHQ/anubis/lib/store/bbolt"
|
||||||
_ "github.com/TecharoHQ/anubis/lib/store/memory"
|
_ "github.com/TecharoHQ/anubis/lib/store/memory"
|
||||||
|
_ "github.com/TecharoHQ/anubis/lib/store/s3api"
|
||||||
_ "github.com/TecharoHQ/anubis/lib/store/valkey"
|
_ "github.com/TecharoHQ/anubis/lib/store/valkey"
|
||||||
)
|
)
|
||||||
|
|||||||
107
lib/store/s3api/factory.go
Normal file
107
lib/store/s3api/factory.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package s3api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/TecharoHQ/anubis/lib/store"
|
||||||
|
awsConfig "github.com/aws/aws-sdk-go-v2/config"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoRegion = errors.New("s3api.Config: no region env var name defined")
|
||||||
|
ErrNoAccessKeyID = errors.New("s3api.Config: no access key id env var name defined")
|
||||||
|
ErrNoSecretAccessKey = errors.New("s3api.Config: no secret access key env var name defined")
|
||||||
|
ErrNoBucketName = errors.New("s3api.Config: no bucket name env var name defined")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
store.Register("s3api", Factory{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// S3API is the subset of the AWS S3 client used by this store. It enables mocking in tests.
|
||||||
|
type S3API interface {
|
||||||
|
PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
|
||||||
|
GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error)
|
||||||
|
DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error)
|
||||||
|
HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factory builds an S3-backed store. Tests can inject a Mock via Client.
|
||||||
|
// Factory can optionally carry a preconstructed S3 client (e.g., a mock in tests).
|
||||||
|
type Factory struct {
|
||||||
|
Client S3API
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Factory) Build(ctx context.Context, data json.RawMessage) (store.Interface, error) {
|
||||||
|
var config Config
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(data), &config); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %w", store.ErrBadConfig, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.Valid(); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %w", store.ErrBadConfig, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.BucketName == "" {
|
||||||
|
return nil, fmt.Errorf("%w: %s", store.ErrBadConfig, ErrNoBucketName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a client was injected (e.g., tests), use it directly.
|
||||||
|
if f.Client != nil {
|
||||||
|
return &Store{
|
||||||
|
s3: f.Client,
|
||||||
|
bucket: config.BucketName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := awsConfig.LoadDefaultConfig(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't load AWS config from environment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := s3.NewFromConfig(cfg, func(o *s3.Options) {
|
||||||
|
o.UsePathStyle = config.PathStyle
|
||||||
|
})
|
||||||
|
|
||||||
|
return &Store{
|
||||||
|
s3: client,
|
||||||
|
bucket: config.BucketName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Factory) Valid(data json.RawMessage) error {
|
||||||
|
var config Config
|
||||||
|
if err := json.Unmarshal([]byte(data), &config); err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", store.ErrBadConfig, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.Valid(); err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", store.ErrBadConfig, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
PathStyle bool `json:"pathStyle"`
|
||||||
|
BucketName string `json:"bucketName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) Valid() error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
if c.BucketName == "" {
|
||||||
|
errs = append(errs, ErrNoBucketName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return fmt.Errorf("s3api.Config: invalid config: %w", errors.Join(errs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
78
lib/store/s3api/s3api.go
Normal file
78
lib/store/s3api/s3api.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package s3api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TecharoHQ/anubis/lib/store"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Store struct {
|
||||||
|
s3 S3API
|
||||||
|
bucket string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Delete(ctx context.Context, key string) error {
|
||||||
|
normKey := strings.ReplaceAll(key, ":", "/")
|
||||||
|
// Emulate not found by probing first.
|
||||||
|
if _, err := s.s3.HeadObject(ctx, &s3.HeadObjectInput{Bucket: &s.bucket, Key: &normKey}); err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", store.ErrNotFound, err)
|
||||||
|
}
|
||||||
|
if _, err := s.s3.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: &s.bucket, Key: &normKey}); err != nil {
|
||||||
|
return fmt.Errorf("can't delete from s3: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Get(ctx context.Context, key string) ([]byte, error) {
|
||||||
|
normKey := strings.ReplaceAll(key, ":", "/")
|
||||||
|
out, err := s.s3.GetObject(ctx, &s3.GetObjectInput{
|
||||||
|
Bucket: &s.bucket,
|
||||||
|
Key: &normKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %w", store.ErrNotFound, err)
|
||||||
|
}
|
||||||
|
defer out.Body.Close()
|
||||||
|
if msStr, ok := out.Metadata["x-anubis-expiry-ms"]; ok && msStr != "" {
|
||||||
|
if ms, err := strconv.ParseInt(msStr, 10, 64); err == nil {
|
||||||
|
if time.Now().UnixMilli() >= ms {
|
||||||
|
_, _ = s.s3.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: &s.bucket, Key: &normKey})
|
||||||
|
return nil, store.ErrNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(out.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't read s3 object: %w", err)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Set(ctx context.Context, key string, value []byte, expiry time.Duration) error {
|
||||||
|
normKey := strings.ReplaceAll(key, ":", "/")
|
||||||
|
// S3 has no native TTL; we store object with metadata X-Anubis-Expiry as epoch seconds.
|
||||||
|
var meta map[string]string
|
||||||
|
if expiry > 0 {
|
||||||
|
exp := time.Now().Add(expiry).UnixMilli()
|
||||||
|
meta = map[string]string{"x-anubis-expiry-ms": fmt.Sprintf("%d", exp)}
|
||||||
|
}
|
||||||
|
_, err := s.s3.PutObject(ctx, &s3.PutObjectInput{
|
||||||
|
Bucket: &s.bucket,
|
||||||
|
Key: &normKey,
|
||||||
|
Body: bytes.NewReader(value),
|
||||||
|
Metadata: meta,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't put s3 object: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Store) IsPersistent() bool { return true }
|
||||||
140
lib/store/s3api/s3api_test.go
Normal file
140
lib/store/s3api/s3api_test.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package s3api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TecharoHQ/anubis/lib/store/storetest"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mockS3 is an in-memory mock of the methods we use.
|
||||||
|
type mockS3 struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
bucket string
|
||||||
|
data map[string][]byte
|
||||||
|
meta map[string]map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockS3) PutObject(ctx context.Context, in *s3.PutObjectInput, _ ...func(*s3.Options)) (*s3.PutObjectOutput, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
if m.data == nil {
|
||||||
|
m.data = map[string][]byte{}
|
||||||
|
}
|
||||||
|
if m.meta == nil {
|
||||||
|
m.meta = map[string]map[string]string{}
|
||||||
|
}
|
||||||
|
b, _ := io.ReadAll(in.Body)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.bucket = aws.ToString(in.Bucket)
|
||||||
|
return &s3.PutObjectOutput{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockS3) GetObject(ctx context.Context, in *s3.GetObjectInput, _ ...func(*s3.Options)) (*s3.GetObjectOutput, error) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
b, ok := m.data[aws.ToString(in.Key)]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("not found")
|
||||||
|
}
|
||||||
|
out := &s3.GetObjectOutput{Body: io.NopCloser(bytes.NewReader(b))}
|
||||||
|
if md, ok := m.meta[aws.ToString(in.Key)]; ok {
|
||||||
|
out.Metadata = md
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockS3) DeleteObject(ctx context.Context, in *s3.DeleteObjectInput, _ ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
delete(m.data, aws.ToString(in.Key))
|
||||||
|
delete(m.meta, aws.ToString(in.Key))
|
||||||
|
return &s3.DeleteObjectOutput{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockS3) HeadObject(ctx context.Context, in *s3.HeadObjectInput, _ ...func(*s3.Options)) (*s3.HeadObjectOutput, error) {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
if _, ok := m.data[aws.ToString(in.Key)]; !ok {
|
||||||
|
return nil, fmt.Errorf("not found")
|
||||||
|
}
|
||||||
|
return &s3.HeadObjectOutput{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImpl(t *testing.T) {
|
||||||
|
mock := &mockS3{}
|
||||||
|
f := Factory{Client: mock}
|
||||||
|
|
||||||
|
data, _ := json.Marshal(Config{
|
||||||
|
BucketName: "bucket",
|
||||||
|
})
|
||||||
|
|
||||||
|
storetest.Common(t, f, json.RawMessage(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyNormalization(t *testing.T) {
|
||||||
|
mock := &mockS3{}
|
||||||
|
f := Factory{Client: mock}
|
||||||
|
|
||||||
|
data, _ := json.Marshal(Config{
|
||||||
|
BucketName: "anubis",
|
||||||
|
})
|
||||||
|
|
||||||
|
s, err := f.Build(t.Context(), json.RawMessage(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := "a:b:c"
|
||||||
|
val := []byte("value")
|
||||||
|
if err := s.Set(t.Context(), key, val, 0); err != nil {
|
||||||
|
t.Fatalf("Set failed: %v", err)
|
||||||
|
}
|
||||||
|
// Ensure mock saw normalized key
|
||||||
|
mock.mu.RLock()
|
||||||
|
_, hasRaw := mock.data["a:b:c"]
|
||||||
|
got, hasNorm := mock.data["a/b/c"]
|
||||||
|
mock.mu.RUnlock()
|
||||||
|
if hasRaw {
|
||||||
|
t.Fatalf("mock contains raw key with colon; normalization failed")
|
||||||
|
}
|
||||||
|
if !hasNorm || !bytes.Equal(got, val) {
|
||||||
|
t.Fatalf("normalized key missing or wrong value: got=%q", string(got))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get using colon key should work
|
||||||
|
out, err := s.Get(t.Context(), key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Get failed: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(out, val) {
|
||||||
|
t.Fatalf("Get returned wrong value: got=%q", string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete using colon key should delete normalized object
|
||||||
|
if err := s.Delete(t.Context(), key); err != nil {
|
||||||
|
t.Fatalf("Delete failed: %v", err)
|
||||||
|
}
|
||||||
|
// Give any async cleanup in tests a tick (not needed for mock, but harmless)
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
mock.mu.RLock()
|
||||||
|
_, exists := mock.data["a/b/c"]
|
||||||
|
mock.mu.RUnlock()
|
||||||
|
if exists {
|
||||||
|
t.Fatalf("normalized key still exists after Delete")
|
||||||
|
}
|
||||||
|
}
|
||||||
710
package-lock.json
generated
710
package-lock.json
generated
@@ -19,7 +19,7 @@
|
|||||||
"playwright": "^1.52.0",
|
"playwright": "^1.52.0",
|
||||||
"postcss-cli": "^11.0.1",
|
"postcss-cli": "^11.0.1",
|
||||||
"postcss-import": "^16.1.1",
|
"postcss-import": "^16.1.1",
|
||||||
"postcss-import-url": "^7.2.0",
|
"postcss-import-url": "^1.0.0",
|
||||||
"postcss-url": "^10.1.3"
|
"postcss-url": "^10.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -553,6 +553,13 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/abbrev": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/ansi-regex": {
|
"node_modules/ansi-regex": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
@@ -651,6 +658,13 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bluebird": {
|
||||||
|
"version": "3.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||||
|
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/boolbase": {
|
"node_modules/boolbase": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
@@ -774,6 +788,18 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cli": {
|
||||||
|
"version": "0.4.4-2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cli/-/cli-0.4.4-2.tgz",
|
||||||
|
"integrity": "sha512-zvFHTz+T8S4gejPHNVtdqc0mDnWmZcwd5juDF4ScZkPerNdl/9aiWcBv3l57v81jzq+n89eYLkRJdvc5aWJROA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"glob": ">= 3.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cliui": {
|
"node_modules/cliui": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
@@ -833,6 +859,17 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/config-chain": {
|
||||||
|
"version": "1.1.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
|
||||||
|
"integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ini": "^1.3.4",
|
||||||
|
"proto-list": "~1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css-declaration-sorter": {
|
"node_modules/css-declaration-sorter": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz",
|
||||||
@@ -1047,6 +1084,23 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/deep-equal": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-rUCt39nKM7s6qUyYgp/reJmtXjgkOS/JbLO24DioMZaBNkD3b7C7cD3zJjSyjclEElNTpetAIRD6fMIbBIbX1Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/dependency-graph": {
|
"node_modules/dependency-graph": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz",
|
||||||
@@ -1057,6 +1111,15 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/diff": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-0bTLzyr1S59cPsgAD/lR+ivvHTbgPb+k/mUR6WGqma1J6QDU+kUegI8uQFuH/cMUNK7JGN3Tk1Y5Jf2MO85WrA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dom-serializer": {
|
"node_modules/dom-serializer": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
@@ -1195,6 +1258,16 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/escape-string-regexp": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -1222,10 +1295,19 @@
|
|||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fresh": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-ROG9M8tikYOuOJsvRBggh10WiQ/JebnldAwuCaQyFoiAUIE9XrYVnpznIjOQGZfCMzxzEBYHQr/LHJp3tcndzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "11.3.0",
|
"version": "11.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz",
|
||||||
"integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
|
"integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1237,6 +1319,13 @@
|
|||||||
"node": ">=14.14"
|
"node": ">=14.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs.realpath": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
@@ -1272,6 +1361,28 @@
|
|||||||
"node": "6.* || 8.* || >= 10.*"
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/glob": {
|
||||||
|
"version": "7.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"inflight": "^1.0.4",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "^3.1.1",
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"path-is-absolute": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
@@ -1285,6 +1396,19 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/glob/node_modules/minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/graceful-fs": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
@@ -1292,6 +1416,35 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/growl": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/growl/-/growl-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-VWv7s1EI41AG2LiCr7uAuxWikLDN1SQOuEUc37d/P34NAIIYgkvWYngNw0d9d9iCrDFL0SYCE9UQpxhIjjtuLg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/has-ansi": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-ansi/node_modules/ansi-regex": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
@@ -1312,6 +1465,32 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/inflight": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||||
|
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.3.0",
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/ini": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/is-binary-path": {
|
"node_modules/is-binary-path": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
@@ -1391,10 +1570,68 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/jade": {
|
||||||
|
"version": "0.26.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz",
|
||||||
|
"integrity": "sha512-mkk3vzUHFjzKjpCXeu+IjXeZD+QOTjUUdubgmHtHTDwvAO2ZTkMTTVrapts5CWz3JvJryh/4KWZpjeZrCepZ3A==",
|
||||||
|
"deprecated": "Jade has been renamed to pug, please install the latest version of pug instead of jade",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "0.6.1",
|
||||||
|
"mkdirp": "0.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"jade": "bin/jade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jade/node_modules/commander": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-0fLycpl1UMTGX257hRsu/arL/cUbcvQM4zMKwvLvzXtfdezIV4yotPS2dYtknF+NmEfWSoCEF6+hj9XLm/6hEw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jade/node_modules/mkdirp": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==",
|
||||||
|
"deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT/X11",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/js-base64": {
|
||||||
|
"version": "2.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
|
||||||
|
"integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/js-beautify": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-0o7oku1AcG66QoDIoSLCBENbyFgV6WHoqnZhC8oL4URTWYDzIXWo3tTGTLrLh6jR91miKS5YC+WBZeYC5iZMQg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"config-chain": "~1.1.5",
|
||||||
|
"mkdirp": "0.3.5",
|
||||||
|
"nopt": "~2.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"css-beautify": "js/bin/css-beautify.js",
|
||||||
|
"html-beautify": "js/bin/html-beautify.js",
|
||||||
|
"js-beautify": "js/bin/js-beautify.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsonfile": {
|
"node_modules/jsonfile": {
|
||||||
"version": "6.1.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1417,13 +1654,6 @@
|
|||||||
"url": "https://github.com/sponsors/antonk52"
|
"url": "https://github.com/sponsors/antonk52"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash.assign": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.memoize": {
|
"node_modules/lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
@@ -1431,13 +1661,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.trim": {
|
|
||||||
"version": "4.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.trim/-/lodash.trim-4.5.1.tgz",
|
|
||||||
"integrity": "sha512-nJAlRl/K+eiOehWKDzoBVrSMhK0K3A3YQsUNXHQa5yIrKBAhsZgSu3KoAFoFT+mEgiyBHddZ0pRk1ITpIp90Wg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.uniq": {
|
"node_modules/lodash.uniq": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
||||||
@@ -1445,6 +1668,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lru-cache": {
|
||||||
|
"version": "2.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz",
|
||||||
|
"integrity": "sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/make-dir": {
|
"node_modules/make-dir": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||||
@@ -1494,6 +1724,95 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mkdirp": {
|
||||||
|
"version": "0.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
|
||||||
|
"integrity": "sha512-8OCq0De/h9ZxseqzCH8Kw/Filf5pF/vMI6+BH7Lu0jXz2pqYCjTAQRolSxRIi+Ax+oCCjlxoJMP0YQ4XlrQNHg==",
|
||||||
|
"deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/mocha": {
|
||||||
|
"version": "1.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mocha/-/mocha-1.17.0.tgz",
|
||||||
|
"integrity": "sha512-Bmjo5ZIr+RcxCKRLFpE7tpGiYemqCkWNVBx31seyUv+c45MahZcBBcoRN33yMhvOBmiq0ABhpENk19WtM3BcOw==",
|
||||||
|
"deprecated": "Mocha v1.x is no longer supported.",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "2.0.0",
|
||||||
|
"debug": "*",
|
||||||
|
"diff": "1.0.7",
|
||||||
|
"glob": "3.2.3",
|
||||||
|
"growl": "1.7.x",
|
||||||
|
"jade": "0.26.3",
|
||||||
|
"mkdirp": "0.3.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"_mocha": "bin/_mocha",
|
||||||
|
"mocha": "bin/mocha"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mocha/node_modules/commander": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-qebjpyeaA/nJ4w3EO2cV2++/zEkccPnjWogzA2rff+Lk8ILI75vULeTmyd4wPxWdKwtP3J+G39IXVZadh0UHyw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mocha/node_modules/glob": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-WPaLsMHD1lYEqAmIQI6VOJSPwuBdGShDWnj1yUo0vQqEO809R8W3LM9OVU13CnnDhyv/EiNwOtxEW74SmrzS6w==",
|
||||||
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "~2.0.0",
|
||||||
|
"inherits": "2",
|
||||||
|
"minimatch": "~0.2.11"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mocha/node_modules/graceful-fs": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-hcj/NTUWv+C3MbqrVb9F+aH6lvTwEHJdx2foBxlrVq5h6zE8Bfu4pv4CAAqbDcZrw/9Ak5lsRXlY9Ao8/F0Tuw==",
|
||||||
|
"deprecated": "please upgrade to graceful-fs 4 for compatibility with current and future versions of Node.js",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mocha/node_modules/minimatch": {
|
||||||
|
"version": "0.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
|
||||||
|
"integrity": "sha512-zZ+Jy8lVWlvqqeM8iZB7w7KmQkoJn8djM585z88rywrEbzoqawVa9FR5p2hwD+y74nfuKOjmNvi9gtWJNLqHvA==",
|
||||||
|
"deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "2",
|
||||||
|
"sigmund": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
@@ -1521,6 +1840,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nopt": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-x8vXm7BZ2jE1Txrxh/hO74HTuYZQEbo8edoRcANgdZ4+PCV+pbjd/xdummkmjjC7LU5EjPzlu8zEq/oxWylnKA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"abbrev": "1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nopt": "bin/nopt.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
@@ -1554,6 +1886,26 @@
|
|||||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-is-absolute": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-parse": {
|
"node_modules/path-parse": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
@@ -1561,6 +1913,83 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/phpfn": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/phpfn/-/phpfn-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-hwl2fXpXDOHCOlFDkNYXwD4FUKsddgXooF7Cb8eyynt82Ej9DLVfL6P/2d6L0uQghJq1X6DUnTd0rKM3yC8oOw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"phpjs": "latest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/phpjs": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/phpjs/-/phpjs-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-S/V298ABWBDLsWgssVl91JmexMvTmmBR4oufeHvQU3W63+xOBluVtbVEoMyxv6ZdFuj/fx6BXe/WC6gWnO+lig==",
|
||||||
|
"deprecated": "phpjs is no longer maintained. Please use Locutus instead: https://locutus.io",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"cli": "0.4.4-2",
|
||||||
|
"deep-equal": "0.1.2",
|
||||||
|
"glob": "3.2.1",
|
||||||
|
"js-beautify": "1.4.2",
|
||||||
|
"mocha": "1.17.0",
|
||||||
|
"send": "0.1.0",
|
||||||
|
"underscore": "1.5.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/phpjs/node_modules/glob": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-wvxQZUqjkvW//FJMr/DCmPlAOFcrmf2ojnUddQTdgAQ5XkKL8ILfob0Rz+Ch/fSiols6EtiHRJS3i9W0kBRZmQ==",
|
||||||
|
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "~1.2.0",
|
||||||
|
"inherits": "1",
|
||||||
|
"minimatch": "~0.2.11"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/phpjs/node_modules/graceful-fs": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-iiTUZ5vZ+2ZV+h71XAgwCSu6+NAizhFU3Yw8aC/hH5SQ3SnISqEqAek40imAFGtDcwJKNhXvSY+hzIolnLwcdQ==",
|
||||||
|
"deprecated": "please upgrade to graceful-fs 4 for compatibility with current and future versions of Node.js",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/phpjs/node_modules/inherits": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Al67oatbRSo3RV5hRqIoln6Y5yMVbJSIn4jEJNL7VCImzq/kLr7vvb6sFRJXqr8rpHc/2kJOM+y0sPKN47VdzA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/phpjs/node_modules/minimatch": {
|
||||||
|
"version": "0.2.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
|
||||||
|
"integrity": "sha512-zZ+Jy8lVWlvqqeM8iZB7w7KmQkoJn8djM585z88rywrEbzoqawVa9FR5p2hwD+y74nfuKOjmNvi9gtWJNLqHvA==",
|
||||||
|
"deprecated": "Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "2",
|
||||||
|
"sigmund": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -1825,23 +2254,116 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-import-url": {
|
"node_modules/postcss-import-url": {
|
||||||
"version": "7.2.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-import-url/-/postcss-import-url-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-import-url/-/postcss-import-url-1.0.0.tgz",
|
||||||
"integrity": "sha512-El61K/5+Rv753G9mBiHyQlOIN2mBfN0YHPMXLlgIo/m1+tPDLM32wd97WoUjc8FHUnC6EyyfVA8RDuKoyuVl0Q==",
|
"integrity": "sha512-sXZVBws7VJZDc3P60oTI/7hR5I5EZnjIrmm9QFQY6iwhdmRHi4o9deYoAcnV6jaKrPzzaqO8VGrxf6X2yxUfHQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Beerware",
|
||||||
|
"dependencies": {
|
||||||
|
"bluebird": "^3.0.2",
|
||||||
|
"http-https": "^1.0.0",
|
||||||
|
"is-url": "^1.2.1",
|
||||||
|
"phpfn": "^1.0.0",
|
||||||
|
"postcss": "^5.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-import-url/node_modules/ansi-regex": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-import-url/node_modules/ansi-styles": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-import-url/node_modules/chalk": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"http-https": "^1.0.0",
|
"ansi-styles": "^2.2.1",
|
||||||
"is-url": "^1.2.4",
|
"escape-string-regexp": "^1.0.2",
|
||||||
"lodash.assign": "^4.2.0",
|
"has-ansi": "^2.0.0",
|
||||||
"lodash.trim": "^4.5.1",
|
"strip-ansi": "^3.0.0",
|
||||||
"resolve-relative-url": "^1.0.0"
|
"supports-color": "^2.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-import-url/node_modules/chalk/node_modules/supports-color": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-import-url/node_modules/has-flag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-import-url/node_modules/postcss": {
|
||||||
|
"version": "5.2.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz",
|
||||||
|
"integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^1.1.3",
|
||||||
|
"js-base64": "^2.1.9",
|
||||||
|
"source-map": "^0.5.6",
|
||||||
|
"supports-color": "^3.2.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"engines": {
|
||||||
"postcss": "^8.0.0"
|
"node": ">=0.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-import-url/node_modules/strip-ansi": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-import-url/node_modules/supports-color": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss-load-config": {
|
"node_modules/postcss-load-config": {
|
||||||
@@ -2347,21 +2869,20 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/proto-list": {
|
||||||
"version": "1.3.2",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
||||||
"integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==",
|
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/querystring": {
|
"node_modules/range-parser": {
|
||||||
"version": "0.2.0",
|
"version": "0.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-0.0.4.tgz",
|
||||||
"integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==",
|
"integrity": "sha512-okJVEq9DbZyg+5lD8pr6ooQmeA0uu8DYIyAU7VK1WUUK7hctI1yw2ZHhKiKjB6RXaDrYRmTR4SsIHkyiQpaLMA==",
|
||||||
"deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
|
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.x"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
@@ -2418,16 +2939,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/resolve-relative-url": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/resolve-relative-url/-/resolve-relative-url-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-zpcelQBAmrwckiyRmym9os1goECU3EzuTU/UrYkGzXV0i14n8FkyGUvwkOYA5klqVLq1Hz/EiFZMS7bZQdd+EA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"url": "0.10.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/sax": {
|
"node_modules/sax": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||||
@@ -2445,6 +2956,34 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/send": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/send/-/send-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-D/GaJQQYx7ICNq9Te5V4wHetfDQdFk3HJ4oBfDUBNW7XQmLbJ8sQDm/wFvVUUpKN8tluOnO1dFdM8KODn6D79w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "*",
|
||||||
|
"fresh": "0.1.0",
|
||||||
|
"mime": "1.2.6",
|
||||||
|
"range-parser": "0.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/send/node_modules/mime": {
|
||||||
|
"version": "1.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.6.tgz",
|
||||||
|
"integrity": "sha512-S4yfg1ehMduQ5F3NeTUUWJesnut4RvymaRSatO4etOm68yZE98oCg2GtgG0coGYx03GCv240sezMvRwFk8DUKw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sigmund": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/slash": {
|
"node_modules/slash": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
|
||||||
@@ -2458,6 +2997,16 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
|
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -2560,14 +3109,14 @@
|
|||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.14",
|
"version": "0.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||||
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
|
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.5.0",
|
||||||
"picomatch": "^4.0.2"
|
"picomatch": "^4.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
@@ -2577,11 +3126,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tinyglobby/node_modules/fdir": {
|
"node_modules/tinyglobby/node_modules/fdir": {
|
||||||
"version": "6.4.6",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
|
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"picomatch": "^3 || ^4"
|
"picomatch": "^3 || ^4"
|
||||||
},
|
},
|
||||||
@@ -2592,9 +3144,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2623,6 +3175,12 @@
|
|||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
|
"node_modules/underscore": {
|
||||||
|
"version": "1.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.5.2.tgz",
|
||||||
|
"integrity": "sha512-yejOFsRnTJs0N9CK5Apzf6maDO2djxGoLLrlZlvGs2o9ZQuhIhDL18rtFyy4FBIbOkzA6+4hDgXbgz5EvDQCXQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||||
@@ -2664,17 +3222,6 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/url": {
|
|
||||||
"version": "0.10.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
|
|
||||||
"integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"punycode": "1.3.2",
|
|
||||||
"querystring": "0.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
@@ -2700,6 +3247,13 @@
|
|||||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/xxhashjs": {
|
"node_modules/xxhashjs": {
|
||||||
"version": "0.2.2",
|
"version": "0.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
|
||||||
@@ -2721,9 +3275,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
||||||
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
|
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"playwright": "^1.52.0",
|
"playwright": "^1.52.0",
|
||||||
"postcss-cli": "^11.0.1",
|
"postcss-cli": "^11.0.1",
|
||||||
"postcss-import": "^16.1.1",
|
"postcss-import": "^16.1.1",
|
||||||
"postcss-import-url": "^7.2.0",
|
"postcss-import-url": "^1.0.0",
|
||||||
"postcss-url": "^10.1.3"
|
"postcss-url": "^10.1.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
20
test/go.mod
20
test/go.mod
@@ -5,7 +5,7 @@ go 1.24.5
|
|||||||
replace github.com/TecharoHQ/anubis => ..
|
replace github.com/TecharoHQ/anubis => ..
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/TecharoHQ/anubis v1.21.3
|
github.com/TecharoHQ/anubis v1.22.0
|
||||||
github.com/docker/docker v28.3.2+incompatible
|
github.com/docker/docker v28.3.2+incompatible
|
||||||
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
|
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
@@ -18,6 +18,24 @@ require (
|
|||||||
github.com/TecharoHQ/thoth-proto v0.4.0 // indirect
|
github.com/TecharoHQ/thoth-proto v0.4.0 // indirect
|
||||||
github.com/a-h/templ v0.3.924 // indirect
|
github.com/a-h/templ v0.3.924 // indirect
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.31.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 // indirect
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 // indirect
|
||||||
|
github.com/aws/smithy-go v1.23.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/containerd/errdefs v1.0.0 // indirect
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
|
|||||||
36
test/go.sum
36
test/go.sum
@@ -16,6 +16,42 @@ github.com/a-h/templ v0.3.924 h1:t5gZqTneXqvehpNZsgtnlOscnBboNh9aASBH2MgV/0k=
|
|||||||
github.com/a-h/templ v0.3.924/go.mod h1:FFAu4dI//ESmEN7PQkJ7E7QfnSEMdcnu7QrAY8Dn334=
|
github.com/a-h/templ v0.3.924/go.mod h1:FFAu4dI//ESmEN7PQkJ7E7QfnSEMdcnu7QrAY8Dn334=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.38.3 h1:B6cV4oxnMs45fql4yRH+/Po/YU+597zgWqvDpYMturk=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.38.3/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
|
||||||
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.31.6 h1:a1t8fXY4GT4xjyJExz4knbuoxSCacB5hT/WgtfPyLjo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.31.6/go.mod h1:5ByscNi7R+ztvOGzeUaIu49vkMk2soq5NaH5PYe33MQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 h1:xdJnXCouCx8Y0NncgoptztUocIYLKeQxrCgN6x9sdhg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/credentials v1.18.10/go.mod h1:7tQk08ntj914F/5i9jC4+2HQTAuJirq7m1vZVIhEkWs=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 h1:wbjnrrMnKew78/juW7I2BtKQwa1qlf6EjQgS69uYY14=
|
||||||
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6/go.mod h1:AtiqqNrDioJXuUgz3+3T0mBWN7Hro2n9wll2zRUc0ww=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 h1:uF68eJA6+S9iVr9WgX1NaRGyQ/6MdIyc4JNUo6TN1FA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6/go.mod h1:qlPeVZCGPiobx8wb1ft0GHT5l+dc6ldnwInDFaMvC7Y=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 h1:pa1DEC6JoI0zduhZePp3zmhWvk/xxm4NB8Hy/Tlsgos=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6/go.mod h1:gxEjPebnhWGJoaDdtDkA0JX46VRg1wcTHYe63OfX5pE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6 h1:R0tNFJqfjHL3900cqhXuwQ+1K4G0xc9Yf8EDbFXCKEw=
|
||||||
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6/go.mod h1:y/7sDdu+aJvPtGXr4xYosdpq9a6T9Z0jkXfugmti0rI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6 h1:hncKj/4gR+TPauZgTAsxOxNcvBayhUlYZ6LO/BYiQ30=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6/go.mod h1:OiIh45tp6HdJDDJGnja0mw8ihQGz3VGrUflLqSL0SmM=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 h1:LHS1YAIJXJ4K9zS+1d/xa9JAA9sL2QyXIQCQFQW/X08=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6/go.mod h1:c9PCiTEuh0wQID5/KqA32J+HAgZxN9tOGXKCiYJjTZI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6 h1:nEXUSAwyUfLTgnc9cxlDWy637qsq4UWwp3sNAfl0Z3Y=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6/go.mod h1:HGzIULx4Ge3Do2V0FaiYKcyKzOqwrhUZgCI77NisswQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3 h1:ETkfWcXP2KNPLecaDa++5bsQhCRa5M5sLUJa5DWYIIg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3/go.mod h1:+/3ZTqoYb3Ur7DObD00tarKMLMuKg8iqz5CHEanqTnw=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 h1:8OLZnVJPvjnrxEwHFg9hVUof/P4sibH+Ea4KKuqAGSg=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1/go.mod h1:27M3BpVi0C02UiQh1w9nsBEit6pLhlaH3NHna6WUbDE=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 h1:gKWSTnqudpo8dAxqBqZnDoDWCiEh/40FziUjr/mo6uA=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2/go.mod h1:x7+rkNmRoEN1U13A6JE2fXne9EWyJy54o3n6d4mGaXQ=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 h1:YZPjhyaGzhDQEvsffDEcpycq49nl7fiGcfJTIo8BszI=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2/go.mod h1:2dIN8qhQfv37BdUYGgEC8Q3tteM3zFxTI1MLO2O3J3c=
|
||||||
|
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
|
||||||
|
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
|||||||
19
web/build.sh
19
web/build.sh
@@ -39,9 +39,18 @@ for the JavaScript code in this page.
|
|||||||
mkdir -p static/locales
|
mkdir -p static/locales
|
||||||
cp ../lib/localization/locales/*.json static/locales/
|
cp ../lib/localization/locales/*.json static/locales/
|
||||||
|
|
||||||
for file in js/*.mjs js/worker/*.mjs; do
|
shopt -s nullglob globstar
|
||||||
esbuild "${file}" --sourcemap --bundle --minify --outfile=static/"${file}" --banner:js="${LICENSE}"
|
|
||||||
gzip -f -k -n static/${file}
|
for file in js/**/*.ts js/**/*.mjs; do
|
||||||
zstd -f -k --ultra -22 static/${file}
|
out="static/${file}"
|
||||||
brotli -fZk static/${file}
|
if [[ "$file" == *.ts ]]; then
|
||||||
|
out="static/${file%.ts}.mjs"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$out")"
|
||||||
|
|
||||||
|
esbuild "$file" --sourcemap --bundle --minify --outfile="$out" --banner:js="$LICENSE"
|
||||||
|
gzip -f -k -n "$out"
|
||||||
|
zstd -f -k --ultra -22 "$out"
|
||||||
|
brotli -fZk "$out"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
|
type ProgressCallback = (nonce: number) => void;
|
||||||
|
|
||||||
|
interface ProcessOptions {
|
||||||
|
basePrefix: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getHardwareConcurrency = () =>
|
||||||
|
navigator.hardwareConcurrency !== undefined ? navigator.hardwareConcurrency : 1;
|
||||||
|
|
||||||
export default function process(
|
export default function process(
|
||||||
{ basePrefix, version },
|
options: ProcessOptions,
|
||||||
data,
|
data: string,
|
||||||
difficulty = 5,
|
difficulty: number = 5,
|
||||||
signal = null,
|
signal: AbortSignal | null = null,
|
||||||
progressCallback = null,
|
progressCallback?: ProgressCallback,
|
||||||
threads = Math.trunc(Math.max(navigator.hardwareConcurrency / 2, 1)),
|
threads: number = Math.trunc(Math.max(getHardwareConcurrency() / 2, 1)),
|
||||||
) {
|
): Promise<string> {
|
||||||
console.debug("fast algo");
|
console.debug("fast algo");
|
||||||
|
|
||||||
let workerMethod = window.crypto !== undefined ? "webcrypto" : "purejs";
|
let workerMethod = window.crypto !== undefined ? "webcrypto" : "purejs";
|
||||||
@@ -16,13 +26,17 @@ export default function process(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let webWorkerURL = `${basePrefix}/.within.website/x/cmd/anubis/static/js/worker/sha256-${workerMethod}.mjs?cacheBuster=${version}`;
|
let webWorkerURL = `${options.basePrefix}/.within.website/x/cmd/anubis/static/js/worker/sha256-${workerMethod}.mjs?cacheBuster=${options.version}`;
|
||||||
|
|
||||||
console.log(webWorkerURL);
|
const workers: Worker[] = [];
|
||||||
|
|
||||||
const workers = [];
|
|
||||||
let settled = false;
|
let settled = false;
|
||||||
|
|
||||||
|
const onAbort = () => {
|
||||||
|
console.log("PoW aborted");
|
||||||
|
cleanup();
|
||||||
|
reject(new DOMException("Aborted", "AbortError"));
|
||||||
|
};
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
if (settled) {
|
if (settled) {
|
||||||
return;
|
return;
|
||||||
@@ -34,12 +48,6 @@ export default function process(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAbort = () => {
|
|
||||||
console.log("PoW aborted");
|
|
||||||
cleanup();
|
|
||||||
reject(new DOMException("Aborted", "AbortError"));
|
|
||||||
};
|
|
||||||
|
|
||||||
if (signal != null) {
|
if (signal != null) {
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
return onAbort();
|
return onAbort();
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import fast from "./fast.mjs";
|
import fast from "./fast";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
fast: fast,
|
fast: fast,
|
||||||
@@ -1,20 +1,24 @@
|
|||||||
import algorithms from "./algorithms/index.mjs";
|
import algorithms from "./algorithms";
|
||||||
|
|
||||||
const defaultDifficulty = 4;
|
const defaultDifficulty = 4;
|
||||||
|
|
||||||
const status = document.getElementById("status");
|
const status: HTMLParagraphElement = document.getElementById("status") as HTMLParagraphElement;
|
||||||
const difficultyInput = document.getElementById("difficulty-input");
|
const difficultyInput: HTMLInputElement = document.getElementById("difficulty-input") as HTMLInputElement;
|
||||||
const algorithmSelect = document.getElementById("algorithm-select");
|
const algorithmSelect: HTMLSelectElement = document.getElementById("algorithm-select") as HTMLSelectElement;
|
||||||
const compareSelect = document.getElementById("compare-select");
|
const compareSelect: HTMLSelectElement = document.getElementById("compare-select") as HTMLSelectElement;
|
||||||
const header = document.getElementById("table-header");
|
const header: HTMLTableRowElement = document.getElementById("table-header") as HTMLTableRowElement;
|
||||||
const headerCompare = document.getElementById("table-header-compare");
|
const headerCompare: HTMLTableSectionElement = document.getElementById("table-header-compare") as HTMLTableSectionElement;
|
||||||
const results = document.getElementById("results");
|
const results: HTMLTableRowElement = document.getElementById("results") as HTMLTableRowElement;
|
||||||
|
|
||||||
const setupControls = () => {
|
const setupControls = () => {
|
||||||
difficultyInput.value = defaultDifficulty;
|
if (defaultDifficulty == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
difficultyInput.value = defaultDifficulty.toString();
|
||||||
for (const alg of Object.keys(algorithms)) {
|
for (const alg of Object.keys(algorithms)) {
|
||||||
const option1 = document.createElement("option");
|
const option1 = document.createElement("option");
|
||||||
algorithmSelect.append(option1);
|
algorithmSelect?.append(option1);
|
||||||
const option2 = document.createElement("option");
|
const option2 = document.createElement("option");
|
||||||
compareSelect.append(option2);
|
compareSelect.append(option2);
|
||||||
option1.value = option1.innerText = option2.value = option2.innerText = alg;
|
option1.value = option1.innerText = option2.value = option2.innerText = alg;
|
||||||
@@ -116,13 +120,13 @@ const benchmarkLoop = async (controller) => {
|
|||||||
await benchmarkLoop(controller);
|
await benchmarkLoop(controller);
|
||||||
};
|
};
|
||||||
|
|
||||||
let controller = null;
|
let controller: AbortController | null = null;
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
stats.time = stats.iters = 0;
|
stats.time = stats.iters = 0;
|
||||||
comparison.time = comparison.iters = 0;
|
comparison.time = comparison.iters = 0;
|
||||||
results.innerHTML = status.innerText = "";
|
results.innerHTML = status.innerText = "";
|
||||||
|
|
||||||
const table = results.parentElement;
|
const table = results.parentElement as HTMLElement;
|
||||||
if (compareSelect.value !== "NONE") {
|
if (compareSelect.value !== "NONE") {
|
||||||
table.style.gridTemplateColumns = "repeat(4,auto)";
|
table.style.gridTemplateColumns = "repeat(4,auto)";
|
||||||
header.style.display = "none";
|
header.style.display = "none";
|
||||||
@@ -1,12 +1,21 @@
|
|||||||
import algorithms from "./algorithms/index.mjs";
|
import algorithms from "./algorithms";
|
||||||
|
|
||||||
// from Xeact
|
// from Xeact
|
||||||
const u = (url = "", params = {}) => {
|
const u = (url: string = "", params: Record<string, any> = {}) => {
|
||||||
let result = new URL(url, window.location.href);
|
let result = new URL(url, window.location.href);
|
||||||
Object.entries(params).forEach(([k, v]) => result.searchParams.set(k, v));
|
Object.entries(params).forEach(([k, v]) => result.searchParams.set(k, v));
|
||||||
return result.toString();
|
return result.toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const j = (id: string): any | null => {
|
||||||
|
const elem = document.getElementById(id);
|
||||||
|
if (elem === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(elem.textContent);
|
||||||
|
};
|
||||||
|
|
||||||
const imageURL = (mood, cacheBuster, basePrefix) =>
|
const imageURL = (mood, cacheBuster, basePrefix) =>
|
||||||
u(`${basePrefix}/.within.website/x/cmd/anubis/static/img/${mood}.webp`, {
|
u(`${basePrefix}/.within.website/x/cmd/anubis/static/img/${mood}.webp`, {
|
||||||
cacheBuster,
|
cacheBuster,
|
||||||
@@ -14,9 +23,10 @@ const imageURL = (mood, cacheBuster, basePrefix) =>
|
|||||||
|
|
||||||
// Detect available languages by loading the manifest
|
// Detect available languages by loading the manifest
|
||||||
const getAvailableLanguages = async () => {
|
const getAvailableLanguages = async () => {
|
||||||
const basePrefix = JSON.parse(
|
const basePrefix = j("anubis_base_prefix");
|
||||||
document.getElementById("anubis_base_prefix").textContent,
|
if (basePrefix === null) {
|
||||||
);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`);
|
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/manifest.json`);
|
||||||
@@ -38,9 +48,11 @@ const getBrowserLanguage = async () =>
|
|||||||
|
|
||||||
// Load translations from JSON files
|
// Load translations from JSON files
|
||||||
const loadTranslations = async (lang) => {
|
const loadTranslations = async (lang) => {
|
||||||
const basePrefix = JSON.parse(
|
const basePrefix = j("anubis_base_prefix");
|
||||||
document.getElementById("anubis_base_prefix").textContent,
|
if (basePrefix === null) {
|
||||||
);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`);
|
const response = await fetch(`${basePrefix}/.within.website/x/cmd/anubis/static/locales/${lang}.json`);
|
||||||
return await response.json();
|
return await response.json();
|
||||||
@@ -54,9 +66,10 @@ const loadTranslations = async (lang) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getRedirectUrl = () => {
|
const getRedirectUrl = () => {
|
||||||
const publicUrl = JSON.parse(
|
const publicUrl = j("anubis_public_url");
|
||||||
document.getElementById("anubis_public_url").textContent,
|
if (publicUrl === null) {
|
||||||
);
|
return;
|
||||||
|
}
|
||||||
if (publicUrl && window.location.href.startsWith(publicUrl)) {
|
if (publicUrl && window.location.href.startsWith(publicUrl)) {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
return urlParams.get('redir');
|
return urlParams.get('redir');
|
||||||
@@ -91,16 +104,14 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
|||||||
value: navigator.cookieEnabled,
|
value: navigator.cookieEnabled,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const status = document.getElementById("status");
|
|
||||||
const image = document.getElementById("image");
|
const status: HTMLParagraphElement = document.getElementById("status") as HTMLParagraphElement;
|
||||||
const title = document.getElementById("title");
|
const image: HTMLImageElement = document.getElementById("image") as HTMLImageElement;
|
||||||
const progress = document.getElementById("progress");
|
const title: HTMLHeadingElement = document.getElementById("title") as HTMLHeadingElement;
|
||||||
const anubisVersion = JSON.parse(
|
const progress: HTMLDivElement = document.getElementById("progress") as HTMLDivElement;
|
||||||
document.getElementById("anubis_version").textContent,
|
|
||||||
);
|
const anubisVersion = j("anubis_version");
|
||||||
const basePrefix = JSON.parse(
|
const basePrefix = j("anubis_base_prefix");
|
||||||
document.getElementById("anubis_base_prefix").textContent,
|
|
||||||
);
|
|
||||||
const details = document.querySelector("details");
|
const details = document.querySelector("details");
|
||||||
let userReadDetails = false;
|
let userReadDetails = false;
|
||||||
|
|
||||||
@@ -132,9 +143,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { challenge, rules } = JSON.parse(
|
const { challenge, rules } = j("anubis_challenge");
|
||||||
document.getElementById("anubis_challenge").textContent,
|
|
||||||
);
|
|
||||||
|
|
||||||
const process = algorithms[rules.algorithm];
|
const process = algorithms[rules.algorithm];
|
||||||
if (!process) {
|
if (!process) {
|
||||||
@@ -182,7 +191,9 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
|||||||
const probability = Math.pow(1 - likelihood, iters);
|
const probability = Math.pow(1 - likelihood, iters);
|
||||||
const distance = (1 - Math.pow(probability, 2)) * 100;
|
const distance = (1 - Math.pow(probability, 2)) * 100;
|
||||||
progress["aria-valuenow"] = distance;
|
progress["aria-valuenow"] = distance;
|
||||||
progress.firstElementChild.style.width = `${distance}%`;
|
if (progress.firstElementChild !== null) {
|
||||||
|
(progress.firstElementChild as HTMLElement).style.width = `${distance}%`;
|
||||||
|
}
|
||||||
|
|
||||||
if (probability < 0.1 && !showingApology) {
|
if (probability < 0.1 && !showingApology) {
|
||||||
status.append(
|
status.append(
|
||||||
@@ -197,7 +208,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
|||||||
console.log({ hash, nonce });
|
console.log({ hash, nonce });
|
||||||
|
|
||||||
if (userReadDetails) {
|
if (userReadDetails) {
|
||||||
const container = document.getElementById("progress");
|
const container: HTMLDivElement = document.getElementById("progress") as HTMLDivElement;
|
||||||
|
|
||||||
// Style progress bar as a continue button
|
// Style progress bar as a continue button
|
||||||
container.style.display = "flex";
|
container.style.display = "flex";
|
||||||
@@ -6,7 +6,7 @@ const calculateSHA256 = (text) => {
|
|||||||
return hash.digest();
|
return hash.digest();
|
||||||
};
|
};
|
||||||
|
|
||||||
function toHexString(arr) {
|
function toHexString(arr: Uint8Array): string {
|
||||||
return Array.from(arr)
|
return Array.from(arr)
|
||||||
.map((c) => c.toString(16).padStart(2, "0"))
|
.map((c) => c.toString(16).padStart(2, "0"))
|
||||||
.join("");
|
.join("");
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const calculateSHA256 = async (input) => {
|
|
||||||
|
const calculateSHA256 = async (input: string) => {
|
||||||
const data = encoder.encode(input);
|
const data = encoder.encode(input);
|
||||||
return await crypto.subtle.digest("SHA-256", data);
|
return await crypto.subtle.digest("SHA-256", data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toHexString = (byteArray) => {
|
const toHexString = (byteArray: Uint8Array) => {
|
||||||
return byteArray.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
|
return byteArray.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
|
||||||
};
|
};
|
||||||
|
|
||||||
Reference in New Issue
Block a user