<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Web on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/web/</link><description>Recent content in Web on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Sun, 22 Jun 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/web/index.xml" rel="self" type="application/rss+xml"/><item><title>Writing a Browser Extension</title><link>https://blog.iankulin.com/writing-a-browser-extension/</link><pubDate>Sun, 22 Jun 2025 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/writing-a-browser-extension/</guid><description>&lt;p&gt;Web pages are mostly just a collection of HTML, CSS, and JavaScript, so if we had some way of adding some of these into a web page, perhaps from our browser we could add new behaviour to a web page, right?&lt;/p&gt;
&lt;p&gt;Yes; users have long used tools like Greasemonkey (or similar userscript managers) to inject scripts into pages. Better still, modern browsers expose JavaScript APIs that let us interact directly with the browser itself. Enter: browser extensions.&lt;/p&gt;
&lt;p&gt;It turns out this is quite simple to do. And, it&amp;rsquo;s well documented (here&amp;rsquo;s the &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension"&gt;step-by-step for Firefox&lt;/a&gt;), but if you want, follow along with me while I solve a gripe with a browser extension for Firefox.&lt;/p&gt;
&lt;h3 id="extension-or-add-on"&gt;Extension or Add-On?&lt;/h3&gt;
&lt;p&gt;Before we get started, let&amp;rsquo;s clear up some naming ambiguity. Firefox has &lt;a href="https://addons.mozilla.org/en-US/firefox/"&gt;add-on&amp;rsquo;s&lt;/a&gt; - these are extensions, plus some goodies like themes. I&amp;rsquo;m going to continue to call them &lt;em&gt;extensions&lt;/em&gt; - it&amp;rsquo;s browser non-specific and you&amp;rsquo;re using the &lt;a href="https://extensionworkshop.com/documentation/develop/about-the-webextensions-api/"&gt;Web-Extension API&lt;/a&gt; which is mostly-browser agnostic way of doing these things.&lt;/p&gt;
&lt;h3 id="the-gripe"&gt;The Gripe&lt;/h3&gt;
&lt;p&gt;I often want to steal an image from the web, so I right click to open it in a new tab, and save it, only to find it&amp;rsquo;s a .&lt;code&gt;webp&lt;/code&gt; that can&amp;rsquo;t be used for whatever I wanted to steal it for. I often notice is that the original image is a .jpg but it&amp;rsquo;s been converted (often by a SASS image conversion product such as &lt;a href="https://docs.imgix.com/en-US/apis/rendering/overview"&gt;imgx&lt;/a&gt;, &lt;a href="https://imagekit.io/guides/image-optimization/#chapter-7---resizing-images-to-fit-the-layout"&gt;imagekit&lt;/a&gt;, or &lt;a href="https://sirv.com/help/articles/dynamic-imaging/"&gt;sirv&lt;/a&gt; which apply transformations via query parameters like ?w=600). They&amp;rsquo;ll have links like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;https://demo.sirv.com/look.jpg?w=600
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://demo.sirv.com/look.jpg?w=600"&gt;View embed&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;https://demo.sirv.com/look.jpg?w=200
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/look.jpg" alt=""&gt;&lt;/p&gt;
&lt;h3 id="the-solution"&gt;The solution&lt;/h3&gt;
&lt;p&gt;These are &amp;ldquo;query parameters&amp;rdquo;. It&amp;rsquo;s a smart, simple way of applying transformations to images. On the server end, the parameters are interpreted as what to do to each image. Of course, if you just remove the parameters, you get the original image.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;https://demo.sirv.com/look.jpg
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/look-c0a2d705-f421-4d99-9ba9-1b8694354e78.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;And if I&amp;rsquo;m stealing it, that&amp;rsquo;s what I want. So my plan is to write a browser extension that allows me right click on an image, and open it up in a new tab with all the query parameters removed.&lt;/p&gt;
&lt;h3 id="manifest"&gt;Manifest&lt;/h3&gt;
&lt;p&gt;You might have heard about the new &amp;ldquo;Manifest 3&amp;rdquo; that limits what extensions can do (and breaks ad blockers) in Chrome? Manifest v2 remains fully supported in Firefox, and offers simpler APIs for things like background scripts, which are perfect for small utility extensions like this. Here&amp;rsquo;s our manifest.js:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{
 &amp;#34;manifest_version&amp;#34;: 2,
 &amp;#34;name&amp;#34;: &amp;#34;Clean Image Opener&amp;#34;,
 &amp;#34;version&amp;#34;: &amp;#34;1.1&amp;#34;,
 &amp;#34;description&amp;#34;: &amp;#34;Open images in new tabs with query parameters stripped&amp;#34;,

 &amp;#34;permissions&amp;#34;: [&amp;#34;contextMenus&amp;#34;, &amp;#34;tabs&amp;#34;],
 &amp;#34;incognito&amp;#34;: &amp;#34;spanning&amp;#34;,

 &amp;#34;background&amp;#34;: {
 &amp;#34;scripts&amp;#34;: [&amp;#34;background.js&amp;#34;]
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The manifest is the meta-data portion of the extension. Most of these fields are pretty obvious, but let&amp;rsquo;s talk about a couple:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;**&amp;quot;permissions&amp;quot;: [&amp;quot;contextMenus&amp;quot;, &amp;quot;tabs&amp;quot;]**&lt;/code&gt; - here we are specifying what permissions our code is going to need. The browser uses this to block API calls that would need any other permissions. It&amp;rsquo;s part of &lt;em&gt;principle of least privilege&lt;/em&gt; system that makes clear to users what can be done, then builds those restrictions into the execution.&lt;/p&gt;
&lt;p&gt;In this case were asking for &lt;code&gt;&amp;quot;contextMenus&amp;quot;&lt;/code&gt; because we want to add something to the right click menu, and &lt;code&gt;&amp;quot;tabs&amp;quot;&lt;/code&gt; because we want to open one with a URL we pass it.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s also worth noting that these are the browser functions that are being restricted - our code still has access to the web page data (ie the URL of the image we&amp;rsquo;re right clicking on) since the DOM is all in the user scope anyway - for example you can open the developer tools to access that information. This is still a potential risk though - for example if a malicious extension wanted to collect that viewed image url and export it as telemetry. Browser extensions need defenses other than the manifest for those types of attacks.&lt;/p&gt;
&lt;p&gt;There are many different permissions - &amp;ldquo;history&amp;rdquo;, &amp;ldquo;clipboard&amp;rdquo;, &amp;ldquo;webrequest&amp;rdquo;. The important intent is that the user can reasonably be aware of what&amp;rsquo;s being asked and weigh it up against what the extension is doing for them. The first layer of security for add-ons lies with us - the developer. Browser extensions have wide access to user activity and should be kept as minimal as possible to avoid abuse or privacy leakage.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;**&amp;quot;incognito&amp;quot;: &amp;quot;spanning&amp;quot;**&lt;/code&gt; - Here we&amp;rsquo;re saying how we want the extension to work in incognito mode. In the Manifest 2 specification there are three options for incognito - &amp;ldquo;not_allowed&amp;rdquo;, &amp;ldquo;split&amp;rdquo; and &amp;ldquo;spanning&amp;rdquo;. &amp;ldquo;split&amp;rdquo; was intended to allow the extension in both modes, but not allow data to be shared between them - essentially to run separate copies of the extension in each mode. It&amp;rsquo;s not implemented in modern Firefox - I suspect because of earlier security problems, so we&amp;rsquo;re using &amp;ldquo;spanning&amp;rdquo; which should indicate to the user of the extension that data may be passed between the two modes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;**&amp;quot;background&amp;quot;: { &amp;quot;scripts&amp;quot;: [&amp;quot;background.js&amp;quot;] }**&lt;/code&gt; - we want our script to run in the background (to listen for right-clicks and act on them) so it goes in here.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have a look at the first part of background.js:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Create the context menu item when the extension starts
browser.contextMenus.create({
 id: &amp;#34;open-clean-image&amp;#34;,
 title: &amp;#34;Open image in new tab (no parameters)&amp;#34;,
 contexts: [&amp;#34;image&amp;#34;],
 documentUrlPatterns: [&amp;#34;&amp;lt;all_urls&amp;gt;&amp;#34;],
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;title:&lt;/code&gt; is just the text that appears in the context menu when the user write clicks on an image (this is the &lt;code&gt;contexts: [&amp;quot;image&amp;quot;]&lt;/code&gt; part). This can happen on &lt;code&gt;[&amp;quot;&amp;lt;all_urls&amp;gt;&amp;quot;]&lt;/code&gt; (we could have restricted it to a particular domain or sub-domain). The &lt;code&gt;id:&lt;/code&gt; is just how we are going to reference it in the click handler. Speaking of:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Handle context menu clicks
browser.contextMenus.onClicked.addListener((info, tab) =&amp;gt; {
 if (info.menuItemId === &amp;#34;open-clean-image&amp;#34;) {
 // Get the image URL and strip query parameters
 const originalUrl = info.srcUrl;
 const cleanUrl = stripQueryParameters(originalUrl);

 // Open the clean URL in a new tab
 browser.tabs
 .create({
 url: cleanUrl,
 active: true,
 })
 .catch((error) =&amp;gt; {
 console.error(&amp;#34;Failed to create tab:&amp;#34;, error);
 });
 }
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Not much explanation needed here due to good naming and generous comments ;-)&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s pretty much the whole extension. Of course there&amp;rsquo;s a &lt;code&gt;stripQueryParameters()&lt;/code&gt; somewhere. For now, just imagine it says:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;function stripQueryParameters(url) {
 return url.split(&amp;#39;?&amp;#39;)[0];
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="testing"&gt;Testing&lt;/h3&gt;
&lt;p&gt;To load our new Firefox extension to try it out, go to the url &lt;a href="debugging#/runtime/this-firefox"&gt;about:debugging#/runtime/this-firefox&lt;/a&gt; where we want to &amp;ldquo;Load a Temporary Add-on&amp;rdquo; - in the open dialog choose your manifest file.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-06-21-at-20.23.08.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Now the extension should be working for normal browser mode, to enable it in incognito, you&amp;rsquo;ll need to head into the Firefox menu &amp;ldquo;Add-ons and themes&amp;rdquo; (&lt;a href="addons"&gt;about:addons&lt;/a&gt;) then open the &amp;ldquo;Manage&amp;rdquo; menu for the extension and turn on &amp;ldquo;Run in Private Windows&amp;rdquo;.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-06-22-at-08.27.38.png" width="913" alt=""&gt;
&lt;h3 id="publishing"&gt;Publishing&lt;/h3&gt;
&lt;p&gt;Publishing an extension to the Firefox Add-ons store could be a whole post, but it&amp;rsquo;s the sort of thing you can follow your nose through, by starting at a&lt;a href="https://addons.mozilla.org/en-US/developers/"&gt;ddons.mozilla.org/en-US/developers&lt;/a&gt;. There is a semi-manual review process, so don&amp;rsquo;t expect it to be instant.&lt;/p&gt;
&lt;p&gt;The source for this project is &lt;a href="https://github.com/IanKulin/clean-image/tree/v1.1"&gt;available here&lt;/a&gt;, or install it into Firefox from &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/clean-image-opener/"&gt;here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>User Sessions &amp; Cookies in Node</title><link>https://blog.iankulin.com/user-sessions-cookies-in-node/</link><pubDate>Fri, 09 Feb 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/user-sessions-cookies-in-node/</guid><description>&lt;p&gt;When you are learning app development, you can create all sorts of apps that work for you, but for any serious app, it&amp;rsquo;s going to need to authenticate users and persist sessions across visits. So much so, that as a professional developer, you&amp;rsquo;ll probably build that out first - it becomes a sort of boiler plate you always drop in.&lt;/p&gt;
&lt;p&gt;In this post, focusing on the server side, using node, express, and particularly express-session, I&amp;rsquo;ll try and build up from nothing to a reasonable usable user login system explaining the increasing complexity and reasons for it. To follow along you&amp;rsquo;ll need basic familiarity with node and express.&lt;/p&gt;
&lt;h3 id="the-problem-were-addressing"&gt;The problem we&amp;rsquo;re addressing&lt;/h3&gt;
&lt;p&gt;For most web applications, we need to persist state &lt;em&gt;per user&lt;/em&gt;. For example, if you go to a drawing app and start a drawing, you want it to be there when you come back to the app. Additionally, you don&amp;rsquo;t want to come back to someone else&amp;rsquo;s half-drawn app, or, have them drawing over your picture. What we really want is something like this:&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/20240126-sessions-1.drawio-1.png" width="283" alt=""&gt;
&lt;p&gt;User 1 sees their picture of the star, and User 2 sees their picture of a heart.&lt;/p&gt;
&lt;p&gt;Since HTTP is &lt;a href="https://en.wikipedia.org/wiki/Stateless_protocol"&gt;stateless&lt;/a&gt; - a request to &lt;code&gt;/picture&lt;/code&gt; from one user is indistinguishable from another users request to &lt;code&gt;/picture&lt;/code&gt; - so we need to add something to allow the server to distinguish between the two. The something would be a bit of &lt;em&gt;state&lt;/em&gt; that the server passes back to the user, then the user sends it in with their actions so the server can identify them.&lt;/p&gt;
&lt;p&gt;There are a few of ways to do this. The first (which is not the subject of this post) is to store that in the URL. For example, when a user in the above app requests to create a picture, we could generate a &lt;a href="https://www.techtarget.com/searchwindowsserver/definition/GUID-global-unique-identifier"&gt;GUID&lt;/a&gt; for them, then redirect them to a URL based on that - perhaps /picture/cbe34f. Thereafter, all their requests could include that GUID. This can be a useful way of managing session state and has some affordances that the other method does not, but it&amp;rsquo;s not the most common.&lt;/p&gt;
&lt;p&gt;Another system that was extensively used in the early days of the web was to embed a hidden input with the GUID in the HTML returned to the user. When the user submitted the form later, the GUID was available&lt;/p&gt;
&lt;p&gt;The most common method is for the server to create a bit of state (our GUID), send it to the user and have the user&amp;rsquo;s browser store it, and return it with every request. You will know this bit of state as a &lt;em&gt;cookie&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="code-example"&gt;Code example&lt;/h3&gt;
&lt;p&gt;Enough theory, lets look at some code. If you google &amp;lsquo;simple node express session&amp;rsquo; you&amp;rsquo;ll find this, or something almost identical. Instead of the state we&amp;rsquo;re trying to persist being a picture of a heart or a star, we&amp;rsquo;re trying to remember how many times each user has visited our web site. Note these are separate counts for each user - a new visitor will start at zero, then count up each time they come back or refresh their page.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re using a couple of packages, so to run this example, you&amp;rsquo;ll first need to install them with &lt;code&gt;npm i express express-session&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;express&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;session&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;express-session&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;use&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secret&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;your-secret-key&amp;#39;&lt;/span&gt;, 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resave&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;saveUninitialized&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Access session data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`Views: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;listen&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;Server is running on port 3000&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we load this page from &lt;code&gt;http://localhost:3000&lt;/code&gt; it says &amp;ldquo;Views: 1&amp;rdquo;. Reloading the page it will say &amp;ldquo;Views: 2&amp;rdquo;. If I open a different browser and visit the server, we&amp;rsquo;ll be back to &amp;ldquo;Views: 1&amp;rdquo;. So what&amp;rsquo;s the magic that&amp;rsquo;s happening here?&lt;/p&gt;
&lt;p&gt;On the first request from a browser it hasn&amp;rsquo;t seen before, express-session is generating a session ID, and sending that back to the browser (along with &amp;lsquo;Views: 1&amp;rsquo;) and asking the browser to store it as a cookie. Thereafter, every request to this website &lt;code&gt;http://localhost:3000&lt;/code&gt; will include the cookie containing the session ID. The number of views is being stored on the server in memory. Express-session does the work of looking up the number of views for a particular session ID returned in a cookie so we have the luxury of just grabbing that from &lt;code&gt;rec.session.views&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Thanks to the magic of browser development tools, we can have a look in the request header from the browser and see the cookie with it&amp;rsquo;s session ID contents:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-01-26-at-9.42.43-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s also possible to go and find the cookie your browser has stored. Again, this is easiest in the developer tools - look under &amp;lsquo;Storage&amp;rsquo;:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-01-26-at-10.19.16-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-01-26-at-10.19.16-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Currently, we&amp;rsquo;re only storing the links between session_ids and the number of views in memory in the server, so if the server restarts, we&amp;rsquo;ll lose them, and everyone will go back to &amp;lsquo;Views: 1&amp;rsquo;.&lt;/p&gt;
&lt;h3 id="persisting-across-server-restarts"&gt;Persisting across server restarts&lt;/h3&gt;
&lt;p&gt;If we want our view counts to not be reset to zero each time the server restarts, we&amp;rsquo;ll need to save them somehow. express-session doesn&amp;rsquo;t let us access its internal array of sessions, but it does provide a mechanism for that access with the concept of &lt;em&gt;stores&lt;/em&gt;. Usually we&amp;rsquo;d keep the sessions in a database, but as files is a simpler solution for a blog post. There is a package called &lt;code&gt;session-file-store&lt;/code&gt; that will slot into &lt;code&gt;expression-session&lt;/code&gt; that will just use the file system to persist the session-view count key pairs for us. After installing it with &lt;code&gt;npm i session-file-store&lt;/code&gt;, we just declare a const for it, and pass it in when initialising the session middleware.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;express&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;session&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;express-session&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FileStore&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;session-file-store&amp;#34;&lt;/span&gt;)(&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;use&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secret&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;your-secret-key&amp;#34;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// This should be a secret, used to sign the session ID cookie
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resave&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;saveUninitialized&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;store&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FileStore&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;session_cookie&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Access session data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`Views: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;listen&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Server is running on port 3000&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now view counts for each browser are persisted even when the server is re-started. If we look inside one of the files we should see a view_count field:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-01-26-at-12.22.45-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-01-26-at-12.22.45-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The name of each file is the session id, but these don&amp;rsquo;t match the session id&amp;rsquo;s you see in the browser cookies - presumably because they&amp;rsquo;ve been encrypted by the secret we used when establishing the express-session.&lt;/p&gt;
&lt;p&gt;So this is a better experience - our users see their own view counts increasing on each refresh, and the view counts don&amp;rsquo;t get lost when you take the server down for maintenance. But what happens if the user wants to pick up their phone and check their view count. It&amp;rsquo;s a different browser without the cookie containing the session id, so they&amp;rsquo;ll get &amp;lsquo;View: 1&amp;rsquo; again.&lt;/p&gt;
&lt;p&gt;Or, what if your partner sits down at your laptop to check &lt;em&gt;their&lt;/em&gt; view count, expecting it to be &amp;lsquo;1&amp;rsquo; because they&amp;rsquo;ve never visited this page, and they see your view count instead?&lt;/p&gt;
&lt;h3 id="users"&gt;Users&lt;/h3&gt;
&lt;p&gt;Our current system is really based around browser instances, but we want it to be about people. So a better system, and one that you&amp;rsquo;ll be used to is the concept of &lt;em&gt;logging in&lt;/em&gt; as a &lt;em&gt;user&lt;/em&gt;. This can solve both the problems we described above - users will be able to log in from any browser and see only their own data.&lt;/p&gt;
&lt;p&gt;This is going to take things up a substantial step in complexity because now instead of two bits of data (the cookie with the session_id, and the session store linking the session_id and the view count) there is going to be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The cookie with the &lt;em&gt;session_id&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;The session store linking the &lt;em&gt;session_id&lt;/em&gt; and the &lt;em&gt;user&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;A new store we will have to manage that links the &lt;em&gt;user&lt;/em&gt; and the &lt;em&gt;view_count&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, we&amp;rsquo;ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some mechanism to create a user&lt;/li&gt;
&lt;li&gt;Some mechanism to log in&lt;/li&gt;
&lt;li&gt;And log out&lt;/li&gt;
&lt;li&gt;If there&amp;rsquo;s no user logged in, redirect to the log in page&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="log-out"&gt;Log out&lt;/h4&gt;
&lt;p&gt;Logging the user out is reasonably simple - we just delete the session information. In a real app we&amp;rsquo;d have a &amp;rsquo;log out&amp;rsquo; button of some sort, but for simplicity here, I&amp;rsquo;m just going to add a &amp;rsquo;log out&amp;rsquo; route.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/logout&amp;#34;, (req, res) =&amp;gt; { req.session.destroy((err) =&amp;gt; { if (err) { return console.log(err); } res.clearCookie(&amp;#34;session_cookie&amp;#34;); res.redirect(&amp;#34;/&amp;#34;); });});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;clearCookie()&lt;/code&gt; tells the browser to delete the cookie - which just saves us from console messages later when the browser sends it, but express-session can&amp;rsquo;t find the matching session.&lt;/p&gt;
&lt;p&gt;If you add this to the code from earlier and run it, view will count up as before, but when you visit the &lt;code&gt;/logout&lt;/code&gt; endpoint views will be set back to 1.&lt;/p&gt;
&lt;h4 id="users--view-counts"&gt;Users &amp;amp; view counts&lt;/h4&gt;
&lt;p&gt;In a real application we&amp;rsquo;d be keeping this in a database, but again for simplicity of this demo, I&amp;rsquo;m just going to keep an array of objects and persist them as a text file. The array will be user_views, and somewhere at the top of our code we&amp;rsquo;ll add:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;let user_views = [];// if the user_views.json file exists, read it and parse it to user_views arrayif (fs.existsSync(&amp;#34;./user_views.json&amp;#34;)) { user_views = JSON.parse(fs.readFileSync(&amp;#34;./user_views.json&amp;#34;));}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="creating-a-user"&gt;Creating a user&lt;/h4&gt;
&lt;p&gt;We&amp;rsquo;ll use a route parameter to create a new user (like &lt;code&gt;/create/jane&lt;/code&gt; or /&lt;code&gt;create/robert&lt;/code&gt; where the second part is accessible in &lt;code&gt;req.params&lt;/code&gt;). That user will be set as the session user, then we&amp;rsquo;ll add it to our array and save the array to disk.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/create/:user&amp;#34;, (req, res) =&amp;gt; { const user = req.params.user; const views = 0; req.session.user = user; user_views.push({ user, views }); fs.writeFile(&amp;#34;./user_views.json&amp;#34;, JSON.stringify(user_views), (err) =&amp;gt; { if (err) { res.send(err); return; } }); res.send(`User ${user} created &amp;amp; logged in`);});
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="logging-a-user-in"&gt;Logging a user in&lt;/h4&gt;
&lt;p&gt;If the user is in our array, we&amp;rsquo;ll set the session user to it, otherwise redirect to the create endpoint:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/login/:user&amp;#34;, (req, res) =&amp;gt; { // see if this user is in the user_views array if (user_views.find((u) =&amp;gt; u.user === req.params.user)) { req.session.user = req.params.user; res.send(`User ${req.params.user} logged in`); return; } else { res.redirect(`/create/${req.params.user}`); }});
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="showing-the-view-count"&gt;Showing the view count&lt;/h4&gt;
&lt;p&gt;The default route that shows our view count has a few jobs to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check we&amp;rsquo;re logged in, if not, tell the user to log in&lt;/li&gt;
&lt;li&gt;If we are logged in, fetch the view count for that user&lt;/li&gt;
&lt;li&gt;Increment the view count&lt;/li&gt;
&lt;li&gt;Show it to the user&lt;/li&gt;
&lt;li&gt;Re-save the user_views data since we&amp;rsquo;ve mutated it&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; { if (req.session.user) { const user_view = user_views.find((u) =&amp;gt; u.user === req.session.user); user_view.views++; res.send(`User &amp;#34;${req.session.user}&amp;#34; has ${user_view.views} views`); writeUserViewFile(); } else { res.send(&amp;#34;Please log in&amp;#34;); }});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So that&amp;rsquo;s our system for associating the view counts with a user done. Here&amp;rsquo;s the whole thing where we&amp;rsquo;re up to (I&amp;rsquo;ve done a little refactoring).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;express&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;session&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;express-session&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FileStore&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;session-file-store&amp;#34;&lt;/span&gt;)(&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;fs&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user_view_file&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;./user_views.json&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cookie_name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;session_cookie&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user_views&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// if the user_views.json file exists, read it and parse it to user_views array
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;existsSync&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user_view_file&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;user_views&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;parse&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;readFileSync&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user_view_file&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;writeUserViewFile&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;writeFile&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user_view_file&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stringify&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user_views&lt;/span&gt;), (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;findUser&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user_views&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;find&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;u&lt;/span&gt;) =&amp;gt; &lt;span style="color:#a6e22e"&gt;u&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;use&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secret&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;your-secret-keyz&amp;#34;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// This should be a secret, used to sign the session ID cookie
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resave&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;saveUninitialized&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;store&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FileStore&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cookie_name&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user_view&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;findUser&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;user_view&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`User &amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34; has &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;user_view&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; views`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;writeUserViewFile&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Please log in&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/logout&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;destroy&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;clearCookie&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cookie_name&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;redirect&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/create/:user&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;params&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;views&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;user_views&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;push&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;views&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;writeUserViewFile&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`User &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; created &amp;amp; logged in`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/login/:user&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// see if this user is in the user_views array
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;findUser&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;params&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;params&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`User &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;params&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; logged in`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;redirect&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`/create/&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;params&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;listen&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Server is running on port 3000&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="authentication"&gt;Authentication&lt;/h3&gt;
&lt;p&gt;It won&amp;rsquo;t have escaped your attention that our view counting app isn&amp;rsquo;t at all secure. At the moment, any one can log in as &amp;lsquo;jane&amp;rsquo; and see her view count. The common way to address this is to also require a password.&lt;/p&gt;
&lt;p&gt;I will eventually have to start serving some actual HTML, but for this next step I&amp;rsquo;ll stick to just using the routes. So our user interface will be this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logging in &lt;code&gt;/login/&amp;lt;user&amp;gt;/&amp;lt;password&amp;gt;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Check the user exists, and that this is the correct password&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Creating a new user &lt;code&gt;/create/&amp;lt;user&amp;gt;/&amp;lt;password&amp;gt;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Save the new user and password to the file.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We&amp;rsquo;ll do create first:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/create/:user/:password&amp;#34;, (req, res) =&amp;gt; { const user = req.params.user; const password = req.params.password; const views = 0; req.session.user = user; user_views.push({ user, password, views }); writeUserViewFile(); res.send(`User ${user} created &amp;amp; logged in`);});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We should probably first check there&amp;rsquo;s not an existing user with the same name to avoid having two users and the second user never being able to log in.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/create/:user/:password&amp;#34;, (req, res) =&amp;gt; { if (findUser(req.params.user)) { res.send(`User ${req.params.user} already exists`); return; } const user = req.params.user; const password = req.params.password; const views = 0; req.session.user = user; user_views.push({ user, password, views }); writeUserViewFile(); res.send(`User ${user} created &amp;amp; logged in`);});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and logging in:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/login/:user/:password&amp;#34;, (req, res) =&amp;gt; { const user = findUser(req.params.user); if (user &amp;amp;&amp;amp; user.password === req.params.password) { req.session.user = req.params.user; res.send(`User ${req.params.user} logged in`); return; } else { res.send(`Incorrect username or password`); }});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Although this is an improvement, clearly, it would a stretch to call this improved &lt;em&gt;security&lt;/em&gt;. Here&amp;rsquo;s a few things that are jumping out at me, and I&amp;rsquo;m sure there&amp;rsquo;s some being missed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We&amp;rsquo;re transmitting plaintext passwords over http&lt;/li&gt;
&lt;li&gt;We have plaintext passwords in a URL - any intermediate networking that&amp;rsquo;s recording access logs will the logging our passwords&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re storing plaintext passwords on our server in the &lt;code&gt;user_views.json&lt;/code&gt; file. So when our server is breached, our users&amp;rsquo; passwords will get sold on the dark web and they&amp;rsquo;ll be hacked if they&amp;rsquo;ve reused name/password combos.&lt;/li&gt;
&lt;li&gt;Passwords are limited to characters that reliably work in URLs&lt;/li&gt;
&lt;li&gt;Our passwords and user names may be open to injection attacks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So it seems like there is four jobs to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Encrypt the passwords&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t use URLs for passing this data&lt;/li&gt;
&lt;li&gt;Sanitise our inputs&lt;/li&gt;
&lt;li&gt;Enforce HTTPS&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="passwords"&gt;Passwords&lt;/h4&gt;
&lt;p&gt;Although I described this as &amp;rsquo;encrypting&amp;rsquo; that&amp;rsquo;s not exactly what we&amp;rsquo;re going to do. The current state of the art for this is to &lt;em&gt;hash&lt;/em&gt; and &lt;em&gt;salt&lt;/em&gt; passwords before storing them.&lt;/p&gt;
&lt;p&gt;hash - turn the password into some gooblygook such that that the hashed version always comes out the same if you put the same password into it. Preferably the hash is always the same length regardless of the length of the input password.&lt;/p&gt;
&lt;p&gt;salt - mix some random characters in to the the hashed password so that the combination of hashing and salting the password comes out different every time, even though you are putting in the same password as input to the process.&lt;/p&gt;
&lt;p&gt;How this is going to work is that once we get the users password, we&amp;rsquo;ll hash and salt it, then save the result of that in our array (and eventually file). If someone has access to that file, it&amp;rsquo;s practically impossible for them to reverse engineer the password - even if they have a known password somewhere else in the file to work from. Then when a user attempts to log in, we&amp;rsquo;ll take the password they gave us and hash it, and we&amp;rsquo;ll &amp;lsquo;un-salt&amp;rsquo; the password from the file and compare those two hashed versions of the passwords. If they are the same, then we know the passwords were also the same, and we can log this user in.&lt;/p&gt;
&lt;p&gt;This is a fun area of computational science, so it&amp;rsquo;s somewhat tempting to write our own hash and salting functions. This is widely regarded as a Bad Idea. As long as you don&amp;rsquo;t run into supply chain problems, a proper library, written by cryptographic experts, subject to public scrutiny, that gets updates when vulnerabilities are discovered is always going to be more secure. Almost universally, the answer for JS developers is &lt;a href="https://www.npmjs.com/package/bcrypt"&gt;bcrypt&lt;/a&gt;. We&amp;rsquo;ll install that with &lt;code&gt;npm i bcrypt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s our create and login endpoints using bcrypt with the changes highlighted.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/create/:user/:password&amp;#34;, (req, res) =&amp;gt; { if (findUser(req.params.user)) { res.send(`User ${req.params.user} already exists`); return; } const user = req.params.user; const hash = bcrypt.hashSync(req.params.password, saltRounds); const views = 0; req.session.user = user;  user_views.push({ user, hash, views });  writeUserViewFile();  res.send(`User ${user} created &amp;amp; logged in`); });});app.get(&amp;#34;/login/:user/:password&amp;#34;, (req, res) =&amp;gt; { const user = findUser(req.params.user); if (user &amp;amp;&amp;amp; bcrypt.compareSync(req.params.password, user.hash)) { req.session.user = req.params.user; res.send(`User ${req.params.user} logged in`); return; } else { res.send(`Incorrect username or password`); }});
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="forms"&gt;Forms&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-01-27-at-1.44.22-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-01-27-at-1.44.22-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To move the passwords out of the URLs, we&amp;rsquo;ll present a simple forms to the user for creating and logging in. The log in page is the basic user name / password form you&amp;rsquo;ve built before. This is served from the app.get(&amp;quot;/login&amp;quot;) route.&lt;/p&gt;
&lt;p&gt;And here&amp;rsquo;s the endpoint it posts to:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.post(&amp;#34;/login&amp;#34;, (req, res) =&amp;gt; { const user = findUser(req.body.username); if (user &amp;amp;&amp;amp; bcrypt.compareSync(req.body.password, user.hash)) { req.session.user = req.body.username; req.session.save((err) =&amp;gt; { if (err) { res.send(&amp;#39;Cookie saving error, &amp;lt;a href=&amp;#34;/login&amp;#34;&amp;gt;try again&amp;lt;/a&amp;gt;`&amp;#39;); } else { res.redirect(&amp;#34;/&amp;#34;); } }); } else { res.send(`Incorrect username or password, &amp;lt;a href=&amp;#34;/login&amp;#34;&amp;gt;try again&amp;lt;/a&amp;gt;`); }});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Normally express-session will deal with saving the session without us having to worry about, but I decided that a successful login should be followed to a re-direct to the view count page.&lt;/p&gt;
&lt;p&gt;Since express-session normally does its saving at the end of a http response, and if we&amp;rsquo;re redirecting, that response hasn&amp;rsquo;t happened. There&amp;rsquo;s a bit more about that &lt;a href="https://expressjs.com/en/resources/middleware/session.html"&gt;here&lt;/a&gt; (search for session save).&lt;/p&gt;
&lt;p&gt;Apart from that, the changes are really just grabbing the user name and passwords from the form post body instead of the URL.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;/register&lt;/code&gt; form is the same as the log in, but with a second password field and some client side scripting to check the two passwords are the same. Processing the new user is very similar to the previous &lt;code&gt;create&lt;/code&gt; route.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.post(&amp;#34;/register&amp;#34;, (req, res) =&amp;gt; { if (findUser(req.body.username)) { res.send(`User ${req.body.username} already exists`); return; } const user = req.body.username; const hash = bcrypt.hashSync(req.body.password, saltRounds); const views = 0; req.session.user = user; user_views.push({ user, hash, views }); writeUserViewFile(); req.session.save((err) =&amp;gt; { if (err) { res.send(&amp;#39;Cookie saving error, &amp;lt;a href=&amp;#34;/login&amp;#34;&amp;gt;try again&amp;lt;/a&amp;gt;`&amp;#39;); } else { res.redirect(&amp;#34;/&amp;#34;); } });});
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="sanitising-inputs"&gt;Sanitising inputs&lt;/h4&gt;
&lt;p&gt;Cautious developers do not trust any input from users. There are numerous libraries to deal with cleaning it up which I&amp;rsquo;d recommend you consider, but for our case here let&amp;rsquo;s think through what the possibilities are:&lt;/p&gt;
&lt;p&gt;password - the password is going to be hashed and salted before it is stored, and never used to build HTML. The only risk I can think of would be if a very long password might cause some sort of trouble - so we can just truncate it. Note we don&amp;rsquo;t even need to tell the user - if we truncate it when they register, and when they log in, they&amp;rsquo;ll still match.&lt;/p&gt;
&lt;p&gt;user name - is stored in a json file, and is output to the user. In the future that output is likely to be HTML. &lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s our &lt;code&gt;users_view.json&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[ { &amp;#34;user&amp;#34;: &amp;#34;user1&amp;#34;, &amp;#34;hash&amp;#34;: &amp;#34;$2b$10$8Zf9LZWH78mWnSKjxKQxXe9TlPoqe7L3SOABcPHIUQ5Pq3jIbVQVm&amp;#34;, &amp;#34;views&amp;#34;: 16 }, { &amp;#34;user&amp;#34;: &amp;#34;user2&amp;#34;, &amp;#34;hash&amp;#34;: &amp;#34;$2b$10$f/QAQ7we6Hh/hTx35LjfGeYtCY8aRG3ZqbJZqhEZRDXUqxkKCgPhq&amp;#34;, &amp;#34;views&amp;#34;: 14 }, { &amp;#34;user&amp;#34;: &amp;#34;user3&amp;#34;, &amp;#34;hash&amp;#34;: &amp;#34;$2b$10$KBZe6orYpjG3JeIGhnLDCuyYQXxfVZYBjovGf3XIdU8rr6kXlNrLC&amp;#34;, &amp;#34;views&amp;#34;: 1 }]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The obvious attack here would be injection. For example a hacker might register with the user name:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;user4&amp;quot;, &amp;quot;role&amp;quot;: &amp;quot;admin&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s a smart try at privileged escalation. Even if we had an &amp;lsquo;admin&amp;rsquo; role, it wouldn&amp;rsquo;t actually work though since any double quotes will be escaped by JSON.stringify(), but to be cautious, we can eliminate the possibility by just deleting any double quotes out.&lt;/p&gt;
&lt;p&gt;Another injection possibility would be with HTML. Perhaps we will display the logged in user later inside a &lt;div&gt;, something like:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;div&amp;gt;User: user1&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Our hacker might try registering this as their user name as:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;user1&amp;lt;/div&amp;gt;&amp;lt;script&amp;gt;alert('you've been hacked')&amp;lt;/script&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not great to allow anyone on the internet to run code in our visitors&amp;rsquo; browsers.&lt;/p&gt;
&lt;p&gt;You should really use a library designed by someone who knows what they are doing, but I just wanted to do enough here to prompt you to think about. For that demo purpose, I&amp;rsquo;m going to replace all &amp;quot; &amp;lt; and &amp;gt; with _&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// sanitise a string by replacing all &amp;#34; &amp;lt; and &amp;gt; with _ (underscore) // and truncating it at 20 charactersfunction sanitise(str) { return str.replace(/[&amp;#34;&amp;lt;&amp;gt;]/g, &amp;#34;_&amp;#34;).slice(0, 20);}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is all a bit doge. If we are going to have rules like this (and the other rules we should have about min lengths) we should be implementing them in the browser and on the backend, and there should be helpful prompting to the users to enable them to understand and correct their inputs. As I mentioned earlier, this is just to get you to think about it.&lt;/p&gt;
&lt;h4 id="https"&gt;HTTPS&lt;/h4&gt;
&lt;p&gt;In the list of four security improvements we wanted to make, the last one was to enforce HTTPS. There&amp;rsquo;s two places to do this. One is that when we initialise our session at the top of the app, we can tell it we want &amp;lsquo;secure&amp;rsquo; cookies. This setting means that cookies will not be sent over plain HTTP, but only the end-to-end encrypted HTTPS. Currently our cookies contain a user name:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;{ &amp;#34;cookie&amp;#34;: { &amp;#34;originalMaxAge&amp;#34;: null, &amp;#34;expires&amp;#34;: null, &amp;#34;httpOnly&amp;#34;: true, &amp;#34;path&amp;#34;: &amp;#34;/&amp;#34; }, &amp;#34;__lastAccess&amp;#34;: 1706315491081, &amp;#34;user&amp;#34;: &amp;#34;user1&amp;#34;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Even though a user name isn&amp;rsquo;t a lot of information, it could still be critical. If there was rumors about your company being acquired and a hacker leaked that j_bezos had been looking at your view counts, it could have implications. To turn on secure cookies:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.use( session({ secret: sessionSecret, resave: false, saveUninitialized: true, store: new FileStore(), name: cookie_name, cookie: { secure: true, httpOnly: true, }, }));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that your infrastructure needs to support HTTPS for this to be useful - if the cookies are not sent, this particular app is rendered useless.&lt;/p&gt;
&lt;p&gt;But we want to enforce HTTPS anyway, because a bigger problem is that if we don&amp;rsquo;t someone could intercept the login form data and collect plaintext usernames and passwords. &lt;/p&gt;
&lt;p&gt;I generally do this by running all web services behind NGINX as a proxy. Then the NGINX configs can be set to redirect all HTTP requests to HTTPS, and valid requests can be passed off to your app. Here&amp;rsquo;s the sort of thing you might have in a config.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;server { listen 80; server_name viewcount.example.com; return 301 https://$host$request_uri;}server { listen 443 ssl; server_name viewcount.example.com; # SSL certificate configuration ssl_certificate /path/to/your/certificate.crt; ssl_certificate_key /path/to/your/private-key.key; # Additional SSL configuration, such as preferred protocols and ciphers, can be added here location / { # Your application configuration goes here # Proxy pass to your Node.js or other application server # Example for proxying to a Node.js server running on localhost:3000 proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection &amp;#39;upgrade&amp;#39;; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; }}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are other security actions that can be taken at the NGINX level - things like rate limiting, blocking particular IP ranges, and access logging - all of which can be handy for protecting your endpoint from bad actors.&lt;/p&gt;
&lt;h4 id="other-security"&gt;Other security&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;d normally have the cookie secret in a .env file that was being .gitignored. A good hacker hobby is to scan public code repos for API keys and other secrets.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/helmetjs/helmet"&gt;Helmet.js&lt;/a&gt; is a sort of magic bullet for enforcing some security around request headers.&lt;/li&gt;
&lt;li&gt;Probably a stack of other things - ask your senior dev.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;rsquo;s our app with the changes we&amp;rsquo;ve discussed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;express&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;session&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;express-session&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FileStore&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;session-file-store&amp;#34;&lt;/span&gt;)(&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;fs&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bcrypt&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;bcrypt&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;saltRounds&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user_view_file&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;./user_views.json&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cookie_name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;session_cookie&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;use&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;express&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;urlencoded&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;extended&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt; }));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user_views&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// if the user_views.json file exists, read it and parse it to user_views array
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;existsSync&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user_view_file&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;user_views&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;parse&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;readFileSync&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user_view_file&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;writeUserViewFile&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;writeFile&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user_view_file&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stringify&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user_views&lt;/span&gt;), (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;findUser&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user_views&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;find&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;u&lt;/span&gt;) =&amp;gt; &lt;span style="color:#a6e22e"&gt;u&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// sanitise a string by replacing all &amp;#34; &amp;lt; and &amp;gt; with _ (underscore)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// and truncating it at 20 characters
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sanitise&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;str&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;str&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;replace&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;/[&amp;#34;&amp;lt;&amp;gt;]/g&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;_&amp;#34;&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;slice&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;use&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secret&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;your-secret-keyz&amp;#34;&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// This should be a secret, used to sign the session ID cookie
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resave&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;saveUninitialized&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;store&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FileStore&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cookie_name&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user_view&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;findUser&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;user_view&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`Error finding user, &amp;lt;a href=&amp;#34;/login&amp;#34;&amp;gt;try again&amp;lt;/a&amp;gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;user_view&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;`User &amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34; has &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;user_view&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;views&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; views, &amp;lt;a href=&amp;#34;/&amp;#34;&amp;gt;reload&amp;lt;/a&amp;gt; or &amp;lt;a href=&amp;#34;/logout&amp;#34;&amp;gt;logout&amp;lt;/a&amp;gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;writeUserViewFile&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;redirect&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/login&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/logout&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;destroy&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;clearCookie&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cookie_name&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;redirect&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;post&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/register&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sanitise&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;username&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;findUser&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`User &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;username&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; already exists`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hash&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bcrypt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;hashSync&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;password&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;saltRounds&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;views&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;user_views&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;push&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;hash&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;views&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;writeUserViewFile&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;save&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;Cookie saving error, &amp;lt;a href=&amp;#34;/login&amp;#34;&amp;gt;try again&amp;lt;/a&amp;gt;`&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;redirect&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;post&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/login&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;username&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sanitise&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;username&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;findUser&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;username&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bcrypt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;compareSync&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;password&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;hash&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;username&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;session&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;save&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;Cookie saving error, &amp;lt;a href=&amp;#34;/login&amp;#34;&amp;gt;try again&amp;lt;/a&amp;gt;`&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;redirect&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`Incorrect username or password, &amp;lt;a href=&amp;#34;/login&amp;#34;&amp;gt;try again&amp;lt;/a&amp;gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/login&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;sendFile&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;__dirname&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/login.html&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/register&amp;#34;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;sendFile&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;__dirname&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/register.html&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;listen&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Server is running on port 3000&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Nearly every web app we write is going to need a user auth and session management solution. In this very long post we&amp;rsquo;ve looked at a way to develop that from scratch in Express/Node. In the process our code base went from about 10 lines to 130. Now that it&amp;rsquo;s done however, the only extra code to ensure users are only accessing the routes they should will be a line at the entry point of each route.&lt;/p&gt;
&lt;p&gt;Since building out session management is such a common and onerous task, and one that can have serious consequences if not done correctly, you might be wondering if there&amp;rsquo;s libraries to do some of this, and other, lifting for us. There is, and I plan to look at some in the future.&lt;/p&gt;</description></item><item><title>Updating SSL Certificates</title><link>https://blog.iankulin.com/updating-ssl-certificates/</link><pubDate>Wed, 12 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/updating-ssl-certificates/</guid><description>&lt;p&gt;When I first installed my SSL certificates, &lt;a href="https://blog.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/"&gt;I mentioned&lt;/a&gt; it&amp;rsquo;s a process I need to automate before they came up for expiry, but here we are ten days out, and I haven&amp;rsquo;t done that yet, but I have been keeping an eye on it though the excellent display and notifications set up in &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;Uptime Kuma&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-5.36.01-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-5.36.01-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Updating the certificates is easy. When I went into the site at PorkBun (where I purchased the domain and who do the primary DNS for the site, the next certificates were sitting there to be downloaded. My existing certificates were due to expire on 30th July, and these had been generated on 3rd July.&lt;/p&gt;
&lt;p&gt;The bundle included the same files as last time. You might remember from last &lt;a href="https://blog.iankulin.com/installing-ssl-certificates-with-nginx-on-docker/"&gt;time&lt;/a&gt; that we need to join the &lt;code&gt;domain.cert.pem&lt;/code&gt; and &lt;code&gt;intermediate.cert.pem&lt;/code&gt; to make the &lt;code&gt;fullchain.pem&lt;/code&gt; file. I had just &lt;code&gt;cat&lt;/code&gt;&amp;rsquo;d them together and this had caused an issue as there&amp;rsquo;s no newline character at the end of the first file. I got smarter this time and googled up this &lt;a href="https://stackoverflow.com/questions/8183191/concatenating-files-and-insert-new-line-in-between-files/23549826#23549826"&gt;solution&lt;/a&gt; which did the trick by using echo to insert the newline:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-5.57.44-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-5.57.44-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Once that was done, I uploaded them to the nginx directory where I stored them last time. Nginx reloads the config on restart, although there&amp;rsquo;s probably a neater way as well, so I just restarted the container with Docker compose to pick up the new certificates. While I was doing that I got the ping from Uptime Kuma via &lt;a href="https://ntfy.sh/"&gt;ntfy&lt;/a&gt; to say it was down, then up. I had a look at the display, and it&amp;rsquo;s showing I&amp;rsquo;ve got another 84 days left on the cert.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-6.10.32-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-07-10-at-6.10.32-pm.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So, 84 days for me to get around to automating this.&lt;/p&gt;</description></item><item><title>Document Object Model - ToDo</title><link>https://blog.iankulin.com/document-object-model-todo/</link><pubDate>Mon, 02 Jan 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/document-object-model-todo/</guid><description>&lt;p&gt;I&amp;rsquo;m up to Section 12 of the Complete Web Developer course &amp;ldquo;DOM Manipulation&amp;rdquo; and it feels like we&amp;rsquo;re finally at the stage of pulling everything (HTML, CSS &amp;amp; JavaScript) together to make minimal web apps. Since the course is light on building challenges, I&amp;rsquo;ve set myself one - to make a simple todo list (the classic step up from &amp;ldquo;hello world&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;The Document Object Model is an entity representing the HTML with attached CSS for a page. The magic is that we can access this in JavaScript, and therefore change it, including hooking into events on it - such as a user pressing a button.&lt;/p&gt;
&lt;p&gt;For our ToDo app, we&amp;rsquo;ll need to allow the user to add an item by typing in some text and pressing a button to add it. There will be a list those items that grows as the user adds to it. As the user completes items, they click on them to signify they have been done - and the items are crossed out.&lt;/p&gt;
&lt;p&gt;So for HTML, we need a text input, a button, and a list:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;html&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lang&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;en&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;meta&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;charset&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;UTF-8&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;meta&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http-equiv&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;X-UA-Compatible&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;IE=edge&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;meta&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;content&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;&amp;gt;ToDo&amp;lt;/&lt;span style="color:#f92672"&gt;title&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;h1&lt;/span&gt;&amp;gt;Todo List&amp;lt;/&lt;span style="color:#f92672"&gt;h1&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;header&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;input&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;txtItem&amp;#34;&lt;/span&gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;button&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;button&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;btnAddItem&amp;#34;&lt;/span&gt;&amp;gt;Add Item&amp;lt;/&lt;span style="color:#f92672"&gt;button&lt;/span&gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;header&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;main&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;ul&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;ulItems&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;li&lt;/span&gt;&amp;gt;Sample Item&amp;lt;/&lt;span style="color:#f92672"&gt;li&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;ul&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;main&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;script&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;script.js&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style="color:#f92672"&gt;script&lt;/span&gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;html&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m not really seeing any consistent naming conventions for element ids in the code snippets I&amp;rsquo;ve seen around the web. Since everything is hard coded strings this seems like inviting disaster. I&amp;rsquo;ve adopted a VB/Delphi convention of abbreviating the type at the start of the name and camelcasing. So the button to add an item becomes &amp;ldquo;btnAddItem&amp;rdquo;. I wish (and perhaps there is) there was a VS Code extension to make the links between my HTML and JavaScript more obvious to reduce those potential errors.&lt;/p&gt;
&lt;h4 id="adding-an-item"&gt;Adding an item&lt;/h4&gt;
&lt;p&gt;The first problem, of catching the user input (pressing enter in the text, or clicking the button) was mostly solved for me by the CWD tutorial. It involves adding event listeners to both those elements.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;var txtItem = document.getElementById(&amp;#34;txtItem&amp;#34;)
var btnItem = document.getElementById(&amp;#34;btnAddItem&amp;#34;)

txtItem.addEventListener(&amp;#34;keydown&amp;#34;, respondToKeyPress)
btnItem.addEventListener(&amp;#34;click&amp;#34;, addNewItem)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;document&lt;/code&gt; in those first two lines is the Document Object Model (DOM) object. It has a method for finding HTML elements by the ids we assigned them in the HTML. These ids should be unique in the file, but the browsers don&amp;rsquo;t enforce this. If you&amp;rsquo;ve used duplicate ids, getElementById() will probably return the first one it found.&lt;/p&gt;
&lt;p&gt;Since we&amp;rsquo;re using hard coded strings, an anticipatable error would be misspelling the id either in the HTML or JavaScript. If that happens, getElementByID will return null. I would have thought we should be testing for this, or at least asserting it, but I don&amp;rsquo;t see either happening in the code I&amp;rsquo;ve seen. There is a console.assert(), but of course it&amp;rsquo;s not removed for production builds by a compiler.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-26-at-8.59.27-am.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[addEventListener()](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)&lt;/code&gt; does what it says on the box. You need to tell it which event (another string) and pass it the name of the handler function. It&amp;rsquo;s not specified here, but the handlers can have access to an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Event"&gt;Event&lt;/a&gt; object which turns out to be handy. Here&amp;rsquo;s the two handlers referenced in the code above:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;function respondToKeyPress(event) {
 if (event.code === &amp;#34;Enter&amp;#34;) {
 addNewItem() 
 }
}

function addNewItem() {
 if (txtItem.value.length &amp;gt; 0) {
 var li = document.createElement(&amp;#34;li&amp;#34;);
 li.appendChild(document.createTextNode(txtItem.value));
 ulItems.appendChild(li);
 li.onclick = listItemClick
 txtItem.value = &amp;#34;&amp;#34;
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first one just uses the Event object to check the user has pressed enter to add an item, and if so, calls the other handler to add the item. If the &amp;ldquo;Add Item&amp;rdquo; button is pressed, only the addNewItem() handler is called.&lt;/p&gt;
&lt;p&gt;After checking that there&amp;rsquo;s actually some text to add, it creates a new &lt;li&gt; item, appends the text to it, then appends the new item to our unordered list. The next line: &lt;code&gt;li.onclick = listItemClick&lt;/code&gt; adds an eventListener for &amp;ldquo;click&amp;rdquo; to this list new item. We&amp;rsquo;ll use this same handler later to detect clicks on any of the todo items in the list. This same event handler was also attached to any preexisting &lt;li&gt; elements at page load with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;var links = document.getElementsByTagName(&amp;#39;li&amp;#39;);
for (var i = 0; i &amp;lt; links.length; i++) {
 var link = links[i];
 link.onclick = listItemClick;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The very last thing that addNewItem() does is to clear the text input ready for the next item.&lt;/p&gt;
&lt;h4 id="crossing-things-off-the-list"&gt;Crossing things off the list&lt;/h4&gt;
&lt;p&gt;The most satisfying thing to do with a list is to cross things off it. The UX here will be that the user clicks on an item, and it gets crossed off but stay&amp;rsquo;s visible. We probably need to be able to reverse that process to. If the user accidentally crosses it off, they should be able to clock on it again to make it appear un-crossed.&lt;/p&gt;
&lt;p&gt;First up, we need to capture the click event. We&amp;rsquo;ve already seen how to do that in the code snippets above. When the page is first loaded, a loop adds the listItemClick functionto the .onClick event of every &lt;li&gt; item, and the same function is added to each new &lt;li&gt; item that&amp;rsquo;s created in addNewItem().&lt;/p&gt;
&lt;p&gt;Since this single handler (addNewItem())is handling the clicks for every list item, but we need to cross them off individually, we need some way of accessing the current clicked &lt;li&gt; inside the handler so we can decorate it accordingly.&lt;/p&gt;
&lt;p&gt;We saw above that the handlers can access the Event that is instigating them. Event contains a property &amp;ldquo;target&amp;rdquo; that is the element that triggered the event, so we can just use that. Here&amp;rsquo;s my first attempt at the listItemClick() handler.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;function listItemClick(event) {
 if (event.target.style.textDecoration === &amp;#34;line-through&amp;#34;) {
 event.target.style.textDecoration = &amp;#34;&amp;#34; 
 } else {
 event.target.style.textDecoration = &amp;#34;line-through&amp;#34;
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Setting &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration"&gt;.textDecoration&lt;/a&gt; to &amp;ldquo;line-through&amp;rdquo; has the same effect as setting it in CSS - a line is drawn through the text &lt;del&gt;like this&lt;/del&gt;. This code works fine - if we click on it, the todo item has the line drawn through it, then if we click it again, the line disappears.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a couple of potential problems though. The first is we are mixing up the behaviour (which is rightly a JavaScript concern) with how things look (which should be a CSS concern). For example, perhaps the UI team want completed items to be faded rather than crossed out.&lt;/p&gt;
&lt;p&gt;The second problem is related to that. We&amp;rsquo;re testing if &lt;code&gt;event.target.style.textDecoration === &amp;quot;line-through&amp;quot;&lt;/code&gt; when really what we are interested in is if this item is completed. There are a number of textDecorations, for example it&amp;rsquo;s possible the textDecoration could be &amp;ldquo;line-through underline red&amp;rdquo; because of a CSS entry.&lt;/p&gt;
&lt;p&gt;The solution to both these issues would be to use a class to indicate the completed/uncompleted status of each list item, and let the UI team set how these should be displayed in the CSS. If we use the class name &lt;code&gt;completed&lt;/code&gt;, we could use some simple CSS like this to make a fat red line:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.completed {
 text-decoration-line: line-through;
 text-decoration-color: red;
 text-decoration-thickness: 3px;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And we could convert out listItemClick code to change the class with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;function listItemClick(event) {
 if (event.target.classList.contains(&amp;#34;completed&amp;#34;)) {
 event.target.classList.remove(&amp;#34;completed&amp;#34;) 
 }
 else {
 event.target.classList.add(&amp;#34;completed&amp;#34;) 
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That works nicely. We&amp;rsquo;ve separated our concerns, and .completed items are clearly and satisfyingly crossed out.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2022-12-26-at-2.30.59-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2022-12-26-at-2.30.59-pm.png" width="543" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In fact, even this can be slightly improved on. There is a .toggle() method for turning a class off and on for an element, so we can eliminate some code by using that. As well as being simpler, we&amp;rsquo;ve removed the possibility of a classname typo the three times we use it. So:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;function listItemClick(event) {
 event.target.classList.toggle(&amp;#34;completed&amp;#34;) 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since our handler is down to a single line of code now, you might consider eliminating it by just passing the contents to the .onclick rather than a call to this function, however we do that in two places (the initial set up and again when each &lt;li&gt; is added) plus it would reduce our readability, so I&amp;rsquo;ll leave it like this.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/iankulin.github.io/tree/main/todo001"&gt;source&lt;/a&gt; or &lt;a href="https://iankulin.github.io/todo001/"&gt;try it out&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>