Jekyll2025-01-04T14:49:36+00:00https://fusectore.dev/feed.xmlfusectore.devMy blog on programming.Exploring Identities in the AT Protocol via Bluesky2025-01-04T00:00:00+00:002025-01-04T00:00:00+00:00https://fusectore.dev/2025/01/04/atproto-identity<p>The AT Protocol is an open, decentralized network for building social applications.</p> <p>Bluesky, a relatively new social network, is built on top of the AT Protocol. It recently piqued my interest as I came across this fascinating research paper <a href="https://arxiv.org/abs/2402.03239">Bluesky and the AT Protocol: Usable Decentralized Social Media</a>.</p> <p>In this post, we’ll dive into one of the key elements of the AT Protocol: <strong>identities</strong>. We will start with Bluesky user handles, explore additional information about them, and see what else we can discover.</p> <h3 id="did-resolution">DID Resolution</h3> <p>A handle in Bluesky is associated with a DID, which can be resolved using DNS or HTTPS.</p> <blockquote> <p><strong>decentralized identifier (DID)</strong></p> <p>A globally unique persistent identifier that does not require a centralized registration authority and is often generated and/or registered cryptographically. […] Many—but not all—DID methods make use of distributed ledger technology (DLT) or some other form of decentralized network.</p> <p><em>From <a href="https://www.w3.org/TR/did-core/#dfn-decentralized-identifiers">Decentralized Identifiers (DIDs) v1.0</a></em></p> </blockquote> <p>The DID for <code class="language-plaintext highlighter-rouge">@jay.bsky.team</code> can be resolved via a DNS record. Example with <code class="language-plaintext highlighter-rouge">dig</code>:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> dig _atproto.jay.bsky.team TXT <span class="o">[</span>...] _atproto.jay.bsky.team. 14400 IN TXT <span class="s2">"did=did:plc:oky5czdrnfjpqslsw2a5iclo"</span> </code></pre></div></div> <p>If you don’t have <code class="language-plaintext highlighter-rouge">dig</code> installed, try an online tool like <a href="https://www.nslookup.io/domains/_atproto.jay.bsky.team/dns-records/txt/">nslookup.io</a>.</p> <p>If no DNS TXT entry exists, the handle may resolve to a DID through a well-known HTTPS endpoint. Let’s take the example of <code class="language-plaintext highlighter-rouge">@jamesgunn.bsky.social</code>:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> curl https://jamesgunn.bsky.social/.well-known/atproto-did did:plc:lotavzt36yanhfy3j3gpysyj </code></pre></div></div> <p>If you don’t have <code class="language-plaintext highlighter-rouge">curl</code> installed, you can just open the link in your browser <a href="https://jamesgunn.bsky.social/.well-known/atproto-did">https://jamesgunn.bsky.social/.well-known/atproto-did</a>.</p> <h3 id="plc">PLC</h3> <p>Once you have a DID, examine the part after the first colon. This is known as the method. The above two examples use <code class="language-plaintext highlighter-rouge">plc</code>, which stands for Public Ledger of Credentials. These PLC DIDs can be looked up in the PLC directory at <a href="https://plc.directory">plc.directory</a>.</p> <p><em>The AT Protocol also supports the <code class="language-plaintext highlighter-rouge">web</code> method, though we focus on <code class="language-plaintext highlighter-rouge">plc</code> here.</em></p> <p>We can use <code class="language-plaintext highlighter-rouge">curl</code> again to get more information about a DID. Let’s take <code class="language-plaintext highlighter-rouge">@jay.bsky.team</code>’s DID as example.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> curl https://plc.directory/did:plc:oky5czdrnfjpqslsw2a5iclo/log | jq <span class="o">[</span> <span class="o">{</span> <span class="s2">"sig"</span>: <span class="s2">"KuN3A61golVNSDU71wZKLP9lVuXk6YekJAz1lwDzrPsNTEWHBBW_8zSyV6pDxV4KiYXuAXlS1Ik47XkjQZ94mA"</span>, <span class="s2">"prev"</span>: null, <span class="s2">"type"</span>: <span class="s2">"create"</span>, <span class="s2">"handle"</span>: <span class="s2">"jay.bsky.social"</span>, <span class="s2">"service"</span>: <span class="s2">"https://bsky.social"</span>, <span class="s2">"signingKey"</span>: <span class="s2">"did:key:zQ3shP5TBe1sQfSttXty15FAEHV1DZgcxRZNxvEWnPfLFwLxJ"</span>, <span class="s2">"recoveryKey"</span>: <span class="s2">"did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg"</span> <span class="o">}</span>, <span class="o">{</span> <span class="s2">"sig"</span>: <span class="s2">"TY3ot8yFF-0LL8ceoRwSGaNQj0F1aj-IApYXRGT21G1fnznKHUHT1QE7c1aTrYd9PQLVvXUGag6CZ9EEeIKqgA"</span>, <span class="s2">"prev"</span>: <span class="s2">"bafyreidswhiwi4ljkl4es4vwqhkas3spmmktortqbp6lkrb5v7qqdfr3mm"</span>, <span class="s2">"type"</span>: <span class="s2">"plc_operation"</span>, <span class="s2">"services"</span>: <span class="o">{</span> <span class="s2">"atproto_pds"</span>: <span class="o">{</span> <span class="s2">"type"</span>: <span class="s2">"AtprotoPersonalDataServer"</span>, <span class="s2">"endpoint"</span>: <span class="s2">"https://bsky.social"</span> <span class="o">}</span> <span class="o">}</span>, <span class="s2">"alsoKnownAs"</span>: <span class="o">[</span> <span class="s2">"at://jay.bsky.social"</span> <span class="o">]</span>, <span class="s2">"rotationKeys"</span>: <span class="o">[</span> <span class="s2">"did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg"</span>, <span class="s2">"did:key:zQ3shpKnbdPx3g3CmPf5cRVTPe1HtSwVn5ish3wSnDPQCbLJK"</span> <span class="o">]</span>, <span class="s2">"verificationMethods"</span>: <span class="o">{</span> <span class="s2">"atproto"</span>: <span class="s2">"did:key:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF"</span> <span class="o">}</span> <span class="o">}</span>, <span class="o">{</span> <span class="s2">"sig"</span>: <span class="s2">"WmQmobHA6abtu8xIkBSrDkkoMNjOxP1Tl_1zl1DbIZlA9kjOyxjX-J1rzwWVy3stdzMowpBeBnedkAug84n_RQ"</span>, <span class="s2">"prev"</span>: <span class="s2">"bafyreicgb25yf5fro22oyhtkbgerzr4nume4sx757r525skxdzpeoeseha"</span>, <span class="s2">"type"</span>: <span class="s2">"plc_operation"</span>, <span class="s2">"services"</span>: <span class="o">{</span> <span class="s2">"atproto_pds"</span>: <span class="o">{</span> <span class="s2">"type"</span>: <span class="s2">"AtprotoPersonalDataServer"</span>, <span class="s2">"endpoint"</span>: <span class="s2">"https://bsky.social"</span> <span class="o">}</span> <span class="o">}</span>, <span class="s2">"alsoKnownAs"</span>: <span class="o">[</span> <span class="s2">"at://jay.bsky.team"</span> <span class="o">]</span>, <span class="s2">"rotationKeys"</span>: <span class="o">[</span> <span class="s2">"did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg"</span>, <span class="s2">"did:key:zQ3shpKnbdPx3g3CmPf5cRVTPe1HtSwVn5ish3wSnDPQCbLJK"</span> <span class="o">]</span>, <span class="s2">"verificationMethods"</span>: <span class="o">{</span> <span class="s2">"atproto"</span>: <span class="s2">"did:key:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF"</span> <span class="o">}</span> <span class="o">}</span>, <span class="o">{</span> <span class="s2">"sig"</span>: <span class="s2">"dhstt4uPua8OVs8TVzHTqtCnNMsBDN7kjxIBmTIYBDRkknrtprBJ_AISXFoBZyqfoxq2altp-vlRAPEKSh5zeg"</span>, <span class="s2">"prev"</span>: <span class="s2">"bafyreihmf7dapx27fexc7jwj4cdpbxcmhnnwo3x5vrpvbr6lclzj7gpsmi"</span>, <span class="s2">"type"</span>: <span class="s2">"plc_operation"</span>, <span class="s2">"services"</span>: <span class="o">{</span> <span class="s2">"atproto_pds"</span>: <span class="o">{</span> <span class="s2">"type"</span>: <span class="s2">"AtprotoPersonalDataServer"</span>, <span class="s2">"endpoint"</span>: <span class="s2">"https://morel.us-east.host.bsky.network"</span> <span class="o">}</span> <span class="o">}</span>, <span class="s2">"alsoKnownAs"</span>: <span class="o">[</span> <span class="s2">"at://jay.bsky.team"</span> <span class="o">]</span>, <span class="s2">"rotationKeys"</span>: <span class="o">[</span> <span class="s2">"did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg"</span>, <span class="s2">"did:key:zQ3shpKnbdPx3g3CmPf5cRVTPe1HtSwVn5ish3wSnDPQCbLJK"</span> <span class="o">]</span>, <span class="s2">"verificationMethods"</span>: <span class="o">{</span> <span class="s2">"atproto"</span>: <span class="s2">"did:key:zQ3shtJpFGgEG3tv3ERKvjo7VHbjDPVyvjYvW7gpie49rtNtc"</span> <span class="o">}</span> <span class="o">}</span> <span class="o">]</span> </code></pre></div></div> <p>Again, feel free to click on it instead, <a href="https://plc.directory/did:plc:oky5czdrnfjpqslsw2a5iclo/log">https://plc.directory/did:plc:oky5czdrnfjpqslsw2a5iclo/log</a>.</p> <p>This returns a list of all operations that were performed on this DID. The first entry is the genesis operation — the one that created the DID. It is the only one where <code class="language-plaintext highlighter-rouge">prev</code> (previous entry) is <code class="language-plaintext highlighter-rouge">null</code>, because it is the first one.</p> <p>Note that the first entry of this DID’s log has a different format. This entry uses the legacy format. Currently, newly created DIDs use the new format (subsequent records), but <code class="language-plaintext highlighter-rouge">@jay.bsky.team</code> has been around for a while (the account belongs to Bluesky’s CEO).</p> <p>If you look through the logs you will notice a couple of changes that were made to her identity over time:</p> <ul> <li>the handle was changed from <code class="language-plaintext highlighter-rouge">jay.bsky.social</code> to <code class="language-plaintext highlighter-rouge">jay.bsky.team</code> (note that the legacy format used <code class="language-plaintext highlighter-rouge">handle</code> whereas the new format uses <code class="language-plaintext highlighter-rouge">alsoKnownAs</code>) <ul> <li>the handle now includes <code class="language-plaintext highlighter-rouge">at://</code></li> </ul> </li> <li>the personal data server changed from <code class="language-plaintext highlighter-rouge">https://bsky.social</code> to <code class="language-plaintext highlighter-rouge">https://morel.us-east.host.bsky.network</code></li> </ul> <h3 id="signatures">Signatures</h3> <p>Each entry in the log includes a <code class="language-plaintext highlighter-rouge">sig</code> field, which contains the signature. This cryptographic signature is derived from the other fields in the entry. For the genesis operation, the <code class="language-plaintext highlighter-rouge">rotationKeys</code> (or <code class="language-plaintext highlighter-rouge">signingKey</code> for legacy) field contains the public key(s) of the key pair(s) with which the entry was signed.</p> <p>Subsequent operations need to be signed with the key from the previous entry. Additionally, the <code class="language-plaintext highlighter-rouge">prev</code> field references the previous entry.</p> <h3 id="creating-a-did">Creating a DID</h3> <p>The identifier in the DID, which is the last part of the colon-separated string, is constructed from the genesis operation. For <code class="language-plaintext highlighter-rouge">@jay.bsky.team</code>:</p> <ul> <li>the identifier is <code class="language-plaintext highlighter-rouge">oky5czdrnfjpqslsw2a5iclo</code></li> <li>the genesis operation is this JSON document:</li> </ul> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"sig"</span><span class="p">:</span><span class="w"> </span><span class="s2">"KuN3A61golVNSDU71wZKLP9lVuXk6YekJAz1lwDzrPsNTEWHBBW_8zSyV6pDxV4KiYXuAXlS1Ik47XkjQZ94mA"</span><span class="p">,</span><span class="w"> </span><span class="nl">"prev"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"create"</span><span class="p">,</span><span class="w"> </span><span class="nl">"handle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"jay.bsky.social"</span><span class="p">,</span><span class="w"> </span><span class="nl">"service"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://bsky.social"</span><span class="p">,</span><span class="w"> </span><span class="nl">"signingKey"</span><span class="p">:</span><span class="w"> </span><span class="s2">"did:key:zQ3shP5TBe1sQfSttXty15FAEHV1DZgcxRZNxvEWnPfLFwLxJ"</span><span class="p">,</span><span class="w"> </span><span class="nl">"recoveryKey"</span><span class="p">:</span><span class="w"> </span><span class="s2">"did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>Steps:</p> <ul> <li>encode the genesis operation in DAG-CBOR (this is a concise binary format and looks a bit like JSON)</li> <li>create a SHA256 hash of the encoded document</li> <li>encode the hash with base 32 and make its letters lowercase.</li> <li>take the first 24 characters</li> <li>add did:plc: as a prefix to create the 32-character DID</li> </ul> <h3 id="verification">Verification</h3> <p>Based on what we’ve learned so far we can verify a handle from Bluesky. For example, if we want to check <code class="language-plaintext highlighter-rouge">@jay.bsky.team</code>:</p> <ul> <li>resolve DID via DNS: <code class="language-plaintext highlighter-rouge">did:plc:oky5czdrnfjpqslsw2a5iclo</code></li> <li>look up DID in PLC directory</li> <li>the handle in the <code class="language-plaintext highlighter-rouge">alsoKnownAs</code> (or <code class="language-plaintext highlighter-rouge">handle</code> for legacy) field must match <code class="language-plaintext highlighter-rouge">jay.bsky.team</code></li> <li>verify all operations using the public keys and signatures</li> <li>reconstruct the DID from the genesis operation and ensure it is the same as the one we found in the DNS TXT record</li> </ul> <p>Can we trust the PLC directory though? Say a malicious actor has taken over control and modified all entries in the log. The hacker even constructed the entries in the log so that all the signatures appear valid. How would we still know the PLC directory is compromised? Remember that the DID can be reconstructed from the genesis operation. If that were modified, we’d end up with a different DID from the one we found in the DNS TXT record.</p> <h3 id="summary">Summary</h3> <p>This blog explores the AT Protocol, the decentralized foundation of social applications like Bluesky, with a focus on user identities and their technical underpinnings. It explains how Bluesky user handles are associated with Decentralized Identifiers (DIDs), which can be resolved using DNS records or HTTPS endpoints. Detailed examples demonstrate how tools like dig and curl retrieve DID data, revealing the method (plc) and structure used to catalog them in the Public Ledger of Credentials (PLC). The post also examines the DID lifecycle, showcasing how historical operations and changes—such as updates to handles or servers—are logged and accessible for transparency.</p> <p>There are several aspects that we did not investigate, such as:</p> <ul> <li>the <code class="language-plaintext highlighter-rouge">web</code> method for DIDs</li> <li>tombstone operations in the log</li> <li>recovery</li> <li>how exactly to construct the <code class="language-plaintext highlighter-rouge">prev</code> entry in the DID document</li> <li>what the other fields in the DID document mean</li> </ul> <p>If you are curious, the links in the next section will help you answer these questions.</p> <h2 id="links--further-reading">Links &amp; Further Reading</h2> <ul> <li><a href="https://bsky.app">Bluesky Application</a></li> <li><a href="https://atproto.com">AT Protocol</a> <ul> <li><a href="https://atproto.com/guides/identity">AT Protocol Identity</a></li> <li><a href="https://atproto.com/specs/did">AT Protocol DID</a></li> <li><a href="https://atproto.com/specs/handle">AT Protocol Handle</a></li> </ul> </li> <li><a href="https://www.w3.org/TR/did-core">Decentralized Identifiers (DIDs) v1.0</a></li> <li><a href="https://web.plc.directory/spec/v0.1/did-plc"><code class="language-plaintext highlighter-rouge">did:plc</code> Method Specification</a></li> <li><a href="https://ipld.io/specs/codecs/dag-cbor/spec/">Specification: DAG-CBOR</a></li> </ul>The AT Protocol is an open, decentralized network for building social applications. Bluesky, a relatively new social network, is built on top of the AT Protocol. It recently piqued my interest as I came across this fascinating research paper Bluesky and the AT Protocol: Usable Decentralized Social Media. In this post, we’ll dive into one of the key elements of the AT Protocol: identities. We will start with Bluesky user handles, explore additional information about them, and see what else we can discover. DID Resolution A handle in Bluesky is associated with a DID, which can be resolved using DNS or HTTPS. decentralized identifier (DID) A globally unique persistent identifier that does not require a centralized registration authority and is often generated and/or registered cryptographically. […] Many—but not all—DID methods make use of distributed ledger technology (DLT) or some other form of decentralized network. From Decentralized Identifiers (DIDs) v1.0 The DID for @jay.bsky.team can be resolved via a DNS record. Example with dig: &gt; dig _atproto.jay.bsky.team TXT [...] _atproto.jay.bsky.team. 14400 IN TXT "did=did:plc:oky5czdrnfjpqslsw2a5iclo" If you don’t have dig installed, try an online tool like nslookup.io. If no DNS TXT entry exists, the handle may resolve to a DID through a well-known HTTPS endpoint. Let’s take the example of @jamesgunn.bsky.social: &gt; curl https://jamesgunn.bsky.social/.well-known/atproto-did did:plc:lotavzt36yanhfy3j3gpysyj If you don’t have curl installed, you can just open the link in your browser https://jamesgunn.bsky.social/.well-known/atproto-did. PLC Once you have a DID, examine the part after the first colon. This is known as the method. The above two examples use plc, which stands for Public Ledger of Credentials. These PLC DIDs can be looked up in the PLC directory at plc.directory. The AT Protocol also supports the web method, though we focus on plc here. We can use curl again to get more information about a DID. Let’s take @jay.bsky.team’s DID as example. &gt; curl https://plc.directory/did:plc:oky5czdrnfjpqslsw2a5iclo/log | jq [ { "sig": "KuN3A61golVNSDU71wZKLP9lVuXk6YekJAz1lwDzrPsNTEWHBBW_8zSyV6pDxV4KiYXuAXlS1Ik47XkjQZ94mA", "prev": null, "type": "create", "handle": "jay.bsky.social", "service": "https://bsky.social", "signingKey": "did:key:zQ3shP5TBe1sQfSttXty15FAEHV1DZgcxRZNxvEWnPfLFwLxJ", "recoveryKey": "did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg" }, { "sig": "TY3ot8yFF-0LL8ceoRwSGaNQj0F1aj-IApYXRGT21G1fnznKHUHT1QE7c1aTrYd9PQLVvXUGag6CZ9EEeIKqgA", "prev": "bafyreidswhiwi4ljkl4es4vwqhkas3spmmktortqbp6lkrb5v7qqdfr3mm", "type": "plc_operation", "services": { "atproto_pds": { "type": "AtprotoPersonalDataServer", "endpoint": "https://bsky.social" } }, "alsoKnownAs": [ "at://jay.bsky.social" ], "rotationKeys": [ "did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg", "did:key:zQ3shpKnbdPx3g3CmPf5cRVTPe1HtSwVn5ish3wSnDPQCbLJK" ], "verificationMethods": { "atproto": "did:key:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF" } }, { "sig": "WmQmobHA6abtu8xIkBSrDkkoMNjOxP1Tl_1zl1DbIZlA9kjOyxjX-J1rzwWVy3stdzMowpBeBnedkAug84n_RQ", "prev": "bafyreicgb25yf5fro22oyhtkbgerzr4nume4sx757r525skxdzpeoeseha", "type": "plc_operation", "services": { "atproto_pds": { "type": "AtprotoPersonalDataServer", "endpoint": "https://bsky.social" } }, "alsoKnownAs": [ "at://jay.bsky.team" ], "rotationKeys": [ "did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg", "did:key:zQ3shpKnbdPx3g3CmPf5cRVTPe1HtSwVn5ish3wSnDPQCbLJK" ], "verificationMethods": { "atproto": "did:key:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF" } }, { "sig": "dhstt4uPua8OVs8TVzHTqtCnNMsBDN7kjxIBmTIYBDRkknrtprBJ_AISXFoBZyqfoxq2altp-vlRAPEKSh5zeg", "prev": "bafyreihmf7dapx27fexc7jwj4cdpbxcmhnnwo3x5vrpvbr6lclzj7gpsmi", "type": "plc_operation", "services": { "atproto_pds": { "type": "AtprotoPersonalDataServer", "endpoint": "https://morel.us-east.host.bsky.network" } }, "alsoKnownAs": [ "at://jay.bsky.team" ], "rotationKeys": [ "did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg", "did:key:zQ3shpKnbdPx3g3CmPf5cRVTPe1HtSwVn5ish3wSnDPQCbLJK" ], "verificationMethods": { "atproto": "did:key:zQ3shtJpFGgEG3tv3ERKvjo7VHbjDPVyvjYvW7gpie49rtNtc" } } ] Again, feel free to click on it instead, https://plc.directory/did:plc:oky5czdrnfjpqslsw2a5iclo/log. This returns a list of all operations that were performed on this DID. The first entry is the genesis operation — the one that created the DID. It is the only one where prev (previous entry) is null, because it is the first one. Note that the first entry of this DID’s log has a different format. This entry uses the legacy format. Currently, newly created DIDs use the new format (subsequent records), but @jay.bsky.team has been around for a while (the account belongs to Bluesky’s CEO). If you look through the logs you will notice a couple of changes that were made to her identity over time: the handle was changed from jay.bsky.social to jay.bsky.team (note that the legacy format used handle whereas the new format uses alsoKnownAs) the handle now includes at:// the personal data server changed from https://bsky.social to https://morel.us-east.host.bsky.network Signatures Each entry in the log includes a sig field, which contains the signature. This cryptographic signature is derived from the other fields in the entry. For the genesis operation, the rotationKeys (or signingKey for legacy) field contains the public key(s) of the key pair(s) with which the entry was signed. Subsequent operations need to be signed with the key from the previous entry. Additionally, the prev field references the previous entry. Creating a DID The identifier in the DID, which is the last part of the colon-separated string, is constructed from the genesis operation. For @jay.bsky.team: the identifier is oky5czdrnfjpqslsw2a5iclo the genesis operation is this JSON document: { "sig": "KuN3A61golVNSDU71wZKLP9lVuXk6YekJAz1lwDzrPsNTEWHBBW_8zSyV6pDxV4KiYXuAXlS1Ik47XkjQZ94mA", "prev": null, "type": "create", "handle": "jay.bsky.social", "service": "https://bsky.social", "signingKey": "did:key:zQ3shP5TBe1sQfSttXty15FAEHV1DZgcxRZNxvEWnPfLFwLxJ", "recoveryKey": "did:key:zQ3shhCGUqDKjStzuDxPkTxN6ujddP4RkEKJJouJGRRkaLGbg" } Steps: encode the genesis operation in DAG-CBOR (this is a concise binary format and looks a bit like JSON) create a SHA256 hash of the encoded document encode the hash with base 32 and make its letters lowercase. take the first 24 characters add did:plc: as a prefix to create the 32-character DID Verification Based on what we’ve learned so far we can verify a handle from Bluesky. For example, if we want to check @jay.bsky.team: resolve DID via DNS: did:plc:oky5czdrnfjpqslsw2a5iclo look up DID in PLC directory the handle in the alsoKnownAs (or handle for legacy) field must match jay.bsky.team verify all operations using the public keys and signatures reconstruct the DID from the genesis operation and ensure it is the same as the one we found in the DNS TXT record Can we trust the PLC directory though? Say a malicious actor has taken over control and modified all entries in the log. The hacker even constructed the entries in the log so that all the signatures appear valid. How would we still know the PLC directory is compromised? Remember that the DID can be reconstructed from the genesis operation. If that were modified, we’d end up with a different DID from the one we found in the DNS TXT record. Summary This blog explores the AT Protocol, the decentralized foundation of social applications like Bluesky, with a focus on user identities and their technical underpinnings. It explains how Bluesky user handles are associated with Decentralized Identifiers (DIDs), which can be resolved using DNS records or HTTPS endpoints. Detailed examples demonstrate how tools like dig and curl retrieve DID data, revealing the method (plc) and structure used to catalog them in the Public Ledger of Credentials (PLC). The post also examines the DID lifecycle, showcasing how historical operations and changes—such as updates to handles or servers—are logged and accessible for transparency. There are several aspects that we did not investigate, such as: the web method for DIDs tombstone operations in the log recovery how exactly to construct the prev entry in the DID document what the other fields in the DID document mean If you are curious, the links in the next section will help you answer these questions. Links &amp; Further Reading Bluesky Application AT Protocol AT Protocol Identity AT Protocol DID AT Protocol Handle Decentralized Identifiers (DIDs) v1.0 did:plc Method Specification Specification: DAG-CBORHow well do you know GitHub Actions?2022-09-25T00:00:00+00:002022-09-25T00:00:00+00:00https://fusectore.dev/2022/09/25/github-actions-pitfalls<p>During one of my assignments, I worked with GitHub Actions pretty much every day. I implemented workflows, created new actions, and helped people migrate their projects from jenkins to actions.</p> <p>As much as I like actions – and I like them a lot – there are some things that caught me off guard. I have collected some of these things, both for other people to let them know, and for myself as a future reference.</p> <p>How well do you know actions? Can you answer all of these questions correctly?</p> <p>ℹ️ Assume that all YAML and code is valid/compiles</p> <h2 id="output-from-a-previous-job">Output from a previous job</h2> <p>What does the following print?</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">jobs</span><span class="pi">:</span> <span class="na">jobOne</span><span class="pi">:</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">outputs</span><span class="pi">:</span> <span class="na">foo</span><span class="pi">:</span> <span class="s">${{ steps.foo.outputs.foo }}</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">echo '::set-output name=foo::bar'</span> <span class="na">id</span><span class="pi">:</span> <span class="s">foo</span> <span class="na">jobTwo</span><span class="pi">:</span> <span class="na">needs</span><span class="pi">:</span> <span class="s">jobOne</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">echo hello</span> <span class="na">jobThree</span><span class="pi">:</span> <span class="na">needs</span><span class="pi">:</span> <span class="s">jobTwo</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">echo ${{ needs.jobOne.outputs.foo }}</span> </code></pre></div></div> <p>Solution: It prints an empty line, because if you want to access an output from a job, you need to list it as a dependency. Outputs from transitive dependencies cannot be accessed.</p> <p>To fix this, make the third job depend on the first job explicitly <code class="language-plaintext highlighter-rouge">needs: [jobOne, jobTwo]</code></p> <h2 id="exec-explodes">Exec explodes</h2> <p>GitHub provides action developers with a <a href="https://github.com/actions/toolkit">toolkit</a> to develop actions in JavaScript. One of the functions in there is called <code class="language-plaintext highlighter-rouge">exec</code> and can be used to run a command. Here’s its <a href="https://github.com/actions/toolkit/blob/main/packages/exec/src/exec.ts#L17">signature</a>:</p> <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** * Exec a command. * Output will be streamed to the live console. * Returns promise with return code * * @param commandLine command to execute (can include additional args). Must be correctly escaped. * @param args optional arguments for tool. Escaping is handled by the lib. * @param options optional exec options. See ExecOptions * @returns Promise&lt;number&gt; exit code */</span> <span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">exec</span><span class="p">(</span> <span class="nx">commandLine</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">args</span><span class="p">?:</span> <span class="kr">string</span><span class="p">[],</span> <span class="nx">options</span><span class="p">?:</span> <span class="nx">ExecOptions</span> <span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="kr">number</span><span class="o">&gt;</span> </code></pre></div></div> <p>In an action, you might use it like so:</p> <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">returnCode</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">exec</span><span class="p">.</span><span class="nx">exec</span><span class="p">(</span><span class="dl">'</span><span class="s1">node</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span><span class="dl">'</span><span class="s1">index.js</span><span class="dl">'</span><span class="p">]);</span> <span class="k">if</span> <span class="p">(</span><span class="nx">returnCode</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Uh! Something did not quite work :(</span><span class="dl">'</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <p>Do you see any problems with that code?</p> <p>The problem is that <code class="language-plaintext highlighter-rouge">exec</code> does not return a non-zero return code if the command fails. Instead, it returns a rejected promise.</p> <p>So if you want to handle the error case, you need to wrap it in a try-catch block.</p> <p>While this behavior can be changed by passing <a href="https://github.com/actions/toolkit/blob/main/packages/exec/src/interfaces.ts#L28">ignoreReturnCode</a> as the third argument <code class="language-plaintext highlighter-rouge">ExecOptions</code>, the default behavior is very surprising. I have seen many examples of people checking the return code without knowing how this function really behaves.</p> <p>Nevertheless, I still think throwing an error by default is probably not a bad choice, given that (A) many people ignore error handling and (B) you probably want to abort on error.</p> <h2 id="push-for-all">Push for all?</h2> <p>Say you’re working on your open source project, and you just started using actions to make sure the build always passes:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span> <span class="s">push</span> <span class="na">jobs</span><span class="pi">:</span> <span class="na">build</span><span class="pi">:</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span> <span class="c1"># etc..</span> </code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">push</code> trigger seems to do the trick in all cases: You can push to a branch (or pull request) and the build runs. You can push to the main branch and the build runs.</p> <p>But there’s a scenario where it won’t run. Do you know when?</p> <p>Solution: If I fork your project and create a pull request, then <a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull-request-events-for-forked-repositories-2">push won’t trigger</a>. If you want the PRs of external contributors to be validated with your workflows as well, you need to use the trigger <code class="language-plaintext highlighter-rouge">pull_request</code>. And once you do that, you also need to make sure the <code class="language-plaintext highlighter-rouge">push</code> only triggers on main, because otherwise your own pull requests will trigger the workflow twice (both from <code class="language-plaintext highlighter-rouge">push</code> and <code class="language-plaintext highlighter-rouge">pull_request</code>):</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span> <span class="na">pull_request</span><span class="pi">:</span> <span class="na">push</span><span class="pi">:</span> <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">main</span><span class="pi">]</span> </code></pre></div></div> <p>When talking about running actions from contributors, it is important to mention that:</p> <ul> <li>You can control how actions are triggered in pull requests from forks by <a href="https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks">Approving workflow runs from public forks</a></li> <li>Secrets are generally not available in PRs from forks, except for <code class="language-plaintext highlighter-rouge">$GITHUB_TOKEN</code>, which is read-only. There’s a trigger called <code class="language-plaintext highlighter-rouge">pull_request_target</code> to work around that, but you need to be <a href="https://securitylab.github.com/research/github-actions-preventing-pwn-requests/">careful</a> as this open up the gates for potential vulnerabilities.</li> </ul> <h2 id="too-much-information-github">Too much information, GitHub</h2> <p>Say I have an open pull request based on a branch called <code class="language-plaintext highlighter-rouge">my-feature</code>, and the following workflow is triggered. What does it print?</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span> <span class="s">pull_request</span> <span class="na">jobs</span><span class="pi">:</span> <span class="na">branch</span><span class="pi">:</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">echo ${{ github.ref_name }}</span> </code></pre></div></div> <p>I’ll even give you the docs on <a href="https://docs.github.com/en/actions/learn-github-actions/contexts#github-context"><code class="language-plaintext highlighter-rouge">github.ref_name</code></a>:</p> <blockquote> <p>The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, <code class="language-plaintext highlighter-rouge">feature-branch-1</code>.</p> </blockquote> <p>I’m going to guess you said <code class="language-plaintext highlighter-rouge">my-feature</code>, but unfortunately you’re wrong.</p> <p>It is going to print the PR number followed by <code class="language-plaintext highlighter-rouge">/merge</code>. For example, it would print <code class="language-plaintext highlighter-rouge">18/merge</code> for the 18th pull request in your repository.</p> <p>Why is that? 🤔</p> <p>The simple explanation is that when you create a pull request on GitHub, they create some additional internal(!) branches to make it easier for them to manage your pull request and tell you how it compares to the branch you want to merge into. As a result of that, when you run a workflow on a <code class="language-plaintext highlighter-rouge">pull_request</code> event, it runs on one of these “internal” branches rather than the one you just pushed. In terms of contents on your branch, this doesn’t make a difference – you’re still running your builds and tests on your contents – it’s just the branch name that is different.</p> <p>Unfortunately, this implementation detail is exposed here.</p> <p>Please see this <a href="https://stackoverflow.com/a/63595981/1080523">answer on stackoverflow</a> for more information.</p> <p><em>Note that <code class="language-plaintext highlighter-rouge">$GITHUB_SHA</code> is <a href="https://github.com/orgs/community/discussions/26325">not what you’d expect</a> either</em></p> <p>So how can you access the name of your branch in a pull request? It’s in the payload from the event:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span> <span class="s">pull_request</span> <span class="na">jobs</span><span class="pi">:</span> <span class="na">branch</span><span class="pi">:</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">echo ${{ github.event.pull_request.head.ref }}</span> </code></pre></div></div> <h2 id="bonus-am-i-there-or-not">Bonus: Am I there or not?</h2> <p>Consider this reusable workflow:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span> <span class="na">workflow_call</span><span class="pi">:</span> <span class="na">inputs</span><span class="pi">:</span> <span class="na">my-input</span><span class="pi">:</span> <span class="na">description</span><span class="pi">:</span> <span class="s">optional input</span> <span class="na">required</span><span class="pi">:</span> <span class="no">false</span> <span class="na">type</span><span class="pi">:</span> <span class="s">string</span> <span class="na">jobs</span><span class="pi">:</span> <span class="na">greet</span><span class="pi">:</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">my/action@v1</span> <span class="na">with</span><span class="pi">:</span> <span class="na">my-input</span><span class="pi">:</span> <span class="s">${{ inputs.my-input }}</span> </code></pre></div></div> <p>Let’s say the action <code class="language-plaintext highlighter-rouge">my/action@v1</code> has an optional input <code class="language-plaintext highlighter-rouge">my-input</code> which has a default value <code class="language-plaintext highlighter-rouge">my-value</code>. Now if you were to call the reusable workflow as shown in the following example, what value would be passed to <code class="language-plaintext highlighter-rouge">my/action@v1</code>? Would the default be used?</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">jobs</span><span class="pi">:</span> <span class="na">jobA</span><span class="pi">:</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">org/repo/.github/workflows/workflow.yml@v1</span> </code></pre></div></div> <p>Solution: An empty string is passed to <code class="language-plaintext highlighter-rouge">my/action@v1</code> as <code class="language-plaintext highlighter-rouge">my-input</code>.</p> <p>The way this works is that if no value is passed to the reusable workflow, then the input <code class="language-plaintext highlighter-rouge">my-input</code> is still passed to the action, but it’s passed as an empty string. And even though we usually see an empty string as “no value passed”, the default value is not used here, because we did pass <em>something</em>.</p> <p>If you wanted to use the default value if no input was passed, then you’d have to invoke the action differently based on whether the input was passed to the reusable workflow.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">my/action@v1</span> <span class="na">if</span><span class="pi">:</span> <span class="s">inputs.my-input</span> <span class="na">with</span><span class="pi">:</span> <span class="na">my-input</span><span class="pi">:</span> <span class="s">${{ inputs.my-input }}</span> <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">my/action@v1</span> <span class="na">if</span><span class="pi">:</span> <span class="s">inputs.my-input == ''</span> </code></pre></div></div> <p>This has been a <a href="https://github.com/actions/runner/issues/924">long-standing issue</a> in the runner, but there’s likely no easy solution that does not break existing workflows.</p>During one of my assignments, I worked with GitHub Actions pretty much every day. I implemented workflows, created new actions, and helped people migrate their projects from jenkins to actions. As much as I like actions – and I like them a lot – there are some things that caught me off guard. I have collected some of these things, both for other people to let them know, and for myself as a future reference. How well do you know actions? Can you answer all of these questions correctly? ℹ️ Assume that all YAML and code is valid/compiles Output from a previous job What does the following print? jobs: jobOne: runs-on: ubuntu-latest outputs: foo: ${{ steps.foo.outputs.foo }} steps: - run: echo '::set-output name=foo::bar' id: foo jobTwo: needs: jobOne runs-on: ubuntu-latest steps: - run: echo hello jobThree: needs: jobTwo runs-on: ubuntu-latest steps: - run: echo ${{ needs.jobOne.outputs.foo }} Solution: It prints an empty line, because if you want to access an output from a job, you need to list it as a dependency. Outputs from transitive dependencies cannot be accessed. To fix this, make the third job depend on the first job explicitly needs: [jobOne, jobTwo] Exec explodes GitHub provides action developers with a toolkit to develop actions in JavaScript. One of the functions in there is called exec and can be used to run a command. Here’s its signature: /** * Exec a command. * Output will be streamed to the live console. * Returns promise with return code * * @param commandLine command to execute (can include additional args). Must be correctly escaped. * @param args optional arguments for tool. Escaping is handled by the lib. * @param options optional exec options. See ExecOptions * @returns Promise&lt;number&gt; exit code */ export async function exec( commandLine: string, args?: string[], options?: ExecOptions ): Promise&lt;number&gt; In an action, you might use it like so: const returnCode = await exec.exec('node', ['index.js']); if (returnCode != 0) { console.error('Uh! Something did not quite work :(') } Do you see any problems with that code? The problem is that exec does not return a non-zero return code if the command fails. Instead, it returns a rejected promise. So if you want to handle the error case, you need to wrap it in a try-catch block. While this behavior can be changed by passing ignoreReturnCode as the third argument ExecOptions, the default behavior is very surprising. I have seen many examples of people checking the return code without knowing how this function really behaves. Nevertheless, I still think throwing an error by default is probably not a bad choice, given that (A) many people ignore error handling and (B) you probably want to abort on error. Push for all? Say you’re working on your open source project, and you just started using actions to make sure the build always passes: on: push jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 # etc.. The push trigger seems to do the trick in all cases: You can push to a branch (or pull request) and the build runs. You can push to the main branch and the build runs. But there’s a scenario where it won’t run. Do you know when? Solution: If I fork your project and create a pull request, then push won’t trigger. If you want the PRs of external contributors to be validated with your workflows as well, you need to use the trigger pull_request. And once you do that, you also need to make sure the push only triggers on main, because otherwise your own pull requests will trigger the workflow twice (both from push and pull_request): on: pull_request: push: branches: [main] When talking about running actions from contributors, it is important to mention that: You can control how actions are triggered in pull requests from forks by Approving workflow runs from public forks Secrets are generally not available in PRs from forks, except for $GITHUB_TOKEN, which is read-only. There’s a trigger called pull_request_target to work around that, but you need to be careful as this open up the gates for potential vulnerabilities. Too much information, GitHub Say I have an open pull request based on a branch called my-feature, and the following workflow is triggered. What does it print? on: pull_request jobs: branch: runs-on: ubuntu-latest steps: - run: echo ${{ github.ref_name }} I’ll even give you the docs on github.ref_name: The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. I’m going to guess you said my-feature, but unfortunately you’re wrong. It is going to print the PR number followed by /merge. For example, it would print 18/merge for the 18th pull request in your repository. Why is that? 🤔 The simple explanation is that when you create a pull request on GitHub, they create some additional internal(!) branches to make it easier for them to manage your pull request and tell you how it compares to the branch you want to merge into. As a result of that, when you run a workflow on a pull_request event, it runs on one of these “internal” branches rather than the one you just pushed. In terms of contents on your branch, this doesn’t make a difference – you’re still running your builds and tests on your contents – it’s just the branch name that is different. Unfortunately, this implementation detail is exposed here. Please see this answer on stackoverflow for more information. Note that $GITHUB_SHA is not what you’d expect either So how can you access the name of your branch in a pull request? It’s in the payload from the event: on: pull_request jobs: branch: runs-on: ubuntu-latest steps: - run: echo ${{ github.event.pull_request.head.ref }} Bonus: Am I there or not? Consider this reusable workflow: on: workflow_call: inputs: my-input: description: optional input required: false type: string jobs: greet: runs-on: ubuntu-latest steps: - uses: my/action@v1 with: my-input: ${{ inputs.my-input }} Let’s say the action my/action@v1 has an optional input my-input which has a default value my-value. Now if you were to call the reusable workflow as shown in the following example, what value would be passed to my/action@v1? Would the default be used? jobs: jobA: uses: org/repo/.github/workflows/workflow.yml@v1 Solution: An empty string is passed to my/action@v1 as my-input. The way this works is that if no value is passed to the reusable workflow, then the input my-input is still passed to the action, but it’s passed as an empty string. And even though we usually see an empty string as “no value passed”, the default value is not used here, because we did pass something. If you wanted to use the default value if no input was passed, then you’d have to invoke the action differently based on whether the input was passed to the reusable workflow. steps: - uses: my/action@v1 if: inputs.my-input with: my-input: ${{ inputs.my-input }} - uses: my/action@v1 if: inputs.my-input == '' This has been a long-standing issue in the runner, but there’s likely no easy solution that does not break existing workflows.Notes from the Enlightened - Productivity Tip with Todoist2022-06-26T00:00:00+00:002022-06-26T00:00:00+00:00https://fusectore.dev/2022/06/26/how-i-use-todoist<p>Earlier this year, after three years, ten months, two weeks, and three days after having created my <a href="https://todoist.com">Todoist</a> account, I reached the Enlightened status, which is the highest level you can reach in Todoist’s gamification system. According to Todoist, only 0.05% of their users reach this status.</p> <p>I have been described as productive or structured by my peers. While I don’t think I’m as “productive” or “structured” as some might think, I still believe todoist helps a lot with organizing everyday tasks.</p> <p>In this post, I’m going to describe how I use Todoist to organize my work and life.</p> <h2 id="writing-things-down-frees-my-mind">Writing Things Down Frees My Mind</h2> <p>A few years ago, I read this book called <a href="https://en.wikipedia.org/wiki/Getting_Things_Done">Getting Things Done</a>. I don’t remember much, but one thing stuck with me: Imagine you need to mail an important letter tomorrow. This might prevent you from falling asleep easily, because you keep thinking about what would happen if you forgot to mail it.</p> <p>In these situations, I always add a task to Todoist and set a reminder for when I need to do it. This helps me free my mind and focus on something more important – or nothing at all, when I’d like to fall asleep.</p> <h2 id="stick-with-one-tool">Stick With One Tool</h2> <p>Are you reading <a href="https://www.producthunt.com/">Product Hunt</a>? <a href="https://news.ycombinator.com/">Hacker News</a>? There’s a new tool for keeping tasks almost every day.</p> <p>While trying them might be very tempting, I am convinced this is actually counter-productive. In fact, this is probably a form of procrastination: Instead of using one imperfect tool, you keep switching between tools only to discover that they’re not perfect either.</p> <p>Todoist is pretty simple. At least on the surface, it’s really just a piece of software that lets you create a task and set a due date. I don’t think more is needed to organize my life: A list of tasks, due dates, and potentially some form of categorization.</p> <h2 id="groom-your-inbox">Groom Your Inbox</h2> <p>Todoist comes with several views. The one I use most often is “Today”. In there are the tasks I want to finish today. Another one is the “Inbox”. Those are the tasks without a date.</p> <p>In terms of tasks that land in the Inbox, because they miss a due date, I have decided for myself that that’s a red flag. There is no task that has no due date. There might be a task that is not important, but even that task probably needs to be done by a certain date.</p> <p>Therefore, about once per month, I scroll through my Inbox and make sure every task has a due date. Sometimes, I would see a task where I actually forgot to set a date, and I’d fix that. However, what happens more often is that there are things in my Inbox, which are not really tasks, but rather just some notes I jotted down when I was on the go. I move those to the respective project, but more on that in the next section.</p> <h2 id="gather-notes-in-projects">Gather Notes In Projects</h2> <p>I don’t just use Todoist for things I need to do. I also use it to collect articles I’d like to read, recipes I’d like to try, things I’d like to do, etc.</p> <p>For these types of notes, I create a project. For example, I have a project with restaurants I have been to. For each restaurant, I add a short comment with my impression and where it is located.</p> <h3 id="procrastination-on-hackernews">Procrastination on HackerNews</h3> <p>HackerNews is my favorite place to slack away. If I’m working on a task I don’t like, suddenly every article on HackerNews seems very interesting to read.</p> <p>Instead of actually reading them, I just add them to a project in Todoist called “Articles to Read”. Whenever I do have some time to read (e.g. traveling on a train), I will look at this list of articles and start reading. In this situation, I often find the headlines much less appealing, and I just remove the task without reading the article.</p> <h2 id="add-people-tags">Add People Tags</h2> <p>There are always things I need to discuss with other people.</p> <p>For example, while working on accounting for my company, I might have a question for my accountant. Since it is usually not possible to bring these questions up right away, I usually create a task in Todoist and use a tag with the person’s name I want to discuss it with. This allows me to pull up a list of questions for my accountant when I am on the phone with her.</p> <p>Or, while working on a project at work, I may come up with an idea to improve the way we work. I’m sure we all had. But then when I have this meeting with my boss and the “uhm, so what else?” - question comes up, I now actually have a list of points to discuss. Instead of “uhm, yeah, I had something, but now I can’t remember”</p> <h2 id="set-up-recurring-tasks">Set Up Recurring Tasks</h2> <p>Remember the grooming I mentioned above? How do I not forget to do that? There’s a task that repeats every month.</p> <p>Another example of a recurring task I have is on Monday morning to look at my week’s schedule.</p> <p>I actually have a lot of recurring tasks. Sometimes, I would even create them as a temporary reminder of things I need to do for a short period of time. For example, I would really like to stretch every day. In order to get into the habit of doing that, I have created a task that repeats every day at 1 p.m.</p> <h2 id="use-magic-text-to-schedule">Use Magic Text To Schedule</h2> <p>As I’m working on a Mac these days, I create most of my tasks with the <a href="https://todoist.com/help/articles/keyboard-shortcuts">keyboard shortcut</a> <code class="language-plaintext highlighter-rouge">CMD (⌘) + CTRL + A</code>. When adding a task, I also immediately set a due date by just typing it in and Todoist automagically recognizes it.</p> <p>If you type a <code class="language-plaintext highlighter-rouge">#</code> into the field, a dropdown shows up with all projects. I use this to quickly categorize a task.</p> <p>So imagine someone sends me an interesting link via Slack and I don’t want to read it right away:</p> <ul> <li>Open the “Quick Add” on my Mac with the shortcut described above</li> <li>Paste the link (Todoist automatically pulls in the title of the page)</li> <li>Type a <code class="language-plaintext highlighter-rouge">#</code> and find the respective project by typing the first few letters of “Articles”</li> <li>Enter 🤓</li> </ul> <h2 id="make-packing-lists-using-templates">Make Packing Lists Using Templates</h2> <p>I used to return to my home country on a regular basis when I was living abroad. Here’s my problem: I’m always terrified that I’m going to forget something (and it often happened). So what I did with Todoist was create a project with all the things I needed to pack (one task per item).</p> <p>Now, whenever I am about to travel, I copy the project and start ticking off tasks as I pack my backpack. This makes the original project kind of a template, and the copy is the one I actually work on.</p> <p>If I do forget something, I just need to make sure to add it to the template project, and next time I won’t forget.</p> <h2 id="make-mundane-tasks-more-fun">Make Mundane Tasks More Fun</h2> <p>Sometimes I need to work on a mundane task, and I have difficulty focusing. When I become aware of that, I sometimes create a few tasks or subtasks in Todoist. By splitting off one big, not-so-interesting task into several still-not-very-interesting subtasks, I at least get the feeling of accomplishing something whenever I can tick off one of the smaller tasks.</p> <p>This way, I gamify my work, and it helps me get even the boring stuff done.</p> <h2 id="conclusion">Conclusion</h2> <p>By now, you probably guessed that Todoist is pretty central to my life. And there would be more to talk about, but I don’t want to make this article too long.</p> <p>I hope this list inspires you to use some sort of todo app as well and helps you stay on top of your tasks.</p> <p>If you have any suggestions or tricks for Todoist or productivity in general, please get in touch with me via email.</p>Earlier this year, after three years, ten months, two weeks, and three days after having created my Todoist account, I reached the Enlightened status, which is the highest level you can reach in Todoist’s gamification system. According to Todoist, only 0.05% of their users reach this status. I have been described as productive or structured by my peers. While I don’t think I’m as “productive” or “structured” as some might think, I still believe todoist helps a lot with organizing everyday tasks. In this post, I’m going to describe how I use Todoist to organize my work and life. Writing Things Down Frees My Mind A few years ago, I read this book called Getting Things Done. I don’t remember much, but one thing stuck with me: Imagine you need to mail an important letter tomorrow. This might prevent you from falling asleep easily, because you keep thinking about what would happen if you forgot to mail it. In these situations, I always add a task to Todoist and set a reminder for when I need to do it. This helps me free my mind and focus on something more important – or nothing at all, when I’d like to fall asleep. Stick With One Tool Are you reading Product Hunt? Hacker News? There’s a new tool for keeping tasks almost every day. While trying them might be very tempting, I am convinced this is actually counter-productive. In fact, this is probably a form of procrastination: Instead of using one imperfect tool, you keep switching between tools only to discover that they’re not perfect either. Todoist is pretty simple. At least on the surface, it’s really just a piece of software that lets you create a task and set a due date. I don’t think more is needed to organize my life: A list of tasks, due dates, and potentially some form of categorization. Groom Your Inbox Todoist comes with several views. The one I use most often is “Today”. In there are the tasks I want to finish today. Another one is the “Inbox”. Those are the tasks without a date. In terms of tasks that land in the Inbox, because they miss a due date, I have decided for myself that that’s a red flag. There is no task that has no due date. There might be a task that is not important, but even that task probably needs to be done by a certain date. Therefore, about once per month, I scroll through my Inbox and make sure every task has a due date. Sometimes, I would see a task where I actually forgot to set a date, and I’d fix that. However, what happens more often is that there are things in my Inbox, which are not really tasks, but rather just some notes I jotted down when I was on the go. I move those to the respective project, but more on that in the next section. Gather Notes In Projects I don’t just use Todoist for things I need to do. I also use it to collect articles I’d like to read, recipes I’d like to try, things I’d like to do, etc. For these types of notes, I create a project. For example, I have a project with restaurants I have been to. For each restaurant, I add a short comment with my impression and where it is located. Procrastination on HackerNews HackerNews is my favorite place to slack away. If I’m working on a task I don’t like, suddenly every article on HackerNews seems very interesting to read. Instead of actually reading them, I just add them to a project in Todoist called “Articles to Read”. Whenever I do have some time to read (e.g. traveling on a train), I will look at this list of articles and start reading. In this situation, I often find the headlines much less appealing, and I just remove the task without reading the article. Add People Tags There are always things I need to discuss with other people. For example, while working on accounting for my company, I might have a question for my accountant. Since it is usually not possible to bring these questions up right away, I usually create a task in Todoist and use a tag with the person’s name I want to discuss it with. This allows me to pull up a list of questions for my accountant when I am on the phone with her. Or, while working on a project at work, I may come up with an idea to improve the way we work. I’m sure we all had. But then when I have this meeting with my boss and the “uhm, so what else?” - question comes up, I now actually have a list of points to discuss. Instead of “uhm, yeah, I had something, but now I can’t remember” Set Up Recurring Tasks Remember the grooming I mentioned above? How do I not forget to do that? There’s a task that repeats every month. Another example of a recurring task I have is on Monday morning to look at my week’s schedule. I actually have a lot of recurring tasks. Sometimes, I would even create them as a temporary reminder of things I need to do for a short period of time. For example, I would really like to stretch every day. In order to get into the habit of doing that, I have created a task that repeats every day at 1 p.m. Use Magic Text To Schedule As I’m working on a Mac these days, I create most of my tasks with the keyboard shortcut CMD (⌘) + CTRL + A. When adding a task, I also immediately set a due date by just typing it in and Todoist automagically recognizes it. If you type a # into the field, a dropdown shows up with all projects. I use this to quickly categorize a task. So imagine someone sends me an interesting link via Slack and I don’t want to read it right away: Open the “Quick Add” on my Mac with the shortcut described above Paste the link (Todoist automatically pulls in the title of the page) Type a # and find the respective project by typing the first few letters of “Articles” Enter 🤓 Make Packing Lists Using Templates I used to return to my home country on a regular basis when I was living abroad. Here’s my problem: I’m always terrified that I’m going to forget something (and it often happened). So what I did with Todoist was create a project with all the things I needed to pack (one task per item). Now, whenever I am about to travel, I copy the project and start ticking off tasks as I pack my backpack. This makes the original project kind of a template, and the copy is the one I actually work on. If I do forget something, I just need to make sure to add it to the template project, and next time I won’t forget. Make Mundane Tasks More Fun Sometimes I need to work on a mundane task, and I have difficulty focusing. When I become aware of that, I sometimes create a few tasks or subtasks in Todoist. By splitting off one big, not-so-interesting task into several still-not-very-interesting subtasks, I at least get the feeling of accomplishing something whenever I can tick off one of the smaller tasks. This way, I gamify my work, and it helps me get even the boring stuff done. Conclusion By now, you probably guessed that Todoist is pretty central to my life. And there would be more to talk about, but I don’t want to make this article too long. I hope this list inspires you to use some sort of todo app as well and helps you stay on top of your tasks. If you have any suggestions or tricks for Todoist or productivity in general, please get in touch with me via email.RSA/ECB/PKCS1Padding in Node.js2021-12-17T00:00:00+00:002021-12-17T00:00:00+00:00https://fusectore.dev/2021/12/17/java-rsa-ecb-in-node<p>In Java, you can encrypt a string using the cryptic <code class="language-plaintext highlighter-rouge">RSA/ECB/PKCS1Padding</code> algorithm. If you try to do the same thing in Node.js, you’ll soon realize that this is not possible. Not because the equivalent does not exist in Node.js, but because the “algorithm” <code class="language-plaintext highlighter-rouge">RSA/ECB/PKCS1Padding</code> does not really exist and the identifier in Java is misleading.</p> <h2 id="what-is-the-problem">What is the problem?</h2> <p>Let’s take a look at the following code in Java:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">byte</span><span class="o">[]</span> <span class="nf">encrypt</span><span class="o">(</span><span class="nc">String</span> <span class="n">message</span><span class="o">,</span> <span class="nc">PublicKey</span> <span class="n">publicKey</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span> <span class="nc">Cipher</span> <span class="n">cipher</span> <span class="o">=</span> <span class="nc">Cipher</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="s">"RSA/ECB/PKCS1Padding"</span><span class="o">);</span> <span class="n">cipher</span><span class="o">.</span><span class="na">init</span><span class="o">(</span><span class="nc">Cipher</span><span class="o">.</span><span class="na">ENCRYPT_MODE</span><span class="o">,</span> <span class="n">publicKey</span><span class="o">);</span> <span class="k">return</span> <span class="n">cipher</span><span class="o">.</span><span class="na">doFinal</span><span class="o">(</span><span class="n">message</span><span class="o">.</span><span class="na">getBytes</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <p>This method takes a plaintext message and encrypts it using the specified public key. Looking at the invocation <code class="language-plaintext highlighter-rouge">getInstance</code> on <code class="language-plaintext highlighter-rouge">Cipher</code> we get the idea that the algorithm used is <code class="language-plaintext highlighter-rouge">RSA/ECB/PKCS1Padding</code>. But what is this?</p> <p>“RSA” sounds like it is using <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">asymmetric encryption</a>, but “ECB” is a <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#ECB">block cipher mode of operation</a> typically used to encrypt multiple blocks of data using a symmetric key.</p> <p>So what does this cipher actually do? Does it create a random key for the symmetric encryption and then encrypt that key with the public key appending it to the message as is done in <a href="https://en.wikipedia.org/wiki/Hybrid_cryptosystem">hybrid encryption</a>?</p> <p>Well, no. The name is just misleading. In fact, looking at the source code of the class Cipher, we see that the actual implementation for the encryption is in a class called <a href="https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java">RSACipher.java</a>. And inside that class, the following method reveals it all:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// modes do not make sense for RSA, but allow ECB</span> <span class="c1">// see JCE spec</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">engineSetMode</span><span class="o">(</span><span class="nc">String</span> <span class="n">mode</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">NoSuchAlgorithmException</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">mode</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="s">"ECB"</span><span class="o">)</span> <span class="o">==</span> <span class="kc">false</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">NoSuchAlgorithmException</span><span class="o">(</span><span class="s">"Unsupported mode "</span> <span class="o">+</span> <span class="n">mode</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>So <code class="language-plaintext highlighter-rouge">ECB</code> is used as an identifier, but has no actual meaning or influence. Passing anything else makes this method fail.</p> <h3 id="what-does-really-happen">What does really happen?</h3> <p>We haven’t talked about padding yet. I won’t go into too much detail, but what you need to know is that encryption works on fixed-size blocks of data. For example, if you have a message of 30 bytes and the algorithm encrypts 45 bytes at a time, then you need to add 15 bytes to the message. When decrypting, that algorithm also needs to know that these 15 bytes were only added as padding and are not part of the original message.</p> <p>What if the message is bigger that the amount of bytes that are encrypted at a time? Usually, you’d encrypt the message block-by-block combing the individual blocks together. This is where ECB would come into play. As we saw before, ECB is ignored though. Therefore, this method would throw an exception when called with a message that is bigger than one block.</p> <p>Knowing that ECB is ignored, what this algorithm really does is encrypt the message with the public key using PKCS1 padding.</p> <h2 id="how-can-the-equivalent-be-done-with-nodejs">How can the equivalent be done with Node.js?</h2> <p>Let’s say we want to do the same in Node.js. We wouldn’t be the first one to search online for “how to do RSA/ECB/PKCS1Padding in Node.js”. This has been asked in <a href="https://github.com/digitalbazaar/forge/issues/407">a popular node library’s issue tracker</a> but also on <a href="https://stackoverflow.com/questions/2855326/how-can-i-create-a-key-using-rsa-ecb-pkcs1padding-in-python/2856628">StackOverflow for python</a></p> <p>All answers point to the same: It does not make sense and therefore does not exist. However, knowing what we learned above, what we actually need is a way to encrypt a message with RSA using PKCS1 padding. This makes the search easier.</p> <p>In Node.js, we can use the module <a href="https://www.npmjs.com/package/node-forge">node-forge</a>. You can install it using the following command:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> node <span class="nb">install </span>node-forge </code></pre></div></div> <p>And then we can use it to encrypt the message:</p> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">encrypt</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="nx">publicKey</span><span class="p">)</span> <span class="p">{</span> <span class="kd">var</span> <span class="nx">bytes</span> <span class="o">=</span> <span class="nx">forge</span><span class="p">.</span><span class="nx">util</span><span class="p">.</span><span class="nx">createBuffer</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">).</span><span class="nx">getBytes</span><span class="p">();</span> <span class="k">return</span> <span class="nx">publicKey</span><span class="p">.</span><span class="nx">encrypt</span><span class="p">(</span><span class="nx">bytes</span><span class="p">,</span> <span class="dl">'</span><span class="s1">RSAES-PKCS1-V1_5</span><span class="dl">'</span><span class="p">);</span> <span class="p">}</span> </code></pre></div></div> <p>This is assuming that the specified <code class="language-plaintext highlighter-rouge">publicKey</code> has been created with forge as well. There are several examples in their <a href="https://github.com/digitalbazaar/forge#rsa">documentation</a> how a public key can be created.</p> <h2 id="conclusion">Conclusion</h2> <p>The naming for this algorithm is really misleading. This makes the search for the equivalent implementation much more difficult than it should be. Instead of finding what we’re looking for, we had to take a look at the source code to see what really happens.</p> <p>Once it’s clear what’s really happening, the solution turns out to be quite simple though.</p>In Java, you can encrypt a string using the cryptic RSA/ECB/PKCS1Padding algorithm. If you try to do the same thing in Node.js, you’ll soon realize that this is not possible. Not because the equivalent does not exist in Node.js, but because the “algorithm” RSA/ECB/PKCS1Padding does not really exist and the identifier in Java is misleading. What is the problem? Let’s take a look at the following code in Java: public byte[] encrypt(String message, PublicKey publicKey) throws Exception { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(message.getBytes()); } This method takes a plaintext message and encrypts it using the specified public key. Looking at the invocation getInstance on Cipher we get the idea that the algorithm used is RSA/ECB/PKCS1Padding. But what is this? “RSA” sounds like it is using asymmetric encryption, but “ECB” is a block cipher mode of operation typically used to encrypt multiple blocks of data using a symmetric key. So what does this cipher actually do? Does it create a random key for the symmetric encryption and then encrypt that key with the public key appending it to the message as is done in hybrid encryption? Well, no. The name is just misleading. In fact, looking at the source code of the class Cipher, we see that the actual implementation for the encryption is in a class called RSACipher.java. And inside that class, the following method reveals it all: // modes do not make sense for RSA, but allow ECB // see JCE spec protected void engineSetMode(String mode) throws NoSuchAlgorithmException { if (mode.equalsIgnoreCase("ECB") == false) { throw new NoSuchAlgorithmException("Unsupported mode " + mode); } } So ECB is used as an identifier, but has no actual meaning or influence. Passing anything else makes this method fail. What does really happen? We haven’t talked about padding yet. I won’t go into too much detail, but what you need to know is that encryption works on fixed-size blocks of data. For example, if you have a message of 30 bytes and the algorithm encrypts 45 bytes at a time, then you need to add 15 bytes to the message. When decrypting, that algorithm also needs to know that these 15 bytes were only added as padding and are not part of the original message. What if the message is bigger that the amount of bytes that are encrypted at a time? Usually, you’d encrypt the message block-by-block combing the individual blocks together. This is where ECB would come into play. As we saw before, ECB is ignored though. Therefore, this method would throw an exception when called with a message that is bigger than one block. Knowing that ECB is ignored, what this algorithm really does is encrypt the message with the public key using PKCS1 padding. How can the equivalent be done with Node.js? Let’s say we want to do the same in Node.js. We wouldn’t be the first one to search online for “how to do RSA/ECB/PKCS1Padding in Node.js”. This has been asked in a popular node library’s issue tracker but also on StackOverflow for python All answers point to the same: It does not make sense and therefore does not exist. However, knowing what we learned above, what we actually need is a way to encrypt a message with RSA using PKCS1 padding. This makes the search easier. In Node.js, we can use the module node-forge. You can install it using the following command: &gt; node install node-forge And then we can use it to encrypt the message: function encrypt(message, publicKey) { var bytes = forge.util.createBuffer(message, 'utf8').getBytes(); return publicKey.encrypt(bytes, 'RSAES-PKCS1-V1_5'); } This is assuming that the specified publicKey has been created with forge as well. There are several examples in their documentation how a public key can be created. Conclusion The naming for this algorithm is really misleading. This makes the search for the equivalent implementation much more difficult than it should be. Instead of finding what we’re looking for, we had to take a look at the source code to see what really happens. Once it’s clear what’s really happening, the solution turns out to be quite simple though.Fancy GitHub Actions with Problem Matchers2021-11-19T00:00:00+00:002021-11-19T00:00:00+00:00https://fusectore.dev/2021/11/19/github-action-problem-matchers<p>GitHub Actions is the CI/CD platform for GitHub. It’s a powerful tool that makes it easy to run CI/CD pipelines on GitHub.</p> <p>For (aspiring) authors of Actions, there is a little known tool that can help you propel your Action from one that shows some output in the logs to one that directly annotates problems in your code making it visible in pull requests.</p> <p>I’m talking about <a href="https://github.com/actions/toolkit/blob/main/docs/problem-matchers.md">Problem Matchers</a> and the annotations they can create:</p> <p><img src="/assets/images/2021-11-19-action-problem-matcher.png" alt="Problem Matcher Annotation" /></p> <h2 id="what-is-a-problem-matcher">What is a Problem Matcher?</h2> <p>A Problem Matcher is a small piece of configuration that tells GitHub Actions what lines of your logs correspond to what lines of your code.</p> <p>Imagine a tool that creates error messages like the following:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> json-validator myconfig.json WARN: myconfig.json:1:9: Missing attribute <span class="s1">'age'</span> ERROR: myconfig.json:1:4: Unexpected token <span class="s1">'}'</span> </code></pre></div></div> <p>These logs contain all information necessary in order to create annotations as shown above:</p> <ul> <li>each line starts with the severity of the message</li> <li>it then specifies the filename and location where the problem occurred</li> <li>and the last part of the line is the actual problem</li> </ul> <p>A Problem Matcher tells GitHub exactly this: how bad is the problem (severity), where did it occur (filename and line number), and what is the problem itself (message).</p> <h2 id="how-can-i-create-a-problem-matcher">How can I create a Problem Matcher?</h2> <p>Let’s say you want to create a Problem Matcher for the hypothetical <code class="language-plaintext highlighter-rouge">json-validator</code> above. There are two steps you need to follow in order to create and use such a Problem Matcher.</p> <h3 id="step-1-define-the-problem-matcher">Step 1: Define the Problem Matcher</h3> <p>A Problem Matcher is defined in a JSON file and consists of an owner and a pattern that defines how to scan the logs and extract the relevant information.</p> <p>The following is an example Problem Matcher for the <code class="language-plaintext highlighter-rouge">json-validator</code>.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"problemMatcher"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"owner"</span><span class="p">:</span><span class="w"> </span><span class="s2">"json-validator"</span><span class="p">,</span><span class="w"> </span><span class="nl">"pattern"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"regexp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^(ERROR|WARN|INFO):</span><span class="se">\\</span><span class="s2">s(.+):(</span><span class="se">\\</span><span class="s2">d+):(</span><span class="se">\\</span><span class="s2">d+):</span><span class="se">\\</span><span class="s2">s(.+)$"</span><span class="p">,</span><span class="w"> </span><span class="nl">"severity"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="nl">"file"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="nl">"line"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="nl">"column"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>I have given it a name, <code class="language-plaintext highlighter-rouge">json-validtor</code> and defined the regular expression in the <code class="language-plaintext highlighter-rouge">regexp</code> field. This regex says:</p> <ul> <li>log lines start with <code class="language-plaintext highlighter-rouge">ERROR</code>, <code class="language-plaintext highlighter-rouge">WARN</code> or <code class="language-plaintext highlighter-rouge">INFO</code></li> <li>after a colon and a space , there is a filename</li> <li>after another colon, there is a line number</li> <li>after another colon, there is a column number</li> <li>after another colon, there is a message</li> </ul> <p>With the attributes below the <code class="language-plaintext highlighter-rouge">regexp</code> attribute, we’re defining which match group (these are the patterns in brackets) of the regular expression map to which attribute. For example, by defining <code class="language-plaintext highlighter-rouge">severity: 1</code>, we’re telling the regex that the first match group is the severity.</p> <p>And that’s it! All we now need to do is install the Problem Matcher.</p> <h3 id="step-2-install-the-problem-matcher">Step 2: Install the Problem Matcher</h3> <p>With the JSON file from the first step stored as <code class="language-plaintext highlighter-rouge">matcher.json</code>, we can go ahead and install the matcher either in a workflow or as part of an Action. This can be done with the command <code class="language-plaintext highlighter-rouge">add-matcher</code>, like so:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">echo "::add-matcher::matcher.json"</span> <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">json-validator myconfig.json</span> <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">echo "::remove-matcher owner=json-validator::"</span> </code></pre></div></div> <p>With these steps, we’re first installing the Problem Matcher, then running the Action, and eventually removing the Problem Matcher again.</p> <p><em>Note that a Problem Matcher is removed by specifying the owner of the matcher. Removing it makes sure the Problem Matcher is not applied to unrelated Actions that emit similar log files.</em></p> <h2 id="conclusion">Conclusion</h2> <p>Hopefully this article gives you a good overview of how Problem Matchers work and how to use them.</p> <p>If you want to see them used in practice, checkout out the <a href="https://github.com/actions/setup-node">setup-node</a> action.</p> <p>If you’re looking for something to play around with, or if you want a head start for a simple action that uses Problem Matchers, you might want to take a look at my <a href="https://github.com/rethab/bash-action">bash-action</a>.</p>GitHub Actions is the CI/CD platform for GitHub. It’s a powerful tool that makes it easy to run CI/CD pipelines on GitHub. For (aspiring) authors of Actions, there is a little known tool that can help you propel your Action from one that shows some output in the logs to one that directly annotates problems in your code making it visible in pull requests. I’m talking about Problem Matchers and the annotations they can create: What is a Problem Matcher? A Problem Matcher is a small piece of configuration that tells GitHub Actions what lines of your logs correspond to what lines of your code. Imagine a tool that creates error messages like the following: &gt; json-validator myconfig.json WARN: myconfig.json:1:9: Missing attribute 'age' ERROR: myconfig.json:1:4: Unexpected token '}' These logs contain all information necessary in order to create annotations as shown above: each line starts with the severity of the message it then specifies the filename and location where the problem occurred and the last part of the line is the actual problem A Problem Matcher tells GitHub exactly this: how bad is the problem (severity), where did it occur (filename and line number), and what is the problem itself (message). How can I create a Problem Matcher? Let’s say you want to create a Problem Matcher for the hypothetical json-validator above. There are two steps you need to follow in order to create and use such a Problem Matcher. Step 1: Define the Problem Matcher A Problem Matcher is defined in a JSON file and consists of an owner and a pattern that defines how to scan the logs and extract the relevant information. The following is an example Problem Matcher for the json-validator. { "problemMatcher": [ { "owner": "json-validator", "pattern": [ { "regexp": "^(ERROR|WARN|INFO):\\s(.+):(\\d+):(\\d+):\\s(.+)$", "severity": 1, "file": 2, "line": 3, "column": 4, "message": 5 } ] } ] } I have given it a name, json-validtor and defined the regular expression in the regexp field. This regex says: log lines start with ERROR, WARN or INFO after a colon and a space , there is a filename after another colon, there is a line number after another colon, there is a column number after another colon, there is a message With the attributes below the regexp attribute, we’re defining which match group (these are the patterns in brackets) of the regular expression map to which attribute. For example, by defining severity: 1, we’re telling the regex that the first match group is the severity. And that’s it! All we now need to do is install the Problem Matcher. Step 2: Install the Problem Matcher With the JSON file from the first step stored as matcher.json, we can go ahead and install the matcher either in a workflow or as part of an Action. This can be done with the command add-matcher, like so: steps: - run: echo "::add-matcher::matcher.json" - run: json-validator myconfig.json - run: echo "::remove-matcher owner=json-validator::" With these steps, we’re first installing the Problem Matcher, then running the Action, and eventually removing the Problem Matcher again. Note that a Problem Matcher is removed by specifying the owner of the matcher. Removing it makes sure the Problem Matcher is not applied to unrelated Actions that emit similar log files. Conclusion Hopefully this article gives you a good overview of how Problem Matchers work and how to use them. If you want to see them used in practice, checkout out the setup-node action. If you’re looking for something to play around with, or if you want a head start for a simple action that uses Problem Matchers, you might want to take a look at my bash-action.Troubleshooting Netctl under Arch Linux2016-12-28T00:00:00+00:002016-12-28T00:00:00+00:00https://fusectore.dev/2016/12/28/netctl-arch-linux<p>I recently had an issue connecting to a WLAN with my ArchLinux/Systemd/Netctl notebook. While analyzing it, I found a few ways to troubleshoot issues with netctl.</p> <p>For the following instructions and examples, please note that my wlan interface is called <code class="language-plaintext highlighter-rouge">wlp2s0</code> and may differ on your system. You may get a list of interfaces with the command</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> ip <span class="nb">link</span> </code></pre></div></div> <h3 id="is-your-profile-loaded">Is your profile loaded?</h3> <p>After I copied and adapted one of the profiles from <code class="language-plaintext highlighter-rouge">/etc/netctl/examples</code> and tried to switch to it, I got the following error message:</p> <p><code class="language-plaintext highlighter-rouge">Profile 'wlp2s0-33C3' does not exist or is not available</code></p> <p>The problem was that I did not reload netctl-auto. This is how that can be done:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> <span class="nb">sudo </span>systemctl restart [email protected] </code></pre></div></div> <p>It is usually handy to have the journal log running in a separate console while looking into issues of this sort. When reloading netctl-auto for example, you could have the journal log running with</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> <span class="nb">sudo </span>journalctl <span class="nt">-xef</span> </code></pre></div></div> <p>If the profile was successfully loaded, you should see the output</p> <p><code class="language-plaintext highlighter-rouge">Dec 28 13:24:28 asus-rethab netctl-auto[3204]: Included profile 'wlp2s0-33C3'</code></p> <h3 id="issues-with-the-profile">Issues with the profile?</h3> <p>The ESSID of the network I was trying to connect to was <code class="language-plaintext highlighter-rouge">33C3</code>. For some reason, I forgot to put this into quotes in the netctl profile so it would simply not be activated and keep falling back to some other default profile. I figured this out by instructing netctl to tell me more about what it is doing with the environment variable <code class="language-plaintext highlighter-rouge">NETCTL_DEBUG</code> like so:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> <span class="nv">NETCTL_DEBUG</span><span class="o">=</span><span class="nb">true sudo </span>netctl-auto switch-to wlp2s0-33C3 </code></pre></div></div> <p>This way I learned that <code class="language-plaintext highlighter-rouge">wpa_cli</code> was trying different networks and eventually seemed to fallback to the default. So I ran <code class="language-plaintext highlighter-rouge">wpa_cli</code> manually to see which options it tried:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$&gt;</span> <span class="nb">sudo </span>wpa_cli <span class="nt">-i</span> wlp2s0 list_networks </code></pre></div></div> <p>This listed me all familiar networks plus one that was named <code class="language-plaintext highlighter-rouge">3\xc3</code> which lead me to suspect that something with the ESSID must be wrong.</p>I recently had an issue connecting to a WLAN with my ArchLinux/Systemd/Netctl notebook. While analyzing it, I found a few ways to troubleshoot issues with netctl. For the following instructions and examples, please note that my wlan interface is called wlp2s0 and may differ on your system. You may get a list of interfaces with the command $&gt; ip link Is your profile loaded? After I copied and adapted one of the profiles from /etc/netctl/examples and tried to switch to it, I got the following error message: Profile 'wlp2s0-33C3' does not exist or is not available The problem was that I did not reload netctl-auto. This is how that can be done: $&gt; sudo systemctl restart [email protected] It is usually handy to have the journal log running in a separate console while looking into issues of this sort. When reloading netctl-auto for example, you could have the journal log running with $&gt; sudo journalctl -xef If the profile was successfully loaded, you should see the output Dec 28 13:24:28 asus-rethab netctl-auto[3204]: Included profile 'wlp2s0-33C3' Issues with the profile? The ESSID of the network I was trying to connect to was 33C3. For some reason, I forgot to put this into quotes in the netctl profile so it would simply not be activated and keep falling back to some other default profile. I figured this out by instructing netctl to tell me more about what it is doing with the environment variable NETCTL_DEBUG like so: $&gt; NETCTL_DEBUG=true sudo netctl-auto switch-to wlp2s0-33C3 This way I learned that wpa_cli was trying different networks and eventually seemed to fallback to the default. So I ran wpa_cli manually to see which options it tried: $&gt; sudo wpa_cli -i wlp2s0 list_networks This listed me all familiar networks plus one that was named 3\xc3 which lead me to suspect that something with the ESSID must be wrong.Practical Dependency Injection for the Play Framework using Guice: Part 12016-11-26T00:00:00+00:002016-11-26T00:00:00+00:00https://fusectore.dev/2016/11/26/dependency-injection-with-play<p>This is the first part in a series of articles on dependency injection for the play framework using guice. As soon as I’ve written the next part, it will be linked here.</p> <p>Please note that while the examples are written in Scala, the principles apply for Java likewise.</p> <p>Furthermore, please be aware that Guice is not the only framework to do dependency injection with. It is probably the most prominent one in the play ecosystem though. Guice does so called <em>runtime dependency injection</em>. This means dependencies are resolved at runtime. An alternative approach is <em>compile time dependency injection</em>, which essentially means if a dependency cannot be resolved, the code doesn’t even compile. Please read the play documentation for more on the latter: <a href="https://www.playframework.com/documentation/2.5.x/ScalaCompileTimeDependencyInjection">Compile Time Dependency Injection</a></p> <h2 id="why-dependency-injection">Why Dependency Injection?</h2> <p>At the time of writing, version 2.5 is released and the play framework is in the process of moving away from global state (<a href="http://stackoverflow.com/a/40799224/1080523">What exactly is that global state?</a>). While older version heavily relied on the function <code class="language-plaintext highlighter-rouge">Play.current</code>, this has now been deprecated and people are looking for ways to restructure their applications to no longer depend on this.</p> <p>Accessing the currently running application used to be the central access point for a variety of components such as <code class="language-plaintext highlighter-rouge">Configuration</code>, <code class="language-plaintext highlighter-rouge">ActorSystem</code>, <code class="language-plaintext highlighter-rouge">Connection</code>, <code class="language-plaintext highlighter-rouge">WSClient</code> and many more. Therefore, we now need a new way to access them.</p> <h2 id="a-simple-example">A Simple Example</h2> <p>The following example shows how we could use the configuration in a controller where we previously might have used <code class="language-plaintext highlighter-rouge">Play.configuration</code>. Adding the annotation <code class="language-plaintext highlighter-rouge">javax.inject.Inject</code> directly after the class name allows us to specify a number of components that we would like an instance to be injected.</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyController</span> <span class="nd">@Inject</span><span class="o">()</span> <span class="o">(</span><span class="nl">config:</span> <span class="nc">Configuration</span><span class="o">)</span> <span class="o">{</span> <span class="n">val</span> <span class="n">maxSize</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="na">getInt</span><span class="o">(</span><span class="s">"maxSize"</span><span class="o">)</span> <span class="o">}</span> </code></pre></div></div> <h3 id="what-just-happened">What just happened?</h3> <p>If we look at the above example again, we can see that our controller is a simple class that takes one parameter. But how does the framework know which exact parameter is to be passed in there? This is defined by using so called <em>bindings</em>. A binding can be roughly formulated by saying <em>if a thing of type A needs to be injected into a constructor, which concrete instance of A shall be taken</em>.</p> <p>A number of bindings are provided by the play framework. For example, as seen above, the play framework defines (in a class called <code class="language-plaintext highlighter-rouge">BuiltinModule.scala</code>, for the impatient) which concrete instance of Configuration is to be injected if a user requests a Configuration. An (incomplete) list of the components that are provided by the play framework can be found in the <a href="https://www.playframework.com/documentation/2.4.x/Migration24#Dependency-Injected-Components">2.4 Migration Guide</a>.</p> <h3 id="migrating-companion-objects">Migrating Companion Objects</h3> <p>A consequence of injecting instances is that code that requires such a component can no longer live in a companion object as it could before. Let’s say we have a service that has to access the configuration and a controller that uses the service:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">object</span> <span class="nc">MyService</span> <span class="o">{</span> <span class="n">def</span> <span class="nl">getMaxSize:</span> <span class="nc">Option</span><span class="o">[</span><span class="nc">Int</span><span class="o">]</span> <span class="o">=</span> <span class="nc">Play</span><span class="o">.</span><span class="na">configuration</span><span class="o">.</span><span class="na">getInt</span><span class="o">(</span><span class="s">"maxSize"</span><span class="o">)</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">MyController</span> <span class="o">{</span> <span class="n">val</span> <span class="n">maxSize</span> <span class="o">=</span> <span class="nc">MyService</span><span class="o">.</span><span class="na">getMaxSize</span> <span class="o">}</span> </code></pre></div></div> <p>Now that we need to avoid static access to the configuration and inject it instead, we also need to migrate the service to a class. But then how does the controller access the service? Just like the service injects the configuration, the controller now needs to inject the service:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyService</span> <span class="nd">@Inject</span><span class="o">()</span> <span class="o">(</span><span class="nl">config:</span> <span class="nc">Configuration</span><span class="o">)</span> <span class="o">{</span> <span class="n">def</span> <span class="nl">getMaxSize:</span> <span class="nc">Option</span><span class="o">[</span><span class="nc">Int</span><span class="o">]</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="na">getInt</span><span class="o">(</span><span class="s">"maxSize"</span><span class="o">)</span> <span class="o">}</span> <span class="kd">class</span> <span class="nc">MyController</span> <span class="nd">@Inject</span><span class="o">()</span> <span class="o">(</span><span class="nl">myService:</span> <span class="nc">MyService</span><span class="o">)</span> <span class="o">{</span> <span class="n">val</span> <span class="n">maxSize</span> <span class="o">=</span> <span class="n">myService</span><span class="o">.</span><span class="na">getMaxSize</span> <span class="o">}</span> </code></pre></div></div> <h2 id="why-go-through-all-this-hassle">Why go through all this Hassle?</h2> <p>As the above example with the service and the controller has shown, using dependency injection increased the size of the code and made it a bit more complicated as when compared with directly invoking the method on the companion object.</p> <p>One big win of dependency injection, however, is testability. Just imagine for a second how you’d write a test for the method <code class="language-plaintext highlighter-rouge">getMaxSize</code> in the companion object. Since it accesses the configuration from the currently running application, you’d need to make sure that an application is running when you’re testing that method. Think about this: You need an entire play application running just to test a simple method!</p> <p>Injecting the dependencies greatly simplifies that. You can now instantiate the service directly in your test and pass in the configuration required for your test - scenario. You could even easily test the behavior if the key <code class="language-plaintext highlighter-rouge">maxSize</code> does not exist (the example uses <a href="http://etorreborre.github.io/specs2/">specs2</a>).</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyServiceTest</span> <span class="kd">extends</span> <span class="nc">Specification</span> <span class="o">{</span> <span class="s">"getMaxSize"</span> <span class="n">should</span> <span class="o">{</span> <span class="s">"return the size"</span> <span class="o">&gt;&gt;</span> <span class="o">{</span> <span class="n">val</span> <span class="n">config</span> <span class="o">=</span> <span class="nc">Configuration</span><span class="o">(</span><span class="s">"maxSize"</span> <span class="o">-&gt;</span> <span class="mi">4</span><span class="o">)</span> <span class="n">val</span> <span class="n">service</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MyService</span><span class="o">(</span><span class="n">config</span><span class="o">)</span> <span class="n">service</span><span class="o">.</span><span class="na">getMaxSize</span> <span class="n">must_</span><span class="o">===</span> <span class="nc">Some</span><span class="o">(</span><span class="mi">4</span><span class="o">)</span> <span class="o">}</span> <span class="s">"return none if not configured"</span> <span class="o">&gt;&gt;</span> <span class="o">{</span> <span class="n">val</span> <span class="n">config</span> <span class="o">=</span> <span class="nc">Configuration</span><span class="o">()</span> <span class="n">val</span> <span class="n">service</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MyService</span><span class="o">(</span><span class="n">config</span><span class="o">)</span> <span class="n">service</span><span class="o">.</span><span class="na">getMaxSize</span> <span class="n">must</span> <span class="n">beNone</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <h2 id="conclusion">Conclusion</h2> <p>In this first article, I have tried to shed some light into why the play framework uses dependency injection in the first place as well as a few examples on how to use dependency injection within an application and the benefits that come along with it.</p> <p>Stay tuned for the following parts where we’re going to look at more realistic examples, learn how to define bindings and much more!</p> <h3 id="links-and-further-reading">Links and Further Reading</h3> <ul> <li>Google Guice 101, Bob Lee Jesse Wilson: <a href="https://www.youtube.com/watch?v=FFXhXZnmEQM">https://www.youtube.com/watch?v=FFXhXZnmEQM</a></li> <li>Guice Docs: <a href="https://github.com/google/guice/wiki/Motivation">https://github.com/google/guice/wiki/Motivation</a></li> <li>Assisted Inject: <a href="https://github.com/google/guice/wiki/AssistedInject">https://github.com/google/guice/wiki/AssistedInject</a></li> </ul>This is the first part in a series of articles on dependency injection for the play framework using guice. As soon as I’ve written the next part, it will be linked here. Please note that while the examples are written in Scala, the principles apply for Java likewise. Furthermore, please be aware that Guice is not the only framework to do dependency injection with. It is probably the most prominent one in the play ecosystem though. Guice does so called runtime dependency injection. This means dependencies are resolved at runtime. An alternative approach is compile time dependency injection, which essentially means if a dependency cannot be resolved, the code doesn’t even compile. Please read the play documentation for more on the latter: Compile Time Dependency Injection Why Dependency Injection? At the time of writing, version 2.5 is released and the play framework is in the process of moving away from global state (What exactly is that global state?). While older version heavily relied on the function Play.current, this has now been deprecated and people are looking for ways to restructure their applications to no longer depend on this. Accessing the currently running application used to be the central access point for a variety of components such as Configuration, ActorSystem, Connection, WSClient and many more. Therefore, we now need a new way to access them. A Simple Example The following example shows how we could use the configuration in a controller where we previously might have used Play.configuration. Adding the annotation javax.inject.Inject directly after the class name allows us to specify a number of components that we would like an instance to be injected. class MyController @Inject() (config: Configuration) { val maxSize = config.getInt("maxSize") } What just happened? If we look at the above example again, we can see that our controller is a simple class that takes one parameter. But how does the framework know which exact parameter is to be passed in there? This is defined by using so called bindings. A binding can be roughly formulated by saying if a thing of type A needs to be injected into a constructor, which concrete instance of A shall be taken. A number of bindings are provided by the play framework. For example, as seen above, the play framework defines (in a class called BuiltinModule.scala, for the impatient) which concrete instance of Configuration is to be injected if a user requests a Configuration. An (incomplete) list of the components that are provided by the play framework can be found in the 2.4 Migration Guide. Migrating Companion Objects A consequence of injecting instances is that code that requires such a component can no longer live in a companion object as it could before. Let’s say we have a service that has to access the configuration and a controller that uses the service: object MyService { def getMaxSize: Option[Int] = Play.configuration.getInt("maxSize") } class MyController { val maxSize = MyService.getMaxSize } Now that we need to avoid static access to the configuration and inject it instead, we also need to migrate the service to a class. But then how does the controller access the service? Just like the service injects the configuration, the controller now needs to inject the service: class MyService @Inject() (config: Configuration) { def getMaxSize: Option[Int] = config.getInt("maxSize") } class MyController @Inject() (myService: MyService) { val maxSize = myService.getMaxSize } Why go through all this Hassle? As the above example with the service and the controller has shown, using dependency injection increased the size of the code and made it a bit more complicated as when compared with directly invoking the method on the companion object. One big win of dependency injection, however, is testability. Just imagine for a second how you’d write a test for the method getMaxSize in the companion object. Since it accesses the configuration from the currently running application, you’d need to make sure that an application is running when you’re testing that method. Think about this: You need an entire play application running just to test a simple method! Injecting the dependencies greatly simplifies that. You can now instantiate the service directly in your test and pass in the configuration required for your test - scenario. You could even easily test the behavior if the key maxSize does not exist (the example uses specs2). class MyServiceTest extends Specification { "getMaxSize" should { "return the size" &gt;&gt; { val config = Configuration("maxSize" -&gt; 4) val service = new MyService(config) service.getMaxSize must_=== Some(4) } "return none if not configured" &gt;&gt; { val config = Configuration() val service = new MyService(config) service.getMaxSize must beNone } } } Conclusion In this first article, I have tried to shed some light into why the play framework uses dependency injection in the first place as well as a few examples on how to use dependency injection within an application and the benefits that come along with it. Stay tuned for the following parts where we’re going to look at more realistic examples, learn how to define bindings and much more! Links and Further Reading Google Guice 101, Bob Lee Jesse Wilson: https://www.youtube.com/watch?v=FFXhXZnmEQM Guice Docs: https://github.com/google/guice/wiki/Motivation Assisted Inject: https://github.com/google/guice/wiki/AssistedInject