package user import ( "context" "crypto/rand" "encoding/base64" "fmt" validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/go-ozzo/ozzo-validation/v4/is" "github.com/volatiletech/sqlboiler/v4/boil" "gitlab.com/alexkavon/newsstand/src/models" "golang.org/x/crypto/argon2" ) func init() { models.AddUserHook(boil.BeforeInsertHook, validate) // should always be last models.AddUserHook(boil.BeforeInsertHook, hashSecret) } func validate(ctx context.Context, exec boil.ContextExecutor, u *models.User) error { // validate user err := validation.ValidateStruct(u, validation.Field(&u.Username, validation.Required, validation.Length(3, 50)), validation.Field(&u.Secret, validation.Required, validation.Length(8, 128)), validation.Field(&u.Email, validation.Required, is.Email), ) if err != nil { return err } return nil } func hashSecret(ctx context.Context, exec boil.ContextExecutor, u *models.User) error { hashconf := &struct { memory uint32 iterations uint32 parallelism uint8 keyLength uint32 saltLength uint32 }{64 * 1024, 3, 2, 12, 16} salt := make([]byte, hashconf.saltLength) _, err := rand.Read(salt) if err != nil { return err } hash := argon2.IDKey( []byte(u.Secret), salt, hashconf.iterations, hashconf.memory, hashconf.parallelism, hashconf.keyLength, ) b64Salt := base64.RawStdEncoding.EncodeToString(salt) b64Hash := base64.RawStdEncoding.EncodeToString(hash) encodedHash := fmt.Sprintf( "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, hashconf.memory, hashconf.iterations, hashconf.parallelism, b64Salt, b64Hash, ) u.Secret = encodedHash return nil }