Skip to content

Commit

Permalink
chore: enhance mechanism enkripsi
Browse files Browse the repository at this point in the history
  • Loading branch information
dyaksa committed Jul 23, 2024
1 parent 4eb09c4 commit 3dc10b4
Show file tree
Hide file tree
Showing 9 changed files with 478 additions and 17 deletions.
31 changes: 28 additions & 3 deletions crypto/crypto.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package crypto

import (
"database/sql"
"errors"
"fmt"

"github.com/dyaksa/encryption-pii/cmd"
"github.com/dyaksa/encryption-pii/crypto/aesx"
"github.com/dyaksa/encryption-pii/crypto/core"
"github.com/dyaksa/encryption-pii/crypto/hmacx"
_ "github.com/lib/pq"
)

type (
Expand All @@ -32,19 +35,25 @@ func isValidKeySize(key []byte) bool {
return false
}

type Opts func(*Crypto) error

type Crypto struct {
AESKey *string `env:"AES_KEY,expand" json:"aes_key"`
HMACKey *string `env:"HMAC_KEY,expand" json:"hmac_key"`

aes *core.KeySet[core.PrimitiveAES]
hmac *core.KeySet[core.PrimitiveHMAC]

Host *string `env:"HEAP_DB_HOST" envDefault:"localhost" json:"db_host"`
Port *string `env:"HEAP_DB_PORT" envDefault:"5432" json:"db_port"`
User *string `env:"HEAP_DB_USER" envDefault:"user" json:"db_user"`
Pass *string `env:"HEAP_DB_PASS" envDefault:"password" json:"db_pass"`
Name *string `env:"HEAP_DB_NAME" envDefault:"dbname" json:"db_name"`

dbHeapPsql *sql.DB

keySize AesKeySize
}

func New(keySize AesKeySize, opts ...Opts) (c *Crypto, err error) {
func New(keySize AesKeySize) (c *Crypto, err error) {
c = &Crypto{
keySize: keySize,
}
Expand All @@ -63,6 +72,22 @@ func New(keySize AesKeySize, opts ...Opts) (c *Crypto, err error) {
return c, nil
}

func (c *Crypto) InitHeapDatabase() (*sql.DB, error) {
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
*c.Host, *c.Port, *c.User, *c.Pass, *c.Name)
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, err
}

if err := db.Ping(); err != nil {
return nil, err
}

c.dbHeapPsql = db
return db, nil
}

func (c *Crypto) initEnv() error {
return cmd.EnvLoader(c, cmd.OptionsEnv{DotEnv: true, Prefix: "CRYPTO_"})
}
Expand Down
111 changes: 97 additions & 14 deletions crypto/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (

"github.com/dyaksa/encryption-pii/crypto/hmacx"
"github.com/dyaksa/encryption-pii/crypto/types"
"github.com/dyaksa/encryption-pii/validate/nik"
"github.com/dyaksa/encryption-pii/validate/npwp"
"github.com/dyaksa/encryption-pii/validate/phone"
"github.com/google/uuid"
)

Expand Down Expand Up @@ -128,6 +131,69 @@ func GenerateSQLConditions(data any) (strs []string) {
return
}

type ResultHeap struct {
Column string `json:"column"`
Value string `json:"value"`
}

func (c *Crypto) BindHeap(entity any) (err error) {
entityPtrValue := reflect.ValueOf(entity)
if entityPtrValue.Kind() != reflect.Ptr {
return fmt.Errorf("entity harus berupa pointer")
}

entityValue := entityPtrValue.Elem()
entityType := entityValue.Type()

for i := 0; i < entityType.NumField(); i++ {
field := entityType.Field(i)
if _, ok := field.Tag.Lookup("txt_heap_table"); ok {
plainTextFieldName := field.Name[:len(field.Name)-4]
bidxField := entityValue.FieldByName(field.Name)
txtHeapTable := field.Tag.Get("txt_heap_table")

switch originalValue := entityValue.FieldByName(plainTextFieldName).Interface().(type) {
case types.AESChiper:
str, heaps := c.buildHeap(originalValue.To(), txtHeapTable)
err = c.saveToHeap(context.Background(), c.dbHeapPsql, heaps)
if err != nil {
return fmt.Errorf("failed to save to heap: %w", err)
}
bidxField.SetString(str)
}
}
}
return nil
}

func (c *Crypto) saveToHeap(ctx context.Context, db *sql.DB, textHeaps []TextHeap) (err error) {
for _, th := range textHeaps {
query := new(strings.Builder)
query.WriteString("INSERT INTO ")
query.WriteString(th.Type)
query.WriteString(" (content, hash) VALUES ($1, $2)")
if ok, _ := isHashExist(ctx, db, th.Type, FindTextHeapByHashParams{Hash: th.Hash}); !ok {
_, err = db.ExecContext(ctx, query.String(), th.Content, th.Hash)
}
}
return
}

func (c *Crypto) buildHeap(value string, typeHeap string) (s string, th []TextHeap) {
var values = split(value)
builder := new(strings.Builder)
for _, value := range values {
builder.WriteString(hmacx.HMACHash(c.HMACFunc(), value).Hash().ToLast8DigitValue())
th = append(th, TextHeap{
Content: strings.ToLower(value),
Type: typeHeap,
Hash: hmacx.HMACHash(c.HMACFunc(), value).Hash().ToLast8DigitValue(),
})
}
return builder.String(), th
}

// Deprecated: any is deprecated. Use interface{} instead.
func InsertWithHeap[T Entity](c *Crypto, ctx context.Context, tx *sql.Tx, tableName string, entity any, generic T) (a T, err error) {
entityValue := reflect.ValueOf(entity)
entityType := entityValue.Type()
Expand Down Expand Up @@ -191,7 +257,7 @@ func InsertWithHeap[T Entity](c *Crypto, ctx context.Context, tx *sql.Tx, tableN

switch fieldValue := entityValue.Field(i).Interface().(type) {
case types.AESChiper:
str, heaps := BuildHeap(c, fieldValue.To(), field.Tag.Get("txt_heap_table"))
str, heaps := buildHeap(c, fieldValue.To(), field.Tag.Get("txt_heap_table"))
th = append(th, heaps...)
args = append(args, str)
}
Expand All @@ -201,6 +267,11 @@ func InsertWithHeap[T Entity](c *Crypto, ctx context.Context, tx *sql.Tx, tableN

query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s) RETURNING id", tableName, strings.Join(fieldNames, ", "), strings.Join(placeholders, ", "))

err = saveToHeap(ctx, c.dbHeapPsql, th)
if err != nil {
return a, fmt.Errorf("failed to save to heap please check heap db connection: %w", err)
}

stmt, err := tx.PrepareContext(ctx, query)
if err != nil {
return a, fmt.Errorf("failed to prepare statement: %w", err)
Expand All @@ -212,13 +283,10 @@ func InsertWithHeap[T Entity](c *Crypto, ctx context.Context, tx *sql.Tx, tableN
return a, fmt.Errorf("failed to execute statement: %w", err)
}

err = SaveToHeap(ctx, tx, th)
if err != nil {
return a, fmt.Errorf("failed to save to heap: %w", err)
}
return a, nil
}

// Deprecated: any is deprecated. Use interface{} instead.
func UpdateWithHeap(c *Crypto, ctx context.Context, tx *sql.Tx, tableName string, entity any, id string) error {
entityValue := reflect.ValueOf(entity)
entityType := entityValue.Type()
Expand Down Expand Up @@ -283,7 +351,7 @@ func UpdateWithHeap(c *Crypto, ctx context.Context, tx *sql.Tx, tableName string

switch fieldValue := entityValue.Field(i).Interface().(type) {
case types.AESChiper:
str, heaps := BuildHeap(c, fieldValue.To(), field.Tag.Get("txt_heap_table"))
str, heaps := buildHeap(c, fieldValue.To(), field.Tag.Get("txt_heap_table"))
th = append(th, heaps...)
args = append(args, str)
}
Expand All @@ -309,7 +377,7 @@ func UpdateWithHeap(c *Crypto, ctx context.Context, tx *sql.Tx, tableName string
return fmt.Errorf("failed to execute statement: %w", err)
}

err = SaveToHeap(ctx, tx, th)
err = saveToHeap(ctx, c.dbHeapPsql, th)
if err != nil {
return fmt.Errorf("failed to save to heap: %w", err)
}
Expand Down Expand Up @@ -361,7 +429,7 @@ func QueryLike[T Entity](ctx context.Context, basQuery string, tx *sql.Tx, iOpti
return
}

func BuildHeap(c *Crypto, value string, typeHeap string) (s string, th []TextHeap) {
func buildHeap(c *Crypto, value string, typeHeap string) (s string, th []TextHeap) {
var values = split(value)
builder := new(strings.Builder)
for _, value := range values {
Expand Down Expand Up @@ -401,25 +469,25 @@ func SearchContents(ctx context.Context, tx *sql.Tx, table string, args FindText
return
}

func SaveToHeap(ctx context.Context, tx *sql.Tx, textHeaps []TextHeap) (err error) {
func saveToHeap(ctx context.Context, db *sql.DB, textHeaps []TextHeap) (err error) {
for _, th := range textHeaps {
query := new(strings.Builder)
query.WriteString("INSERT INTO ")
query.WriteString(th.Type)
query.WriteString(" (content, hash) VALUES ($1, $2)")
if ok, _ := isHashExist(ctx, tx, th.Type, FindTextHeapByHashParams{Hash: th.Hash}); !ok {
_, err = tx.ExecContext(ctx, query.String(), th.Content, th.Hash)
if ok, _ := isHashExist(ctx, db, th.Type, FindTextHeapByHashParams{Hash: th.Hash}); !ok {
_, err = db.ExecContext(ctx, query.String(), th.Content, th.Hash)
}
}
return
}

func isHashExist(ctx context.Context, tx *sql.Tx, typeHeap string, args FindTextHeapByHashParams) (bool, error) {
func isHashExist(ctx context.Context, db *sql.DB, typeHeap string, args FindTextHeapByHashParams) (bool, error) {
var query = new(strings.Builder)
query.WriteString("SELECT hash FROM ")
query.WriteString(typeHeap)
query.WriteString(" WHERE hash = $1")
row := tx.QueryRowContext(ctx, query.String(), args.Hash)
row := db.QueryRowContext(ctx, query.String(), args.Hash)
var i FindTextHeapRow
err := row.Scan(&i.Hash)
if err != nil {
Expand All @@ -435,8 +503,23 @@ func split(value string) (s []string) {
var sep = " "
reg := "[a-zA-Z0-9]+"
regex := regexp.MustCompile(reg)
if validateEmail(value) {
switch {
case validateEmail(value):
sep = "@"
case phone.IsValid((value)):
parse, err := phone.Parse(value)
if err != nil {
return
}
value = parse.ToString()
sep = "-"
case nik.IsValid((value)) || npwp.IsValid((value)):
parse, err := nik.Parse(value)
if err != nil {
return
}
value = parse.ToString()
sep = "."
}
parts := strings.Split(value, sep)
for _, part := range parts {
Expand Down
108 changes: 108 additions & 0 deletions validate/nik/nik.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package nik

import (
"fmt"
"regexp"
"strings"
"time"

"github.com/dyaksa/encryption-pii/validate"
)

type NIK[T any] struct {
v T
}

func Parse[T any](v T) (NIK[T], error) {
switch any(v).(type) {
case string:
if !IsValid(any(v).(string)) {
return NIK[T]{}, fmt.Errorf("invalid data NIK %v", v)
}
return NIK[T]{v: v}, nil
default:
return NIK[T]{}, fmt.Errorf("invalid type %T", v)
}
}

func (n NIK[T]) Value() T {
return n.v
}

func (n NIK[T]) ToString() string {
validNik := regexp.MustCompile(NIK_REGEX).FindStringSubmatch(numbersOnly(n.v))
return fmt.Sprintf("%v", strings.Join(validNik[1:], "."))
}

func (n NIK[T]) ToStringP() *string {
validNik := regexp.MustCompile(NIK_REGEX).FindStringSubmatch(numbersOnly(n.v))
result := fmt.Sprintf("%v", strings.Join(validNik[1:], "."))
return &result
}

func (n NIK[T]) ToSlice() []string {
validNik := regexp.MustCompile(NIK_REGEX).FindStringSubmatch(numbersOnly(n.v))
return validNik[1:]
}

func IsValid(nik string) bool {
if len(nik) != NIK_LENGTH {
return false
}

if !regexp.MustCompile(NIK_REGEX).MatchString(nik) {
return false
}

validNik := regexp.MustCompile(NIK_REGEX).FindStringSubmatch(numbersOnly(nik))
if validNik == nil {
return false
}

validProvince := includes(convertProvinceDataToBoolMap(validate.PROVINCE_DATA), validNik[1])
if !validProvince {
return false
}

cBirthday := reformatBirthday(validNik[4])

_, err := formatDate("19" + cBirthday)
validBirthday := err == nil

return validProvince && validBirthday
}

func includes(array map[string]bool, key string) bool {
_, found := array[key]
return found
}

func convertProvinceDataToBoolMap(data map[string]validate.ProvinceData) map[string]bool {
boolMap := make(map[string]bool)
for key := range data {
boolMap[key] = true
}
return boolMap
}

func numbersOnly(v interface{}) (value string) {
input := fmt.Sprintf("%v", v)

re := regexp.MustCompile(`[^\d]`)
value = re.ReplaceAllString(input, "")
return
}

func formatDate(dateStr string) (time.Time, error) {
return time.Parse("20060102", dateStr)
}

func reformatBirthday(datePart string) string {
if len(datePart) == 6 {
day := datePart[:2]
month := datePart[2:4]
year := datePart[4:6]
return fmt.Sprintf("%s%s%s", year, month, day)
}
return ""
}
7 changes: 7 additions & 0 deletions validate/nik/regx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package nik

// NIK_REGEX is a regular expression pattern used to validate a National Identification Number (NIK).
const NIK_REGEX = `^(\d{2})(\d{2})(\d{2})(\d{6})(\d{4})$`

// NIK_LENGTH is the length of a National Identification Number (NIK) in Indonesia.
const NIK_LENGTH = 16
Loading

0 comments on commit 3dc10b4

Please sign in to comment.