diff --git a/backend/internal/service/user_service.go b/backend/internal/service/user_service.go index 315de043..1dacffdb 100644 --- a/backend/internal/service/user_service.go +++ b/backend/internal/service/user_service.go @@ -876,7 +876,7 @@ func NewOneTimeAccessToken(userID string, ttl time.Duration, withDeviceToken boo tokenLength = 6 } - token, err := utils.GenerateRandomAlphanumericString(tokenLength) + token, err := utils.GenerateRandomUnambiguousString(tokenLength) if err != nil { return nil, err } diff --git a/backend/internal/utils/string_util.go b/backend/internal/utils/string_util.go index 1f7ee7a1..b20f083f 100644 --- a/backend/internal/utils/string_util.go +++ b/backend/internal/utils/string_util.go @@ -14,6 +14,17 @@ import ( // GenerateRandomAlphanumericString generates a random alphanumeric string of the given length func GenerateRandomAlphanumericString(length int) (string, error) { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + return GenerateRandomString(length, charset) +} + +// GenerateRandomUnambiguousString generates a random string of the given length using unambiguous characters +func GenerateRandomUnambiguousString(length int) (string, error) { + const charset = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789" + return GenerateRandomString(length, charset) +} + +// GenerateRandomString generates a random string of the given length using the provided character set +func GenerateRandomString(length int, charset string) (string, error) { if length <= 0 { return "", errors.New("length must be a positive integer") diff --git a/backend/internal/utils/string_util_test.go b/backend/internal/utils/string_util_test.go index d6faab04..38251d5d 100644 --- a/backend/internal/utils/string_util_test.go +++ b/backend/internal/utils/string_util_test.go @@ -2,6 +2,7 @@ package utils import ( "regexp" + "strings" "testing" ) @@ -49,6 +50,77 @@ func TestGenerateRandomAlphanumericString(t *testing.T) { }) } +func TestGenerateRandomUnambiguousString(t *testing.T) { + t.Run("valid length returns correct string", func(t *testing.T) { + const length = 10 + str, err := GenerateRandomUnambiguousString(length) + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if len(str) != length { + t.Errorf("Expected length %d, got %d", length, len(str)) + } + + matched, err := regexp.MatchString(`^[abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789]+$`, str) + if err != nil { + t.Errorf("Regex match failed: %v", err) + } + if !matched { + t.Errorf("String contains ambiguous characters: %s", str) + } + }) + + t.Run("zero length returns error", func(t *testing.T) { + _, err := GenerateRandomUnambiguousString(0) + if err == nil { + t.Error("Expected error for zero length, got nil") + } + }) + + t.Run("negative length returns error", func(t *testing.T) { + _, err := GenerateRandomUnambiguousString(-1) + if err == nil { + t.Error("Expected error for negative length, got nil") + } + }) +} + +func TestGenerateRandomString(t *testing.T) { + t.Run("valid length returns characters from charset", func(t *testing.T) { + const length = 20 + const charset = "abc" + str, err := GenerateRandomString(length, charset) + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if len(str) != length { + t.Errorf("Expected length %d, got %d", length, len(str)) + } + + for _, r := range str { + if !strings.ContainsRune(charset, r) { + t.Fatalf("String contains character outside charset: %q", r) + } + } + }) + + t.Run("zero length returns error", func(t *testing.T) { + _, err := GenerateRandomString(0, "abc") + if err == nil { + t.Error("Expected error for zero length, got nil") + } + }) + + t.Run("negative length returns error", func(t *testing.T) { + _, err := GenerateRandomString(-1, "abc") + if err == nil { + t.Error("Expected error for negative length, got nil") + } + }) +} + func TestCapitalizeFirstLetter(t *testing.T) { tests := []struct { name string diff --git a/frontend/src/app.css b/frontend/src/app.css index 361a0230..a29105f9 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -140,7 +140,6 @@ /* Font */ --font-playfair: 'Playfair Display', serif; - --font-code: 'Google Sans', sans-serif; } @layer base { @@ -176,11 +175,6 @@ font-weight: 700; src: url('/fonts/PlayfairDisplay-Bold.woff') format('woff'); } - @font-face { - font-family: 'Google Sans'; - font-weight: 600; - src: url('/fonts/GoogleSansCode-SemiBold.ttf') format('truetype'); - } } @keyframes accordion-down { diff --git a/frontend/src/lib/components/one-time-link-modal.svelte b/frontend/src/lib/components/one-time-link-modal.svelte index 3fea80df..3920b809 100644 --- a/frontend/src/lib/components/one-time-link-modal.svelte +++ b/frontend/src/lib/components/one-time-link-modal.svelte @@ -112,7 +112,7 @@ {:else}
-

{code}

+

{code}

@@ -124,12 +124,12 @@ -

{oneTimeLink!}

+

{oneTimeLink!}

{/if} diff --git a/frontend/src/routes/settings/account/login-code-modal.svelte b/frontend/src/routes/settings/account/login-code-modal.svelte index a98c004e..709a047a 100644 --- a/frontend/src/routes/settings/account/login-code-modal.svelte +++ b/frontend/src/routes/settings/account/login-code-modal.svelte @@ -51,7 +51,7 @@
-

{code}

+

{code}

@@ -62,12 +62,12 @@ -

{loginCodeLink!}

+

{loginCodeLink!}

diff --git a/frontend/static/fonts/GoogleSansCode-SemiBold.ttf b/frontend/static/fonts/GoogleSansCode-SemiBold.ttf deleted file mode 100644 index d3e7e535..00000000 Binary files a/frontend/static/fonts/GoogleSansCode-SemiBold.ttf and /dev/null differ