feat(auth): use Authelia binary for password hashing instead of own argon2

This commit is contained in:
Claus Lohmar 2026-06-14 17:24:28 +00:00
parent e8906df62e
commit 19ae62da45
2 changed files with 32 additions and 23 deletions

View file

@ -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)
}

View file

@ -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$<salt>$<hash>
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.