Files
sonr/x/did/client/cli/login.go
T
Prad NukalaandGitHub 13e6c3e84d Master (#1262)
* clear

* feat: Add everything

* fix: Commenht
2025-10-03 14:45:52 -04:00

137 lines
3.7 KiB
Go

// Package cli contains the implementation of the CLI commands
package cli
import (
"fmt"
"net"
"os"
"os/exec"
"runtime"
"strings"
"time"
"cosmossdk.io/log"
"github.com/sonr-io/sonr/x/did/client/server"
)
// LoginUserWithWebAuthn authenticates a user using WebAuthn through browser interaction
func LoginUserWithWebAuthn(username string) error {
logger := log.NewLogger(os.Stderr)
// If no username provided, prompt for it using standard input
if strings.TrimSpace(username) == "" {
var err error
username, err = promptForUsername()
if err != nil {
return fmt.Errorf("failed to get username: %w", err)
}
}
// Initialize database and check if username exists
if err := server.InitDB(); err != nil {
logger.Warn("Failed to initialize database", "error", err)
return fmt.Errorf("failed to initialize database: %w", err)
}
// Check if username exists with WebAuthn credentials
service := server.NewWebAuthnCredentialService()
existingCredentials, err := service.GetByUsername(username)
if err != nil || len(existingCredentials) == 0 {
return fmt.Errorf(
"username '%s' not found or has no WebAuthn credentials. Please register first.",
username,
)
}
logger.Info(
"Found WebAuthn credentials for user",
"username",
username,
"credentialCount",
len(existingCredentials),
)
// Find available port for auth server
port, err := findAvailablePortForLogin()
if err != nil {
return fmt.Errorf("failed to find available port: %w", err)
}
// Create channel to signal completion
done := make(chan error, 1)
// Setup server with WebAuthn login context
err = server.StartAuthServerForLogin(port, username, done)
if err != nil {
return fmt.Errorf("failed to start auth server: %w", err)
}
defer func() {
if stopErr := server.StopAuthServer(); stopErr != nil {
logger.Error("Failed to stop auth server", "error", stopErr)
}
}()
// Wait for server to be ready
time.Sleep(500 * time.Millisecond)
// Open browser to WebAuthn login page
url := fmt.Sprintf("http://localhost:%d/login?username=%s", port, username)
logger.Info("Opening browser for WebAuthn login", "url", url)
if err := openBrowserForLogin(url); err != nil {
logger.Warn("Failed to open browser automatically", "error", err)
logger.Info("Please navigate manually to the URL", "url", url)
}
logger.Info("Waiting for WebAuthn login to complete...")
// Wait for login to complete or timeout (30 seconds for login vs 10 for registration)
select {
case err := <-done:
if err != nil {
return fmt.Errorf("WebAuthn login failed: %w", err)
}
logger.Info("WebAuthn login completed successfully")
return nil
case <-time.After(30 * time.Second):
logger.Warn("WebAuthn login timed out after 30 seconds")
return fmt.Errorf("WebAuthn login timed out after 30 seconds - please try again")
}
}
// findAvailablePortForLogin finds an available port starting from 8090 to avoid conflicts with registration
func findAvailablePortForLogin() (int, error) {
for port := 8090; port < 8100; port++ {
conn, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err == nil {
_ = conn.Close()
return port, nil
}
}
return 0, fmt.Errorf("no available port found in range 8090-8100")
}
// openBrowserForLogin opens the default browser with the given login URL
func openBrowserForLogin(url string) error {
var cmd string
var args []string
switch runtime.GOOS {
case "darwin":
cmd = "open"
args = []string{url}
case "linux":
cmd = "xdg-open"
args = []string{url}
case "windows":
cmd = "rundll32"
args = []string{"url.dll,FileProtocolHandler", url}
default:
return fmt.Errorf("unsupported platform: %s", runtime.GOOS)
}
// #nosec G204 - cmd is hardcoded based on OS, not user input
return exec.Command(cmd, args...).Start()
}