the last implementation just accepted any input for the shipping validation and then only validated the address on payment. this is now changed to validation upon creation upon finishing the shipping address form. when a user completes the shipping form, a client-side check is first made to just simply check if there are any empty fields, if this check passes it gets check server-side by shippo. If the address is not found it will return an address not found error, and the user will not be able to continue to the payment form. The invalid address will also not be saved to the database. if the check pass, we simply save the address to the database with an ID. this ID is then passe in when doing the payment, instead of validating at the payment phase. this implementation currently only uses Shippo, which i have come to realize does NOT support shipping in countries outside of the US (i should have read the friendly manual before using Shippo). This means that a huge rewrite is due. i will focus on adding address validation for Norway first since thats where i live. i plan on using Bring's api for this validation. i will still keep Shippo for US based address verification even though this project is focused fon nordic shipping - just for a rainy day if some poor soul decides to order from AMERICA
131 lines
3.1 KiB
Go
131 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/ssh"
|
|
"github.com/charmbracelet/wish"
|
|
"github.com/charmbracelet/wish/bubbletea"
|
|
"github.com/charmbracelet/wish/logging"
|
|
|
|
"terminalShop/pkg/auth"
|
|
"terminalShop/pkg/config"
|
|
"terminalShop/pkg/database"
|
|
"terminalShop/pkg/models"
|
|
"terminalShop/pkg/tui"
|
|
)
|
|
|
|
const (
|
|
host = "localhost"
|
|
port = "23456"
|
|
)
|
|
|
|
var (
|
|
authService *auth.SSHAuthService
|
|
apiURL string
|
|
)
|
|
|
|
func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
|
|
// Extract SSH public key from session
|
|
pubKey := s.PublicKey()
|
|
|
|
var user *models.User
|
|
var token string
|
|
|
|
if pubKey != nil {
|
|
// Authenticate with SSH key (auto-creates user on first connect!)
|
|
authenticatedUser, jwtToken, err := authService.AuthenticateSSHKey(pubKey)
|
|
if err != nil {
|
|
log.Printf("Auth error: %v", err)
|
|
// Continue as guest
|
|
} else {
|
|
user = authenticatedUser
|
|
token = jwtToken
|
|
displayName := user.SSHKeyFingerprint[:16]
|
|
if user.Name != "" {
|
|
displayName = user.Name
|
|
}
|
|
log.Printf("User authenticated: %s (ID: %d)", displayName, user.ID)
|
|
}
|
|
}
|
|
|
|
// Create TUI model with user context (no registration screen needed!)
|
|
m := tui.NewModelWithAuth(user, false, s.PublicKey(), token, apiURL)
|
|
return m, []tea.ProgramOption{tea.WithAltScreen()}
|
|
}
|
|
|
|
func main() {
|
|
// Load configuration
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
log.Fatalf("Failed to load config: %v", err)
|
|
}
|
|
|
|
// Initialize database connection (for user management)
|
|
// Use same database as API server
|
|
db, err := database.Connect(cfg.DatabaseURL)
|
|
if err != nil {
|
|
log.Fatalf("Failed to connect to database: %v", err)
|
|
}
|
|
defer database.Close()
|
|
|
|
// Run migrations (includes users table)
|
|
if err := database.Migrate(db); err != nil {
|
|
log.Fatalf("Failed to run migrations: %v", err)
|
|
}
|
|
|
|
// Initialize JWT manager (uses same secret as API)
|
|
jwtManager := auth.NewJWTManager(cfg.JWTSecret, 30*time.Minute)
|
|
|
|
// Initialize SSH auth service
|
|
authService = auth.NewSSHAuthService(db, jwtManager)
|
|
|
|
// Set API URL from config for TUI connections
|
|
apiURL = cfg.APIURL
|
|
log.Printf("TUI will connect to API at: %s", apiURL)
|
|
|
|
// Create SSH server
|
|
s, err := wish.NewServer(
|
|
wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
|
|
wish.WithHostKeyPath(".ssh/term_info_ed25519"),
|
|
wish.WithPublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
|
|
// Allow all public keys (we handle auth in the tea handler)
|
|
return true
|
|
}),
|
|
wish.WithMiddleware(
|
|
bubbletea.Middleware(teaHandler),
|
|
logging.Middleware(),
|
|
),
|
|
)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
done := make(chan os.Signal, 1)
|
|
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
log.Printf("Starting SSH server on %s:%s", host, port)
|
|
log.Printf("Connect with: ssh %s -p %s", host, port)
|
|
|
|
go func() {
|
|
if err = s.ListenAndServe(); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
}()
|
|
|
|
<-done
|
|
log.Println("Stopping SSH server")
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
if err := s.Shutdown(ctx); err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
}
|