mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-10-06 06:19:08 +00:00
Add authentication to protected CLI commands
This commit adds wallet-based authentication to protected CLI commands by removing the manual auth command and automatically prompting for credentials when needed. Protected commands will check for valid credentials and trigger the auth
This commit is contained in:
parent
076edf4208
commit
1fca8cb411
195
cmd/cli/main.go
195
cmd/cli/main.go
@ -6,8 +6,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
@ -88,8 +86,7 @@ func main() {
|
||||
handlePeerID()
|
||||
case "help", "--help", "-h":
|
||||
showHelp()
|
||||
case "auth":
|
||||
handleAuth(args)
|
||||
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
|
||||
showHelp()
|
||||
@ -192,6 +189,9 @@ func handleStatus() {
|
||||
}
|
||||
|
||||
func handleQuery(sql string) {
|
||||
// Ensure user is authenticated
|
||||
_ = ensureAuthenticated()
|
||||
|
||||
client, err := createClient()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err)
|
||||
@ -221,6 +221,9 @@ func handleStorage(args []string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Ensure user is authenticated
|
||||
_ = ensureAuthenticated()
|
||||
|
||||
client, err := createClient()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err)
|
||||
@ -290,6 +293,9 @@ func handlePubSub(args []string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Ensure user is authenticated
|
||||
_ = ensureAuthenticated()
|
||||
|
||||
client, err := createClient()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err)
|
||||
@ -365,164 +371,18 @@ func handlePubSub(args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// handleAuth launches a local webpage to perform wallet signature and obtain an API key.
|
||||
// Usage: network-cli auth [--gateway <url>] [--namespace <ns>] [--wallet <evm_addr>] [--plan <free|premium>]
|
||||
func handleAuth(args []string) {
|
||||
// Defaults
|
||||
gatewayURL := getenvDefault("GATEWAY_URL", "http://localhost:8080")
|
||||
namespace := getenvDefault("GATEWAY_NAMESPACE", "default")
|
||||
wallet := ""
|
||||
plan := "free"
|
||||
// ensureAuthenticated ensures the user has valid credentials for the gateway
|
||||
// Returns the credentials or exits the program on failure
|
||||
func ensureAuthenticated() *auth.Credentials {
|
||||
gatewayURL := auth.GetDefaultGatewayURL()
|
||||
|
||||
// Parse simple flags
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--gateway":
|
||||
if i+1 < len(args) {
|
||||
gatewayURL = strings.TrimSpace(args[i+1])
|
||||
i++
|
||||
}
|
||||
case "--namespace":
|
||||
if i+1 < len(args) {
|
||||
namespace = strings.TrimSpace(args[i+1])
|
||||
i++
|
||||
}
|
||||
case "--wallet":
|
||||
if i+1 < len(args) {
|
||||
wallet = strings.TrimSpace(args[i+1])
|
||||
i++
|
||||
}
|
||||
case "--plan":
|
||||
if i+1 < len(args) {
|
||||
plan = strings.TrimSpace(strings.ToLower(args[i+1]))
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spin up local HTTP server on random port
|
||||
ln, err := net.Listen("tcp", "localhost:0")
|
||||
credentials, err := auth.GetOrPromptForCredentials(gatewayURL)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to listen: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "Authentication failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer ln.Close()
|
||||
addr := ln.Addr().String()
|
||||
// Normalize URL host to localhost for consistency with gateway default
|
||||
parts := strings.Split(addr, ":")
|
||||
listenURL := "http://localhost:" + parts[len(parts)-1] + "/"
|
||||
|
||||
// Channel to receive API key
|
||||
type result struct {
|
||||
APIKey string `json:"api_key"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
resCh := make(chan result, 1)
|
||||
srv := &http.Server{}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
// Root serves the HTML page with embedded gateway URL and defaults
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
fmt.Fprintf(w, `<!doctype html>
|
||||
<html>
|
||||
<head><meta charset="utf-8"><title>DeBros Auth</title>
|
||||
<style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;margin:2rem;max-width:720px}input,button,select{font-size:1rem;padding:.5rem;margin:.25rem 0}code{background:#f5f5f5;padding:.2rem .4rem;border-radius:4px}</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Authenticate with Wallet to Get API Key</h2>
|
||||
<p>This will create or return an API key for namespace <code id="ns"></code> on gateway <code id="gw"></code>.</p>
|
||||
<label>Wallet Address</label><br>
|
||||
<input id="wallet" placeholder="0x..." style="width:100%%"/><br>
|
||||
<label>Plan</label><br>
|
||||
<select id="plan"><option value="free">free</option><option value="premium">premium (0.1 ETH)</option></select><br>
|
||||
<button id="connect">Connect Wallet</button>
|
||||
<button id="sign">Sign & Generate API Key</button>
|
||||
<pre id="out" style="white-space:pre-wrap"></pre>
|
||||
<script>
|
||||
const GATEWAY = %q;
|
||||
const DEFAULT_NS = %q;
|
||||
const DEFAULT_WALLET = %q;
|
||||
document.getElementById('gw').textContent = GATEWAY;
|
||||
document.getElementById('ns').textContent = DEFAULT_NS;
|
||||
document.getElementById('wallet').value = DEFAULT_WALLET;
|
||||
document.getElementById('plan').value = %q;
|
||||
const out = document.getElementById('out');
|
||||
function log(m){ out.textContent += m + "\n" }
|
||||
document.getElementById('connect').onclick = async () => {
|
||||
if (!window.ethereum) { log('No wallet provider found (window.ethereum). Install MetaMask.'); return }
|
||||
try { await window.ethereum.request({ method:'eth_requestAccounts' }); log('Wallet connected.'); } catch(e){ log('Connect failed: '+e.message) }
|
||||
};
|
||||
document.getElementById('sign').onclick = async () => {
|
||||
try {
|
||||
const wallet = document.getElementById('wallet').value.trim();
|
||||
const plan = document.getElementById('plan').value;
|
||||
if (!/^0x[0-9a-fA-F]{40}$/.test(wallet)) { log('Enter a valid EVM address'); return }
|
||||
// Request nonce
|
||||
const ch = await fetch(GATEWAY+"/v1/auth/challenge", {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({wallet, purpose:'api_key', namespace: DEFAULT_NS})});
|
||||
if (!ch.ok) { const t = await ch.text(); log('Challenge failed: '+t); return }
|
||||
const cj = await ch.json();
|
||||
const nonce = cj.nonce;
|
||||
// Sign nonce
|
||||
let sig = await window.ethereum.request({ method:'personal_sign', params:[ nonce, wallet ] });
|
||||
// Issue or fetch API key
|
||||
const resp = await fetch(GATEWAY+"/v1/auth/api-key", {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({wallet, nonce, signature: sig, namespace: DEFAULT_NS, plan})});
|
||||
if (!resp.ok) { const t = await resp.text(); log('Issue API key failed: '+t); return }
|
||||
const data = await resp.json();
|
||||
log('API Key: '+data.api_key+'\nNamespace: '+data.namespace);
|
||||
// Send back to CLI
|
||||
await fetch('/callback', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(data)});
|
||||
} catch(e){ log('Error: '+e.message) }
|
||||
};
|
||||
</script>
|
||||
</body></html>`, gatewayURL, namespace, wallet, plan)
|
||||
})
|
||||
// Callback to deliver API key back to CLI
|
||||
mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
var payload struct {
|
||||
APIKey string `json:"api_key"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(payload.APIKey) == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case resCh <- result{APIKey: payload.APIKey, Namespace: payload.Namespace}:
|
||||
default:
|
||||
}
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
go func() { time.Sleep(500 * time.Millisecond); _ = srv.Close() }()
|
||||
})
|
||||
srv.Handler = mux
|
||||
|
||||
// Open browser
|
||||
url := listenURL
|
||||
go func() {
|
||||
// Try to open in default browser
|
||||
_ = openBrowser(url)
|
||||
}()
|
||||
|
||||
// Serve and wait for result or timeout
|
||||
go func() { _ = srv.Serve(ln) }()
|
||||
fmt.Printf("🌐 Please complete authentication in your browser: %s\n", url)
|
||||
select {
|
||||
case r := <-resCh:
|
||||
fmt.Printf("✅ API Key issued for namespace '%s'\n", r.Namespace)
|
||||
fmt.Printf("%s\n", r.APIKey)
|
||||
case <-time.After(5 * time.Minute):
|
||||
fmt.Fprintf(os.Stderr, "Timed out waiting for wallet signature.\n")
|
||||
_ = srv.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
return credentials
|
||||
}
|
||||
|
||||
func openBrowser(target string) error {
|
||||
@ -737,26 +597,31 @@ func isPrintableText(s string) bool {
|
||||
func showHelp() {
|
||||
fmt.Printf("Network CLI - Distributed P2P Network Management Tool\n\n")
|
||||
fmt.Printf("Usage: network-cli <command> [args...]\n\n")
|
||||
fmt.Printf("🔐 Authentication: Commands requiring authentication will automatically prompt for wallet connection.\n\n")
|
||||
fmt.Printf("Commands:\n")
|
||||
fmt.Printf(" health - Check network health\n")
|
||||
fmt.Printf(" peers - List connected peers\n")
|
||||
fmt.Printf(" status - Show network status\n")
|
||||
fmt.Printf(" peer-id - Show this node's peer ID\n")
|
||||
fmt.Printf(" query <sql> - Execute database query\n")
|
||||
fmt.Printf(" storage get <key> - Get value from storage\n")
|
||||
fmt.Printf(" storage put <key> <value> - Store value in storage\n")
|
||||
fmt.Printf(" storage list [prefix] - List storage keys\n")
|
||||
fmt.Printf(" pubsub publish <topic> <msg> - Publish message\n")
|
||||
fmt.Printf(" pubsub subscribe <topic> [duration] - Subscribe to topic\n")
|
||||
fmt.Printf(" pubsub topics - List topics\n")
|
||||
fmt.Printf(" query <sql> 🔐 Execute database query\n")
|
||||
fmt.Printf(" storage get <key> 🔐 Get value from storage\n")
|
||||
fmt.Printf(" storage put <key> <value> 🔐 Store value in storage\n")
|
||||
fmt.Printf(" storage list [prefix] 🔐 List storage keys\n")
|
||||
fmt.Printf(" pubsub publish <topic> <msg> 🔐 Publish message\n")
|
||||
fmt.Printf(" pubsub subscribe <topic> [duration] 🔐 Subscribe to topic\n")
|
||||
fmt.Printf(" pubsub topics 🔐 List topics\n")
|
||||
fmt.Printf(" connect <peer_address> - Connect to peer\n")
|
||||
fmt.Printf(" auth [--gateway URL] [--namespace NS] [--wallet 0x..] [--plan free|premium] - Obtain API key via wallet signature\n")
|
||||
|
||||
fmt.Printf(" help - Show this help\n\n")
|
||||
fmt.Printf("Global Flags:\n")
|
||||
fmt.Printf(" -b, --bootstrap <addr> - Bootstrap peer address (default: /ip4/127.0.0.1/tcp/4001)\n")
|
||||
fmt.Printf(" -f, --format <format> - Output format: table, json (default: table)\n")
|
||||
fmt.Printf(" -t, --timeout <duration> - Operation timeout (default: 30s)\n")
|
||||
fmt.Printf(" --production - Connect to production bootstrap peers\n\n")
|
||||
fmt.Printf("Authentication:\n")
|
||||
fmt.Printf(" Commands marked with 🔐 will automatically prompt for wallet authentication\n")
|
||||
fmt.Printf(" if no valid credentials are found. You can manage multiple wallets and\n")
|
||||
fmt.Printf(" choose between them during the authentication flow.\n\n")
|
||||
fmt.Printf("Examples:\n")
|
||||
fmt.Printf(" network-cli health\n")
|
||||
fmt.Printf(" network-cli peer-id\n")
|
||||
|
395
pkg/auth/enhanced_auth.go
Normal file
395
pkg/auth/enhanced_auth.go
Normal file
@ -0,0 +1,395 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EnhancedCredentialStore manages multiple credentials per gateway
|
||||
type EnhancedCredentialStore struct {
|
||||
Gateways map[string]*GatewayCredentials `json:"gateways"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// GatewayCredentials holds multiple credentials for a single gateway
|
||||
type GatewayCredentials struct {
|
||||
Credentials []*Credentials `json:"credentials"`
|
||||
DefaultIndex int `json:"default_index"`
|
||||
LastUsedIndex int `json:"last_used_index"`
|
||||
}
|
||||
|
||||
// AuthChoice represents user's choice during authentication
|
||||
type AuthChoice int
|
||||
|
||||
const (
|
||||
AuthChoiceUseCredential AuthChoice = iota
|
||||
AuthChoiceAddCredential
|
||||
AuthChoiceLogout
|
||||
AuthChoiceExit
|
||||
)
|
||||
|
||||
// LoadEnhancedCredentials loads the enhanced credential store
|
||||
func LoadEnhancedCredentials() (*EnhancedCredentialStore, error) {
|
||||
credPath, err := GetCredentialsPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If file doesn't exist, return empty store
|
||||
if _, err := os.Stat(credPath); os.IsNotExist(err) {
|
||||
return &EnhancedCredentialStore{
|
||||
Gateways: make(map[string]*GatewayCredentials),
|
||||
Version: "2.0",
|
||||
}, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(credPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read credentials file: %w", err)
|
||||
}
|
||||
|
||||
// Try to parse as enhanced store first
|
||||
var enhancedStore EnhancedCredentialStore
|
||||
if err := json.Unmarshal(data, &enhancedStore); err == nil && enhancedStore.Version == "2.0" {
|
||||
// Initialize maps if nil
|
||||
if enhancedStore.Gateways == nil {
|
||||
enhancedStore.Gateways = make(map[string]*GatewayCredentials)
|
||||
}
|
||||
return &enhancedStore, nil
|
||||
}
|
||||
|
||||
// Fall back to old format and migrate
|
||||
var oldStore CredentialStore
|
||||
if err := json.Unmarshal(data, &oldStore); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse credentials file: %w", err)
|
||||
}
|
||||
|
||||
// Migrate old format to new
|
||||
enhancedStore = EnhancedCredentialStore{
|
||||
Gateways: make(map[string]*GatewayCredentials),
|
||||
Version: "2.0",
|
||||
}
|
||||
|
||||
for gatewayURL, creds := range oldStore.Gateways {
|
||||
if creds != nil {
|
||||
enhancedStore.Gateways[gatewayURL] = &GatewayCredentials{
|
||||
Credentials: []*Credentials{creds},
|
||||
DefaultIndex: 0,
|
||||
LastUsedIndex: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &enhancedStore, nil
|
||||
}
|
||||
|
||||
// Save saves the enhanced credential store
|
||||
func (store *EnhancedCredentialStore) Save() error {
|
||||
credPath, err := GetCredentialsPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if store.Version == "" {
|
||||
store.Version = "2.0"
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(store, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal credentials: %w", err)
|
||||
}
|
||||
|
||||
return os.WriteFile(credPath, data, 0600)
|
||||
}
|
||||
|
||||
// AddCredential adds a new credential for the gateway
|
||||
func (store *EnhancedCredentialStore) AddCredential(gatewayURL string, creds *Credentials) {
|
||||
if store.Gateways == nil {
|
||||
store.Gateways = make(map[string]*GatewayCredentials)
|
||||
}
|
||||
|
||||
gatewayCredentials := store.Gateways[gatewayURL]
|
||||
if gatewayCredentials == nil {
|
||||
gatewayCredentials = &GatewayCredentials{
|
||||
Credentials: []*Credentials{},
|
||||
DefaultIndex: 0,
|
||||
LastUsedIndex: 0,
|
||||
}
|
||||
store.Gateways[gatewayURL] = gatewayCredentials
|
||||
}
|
||||
|
||||
// Check if credential already exists (by wallet address)
|
||||
for i, existing := range gatewayCredentials.Credentials {
|
||||
if strings.EqualFold(existing.Wallet, creds.Wallet) {
|
||||
// Update existing credential
|
||||
gatewayCredentials.Credentials[i] = creds
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Add new credential
|
||||
gatewayCredentials.Credentials = append(gatewayCredentials.Credentials, creds)
|
||||
}
|
||||
|
||||
// GetDefaultCredential returns the default credential for a gateway
|
||||
func (store *EnhancedCredentialStore) GetDefaultCredential(gatewayURL string) *Credentials {
|
||||
gatewayCredentials := store.Gateways[gatewayURL]
|
||||
if gatewayCredentials == nil || len(gatewayCredentials.Credentials) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure default index is valid
|
||||
if gatewayCredentials.DefaultIndex < 0 || gatewayCredentials.DefaultIndex >= len(gatewayCredentials.Credentials) {
|
||||
gatewayCredentials.DefaultIndex = 0
|
||||
}
|
||||
|
||||
return gatewayCredentials.Credentials[gatewayCredentials.DefaultIndex]
|
||||
}
|
||||
|
||||
// SetDefaultCredential sets the default credential by index
|
||||
func (store *EnhancedCredentialStore) SetDefaultCredential(gatewayURL string, index int) bool {
|
||||
gatewayCredentials := store.Gateways[gatewayURL]
|
||||
if gatewayCredentials == nil || index < 0 || index >= len(gatewayCredentials.Credentials) {
|
||||
return false
|
||||
}
|
||||
|
||||
gatewayCredentials.DefaultIndex = index
|
||||
gatewayCredentials.LastUsedIndex = index
|
||||
return true
|
||||
}
|
||||
|
||||
// ClearAllCredentials removes all credentials
|
||||
func (store *EnhancedCredentialStore) ClearAllCredentials() {
|
||||
store.Gateways = make(map[string]*GatewayCredentials)
|
||||
}
|
||||
|
||||
// DisplayCredentialMenu shows the interactive credential selection menu
|
||||
func (store *EnhancedCredentialStore) DisplayCredentialMenu(gatewayURL string) (AuthChoice, int, error) {
|
||||
gatewayCredentials := store.Gateways[gatewayURL]
|
||||
|
||||
if gatewayCredentials == nil || len(gatewayCredentials.Credentials) == 0 {
|
||||
fmt.Println("\n🔐 No credentials found. Choose an option:")
|
||||
fmt.Println("1. Authenticate with new wallet")
|
||||
fmt.Println("2. Exit")
|
||||
fmt.Print("Choose (1-2): ")
|
||||
|
||||
choice, err := readUserChoice(2)
|
||||
if err != nil {
|
||||
return AuthChoiceExit, -1, err
|
||||
}
|
||||
|
||||
switch choice {
|
||||
case 1:
|
||||
return AuthChoiceAddCredential, -1, nil
|
||||
case 2:
|
||||
return AuthChoiceExit, -1, nil
|
||||
default:
|
||||
return AuthChoiceExit, -1, fmt.Errorf("invalid choice")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n🔐 Multiple wallets available for %s:\n", gatewayURL)
|
||||
|
||||
// Display credentials
|
||||
for i, creds := range gatewayCredentials.Credentials {
|
||||
defaultMark := ""
|
||||
if i == gatewayCredentials.DefaultIndex {
|
||||
defaultMark = " (default)"
|
||||
}
|
||||
|
||||
// Format wallet address for display
|
||||
displayAddr := creds.Wallet
|
||||
if len(displayAddr) > 10 {
|
||||
displayAddr = displayAddr[:6] + "..." + displayAddr[len(displayAddr)-4:]
|
||||
}
|
||||
|
||||
statusEmoji := "✅"
|
||||
if !creds.IsValid() {
|
||||
statusEmoji = "❌"
|
||||
}
|
||||
|
||||
planInfo := ""
|
||||
if creds.Plan != "" {
|
||||
planInfo = fmt.Sprintf(" (%s)", creds.Plan)
|
||||
}
|
||||
|
||||
fmt.Printf("%d. %s %s%s%s\n", i+1, statusEmoji, displayAddr, planInfo, defaultMark)
|
||||
}
|
||||
|
||||
fmt.Printf("%d. Add new wallet\n", len(gatewayCredentials.Credentials)+1)
|
||||
fmt.Printf("%d. Logout (clear all credentials)\n", len(gatewayCredentials.Credentials)+2)
|
||||
fmt.Printf("%d. Exit\n", len(gatewayCredentials.Credentials)+3)
|
||||
|
||||
maxChoice := len(gatewayCredentials.Credentials) + 3
|
||||
fmt.Printf("Choose (1-%d): ", maxChoice)
|
||||
|
||||
choice, err := readUserChoice(maxChoice)
|
||||
if err != nil {
|
||||
return AuthChoiceExit, -1, err
|
||||
}
|
||||
|
||||
if choice <= len(gatewayCredentials.Credentials) {
|
||||
// User selected a credential
|
||||
return AuthChoiceUseCredential, choice - 1, nil
|
||||
} else if choice == len(gatewayCredentials.Credentials)+1 {
|
||||
// Add new credential
|
||||
return AuthChoiceAddCredential, -1, nil
|
||||
} else if choice == len(gatewayCredentials.Credentials)+2 {
|
||||
// Logout
|
||||
return AuthChoiceLogout, -1, nil
|
||||
} else {
|
||||
// Exit
|
||||
return AuthChoiceExit, -1, nil
|
||||
}
|
||||
}
|
||||
|
||||
// readUserChoice reads and validates user input
|
||||
func readUserChoice(maxChoice int) (int, error) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to read input: %w", err)
|
||||
}
|
||||
|
||||
choiceStr := strings.TrimSpace(input)
|
||||
choice, err := strconv.Atoi(choiceStr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid input: please enter a number")
|
||||
}
|
||||
|
||||
if choice < 1 || choice > maxChoice {
|
||||
return 0, fmt.Errorf("invalid choice: please enter a number between 1 and %d", maxChoice)
|
||||
}
|
||||
|
||||
return choice, nil
|
||||
}
|
||||
|
||||
// GetOrPromptForCredentials handles the complete authentication flow
|
||||
func GetOrPromptForCredentials(gatewayURL string) (*Credentials, error) {
|
||||
store, err := LoadEnhancedCredentials()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load credential store: %w", err)
|
||||
}
|
||||
|
||||
// Check if we have a valid default credential
|
||||
defaultCreds := store.GetDefaultCredential(gatewayURL)
|
||||
if defaultCreds != nil && defaultCreds.IsValid() {
|
||||
// Update last used time
|
||||
defaultCreds.UpdateLastUsed()
|
||||
if err := store.Save(); err != nil {
|
||||
// Log warning but don't fail
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to update last used time: %v\n", err)
|
||||
}
|
||||
return defaultCreds, nil
|
||||
}
|
||||
|
||||
// Need to prompt user for credential selection
|
||||
for {
|
||||
choice, credIndex, err := store.DisplayCredentialMenu(gatewayURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("menu selection failed: %w", err)
|
||||
}
|
||||
|
||||
switch choice {
|
||||
case AuthChoiceUseCredential:
|
||||
gatewayCredentials := store.Gateways[gatewayURL]
|
||||
if gatewayCredentials == nil || credIndex < 0 || credIndex >= len(gatewayCredentials.Credentials) {
|
||||
fmt.Println("❌ Invalid credential selection")
|
||||
continue
|
||||
}
|
||||
|
||||
selectedCreds := gatewayCredentials.Credentials[credIndex]
|
||||
if !selectedCreds.IsValid() {
|
||||
fmt.Println("❌ Selected credentials are invalid or expired")
|
||||
continue
|
||||
}
|
||||
|
||||
// Update default and last used
|
||||
store.SetDefaultCredential(gatewayURL, credIndex)
|
||||
selectedCreds.UpdateLastUsed()
|
||||
|
||||
if err := store.Save(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to save credentials: %v\n", err)
|
||||
}
|
||||
|
||||
return selectedCreds, nil
|
||||
|
||||
case AuthChoiceAddCredential:
|
||||
fmt.Println("\n🌐 Opening browser for wallet authentication...")
|
||||
newCreds, err := PerformWalletAuthentication(gatewayURL)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ Authentication failed: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add the new credential
|
||||
store.AddCredential(gatewayURL, newCreds)
|
||||
|
||||
// Set as default if it's the first credential
|
||||
gatewayCredentials := store.Gateways[gatewayURL]
|
||||
if gatewayCredentials != nil && len(gatewayCredentials.Credentials) == 1 {
|
||||
store.SetDefaultCredential(gatewayURL, 0)
|
||||
}
|
||||
|
||||
if err := store.Save(); err != nil {
|
||||
return nil, fmt.Errorf("failed to save new credentials: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ Wallet %s added successfully\n", newCreds.Wallet)
|
||||
return newCreds, nil
|
||||
|
||||
case AuthChoiceLogout:
|
||||
store.ClearAllCredentials()
|
||||
if err := store.Save(); err != nil {
|
||||
return nil, fmt.Errorf("failed to clear credentials: %w", err)
|
||||
}
|
||||
fmt.Println("✅ All credentials cleared")
|
||||
continue
|
||||
|
||||
case AuthChoiceExit:
|
||||
return nil, fmt.Errorf("authentication cancelled by user")
|
||||
|
||||
default:
|
||||
fmt.Println("❌ Invalid choice")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HasValidEnhancedCredentials checks if there are valid credentials for the default gateway
|
||||
func HasValidEnhancedCredentials() (bool, error) {
|
||||
store, err := LoadEnhancedCredentials()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
gatewayURL := GetDefaultGatewayURL()
|
||||
defaultCreds := store.GetDefaultCredential(gatewayURL)
|
||||
|
||||
return defaultCreds != nil && defaultCreds.IsValid(), nil
|
||||
}
|
||||
|
||||
// GetValidEnhancedCredentials returns valid credentials for the default gateway
|
||||
func GetValidEnhancedCredentials() (*Credentials, error) {
|
||||
store, err := LoadEnhancedCredentials()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gatewayURL := GetDefaultGatewayURL()
|
||||
defaultCreds := store.GetDefaultCredential(gatewayURL)
|
||||
|
||||
if defaultCreds == nil {
|
||||
return nil, fmt.Errorf("no credentials found for gateway %s", gatewayURL)
|
||||
}
|
||||
|
||||
if !defaultCreds.IsValid() {
|
||||
return nil, fmt.Errorf("credentials for gateway %s are expired or invalid", gatewayURL)
|
||||
}
|
||||
|
||||
return defaultCreds, nil
|
||||
}
|
@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.debros.io/DeBros/network/pkg/client"
|
||||
"git.debros.io/DeBros/network/pkg/storage"
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
@ -97,12 +98,14 @@ func (g *Gateway) challengeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Insert namespace if missing, fetch id
|
||||
ctx := r.Context()
|
||||
// Use internal context to bypass authentication for system operations
|
||||
internalCtx := client.WithInternalAuth(ctx)
|
||||
db := g.client.Database()
|
||||
if _, err := db.Query(ctx, "INSERT OR IGNORE INTO namespaces(name) VALUES (?)", ns); err != nil {
|
||||
if _, err := db.Query(internalCtx, "INSERT OR IGNORE INTO namespaces(name) VALUES (?)", ns); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
nres, err := db.Query(ctx, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns)
|
||||
nres, err := db.Query(internalCtx, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns)
|
||||
if err != nil || nres == nil || nres.Count == 0 || len(nres.Rows) == 0 || len(nres.Rows[0]) == 0 {
|
||||
writeError(w, http.StatusInternalServerError, "failed to resolve namespace")
|
||||
return
|
||||
@ -110,7 +113,7 @@ func (g *Gateway) challengeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
nsID := nres.Rows[0][0]
|
||||
|
||||
// Store nonce with 5 minute expiry
|
||||
if _, err := db.Query(ctx,
|
||||
if _, err := db.Query(internalCtx,
|
||||
"INSERT INTO nonces(namespace_id, wallet, nonce, purpose, expires_at) VALUES (?, ?, ?, ?, datetime('now', '+5 minutes'))",
|
||||
nsID, req.Wallet, nonce, req.Purpose,
|
||||
); err != nil {
|
||||
@ -158,6 +161,8 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
ctx := r.Context()
|
||||
// Use internal context to bypass authentication for system operations
|
||||
internalCtx := client.WithInternalAuth(ctx)
|
||||
db := g.client.Database()
|
||||
nsID, err := g.resolveNamespaceID(ctx, ns)
|
||||
if err != nil {
|
||||
@ -165,7 +170,7 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
q := "SELECT id FROM nonces WHERE namespace_id = ? AND wallet = ? AND nonce = ? AND used_at IS NULL AND (expires_at IS NULL OR expires_at > datetime('now')) LIMIT 1"
|
||||
nres, err := db.Query(ctx, q, nsID, req.Wallet, req.Nonce)
|
||||
nres, err := db.Query(internalCtx, q, nsID, req.Wallet, req.Nonce)
|
||||
if err != nil || nres == nil || nres.Count == 0 {
|
||||
writeError(w, http.StatusBadRequest, "invalid or expired nonce")
|
||||
return
|
||||
@ -206,7 +211,7 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Mark nonce used now (after successful verification)
|
||||
if _, err := db.Query(ctx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
|
||||
if _, err := db.Query(internalCtx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@ -227,7 +232,7 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
refresh := base64.RawURLEncoding.EncodeToString(rbuf)
|
||||
if _, err := db.Query(ctx, "INSERT INTO refresh_tokens(namespace_id, subject, token, audience, expires_at) VALUES (?, ?, ?, ?, datetime('now', '+30 days'))", nsID, req.Wallet, refresh, "gateway"); err != nil {
|
||||
if _, err := db.Query(internalCtx, "INSERT INTO refresh_tokens(namespace_id, subject, token, audience, expires_at) VALUES (?, ?, ?, ?, datetime('now', '+30 days'))", nsID, req.Wallet, refresh, "gateway"); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@ -282,6 +287,8 @@ func (g *Gateway) issueAPIKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
ctx := r.Context()
|
||||
// Use internal context to bypass authentication for system operations
|
||||
internalCtx := client.WithInternalAuth(ctx)
|
||||
db := g.client.Database()
|
||||
// Resolve namespace id
|
||||
nsID, err := g.resolveNamespaceID(ctx, ns)
|
||||
@ -291,7 +298,7 @@ func (g *Gateway) issueAPIKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
// Validate nonce exists and not used/expired
|
||||
q := "SELECT id FROM nonces WHERE namespace_id = ? AND wallet = ? AND nonce = ? AND used_at IS NULL AND (expires_at IS NULL OR expires_at > datetime('now')) LIMIT 1"
|
||||
nres, err := db.Query(ctx, q, nsID, req.Wallet, req.Nonce)
|
||||
nres, err := db.Query(internalCtx, q, nsID, req.Wallet, req.Nonce)
|
||||
if err != nil || nres == nil || nres.Count == 0 {
|
||||
writeError(w, http.StatusBadRequest, "invalid or expired nonce")
|
||||
return
|
||||
@ -326,13 +333,13 @@ func (g *Gateway) issueAPIKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
// Mark nonce used
|
||||
if _, err := db.Query(ctx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
|
||||
if _, err := db.Query(internalCtx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
// Check if api key exists for (namespace, wallet) via linkage table
|
||||
var apiKey string
|
||||
r1, err := db.Query(ctx, "SELECT api_keys.key FROM wallet_api_keys JOIN api_keys ON wallet_api_keys.api_key_id = api_keys.id WHERE wallet_api_keys.namespace_id = ? AND LOWER(wallet_api_keys.wallet) = LOWER(?) LIMIT 1", nsID, req.Wallet)
|
||||
r1, err := db.Query(internalCtx, "SELECT api_keys.key FROM wallet_api_keys JOIN api_keys ON wallet_api_keys.api_key_id = api_keys.id WHERE wallet_api_keys.namespace_id = ? AND LOWER(wallet_api_keys.wallet) = LOWER(?) LIMIT 1", nsID, req.Wallet)
|
||||
if err == nil && r1 != nil && r1.Count > 0 && len(r1.Rows) > 0 && len(r1.Rows[0]) > 0 {
|
||||
if s, ok := r1.Rows[0][0].(string); ok {
|
||||
apiKey = s
|
||||
@ -349,21 +356,21 @@ func (g *Gateway) issueAPIKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
apiKey = "ak_" + base64.RawURLEncoding.EncodeToString(buf) + ":" + ns
|
||||
if _, err := db.Query(ctx, "INSERT INTO api_keys(key, name, namespace_id) VALUES (?, ?, ?)", apiKey, "", nsID); err != nil {
|
||||
if _, err := db.Query(internalCtx, "INSERT INTO api_keys(key, name, namespace_id) VALUES (?, ?, ?)", apiKey, "", nsID); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
// Create linkage
|
||||
// Find api_key id
|
||||
rid, err := db.Query(ctx, "SELECT id FROM api_keys WHERE key = ? LIMIT 1", apiKey)
|
||||
rid, err := db.Query(internalCtx, "SELECT id FROM api_keys WHERE key = ? LIMIT 1", apiKey)
|
||||
if err == nil && rid != nil && rid.Count > 0 && len(rid.Rows) > 0 && len(rid.Rows[0]) > 0 {
|
||||
apiKeyID := rid.Rows[0][0]
|
||||
_, _ = db.Query(ctx, "INSERT OR IGNORE INTO wallet_api_keys(namespace_id, wallet, api_key_id) VALUES (?, ?, ?)", nsID, strings.ToLower(req.Wallet), apiKeyID)
|
||||
_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO wallet_api_keys(namespace_id, wallet, api_key_id) VALUES (?, ?, ?)", nsID, strings.ToLower(req.Wallet), apiKeyID)
|
||||
}
|
||||
}
|
||||
// Record ownerships (best-effort)
|
||||
_, _ = db.Query(ctx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, 'api_key', ?)", nsID, apiKey)
|
||||
_, _ = db.Query(ctx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, 'wallet', ?)", nsID, req.Wallet)
|
||||
_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, 'api_key', ?)", nsID, apiKey)
|
||||
_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, 'wallet', ?)", nsID, req.Wallet)
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"api_key": apiKey,
|
||||
@ -399,8 +406,10 @@ func (g *Gateway) apiKeyToJWTHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Validate and get namespace
|
||||
db := g.client.Database()
|
||||
ctx := r.Context()
|
||||
// Use internal context to bypass authentication for system operations
|
||||
internalCtx := client.WithInternalAuth(ctx)
|
||||
q := "SELECT namespaces.name FROM api_keys JOIN namespaces ON api_keys.namespace_id = namespaces.id WHERE api_keys.key = ? LIMIT 1"
|
||||
res, err := db.Query(ctx, q, key)
|
||||
res, err := db.Query(internalCtx, q, key)
|
||||
if err != nil || res == nil || res.Count == 0 || len(res.Rows) == 0 || len(res.Rows[0]) == 0 {
|
||||
writeError(w, http.StatusUnauthorized, "invalid API key")
|
||||
return
|
||||
@ -467,6 +476,8 @@ func (g *Gateway) registerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
ctx := r.Context()
|
||||
// Use internal context to bypass authentication for system operations
|
||||
internalCtx := client.WithInternalAuth(ctx)
|
||||
db := g.client.Database()
|
||||
nsID, err := g.resolveNamespaceID(ctx, ns)
|
||||
if err != nil {
|
||||
@ -475,7 +486,7 @@ func (g *Gateway) registerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
// Validate nonce
|
||||
q := "SELECT id FROM nonces WHERE namespace_id = ? AND wallet = ? AND nonce = ? AND used_at IS NULL AND (expires_at IS NULL OR expires_at > datetime('now')) LIMIT 1"
|
||||
nres, err := db.Query(ctx, q, nsID, req.Wallet, req.Nonce)
|
||||
nres, err := db.Query(internalCtx, q, nsID, req.Wallet, req.Nonce)
|
||||
if err != nil || nres == nil || nres.Count == 0 || len(nres.Rows) == 0 || len(nres.Rows[0]) == 0 {
|
||||
writeError(w, http.StatusBadRequest, "invalid or expired nonce")
|
||||
return
|
||||
@ -515,7 +526,7 @@ func (g *Gateway) registerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Mark nonce used now (after successful verification)
|
||||
if _, err := db.Query(ctx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
|
||||
if _, err := db.Query(internalCtx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@ -533,13 +544,13 @@ func (g *Gateway) registerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
appID := "app_" + base64.RawURLEncoding.EncodeToString(buf)
|
||||
|
||||
// Persist app
|
||||
if _, err := db.Query(ctx, "INSERT INTO apps(namespace_id, app_id, name, public_key) VALUES (?, ?, ?, ?)", nsID, appID, req.Name, pubHex); err != nil {
|
||||
if _, err := db.Query(internalCtx, "INSERT INTO apps(namespace_id, app_id, name, public_key) VALUES (?, ?, ?, ?)", nsID, appID, req.Name, pubHex); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Record namespace ownership by wallet (best-effort)
|
||||
_, _ = db.Query(ctx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, ?, ?)", nsID, "wallet", req.Wallet)
|
||||
_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, ?, ?)", nsID, "wallet", req.Wallet)
|
||||
|
||||
writeJSON(w, http.StatusCreated, map[string]any{
|
||||
"client_id": appID,
|
||||
@ -583,6 +594,8 @@ func (g *Gateway) refreshHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
ctx := r.Context()
|
||||
// Use internal context to bypass authentication for system operations
|
||||
internalCtx := client.WithInternalAuth(ctx)
|
||||
db := g.client.Database()
|
||||
nsID, err := g.resolveNamespaceID(ctx, ns)
|
||||
if err != nil {
|
||||
@ -590,7 +603,7 @@ func (g *Gateway) refreshHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
q := "SELECT subject FROM refresh_tokens WHERE namespace_id = ? AND token = ? AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > datetime('now')) LIMIT 1"
|
||||
rres, err := db.Query(ctx, q, nsID, req.RefreshToken)
|
||||
rres, err := db.Query(internalCtx, q, nsID, req.RefreshToken)
|
||||
if err != nil || rres == nil || rres.Count == 0 {
|
||||
writeError(w, http.StatusUnauthorized, "invalid or expired refresh token")
|
||||
return
|
||||
@ -972,6 +985,8 @@ func (g *Gateway) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
ctx := r.Context()
|
||||
// Use internal context to bypass authentication for system operations
|
||||
internalCtx := client.WithInternalAuth(ctx)
|
||||
db := g.client.Database()
|
||||
nsID, err := g.resolveNamespaceID(ctx, ns)
|
||||
if err != nil {
|
||||
@ -981,7 +996,7 @@ func (g *Gateway) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if strings.TrimSpace(req.RefreshToken) != "" {
|
||||
// Revoke specific token
|
||||
if _, err := db.Query(ctx, "UPDATE refresh_tokens SET revoked_at = datetime('now') WHERE namespace_id = ? AND token = ? AND revoked_at IS NULL", nsID, req.RefreshToken); err != nil {
|
||||
if _, err := db.Query(internalCtx, "UPDATE refresh_tokens SET revoked_at = datetime('now') WHERE namespace_id = ? AND token = ? AND revoked_at IS NULL", nsID, req.RefreshToken); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
@ -1001,7 +1016,7 @@ func (g *Gateway) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusUnauthorized, "jwt required for all=true")
|
||||
return
|
||||
}
|
||||
if _, err := db.Query(ctx, "UPDATE refresh_tokens SET revoked_at = datetime('now') WHERE namespace_id = ? AND subject = ? AND revoked_at IS NULL", nsID, subject); err != nil {
|
||||
if _, err := db.Query(internalCtx, "UPDATE refresh_tokens SET revoked_at = datetime('now') WHERE namespace_id = ? AND subject = ? AND revoked_at IS NULL", nsID, subject); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -1,15 +1,19 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"context"
|
||||
|
||||
"git.debros.io/DeBros/network/pkg/client"
|
||||
)
|
||||
|
||||
func (g *Gateway) resolveNamespaceID(ctx context.Context, ns string) (interface{}, error) {
|
||||
// Use internal context to bypass authentication for system operations
|
||||
internalCtx := client.WithInternalAuth(ctx)
|
||||
db := g.client.Database()
|
||||
if _, err := db.Query(ctx, "INSERT OR IGNORE INTO namespaces(name) VALUES (?)", ns); err != nil {
|
||||
if _, err := db.Query(internalCtx, "INSERT OR IGNORE INTO namespaces(name) VALUES (?)", ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := db.Query(ctx, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns)
|
||||
res, err := db.Query(internalCtx, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns)
|
||||
if err != nil || res == nil || res.Count == 0 || len(res.Rows) == 0 || len(res.Rows[0]) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user