From e12f8363d083d8e3c84611a463b9323f144f827b Mon Sep 17 00:00:00 2001 From: Joe Averbukh Date: Fri, 20 Mar 2026 16:42:15 -0700 Subject: [PATCH 1/4] [CLI] Add test for --as-token --- .../cli/__tests__/e2e/cli.e2e.test.ts | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/client/packages/cli/__tests__/e2e/cli.e2e.test.ts b/client/packages/cli/__tests__/e2e/cli.e2e.test.ts index 5bb31d2f53..7e96a7d320 100644 --- a/client/packages/cli/__tests__/e2e/cli.e2e.test.ts +++ b/client/packages/cli/__tests__/e2e/cli.e2e.test.ts @@ -1010,6 +1010,89 @@ export default _schema; } }); + it('--as-token runs query as a user identified by refresh token', async () => { + const { appId, adminToken } = await createTempApp(); + + const schemaWithCreator = ` +import { i } from "@instantdb/core"; +const _schema = i.schema({ + entities: { + posts: i.entity({ + title: i.string(), + creatorEmail: i.string(), + }), + }, +}); +export default _schema; +`; + + const restrictedPerms = `export default { + posts: { + allow: { + view: "auth.email == data.creatorEmail", + }, + }, +}; +`; + + const project = await createTestProject({ + appId, + schemaFile: schemaWithCreator, + permsFile: restrictedPerms, + }); + + try { + const pushResult = await runCli(['push', '--yes'], { + cwd: project.dir, + env: { + INSTANT_CLI_AUTH_TOKEN: adminToken, + INSTANT_APP_ID: appId, + }, + }); + expect(pushResult.exitCode).toBe(0); + + const alice = await createAppUser(appId, adminToken, 'alice@test.com'); + + await adminTransact(appId, adminToken, [ + [ + 'update', + 'posts', + randomUUID(), + { title: "Alice's Post", creatorEmail: 'alice@test.com' }, + ], + [ + 'update', + 'posts', + randomUUID(), + { title: "Bob's Post", creatorEmail: 'bob@test.com' }, + ], + ]); + + // Using Alice's refresh token should only show Alice's post + const result = await runCli( + [ + 'query', + '--as-token', + alice.refreshToken, + JSON.stringify({ posts: {} }), + ], + { + cwd: project.dir, + env: { + INSTANT_CLI_AUTH_TOKEN: adminToken, + INSTANT_APP_ID: appId, + }, + }, + ); + expect(result.exitCode).toBe(0); + const data = JSON.parse(result.stdout); + expect(data.posts).toHaveLength(1); + expect(data.posts[0].title).toBe("Alice's Post"); + } finally { + await project.cleanup(); + } + }); + it('--as-guest runs query as unauthenticated user', async () => { const { appId, adminToken } = await createTempApp(); From f51a55060d49250ee75054325b608f388878cec1 Mon Sep 17 00:00:00 2001 From: Joe Averbukh Date: Fri, 20 Mar 2026 16:43:57 -0700 Subject: [PATCH 2/4] [CLI] Implement --as-token --- client/packages/cli/src/index.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/client/packages/cli/src/index.js b/client/packages/cli/src/index.js index 8031fc40a4..4ca240485c 100644 --- a/client/packages/cli/src/index.js +++ b/client/packages/cli/src/index.js @@ -596,6 +596,10 @@ program .option('--admin', 'Run the query as admin (bypasses permissions)') .option('--as-email ', 'Run the query as a specific user by email') .option('--as-guest', 'Run the query as an unauthenticated guest') + .option( + '--as-token ', + 'Run the query as a user identified by refresh token', + ) .description('Run an InstaQL query against your app.') .action(async function (queryArg, opts) { await handleQuery(queryArg, opts); @@ -716,11 +720,15 @@ async function detectAppIdQuietly(opts) { } async function handleQuery(queryArg, opts) { - const contextCount = - (opts.admin ? 1 : 0) + (opts.asEmail ? 1 : 0) + (opts.asGuest ? 1 : 0); - if (contextCount > 1) { + const contexts = [ + opts.admin, + opts.asEmail, + opts.asGuest, + opts.asToken, + ].filter(Boolean); + if (contexts.length > 1) { error( - 'Please specify exactly one context: --admin, --as-email , or --as-guest', + 'Please specify exactly one context: --admin, --as-email , --as-guest, or --as-token ', ); return process.exit(1); } @@ -747,6 +755,8 @@ async function handleQuery(queryArg, opts) { headers['as-email'] = opts.asEmail; } else if (opts.asGuest) { headers['as-guest'] = 'true'; + } else if (opts.asToken) { + headers['as-token'] = opts.asToken; } const res = await fetchJson({ From 7651ffaa43d0f0709abd4553a54f3f81de2ce4af Mon Sep 17 00:00:00 2001 From: Joe Averbukh Date: Fri, 20 Mar 2026 16:44:10 -0700 Subject: [PATCH 3/4] Update docs for --as-token --- client/www/pages/docs/cli.md | 1 + 1 file changed, 1 insertion(+) diff --git a/client/www/pages/docs/cli.md b/client/www/pages/docs/cli.md index 706cd04019..2f76998d50 100644 --- a/client/www/pages/docs/cli.md +++ b/client/www/pages/docs/cli.md @@ -113,6 +113,7 @@ Each query requires an auth context flag: - `--admin` bypasses permissions (default) - `--as-email ` runs the query as a specific user with permissions applied - `--as-guest` runs the query as an unauthenticated guest +- `--as-token ` runs the query as a user identified by their refresh token For example, to see what a specific user can access: From 07b6088fdc86556bc15526674d741c8c1213230a Mon Sep 17 00:00:00 2001 From: Joe Averbukh Date: Fri, 20 Mar 2026 16:44:21 -0700 Subject: [PATCH 4/4] Bump v0.22.169 --- client/packages/version/src/version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/packages/version/src/version.ts b/client/packages/version/src/version.ts index 402a2289bf..e1fe34cf81 100644 --- a/client/packages/version/src/version.ts +++ b/client/packages/version/src/version.ts @@ -2,6 +2,6 @@ // Update the version here and merge your code to main to // publish a new version of all of the packages to npm. -const version = 'v0.22.168'; +const version = 'v0.22.169'; export { version };