<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: schmudde</title>
    <description>The latest articles on DEV Community by schmudde (@schmudde).</description>
    <link>https://dev.to/schmudde</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F40138%2F0a3b9fc1-ab58-475c-aa70-cf0121107443.jpeg</url>
      <title>DEV Community: schmudde</title>
      <link>https://dev.to/schmudde</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/schmudde"/>
    <language>en</language>
    <item>
      <title>Turning URLs Into Meaningful Names Using Clojure</title>
      <dc:creator>schmudde</dc:creator>
      <pubDate>Wed, 01 Dec 2021 13:13:25 +0000</pubDate>
      <link>https://dev.to/schmudde/turning-urls-into-meaningful-names-using-clojure-1ggf</link>
      <guid>https://dev.to/schmudde/turning-urls-into-meaningful-names-using-clojure-1ggf</guid>
      <description>&lt;p&gt;URLs are wonderful to work with because they are structured, human meaningful, globally unique identifiers. But they do have a few thorny edge cases.&lt;/p&gt;

&lt;p&gt;The following examples explore the domain name system using Clojure. The goal is to derive meaningful information about a resource owner's identity from any URL. For example, the URLs &lt;a href="http://wikipedia.org/wiki/Peoria"&gt;&lt;code&gt;http://wikipedia.org/wiki/Peoria&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Peoria"&gt;&lt;code&gt;https://en.wikipedia.org/wiki/Peoria&lt;/code&gt;&lt;/a&gt; look different but they both resolve to the same document and they are both managed by the Wikimedia Foundation. They do not pass the threshold of being meaningfully different.&lt;/p&gt;

&lt;p&gt;There is some subjectivity in this analysis. The exploration in this article goes all the way back to 1987 in an effort to automatically determine the unique identity of a domain's owner. The work primarily relies on two libraries, the &lt;a href="https://commons.apache.org/proper/commons-validator/"&gt;Apache Commons Validator API&lt;/a&gt; and Java's &lt;a href="https://docs.oracle.com/javase/7/docs/api/java/net/URI.html"&gt;Uniform Resource Identifier (URI)&lt;/a&gt; reference. The latter is our parser while the former is, unsurprisingly, our validator.&lt;/p&gt;

&lt;p&gt;First, create the Clojure namespace with the requisite libraries. Java's URI library, java.net.URI, comes with Clojure. To use the Apache Commons Validator API, add &lt;code&gt;commons-validator/commons-validator {:mvn/version "1.7"}&lt;/code&gt; to the project's &lt;code&gt;deps.edn&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(ns domain-names
  (:import [org.apache.commons.validator.routines UrlValidator DomainValidator]
           [java.net URI])
  (:require [clojure.string :as str]
            [clojure.edn :as edn]))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;;; With that out of the way, it's time for some exploration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain Names Explained
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The URL
&lt;/h3&gt;

&lt;p&gt;A Uniform Resource Locator (URL) is the addresse of a unique resource on the web. A URL can be validated using the &lt;code&gt;.isValid&lt;/code&gt; &lt;a href="https://commons.apache.org/proper/commons-validator/apidocs/org/apache/commons/validator/routines/UrlValidator.html"&gt;method&lt;/a&gt;.  It's easy to create a custom function in Clojure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(defn url? [url]
   (-&amp;gt; (UrlValidator.)
       (.isValid url)))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;(url? "http://schmud.de")&lt;/code&gt; ⇒ &lt;code&gt;true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;eMails won't pass. Neither will domain names.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(url? "email@example.com")&lt;/code&gt; ⇒ &lt;code&gt;false&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(url? "schmud.de")&lt;/code&gt; ⇒ &lt;code&gt;false&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What Is a Domain Name?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.ietf.org/rfc/rfc1034.txt"&gt;RFC 1034&lt;/a&gt; laid the foundation for domain names in November 1987. The domain name indicates which server hosts a web resource. Domain names are owned by a person or an organization. Like all proper names, they are a form of identification.&lt;/p&gt;

&lt;p&gt;This identification is what we're after. Who is the owner of this resource? It turns out answering that question is not so simple because subdomains can indicate meaningful distinctions. Consider these subdomains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;www.tumblr.com&lt;/code&gt; is not meaningfully different from &lt;code&gt;tumblr.com&lt;/code&gt;. They are both used to indicate the entity known as Tumblr.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://schmudde.tumblr.com/"&gt;&lt;code&gt;schmudde.tumblr.com&lt;/code&gt;&lt;/a&gt; is meaningfully unique from &lt;a href="https://journalofanobody.tumblr.com"&gt;&lt;code&gt;journalofanobody.tumblr.com&lt;/code&gt;&lt;/a&gt;. Within the domain of Tumblr, there is only one subdomain named schmudde, which points to a distinct person (myself).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://azure.microsoft.com"&gt;&lt;code&gt;https://azure.microsoft.com&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://www.microsoft.com/windows"&gt;&lt;code&gt;https://www.microsoft.com/windows&lt;/code&gt;&lt;/a&gt; shows how Microsoft thinks about two different products. The &lt;code&gt;www&lt;/code&gt; subdomain does not indicate a meaningful distinction while the &lt;code&gt;azure&lt;/code&gt; prefix reflect's Microsoft's corporate structure; Azure makes up one of Microsoft's four distinct engineering divisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on your purpose, subdomain distinctions may be significant.  Some further understainding of domain name is necessary before moving on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Root Domain, the Top Level Domain, and the Subdomain
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Root Domain and Subdomains
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Subdomains&lt;/em&gt; are defined relatively. RFC 1034 uses this example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;A.B.C.D&lt;/code&gt; is a subdomain of &lt;code&gt;B.C.D&lt;/code&gt;, &lt;code&gt;C.D&lt;/code&gt;, &lt;code&gt;D&lt;/code&gt;, and &lt;code&gt;" "&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thus the &lt;em&gt;root domain&lt;/em&gt; is the only absolute component of a URL. It is &lt;code&gt;" "&lt;/code&gt;. A more concrete example from the RFC may help illustrate:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                               |
                               |
         +---------------------+------------------+
         |                     |                  |
        MIL                   EDU                ARPA
         |                     |                  |
         |                     |                  |
   +-----+-----+               |     +------+-----+-----+
   |     |     |               |     |      |           |
  BRL  NOSC  DARPA             |  IN-ADDR  SRI-NIC     ACC
                               |
   +--------+------------------+---------------+--------+
   |        |                  |               |        |
  UCI      MIT                 |              UDEL     YALE
            |                 ISI
            |                  |
        +---+---+              |
        |       |              |
       LCS  ACHILLES  +--+-----+-----+--------+
        |             |  |     |     |        |
        XX            A  C   VAXA  VENERA Mockapetris
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The RFC explains:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The root domain has three immediate subdomains: MIL, EDU, and ARPA.  The &lt;a href="http://LCS.MIT.EDU"&gt;LCS.MIT.EDU&lt;/a&gt; domain has one immediate subdomain named &lt;a href="http://XX.LCS.MIT.EDU"&gt;XX.LCS.MIT.EDU&lt;/a&gt;. All of the leaves are also domains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These days the root domain sometimes refers to the domain name registered with a domain name registrar. For example, I registered &lt;code&gt;netart.today&lt;/code&gt;, not &lt;code&gt;www.netart.today&lt;/code&gt;. The more precise definition is the domain which all other subdomains branch from.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Top Level Domain
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;mil&lt;/code&gt; and &lt;code&gt;edu&lt;/code&gt; eventually became known as generic Top Level Domains (gTLD) along with other familiar extentions like &lt;code&gt;com&lt;/code&gt; and &lt;code&gt;org&lt;/code&gt;. &lt;a href="https://commons.apache.org/proper/commons-validator/apidocs/org/apache/commons/validator/routines/DomainValidator.html"&gt;The Apache Commons Validator&lt;/a&gt; can help identify the Top Level Domains (TLDs) as currently defined and maintained by the Internet Assigned Numbers Authority (IANA).&lt;/p&gt;

