util for password and configs

This commit is contained in:
nochill 2023-09-08 22:25:22 +07:00
parent eb4e6b786a
commit 6b340d7195
8 changed files with 264 additions and 0 deletions

32
util/config.go Normal file
View File

@ -0,0 +1,32 @@
package util
import (
"time"
"github.com/spf13/viper"
)
type Config struct {
DBDriver string `mapstructure:"DB_TYPE"`
DBSource string `mapstructure:"DB_SOURCE"`
ServerAddress string `mapstructure:"SERVER_ADDRESS"`
TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"`
TokenDuration time.Duration `mapstructure:"TOKEN_DURATION"`
RefreshTokenDuration time.Duration `mapstructure:"REFRESH_TOKEN_DURATION"`
}
func LoadConfig(path string) (config Config, err error) {
viper.AddConfigPath(path)
viper.SetConfigName("dev")
viper.SetConfigType("env")
viper.AutomaticEnv()
err = viper.ReadInConfig()
if err != nil {
return
}
err = viper.Unmarshal(&config)
return
}

19
util/password.go Normal file
View File

@ -0,0 +1,19 @@
package util
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string) (string, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", fmt.Errorf("Failed to hash password: %w", err)
}
return string(hashedPassword), nil
}
func CheckPassword(password string, hashedPassword string) error {
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}

23
util/password_test.go Normal file
View File

@ -0,0 +1,23 @@
package util
import (
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
)
func TestPasswordUtil(t *testing.T) {
password := RandomString(10)
hashedPassword, err := HashPassword(password)
require.NoError(t, err)
require.NotEmpty(t, hashedPassword)
err = CheckPassword(password, hashedPassword)
require.NoError(t, err)
wrongPassword := RandomString(5)
err = CheckPassword(wrongPassword, hashedPassword)
require.EqualError(t, err, bcrypt.ErrMismatchedHashAndPassword.Error())
}

41
util/random.go Normal file
View File

@ -0,0 +1,41 @@
package util
import (
"fmt"
"math/rand"
"strings"
"time"
)
const alphabet = "abcdefghijklmnopqrstuvwxyz"
func init() {
rand.Seed(time.Now().UnixNano())
}
// RandomInt generates a random integer between min and max
func RandomInt(min, max int64) int64 {
return min + rand.Int63n(max-min+1)
}
// RandomString generates a random string of length n
func RandomString(n int) string {
var sb strings.Builder
k := len(alphabet)
for i := 0; i < n; i++ {
c := alphabet[rand.Intn(k)]
sb.WriteByte(c)
}
return sb.String()
}
func RandomEmail() string {
return fmt.Sprintf("%s@mail.com", RandomString(5))
}
func RandomTransactionCode(prefix string, merchant_index int64) string {
time_now := time.Now().Unix()
return fmt.Sprintf("%s%d%d", prefix, merchant_index, time_now)
}

8
util/token/maker.go Normal file
View File

@ -0,0 +1,8 @@
package token
import "time"
type Maker interface {
CreateToken(email string, userID int, duration time.Duration) (string, *Payload, error)
VerifyToken(token string) (*Payload, error)
}

53
util/token/paseto.go Normal file
View File

@ -0,0 +1,53 @@
package token
import (
"fmt"
"time"
"github.com/aead/chacha20poly1305"
"github.com/o1egl/paseto"
)
type PasetoMaker struct {
paseto *paseto.V2
symmetricKey []byte
}
func NewPasetoMaker(symmetricKey string) (Maker, error) {
if len(symmetricKey) != chacha20poly1305.KeySize {
return nil, fmt.Errorf("invalid key size: must be exactly %d characters", chacha20poly1305.KeySize)
}
maker := &PasetoMaker{
paseto: paseto.NewV2(),
symmetricKey: []byte(symmetricKey),
}
return maker, nil
}
func (maker *PasetoMaker) CreateToken(email string, UserID int, duration time.Duration) (string, *Payload, error) {
payload, err := NewPayload(email, UserID, duration)
if err != nil {
return "", payload, err
}
token, err := maker.paseto.Encrypt(maker.symmetricKey, payload, nil)
return token, payload, err
}
func (maker *PasetoMaker) VerifyToken(token string) (*Payload, error) {
payload := &Payload{}
err := maker.paseto.Decrypt(token, maker.symmetricKey, payload, nil)
if err != nil {
return nil, ErrInvalidToken
}
err = payload.Valid()
if err != nil {
return nil, err
}
return payload, nil
}

52
util/token/paseto_test.go Normal file
View File

@ -0,0 +1,52 @@
package token
import (
"math/rand"
"testing"
"time"
"git.nochill.in/nochill/hiling_go/util"
"github.com/stretchr/testify/require"
)
var userID = rand.Intn(10)
func TestPasetoMaker(t *testing.T) {
maker, err := NewPasetoMaker(util.RandomString(32))
require.NoError(t, err)
email := util.RandomEmail()
duration := time.Minute
issuedAt := time.Now()
expiredAt := issuedAt.Add(duration)
token, payload, err := maker.CreateToken(email, userID, duration)
require.NoError(t, err)
require.NotEmpty(t, token)
require.NotEmpty(t, payload)
payload, err = maker.VerifyToken(token)
require.NoError(t, err)
require.NotEmpty(t, payload)
// require.NotZero(t, payload.ID)
require.Equal(t, email, payload.Email)
require.WithinDuration(t, issuedAt, payload.IssuedAt, time.Second)
require.WithinDuration(t, expiredAt, payload.ExpiredAt, time.Second)
}
func TestExpiredPasetoToken(t *testing.T) {
maker, err := NewPasetoMaker(util.RandomString(32))
require.NoError(t, err)
token, payload, err := maker.CreateToken(util.RandomEmail(), userID, -time.Minute)
require.NoError(t, err)
require.NotEmpty(t, token)
require.NotEmpty(t, payload)
payload, err = maker.VerifyToken(token)
require.Error(t, err)
require.EqualError(t, err, ErrExpiredToken.Error())
require.Nil(t, payload)
}

36
util/token/payload.go Normal file
View File

@ -0,0 +1,36 @@
package token
import (
"errors"
"time"
)
var (
ErrExpiredToken = errors.New("token has expired")
ErrInvalidToken = errors.New("token is invalid")
)
type Payload struct {
Email string `json:"email"`
UserID int `json:"user_id"`
IssuedAt time.Time `json:"issued_at"`
ExpiredAt time.Time `json:"expired_at"`
}
func NewPayload(email string, user_id int, duration time.Duration) (*Payload, error) {
payload := &Payload{
Email: email,
UserID: user_id,
IssuedAt: time.Now(),
ExpiredAt: time.Now().Add(duration),
}
return payload, nil
}
func (payload *Payload) Valid() error {
if time.Now().After(payload.ExpiredAt) {
return ErrExpiredToken
}
return nil
}