@orangecheck/me-client 0.32.2 → 0.32.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1035,6 +1035,7 @@ async function defaultLive() {
1035
1035
  var federations = { list, live, get, defaultLive };
1036
1036
 
1037
1037
  // src/scope.ts
1038
+ var CONSENT_ORIGIN = "https://ochk.app";
1038
1039
  var VALID_SCOPES = [
1039
1040
  "bitcoin_address",
1040
1041
  "email",
@@ -1090,7 +1091,7 @@ function request(scopes2, options) {
1090
1091
  if (typeof window === "undefined") {
1091
1092
  throw new Error("oc.scope.request: must be called in a browser environment");
1092
1093
  }
1093
- const origin2 = options.origin ?? getOrigin();
1094
+ const origin2 = options.origin ?? CONSENT_ORIGIN;
1094
1095
  const url = new URL("/me/scope-grant", origin2);
1095
1096
  url.searchParams.set("project_key", options.project_key);
1096
1097
  url.searchParams.set("scopes", scopes2.join(","));
@@ -1232,33 +1233,85 @@ async function verifyEd255192(key, sig, msg) {
1232
1233
  return false;
1233
1234
  }
1234
1235
  }
1236
+ var SIGNED_FIELDS = [
1237
+ "v",
1238
+ "kind",
1239
+ "did_oc",
1240
+ "event_count",
1241
+ "human_event_count",
1242
+ "lifetime_sats",
1243
+ "distinct_integrator_count",
1244
+ "oldest_event_at",
1245
+ "active_days",
1246
+ "computed_at",
1247
+ "expires_at"
1248
+ ];
1249
+ var COUNTS = [
1250
+ "event_count",
1251
+ "human_event_count",
1252
+ "lifetime_sats",
1253
+ "distinct_integrator_count",
1254
+ "active_days"
1255
+ ];
1256
+ function readSignedAttestation(canonical, id) {
1257
+ const text = new TextDecoder().decode(canonical);
1258
+ let parsed;
1259
+ try {
1260
+ parsed = JSON.parse(text);
1261
+ } catch {
1262
+ return null;
1263
+ }
1264
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
1265
+ const o = parsed;
1266
+ if (o.v !== 1 || o.kind !== "oc-me-activity" || typeof o.did_oc !== "string") return null;
1267
+ const countsOk = COUNTS.every((k) => {
1268
+ const n = o[k];
1269
+ return typeof n === "number" && Number.isSafeInteger(n) && n >= 0;
1270
+ });
1271
+ if (!countsOk) return null;
1272
+ if (o.oldest_event_at !== null && typeof o.oldest_event_at !== "string") return null;
1273
+ if (typeof o.computed_at !== "string" || typeof o.expires_at !== "string") return null;
1274
+ const ordered = Object.fromEntries(SIGNED_FIELDS.map((k) => [k, o[k]]));
1275
+ if (JSON.stringify(ordered) !== text) return null;
1276
+ return { id, ...ordered };
1277
+ }
1278
+ function sameAttestation(copy, signed) {
1279
+ const c = copy;
1280
+ const v = signed;
1281
+ return ["id", ...SIGNED_FIELDS].every((k) => c[k] === v[k]);
1282
+ }
1235
1283
  async function verifyActivityAttestation(bundle, options = {}) {
1236
1284
  const checks = [];
1237
- const shapeOk = !!bundle && typeof bundle === "object" && typeof bundle.sig === "string" && typeof bundle.kid === "string" && typeof bundle.canonical_b64 === "string" && !!bundle.attestation && typeof bundle.attestation.id === "string" && typeof bundle.attestation.expires_at === "string";
1285
+ const shapeOk = !!bundle && typeof bundle === "object" && typeof bundle.sig === "string" && typeof bundle.kid === "string" && typeof bundle.canonical_b64 === "string" && (bundle.attestation === void 0 || !!bundle.attestation && typeof bundle.attestation === "object");
1238
1286
  checks.push({
1239
1287
  ok: shapeOk,
1240
- label: "bundle shape \xB7 sig / kid / canonical_b64 / attestation"
1288
+ label: "bundle shape \xB7 sig / kid / canonical_b64"
1241
1289
  });
1242
1290
  if (!shapeOk) {
1243
1291
  return { ok: false, reason: "bundle shape invalid", checks };
1244
1292
  }
1245
- const a = bundle.attestation;
1246
1293
  const canonical = base64ToBytes(bundle.canonical_b64);
1247
1294
  checks.push({
1248
1295
  ok: canonical !== null,
1249
1296
  label: "canonical_b64 decodes",
1250
1297
  detail: canonical ? `${canonical.length} bytes` : "invalid base64"
1251
1298
  });
1252
- if (!canonical) return { ok: false, reason: "canonical_b64 invalid base64", checks, attestation: a };
1253
- const digest = await sha256(canonical);
1254
- const computedId = bytesToHex(digest);
1255
- const idOk = computedId === a.id;
1299
+ if (!canonical) return { ok: false, reason: "canonical_b64 invalid base64", checks };
1300
+ const computedId = bytesToHex(await sha256(canonical));
1301
+ const signed = readSignedAttestation(canonical, computedId);
1302
+ checks.push({
1303
+ ok: signed !== null,
1304
+ label: "canonical_bytes are an oc-me-activity attestation in canonical form"
1305
+ });
1306
+ if (!signed) return { ok: false, reason: "canonical bytes are not an activity attestation", checks };
1307
+ const copy = bundle.attestation;
1308
+ const copyOk = copy === void 0 || sameAttestation(copy, signed);
1256
1309
  checks.push({
1257
- ok: idOk,
1258
- label: "sha256(canonical_bytes) === id",
1259
- detail: idOk ? a.id : `expected ${a.id}, got ${computedId}`
1310
+ ok: copyOk,
1311
+ label: "attestation === the signed bytes, id = sha256(canonical_bytes)",
1312
+ detail: copyOk ? computedId : "the readable copy differs from what was signed"
1260
1313
  });
1261
- if (!idOk) return { ok: false, reason: "id does not match canonical-bytes hash", checks, attestation: a };
1314
+ if (!copyOk) return { ok: false, reason: "attestation does not match the signed bytes", checks };
1262
1315
  let key = null;
1263
1316
  if (options.jwk) {
1264
1317
  if (options.jwk.kid !== bundle.kid) {
@@ -1267,7 +1320,7 @@ async function verifyActivityAttestation(bundle, options = {}) {
1267
1320
  label: "JWK with matching kid present",
1268
1321
  detail: `provided jwk.kid=${options.jwk.kid} but bundle.kid=${bundle.kid}`
1269
1322
  });
1270
- return { ok: false, reason: "kid mismatch with provided jwk", checks, attestation: a };
1323
+ return { ok: false, reason: "kid mismatch with provided jwk", checks };
1271
1324
  }
1272
1325
  try {
1273
1326
  key = await importJWK(options.jwk, "EdDSA");
@@ -1278,7 +1331,7 @@ async function verifyActivityAttestation(bundle, options = {}) {
1278
1331
  label: "JWK with matching kid present",
1279
1332
  detail: `provided jwk failed import: ${err.message}`
1280
1333
  });
1281
- return { ok: false, reason: "provided jwk failed import", checks, attestation: a };
1334
+ return { ok: false, reason: "provided jwk failed import", checks };
1282
1335
  }
1283
1336
  } else {
1284
1337
  const issuer = options.issuer ?? DEFAULT_ISSUER2;
@@ -1297,7 +1350,7 @@ async function verifyActivityAttestation(bundle, options = {}) {
1297
1350
  detail: key ? `kid ${bundle.kid}` : `no JWK with kid ${bundle.kid} at ${issuer}${ENVELOPE_JWKS_PATH}`
1298
1351
  });
1299
1352
  if (!key) {
1300
- return { ok: false, reason: `kid ${bundle.kid} not in envelope JWKS`, checks, attestation: a };
1353
+ return { ok: false, reason: `kid ${bundle.kid} not in envelope JWKS`, checks };
1301
1354
  }
1302
1355
  } catch (err) {
1303
1356
  checks.push({
@@ -1308,8 +1361,7 @@ async function verifyActivityAttestation(bundle, options = {}) {
1308
1361
  return {
1309
1362
  ok: false,
1310
1363
  reason: `envelope JWKS unavailable: ${err.message}`,
1311
- checks,
1312
- attestation: a
1364
+ checks
1313
1365
  };
1314
1366
  }
1315
1367
  }
@@ -1320,7 +1372,7 @@ async function verifyActivityAttestation(bundle, options = {}) {
1320
1372
  label: "ed25519.verify(sig, canonical_bytes, jwk.public)",
1321
1373
  detail: "malformed sig (expected hex)"
1322
1374
  });
1323
- return { ok: false, reason: "malformed sig", checks, attestation: a };
1375
+ return { ok: false, reason: "malformed sig", checks };
1324
1376
  }
1325
1377
  const sigOk = await verifyEd255192(key, sigBytes, canonical);
1326
1378
  checks.push({
@@ -1328,17 +1380,17 @@ async function verifyActivityAttestation(bundle, options = {}) {
1328
1380
  label: "ed25519.verify(sig, canonical_bytes, jwk.public)",
1329
1381
  detail: sigOk ? "signature valid" : "signature does not match"
1330
1382
  });
1331
- if (!sigOk) return { ok: false, reason: "signature does not match", checks, attestation: a };
1383
+ if (!sigOk) return { ok: false, reason: "signature does not match", checks };
1332
1384
  const now = options.now ?? /* @__PURE__ */ new Date();
1333
- const expiresMs = Date.parse(a.expires_at);
1385
+ const expiresMs = Date.parse(signed.expires_at);
1334
1386
  const freshOk = Number.isFinite(expiresMs) && expiresMs > now.getTime();
1335
1387
  checks.push({
1336
1388
  ok: freshOk,
1337
1389
  label: "now < attestation.expires_at",
1338
1390
  detail: freshOk ? `expires ${new Date(expiresMs).toISOString()}` : "attestation has expired"
1339
1391
  });
1340
- if (!freshOk) return { ok: false, reason: "attestation has expired", checks, attestation: a };
1341
- return { ok: true, attestation: a, checks };
1392
+ if (!freshOk) return { ok: false, reason: "attestation has expired", checks };
1393
+ return { ok: true, attestation: signed, checks };
1342
1394
  }
1343
1395
  async function refreshEnvelopeJwks(issuer = DEFAULT_ISSUER2) {
1344
1396
  jwksCache2.delete(issuer);