feat(auth): support multi-profile login#500
Conversation
|
Runtime acceptance updated after commit 5dc5b03. Verification:
Test docs: |
|
Verified current PR head Passed acceptance for the multi-organization profile loop. Final review found no blocking issues; the last P2 gap was closed by adding root Changes:
Verification:
Full
|
Ralph 验收补充本轮按技术方案优先收敛了多组织登录能力,并把在线资料、PRD、技术方案、验收评审都落到了 关键结论
验证go test ./internal/auth ./internal/app -run 'Test(MultiProfile|RuntimeProfile|DeleteProfile|UpsertProfile|LoadProfiles|LegacyKeychain|WriteProfile|ProfileList|ProfileUse|AuthStatus|AuthLogout|AuthLogin|ResolveAuthLogin|EnrichAuthLogin|RootHelp|RootShortHelp|RootCommand)'结果: 另已验证本地打包安装:
残余说明全量 |
Ralph 验收补充本轮按技术方案优先收敛了多组织登录能力,并把在线资料、PRD、技术方案、验收评审都落到了 关键结论
验证go test ./internal/auth ./internal/app -run 'Test(MultiProfile|RuntimeProfile|DeleteProfile|UpsertProfile|LoadProfiles|LegacyKeychain|WriteProfile|ProfileList|ProfileUse|ProfileSwitch|AuthSwitch|AuthStatus|AuthLogout|AuthLogin|ResolveAuthLogin|EnrichAuthLogin|RootHelp|RootShortHelp|RootCommand)'结果: 另已验证本地打包安装:
残余说明全量 |
Ralph 验收补充本轮按技术方案优先收敛了多组织登录能力,并把在线资料、PRD、技术方案、验收评审都落到了 关键结论
验证go test ./internal/auth ./internal/app -run 'Test(MultiProfile|RuntimeProfile|DeleteProfile|UpsertProfile|LoadProfiles|LegacyKeychain|WriteProfile|ProfileList|ProfileUse|ProfileSwitch|AuthSwitch|AuthStatus|AuthLogout|AuthLogin|ResolveAuthLogin|EnrichAuthLogin|RootHelp|RootShortHelp|RootCommand)'结果: 另已验证本地打包安装:
残余说明全量 |
Ralph 验收补充本轮按技术方案优先收敛了多组织登录能力,并把在线资料、PRD、技术方案、验收评审都落到了 关键结论
验证go test ./internal/auth ./internal/app -run 'Test(MultiProfile|RuntimeProfile|DeleteProfile|UpsertProfile|LoadProfiles|LegacyKeychain|WriteProfile|ProfileList|ProfileUse|ProfileSwitch|AuthSwitch|AuthStatus|AuthLogout|AuthLogin|ResolveAuthLogin|EnrichAuthLogin|RootHelp|RootShortHelp|RootCommand)'结果: 另已验证本地打包安装:
残余说明全量 |
Ralph 验收补充本轮按技术方案优先收敛了多组织登录能力,并把在线资料、PRD、技术方案、验收评审都落到了 关键结论
验证go test ./internal/auth ./internal/app -run 'Test(MultiProfile|RuntimeProfile|DeleteProfile|UpsertProfile|LoadProfiles|LegacyKeychain|WriteProfile|ProfileList|ProfileUse|ProfileSwitch|AuthCommandDoesNotExposeSwitch|AuthStatus|AuthLogout|AuthLogin|ResolveAuthLogin|EnrichAuthLogin|RootHelp|RootShortHelp|RootCommand)'
go test ./internal/auth ./internal/app结果: 另已验证本地打包安装,本机 |
Profile switch TUI fixUpdated current PR head to What changed:
Verification:
|
…stence Wrap all profiles.json read-modify-write paths (profile switch/use/remove, status marking, token save, logout) in the existing dual-layer lock via a new withProfilesLock helper. Split each writer into a public (locking) entry point plus a lock-free *Locked variant so the non-reentrant lock is never re-acquired; the refresh path (oauth_helpers) and the load-path legacy migration now call the lock-free saver to avoid self-deadlock. Also: write profiles.json and the token marker via per-write random temp names (uuid) to stop concurrent writers from corrupting a fixed .tmp file; quarantine an unparseable profiles.json and rebuild an empty config so the CLI can self-heal instead of locking out auth reset/logout; make DeleteAllTokenData proceed even if profiles.json cannot be read; and stop SyncLegacyTokenMirror from deleting the legacy mirror on a transient keychain read error.
When no explicit --profile is given, LoadTokenDataForProfile resolves the
current/primary profile and reads its per-corp keychain slot. If that slot
read failed, the code silently fell through to the legacy single token slot,
which after any drift between the legacy mirror and the current profile could
belong to a different organization. The command would then run as the wrong
org with no indication to the user.
Reproduction (conceptual):
- profiles.json currentProfile = corpA
- corpA's keychain slot is unreadable, legacy single slot still holds corpB
- any read command (no --profile) silently used corpB's token
Fix: when a profile is resolved but its slot read fails and no --profile was
given, only fall back to the legacy single slot when its CorpID matches the
resolved profile (same org); otherwise return the original error instead of
acting as a different organization. The no-profile legacy path (pre-migration
installs with no resolved profile) is unchanged.
Tests:
- Covered by the existing internal/auth suite under go test -race; the
same-org fallback preserves the legacy-mirror case while the cross-org
case now surfaces the read error.
The skills had no guidance on the multi-profile capability, so an agent would
treat the CLI as single-org: when a lookup missed in the current org it would
give up or ask the user instead of searching other logged-in orgs. The multi
skill set also referenced a `dws-shared` prerequisite that was never actually
installed, and the only multi-org hints lived inline in three product skills.
This adds, in source only:
- A "multi-org / profile" section in the mono SKILL.md (concept, commands,
cross-org rule, aggregation, safety guardrails) plus a decision-tree entry,
trigger conditions, and a corrected logout danger-table row (logout removes
all orgs by default; removing the primary silently re-elects a new primary,
confirm before removing the primary).
- A standalone skills/multi/dingtalk-profile skill mirroring the same content.
- A new skills/multi/dws-shared skill that carries auth, global flags and the
multi-org rule, so every product skill's PREREQUISITE resolves and all
read/search skills inherit the cross-org behavior without per-skill edits.
- Cross-org fallback notes on dingtalk-aisearch / chat / contact.
To guarantee the prerequisite actually ships, multi-mode install now force-
includes dws-shared even when --skill / --exclude narrows the set (no-op when
the source has no dws-shared, preserving older layouts).
Tests:
- internal/app: TestP1SharedAlwaysIncludedWithSkillFilter installs with
`-s aitable` and asserts dws-shared still lands in the destination;
TestP1SharedNoopWhenAbsent guards the older-layout no-op.
- go test -race ./internal/auth/... ./internal/app/... passes.
…file-login # Conflicts: # .github/workflows/auto-dev-release.yml
PeterGuy326
left a comment
There was a problem hiding this comment.
看过了,CI 全绿,本地 go test ./internal/auth ./internal/app、多组织 profile 切换和 --profile 都验过,没问题。
一个小建议(不卡合入):Verification 段补一下多组织手测流程——升级迁移、第二个组织登录、profile use - 切换、auth logout 清槽,每步贴预期 vs 实际;再点一句 keychain 不随 DWS_CONFIG_DIR 隔离这个坑,方便别人复现。
Summary
profiles.json) for primary/current/previous DingTalk org profiles.auth-token:<corpId>) while preserving the legacyauth-tokenmirror.dws profile list/use/use -, global--profile, and profile-awareauth status/logout/reset.Server-side impact
No server change is required for this PR. The implementation reuses the existing OAuth/device login responses and the existing
TokenDatafields (CorpID,CorpName,UserID,UserName,ClientID, refresh/access expiry). All new selection and storage behavior is local to the CLI:profiles.jsonmetadata plus keychain slots.Server changes would only be needed for future scope outside this PR, such as automatic discovery of every org a user belongs to, cross-org aggregation APIs, or real/embedded host multi-profile hook protocol expansion.
Verification
go test ./internal/auth ./internal/appgo build ./...git diff --checkNotes
go test ./...still has unrelated environment/fixture failures in this checkout:internal/transportstdio initialize timeout,test/cli_compatmissingtestdata/empty_catalog.json, andtest/scriptsmissing package version/git tag.