fix(cli): support repairing api-auth and stripe-key records

put --name now accepts api-auth (JSON) and stripe-key (the raw
pk_live_ key), so status failure messages can point at a working
repair path instead of the setup wizard, which refuses an existing
vault. The wizard also prints explicit recovery instructions when it
aborts after creating the vault, and validates the site origin
exactly like the web server.
This commit is contained in:
mizorewww committed 2026-10-03 12:09:38 +08:00
1 parent a8e7565448
commit f18083f33e
2 files changed
+47 -28

No files matched your search

+30 -16
View File
@@ -1,6 +1,7 @@
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
@@ -71,7 +72,7 @@ func run() error {
pay := f.Bool("pay", false, "pay only at the exact allowed BDT total")
name := f.String("name", "", "secret name for put")
f.Usage = func() {
fmt.Fprintln(f.Output(), "Usage: xgift <setup|init|status|billing|import-chrome|put|proxy|check|username> [flags]\nsetup is the interactive first-time wizard; init reads a JSON object from stdin; put reads one JSON value from stdin.")
fmt.Fprintln(f.Output(), "Usage: xgift <setup|init|status|billing|import-chrome|put|proxy|check|username> [flags]\nsetup is the interactive first-time wizard; init reads a JSON object from stdin; put reads one JSON value from stdin (stripe-key: the raw pk_live_ key).")
f.PrintDefaults()
}
if err := f.Parse(rest); err != nil {
@@ -191,22 +192,22 @@ func run() error {
}
raw, e := v.Get("api-auth")
if e != nil {
return errors.New("record api-auth is missing; run setup")
return errors.New("record api-auth is missing; fix with put --name api-auth")
}
var auth struct{ Authorization string }
if e = json.Unmarshal(raw, &auth); e != nil || !strings.HasPrefix(auth.Authorization, "Bearer ") {
clear(raw)
return errors.New("invalid api-auth record; run setup to rewrite it")
return errors.New("invalid api-auth record; rewrite with put --name api-auth")
}
clear(raw)
fmt.Println("api-auth: encrypted record verified")
key, e := v.Get("stripe-key")
if e != nil {
return errors.New("record stripe-key is missing; run setup")
return errors.New("record stripe-key is missing; fix with put --name stripe-key")
}
if !stripeKeyPattern.Match(key) {
clear(key)
return errors.New("invalid stripe-key record; run setup to rewrite it")
return errors.New("invalid stripe-key record; rewrite with put --name stripe-key")
}
clear(key)
fmt.Println("stripe-key: encrypted record verified")
@@ -223,18 +224,31 @@ func run() error {
fmt.Println("X cookies refreshed in encrypted SQLite vault")
return nil
case "put":
if *name != "proxy" && *name != "card" && *name != "cookies" {
return errors.New("--name must be proxy, card or cookies")
switch *name {
case "proxy", "card", "cookies", "api-auth":
b, e := io.ReadAll(io.LimitReader(os.Stdin, 1024*1024))
if e != nil {
return e
}
defer clear(b)
if !json.Valid(b) {
return errors.New("stdin must be valid JSON")
}
return v.Put(*name, b)
case "stripe-key":
b, e := io.ReadAll(io.LimitReader(os.Stdin, 4096))
if e != nil {
return e
}
defer clear(b)
b = bytes.TrimSpace(b)
if !stripeKeyPattern.Match(b) {
return errors.New("stdin must be the pk_live_ publishable key")
}
return v.Put(*name, b)
default:
return errors.New("--name must be proxy, card, cookies, api-auth or stripe-key")
}
b, e := io.ReadAll(io.LimitReader(os.Stdin, 1024*1024))
if e != nil {
return e
}
defer clear(b)
if !json.Valid(b) {
return errors.New("stdin must be valid JSON")
}
return v.Put(*name, b)
}
if command != "proxy" && command != "check" && !regexp.MustCompile(`^@?[A-Za-z0-9_]{1,15}$`).MatchString(command) {
return errors.New("invalid command or X username")
+17 -12
View File
@@ -189,17 +189,22 @@ func runSetup(ctx context.Context, db, passwordFileFlag string) error {
defer v.Close()
fmt.Printf("加密保管库已创建:%s\n\n", db)
if err = w.setupXCredentials(v); err != nil {
return err
steps := []func() error{
func() error { return w.setupXCredentials(v) },
func() error { return w.setupCard(v) },
func() error { return w.setupProxy(ctx, v) },
func() error { return w.setupStripeKey(v) },
}
if err = w.setupCard(v); err != nil {
return err
}
if err = w.setupProxy(ctx, v); err != nil {
return err
}
if err = w.setupStripeKey(v); err != nil {
return err
for _, step := range steps {
if err = step(); err != nil {
fmt.Println()
fmt.Printf("配置未完成:%v\n", err)
fmt.Println("已创建的保管库会保留已写入的记录。修复方式:")
fmt.Printf(" 1) 删除 %s、%s 和旁边的 password-path 后重新运行 setup;或\n", db, passwordFile)
fmt.Println(" 2) 用 put --name <proxy|card|cookies|api-auth|stripe-key> / billing / import-chrome 补写缺失记录,")
fmt.Println(" 再以 status 确认全部记录就绪。")
return err
}
}
siteConfigured, err := w.setupSite(db, passwordFile)
if err != nil {
@@ -550,8 +555,8 @@ func (w *wizard) setupSite(db, passwordFile string) (bool, error) {
return false, err
}
u, err := url.Parse(origin)
if err != nil || u.Scheme != "https" || u.Host == "" || (u.Path != "" && u.Path != "/") || u.RawQuery != "" || u.Fragment != "" {
return false, errors.New("Origin 必须是 https://<主机名> 形式")
if err != nil || u.Scheme != "https" || u.Host == "" || u.Path != "" || u.RawQuery != "" || u.Fragment != "" || u.User != nil {
return false, errors.New("Origin 必须是 https://<主机名> 形式(不带路径、查询或用户信息)")
}
listen, err := w.prompt("监听地址", "127.0.0.1:8787")
if err != nil {