Add an 80s-style green-on-black bank terminal shell ("banking") using
charmbracelet/bubbletea for full-screen TUI rendering over SSH.
Screens: login, main menu, account summary, account detail with
transactions, wire transfer wizard (6-step form capturing routing
number, destination, beneficiary, amount, memo, auth code), transaction
history with pagination, secure messages with breadcrumb content (fake
internal IPs, vault codes), change PIN, and hidden admin access (99)
that locks after 3 failed attempts with COBOL-style error output.
All key actions (login, navigation, wire transfers, admin attempts) are
logged to the session store. Wire transfer data is the honeypot gold.
Configurable via [shell.banking] in TOML: bank_name, terminal_id, region.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
112 lines
2.9 KiB
Go
112 lines
2.9 KiB
Go
package banking
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
type adminModel struct {
|
|
pin string
|
|
attempts int
|
|
locked bool
|
|
}
|
|
|
|
func newAdminModel() adminModel {
|
|
return adminModel{}
|
|
}
|
|
|
|
func (m adminModel) Update(msg tea.Msg) (adminModel, tea.Cmd) {
|
|
if m.locked {
|
|
return m, nil
|
|
}
|
|
|
|
keyMsg, ok := msg.(tea.KeyMsg)
|
|
if !ok {
|
|
return m, nil
|
|
}
|
|
|
|
switch keyMsg.Type {
|
|
case tea.KeyEnter:
|
|
if m.pin != "" {
|
|
m.attempts++
|
|
if m.attempts >= 3 {
|
|
m.locked = true
|
|
}
|
|
m.pin = ""
|
|
}
|
|
case tea.KeyBackspace:
|
|
if len(m.pin) > 0 {
|
|
m.pin = m.pin[:len(m.pin)-1]
|
|
}
|
|
default:
|
|
ch := keyMsg.String()
|
|
if len(ch) == 1 && ch[0] >= 32 && ch[0] < 127 && len(m.pin) < 20 {
|
|
m.pin += ch
|
|
}
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func (m adminModel) View() string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString("\n")
|
|
b.WriteString(centerText("SYSTEM ADMINISTRATION"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(thinDivider())
|
|
b.WriteString("\n\n")
|
|
|
|
if m.locked {
|
|
b.WriteString(errorStyle.Render(" *** ACCESS DENIED ***"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(errorStyle.Render(" MAXIMUM AUTHENTICATION ATTEMPTS EXCEEDED"))
|
|
b.WriteString("\n")
|
|
b.WriteString(errorStyle.Render(" TERMINAL LOCKED - INCIDENT LOGGED"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(baseStyle.Render(" SECURITY ALERT HAS BEEN DISPATCHED TO:"))
|
|
b.WriteString("\n")
|
|
b.WriteString(baseStyle.Render(" - INFORMATION SECURITY DEPT"))
|
|
b.WriteString("\n")
|
|
b.WriteString(baseStyle.Render(" - BRANCH SECURITY OFFICER"))
|
|
b.WriteString("\n")
|
|
b.WriteString(baseStyle.Render(" - FEDERAL RESERVE OVERSIGHT"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(baseStyle.Render(fmt.Sprintf(" INCIDENT REF: SEC-%d-ADMIN-BRUTE", 20240000+m.attempts)))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(dimStyle.Render(" IEF4271I UNAUTHORIZED ACCESS ATTEMPT - ABEND S0C4"))
|
|
b.WriteString("\n")
|
|
b.WriteString(dimStyle.Render(" IEF4272I JOB SECADMIN STEP0001 - COND CODE 4088"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(thinDivider())
|
|
b.WriteString("\n\n")
|
|
b.WriteString(dimStyle.Render(" PRESS ANY KEY TO RETURN TO MAIN MENU"))
|
|
b.WriteString("\n")
|
|
} else {
|
|
b.WriteString(titleStyle.Render(" RESTRICTED ACCESS - ADMINISTRATOR ONLY"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(baseStyle.Render(" THIS FUNCTION REQUIRES LEVEL 5 SECURITY CLEARANCE."))
|
|
b.WriteString("\n")
|
|
b.WriteString(baseStyle.Render(" ALL ACCESS ATTEMPTS ARE LOGGED AND AUDITED."))
|
|
b.WriteString("\n\n")
|
|
|
|
if m.attempts > 0 {
|
|
b.WriteString(errorStyle.Render(fmt.Sprintf(" INVALID CREDENTIALS (%d OF 3 ATTEMPTS)", m.attempts)))
|
|
b.WriteString("\n\n")
|
|
}
|
|
|
|
b.WriteString(titleStyle.Render(" ADMIN PIN: "))
|
|
masked := strings.Repeat("*", len(m.pin))
|
|
b.WriteString(inputStyle.Render(masked))
|
|
b.WriteString(inputStyle.Render("_"))
|
|
b.WriteString("\n\n")
|
|
b.WriteString(thinDivider())
|
|
b.WriteString("\n\n")
|
|
b.WriteString(dimStyle.Render(" PRESS ESC TO RETURN TO MAIN MENU"))
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
return b.String()
|
|
}
|