fix(lark): preserve app secrets during credential switches (#4820)

This commit is contained in:
Daoyuan Li 2026-09-01 00:37:19 -07:00 committed by GitHub
parent b552b5015c
commit df57f8e269
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 24 additions and 6 deletions

View File

@ -886,6 +886,8 @@ is per-user (it never touches another user's credentials), validates the new
credentials through the official CLI's live tenant-token probe before replacing
the active app, and revokes/removes the previous app's OAuth tokens. A rejected
credential change does not supersede an in-progress setup or authorization flow.
The previous OAuth data is cleared before the CLI stores the replacement app,
so the new file-backed keychain secret remains available during reconnection.
DeerFlow then immediately opens browser authorization for the newly bound app so
the switch ends in a usable connection.

View File

@ -7,6 +7,16 @@ Request trace correlation is controlled by `logging.enhance.enabled` at **both**
The same ContextVar value is injected into enhanced log records as `trace_id` and into Langfuse metadata as `deerflow_trace_id`.
### Managed Lark CLI credentials (`integrations/lark_cli.py`)
App registration and direct app switching replace the per-user Lark credential
tree transactionally. Clear the old OAuth data before running `lark-cli config
init`: on Linux that command writes the new app secret into the file-backed
keychain under the data directory, so clearing the directory afterward would
leave `config.json` with a dangling keychain reference. The transaction snapshot
still supplies the previous OAuth data for logout and restores the complete old
tree if any switch step fails.
`logging` is registered as a **restart-required** field
(`STARTUP_ONLY_FIELDS["logging"]`): `configure_logging()` installs the trace-context
filter and enhanced formatter on root handlers only during app.py lifespan startup,

View File

@ -1484,8 +1484,8 @@ def _replace_lark_app_credentials_locked(user_id: str, *, app_id: str, app_secre
ensure_lark_cli_credential_tree(user_id)
root = _lark_cli_credential_root(user_id)
with _lark_credential_transaction(user_id, root) as snapshot:
_save_lark_app_config_with_cli(user_id, app_id=app_id, app_secret=app_secret, brand=brand)
_clear_directory_contents(lark_cli_data_dir(user_id))
_save_lark_app_config_with_cli(user_id, app_id=app_id, app_secret=app_secret, brand=brand)
_revoke_lark_auth_from_snapshot(snapshot)

View File

@ -1390,6 +1390,8 @@ def test_complete_lark_config_saves_app_credentials_and_returns_status(monkeypat
(config_dir / "config.json").write_text("old-config", encoding="utf-8")
token_file = data_dir / "token.json"
token_file.write_text("old-token", encoding="utf-8")
master_key = data_dir / "master.key"
app_secret_file = data_dir / "appsecret_cli_mock.enc"
generation = _advance_lark_flow()
monkeypatch.setattr(
@ -1401,11 +1403,13 @@ def test_complete_lark_config_saves_app_credentials_and_returns_status(monkeypat
"user_info": {"tenant_brand": "feishu"},
},
)
monkeypatch.setattr(
lark_cli,
"_save_lark_app_config_with_cli",
lambda user_id, **kwargs: captured.update({"user_id": user_id, **kwargs}),
)
def _save(user_id, **kwargs):
captured.update({"user_id": user_id, **kwargs})
master_key.write_text("new-master-key", encoding="utf-8")
app_secret_file.write_text("encrypted-app-secret", encoding="utf-8")
monkeypatch.setattr(lark_cli, "_save_lark_app_config_with_cli", _save)
monkeypatch.setattr(
lark_cli,
"_revoke_lark_auth_from_snapshot",
@ -1445,6 +1449,8 @@ def test_complete_lark_config_saves_app_credentials_and_returns_status(monkeypat
assert result.generation == generation
assert revoked == ["old-token"]
assert not token_file.exists()
assert master_key.read_text(encoding="utf-8") == "new-master-key"
assert app_secret_file.read_text(encoding="utf-8") == "encrypted-app-secret"
assert captured == {
"user_id": "alice",
"app_id": "cli_mock",