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"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -547,7 +548,13 @@ func TestGeneratePassword(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHashPassword(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$") {
|
if !contains(hash, "$argon2id$") {
|
||||||
t.Errorf("expected argon2id prefix, got %q", hash)
|
t.Errorf("expected argon2id prefix, got %q", hash)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,10 @@ package admin
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os/exec"
|
||||||
"golang.org/x/crypto/argon2"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User represents a managed user in the NextWks admin system.
|
// User represents a managed user in the NextWks admin system.
|
||||||
|
|
@ -125,8 +124,13 @@ func (s *UserStore) Create(req CreateUserRequest) []CreateUserResult {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash password with argon2id
|
// Hash password using Authelia's own crypto tool
|
||||||
hash := hashPassword(password)
|
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
|
// Default role to "user" if not set
|
||||||
if input.Role == "" {
|
if input.Role == "" {
|
||||||
|
|
@ -204,23 +208,21 @@ func generatePassword(length int) (string, error) {
|
||||||
return string(result), nil
|
return string(result), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashPassword hashes a password using argon2id (matching Authelia's format).
|
// hashWithAuthelia uses Authelia's own binary to hash a password.
|
||||||
func hashPassword(password string) string {
|
func hashWithAuthelia(password string) (string, error) {
|
||||||
salt := make([]byte, 16)
|
cmd := exec.Command("/opt/authelia/authelia", "crypto", "hash", "generate", "--password", password)
|
||||||
rand.Read(salt)
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
hash := argon2.IDKey([]byte(password), salt, 3, 65536, 4, 32)
|
return "", fmt.Errorf("authelia hash: %w", err)
|
||||||
|
}
|
||||||
// Format: $argon2id$v=19$m=65536,t=3,p=4$<salt>$<hash>
|
// Output format: "Digest: $argon2id$v=19$m=65536,t=3,p=4$salt$hash"
|
||||||
saltB64 := encodeBase64Raw(salt)
|
fields := strings.Fields(string(out))
|
||||||
hashB64 := encodeBase64Raw(hash)
|
for _, f := range fields {
|
||||||
|
if strings.HasPrefix(f, "$argon2") {
|
||||||
return fmt.Sprintf("$argon2id$v=19$m=65536,t=3,p=4$%s$%s", saltB64, hashB64)
|
return f, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// encodeBase64Raw encodes to raw standard base64 (no padding), matching Authelia.
|
return "", fmt.Errorf("could not find hash in authelia output: %s", string(out))
|
||||||
func encodeBase64Raw(data []byte) string {
|
|
||||||
return base64.RawStdEncoding.EncodeToString(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDB returns the underlying database connection for sync operations.
|
// GetDB returns the underlying database connection for sync operations.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue