refactor(nanoid): replace gonanoid with custom nanoid implementation for ID generation
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
+2
-2
@@ -7,11 +7,11 @@ import (
|
|||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/deluan/rest"
|
"github.com/deluan/rest"
|
||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
|
||||||
"github.com/navidrome/navidrome/conf"
|
"github.com/navidrome/navidrome/conf"
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
. "github.com/navidrome/navidrome/utils/gg"
|
. "github.com/navidrome/navidrome/utils/gg"
|
||||||
|
"github.com/navidrome/navidrome/utils/nanoid"
|
||||||
"github.com/navidrome/navidrome/utils/slice"
|
"github.com/navidrome/navidrome/utils/slice"
|
||||||
"github.com/navidrome/navidrome/utils/str"
|
"github.com/navidrome/navidrome/utils/str"
|
||||||
)
|
)
|
||||||
@@ -73,7 +73,7 @@ type shareRepositoryWrapper struct {
|
|||||||
|
|
||||||
func (r *shareRepositoryWrapper) newId() (string, error) {
|
func (r *shareRepositoryWrapper) newId() (string, error) {
|
||||||
for {
|
for {
|
||||||
id, err := gonanoid.Generate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 10)
|
id, err := nanoid.Generate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ require (
|
|||||||
github.com/kr/pretty v0.3.1
|
github.com/kr/pretty v0.3.1
|
||||||
github.com/lestrrat-go/jwx/v3 v3.0.13
|
github.com/lestrrat-go/jwx/v3 v3.0.13
|
||||||
github.com/maruel/natural v1.3.0
|
github.com/maruel/natural v1.3.0
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.34
|
github.com/mattn/go-sqlite3 v1.14.34
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27
|
github.com/microcosm-cc/bluemonday v1.0.27
|
||||||
github.com/mileusna/useragent v1.3.5
|
github.com/mileusna/useragent v1.3.5
|
||||||
|
|||||||
@@ -177,8 +177,6 @@ github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLO
|
|||||||
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
|
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
|
||||||
github.com/maruel/natural v1.3.0 h1:VsmCsBmEyrR46RomtgHs5hbKADGRVtliHTyCOLFBpsg=
|
github.com/maruel/natural v1.3.0 h1:VsmCsBmEyrR46RomtgHs5hbKADGRVtliHTyCOLFBpsg=
|
||||||
github.com/maruel/natural v1.3.0/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
github.com/maruel/natural v1.3.0/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
|
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||||
|
|||||||
+2
-2
@@ -6,12 +6,12 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
|
"github.com/navidrome/navidrome/utils/nanoid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRandom() string {
|
func NewRandom() string {
|
||||||
id, err := gonanoid.Generate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 22)
|
id, err := nanoid.Generate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 22)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Could not generate new ID", err)
|
log.Error("Could not generate new ID", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package nanoid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Generate returns a cryptographically secure random string of `size` characters
|
||||||
|
// drawn from `alphabet`. It uses bitmask with rejection sampling to avoid modulo bias.
|
||||||
|
// The alphabet must be non-empty, contain at most 255 characters, and consist only of
|
||||||
|
// ASCII characters. Non-ASCII alphabets (e.g., multi-byte UTF-8) are not supported.
|
||||||
|
func Generate(alphabet string, size int) (string, error) {
|
||||||
|
if len(alphabet) == 0 || len(alphabet) > 255 {
|
||||||
|
return "", errors.New("alphabet must be non-empty and at most 255 characters")
|
||||||
|
}
|
||||||
|
if size <= 0 {
|
||||||
|
return "", errors.New("size must be a positive integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
mask := getMask(len(alphabet))
|
||||||
|
step := int(math.Ceil(1.6 * float64(mask) * float64(size) / float64(len(alphabet))))
|
||||||
|
|
||||||
|
id := make([]byte, size)
|
||||||
|
bytes := make([]byte, step)
|
||||||
|
for j := 0; ; {
|
||||||
|
if _, err := rand.Read(bytes); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for i := range step {
|
||||||
|
idx := int(bytes[i]) & mask
|
||||||
|
if idx < len(alphabet) {
|
||||||
|
id[j] = alphabet[idx]
|
||||||
|
j++
|
||||||
|
if j == size {
|
||||||
|
return string(id), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMask returns the smallest bitmask >= alphabetSize-1.
|
||||||
|
func getMask(alphabetSize int) int {
|
||||||
|
for i := 1; i <= 8; i++ {
|
||||||
|
mask := (2 << uint(i)) - 1
|
||||||
|
if mask >= alphabetSize-1 {
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package nanoid_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/utils/nanoid"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNanoid(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Nanoid Suite")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("Generate", func() {
|
||||||
|
const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
It("generates a string of the requested length", func() {
|
||||||
|
id, err := nanoid.Generate(alphabet, 22)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(id).To(HaveLen(22))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("generates a short string of the requested length", func() {
|
||||||
|
id, err := nanoid.Generate(alphabet, 10)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(id).To(HaveLen(10))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("only contains characters from the alphabet", func() {
|
||||||
|
id, err := nanoid.Generate(alphabet, 100)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
for _, c := range id {
|
||||||
|
Expect(alphabet).To(ContainSubstring(string(c)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("generates unique IDs", func() {
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for range 1000 {
|
||||||
|
id, err := nanoid.Generate(alphabet, 22)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(seen).ToNot(HaveKey(id))
|
||||||
|
seen[id] = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("works with a single-character alphabet", func() {
|
||||||
|
id, err := nanoid.Generate("a", 5)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(id).To(Equal("aaaaa"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("works with a small alphabet", func() {
|
||||||
|
id, err := nanoid.Generate("ab", 10)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(id).To(HaveLen(10))
|
||||||
|
for _, c := range id {
|
||||||
|
Expect(string(c)).To(BeElementOf("a", "b"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns error on empty alphabet", func() {
|
||||||
|
_, err := nanoid.Generate("", 10)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns error on alphabet larger than 255 characters", func() {
|
||||||
|
bigAlphabet := make([]byte, 256)
|
||||||
|
for i := range bigAlphabet {
|
||||||
|
bigAlphabet[i] = byte(i)
|
||||||
|
}
|
||||||
|
_, err := nanoid.Generate(string(bigAlphabet), 10)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns error on non-positive size", func() {
|
||||||
|
_, err := nanoid.Generate(alphabet, 0)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
|
||||||
|
_, err = nanoid.Generate(alphabet, -1)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user