structure: 确定基本结构(保持基本形式, 采用组合)

This commit is contained in:
loveuer
2024-11-01 17:47:33 +08:00
parent 9e8a47a7c6
commit 56cfd42bb9
52 changed files with 1003 additions and 176 deletions

View File

@@ -10,7 +10,7 @@ import (
"github.com/loveuer/nf/nft/resp"
"golang.org/x/oauth2"
"net/http"
"uauth/internal/tool"
"uauth/tool"
)
//go:embed login.html

View File

@@ -4,10 +4,11 @@ import (
"github.com/spf13/cobra"
"uauth/internal/opt"
"uauth/internal/serve"
"uauth/internal/store/cache"
"uauth/internal/store/db"
"uauth/internal/tool"
"uauth/model"
"uauth/pkg/cache"
"uauth/pkg/rbac"
"uauth/pkg/store"
"uauth/tool"
)
func initServe() *cobra.Command {
@@ -16,8 +17,9 @@ func initServe() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
tool.TablePrinter(opt.Cfg)
tool.Must(cache.Init(opt.Cfg.Svc.Cache))
tool.Must(db.Init(cmd.Context(), opt.Cfg.Svc.DB))
tool.Must(model.Init(db.Default.Session()))
tool.Must(store.Init(opt.Cfg.Svc.DB, store.Config{Debug: opt.Cfg.Debug}))
tool.Must(model.Init(store.Default.Session(tool.Timeout())))
tool.Must(rbac.Init(store.Default, cache.Client))
return serve.Run(cmd.Context())
},
}

View File

@@ -1,22 +0,0 @@
package interfaces
import (
"context"
"time"
)
type Cacher interface {
Get(ctx context.Context, key string) ([]byte, error)
GetScan(ctx context.Context, key string) Scanner
GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error)
GetExScan(ctx context.Context, key string, duration time.Duration) Scanner
// Set value 会被序列化, 优先使用 MarshalBinary 方法, 没有则执行 json.Marshal
Set(ctx context.Context, key string, value any) error
// SetEx value 会被序列化, 优先使用 MarshalBinary 方法, 没有则执行 json.Marshal
SetEx(ctx context.Context, key string, value any, duration time.Duration) error
Del(ctx context.Context, keys ...string) error
}
type Scanner interface {
Scan(model any) error
}

View File

@@ -1,11 +0,0 @@
package interfaces
type Enum interface {
Value() int64
Code() string
Label() string
MarshalJSON() ([]byte, error)
All() []Enum
}

View File

@@ -1,7 +0,0 @@
package interfaces
type OpLogger interface {
Enum
Render(content map[string]any) (string, error)
Template() string
}

View File

@@ -1,118 +0,0 @@
package auth
import (
"errors"
"github.com/loveuer/nf"
"github.com/loveuer/nf/nft/log"
"github.com/loveuer/nf/nft/resp"
"net/http"
"time"
"uauth/internal/store/cache"
"uauth/internal/tool"
"uauth/model"
)
type Config struct {
IgnoreFn func(c *nf.Ctx) bool
TokenFn func(c *nf.Ctx) (string, bool)
GetUserFn func(c *nf.Ctx, token string) (*model.User, error)
NextOnError bool
}
var (
defaultIgnoreFn = func(c *nf.Ctx) bool {
return false
}
defaultTokenFn = func(c *nf.Ctx) (string, bool) {
var token string
if token = c.Request.Header.Get("Authorization"); token != "" {
return token, true
}
if token = c.Query("access_token"); token != "" {
return token, true
}
if token = c.Cookies("access_token"); token != "" {
return token, true
}
return "", false
}
defaultGetUserFn = func(c *nf.Ctx, token string) (*model.User, error) {
var (
err error
op = new(model.User)
key = cache.Prefix + "token:" + token
)
if err = cache.Client.GetExScan(tool.Timeout(3), key, 24*time.Hour).Scan(op); err != nil {
if errors.Is(err, cache.ErrorKeyNotFound) {
return nil, err
}
log.Error("[M] cache client get user by token key = %s, err = %s", key, err.Error())
return nil, errors.New("Internal Server Error")
}
return op, nil
}
)
func New(cfgs ...*Config) nf.HandlerFunc {
var cfg = &Config{}
if len(cfgs) > 0 && cfgs[0] != nil {
cfg = cfgs[0]
}
if cfg.IgnoreFn == nil {
cfg.IgnoreFn = defaultIgnoreFn
}
if cfg.TokenFn == nil {
cfg.TokenFn = defaultTokenFn
}
if cfg.GetUserFn == nil {
cfg.GetUserFn = defaultGetUserFn
}
return func(c *nf.Ctx) error {
if cfg.IgnoreFn(c) {
return c.Next()
}
token, ok := cfg.TokenFn(c)
if !ok {
if cfg.NextOnError {
return c.Next()
}
return resp.Resp401(c, nil, "请登录")
}
op, err := cfg.GetUserFn(c, token)
if err != nil {
if cfg.NextOnError {
return c.Next()
}
if errors.Is(err, cache.ErrorKeyNotFound) {
return c.Status(http.StatusUnauthorized).JSON(map[string]any{
"status": 500,
"msg": "用户认证信息不存在或已过期, 请重新登录",
})
}
return c.Status(http.StatusInternalServerError).SendString("Internal Server Error")
}
c.Locals("user", op)
return c.Next()
}
}

View File

@@ -12,9 +12,10 @@ import (
"net/http"
"net/url"
"time"
"uauth/internal/store/cache"
"uauth/internal/store/db"
"uauth/model"
"uauth/pkg/cache"
"uauth/pkg/store"
"uauth/tool"
)
//go:embed serve_approve.html
@@ -51,7 +52,8 @@ func Approve(c *nf.Ctx) error {
return c.Status(http.StatusBadRequest).SendString("Bad Request: invalid redirect uri")
}
if err = db.Default.Session().Where("client_id", req.ClientId).Take(client).Error; err != nil {
if err = store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Where("client_id", req.ClientId).Take(client).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return c.Status(http.StatusBadRequest).SendString("Bad Request: invalid client_id")
}
@@ -60,7 +62,8 @@ func Approve(c *nf.Ctx) error {
return c.Status(http.StatusInternalServerError).SendString("Internal Server Error")
}
db.Default.Session().Clauses(clause.OnConflict{DoNothing: true}).
store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Clauses(clause.OnConflict{DoNothing: true}).
Create(&model.AuthorizationRecord{
UserId: op.Id,
ClientId: client.Id,

View File

@@ -11,9 +11,10 @@ import (
"net/http"
"net/url"
"time"
"uauth/internal/store/cache"
"uauth/internal/store/db"
"uauth/model"
"uauth/pkg/cache"
"uauth/pkg/store"
"uauth/tool"
)
var (
@@ -59,7 +60,8 @@ func Authorize(c *nf.Ctx) error {
log.Info("[S] Authorize: username = %s, client_id = %s", op.Username, req.ClientId)
if err = db.Default.Session().Where("client_id", req.ClientId).Take(client).Error; err != nil {
if err = store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Where("client_id", req.ClientId).Take(client).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return c.Status(http.StatusBadRequest).SendString("Bad Request: invalid client_id")
}
@@ -68,7 +70,8 @@ func Authorize(c *nf.Ctx) error {
return c.Status(http.StatusInternalServerError).SendString("Internal Server Error")
}
if err = db.Default.Session().Model(&model.AuthorizationRecord{}).
if err = store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Model(&model.AuthorizationRecord{}).
Where("user_id", op.Id).
Where("client_id", client.Id).
Take(authRecord).

View File

@@ -9,10 +9,10 @@ import (
"gorm.io/gorm"
"net/http"
"time"
"uauth/internal/store/cache"
"uauth/internal/store/db"
"uauth/internal/tool"
"uauth/model"
"uauth/pkg/cache"
"uauth/pkg/store"
"uauth/tool"
)
var (
@@ -43,7 +43,8 @@ func LoginPage(c *nf.Ctx) error {
return resp.Resp400(c, req)
}
if err = db.Default.Session().Model(&model.Client{}).
if err = store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Model(&model.Client{}).
Where("client_id = ?", req.ClientId).
Take(client).
Error; err != nil {
@@ -96,7 +97,8 @@ func LoginAction(c *nf.Ctx) error {
return c.Status(http.StatusBadRequest).SendString("Bad Request: username, password is required")
}
if err = db.Default.Session().Model(&model.User{}).
if err = store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Model(&model.User{}).
Where("username = ?", req.Username).
Take(op).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {

View File

@@ -6,9 +6,9 @@ import (
"github.com/loveuer/nf"
"github.com/loveuer/nf/nft/resp"
"gorm.io/gorm"
"uauth/internal/store/db"
"uauth/internal/tool"
"uauth/model"
"uauth/pkg/store"
"uauth/tool"
)
func ClientRegistry(c *nf.Ctx) error {
@@ -36,7 +36,8 @@ func ClientRegistry(c *nf.Ctx) error {
ClientSecret: Secret,
}
if err = db.Default.Session().Create(platform).Error; err != nil {
if err = store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Create(platform).Error; err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return resp.Resp400(c, err, "当前平台已经存在")
}
@@ -83,7 +84,8 @@ func UserRegistryAction(c *nf.Ctx) error {
Password: tool.NewPassword(req.Password),
}
if err = db.Default.Session().Create(op).Error; err != nil {
if err = store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Create(op).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return resp.Resp400(c, err, "用户名已存在")
}

View File

@@ -9,10 +9,10 @@ import (
"gorm.io/gorm"
"net/http"
"strings"
"uauth/internal/store/cache"
"uauth/internal/store/db"
"uauth/internal/tool"
"uauth/model"
"uauth/pkg/cache"
"uauth/pkg/store"
"uauth/tool"
)
func verifyClient(c *nf.Ctx) (*model.Client, error) {
@@ -46,7 +46,8 @@ func verifyClient(c *nf.Ctx) (*model.Client, error) {
}
clientId, clientSecret := strs[0], strs[1]
if err = db.Default.Session().Model(&model.Client{}).
if err = store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Model(&model.Client{}).
Where("client_id", clientId).
Take(client).
Error; err != nil {
@@ -84,7 +85,8 @@ func HandleToken(c *nf.Ctx) error {
username := c.Form("username")
password := c.Form("password")
if err = db.Default.Session().Model(&model.User{}).
if err = store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Model(&model.User{}).
Where("username = ?", username).
Take(op).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -118,7 +120,8 @@ func HandleToken(c *nf.Ctx) error {
}
op.Id = opId
if err = db.Default.Session().Take(op).Error; err != nil {
if err = store.Default.Session(tool.TimeoutCtx(c.Context(), 3)).
Take(op).Error; err != nil {
log.Error("[S] handleToken: get op by id err, id = %d, err = %s", opId, err.Error())
return c.Status(http.StatusInternalServerError).SendString("Internal Server Error")
}

View File

@@ -4,10 +4,10 @@ import (
"context"
"github.com/loveuer/nf"
"github.com/loveuer/nf/nft/log"
"uauth/internal/middleware/auth"
"uauth/internal/opt"
"uauth/internal/serve/handler"
"uauth/internal/tool"
"uauth/pkg/middleware/auth"
"uauth/tool"
)
func Run(ctx context.Context) error {

View File

@@ -1,117 +0,0 @@
package cache
import (
"context"
"github.com/hashicorp/golang-lru/v2/expirable"
_ "github.com/hashicorp/golang-lru/v2/expirable"
"time"
"uauth/internal/interfaces"
)
var _ interfaces.Cacher = (*_lru)(nil)
type _lru struct {
client *expirable.LRU[string, *_lru_value]
}
type _lru_value struct {
duration time.Duration
last time.Time
bs []byte
}
func (l *_lru) Get(ctx context.Context, key string) ([]byte, error) {
v, ok := l.client.Get(key)
if !ok {
return nil, ErrorKeyNotFound
}
if v.duration == 0 {
return v.bs, nil
}
if time.Now().Sub(v.last) > v.duration {
l.client.Remove(key)
return nil, ErrorKeyNotFound
}
return v.bs, nil
}
func (l *_lru) GetScan(ctx context.Context, key string) interfaces.Scanner {
return newScanner(l.Get(ctx, key))
}
func (l *_lru) GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error) {
v, ok := l.client.Get(key)
if !ok {
return nil, ErrorKeyNotFound
}
if v.duration == 0 {
return v.bs, nil
}
now := time.Now()
if now.Sub(v.last) > v.duration {
l.client.Remove(key)
return nil, ErrorKeyNotFound
}
l.client.Add(key, &_lru_value{
duration: duration,
last: now,
bs: v.bs,
})
return v.bs, nil
}
func (l *_lru) GetExScan(ctx context.Context, key string, duration time.Duration) interfaces.Scanner {
return newScanner(l.GetEx(ctx, key, duration))
}
func (l *_lru) Set(ctx context.Context, key string, value any) error {
bs, err := handleValue(value)
if err != nil {
return err
}
l.client.Add(key, &_lru_value{
duration: 0,
last: time.Now(),
bs: bs,
})
return nil
}
func (l *_lru) SetEx(ctx context.Context, key string, value any, duration time.Duration) error {
bs, err := handleValue(value)
if err != nil {
return err
}
l.client.Add(key, &_lru_value{
duration: duration,
last: time.Now(),
bs: bs,
})
return nil
}
func (l *_lru) Del(ctx context.Context, keys ...string) error {
for _, key := range keys {
l.client.Remove(key)
}
return nil
}
func newLRUCache() (interfaces.Cacher, error) {
client := expirable.NewLRU[string, *_lru_value](1024*1024, nil, 0)
return &_lru{client: client}, nil
}

View File

@@ -1,82 +0,0 @@
package cache
import (
"context"
"errors"
"fmt"
"time"
"uauth/internal/interfaces"
"gitea.com/taozitaozi/gredis"
)
var _ interfaces.Cacher = (*_mem)(nil)
type _mem struct {
client *gredis.Gredis
}
func (m *_mem) GetScan(ctx context.Context, key string) interfaces.Scanner {
return newScanner(m.Get(ctx, key))
}
func (m *_mem) GetExScan(ctx context.Context, key string, duration time.Duration) interfaces.Scanner {
return newScanner(m.GetEx(ctx, key, duration))
}
func (m *_mem) Get(ctx context.Context, key string) ([]byte, error) {
v, err := m.client.Get(key)
if err != nil {
if errors.Is(err, gredis.ErrKeyNotFound) {
return nil, ErrorKeyNotFound
}
return nil, err
}
bs, ok := v.([]byte)
if !ok {
return nil, fmt.Errorf("invalid value type=%T", v)
}
return bs, nil
}
func (m *_mem) GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error) {
v, err := m.client.GetEx(key, duration)
if err != nil {
if errors.Is(err, gredis.ErrKeyNotFound) {
return nil, ErrorKeyNotFound
}
return nil, err
}
bs, ok := v.([]byte)
if !ok {
return nil, fmt.Errorf("invalid value type=%T", v)
}
return bs, nil
}
func (m *_mem) Set(ctx context.Context, key string, value any) error {
bs, err := handleValue(value)
if err != nil {
return err
}
return m.client.Set(key, bs)
}
func (m *_mem) SetEx(ctx context.Context, key string, value any, duration time.Duration) error {
bs, err := handleValue(value)
if err != nil {
return err
}
return m.client.SetEx(key, bs, duration)
}
func (m *_mem) Del(ctx context.Context, keys ...string) error {
m.client.Delete(keys...)
return nil
}

View File

@@ -1,72 +0,0 @@
package cache
import (
"context"
"errors"
"github.com/go-redis/redis/v8"
"time"
"uauth/internal/interfaces"
)
type _redis struct {
client *redis.Client
}
func (r *_redis) Get(ctx context.Context, key string) ([]byte, error) {
result, err := r.client.Get(ctx, key).Result()
if err != nil {
if errors.Is(err, redis.Nil) {
return nil, ErrorKeyNotFound
}
return nil, err
}
return []byte(result), nil
}
func (r *_redis) GetScan(ctx context.Context, key string) interfaces.Scanner {
return newScanner(r.Get(ctx, key))
}
func (r *_redis) GetEx(ctx context.Context, key string, duration time.Duration) ([]byte, error) {
result, err := r.client.GetEx(ctx, key, duration).Result()
if err != nil {
if errors.Is(err, redis.Nil) {
return nil, ErrorKeyNotFound
}
return nil, err
}
return []byte(result), nil
}
func (r *_redis) GetExScan(ctx context.Context, key string, duration time.Duration) interfaces.Scanner {
return newScanner(r.GetEx(ctx, key, duration))
}
func (r *_redis) Set(ctx context.Context, key string, value any) error {
bs, err := handleValue(value)
if err != nil {
return err
}
_, err = r.client.Set(ctx, key, bs, redis.KeepTTL).Result()
return err
}
func (r *_redis) SetEx(ctx context.Context, key string, value any, duration time.Duration) error {
bs, err := handleValue(value)
if err != nil {
return err
}
_, err = r.client.SetEX(ctx, key, bs, duration).Result()
return err
}
func (r *_redis) Del(ctx context.Context, keys ...string) error {
return r.client.Del(ctx, keys...).Err()
}

View File

@@ -1,42 +0,0 @@
package cache
import (
"encoding/json"
"uauth/internal/interfaces"
)
const (
Prefix = "sys:uauth:"
)
var (
Client interfaces.Cacher
)
type encoded_value interface {
MarshalBinary() ([]byte, error)
}
type decoded_value interface {
UnmarshalBinary(bs []byte) error
}
func handleValue(value any) ([]byte, error) {
var (
bs []byte
err error
)
switch value.(type) {
case []byte:
return value.([]byte), nil
}
if imp, ok := value.(encoded_value); ok {
bs, err = imp.MarshalBinary()
} else {
bs, err = json.Marshal(value)
}
return bs, err
}

View File

@@ -1,7 +0,0 @@
package cache
import "errors"
var (
ErrorKeyNotFound = errors.New("key not found")
)

View File

@@ -1,69 +0,0 @@
package cache
import (
"fmt"
"gitea.com/taozitaozi/gredis"
"github.com/go-redis/redis/v8"
"net/url"
"strings"
"uauth/internal/tool"
)
func Init(uri string) error {
var (
err error
)
strs := strings.Split(uri, "::")
switch strs[0] {
case "memory":
gc := gredis.NewGredis(1024 * 1024)
Client = &_mem{client: gc}
case "lru":
if Client, err = newLRUCache(); err != nil {
return err
}
case "redis":
var (
ins *url.URL
err error
)
if len(strs) != 2 {
return fmt.Errorf("cache.Init: invalid cache uri: %s", uri)
}
uri := strs[1]
if !strings.Contains(uri, "://") {
uri = fmt.Sprintf("redis://%s", uri)
}
if ins, err = url.Parse(uri); err != nil {
return fmt.Errorf("cache.Init: url parse cache uri: %s, err: %s", uri, err.Error())
}
addr := ins.Host
username := ins.User.Username()
password, _ := ins.User.Password()
var rc *redis.Client
rc = redis.NewClient(&redis.Options{
Addr: addr,
Username: username,
Password: password,
})
if err = rc.Ping(tool.Timeout(5)).Err(); err != nil {
return fmt.Errorf("cache.Init: redis ping err: %s", err.Error())
}
Client = &_redis{client: rc}
default:
return fmt.Errorf("cache type %s not support", strs[0])
}
return nil
}

View File

@@ -1,20 +0,0 @@
package cache
import "encoding/json"
type scanner struct {
err error
bs []byte
}
func (s *scanner) Scan(model any) error {
if s.err != nil {
return s.err
}
return json.Unmarshal(s.bs, model)
}
func newScanner(bs []byte, err error) *scanner {
return &scanner{bs: bs, err: err}
}

View File

@@ -1,45 +0,0 @@
package db
import (
"context"
"uauth/internal/opt"
"uauth/internal/tool"
"gorm.io/gorm"
)
var (
Default *Client
)
type Client struct {
ctx context.Context
cli *gorm.DB
ttype string
}
func (c *Client) Type() string {
return c.ttype
}
func (c *Client) Session(ctxs ...context.Context) *gorm.DB {
var ctx context.Context
if len(ctxs) > 0 && ctxs[0] != nil {
ctx = ctxs[0]
} else {
ctx = tool.Timeout(30)
}
session := c.cli.Session(&gorm.Session{Context: ctx})
if opt.Cfg.Debug {
session = session.Debug()
}
return session
}
func (c *Client) Close() {
d, _ := c.cli.DB()
d.Close()
}

View File

@@ -1,9 +0,0 @@
package db
import (
"testing"
)
func TestOpen(t *testing.T) {
}

View File

@@ -1,52 +0,0 @@
package db
import (
"context"
"fmt"
"strings"
"github.com/glebarez/sqlite"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func New(ctx context.Context, uri string) (*Client, error) {
strs := strings.Split(uri, "::")
if len(strs) != 2 {
return nil, fmt.Errorf("db.Init: opt db uri invalid: %s", uri)
}
c := &Client{ttype: strs[0]}
var (
err error
dsn = strs[1]
)
switch strs[0] {
case "sqlite":
c.cli, err = gorm.Open(sqlite.Open(dsn))
case "mysql":
c.cli, err = gorm.Open(mysql.Open(dsn))
case "postgres":
c.cli, err = gorm.Open(postgres.Open(dsn))
default:
return nil, fmt.Errorf("db type only support: [sqlite, mysql, postgres], unsupported db type: %s", strs[0])
}
if err != nil {
return nil, fmt.Errorf("db.Init: open %s with dsn:%s, err: %w", strs[0], dsn, err)
}
return c, nil
}
func Init(ctx context.Context, uri string) (err error) {
if Default, err = New(ctx, uri); err != nil {
return err
}
return nil
}

View File

@@ -1,104 +0,0 @@
package tool
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"time"
)
func GenerateTlsConfig() (serverTLSConf *tls.Config, clientTLSConf *tls.Config, err error) {
ca := &x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
Organization: []string{"Company, INC."},
Country: []string{"US"},
Province: []string{"California"},
Locality: []string{"San Francisco"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(99, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
// create our private and public key
caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, err
}
// create the CA
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
if err != nil {
return nil, nil, err
}
// pem encode
caPEM := new(bytes.Buffer)
pem.Encode(caPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})
caPrivKeyPEM := new(bytes.Buffer)
pem.Encode(caPrivKeyPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
})
// set up our server certificate
cert := &x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
Organization: []string{"Company, INC."},
Country: []string{"US"},
Province: []string{"California"},
Locality: []string{"San Francisco"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, err
}
certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey)
if err != nil {
return nil, nil, err
}
certPEM := new(bytes.Buffer)
pem.Encode(certPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
certPrivKeyPEM := new(bytes.Buffer)
pem.Encode(certPrivKeyPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
})
serverCert, err := tls.X509KeyPair(certPEM.Bytes(), certPrivKeyPEM.Bytes())
if err != nil {
return nil, nil, err
}
serverTLSConf = &tls.Config{
Certificates: []tls.Certificate{serverCert},
}
certpool := x509.NewCertPool()
certpool.AppendCertsFromPEM(caPEM.Bytes())
clientTLSConf = &tls.Config{
RootCAs: certpool,
}
return
}

View File

@@ -1,38 +0,0 @@
package tool
import (
"context"
"time"
)
func Timeout(seconds ...int) (ctx context.Context) {
var (
duration time.Duration
)
if len(seconds) > 0 && seconds[0] > 0 {
duration = time.Duration(seconds[0]) * time.Second
} else {
duration = time.Duration(30) * time.Second
}
ctx, _ = context.WithTimeout(context.Background(), duration)
return
}
func TimeoutCtx(ctx context.Context, seconds ...int) context.Context {
var (
duration time.Duration
)
if len(seconds) > 0 && seconds[0] > 0 {
duration = time.Duration(seconds[0]) * time.Second
} else {
duration = time.Duration(30) * time.Second
}
nctx, _ := context.WithTimeout(ctx, duration)
return nctx
}

View File

@@ -1,30 +0,0 @@
package tool
import (
"io"
"os"
)
func CopyFile(src string, dst string) (err error) {
// Open the source file
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
// Create the destination file
destinationFile, err := os.Create(dst)
if err != nil {
return err
}
defer destinationFile.Close()
// Copy the contents from source to destination
_, err = io.Copy(destinationFile, sourceFile)
if err != nil {
return err
}
return nil
}

View File

@@ -1,24 +0,0 @@
package tool
import "fmt"
func HumanDuration(nano int64) string {
duration := float64(nano)
unit := "ns"
if duration >= 1000 {
duration /= 1000
unit = "us"
}
if duration >= 1000 {
duration /= 1000
unit = "ms"
}
if duration >= 1000 {
duration /= 1000
unit = " s"
}
return fmt.Sprintf("%6.2f%s", duration, unit)
}

View File

@@ -1,11 +0,0 @@
package tool
import "github.com/loveuer/nf/nft/log"
func Must(errs ...error) {
for _, err := range errs {
if err != nil {
log.Panic(err.Error())
}
}
}

View File

@@ -1,84 +0,0 @@
package tool
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"github.com/loveuer/nf/nft/log"
"golang.org/x/crypto/pbkdf2"
"regexp"
"strconv"
"strings"
)
const (
EncryptHeader string = "pbkdf2:sha256" // 用户密码加密
)
func NewPassword(password string) string {
return EncryptPassword(password, RandomString(8), int(RandomInt(50000)+100000))
}
func ComparePassword(in, db string) bool {
strs := strings.Split(db, "$")
if len(strs) != 3 {
log.Error("password in db invalid: %s", db)
return false
}
encs := strings.Split(strs[0], ":")
if len(encs) != 3 {
log.Error("password in db invalid: %s", db)
return false
}
encIteration, err := strconv.Atoi(encs[2])
if err != nil {
log.Error("password in db invalid: %s, convert iter err: %s", db, err)
return false
}
return EncryptPassword(in, strs[1], encIteration) == db
}
func EncryptPassword(password, salt string, iter int) string {
hash := pbkdf2.Key([]byte(password), []byte(salt), iter, 32, sha256.New)
encrypted := hex.EncodeToString(hash)
return fmt.Sprintf("%s:%d$%s$%s", EncryptHeader, iter, salt, encrypted)
}
func CheckPassword(password string) error {
if len(password) < 8 || len(password) > 32 {
return errors.New("密码长度不符合")
}
var (
err error
match bool
patternList = []string{`[0-9]+`, `[a-z]+`, `[A-Z]+`, `[!@#%]+`} //, `[~!@#$%^&*?_-]+`}
matchAccount = 0
tips = []string{"缺少数字", "缺少小写字母", "缺少大写字母", "缺少'!@#%'"}
locktips = make([]string, 0)
)
for idx, pattern := range patternList {
match, err = regexp.MatchString(pattern, password)
if err != nil {
log.Warn("regex match string err, reg_str: %s, err: %v", pattern, err)
return errors.New("密码强度不够")
}
if match {
matchAccount++
} else {
locktips = append(locktips, tips[idx])
}
}
if matchAccount < 3 {
return fmt.Errorf("密码强度不够, 可能 %s", strings.Join(locktips, ", "))
}
return nil
}

View File

@@ -1,11 +0,0 @@
package tool
import "testing"
func TestEncPassword(t *testing.T) {
password := "123456"
result := EncryptPassword(password, RandomString(8), 50000)
t.Logf("sum => %s", result)
}

View File

@@ -1,54 +0,0 @@
package tool
import (
"crypto/rand"
"math/big"
)
var (
letters = []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
letterNum = []byte("0123456789")
letterLow = []byte("abcdefghijklmnopqrstuvwxyz")
letterCap = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
letterSyb = []byte("!@#$%^&*()_+-=")
)
func RandomInt(max int64) int64 {
num, _ := rand.Int(rand.Reader, big.NewInt(max))
return num.Int64()
}
func RandomString(length int) string {
result := make([]byte, length)
for i := 0; i < length; i++ {
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
result[i] = letters[num.Int64()]
}
return string(result)
}
func RandomPassword(length int, withSymbol bool) string {
result := make([]byte, length)
kind := 3
if withSymbol {
kind++
}
for i := 0; i < length; i++ {
switch i % kind {
case 0:
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterNum))))
result[i] = letterNum[num.Int64()]
case 1:
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterLow))))
result[i] = letterLow[num.Int64()]
case 2:
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterCap))))
result[i] = letterCap[num.Int64()]
case 3:
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letterSyb))))
result[i] = letterSyb[num.Int64()]
}
}
return string(result)
}

