Why: creating an account and its activation key, and changing a password, were separate commits, so a crash could leave a user without a key or a new password with a still-valid login key. Evidence: SELF go test ./internal/store including TestConcurrentCreateOneWinner, TestPasswordChangeIsAtomic, TestOpenExistingDoesNotMigrateOrCreate, and TestBootstrapRefusesExistingBusiness. PostgreSQL cases are NOT_RUN locally. Impact: strict insert returns ErrConflict instead of overwriting. Account CLI opens an existing database without migrating. --dsn and --password still work and warn. capabilities --json does not touch the database. Refs: docs/svc-deploy/u7-account-tx-audit.md Co-authored-by: Cursor <cursoragent@cursor.com>
90 lines
2.0 KiB
Go
90 lines
2.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
)
|
|
|
|
func runCapabilities(args []string) int {
|
|
fs := flag.NewFlagSet("capabilities", flag.ExitOnError)
|
|
asJSON := fs.Bool("json", false, "以 JSON 打印能力,不打开数据库")
|
|
_ = fs.Parse(args)
|
|
caps := map[string]any{
|
|
"accountTransaction": true,
|
|
"accountAudit": true,
|
|
"accountOpsOpen": true,
|
|
"passwordStdin": true,
|
|
"dsnFile": true,
|
|
"outFile": true,
|
|
}
|
|
if !*asJSON {
|
|
fmt.Fprintln(os.Stderr, "用法:agent-system capabilities --json")
|
|
return 2
|
|
}
|
|
enc := json.NewEncoder(os.Stdout)
|
|
enc.SetIndent("", " ")
|
|
if err := enc.Encode(caps); err != nil {
|
|
fmt.Fprintln(os.Stderr, "capabilities:", err)
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func resolveAccountDSN(flagDSN, dsnFile string) (string, error) {
|
|
if flagDSN != "" {
|
|
fmt.Fprintln(os.Stderr, "warning: --dsn is deprecated; use AGENT_SYSTEM_DSN or --dsn-file")
|
|
}
|
|
if dsnFile != "" {
|
|
info, err := os.Stat(dsnFile)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if info.Mode().Perm() != 0o600 {
|
|
return "", fmt.Errorf("dsn file %s must be mode 0600", dsnFile)
|
|
}
|
|
raw, err := os.ReadFile(dsnFile)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(trimNewline(raw)), nil
|
|
}
|
|
if env := os.Getenv("AGENT_SYSTEM_DSN"); env != "" {
|
|
return env, nil
|
|
}
|
|
return flagDSN, nil
|
|
}
|
|
|
|
func resolveAccountPassword(flagPassword string, fromStdin bool) (string, error) {
|
|
if flagPassword != "" {
|
|
fmt.Fprintln(os.Stderr, "warning: --password is deprecated; use --password-stdin")
|
|
}
|
|
if !fromStdin {
|
|
return flagPassword, nil
|
|
}
|
|
raw, err := io.ReadAll(os.Stdin)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(trimNewline(raw)), nil
|
|
}
|
|
|
|
func writeExclusiveSecret(path, body string) error {
|
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
_, err = io.WriteString(f, body+"\n")
|
|
return err
|
|
}
|
|
|
|
func trimNewline(raw []byte) []byte {
|
|
for len(raw) > 0 && (raw[len(raw)-1] == '\n' || raw[len(raw)-1] == '\r') {
|
|
raw = raw[:len(raw)-1]
|
|
}
|
|
return raw
|
|
}
|