@foir/sdk 0.21.0 → 0.22.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 +84 -1
- package/README.md +9 -9
- package/dist/app/client.d.ts +90 -84
- package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +14 -13
- package/dist/app/dispatch.d.ts +5 -5
- package/dist/app/errors.d.ts +11 -11
- package/dist/app/index.d.ts +4 -4
- package/dist/app/naming.d.ts +1 -1
- package/dist/app/ticket.d.ts +2 -2
- package/dist/app/vocabulary.generated.d.ts +2 -2
- package/dist/app-contract.js +3 -3
- package/dist/app.js +92 -90
- package/dist/auth/access.d.ts +19 -19
- package/dist/auth/auth.d.ts +216 -114
- package/dist/auth/hosted.d.ts +14 -14
- package/dist/auth/index.d.ts +7 -7
- package/dist/auth/operations.d.ts +128 -32
- package/dist/auth/ops.d.ts +39 -21
- package/dist/auth/session-client.d.ts +3 -3
- package/dist/auth-session.js +3 -3
- package/dist/auth.js +234 -135
- package/dist/client.js +4 -4
- package/dist/data/builtins.d.ts +26 -26
- package/dist/data/client.d.ts +2 -2
- package/dist/data/decimal.d.ts +1 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/manifest.d.ts +1 -1
- package/dist/data/schema.d.ts +1 -1
- package/dist/data/transport.d.ts +10 -10
- package/dist/data/ws-transport.d.ts +1 -1
- package/dist/data.js +6 -6
- package/dist/graphql/content-client.d.ts +13 -13
- package/dist/live/drift.d.ts +1 -1
- package/dist/media/screen-capture.d.ts +1 -1
- package/dist/next/index.d.ts +2 -2
- package/dist/next/session.d.ts +10 -10
- package/dist/next.js +77 -56
- package/dist/placement/index.d.ts +13 -13
- package/dist/placement.js +9 -6
- package/dist/push.js +61 -40
- package/dist/react/FoirFeedback.d.ts +2 -2
- package/dist/react/FoirProvider.d.ts +23 -23
- package/dist/react/SignIn.d.ts +4 -4
- package/dist/react/UserButton.d.ts +1 -1
- package/dist/react/feedback-notes.d.ts +4 -4
- package/dist/react/index.d.ts +3 -3
- package/dist/react.js +119 -95
- package/dist/realtime/index.d.ts +1 -1
- package/dist/server/callback-client.d.ts +4 -4
- package/dist/server/events-client.d.ts +16 -16
- package/dist/server/operation-types.d.ts +8 -8
- package/dist/server/require-scoped-token.d.ts +3 -3
- package/dist/server/types.d.ts +1 -1
- package/dist/server/verify-scoped-token.d.ts +3 -3
- package/dist/server.js +16 -13
- package/dist/transport/foir-http.d.ts +13 -13
- package/dist/types/index.d.ts +2 -2
- package/package.json +3 -3
package/dist/react.js
CHANGED
|
@@ -205,8 +205,8 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
205
205
|
downgrade;
|
|
206
206
|
constructor(downgrade) {
|
|
207
207
|
super(
|
|
208
|
-
`[foir] your
|
|
209
|
-
{ graphQLErrors: [{ message: "
|
|
208
|
+
`[foir] your user token was ${downgrade.userToken} (${downgrade.reason}), so the request was served as ${downgrade.servedAs}`,
|
|
209
|
+
{ graphQLErrors: [{ message: "user token rejected", extensions: { code: "AUTH_DOWNGRADED" } }] }
|
|
210
210
|
);
|
|
211
211
|
this.name = "FoirAuthDowngradedError";
|
|
212
212
|
this.downgrade = downgrade;
|
|
@@ -214,7 +214,7 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
214
214
|
};
|
|
215
215
|
var FoirSessionExpiredError = class extends FoirRequestError {
|
|
216
216
|
constructor(options) {
|
|
217
|
-
super("[foir] the
|
|
217
|
+
super("[foir] the user session has expired and could not be refreshed: sign in again", {
|
|
218
218
|
cause: options?.cause,
|
|
219
219
|
graphQLErrors: [{ message: "session expired", extensions: { code: "SESSION_EXPIRED" } }]
|
|
220
220
|
});
|
|
@@ -231,7 +231,7 @@ function reportAuthDowngrade(envelope) {
|
|
|
231
231
|
if (warnedDowngrade === downgrade.reason) return;
|
|
232
232
|
warnedDowngrade = downgrade.reason;
|
|
233
233
|
console.warn(
|
|
234
|
-
`[foir] your
|
|
234
|
+
`[foir] your user token was ${downgrade.userToken} (${downgrade.reason}) \u2014 this request was served as ${downgrade.servedAs}, so auth-targeted content resolved to its anonymous variant. ` + (downgrade.reason === "expired" ? "Re-exchange the token and retry." : "Check which credential you are sending.")
|
|
235
235
|
);
|
|
236
236
|
}
|
|
237
237
|
function refusePrunedDocument(json, options) {
|
|
@@ -950,42 +950,63 @@ var AUTH_OPS = {
|
|
|
950
950
|
authConfig: "authConfig",
|
|
951
951
|
authProviders: "authProviders",
|
|
952
952
|
defaultAuthProvider: "defaultAuthProvider",
|
|
953
|
-
|
|
953
|
+
signupContext: "signupContext",
|
|
954
954
|
// Post-credential self reads.
|
|
955
955
|
currentUser: "currentUser",
|
|
956
956
|
myProviderToken: "myProviderToken",
|
|
957
|
-
// What the calling credential may do, and for a signed-in
|
|
957
|
+
// What the calling credential may do, and for a signed-in user the roles
|
|
958
958
|
// they hold. `./access` reads it.
|
|
959
959
|
whoami: "whoami",
|
|
960
960
|
// Methods.
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
961
|
+
login: "login",
|
|
962
|
+
register: "register",
|
|
963
|
+
requestOTP: "requestOTP",
|
|
964
|
+
loginWithOTP: "loginWithOTP",
|
|
965
965
|
// Session lifecycle.
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
// Password management and account lifecycle
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
966
|
+
refreshToken: "refreshToken",
|
|
967
|
+
logout: "logout",
|
|
968
|
+
joinWaitlist: "joinWaitlist",
|
|
969
|
+
// Password management and account lifecycle.
|
|
970
|
+
requestPasswordReset: "requestPasswordReset",
|
|
971
|
+
resetPassword: "resetPassword",
|
|
972
|
+
updatePassword: "updatePassword",
|
|
973
|
+
verifyEmail: "verifyEmail",
|
|
974
974
|
deleteMyAccount: "deleteMyAccount",
|
|
975
|
-
// The
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
//
|
|
980
|
-
//
|
|
975
|
+
// The user's own profile, and the apps they have agreed may act for them.
|
|
976
|
+
updateMyProfile: "updateMyProfile",
|
|
977
|
+
myAuthorizedApps: "myAuthorizedApps",
|
|
978
|
+
revokeAuthorizedApp: "revokeAuthorizedApp",
|
|
979
|
+
// Where they are signed in, their personal access tokens, and the
|
|
980
|
+
// providers they sign in with.
|
|
981
|
+
mySessions: "mySessions",
|
|
982
|
+
endMySession: "endMySession",
|
|
983
|
+
signOutEverywhere: "signOutEverywhere",
|
|
984
|
+
createPersonalAccessToken: "createPersonalAccessToken",
|
|
985
|
+
mySignInMethods: "mySignInMethods",
|
|
986
|
+
connectSignInMethod: "connectSignInMethod",
|
|
987
|
+
disconnectSignInMethod: "disconnectSignInMethod",
|
|
988
|
+
// Their second factors. A change needs one proven first, by a step-up.
|
|
989
|
+
myFactors: "myFactors",
|
|
990
|
+
beginStepUp: "beginStepUp",
|
|
991
|
+
completeStepUp: "completeStepUp",
|
|
992
|
+
beginFactorEnrollment: "beginFactorEnrollment",
|
|
993
|
+
completeFactorEnrollment: "completeFactorEnrollment",
|
|
994
|
+
removeFactor: "removeFactor",
|
|
995
|
+
makeRecoveryCodes: "makeRecoveryCodes",
|
|
996
|
+
// The organizations the caller may stand in. Nothing selects one: a request
|
|
997
|
+
// names the organization it acts in (`orgId` or `orgUrl` below), and the
|
|
998
|
+
// platform checks the caller stands there every time.
|
|
981
999
|
myOrganizations: "myOrganizations",
|
|
982
|
-
selectMyOrganization: "selectMyOrganization",
|
|
983
1000
|
// Founding one. Present in a manifest only when the project opted in.
|
|
984
|
-
|
|
1001
|
+
registerOrganization: "registerOrganization",
|
|
985
1002
|
// External identity providers.
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1003
|
+
loginWithProvider: "loginWithProvider",
|
|
1004
|
+
providerCallback: "providerCallback",
|
|
1005
|
+
providerVerifyOTP: "providerVerifyOTP",
|
|
1006
|
+
// A provider sign-in that names an address somebody already holds, waiting
|
|
1007
|
+
// for the code sent to it.
|
|
1008
|
+
confirmProviderLink: "confirmProviderLink",
|
|
1009
|
+
resendProviderLinkCode: "resendProviderLinkCode"
|
|
989
1010
|
};
|
|
990
1011
|
function authClientConfig(env, options) {
|
|
991
1012
|
const apiKey = resolveFoirApiKey(env);
|
|
@@ -1048,28 +1069,28 @@ function stripUndefined(args) {
|
|
|
1048
1069
|
// src/auth/auth.ts
|
|
1049
1070
|
var TOKEN_KEY = "foir:token";
|
|
1050
1071
|
var REFRESH_KEY = "foir:refreshToken";
|
|
1051
|
-
var
|
|
1072
|
+
var USER_ID_KEY = "foir:userId";
|
|
1052
1073
|
var EMAIL_KEY = "foir:email";
|
|
1053
|
-
async function
|
|
1054
|
-
return authOp(env, AUTH_OPS.
|
|
1074
|
+
async function login(env, vars) {
|
|
1075
|
+
return authOp(env, AUTH_OPS.login, vars);
|
|
1055
1076
|
}
|
|
1056
|
-
async function
|
|
1057
|
-
return authOp(env, AUTH_OPS.
|
|
1077
|
+
async function register(env, vars) {
|
|
1078
|
+
return authOp(env, AUTH_OPS.register, vars);
|
|
1058
1079
|
}
|
|
1059
|
-
async function
|
|
1060
|
-
return authOp(env, AUTH_OPS.
|
|
1080
|
+
async function requestOTP(env, email) {
|
|
1081
|
+
return authOp(env, AUTH_OPS.requestOTP, { email });
|
|
1061
1082
|
}
|
|
1062
|
-
async function
|
|
1063
|
-
return authOp(env, AUTH_OPS.
|
|
1083
|
+
async function loginWithOTP(env, vars) {
|
|
1084
|
+
return authOp(env, AUTH_OPS.loginWithOTP, vars);
|
|
1064
1085
|
}
|
|
1065
|
-
async function
|
|
1066
|
-
return authOp(env, AUTH_OPS.
|
|
1086
|
+
async function refreshTokens(env, refreshToken) {
|
|
1087
|
+
return authOp(env, AUTH_OPS.refreshToken, { refreshToken });
|
|
1067
1088
|
}
|
|
1068
|
-
async function
|
|
1069
|
-
return authOp(env, AUTH_OPS.
|
|
1089
|
+
async function loginWithProvider(env, input) {
|
|
1090
|
+
return authOp(env, AUTH_OPS.loginWithProvider, { input });
|
|
1070
1091
|
}
|
|
1071
|
-
async function
|
|
1072
|
-
return authOp(env, AUTH_OPS.
|
|
1092
|
+
async function providerCallback(env, input) {
|
|
1093
|
+
return authOp(env, AUTH_OPS.providerCallback, { input });
|
|
1073
1094
|
}
|
|
1074
1095
|
async function getAuthProviders(env) {
|
|
1075
1096
|
return authOp(env, AUTH_OPS.authProviders);
|
|
@@ -1077,42 +1098,42 @@ async function getAuthProviders(env) {
|
|
|
1077
1098
|
async function saveSession(adapter, result) {
|
|
1078
1099
|
await adapter.set(TOKEN_KEY, result.token);
|
|
1079
1100
|
await adapter.set(REFRESH_KEY, result.refreshToken);
|
|
1080
|
-
if (!result.
|
|
1081
|
-
return await
|
|
1101
|
+
if (!result.userId) {
|
|
1102
|
+
return await getSessionUser(adapter) ?? { id: "", email: "" };
|
|
1082
1103
|
}
|
|
1083
|
-
await adapter.set(
|
|
1104
|
+
await adapter.set(USER_ID_KEY, result.userId);
|
|
1084
1105
|
await adapter.set(EMAIL_KEY, result.email);
|
|
1085
|
-
return { id: result.
|
|
1106
|
+
return { id: result.userId, email: result.email };
|
|
1086
1107
|
}
|
|
1087
1108
|
async function clearSession(adapter) {
|
|
1088
1109
|
await adapter.unset(TOKEN_KEY);
|
|
1089
1110
|
await adapter.unset(REFRESH_KEY);
|
|
1090
|
-
await adapter.unset(
|
|
1111
|
+
await adapter.unset(USER_ID_KEY);
|
|
1091
1112
|
await adapter.unset(EMAIL_KEY);
|
|
1092
1113
|
}
|
|
1093
1114
|
async function getSessionToken(adapter) {
|
|
1094
1115
|
return await adapter.get(TOKEN_KEY) ?? null;
|
|
1095
1116
|
}
|
|
1096
|
-
async function
|
|
1097
|
-
const id = await adapter.get(
|
|
1117
|
+
async function getSessionUser(adapter) {
|
|
1118
|
+
const id = await adapter.get(USER_ID_KEY) ?? null;
|
|
1098
1119
|
const email = await adapter.get(EMAIL_KEY) ?? null;
|
|
1099
1120
|
if (!id) return null;
|
|
1100
1121
|
return { id, email: email ?? "" };
|
|
1101
1122
|
}
|
|
1102
|
-
async function
|
|
1103
|
-
return saveSession(adapter, await
|
|
1123
|
+
async function signInWithPassword(adapter, env, vars) {
|
|
1124
|
+
return saveSession(adapter, await login(env, vars));
|
|
1104
1125
|
}
|
|
1105
|
-
async function
|
|
1106
|
-
return saveSession(adapter, await
|
|
1126
|
+
async function signUpWithPassword(adapter, env, vars) {
|
|
1127
|
+
return saveSession(adapter, await register(env, vars));
|
|
1107
1128
|
}
|
|
1108
|
-
async function
|
|
1109
|
-
return saveSession(adapter, await
|
|
1129
|
+
async function signInWithOTP(adapter, env, vars) {
|
|
1130
|
+
return saveSession(adapter, await loginWithOTP(env, vars));
|
|
1110
1131
|
}
|
|
1111
1132
|
async function refreshSession(adapter, env) {
|
|
1112
1133
|
const refreshToken = await adapter.get(REFRESH_KEY) ?? null;
|
|
1113
1134
|
if (!refreshToken) return null;
|
|
1114
1135
|
try {
|
|
1115
|
-
const result = await
|
|
1136
|
+
const result = await refreshTokens(env, refreshToken);
|
|
1116
1137
|
await saveSession(adapter, result);
|
|
1117
1138
|
return { token: result.token, refreshToken: result.refreshToken };
|
|
1118
1139
|
} catch {
|
|
@@ -1256,7 +1277,8 @@ var GRANT_TABLE = [
|
|
|
1256
1277
|
["roles:write", false, [], []],
|
|
1257
1278
|
["projects:read", false, [], []],
|
|
1258
1279
|
["projects:write", false, [], []],
|
|
1259
|
-
["project:
|
|
1280
|
+
["project:stand", false, [], []],
|
|
1281
|
+
["project:manage", false, [], ["project:stand", "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:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "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", "audit:read"]],
|
|
1260
1282
|
["projects:create", false, [], []],
|
|
1261
1283
|
["projects:delete", false, [], []],
|
|
1262
1284
|
["tenants:read", false, [], []],
|
|
@@ -1269,12 +1291,14 @@ var GRANT_TABLE = [
|
|
|
1269
1291
|
["usage:read", false, [], []],
|
|
1270
1292
|
["controlkeys:read", false, [], []],
|
|
1271
1293
|
["controlkeys:write", false, [], []],
|
|
1294
|
+
["tenantsigninproviders:read", false, [], []],
|
|
1295
|
+
["tenantsigninproviders:write", false, [], []],
|
|
1272
1296
|
["tenanthooks:read", false, [], []],
|
|
1273
1297
|
["tenanthooks:write", false, [], []],
|
|
1274
1298
|
["apps:install", false, [], []],
|
|
1275
|
-
["tenant:manage", false, [], ["project:manage", "projects:create", "projects:delete", "tenants:read", "tenants:write", "tenants:delete", "export:run", "billing:read", "billing:manage", "usage:read", "controlkeys:read", "controlkeys:write", "tenanthooks:read", "tenanthooks:write", "apps:install"]],
|
|
1299
|
+
["tenant:manage", false, [], ["project:manage", "projects:create", "projects:delete", "tenants:read", "tenants:write", "tenants:delete", "export:run", "billing:read", "billing:manage", "usage:read", "controlkeys:read", "controlkeys:write", "tenanthooks:read", "tenanthooks:write", "tenantsigninproviders:read", "tenantsigninproviders:write", "apps:install"]],
|
|
1276
1300
|
["platform:read", false, [], []],
|
|
1277
|
-
["platform:manage", false, [], ["tenant:manage", "platform:read", "platform:create_workspace"
|
|
1301
|
+
["platform:manage", false, [], ["tenant:manage", "platform:read", "platform:create_workspace"]],
|
|
1278
1302
|
["platform:create_workspace", false, [], []],
|
|
1279
1303
|
["users:read", false, ["own", "named", "all"], []],
|
|
1280
1304
|
["users:write", false, ["own", "all"], []],
|
|
@@ -1373,15 +1397,15 @@ function holds(held, required) {
|
|
|
1373
1397
|
|
|
1374
1398
|
// src/auth/access.ts
|
|
1375
1399
|
function accessFrom(who) {
|
|
1376
|
-
const
|
|
1377
|
-
if (!
|
|
1400
|
+
const user = who?.credential?.user;
|
|
1401
|
+
if (!user?.id) return null;
|
|
1378
1402
|
return {
|
|
1379
|
-
|
|
1380
|
-
roles: Array.isArray(
|
|
1403
|
+
userId: user.id,
|
|
1404
|
+
roles: Array.isArray(user.roles) ? [...user.roles] : null,
|
|
1381
1405
|
scopes: [...who?.authority?.effective ?? []]
|
|
1382
1406
|
};
|
|
1383
1407
|
}
|
|
1384
|
-
async function
|
|
1408
|
+
async function userAccess(env, options) {
|
|
1385
1409
|
return accessFrom(await authOp(env, AUTH_OPS.whoami, void 0, options));
|
|
1386
1410
|
}
|
|
1387
1411
|
async function getSessionAccess(adapter, env) {
|
|
@@ -1461,14 +1485,14 @@ function createFoirSessionClient(config) {
|
|
|
1461
1485
|
} catch {
|
|
1462
1486
|
return cached;
|
|
1463
1487
|
}
|
|
1464
|
-
if (!json.access_token || !json.
|
|
1488
|
+
if (!json.access_token || !json.user_id) return cached;
|
|
1465
1489
|
const expiresIn = typeof json.expires_in === "number" ? json.expires_in : 0;
|
|
1466
1490
|
const next = {
|
|
1467
1491
|
token: json.access_token,
|
|
1468
1492
|
expiresAt: Date.now() + expiresIn * 1e3,
|
|
1469
|
-
user: {
|
|
1493
|
+
user: { userId: json.user_id, email: json.email ?? "" }
|
|
1470
1494
|
};
|
|
1471
|
-
const changed = cached?.user.
|
|
1495
|
+
const changed = cached?.user.userId !== next.user.userId;
|
|
1472
1496
|
cached = next;
|
|
1473
1497
|
scheduleRefresh(next.expiresAt);
|
|
1474
1498
|
if (changed) emit(next.user);
|
|
@@ -1569,13 +1593,13 @@ function FoirProvider({
|
|
|
1569
1593
|
const adapterRef = useRef(adapter ?? createLocalStorageAdapter());
|
|
1570
1594
|
if (adapter && adapter !== adapterRef.current) adapterRef.current = adapter;
|
|
1571
1595
|
const [status, setStatus] = useState("loading");
|
|
1572
|
-
const [
|
|
1596
|
+
const [user, setUser] = useState(null);
|
|
1573
1597
|
const [access, setAccess] = useState(null);
|
|
1574
1598
|
const [accessStatus, setAccessStatus] = useState("idle");
|
|
1575
1599
|
const accessRead = useRef(0);
|
|
1576
1600
|
const accessPending = useRef(null);
|
|
1577
1601
|
const apply = useCallback((c) => {
|
|
1578
|
-
|
|
1602
|
+
setUser(c);
|
|
1579
1603
|
setStatus(c ? "authenticated" : "unauthenticated");
|
|
1580
1604
|
accessRead.current += 1;
|
|
1581
1605
|
setAccess(null);
|
|
@@ -1600,10 +1624,10 @@ function FoirProvider({
|
|
|
1600
1624
|
void (async () => {
|
|
1601
1625
|
if (hosted) {
|
|
1602
1626
|
const u = await hosted.getUser();
|
|
1603
|
-
if (!cancelled) apply(u ? { id: u.
|
|
1627
|
+
if (!cancelled) apply(u ? { id: u.userId, email: u.email } : null);
|
|
1604
1628
|
return;
|
|
1605
1629
|
}
|
|
1606
|
-
const c = await
|
|
1630
|
+
const c = await getSessionUser(adapterRef.current);
|
|
1607
1631
|
if (!cancelled) apply(c);
|
|
1608
1632
|
})();
|
|
1609
1633
|
return () => {
|
|
@@ -1618,7 +1642,7 @@ function FoirProvider({
|
|
|
1618
1642
|
(action) => {
|
|
1619
1643
|
if (hosted) {
|
|
1620
1644
|
throw new Error(
|
|
1621
|
-
`[foir] ${action}() is not available in hostedSession mode \u2014 the
|
|
1645
|
+
`[foir] ${action}() is not available in hostedSession mode \u2014 the user signs in at your Foir login domain. Redirect to hostedSignInUrl() instead.`
|
|
1622
1646
|
);
|
|
1623
1647
|
}
|
|
1624
1648
|
},
|
|
@@ -1627,16 +1651,16 @@ function FoirProvider({
|
|
|
1627
1651
|
const signIn = useCallback(
|
|
1628
1652
|
async (vars) => {
|
|
1629
1653
|
assertTokenMode("signIn");
|
|
1630
|
-
const c = await
|
|
1654
|
+
const c = await signInWithPassword(adapterRef.current, envRef.current, vars);
|
|
1631
1655
|
apply(c);
|
|
1632
1656
|
return c;
|
|
1633
1657
|
},
|
|
1634
1658
|
[apply, assertTokenMode]
|
|
1635
1659
|
);
|
|
1636
|
-
const
|
|
1660
|
+
const register2 = useCallback(
|
|
1637
1661
|
async (vars) => {
|
|
1638
1662
|
assertTokenMode("register");
|
|
1639
|
-
const c = await
|
|
1663
|
+
const c = await signUpWithPassword(adapterRef.current, envRef.current, vars);
|
|
1640
1664
|
apply(c);
|
|
1641
1665
|
return c;
|
|
1642
1666
|
},
|
|
@@ -1645,14 +1669,14 @@ function FoirProvider({
|
|
|
1645
1669
|
const requestOtp = useCallback(
|
|
1646
1670
|
(email) => {
|
|
1647
1671
|
assertTokenMode("requestOtp");
|
|
1648
|
-
return
|
|
1672
|
+
return requestOTP(envRef.current, email);
|
|
1649
1673
|
},
|
|
1650
1674
|
[assertTokenMode]
|
|
1651
1675
|
);
|
|
1652
1676
|
const signInWithOtp = useCallback(
|
|
1653
1677
|
async (vars) => {
|
|
1654
1678
|
assertTokenMode("signInWithOtp");
|
|
1655
|
-
const c = await
|
|
1679
|
+
const c = await signInWithOTP(adapterRef.current, envRef.current, vars);
|
|
1656
1680
|
apply(c);
|
|
1657
1681
|
return c;
|
|
1658
1682
|
},
|
|
@@ -1661,7 +1685,7 @@ function FoirProvider({
|
|
|
1661
1685
|
const signInWithProvider = useCallback(
|
|
1662
1686
|
async (providerKey, opts) => {
|
|
1663
1687
|
assertTokenMode("signInWithProvider");
|
|
1664
|
-
const res = await
|
|
1688
|
+
const res = await loginWithProvider(envRef.current, {
|
|
1665
1689
|
providerKey,
|
|
1666
1690
|
...opts
|
|
1667
1691
|
});
|
|
@@ -1674,7 +1698,7 @@ function FoirProvider({
|
|
|
1674
1698
|
const completeProviderLogin = useCallback(
|
|
1675
1699
|
async (input) => {
|
|
1676
1700
|
assertTokenMode("completeProviderLogin");
|
|
1677
|
-
const result = await
|
|
1701
|
+
const result = await providerCallback(envRef.current, input);
|
|
1678
1702
|
const c = await saveSession(adapterRef.current, result);
|
|
1679
1703
|
apply(c);
|
|
1680
1704
|
return c;
|
|
@@ -1710,7 +1734,7 @@ function FoirProvider({
|
|
|
1710
1734
|
const pending = (async () => {
|
|
1711
1735
|
try {
|
|
1712
1736
|
const next = hosted ? await hosted.getToken().then(
|
|
1713
|
-
(bearerToken) => bearerToken ?
|
|
1737
|
+
(bearerToken) => bearerToken ? userAccess(envRef.current, { bearerToken }) : null
|
|
1714
1738
|
) : await getSessionAccess(adapterRef.current, envRef.current);
|
|
1715
1739
|
if (accessRead.current !== read) return;
|
|
1716
1740
|
setAccess(next);
|
|
@@ -1745,7 +1769,7 @@ function FoirProvider({
|
|
|
1745
1769
|
const value = useMemo(
|
|
1746
1770
|
() => ({
|
|
1747
1771
|
status,
|
|
1748
|
-
|
|
1772
|
+
user,
|
|
1749
1773
|
access,
|
|
1750
1774
|
accessStatus,
|
|
1751
1775
|
loadAccess,
|
|
@@ -1753,7 +1777,7 @@ function FoirProvider({
|
|
|
1753
1777
|
env,
|
|
1754
1778
|
adapter: adapterRef.current,
|
|
1755
1779
|
signIn,
|
|
1756
|
-
register,
|
|
1780
|
+
register: register2,
|
|
1757
1781
|
requestOtp,
|
|
1758
1782
|
signInWithOtp,
|
|
1759
1783
|
signInWithProvider,
|
|
@@ -1766,14 +1790,14 @@ function FoirProvider({
|
|
|
1766
1790
|
}),
|
|
1767
1791
|
[
|
|
1768
1792
|
status,
|
|
1769
|
-
|
|
1793
|
+
user,
|
|
1770
1794
|
access,
|
|
1771
1795
|
accessStatus,
|
|
1772
1796
|
loadAccess,
|
|
1773
1797
|
ensureAccess,
|
|
1774
1798
|
env,
|
|
1775
1799
|
signIn,
|
|
1776
|
-
|
|
1800
|
+
register2,
|
|
1777
1801
|
requestOtp,
|
|
1778
1802
|
signInWithOtp,
|
|
1779
1803
|
signInWithProvider,
|
|
@@ -1795,16 +1819,16 @@ function useFoirContext() {
|
|
|
1795
1819
|
return ctx;
|
|
1796
1820
|
}
|
|
1797
1821
|
function useFoirSession() {
|
|
1798
|
-
const { status,
|
|
1822
|
+
const { status, user } = useFoirContext();
|
|
1799
1823
|
return {
|
|
1800
1824
|
status,
|
|
1801
|
-
|
|
1825
|
+
user,
|
|
1802
1826
|
isLoading: status === "loading",
|
|
1803
1827
|
isAuthenticated: status === "authenticated"
|
|
1804
1828
|
};
|
|
1805
1829
|
}
|
|
1806
1830
|
function useFoirUser() {
|
|
1807
|
-
return useFoirContext().
|
|
1831
|
+
return useFoirContext().user;
|
|
1808
1832
|
}
|
|
1809
1833
|
function useFoirAccess() {
|
|
1810
1834
|
const { status, access, accessStatus, loadAccess, ensureAccess } = useFoirContext();
|
|
@@ -1827,7 +1851,7 @@ function useFoirAccess() {
|
|
|
1827
1851
|
function useFoirAuth() {
|
|
1828
1852
|
const {
|
|
1829
1853
|
signIn,
|
|
1830
|
-
register,
|
|
1854
|
+
register: register2,
|
|
1831
1855
|
requestOtp,
|
|
1832
1856
|
signInWithOtp,
|
|
1833
1857
|
signInWithProvider,
|
|
@@ -1840,7 +1864,7 @@ function useFoirAuth() {
|
|
|
1840
1864
|
} = useFoirContext();
|
|
1841
1865
|
return {
|
|
1842
1866
|
signIn,
|
|
1843
|
-
register,
|
|
1867
|
+
register: register2,
|
|
1844
1868
|
requestOtp,
|
|
1845
1869
|
signInWithOtp,
|
|
1846
1870
|
signInWithProvider,
|
|
@@ -1948,8 +1972,8 @@ function SignIn({
|
|
|
1948
1972
|
if (hostedUrl) {
|
|
1949
1973
|
return /* @__PURE__ */ jsx3("div", { className, "data-foir-signin": true, children: /* @__PURE__ */ jsx3("a", { href: hostedUrl, "data-foir-hosted-login": true, style: styled ? submitStyle : void 0, children: "Sign in" }) });
|
|
1950
1974
|
}
|
|
1951
|
-
const finish = (
|
|
1952
|
-
onSignIn?.(
|
|
1975
|
+
const finish = (user) => {
|
|
1976
|
+
onSignIn?.(user);
|
|
1953
1977
|
if (afterSignInUrl && typeof window !== "undefined") {
|
|
1954
1978
|
window.location.assign(afterSignInUrl);
|
|
1955
1979
|
}
|
|
@@ -2225,7 +2249,7 @@ async function uploadFeedbackAttachment(client, screenshot, fetchImpl = globalTh
|
|
|
2225
2249
|
const files = client.ns.files;
|
|
2226
2250
|
if (!files) {
|
|
2227
2251
|
throw new Error(
|
|
2228
|
-
"[foir] this client cannot reach the files namespace, so a screenshot cannot be uploaded. Attaching one needs `files:write` on the key and on the signed-in
|
|
2252
|
+
"[foir] this client cannot reach the files namespace, so a screenshot cannot be uploaded. Attaching one needs `files:write` on the key and on the signed-in user's role."
|
|
2229
2253
|
);
|
|
2230
2254
|
}
|
|
2231
2255
|
const ticket = readUploadTicket(
|
|
@@ -2617,7 +2641,7 @@ function failureCopy(fault) {
|
|
|
2617
2641
|
function reportFault(fault, err, action) {
|
|
2618
2642
|
if (fault.kind === "scope") {
|
|
2619
2643
|
console.error(
|
|
2620
|
-
`[foir] <FoirFeedback>: the API refused with "missing required scope: ${fault.scope}". This is not a bad API key: the key reached the surface. The signed-in
|
|
2644
|
+
`[foir] <FoirFeedback>: the API refused with "missing required scope: ${fault.scope}". This is not a bad API key: the key reached the surface. The signed-in USER's role does not grant ${fault.scope}, and a new project's default user role does not carry the notes scopes. Add it to the role that user holds, or to the project's default user role, and this works with no code change.`,
|
|
2621
2645
|
err
|
|
2622
2646
|
);
|
|
2623
2647
|
return;
|
package/dist/realtime/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* provider's url params current for whenever it next reconnects.
|
|
12
12
|
*
|
|
13
13
|
* Agnostic by design: token-agnostic (caller supplies `getToken` — an internal
|
|
14
|
-
* mint for
|
|
14
|
+
* mint for the console, a user access token for an app), persistence-agnostic
|
|
15
15
|
* (never persists), doc-schema-agnostic (caller owns the `Y.Doc` + shared types).
|
|
16
16
|
*
|
|
17
17
|
* `yjs` + `y-websocket` are OPTIONAL peer deps gated to this subpath, so a
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* pushes a DLQ entry.
|
|
13
13
|
* - `progress({pct, message, metadata})` — non-terminal update; the
|
|
14
14
|
* monotonic guard at the platform DB layer drops out-of-order packets.
|
|
15
|
-
* - `cancel()
|
|
16
|
-
*
|
|
15
|
+
* - `cancel()`: the extension cancels itself (rare; a run is normally
|
|
16
|
+
* cancelled from the console).
|
|
17
17
|
*
|
|
18
18
|
* Each call carries:
|
|
19
19
|
* - `Authorization: Bearer <callback.token>` — Ed25519 scoped token.
|
|
@@ -42,7 +42,7 @@ export interface CallbackResult {
|
|
|
42
42
|
*/
|
|
43
43
|
status: string;
|
|
44
44
|
/**
|
|
45
|
-
* True when
|
|
45
|
+
* True when someone cancelled the execution while the extension was
|
|
46
46
|
* processing — the platform refused to overwrite the cancelled state.
|
|
47
47
|
* Extensions should stop work and (optionally) call `fail('CANCELLED')`.
|
|
48
48
|
*/
|
|
@@ -66,7 +66,7 @@ export interface CallbackClient<TResult = unknown> {
|
|
|
66
66
|
details?: Record<string, unknown>;
|
|
67
67
|
}): Promise<CallbackResult>;
|
|
68
68
|
/**
|
|
69
|
-
* Returns `{cancelled: true}` when
|
|
69
|
+
* Returns `{cancelled: true}` when someone has cancelled it: the extension
|
|
70
70
|
* must stop work. `applied: false` means the monotonic guard dropped
|
|
71
71
|
* the update because pct was lower than the persisted value.
|
|
72
72
|
*/
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
* ```
|
|
19
19
|
*
|
|
20
20
|
* ---------------------------------------------------------------------------
|
|
21
|
-
*
|
|
21
|
+
* PROJECT SURFACE. Your app's users do not reach it.
|
|
22
22
|
* ---------------------------------------------------------------------------
|
|
23
23
|
*
|
|
24
24
|
* DECIDED 2026-08-04. The event log is reachable by an `sk_` api key
|
|
25
|
-
* and by
|
|
26
|
-
*
|
|
25
|
+
* and by a console session. It is NOT reachable by a user token, there is no
|
|
26
|
+
* user lane for it on api-public, and a `pk_` key can never be minted
|
|
27
27
|
* holding `events:read` at all.
|
|
28
28
|
*
|
|
29
29
|
* The reason is a predicate asymmetry rather than a policy preference.
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* owner, admin_owner, access_mode, grantee, comp_parent, read_borrow and
|
|
32
32
|
* slice_parent terms on top of it. A containment-only read over the log
|
|
33
33
|
* therefore describes rows whose underlying records the same caller could not
|
|
34
|
-
* read — and every row carries `actor`, naming which OTHER
|
|
34
|
+
* read — and every row carries `actor`, naming which OTHER user acted. In a
|
|
35
35
|
* project with owner confinement that is a per-record activity feed of other
|
|
36
36
|
* people's actions.
|
|
37
37
|
*
|
|
@@ -51,13 +51,13 @@
|
|
|
51
51
|
* built the client.
|
|
52
52
|
*
|
|
53
53
|
* 3. **There is no `bearerToken` option, and an `Authorization` header is
|
|
54
|
-
* refused.** On api-public a request presenting `sk_` plus a
|
|
55
|
-
* bearer resolves the
|
|
56
|
-
* from every
|
|
57
|
-
* shopper's token to
|
|
54
|
+
* refused.** On api-public a request presenting `sk_` plus a user
|
|
55
|
+
* bearer resolves the USER as the authority, and `events` is stripped
|
|
56
|
+
* from every user authority upstream — so attaching a signed-in
|
|
57
|
+
* shopper's token to a project read turns a working call into a 403 that
|
|
58
58
|
* reads like a scope problem. Refusing it here says what actually happened.
|
|
59
59
|
*
|
|
60
|
-
* Widening this later is easy. Teaching
|
|
60
|
+
* Widening this later is easy. Teaching users a capability they do not have
|
|
61
61
|
* is a support burden that cannot be retracted.
|
|
62
62
|
*
|
|
63
63
|
* ---------------------------------------------------------------------------
|
|
@@ -79,7 +79,7 @@ export declare const FOIR_EVENTS_PATH = "/v1/events";
|
|
|
79
79
|
/**
|
|
80
80
|
* The scope an `sk_` key must hold. Never mintable on a `pk_` — `events` is
|
|
81
81
|
* absent from the platform's `publicSafeScopeResources` — and never present in a
|
|
82
|
-
*
|
|
82
|
+
* user's resolved authority.
|
|
83
83
|
*/
|
|
84
84
|
export declare const FOIR_EVENTS_SCOPE = "events:read";
|
|
85
85
|
/**
|
|
@@ -91,9 +91,9 @@ export declare const FOIR_EVENTS_SCOPE = "events:read";
|
|
|
91
91
|
export declare const FOIR_EVENTS_RETENTION_DAYS = 90;
|
|
92
92
|
/** Who caused an event. `null` when the emit site attributed nobody. */
|
|
93
93
|
export interface FoirEventActor {
|
|
94
|
-
/** e.g. `"
|
|
94
|
+
/** e.g. `"user"`, `"api_key"`, `"system"`. */
|
|
95
95
|
kind?: string;
|
|
96
|
-
/** e.g. `"
|
|
96
|
+
/** e.g. `"user:usr_123"`. */
|
|
97
97
|
ref?: string;
|
|
98
98
|
[key: string]: unknown;
|
|
99
99
|
}
|
|
@@ -119,7 +119,7 @@ export interface FoirEvent {
|
|
|
119
119
|
* The event's own flat keys, plus `state` and `stateSchema`.
|
|
120
120
|
*
|
|
121
121
|
* `state` is the resource AS OF the event — self-contained, not a pointer to
|
|
122
|
-
* current state — for the
|
|
122
|
+
* current state — for the user, session, operation and workspace-plane
|
|
123
123
|
* families. It is null for `record.*` events, deliberately and permanently:
|
|
124
124
|
* `events.view` is plain tenant/project containment while `record.view`
|
|
125
125
|
* carries owner, grant, borrow and per-field terms, so a copy of record data
|
|
@@ -203,12 +203,12 @@ export interface FoirEventsClientConfig {
|
|
|
203
203
|
* An admin key holding `events:read`.
|
|
204
204
|
*
|
|
205
205
|
* A publishable or a server key is refused here rather than at the server:
|
|
206
|
-
* see the
|
|
206
|
+
* see the note at the top of this file.
|
|
207
207
|
*/
|
|
208
208
|
apiKey: string;
|
|
209
209
|
/**
|
|
210
|
-
* Extra headers. An `Authorization` header is refused: a
|
|
211
|
-
* flips api-public's resolved authority to the
|
|
210
|
+
* Extra headers. An `Authorization` header is refused: a user bearer
|
|
211
|
+
* flips api-public's resolved authority to the user, and no user
|
|
212
212
|
* authority can contain `events:read`.
|
|
213
213
|
*/
|
|
214
214
|
headers?: Readonly<Record<string, string>>;
|
|
@@ -147,17 +147,17 @@ export interface OperationContext {
|
|
|
147
147
|
/** Project ID */
|
|
148
148
|
projectId: string;
|
|
149
149
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
150
|
+
* Who started the run. Always present, and the empty string when nobody
|
|
151
|
+
* did, as for a hook's run.
|
|
152
152
|
*/
|
|
153
|
-
|
|
153
|
+
actorId: string;
|
|
154
154
|
/**
|
|
155
|
-
* The
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
155
|
+
* The user the run acts for, when it acts for one: the signed-in user who
|
|
156
|
+
* ran it, or the owner of the record whose event fired the hook. The
|
|
157
|
+
* dispatch token carries the same user, so a client built from it acts for
|
|
158
|
+
* them too.
|
|
159
159
|
*/
|
|
160
|
-
|
|
160
|
+
userId?: string;
|
|
161
161
|
/** When the platform built this body, RFC 3339 in UTC to the second. */
|
|
162
162
|
timestamp: string;
|
|
163
163
|
}
|