@foir/sdk 0.19.0 → 0.20.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/dist/react.js CHANGED
@@ -560,7 +560,7 @@ function toVariableValue(value, label) {
560
560
  return value.toISOString();
561
561
  }
562
562
  if (Array.isArray(value)) {
563
- return value.filter((item) => item !== void 0).map((item, index) => toVariableValue(item, `${label}[${index}]`));
563
+ return value.filter((item) => item !== void 0).map((item, index2) => toVariableValue(item, `${label}[${index2}]`));
564
564
  }
565
565
  if (kind === "object") {
566
566
  const out = {};
@@ -915,6 +915,9 @@ var AUTH_OPS = {
915
915
  // Post-credential self reads.
916
916
  currentUser: "currentUser",
917
917
  myProviderToken: "myProviderToken",
918
+ // What the calling credential may do, and for a signed-in customer the roles
919
+ // they hold. `./access` reads it.
920
+ whoami: "whoami",
918
921
  // Methods.
919
922
  customerLogin: "customerLogin",
920
923
  customerRegister: "customerRegister",
@@ -1078,6 +1081,280 @@ async function refreshSession(adapter, env) {
1078
1081
  return null;
1079
1082
  }
1080
1083
  }
1084
+ function isAuthExpiredError(err) {
1085
+ if (!(err instanceof FoirRequestError)) return false;
1086
+ if (err.status === 401) return true;
1087
+ return (err.graphQLErrors ?? []).some(
1088
+ (e) => /authentication required/i.test(e.message)
1089
+ );
1090
+ }
1091
+ var refreshing = /* @__PURE__ */ new WeakMap();
1092
+ function refreshSessionOnce(adapter, env) {
1093
+ const running = refreshing.get(adapter);
1094
+ if (running) return running;
1095
+ const next = refreshSession(adapter, env).finally(() => refreshing.delete(adapter));
1096
+ refreshing.set(adapter, next);
1097
+ return next;
1098
+ }
1099
+ function sessionLapse(err) {
1100
+ if (err instanceof FoirAuthDowngradedError) {
1101
+ switch (err.downgrade.reason) {
1102
+ case "expired":
1103
+ return "refresh";
1104
+ case "invalid":
1105
+ return "expired";
1106
+ default:
1107
+ return null;
1108
+ }
1109
+ }
1110
+ return isAuthExpiredError(err) ? "refresh" : null;
1111
+ }
1112
+ async function withSessionRefresh(adapter, env, run) {
1113
+ const token = await getSessionToken(adapter);
1114
+ try {
1115
+ return await run(token ?? void 0);
1116
+ } catch (err) {
1117
+ const lapse = sessionLapse(err);
1118
+ if (!lapse) throw err;
1119
+ if (lapse === "expired") throw new FoirSessionExpiredError({ cause: err });
1120
+ const refreshed = await refreshSessionOnce(adapter, env);
1121
+ if (!refreshed) {
1122
+ if (err instanceof FoirAuthDowngradedError) throw new FoirSessionExpiredError({ cause: err });
1123
+ throw err;
1124
+ }
1125
+ try {
1126
+ return await run(refreshed.token);
1127
+ } catch (again) {
1128
+ if (again instanceof FoirAuthDowngradedError) throw new FoirSessionExpiredError({ cause: again });
1129
+ throw again;
1130
+ }
1131
+ }
1132
+ }
1133
+ async function authedOp(adapter, env, op, args) {
1134
+ return withSessionRefresh(
1135
+ adapter,
1136
+ env,
1137
+ (bearerToken) => authOp(env, op, args, { bearerToken })
1138
+ );
1139
+ }
1140
+
1141
+ // src/auth/grant-table.generated.ts
1142
+ var GRANT_TABLE = [
1143
+ ["records:read", true, ["own", "named", "all"], []],
1144
+ ["records:create", true, [], []],
1145
+ ["records:write", true, ["own", "named", "all"], []],
1146
+ ["records:delete", true, ["own", "named", "all"], []],
1147
+ ["records:publish", true, ["own", "all"], []],
1148
+ ["records:attribute", true, [], []],
1149
+ ["search:read", true, [], []],
1150
+ ["embeddings:read", false, [], []],
1151
+ ["embeddings:write", false, [], []],
1152
+ ["schemas:read", false, [], []],
1153
+ ["drafts:read", false, [], []],
1154
+ ["files:read", false, ["own", "all"], []],
1155
+ ["files:write", false, ["own", "all"], []],
1156
+ ["files:delete", false, ["own", "all"], []],
1157
+ ["notifications:read", false, ["own", "all"], []],
1158
+ ["notifications:write", false, ["own", "named", "all"], []],
1159
+ ["notifications:consent", false, [], []],
1160
+ ["pushcredentials:read", false, [], []],
1161
+ ["pushcredentials:write", false, [], []],
1162
+ ["notes:read", false, [], []],
1163
+ ["notes:write", false, [], []],
1164
+ ["notes:create", false, [], []],
1165
+ ["approvals:read", false, [], []],
1166
+ ["approvals:write", false, [], []],
1167
+ ["rooms:read", true, [], []],
1168
+ ["rooms:write", true, [], []],
1169
+ ["presence:read", true, [], []],
1170
+ ["operations:read", false, [], []],
1171
+ ["operations:execute", true, [], []],
1172
+ ["operations:write", false, [], []],
1173
+ ["operations:complete", false, [], []],
1174
+ ["status:write", false, [], []],
1175
+ ["appconfig:read", false, [], []],
1176
+ ["schedules:read", false, [], []],
1177
+ ["schedules:write", false, [], []],
1178
+ ["hooks:read", false, [], []],
1179
+ ["hooks:write", false, [], []],
1180
+ ["relyingparties:read", false, [], []],
1181
+ ["relyingparties:write", false, [], []],
1182
+ ["domains:read", false, [], []],
1183
+ ["domains:write", false, [], []],
1184
+ ["secrets:read", false, [], []],
1185
+ ["secrets:list", false, [], []],
1186
+ ["secrets:put", false, [], []],
1187
+ ["secrets:delete", false, [], []],
1188
+ ["appsecrets:read", false, [], []],
1189
+ ["appsecrets:list", false, [], []],
1190
+ ["appsecrets:put", false, [], []],
1191
+ ["appsecrets:delete", false, [], []],
1192
+ ["opsecrets:read", false, [], []],
1193
+ ["opsecrets:write", false, [], []],
1194
+ ["settings:read", false, [], []],
1195
+ ["settings:write", false, [], []],
1196
+ ["designtokens:read", false, [], []],
1197
+ ["designtokens:write", false, [], []],
1198
+ ["models:read", false, [], []],
1199
+ ["models:write", false, [], []],
1200
+ ["configs:read", false, [], []],
1201
+ ["configs:write", false, [], []],
1202
+ ["apps:read", false, [], []],
1203
+ ["apps:write", false, [], []],
1204
+ ["events:read", false, [], []],
1205
+ ["targeting:debug", false, [], []],
1206
+ ["visitors:forward", false, [], []],
1207
+ ["apikeys:read", false, [], []],
1208
+ ["apikeys:write", false, [], []],
1209
+ ["organizations:read", false, [], []],
1210
+ ["organizations:write", false, [], []],
1211
+ ["organizations:create", false, [], []],
1212
+ ["organizations:delete", false, [], []],
1213
+ ["organizations:reach", false, [], []],
1214
+ ["members:read", false, [], []],
1215
+ ["members:write", false, [], []],
1216
+ ["roles:read", false, [], []],
1217
+ ["roles:write", false, [], []],
1218
+ ["projects:read", false, [], []],
1219
+ ["projects:write", false, [], []],
1220
+ ["project:manage", false, [], ["records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read", "notes:write", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "relyingparties:read", "relyingparties:write", "domains:read", "domains:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write"]],
1221
+ ["projects:create", false, [], []],
1222
+ ["projects:delete", false, [], []],
1223
+ ["tenants:read", false, [], []],
1224
+ ["tenants:write", false, [], []],
1225
+ ["tenants:delete", false, [], []],
1226
+ ["audit:read", false, [], []],
1227
+ ["export:run", false, [], []],
1228
+ ["billing:read", false, [], []],
1229
+ ["billing:manage", false, [], []],
1230
+ ["usage:read", false, [], []],
1231
+ ["controlkeys:read", false, [], []],
1232
+ ["controlkeys:write", false, [], []],
1233
+ ["tenanthooks:read", false, [], []],
1234
+ ["tenanthooks:write", false, [], []],
1235
+ ["apps:install", false, [], []],
1236
+ ["tenant:manage", false, [], ["project:manage", "projects:create", "projects:delete", "tenants:read", "tenants:write", "tenants:delete", "audit:read", "export:run", "billing:read", "billing:manage", "usage:read", "controlkeys:read", "controlkeys:write", "tenanthooks:read", "tenanthooks:write", "apps:install"]],
1237
+ ["platform:read", false, [], []],
1238
+ ["platform:manage", false, [], ["tenant:manage", "platform:read", "platform:create_workspace", "users:setup"]],
1239
+ ["platform:create_workspace", false, [], []],
1240
+ ["users:read", false, ["own", "named", "all"], []],
1241
+ ["users:write", false, ["own", "all"], []],
1242
+ ["users:setup", false, [], []],
1243
+ ["users:delete", false, ["own", "all"], []],
1244
+ ["users:create", false, [], []],
1245
+ ["identities:read", false, [], []],
1246
+ ["identities:delete", false, [], []],
1247
+ ["usersecrets:read", false, [], []],
1248
+ ["usersecrets:list", false, [], []],
1249
+ ["usersecrets:put", false, [], []],
1250
+ ["usersecrets:delete", false, [], []],
1251
+ ["auth:login_with_foir", false, [], []],
1252
+ ["signinproviders:read", false, [], []],
1253
+ ["signinproviders:write", false, [], []]
1254
+ ];
1255
+
1256
+ // src/auth/grants.ts
1257
+ var WILDCARD = "*";
1258
+ var RESOURCE_WILDCARD = ":*";
1259
+ var index;
1260
+ function catalogue() {
1261
+ if (!index) {
1262
+ const byName = new Map(GRANT_TABLE.map((row) => [row[0], row]));
1263
+ const longestFirst = [...byName.keys()].sort(
1264
+ (a, b) => a.length !== b.length ? b.length - a.length : a < b ? -1 : 1
1265
+ );
1266
+ index = { byName, longestFirst };
1267
+ }
1268
+ return index;
1269
+ }
1270
+ var reachRank = { own: 1, named: 2, all: 3 };
1271
+ function hasReach(permission, reach) {
1272
+ const reaches = permission[2];
1273
+ return reaches.length === 0 ? reach === "own" : reaches.includes(reach);
1274
+ }
1275
+ function resourceOf(name) {
1276
+ const i = name.indexOf(":");
1277
+ return i < 0 ? name : name.slice(0, i);
1278
+ }
1279
+ function parseGrant(s) {
1280
+ const { byName, longestFirst } = catalogue();
1281
+ for (const name of longestFirst) {
1282
+ if (!s.startsWith(name)) continue;
1283
+ let rest = s.slice(name.length);
1284
+ if (rest !== "" && rest[0] !== "." && rest[0] !== ":") continue;
1285
+ const permission = byName.get(name);
1286
+ let reach = "own";
1287
+ if (rest.startsWith(".")) {
1288
+ const after = rest.slice(1);
1289
+ const colon = after.indexOf(":");
1290
+ const word = colon < 0 ? after : after.slice(0, colon);
1291
+ if (word !== "named" && word !== "all") return null;
1292
+ if (!hasReach(permission, word)) return null;
1293
+ reach = word;
1294
+ rest = after.slice(word.length);
1295
+ }
1296
+ let target = "";
1297
+ if (rest.startsWith(":")) {
1298
+ target = rest.slice(1);
1299
+ if (!permission[1]) return null;
1300
+ if (target === "" || /[: \t\n*]/.test(target)) return null;
1301
+ }
1302
+ return { permission, reach, target };
1303
+ }
1304
+ return null;
1305
+ }
1306
+ function covers(held, required) {
1307
+ return held.permission === required.permission && reachRank[held.reach] >= reachRank[required.reach] && (held.target === "" || held.target === required.target);
1308
+ }
1309
+ function resourceWildcard(held) {
1310
+ if (!held.endsWith(RESOURCE_WILDCARD)) return null;
1311
+ const resource = held.slice(0, -RESOURCE_WILDCARD.length);
1312
+ return resource.includes(":") ? null : resource;
1313
+ }
1314
+ function holdsAt(held, required, depth) {
1315
+ for (const h of held) {
1316
+ if (h === WILDCARD) return true;
1317
+ const resource = resourceWildcard(h);
1318
+ if (resource !== null) {
1319
+ if (resource === resourceOf(required.permission[0])) return true;
1320
+ continue;
1321
+ }
1322
+ const grant = parseGrant(h);
1323
+ if (grant === null) continue;
1324
+ if (covers(grant, required)) return true;
1325
+ const implies = grant.permission[3];
1326
+ if (depth < 4 && implies.length > 0 && holdsAt(implies, required, depth + 1)) return true;
1327
+ }
1328
+ return false;
1329
+ }
1330
+ function holds(held, required) {
1331
+ const grant = parseGrant(required);
1332
+ return grant !== null && holdsAt(held, grant, 0);
1333
+ }
1334
+
1335
+ // src/auth/access.ts
1336
+ function accessFrom(who) {
1337
+ const customer = who?.credential?.customer;
1338
+ if (!customer?.id) return null;
1339
+ return {
1340
+ customerId: customer.id,
1341
+ roles: Array.isArray(customer.roles) ? [...customer.roles] : null,
1342
+ scopes: [...who?.authority?.effective ?? []]
1343
+ };
1344
+ }
1345
+ async function customerAccess(env, options) {
1346
+ return accessFrom(await authOp(env, AUTH_OPS.whoami, void 0, options));
1347
+ }
1348
+ async function getSessionAccess(adapter, env) {
1349
+ if (!await getSessionToken(adapter)) return null;
1350
+ return accessFrom(await authedOp(adapter, env, AUTH_OPS.whoami));
1351
+ }
1352
+ function hasRole(access, key) {
1353
+ return access?.roles?.includes(key) ?? false;
1354
+ }
1355
+ function hasScope(access, scope) {
1356
+ return access ? holds(access.scopes, scope) : false;
1357
+ }
1081
1358
 
1082
1359
  // src/auth/session-client.ts
1083
1360
  var DEFAULT_SESSION_PATH = "/session/token";
@@ -1254,9 +1531,16 @@ function FoirProvider({
1254
1531
  if (adapter && adapter !== adapterRef.current) adapterRef.current = adapter;
1255
1532
  const [status, setStatus] = useState("loading");
1256
1533
  const [customer, setCustomer] = useState(null);
1534
+ const [access, setAccess] = useState(null);
1535
+ const [accessStatus, setAccessStatus] = useState("idle");
1536
+ const accessRead = useRef(0);
1537
+ const accessPending = useRef(null);
1257
1538
  const apply = useCallback((c) => {
1258
1539
  setCustomer(c);
1259
1540
  setStatus(c ? "authenticated" : "unauthenticated");
1541
+ accessRead.current += 1;
1542
+ setAccess(null);
1543
+ setAccessStatus("idle");
1260
1544
  }, []);
1261
1545
  const loginOrigin = hostedSession?.loginOrigin;
1262
1546
  const sessionPath = hostedSession?.sessionPath;
@@ -1381,6 +1665,39 @@ function FoirProvider({
1381
1665
  () => hosted ? hosted.getToken() : getSessionToken(adapterRef.current),
1382
1666
  [hosted]
1383
1667
  );
1668
+ const loadAccess = useCallback(() => {
1669
+ const read = accessRead.current += 1;
1670
+ setAccessStatus((was) => was === "ready" ? was : "loading");
1671
+ const pending = (async () => {
1672
+ try {
1673
+ const next = hosted ? await hosted.getToken().then(
1674
+ (bearerToken) => bearerToken ? customerAccess(envRef.current, { bearerToken }) : null
1675
+ ) : await getSessionAccess(adapterRef.current, envRef.current);
1676
+ if (accessRead.current !== read) return;
1677
+ setAccess(next);
1678
+ setAccessStatus("ready");
1679
+ } catch {
1680
+ if (accessRead.current !== read) return;
1681
+ setAccess(null);
1682
+ setAccessStatus("unavailable");
1683
+ }
1684
+ })();
1685
+ accessPending.current = pending;
1686
+ void pending.finally(() => {
1687
+ if (accessPending.current === pending) accessPending.current = null;
1688
+ });
1689
+ return pending;
1690
+ }, [hosted]);
1691
+ const ensureAccess = useCallback(
1692
+ () => accessPending.current ?? loadAccess(),
1693
+ [loadAccess]
1694
+ );
1695
+ const accessWasRead = accessStatus !== "idle";
1696
+ const refreshWithAccess = useCallback(async () => {
1697
+ const ok = await refresh();
1698
+ if (ok && accessWasRead) await loadAccess();
1699
+ return ok;
1700
+ }, [refresh, loadAccess, accessWasRead]);
1384
1701
  const getProviders = useCallback(() => getAuthProviders(envRef.current), []);
1385
1702
  const hostedSignInUrl = useMemo(
1386
1703
  () => hosted ? (returnTo) => hosted.signInUrl(returnTo) : null,
@@ -1390,6 +1707,10 @@ function FoirProvider({
1390
1707
  () => ({
1391
1708
  status,
1392
1709
  customer,
1710
+ access,
1711
+ accessStatus,
1712
+ loadAccess,
1713
+ ensureAccess,
1393
1714
  env,
1394
1715
  adapter: adapterRef.current,
1395
1716
  signIn,
@@ -1399,7 +1720,7 @@ function FoirProvider({
1399
1720
  signInWithProvider,
1400
1721
  completeProviderLogin,
1401
1722
  signOut: signOut2,
1402
- refresh,
1723
+ refresh: refreshWithAccess,
1403
1724
  getToken,
1404
1725
  getProviders,
1405
1726
  hostedSignInUrl
@@ -1407,6 +1728,10 @@ function FoirProvider({
1407
1728
  [
1408
1729
  status,
1409
1730
  customer,
1731
+ access,
1732
+ accessStatus,
1733
+ loadAccess,
1734
+ ensureAccess,
1410
1735
  env,
1411
1736
  signIn,
1412
1737
  register,
@@ -1415,7 +1740,7 @@ function FoirProvider({
1415
1740
  signInWithProvider,
1416
1741
  completeProviderLogin,
1417
1742
  signOut2,
1418
- refresh,
1743
+ refreshWithAccess,
1419
1744
  getToken,
1420
1745
  getProviders,
1421
1746
  hostedSignInUrl
@@ -1442,6 +1767,24 @@ function useFoirSession() {
1442
1767
  function useFoirUser() {
1443
1768
  return useFoirContext().customer;
1444
1769
  }
1770
+ function useFoirAccess() {
1771
+ const { status, access, accessStatus, loadAccess, ensureAccess } = useFoirContext();
1772
+ useEffect(() => {
1773
+ if (status === "authenticated" && accessStatus === "idle") void ensureAccess();
1774
+ }, [status, accessStatus, ensureAccess]);
1775
+ const settled = status === "loading" ? "loading" : status === "unauthenticated" ? "ready" : accessStatus === "idle" ? "loading" : accessStatus;
1776
+ return useMemo(
1777
+ () => ({
1778
+ status: settled,
1779
+ isLoading: settled === "loading",
1780
+ access,
1781
+ hasRole: (key) => hasRole(access, key),
1782
+ hasScope: (scope) => hasScope(access, scope),
1783
+ reload: loadAccess
1784
+ }),
1785
+ [settled, access, loadAccess]
1786
+ );
1787
+ }
1445
1788
  function useFoirAuth() {
1446
1789
  const {
1447
1790
  signIn,
@@ -2280,6 +2623,7 @@ export {
2280
2623
  SignIn,
2281
2624
  UserButton,
2282
2625
  createLocalStorageAdapter,
2626
+ useFoirAccess,
2283
2627
  useFoirAuth,
2284
2628
  useFoirSession,
2285
2629
  useFoirUser
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * const events = createEventsClient({
9
9
  * apiUrl: process.env.PLATFORM_API_URL!,
10
- * apiKey: process.env.FOIR_SECRET_KEY!, // sk_…, holding events:list.project
10
+ * apiKey: process.env.FOIR_SECRET_KEY!, // sk_…, holding events:read
11
11
  * });
12
12
  *
13
13
  * const page = await events.list({ after: lastSeq, limit: 500 });
@@ -24,7 +24,7 @@
24
24
  * DECIDED 2026-08-04. The event log is reachable by an `sk_` api key
25
25
  * and by an admin session. It is NOT reachable by a customer token, there is no
26
26
  * customer lane for it on api-public, and a `pk_` key can never be minted
27
- * holding `events:list.project` at all.
27
+ * holding `events:read` at all.
28
28
  *
29
29
  * The reason is a predicate asymmetry rather than a policy preference.
30
30
  * `events.view` is plain tenant+project containment; `record.view` carries
@@ -81,7 +81,7 @@ export declare const FOIR_EVENTS_PATH = "/v1/events";
81
81
  * absent from the platform's `publicSafeScopeResources` — and never present in a
82
82
  * customer's resolved authority.
83
83
  */
84
- export declare const FOIR_EVENTS_SCOPE = "events:list.project";
84
+ export declare const FOIR_EVENTS_SCOPE = "events:read";
85
85
  /**
86
86
  * The flat retention window, in days. Reported on every page as
87
87
  * {@link FoirEventRetention.windowDays} too; this constant is the one to compare
@@ -200,7 +200,7 @@ export interface FoirEventsClientConfig {
200
200
  */
201
201
  apiUrl: string;
202
202
  /**
203
- * An `sk_` secret key holding `events:list.project`.
203
+ * An `sk_` secret key holding `events:read`.
204
204
  *
205
205
  * A `pk_` key is refused here rather than at the server — see the operator
206
206
  * note at the top of this file.
@@ -209,7 +209,7 @@ export interface FoirEventsClientConfig {
209
209
  /**
210
210
  * Extra headers. An `Authorization` header is refused: a customer bearer
211
211
  * flips api-public's resolved authority to the customer, and no customer
212
- * authority can contain `events:list.project`.
212
+ * authority can contain `events:read`.
213
213
  */
214
214
  headers?: Readonly<Record<string, string>>;
215
215
  /** `fetch` implementation. Defaults to `globalThis.fetch`. */
@@ -1,6 +1,6 @@
1
1
  export { createServerClient, ServerClientError } from './server-client';
2
2
  export { verifyWebhookSignature } from './webhook-helpers';
3
- export { verifyScopedToken, parseContextHeader, parseSubject, recordsReadModels, recordsWriteModels, type ScopedTokenClaims, type ScopedTokenSubject, type VerifyScopedTokenOptions, } from './verify-scoped-token';
3
+ export { verifyScopedToken, parseContextHeader, parseSubject, recordsReadModels, recordsWriteModels, recordsCreateModels, type ScopedTokenClaims, type ScopedTokenSubject, type VerifyScopedTokenOptions, } from './verify-scoped-token';
4
4
  export { createScopedTokenVerifier, ScopedTokenUnavailableError, type ScopedTokenVerifier, type ScopedTokenVerifierOptions, type ScopedTokenVerifierStats, } from './verify-scoped-token';
5
5
  export { requireScopedToken, ScopedTokenError, FOIR_TOKEN_HEADER, FOIR_CONTEXT_HEADER, type RequireScopedTokenOptions, type ScopedTokenIngress, } from './require-scoped-token';
6
6
  export { readDispatch, OperationPayloadError, type BodyCarrier, } from './read-dispatch';
@@ -0,0 +1,8 @@
1
+ /** Stands, below, where a capability or a grant names a model. */
2
+ export declare const OLDER_CLAIM_TARGET = "{target}";
3
+ /**
4
+ * What each capability a token said before it said its grants was worth, as
5
+ * the platform reads one. A capability that was worth nothing is worth nothing
6
+ * here.
7
+ */
8
+ export declare const OLDER_CAPABILITY_GRANTS: Readonly<Record<string, readonly string[]>>;
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * The verified token is also the endpoint's credential for calling Foir back.
20
20
  * `dispatch` on the result is the token as a data-client credential, so the
21
- * endpoint acts as the operation, with the capabilities the operation
21
+ * endpoint acts as the operation, with the grants the operation
22
22
  * declared, and holds no key of its own. `generated` is the module
23
23
  * `foir types --operation <key>` wrote for the operation:
24
24
  *
@@ -24,24 +24,41 @@ export interface ScopedTokenClaims {
24
24
  * {@link parseSubject} to split into a typed object.
25
25
  */
26
26
  sub: string;
27
- cap: Array<string>;
27
+ /**
28
+ * What the token carries, as grants in the permission catalogue's names:
29
+ * `records:read:<model>`, `records:write:<model>`,
30
+ * `records:create:<model>`, `users:create` and the rest. It is the whole of
31
+ * what the token may do. A token minted before a token said its grants is
32
+ * read as what its older claims were worth, as the platform reads it, so
33
+ * this is always set.
34
+ */
35
+ grants: Array<string>;
28
36
  jti: string;
29
37
  exp: number;
30
38
  nbf: number;
31
39
  iat: number;
32
40
  ctx?: Record<string, string>;
33
41
  /**
34
- * Records-read model allow-list. Present when the operation declared one
35
- * or more `records:read:<model>` capabilities. Pair with the
36
- * `records:read` marker in {@link ScopedTokenClaims.cap} to authorize a
37
- * record read for a specific model.
42
+ * The same grants in the names a token said them with before it said its
43
+ * grants. They never say more than {@link ScopedTokenClaims.grants}, and
44
+ * can say less: a grant the older names had no word for is left out.
45
+ *
46
+ * @deprecated Read {@link ScopedTokenClaims.grants}. The platform stops
47
+ * sending this once no token minted before is live.
48
+ */
49
+ cap?: Array<string>;
50
+ /**
51
+ * The models the older `records:read` marker in
52
+ * {@link ScopedTokenClaims.cap} was for.
53
+ *
54
+ * @deprecated Read {@link recordsReadModels}.
38
55
  */
39
56
  rrm?: Array<string>;
40
57
  /**
41
- * Records-write model allow-list. Present when the operation declared
42
- * one or more `records:write:<model>` capabilities. Pair with the
43
- * `records:write` marker in {@link ScopedTokenClaims.cap} to authorize
44
- * a record write for a specific model.
58
+ * The models the older `records:write` marker in
59
+ * {@link ScopedTokenClaims.cap} was for.
60
+ *
61
+ * @deprecated Read {@link recordsWriteModels}.
45
62
  */
46
63
  rwm?: Array<string>;
47
64
  /**
@@ -77,19 +94,21 @@ export interface ScopedTokenSubject {
77
94
  */
78
95
  export declare function parseSubject(sub: string): ScopedTokenSubject;
79
96
  /**
80
- * Extract the `records:read:<model>` allow-list from a verified claim
81
- * set. Returns an empty array when the token grants no per-model read
82
- * capabilities. Convenience over reading {@link ScopedTokenClaims.rrm}
83
- * directly, mirroring the platform's
84
- * `scopedtoken.Claims.RecordsReadModels` field.
97
+ * The models a verified token may read records of: each model a
98
+ * `records:read:<model>` grant names. Empty when it names none.
85
99
  */
86
100
  export declare function recordsReadModels(claims: ScopedTokenClaims): Array<string>;
87
101
  /**
88
- * Extract the `records:write:<model>` allow-list from a verified claim
89
- * set. Returns an empty array when the token grants no per-model write
90
- * capabilities. Mirror of {@link recordsReadModels} for the write side.
102
+ * The models a verified token may change records of: each model a
103
+ * `records:write:<model>` grant names. Changing a record and making one are
104
+ * two permissions: see {@link recordsCreateModels}.
91
105
  */
92
106
  export declare function recordsWriteModels(claims: ScopedTokenClaims): Array<string>;
107
+ /**
108
+ * The models a verified token may make records of: each model a
109
+ * `records:create:<model>` grant names.
110
+ */
111
+ export declare function recordsCreateModels(claims: ScopedTokenClaims): Array<string>;
93
112
  export interface VerifyScopedTokenOptions {
94
113
  /**
95
114
  * Expected `iss` claim. Defaults to `'foir-platform'`, the issuer the