working on auth.

mailer, basic setup with html template and a dev treansport
This commit is contained in:
2024-11-15 21:42:15 +05:30
parent b0db98452a
commit 26a00c9f7c
45 changed files with 923 additions and 252 deletions

75
util/crypto/hash.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}