fix(account): close create modal on success and make cookies optional
- AccountList.createAccount 成功后关闭创建弹窗(此前只提示不关闭)
- Cookies 前端改为可选:留空代表扫码登录场景,提交时不携带该字段
- 后端 validAccount 允许空凭据(此前 http.ParseCookie("") 直接拒绝)
- CreateAccount 空凭据时跳过 keyring 写入,不产生空值记录;回滚补偿仅在实际写入过凭据时执行
验证:go test ./... / go vet ./... / 双端构建通过;web vitest 66 用例通过(新增无 cookies 创建+弹窗关闭用例);npm run build 通过
This commit is contained in:
@@ -273,15 +273,20 @@ func (s *Store) CreateAccount(ctx context.Context, account Account, credentials
|
||||
if !validAccount(account) || credentials == nil {
|
||||
return ErrInvalid
|
||||
}
|
||||
if err := credentials.Store(ctx, account.CredentialReference, account.CredentialKey, account.Cookies); err != nil {
|
||||
storeErr := errors.New("store account credential")
|
||||
if cleanupErr := credentials.Delete(context.WithoutCancel(ctx), account.CredentialReference, account.CredentialKey); cleanupErr != nil {
|
||||
storeErr = errors.Join(storeErr, errors.New("delete incomplete account credential"))
|
||||
// 空凭据(扫码登录场景)不写 keyring:凭据留待后续登录/同步链路补齐
|
||||
stored := false
|
||||
if account.Cookies != "" {
|
||||
if err := credentials.Store(ctx, account.CredentialReference, account.CredentialKey, account.Cookies); err != nil {
|
||||
storeErr := errors.New("store account credential")
|
||||
if cleanupErr := credentials.Delete(context.WithoutCancel(ctx), account.CredentialReference, account.CredentialKey); cleanupErr != nil {
|
||||
storeErr = errors.Join(storeErr, errors.New("delete incomplete account credential"))
|
||||
}
|
||||
return storeErr
|
||||
}
|
||||
return storeErr
|
||||
stored = true
|
||||
}
|
||||
defer func() {
|
||||
if err != nil && !errors.Is(err, ErrAccountCreationUnknown) {
|
||||
if stored && err != nil && !errors.Is(err, ErrAccountCreationUnknown) {
|
||||
if cleanupErr := credentials.Delete(context.WithoutCancel(ctx), account.CredentialReference, account.CredentialKey); cleanupErr != nil {
|
||||
err = errors.Join(err, errors.New("delete orphaned account credential"))
|
||||
}
|
||||
@@ -393,8 +398,13 @@ func validAccount(account Account) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
_, err := http.ParseCookie(account.Cookies)
|
||||
return err == nil
|
||||
// 空凭据合法(扫码登录场景);非空时才校验 Cookie Header 格式
|
||||
if account.Cookies != "" {
|
||||
if _, err := http.ParseCookie(account.Cookies); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Store) CreateDraft(ctx context.Context, draft Draft) error {
|
||||
|
||||
@@ -79,6 +79,11 @@ func TestValidationRejectsInvalidInputsBeforePersistence(t *testing.T) {
|
||||
t.Fatalf("supported platform rejected: %s", platform)
|
||||
}
|
||||
}
|
||||
empty := valid
|
||||
empty.Cookies = ""
|
||||
if !validAccount(empty) {
|
||||
t.Fatal("empty cookies must stay valid (scan-to-login account)")
|
||||
}
|
||||
for name, mutate := range map[string]func(*Account){
|
||||
"id": func(account *Account) { account.ID = "INVALID" },
|
||||
"name": func(account *Account) { account.Name = " " },
|
||||
@@ -725,6 +730,38 @@ func TestPhaseAOfflineWorkflow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateAccountWithoutCookiesSkipsCredentialStore(t *testing.T) {
|
||||
databaseURL := os.Getenv("CREATORHUB_POSTGRES_TEST_URL")
|
||||
if databaseURL == "" {
|
||||
t.Skip("set CREATORHUB_POSTGRES_TEST_URL to run PostgreSQL integration coverage")
|
||||
}
|
||||
ctx := context.Background()
|
||||
store, err := Open(ctx, databaseURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = store.Close() })
|
||||
applyHubMigrationsForPhaseATest(t, store)
|
||||
if _, err := store.db.ExecContext(ctx, `
|
||||
TRUNCATE audit_event, execution_attempt, operation_task, confirmation, content_draft,
|
||||
runtime_instance, environment_binding, network_exit, social_account, credential_reference,
|
||||
browser_env, browser_image, gateway RESTART IDENTITY CASCADE`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
credentials := &testCredentialBridge{values: map[string]string{}}
|
||||
account := Account{ID: "account-no-cookies", Name: "扫码账号", Platform: "douyin", PlatformAccountKey: "qr-login",
|
||||
Tags: []string{}, Cookies: "",
|
||||
CredentialReference: CredentialReference{ID: "account-no-cookies-cookies", Provider: "os_keyring"}, CredentialKey: "creatorhub/account-no-cookies/cookies"}
|
||||
if err := store.CreateAccount(ctx, account, credentials); err != nil {
|
||||
t.Fatalf("creating an account without cookies failed: %v", err)
|
||||
}
|
||||
if _, stored := credentials.values[account.CredentialKey]; stored {
|
||||
t.Fatal("empty cookies must not be written to the credential provider")
|
||||
}
|
||||
assertCount(t, store, `SELECT count(*) FROM social_account WHERE id = $1`, 1, account.ID)
|
||||
assertCount(t, store, `SELECT count(*) FROM credential_reference WHERE id = $1`, 1, account.CredentialReference.ID)
|
||||
}
|
||||
|
||||
func TestAccountCredentialCommitResult(t *testing.T) {
|
||||
databaseURL := os.Getenv("CREATORHUB_POSTGRES_TEST_URL")
|
||||
if databaseURL == "" {
|
||||
|
||||
Reference in New Issue
Block a user