@learncard/network-plugin 2.13.16 → 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.
@@ -17545,6 +17545,56 @@ mustache.Writer = Writer;
17545
17545
  var mustache_default = mustache;
17546
17546
 
17547
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");
17548
17598
  var uint8ArrayToBase64Url = /* @__PURE__ */ __name((bytes) => {
17549
17599
  let binary = "";
17550
17600
  for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
@@ -17638,8 +17688,20 @@ var issueCredentialWithNetworkStatus = /* @__PURE__ */ __name(async (learnCard,
17638
17688
  }, "issueCredentialWithNetworkStatus");
17639
17689
  async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, options) {
17640
17690
  const apiToken = typeof apiTokenOrOptions === "string" ? apiTokenOrOptions : void 0;
17641
- const guardianApprovalGetter = (typeof apiTokenOrOptions === "object" ? apiTokenOrOptions?.guardianApprovalGetter : void 0) ?? options?.guardianApprovalGetter;
17642
- 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
+ };
17643
17705
  let did = "";
17644
17706
  try {
17645
17707
  const idPlane = learnCard?.id;
@@ -17708,8 +17770,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17708
17770
  }
17709
17771
  }, "getBoostTemplateForIssuance");
17710
17772
  const getInboxEndpointForDid = /* @__PURE__ */ __name(async (_learnCard, recipientDid) => {
17711
- const serviceUrl = new URL(url2);
17712
- 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}` : ""}`;
17713
17775
  const localServiceDid = `did:web:${serviceDomain}`;
17714
17776
  const getInferredServiceDid = /* @__PURE__ */ __name((did2) => {
17715
17777
  if (!did2.startsWith("did:web:")) return null;
@@ -17730,12 +17792,12 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17730
17792
  }), "getInboxService");
17731
17793
  const inferredServiceDid = getInferredServiceDid(recipientDid);
17732
17794
  if (inferredServiceDid === localServiceDid) {
17733
- return `${serviceUrl.origin}/api/inbox/receive`;
17795
+ return `${serviceUrl2.origin}/api/inbox/receive`;
17734
17796
  }
17735
17797
  const didDoc = await _learnCard.invoke.resolveDid(recipientDid);
17736
17798
  const inboxService = getInboxService(didDoc);
17737
17799
  if ((inboxService?.serviceDid || inferredServiceDid) === localServiceDid) {
17738
- return `${serviceUrl.origin}/api/inbox/receive`;
17800
+ return `${serviceUrl2.origin}/api/inbox/receive`;
17739
17801
  }
17740
17802
  if (!inboxService?.serviceEndpoint) {
17741
17803
  throw new Error(
@@ -17897,7 +17959,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17897
17959
  return client.profileManager.updateProfileManager.mutate(manager);
17898
17960
  }, "updateProfileManagerProfile"),
17899
17961
  deleteProfile: /* @__PURE__ */ __name(async () => {
17900
- if (!userData) throw new Error("Account does not exist!");
17962
+ await initialQuery;
17963
+ if (!apiToken) await ensureUser();
17901
17964
  const result = await client.profile.deleteProfile.mutate();
17902
17965
  if (result) {
17903
17966
  userData = void 0;
@@ -18048,11 +18111,10 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
18048
18111
  proofFormat: "jwt",
18049
18112
  challenge: `inbox-federation-${crypto.randomUUID()}`
18050
18113
  });
18051
- let receiveUrl = inboxEndpoint;
18052
- if (receiveUrl.includes("localhost")) {
18053
- receiveUrl = receiveUrl.replace("https://", "http://");
18054
- }
18114
+ const receiveUrl = validateFederationUrl(inboxEndpoint, federationConfig);
18055
18115
  const response = await fetch(receiveUrl, {
18116
+ // A redirect must not bypass endpoint validation or forward credentials.
18117
+ redirect: "error",
18056
18118
  method: "POST",
18057
18119
  headers: {
18058
18120
  "Content-Type": "application/json",
@@ -18527,8 +18589,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
18527
18589
  const isDid = recipient.startsWith("did:");
18528
18590
  const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipient);
18529
18591
  const isPhone = /^\+?[\d\s-]{10,}$/.test(recipient.replace(/[\s-]/g, ""));
18530
- const serviceUrl = new URL(url2);
18531
- 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}` : ""}`;
18532
18594
  const recipientDomain = recipient.startsWith("did:web:") ? recipient.split(":")[2] : void 0;
18533
18595
  const isRemoteDidWebRecipient = recipient.startsWith("did:web:") && recipientDomain !== serviceDomain;
18534
18596
  if (isRemoteDidWebRecipient && input.templateUri) {