&lt;p&gt;The familiar gTLDs:&lt;br&gt;
&lt;code&gt;(.isValidGenericTld (DomainValidator/getInstance) ".edu")&lt;/code&gt; ⇒ &lt;code&gt;true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Country codes as TLDs:&lt;br&gt;
&lt;code&gt;(.isValidCountryCodeTld (DomainValidator/getInstance) ".cn")&lt;/code&gt; ⇒ &lt;code&gt;true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;TLDs reserved for internet infrastructure:&lt;br&gt;
&lt;code&gt;(.isValidInfrastructureTld (DomainValidator/getInstance) ".arpa")&lt;/code&gt; ⇒ &lt;code&gt;true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now use the &lt;code&gt;DomainValidator&lt;/code&gt;'s general &lt;code&gt;.isValid&lt;/code&gt; method to see if a gTLD + two subdomains is valid:&lt;br&gt;
&lt;code&gt;(.isValid (DomainValidator/getInstance) "www.schmud.de")&lt;/code&gt; ⇒ &lt;code&gt;true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add a protocol to the subdomains and the gTLD to form a URL:&lt;br&gt;
&lt;code&gt;(.isValid (DomainValidator/getInstance) "http://www.schmud.de")&lt;/code&gt; ⇒ &lt;code&gt;false&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A URL ≠ a domain name.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting the Domain Name
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Parsing URLs
&lt;/h3&gt;

&lt;p&gt;Getting the domain name from a URL means we need to parse the URL string. Parsing URLs in Clojure and Java can be done with &lt;a href="https://docs.oracle.com/javase/7/docs/api/java/net/URI.html"&gt;java.net.URI&lt;/a&gt;. The URI object is parsed into these components: &lt;code&gt;[scheme:][//authority][path][?query][#fragment]&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Authority
&lt;/h3&gt;

&lt;p&gt;The Authority is the component that will hold the domain name (aka host name) in java.net.URI objects. It can also hold an authentication section (username and password) and an optional port number precededby a &lt;code&gt;:&lt;/code&gt;. But I don't want those. To exclusively get the host, use &lt;code&gt;.getHost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's a URL with a port number, scheme, and path to demonstrate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(-&amp;gt; (java.net.URI/create "https://schmud.de:443/books.html")
        .getHost)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⇒ &lt;code&gt;"schmud.de"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;java.net.URI/create&lt;/code&gt; method expects strings that it can parse as a URI object. If you just give it a domain name, it will not fail but it also will not parse the host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(-&amp;gt; (java.net.URI/create "schmud.de")
        .getHost)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⇒ &lt;code&gt;nil&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But it will fail if you give it something that it cannot parse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(try
  (-&amp;gt; (java.net.URI/create "://schmud.de/books.html")
      .getHost)
  (catch Exception e e))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⇒ &lt;code&gt;error&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The best solution probably includes validation. The code won't even try to parse bad input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(#(when-let [valid-url (when (url? %) %)]
    (-&amp;gt; valid-url
        java.net.URI/create
        .getHost)) "http://schmud.de/books.html")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⇒ &lt;code&gt;"schmud.de"&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(#(when-let [valid-url (when (url? %) %)]
    (-&amp;gt; valid-url
        java.net.URI/create
        .getHost)) "://schmud.de/books.html")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⇒ &lt;code&gt;nil&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The URL Validator also guards against TLDs that do not exist. In this case, the fictitious &lt;code&gt;.ded&lt;/code&gt; TLD.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(#(when-let [valid-url (when (url? %) %)]
    (-&amp;gt; valid-url
        java.net.URI/create
        .getHost)) "http://schmud.ded")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⇒ &lt;code&gt;nil&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.getHost&lt;/code&gt; method itself suffers one drawback: it can trip up when interpreting URL schemes that include alphabetic characters outside the Latin alphabet (essentially URL schemes after &lt;a href="https://datatracker.ietf.org/doc/html/rfc2396"&gt;RFC 2396&lt;/a&gt;). Consider the difference:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(.getHost (java.net.URI/create "http://köpönyeg.hu"))&lt;/code&gt; ⇒ &lt;code&gt;nil&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(.getRawAuthority (java.net.URI/create "http://köpönyeg.hu"))&lt;/code&gt; ⇒ &lt;code&gt;"köpönyeg.hu"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.getRawAuthority&lt;/code&gt; returns the value without interpreting any escaped octets. The strings returned by these methods may contain both escaped octets and other characters, and will not contain any illegal characters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parsing Subdomains
&lt;/h3&gt;

&lt;p&gt;It's important to remember the goal when deciding how to deal with subdomains. I'm looking for any meaningfully unique identity. &lt;code&gt;azure.microsoft.com&lt;/code&gt; and &lt;code&gt;schmudde.tumblr.com&lt;/code&gt; are meaningfully unique in a way that &lt;code&gt;en.wikipedia.org&lt;/code&gt; and &lt;code&gt;www.schmud.de&lt;/code&gt; are not.&lt;/p&gt;

&lt;p&gt;A more strict solution may consider all known &lt;a href="https://publicsuffix.org/list/"&gt;public suffixes&lt;/a&gt; and only accept a top level domain + one subdomain.&lt;/p&gt;

&lt;p&gt;My implementation takes the opposite approach. It removes &lt;a href="https://github.com/danielmiessler/SecLists/blob/master/Discovery/DNS/subdomains-top1million-5000.txt"&gt;common subdomains&lt;/a&gt; compiled by SecLists. Most of the 5000+ common subdomains on the list do not provide meaningful identity differentiation, such as &lt;code&gt;www.&lt;/code&gt;, &lt;code&gt;en.&lt;/code&gt;, &lt;code&gt;m.&lt;/code&gt;, &lt;code&gt;shop.&lt;/code&gt;, and &lt;code&gt;www.news.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(def common-subdomains
  "Slurp and sort then drop the first 200 longest names because
   they seem especially esoteric."
  (-&amp;gt;&amp;gt; (slurp "resources/subdomains-top1million-5000.txt")
                            clojure.edn/read-string
                            (sort-by count)
                            reverse
                            (drop 200)))

