renommage lassistanoque
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package password
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
type params struct {
|
||||
memory uint32 // en KiB
|
||||
iterations uint32
|
||||
parallelism uint8
|
||||
saltLength uint32
|
||||
keyLength uint32
|
||||
}
|
||||
|
||||
var defaultParams = ¶ms{
|
||||
memory: 64 * 1024, // 64 MB
|
||||
iterations: 3,
|
||||
parallelism: 2,
|
||||
saltLength: 16,
|
||||
keyLength: 32,
|
||||
}
|
||||
|
||||
// generateSalt crée un salt aléatoire cryptographiquement sûr
|
||||
func generateSalt(length uint32) ([]byte, error) {
|
||||
salt := make([]byte, length)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return salt, nil
|
||||
}
|
||||
|
||||
// HashPassword génère un hash Argon2id encodé (salt + hash + paramètres inclus)
|
||||
func HashPassword(password string) (string, error) {
|
||||
salt, err := generateSalt(defaultParams.saltLength)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hash := argon2.IDKey(
|
||||
[]byte(password),
|
||||
salt,
|
||||
defaultParams.iterations,
|
||||
defaultParams.memory,
|
||||
defaultParams.parallelism,
|
||||
defaultParams.keyLength,
|
||||
)
|
||||
|
||||
// Format standard: $argon2id$v=19$m=65536,t=3,p=2$salt$hash
|
||||
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
|
||||
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
|
||||
|
||||
encoded := fmt.Sprintf(
|
||||
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
|
||||
argon2.Version,
|
||||
defaultParams.memory,
|
||||
defaultParams.iterations,
|
||||
defaultParams.parallelism,
|
||||
b64Salt,
|
||||
b64Hash,
|
||||
)
|
||||
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
// VerifyPassword compare un mot de passe en clair avec un hash stocké
|
||||
func VerifyPassword(password, encodedHash string) (bool, error) {
|
||||
p, salt, hash, err := decodeHash(encodedHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
otherHash := argon2.IDKey(
|
||||
[]byte(password),
|
||||
salt,
|
||||
p.iterations,
|
||||
p.memory,
|
||||
p.parallelism,
|
||||
p.keyLength,
|
||||
)
|
||||
|
||||
// Comparaison en temps constant pour éviter les timing attacks
|
||||
if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// decodeHash parse la chaîne encodée pour en extraire params, salt et hash
|
||||
func decodeHash(encodedHash string) (*params, []byte, []byte, error) {
|
||||
parts := strings.Split(encodedHash, "$")
|
||||
if len(parts) != 6 {
|
||||
return nil, nil, nil, errors.New("format de hash invalide")
|
||||
}
|
||||
|
||||
var version int
|
||||
_, err := fmt.Sscanf(parts[2], "v=%d", &version)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if version != argon2.Version {
|
||||
return nil, nil, nil, errors.New("version argon2 incompatible")
|
||||
}
|
||||
|
||||
p := ¶ms{}
|
||||
_, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &p.memory, &p.iterations, &p.parallelism)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
p.saltLength = uint32(len(salt))
|
||||
|
||||
hash, err := base64.RawStdEncoding.DecodeString(parts[5])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
p.keyLength = uint32(len(hash))
|
||||
|
||||
return p, salt, hash, nil
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package password
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||
"trankilou.fr/lassistanoque/backend/internal/service/auth"
|
||||
)
|
||||
|
||||
type PasswordAuthenticator struct {
|
||||
tokenManager auth.TokenManager
|
||||
userRepository domain.UserRepository
|
||||
}
|
||||
|
||||
func NewPasswordAuthenticator(
|
||||
tokenManager auth.TokenManager,
|
||||
userRepository domain.UserRepository,
|
||||
) *PasswordAuthenticator {
|
||||
return &PasswordAuthenticator{
|
||||
tokenManager,
|
||||
userRepository,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PasswordAuthenticator) Authenticate(ctx context.Context, creds auth.Credentials) (*auth.Session, error) {
|
||||
|
||||
user, err := s.userRepository.FindUserByEmail(creds.Email)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unauthorized user %s : %s", creds.Email, err)
|
||||
}
|
||||
|
||||
ok, err := VerifyPassword(creds.Password, user.Password)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Password don't match")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error verifying password: %s", err)
|
||||
}
|
||||
|
||||
accessToken, expiresAt, err := s.tokenManager.GenerateAccessToken(user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to generate access token: %s", err)
|
||||
}
|
||||
refreshToken, _, err := s.tokenManager.GenerateRefreshToken(user.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to generate refresh token: %s", err)
|
||||
}
|
||||
|
||||
return &auth.Session{
|
||||
User: user,
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresAt: expiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *PasswordAuthenticator) Register(ctx context.Context, registration auth.Registration) (*auth.Session, error) {
|
||||
|
||||
hashedPassord, err := HashPassword(registration.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := s.userRepository.CreateUser(&domain.User{
|
||||
Email: registration.Email,
|
||||
Firstname: registration.Firstname,
|
||||
Lastname: registration.Lastname,
|
||||
Password: hashedPassord,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = user
|
||||
return nil, nil
|
||||
}
|
||||
Reference in New Issue
Block a user