mirror of
https://github.com/marcusDenslow/terminalShop.git
synced 2026-10-25 02:52:20 +02:00
refactor: remove putting ssh key in memory at all, just use it for auth
changed the ssh key to just do fingerprinting and not store it at all, and not be able to edit it either in the account page
This commit is contained in:
@@ -83,34 +83,6 @@ func (h *AuthHandler) GetToken(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.SSHPublicKey != "" {
|
||||
var sshKey models.SSHKey
|
||||
// Use Unscoped to include soft-deleted rows, avoiding the
|
||||
// UNIQUE constraint conflict when a key is re-registered.
|
||||
err := db.Unscoped().Where("fingerprint = ?", req.Fingerprint).First(&sshKey).Error
|
||||
if err != nil {
|
||||
// Key doesn't exist at all — create it
|
||||
sshKey = models.SSHKey{
|
||||
UserID: user.ID,
|
||||
Fingerprint: req.Fingerprint,
|
||||
PublicKey: req.SSHPublicKey,
|
||||
}
|
||||
if err := db.Create(&sshKey).Error; err != nil {
|
||||
utils.RespondError(w, http.StatusInternalServerError, "DATABASE_ERROR", "failed to save ssh key", nil)
|
||||
return
|
||||
}
|
||||
} else if sshKey.DeletedAt.Valid {
|
||||
// Key was soft-deleted — restore it
|
||||
sshKey.DeletedAt = gorm.DeletedAt{}
|
||||
sshKey.PublicKey = req.SSHPublicKey
|
||||
sshKey.UserID = user.ID
|
||||
if err := db.Unscoped().Save(&sshKey).Error; err != nil {
|
||||
utils.RespondError(w, http.StatusInternalServerError, "DATABASE_ERROR", "failed to restore ssh key", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
token, err := h.jwtManager.GenerateToken(user.ID, user.Email, user.Name)
|
||||
if err != nil {
|
||||
utils.RespondError(w, http.StatusInternalServerError, "TOKEN_ERROR", "failed to generate token", nil)
|
||||
|
||||
@@ -37,9 +37,6 @@ func (h *ViewHandler) GetViewInit(w http.ResponseWriter, r *http.Request) {
|
||||
var cards []models.Card
|
||||
db.Where("user_id = ?", userID).Find(&cards)
|
||||
|
||||
var sshKeys []models.SSHKey
|
||||
db.Where("user_id = ?", userID).Order("created_at ASC").Find(&sshKeys)
|
||||
|
||||
var orders []models.Order
|
||||
db.Where("user_id = ?", userID).Preload("Items").Order("created_at desc").Find(&orders)
|
||||
var cartItems []models.CartItem
|
||||
@@ -53,7 +50,6 @@ func (h *ViewHandler) GetViewInit(w http.ResponseWriter, r *http.Request) {
|
||||
"cart": cartItems,
|
||||
"addresses": addresses,
|
||||
"cards": cards,
|
||||
"ssh_keys": sshKeys,
|
||||
"orders": orders,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ func SetupRoutes(
|
||||
addressHandler := handlers.NewAddressHandler(shippoAPIKey, bringAPIUID, bringAPIKey)
|
||||
viewHandler := handlers.NewViewHandler()
|
||||
webhookHandler := handlers.NewWebhookHandler(stripeWebhookSecret, stripeSecretKey)
|
||||
sshKeyHandler := handlers.NewSSHKeyHandler()
|
||||
|
||||
// Short payment redirect and success page — no auth required.
|
||||
r.Get("/pay/{token}", handlers.PayRedirect)
|
||||
@@ -115,14 +114,6 @@ func SetupRoutes(
|
||||
r.Delete("/{id}", addressHandler.DeleteAddress)
|
||||
r.Put("/{id}/default", addressHandler.SetDefaultAddress)
|
||||
})
|
||||
|
||||
// SSH Keys
|
||||
r.Route("/ssh-keys", func(r chi.Router) {
|
||||
// GET doesnt need id because it gets every ssh key
|
||||
r.Get("/", sshKeyHandler.GetSSHKeys)
|
||||
// Delete needs an id to delete only one key
|
||||
r.Delete("/{id}", sshKeyHandler.DeleteSSHKeys)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -628,64 +628,6 @@ func (c *Client) DeleteCard(id uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type SSHKeyResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Data struct {
|
||||
SSHKeys []models.SSHKey `json:"ssh_keys"`
|
||||
} `json:"data"`
|
||||
Error *APIError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) GetSSHKeys() ([]models.SSHKey, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/ssh-keys", c.BaseURL)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch ssh keys: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var keysResp SSHKeyResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&keysResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode ssh keys response: %w", err)
|
||||
}
|
||||
|
||||
if !keysResp.Success {
|
||||
if keysResp.Error != nil {
|
||||
return nil, fmt.Errorf("%s: %s", keysResp.Error.Code, keysResp.Error.Message)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to fetch ssh keys")
|
||||
}
|
||||
return keysResp.Data.SSHKeys, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) DeleteSSHKey(id uint) error {
|
||||
url := fmt.Sprintf("%s/api/v1/ssh-keys/%d", c.BaseURL, id)
|
||||
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create delete request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete ssh key: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("delete ssh key failed with status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type OrderResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Data struct {
|
||||
@@ -842,7 +784,6 @@ type ViewInitData struct {
|
||||
Cart []CartItemData `json:"cart"`
|
||||
Addresses []models.Address `json:"addresses"`
|
||||
Cards []models.Card `json:"cards"`
|
||||
SSHKeys []models.SSHKey `json:"ssh_keys"`
|
||||
Orders []models.Order `json:"orders"`
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ func Migrate(db *gorm.DB) error {
|
||||
&models.Order{}, // Completed purchases
|
||||
&models.OrderItem{}, // Line items within orders
|
||||
&models.Address{}, // Saved addresses
|
||||
&models.SSHKey{}, // One of the users ssh keys
|
||||
); err != nil {
|
||||
return fmt.Errorf("migration failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ var AccountMenuItems = []string{
|
||||
"order history",
|
||||
"addresses",
|
||||
"cards",
|
||||
"ssh keys",
|
||||
"faq",
|
||||
"about",
|
||||
}
|
||||
|
||||
@@ -49,8 +49,6 @@ func (m Model) BuildAccountView(availableHeight int) string {
|
||||
detailView = m.AddressesView(detailContentWidth)
|
||||
case "cards":
|
||||
detailView = m.CardsView(detailContentWidth)
|
||||
case "ssh keys":
|
||||
detailView = m.SSHKeysView(detailContentWidth)
|
||||
case "faq":
|
||||
questionStyle := m.theme.TextHighlight().Bold(true)
|
||||
faqContent := ""
|
||||
@@ -222,10 +220,6 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
if m.account.cardCursor > 0 {
|
||||
m.account.cardCursor--
|
||||
}
|
||||
} else if m.account.sshKeyListFocused && m.account.sshKeyDeleting == nil {
|
||||
if m.account.sshKeyCursor > 0 {
|
||||
m.account.sshKeyCursor--
|
||||
}
|
||||
} else if m.account.faqFocused {
|
||||
m.account.scrollOffset -= 3
|
||||
if m.account.scrollOffset < 0 {
|
||||
@@ -255,10 +249,6 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
if m.account.cardCursor < len(m.SavedCards)-1 {
|
||||
m.account.cardCursor++
|
||||
}
|
||||
} else if m.account.sshKeyListFocused && m.account.sshKeyDeleting == nil {
|
||||
if m.account.sshKeyCursor < len(m.SSHKeys)-1 {
|
||||
m.account.sshKeyCursor++
|
||||
}
|
||||
} else if m.account.faqFocused {
|
||||
m.account.scrollOffset += 3
|
||||
maxScroll := m.computeFaqScrollMax()
|
||||
@@ -282,7 +272,7 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
if targetBottom > m.account.scrollOffset+viewportHeight {
|
||||
m.account.scrollOffset = targetBottom - viewportHeight
|
||||
}
|
||||
} else if m.account.orderViewState == 0 && !m.account.addressListFocused && !m.account.cardListFocused && !m.account.sshKeyListFocused && m.account.cursor < len(models.AccountMenuItems)-1 {
|
||||
} else if m.account.orderViewState == 0 && !m.account.addressListFocused && !m.account.cardListFocused && m.account.cursor < len(models.AccountMenuItems)-1 {
|
||||
m.account.cursor++
|
||||
m.account.scrollOffset = 0
|
||||
}
|
||||
@@ -309,8 +299,6 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
m.account.addressDeleting = &m.account.addressCursor
|
||||
} else if m.account.cardListFocused && m.account.cardDeleting == nil && m.account.cardCursor < len(m.SavedCards) {
|
||||
m.account.cardDeleting = &m.account.cardCursor
|
||||
} else if m.account.sshKeyListFocused && m.account.sshKeyDeleting == nil && m.account.sshKeyCursor < len(m.SSHKeys) {
|
||||
m.account.sshKeyDeleting = &m.account.sshKeyCursor
|
||||
}
|
||||
|
||||
case "y":
|
||||
@@ -332,21 +320,11 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
m.account.cardCursor--
|
||||
}
|
||||
return m, m.deleteCardCmd(card.ID)
|
||||
} else if m.account.sshKeyDeleting != nil {
|
||||
cursor := *m.account.sshKeyDeleting
|
||||
key := m.SSHKeys[cursor]
|
||||
m.account.sshKeyDeleting = nil
|
||||
m.SSHKeys = append(m.SSHKeys[:cursor], m.SSHKeys[cursor+1:]...)
|
||||
if m.account.sshKeyCursor >= len(m.SSHKeys) && m.account.sshKeyCursor > 0 {
|
||||
m.account.sshKeyCursor--
|
||||
}
|
||||
return m, m.deleteSSHKeyCmd(key.ID)
|
||||
}
|
||||
|
||||
case "n":
|
||||
m.account.addressDeleting = nil
|
||||
m.account.cardDeleting = nil
|
||||
m.account.sshKeyDeleting = nil
|
||||
|
||||
case "p", "enter":
|
||||
switch selectedItem {
|
||||
@@ -372,16 +350,6 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
}
|
||||
case "ssh keys":
|
||||
if !m.account.sshKeyListFocused {
|
||||
m.account.sshKeyListFocused = true
|
||||
m.account.sshKeyCursor = 0
|
||||
m.footer = []footerCommand{
|
||||
{key: "j/k", value: "keys"},
|
||||
{key: "x", value: "delete"},
|
||||
{key: "esc", value: "back"},
|
||||
}
|
||||
}
|
||||
case "addresses":
|
||||
if !m.account.addressListFocused {
|
||||
m.account.addressListFocused = true
|
||||
@@ -437,11 +405,6 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) {
|
||||
m.account.cardListFocused = false
|
||||
m.account.cardCursor = 0
|
||||
m.footer = accountDefaultFooter
|
||||
} else if m.account.sshKeyDeleting != nil {
|
||||
m.account.sshKeyDeleting = nil
|
||||
} else if m.account.sshKeyListFocused {
|
||||
m.account.sshKeyListFocused = false
|
||||
m.account.sshKeyCursor = 0
|
||||
m.footer = accountDefaultFooter
|
||||
} else if m.account.faqFocused {
|
||||
m.account.faqFocused = false
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
package tui
|
||||
|
||||
func (m Model) SSHKeysView(width int) string {
|
||||
titleSTyle := m.theme.TextAccent().Bold(true).MarginBottom(2)
|
||||
contentStyle := m.theme.TextBody().Width(width)
|
||||
activeStyle := m.theme.TextAccent().Bold(true)
|
||||
|
||||
|
||||
lines := titleSTyle.Render("SSH keys") + "\n\n"
|
||||
if !m.SSHKeysLoaded {
|
||||
lines += contentStyle.Render("Loading ssh keys...")
|
||||
} else if len(m.SSHKeys) == 0 {
|
||||
lines += contentStyle.Render("No saved ssh keys")
|
||||
} else {
|
||||
for i, key := range m.SSHKeys {
|
||||
isSelected := m.account.sshKeyListFocused && i == m.account.sshKeyCursor
|
||||
label := key.Fingerprint
|
||||
if key.Comment != "" {
|
||||
label += " " + key.Comment
|
||||
}
|
||||
if len(label) > width-4 {
|
||||
label += label[:width-4]
|
||||
}
|
||||
if m.account.sshKeyDeleting != nil && *m.account.sshKeyDeleting == i {
|
||||
lines += m.theme.TextError().Bold(true).Render(" delete? (y/n)") + "\n"
|
||||
} else if isSelected {
|
||||
lines += activeStyle.Render("> "+label) + "\n"
|
||||
} else {
|
||||
lines += contentStyle.Render(" "+label) + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
if m.account.sshKeyListFocused {
|
||||
lines += "\n" + m.theme.TextDim().Render("x: delete esc: back")
|
||||
} else {
|
||||
lines += "\n" + m.theme.TextDim().Render("enter: manage")
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
@@ -77,8 +77,6 @@ type Model struct {
|
||||
SavedCards []models.Card
|
||||
Orders []models.Order
|
||||
OrdersLoaded bool
|
||||
SSHKeys []models.SSHKey
|
||||
SSHKeysLoaded bool
|
||||
FAQs []FAQ
|
||||
|
||||
// Page navigation
|
||||
@@ -180,9 +178,6 @@ type accountState struct {
|
||||
cardCursor int
|
||||
addressDeleting *int
|
||||
cardDeleting *int
|
||||
sshKeyListFocused bool
|
||||
sshKeyCursor int
|
||||
sshKeyDeleting *int
|
||||
}
|
||||
|
||||
type reviewState struct {
|
||||
@@ -519,17 +514,6 @@ type CardDeletedMsg struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// SSHKeysMsg is sent when ssh keys are fetched from the API
|
||||
type SSHKeysMsg struct {
|
||||
Keys []models.SSHKey
|
||||
Err error
|
||||
}
|
||||
|
||||
// SSHKeyDeletedMsg is sent when an ssh key is deleted
|
||||
type SSHKeyDeletedMsg struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (m Model) fetchAddressesCmd() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if m.APIClient == nil || m.User == nil {
|
||||
@@ -592,26 +576,6 @@ func (m Model) deleteCardCmd(id uint) tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) fetchSSHKeysCmd() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if m.APIClient == nil || m.User == nil {
|
||||
return SSHKeysMsg{Err: fmt.Errorf("not authenticated")}
|
||||
}
|
||||
keys, err := m.APIClient.GetSSHKeys()
|
||||
return SSHKeysMsg{Keys: keys, Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) deleteSSHKeyCmd(id uint) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
if m.APIClient == nil || m.User == nil {
|
||||
return SSHKeyDeletedMsg{Err: fmt.Errorf("not authenticated")}
|
||||
}
|
||||
err := m.APIClient.DeleteSSHKey(id)
|
||||
return SSHKeyDeletedMsg{Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
// checkoutWithSavedCard sets address and saved card on the cart, then converts.
|
||||
// Used when the user selects an existing card from the list (no tokenization needed).
|
||||
func (m Model) checkoutWithSavedCard() tea.Cmd {
|
||||
|
||||
@@ -89,8 +89,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.loadCartFromAPI(cart)
|
||||
m.SavedAddresses = msg.Data.Addresses
|
||||
m.SavedCards = msg.Data.Cards
|
||||
m.SSHKeys = msg.Data.SSHKeys
|
||||
m.SSHKeysLoaded = true
|
||||
m.Orders = msg.Data.Orders
|
||||
m.OrdersLoaded = true
|
||||
m.splash.dataReady = true
|
||||
@@ -133,17 +131,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.ErrorMsg = fmt.Sprintf("failed to delete card: %v", msg.Err)
|
||||
}
|
||||
return m, nil
|
||||
case SSHKeysMsg:
|
||||
if msg.Err == nil {
|
||||
m.SSHKeys = msg.Keys
|
||||
m.SSHKeysLoaded = true
|
||||
}
|
||||
return m, nil
|
||||
case SSHKeyDeletedMsg:
|
||||
if msg.Err != nil {
|
||||
m.ErrorMsg = fmt.Sprintf("failed to delete ssh key: %v", msg.Err)
|
||||
}
|
||||
return m, nil
|
||||
case DelayCompleteMsg:
|
||||
m.splash.delayDone = true
|
||||
if m.currentPage == splashPage && m.splash.dataReady {
|
||||
|
||||
Reference in New Issue
Block a user