@indigoai-us/hq-cli 5.345.5 → 5.345.6
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 +2 -1
- package/dist/commands/login.js +2 -2
- package/dist/commands/mesh.js +17 -17
- package/dist/lib/work-context/config.d.ts +13 -12
- package/dist/lib/work-context/config.js +18 -18
- package/dist/lib/work-context/person-identity.d.ts +11 -7
- package/dist/lib/work-context/person-identity.js +24 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [5.345.
|
|
5
|
+
## [5.345.6] — 2026-10-05
|
|
6
|
+
- `hq mesh context default set <slug> --count-usage` now works for people signed in with a normal HQ account. It previously refused with "the signed-in person could not be determined". An opt-in saved by 5.344.0 is ignored; run the command again to opt in.
|
|
6
7
|
- `hq daemon sync status` now identifies the executing runner and derives its hq-cloud version from the owner status or runner package when needed.
|
|
7
8
|
|
|
8
9
|
## [5.345.4] — 2026-10-05
|
package/dist/commands/login.js
CHANGED
|
@@ -9,7 +9,7 @@ import { cognitoConfigForLoginProvider } from '../utils/login-provider.js';
|
|
|
9
9
|
import * as readline from "node:readline/promises";
|
|
10
10
|
import { listActiveMembershipCompanies } from "../lib/mesh/api.js";
|
|
11
11
|
import { retainUsageAttributionForPerson, setDefaultCompany } from "../lib/work-context/config.js";
|
|
12
|
-
import {
|
|
12
|
+
import { cognitoSubFromTokens } from "../lib/work-context/person-identity.js";
|
|
13
13
|
import { ensureDefaultCompany } from "../lib/work-context/default-company.js";
|
|
14
14
|
import { workContextRoot } from "../lib/work-context/paths.js";
|
|
15
15
|
function peekIdToken(idToken) {
|
|
@@ -66,7 +66,7 @@ export function registerLoginCommand(program) {
|
|
|
66
66
|
try {
|
|
67
67
|
// The usage-attribution opt-in belongs to the person who gave it. A
|
|
68
68
|
// different (or unknown) account signing in on this OS user drops it.
|
|
69
|
-
retainUsageAttributionForPerson(
|
|
69
|
+
retainUsageAttributionForPerson(cognitoSubFromTokens(loadCachedTokens()), {
|
|
70
70
|
root: workContextRoot(),
|
|
71
71
|
});
|
|
72
72
|
}
|
package/dist/commands/mesh.js
CHANGED
|
@@ -20,7 +20,7 @@ import { buildPreflightFixtures } from "../lib/work-context/preflight-contract.j
|
|
|
20
20
|
import { createCandidatesFetcher, createMigratePoster, createOrganizePoster, createWorkSessionDeliverer, fetchCompanyLive, formatCompanyLiveTable, openMeshTransport, probeMigrationCapabilityForMemberships, requireToken, } from "../lib/mesh/client.js";
|
|
21
21
|
import { clearDefaultCompany, effectiveUsageAttribution, isDefaultCompanyDisabled, readDeviceConfig, recordMigrationCapabilitySnapshot, setRepairHeldWithDefault, setDefaultCompany, setUsageAttribution, } from "../lib/work-context/config.js";
|
|
22
22
|
import { ensureDefaultCompany } from "../lib/work-context/default-company.js";
|
|
23
|
-
import {
|
|
23
|
+
import { cognitoSubFromTokens } from "../lib/work-context/person-identity.js";
|
|
24
24
|
import { DefaultCompanyUnavailableError, WorkContextError, } from "../lib/work-context/errors.js";
|
|
25
25
|
import { isValidSessionId } from "../lib/mesh/live/session-identity.js";
|
|
26
26
|
import { flushSessionEvents } from "../lib/mesh/live/flush.js";
|
|
@@ -1212,17 +1212,17 @@ export async function askYesNo(question, io = {}) {
|
|
|
1212
1212
|
rl.close();
|
|
1213
1213
|
}
|
|
1214
1214
|
}
|
|
1215
|
-
/** The signed-in person's
|
|
1216
|
-
function
|
|
1215
|
+
/** The signed-in person's Cognito sub from the cached session, or null. */
|
|
1216
|
+
function currentCognitoSub() {
|
|
1217
1217
|
try {
|
|
1218
|
-
return
|
|
1218
|
+
return cognitoSubFromTokens(loadCachedTokens());
|
|
1219
1219
|
}
|
|
1220
1220
|
catch {
|
|
1221
1221
|
return null;
|
|
1222
1222
|
}
|
|
1223
1223
|
}
|
|
1224
|
-
function usageAttributionJson(cfg,
|
|
1225
|
-
const effective = effectiveUsageAttribution(cfg,
|
|
1224
|
+
function usageAttributionJson(cfg, cognitoSub) {
|
|
1225
|
+
const effective = effectiveUsageAttribution(cfg, cognitoSub);
|
|
1226
1226
|
return { countUsage: effective !== null, usageAttribution: effective };
|
|
1227
1227
|
}
|
|
1228
1228
|
async function runContextDefaultGet(opts) {
|
|
@@ -1244,7 +1244,7 @@ async function runContextDefaultGet(opts) {
|
|
|
1244
1244
|
needsChoice: !current && !disabled && memberships.length > 1,
|
|
1245
1245
|
memberships,
|
|
1246
1246
|
repairHeldWithDefault: cfg.repairHeldWithDefault === true,
|
|
1247
|
-
...usageAttributionJson(cfg,
|
|
1247
|
+
...usageAttributionJson(cfg, currentCognitoSub()),
|
|
1248
1248
|
}, null, 2));
|
|
1249
1249
|
return;
|
|
1250
1250
|
}
|
|
@@ -1257,7 +1257,7 @@ async function runContextDefaultGet(opts) {
|
|
|
1257
1257
|
return;
|
|
1258
1258
|
}
|
|
1259
1259
|
console.log(`Default company: ${current.slug}${current.uid ? ` (${current.uid})` : ""} enabled=${current.enabled} source=${source}`);
|
|
1260
|
-
console.log(effectiveUsageAttribution(cfg,
|
|
1260
|
+
console.log(effectiveUsageAttribution(cfg, currentCognitoSub())
|
|
1261
1261
|
? usageCountedLine(current.slug)
|
|
1262
1262
|
: usageNotCountedLine(current.slug));
|
|
1263
1263
|
}
|
|
@@ -1269,8 +1269,8 @@ async function runContextDefaultSet(slug, opts, promptDeps = {}) {
|
|
|
1269
1269
|
: opts.countUsage === false
|
|
1270
1270
|
? "opt-out"
|
|
1271
1271
|
: "keep-if-same";
|
|
1272
|
-
const
|
|
1273
|
-
const priorOptIn = effectiveUsageAttribution(readDeviceConfig({ root }),
|
|
1272
|
+
const cognitoSub = currentCognitoSub();
|
|
1273
|
+
const priorOptIn = effectiveUsageAttribution(readDeviceConfig({ root }), cognitoSub);
|
|
1274
1274
|
let token;
|
|
1275
1275
|
try {
|
|
1276
1276
|
token = await requireToken();
|
|
@@ -1311,7 +1311,7 @@ async function runContextDefaultSet(slug, opts, promptDeps = {}) {
|
|
|
1311
1311
|
root,
|
|
1312
1312
|
migrationCapability,
|
|
1313
1313
|
usageAttribution: usageChoice,
|
|
1314
|
-
|
|
1314
|
+
cognitoSub,
|
|
1315
1315
|
validateMembership: async (candidate) => {
|
|
1316
1316
|
if (!token) {
|
|
1317
1317
|
throw new DefaultCompanyUnavailableError(`Cannot verify membership for company "${candidate}" (no Cognito session; offline or not logged in)`);
|
|
@@ -1340,7 +1340,7 @@ async function runContextDefaultSet(slug, opts, promptDeps = {}) {
|
|
|
1340
1340
|
ok: true,
|
|
1341
1341
|
config: configured,
|
|
1342
1342
|
migrationWarning,
|
|
1343
|
-
...usageAttributionJson(configured,
|
|
1343
|
+
...usageAttributionJson(configured, cognitoSub),
|
|
1344
1344
|
}, null, 2));
|
|
1345
1345
|
return;
|
|
1346
1346
|
}
|
|
@@ -1352,8 +1352,8 @@ async function runContextDefaultSet(slug, opts, promptDeps = {}) {
|
|
|
1352
1352
|
(() => isInteractiveTerminal()))();
|
|
1353
1353
|
if (usageChoice === "keep-if-same" &&
|
|
1354
1354
|
interactive &&
|
|
1355
|
-
|
|
1356
|
-
!effectiveUsageAttribution(configured,
|
|
1355
|
+
cognitoSub &&
|
|
1356
|
+
!effectiveUsageAttribution(configured, cognitoSub) &&
|
|
1357
1357
|
stored?.uid?.startsWith("cmp_")) {
|
|
1358
1358
|
console.error(usageOptInExplainer(company));
|
|
1359
1359
|
let yes = false;
|
|
@@ -1364,9 +1364,9 @@ async function runContextDefaultSet(slug, opts, promptDeps = {}) {
|
|
|
1364
1364
|
yes = false;
|
|
1365
1365
|
}
|
|
1366
1366
|
if (yes)
|
|
1367
|
-
configured = setUsageAttribution({ companyUid: stored.uid,
|
|
1367
|
+
configured = setUsageAttribution({ companyUid: stored.uid, cognitoSub }, { root });
|
|
1368
1368
|
}
|
|
1369
|
-
console.log(effectiveUsageAttribution(configured,
|
|
1369
|
+
console.log(effectiveUsageAttribution(configured, cognitoSub)
|
|
1370
1370
|
? usageCountedLine(company)
|
|
1371
1371
|
: usageNotCountedLine(company));
|
|
1372
1372
|
if (migrationWarning)
|
|
@@ -1503,7 +1503,7 @@ async function runContextDefaultClear(opts) {
|
|
|
1503
1503
|
const hadOptIn = readDeviceConfig({ root }).usageAttribution !== undefined;
|
|
1504
1504
|
const cfg = clearDefaultCompany({ root });
|
|
1505
1505
|
if (opts.json) {
|
|
1506
|
-
console.log(JSON.stringify({ ok: true, config: cfg, ...usageAttributionJson(cfg,
|
|
1506
|
+
console.log(JSON.stringify({ ok: true, config: cfg, ...usageAttributionJson(cfg, currentCognitoSub()) }, null, 2));
|
|
1507
1507
|
return;
|
|
1508
1508
|
}
|
|
1509
1509
|
console.log("Default company cleared.");
|
|
@@ -56,15 +56,16 @@ export interface UsageAttributionOptIn {
|
|
|
56
56
|
mode: "default-company";
|
|
57
57
|
companyUid: string;
|
|
58
58
|
/**
|
|
59
|
-
* The signed-in person who consented: the `
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* The signed-in person who consented: the Cognito `sub` claim of the cached
|
|
60
|
+
* person session. Consent never carries to another account on the same OS
|
|
61
|
+
* user. Older records that carry only `personUid` are treated as not opted
|
|
62
|
+
* in (fail closed).
|
|
62
63
|
*/
|
|
63
|
-
|
|
64
|
+
cognitoSub: string;
|
|
64
65
|
optedInAt: string;
|
|
65
66
|
}
|
|
66
|
-
/** A
|
|
67
|
-
export declare function
|
|
67
|
+
/** A Cognito `sub` claim: any non-empty string (after trimming). */
|
|
68
|
+
export declare function isCognitoSub(value: unknown): value is string;
|
|
68
69
|
/** How `setDefaultCompany` treats the usage-attribution opt-in. */
|
|
69
70
|
export type UsageAttributionChoice =
|
|
70
71
|
/** Write an opt-in for the newly set company (requires its uid). */
|
|
@@ -113,10 +114,10 @@ export interface DeviceConfigDeps {
|
|
|
113
114
|
*/
|
|
114
115
|
usageAttribution?: UsageAttributionChoice;
|
|
115
116
|
/**
|
|
116
|
-
* The signed-in person (
|
|
117
|
+
* The signed-in person (Cognito `sub`). Required to write an opt-in; an existing
|
|
117
118
|
* opt-in is kept only when it was given by this person.
|
|
118
119
|
*/
|
|
119
|
-
|
|
120
|
+
cognitoSub?: string | null;
|
|
120
121
|
}
|
|
121
122
|
/** Acquire an interprocess config lock; exported for direct lock regression tests. */
|
|
122
123
|
export declare function acquireDeviceConfigLock(root: string, options?: {
|
|
@@ -130,7 +131,7 @@ export declare function sanitizeUsageAttribution(raw: unknown): UsageAttribution
|
|
|
130
131
|
* default company, and given by the signed-in person. Mirrors the hq-cloud
|
|
131
132
|
* reader. No signed-in person means no opt-in.
|
|
132
133
|
*/
|
|
133
|
-
export declare function effectiveUsageAttribution(cfg: Pick<WorkContextDeviceConfig, "defaultCompany" | "usageAttribution">,
|
|
134
|
+
export declare function effectiveUsageAttribution(cfg: Pick<WorkContextDeviceConfig, "defaultCompany" | "usageAttribution">, cognitoSub: string | null | undefined): UsageAttributionOptIn | null;
|
|
134
135
|
/** Injection points for assertSafeConfigPath (tests only). */
|
|
135
136
|
export interface SafeConfigPathIo {
|
|
136
137
|
platform?: NodeJS.Platform;
|
|
@@ -162,14 +163,14 @@ export declare function setDefaultCompany(slug: string, deps: DeviceConfigDeps):
|
|
|
162
163
|
*/
|
|
163
164
|
export declare function setUsageAttribution(optIn: {
|
|
164
165
|
companyUid: string;
|
|
165
|
-
|
|
166
|
+
cognitoSub: string;
|
|
166
167
|
} | null, deps: Pick<DeviceConfigDeps, "root" | "now">): WorkContextDeviceConfig;
|
|
167
168
|
/**
|
|
168
|
-
* Drop the usage-attribution opt-in unless it was given by `
|
|
169
|
+
* Drop the usage-attribution opt-in unless it was given by `cognitoSub`. Called
|
|
169
170
|
* on login (identity may have changed) and logout (null: always drop). A
|
|
170
171
|
* config with no opt-in, or one already matching, is left untouched.
|
|
171
172
|
*/
|
|
172
|
-
export declare function retainUsageAttributionForPerson(
|
|
173
|
+
export declare function retainUsageAttributionForPerson(cognitoSub: string | null | undefined, deps: Pick<DeviceConfigDeps, "root" | "now">): boolean;
|
|
173
174
|
export declare function clearDefaultCompany(deps: DeviceConfigDeps): WorkContextDeviceConfig;
|
|
174
175
|
/**
|
|
175
176
|
* Read the persisted company mapping for a repo identity key (gap 4).
|
|
@@ -9,9 +9,9 @@ import { DefaultCompanyUnavailableError, UnsafeConfigPathError, WorkContextError
|
|
|
9
9
|
import { workContextConfigPath } from "./paths.js";
|
|
10
10
|
import { assertWindowsOwnerOnlyAcl } from "./windows-acl.js";
|
|
11
11
|
const DEVICE_CONFIG_SCHEMA_VERSION = 1;
|
|
12
|
-
/** A
|
|
13
|
-
export function
|
|
14
|
-
return typeof value === "string" &&
|
|
12
|
+
/** A Cognito `sub` claim: any non-empty string (after trimming). */
|
|
13
|
+
export function isCognitoSub(value) {
|
|
14
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
15
15
|
}
|
|
16
16
|
const CONFIG_LOCK_TIMEOUT_MS = 5_000;
|
|
17
17
|
const CONFIG_LOCK_RETRY_MS = 10;
|
|
@@ -158,24 +158,24 @@ export function sanitizeUsageAttribution(raw) {
|
|
|
158
158
|
if (v.mode !== "default-company")
|
|
159
159
|
return undefined;
|
|
160
160
|
const companyUid = typeof v.companyUid === "string" ? v.companyUid.trim() : "";
|
|
161
|
-
const
|
|
161
|
+
const cognitoSub = typeof v.cognitoSub === "string" ? v.cognitoSub.trim() : "";
|
|
162
162
|
const optedInAt = typeof v.optedInAt === "string" ? v.optedInAt.trim() : "";
|
|
163
163
|
if (!companyUid.startsWith("cmp_") ||
|
|
164
|
-
!
|
|
164
|
+
!isCognitoSub(cognitoSub) ||
|
|
165
165
|
!optedInAt ||
|
|
166
166
|
!Number.isFinite(Date.parse(optedInAt))) {
|
|
167
167
|
return undefined;
|
|
168
168
|
}
|
|
169
|
-
return { mode: "default-company", companyUid,
|
|
169
|
+
return { mode: "default-company", companyUid, cognitoSub, optedInAt };
|
|
170
170
|
}
|
|
171
171
|
/**
|
|
172
172
|
* The opt-in that is in effect: present, for the uid of the current enabled
|
|
173
173
|
* default company, and given by the signed-in person. Mirrors the hq-cloud
|
|
174
174
|
* reader. No signed-in person means no opt-in.
|
|
175
175
|
*/
|
|
176
|
-
export function effectiveUsageAttribution(cfg,
|
|
176
|
+
export function effectiveUsageAttribution(cfg, cognitoSub) {
|
|
177
177
|
const opt = cfg.usageAttribution;
|
|
178
|
-
if (!opt || !
|
|
178
|
+
if (!opt || !isCognitoSub(cognitoSub) || opt.cognitoSub !== cognitoSub)
|
|
179
179
|
return null;
|
|
180
180
|
const def = cfg.defaultCompany;
|
|
181
181
|
if (!opt || !def || def.enabled !== true || def.source === "disabled")
|
|
@@ -332,27 +332,27 @@ export async function setDefaultCompany(slug, deps) {
|
|
|
332
332
|
defaultCompany.uid = resolvedUid;
|
|
333
333
|
const choice = deps.usageAttribution ?? "keep-if-same";
|
|
334
334
|
let usageAttribution;
|
|
335
|
-
const
|
|
335
|
+
const cognitoSub = isCognitoSub(deps.cognitoSub) ? deps.cognitoSub : undefined;
|
|
336
336
|
if (choice === "opt-in") {
|
|
337
337
|
if (!resolvedUid?.startsWith("cmp_")) {
|
|
338
338
|
throw new WorkContextError("UsageAttributionUnavailable", `Cannot count usage toward "${trimmed}": its company uid is unknown`);
|
|
339
339
|
}
|
|
340
|
-
if (!
|
|
340
|
+
if (!cognitoSub) {
|
|
341
341
|
throw new WorkContextError("UsageAttributionUnavailable", `Cannot count usage toward "${trimmed}": the signed-in person could not be determined`);
|
|
342
342
|
}
|
|
343
343
|
usageAttribution = {
|
|
344
344
|
mode: "default-company",
|
|
345
345
|
companyUid: resolvedUid,
|
|
346
|
-
|
|
346
|
+
cognitoSub,
|
|
347
347
|
optedInAt: now().toISOString(),
|
|
348
348
|
};
|
|
349
349
|
}
|
|
350
350
|
else if (choice === "keep-if-same") {
|
|
351
351
|
usageAttribution =
|
|
352
352
|
resolvedUid &&
|
|
353
|
-
|
|
353
|
+
cognitoSub &&
|
|
354
354
|
prior.usageAttribution?.companyUid === resolvedUid &&
|
|
355
|
-
prior.usageAttribution.
|
|
355
|
+
prior.usageAttribution.cognitoSub === cognitoSub
|
|
356
356
|
? prior.usageAttribution
|
|
357
357
|
: undefined;
|
|
358
358
|
}
|
|
@@ -388,13 +388,13 @@ export function setUsageAttribution(optIn, deps) {
|
|
|
388
388
|
def.source !== "chosen" ||
|
|
389
389
|
!def.uid?.startsWith("cmp_") ||
|
|
390
390
|
def.uid !== optIn.companyUid ||
|
|
391
|
-
!
|
|
391
|
+
!isCognitoSub(optIn.cognitoSub)) {
|
|
392
392
|
throw new WorkContextError("UsageAttributionUnavailable", "Usage can only be counted toward a default company you chose on this device");
|
|
393
393
|
}
|
|
394
394
|
usageAttribution = {
|
|
395
395
|
mode: "default-company",
|
|
396
396
|
companyUid: def.uid,
|
|
397
|
-
|
|
397
|
+
cognitoSub: optIn.cognitoSub,
|
|
398
398
|
optedInAt: now().toISOString(),
|
|
399
399
|
};
|
|
400
400
|
}
|
|
@@ -413,11 +413,11 @@ export function setUsageAttribution(optIn, deps) {
|
|
|
413
413
|
});
|
|
414
414
|
}
|
|
415
415
|
/**
|
|
416
|
-
* Drop the usage-attribution opt-in unless it was given by `
|
|
416
|
+
* Drop the usage-attribution opt-in unless it was given by `cognitoSub`. Called
|
|
417
417
|
* on login (identity may have changed) and logout (null: always drop). A
|
|
418
418
|
* config with no opt-in, or one already matching, is left untouched.
|
|
419
419
|
*/
|
|
420
|
-
export function retainUsageAttributionForPerson(
|
|
420
|
+
export function retainUsageAttributionForPerson(cognitoSub, deps) {
|
|
421
421
|
if (!fs.existsSync(workContextConfigPath(deps.root)))
|
|
422
422
|
return false;
|
|
423
423
|
return withDeviceConfigLock(deps.root, () => {
|
|
@@ -425,7 +425,7 @@ export function retainUsageAttributionForPerson(personUid, deps) {
|
|
|
425
425
|
const opt = prior.usageAttribution;
|
|
426
426
|
if (!opt)
|
|
427
427
|
return false;
|
|
428
|
-
if (
|
|
428
|
+
if (isCognitoSub(cognitoSub) && opt.cognitoSub === cognitoSub)
|
|
429
429
|
return false;
|
|
430
430
|
const now = deps.now ?? (() => new Date());
|
|
431
431
|
const { usageAttribution: _dropped, ...rest } = prior;
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
* Signed-in person identity for the usage-attribution opt-in.
|
|
3
3
|
*
|
|
4
4
|
* Source: the cached Cognito person session (`~/.hq/cognito-tokens.json`,
|
|
5
|
-
* read through `loadCachedTokens`). The
|
|
6
|
-
* claim of the ID token (falling back to the access token)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* read through `loadCachedTokens`). The identity key is the Cognito `sub`
|
|
6
|
+
* claim of the ID token (falling back to the access token); it must be a
|
|
7
|
+
* non-empty string. Human sessions carry no `custom:entityUid` claim (hq-pro
|
|
8
|
+
* sets it only on agent and outpost machine identities), so it is not used.
|
|
9
|
+
* An agent or outpost session has its own Cognito user and therefore a
|
|
10
|
+
* different `sub`, so it could never match a person's opt-in; a token that
|
|
11
|
+
* names a machine identity (`custom:entityType` agent/outpost, or a
|
|
12
|
+
* non-`prs_` `custom:entityUid`) is refused outright. Claims are read
|
|
13
|
+
* without verification; this decides only whether local consent belongs to
|
|
14
|
+
* the account that is signed in, never authorization.
|
|
11
15
|
*/
|
|
12
|
-
export declare function
|
|
16
|
+
export declare function cognitoSubFromTokens(tokens: {
|
|
13
17
|
idToken?: string;
|
|
14
18
|
accessToken?: string;
|
|
15
19
|
} | null | undefined): string | null;
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
* Signed-in person identity for the usage-attribution opt-in.
|
|
3
3
|
*
|
|
4
4
|
* Source: the cached Cognito person session (`~/.hq/cognito-tokens.json`,
|
|
5
|
-
* read through `loadCachedTokens`). The
|
|
6
|
-
* claim of the ID token (falling back to the access token)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* read through `loadCachedTokens`). The identity key is the Cognito `sub`
|
|
6
|
+
* claim of the ID token (falling back to the access token); it must be a
|
|
7
|
+
* non-empty string. Human sessions carry no `custom:entityUid` claim (hq-pro
|
|
8
|
+
* sets it only on agent and outpost machine identities), so it is not used.
|
|
9
|
+
* An agent or outpost session has its own Cognito user and therefore a
|
|
10
|
+
* different `sub`, so it could never match a person's opt-in; a token that
|
|
11
|
+
* names a machine identity (`custom:entityType` agent/outpost, or a
|
|
12
|
+
* non-`prs_` `custom:entityUid`) is refused outright. Claims are read
|
|
13
|
+
* without verification; this decides only whether local consent belongs to
|
|
14
|
+
* the account that is signed in, never authorization.
|
|
11
15
|
*/
|
|
12
|
-
import {
|
|
16
|
+
import { isCognitoSub } from "./config.js";
|
|
13
17
|
function claimsOf(token) {
|
|
14
18
|
if (typeof token !== "string")
|
|
15
19
|
return null;
|
|
@@ -26,13 +30,22 @@ function claimsOf(token) {
|
|
|
26
30
|
return null;
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
|
-
export function
|
|
33
|
+
export function cognitoSubFromTokens(tokens) {
|
|
30
34
|
if (!tokens)
|
|
31
35
|
return null;
|
|
32
36
|
for (const token of [tokens.idToken, tokens.accessToken]) {
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
|
|
37
|
+
const claims = claimsOf(token);
|
|
38
|
+
if (!claims)
|
|
39
|
+
continue;
|
|
40
|
+
const entityType = claims["custom:entityType"];
|
|
41
|
+
const entityUid = claims["custom:entityUid"];
|
|
42
|
+
if (entityType === "agent" || entityType === "outpost")
|
|
43
|
+
return null;
|
|
44
|
+
if (typeof entityUid === "string" && !entityUid.startsWith("prs_"))
|
|
45
|
+
return null;
|
|
46
|
+
const sub = claims.sub;
|
|
47
|
+
if (isCognitoSub(sub))
|
|
48
|
+
return sub.trim();
|
|
36
49
|
}
|
|
37
50
|
return null;
|
|
38
51
|
}
|