@rowstile/pg 0.1.0-alpha.4 → 0.1.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -68
- package/dist/index.d.ts +1 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,83 +1,64 @@
|
|
|
1
|
-
# rowstile
|
|
1
|
+
# @rowstile/pg
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
row-level security. It holds no rowstile logic: it calls the `authz.*` functions the policy made, and
|
|
7
|
-
translates their answers.
|
|
3
|
+
[rowstile](https://rowstile.dev) is a policy language for PostgreSQL. You write who may see and change each
|
|
4
|
+
row in one file, and it becomes row-level security: the database filters every read and refuses every write
|
|
5
|
+
the rules don't allow, whatever the code asks.
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
This package is rowstile for node-postgres (`pg`). `authz(pool)` gives transactions that sign in as whoever
|
|
8
|
+
the code acts for, the runtime's calls (`can`, `permsOf`, `list`, `share`), and `expect`, which turns an UPDATE
|
|
9
|
+
or DELETE that changed nothing into `NotFound` or `Refused` with the reason. `changes(pool)` is the feed for
|
|
10
|
+
live updates.
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
# @next on each while only an alpha is published: a plain install gets an older one
|
|
14
|
+
npm install @rowstile/client@next @rowstile/pg@next
|
|
15
|
+
npm install --save-dev rowstile@next # the command: rowstile dev, rowstile migrate
|
|
16
|
+
```
|
|
10
17
|
|
|
11
18
|
```ts
|
|
12
|
-
|
|
13
|
-
import { PrismaPg } from "@prisma/adapter-pg";
|
|
14
|
-
import { authz, signedIn } from "@rowstile/prisma";
|
|
15
|
-
import "@rowstile/next"; // signed-in reads never land in a cache
|
|
16
|
-
import "./authz.gen"; // the policy's names (rowstile client)
|
|
19
|
+
import { authz as rowstile } from "@rowstile/pg";
|
|
17
20
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
const p = new pg.Pool({ connectionString: APP });
|
|
22
|
+
const a = rowstile(p);
|
|
23
|
+
expect(await a.transaction(async (c) => (await c.query("SELECT id FROM app.notes ORDER BY id")).rows.map((r) => r.id), "2")).toEqual([2, 3]);
|
|
21
24
|
```
|
|
22
25
|
|
|
26
|
+
`transaction(fn, who)` signs in as `who`, or as whoever `actingAs` set. An UPDATE the rules refuse changes 0
|
|
27
|
+
rows, without an error; `expect` asks the database why:
|
|
28
|
+
|
|
23
29
|
```ts
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
30
|
+
await expect(actingAs("3", () => a.transaction(async (c) => {
|
|
31
|
+
const r = await c.query("UPDATE app.notes SET body = 'x' WHERE id = 1");
|
|
32
|
+
return a.expect(r, "app.notes", "update", 1);
|
|
33
|
+
}))).rejects.toBeRefused("update");
|
|
29
34
|
```
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| `@rowstile/react` | `<AuthzProvider>`, `usePerms(type, ids)` (one call for a list), `<Can>`, `useShares` and a headless `<ShareDialog>`, `useAccessRequest`; kept current over server-sent events |
|
|
40
|
-
| `@rowstile/vitest` | `expect.extend(matchers)`: `toBeRefused(command, reason)`, `toBeNotFound()`; `asUser(who, fn)`; `databasePerWorker(url)`. Vitest 4 or 5 |
|
|
36
|
+
## Good to know
|
|
37
|
+
|
|
38
|
+
- Every transaction signs in, even as nobody, and nothing uses a session-level `SET`: pools and poolers in
|
|
39
|
+
transaction mode are safe.
|
|
40
|
+
- `await a.check()` at start-up throws `ConnectionProblem` on a connection that skips row-level security (a
|
|
41
|
+
superuser, `BYPASSRLS`, the tables' owner).
|
|
42
|
+
- `changes(pool)` needs a connection of its own, straight to Postgres: `LISTEN` hears nothing through a
|
|
43
|
+
pooler in transaction mode.
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
or `null` (nobody: only what `anyone` may see). A plain id is always a user's, whatever it holds: `"service:3"`
|
|
44
|
-
is the user with that id, so an id from outside (a username, an identity provider's subject) can't name a
|
|
45
|
-
service. `parsePrincipal("service:3")` reads back what `describe()` wrote, for text your own code wrote. Whoever the code acts for (`actingAs`, a `job`)
|
|
46
|
-
comes first, else the `user` function, else nobody; a `who` given to one call (`transaction(fn, who)`) comes
|
|
47
|
-
before all, and `null` there is nobody. Every transaction signs in, even as nobody, and nothing uses a
|
|
48
|
-
session-level `SET`, so pools and poolers in transaction mode are safe; the change feed needs its own connection
|
|
49
|
-
to Postgres ([Behind a pooler](../../docs/operations.md#behind-a-pooler)).
|
|
45
|
+
## More
|
|
50
46
|
|
|
51
|
-
|
|
47
|
+
- [Node apps: pg, postgres.js and Drizzle](https://rowstile.dev/stacks/node): the whole setup, step by step, from an app the tests run.
|
|
48
|
+
- [The TypeScript SDK](https://rowstile.dev/sdk/typescript): every package, and the traps they handle.
|
|
49
|
+
- [Getting started](https://rowstile.dev/getting-started): a first policy, step by step.
|
|
50
|
+
- [Source and issues](https://github.com/rowstile/rowstile).
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
the render is dynamic, and a signed-in read inside `"use cache"` or `unstable_cache` fails instead of being
|
|
55
|
-
cached for the next user. Reads as nobody may be cached.
|
|
56
|
-
- **Prisma answers some calls together.** It dispatches the `findUnique` calls of one tick, and array
|
|
57
|
-
transactions, from the first caller's context. `authz()` runs each `findUnique` in a transaction of its own,
|
|
58
|
-
and refuses `$transaction([...])` (use `$transaction(async (tx) => ...)`), so no query runs as someone else.
|
|
59
|
-
Use the client `$extends(authz())` returns, and only that one: the client it was made from is refused
|
|
60
|
-
whenever it is used, inside your own extensions too. An extension you add to the client `authz()` returns is
|
|
61
|
-
placed before it, so `authz()` stays the last one: each of your query hooks runs once for a call, and sees
|
|
62
|
-
the errors `authz()` makes (`Refused`, `NotFound`).
|
|
63
|
-
- **Inserts that read the row back** (`RETURNING`, Prisma's `create`) also need the select rule: Postgres's own
|
|
64
|
-
message comes back as `Refused` naming the select rule.
|
|
65
|
-
- **Prisma's promises are lazy**: `actingAs` and `job` await what they return inside their scope.
|
|
66
|
-
- **A connection that drops.** node-postgres stops the process when a client that is checked out loses its
|
|
67
|
-
connection and nobody listens: `@rowstile/pg` listens while it holds one, so the transaction fails instead.
|
|
68
|
-
`changes(pool)` listens again after its connection drops, and tells its subscribers once.
|
|
69
|
-
- **An update that changed nothing** is `NotFound` when the user can't see the row, `Refused` when the database
|
|
70
|
-
says no, and `NotFound` again when the rule allows it and the statement matched nothing for another reason
|
|
71
|
-
(a `where` with more than the key).
|
|
72
|
-
- **The answers in the browser are one user's.** Give `<AuthzProvider user={id}>` if the user can change
|
|
73
|
-
without a page load: the hooks forget what they had and ask again.
|
|
52
|
+
The other packages:
|
|
74
53
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
54
|
+
| package | for |
|
|
55
|
+
|---|---|
|
|
56
|
+
| [`@rowstile/client`](https://www.npmjs.com/package/@rowstile/client) | who a transaction acts for, the errors, the runtime's calls |
|
|
57
|
+
| [`@rowstile/prisma`](https://www.npmjs.com/package/@rowstile/prisma) | Prisma |
|
|
58
|
+
| [`@rowstile/postgres`](https://www.npmjs.com/package/@rowstile/postgres) | postgres.js |
|
|
59
|
+
| [`@rowstile/drizzle`](https://www.npmjs.com/package/@rowstile/drizzle) | Drizzle ORM |
|
|
60
|
+
| [`@rowstile/next`](https://www.npmjs.com/package/@rowstile/next) | Next.js: route handlers, server actions, caches |
|
|
61
|
+
| [`@rowstile/react`](https://www.npmjs.com/package/@rowstile/react) | React: hooks and a share dialog |
|
|
62
|
+
| [`@rowstile/vitest`](https://www.npmjs.com/package/@rowstile/vitest) | Vitest: matchers, a database per worker |
|
|
79
63
|
|
|
80
|
-
|
|
81
|
-
`integrations/nextjs/` is the conformance suite: a small Next.js app on Prisma 7 and Prisma Migrate, and the
|
|
82
|
-
checks every supported stack passes, with pg, postgres.js, Drizzle and the React kit
|
|
83
|
-
(`integrations/nextjs/test.sh`).
|
|
64
|
+
rowstile is a 0.x preview: names may still change before 1.0. Apache-2.0.
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export declare function authz(pool: Pool, options?: Options): {
|
|
|
33
33
|
problem: string;
|
|
34
34
|
}[]>;
|
|
35
35
|
expect<R>(result: R, table: string, command: "update" | "delete", id: import("@rowstile/client").Id, row?: object): Promise<R>;
|
|
36
|
+
verdict(table: string, command: string, id: import("@rowstile/client").Id, row?: object): Promise<import("@rowstile/client").NotFound | import("@rowstile/client").Refused>;
|
|
36
37
|
check(): Promise<void>;
|
|
37
38
|
pool: Pool;
|
|
38
39
|
/** Runs fn in a transaction signed in as whoever the code acts for (or `who`, if given). */
|
package/package.json
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rowstile/pg",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.6",
|
|
4
4
|
"description": "rowstile with node-postgres (pg): transactions signed in as the request's user",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://rowstile.dev",
|
|
7
|
+
"keywords": ["rowstile", "postgres", "postgresql", "authorization", "row-level-security", "rls", "permissions", "access-control", "rbac", "rebac", "multi-tenant", "pg", "node-postgres"],
|
|
6
8
|
"repository": { "type": "git", "url": "git+https://github.com/rowstile/rowstile.git", "directory": "sdk/typescript/pg" },
|
|
7
9
|
"type": "module",
|
|
8
10
|
"main": "./dist/index.js",
|
|
@@ -10,6 +12,6 @@
|
|
|
10
12
|
"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
|
|
11
13
|
"files": ["dist"],
|
|
12
14
|
"sideEffects": false,
|
|
13
|
-
"dependencies": { "@rowstile/client": "0.1.0-alpha.
|
|
15
|
+
"dependencies": { "@rowstile/client": "0.1.0-alpha.6" },
|
|
14
16
|
"peerDependencies": { "pg": ">=8" }
|
|
15
17
|
}
|