mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-06-26 07:08:06 +02:00
feat: global usernames and rename workspace to default_workspace (#1001)
Users are global entities, not scoped to workspaces. This change: Track A — Global usernames: - Change iam_users_by_username to PRIMARY KEY (username), removing workspace from the lookup key - Login looks up username globally, no workspace required - Username uniqueness is enforced globally, not per-workspace - Login -w now overrides the JWT workspace (session workspace) rather than selecting which user registry to search Track B — Rename workspace to default_workspace: - UserRecord.workspace → UserRecord.default_workspace - Identity.workspace → Identity.default_workspace - JWT claim "workspace" → "default_workspace" - IamResponse.resolved_workspace → resolved_default_workspace - WebSocket auth-ok frame field → default_workspace - Socket clients read default_workspace from auth-ok - _user_record_to_dict wire key → default_workspace - CLI help text and output updated throughout - Test files updated for renamed fields
This commit is contained in:
parent
16f8cfd972
commit
0a828379be
19 changed files with 101 additions and 113 deletions
|
|
@ -94,7 +94,9 @@ class AsyncSocketClient:
|
|||
|
||||
if resp.get("type") == "auth-ok":
|
||||
if not self._workspace_explicit:
|
||||
self.workspace = resp.get("workspace", self.workspace)
|
||||
self.workspace = resp.get(
|
||||
"default_workspace", self.workspace,
|
||||
)
|
||||
elif resp.get("type") == "auth-failed":
|
||||
await self._socket.close()
|
||||
raise ProtocolException(
|
||||
|
|
|
|||
|
|
@ -168,7 +168,9 @@ class SocketClient:
|
|||
|
||||
if resp.get("type") == "auth-ok":
|
||||
if self.workspace == "default":
|
||||
self.workspace = resp.get("workspace", self.workspace)
|
||||
self.workspace = resp.get(
|
||||
"default_workspace", self.workspace,
|
||||
)
|
||||
elif resp.get("type") == "auth-failed":
|
||||
await self._socket.close()
|
||||
raise ProtocolException(
|
||||
|
|
|
|||
|
|
@ -65,31 +65,25 @@ class IamClient(RequestResponse):
|
|||
async def authenticate_anonymous(self, timeout=IAM_TIMEOUT):
|
||||
"""Request anonymous access from the IAM regime.
|
||||
|
||||
Returns ``(user_id, workspace, roles)`` if the regime permits
|
||||
anonymous access, or raises ``RuntimeError`` with error type
|
||||
``auth-failed`` if it does not."""
|
||||
Returns ``(user_id, default_workspace, roles)`` if the regime
|
||||
permits anonymous access, or raises ``RuntimeError`` with
|
||||
error type ``auth-failed`` if it does not."""
|
||||
resp = await self._request(
|
||||
operation="authenticate-anonymous",
|
||||
timeout=timeout,
|
||||
)
|
||||
return (
|
||||
resp.resolved_user_id,
|
||||
resp.resolved_workspace,
|
||||
resp.resolved_default_workspace,
|
||||
list(resp.resolved_roles),
|
||||
)
|
||||
|
||||
async def resolve_api_key(self, api_key, timeout=IAM_TIMEOUT):
|
||||
"""Resolve a plaintext API key to its identity triple.
|
||||
|
||||
Returns ``(user_id, workspace, roles)`` or raises
|
||||
Returns ``(user_id, default_workspace, roles)`` or raises
|
||||
``RuntimeError`` with error type ``auth-failed`` if the key is
|
||||
unknown / expired / revoked.
|
||||
|
||||
Note: the ``roles`` value is a regime-internal hint and is
|
||||
not used by the gateway directly under the IAM contract;
|
||||
all authorisation decisions go through ``authorise()``.
|
||||
Returned here only for backward compatibility with callers
|
||||
that haven't migrated."""
|
||||
unknown / expired / revoked."""
|
||||
resp = await self._request(
|
||||
operation="resolve-api-key",
|
||||
api_key=api_key,
|
||||
|
|
@ -97,7 +91,7 @@ class IamClient(RequestResponse):
|
|||
)
|
||||
return (
|
||||
resp.resolved_user_id,
|
||||
resp.resolved_workspace,
|
||||
resp.resolved_default_workspace,
|
||||
list(resp.resolved_roles),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ def _user_record_to_dict(r):
|
|||
return None
|
||||
return {
|
||||
"id": r.id,
|
||||
"workspace": r.workspace,
|
||||
"default_workspace": r.default_workspace,
|
||||
"username": r.username,
|
||||
"name": r.name,
|
||||
"email": r.email,
|
||||
|
|
@ -218,8 +218,8 @@ class IamResponseTranslator(MessageTranslator):
|
|||
result["signing_key_public"] = obj.signing_key_public
|
||||
if obj.resolved_user_id:
|
||||
result["resolved_user_id"] = obj.resolved_user_id
|
||||
if obj.resolved_workspace:
|
||||
result["resolved_workspace"] = obj.resolved_workspace
|
||||
if obj.resolved_default_workspace:
|
||||
result["resolved_default_workspace"] = obj.resolved_default_workspace
|
||||
if obj.resolved_roles:
|
||||
result["resolved_roles"] = list(obj.resolved_roles)
|
||||
if obj.temporary_password:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class UserInput:
|
|||
@dataclass
|
||||
class UserRecord:
|
||||
id: str = ""
|
||||
workspace: str = ""
|
||||
default_workspace: str = ""
|
||||
username: str = ""
|
||||
name: str = ""
|
||||
email: str = ""
|
||||
|
|
@ -160,7 +160,7 @@ class IamResponse:
|
|||
|
||||
# resolve-api-key
|
||||
resolved_user_id: str = ""
|
||||
resolved_workspace: str = ""
|
||||
resolved_default_workspace: str = ""
|
||||
resolved_roles: list[str] = field(default_factory=list)
|
||||
|
||||
# reset-password
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue