From 19ae62da453b9121275fb9369fd2fccef2087d11 Mon Sep 17 00:00:00 2001 From: cclohmar Date: Sun, 14 Jun 2026 17:24:28 +0000 Subject: [PATCH] feat(auth): use Authelia binary for password hashing instead of own argon2 --- src/core/admin/admin_test.go | 9 ++++++- src/core/admin/users.go | 46 +++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/core/admin/admin_test.go b/src/core/admin/admin_test.go index 6c4ac9a..c885b64 100644 --- a/src/core/admin/admin_test.go +++ b/src/core/admin/admin_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "os" + "os/exec" "path/filepath" "testing" @@ -547,7 +548,13 @@ func TestGeneratePassword(t *testing.T) { } func TestHashPassword(t *testing.T) { - hash := hashPassword("test-password") + if _, err := exec.LookPath("/opt/authelia/authelia"); err != nil { + t.Skip("Authelia binary not available for testing") + } + hash, err := hashWithAuthelia("test-password") + if err != nil { + t.Fatalf("hashWithAuthelia failed: %v", err) + } if !contains(hash, "$argon2id$") { t.Errorf("expected argon2id prefix, got %q", hash) } diff --git a/src/core/admin/users.go b/src/core/admin/users.go index 2b0adb3..1ab1dc5 100644 --- a/src/core/admin/users.go +++ b/src/core/admin/users.go @@ -3,11 +3,10 @@ package admin import ( "crypto/rand" "database/sql" - "encoding/base64" "fmt" "math/big" - - "golang.org/x/crypto/argon2" + "os/exec" + "strings" ) // User represents a managed user in the NextWks admin system. @@ -125,8 +124,13 @@ func (s *UserStore) Create(req CreateUserRequest) []CreateUserResult { continue } - // Hash password with argon2id - hash := hashPassword(password) + // Hash password using Authelia's own crypto tool + hash, err := hashWithAuthelia(password) + if err != nil { + result.Error = fmt.Sprintf("password hashing failed: %v", err) + results = append(results, result) + continue + } // Default role to "user" if not set if input.Role == "" { @@ -204,23 +208,21 @@ func generatePassword(length int) (string, error) { return string(result), nil } -// hashPassword hashes a password using argon2id (matching Authelia's format). -func hashPassword(password string) string { - salt := make([]byte, 16) - rand.Read(salt) - - hash := argon2.IDKey([]byte(password), salt, 3, 65536, 4, 32) - - // Format: $argon2id$v=19$m=65536,t=3,p=4$$ - saltB64 := encodeBase64Raw(salt) - hashB64 := encodeBase64Raw(hash) - - return fmt.Sprintf("$argon2id$v=19$m=65536,t=3,p=4$%s$%s", saltB64, hashB64) -} - -// encodeBase64Raw encodes to raw standard base64 (no padding), matching Authelia. -func encodeBase64Raw(data []byte) string { - return base64.RawStdEncoding.EncodeToString(data) +// hashWithAuthelia uses Authelia's own binary to hash a password. +func hashWithAuthelia(password string) (string, error) { + cmd := exec.Command("/opt/authelia/authelia", "crypto", "hash", "generate", "--password", password) + out, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("authelia hash: %w", err) + } + // Output format: "Digest: $argon2id$v=19$m=65536,t=3,p=4$salt$hash" + fields := strings.Fields(string(out)) + for _, f := range fields { + if strings.HasPrefix(f, "$argon2") { + return f, nil + } + } + return "", fmt.Errorf("could not find hash in authelia output: %s", string(out)) } // GetDB returns the underlying database connection for sync operations.