@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.
@@ -15660,7 +15660,8 @@ var LCNAuthedProfileValidator = LCNPublicProfileValidator.extend({
15660
15660
  did: LCNProfileValidator.shape.did
15661
15661
  });
15662
15662
  var LCNConnectionProfileValidator = LCNAuthedProfileValidator.extend({
15663
- email: LCNProfileValidator.shape.email
15663
+ email: LCNProfileValidator.shape.email,
15664
+ connectedAt: external_exports.iso.datetime().optional().describe("When the viewer and this profile became connected.")
15664
15665
  });
15665
15666
  var LCNVisibleProfileValidator = external_exports.union([
15666
15667
  LCNConnectionProfileValidator.strict(),
@@ -17544,6 +17545,56 @@ mustache.Writer = Writer;
17544
17545
  var mustache_default = mustache;
17545
17546
 
17546
17547
  // src/plugin.ts
17548
+ var LEARNCARD_DOMAIN_SUFFIXES = [".learncard.com", ".learncard.app", ".learncard.ai"];
17549
+ var isLearnCardDomain = /* @__PURE__ */ __name((host) => {
17550
+ return LEARNCARD_DOMAIN_SUFFIXES.some(
17551
+ (suffix) => host === suffix.replace(/^\./, "") || host.endsWith(suffix)
17552
+ );
17553
+ }, "isLearnCardDomain");
17554
+ var isLocalhostHost = /* @__PURE__ */ __name((host) => {
17555
+ const lower = host.toLowerCase();
17556
+ return lower === "localhost" || lower === "127.0.0.1";
17557
+ }, "isLocalhostHost");
17558
+ var validateFederationUrl = /* @__PURE__ */ __name((userProvidedUrl, config2) => {
17559
+ let parsed;
17560
+ try {
17561
+ parsed = new URL(userProvidedUrl);
17562
+ } catch {
17563
+ throw new Error(`Invalid federation endpoint URL: ${userProvidedUrl}`);
17564
+ }
17565
+ if (config2.serviceOrigin) {
17566
+ const endpointOrigin = parsed.origin;
17567
+ if (endpointOrigin === config2.serviceOrigin) {
17568
+ return parsed.href;
17569
+ }
17570
+ }
17571
+ const host = parsed.hostname.toLowerCase();
17572
+ const isLocalhost = isLocalhostHost(host);
17573
+ if (isLocalhost) {
17574
+ if (!config2.allowLocalhostFederation) {
17575
+ throw new Error(
17576
+ `Localhost federation is disabled. Set allowLocalhostFederation: true for development.`
17577
+ );
17578
+ }
17579
+ if (parsed.protocol === "https:") {
17580
+ parsed.protocol = "http:";
17581
+ }
17582
+ return parsed.href;
17583
+ }
17584
+ if (parsed.protocol !== "https:") {
17585
+ throw new Error(`Federation requires HTTPS for non-localhost hosts: ${userProvidedUrl}`);
17586
+ }
17587
+ if (config2.trustedFederationHosts !== void 0) {
17588
+ const trustedHosts = new Set(config2.trustedFederationHosts);
17589
+ const allowAnyHost = trustedHosts.has("*");
17590
+ if (!allowAnyHost && !isLearnCardDomain(host) && !trustedHosts.has(host)) {
17591
+ throw new Error(
17592
+ `Federation host '${host}' is not trusted. Add it to trustedFederationHosts or use '*' to allow any host.`
17593
+ );
17594
+ }
17595
+ }
17596
+ return parsed.href;
17597
+ }, "validateFederationUrl");
17547
17598
  var uint8ArrayToBase64Url = /* @__PURE__ */ __name((bytes) => {
17548
17599
  let binary = "";
17549
17600
  for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
@@ -17637,8 +17688,20 @@ var issueCredentialWithNetworkStatus = /* @__PURE__ */ __name(async (learnCard,
17637
17688
  }, "issueCredentialWithNetworkStatus");
17638
17689
  async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, options) {
17639
17690
  const apiToken = typeof apiTokenOrOptions === "string" ? apiTokenOrOptions : void 0;
17640
- const guardianApprovalGetter = (typeof apiTokenOrOptions === "object" ? apiTokenOrOptions?.guardianApprovalGetter : void 0) ?? options?.guardianApprovalGetter;
17641
- const extraHeaders = (typeof apiTokenOrOptions === "object" ? apiTokenOrOptions?.extraHeaders : void 0) ?? options?.extraHeaders;
17691
+ const resolvedOptions = (typeof apiTokenOrOptions === "object" ? apiTokenOrOptions : options) ?? {};
17692
+ const {
17693
+ guardianApprovalGetter,
17694
+ extraHeaders,
17695
+ trustedFederationHosts,
17696
+ allowLocalhostFederation
17697
+ } = resolvedOptions;
17698
+ const serviceUrl = new URL(url2);
17699
+ const serviceIsLocalhost = isLocalhostHost(serviceUrl.hostname);
17700
+ const federationConfig = {
17701
+ trustedFederationHosts,
17702
+ allowLocalhostFederation: allowLocalhostFederation ?? serviceIsLocalhost,
17703
+ serviceOrigin: serviceUrl.origin
17704
+ };
17642
17705
  let did = "";
17643
17706
  try {
17644
17707
  const idPlane = learnCard?.id;
@@ -17707,8 +17770,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17707
17770
  }
17708
17771
  }, "getBoostTemplateForIssuance");
17709
17772
  const getInboxEndpointForDid = /* @__PURE__ */ __name(async (_learnCard, recipientDid) => {
17710
- const serviceUrl = new URL(url2);
17711
- const serviceDomain = `${serviceUrl.hostname}${serviceUrl.port ? `%3A${serviceUrl.port}` : ""}`;
17773
+ const serviceUrl2 = new URL(url2);
17774
+ const serviceDomain = `${serviceUrl2.hostname}${serviceUrl2.port ? `%3A${serviceUrl2.port}` : ""}`;
17712
17775
  const localServiceDid = `did:web:${serviceDomain}`;
17713
17776
  const getInferredServiceDid = /* @__PURE__ */ __name((did2) => {
17714
17777
  if (!did2.startsWith("did:web:")) return null;
@@ -17729,12 +17792,12 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17729
17792
  }), "getInboxService");
17730
17793
  const inferredServiceDid = getInferredServiceDid(recipientDid);
17731
17794
  if (inferredServiceDid === localServiceDid) {
17732
- return `${serviceUrl.origin}/api/inbox/receive`;
17795
+ return `${serviceUrl2.origin}/api/inbox/receive`;
17733
17796
  }
17734
17797
  const didDoc = await _learnCard.invoke.resolveDid(recipientDid);
17735
17798
  const inboxService = getInboxService(didDoc);
17736
17799
  if ((inboxService?.serviceDid || inferredServiceDid) === localServiceDid) {
17737
- return `${serviceUrl.origin}/api/inbox/receive`;
17800
+ return `${serviceUrl2.origin}/api/inbox/receive`;
17738
17801
  }
17739
17802
  if (!inboxService?.serviceEndpoint) {
17740
17803
  throw new Error(
@@ -17896,7 +17959,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17896
17959
  return client.profileManager.updateProfileManager.mutate(manager);
17897
17960
  }, "updateProfileManagerProfile"),
17898
17961
  deleteProfile: /* @__PURE__ */ __name(async () => {
17899
- if (!userData) throw new Error("Account does not exist!");
17962
+ await initialQuery;
17963
+ if (!apiToken) await ensureUser();
17900
17964
  const result = await client.profile.deleteProfile.mutate();
17901
17965
  if (result) {
17902
17966
  userData = void 0;
@@ -18047,11 +18111,10 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
18047
18111
  proofFormat: "jwt",
18048
18112
  challenge: `inbox-federation-${crypto.randomUUID()}`
18049
18113
  });
18050
- let receiveUrl = inboxEndpoint;
18051
- if (receiveUrl.includes("localhost")) {
18052
- receiveUrl = receiveUrl.replace("https://", "http://");
18053
- }
18114
+ const receiveUrl = validateFederationUrl(inboxEndpoint, federationConfig);
18054
18115
  const response = await fetch(receiveUrl, {
18116
+ // A redirect must not bypass endpoint validation or forward credentials.
18117
+ redirect: "error",
18055
18118
  method: "POST",
18056
18119
  headers: {
18057
18120
  "Content-Type": "application/json",
@@ -18526,8 +18589,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
18526
18589
  const isDid = recipient.startsWith("did:");
18527
18590
  const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipient);
18528
18591
  const isPhone = /^\+?[\d\s-]{10,}$/.test(recipient.replace(/[\s-]/g, ""));
18529
- const serviceUrl = new URL(url2);
18530
- const serviceDomain = `${serviceUrl.hostname}${serviceUrl.port ? `%3A${serviceUrl.port}` : ""}`;
18592
+ const serviceUrl2 = new URL(url2);
18593
+ const serviceDomain = `${serviceUrl2.hostname}${serviceUrl2.port ? `%3A${serviceUrl2.port}` : ""}`;
18531
18594
  const recipientDomain = recipient.startsWith("did:web:") ? recipient.split(":")[2] : void 0;
18532
18595
  const isRemoteDidWebRecipient = recipient.startsWith("did:web:") && recipientDomain !== serviceDomain;
18533
18596
  if (isRemoteDidWebRecipient && input.templateUri) {
@@ -19327,7 +19390,9 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
19327
19390
  }, "getActivityChain"),
19328
19391
  isServiceTrusted: /* @__PURE__ */ __name(async (_learnCard, serviceDid) => {
19329
19392
  const trustedServices = await client.federation.getTrustedServices.query({});
19330
- return trustedServices.some((s) => s.did === serviceDid);
19393
+ return trustedServices.some(
19394
+ (service) => service.did === serviceDid
19395
+ );
19331
19396
  }, "isServiceTrusted"),
19332
19397
  getTrustedServices: /* @__PURE__ */ __name(async () => {
19333
19398
  return client.federation.getTrustedServices.query({});
@@ -19383,9 +19448,7 @@ var getVerifyBoostPlugin = /* @__PURE__ */ __name(async (learnCard, trustedBoost
19383
19448
  const boostCredential = credential?.boostCredential;
19384
19449
  try {
19385
19450
  if (boostCredential) {
19386
- const verifyBoostCredential = await learnCard.invoke.verifyCredential(
19387
- boostCredential
19388
- );
19451
+ const verifyBoostCredential = await learnCard.invoke.verifyCredential(boostCredential);
19389
19452
  const boostCredentialErrors = verifyBoostCredential.errors ?? [];
19390
19453
  if (verifyBoostCredential.status?.length) {
19391
19454
  verificationCheck.status = [