mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-03 01:41:44 +00:00
137 lines
3.7 KiB
Go
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()
|
|
}
|