@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.
@@ -17528,6 +17528,56 @@ mustache.Writer = Writer;
17528
17528
  var mustache_default = mustache;
17529
17529
 
17530
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");
17531
17581
  var uint8ArrayToBase64Url = /* @__PURE__ */ __name((bytes) => {
17532
17582
  let binary = "";
17533
17583
  for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
@@ -17621,8 +17671,20 @@ var issueCredentialWithNetworkStatus = /* @__PURE__ */ __name(async (learnCard,
17621
17671
  }, "issueCredentialWithNetworkStatus");
17622
17672
  async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, options) {
17623
17673
  const apiToken = typeof apiTokenOrOptions === "string" ? apiTokenOrOptions : void 0;
17624
- const guardianApprovalGetter = (typeof apiTokenOrOptions === "object" ? apiTokenOrOptions?.guardianApprovalGetter : void 0) ?? options?.guardianApprovalGetter;
17625
- 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
+ };
17626
17688
  let did = "";
17627
17689
  try {
17628
17690
  const idPlane = learnCard?.id;
@@ -17691,8 +17753,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17691
17753
  }
17692
17754
  }, "getBoostTemplateForIssuance");
17693
17755
  const getInboxEndpointForDid = /* @__PURE__ */ __name(async (_learnCard, recipientDid) => {
17694
- const serviceUrl = new URL(url2);
17695
- 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}` : ""}`;
17696
17758
  const localServiceDid = `did:web:${serviceDomain}`;
17697
17759
  const getInferredServiceDid = /* @__PURE__ */ __name((did2) => {
17698
17760
  if (!did2.startsWith("did:web:")) return null;
@@ -17713,12 +17775,12 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17713
17775
  }), "getInboxService");
17714
17776
  const inferredServiceDid = getInferredServiceDid(recipientDid);
17715
17777
  if (inferredServiceDid === localServiceDid) {
17716
- return `${serviceUrl.origin}/api/inbox/receive`;
17778
+ return `${serviceUrl2.origin}/api/inbox/receive`;
17717
17779
  }
17718
17780
  const didDoc = await _learnCard.invoke.resolveDid(recipientDid);
17719
17781
  const inboxService = getInboxService(didDoc);
17720
17782
  if ((inboxService?.serviceDid || inferredServiceDid) === localServiceDid) {
17721
- return `${serviceUrl.origin}/api/inbox/receive`;
17783
+ return `${serviceUrl2.origin}/api/inbox/receive`;
17722
17784
  }
17723
17785
  if (!inboxService?.serviceEndpoint) {
17724
17786
  throw new Error(
@@ -17880,7 +17942,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
17880
17942
  return client.profileManager.updateProfileManager.mutate(manager);
17881
17943
  }, "updateProfileManagerProfile"),
17882
17944
  deleteProfile: /* @__PURE__ */ __name(async () => {
17883
- if (!userData) throw new Error("Account does not exist!");
17945
+ await initialQuery;
17946
+ if (!apiToken) await ensureUser();
17884
17947
  const result = await client.profile.deleteProfile.mutate();
17885
17948
  if (result) {
17886
17949
  userData = void 0;
@@ -18031,11 +18094,10 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
18031
18094
  proofFormat: "jwt",
18032
18095
  challenge: `inbox-federation-${crypto.randomUUID()}`
18033
18096
  });
18034
- let receiveUrl = inboxEndpoint;
18035
- if (receiveUrl.includes("localhost")) {
18036
- receiveUrl = receiveUrl.replace("https://", "http://");
18037
- }
18097
+ const receiveUrl = validateFederationUrl(inboxEndpoint, federationConfig);
18038
18098
  const response = await fetch(receiveUrl, {
18099
+ // A redirect must not bypass endpoint validation or forward credentials.
18100
+ redirect: "error",
18039
18101
  method: "POST",
18040
18102
  headers: {
18041
18103
  "Content-Type": "application/json",
@@ -18510,8 +18572,8 @@ async function getLearnCardNetworkPlugin(learnCard, url2, apiTokenOrOptions, opt
18510
18572
  const isDid = recipient.startsWith("did:");
18511
18573
  const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipient);
18512
18574
  const isPhone = /^\+?[\d\s-]{10,}$/.test(recipient.replace(/[\s-]/g, ""));
18513
- const serviceUrl = new URL(url2);
18514
- 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}` : ""}`;
18515
18577
  const recipientDomain = recipient.startsWith("did:web:") ? recipient.split(":")[2] : void 0;
18516
18578
  const isRemoteDidWebRecipient = recipient.startsWith("did:web:") && recipientDomain !== serviceDomain;
18517
18579
  if (isRemoteDidWebRecipient && input.templateUri) {