Encrypt Static Nuxt Site
Tags: jamstack, javascript, nuxt
Published: 2021-07-30 18:37:06

As a JAMStack developer, I often run into limitations in some of the projects that I am working on. I find that most of these limitations can be solved by using third-party tools, and actually work pretty well. However, these usually come with a price tag, which is totally fair and cool, but when I am not making money on a project, that seems overkill. This specific project I was working on needed to be password protected. That is pretty fair. A lot of projects need to have some content behind lock and key, but JAMStack sites may not be the best or easiest way to go about this. I set out to find a way that would make it possible to fully protect content on a page. 

The first thing that I looked into was using vanilla JavaScript. A lot of you are thinking right now that it wouldn't be the best option, and you would be right. I have used front-end "Authentication" by having the user put in a password and it would toggle the visibility of the information. That works in some cases where you want the content protected from the public. To be fair, only 99% of people visiting the site would even know how to dig into the JavaScript code and figure out what is going on, and even less would care to spend a few hours figuring it out. For this specific project, I wanted a little more protection than that offered. 

My next stop was looking at [Firebase Authentication](https://firebase.google.com/docs/auth). This is pretty sweet, and is designed for JAMStack sites like what I was building. However, the more I thought about it, the authentication wouldn't protect my static content. Firebase Authentication will only protect the content stored in Firebase, such as Firestore or RealtimeDB. So my static HTML code could still be accessed by someone who really wanted to get the data.

## Staticrypt

I kept searching and stumbled accross [Staticrypt](https://robinmoisson.github.io/staticrypt/). This looked pretty promising. This is based on the [crypto.js library](https://github.com/brix/crypto-js) and will take a full HTML page and encrypt it. The user will then need to enter the password for the script to un-encrypt it. It sounded pretty neat. I plugged in some HTML code and a password to the site to test it out, and sure enough, it spat out an encrypted page. It looked promising enough to look further into.

## Website Build Tools

The next step was to look at what type of website building tools I wanted to use. I was testing Staticrypt with the CLI and thought for sure that I was going to need to use a single page application, because it would only encrypt one file. But then I found this little snippet and that changed a lot:

``` bash
find . -type f -name "*.html" -exec staticrypt {} mypassword \;
```

That script when ran in the terminal will find each HTML file in your directory and encrypt it. With that, my first thought was to use [Eleventy](https://www.11ty.dev/). I really am a big fan of Eleventy. I had a grandiose idea one day to make something that wasn't nearly as cool or easy to use as Eleventy. I started playing with some scripts and a test Eleventy page, and found an issue after a few hours of work. When you encrypt each HTML page, you need to pass in a password for each page you navigate to. So I tried a few workarounds like storing the password in the users localstorage, and on each page, populating the input and submitting the form. It was a little glitchy, and it certainly didn't have a good user experience with the quick flashes of a password page. So I tried something new.

## Nuxt to the rescue!

I am a really big fan of [Vue](https://vuejs.org/), but I really like developing with it using [Nuxt](https://nuxtjs.org/). I especially like the auto-routing based on your directory structure, the auto-importing components, and a lot of other awesome [modules](https://modules.nuxtjs.org/) that are easy to incorporate. This would have been a lot easier project to set up if I just created a Vue project, but I like developing with Nuxt so much that I went through some extra hassle with the setup that I want to share with others. Keep in mind, this is not the cleanest or best solution, and there is room for improvements. So if you find a better way of implementing it, let me know in the comments.

## Starting a Nuxt Site

**This assumes that you have some prior knowledge with Nuxt.**

1. Create a standard Nuxt site: `npx create-nuxt-app nuxt-encrypt` (When asked for the deployment target, use `static`).
2. Create multiple Nuxt pages (ex. about.vue, cool.vue) and add some simple navigation.
3. Test out the site with `npm run dev`.

So that is the quickest Nuxt site I know how to setup for this. Running the project in development mode will hot reload the project.

## Adding Staticrypt

**Warning: I have only tested this on a Mac and Linux using the standard terminals.** If you are on Windows, you may need some modifications. Let me know in the comments if there are better cross-operating system alternatives.

1. Install Staticrypt: `npm i staticrypt`.
2. Add a new file to the root of the project called `remove-unencrypted.js`.
3. Adjust the `package.json` scripts section.

**`package.json` Snippet**

```json
{  
  "scripts": {
    "dev": "nuxt",
    "start": "nuxt start",
    "generate": "nuxt generate && npm run encrypt && npm run remove_unencrypted",
    "encrypt": "cd dist && find . -type f -name '*.html' -exec npx staticrypt {} custompassword \\;",
    "remove_unencrypted": "node remove-unencrypted"
  },
}
```

**`remove-unancrypted.js` File**

```js
const path = require("path");
const fs = require("fs");

const listDir = (dir, fileList = []) => {
  let files = fs.readdirSync(dir);

  files.forEach((file) => {
    if (fs.statSync(path.join(dir, file)).isDirectory()) {
      fileList = listDir(path.join(dir, file), fileList);
    } else {
      if (/\.html$/.test(file)) {
        let src = path.join(dir, file);
        fileList.push(`./${src}`);
      }
    }
  });

  return fileList;
};

console.log("\nReplacing Encrypting Files...\n")

filesArray = listDir("./dist");
module.exports = filesArray.forEach((file) => {
  if (file.includes("_encrypted")) {
    console.log("Replaced ", file);
    fs.rename(file, file.replace("_encrypted", ""), (err) => {
      if (err) {
        console.error(err);
      }
    });
  }
});
```
## Generating Static HTML Files

This works pretty good straight out of the box. When we run `npm run generate` it will do a little more than just generate static files. The first command, `nuxt generate` does the standard generate command, and turns everything into a static HTML page and places them in the `dist` directory. 

The second command, `npm run encrypt` will encrypt each one of those HTML pages in the `dist` directory. Note that in the `encrypt` script, this example sets the password to `coolpassword`. You can change this to be whatever you want. It should be a string with no spaces. 

The third script is to remove the unencrypted files. If you miss this step, what happens is it will encrypt the contents of one file, then save it as a copy. For example, index.html -> index_encrypted.html. This is what the `remove-unencrypted.js` file does. It will recursively go through your project, find any HTML file with `_encrypted` in it and rename it, which in turn replaces the original file. I don't have much experience writing Node files, so there might be a better script out there for this, but my script gets the job done.

## Previewing your Files

Running `npm run generate` should leave a few cues in the console on what is going on. Hopefully you don't get any errors. Once those three commands are finished, then you can run `npm run start`. This starts the files in your `dist` directory in it's own server. You should see when you go to your localhost page that you are now prompted with a password. Enter the password you set in the `encrypt` script (in this case, `coolpassword`), and you should have access to your content! Just for fun, look at the source code for the file loaded in. There will be no trace of your original code. It will just be the password page, and a bunch of gobbledegook. That gobbledegook is your content.

Remember how I said that I tried this with 11ty first? My issue with 11ty was that on every page, we needed to put in a password? Well, with Nuxt, we don't need to worry about that. Nuxt does create an individual page for each file, but once you open the file, the SPA side of Nuxt takes over. That means that navigating to new pages doesn't do a new HTTP request, but loads in the content via JavaScript. 

There is a caveat to this. Be sure that when you use page navigation, you use `<nuxt-link>` rather than an `<a>` tag. If you use the `<a>` tag, then you might as well just use something like 11ty, and figure out a workaround for the password prompt every time.

## Limitations

There is always going to be some issues with everything. For example, since this page is loaded in via JavaScript, you will notice that the code for each page is technically in a JavaScript file, and that DOES NOT get encrypted (as well as images or other files). The JavaScript files are obscurely named, and I do not see them in the sources in a browser console, so I figure it is pretty safe. Staticrypt also mentions that it isn't flawless, so they don't recommend putting very sensitive things like banking on there. So take a deep look at what it is that you are protecting. Maybe you just need to pay a company for a better solution. For me, and my project, this works just fine.

## Bonus: Customizing the Password Page

You can also modify the HTML password protected template page to your liking. Staticrypt's documentation shows this:

```
  -f, --file-template  Path to custom HTML template with password prompt.
                          [string] [default: "[...]/cli/password_template.html"]
```

If you go into your `node-modules` and find that `password_template.html` file, you can copy that to the root of your project and modify it. Then change your `encrypt` script to: 

```
"encrypt": "cd dist && find . -type f -name '*.html' -exec npx staticrypt {} coolpassword -f=../password_template.html \\;",
```

Running `npm run encrypt` will then use your password template from the root of your project instead.

## Conclusion

So, if you are looking for a simple way to password-protect your static website, consider using Staticrypt with Nuxt. It is easy to set up for basic usage, and produces a pretty secure website.