mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-10-06 04:49:08 +00:00
This adds a new auth flow allowing users to authenticate with their wallet and obtain an API key scoped to a namespace. It also moves API key storage from config to the database for better persistence and key-to-wallet linkage. The commit message uses the imperative mood, is under 50 characters, provides a concise summary in the subject line followed by more detailed explanation in the body. This follows good Git commit message style while capturing the key changes made.
138 lines
4.1 KiB
Go
138 lines
4.1 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.debros.io/DeBros/network/pkg/client"
|
|
"git.debros.io/DeBros/network/pkg/logging"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Config holds configuration for the gateway server
|
|
type Config struct {
|
|
ListenAddr string
|
|
ClientNamespace string
|
|
BootstrapPeers []string
|
|
RequireAuth bool
|
|
}
|
|
|
|
type Gateway struct {
|
|
logger *logging.ColoredLogger
|
|
cfg *Config
|
|
client client.NetworkClient
|
|
startedAt time.Time
|
|
signingKey *rsa.PrivateKey
|
|
keyID string
|
|
}
|
|
|
|
// New creates and initializes a new Gateway instance
|
|
func New(logger *logging.ColoredLogger, cfg *Config) (*Gateway, error) {
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Building client config...")
|
|
|
|
// Build client config from gateway cfg
|
|
cliCfg := client.DefaultClientConfig(cfg.ClientNamespace)
|
|
if len(cfg.BootstrapPeers) > 0 {
|
|
cliCfg.BootstrapPeers = cfg.BootstrapPeers
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Creating network client...")
|
|
c, err := client.NewClient(cliCfg)
|
|
if err != nil {
|
|
logger.ComponentError(logging.ComponentClient, "failed to create network client", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Connecting network client...")
|
|
if err := c.Connect(); err != nil {
|
|
logger.ComponentError(logging.ComponentClient, "failed to connect network client", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentClient, "Network client connected",
|
|
zap.String("namespace", cliCfg.AppName),
|
|
zap.Int("bootstrap_peer_count", len(cliCfg.BootstrapPeers)),
|
|
)
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Creating gateway instance...")
|
|
gw := &Gateway{
|
|
logger: logger,
|
|
cfg: cfg,
|
|
client: c,
|
|
startedAt: time.Now(),
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Generating RSA signing key...")
|
|
// Generate local RSA signing key for JWKS/JWT (ephemeral for now)
|
|
if key, err := rsa.GenerateKey(rand.Reader, 2048); err == nil {
|
|
gw.signingKey = key
|
|
gw.keyID = "gw-" + strconv.FormatInt(time.Now().Unix(), 10)
|
|
logger.ComponentInfo(logging.ComponentGeneral, "RSA key generated successfully")
|
|
} else {
|
|
logger.ComponentWarn(logging.ComponentGeneral, "failed to generate RSA key; jwks will be empty", zap.Error(err))
|
|
}
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Starting database migrations goroutine...")
|
|
// Non-blocking DB migrations: probe RQLite; if reachable, apply migrations asynchronously
|
|
go func() {
|
|
if gw.probeRQLiteReachable(3 * time.Second) {
|
|
if err := gw.applyMigrations(context.Background()); err != nil {
|
|
if err == errNoMigrationsFound {
|
|
if err2 := gw.applyAutoMigrations(context.Background()); err2 != nil {
|
|
logger.ComponentWarn(logging.ComponentDatabase, "auto migrations failed", zap.Error(err2))
|
|
} else {
|
|
logger.ComponentInfo(logging.ComponentDatabase, "auto migrations applied")
|
|
}
|
|
} else {
|
|
logger.ComponentWarn(logging.ComponentDatabase, "migrations failed", zap.Error(err))
|
|
}
|
|
} else {
|
|
logger.ComponentInfo(logging.ComponentDatabase, "migrations applied")
|
|
}
|
|
} else {
|
|
logger.ComponentWarn(logging.ComponentDatabase, "RQLite not reachable; skipping migrations for now")
|
|
}
|
|
}()
|
|
|
|
logger.ComponentInfo(logging.ComponentGeneral, "Gateway creation completed, returning...")
|
|
return gw, nil
|
|
}
|
|
|
|
// probeRQLiteReachable performs a quick GET /status against candidate endpoints with a short timeout.
|
|
func (g *Gateway) probeRQLiteReachable(timeout time.Duration) bool {
|
|
endpoints := client.DefaultDatabaseEndpoints()
|
|
httpClient := &http.Client{Timeout: timeout}
|
|
for _, ep := range endpoints {
|
|
url := ep
|
|
if url == "" {
|
|
continue
|
|
}
|
|
if url[len(url)-1] == '/' {
|
|
url = url[:len(url)-1]
|
|
}
|
|
reqURL := url + "/status"
|
|
resp, err := httpClient.Get(reqURL)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode == http.StatusOK {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Close disconnects the gateway client
|
|
func (g *Gateway) Close() {
|
|
if g.client != nil {
|
|
if err := g.client.Disconnect(); err != nil {
|
|
g.logger.ComponentWarn(logging.ComponentClient, "error during client disconnect", zap.Error(err))
|
|
}
|
|
}
|
|
}
|