Skip to content

feat(auth): multi-workspace credential set and session management - #522

Closed
khaliqgant wants to merge 2 commits into
mainfrom
feature/multi-workspace-auth
Closed

feat(auth): multi-workspace credential set and session management#522
khaliqgant wants to merge 2 commits into
mainfrom
feature/multi-workspace-auth

Conversation

@khaliqgant

@khaliqgantkhaliqgant commented Mar 10, 2026

Copy link
Copy Markdown
Member

Summary

Adds the auth-layer foundation for multi-workspace support. This is the credential/session management piece that complements the runtime impl in #519 and the spec in #517.

Changes

  • WorkspaceCredential — renamed from CredentialCache (type alias preserved for backward compat)
  • workspace_alias field — human-friendly workspace selectors (e.g. "personal", "work")
  • CredentialSet — manages N workspace memberships with a default, flexible deserialization (supports legacy single-credential, array, and new set format)
  • AuthSessionSet — runtime session container with default session lookup
  • Selector lookup — find membership by workspace_id or alias (case-insensitive)
  • Auto-normalize — filters empty API keys, auto-defaults when single membership

Related


Open with Devin

- Rename CredentialCache → WorkspaceCredential (type alias preserved)
- Add workspace_alias field for human-friendly workspace selectors
- New CredentialSet: manages multiple workspace memberships with default
- New AuthSessionSet: runtime session container with default lookup
- Flexible deserialization: supports legacy single-credential, array, and
new credential set formats
- Selector lookup by workspace_id or alias (case-insensitive)
- Auto-normalize: empty API keys filtered, single-membership auto-default

@devin-ai-integrationdevin-ai-integrationBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment threadsrc/auth.rs
Comment on lines +69 to +71
if let Ok(set) = serde_json::from_value::<CredentialSet>(value.clone()) {
return Ok(Self::normalize(set));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CredentialSet deserialization always succeeds first, making legacy/WorkspaceSource fallback branches dead code

CredentialSet derives Default and both its fields (memberships, default_workspace_id) carry #[serde(default)] (src/auth.rs:29-39). Because serde ignores unknown fields by default, serde_json::from_value::<CredentialSet>(value) will succeed for any JSON object — including a legacy WorkspaceCredential object or a WorkspaceSource object — producing an empty CredentialSet (zero memberships, no default). This means the first branch at line 69 always matches for object inputs, so the legacy WorkspaceCredential branch (line 73), the Vec<WorkspaceCredential> branch (line 77), and the WorkspaceSource branch (line 81) are never reached for JSON objects. A legacy credential cache file like {"workspace_id":"ws1","agent_id":"a1","api_key":"rk_live_x",...} will be silently parsed into an empty CredentialSet with no memberships, losing all credential data.

Example of the silent data loss

Given legacy JSON:

{"workspace_id":"ws1","agent_id":"a1","api_key":"rk_live_x","updated_at":"2025-01-01T00:00:00Z"}

from_value hits line 69, deserializes it as CredentialSet{memberships: [], default_workspace_id: None}, normalizes it (still empty), and returns — the WorkspaceCredential branch at line 73 is never tried.

Prompt for agents
In src/auth.rs, the CredentialSet::from_value method at line 68-99 tries to deserialize as CredentialSet first (line 69), but because CredentialSet has #[serde(default)] on all fields and no #[serde(deny_unknown_fields)], any JSON object successfully deserializes as an empty CredentialSet. This makes all subsequent fallback branches (WorkspaceCredential, Vec<WorkspaceCredential>, WorkspaceSource) unreachable for JSON objects.
Fix options:
1. Add #[serde(deny_unknown_fields)] to CredentialSet so that objects with unrecognized keys (like workspace_id, agent_id, etc.) fail to deserialize as CredentialSet.
2. Alternatively, after successfully deserializing as CredentialSet at line 69, check whether the result is meaningful (e.g. set.memberships.is_empty() && set.default_workspace_id.is_none()) and only return it if it has actual data — otherwise fall through to the legacy branches.
3. Or reorder the branches so that more specific types (WorkspaceCredential, which has required fields) are tried before the permissive CredentialSet.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@khaliqgant

Copy link
Copy Markdown
MemberAuthor

Closing — all auth changes (WorkspaceCredential, CredentialSet, AuthSessionSet) are already included in PR #519 (multi-workspace runtime impl). This PR is redundant.

@willwashburn
willwashburn deleted the feature/multi-workspace-auth branch May 15, 2026 13:09
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@khaliqgant