@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/CHANGELOG.md +279 -0
- package/dist/app/client.d.ts +109 -21
- package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +223 -211
- package/dist/app/errors.d.ts +2 -2
- package/dist/app/index.d.ts +4 -4
- package/dist/app/manifest.d.ts +1 -1
- package/dist/app/vocabulary.generated.d.ts +13 -10
- package/dist/app-contract.js +27 -31
- package/dist/app.js +80 -23
- package/dist/auth/access.d.ts +106 -0
- package/dist/auth/grant-table.generated.d.ts +13 -0
- package/dist/auth/grants.d.ts +38 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/operations.d.ts +6 -1
- package/dist/auth/ops.d.ts +1 -0
- package/dist/auth.js +269 -1
- package/dist/data/builtins.d.ts +59 -3
- package/dist/data/index.d.ts +1 -1
- package/dist/data/transport.d.ts +2 -2
- package/dist/next.js +3 -0
- package/dist/placement/index.d.ts +40 -8
- package/dist/placement.js +224 -2
- package/dist/push.js +3 -0
- package/dist/react/FoirProvider.d.ts +40 -1
- package/dist/react/feedback-notes.d.ts +1 -1
- package/dist/react/index.d.ts +5 -4
- package/dist/react.js +347 -3
- package/dist/server/events-client.d.ts +5 -5
- package/dist/server/index.d.ts +1 -1
- package/dist/server/older-claims.generated.d.ts +8 -0
- package/dist/server/require-scoped-token.d.ts +1 -1
- package/dist/server/verify-scoped-token.d.ts +36 -17
- package/dist/server.js +267 -5
- package/package.json +6 -5
package/dist/auth/ops.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export declare const AUTH_OPS: {
|
|
|
44
44
|
readonly customerSignupContext: "customerSignupContext";
|
|
45
45
|
readonly currentUser: "currentUser";
|
|
46
46
|
readonly myProviderToken: "myProviderToken";
|
|
47
|
+
readonly whoami: "whoami";
|
|
47
48
|
readonly customerLogin: "customerLogin";
|
|
48
49
|
readonly customerRegister: "customerRegister";
|
|
49
50
|
readonly customerRequestOTP: "customerRequestOTP";
|
package/dist/auth.js
CHANGED
|
@@ -679,7 +679,7 @@ function toVariableValue(value, label) {
|
|
|
679
679
|
return value.toISOString();
|
|
680
680
|
}
|
|
681
681
|
if (Array.isArray(value)) {
|
|
682
|
-
return value.filter((item) => item !== void 0).map((item,
|
|
682
|
+
return value.filter((item) => item !== void 0).map((item, index2) => toVariableValue(item, `${label}[${index2}]`));
|
|
683
683
|
}
|
|
684
684
|
if (kind === "object") {
|
|
685
685
|
const out = {};
|
|
@@ -1034,6 +1034,9 @@ var AUTH_OPS = {
|
|
|
1034
1034
|
// Post-credential self reads.
|
|
1035
1035
|
currentUser: "currentUser",
|
|
1036
1036
|
myProviderToken: "myProviderToken",
|
|
1037
|
+
// What the calling credential may do, and for a signed-in customer the roles
|
|
1038
|
+
// they hold. `./access` reads it.
|
|
1039
|
+
whoami: "whoami",
|
|
1037
1040
|
// Methods.
|
|
1038
1041
|
customerLogin: "customerLogin",
|
|
1039
1042
|
customerRegister: "customerRegister",
|
|
@@ -1357,6 +1360,264 @@ async function requireAdmin(adapter, options) {
|
|
|
1357
1360
|
return user;
|
|
1358
1361
|
}
|
|
1359
1362
|
|
|
1363
|
+
// src/auth/grant-table.generated.ts
|
|
1364
|
+
var GRANT_TABLE = [
|
|
1365
|
+
["records:read", true, ["own", "named", "all"], []],
|
|
1366
|
+
["records:create", true, [], []],
|
|
1367
|
+
["records:write", true, ["own", "named", "all"], []],
|
|
1368
|
+
["records:delete", true, ["own", "named", "all"], []],
|
|
1369
|
+
["records:publish", true, ["own", "all"], []],
|
|
1370
|
+
["records:attribute", true, [], []],
|
|
1371
|
+
["search:read", true, [], []],
|
|
1372
|
+
["embeddings:read", false, [], []],
|
|
1373
|
+
["embeddings:write", false, [], []],
|
|
1374
|
+
["schemas:read", false, [], []],
|
|
1375
|
+
["drafts:read", false, [], []],
|
|
1376
|
+
["files:read", false, ["own", "all"], []],
|
|
1377
|
+
["files:write", false, ["own", "all"], []],
|
|
1378
|
+
["files:delete", false, ["own", "all"], []],
|
|
1379
|
+
["notifications:read", false, ["own", "all"], []],
|
|
1380
|
+
["notifications:write", false, ["own", "named", "all"], []],
|
|
1381
|
+
["notifications:consent", false, [], []],
|
|
1382
|
+
["pushcredentials:read", false, [], []],
|
|
1383
|
+
["pushcredentials:write", false, [], []],
|
|
1384
|
+
["notes:read", false, [], []],
|
|
1385
|
+
["notes:write", false, [], []],
|
|
1386
|
+
["notes:create", false, [], []],
|
|
1387
|
+
["approvals:read", false, [], []],
|
|
1388
|
+
["approvals:write", false, [], []],
|
|
1389
|
+
["rooms:read", true, [], []],
|
|
1390
|
+
["rooms:write", true, [], []],
|
|
1391
|
+
["presence:read", true, [], []],
|
|
1392
|
+
["operations:read", false, [], []],
|
|
1393
|
+
["operations:execute", true, [], []],
|
|
1394
|
+
["operations:write", false, [], []],
|
|
1395
|
+
["operations:complete", false, [], []],
|
|
1396
|
+
["status:write", false, [], []],
|
|
1397
|
+
["appconfig:read", false, [], []],
|
|
1398
|
+
["schedules:read", false, [], []],
|
|
1399
|
+
["schedules:write", false, [], []],
|
|
1400
|
+
["hooks:read", false, [], []],
|
|
1401
|
+
["hooks:write", false, [], []],
|
|
1402
|
+
["relyingparties:read", false, [], []],
|
|
1403
|
+
["relyingparties:write", false, [], []],
|
|
1404
|
+
["domains:read", false, [], []],
|
|
1405
|
+
["domains:write", false, [], []],
|
|
1406
|
+
["secrets:read", false, [], []],
|
|
1407
|
+
["secrets:list", false, [], []],
|
|
1408
|
+
["secrets:put", false, [], []],
|
|
1409
|
+
["secrets:delete", false, [], []],
|
|
1410
|
+
["appsecrets:read", false, [], []],
|
|
1411
|
+
["appsecrets:list", false, [], []],
|
|
1412
|
+
["appsecrets:put", false, [], []],
|
|
1413
|
+
["appsecrets:delete", false, [], []],
|
|
1414
|
+
["opsecrets:read", false, [], []],
|
|
1415
|
+
["opsecrets:write", false, [], []],
|
|
1416
|
+
["settings:read", false, [], []],
|
|
1417
|
+
["settings:write", false, [], []],
|
|
1418
|
+
["designtokens:read", false, [], []],
|
|
1419
|
+
["designtokens:write", false, [], []],
|
|
1420
|
+
["models:read", false, [], []],
|
|
1421
|
+
["models:write", false, [], []],
|
|
1422
|
+
["configs:read", false, [], []],
|
|
1423
|
+
["configs:write", false, [], []],
|
|
1424
|
+
["apps:read", false, [], []],
|
|
1425
|
+
["apps:write", false, [], []],
|
|
1426
|
+
["events:read", false, [], []],
|
|
1427
|
+
["targeting:debug", false, [], []],
|
|
1428
|
+
["visitors:forward", false, [], []],
|
|
1429
|
+
["apikeys:read", false, [], []],
|
|
1430
|
+
["apikeys:write", false, [], []],
|
|
1431
|
+
["organizations:read", false, [], []],
|
|
1432
|
+
["organizations:write", false, [], []],
|
|
1433
|
+
["organizations:create", false, [], []],
|
|
1434
|
+
["organizations:delete", false, [], []],
|
|
1435
|
+
["organizations:reach", false, [], []],
|
|
1436
|
+
["members:read", false, [], []],
|
|
1437
|
+
["members:write", false, [], []],
|
|
1438
|
+
["roles:read", false, [], []],
|
|
1439
|
+
["roles:write", false, [], []],
|
|
1440
|
+
["projects:read", false, [], []],
|
|
1441
|
+
["projects:write", false, [], []],
|
|
1442
|
+
["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"]],
|
|
1443
|
+
["projects:create", false, [], []],
|
|
1444
|
+
["projects:delete", false, [], []],
|
|
1445
|
+
["tenants:read", false, [], []],
|
|
1446
|
+
["tenants:write", false, [], []],
|
|
1447
|
+
["tenants:delete", false, [], []],
|
|
1448
|
+
["audit:read", false, [], []],
|
|
1449
|
+
["export:run", false, [], []],
|
|
1450
|
+
["billing:read", false, [], []],
|
|
1451
|
+
["billing:manage", false, [], []],
|
|
1452
|
+
["usage:read", false, [], []],
|
|
1453
|
+
["controlkeys:read", false, [], []],
|
|
1454
|
+
["controlkeys:write", false, [], []],
|
|
1455
|
+
["tenanthooks:read", false, [], []],
|
|
1456
|
+
["tenanthooks:write", false, [], []],
|
|
1457
|
+
["apps:install", false, [], []],
|
|
1458
|
+
["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"]],
|
|
1459
|
+
["platform:read", false, [], []],
|
|
1460
|
+
["platform:manage", false, [], ["tenant:manage", "platform:read", "platform:create_workspace", "users:setup"]],
|
|
1461
|
+
["platform:create_workspace", false, [], []],
|
|
1462
|
+
["users:read", false, ["own", "named", "all"], []],
|
|
1463
|
+
["users:write", false, ["own", "all"], []],
|
|
1464
|
+
["users:setup", false, [], []],
|
|
1465
|
+
["users:delete", false, ["own", "all"], []],
|
|
1466
|
+
["users:create", false, [], []],
|
|
1467
|
+
["identities:read", false, [], []],
|
|
1468
|
+
["identities:delete", false, [], []],
|
|
1469
|
+
["usersecrets:read", false, [], []],
|
|
1470
|
+
["usersecrets:list", false, [], []],
|
|
1471
|
+
["usersecrets:put", false, [], []],
|
|
1472
|
+
["usersecrets:delete", false, [], []],
|
|
1473
|
+
["auth:login_with_foir", false, [], []],
|
|
1474
|
+
["signinproviders:read", false, [], []],
|
|
1475
|
+
["signinproviders:write", false, [], []]
|
|
1476
|
+
];
|
|
1477
|
+
|
|
1478
|
+
// src/auth/grants.ts
|
|
1479
|
+
var WILDCARD = "*";
|
|
1480
|
+
var RESOURCE_WILDCARD = ":*";
|
|
1481
|
+
var index;
|
|
1482
|
+
function catalogue() {
|
|
1483
|
+
if (!index) {
|
|
1484
|
+
const byName = new Map(GRANT_TABLE.map((row) => [row[0], row]));
|
|
1485
|
+
const longestFirst = [...byName.keys()].sort(
|
|
1486
|
+
(a, b) => a.length !== b.length ? b.length - a.length : a < b ? -1 : 1
|
|
1487
|
+
);
|
|
1488
|
+
index = { byName, longestFirst };
|
|
1489
|
+
}
|
|
1490
|
+
return index;
|
|
1491
|
+
}
|
|
1492
|
+
var reachRank = { own: 1, named: 2, all: 3 };
|
|
1493
|
+
function hasReach(permission, reach) {
|
|
1494
|
+
const reaches = permission[2];
|
|
1495
|
+
return reaches.length === 0 ? reach === "own" : reaches.includes(reach);
|
|
1496
|
+
}
|
|
1497
|
+
function resourceOf(name) {
|
|
1498
|
+
const i = name.indexOf(":");
|
|
1499
|
+
return i < 0 ? name : name.slice(0, i);
|
|
1500
|
+
}
|
|
1501
|
+
function parseGrant(s) {
|
|
1502
|
+
const { byName, longestFirst } = catalogue();
|
|
1503
|
+
for (const name of longestFirst) {
|
|
1504
|
+
if (!s.startsWith(name)) continue;
|
|
1505
|
+
let rest = s.slice(name.length);
|
|
1506
|
+
if (rest !== "" && rest[0] !== "." && rest[0] !== ":") continue;
|
|
1507
|
+
const permission = byName.get(name);
|
|
1508
|
+
let reach = "own";
|
|
1509
|
+
if (rest.startsWith(".")) {
|
|
1510
|
+
const after = rest.slice(1);
|
|
1511
|
+
const colon = after.indexOf(":");
|
|
1512
|
+
const word = colon < 0 ? after : after.slice(0, colon);
|
|
1513
|
+
if (word !== "named" && word !== "all") return null;
|
|
1514
|
+
if (!hasReach(permission, word)) return null;
|
|
1515
|
+
reach = word;
|
|
1516
|
+
rest = after.slice(word.length);
|
|
1517
|
+
}
|
|
1518
|
+
let target = "";
|
|
1519
|
+
if (rest.startsWith(":")) {
|
|
1520
|
+
target = rest.slice(1);
|
|
1521
|
+
if (!permission[1]) return null;
|
|
1522
|
+
if (target === "" || /[: \t\n*]/.test(target)) return null;
|
|
1523
|
+
}
|
|
1524
|
+
return { permission, reach, target };
|
|
1525
|
+
}
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
function covers(held, required) {
|
|
1529
|
+
return held.permission === required.permission && reachRank[held.reach] >= reachRank[required.reach] && (held.target === "" || held.target === required.target);
|
|
1530
|
+
}
|
|
1531
|
+
function resourceWildcard(held) {
|
|
1532
|
+
if (!held.endsWith(RESOURCE_WILDCARD)) return null;
|
|
1533
|
+
const resource = held.slice(0, -RESOURCE_WILDCARD.length);
|
|
1534
|
+
return resource.includes(":") ? null : resource;
|
|
1535
|
+
}
|
|
1536
|
+
function holdsAt(held, required, depth) {
|
|
1537
|
+
for (const h of held) {
|
|
1538
|
+
if (h === WILDCARD) return true;
|
|
1539
|
+
const resource = resourceWildcard(h);
|
|
1540
|
+
if (resource !== null) {
|
|
1541
|
+
if (resource === resourceOf(required.permission[0])) return true;
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
const grant = parseGrant(h);
|
|
1545
|
+
if (grant === null) continue;
|
|
1546
|
+
if (covers(grant, required)) return true;
|
|
1547
|
+
const implies = grant.permission[3];
|
|
1548
|
+
if (depth < 4 && implies.length > 0 && holdsAt(implies, required, depth + 1)) return true;
|
|
1549
|
+
}
|
|
1550
|
+
return false;
|
|
1551
|
+
}
|
|
1552
|
+
function holds(held, required) {
|
|
1553
|
+
const grant = parseGrant(required);
|
|
1554
|
+
return grant !== null && holdsAt(held, grant, 0);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// src/auth/access.ts
|
|
1558
|
+
function accessFrom(who) {
|
|
1559
|
+
const customer = who?.credential?.customer;
|
|
1560
|
+
if (!customer?.id) return null;
|
|
1561
|
+
return {
|
|
1562
|
+
customerId: customer.id,
|
|
1563
|
+
roles: Array.isArray(customer.roles) ? [...customer.roles] : null,
|
|
1564
|
+
scopes: [...who?.authority?.effective ?? []]
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
async function customerAccess(env, options) {
|
|
1568
|
+
return accessFrom(await authOp(env, AUTH_OPS.whoami, void 0, options));
|
|
1569
|
+
}
|
|
1570
|
+
async function getSessionAccess(adapter, env) {
|
|
1571
|
+
if (!await getSessionToken(adapter)) return null;
|
|
1572
|
+
return accessFrom(await authedOp(adapter, env, AUTH_OPS.whoami));
|
|
1573
|
+
}
|
|
1574
|
+
function hasRole(access, key) {
|
|
1575
|
+
return access?.roles?.includes(key) ?? false;
|
|
1576
|
+
}
|
|
1577
|
+
function hasScope(access, scope) {
|
|
1578
|
+
return access ? holds(access.scopes, scope) : false;
|
|
1579
|
+
}
|
|
1580
|
+
var FoirAccessDeniedError = class extends Error {
|
|
1581
|
+
code = "FOIR_ACCESS_DENIED";
|
|
1582
|
+
reason;
|
|
1583
|
+
/** The role that was required, when one was. */
|
|
1584
|
+
role;
|
|
1585
|
+
/** The scope that was required, when one was. */
|
|
1586
|
+
scope;
|
|
1587
|
+
constructor(reason, required) {
|
|
1588
|
+
super(denialMessage(reason, required));
|
|
1589
|
+
this.name = "FoirAccessDeniedError";
|
|
1590
|
+
this.reason = reason;
|
|
1591
|
+
this.role = required.role;
|
|
1592
|
+
this.scope = required.scope;
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
function denialMessage(reason, required) {
|
|
1596
|
+
switch (reason) {
|
|
1597
|
+
case "signed_out":
|
|
1598
|
+
return "[foir] nobody is signed in";
|
|
1599
|
+
case "role_not_held":
|
|
1600
|
+
return `[foir] the signed-in customer does not hold the role "${required.role}"`;
|
|
1601
|
+
case "roles_unknown":
|
|
1602
|
+
return `[foir] the platform did not say which roles the signed-in customer holds, so "${required.role}" could not be checked`;
|
|
1603
|
+
case "scope_not_held":
|
|
1604
|
+
return `[foir] the signed-in customer's scopes do not cover "${required.scope}"`;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
async function requireRole(adapter, env, key) {
|
|
1608
|
+
const access = await getSessionAccess(adapter, env);
|
|
1609
|
+
if (!access) throw new FoirAccessDeniedError("signed_out", { role: key });
|
|
1610
|
+
if (access.roles === null) throw new FoirAccessDeniedError("roles_unknown", { role: key });
|
|
1611
|
+
if (!access.roles.includes(key)) throw new FoirAccessDeniedError("role_not_held", { role: key });
|
|
1612
|
+
return access;
|
|
1613
|
+
}
|
|
1614
|
+
async function requireScope(adapter, env, scope) {
|
|
1615
|
+
const access = await getSessionAccess(adapter, env);
|
|
1616
|
+
if (!access) throw new FoirAccessDeniedError("signed_out", { scope });
|
|
1617
|
+
if (!holds(access.scopes, scope)) throw new FoirAccessDeniedError("scope_not_held", { scope });
|
|
1618
|
+
return access;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1360
1621
|
// src/auth/hosted.ts
|
|
1361
1622
|
var DEFAULT_COOKIE_NAME = "foir_customer_session";
|
|
1362
1623
|
var DEFAULT_SESSION_PATH = "/session/token";
|
|
@@ -1567,6 +1828,7 @@ function trimSlash(s) {
|
|
|
1567
1828
|
}
|
|
1568
1829
|
export {
|
|
1569
1830
|
AUTH_OPS,
|
|
1831
|
+
FoirAccessDeniedError,
|
|
1570
1832
|
FoirAuthDowngradedError,
|
|
1571
1833
|
FoirSessionExpiredError,
|
|
1572
1834
|
authedOp,
|
|
@@ -1574,6 +1836,7 @@ export {
|
|
|
1574
1836
|
clearSession,
|
|
1575
1837
|
createFoirSessionClient,
|
|
1576
1838
|
currentUser,
|
|
1839
|
+
customerAccess,
|
|
1577
1840
|
customerJoinWaitlist,
|
|
1578
1841
|
customerLogin,
|
|
1579
1842
|
customerLoginOTP,
|
|
@@ -1594,8 +1857,11 @@ export {
|
|
|
1594
1857
|
getAuthConfig,
|
|
1595
1858
|
getAuthProviders,
|
|
1596
1859
|
getDefaultAuthProvider,
|
|
1860
|
+
getSessionAccess,
|
|
1597
1861
|
getSessionCustomer,
|
|
1598
1862
|
getSessionToken,
|
|
1863
|
+
hasRole,
|
|
1864
|
+
hasScope,
|
|
1599
1865
|
hostedLoginUrl,
|
|
1600
1866
|
hostedLogoutUrl,
|
|
1601
1867
|
loginWithOTP,
|
|
@@ -1608,6 +1874,8 @@ export {
|
|
|
1608
1874
|
registerWithPassword,
|
|
1609
1875
|
requireAdmin,
|
|
1610
1876
|
requireAuth,
|
|
1877
|
+
requireRole,
|
|
1878
|
+
requireScope,
|
|
1611
1879
|
resetPasswordAndSignIn,
|
|
1612
1880
|
saveSession,
|
|
1613
1881
|
selectOrganization,
|
package/dist/data/builtins.d.ts
CHANGED
|
@@ -520,7 +520,7 @@ export interface FoirCustomerNote {
|
|
|
520
520
|
* What a note surface has to know about the project before it renders anything
|
|
521
521
|
* — `foir.ns.notes.noteConfig`.
|
|
522
522
|
*
|
|
523
|
-
* Charged `notes:
|
|
523
|
+
* Charged `notes:create` rather than `notes:read`, and refused to a caller with
|
|
524
524
|
* no customer session, because the only caller it is useful to is one that can
|
|
525
525
|
* submit. So a composer can read it as its single readiness probe: an answer
|
|
526
526
|
* means this visitor may write, and a refusal names which half is missing.
|
|
@@ -551,7 +551,54 @@ export interface FoirMyNotesArgs {
|
|
|
551
551
|
* internal ones.
|
|
552
552
|
*/
|
|
553
553
|
export type FoirMyNotesResult = FoirConnection<FoirCustomerNote>;
|
|
554
|
-
/**
|
|
554
|
+
/**
|
|
555
|
+
* `writeNote` arguments: the one note create, for a signed-in customer and for
|
|
556
|
+
* a visitor alike. It asks `notes:create` of both.
|
|
557
|
+
*
|
|
558
|
+
* A signed-in customer's note is theirs and comes back in `myNotes`. A
|
|
559
|
+
* visitor's, through a key that gives `notes:create` to whoever comes through
|
|
560
|
+
* it, on a project that takes visitors' notes, is nobody's: ask for its `id`
|
|
561
|
+
* and `createdAt` and nothing else, because nothing reads it back.
|
|
562
|
+
*
|
|
563
|
+
* Served by a data API that has the field. `<FoirFeedback>` still writes
|
|
564
|
+
* through `createMyNote`, the same create under its older name, which every
|
|
565
|
+
* data API serves.
|
|
566
|
+
*/
|
|
567
|
+
export interface FoirWriteNoteArgs {
|
|
568
|
+
/** A visitor may write about a record and nothing else. */
|
|
569
|
+
subjectType: FoirNoteSubjectType;
|
|
570
|
+
subjectId: string;
|
|
571
|
+
/** Required when `subjectType` is `'record'`, refused otherwise. */
|
|
572
|
+
subjectModelKey?: string;
|
|
573
|
+
body: string;
|
|
574
|
+
/** Reply within an existing thread. Refused where nobody is signed in. */
|
|
575
|
+
parentNoteId?: string;
|
|
576
|
+
/**
|
|
577
|
+
* Files to submit with this note, already uploaded through
|
|
578
|
+
* `createFileUpload` / `confirmFileUpload`. Refused where nobody is signed in.
|
|
579
|
+
*
|
|
580
|
+
* A file this caller cannot read is REFUSED and the note is not written. That
|
|
581
|
+
* is not a rough edge to code around: a create that accepted the note and
|
|
582
|
+
* dropped the attachment would leave the author believing they sent a
|
|
583
|
+
* screenshot nobody has.
|
|
584
|
+
*/
|
|
585
|
+
attachmentFileIds?: string[];
|
|
586
|
+
/**
|
|
587
|
+
* Where the operators may reply to a visitor. Refused from a signed-in
|
|
588
|
+
* customer, whose note has an author.
|
|
589
|
+
*
|
|
590
|
+
* Captured as typed and NEVER verified: it is not treated as proof of who
|
|
591
|
+
* wrote the note, and nothing is sent to it to confirm it. Do not build a
|
|
592
|
+
* flow that assumes the address is reachable.
|
|
593
|
+
*/
|
|
594
|
+
contactEmail?: string;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* `createMyNote` arguments.
|
|
598
|
+
*
|
|
599
|
+
* @deprecated `createMyNote` is the older name of `writeNote` for a signed-in
|
|
600
|
+
* customer. Use `writeNote` and {@link FoirWriteNoteArgs}.
|
|
601
|
+
*/
|
|
555
602
|
export interface FoirCreateMyNoteArgs {
|
|
556
603
|
subjectType: FoirNoteSubjectType;
|
|
557
604
|
subjectId: string;
|
|
@@ -581,6 +628,10 @@ export interface FoirCreateMyNoteArgs {
|
|
|
581
628
|
*
|
|
582
629
|
* WRITE-ONLY. Nothing reads an anonymous note back on the public plane, which
|
|
583
630
|
* is why the result is a receipt rather than the note.
|
|
631
|
+
*
|
|
632
|
+
* @deprecated `submitAnonymousNote` is the older name of `writeNote` for a
|
|
633
|
+
* visitor. Use `writeNote` and {@link FoirWriteNoteArgs}, asking for `id` and
|
|
634
|
+
* `createdAt`.
|
|
584
635
|
*/
|
|
585
636
|
export interface FoirSubmitAnonymousNoteArgs {
|
|
586
637
|
/**
|
|
@@ -606,7 +657,12 @@ export interface FoirSubmitAnonymousNoteArgs {
|
|
|
606
657
|
*/
|
|
607
658
|
contactEmail?: string;
|
|
608
659
|
}
|
|
609
|
-
/**
|
|
660
|
+
/**
|
|
661
|
+
* What `submitAnonymousNote` returns.
|
|
662
|
+
*
|
|
663
|
+
* @deprecated With the field it answers. `writeNote` answers a visitor with
|
|
664
|
+
* the note's `id` and `createdAt`.
|
|
665
|
+
*/
|
|
610
666
|
export interface FoirAnonymousNoteReceipt {
|
|
611
667
|
/**
|
|
612
668
|
* A reference for this submission, to quote when following it up.
|
package/dist/data/index.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ export { planFoirModelCall, planFoirNamespaceCall } from './document';
|
|
|
59
59
|
export type { FoirCall, FoirDocumentPlan, FoirNamespaceCall, FoirNamespaceSelections, FoirRecordPath, FoirSelectionModel, FoirSelectionNamespace, FoirSelections, FoirSelectionSource, FoirSelectionUnions, } from './document';
|
|
60
60
|
export { createFoirSubjectRegistry, recordFoirSubjects } from './subjects';
|
|
61
61
|
export type { FoirSubject, FoirSubjectEntry, FoirSubjectOptions, FoirSubjectOrigin, FoirSubjectRegistry, } from './subjects';
|
|
62
|
-
export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirSubmitAnonymousNoteArgs, FoirAnonymousNoteReceipt, FoirCustomerNote, FoirCustomerNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirRecordOwner, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
|
|
62
|
+
export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirWriteNoteArgs, FoirSubmitAnonymousNoteArgs, FoirAnonymousNoteReceipt, FoirCustomerNote, FoirCustomerNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirRecordOwner, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
|
|
63
63
|
/**
|
|
64
64
|
* Pre-rename spellings. The `<Name>Block` wrappers and the `<Name>Value`
|
|
65
65
|
* split are the same thing as the types above — an item IS its value — so each
|
package/dist/data/transport.d.ts
CHANGED
|
@@ -155,10 +155,10 @@ export type FoirOperationGeneratedModule<S extends FoirSchemaDefinition = FoirSc
|
|
|
155
155
|
* const foir = createFoirClient({ apiUrl, dispatch, generated });
|
|
156
156
|
* ```
|
|
157
157
|
*
|
|
158
|
-
* The endpoint calls Foir AS the operation, with the
|
|
158
|
+
* The endpoint calls Foir AS the operation, with the grants the operation
|
|
159
159
|
* declared, and holds no key of its own. api-public accepts the token as a
|
|
160
160
|
* caller in its own right: it builds the schema overlay from the token's
|
|
161
|
-
*
|
|
161
|
+
* grants, and it reads the organization and the customer from the
|
|
162
162
|
* token's claims. So nothing a key client configures beside its key
|
|
163
163
|
* (`bearerToken`, `orgId`, `channel`) means anything here, and none of it is
|
|
164
164
|
* accepted.
|
package/dist/next.js
CHANGED
|
@@ -921,6 +921,9 @@ var AUTH_OPS = {
|
|
|
921
921
|
// Post-credential self reads.
|
|
922
922
|
currentUser: "currentUser",
|
|
923
923
|
myProviderToken: "myProviderToken",
|
|
924
|
+
// What the calling credential may do, and for a signed-in customer the roles
|
|
925
|
+
// they hold. `./access` reads it.
|
|
926
|
+
whoami: "whoami",
|
|
924
927
|
// Methods.
|
|
925
928
|
customerLogin: "customerLogin",
|
|
926
929
|
customerRegister: "customerRegister",
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* reaches a cross-site frame, so the console hands the page a short-lived token
|
|
6
6
|
* instead, by message, to your placement's origin only. The token is the
|
|
7
7
|
* operator looking at the page, acting through your app at the level your app
|
|
8
|
-
* is installed at, carrying
|
|
9
|
-
*
|
|
8
|
+
* is installed at, carrying what your placement declares and this operator
|
|
9
|
+
* holds, as grants in the permission catalogue's names. Your page runs no
|
|
10
|
+
* login of its own.
|
|
10
11
|
*
|
|
11
12
|
* ```ts
|
|
12
13
|
* import { connectPlacement, FOIR_CONSOLE_ORIGINS } from '@foir/sdk/placement';
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
* apiUrl: 'https://api.foir.dev',
|
|
16
17
|
* consoleOrigins: FOIR_CONSOLE_ORIGINS.production,
|
|
17
18
|
* });
|
|
18
|
-
* if (session.can('records:write:preorder')) showEditButtons();
|
|
19
|
+
* if (session.can('records:write.all:preorder')) showEditButtons();
|
|
19
20
|
* const data = await session.request('{ preorders { items { id } } }');
|
|
20
21
|
* ```
|
|
21
22
|
*
|
|
@@ -52,14 +53,38 @@ export interface PlacementGrant {
|
|
|
52
53
|
expiresAt: string;
|
|
53
54
|
/** The organisation the page acts at: your app's install. Empty for the project. */
|
|
54
55
|
orgId: string;
|
|
55
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* What the token carries: the grants your placement declares that this
|
|
58
|
+
* operator holds, in the permission catalogue's names. A grant the operator
|
|
59
|
+
* holds part of is carried as that part, so ask with `can` and not by
|
|
60
|
+
* looking for a spelling here.
|
|
61
|
+
*/
|
|
62
|
+
grants: string[];
|
|
63
|
+
/** Each grant your placement declares that the token does not carry whole, and why. */
|
|
64
|
+
withheldGrants: {
|
|
65
|
+
grant: string;
|
|
66
|
+
reason: string;
|
|
67
|
+
}[];
|
|
68
|
+
/**
|
|
69
|
+
* @deprecated Read `grants`. The same list under the name it had before a
|
|
70
|
+
* placement declared grants.
|
|
71
|
+
*/
|
|
56
72
|
grantedScopes: string[];
|
|
57
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* @deprecated Read `withheldGrants`. The same list under the name it had
|
|
75
|
+
* before a placement declared grants.
|
|
76
|
+
*/
|
|
58
77
|
withheld: {
|
|
59
78
|
scope: string;
|
|
60
79
|
reason: string;
|
|
61
80
|
}[];
|
|
62
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* What a console sends with a token. A console of the release before sends
|
|
84
|
+
* only `grantedScopes` and `withheld`, and the session reads `grants` from
|
|
85
|
+
* those.
|
|
86
|
+
*/
|
|
87
|
+
type PlacementGrantMessage = Pick<PlacementGrant, 'accessToken' | 'expiresAt' | 'orgId'> & Partial<Pick<PlacementGrant, 'grants' | 'withheldGrants' | 'grantedScopes' | 'withheld'>>;
|
|
63
88
|
/** How the console looks, for an embedded page to match. */
|
|
64
89
|
export interface PlacementAppearance {
|
|
65
90
|
/**
|
|
@@ -71,7 +96,7 @@ export interface PlacementAppearance {
|
|
|
71
96
|
/** A message the console sends an embedded page. */
|
|
72
97
|
export type PlacementHostMessage = {
|
|
73
98
|
type: typeof PLACEMENT_MESSAGE.token;
|
|
74
|
-
grant:
|
|
99
|
+
grant: PlacementGrantMessage;
|
|
75
100
|
} | {
|
|
76
101
|
type: typeof PLACEMENT_MESSAGE.refused;
|
|
77
102
|
reason: string;
|
|
@@ -138,8 +163,14 @@ export interface PlacementSession {
|
|
|
138
163
|
* older than this). Kept current as the operator changes it.
|
|
139
164
|
*/
|
|
140
165
|
readonly appearance: PlacementAppearance | null;
|
|
141
|
-
/**
|
|
142
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Whether the token covers this grant, to hide or grey out what the operator
|
|
168
|
+
* cannot do. It answers as Foir's API would: a grant for every model covers
|
|
169
|
+
* the same grant for one, and one for every owner's rows covers a person's
|
|
170
|
+
* own. `can('records:read.all:order')` is true for a token that carries
|
|
171
|
+
* `records:read.all`.
|
|
172
|
+
*/
|
|
173
|
+
can(grant: string): boolean;
|
|
143
174
|
/** A GraphQL request to Foir's API as the operator, through your app. */
|
|
144
175
|
request<TResult = unknown>(document: string | {
|
|
145
176
|
toString(): string;
|
|
@@ -156,3 +187,4 @@ export interface PlacementSession {
|
|
|
156
187
|
* console gives no token, or when none arrives in time.
|
|
157
188
|
*/
|
|
158
189
|
export declare function connectPlacement(options: ConnectPlacementOptions): Promise<PlacementSession>;
|
|
190
|
+
export {};
|