PrintToVeraeTimeStamp/cmd/verae-print/keysetup_darwin.go

177 lines
6.1 KiB
Go

//go:build darwin
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"strings"
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/settings"
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/vlog"
)
// macOS in-installer key registration, run by the pkg postinstall as the
// console user (launchctl asuser + sudo -H -u). Native AppleScript dialogs;
// passwords come back through osascript's stdout pipe only — never argv,
// never files, never logs. Keys are logged masked only.
const dlgTitle = "Verae Print Stamp"
const (
machineWord = "Mac"
keyStoreDesc = "Keychain + ~/.verae"
)
func openURL(u string) { _ = exec.Command("open", u).Start() }
func guiAvailable() bool { return true }
// AppleScript gets all user-visible text via argv (no string interpolation →
// no injection). Output: "<button>\t<text returned>" or "CANCEL".
const dialogScript = `on run argv
set msg to item 1 of argv
set defAns to item 2 of argv
set hide to (item 3 of argv) is "1"
set btns to paragraphs of (item 4 of argv)
set defBtn to item 5 of argv
set useAnswer to (item 6 of argv) is "1"
tell me to activate
try
if useAnswer then
if hide then
set r to display dialog msg default answer defAns with hidden answer buttons btns default button defBtn with title "` + dlgTitle + `" giving up after 900
else
set r to display dialog msg default answer defAns buttons btns default button defBtn with title "` + dlgTitle + `" giving up after 900
end if
if gave up of r then return "CANCEL"
return (button returned of r) & tab & (text returned of r)
else
set r to display dialog msg buttons btns default button defBtn with title "` + dlgTitle + `" giving up after 900
if gave up of r then return "CANCEL"
return (button returned of r) & tab
end if
on error number -128
return "CANCEL"
end try
end run`
const chooseScript = `on run argv
set msg to item 1 of argv
set opts to paragraphs of (item 2 of argv)
tell me to activate
set r to choose from list opts with prompt msg default items {item 1 of opts} OK button name "Continue" cancel button name "Skip" with title "` + dlgTitle + `"
if r is false then return "CANCEL"
return item 1 of r
end run`
func osa(script string, args ...string) (string, error) {
cmd := exec.Command("osascript", append([]string{"-e", script, "--"}, args...)...)
out, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("osascript: %v", err)
}
return strings.TrimRight(string(out), "\n"), nil
}
// ask shows a dialog; returns (button, answer).
func ask(msg, def string, hidden, withAnswer bool, buttons []string, defBtn string) (string, string, error) {
b2 := func(v bool) string {
if v {
return "1"
}
return "0"
}
out, err := osa(dialogScript, msg, def, b2(hidden), strings.Join(buttons, "\n"), defBtn, b2(withAnswer))
if err != nil {
return "", "", err
}
if out == "CANCEL" {
return "", "", errCancel
}
btn, ans, _ := strings.Cut(out, "\t")
return btn, ans, nil
}
func say(msg string) { _, _, _ = ask(msg, "", false, false, []string{"OK"}, "OK") }
func choose(msg string, opts []string) (string, error) {
out, err := osa(chooseScript, msg, strings.Join(opts, "\n"))
if err != nil {
return "", err
}
if out == "CANCEL" {
return "", errCancel
}
return out, nil
}
func runMacKeySetup(args []string) int {
fs := flag.NewFlagSet("mac-key-setup", flag.ContinueOnError)
askOpen := fs.Bool("ask-open", true, "also ask what to open after each print")
if err := fs.Parse(args); err != nil {
return 2
}
vlog.Init("installer", false)
vlog.Printf("mac-key-setup start user=%s home=%s", os.Getenv("USER"), os.Getenv("HOME"))
release, ok := promptLock()
if !ok {
say("A Verae sign-in window is already open.")
return 1
}
result, saved := keyFlow("")
release()
vlog.Printf("mac-key-setup key result: %s", result)
fmt.Println("key:", result)
if saved || strings.HasPrefix(result, "kept") {
if n := len(listPending()); n > 0 {
done, remaining, _ := retryPending(stampFile)
vlog.Printf("mac-key-setup: retried held print jobs: %d stamped, %d remaining", done, remaining)
fmt.Printf("pending jobs: %d stamped, %d remaining\n", done, remaining)
}
}
if *askOpen {
mode := askOpenOnComplete()
fmt.Println("openOnComplete:", mode)
fmt.Println("promptForFilename:", askPromptForFilename())
}
return 0
}
func askOpenOnComplete() string {
cur, _ := settings.Load()
def := map[string]string{settings.OpenFolder: "Show in Finder", settings.OpenPDF: "Open the PDF", settings.OpenNone: "Do nothing"}[cur.OpenOnComplete]
btn, _, err := ask("After each print is timestamped:\n\n• Show in Finder - reveal the new certificate PDF in Verae Timestamps (recommended)\n• Open the PDF - open the certificate in Preview\n• Do nothing", "", false, false, []string{"Do nothing", "Open the PDF", "Show in Finder"}, def)
mode := cur.OpenOnComplete
if err == nil {
mode = map[string]string{"Show in Finder": settings.OpenFolder, "Open the PDF": settings.OpenPDF, "Do nothing": settings.OpenNone}[btn]
}
if err := settings.SetOpenOnComplete(settings.UserPath(""), mode); err != nil {
vlog.Errorf("mac-key-setup: write config: %v", err)
} else {
vlog.Printf("mac-key-setup: openOnComplete=%s saved to %s", mode, settings.UserPath(""))
}
return mode
}
// askPromptForFilename: Save As dialog for every print (default on)?
func askPromptForFilename() bool {
cur, _ := settings.Load()
def := "Ask each time"
if !cur.Prompt() {
def = "Save automatically"
}
btn, _, err := ask("When you print to Verae Timestamping:\n\n• Ask each time - choose the file name and folder in a Save dialog (Cancel discards the print, nothing is stamped)\n• Save automatically - use the document title in Documents/Verae Timestamps", "", false, false, []string{"Save automatically", "Ask each time"}, def)
on := cur.Prompt()
if err == nil {
on = btn == "Ask each time"
}
if err := settings.SetPromptForFilename(settings.UserPath(""), map[bool]string{true: "true", false: "false"}[on]); err != nil {
vlog.Errorf("mac-key-setup: write config: %v", err)
} else {
vlog.Printf("mac-key-setup: promptForFilename=%v saved to %s", on, settings.UserPath(""))
}
return on
}