Skip to content

Commit 3dc10b4

Browse files
committed
chore: enhance mechanism enkripsi
1 parent 4eb09c4 commit 3dc10b4

File tree

9 files changed

+478
-17
lines changed

9 files changed

+478
-17
lines changed

crypto/crypto.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package crypto
22

33
import (
4+
"database/sql"
45
"errors"
6+
"fmt"
57

68
"github.com/dyaksa/encryption-pii/cmd"
79
"github.com/dyaksa/encryption-pii/crypto/aesx"
810
"github.com/dyaksa/encryption-pii/crypto/core"
911
"github.com/dyaksa/encryption-pii/crypto/hmacx"
12+
_ "github.com/lib/pq"
1013
)
1114

1215
type (
@@ -32,19 +35,25 @@ func isValidKeySize(key []byte) bool {
3235
return false
3336
}
3437

35-
type Opts func(*Crypto) error
36-
3738
type Crypto struct {
3839
AESKey *string `env:"AES_KEY,expand" json:"aes_key"`
3940
HMACKey *string `env:"HMAC_KEY,expand" json:"hmac_key"`
4041

4142
aes *core.KeySet[core.PrimitiveAES]
4243
hmac *core.KeySet[core.PrimitiveHMAC]
4344

45+
Host *string `env:"HEAP_DB_HOST" envDefault:"localhost" json:"db_host"`
46+
Port *string `env:"HEAP_DB_PORT" envDefault:"5432" json:"db_port"`
47+
User *string `env:"HEAP_DB_USER" envDefault:"user" json:"db_user"`
48+
Pass *string `env:"HEAP_DB_PASS" envDefault:"password" json:"db_pass"`
49+
Name *string `env:"HEAP_DB_NAME" envDefault:"dbname" json:"db_name"`
50+
51+
dbHeapPsql *sql.DB
52+
4453
keySize AesKeySize
4554
}
4655

47-
func New(keySize AesKeySize, opts ...Opts) (c *Crypto, err error) {
56+
func New(keySize AesKeySize) (c *Crypto, err error) {
4857
c = &Crypto{
4958
keySize: keySize,
5059
}
@@ -63,6 +72,22 @@ func New(keySize AesKeySize, opts ...Opts) (c *Crypto, err error) {
6372
return c, nil
6473
}
6574

75+
func (c *Crypto) InitHeapDatabase() (*sql.DB, error) {
76+
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
77+
*c.Host, *c.Port, *c.User, *c.Pass, *c.Name)
78+
db, err := sql.Open("postgres", dsn)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
if err := db.Ping(); err != nil {
84+
return nil, err
85+
}
86+
87+
c.dbHeapPsql = db
88+
return db, nil
89+
}
90+
6691
func (c *Crypto) initEnv() error {
6792
return cmd.EnvLoader(c, cmd.OptionsEnv{DotEnv: true, Prefix: "CRYPTO_"})
6893
}

crypto/utils.go

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import (
1010

1111
"github.com/dyaksa/encryption-pii/crypto/hmacx"
1212
"github.com/dyaksa/encryption-pii/crypto/types"
13+
"github.com/dyaksa/encryption-pii/validate/nik"
14+
"github.com/dyaksa/encryption-pii/validate/npwp"
15+
"github.com/dyaksa/encryption-pii/validate/phone"
1316
"github.com/google/uuid"
1417
)
1518

@@ -128,6 +131,69 @@ func GenerateSQLConditions(data any) (strs []string) {
128131
return
129132
}
130133

134+
type ResultHeap struct {
135+
Column string `json:"column"`
136+
Value string `json:"value"`
137+
}
138+
139+
func (c *Crypto) BindHeap(entity any) (err error) {
140+
entityPtrValue := reflect.ValueOf(entity)
141+
if entityPtrValue.Kind() != reflect.Ptr {
142+
return fmt.Errorf("entity harus berupa pointer")
143+
}
144+
145+
entityValue := entityPtrValue.Elem()
146+
entityType := entityValue.Type()
147+
148+
for i := 0; i < entityType.NumField(); i++ {
149+
field := entityType.Field(i)
150+
if _, ok := field.Tag.Lookup("txt_heap_table"); ok {
151+
plainTextFieldName := field.Name[:len(field.Name)-4]
152+
bidxField := entityValue.FieldByName(field.Name)
153+
txtHeapTable := field.Tag.Get("txt_heap_table")
154+
155+
switch originalValue := entityValue.FieldByName(plainTextFieldName).Interface().(type) {
156+
case types.AESChiper:
157+
str, heaps := c.buildHeap(originalValue.To(), txtHeapTable)
158+
err = c.saveToHeap(context.Background(), c.dbHeapPsql, heaps)
159+
if err != nil {
160+
return fmt.Errorf("failed to save to heap: %w", err)
161+
}
162+
bidxField.SetString(str)
163+
}
164+
}
165+
}
166+
return nil
167+
}
168+
169+
func (c *Crypto) saveToHeap(ctx context.Context, db *sql.DB, textHeaps []TextHeap) (err error) {
170+
for _, th := range textHeaps {
171+
query := new(strings.Builder)
172+
query.WriteString("INSERT INTO ")
173+
query.WriteString(th.Type)
174+
query.WriteString(" (content, hash) VALUES ($1, $2)")
175+
if ok, _ := isHashExist(ctx, db, th.Type, FindTextHeapByHashParams{Hash: th.Hash}); !ok {
176+
_, err = db.ExecContext(ctx, query.String(), th.Content, th.Hash)
177+
}
178+
}
179+
return
180+
}
181+
182+
func (c *Crypto) buildHeap(value string, typeHeap string) (s string, th []TextHeap) {
183+
var values = split(value)
184+
builder := new(strings.Builder)
185+
for _, value := range values {
186+
builder.WriteString(hmacx.HMACHash(c.HMACFunc(), value).Hash().ToLast8DigitValue())
187+
th = append(th, TextHeap{
188+
Content: strings.ToLower(value),
189+
Type: typeHeap,
190+
Hash: hmacx.HMACHash(c.HMACFunc(), value).Hash().ToLast8DigitValue(),
191+
})
192+
}
193+
return builder.String(), th
194+
}
195+
196+
// Deprecated: any is deprecated. Use interface{} instead.
131197
func InsertWithHeap[T Entity](c *Crypto, ctx context.Context, tx *sql.Tx, tableName string, entity any, generic T) (a T, err error) {
132198
entityValue := reflect.ValueOf(entity)
133199
entityType := entityValue.Type()
@@ -191,7 +257,7 @@ func InsertWithHeap[T Entity](c *Crypto, ctx context.Context, tx *sql.Tx, tableN
191257

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

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

270+
err = saveToHeap(ctx, c.dbHeapPsql, th)
271+
if err != nil {
272+
return a, fmt.Errorf("failed to save to heap please check heap db connection: %w", err)
273+
}
274+
204275
stmt, err := tx.PrepareContext(ctx, query)
205276
if err != nil {
206277
return a, fmt.Errorf("failed to prepare statement: %w", err)
@@ -212,13 +283,10 @@ func InsertWithHeap[T Entity](c *Crypto, ctx context.Context, tx *sql.Tx, tableN
212283
return a, fmt.Errorf("failed to execute statement: %w", err)
213284
}
214285

215-
err = SaveToHeap(ctx, tx, th)
216-
if err != nil {
217-
return a, fmt.Errorf("failed to save to heap: %w", err)
218-
}
219286
return a, nil
220287
}
221288

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

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

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

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

404-
func SaveToHeap(ctx context.Context, tx *sql.Tx, textHeaps []TextHeap) (err error) {
472+
func saveToHeap(ctx context.Context, db *sql.DB, textHeaps []TextHeap) (err error) {
405473
for _, th := range textHeaps {
406474
query := new(strings.Builder)
407475
query.WriteString("INSERT INTO ")
408476
query.WriteString(th.Type)
409477
query.WriteString(" (content, hash) VALUES ($1, $2)")
410-
if ok, _ := isHashExist(ctx, tx, th.Type, FindTextHeapByHashParams{Hash: th.Hash}); !ok {
411-
_, err = tx.ExecContext(ctx, query.String(), th.Content, th.Hash)
478+
if ok, _ := isHashExist(ctx, db, th.Type, FindTextHeapByHashParams{Hash: th.Hash}); !ok {
479+
_, err = db.ExecContext(ctx, query.String(), th.Content, th.Hash)
412480
}
413481
}
414482
return
415483
}
416484

417-
func isHashExist(ctx context.Context, tx *sql.Tx, typeHeap string, args FindTextHeapByHashParams) (bool, error) {
485+
func isHashExist(ctx context.Context, db *sql.DB, typeHeap string, args FindTextHeapByHashParams) (bool, error) {
418486
var query = new(strings.Builder)
419487
query.WriteString("SELECT hash FROM ")
420488
query.WriteString(typeHeap)
421489
query.WriteString(" WHERE hash = $1")
422-
row := tx.QueryRowContext(ctx, query.String(), args.Hash)
490+
row := db.QueryRowContext(ctx, query.String(), args.Hash)
423491
var i FindTextHeapRow
424492
err := row.Scan(&i.Hash)
425493
if err != nil {
@@ -435,8 +503,23 @@ func split(value string) (s []string) {
435503
var sep = " "
436504
reg := "[a-zA-Z0-9]+"
437505
regex := regexp.MustCompile(reg)
438-
if validateEmail(value) {
506+
switch {
507+
case validateEmail(value):
439508
sep = "@"
509+
case phone.IsValid((value)):
510+
parse, err := phone.Parse(value)
511+
if err != nil {
512+
return
513+
}
514+
value = parse.ToString()
515+
sep = "-"
516+
case nik.IsValid((value)) || npwp.IsValid((value)):
517+
parse, err := nik.Parse(value)
518+
if err != nil {
519+
return
520+
}
521+
value = parse.ToString()
522+
sep = "."
440523
}
441524
parts := strings.Split(value, sep)
442525
for _, part := range parts {

validate/nik/nik.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package nik
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strings"
7+
"time"
8+
9+
"github.com/dyaksa/encryption-pii/validate"
10+
)
11+
12+
type NIK[T any] struct {
13+
v T
14+
}
15+
16+
func Parse[T any](v T) (NIK[T], error) {
17+
switch any(v).(type) {
18+
case string:
19+
if !IsValid(any(v).(string)) {
20+
return NIK[T]{}, fmt.Errorf("invalid data NIK %v", v)
21+
}
22+
return NIK[T]{v: v}, nil
23+
default:
24+
return NIK[T]{}, fmt.Errorf("invalid type %T", v)
25+
}
26+
}
27+
28+
func (n NIK[T]) Value() T {
29+
return n.v
30+
}
31+
32+
func (n NIK[T]) ToString() string {
33+
validNik := regexp.MustCompile(NIK_REGEX).FindStringSubmatch(numbersOnly(n.v))
34+
return fmt.Sprintf("%v", strings.Join(validNik[1:], "."))
35+
}
36+
37+
func (n NIK[T]) ToStringP() *string {
38+
validNik := regexp.MustCompile(NIK_REGEX).FindStringSubmatch(numbersOnly(n.v))
39+
result := fmt.Sprintf("%v", strings.Join(validNik[1:], "."))
40+
return &result
41+
}
42+
43+
func (n NIK[T]) ToSlice() []string {
44+
validNik := regexp.MustCompile(NIK_REGEX).FindStringSubmatch(numbersOnly(n.v))
45+
return validNik[1:]
46+
}
47+
48+
func IsValid(nik string) bool {
49+
if len(nik) != NIK_LENGTH {
50+
return false
51+
}
52+
53+
if !regexp.MustCompile(NIK_REGEX).MatchString(nik) {
54+
return false
55+
}
56+
57+
validNik := regexp.MustCompile(NIK_REGEX).FindStringSubmatch(numbersOnly(nik))
58+
if validNik == nil {
59+
return false
60+
}
61+
62+
validProvince := includes(convertProvinceDataToBoolMap(validate.PROVINCE_DATA), validNik[1])
63+
if !validProvince {
64+
return false
65+
}
66+
67+
cBirthday := reformatBirthday(validNik[4])
68+
69+
_, err := formatDate("19" + cBirthday)
70+
validBirthday := err == nil
71+
72+
return validProvince && validBirthday
73+
}
74+
75+
func includes(array map[string]bool, key string) bool {
76+
_, found := array[key]
77+
return found
78+
}
79+
80+
func convertProvinceDataToBoolMap(data map[string]validate.ProvinceData) map[string]bool {
81+
boolMap := make(map[string]bool)
82+
for key := range data {
83+
boolMap[key] = true
84+
}
85+
return boolMap
86+
}
87+
88+
func numbersOnly(v interface{}) (value string) {
89+
input := fmt.Sprintf("%v", v)
90+
91+
re := regexp.MustCompile(`[^\d]`)
92+
value = re.ReplaceAllString(input, "")
93+
return
94+
}
95+
96+
func formatDate(dateStr string) (time.Time, error) {
97+
return time.Parse("20060102", dateStr)
98+
}
99+
100+
func reformatBirthday(datePart string) string {
101+
if len(datePart) == 6 {
102+
day := datePart[:2]
103+
month := datePart[2:4]
104+
year := datePart[4:6]
105+
return fmt.Sprintf("%s%s%s", year, month, day)
106+
}
107+
return ""
108+
}

validate/nik/regx.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package nik
2+
3+
// NIK_REGEX is a regular expression pattern used to validate a National Identification Number (NIK).
4+
const NIK_REGEX = `^(\d{2})(\d{2})(\d{2})(\d{6})(\d{4})$`
5+
6+
// NIK_LENGTH is the length of a National Identification Number (NIK) in Indonesia.
7+
const NIK_LENGTH = 16

0 commit comments

Comments
 (0)