diff --git a/tests/unit/test_gateway/test_auth.py b/tests/unit/test_gateway/test_auth.py index 8ffcafa1..2775b0b0 100644 --- a/tests/unit/test_gateway/test_auth.py +++ b/tests/unit/test_gateway/test_auth.py @@ -86,19 +86,19 @@ class TestVerifyJwtEddsa: def test_valid_jwt_passes(self): priv, pub = make_keypair() claims = { - "sub": "user-1", "workspace": "default", + "sub": "user-1", "default_workspace": "default", "iat": int(time.time()), "exp": int(time.time()) + 60, } token = sign_jwt(priv, claims) got = _verify_jwt_eddsa(token, pub) assert got["sub"] == "user-1" - assert got["workspace"] == "default" + assert got["default_workspace"] == "default" def test_expired_jwt_rejected(self): priv, pub = make_keypair() claims = { - "sub": "user-1", "workspace": "default", + "sub": "user-1", "default_workspace": "default", "iat": int(time.time()) - 3600, "exp": int(time.time()) - 1, } @@ -110,7 +110,7 @@ class TestVerifyJwtEddsa: priv_a, _ = make_keypair() _, pub_b = make_keypair() claims = { - "sub": "user-1", "workspace": "default", + "sub": "user-1", "default_workspace": "default", "iat": int(time.time()), "exp": int(time.time()) + 60, } @@ -130,7 +130,7 @@ class TestVerifyJwtEddsa: # since we expect it to bail before verifying. header = {"alg": "HS256", "typ": "JWT", "kid": "x"} payload = { - "sub": "user-1", "workspace": "default", + "sub": "user-1", "default_workspace": "default", "iat": int(time.time()), "exp": int(time.time()) + 60, } h = _b64url(json.dumps(header, separators=(",", ":")).encode()) @@ -148,11 +148,11 @@ class TestIdentity: def test_fields(self): i = Identity( - handle="u", workspace="w", + handle="u", default_workspace="w", principal_id="u", source="api-key", ) assert i.handle == "u" - assert i.workspace == "w" + assert i.default_workspace == "w" assert i.principal_id == "u" assert i.source == "api-key" @@ -208,7 +208,7 @@ class TestIamAuthDispatch: async def test_valid_jwt_resolves_to_identity(self): priv, pub = make_keypair() claims = { - "sub": "user-1", "workspace": "default", + "sub": "user-1", "default_workspace": "default", "iat": int(time.time()), "exp": int(time.time()) + 60, } @@ -221,7 +221,7 @@ class TestIamAuthDispatch: make_request(f"Bearer {token}") ) assert ident.handle == "user-1" - assert ident.workspace == "default" + assert ident.default_workspace == "default" assert ident.principal_id == "user-1" assert ident.source == "jwt" @@ -231,7 +231,7 @@ class TestIamAuthDispatch: # must not validate — even ones that would otherwise pass. priv, _ = make_keypair() claims = { - "sub": "user-1", "workspace": "default", + "sub": "user-1", "default_workspace": "default", "iat": int(time.time()), "exp": int(time.time()) + 60, } token = sign_jwt(priv, claims) @@ -259,7 +259,7 @@ class TestIamAuthDispatch: make_request("Bearer tg_testkey") ) assert ident.handle == "user-xyz" - assert ident.workspace == "default" + assert ident.default_workspace == "default" assert ident.principal_id == "user-xyz" assert ident.source == "api-key" @@ -338,9 +338,9 @@ class TestAuthorise: decision for the regime's TTL (clamped above), and raises 403 on deny / 401 on regime error (fail closed).""" - def _make_identity(self, handle="u-1", workspace="default"): + def _make_identity(self, handle="u-1", default_workspace="default"): return Identity( - handle=handle, workspace=workspace, + handle=handle, default_workspace=default_workspace, principal_id=handle, source="api-key", ) diff --git a/tests/unit/test_gateway/test_capabilities.py b/tests/unit/test_gateway/test_capabilities.py index 4f781b16..04bac133 100644 --- a/tests/unit/test_gateway/test_capabilities.py +++ b/tests/unit/test_gateway/test_capabilities.py @@ -25,11 +25,11 @@ from trustgraph.gateway.capabilities import ( class _Identity: """Stand-in for auth.Identity — under the IAM contract it has - just ``handle``, ``workspace``, ``principal_id``, ``source``.""" + just ``handle``, ``default_workspace``, ``principal_id``, ``source``.""" - def __init__(self, handle="user-1", workspace="default"): + def __init__(self, handle="user-1", default_workspace="default"): self.handle = handle - self.workspace = workspace + self.default_workspace = default_workspace self.principal_id = handle self.source = "api-key" @@ -105,14 +105,14 @@ class TestEnforceWorkspace: async def test_default_fills_from_identity(self): data = {"operation": "x"} auth = _allow_auth() - await enforce_workspace(data, _Identity(workspace="default"), auth) + await enforce_workspace(data, _Identity(default_workspace="default"), auth) assert data["workspace"] == "default" @pytest.mark.asyncio async def test_caller_supplied_workspace_kept(self): data = {"workspace": "acme", "operation": "x"} auth = _allow_auth() - await enforce_workspace(data, _Identity(workspace="default"), auth) + await enforce_workspace(data, _Identity(default_workspace="default"), auth) assert data["workspace"] == "acme" @pytest.mark.asyncio diff --git a/tests/unit/test_gateway/test_socket_graceful_shutdown.py b/tests/unit/test_gateway/test_socket_graceful_shutdown.py index 6c3e323b..8116aa51 100644 --- a/tests/unit/test_gateway/test_socket_graceful_shutdown.py +++ b/tests/unit/test_gateway/test_socket_graceful_shutdown.py @@ -28,7 +28,7 @@ TEST_CAP = "graph:write" def _valid_identity(): return Identity( handle="test-user", - workspace="default", + default_workspace="default", principal_id="test-user", source="api-key", ) diff --git a/tests/unit/test_iam/test_noauth_handler.py b/tests/unit/test_iam/test_noauth_handler.py index 38461b62..7bfdac0c 100644 --- a/tests/unit/test_iam/test_noauth_handler.py +++ b/tests/unit/test_iam/test_noauth_handler.py @@ -32,7 +32,7 @@ class TestAuthenticateAnonymous: ) assert resp.error is None assert resp.resolved_user_id == "anon" - assert resp.resolved_workspace == "ws" + assert resp.resolved_default_workspace == "ws" assert "admin" in list(resp.resolved_roles) @pytest.mark.asyncio @@ -44,7 +44,7 @@ class TestAuthenticateAnonymous: _make_request(operation="authenticate-anonymous") ) assert resp.resolved_user_id == "dev-user" - assert resp.resolved_workspace == "dev-ws" + assert resp.resolved_default_workspace == "dev-ws" class TestResolveApiKey: @@ -57,7 +57,7 @@ class TestResolveApiKey: ) assert resp.error is None assert resp.resolved_user_id == "anonymous" - assert resp.resolved_workspace == "default" + assert resp.resolved_default_workspace == "default" class TestAuthorise: diff --git a/trustgraph-base/trustgraph/api/async_socket_client.py b/trustgraph-base/trustgraph/api/async_socket_client.py index 7b38a4b1..fdbe2f67 100644 --- a/trustgraph-base/trustgraph/api/async_socket_client.py +++ b/trustgraph-base/trustgraph/api/async_socket_client.py @@ -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( diff --git a/trustgraph-base/trustgraph/api/socket_client.py b/trustgraph-base/trustgraph/api/socket_client.py index 91bc67a1..e87d85ac 100644 --- a/trustgraph-base/trustgraph/api/socket_client.py +++ b/trustgraph-base/trustgraph/api/socket_client.py @@ -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( diff --git a/trustgraph-base/trustgraph/base/iam_client.py b/trustgraph-base/trustgraph/base/iam_client.py index a2878a0a..423abb8e 100644 --- a/trustgraph-base/trustgraph/base/iam_client.py +++ b/trustgraph-base/trustgraph/base/iam_client.py @@ -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), ) diff --git a/trustgraph-base/trustgraph/messaging/translators/iam.py b/trustgraph-base/trustgraph/messaging/translators/iam.py index 9e16d3df..4a456044 100644 --- a/trustgraph-base/trustgraph/messaging/translators/iam.py +++ b/trustgraph-base/trustgraph/messaging/translators/iam.py @@ -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: diff --git a/trustgraph-base/trustgraph/schema/services/iam.py b/trustgraph-base/trustgraph/schema/services/iam.py index 6a657d62..6b2599e0 100644 --- a/trustgraph-base/trustgraph/schema/services/iam.py +++ b/trustgraph-base/trustgraph/schema/services/iam.py @@ -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 diff --git a/trustgraph-cli/trustgraph/cli/create_user.py b/trustgraph-cli/trustgraph/cli/create_user.py index c9253aca..14760454 100644 --- a/trustgraph-cli/trustgraph/cli/create_user.py +++ b/trustgraph-cli/trustgraph/cli/create_user.py @@ -53,7 +53,7 @@ def main(): help="Auth token (default: $TRUSTGRAPH_TOKEN)", ) parser.add_argument( - "--username", required=True, help="Username (unique in workspace)", + "--username", required=True, help="Username (globally unique)", ) parser.add_argument( "--password", default=None, @@ -75,10 +75,7 @@ def main(): ) parser.add_argument( "-w", "--workspace", default=None, - help=( - "Target workspace (admin only; defaults to caller's " - "assigned workspace)" - ), + help="Default workspace for the new user", ) run_main(do_create_user, parser) diff --git a/trustgraph-cli/trustgraph/cli/login.py b/trustgraph-cli/trustgraph/cli/login.py index 0e87c3b0..977cf15b 100644 --- a/trustgraph-cli/trustgraph/cli/login.py +++ b/trustgraph-cli/trustgraph/cli/login.py @@ -51,8 +51,8 @@ def main(): parser.add_argument( "-w", "--workspace", default=None, help=( - "Optional workspace to log in against. Defaults to " - "the user's assigned workspace." + "Override the default workspace for this session's JWT. " + "If omitted, uses the user's stored default workspace." ), ) run_main(do_login, parser) diff --git a/trustgraph-cli/trustgraph/cli/update_user.py b/trustgraph-cli/trustgraph/cli/update_user.py index 5c1dc4d7..d24d5323 100644 --- a/trustgraph-cli/trustgraph/cli/update_user.py +++ b/trustgraph-cli/trustgraph/cli/update_user.py @@ -68,7 +68,7 @@ def do_update_user(args): print(f"username : {rec.get('username', '')}") print(f"name : {rec.get('name', '')}") print(f"email : {rec.get('email', '')}") - print(f"workspace : {rec.get('workspace', '')}") + print(f"default_ws: {rec.get('default_workspace', '')}") print(f"roles : {', '.join(rec.get('roles', []))}") print(f"enabled : {'yes' if rec.get('enabled') else 'no'}") print( @@ -114,7 +114,7 @@ def main(): "-w", "--workspace", default=None, help=( "Optional workspace integrity check — when supplied, " - "iam-svc verifies the target user's home workspace " + "iam-svc verifies the target user's default workspace " "matches" ), ) diff --git a/trustgraph-flow/trustgraph/gateway/auth.py b/trustgraph-flow/trustgraph/gateway/auth.py index 273fcb5a..b08bf650 100644 --- a/trustgraph-flow/trustgraph/gateway/auth.py +++ b/trustgraph-flow/trustgraph/gateway/auth.py @@ -57,16 +57,17 @@ class Identity: # the OSS regime this is the user record's id; the gateway # treats it as a string with no semantic content. handle: str - # The workspace this credential authenticates to. Used by the - # gateway as the default-fill-in for operations that omit a - # workspace. Never used as policy input. - workspace: str + # The user's default workspace. Used by the gateway as the + # default-fill-in for operations that omit a workspace. Not a + # permission boundary — workspace access is controlled by the + # IAM regime's authorise() decision, not by this field. + default_workspace: str # Stable identifier for audit logs. In OSS this is the same # value as ``handle``; not assumed equal in the contract. principal_id: str # How the credential was presented. Non-policy; useful for # logs / metrics only. - source: str # "api-key" | "jwt" + source: str # "api-key" | "jwt" | "anonymous" def _auth_failure(): @@ -256,21 +257,22 @@ class IamAuth: raise _auth_failure() sub = claims.get("sub", "") - ws = claims.get("workspace", "") + ws = claims.get("default_workspace", "") if not sub or not ws: raise _auth_failure() - # JWT carries no policy state under the IAM contract; - # any roles / claims field is ignored here. return Identity( - handle=sub, workspace=ws, principal_id=sub, source="jwt", + handle=sub, default_workspace=ws, + principal_id=sub, source="jwt", ) async def _authenticate_anonymous(self): try: async def _call(client): return await client.authenticate_anonymous() - user_id, workspace, _roles = await self._with_client(_call) + user_id, default_workspace, _roles = await self._with_client( + _call, + ) except Exception as e: logger.debug( f"Anonymous authentication rejected: " @@ -278,11 +280,11 @@ class IamAuth: ) raise _auth_failure() - if not user_id or not workspace: + if not user_id or not default_workspace: raise _auth_failure() return Identity( - handle=user_id, workspace=workspace, + handle=user_id, default_workspace=default_workspace, principal_id=user_id, source="anonymous", ) @@ -305,7 +307,9 @@ class IamAuth: # ``roles`` is returned by the OSS regime as a hint # but is not consulted by the gateway; all policy # decisions go through ``authorise``. - user_id, workspace, _roles = await self._with_client(_call) + user_id, default_workspace, _roles = await self._with_client( + _call, + ) except Exception as e: logger.debug( f"API key resolution failed: " @@ -313,11 +317,11 @@ class IamAuth: ) raise _auth_failure() - if not user_id or not workspace: + if not user_id or not default_workspace: raise _auth_failure() identity = Identity( - handle=user_id, workspace=workspace, + handle=user_id, default_workspace=default_workspace, principal_id=user_id, source="api-key", ) self._key_cache[h] = (identity, now + API_KEY_CACHE_TTL) diff --git a/trustgraph-flow/trustgraph/gateway/capabilities.py b/trustgraph-flow/trustgraph/gateway/capabilities.py index dbbb01e0..c9d3b516 100644 --- a/trustgraph-flow/trustgraph/gateway/capabilities.py +++ b/trustgraph-flow/trustgraph/gateway/capabilities.py @@ -99,7 +99,7 @@ async def enforce_workspace(data, identity, auth, capability=None): return data requested = data.get("workspace", "") - target = requested or identity.workspace + target = requested or identity.default_workspace data["workspace"] = target if target not in auth.known_workspaces: diff --git a/trustgraph-flow/trustgraph/gateway/dispatch/mux.py b/trustgraph-flow/trustgraph/gateway/dispatch/mux.py index 0093c563..c1020998 100644 --- a/trustgraph-flow/trustgraph/gateway/dispatch/mux.py +++ b/trustgraph-flow/trustgraph/gateway/dispatch/mux.py @@ -80,7 +80,7 @@ class Mux: self.identity = identity await self.ws.send_json({ "type": "auth-ok", - "workspace": identity.workspace, + "default_workspace": identity.default_workspace, }) async def receive(self, msg): diff --git a/trustgraph-flow/trustgraph/gateway/registry.py b/trustgraph-flow/trustgraph/gateway/registry.py index 4fba7920..f7f9f882 100644 --- a/trustgraph-flow/trustgraph/gateway/registry.py +++ b/trustgraph-flow/trustgraph/gateway/registry.py @@ -92,7 +92,7 @@ class Operation: # Returns a dict with the appropriate components for the # resource level: {} for SYSTEM, {workspace} for WORKSPACE, # {workspace, flow} for FLOW. Default-fill-in of workspace - # from identity.workspace happens here when applicable. + # from identity.default_workspace happens here when applicable. extract_resource: Callable[[RequestContext], dict] # Build the parameters dict — decision-relevant fields the @@ -141,7 +141,7 @@ def _workspace_from_body(ctx: RequestContext) -> dict: workspace field, defaulting to the caller's bound workspace.""" ws = (ctx.body.get("workspace") if isinstance(ctx.body, dict) else "") if not ws and ctx.identity is not None: - ws = ctx.identity.workspace + ws = ctx.identity.default_workspace return {"workspace": ws} @@ -188,7 +188,7 @@ def _workspace_param_only(ctx: RequestContext) -> dict: or body.get("workspace") ) if not ws and ctx.identity is not None: - ws = ctx.identity.workspace + ws = ctx.identity.default_workspace return {"workspace": ws or ""} diff --git a/trustgraph-flow/trustgraph/iam/noauth/handler.py b/trustgraph-flow/trustgraph/iam/noauth/handler.py index dd70b02d..1d3c6c8f 100644 --- a/trustgraph-flow/trustgraph/iam/noauth/handler.py +++ b/trustgraph-flow/trustgraph/iam/noauth/handler.py @@ -28,14 +28,14 @@ class NoAuthHandler: def _default_identity_response(self): return IamResponse( resolved_user_id=self.default_user_id, - resolved_workspace=self.default_workspace, + resolved_default_workspace=self.default_workspace, resolved_roles=["admin"], ) def _default_user_record(self): return UserRecord( id=self.default_user_id, - workspace=self.default_workspace, + default_workspace=self.default_workspace, username=self.default_user_id, name="Anonymous User", roles=["admin"], diff --git a/trustgraph-flow/trustgraph/iam/service/iam.py b/trustgraph-flow/trustgraph/iam/service/iam.py index 037b6bb0..e945df82 100644 --- a/trustgraph-flow/trustgraph/iam/service/iam.py +++ b/trustgraph-flow/trustgraph/iam/service/iam.py @@ -383,7 +383,7 @@ class IamService: ) = row return UserRecord( id=id or "", - workspace=workspace or "", + default_workspace=workspace or "", username=username or "", name=name or "", email=email or "", @@ -596,14 +596,8 @@ class IamService: if not v.password: return _err("auth-failed", "password required") - # Login accepts an optional workspace parameter. If omitted - # we use the default workspace (OSS single-workspace - # assumption). Multi-workspace enterprise editions swap in a - # resolver that looks across the caller's permitted set. - workspace = v.workspace or DEFAULT_WORKSPACE - user_id = await self.table_store.get_user_id_by_username( - workspace, v.username, + v.username, ) if not user_id: return _err("auth-failed", "no such user") @@ -624,7 +618,10 @@ class IamService: ): return _err("auth-failed", "bad credentials") - ws_row = await self.table_store.get_workspace(ws) + # JWT workspace: login request override, or the user's default. + jwt_workspace = v.workspace or ws + + ws_row = await self.table_store.get_workspace(jwt_workspace) if ws_row is None or not ws_row[2]: return _err("auth-failed", "workspace disabled") @@ -632,14 +629,10 @@ class IamService: now_ts = int(_now_dt().timestamp()) exp_ts = now_ts + JWT_TTL_SECONDS - # Per the IAM contract the gateway never reads policy state - # from the credential — roles stay server-side, reachable - # only via authorise(). JWT carries identity + workspace - # binding only. claims = { "iss": JWT_ISSUER, "sub": id, - "workspace": ws, + "default_workspace": jwt_workspace, "iat": now_ts, "exp": exp_ts, } @@ -878,20 +871,15 @@ class IamService: # user_row indices match get_user columns. Username is [2]. username = user_row[2] - record_workspace = user_row[1] # Revoke all API keys. key_rows = await self.table_store.list_api_keys_by_user(v.user_id) for kr in key_rows: await self.table_store.delete_api_key(kr[0]) - # Remove username lookup — keyed on (workspace, username), - # so use the resolved workspace from the user record rather - # than relying on the caller-supplied filter. + # Remove global username lookup. if username: - await self.table_store.delete_username_lookup( - record_workspace, username, - ) + await self.table_store.delete_username_lookup(username) # Remove user record. await self.table_store.delete_user(v.user_id) @@ -1110,13 +1098,15 @@ class IamService: return _err("auth-failed", "owning user disabled") # Workspace-disabled check. - ws_row = await self.table_store.get_workspace(user.workspace) + ws_row = await self.table_store.get_workspace( + user.default_workspace, + ) if ws_row is None or not ws_row[2]: return _err("auth-failed", "owning workspace disabled") return IamResponse( resolved_user_id=user.id, - resolved_workspace=user.workspace, + resolved_default_workspace=user.default_workspace, resolved_roles=list(user.roles), ) @@ -1143,9 +1133,9 @@ class IamService: if ws is None or not ws[2]: return _err("not-found", "workspace not found or disabled") - # Uniqueness on username within workspace. + # Global username uniqueness. existing = await self.table_store.get_user_id_by_username( - v.workspace, v.user.username, + v.user.username, ) if existing: return _err("duplicate", "username already exists") @@ -1317,8 +1307,9 @@ class IamService: return False, AUTHZ_CACHE_TTL_SECONDS # user_row layout: - # 0:id 1:workspace 2:username 3:name 4:email 5:password_hash - # 6:roles 7:enabled 8:must_change_password 9:created + # 0:id 1:default_workspace 2:username 3:name 4:email + # 5:password_hash 6:roles 7:enabled 8:must_change_password + # 9:created if not user_row[7]: # disabled return False, AUTHZ_CACHE_TTL_SECONDS diff --git a/trustgraph-flow/trustgraph/tables/iam.py b/trustgraph-flow/trustgraph/tables/iam.py index b60e9cff..d06f9e35 100644 --- a/trustgraph-flow/trustgraph/tables/iam.py +++ b/trustgraph-flow/trustgraph/tables/iam.py @@ -94,10 +94,8 @@ class IamTableStore: self.cassandra.execute(""" CREATE TABLE IF NOT EXISTS iam_users_by_username ( - workspace text, - username text, - user_id text, - PRIMARY KEY ((workspace), username) + username text PRIMARY KEY, + user_id text ); """) @@ -175,16 +173,16 @@ class IamTableStore: """) self.put_username_lookup_stmt = c.prepare(""" - INSERT INTO iam_users_by_username (workspace, username, user_id) - VALUES (?, ?, ?) + INSERT INTO iam_users_by_username (username, user_id) + VALUES (?, ?) """) self.get_user_id_by_username_stmt = c.prepare(""" SELECT user_id FROM iam_users_by_username - WHERE workspace = ? AND username = ? + WHERE username = ? """) self.delete_username_lookup_stmt = c.prepare(""" DELETE FROM iam_users_by_username - WHERE workspace = ? AND username = ? + WHERE username = ? """) self.delete_user_stmt = c.prepare(""" DELETE FROM iam_users WHERE id = ? @@ -289,7 +287,7 @@ class IamTableStore: ) await async_execute( self.cassandra, self.put_username_lookup_stmt, - (workspace, username, id), + (username, id), ) async def get_user(self, id): @@ -298,10 +296,10 @@ class IamTableStore: ) return rows[0] if rows else None - async def get_user_id_by_username(self, workspace, username): + async def get_user_id_by_username(self, username): rows = await async_execute( self.cassandra, self.get_user_id_by_username_stmt, - (workspace, username), + (username,), ) return rows[0][0] if rows else None @@ -324,10 +322,10 @@ class IamTableStore: self.cassandra, self.delete_user_stmt, (id,), ) - async def delete_username_lookup(self, workspace, username): + async def delete_username_lookup(self, username): await async_execute( self.cassandra, self.delete_username_lookup_stmt, - (workspace, username), + (username,), ) # ------------------------------------------------------------------