View File

@@ -1,5 +0,0 @@
package tool
func Bulk[T any](slice []T, size int) {
// todo
}

View File

@@ -1 +0,0 @@
package tool

View File

@@ -1,124 +0,0 @@
package tool
import (
"encoding/json"
"fmt"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/loveuer/nf/nft/log"
"io"
"os"
"reflect"
"strings"
)
func TablePrinter(data any, writers ...io.Writer) {
var w io.Writer = os.Stdout
if len(writers) > 0 && writers[0] != nil {
w = writers[0]
}
t := table.NewWriter()
structPrinter(t, "", data)
_, _ = fmt.Fprintln(w, t.Render())
}
func structPrinter(w table.Writer, prefix string, item any) {
Start:
rv := reflect.ValueOf(item)
if rv.IsZero() {
return
}
for rv.Type().Kind() == reflect.Pointer {
rv = rv.Elem()
}
switch rv.Type().Kind() {
case reflect.Invalid,
reflect.Uintptr,
reflect.Chan,
reflect.Func,
reflect.UnsafePointer:
case reflect.Bool,
reflect.Int,
reflect.Int8,
reflect.Int16,
reflect.Int32,
reflect.Int64,
reflect.Uint,
reflect.Uint8,
reflect.Uint16,
reflect.Uint32,
reflect.Uint64,
reflect.Float32,
reflect.Float64,
reflect.Complex64,
reflect.Complex128,
reflect.Interface:
w.AppendRow(table.Row{strings.TrimPrefix(prefix, "."), rv.Interface()})
case reflect.String:
val := rv.String()
if len(val) <= 160 {
w.AppendRow(table.Row{strings.TrimPrefix(prefix, "."), val})
return
}
w.AppendRow(table.Row{strings.TrimPrefix(prefix, "."), val[0:64] + "..." + val[len(val)-64:]})
case reflect.Array, reflect.Slice:
for i := 0; i < rv.Len(); i++ {
p := strings.Join([]string{prefix, fmt.Sprintf("[%d]", i)}, ".")
structPrinter(w, p, rv.Index(i).Interface())
}
case reflect.Map:
for _, k := range rv.MapKeys() {
structPrinter(w, fmt.Sprintf("%s.{%v}", prefix, k), rv.MapIndex(k).Interface())
}
case reflect.Pointer:
goto Start
case reflect.Struct:
for i := 0; i < rv.NumField(); i++ {
p := fmt.Sprintf("%s.%s", prefix, rv.Type().Field(i).Name)
field := rv.Field(i)
//log.Debug("TablePrinter: prefix: %s, field: %v", p, rv.Field(i))
if !field.CanInterface() {
return
}
structPrinter(w, p, field.Interface())
}
}
}
func TableMapPrinter(data []byte) {
m := make(map[string]any)
if err := json.Unmarshal(data, &m); err != nil {
log.Warn(err.Error())
return
}
t := table.NewWriter()
addRow(t, "", m)
fmt.Println(t.Render())
}
func addRow(w table.Writer, prefix string, m any) {
rv := reflect.ValueOf(m)
switch rv.Type().Kind() {
case reflect.Map:
for _, k := range rv.MapKeys() {
key := k.String()
if prefix != "" {
key = strings.Join([]string{prefix, k.String()}, ".")
}
addRow(w, key, rv.MapIndex(k).Interface())
}
case reflect.Slice, reflect.Array:
for i := 0; i < rv.Len(); i++ {
addRow(w, fmt.Sprintf("%s[%d]", prefix, i), rv.Index(i).Interface())
}
default:
w.AppendRow(table.Row{prefix, m})
}
}

View File

@@ -1,13 +0,0 @@
package tool
import "time"
// TodayMidnight 返回今日凌晨
func TodayMidnight() (midnight time.Time) {
now := time.Now()
year, month, day := now.Date()
midnight = time.Date(year, month, day, 0, 0, 0, 0, time.Local)
return
}