Date: 2026-03-01 Service: bin-conversation-manager Scope: Account handler — security, consistency, and cleanup fixes
The conversation-manager's account handler has 8 issues identified during a code review:
- Credentials exposed in API responses —
SecretandTokenfields are included inWebhookMessageand rawAccountresponses, violating the OpenAPI spec's "Write-only" designation. - Inconsistent event publishing — Create and Delete use
PublishEvent(internal only), while Update usesPublishWebhookEvent(internal + customer webhooks). - Error masking — GET/PUT/DELETE return
simpleResponse(500)for all errors including not-found. Other services return 404. - No LINE webhook teardown on delete — Deleting a LINE account leaves a dangling webhook URL registered with LINE's API.
- ExecContext missing in AccountUpdate — Uses
h.db.Execinstead ofh.db.ExecContext, breaking context cancellation. - Missing debug logs — No debug logging after successful data retrieval (required by CLAUDE.md conventions).
- Dead interface surface —
DBHandler.AccountSetis exposed but never called by business logic. - Raw Account struct returned instead of WebhookMessage — Listenhandler serializes the internal struct directly, bypassing the WebhookMessage pattern.
Single PR with all 9 fixes (8 issues + 1 OpenAPI annotation). All changes are within bin-conversation-manager except the OpenAPI annotation. No cross-service dependencies. Each fix is isolated to specific files.
File: models/account/webhook.go
Remove Secret and Token fields from WebhookMessage struct and from ConvertWebhookMessage() method. This corrects a spec violation — the OpenAPI schema already marks these fields as "Write-only."
Pattern reference: bin-agent-manager excludes PasswordHash from its WebhookMessage.
File: pkg/accounthandler/db.go
Change Create (line 61) and Delete (line 142) from PublishEvent to PublishWebhookEvent.
How it works:
PublishWebhookEvent(ctx, customerID, eventType, data)internally calls bothPublishEvent(internal queue,json.Marshal(data)with full credentials) andPublishWebhook(customer webhooks viaCreateWebhookEvent()→ConvertWebhookMessage()→ stripped credentials)- Internal subscribers still get the full struct
- External webhooks get sanitized data
*account.Accountalready satisfies thenotifyhandler.WebhookMessageinterface
Impact: No external service subscribes to conversation-manager account events (confirmed by grep). This is additive — customers with webhooks configured will now receive create/delete events.
File: pkg/listenhandler/v1_accounts.go
Change simpleResponse(500) to simpleResponse(404) in:
processV1AccountsIDGet(line 125)processV1AccountsIDPut(line 185)processV1AccountsIDDelete(line 222)
Matches the convention used by call-manager, agent-manager, and flow-manager. The list endpoint (processV1AccountsGet) keeps 500 since a list error is a server error, not a not-found.
Scoped to accounts only — conversation handlers have the same issue but are out of scope.
Files:
pkg/linehandler/main.go— AddTeardown(ctx context.Context, ac *account.Account) errorto interfacepkg/linehandler/teardown.go— New file: callsc.SetWebhookEndpointURL("").WithContext(ctx).Do()pkg/accounthandler/setup.go— Addteardown()private method mirroringsetup()dispatch pattern (LINE → teardown, SMS → no-op, unknown → nil)pkg/accounthandler/db.go— Restructure Delete flow
Delete flow changes from:
DB delete → Get deleted record → Publish event
To:
Get account → Teardown (best-effort) → DB delete → Get deleted record → Publish event
Edge cases:
- Teardown failure (LINE API down, invalid credentials): log warning, proceed with deletion
- SMS type: teardown is a no-op
- Unknown type: teardown returns nil
File: pkg/dbhandler/account.go line 238
Change h.db.Exec(sqlStr, args...) to h.db.ExecContext(ctx, sqlStr, args...).
File: pkg/accounthandler/db.go
Add debug logs in Get and List:
// Get:
log.WithField("account", res).Debugf("Retrieved account info. account_id: %s", id)
// List:
log.WithField("accounts", res).Debugf("Retrieved account list. count: %d", len(res))Note: CacheHandler.AccountSet (cache setter) is a different method and must stay.
Files:
pkg/dbhandler/main.go— RemoveAccountSetfrom DBHandler interface (line 28)pkg/dbhandler/account.go— RemoveAccountSetfunction (lines 203-213)pkg/dbhandler/account_test.go— RemoveTest_AccountSet(lines 122-208)- Regenerate mocks:
go generate ./...
Confirmed: No callers in cmd/ or pkg/accounthandler/.
File: pkg/listenhandler/v1_accounts.go
Convert to WebhookMessage before marshaling in all 5 handlers:
- Single responses:
json.Marshal(tmp.ConvertWebhookMessage()) - List response: convert each account in slice, then marshal
[]*account.WebhookMessage
The accountHandler interface still returns *account.Account — internal callers (conversationhandler, messagehandler, smshandler, CLI tool) need full credentials.
File: bin-openapi-manager/openapi/openapi.yaml
Add writeOnly: true to secret and token properties in ConversationManagerAccount schema. This formalizes the existing "Write-only" description. oapi-codegen ignores writeOnly for type generation — no impact on generated Go types.
| Test file | Changes |
|---|---|
pkg/accounthandler/db_test.go |
Mock lineHandler.Teardown in Delete test; update Create/Delete to expect PublishWebhookEvent instead of PublishEvent |
pkg/linehandler/teardown_test.go |
New: test Teardown calls SetWebhookEndpointURL("") |
pkg/accounthandler/setup_test.go |
Add teardown dispatch tests (LINE, SMS, unknown type) |
pkg/listenhandler/v1_accounts_test.go |
Update: expect no secret/token in responses; 404 instead of 500 for error cases |
pkg/dbhandler/account_test.go |
Remove Test_AccountSet |
| Mock regeneration | go generate ./... after linehandler and dbhandler interface changes |
| Risk | Mitigation |
|---|---|
| Credentials in internal event queue | Acceptable — internal queue, no external subscribers |
| LINE teardown API failure | Best-effort, logged as warning, non-blocking |
| Breaking API change (no secret/token in responses) | Corrects spec violation — OpenAPI already says "Write-only" |
| 404 masking real server errors | Matches monorepo convention; error details are logged |
| CLI tool credentials in output | Admin tool, full data expected — no change needed |
go mod tidy && go mod vendor && go generate ./... && go test ./... && golangci-lint run -v --timeout 5minbin-conversation-managergo generate ./...inbin-openapi-manager(for Fix 9)go generate ./...inbin-api-manager(if openapi types are consumed)