working on auth.
mailer, basic setup with html template and a dev treansport
This commit is contained in:
75
util/crypto/hash.go
Normal file
75
util/crypto/hash.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
|
||||
"gitserver.in/patialtech/rano/util/logger"
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
func MD5(b []byte) string {
|
||||
hash := md5.Sum(b)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func MD5Int(b []byte) uint64 {
|
||||
n := new(big.Int)
|
||||
n.SetString(MD5(b), 16)
|
||||
return n.Uint64()
|
||||
}
|
||||
|
||||
// Password using Argon2id
|
||||
//
|
||||
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
|
||||
func PasswordHash(pwd string) (hash, salt string, err error) {
|
||||
var sl []byte
|
||||
sl, err = randomSecret(32)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Generate hash
|
||||
h := argon2.IDKey([]byte(pwd), sl, 3, 12288, 1, 32)
|
||||
|
||||
hash = base64.StdEncoding.EncodeToString(h)
|
||||
salt = base64.StdEncoding.EncodeToString(sl)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ComparePassword
|
||||
func ComparePasswordHash(pwd, hash, salt string) bool {
|
||||
var h, s []byte
|
||||
var err error
|
||||
|
||||
if h, err = base64.StdEncoding.DecodeString(hash); err != nil {
|
||||
logger.Error(err, slog.String("ref", "util/crypto.ComparePasswordHash decode hash"))
|
||||
}
|
||||
|
||||
if s, err = base64.StdEncoding.DecodeString(salt); err != nil {
|
||||
logger.Error(err, slog.String("ref", "util/crypto.ComparePasswordHash decode salt"))
|
||||
}
|
||||
|
||||
// Generate hash for comparison.
|
||||
ph := argon2.IDKey([]byte(pwd), s, 3, 12288, 1, 32)
|
||||
|
||||
// Compare the generated hash with the stored hash.
|
||||
// If they don't match return error.
|
||||
return bytes.Equal(h, ph)
|
||||
}
|
||||
|
||||
func randomSecret(length uint32) ([]byte, error) {
|
||||
secret := make([]byte, length)
|
||||
_, err := rand.Read(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
22
util/crypto/hash_test.go
Normal file
22
util/crypto/hash_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package crypto
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPasswordHash(t *testing.T) {
|
||||
pwd := "MY Bingo pwd"
|
||||
hash, salt, err := PasswordHash(pwd)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if hash == "" || salt == "" {
|
||||
t.Error("either hash or password is empty")
|
||||
return
|
||||
}
|
||||
|
||||
if !ComparePasswordHash(pwd, string(hash), string(salt)) {
|
||||
t.Error("supposed to match")
|
||||
}
|
||||
|
||||
}
|
45
util/logger/logger.go
Normal file
45
util/logger/logger.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Info(msg string, args ...any) {
|
||||
a, b := getArgs(args)
|
||||
slog.Info(fmt.Sprintf(msg, a...), b...)
|
||||
}
|
||||
|
||||
func Warn(msg string, args ...any) {
|
||||
a, b := getArgs(args)
|
||||
slog.Warn(fmt.Sprintf(msg, a...), b...)
|
||||
}
|
||||
|
||||
func Error(err error, args ...any) {
|
||||
a, b := getArgs(args)
|
||||
slog.Error(fmt.Sprintf(err.Error(), a...), b...)
|
||||
// TODO: save error log for later scrutiny
|
||||
}
|
||||
|
||||
// Fatal error will exit with os.Exit(1)
|
||||
func Fatal(msg string, args ...any) {
|
||||
a, b := getArgs(args)
|
||||
slog.Error(fmt.Sprintf(msg, a...), b...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func getArgs(args []any) ([]any, []any) {
|
||||
var a []any
|
||||
var b []any
|
||||
for _, arg := range args {
|
||||
switch arg.(type) {
|
||||
case slog.Attr:
|
||||
b = append(b, arg)
|
||||
default:
|
||||
a = append(a, arg)
|
||||
}
|
||||
}
|
||||
|
||||
return a, b
|
||||
}
|
15
util/open/darwin.go
Normal file
15
util/open/darwin.go
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build darwin
|
||||
|
||||
package open
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func open(input string) *exec.Cmd {
|
||||
return exec.Command("open", input)
|
||||
}
|
||||
|
||||
func openWith(input string, appName string) *exec.Cmd {
|
||||
return exec.Command("open", "-a", appName, input)
|
||||
}
|
17
util/open/linux.go
Normal file
17
util/open/linux.go
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build linux
|
||||
|
||||
package open
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// http://sources.debian.net/src/xdg-utils
|
||||
|
||||
func open(input string) *exec.Cmd {
|
||||
return exec.Command("xdg-open", input)
|
||||
}
|
||||
|
||||
func openWith(input string, appName string) *exec.Cmd {
|
||||
return exec.Command(appName, input)
|
||||
}
|
12
util/open/open.go
Normal file
12
util/open/open.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package open
|
||||
|
||||
// WithDefaultApp open a file, directory, or URI using the OS's default application for that object type.
|
||||
func WithDefaultApp(input string) error {
|
||||
cmd := open(input)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// WithApp will open a file directory, or URI using the specified application.
|
||||
func App(input string, appName string) error {
|
||||
return openWith(input, appName).Run()
|
||||
}
|
32
util/open/windows.go
Normal file
32
util/open/windows.go
Normal file
@@ -0,0 +1,32 @@
|
||||
//go:build windows
|
||||
|
||||
package open
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
cmd = "url.dll,FileProtocolHandler"
|
||||
runDll32 = filepath.Join(os.Getenv("SYSTEMROOT"), "System32", "rundll32.exe")
|
||||
)
|
||||
|
||||
func cleaninput(input string) string {
|
||||
r := strings.NewReplacer("&", "^&")
|
||||
return r.Replace(input)
|
||||
}
|
||||
|
||||
func open(input string) *exec.Cmd {
|
||||
cmd := exec.Command(runDll32, cmd, input)
|
||||
// cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func openWith(input string, appName string) *exec.Cmd {
|
||||
cmd := exec.Command("cmd", "/C", "start", "", appName, cleaninput(input))
|
||||
// cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
return cmd
|
||||
}
|
33
util/structs/structs.go
Normal file
33
util/structs/structs.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func Map(obj interface{}) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
|
||||
val := reflect.ValueOf(obj)
|
||||
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
typ := val.Type()
|
||||
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
fieldName := typ.Field(i).Name
|
||||
fieldValueKind := val.Field(i).Kind()
|
||||
var fieldValue interface{}
|
||||
|
||||
if fieldValueKind == reflect.Struct {
|
||||
fieldValue = Map(val.Field(i).Interface())
|
||||
} else {
|
||||
fieldValue = val.Field(i).Interface()
|
||||
}
|
||||
|
||||
result[fieldName] = fieldValue
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
26
util/validate/validate.go
Normal file
26
util/validate/validate.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package validate
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
var validate *validator.Validate
|
||||
|
||||
func init() {
|
||||
validate = validator.New()
|
||||
// register function to get tag name from json tags.
|
||||
validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
||||
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
|
||||
if name == "-" {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
})
|
||||
}
|
||||
|
||||
func Struct(s any) error {
|
||||
return validate.Struct(s)
|
||||
}
|
Reference in New Issue
Block a user