feat: add qr code alternative sign in method (#1594)

Co-authored-by: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
Elias Schneider
2026-07-28 01:30:13 +02:00
committed by GitHub
co-authored by ItalyPaleAle
parent 968f97fa61
commit e1fd1d320f
38 changed files with 2215 additions and 73 deletions
@@ -18,6 +18,11 @@ func AddDeviceTokenCookie(c *gin.Context, deviceToken string) {
c.SetCookie(DeviceTokenCookieName, deviceToken, int(15*time.Minute.Seconds()), "/api/one-time-access-token", "", true, true)
}
func AddDeviceLoginTokenCookie(c *gin.Context, requestID, deviceToken string) {
path := "/api/device-login/requests/" + requestID + "/exchange"
c.SetCookie(DeviceLoginTokenCookieName, deviceToken, int(15*time.Minute.Seconds()), path, "", true, true)
}
func AddReauthenticationTokenCookie(c *gin.Context, reauthenticationToken string) {
c.SetCookie(ReauthenticationTokenCookieName, reauthenticationToken, int(3*time.Minute.Seconds()), "/", "", true, true)
}
@@ -9,6 +9,7 @@ import (
var AccessTokenCookieName = "__Host-access_token"
var SessionIdCookieName = "__Host-session"
var DeviceTokenCookieName = "__Secure-device_token" //nolint:gosec
var DeviceLoginTokenCookieName = "__Secure-device_login_token" //nolint:gosec
var ReauthenticationTokenCookieName = "__Secure-reauthentication_token" //nolint:gosec
func init() {
@@ -16,6 +17,7 @@ func init() {
AccessTokenCookieName = "access_token"
SessionIdCookieName = "session"
DeviceTokenCookieName = "device_token"
DeviceLoginTokenCookieName = "device_login_token"
ReauthenticationTokenCookieName = "reauthentication_token"
}
}
+7
View File
@@ -2,10 +2,17 @@ package utils
import (
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
)
// CreateSha256Hash creates the SHA256 hash of a string, returning a hex-encoded string.
func CreateSha256Hash(input string) string {
hash := sha256.Sum256([]byte(input))
return hex.EncodeToString(hash[:])
}
// ConstantTimeStringEqual compares two strings in constant time to prevent timing attacks.
func ConstantTimeStringEqual(left, right string) bool {
return subtle.ConstantTimeCompare([]byte(left), []byte(right)) == 1
}
+16 -1
View File
@@ -19,10 +19,21 @@ func GenerateRandomAlphanumericString(length int) (string, error) {
// GenerateRandomUnambiguousString generates a random string of the given length using unambiguous characters
func GenerateRandomUnambiguousString(length int) (string, error) {
const charset = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789"
const charset = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ0123456789"
return GenerateRandomString(length, charset)
}
// GenerateRandomUppercaseUnambiguousString generates a random uppercase string of the given length using unambiguous characters
func GenerateRandomUppercaseUnambiguousString(length int) (string, error) {
const charset = "ABCDEFGHJKMNPQRSTUVWXYZ0123456789"
return GenerateRandomString(length, charset)
}
// NormalizeUnambiguousString converts commonly confused letters to the canonical digits used by generated codes
func NormalizeUnambiguousString(value string) string {
return strings.NewReplacer("I", "1", "i", "1", "O", "0", "o", "0").Replace(value)
}
// GenerateRandomString generates a random string of the given length using the provided character set
func GenerateRandomString(length int, charset string) (string, error) {
@@ -30,6 +41,10 @@ func GenerateRandomString(length int, charset string) (string, error) {
return "", errors.New("length must be a positive integer")
}
if len(charset) == 0 {
return "", errors.New("character set must not be empty")
}
// The algorithm below is adapted from https://stackoverflow.com/a/35615565
const (
letterIdxBits = 6 // 6 bits to represent a letter index
+29 -2
View File
@@ -62,7 +62,7 @@ func TestGenerateRandomUnambiguousString(t *testing.T) {
t.Errorf("Expected length %d, got %d", length, len(str))
}
matched, err := regexp.MatchString(`^[abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789]+$`, str)
matched, err := regexp.MatchString(`^[abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ0123456789]+$`, str)
if err != nil {
t.Errorf("Regex match failed: %v", err)
}
@@ -86,6 +86,26 @@ func TestGenerateRandomUnambiguousString(t *testing.T) {
})
}
func TestGenerateRandomUppercaseUnambiguousString(t *testing.T) {
const length = 10
str, err := GenerateRandomUppercaseUnambiguousString(length)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if len(str) != length {
t.Errorf("Expected length %d, got %d", length, len(str))
}
if !regexp.MustCompile(`^[ABCDEFGHJKMNPQRSTUVWXYZ0123456789]+`).MatchString(str) {
t.Errorf("String contains lowercase or ambiguous characters: %s", str)
}
}
func TestNormalizeUnambiguousString(t *testing.T) {
if normalized := NormalizeUnambiguousString("iIoO-abc"); normalized != "1100-abc" {
t.Errorf("Expected ambiguous letters to be converted, got %s", normalized)
}
}
func TestGenerateRandomUnambiguousStringCharacterIndependence(t *testing.T) {
const (
sampleCount = 100_000
@@ -112,7 +132,6 @@ func TestGenerateRandomUnambiguousStringCharacterIndependence(t *testing.T) {
t.Errorf("first and last character collision rate = %.4f, want at most %.4f", collisionRate, maxAcceptableCollisionRate)
}
}
func TestGenerateRandomString(t *testing.T) {
t.Run("valid length returns characters from charset", func(t *testing.T) {
const length = 20
@@ -146,6 +165,14 @@ func TestGenerateRandomString(t *testing.T) {
t.Error("Expected error for negative length, got nil")
}
})
t.Run("empty charset returns error", func(t *testing.T) {
_, err := GenerateRandomString(10, "")
if err == nil {
t.Error("Expected error for empty charset, got nil")
}
})
}
func TestCapitalizeFirstLetter(t *testing.T) {