mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-15 18:02:50 -05:00
rename backend to core
This commit is contained in:
338
core/internal/tui/views_password.go
Normal file
338
core/internal/tui/views_password.go
Normal file
@@ -0,0 +1,338 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func (m Model) viewAuthMethodChoice() string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(m.renderBanner())
|
||||
b.WriteString("\n")
|
||||
|
||||
title := m.styles.Title.Render("Authentication Method")
|
||||
b.WriteString(title)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
message := "Fingerprint authentication is available.\nHow would you like to authenticate?"
|
||||
b.WriteString(m.styles.Normal.Render(message))
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Option 0: Fingerprint
|
||||
if m.selectedConfig == 0 {
|
||||
option := m.styles.SelectedOption.Render("▶ Use Fingerprint")
|
||||
b.WriteString(option)
|
||||
} else {
|
||||
option := m.styles.Normal.Render(" Use Fingerprint")
|
||||
b.WriteString(option)
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
// Option 1: Password
|
||||
if m.selectedConfig == 1 {
|
||||
option := m.styles.SelectedOption.Render("▶ Use Password")
|
||||
b.WriteString(option)
|
||||
} else {
|
||||
option := m.styles.Normal.Render(" Use Password")
|
||||
b.WriteString(option)
|
||||
}
|
||||
b.WriteString("\n\n")
|
||||
|
||||
help := m.styles.Subtle.Render("↑/↓: Navigate, Enter: Select, Esc: Back")
|
||||
b.WriteString(help)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m Model) viewFingerprintAuth() string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(m.renderBanner())
|
||||
b.WriteString("\n")
|
||||
|
||||
title := m.styles.Title.Render("Fingerprint Authentication")
|
||||
b.WriteString(title)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
if m.fingerprintFailed {
|
||||
errorMsg := m.styles.Error.Render("✗ Fingerprint authentication failed")
|
||||
b.WriteString(errorMsg)
|
||||
b.WriteString("\n")
|
||||
retryMsg := m.styles.Subtle.Render("Returning to authentication menu...")
|
||||
b.WriteString(retryMsg)
|
||||
} else {
|
||||
message := "Please place your finger on the fingerprint reader."
|
||||
b.WriteString(m.styles.Normal.Render(message))
|
||||
b.WriteString("\n\n")
|
||||
|
||||
spinner := m.spinner.View()
|
||||
status := m.styles.Normal.Render("Waiting for fingerprint...")
|
||||
b.WriteString(fmt.Sprintf("%s %s", spinner, status))
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m Model) viewPasswordPrompt() string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(m.renderBanner())
|
||||
b.WriteString("\n")
|
||||
|
||||
title := m.styles.Title.Render("Password Authentication")
|
||||
b.WriteString(title)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
message := "Installation requires sudo privileges.\nPlease enter your password to continue:"
|
||||
b.WriteString(m.styles.Normal.Render(message))
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Password input
|
||||
b.WriteString(m.passwordInput.View())
|
||||
b.WriteString("\n")
|
||||
|
||||
// Show validation status
|
||||
if m.packageProgress.step == "Validating sudo password..." {
|
||||
spinner := m.spinner.View()
|
||||
status := m.styles.Normal.Render(m.packageProgress.step)
|
||||
b.WriteString(spinner + " " + status)
|
||||
b.WriteString("\n")
|
||||
} else if m.packageProgress.error != nil {
|
||||
errorMsg := m.styles.Error.Render("✗ " + m.packageProgress.error.Error() + ". Please try again.")
|
||||
b.WriteString(errorMsg)
|
||||
b.WriteString("\n")
|
||||
} else if m.packageProgress.step == "Password validation failed" {
|
||||
errorMsg := m.styles.Error.Render("✗ Incorrect password. Please try again.")
|
||||
b.WriteString(errorMsg)
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
b.WriteString("\n")
|
||||
help := m.styles.Subtle.Render("Enter: Continue, Esc: Back, Ctrl+C: Cancel")
|
||||
b.WriteString(help)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m Model) updateAuthMethodChoiceState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.fingerprintFailed = false
|
||||
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||
switch keyMsg.String() {
|
||||
case "up":
|
||||
if m.selectedConfig > 0 {
|
||||
m.selectedConfig--
|
||||
}
|
||||
case "down":
|
||||
if m.selectedConfig < 1 {
|
||||
m.selectedConfig++
|
||||
}
|
||||
case "enter":
|
||||
if m.selectedConfig == 0 {
|
||||
m.state = StateFingerprintAuth
|
||||
m.isLoading = true
|
||||
return m, tea.Batch(m.spinner.Tick, m.tryFingerprint())
|
||||
} else {
|
||||
m.state = StatePasswordPrompt
|
||||
m.passwordInput.Focus()
|
||||
return m, nil
|
||||
}
|
||||
case "esc":
|
||||
m.state = StateDependencyReview
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Model) updateFingerprintAuthState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if validMsg, ok := msg.(passwordValidMsg); ok {
|
||||
if validMsg.valid {
|
||||
m.sudoPassword = ""
|
||||
m.packageProgress = packageInstallProgressMsg{}
|
||||
m.state = StateInstallingPackages
|
||||
m.isLoading = true
|
||||
return m, tea.Batch(m.spinner.Tick, m.installPackages())
|
||||
} else {
|
||||
m.fingerprintFailed = true
|
||||
return m, m.delayThenReturn()
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := msg.(delayCompleteMsg); ok {
|
||||
m.fingerprintFailed = false
|
||||
m.selectedConfig = 0
|
||||
m.state = StateAuthMethodChoice
|
||||
return m, nil
|
||||
}
|
||||
|
||||
return m, m.listenForLogs()
|
||||
}
|
||||
|
||||
func (m Model) updatePasswordPromptState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
|
||||
if validMsg, ok := msg.(passwordValidMsg); ok {
|
||||
if validMsg.valid {
|
||||
// Password is valid, proceed with installation
|
||||
m.sudoPassword = validMsg.password
|
||||
m.passwordInput.SetValue("") // Clear password input
|
||||
// Clear any error state
|
||||
m.packageProgress = packageInstallProgressMsg{}
|
||||
m.state = StateInstallingPackages
|
||||
m.isLoading = true
|
||||
return m, tea.Batch(m.spinner.Tick, m.installPackages())
|
||||
} else {
|
||||
// Password is invalid, show error and stay on password prompt
|
||||
m.packageProgress = packageInstallProgressMsg{
|
||||
progress: 0.0,
|
||||
step: "Password validation failed",
|
||||
error: fmt.Errorf("incorrect password"),
|
||||
logOutput: "Authentication failed",
|
||||
}
|
||||
m.passwordInput.SetValue("")
|
||||
m.passwordInput.Focus()
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||
switch keyMsg.String() {
|
||||
case "enter":
|
||||
// Don't allow multiple validation attempts while one is in progress
|
||||
if m.packageProgress.step == "Validating sudo password..." {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Validate password first
|
||||
password := m.passwordInput.Value()
|
||||
if password == "" {
|
||||
return m, nil // Don't proceed with empty password
|
||||
}
|
||||
|
||||
// Clear any previous error and show validation in progress
|
||||
m.packageProgress = packageInstallProgressMsg{
|
||||
progress: 0.01,
|
||||
step: "Validating sudo password...",
|
||||
isComplete: false,
|
||||
logOutput: "Testing password with sudo -v",
|
||||
}
|
||||
return m, m.validatePassword(password)
|
||||
case "esc":
|
||||
// Go back to dependency review
|
||||
m.passwordInput.SetValue("")
|
||||
m.packageProgress = packageInstallProgressMsg{} // Clear any validation state
|
||||
m.state = StateDependencyReview
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
m.passwordInput, cmd = m.passwordInput.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func checkFingerprintEnabled() bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Check if pam_fprintd.so is in PAM config
|
||||
cmd := exec.CommandContext(ctx, "grep", "-q", "pam_fprintd.so", "/etc/pam.d/system-auth")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if fprintd-list exists and user has enrolled fingerprints
|
||||
user := os.Getenv("USER")
|
||||
if user == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
listCmd := exec.CommandContext(ctx, "fprintd-list", user)
|
||||
output, err := listCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// If output contains "finger:" or similar, fingerprints are enrolled
|
||||
return strings.Contains(string(output), "finger")
|
||||
}
|
||||
|
||||
func (m Model) delayThenReturn() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
time.Sleep(2 * time.Second)
|
||||
return delayCompleteMsg{}
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) tryFingerprint() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
clearCmd := exec.Command("sudo", "-k")
|
||||
clearCmd.Run()
|
||||
|
||||
tmpDir := os.TempDir()
|
||||
askpassScript := filepath.Join(tmpDir, fmt.Sprintf("danklinux-fp-%d.sh", time.Now().UnixNano()))
|
||||
|
||||
scriptContent := "#!/bin/sh\nexit 1\n"
|
||||
if err := os.WriteFile(askpassScript, []byte(scriptContent), 0700); err != nil {
|
||||
return passwordValidMsg{password: "", valid: false}
|
||||
}
|
||||
defer os.Remove(askpassScript)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "sudo", "-A", "-v")
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("SUDO_ASKPASS=%s", askpassScript))
|
||||
|
||||
err := cmd.Run()
|
||||
|
||||
if err != nil {
|
||||
return passwordValidMsg{password: "", valid: false}
|
||||
}
|
||||
|
||||
return passwordValidMsg{password: "", valid: true}
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) validatePassword(password string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "sudo", "-S", "-v")
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return passwordValidMsg{password: "", valid: false}
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return passwordValidMsg{password: "", valid: false}
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(stdin, "%s\n", password)
|
||||
stdin.Close()
|
||||
if err != nil {
|
||||
return passwordValidMsg{password: "", valid: false}
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
|
||||
if err != nil {
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return passwordValidMsg{password: "", valid: false}
|
||||
}
|
||||
return passwordValidMsg{password: "", valid: false}
|
||||
}
|
||||
|
||||
return passwordValidMsg{password: password, valid: true}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user