feat: add admin panel with multi-chain support and notification system

- Add full admin REST API: auth, API keys, chains, chain tokens, wallets,
  orders, RPC nodes, settings, dashboard stats, notifications
- Add JWT-based admin authentication and API key auth middleware
- Add multi-chain listeners: BSC, ETH, Polygon, Plasma, EVM WebSocket
- Add RPC node health check job with automatic failover
- Add Telegram notification channel for order events
- Add installer for first-run database initialization
- Add new DB models: admin_user, api_key, chain, chain_token,
  rpc_node, settings, notification_channel
- Add order statistics aggregation (daily/monthly/by-chain)
- Serve built admin SPA from embedded www/ assets
- Add comprehensive test coverage for all new features
This commit is contained in:
line-6000
2026-04-22 02:28:31 +08:00
parent 097c716714
commit 6bb47d4b00
247 changed files with 9933 additions and 1279 deletions
+78
View File
@@ -0,0 +1,78 @@
package jwt
import (
"crypto/rand"
"encoding/hex"
"errors"
"time"
"github.com/assimon/luuu/model/data"
"github.com/assimon/luuu/model/mdb"
"github.com/golang-jwt/jwt/v4"
)
// DefaultExpiration is the token lifetime for admin sessions.
const DefaultExpiration = 24 * time.Hour
// AdminClaims is the JWT payload for admin sessions.
type AdminClaims struct {
AdminUserID uint64 `json:"uid"`
Username string `json:"usr"`
jwt.RegisteredClaims
}
// EnsureSecret reads system.jwt_secret from settings; if absent,
// generates a new 32-byte random hex string and persists it. Called once
// at startup so subsequent sign/verify can assume the secret exists.
func EnsureSecret() (string, error) {
secret := data.GetSettingString(mdb.SettingKeyJwtSecret, "")
if secret != "" {
return secret, nil
}
buf := make([]byte, 32)
if _, err := rand.Read(buf); err != nil {
return "", err
}
secret = hex.EncodeToString(buf)
if err := data.SetSetting(mdb.SettingGroupSystem, mdb.SettingKeyJwtSecret, secret, mdb.SettingTypeString); err != nil {
return "", err
}
return secret, nil
}
// Sign returns a signed JWT for the given admin user.
func Sign(userID uint64, username string) (string, error) {
secret, err := EnsureSecret()
if err != nil {
return "", err
}
claims := AdminClaims{
AdminUserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(DefaultExpiration)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(secret))
}
// Parse validates a token string and returns its claims.
func Parse(tokenStr string) (*AdminClaims, error) {
secret, err := EnsureSecret()
if err != nil {
return nil, err
}
claims := &AdminClaims{}
_, err = jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("unexpected signing method")
}
return []byte(secret), nil
})
if err != nil {
return nil, err
}
return claims, nil
}