@cardstack/boxel-cli 0.12.0-unstable.12 → 0.12.0-unstable.13
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/bundled-test-harness/index.html +1 -1
- package/bundled-test-harness/tests/index.html +1 -1
- package/bundled-types/host-app/lib/secure-sign-in.ts +5 -3
- package/bundled-types/host-app/services/matrix-service.ts +3 -2
- package/dist/index.js +281 -281
- package/package.json +1 -1
- package/src/commands/keys/index.ts +36 -0
- package/src/commands/keys/manage.ts +214 -6
- package/src/lib/user-keys.ts +131 -0
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
keysClients,
|
|
13
13
|
keysConnect,
|
|
14
14
|
keysDelete,
|
|
15
|
+
keysLimits,
|
|
15
16
|
keysList,
|
|
16
17
|
keysReplace,
|
|
17
18
|
keysShare,
|
|
@@ -19,8 +20,10 @@ import {
|
|
|
19
20
|
keysShow,
|
|
20
21
|
keysSwap,
|
|
21
22
|
keysUnshare,
|
|
23
|
+
keysUsage,
|
|
22
24
|
runKeysCommand,
|
|
23
25
|
signInToManageKeys,
|
|
26
|
+
type LimitsOptions,
|
|
24
27
|
} from './manage.ts';
|
|
25
28
|
import {
|
|
26
29
|
keysApprove,
|
|
@@ -366,6 +369,39 @@ export function registerKeysCommand(program: Command): void {
|
|
|
366
369
|
runKeysCommand(options.json, () => keysShares(options)),
|
|
367
370
|
);
|
|
368
371
|
|
|
372
|
+
keys
|
|
373
|
+
.command('limits')
|
|
374
|
+
.description(
|
|
375
|
+
"Show or set the limits on how much of one of your keys calls from cards, the AI assistant and the CLI may spend: on a personal key, or with --realm on your share of one there. Each limit takes a number, or default for the platform's. Without a limit to change, shows the limits in force",
|
|
376
|
+
)
|
|
377
|
+
.argument('<name>', 'Your key, or with --realm the label of the share')
|
|
378
|
+
.option('--realm <url>', 'The realm the share is in')
|
|
379
|
+
.option(
|
|
380
|
+
'--rate <burst>/<refill>',
|
|
381
|
+
'Per caller: how many calls at once, and how many a minute refill, such as 25/15 (default for both puts them back)',
|
|
382
|
+
)
|
|
383
|
+
.option('--daily <n>', 'Calls a UTC day, across every caller')
|
|
384
|
+
.option(
|
|
385
|
+
'--monthly <n>',
|
|
386
|
+
'Calls a UTC month, across every caller (default: 30 daily budgets)',
|
|
387
|
+
)
|
|
388
|
+
.option('--json', 'Output JSON')
|
|
389
|
+
.action((name: string, options: LimitsOptions) =>
|
|
390
|
+
runKeysCommand(options.json, () => keysLimits(name, options)),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
keys
|
|
394
|
+
.command('usage')
|
|
395
|
+
.description(
|
|
396
|
+
"Show what one of your keys has served: this UTC day's and month's calls against its budgets, its last use, top callers, refusals by reason and the vendor's answers. With --realm, a share in that realm, for any of the realm's direct writers",
|
|
397
|
+
)
|
|
398
|
+
.argument('<name>', 'Your key, or with --realm the label of the share')
|
|
399
|
+
.option('--realm <url>', 'The realm the share is in')
|
|
400
|
+
.option('--json', 'Output JSON')
|
|
401
|
+
.action((name: string, options: { realm?: string; json?: boolean }) =>
|
|
402
|
+
runKeysCommand(options.json, () => keysUsage(name, options)),
|
|
403
|
+
);
|
|
404
|
+
|
|
369
405
|
keys
|
|
370
406
|
.command('clients')
|
|
371
407
|
.description(
|
|
@@ -29,13 +29,16 @@ import {
|
|
|
29
29
|
addUserKey,
|
|
30
30
|
canonicalRealmUrl,
|
|
31
31
|
deleteUserKey,
|
|
32
|
+
findShare,
|
|
32
33
|
findUserKey,
|
|
34
|
+
getKeyUsage,
|
|
33
35
|
listConnectableClients,
|
|
34
36
|
listOwnShares,
|
|
35
37
|
listRealmShares,
|
|
36
38
|
listUserKeys,
|
|
37
39
|
realmOpensToAnonymous,
|
|
38
40
|
replaceUserKey,
|
|
41
|
+
setKeyLimits,
|
|
39
42
|
setUserKeyAnonymousUse,
|
|
40
43
|
shareUserKey,
|
|
41
44
|
completeOAuthConnect,
|
|
@@ -43,7 +46,10 @@ import {
|
|
|
43
46
|
swapKeyIntoShare,
|
|
44
47
|
unshareUserKey,
|
|
45
48
|
type ConnectableClient,
|
|
49
|
+
type KeyLimitChanges,
|
|
50
|
+
type KeyLimits,
|
|
46
51
|
type KeyShare,
|
|
52
|
+
type KeyUsage,
|
|
47
53
|
type OAuthClientSettings,
|
|
48
54
|
type OAuthConnectOutcome,
|
|
49
55
|
type ServiceAccountDetails,
|
|
@@ -55,12 +61,14 @@ import { finishInSecureWindow, windowTimeout } from './approve.ts';
|
|
|
55
61
|
// put them into realms. Keys belong to the user, so only the share commands
|
|
56
62
|
// name a realm.
|
|
57
63
|
//
|
|
58
|
-
// Reading (`list`, `show`, `shares`, `clients`
|
|
59
|
-
// profile's ordinary realm-server
|
|
60
|
-
// a key (`add`, `replace`,
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
+
// Reading (`list`, `show`, `shares`, `clients`, `usage`, and `limits` with
|
|
65
|
+
// nothing to change) and unsharing use the profile's ordinary realm-server
|
|
66
|
+
// session. A change that stores or hands out a key (`add`, `replace`,
|
|
67
|
+
// `delete`, `share`, `swap`, `connect`) takes a key write ticket for that
|
|
68
|
+
// change, which only a session signed in on the secure origin can mint, so
|
|
69
|
+
// those commands first make sure the profile holds one, opening the secure
|
|
70
|
+
// window's sign-in in the browser when it doesn't. Changing a key's limits
|
|
71
|
+
// takes that session too, with no ticket.
|
|
64
72
|
//
|
|
65
73
|
// A key's value is typed at a masked prompt or read with `--stdin` or
|
|
66
74
|
// `--key-file`, never taken as an argument, and nothing prints one. A refusal
|
|
@@ -1017,6 +1025,206 @@ export async function keysShares(
|
|
|
1017
1025
|
return shares;
|
|
1018
1026
|
}
|
|
1019
1027
|
|
|
1028
|
+
export interface LimitsOptions {
|
|
1029
|
+
realm?: string;
|
|
1030
|
+
// `<burst>/<refill per minute>`; either side may be `default`.
|
|
1031
|
+
rate?: string;
|
|
1032
|
+
daily?: string;
|
|
1033
|
+
monthly?: string;
|
|
1034
|
+
json?: boolean;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// A limit as typed: a number, or `default` for the platform's default. The
|
|
1038
|
+
// realm server says which numbers a limit takes.
|
|
1039
|
+
function limitValue(option: string, value: string): number | null {
|
|
1040
|
+
if (value.trim().toLowerCase() === 'default') {
|
|
1041
|
+
return null;
|
|
1042
|
+
}
|
|
1043
|
+
let number = Number(value);
|
|
1044
|
+
if (value.trim() === '' || !Number.isFinite(number)) {
|
|
1045
|
+
throw new Error(`${option} is a number, or default`);
|
|
1046
|
+
}
|
|
1047
|
+
return number;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function limitChanges(options: LimitsOptions): KeyLimitChanges {
|
|
1051
|
+
let changes: KeyLimitChanges = {};
|
|
1052
|
+
if (options.rate !== undefined) {
|
|
1053
|
+
// A bare `default` puts both halves back, as `--daily default` does its one.
|
|
1054
|
+
let parts =
|
|
1055
|
+
options.rate.trim().toLowerCase() === 'default'
|
|
1056
|
+
? ['default', 'default']
|
|
1057
|
+
: options.rate.split('/');
|
|
1058
|
+
if (parts.length !== 2) {
|
|
1059
|
+
throw new Error(
|
|
1060
|
+
'--rate is <burst>/<refill per minute>, such as 25/15; either may be default, or default for both',
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
changes.rateBurst = limitValue('--rate burst', parts[0]!);
|
|
1064
|
+
changes.rateRefillPerMinute = limitValue('--rate refill', parts[1]!);
|
|
1065
|
+
}
|
|
1066
|
+
if (options.daily !== undefined) {
|
|
1067
|
+
changes.dailyBudget = limitValue('--daily', options.daily);
|
|
1068
|
+
}
|
|
1069
|
+
if (options.monthly !== undefined) {
|
|
1070
|
+
changes.monthlyBudget = limitValue('--monthly', options.monthly);
|
|
1071
|
+
}
|
|
1072
|
+
return changes;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function printLimits(what: string, limits: KeyLimits): void {
|
|
1076
|
+
console.log(`Limits on ${what}:`);
|
|
1077
|
+
console.log(
|
|
1078
|
+
` rate: ${limits.rateBurst} calls at once per caller, refilled at ${limits.rateRefillPerMinute} a minute`,
|
|
1079
|
+
);
|
|
1080
|
+
console.log(` daily budget: ${limits.dailyBudget} calls`);
|
|
1081
|
+
console.log(` monthly budget: ${limits.monthlyBudget} calls`);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// A share named by its label with `--realm`, or else one of the user's own
|
|
1085
|
+
// keys by name.
|
|
1086
|
+
async function limitedTarget(
|
|
1087
|
+
ctx: Context,
|
|
1088
|
+
token: string,
|
|
1089
|
+
name: string,
|
|
1090
|
+
realm: string | undefined,
|
|
1091
|
+
): Promise<{
|
|
1092
|
+
what: string;
|
|
1093
|
+
target: { shareId: string } | { keyId: string };
|
|
1094
|
+
limits: KeyLimits | undefined;
|
|
1095
|
+
}> {
|
|
1096
|
+
if (realm) {
|
|
1097
|
+
let share = await findShare(ctx.realmServerUrl, token, {
|
|
1098
|
+
realm,
|
|
1099
|
+
label: name,
|
|
1100
|
+
});
|
|
1101
|
+
return {
|
|
1102
|
+
what: `${share.label} in ${share.realm}`,
|
|
1103
|
+
target: { shareId: share.id },
|
|
1104
|
+
limits: share.limits,
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
let key = await findUserKey(ctx.realmServerUrl, token, name);
|
|
1108
|
+
return { what: key.name, target: { keyId: key.id }, limits: key.limits };
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// Shows or sets the limits on how much of a key interactive calls may spend:
|
|
1112
|
+
// on a personal key, or with `--realm` on a share of one in that realm. Only
|
|
1113
|
+
// the key's owner sets them, with a session signed in on the secure origin;
|
|
1114
|
+
// a realm's other direct writers can read a share's but are refused changing
|
|
1115
|
+
// them, by the realm server. Without a change, prints the limits in force.
|
|
1116
|
+
export async function keysLimits(
|
|
1117
|
+
name: string,
|
|
1118
|
+
options: LimitsOptions & KeysCommandDeps,
|
|
1119
|
+
): Promise<KeyLimits> {
|
|
1120
|
+
let ctx = context(options.json, options);
|
|
1121
|
+
let changes = limitChanges(options);
|
|
1122
|
+
let changing = Object.keys(changes).length > 0;
|
|
1123
|
+
let token = changing ? await secureToken(ctx) : await ordinaryToken(ctx);
|
|
1124
|
+
let {
|
|
1125
|
+
what,
|
|
1126
|
+
target,
|
|
1127
|
+
limits: current,
|
|
1128
|
+
} = await limitedTarget(ctx, token, name, options.realm);
|
|
1129
|
+
let limits = changing
|
|
1130
|
+
? await setKeyLimits(ctx.realmServerUrl, token, target, changes)
|
|
1131
|
+
: current;
|
|
1132
|
+
if (!limits) {
|
|
1133
|
+
throw new Error(`The realm server didn't list the limits on ${what}`);
|
|
1134
|
+
}
|
|
1135
|
+
emit(ctx, limits, () => {
|
|
1136
|
+
if (changing) {
|
|
1137
|
+
console.log(`${FG_GREEN}Set the limits on ${what}.${RESET}`);
|
|
1138
|
+
}
|
|
1139
|
+
printLimits(what, limits);
|
|
1140
|
+
});
|
|
1141
|
+
return limits;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// The realm server names a caller only to someone who may see them in the
|
|
1145
|
+
// realm: one the reader can't see comes `withheld`, without a name, and a
|
|
1146
|
+
// call with no session comes without a name too.
|
|
1147
|
+
function callerName(
|
|
1148
|
+
ctx: Context,
|
|
1149
|
+
{ caller, withheld }: { caller: string | null; withheld?: boolean },
|
|
1150
|
+
): string {
|
|
1151
|
+
if (withheld) {
|
|
1152
|
+
return "someone you can't see in the realm";
|
|
1153
|
+
}
|
|
1154
|
+
return caller === null
|
|
1155
|
+
? 'anonymous'
|
|
1156
|
+
: caller === ctx.matrixUserId
|
|
1157
|
+
? 'you'
|
|
1158
|
+
: caller;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function printBudget(label: string, use: KeyUsage['budgets']['day']): void {
|
|
1162
|
+
console.log(
|
|
1163
|
+
` ${label}: ${use.used} of ${use.budget} calls ${DIM}(resets ${use.resetsAt})${RESET}`,
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function printUsage(ctx: Context, what: string, usage: KeyUsage): void {
|
|
1168
|
+
console.log(`Usage of ${what}:`);
|
|
1169
|
+
printBudget('today', usage.budgets.day);
|
|
1170
|
+
printBudget('this month', usage.budgets.month);
|
|
1171
|
+
console.log(` calls since ${usage.since}: ${usage.calls}`);
|
|
1172
|
+
if (usage.lastUse) {
|
|
1173
|
+
let { at, operation, outcome } = usage.lastUse;
|
|
1174
|
+
console.log(
|
|
1175
|
+
` last use: ${at}, ${operation} by ${callerName(ctx, usage.lastUse)} (${outcome})`,
|
|
1176
|
+
);
|
|
1177
|
+
} else {
|
|
1178
|
+
console.log(' last use: never');
|
|
1179
|
+
}
|
|
1180
|
+
if (usage.topCallers.length > 0) {
|
|
1181
|
+
console.log(' top callers:');
|
|
1182
|
+
for (let entry of usage.topCallers) {
|
|
1183
|
+
console.log(` ${callerName(ctx, entry)}: ${entry.calls}`);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (usage.refusals.length > 0) {
|
|
1187
|
+
console.log(' refusals:');
|
|
1188
|
+
for (let { code, count } of usage.refusals) {
|
|
1189
|
+
console.log(` ${code}: ${count}`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (usage.vendorOutcomes.length > 0) {
|
|
1193
|
+
console.log(' vendor answers:');
|
|
1194
|
+
for (let { status, count, outcome } of usage.vendorOutcomes) {
|
|
1195
|
+
console.log(` ${status}${outcome ? ` (${outcome})` : ''}: ${count}`);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (usage.grants.length > 0) {
|
|
1199
|
+
console.log(' grants:');
|
|
1200
|
+
for (let grant of usage.grants) {
|
|
1201
|
+
let state =
|
|
1202
|
+
grant.state === 'rejected'
|
|
1203
|
+
? `${FG_RED}rejected${RESET}${grant.rejectedAt ? ` ${grant.rejectedAt}` : ''}`
|
|
1204
|
+
: grant.state;
|
|
1205
|
+
console.log(` ${grant.destination}: ${state}`);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
printLimits(what, usage.limits);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// What a share or a personal key has served: its budgets' use this UTC day
|
|
1212
|
+
// and month, its last use, top callers, refusals by reason, the vendor's
|
|
1213
|
+
// answers and its grants. A personal key's usage is for its owner; a share's,
|
|
1214
|
+
// named by its label with `--realm`, for the realm's direct writers. Reading
|
|
1215
|
+
// needs no secure session.
|
|
1216
|
+
export async function keysUsage(
|
|
1217
|
+
name: string,
|
|
1218
|
+
options: { realm?: string; json?: boolean } & KeysCommandDeps,
|
|
1219
|
+
): Promise<KeyUsage> {
|
|
1220
|
+
let ctx = context(options.json, options);
|
|
1221
|
+
let token = await ordinaryToken(ctx);
|
|
1222
|
+
let { what, target } = await limitedTarget(ctx, token, name, options.realm);
|
|
1223
|
+
let usage = await getKeyUsage(ctx.realmServerUrl, token, target);
|
|
1224
|
+
emit(ctx, usage, () => printUsage(ctx, what, usage));
|
|
1225
|
+
return usage;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1020
1228
|
// How `connect --client` names a client: `platform:<name>` for one of
|
|
1021
1229
|
// Boxel's own, a share's label for one shared into the realm given with
|
|
1022
1230
|
// `--realm`, and otherwise the name of one of the user's own client keys.
|
package/src/lib/user-keys.ts
CHANGED
|
@@ -95,6 +95,8 @@ export interface UserKeyMetadata {
|
|
|
95
95
|
oauth?: OAuthKeyDescription;
|
|
96
96
|
// Only a google-service-account key has one.
|
|
97
97
|
serviceAccount?: ServiceAccountDetails;
|
|
98
|
+
// The limits in force, defaults filled in.
|
|
99
|
+
limits?: KeyLimits;
|
|
98
100
|
createdAt: string;
|
|
99
101
|
updatedAt: string;
|
|
100
102
|
// Whether its owner lets people who aren't signed in use it, wherever a
|
|
@@ -118,6 +120,9 @@ export interface KeyShare {
|
|
|
118
120
|
suspensionReason: string | null;
|
|
119
121
|
suspendedBy: string | null;
|
|
120
122
|
suspendedAt: string | null;
|
|
123
|
+
// The limits in force, defaults filled in; listed for the key's owner and
|
|
124
|
+
// the realm's direct writers.
|
|
125
|
+
limits?: KeyLimits;
|
|
121
126
|
createdAt: string;
|
|
122
127
|
updatedAt: string;
|
|
123
128
|
// In a realm's listing: the operations the share is approved for there,
|
|
@@ -125,6 +130,22 @@ export interface KeyShare {
|
|
|
125
130
|
operations?: KeyShareOperation[];
|
|
126
131
|
}
|
|
127
132
|
|
|
133
|
+
// How much of a key interactive calls may spend: a token bucket per caller
|
|
134
|
+
// (`rateBurst` calls at once, refilled at `rateRefillPerMinute`) and the calls
|
|
135
|
+
// the share or key serves in a UTC day and month, across every caller.
|
|
136
|
+
export interface KeyLimits {
|
|
137
|
+
rateBurst: number;
|
|
138
|
+
rateRefillPerMinute: number;
|
|
139
|
+
dailyBudget: number;
|
|
140
|
+
monthlyBudget: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// A change to some of a key's limits: a limit left out keeps what it was, and
|
|
144
|
+
// null puts it back to the platform's default.
|
|
145
|
+
export type KeyLimitChanges = Partial<{
|
|
146
|
+
[limit in keyof KeyLimits]: number | null;
|
|
147
|
+
}>;
|
|
148
|
+
|
|
128
149
|
export interface KeyShareOperation {
|
|
129
150
|
bindingId: string;
|
|
130
151
|
type: { module: string; name: string };
|
|
@@ -135,6 +156,50 @@ export interface KeyShareOperation {
|
|
|
135
156
|
destination?: string;
|
|
136
157
|
}
|
|
137
158
|
|
|
159
|
+
// What a share or a personal key has served, as the realm server records it.
|
|
160
|
+
// The counts cover `since` (the start of the UTC month) until now; the
|
|
161
|
+
// budgets' `used` is what the limits count, interactive calls charged this
|
|
162
|
+
// UTC day and month.
|
|
163
|
+
export interface KeyUsage {
|
|
164
|
+
id: string;
|
|
165
|
+
limits: KeyLimits;
|
|
166
|
+
budgets: {
|
|
167
|
+
day: KeyBudgetUse;
|
|
168
|
+
month: KeyBudgetUse;
|
|
169
|
+
};
|
|
170
|
+
since: string;
|
|
171
|
+
calls: number;
|
|
172
|
+
lastUse: {
|
|
173
|
+
at: string;
|
|
174
|
+
caller: string | null;
|
|
175
|
+
operation: string;
|
|
176
|
+
outcome: 'sent' | 'refused' | 'failed';
|
|
177
|
+
// Set when the caller is someone the reader can't see in the realm.
|
|
178
|
+
withheld?: boolean;
|
|
179
|
+
} | null;
|
|
180
|
+
// A null caller is a call with no session; one marked `withheld` is every
|
|
181
|
+
// caller the reader can't see in the realm, counted together.
|
|
182
|
+
topCallers: { caller: string | null; calls: number; withheld?: boolean }[];
|
|
183
|
+
refusals: { code: string; count: number }[];
|
|
184
|
+
vendorOutcomes: { status: number; count: number; outcome?: string }[];
|
|
185
|
+
grants: {
|
|
186
|
+
id: string;
|
|
187
|
+
host: string;
|
|
188
|
+
destination: string;
|
|
189
|
+
state: 'active' | 'rejected';
|
|
190
|
+
rejectedAt: string | null;
|
|
191
|
+
lastForbiddenAt: string | null;
|
|
192
|
+
}[];
|
|
193
|
+
// Whether the reader may change the limits: only the key's owner may.
|
|
194
|
+
canEditLimits: boolean;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface KeyBudgetUse {
|
|
198
|
+
used: number;
|
|
199
|
+
budget: number;
|
|
200
|
+
resetsAt: string;
|
|
201
|
+
}
|
|
202
|
+
|
|
138
203
|
export interface KeyWriteTarget {
|
|
139
204
|
action: KeyWriteAction;
|
|
140
205
|
keyId?: string;
|
|
@@ -629,3 +694,69 @@ export async function completeOAuthConnect(
|
|
|
629
694
|
);
|
|
630
695
|
return json.data.attributes;
|
|
631
696
|
}
|
|
697
|
+
|
|
698
|
+
// The user's own share labelled `label` in `realm`, or else the realm's share
|
|
699
|
+
// of that label, for a direct writer of the realm to read or to be refused
|
|
700
|
+
// changing.
|
|
701
|
+
export async function findShare(
|
|
702
|
+
realmServerUrl: string,
|
|
703
|
+
token: string,
|
|
704
|
+
{ realm, label }: { realm: string; label: string },
|
|
705
|
+
fetchFn: FetchFn = fetch,
|
|
706
|
+
): Promise<KeyShare> {
|
|
707
|
+
let canonical = canonicalRealmUrl(realm);
|
|
708
|
+
let own = (await listOwnShares(realmServerUrl, token, {}, fetchFn)).find(
|
|
709
|
+
(share) => share.realm === canonical && share.label === label,
|
|
710
|
+
);
|
|
711
|
+
if (own) {
|
|
712
|
+
return own;
|
|
713
|
+
}
|
|
714
|
+
return findByLabel(
|
|
715
|
+
await listRealmShares(realmServerUrl, token, realm, fetchFn),
|
|
716
|
+
realm,
|
|
717
|
+
label,
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Sets some of a share's or a personal key's limits, which only the key's
|
|
722
|
+
// owner may, with a session signed in on the secure origin. Answers the
|
|
723
|
+
// limits in force afterwards, defaults filled in.
|
|
724
|
+
export async function setKeyLimits(
|
|
725
|
+
realmServerUrl: string,
|
|
726
|
+
token: string,
|
|
727
|
+
target: { shareId: string } | { keyId: string },
|
|
728
|
+
changes: KeyLimitChanges,
|
|
729
|
+
fetchFn: FetchFn = fetch,
|
|
730
|
+
): Promise<KeyLimits> {
|
|
731
|
+
let path =
|
|
732
|
+
'shareId' in target
|
|
733
|
+
? `_key-shares/${encodeURIComponent(target.shareId)}/limits`
|
|
734
|
+
: `_user-keys/${encodeURIComponent(target.keyId)}/limits`;
|
|
735
|
+
let json = await realmServerRequest(realmServerUrl, path, {
|
|
736
|
+
method: 'PUT',
|
|
737
|
+
token,
|
|
738
|
+
fetchFn,
|
|
739
|
+
body: { data: { type: 'key-limits', attributes: changes } },
|
|
740
|
+
});
|
|
741
|
+
return json.data.attributes as KeyLimits;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// What a share or a personal key has served. A share's usage is for the
|
|
745
|
+
// realm's direct writers, a personal key's for its owner; anyone else is
|
|
746
|
+
// answered 404, as for no such share or key.
|
|
747
|
+
export async function getKeyUsage(
|
|
748
|
+
realmServerUrl: string,
|
|
749
|
+
token: string,
|
|
750
|
+
target: { shareId: string } | { keyId: string },
|
|
751
|
+
fetchFn: FetchFn = fetch,
|
|
752
|
+
): Promise<KeyUsage> {
|
|
753
|
+
let path =
|
|
754
|
+
'shareId' in target
|
|
755
|
+
? `_key-shares/${encodeURIComponent(target.shareId)}/usage`
|
|
756
|
+
: `_user-keys/${encodeURIComponent(target.keyId)}/usage`;
|
|
757
|
+
let json = await realmServerRequest(realmServerUrl, path, {
|
|
758
|
+
token,
|
|
759
|
+
fetchFn,
|
|
760
|
+
});
|
|
761
|
+
return fromResource<KeyUsage>(json.data);
|
|
762
|
+
}
|