anbaric 1.32.0 → 1.33.0
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/docs/README.md
CHANGED
|
@@ -26,6 +26,7 @@ What the framework gives you, one capability at a time.
|
|
|
26
26
|
- [Documents and secrets](features/documents-and-secrets.md) — the JSON and secret stores
|
|
27
27
|
- [The SQL store](features/sql-store.md) — a relational database for structured data
|
|
28
28
|
- [Auditing](features/auditing.md) — the record of who changed what
|
|
29
|
+
- [Entitlements](features/entitlements.md) — what a user has been granted, checked per request
|
|
29
30
|
- [Serving a web UI](features/serving-a-web-ui.md) — putting your data on a page
|
|
30
31
|
- [The admin console and widgets](features/admin-console-and-widgets.md) — dashboards and plugins
|
|
31
32
|
- [Deploying](features/deploying.md) — from laptop to Anbaric Cloud
|
package/docs/api/environment.md
CHANGED
|
@@ -35,6 +35,7 @@ defaulting to a local implementation otherwise).
|
|
|
35
35
|
| `ANBARIC_SECRET_STORE_TYPE` | the secret store | in-memory (encrypted) |
|
|
36
36
|
| `ANBARIC_SQL_STORE_TYPE` | the SQL store (`sqlite` / `cloud`\|`postgres`) | SQLite |
|
|
37
37
|
| `ANBARIC_SESSION_RESOLVER_TYPE` | how `Human.fromSession` resolves sessions | in-memory |
|
|
38
|
+
| `ANBARIC_ENTITLEMENTS_TYPE` | how `hasEntitlement` is answered (`cloud` → platform) | permissive (always `true`) |
|
|
38
39
|
|
|
39
40
|
## Store configuration
|
|
40
41
|
|
package/docs/api/typescript.md
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Job, PropertyDefinition,
|
|
11
11
|
Code, Human, SystemActor, Agent, OpenAIAgent, RemoteLLMAgenticAction,
|
|
12
12
|
JsonStoreFactory, SecretStoreFactory, SqlStoreFactory,
|
|
13
|
+
registerEntitlement, hasEntitlement,
|
|
13
14
|
} from "anbaric";
|
|
14
15
|
|
|
15
16
|
import type {Actor, ActorType, JsonSchema} from "anbaric";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Entitlements
|
|
2
|
+
|
|
3
|
+
Roles say what kind of user someone is. **Entitlements** say what a particular
|
|
4
|
+
user has been given: access to a beta feature, an export capability, a paid
|
|
5
|
+
tier. Your app declares the entitlements it cares about and asks whether the
|
|
6
|
+
signed-in user holds one; administrators hand them out from the console.
|
|
7
|
+
|
|
8
|
+
## Declare what you check
|
|
9
|
+
|
|
10
|
+
Register each entitlement once, at startup. That makes it visible in the
|
|
11
|
+
console so it can be granted, and gives it a note explaining what it unlocks.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {registerEntitlement} from "anbaric";
|
|
15
|
+
|
|
16
|
+
await registerEntitlement("export", "Can export reports as CSV");
|
|
17
|
+
await registerEntitlement("beta", "Sees features still in preview");
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Registration is idempotent and scoped to your app - `export` in your app is a
|
|
21
|
+
different entitlement from `export` in another.
|
|
22
|
+
|
|
23
|
+
## Check the signed-in user
|
|
24
|
+
|
|
25
|
+
`hasEntitlement` takes the incoming request (it reads the `anbaric_session`
|
|
26
|
+
cookie, exactly as `Human.fromSession` does) or a raw session token, resolves
|
|
27
|
+
the user, and answers for **your app**:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import {hasEntitlement} from "anbaric";
|
|
31
|
+
|
|
32
|
+
server.on("request", async (request, response) => {
|
|
33
|
+
if (request.url === "/export") {
|
|
34
|
+
if (! await hasEntitlement(request, "export")) {
|
|
35
|
+
response.writeHead(403).end("You don't have the export entitlement");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
...
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
A request with no session, or one the platform can't resolve, is simply not
|
|
44
|
+
entitled - `hasEntitlement` returns `false` rather than throwing.
|
|
45
|
+
|
|
46
|
+
## Global entitlements
|
|
47
|
+
|
|
48
|
+
Some entitlements aren't about one app. A **global entitlement** belongs to no
|
|
49
|
+
app and is created in the console, never by `registerEntitlement`. Every tenant
|
|
50
|
+
starts with one, `access`.
|
|
51
|
+
|
|
52
|
+
A grant can be scoped too. Granting a user `access` **for your app** and
|
|
53
|
+
granting them `access` **globally** both make `hasEntitlement(request, "access")`
|
|
54
|
+
return `true` in your app; only the global grant also satisfies every other app.
|
|
55
|
+
|
|
56
|
+
| Definition | Grant | `hasEntitlement(request, "access")` in `my-app` |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| global `access` | `my-app` / `access` | true |
|
|
59
|
+
| global `access` | global / `access` | true (and in every other app) |
|
|
60
|
+
| global `access` | `other-app` / `access` | false |
|
|
61
|
+
|
|
62
|
+
## Granting
|
|
63
|
+
|
|
64
|
+
Apps can't grant. An administrator opens **Entitlements** in the console, picks
|
|
65
|
+
the user, the entitlement and its scope, and adds a note; the same page revokes
|
|
66
|
+
grants and creates global entitlements. Every grant records who made it.
|
|
67
|
+
|
|
68
|
+
## Locally
|
|
69
|
+
|
|
70
|
+
There's no console on your laptop, so the local implementation is
|
|
71
|
+
**permissive**: `hasEntitlement` always returns `true`. Your code paths run
|
|
72
|
+
unchanged; deployed, the platform answers for real. As with every other
|
|
73
|
+
service, the switch is the environment (`ANBARIC_ENTITLEMENTS_TYPE`), set by the
|
|
74
|
+
platform - don't set it yourself.
|
|
75
|
+
|
|
76
|
+
## Next
|
|
77
|
+
|
|
78
|
+
- [Authorization with actors and roles](../patterns/authorization.md) - roles, predicates and attribution
|
|
79
|
+
- [Environment and factories](../api/environment.md)
|
|
@@ -53,6 +53,27 @@ if (!user.roles.includes("manager")) {
|
|
|
53
53
|
await machine.updateJob(jobId, new Map([["approved", true]]), user);
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
## Grant capabilities with entitlements
|
|
57
|
+
|
|
58
|
+
Roles describe a kind of user; an **entitlement** is something a specific user
|
|
59
|
+
has been given - a beta feature, an export capability, a paid tier. Declare the
|
|
60
|
+
ones your app checks at startup and test them against the request, the same way
|
|
61
|
+
you resolve the actor:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import {hasEntitlement, registerEntitlement} from "anbaric";
|
|
65
|
+
|
|
66
|
+
await registerEntitlement("export", "Can export reports as CSV"); // at startup
|
|
67
|
+
|
|
68
|
+
if (! await hasEntitlement(request, "export")) { // per request
|
|
69
|
+
response.writeHead(403).end("You don't have the export entitlement");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Administrators grant and revoke them from the console's **Entitlements** page;
|
|
75
|
+
locally every check passes. See [Entitlements](../features/entitlements.md).
|
|
76
|
+
|
|
56
77
|
## Design tips
|
|
57
78
|
|
|
58
79
|
- **Least privilege.** Give actors the narrowest roles that let them do their job;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anbaric",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.0",
|
|
4
4
|
"description": "Everything needed to write an Anbaric app: state machines, jobs, document and secret stores, local in-memory implementations and the Anbaric Cloud clients",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"anbaric-impl-cloud": "^1.
|
|
28
|
-
"anbaric-data-store": "^1.
|
|
29
|
-
"anbaric-state-machine": "^1.
|
|
30
|
-
"anbaric-tsapi": "^1.
|
|
27
|
+
"anbaric-impl-cloud": "^1.33.0",
|
|
28
|
+
"anbaric-data-store": "^1.33.0",
|
|
29
|
+
"anbaric-state-machine": "^1.33.0",
|
|
30
|
+
"anbaric-tsapi": "^1.33.0"
|
|
31
31
|
}
|
|
32
32
|
}
|