(defn remove-subdomain-prefix
  "In: Domain name (string)
   Out: Host without a prefix.

   has-more-than-one-subdomain? ensures that shop.google.com -&amp;gt; google.com and shop.com -&amp;gt; shop.com"
  [domain-name]
  (let [has-more-than-one-subdomain? (when (not= (str/index-of domain-name ".")
                                                 (str/last-index-of domain-name "."))
                                       true)
        matching-prefix (-&amp;gt; (partial str/starts-with? domain-name)
                            (filter common-subdomains)
                            first)]
    (if (and has-more-than-one-subdomain? matching-prefix)
      (str/replace-first domain-name matching-prefix "")
      domain-name)))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common subdomains:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(remove-subdomain-prefix "www.company.com")&lt;/code&gt; ⇒ &lt;code&gt;"company.com"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(remove-subdomain-prefix "news.company.com")&lt;/code&gt; ⇒ &lt;code&gt;"company.com"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Common subdomain compounds are also caught. &lt;code&gt;remove-subdomain-prefix&lt;/code&gt; sorts the list of common subdomain prefixes from longest to shortest. Therefore the function removes &lt;code&gt;www.news.&lt;/code&gt; (longer) rather than just &lt;code&gt;www.&lt;/code&gt; or &lt;code&gt;news.&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(remove-subdomain-prefix "www.news.company.com")&lt;/code&gt; ⇒ &lt;code&gt;"company.com"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Common subdomain prefixes do not get confused with the primary subdomain:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(remove-subdomain-prefix "shop.google.com")&lt;/code&gt; ⇒ &lt;code&gt;"google.com"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(remove-subdomain-prefix "shop.com")&lt;/code&gt; ⇒ &lt;code&gt;"shop.com"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Significant subdomains are retained:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(remove-subdomain-prefix "www.news.chevrolet.gm.com")&lt;/code&gt; ⇒ &lt;code&gt;"chevrolet.gm.com"&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting a Meaningful Identifier
&lt;/h2&gt;

&lt;p&gt;Put it all together to get meaningful domain names from any URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(defn get-host [x]
  (if (url? x)
    (-&amp;gt; (java.net.URI/create x)
        .getRawAuthority
        remove-subdomain-prefix)
   (str "`" x "` is not a valid url. Expecting [scheme:][//authority][path][?query][#fragment]")))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;(get-host "http://m.google.com/community")&lt;/code&gt; ⇒ &lt;code&gt;"google.com"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(get-host "ttp://m.google.com/community")&lt;/code&gt; ⇒ &lt;code&gt;"ttp://m.google.com/community is not a valid url. Expecting [scheme:][//authority][path][?query][#fragment]"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;(get-host "http://schmudde.tumblr.com/")&lt;/code&gt; ⇒ &lt;code&gt;"schmudde.tumblr.com"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I believe that the &lt;code&gt;get-host&lt;/code&gt; function makes the right trade offs when considering the stated goals. The next step could be to turn these domain name rules into reusable Clojure specs. Check out Conan Cook's &lt;a href="https://conan.is/blogging/a-spec-for-urls-in-clojure.html"&gt;&lt;em&gt;A spec for URLs in Clojure&lt;/em&gt;&lt;/a&gt; and associated &lt;a href="https://gist.github.com/conan/2edca210999b96ad26d38c1ee96dfe40"&gt;generators&lt;/a&gt; for a good introduction.&lt;/p&gt;

&lt;p&gt;Most of my work deals with identity and time in computing systems. See my posts &lt;a href="https://schmud.de/posts/2020-07-21-storing-time-1.html"&gt;&lt;em&gt;Storing Time - Part 1&lt;/em&gt;&lt;/a&gt; and &lt;a href="https://schmud.de/posts/2020-07-22-storing-time-2.html"&gt;&lt;em&gt;Storing Time - Part 2&lt;/em&gt;&lt;/a&gt; for a deep dive into computational memory and recording time in Clojure and Java. Thanks for reading!&lt;/p&gt;

</description>
      <category>clojure</category>
      <category>dns</category>
      <category>apache</category>
      <category>java</category>
    </item>
    <item>
      <title>Getting Started With the Node Solid Server </title>
      <dc:creator>schmudde</dc:creator>
      <pubDate>Fri, 10 Sep 2021 08:20:41 +0000</pubDate>
      <link>https://dev.to/schmudde/the-node-solid-server-5948</link>
      <guid>https://dev.to/schmudde/the-node-solid-server-5948</guid>
      <description>&lt;p&gt;&lt;strong&gt;A dive into Solid, including a complete how-to for starting your own Solid server using Node.js.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Solid is a &lt;a href="https://solidproject.org/TR/protocol" rel="noopener noreferrer"&gt;specification&lt;/a&gt; that lets people store their data in federated data stores called Pods. The information on each Pod is linked through a person's online &lt;a href="https://solidproject.org/TR/protocol#identity" rel="noopener noreferrer"&gt;identity&lt;/a&gt;. Identity is addressed with a &lt;a href="https://en.wikipedia.org/wiki/WebID" rel="noopener noreferrer"&gt;WebID&lt;/a&gt;, which is just an HTTP URL that points to a profile.&lt;/p&gt;

&lt;p&gt;As depicted in the image below, the personal data pod can include things like pictures, calendar information, and personal contacts. The left side of the image depicts how this information is stored in walled gardens on today's internet platforms. The right shows how the data is stored in a personal data pod. Applications can simply query a Pod to get the information they need to provide their service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextjournal.com%2Fdata%2FQmQ2x8S1AHw3nTQwfGfPmBTABJXLApuow7xNLUqheyg7vG%3Fcontent-type%3Dimage%2Fsvg%252Bxml%26node-id%3De456a32c-a4c7-402b-86f0-30aee7af77ba%26filename%3Dapps-as-views.svg%26node-kind%3Dfile" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextjournal.com%2Fdata%2FQmQ2x8S1AHw3nTQwfGfPmBTABJXLApuow7xNLUqheyg7vG%3Fcontent-type%3Dimage%2Fsvg%252Bxml%26node-id%3De456a32c-a4c7-402b-86f0-30aee7af77ba%26filename%3Dapps-as-views.svg%26node-kind%3Dfile" title="&amp;lt;p&amp;gt;Applications can query a pod to get the data they need. © Ruben Verborgh, CC-by-4.0&amp;lt;/p&amp;gt;" alt="apps-as-views.svg"&gt;&lt;/a&gt; &lt;em&gt;Figure: Applications can query a pod to get the data they need. CC-by-4.0 Ruben Verborgh&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Individuals can therefore manage their personal information in whatever place they choose. The Solid standard makes it easy to grant and revoke access to personal information to specific individuals, organizations, and applications. This is a win for personal privacy.&lt;/p&gt;

&lt;p&gt;Pods make the web more secure because data is not pooled in a single place. Personal information is spread across a federation of servers. While Solid does not prevent individual targeting, its adoption would curb the massive data breaches that have become commonplace.&lt;/p&gt;

&lt;p&gt;Individual Pods can move between servers with very little friction. If a person does not like a server's security practices, terms of service, or notices other forms of abuse, they can simply move their Pod to another server or even &lt;em&gt;host their Pod on their own server&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That last sentence is what this article is about. Running the code in this article will stand up a Solid server that can be found on the public internet. I will take you through each step along the way, including how to access the server from &lt;a href="https://solidproject.org//apps" rel="noopener noreferrer"&gt;an outside Solid application&lt;/a&gt;. Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ngrok
&lt;/h2&gt;

&lt;p&gt;The Solid server runs locally; Ngrok will provide a public URL for the local Solid server.&lt;/p&gt;

&lt;p&gt;I have configured Ngrok to make the Solid server available at &lt;code&gt;https://btf.ngrok.io&lt;/code&gt;. The full &lt;code&gt;ngrok.yml&lt;/code&gt; file can be found in the &lt;strong&gt;Appendix&lt;/strong&gt;. See the &lt;strong&gt;Ngrok Configuration&lt;/strong&gt; section.&lt;/p&gt;

&lt;p&gt;A reserved subdomain (&lt;code&gt;btf&lt;/code&gt; in this case) is only available through the paid plan. If you don't have a paid plan, you'll have to provide Ngrok's generated subdomain to the Solid server before you start it up.&lt;/p&gt;

&lt;p&gt;I'll add my authentication token to the &lt;code&gt;ngrok.yml&lt;/code&gt; configuration file. The token is held in an environmental variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'authtoken:' $ngrok | cat - ngrok.yml &amp;gt; /tmp/out &amp;amp;&amp;amp; mv /tmp/out ngrok.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's time to &lt;code&gt;start&lt;/code&gt; the &lt;code&gt;ngrok&lt;/code&gt; service in the background using &lt;code&gt;nohup&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nohup ./ngrok start --all --config="/ngrok.yml" --log="/tmp/ngrok.log" &amp;amp;&amp;gt; /tmp/ngrok-run.log &amp;amp; sleep 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Ngrok tunnel is now live at &lt;a href="https://btf.ngrok.io/" rel="noopener noreferrer"&gt;https://btf.ngrok.io/&lt;/a&gt;. The next step is standing up a Solid server at &lt;a href="https://localhost:8443" rel="noopener noreferrer"&gt;https://localhost:8443&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solid
&lt;/h2&gt;

&lt;p&gt;There are two Solid server implementations that run on Node.js. The &lt;a href="https://github.com/solid/community-server" rel="noopener noreferrer"&gt;Community Solid Server&lt;/a&gt; (&lt;a href="https://solidproject.org//self-hosting/css" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;) is newer, but this article uses the &lt;a href="https://github.com/solid/node-solid-server" rel="noopener noreferrer"&gt;Node Solid Server&lt;/a&gt; (&lt;a href="https://solidproject.org/self-hosting/nss" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;). Both are open-source Node.js implementations of the same Solid standard, independently maintained.&lt;/p&gt;

