feat(auth): use Authelia binary for password hashing instead of own argon2
This commit is contained in:
parent
e8906df62e
commit
19ae62da45
2 changed files with 32 additions and 23 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue