@learncard/network-plugin 2.13.15 → 2.13.17

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.
@@ -15639,7 +15639,8 @@ var LCNAuthedProfileValidator = LCNPublicProfileValidator.extend({
15639
15639
  did: LCNProfileValidator.shape.did
15640
15640
  });
15641
15641
  var LCNConnectionProfileValidator = LCNAuthedProfileValidator.extend({
15642
- email: LCNProfileValidator.shape.email
15642
+ email: LCNProfileValidator.shape.email,
15643
+ connectedAt: external_exports.iso.datetime().optional().describe("When the viewer and this profile became connected.")
15643
15644
  });
15644
15645
  var LCNVisibleProfileValidator = external_exports.union([
15645
15646
  LCNConnectionProfileValidator.strict(),
@@ -17527,6 +17528,56 @@ mustache.Writer = Writer;
17527
17528
  var mustache_default = mustache;
17528
17529
 
17529
17530
  // src/plugin.ts
17531
+ var LEARNCARD_DOMAIN_SUFFIXES = [".learncard.com", ".learncard.app", ".learncard.ai"];
17532
+ var isLearnCardDomain = /* @__PURE__ */ __name((host) => {
17533
+ return LEARNCARD_DOMAIN_SUFFIXES.some(
17534
+ (suffix) => host === suffix.replace(/^\./, "") || host.endsWith(suffix)
17535
+ );
17536
+ }, "isLearnCardDomain");
17537
+ var isLocalhostHost = /* @__PURE__ */ __name((host) => {
17538
+ const lower = host.toLowerCase();
17539
+ return lower === "localhost" || lower === "127.0.0.1";
17540
+ }, "isLocalhostHost");
17541
+ var validateFederationUrl = /* @__PURE__ */ __name((userProvidedUrl, config2) => {
17542
+ let parsed;
17543
+ try {
17544
+ parsed = new URL(userProvidedUrl);
17545
+ } catch {
17546
+ throw new Error(`Invalid federation endpoint URL: ${userProvidedUrl}`);
17547
+ }
17548
+ if (config2.serviceOrigin) {
17549
+ const endpointOrigin = parsed.origin;
17550
+ if (endpointOrigin === config2.serviceOrigin) {
17551
+ return parsed.href;
17552
+ }
17553
+ }
17554
+ const host = parsed.hostname.toLowerCase();
17555
+ const isLocalhost = isLocalhostHost(host);
17556
+ if (isLocalhost) {
17557
+ if (!config2.allowLocalhostFederation) {
17558
+ throw new Error(
17559
+ `Localhost federation is disabled. Set allowLocalhostFederation: true for development.`
17560
+ );
17561
+ }
17562
+ if (parsed.protocol === "https:") {
17563
+ parsed.protocol = "http:";
17564
+ }
17565
+ return parsed.href;
17566
+ }
17567
+ if (parsed.protocol !== "https:") {
17568
+ throw new Error(`Federation requires HTTPS for non-localhost hosts: ${userProvidedUrl}`);
17569
+ }
17570
+ if (config2.trustedFederationHosts !== void 0) {
17571
+ const trustedHosts = new Set(config2.trustedFederationHosts);
17572
+ const allowAnyHost = trustedHosts.has("*");
17573
+ if (!allowAnyHost && !isLearnCardDomain(host) && !trustedHosts.has(host)) {
17574
+ throw new Error(
17575
+ `Federation host '${host}' is not trusted. Add it to trustedFederationHosts or use '*' to allow any host.`
17576
+ );
17577
+ }
17578
+ }
17579
+ return parsed.href;
17580
+ }, "validateFederationUrl");
17530
17581
  var uint8ArrayToBase64Url = /* @__PURE__ */ __name((bytes) => {
17531
17582
  let binary = "";
17532
17583
  for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
@@ -17620,8 +17671,20 @@ var issueCredentialWithNetworkStatus = /* @__PURE__ */ __name(async (learnCard,
17620
17671
  }, "issueCredentialWithNetworkStatus");
17621
17672
  async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, options) {
17622
17673
  const apiToken = typeof apiTokenOrOptions === "string" ? apiTokenOrOptions : void 0;
17623
- const guardianApprovalGetter = (typeof apiTokenOrOptions === "object" ? apiTokenOrOptions?.guardianApprovalGetter : void 0) ?? options?.guardianApprovalGetter;
17624
- const extraHeaders = (typeof apiTokenOrOptions === "object" ? apiTokenOrOptions?.extraHeaders : void 0) ?? options?.extraHeaders;
17674
+ const resolvedOptions = (typeof apiTokenOrOptions === "object" ? apiTokenOrOptions : options) ?? {};
17675
+ const {
17676
+ guardianApprovalGetter,
17677
+ extraHeaders,
17678
+ trustedFederationHosts,
17679
+ allowLocalhostFederation
17680
+ } = resolvedOptions;
17681
+ const serviceUrl = new URL(url2);
17682
+ const serviceIsLocalhost = isLocalhostHost(serviceUrl.hostname);
17683
+ const federationConfig = {
17684
+ trustedFederationHosts,
17685
+ allowLocalhostFederation: allowLocalhostFederation ?? serviceIsLocalhost,
17686
+ serviceOrigin: serviceUrl.origin
17687
+ };
17625
17688
  let did = "";
17626
17689
  try {
17627
17690
  const idPlane = learnCard?.id;
@@ -17690,8 +17753,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17690
17753
  }
17691
17754
  }, "getBoostTemplateForIssuance");
17692
17755
  const getInboxEndpointForDid = /* @__PURE__ */ __name(async (_learnCard, recipientDid) => {
17693
- const serviceUrl = new URL(url2);
17694
- const serviceDomain = `${serviceUrl.hostname}${serviceUrl.port ? `%3A${serviceUrl.port}` : ""}`;
17756
+ const serviceUrl2 = new URL(url2);
17757
+ const serviceDomain = `${serviceUrl2.hostname}${serviceUrl2.port ? `%3A${serviceUrl2.port}` : ""}`;
17695
17758
  const localServiceDid = `did:web:${serviceDomain}`;
17696
17759
  const getInferredServiceDid = /* @__PURE__ */ __name((did2) => {
17697
17760
  if (!did2.startsWith("did:web:")) return null;
@@ -17712,12 +17775,12 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17712
17775
  }), "getInboxService");
17713
17776
  const inferredServiceDid = getInferredServiceDid(recipientDid);
17714
17777
  if (inferredServiceDid === localServiceDid) {
17715
- return `${serviceUrl.origin}/api/inbox/receive`;
17778
+ return `${serviceUrl2.origin}/api/inbox/receive`;
17716
17779
  }
17717
17780
  const didDoc = await _learnCard.invoke.resolveDid(recipientDid);
17718
17781
  const inboxService = getInboxService(didDoc);
17719
17782
  if ((inboxService?.serviceDid || inferredServiceDid) === localServiceDid) {
17720
- return `${serviceUrl.origin}/api/inbox/receive`;
17783
+ return `${serviceUrl2.origin}/api/inbox/receive`;
17721
17784
  }
17722
17785
  if (!inboxService?.serviceEndpoint) {
17723
17786
  throw new Error(
@@ -17879,7 +17942,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17879
17942
  return client.profileManager.updateProfileManager.mutate(manager);
17880
17943
  }, "updateProfileManagerProfile"),
17881
17944
  deleteProfile: /* @__PURE__ */ __name(async () => {
17882
- if (!userData) throw new Error("Account does not exist!");
17945
+ await initialQuery;
17946
+ if (!apiToken) await ensureUser();
17883
17947
  const result = await client.profile.deleteProfile.mutate();
17884
17948
  if (result) {
17885
17949
  userData = void 0;
@@ -18030,11 +18094,10 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
18030
18094
  proofFormat: "jwt",
18031
18095
  challenge: `inbox-federation-${crypto.randomUUID()}`
18032
18096
  });
18033
- let receiveUrl = inboxEndpoint;
18034
- if (receiveUrl.includes("localhost")) {
18035
- receiveUrl = receiveUrl.replace("https://", "http://");
18036
- }
18097
+ const receiveUrl = validateFederationUrl(inboxEndpoint, federationConfig);
18037
18098
  const response = await fetch(receiveUrl, {
18099
+ // A redirect must not bypass endpoint validation or forward credentials.
18100
+ redirect: "error",
18038
18101
  method: "POST",
18039
18102
  headers: {
18040
18103
  "Content-Type": "application/json",
@@ -18509,8 +18572,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
18509
18572
  const isDid = recipient.startsWith("did:");
18510
18573
  const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipient);
18511
18574
  const isPhone = /^\+?[\d\s-]{10,}$/.test(recipient.replace(/[\s-]/g, ""));
18512
- const serviceUrl = new URL(url2);
18513
- const serviceDomain = `${serviceUrl.hostname}${serviceUrl.port ? `%3A${serviceUrl.port}` : ""}`;
18575
+ const serviceUrl2 = new URL(url2);
18576
+ const serviceDomain = `${serviceUrl2.hostname}${serviceUrl2.port ? `%3A${serviceUrl2.port}` : ""}`;
18514
18577
  const recipientDomain = recipient.startsWith("did:web:") ? recipient.split(":")[2] : void 0;
18515
18578
  const isRemoteDidWebRecipient = recipient.startsWith("did:web:") && recipientDomain !== serviceDomain;
18516
18579
  if (isRemoteDidWebRecipient && input.templateUri) {
@@ -19310,7 +19373,9 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
19310
19373
  }, "getActivityChain"),
19311
19374
  isServiceTrusted: /* @__PURE__ */ __name(async (_learnCard, serviceDid) => {
19312
19375
  const trustedServices = await client.federation.getTrustedServices.query({});
19313
- return trustedServices.some((s) => s.did === serviceDid);
19376
+ return trustedServices.some(
19377
+ (service) => service.did === serviceDid
19378
+ );
19314
19379
  }, "isServiceTrusted"),
19315
19380
  getTrustedServices: /* @__PURE__ */ __name(async () => {
19316
19381
  return client.federation.getTrustedServices.query({});
@@ -19366,9 +19431,7 @@ var getVerifyBoostPlugin = /* @__PURE__ */ __name(async (learnCard, trustedBoost
19366
19431
  const boostCredential = credential?.boostCredential;
19367
19432
  try {
19368
19433
  if (boostCredential) {
19369
- const verifyBoostCredential = await learnCard.invoke.verifyCredential(
19370
- boostCredential
19371
- );
19434
+ const verifyBoostCredential = await learnCard.invoke.verifyCredential(boostCredential);
19372
19435
  const boostCredentialErrors = verifyBoostCredential.errors ?? [];
19373
19436
  if (verifyBoostCredential.status?.length) {
19374
19437
  verificationCheck.status = [