&lt;p&gt;The Solid server provides the necessary tools to store and manage access to data held in Pods on the sever.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server provides the tools to assert your identity (you are who you say you are, &lt;strong&gt;Authentication&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;The server provides the tools to use your identity (sharing personal information, having conversations on the internet, etc..., &lt;strong&gt;Authorization&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;Once you prove you are in control of your Pod, the authentication is done. This authentication step is not application-specific, it's Pod-specific. Therefore &lt;em&gt;a person does **not&lt;/em&gt;* need a unique name and password for each application they use, they only need a single name and password for the Pod.*&lt;/p&gt;

&lt;h3&gt;
  
  
  Authorization
&lt;/h3&gt;

&lt;p&gt;A Solid application will ask you to authorize certain access permissions. The ultimate authority on who has access to your information does not live within the application (which is generally true today), it lives within a person's Pod.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install the server
&lt;/h3&gt;

&lt;p&gt;Now that some of the Solid server's functions are clear, it's time to install the server using NPM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g solid-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure the Server
&lt;/h3&gt;

&lt;p&gt;The server's configuration is held in &lt;code&gt;config.json&lt;/code&gt; below. A few specific notes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;serverUri&lt;/code&gt; must be set to the reverse proxy's URI, &lt;code&gt;https://btf.ngrok.io&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;port&lt;/code&gt; is set to serve information over SSL at &lt;code&gt;8443&lt;/code&gt;. This number matches the port setting of Ngrok's &lt;code&gt;addr&lt;/code&gt; (address), &lt;code&gt;https://localhost:8443&lt;/code&gt;. See &lt;code&gt;ngrok.yml&lt;/code&gt; in the &lt;strong&gt;Appendix&lt;/strong&gt; for more context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setting &lt;code&gt;webid&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; enables authentication and access control by establishing a relation between the &lt;a href="https://www.w3.org/2005/Incubator/webid/spec/tls/#dfn-webid" rel="noopener noreferrer"&gt;WebID&lt;/a&gt; URI and the person's &lt;a href="https://www.w3.org/2005/Incubator/webid/spec/tls/#dfn-public_key" rel="noopener noreferrer"&gt;public key&lt;/a&gt;s&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "root": "/solid/data",
  "port": "8443",
  "serverUri": "https://btf.ngrok.io",
  "webid": true,
  "mount": "/",
  "configPath": "./config",
  "configFile": "./config.json",
  "dbPath": "./.db",
  "sslKey": "/keys/privkey.pem",
  "sslCert": "/keys/fullchain.pem",
  "multiuser": false,
  "server": {
    "name": "The Beyond the Frame Solid Server",
    "description": "This is a Solid server experiment.",
    "logo": ""
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sslKey&lt;/code&gt; and &lt;code&gt;sslCert&lt;/code&gt; still need to be generated and added to the environment. This can be done using the &lt;code&gt;openssl req&lt;/code&gt; command. OpenSSL is a general purpose cryptographic SSL/TLS (Secure Sockets Layer/Transport Layer Security) toolkit, &lt;code&gt;req&lt;/code&gt; is the specific command to fulfill a X.509 Certificate Signing Request.&lt;/p&gt;

&lt;p&gt;Here is the configuration file that provides the necessary cryptographic information and fulfills the &lt;em&gt;&lt;a href="https://www.ibm.com/docs/en/i/7.2?topic=concepts-distinguished-names-dns" rel="noopener noreferrer"&gt;Distinguished Name&lt;/a&gt;&lt;/em&gt; requirements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ req ]
 default_bits       = 2048
 default_md         = sha256
 prompt             = no
 encrypt_key        = no # do not encrypt the keypair, same as -nodes
 distinguished_name = btf_server
[ btf_server ]
 countryName            = "US"
 stateOrProvinceName    = "Illinois"
 localityName           = "Chicago"
 organizationName       = "Beyond the Frame"
 organizationalUnitName = "None"
 commonName             = "example.com"
 emailAddress           = "example@example.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the &lt;code&gt;req&lt;/code&gt; command to generate a 2048-bit (&lt;code&gt;default_bits&lt;/code&gt;) private key (&lt;code&gt;-keyout /keys/privkey.pem&lt;/code&gt;) and then fulfill a certificate request (&lt;code&gt;-out /keys/fullchain.pem&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir /keys
openssl req -outform PEM -keyform PEM -new -x509 -keyout /keys/privkey.pem -config openssl.cnf -days 365 -out /keys/fullchain.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the Solid server needs a folder to serve. The folder is set by  &lt;code&gt;root&lt;/code&gt; in the configuration file above. Make the corresponding directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p /solid/data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run the Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;solid start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open another window and load https//btf.ngrok.io. This will be the result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fugv3f4cprggm1gqyeg2f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fugv3f4cprggm1gqyeg2f.png" alt="Solid server splash screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some explanation on the magic happening behind the scenes is worthwhile.&lt;/p&gt;

&lt;p&gt;Solid requires valid SSL certificates to function properly. They normally cannot be self-signed. The command &lt;code&gt;solid-test start&lt;/code&gt; can be used to accept self-signed certificates. The command is found in &lt;code&gt;solid-server/bin&lt;/code&gt; under the &lt;code&gt;opt/nodejs&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;However, this article gets away with self-signed certificates because Ngrok's servers use the registered &lt;code&gt;ngrok.com&lt;/code&gt; certificates. Ngrok assumes that the server is running privately, so it doesn't need to check our self-signed certificates. If this was production instance, we would want to use our own certificates rather than Ngrok's.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working With Pods
&lt;/h2&gt;

&lt;h3&gt;
  
  
  WebID
&lt;/h3&gt;

&lt;p&gt;Click the blue &lt;strong&gt;Register&lt;/strong&gt; button at https//btf.ngrok.io to create a WebID. The profile associated with the WebID will be at this URL: &lt;a href="https://btf.ngrok.io/profile/card#me" rel="noopener noreferrer"&gt;https://btf.ngrok.io/profile/card#me&lt;/a&gt;. Use this identifier to interact with Solid pods and apps.&lt;/p&gt;

&lt;p&gt;Note: if &lt;code&gt;multiuser&lt;/code&gt; was set to &lt;code&gt;true&lt;/code&gt; in the &lt;code&gt;config.yml&lt;/code&gt;, this WebID could be customized with a user's name to differentiate it from other Pods on this Solid server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Relationships are the stuff of which information is made. Just about everything in the information system looks like a relationship.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;~ &lt;em&gt;&lt;a href="https://bookwyrm.social/book/48395" rel="noopener noreferrer"&gt;Data and Reality&lt;/a&gt;&lt;/em&gt; (87)&lt;/p&gt;

&lt;p&gt;Now that the personal datastore is addressable at &lt;a href="https://btf.ngrok.io/profile/card#me" rel="noopener noreferrer"&gt;https://btf.ngrok.io/profile/card#me&lt;/a&gt;, web applications can request personal information using a standard schema.&lt;/p&gt;

&lt;p&gt;The schema does more than simply address the data, it can describe relationships between bits of information.&lt;/p&gt;

&lt;p&gt;For example, you may host a photograph on your Pod that you share through an application. If I sign into the same application using my WebID, I might be able to leave a comment on the photograph. That comment could exist on my Pod because I am the original author, but you could still view it through the application interface. The relationship between the photo and the comment can be depicted by a schema. It may look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;https://mypod.solid/comments/36756&amp;gt;
    &amp;lt;http://www.w3.org/ns/oa#hasTarget&amp;gt;
        &amp;lt;https://yourpod.solid/photos/beach&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As Ruben Verborgh illustrates in &lt;em&gt;&lt;a href="https://genr.eu/wp/solid/" rel="noopener noreferrer"&gt;Solid: Personal Data Management Through Linked Data&lt;/a&gt;&lt;/em&gt;, the comment on my Pod is linked to the picture on your Pod through a type defined in a schema. We know that my comment is a response to your picture instead of the other way around.&lt;/p&gt;

&lt;p&gt;Like the markup standards that ensure a website is displayed correctly, schema standards for the world wide web promised to create semantic relationships between pieces of information on the web. This effort is at least 20 years old and their website at &lt;a href="https://schema.org/" rel="noopener noreferrer"&gt;schema.org&lt;/a&gt; just &lt;a href="http://blog.schema.org/2021/06/schemaorg-is-ten.html" rel="noopener noreferrer"&gt;turned 10&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Wikidata is one of the most prominent sites built on structured data. They use an &lt;a href="https://www.w3.org/TR/rdf-schema/" rel="noopener noreferrer"&gt;open schema&lt;/a&gt; called &lt;a href="https://www.w3.org/TR/rdf11-primer/" rel="noopener noreferrer"&gt;RDF&lt;/a&gt;. &lt;a href="https://www.w3.org/TR/rdf11-primer/" rel="noopener noreferrer"&gt;RDF graphs&lt;/a&gt; are sets of subject-predicate-object triples (e.g. &lt;code&gt;&amp;lt;Bob&amp;gt;&lt;/code&gt; &lt;em&gt;subject&lt;/em&gt; &lt;code&gt;&amp;lt;is a&amp;gt;&lt;/code&gt; &lt;em&gt;predicate&lt;/em&gt; &lt;code&gt;&amp;lt;human&amp;gt;&lt;/code&gt; &lt;em&gt;object&lt;/em&gt;). The schema also provides a data-modelling vocabulary for RDF data (e.g. &lt;code&gt;&amp;lt;human&amp;gt;&lt;/code&gt; is a &lt;code&gt;subClassOf&lt;/code&gt; &lt;code&gt;&amp;lt;mammal&amp;gt;&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;These structures allow programmers to create &lt;a href="https://solidproject.org/developers/vocabularies" rel="noopener noreferrer"&gt;vocabularies&lt;/a&gt; - which are essentially rules about what information can and cannot be asserted about a domain. For example, a programmer can build a social graph using the &lt;a href="http://xmlns.com/foaf/spec/" rel="noopener noreferrer"&gt;Friend of a Friend vocabulary&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Solid is built to use RDF. &lt;a href="https://solidproject.org/developers/vocabularies/well-known" rel="noopener noreferrer"&gt;Common vocabularies&lt;/a&gt; can parallel the power of social networks, shared calendars, document collaboration, music libraries, etc.... These vocabularies describe what interactions are possible between Pods. Since vocabularies are built on open standards, independent data sources and applications don't have to build their own custom API. Interoperability is baked in, &lt;a href="https://www.w3.org/2005/Incubator/webid/spec/tls/" rel="noopener noreferrer"&gt;centered on web identity&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apps
&lt;/h2&gt;

&lt;p&gt;Solid supports a rich ecosystem of applications that compete on the merits of their service. The best-performing applications will naturally be complimentary with other services that process the data on your pod. As Jacob O'Bryant &lt;a href="https://jacobobryant.com/p/what-im-trying-to-do/#1-help-build-a-career-path-for-software-inventors" rel="noopener noreferrer"&gt;opines on Solid&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There's an interesting analogy to functional programming: it's better to have 100 apps operate on 1 database than 10 apps operate on 10 databases.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Any one of Solid's &lt;a href="https://solidproject.org//apps" rel="noopener noreferrer"&gt;official list of apps&lt;/a&gt; can be used to read and write data to this new Pod. For example, &lt;em&gt;&lt;a href="https://notepod.vincenttunru.com/" rel="noopener noreferrer"&gt;Notepod&lt;/a&gt;&lt;/em&gt; is a simple Solid-compatible note-taking app. &lt;a href="https://notepod.vincenttunru.com/" rel="noopener noreferrer"&gt;Visit the app&lt;/a&gt;, click &lt;strong&gt;Connect&lt;/strong&gt;, and add this Pod's URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4cs636010gpvrlbkz8gr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4cs636010gpvrlbkz8gr.png" alt="Notepod Connect to Pod"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When asked to authenticate, use the username and password previously created on the Solid server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzyj71gv2ahpn17vwph1z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzyj71gv2ahpn17vwph1z.png" alt="Notepod Login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Grant the requested permissions and the app is connected. Anything created in the app is stored in the Pod. It's worth re-emphasizing that the app doesn't have its own username and password. Solid not only solves the problem of siloed data, it solves the proliferation of usernames and passwords that plague today's internet users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope this article helped convey the power of Solid and how easy it is to get started with your own Solid server. The technology will become more powerful and useful as the federated network of servers grow. When working with the configuration files, make sure to configure Ngrok to use your own subdomain (paid Ngrok plan) or dynamically add the generated subdomain to your Solid server configuration (free Ngrok plan).&lt;/p&gt;

&lt;p&gt;Find me at my blog where I discuss information science, art, and narrative: &lt;a href="https://schmud.de/" rel="noopener noreferrer"&gt;Beyond the Frame&lt;/a&gt;. I can also be reached at &lt;a href="https://mastodon.social/@schmudde" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://twitter.com/dschmudde" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Federated Servers
&lt;/h3&gt;

&lt;p&gt;If you don't like the server that hosts your Pod, simply move it to another server. Here is a list of current Solid servers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://signup.pod.inrupt.com" rel="noopener noreferrer"&gt;Inrupt Pod Spaces&lt;/a&gt; by &lt;a href="https://inrupt.com/terms-of-service" rel="noopener noreferrer"&gt;Inrupt, Inc.&lt;/a&gt; hosted at &lt;a href="https://aws.amazon.com" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt; in the USA. I have &lt;a href="https://pod.inrupt.com/schmudde/profile/card#me" rel="noopener noreferrer"&gt;a Pod and public profile&lt;/a&gt; on this server.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pod.inrupt.com/schmudde/profile/card#me" rel="noopener noreferrer"&gt;inru&lt;/a&gt;&lt;a href="https://inrupt.net" rel="noopener noreferrer"&gt;pt.net&lt;/a&gt; by &lt;a href="https://inrupt.com/terms-of-service" rel="noopener noreferrer"&gt;Inrupt, Inc.&lt;/a&gt; hosted at &lt;a href="https://aws.amazon.com" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt; in the USA&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://solidcommunity.net/" rel="noopener noreferrer"&gt;solidcommunity.net&lt;/a&gt; by &lt;a href="https://github.com/solid/solidcommunity.net_operations" rel="noopener noreferrer"&gt;Solid Project&lt;/a&gt; hosted at &lt;a href="https://www.digitalocean.com" rel="noopener noreferrer"&gt;Digital Ocean&lt;/a&gt; in the UK. I have &lt;a href="https://schmudde.solidcommunity.net/profile/card#me" rel="noopener noreferrer"&gt;a Pod and public profile&lt;/a&gt; on this server.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://solidweb.org" rel="noopener noreferrer"&gt;solidweb.org&lt;/a&gt; by &lt;a href="https://gitlab.com/groups/solidweb.org/-/group_members" rel="noopener noreferrer"&gt;Solid Grassroots&lt;/a&gt; hosted at &lt;a href="https://www.hosteurope.de" rel="noopener noreferrer"&gt;Hosteurope&lt;/a&gt; in Germany&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://trinpod.us" rel="noopener noreferrer"&gt;trinpod.us&lt;/a&gt; by &lt;a href="https://graphmetrix.com/terms" rel="noopener noreferrer"&gt;Graphmetrix, Inc.&lt;/a&gt; hosted at &lt;a href="https://aws.amazon.com" rel="noopener noreferrer"&gt;Amazon&lt;/a&gt; in the USA&lt;/li&gt;
&lt;li&gt;When this code is running, there is also a Solid server at &lt;a href="https://btf.ngrok.io" rel="noopener noreferrer"&gt;https://btf.ngrok.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can choose a server based for a variety of ethical or technical reasons. Perhaps you disagree with a change in the terms of service, want more storage capacity, or high-speed transfer. This is illustrated in the &lt;em&gt;data market&lt;/em&gt; section below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextjournal.com%2Fdata%2FQmXheA4Wepw7hvDMPEwwEbmVH5E4avBFxhpNpJVUTAaYMg%3Fcontent-type%3Dimage%2Fsvg%252Bxml%26node-id%3D7a55db87-decc-4b99-afae-64d01f3990c9%26filename%3Ddata-and-app-markets.svg%26node-kind%3Dfile" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fnextjournal.com%2Fdata%2FQmXheA4Wepw7hvDMPEwwEbmVH5E4avBFxhpNpJVUTAaYMg%3Fcontent-type%3Dimage%2Fsvg%252Bxml%26node-id%3D7a55db87-decc-4b99-afae-64d01f3990c9%26filename%3Ddata-and-app-markets.svg%26node-kind%3Dfile" title="&amp;lt;p&amp;gt;Figure: Data and app markets © Ruben Verborgh, CC-by-4.0&amp;lt;/p&amp;gt;" alt="data-and-app-markets.svg"&gt;&lt;/a&gt; &lt;em&gt;Figure: Data and app markets. CC-by-4.0 Ruben Verborgh&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ngrok Configuration
&lt;/h3&gt;

&lt;p&gt;If you use the code from this article, you can use the same Ngrok service, but your &lt;code&gt;subdomain&lt;/code&gt; will be random, rather than &lt;code&gt;btf&lt;/code&gt;. All other configurations are the same.&lt;/p&gt;

&lt;p&gt;When you change the &lt;code&gt;subdomin&lt;/code&gt;, remember that the full domain, subdomain, domain (&lt;code&gt;.ngrok&lt;/code&gt;), and TLD (&lt;code&gt;.io&lt;/code&gt;) must match in three places:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;This Ngrok configuration file at &lt;code&gt;subdomain&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;serverUri&lt;/code&gt; found in Solid's &lt;code&gt;config.json&lt;/code&gt; above.&lt;/li&gt;
&lt;li&gt;The URL in your web browser's address bar.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the Ngrok configuration used in this article:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;region: us
console_ui: true
tunnels:
  btf:
    proto: http
    addr: https://localhost:8443
    subdomain: btf
    bind_tls: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting the &lt;code&gt;https&lt;/code&gt; prefix of &lt;code&gt;https://localhost:8443&lt;/code&gt; forces Ngrok to speak HTTPS to the local server. The &lt;code&gt;8443&lt;/code&gt; port suffix directs it to the Solid server's port.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;proto: http&lt;/code&gt; - the protocol. The &lt;code&gt;http&lt;/code&gt; protocol covers both HTTP and HTTPS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;addr:&lt;/code&gt;&lt;a href="https://localhost:3000" rel="noopener noreferrer"&gt;&lt;code&gt;https://localhost:&lt;/code&gt;&lt;/a&gt;&lt;code&gt;8443&lt;/code&gt; use the full URL to force HTTPS (equivalent to &lt;code&gt;ngrok http https://localhost:8443&lt;/code&gt; on the command line)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bind_tls&lt;/code&gt; - without this setting both HTTP and HTTPS will be available from ngrok

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bind_tls: true&lt;/code&gt; - ngrok only listens on an HTTPS tunnel endpoint&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bind_tls: false&lt;/code&gt; &lt;em&gt;-&lt;/em&gt; ngrok only listens on an HTTP tunnel endpoint&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;host_header: localhost&lt;/code&gt; - unused. But some APIs will want to see the host header of your app rather than 'ngrok'&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Kent, William, and Steve Hoberman. 2012. &lt;em&gt;Data And Reality&lt;/em&gt;. 3rd ed. Westfield, N.J.: Technics Publications.&lt;/li&gt;
&lt;li&gt;‘Linked Data – Design Issues’. Accessed 19 August 2021. &lt;a href="https://www.w3.org/DesignIssues/LinkedData.html" rel="noopener noreferrer"&gt;https://www.w3.org/DesignIssues/LinkedData.html&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Verborgh, Ruben. ‘&lt;a href="https://genr.eu/wp/solid/" rel="noopener noreferrer"&gt;Solid: Personal Data Management Through Linked Data&lt;/a&gt;’, 2018. &lt;a href="https://doi.org/10.25815/50W4-HK79" rel="noopener noreferrer"&gt;https://doi.org/10.25815/50W4-HK79&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This work is licensed under a &lt;a href="https://creativecommons.org/licenses/by/4.0/" rel="noopener noreferrer"&gt;Creative Commons Attribution 4.0 International License&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwk5e444xhjro1c8wqax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwk5e444xhjro1c8wqax.png" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>solid</category>
      <category>decentralized</category>
      <category>federated</category>
    </item>
    <item>
      <title>Simple "Have I Been Pwned" API Calls With Clojure</title>
      <dc:creator>schmudde</dc:creator>
      <pubDate>Fri, 13 Nov 2020 09:45:28 +0000</pubDate>
      <link>https://dev.to/schmudde/using-the-have-i-been-pwned-api-40fl</link>
      <guid>https://dev.to/schmudde/using-the-have-i-been-pwned-api-40fl</guid>
      <description>&lt;p&gt;&lt;strong&gt;';--have i been pwned?&lt;/strong&gt; is the gold standard for seeing if a user's account has been compromised in a data breach. This is usually done using an eMail address, which is what I'll be demonstrating here.&lt;/p&gt;

&lt;p&gt;I will be using the &lt;a href="https://haveibeenpwned.com/API/v3"&gt;Have I Been Pwned (HIBP) API&lt;/a&gt; in this notebook. The API requires a key for a nominal charge of $3.50 a month.¹ Obviously, my key is not available to the public.&lt;/p&gt;

&lt;p&gt;Here's a &lt;a href="https://www.troyhunt.com/authentication-and-the-have-i-been-pwned-api"&gt;full blog post on why&lt;/a&gt; &lt;strong&gt;';--have i been pwned?&lt;/strong&gt; charges for this service. The API is pretty simple, so let's get started.&lt;/p&gt;

&lt;h1&gt;
  
  
  Grab the Data
&lt;/h1&gt;

&lt;p&gt;Use a curl command to grab the data using the API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -H "hibp-api-key:&amp;lt;your-secret&amp;gt;" -H "user-agent: Beyond the Frame" -sS https://haveibeenpwned.com/api/v3/breachedaccount/d%40schmud.com?truncateResponse=false -o "/pwned-accounts.json"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A breakdown of the switches I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔑 &lt;code&gt;-H "hibp-api-key:&amp;lt;your-secret&amp;gt;"&lt;/code&gt;: An HIBP subscription key is required to make an authorized call and can be obtained on &lt;a href="https://haveibeenpwned.com/API/Key"&gt;the API key page&lt;/a&gt;. The key is then passed in a &lt;code&gt;hibp-api-key&lt;/code&gt; header. Replace &lt;code&gt;&amp;lt;your-secret&amp;gt;&lt;/code&gt; with your own key.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-H "user-agent: Beyond the Frame"&lt;/code&gt;: Each request to the API must be accompanied by a user agent request header. Typically this should be the name of the app consuming the service.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-o "/pwned-accounts.json"&lt;/code&gt;: Output the returned JSON data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The URL has two unique features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;d%40schmud.com&lt;/code&gt;: My eMail, &lt;a href="https://www.url-encode-decode.com/"&gt;encoded for a URL&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;?truncateResponse=false&lt;/code&gt;: return the complete breach data for all data breaches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examine the data² returned by Have I Been Pwned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(require '[clojure.data.json :as json])

(def accounts 
    (json/read-str (slurp "/pwned-accounts.json") 
                   :key-fn keyword))

(pprint accounts)
accounts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;output ⇒&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[{"~:Domain":"8tracks.com","~:DataClasses":["Email addresses","Passwords"],"~:IsFabricated":false,"~:IsVerified":true,"~:BreachDate":"2017-06-27","~:PwnCount":17979961,"~:Title":"8tracks","~:IsSensitive":false,"~:IsSpamList":false,"~:LogoPath":"https://haveibeenpwned.com/Content/Images/PwnedLogos/8tracks.png","~:ModifiedDate":"2019-08-25T08:52:21Z","~:Name":"8tracks","~:Description":"In June 2017, the online playlists service known as &amp;lt;a href=\"https://blog.8tracks.com/2017/06/27/password-security-alert/\" target=\"_blank\" rel=\"noopener\"&amp;gt;8Tracks suffered a data breach&amp;lt;/a&amp;gt; which impacted 18 million accounts. In their disclosure, 8Tracks advised that &amp;amp;quot;the vector for the attack was an employee’s GitHub account, which was not secured using two-factor authentication&amp;amp;quot;. Salted SHA-1 password hashes for users who &amp;lt;em&amp;gt;didn't&amp;lt;/em&amp;gt; sign up with either Google or Facebook authentication were also included. The data was provided to HIBP by whitehat security researcher and data analyst Adam Davies and contained almost 8 million unique email addresses. The complete set of 18M records was later provided by JimScott.Sec@protonmail.com and updated in HIBP accordingly.","~:IsRetired":false,"~:AddedDate":"2018-02-16T07:09:30Z"},{"~:Domain":"eatstreet.com","~:DataClasses":["Dates of birth","Email addresses","Genders","Names","Partial credit card data","Passwords","Phone numbers","Physical addresses","Social media profiles"],"~:IsFabricated":false,"~:IsVerified":true,"~:BreachDate":"2019-05-03","~:PwnCount":6353564,"~:Title":"EatStreet","~:IsSensitive":false,"~:IsSpamList":false,"~:LogoPath":"https://haveibeenpwned.com/Content/Images/PwnedLogos/EatStreet.png","~:ModifiedDate":"2019-07-19T11:29:35Z","~:Name":"EatStreet","~:Description":"In May 2019, the online food ordering service &amp;lt;a href=\"https://www.zdnet.com/article/eatstreet-food-ordering-service-discloses-security-breach/\" target=\"_blank\" rel=\"noopener\"&amp;gt;EatStreet suffered a data breach affecting 6.4 million customers&amp;lt;/a&amp;gt;. An extensive amount of personal data was obtained including names, phone numbers, addresses, partial credit card data and passwords stored as bcrypt hashes. The data was provided to HIBP by a source who requested it be attributed to &amp;amp;quot;JimScott.Sec@protonmail.com&amp;amp;quot;.","~:IsRetired":false,"~:AddedDate":"2019-07-19T11:29:35Z"},{"~:Domain":"ticketfly.com","~:DataClasses":["Email addresses","Names","Phone numbers","Physical addresses"],"~:IsFabricated":false,"~:IsVerified":true,"~:BreachDate":"2018-05-31","~:PwnCount":26151608,"~:Title":"Ticketfly","~:IsSensitive":false,"~:IsSpamList":false,"~:LogoPath":"https://haveibeenpwned.com/Content/Images/PwnedLogos/Ticketfly.png","~:ModifiedDate":"2018-07-14T06:06:15Z","~:Name":"Ticketfly","~:Description":"In May 2018, the website for the ticket distribution service &amp;lt;a href=\"https://motherboard.vice.com/en_us/article/mbk3nx/ticketfly-website-database-hacked-data-breach\" target=\"_blank\" rel=\"noopener\"&amp;gt;Ticketfly was defaced by an attacker and was subsequently taken offline&amp;lt;/a&amp;gt;. The attacker allegedly requested a ransom to share details of the vulnerability with Ticketfly but did not receive a reply and subsequently posted the breached data online to a publicly accessible location. The data included over 26 million unique email addresses along with names, physical addresses and phone numbers. Whilst there were no passwords in the publicly leaked data, &amp;lt;a href=https://support.ticketfly.com/customer/en/portal/articles/2941983-ticketfly-cyber-incident-update\" target=\"_blank\" rel=\"noopener\"&amp;gt;Ticketfly later issued an incident update&amp;lt;/a&amp;gt; and stated that &amp;amp;quot;It is possible, however, that hashed values of password credentials could have been accessed&amp;amp;quot;.","~:IsRetired":false,"~:AddedDate":"2018-06-03T06:14:14Z"}]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Parse the Data
&lt;/h1&gt;

&lt;p&gt;Grab three parameters from every title returned and print as HTML.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Title&lt;/strong&gt;: A descriptive title for the breach suitable for displaying to end users. It's unique across all breaches but individual values may change in the future (i.e. if another breach occurs against an organisation already in the system). If a stable value is required to reference the breach, refer to the "Name" attribute instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain&lt;/strong&gt;: The domain of the primary website the breach occurred on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BreachDate&lt;/strong&gt;: The date (with no time) the breach originally occurred on in ISO 8601 format. This is not always accurate — frequently breaches are discovered and reported long after the original incident. Use this attribute as a guide only.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(require '[cljc.java-time.local-date :as ld])

(defn account-&amp;gt;html [account]
  (let [title (:Title account)
        link (str "&amp;lt;a href=\"http://" (:Domain account) "\" target=\"_blank\"&amp;gt;" title "&amp;lt;/a&amp;gt;")
        date (ld/get-year (ld/parse (:BreachDate account)))]
    (str "&amp;lt;li&amp;gt;" link " in " date "&amp;lt;/li&amp;gt;")))

(defn print-accounts [accounts]
  (str "&amp;lt;hr /&amp;gt;&amp;lt;center&amp;gt;&amp;lt;h2&amp;gt;Accounts Associated With This eMail&amp;lt;/h2&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;ul&amp;gt;"
     (apply str (map #(account-&amp;gt;html %) accounts))
       "&amp;lt;/ul&amp;gt;&amp;lt;/center&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;hr /&amp;gt;"))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;output ⇒&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accounts Associated With This eMail&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://8tracks.com/"&gt;8tracks&lt;/a&gt; in 2017&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://eatstreet.com/"&gt;EatStreet&lt;/a&gt; in 2019&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://ticketfly.com/"&gt;Ticketfly&lt;/a&gt; in 2018&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;That's it. Use your key and simply replace the eMail address with any other eMail address and you will get the pertinent results. &lt;/p&gt;

&lt;h1&gt;
  
  
  Appendix
&lt;/h1&gt;

&lt;p&gt;¹ The language of HIBP's API is not very clear. If you cancel your subscription, your API key will remain functional until the subscription renewal is due after which it will cease to work. You can always reactivate it by returning to &lt;a href="https://haveibeenpwned.com/API/Key"&gt;the API key page&lt;/a&gt; and purchasing another one.&lt;/p&gt;

&lt;p&gt;² The &lt;code&gt;deps.edn&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{:deps
 {org.clojure/clojure {:mvn/version "1.10.1"}
  org.clojure/data.json {:mvn/version "1.0.0"}
  cljc.java-time {:mvn/version "0.1.11"}}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a rel="license" href="http://creativecommons.org/licenses/by/4.0/"&gt;&lt;img alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X58A5c4u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.creativecommons.org/l/by/4.0/88x31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X58A5c4u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.creativecommons.org/l/by/4.0/88x31.png" alt="Creative Commons License" width="88" height="31"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This work is licensed under a &lt;a href="https://creativecommons.org/licenses/by/4.0/"&gt;Creative Commons Attribution 4.0 International License&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have I Been Pwned&lt;/em&gt; is the source of the data on this page. Some of the writing is directly cut from their &lt;a href="https://haveibeenpwned.com/API/v3"&gt;API v3 documentation&lt;/a&gt;, also under CC4.0.&lt;/p&gt;

</description>
      <category>api</category>
      <category>clojure</category>
      <category>rest</category>
      <category>functional</category>
    </item>
  </channel>
</rss>
