Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save HerrNiklasRaab/42215f4a5201256b9158a92995a7c8ae to your computer and use it in GitHub Desktop.

Select an option

Save HerrNiklasRaab/42215f4a5201256b9158a92995a7c8ae to your computer and use it in GitHub Desktop.
InstantDB optional-field response shape — owner can't distinguish 'never written' from 'redacted' when the field has a perm rule
// Reproduces InstantDB's response shape for optional fields under different
// permission/value combinations. Run with:
//
// npm i @instantdb/admin
// INSTANTDB_APP_ID=... INSTANTDB_ADMIN_TOKEN=... npx tsx instantdb-optional-field-shape.ts
//
// The app must have the schema + perms below pushed first:
//
// npx instant-cli@latest push schema -p admin -a $INSTANTDB_APP_ID -y
// npx instant-cli@latest push perms -p admin -a $INSTANTDB_APP_ID -y
//
// Expected: case #2 fails. The owner is allowed to read `secretField`, but
// because it has a field-level perm rule AND was never written, InstantDB
// omits the key entirely — indistinguishable from "you can't see it."
import { i, init, id } from "@instantdb/admin";
const schema = i.schema({
entities: {
users: i.entity({
name: i.string(),
secretField: i.string().optional(),
testDate: i.date().indexed().optional(),
status: i.string<"active" | "inactive" | "pending">().indexed().optional(),
role: i.string<"admin" | "member" | "guest">().indexed().optional(),
createdAt: i.date().indexed(),
updatedAt: i.date().indexed(),
deletedAt: i.date().indexed().optional(),
}),
},
});
// Push these via `instant-cli push perms` to the same app.
export const perms = {
users: {
bind: ["isOwner", "auth.id != null && auth.id == data.id"],
allow: {
view: "true",
create: "true",
update: "isOwner",
delete: "true",
},
fields: {
secretField: "isOwner",
},
},
};
const appId = process.env.INSTANTDB_APP_ID;
const adminToken = process.env.INSTANTDB_ADMIN_TOKEN;
if (!appId || !adminToken) {
throw new Error("Set INSTANTDB_APP_ID and INSTANTDB_ADMIN_TOKEN");
}
const adminDb = init({ appId, adminToken, schema });
type UsersSchema = typeof schema;
function userTx(userId: string) {
const chunk = adminDb.tx.users[userId];
if (!chunk) throw new Error(`no tx chunk for users[${userId}]`);
return chunk;
}
async function seedAuthUser(email: string): Promise<string> {
await adminDb.auth.createToken({ email });
const user = await adminDb.auth.getUser({ email });
if (!user) throw new Error(`no auth user for ${email}`);
return user.id;
}
type Row = Record<string, unknown>;
function assert(name: string, cond: boolean, detail?: unknown): void {
if (cond) {
console.log(` ✓ ${name}`);
} else {
console.log(` ✗ ${name}`, detail ?? "");
process.exitCode = 1;
}
}
async function case1_noPermNeverWritten(): Promise<void> {
console.log("\n#1 no field-perm rule, never written");
const userId = id();
const now = new Date().toISOString();
await adminDb.transact([
userTx(userId).update({ name: "Alice", createdAt: now, updatedAt: now }),
]);
const resp = await adminDb.query({
users: { $: { where: { id: userId } } },
});
const row = resp.users[0] as Row | undefined;
assert("row exists", !!row);
if (!row) return;
assert("'testDate' in row", "testDate" in row);
assert("row.testDate === null", row.testDate === null, row.testDate);
}
async function case2_permRuleOwnerNeverWritten(): Promise<void> {
console.log("\n#2 field-perm rule, owner, never written (THIS IS THE BUG)");
const email = `owner-${id()}@example.com`;
const ownerId = await seedAuthUser(email);
const now = new Date().toISOString();
await adminDb.transact([
userTx(ownerId).update({ name: "Owner", createdAt: now, updatedAt: now }),
]);
const resp = await adminDb.asUser({ email }).query({
users: { $: { where: { id: ownerId } } },
});
const row = resp.users[0] as Row | undefined;
assert("row exists", !!row);
if (!row) return;
assert("'secretField' in row (expect TRUE; will FAIL — key is absent)", "secretField" in row);
assert("row.secretField === null", row.secretField === null, row.secretField);
}
async function case3_permRuleNotAllowedSetValue(): Promise<void> {
console.log("\n#3 field-perm rule, non-owner, value written");
const ownerEmail = `owner-${id()}@example.com`;
const viewerEmail = `viewer-${id()}@example.com`;
const ownerId = await seedAuthUser(ownerEmail);
await seedAuthUser(viewerEmail);
const now = new Date().toISOString();
await adminDb.transact([
userTx(ownerId).update({
name: "Owner",
secretField: "top-secret",
createdAt: now,
updatedAt: now,
}),
]);
const resp = await adminDb.asUser({ email: viewerEmail }).query({
users: { $: { where: { id: ownerId } } },
});
const row = resp.users[0] as Row | undefined;
assert("row exists", !!row);
if (!row) return;
assert("'secretField' in row === false", !("secretField" in row));
}
async function case4_permRuleOwnerExplicitNull(): Promise<void> {
console.log("\n#4 field-perm rule, owner, explicitly written null");
const email = `owner-${id()}@example.com`;
const ownerId = await seedAuthUser(email);
const now = new Date().toISOString();
await adminDb.transact([
userTx(ownerId).update({
name: "Owner",
secretField: null,
createdAt: now,
updatedAt: now,
}),
]);
const resp = await adminDb.asUser({ email }).query({
users: { $: { where: { id: ownerId } } },
});
const row = resp.users[0] as Row | undefined;
assert("row exists", !!row);
if (!row) return;
assert("'secretField' in row", "secretField" in row);
assert("row.secretField === null", row.secretField === null, row.secretField);
}
async function case5_permRuleOwnerSetValue(): Promise<void> {
console.log("\n#5 field-perm rule, owner, value written (control)");
const email = `owner-${id()}@example.com`;
const ownerId = await seedAuthUser(email);
const now = new Date().toISOString();
await adminDb.transact([
userTx(ownerId).update({
name: "Owner",
secretField: "top-secret",
createdAt: now,
updatedAt: now,
}),
]);
const resp = await adminDb.asUser({ email }).query({
users: { $: { where: { id: ownerId } } },
});
const row = resp.users[0] as Row | undefined;
assert("row exists", !!row);
if (!row) return;
assert("'secretField' in row", "secretField" in row);
assert("row.secretField === 'top-secret'", row.secretField === "top-secret", row.secretField);
}
await case1_noPermNeverWritten();
await case2_permRuleOwnerNeverWritten();
await case3_permRuleNotAllowedSetValue();
await case4_permRuleOwnerExplicitNull();
await case5_permRuleOwnerSetValue();
void schema as UsersSchema;
void perms;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment