Jason SteinshouerBlog on web development and technology2023-11-09T00:00:00Zhttps://jasonsteinshouer.comJason SteinshouerCreating Java Thread Dumps for Coldfusion with Jstack2023-11-09T00:00:00Zhttps://jasonsteinshouer.com/coldfusion-thread-dumps-with-jstack.html<p>In Fusion Reactor, you can see a stack trace of a request to a Coldfusion application. This is a great tool to see what is going on when trying to debug a performance problem. I highly recommend having Fusion Reactor in your toolbelt.</p>
<p>Another option to see what is going on at a specific point in time is to generate a JVM Thread Dump. You can generate a thread dump by using the <code>jstack</code> tool that comes with the Java Development Kit (JDK). Here is the output of <code>jstack -h</code> on Windows.</p>
<pre><code class="language-bash">$./bin/jstack -h
Usage:
jstack [-l][-e] <pid>
(to connect to running process)
Options:
-l long listing. Prints additional information about locks
-e extended listing. Prints additional information about threads
-? -h --help -help to print this help message
</code></pre>
<p>According to the <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/jstack.html">online documentation</a> , the tool prints a Java stack trace the Java threads in the specified process ID.</p>
<p>To find the Windows process ID (PID), you can use the <code>tasklist</code> command or run the Task Manager program to find it.</p>
<pre><code class="language-bash">D:\ColdFusion2021\jre>tasklist | findstr /c:"coldfusion.exe"
coldfusion.exe 13852 Services 0 7,834,132 K
</code></pre>
<p>We then use that PID with <code>jstack</code> to get our thread dump. You may need to run the Command Prompt as an Administrator to avoid access denied errors. By default it prints the output to the console but here I am redirecting the output to a file to make it easier to read and search later.</p>
<pre><code class="language-bash">D:\ColdFusion2021\jre>.\bin\jstack 13852 > java_dump.txt
</code></pre>
<p>The topic of what to look for in the thread dump could probably be a whole other blog <a href="http://article.it/">article.It</a> will likely depend on the issue you are trying to debug. If you are debugging in your development environment there may only be a single request. If the dump is from production there will probably be multiple threads handling requests at the time the dump was taken. In this case, you may be looking for a specific request or just a thread that is in a running, waiting, or blocked status.</p>
<p>I am sure there are many other ways to do this but this is one that worked for me so wanted to document it here. There is probably a lot of <a href="https://duckduckgo.com/?q=java+thread+dump">content</a> out there on this subject as well as how to <a href="https://duckduckgo.com/?q=analyze+java+thread+dump">analyze the thread dump</a>. <a href="https://www.carehart.org/">Charlie Arehart’s blog</a> is also great resource for this kind of information related to Coldfusion.</p>
Using the CFML Jupyter Kernel with VS Code2023-07-13T00:00:00Zhttps://jasonsteinshouer.com/using-cfml-jupyter-kernel-with-vscode.html<p>My notes for how to setup and use the <a href="https://github.com/jsteinshouer/cfml-jupyter-kernel">CFML Jupyter Kernel</a> locally with VS Code.</p>
<h2 id="overview" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/using-cfml-jupyter-kernel-with-vscode.html#overview">Overview</a></h2>
<p><strong>Pre-requisites</strong></p>
<ol>
<li>Download and install <a href="https://www.python.org/downloads/">Python</a>.</li>
<li>Install VS Code + <a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python">Python Extension</a> + <a href="https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter">Jupyter Extension</a></li>
<li>Install <a href="https://www.ortussolutions.com/products/commandbox#download">CommandBox</a> v5.9.0+ and add it to the system path.</li>
</ol>
<p><strong>Install the CFML Julyter Kernels</strong></p>
<pre><code class="language-bash">python -m pip install cfml-kernel
python -m cfml_kernel.cfscript.install
python -m cfml_kernel.cfml.install
</code></pre>
<p>**Note: Depending on your platform you may need to use the <code>--sys-prefix</code> flag on the last two commands to get VS code to recognize the Jupyter Kernels.</p>
<h2 id="pre-requisites" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/using-cfml-jupyter-kernel-with-vscode.html#pre-requisites">Pre-requisites</a></h2>
<h3 id="python" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/using-cfml-jupyter-kernel-with-vscode.html#python">Python</a></h3>
<p>The CFML Jupyter Kernel is a Python module so Python is needed to run it. <a href="https://www.python.org/downloads/">Download</a> and install the version for your platform. You could also use a package manager to install it as well. i.e. On Windows you could use the <a href="https://chocolatey.org/">Chocolatey package manager</a>.</p>
<pre><code class="language-bash">choco install python
</code></pre>
<h3 id="vs-code" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/using-cfml-jupyter-kernel-with-vscode.html#vs-code">VS Code</a></h3>
<p>I will assume if you are reading this you already have VS Code installed. You will also need to install the <a href="https://marketplace.visualstudio.com/items?itemName=ms-python.python">Python Extension</a> and <a href="https://marketplace.visualstudio.com/items?itemName=ms-toolsai.jupyter">Jupyter Extension</a> VS Code extensions.</p>
<h3 id="commandbox" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/using-cfml-jupyter-kernel-with-vscode.html#commandbox">CommandBox</a></h3>
<p>The cfml-jupyter kernel uses the <a href="https://commandbox.ortusbooks.com/usage/repl">CommandBox REPL</a> so you will need to install version 5.9.0 or greater.<br />
you can download it <a href="https://www.ortussolutions.com/products/commandbox#download">here</a>. Follow the <a href="https://commandbox.ortusbooks.com/setup/installation">installation instructions</a> but make sure it is accesible in the system path.</p>
<h2 id="kernel-installation" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/using-cfml-jupyter-kernel-with-vscode.html#kernel-installation">Kernel Installation</a></h2>
<p>The CFML Jupyter Kernel package can be installed from PyPi using <code>pip</code>.</p>
<pre><code class="language-bash">python -m pip install cfml-kernel
</code></pre>
<p>Once the module is installed you need to use these commands to install the CFScript and Tag kernels so that Jupyter will recognize them.</p>
<pre><code class="language-bash">python -m cfml_kernel.cfscript.install
python -m cfml_kernel.cfml.install
</code></pre>
<p>In Windows I had to install them with the <code>--sys-prefix</code> flag to get VS Code to recognize them.</p>
<pre><code class="language-bash">python -m cfml_kernel.cfscript.install --sys-prefix
python -m cfml_kernel.cfml.install --sys-prefix
</code></pre>
<p>Then after restarting VS Code they would be available as a kernel option.</p>
<h2 id="usage" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/using-cfml-jupyter-kernel-with-vscode.html#usage">Usage</a></h2>
<p>If all the above goes well, you should be able to create a new Jupyter notebook in VS Code by opening the Command Pallete (Ctrl+Shift+P) then typing <code>New Jupyter Notebook</code>.</p>
<p>Then click Select Kernel.</p>
<p><img src="https://jasonsteinshouer.s3.us-west-2.amazonaws.com/images/jupyter/select-kernel-1.png" alt="Select Kernel1" /></p>
<p>Select Jupyter Kernel…</p>
<p><img src="https://jasonsteinshouer.s3.us-west-2.amazonaws.com/images/jupyter/select-kernel-2.png" alt="Select Kernel2" /></p>
<p>Then choose the kernel that you would like to use.</p>
<p><img src="https://jasonsteinshouer.s3.us-west-2.amazonaws.com/images/jupyter/select-kernel-3.png" alt="Select Kernel3" /></p>
<p>You can then Use the +Code button to add new Code Cells. Type the code you want to execute then click the triangle shaped icon to execute the code. Variables should persist between code cell execution.</p>
<p><img src="https://jasonsteinshouer.s3.us-west-2.amazonaws.com/images/jupyter/notebook-example.png" alt="CFML Kernel Usage Example" /></p>
Introducing the CFML Jupyter Kernel2023-04-10T00:00:00Zhttps://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html<h2 id="tldr" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#tldr">TL;DR</a></h2>
<p>I created a new CFML Jupyter Kernel powered by CommandBox. This project is open source, and hosted on GitHub at <a href="https://github.com/jsteinshouer/cfml-jupyter-kernel">https://github.com/jsteinshouer/cfml-jupyter-kernel</a>.</p>
<h2 id="table-of-contents" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#table-of-contents">Table of Contents</a></h2>
<ul>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#introduction">Introduction</a></li>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#what-is-a-jupyter-notebook">What is a Jupyter Notebook?</a>
<ul>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#documents">Documents</a></li>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#jupyter-application">Jupyter Application</a></li>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#kernels">Kernels</a></li>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#use-cases">Use Cases</a></li>
</ul>
</li>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#cfml-kernel">CFML Kernel</a>
<ul>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#magic-commands">Magic Commands</a></li>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#cfml-examples">CFML Examples</a></li>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#usage">Usage</a></li>
<li><a href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#next-steps">Next steps</a></li>
</ul>
</li>
</ul>
<h2 id="introduction" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#introduction">Introduction</a></h2>
<p>I recently went through an introductory course for learning Python on linked in learning. I typically like to do some kind of project when learning a new language or technology.</p>
<p>Jupyter notebooks is something that has interested me so I started playing around to see if I could add a Jupyter Kernel for CFML. I decided to try and implement one that interacts with the CommandBox REPL. While researching I came across some examples doing this with other languages so thought I could possibly use those as a starting point.</p>
<h2 id="what-is-a-jupyter-notebook" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#what-is-a-jupyter-notebook">What is a Jupyter Notebook?</a></h2>
<h3 id="documents" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#documents">Documents</a></h3>
<p>A Jupyter notebook is shareable document that typically contains three elements or “cells” as they are called.</p>
<ol>
<li><strong>Text -</strong> This is formatted text, typically written in Markdown but rendered as HTML.</li>
<li><strong>Code</strong> - This is an input cell that contains code that can be executed in the document.</li>
<li><strong>Output</strong> - These cells will contain the output of the executed code.</li>
</ol>
<p>The documents use specific data schema stored in JSON format. The the file extension they use is <code>ipynb.</code></p>
<h3 id="jupyter-application" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#jupyter-application">Jupyter Application</a></h3>
<p>There are various applications that can be used to interact with and edit Jupyter notebooks.</p>
<p><strong>VS Code</strong></p>
<p>VS Code has a Jupyter extension that allows you to edit and view Jupyter notebooks within VS Code. The nice thing about it is the syntax highlighting and editing experience matches what you get with VS Code.</p>
<p>This means you can also use Github Codespaces as well.</p>
<p><strong>Jupyter Lab</strong></p>
<p>This is a web application that has a nice interface for creating, editing, and viewing Jupyter notebooks.</p>
<p><strong><strong><strong><strong><strong><strong>Others</strong></strong></strong></strong></strong></strong></p>
<p>See <a href="https://jupyter.org/">https://jupyter.org/</a> for information on some of the other options available.</p>
<h3 id="kernels" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#kernels">Kernels</a></h3>
<p>Jupyter kernels add support for different languages. The Jupyter application sends the executable code to the kernel. The Kernel is responsible for executing it and returning the result. They have a <a href="https://github.com/jupyter/jupyter/wiki/Jupyter-kernels">list of kernels here.</a></p>
<h3 id="use-cases" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#use-cases">Use Cases</a></h3>
<p>Jupyter notebooks are used pretty heavily in the data science community. My first exposure to them was when someone did a demo for me using the Pandas Python library to do some data exploration and transformation within a Jupyter notebook interface. More recently I started using Azure Data Studio which has support for running SQL code within a Jupyter notebook.</p>
<p>Besides data science, here are some use cases that came to mind:</p>
<ul>
<li>Learning new languages without needing to setup a complete development environment</li>
<li>Documentation with executable examples</li>
<li>Interactive books to teach programing concepts</li>
<li>Training materials</li>
<li>Research and debugging</li>
</ul>
<p>One example I found was <a href="https://github.com/IHaskell/learn-you-a-haskell-notebook">this project</a> , which is an interactive book for learning Haskell.</p>
<h2 id="cfml-kernel" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#cfml-kernel">CFML Kernel</a></h2>
<p>This project is a Jupyter kernel that uses Python to interact with the CommandBox REPL for executing CFML code. There is a kernel for both cfscript and cfml tags. The code for the project is open source and hosted on Github.</p>
<p><a href="https://github.com/jsteinshouer/cfml-jupyter-kernel">https://github.com/jsteinshouer/cfml-jupyter-kernel</a></p>
<h3 id="magic-commands" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#magic-commands">Magic Commands</a></h3>
<p>The IPython kernel has a concept called <a href="https://ipython.readthedocs.io/en/stable/interactive/magics.html">magic commands</a> which are special commands that can be called in the notebook cell. I have implemented a couple commands for the CFML kernel.</p>
<p><strong>%install</strong></p>
<p>This command can be used to call the <a href="https://commandbox.ortusbooks.com/package-management/installing-packages">CommandBox install command</a> to install packages from ForgeBox.</p>
<p><img src="https://static.jasonsteinshouer.com/images/jupyter/Code_HXXxPbBwNg.png" alt="%install qb" /></p>
<p><strong>%loadjar</strong></p>
<p>This is a wrapper for the CommandBox <a href="https://commandbox.ortusbooks.com/developing-for-commandbox/commands/loading-ad-hoc-jars#classload">classLoad</a> method that will allow you to load custom jar and class files.</p>
<p><img src="https://static.jasonsteinshouer.com/images/jupyter/Code_6iJYpo4Tvy.png" alt="%loadjar" /></p>
<h3 id="cfml-examples" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#cfml-examples">CFML Examples</a></h3>
<p><strong>ArrowFunctions.ipynb</strong></p>
<script src="https://gist.github.com/jsteinshouer/61e426c715fd14fe161bef89be32fc44.js"></script>
<p>You can execute this example on <a href="http://mybinder.org/">mybinder.org</a> using this link:</p>
<p><a href="https://mybinder.org/v2/gh/jsteinshouer/cfml-jupyter-kernel/main?urlpath=/tree/cfml_examples/ArrowFunctions.ipynb"><img src="https://mybinder.org/badge_logo.svg" alt="Run this example on mybinder.org" /></a></p>
<p><strong>cfquery.ipynb</strong></p>
<p>A simple example of using cfquery:</p>
<script src="https://gist.github.com/jsteinshouer/29f86ad2b31a1285c0b36f1e5e7ee6c1.js"></script>
<p>You can execute this example on <a href="http://mybinder.org/">mybinder.org</a> using this link:</p>
<p><a href="https://mybinder.org/v2/gh/jsteinshouer/cfml-jupyter-kernel/main?urlpath=/tree/cfml_examples/cfquery.ipynb"><img src="https://mybinder.org/badge_logo.svg" alt="Run this example on mybinder.org" /></a></p>
<h3 id="usage" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#usage">Usage</a></h3>
<p><strong>Local</strong></p>
<p>It can be installed locally by cloning the repo. CommandBox and Python are required.</p>
<pre><code class="language-bash">git clone https://github.com/jsteinshouer/cfml-jupyter-kernel.git
pip install ./cfml-jupyter-kernel
python -m cfml_kernel.cfscript.install
python -m cfml_kernel.cfml.install
</code></pre>
<p><strong><a href="http://mybinder.org/">MyBinder.org</a></strong></p>
<p>This URL can be used to run the CFML Jupyter kernel and create and edit notebooks. The <code>urlpath</code> parameter can point to any notebook accessible on the internet or you can also upload one.</p>
<p><a href="https://mybinder.org/v2/gh/jsteinshouer/cfml-jupyter-kernel/main?urlpath=/tree">https://mybinder.org/v2/gh/jsteinshouer/cfml-jupyter-kernel/main?urlpath=/tree</a></p>
<p><strong>Docker</strong></p>
<p>I have created a pre-built image with Jupyter, CommandBox, and the CFML kernel installed. Here is an example of how to run it.</p>
<pre><code class="language-bash">docker run -v ${PWD}:/home/jovyan/work -p 8888:8888 -e JUPYTER_TOKEN=123 ghcr.io/jsteinshouer/cfml-jupyter:latest
</code></pre>
<p><strong>Github Codespaces / Dev Container</strong></p>
<p>Here is an example <code>devcontainer.json</code> file that can be used to run the CFML Kernel with Github Codespaces or the VS Code Dev Containers extension.</p>
<pre><code class="language-json">{
"workspaceFolder": "/workspace",
"image": "ghcr.io/jsteinshouer/cfml-jupyter:latest"
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
},
"extensions": [
"formulahendry.auto-close-tag",
"kamasamak.vscode-cfml",
"ms-toolsai.jupyter",
"ms-python.python"
],
"forwardPorts": [
8888
],
"remoteUser": "jovyan"
}
</code></pre>
<h3 id="next-steps" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/introducing-cfml-jupyter-kernel.html#next-steps">Next steps</a></h3>
<p>One thing I am looking to do next is add support for rendering other types of output some as html. There may be some other magic commands that could be useful as well. Please create an issue if you have any ideas.</p>
How To Create a Dynamic Alias for Vue in Vite2023-03-16T00:00:00Zhttps://jasonsteinshouer.com/dynamic-alias-in-vite.html<p>I have a case where I am using Vite to bundle my Javascript for using in multiple pages but is not a single-page application. It was working when running in development mode but when I would build for production I was getting an error because it was using the runtime only distribution of Vue.</p>
<p>Here is an <a href="https://v2.vuejs.org/v2/guide/installation.html#Explanation-of-Different-Builds">explanation of the different Vue distributions</a>. As I understand it, typically all your Vue templates would get compiled at build time so the runtime only version excludes the template parser so that it is slimmer and more efficient. In this case I have Vue templates that are not compiled at build time.</p>
<p>I worked around the issue but setting up a <a href="https://vitejs.dev/config/shared-options.html#resolve-alias">Vite/Rollup alias</a> for Vue that was dynamic based on if I was building for production or in dev mode.</p>
<p>In <code>vite.config.js</code> I defined a constant to contain the path to the <a href="https://v2.vuejs.org/v2/guide/installation.html#Explanation-of-Different-Builds">Vue distribution</a> I wanted to use. I set it to the full production version if it is running in production mode (Which is the default for the <code>build</code> command). I set it to the full esm version as the default for running in development mode.</p>
<p>Here is the <code>vite.config.js</code> file. I left out the full build configuration for brevity.</p>
<pre><code class="language-javascript">import path from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue2'
export default defineConfig(({ command, mode }) => {
const VUE_PATH = mode == 'production' ? 'vue/dist/vue.min.js' : 'vue/dist/vue.esm.js';
return {
plugins: [vue()],
resolve: {
alias: {
'vue': VUE_PATH
}
},
build: {
...
}
}
})
</code></pre>
Issues Running Docker in Git Bash for Windows2022-11-09T00:00:00Zhttps://jasonsteinshouer.com/docker-path-issue-on-bash-for-windows.html<p>For awhile I had issues with trying to run some docker commands using Git Bash in Windows. I would typcially have to move over to Powershell and run them to get it to work the way I expected. An example is this command to try and open a shell for a container it would give me an error.</p>
<pre><code class="language-bash">$ docker exec -t mycontainer /bin/bash
OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: "C:/Program Files/Git/usr/bin/bash": stat C:/Program Files/Git/usr/bin/bash: no such file or directory: unknown
</code></pre>
<p>Another example is when it would mount the wrong directory to a container.</p>
<pre><code>docker run -v /$PWD:/usr/share/nginx/html:ro -p 8000:80 nginx
</code></pre>
<p>Eventually I got frustrated enough to try to find out why. After a bit of searching I found out that the issue is Git Bash will attempt to convert the file path to a windows file path. This caused problems because the docker containers are Linux.</p>
<p>One workaround is to add an extra slash at the beginning of the path. This tells Git Bash to not convert it.</p>
<pre><code class="language-bash">docker exec -t mycontainer //bin/bash
</code></pre>
<pre><code class="language-bash">docker run -v //$PWD://usr/share/nginx/html:ro -p 8000:80 nginx
</code></pre>
<p>You can also globally disable the POSIX path conversion in Git Bash (MinGW) by setting <code>MSYS_NO_PATHCONV=1</code>.</p>
<p>I solution I ended using was to use this function in my <code>.bashrc</code> file to disable the POSIX path conversion just for the <code>docker</code> command.</p>
<pre><code class="language-bash"># Workaround for Docker for Windows in Git Bash.
docker()
{
(export MSYS_NO_PATHCONV=1; "docker.exe" "$@")
}
</code></pre>
<p>I found the solution on this <a href="https://stackoverflow.com/questions/48427366/docker-build-command-add-c-program-files-git-to-the-path-passed-as-build-argu">Stack Overflow</a> thread. There is also a reference to it in this <a href="https://github.com/docker-archive/toolbox/issues/673#issuecomment-355275054">Github Issue</a>.</p>
My Notes on Migrating from Vue CLI to Vite2022-04-18T00:00:00Zhttps://jasonsteinshouer.com/vite-migration-notes.html<p>This is the second part of a series documenting my notes from migrating a Vue.js application to Vue 3 + <a href="https://vitejs.dev/">Vite</a>. This post is focused on moving from Vue CLI to Vite for my development tooling. See my <a href="https://jasonsteinshouer.com/vue3-migration-notes.html">last post</a> where I documented my notes from migrating from Vue 2 to Vue 3. See the <a href="https://github.com/jsteinshouer/movie-list-app/pull/6/files">pull request</a> if for all the changes.</p>
<h3 id="index.html" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vite-migration-notes.html#index.html">Index.html</a></h3>
<p>Vite expects the entry point to be index.html in the application root directory so I removed <code>public/index.htm</code> I added the following <code>index.html</code> to the root from an example Vite app.</p>
<pre><code class="language-html"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
</code></pre>
<h3 id="npm-scripts" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vite-migration-notes.html#npm-scripts">NPM Scripts</a></h3>
<p>I removed the Vue CLI scripts and added some for Vite.</p>
<pre><code class="language-diff">"scripts": {
- "serve": "vue-cli-service serve",
- "build": "vue-cli-service build",
- "lint": "vue-cli-service lint",
+ "dev": "vite --port 3000 --host",
+ "build": "vite build",
+ "preview": "vite preview --port 3000",
...
</code></pre>
<h3 id="dev-dependencies" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vite-migration-notes.html#dev-dependencies">Dev Dependencies</a></h3>
<p>I removed the Vue CLI dev dependencies and added Vite and the Vite Vue plugin.</p>
<pre><code class="language-diff">"devDependencies": {
- "@vue/babel-preset-app": "^4.5.13",
- "@vue/cli-plugin-babel": "^4.5.13",
- "@vue/cli-plugin-eslint": "^4.5.13",
- "@vue/cli-plugin-router": "^4.5.13",
- "@vue/cli-service": "~4.5.0",
- "vue-cli-plugin-axios": "0.0.4",
- "vue-template-compiler": "^2.6.11"
+ "@vitejs/plugin-vue": "^2.2.2",
+ "vite": "^2.8.4"
</code></pre>
<h3 id="add-extensions-for-.vue-components" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vite-migration-notes.html#add-extensions-for-.vue-components">Add extensions for .vue components</a></h3>
<p>Because Vite uses native ESM modules you need to provide the file extension for .vue components. i.e.</p>
<pre><code class="language-diff-javascript">- import NavBar from '@/components/NavBar';
+ import NavBar from '@/components/NavBar.vue'
</code></pre>
<h2 id="vite-config" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vite-migration-notes.html#vite-config">Vite Config</a></h2>
<p>I removed my <code>vue.config.js</code> file and added <code>vite.config.js</code> for my Vite configuration. One thing to mention is that I had setup Vue CLI to proxy API requests to a backend service. Vite has this capability as well. Use the <a href="https://vitejs.dev/config/#server-proxy">server.proxy configuration</a> to set it up.</p>
<pre><code class="language-javascript">server: {
proxy: {
'/api': {
target: 'http://my-backend-server:8080',
changeOrigin: true,
cookieDomainRewrite: "localhost"
},
</code></pre>
<p>Full configuration here:</p>
<pre><code class="language-javascript">import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
proxy: {
'/api': {
target: 'http://my-backend-server:8080',
changeOrigin: true,
cookieDomainRewrite: "localhost"
},
'/tests': {
target: 'http://my-backend-server:8080',
changeOrigin: true,
cookieDomainRewrite: "localhost"
}
}
}
})
</code></pre>
<p>The application I migrated is small but it did not seem as complicated as I expected. Both the Vue.js and Vite documentation is top notch.</p>
<h3 id="resources" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vite-migration-notes.html#resources">Resources</a></h3>
<ul>
<li><a href="https://vitejs.dev/guide/">Vite Guide</a></li>
</ul>
<h3 id="related-posts" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vite-migration-notes.html#related-posts">Related Posts</a></h3>
<ul>
<li><a href="https://jasonsteinshouer.com/vue3-migration-notes.html">My Notes on Migrating to Vue 3</a></li>
</ul>
My Notes on Migrating to Vue 32022-04-16T00:00:00Zhttps://jasonsteinshouer.com/vue3-migration-notes.html<p>I recently migrated a small application from Vue 2 to <a href="https://vuejs.org/">Vue 3</a> and wanted to record my notes here for future reference. I also migrated from Vue CLI to <a href="https://vitejs.dev/">Vite</a> at the same time but am putting those notes into a separate post. Here is a link to the <a href="https://github.com/jsteinshouer/movie-list-app/pull/6/files">pull request</a> if you want to see all the changes.</p>
<h3 id="package.json-dependencies" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vue3-migration-notes.html#package.json-dependencies">Package.json dependencies</a></h3>
<p>I started out by updating my <code>package.json</code> dependencies. I upgraded Vue and VueRouter to the latest versions at the time of this writing.</p>
<pre><code class="language-diff-json">- "vue": "^2.6.11",
- "vue-router": "^3.2.0"
+ "vue": "^3.2.31",
+ "vue-router": "^4.0.5"
</code></pre>
<h3 id="mount-app" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vue3-migration-notes.html#mount-app">Mount App</a></h3>
<p>Vue 2 used a global instance of Vue to mount the application in the DOM. Vue 3 now uses <code>createApp</code> to create an application instance.</p>
<p><code>src/main.js</code></p>
<pre><code class="language-diff-javascript">- import Vue from 'vue'
+ import { createApp } from 'vue'
...
- new Vue({
- router,
- render: h => h(App)
- }).$mount('#app')
+ const app = createApp(App)
+ app.mount('#app')
</code></pre>
<h3 id="axios-plugin" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vue3-migration-notes.html#axios-plugin">Axios Plugin</a></h3>
<p>The Vue CLI had generated some plugin code that added Axios integration into my Vue 2 application. Many plugins would install themselves directly into the Global Vue instance. This does not work with Vue 3 because plugins are installed into the application instance instead of the global Vue instance.</p>
<p>Here are the changes I made to the plugin code to get it to work.</p>
<p>The first change was to remove the import for the global Vue instance.</p>
<pre><code class="language-diff-javascript">- import Vue from 'vue';
</code></pre>
<p>Vue plugins expose an install method. I was able to simplify it by setting my Axios instance as a global property of the Vue application.</p>
<pre><code class="language-diff-javascript">app.config.globalProperties.axios = _axios;
</code></pre>
<p>Here are the full set of changes.</p>
<pre><code class="language-diff-javascript">- import Vue from 'vue';
...
- Plugin.install = function(Vue) {
- window.axios = _axios;
- Vue.axios = _axios;
- Object.defineProperties(Vue.prototype, {
- axios: {
- get() {
- return _axios;
- }
- },
- $axios: {
- get() {
- return _axios;
- }
- },
- });
- };
+ let install = function(app) {
+ app.config.globalProperties.axios = _axios;
+ };
- Vue.use(Plugin)
- export default Plugin;
+ export default { install };
</code></pre>
<p>The code <code>Vue.use(Plugin)</code> is removed and needs to be moved to <code>srcs/main.js</code> file so that it is applied to the application instance. This is what actually installs the plugin so it can be used in the application.</p>
<pre><code class="language-javascript">import axios from '@/plugins/axios'
...
const app = createApp(App)
...
app.use(axios)
app.mount('#app')
</code></pre>
<h2 id="vue-router" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vue3-migration-notes.html#vue-router">Vue Router</a></h2>
<p>As I mentioned previously I had to update Vue Router to v4+ to be compatible with Vue 3. Similar to the Axios plugin the Vue Router configuration also needed to be changed as well. Following the convention of Vue itself Vue Router now exposes <code>createRouter</code> to use for setup of the router.</p>
<h3 id="srcrouterindex.js" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vue3-migration-notes.html#srcrouterindex.js">src/router/index.js</a></h3>
<p>First I had to change the import statements.</p>
<pre><code class="language-diff-javascript">- import Vue from 'vue'
- import VueRouter from 'vue-router'
+ import { createRouter, createWebHistory } from 'vue-router'
</code></pre>
<p>I then removed the statement to tell Vue to use it.</p>
<pre><code class="language-diff-javascript">- Vue.use(VueRouter)
</code></pre>
<p>Here is where <code>createRouter</code> is used to create the router instance instead if <code>VueRouter</code>.</p>
<pre><code class="language-diff-javascript">- const router = new VueRouter({
- mode: 'history',
- routes
- })
+ const router = createRouter({
+ history: createWebHistory(),
+ routes,
+ })
</code></pre>
<h2 id="reactivity" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vue3-migration-notes.html#reactivity">Reactivity</a></h2>
<p>My app was using <code>Vue.observable</code> to create a reactive object for sharing state across multiple components. There is a new <a href="https://vuejs.org/api/reactivity-core.html">Reactivity API</a> for this in Vue 3.</p>
<pre><code class="language-diff-javascript">- import Vue from 'vue';
- export default Vue.observable({
+ import { reactive } from 'vue';
+ export default reactive({
...
});
</code></pre>
<p>Hope these notes may help someone else or future me. Again, you can see the <a href="https://github.com/jsteinshouer/movie-list-app/pull/6">pull request</a> for all the changes including the migration to Vite for a development server.</p>
<h3 id="resources" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vue3-migration-notes.html#resources">Resources</a></h3>
<ul>
<li><a href="https://v3-migration.vuejs.org/">Vue 3 Migration Guide</a></li>
<li><a href="https://vuejs.org/guide/introduction.html">Vue.js Guide</a></li>
</ul>
<h3 id="related-posts" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/vue3-migration-notes.html#related-posts">Related Posts</a></h3>
<ul>
<li><a href="https://jasonsteinshouer.com/vite-migration-notes.html">My Notes on Migrating from Vue CLI to Vite</a></li>
</ul>
My 2021 Dev.to Github Actions Hackathon Submission2021-12-08T00:00:00Zhttps://jasonsteinshouer.com/2021/12/08/my-2021-github-actions-hackathon-submission.html<p>I decided to create project submission for this year’s <a href="https://dev.to/devteam/join-us-for-the-2021-github-actions-hackathon-on-dev-4hn4">Dev.to Github Actions Hackathon</a>. I thought I would share my submission here.</p>
<p>My goal was to learn more about building a modern CFML application with a CI/CD process. I am sure there are a lot of improvements that could be made. One thing I am still working on is adding more documentation. Below is a link to the <a href="http://dev.to/">Dev.to</a> post as well as the project in Github.</p>
<p><a href="https://dev.to/jsteinshouer/building-a-cicd-workflow-for-my-cfmlvuejs-application-2c29">Dev.to post</a></p>
<p><a href="https://github.com/jsteinshouer/movie-list-app">Project on Github</a></p>
<p>The application itself is just basically just a to-do list but for movies. It is very much a work in progress. Here is a general outline of the tech stack that I am using:</p>
<h3 id="api" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2021/12/08/my-2021-github-actions-hackathon-submission.html#api">API</a></h3>
<ul>
<li>CFML - <a href="https://www.lucee.org/">Lucee</a></li>
<li><a href="https://www.coldbox.org/">Coldbox MVC Framework</a></li>
<li><a href="https://quick.ortusbooks.com/">Quick ORM</a> + <a href="https://github.com/coldbox-modules/cfmigrations">cfmigrations</a></li>
<li>MySQL</li>
</ul>
<h3 id="front-end" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2021/12/08/my-2021-github-actions-hackathon-submission.html#front-end">Front-end</a></h3>
<ul>
<li><a href="https://vuejs.org/">Vue.js 2</a></li>
<li><a href="https://router.vuejs.org/">Vue Router</a></li>
<li><a href="https://axios-http.com/docs/intro">Axios</a></li>
<li><a href="https://tailwindcss.com/">TailwindCSS</a></li>
</ul>
<h3 id="testing" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2021/12/08/my-2021-github-actions-hackathon-submission.html#testing">Testing</a></h3>
<p>The project also uses <a href="https://testbox.ortusbooks.com/">TestBox</a> and <a href="https://www.cypress.io/">Cypress</a> for testing.</p>
<h3 id="production" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2021/12/08/my-2021-github-actions-hackathon-submission.html#production">Production</a></h3>
<p>I am running it on the AWS <a href="https://aws.amazon.com/pm/ec2-graviton/">Graviton2</a> (ARM64 processor) micro instance using Docker. AWS is doing a free trial on the Graviton2 micro instances until the end of the year. It is using <a href="https://traefik.io/traefik/">Traefik</a> for reverse proxy and free/automated SSL certificates via Let’s Encrypt. I am also planning to share some more details about how I setup it up in a later post.</p>
<h3 id="conclusion" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2021/12/08/my-2021-github-actions-hackathon-submission.html#conclusion">Conclusion</a></h3>
<p>I do not think there is anything revolutionary here nor do I expect to win a contest prize. For me it was more about learning from the process of creating it.</p>
<p>Sadly, I just realized my last blog post was exactly one year ago 😞. I am hoping to change that in 2022. Maybe this project will be fodder for some new blog entries.</p>
CFML Development with Github Codespaces2020-12-07T00:00:00Zhttps://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html<p>I saw the announcement from Github awhile back for the beta release of their <a href="https://github.com/features/codespaces">Codespaces</a> product. I thought it looked like an interesting idea and decided to sign up. I was given access awhile back but just recently had some time to play around with it a bit.</p>
<p>The idea of running your development environment in containers is not new. However, typically it has applied to just the application itself. Codespaces takes it a step further by packaging everything including the editor (VS Code), extensions, tools, or anything else needed for development inside a container environment. With Codespaces you can develop from any machine that has a internet browser.</p>
<h2 id="getting-started" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#getting-started">Getting Started</a></h2>
<p>For starting out with Codespaces I decided to setup a development environment for a simple CFML application. But before doing that I just did a very very basic Hello World page.</p>
<p>I was also wondering if there was a way to test it out locally before doing it in Codespaces. It turns out that the configuration is about the same as using the <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers">VS Code Remote Containers plugin</a>. This plugin runs your entire development environment in Docker Desktop on your local machine.</p>
<p>Note that not all the features available with this plugin currently work in <a href="https://github.com/features/codespaces">Github Codespaces</a>.</p>
<h2 id="cfml-hello-world-example" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#cfml-hello-world-example">CFML Hello World Example</a></h2>
<p>Configuring your project for Codespaces starts with a json configuration file. It can be either <code>.devcontainer.json</code> or <code>.devcontainer/devcontainer.json</code>. You can find a list of all the supported configuration keys in their <a href="https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/configuring-codespaces-for-your-project#supported-codespace-configuration-keys">documentation</a>.</p>
<h3 id="container-image" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#container-image">Container Image</a></h3>
<p>To setup my development environment I wanted to use the pre-built Docker image provided by Ortus Solutions. So I used the <code>image</code> key to define the image I wanted to use.</p>
<p>At the time of this writing Codespaces only supports Ubuntu based docker images. I tried to use an Alpine image and it failed.</p>
<pre><code class="language-json">{
"name": "CFML Hello World",
"image": "ortussolutions/commandbox:latest",
</code></pre>
<h3 id="workspace-configuration" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#workspace-configuration">Workspace Configuration</a></h3>
<p>The Ortus image expects the application to be in a directory named <code>/app</code> by default so I used the configuration file to configure the environment to use that folder as my workspace and to map my code to that directory.</p>
<pre><code class="language-json">"workspaceFolder": "/app",
"workspaceMount": "source=${localWorkspaceFolder},target=/app,type=bind,consistency=cached",
</code></pre>
<h3 id="vs-code-plugins-and-settings" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#vs-code-plugins-and-settings">VS Code Plugins and Settings</a></h3>
<p>Since the VS Code editor is running inside the container environment we define what VS Code plugins should be installed for this environment. Here I am installing the VS Code CFML plugin and the TestBox plugin. When I have some more time I would like to get the CF Lint plugin working with the environment as well.</p>
<pre><code class="language-json">"extensions": [
"kamasamak.vscode-cfml",
"formulahendry.auto-close-tag",
"ortus-solutions.vscode-testbox"
],
</code></pre>
<p>You can also use the <code>settings</code> key to apply specific settings to the VS Code editor.</p>
<h3 id="starting-the-application" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#starting-the-application">Starting the Application</a></h3>
<p>The <code>forwardPorts</code> setting is used to tell Codespaces which port your app is running on. I used to the <code>postCreateCommand</code> setting to install the application dependencies and start the server after the container is created.</p>
<pre><code class="language-json">"forwardPorts": [8080],
"postCreateCommand": "box install && box server start",
</code></pre>
<p>You will have full access to CommandBox via the terminal so can start and stop the server manually as well.</p>
<p><img src="https://static.jasonsteinshouer.com/images/codespaces/server-start.png" alt="Start CommandBox Server" /></p>
<p>Start a CommandBox server inside Github Codespaces</p>
<p>If you want to see the full code for my hello world test you can see the <a href="https://github.com/jsteinshouer/cfml-hello-codespaces">repo here</a>. Next I wanted to get a little more complex by running a basic To-Do application that saves your to-do items to a database table.</p>
<h2 id="customizing-your-environment" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#customizing-your-environment">Customizing Your Environment</a></h2>
<p>You can also create a <code>Dockerfile</code> as part of your Codespaces configuration if an existing Docker image does not work for you or you need to install additional tools in your environment. Since my To-Do application uses an application server and database service I need to be able to run multiple containers.</p>
<h2 id="running-multiple-containers" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#running-multiple-containers">Running Multiple Containers</a></h2>
<p>Github Codespaces also supports Docker Compose so you can run multiple containers for your development <a href="http://environment.so/">environment.So</a> for this example I created a <code>docker-compose.yml</code> file to configure my application container running the CFML application that connects to a database container running SQL Server.</p>
<p>I use the same <code>devcontainer.json</code> configuration as above but instead of using the <code>image</code> setting I provide a Docker Compose file.</p>
<pre><code class="language-json">{
"name": "CFML App with SQL Server",
"dockerComposeFile": "docker-compose.yml",
</code></pre>
<p>Here is the full <code>docker-compose.yml</code> configuration that I am using for the demo. One thing to note is that Docker Compose version 3 or higher is required.</p>
<pre><code class="language-yaml">version: '3'
services:
app:
image: ortussolutions/commandbox:latest
environment:
- PORT=8080
- DB_NAME=ToDo
- DB_HOST=db
- DB_USER=sa
- DB_PASSWORD=change_this_password
volumes:
- ..:/app:cached
depends_on:
- db
command: sleep infinity
db:
image: mcr.microsoft.com/mssql/server:2019-latest
restart: unless-stopped
environment:
SA_PASSWORD: "change_this_password"
ACCEPT_EULA: "Y"
MSSQL_PID: "Express"
volumes:
- mssql-volume:/var/opt/mssql
volumes:
mssql-volume:
</code></pre>
<p>The full repository for this project can be found here.</p>
<p><a href="https://github.com/jsteinshouer/cfml-codespace-demo">https://github.com/jsteinshouer/cfml-codespace-demo</a></p>
<h2 id="developing-with-codespaces" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#developing-with-codespaces">Developing with Codespaces</a></h2>
<p>Within the Codespaces editor you have access to many of the same features you have when editing in VS Code. Here is the file browser running inside the Codespaces editor.</p>
<p><img src="https://static.jasonsteinshouer.com/images/codespaces/file-browser.png" alt="Codespaces File Browser" /></p>
<p>Codespaces File Explorer</p>
<h3 id="github-integration" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#github-integration">Github Integration</a></h3>
<p>The Codespace is integrated with the Github repository so you can use the editor or terminal to create branches, commit changes, and even create pull requests.</p>
<p><img src="https://static.jasonsteinshouer.com/images/codespaces/source-control.png" alt="Commit changes within the Codespaces UI" /></p>
<p>As I previously mentioned you also get access to the terminal so you can run CommandBox or any other tool installed in the container.</p>
<p><img src="https://static.jasonsteinshouer.com/images/codespaces/commandbox.png" alt="CommandBox running in Codespaces" /></p>
<p>When you run your application, Codepsaces will promt if you would like to open the application in the browser. You can also access it by clicking on this icon at the bottom.</p>
<p><img src="https://static.jasonsteinshouer.com/images/codespaces/ports-icon.png" alt="Available ports in Codespaces" /></p>
<p>Here is what my demo application looks like running in the Codepsaces environment.</p>
<p><img src="https://static.jasonsteinshouer.com/images/codespaces/todo-app.png" alt="Example To-Do application running in Codespaces" /></p>
<p>The <a href="https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/developing-in-a-codespace">Developing in Codespaces</a> documentation provides more detail.</p>
<h3 id="personalization" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#personalization">Personalization</a></h3>
<p><strong>Dotfiles</strong></p>
<p>If you have a dotfiles repository, Codepsaces will automatically clone it in the container and use it to personalize your development environment. I do not have currently have a dotfiles repository so I did not test this out. I did however make a note to look into this in the future. See the <a href="https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/personalizing-codespaces-for-your-account">docs</a> for more details.</p>
<p><strong>VS Code Settings</strong></p>
<p>You can also utilize the VS Code settings sync feature to synconize extensions, settings, and keyboard shortcuts.</p>
<h2 id="pricing" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#pricing">Pricing</a></h2>
<p>At the time of this writing Github Codespaces is currently in public beta release so the pricing details are still not finalized. It is currently free for beta and limited to 2 <a href="http://instances.it/">instances.</a></p>
<p>The documentation states that I will be billed for storage and compute usage when generally available. It currently does not mention a free tier however the pricing details that they do have <a href="https://docs.github.com/en/free-pro-team@latest/github/developing-online-with-codespaces/about-billing-for-codespaces">listed</a> do not seem too expensive. They also say they will have different instance sizes available as time of general availability as well.</p>
<h2 id="conclusion" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/12/07/setup-a-cfml-development-environment-in-codespaces.html#conclusion">Conclusion</a></h2>
<p>I really like the idea of packaging the entire development environment together to make it easier for new developers to get started. I also think this would be great for education and training of new developers. However, since there does not seem to be a free tier I am not sure how much it will take off. We can hope that they decide to offer a free tier for learning and minimal usage.</p>
Exploration in Unit Testing Vue.js Components2020-01-23T09:12:00Zhttps://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html<p>I have been playing around with the Vue.js framework a lot in the last year or two. I went through Udemy course that covered Vue.JS and Vuex. The final project was to build a Stock Trading Simulation using Vue.js and Vuex. The course did not cover testing. I wanted to explore that by adding some unit tests to my Stock Trading application. Here are my notes about some of the things I learned in the process.</p>
<h3 id="what-to-test" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html#what-to-test">What to test?</a></h3>
<p>I am not going to go into the benefits of writing tests in this post but I was curious about what I should test in a Vue.js component. I found some good guidance in this <a href="https://vue-test-utils.vuejs.org/guides/#knowing-what-to-test">guide from the Vue Test Utils documentation</a>.</p>
<blockquote>
<p>Instead, we recommend writing tests that assert your component’s public interface, and treat its internals as a black box. A single test case would assert that some input (user interaction or change of props) provided to the component results in the expected output (render result or emitted custom events).</p>
</blockquote>
<p>So basically I should test input and output and not worry about testing the details of internal implementation.</p>
<h3 id="getting-started" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html#getting-started">Getting started</a></h3>
<p>The <a href="https://cli.vuejs.org/">Vue CLI</a> makes setting up new Vue.js projects simple and easy. So it made sense to check their docs for ways to easily add unit testing to a Vue.js project. It turns out it has support for unit testing built it with either the <a href="https://github.com/facebook/jest">Jest</a> or <a href="https://mochajs.org/">Mocha</a> libraries. I did not have prior experience with either library but had read that the syntax for Jest was very similar to Jasmine so I decided to go with Jest. Adding it to my project was easy as running this command.</p>
<pre><code>vue add @vue/unit-jest
</code></pre>
<p>By default you will put your tests in the <code>tests/unit</code> directory. The test runner will look for anything that ends with <code>.spec.(js|jsx|ts|tsx)</code>.</p>
<p>Running the tests is as simple as this command.</p>
<pre><code>npm run test:unit
</code></pre>
<h3 id="vue-test-utils" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html#vue-test-utils">Vue Test Utils</a></h3>
<p><a href="https://vue-test-utils.vuejs.org/">Vue Test Utils</a> is the official test library for Vue.js. It gives you the ability to mount Vue.js components inside your tests. It also gives you utilities to traverse the rendered HTML to verify the component behavior is what is expected.</p>
<h3 id="shallowmount-vs-mount" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html#shallowmount-vs-mount">ShallowMount vs Mount</a></h3>
<p><a href="https://vue-test-utils.vuejs.org/">Vue Test Utils</a> provides two utilities for mounting Vue.js components in isolation for testing them. They allow you to mock the inputs to the component as well as provides convivence methods for asserting that the outputs are correct. The <code>[Mount](https://vue-test-utils.vuejs.org/guides/#mounting-components)</code> method will mount the component along with any child components as well.</p>
<p>The <code>[shallowMount](https://vue-test-utils.vuejs.org/guides/#shallow-rendering)</code> method will only mount the component itself and stub out any child components. The benefit to this is that it allows you to test the component in isolation as well as makes your tests faster because it doesn’t have to render all the child components. See <a href="https://vue-test-utils.vuejs.org/guides/#shallow-rendering">the docs</a> for more info.</p>
<p>Here is an example of using the <code>shallowMount</code> method to test my component.</p>
<pre><code class="language-javascript">import { shallowMount } from '@vue/test-utils'
import Home from '@/components/Home.vue'
...
it('should display the current amount of funds', () => {
wrapper = shallowMount(Home, {localVue, store});
expect(wrapper.find("h2").text()).toBe("Your Funds: $75,000");
});
</code></pre>
<p>Here is an example where I check that a stubbed child component is would be rendered twice.</p>
<pre><code class="language-javascript">it('should render the stock component for each stock', () => {
wrapper = shallowMount(Stocks, { localVue, store });
expect(wrapper.findAll("app-stock-stub").length).toBe(2);
});
</code></pre>
<h3 id="testing-components-with-vuex" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html#testing-components-with-vuex">Testing Components with Vuex</a></h3>
<p>Since we are unit testing we want to test the component in isolation. So if my component is calling getters, mutators, or actions from a Vuex store, I don’t want to test that at the same time. To handle this I can use the Jest <code>[jest.fn()](https://jestjs.io/docs/en/mock-functions)</code> utility to mock them.</p>
<p>In this example I am using it to mock a Vuex mutation.</p>
<pre><code class="language-javascript">let mutations;
let store;
beforeEach(()=>{
mutations = {
"buy": jest.fn()
}
store = new Vuex.Store({
mutations
});
});
</code></pre>
<p>Then in my tests I can verify it was called.</p>
<pre><code class="language-javascript">it('should call the buy mutation when a quantity is entered and the button is clicked', () => {
wrapper = mount(Stock, { propsData: { stock }, localVue, store});
wrapper.setData( { quantity: 1 } );
wrapper.find("button").trigger("click");
expect(mutations.buy).toHaveBeenCalled();
});
</code></pre>
<h3 id="testing-vuex" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html#testing-vuex">Testing Vuex</a></h3>
<p>For testing a Vuex store you can test it like you would any other Javascript module. Just import it ,call the methods,and verify the results. Here is a simple example.</p>
<pre><code class="language-javascript">import stocks from '@/store/stocks';
describe('store/stocks.js', () => {
describe('mutations.endDay', () => {
it('should randomly change the stock prices', () => {
stocks.state.stocks = [
{ name: "Google", price: 100 },
{ name: "Apple", price: 100 },
{ name: "Twitter", price: 100 }
];
stocks.mutations.endDay(stocks.state);
stocks.state.stocks.forEach( (item) => {
expect(item.price).not.toBe(100);
});
});
});
});
</code></pre>
<h3 id="testing-with-vuetify" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html#testing-with-vuetify">Testing with Vuetify</a></h3>
<p>I used the Vuetify UI component library in project so in order to get my unit tests to run I had to setup the local Vue instance to know about the Vuetify library.</p>
<pre><code class="language-javascript">import Vuetify from 'vuetify'
...
const localVue = createLocalVue()
localVue.use(Vuetify)
</code></pre>
<h3 id="ci-with-travis-ci" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html#ci-with-travis-ci">CI with Travis CI</a></h3>
<p>Now that I have a test suite I wanted to setup my Travis CI build to run them and verify that they pass prior to deploying any changes. In my <code>.travis.yml</code> configuration file I just had to run the <code>npm run test</code> command prior to running the <code>npm run build</code> command. I also run the lint command to catch any formatting or syntax issues. Here is my full configuration.</p>
<pre><code class="language-yml">language: node_js
node_js:
- "stable"
cache:
directories:
- node_modules
script:
- npm run lint
- npm run test
- npm run build
deploy:
provider: pages
skip_cleanup: true
github_token: $github_token
local_dir: dist
on:
branch: master
</code></pre>
<p>Deployment to Github pages by adding this to the <code>vue.config.js</code> file. This lets the build command know that the application should be built to run int in the sub directory <code>stock-trader</code> of my Github pages site.</p>
<pre><code class="language-javascript">module.exports = {
publicPath="stock-trader"
}
</code></pre>
<h3 id="test-coverage" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html#test-coverage">Test Coverage</a></h3>
<p>I was able to add test coverage reporting by adding this to my Jest config in the file <code>jest.config.js</code>.</p>
<pre><code class="language-javascript">collectCoverage: true,
collectCoverageFrom: [
'**/*.{vue}',
'!**/node_modules/**',
'!<rootDir>/dist/**',
'!<rootDir>/src/plugins/**',
'!<rootDir>/tests/unit/**'
],
coverageReporters: ['lcov', 'text-summary']
</code></pre>
<p>This will generate reports in the <code>/coverage</code> directory.</p>
<h3 id="conclusion" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2020/01/23/exlore-unit-testing-in-vue.html#conclusion">Conclusion</a></h3>
<p>It is possible that I am not following some best practices here and welcome any feedback. This post is some notes on things I learned during my initial exploration with unit testing Vuex components. It is not meant to be a deep dive.</p>
Using .NET Integration Services with CommandBox2019-12-18T09:05:00Zhttps://jasonsteinshouer.com/2019/12/18/using-dotnet-integration-with-commandbox.html<p><a href="https://www.ortussolutions.com/products/commandbox">CommandBox</a> is a great tool for getting CFML development environments setup very quickly. I work with an application that uses the .NET Integration feature of Coldfusion. I recently needed to change that functionality so I needed to get it working in my development environment to test it. This is to document the steps I took to get it working with a CommandBox server in my local development environment.</p>
<h3 id="install-coldfusion-.net-integration-services" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/12/18/using-dotnet-integration-with-commandbox.html#install-coldfusion-.net-integration-services">Install Coldfusion .NET Integration Services</a></h3>
<p>First you need to download and install the Coldfusion .NET Integration Services standalone installer. I have included a link to the installer for <a href="https://www.adobe.com/support/coldfusion/downloads.html#cf2018serverinstallers">Coldfusion 2018 here</a>. Once it is downloaded run the installer.</p>
<h3 id="add-configuration-settings" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/12/18/using-dotnet-integration-with-commandbox.html#add-configuration-settings">Add configuration settings</a></h3>
<p>I am using the <a href="https://www.forgebox.io/view/commandbox-dotenv">commandbox-dotenv</a> and <a href="https://cfconfig.ortusbooks.com/">commandbox-cfconfig</a> modules to configure my environment.</p>
<pre><code>box install commandbox-dotenv
box install commandbox-cfconfig
</code></pre>
<p>I added these settings to my <code>.env</code> file for the .NET Integration. In the traditional installation for Coldfusion 2018 you would find these settings in <code><cf home>/lib/neo-dotnet.xml</code>. The port numbers will be defined in the folder that the .NET Integration Services are installed in a file named <code>JNBDotNetSide.exe.config</code>.</p>
<pre><code># .NET Integration Settings
DOTNET_DIR=C:\\ColdFusion2018DotNetService
DOTNET_PORT=6095
DOTNET_CLIENT_PORT=6096
</code></pre>
<p>Lucky for me commandbox-cfconfig supports configuring .NET integration in Coldfusion for me. Here is a snippet of my <code>.cfconfig.json</code> file.</p>
<pre><code>"dotNetInstallDir": "${DOTNET_DIR}",
"dotNetPort": "${DOTNET_PORT}",
"dotNetClientPort": "${DOTNET_CLIENT_PORT}"
</code></pre>
<p>Once you have the .NET Integration Services installed and running you can then restart the server for the new settings to take effect.</p>
<pre><code>box server restart
</code></pre>
<h3 id="test-it" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/12/18/using-dotnet-integration-with-commandbox.html#test-it">Test it</a></h3>
<p>Here is a script that can be used to test it. It just writes the OS version to the screen using .NET.</p>
<pre><code class="language-cfscript"><cfscript>
environment = createObject(".NET", "System.Environment");
writeOutput("OS Version: " & environment.Get_OSVersion() );
</cfscript>
</code></pre>
<p>If it is not working you will likely receive an error message like:</p>
<blockquote>
<p>DotNet Side does not seem to be running.</p>
</blockquote>
Two-Factor Authentication with TOTP and CFML2019-05-26T22:58:00Zhttps://jasonsteinshouer.com/2019/05/26/totp-2fa-in-cfml.html<p>I wanted to see how difficult it would be to implement 2-Factor authentication (2FA) in a CFML application I decided to try to implement use the Time-based One-time Password Algorithm since it has been used as a 2nd-factor for authentication for awhile. There are also many mobile applications available for generating one-time passwords on your mobile device. For this example I am using a demo application running on the Coldbox MVC framework.</p>
<h3 id="what-is-time-based-one-time-password-algorithm-(totp)" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/05/26/totp-2fa-in-cfml.html#what-is-time-based-one-time-password-algorithm-(totp)">What is Time-based One-time Password Algorithm (TOTP)?</a></h3>
<p>TOTP uses a shared key to generate a one-time password that is only valid for a short amount of time. This is typically about 30 seconds.There are mobile applications that can generate these one-time passwords to act as a second authentication method. A process for doing 2-Factor authentication with TOTP will typically look something like this.</p>
<ol>
<li>User will submit their username/password combination to the server via an authentication form</li>
<li>Upon verification, the application then prompts them to enter a TOTP for the 2nd-factor authentication</li>
<li>The user opens an app such as Google Authenticator on their mobile device to get the TOTP</li>
<li>The user enters the TOTP in the application authentication form and submits</li>
<li>The server verifies the TOTP is valid and authenticates the user</li>
</ol>
<h3 id="server-verification" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/05/26/totp-2fa-in-cfml.html#server-verification">Server Verification</a></h3>
<p>First, did some research to see what libraries were available to handle the TOTP creation and verification. I found a couple of options. There is a Java library named <a href="http://wstrange/GoogleAuth">GoogleAuth</a> for working with TOTP.</p>
<p>I also found this <a href="https://github.com/marcins/cf-google-authenticator">CFML component</a> along with a <a href="https://junkheap.net/blog/2013/05/30/implementing-google-authenticator-support-in-coldfusion/">blog post</a> on using it. I decided to use the CFML library even though it had not been updated for a while mainly because it would be a little easier to use in CFML.</p>
<p>I made a few small changes to it for my use. I changed the generateKey() method to use a randomly generated seed instead of an argument. I also added a method to generate a registration QR code on the server using the <a href="https://github.com/zxing/zxing">Zxing</a> library instead of using a Javascript library on the client. Here is the method used to generate the QR code.</p>
<h4 id="qr-code-generation" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/05/26/totp-2fa-in-cfml.html#qr-code-generation">QR Code Generation</a></h4>
<pre><code class="language-cfscript"> public any function getOTPQRCode(required string name, required string key)
{
var data = 'otpauth://totp/#arguments.name#?secret=#arguments.key#';
/* Create the QR Code */
var barcodeFormat = createObject("java","com.google.zxing.BarcodeFormat");
var QRCodeWriter = createObject("java","com.google.zxing.qrcode.QRCodeWriter").init();
var matrixToImageWriter = createObject("java","com.google.zxing.client.j2se.MatrixToImageWriter");
var QRCode = QRCodeWriter.encode( data, barcodeFormat.QR_CODE, "400", "400" );
return matrixToImageWriter.toBufferedImage( QRcode );
}
</code></pre>
<p>You can see <a href="https://github.com/jsteinshouer/owasp-demo-cfml/blob/master/models/security/OneTimePasswordService.cfc">my full version here</a>.</p>
<h3 id="data-model" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/05/26/totp-2fa-in-cfml.html#data-model">Data Model</a></h3>
<p>I added two new database columns to the user table one to flag if the user has 2FA enabled and another to store the users randomly generated shared key used for generating and verifying TOTP.</p>
<pre><code class="language-sql">ALTER TABLE [user] ADD two_factor_auth_enabled bit;
ALTER TABLE [user] ADD two_factor_auth_key nvarchar(50);
</code></pre>
<p>I modified the <code>User</code> model components to use the two new properties.</p>
<h3 id="settings-registration" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/05/26/totp-2fa-in-cfml.html#settings-registration">Settings / Registration</a></h3>
<p>I created a new set of handler actions and views so the user can see the status of their settings as well as a link to enable or disable 2FA.</p>
<p><img src="https://static.jasonsteinshouer.com/images/2fa/2FA-settings.png" alt="Enable 2FA" /></p>
<p>Clicking on the Enable link will call the <code>settings.setupTwoFactorAuthentication</code> action. This action will generate a shared key for the user and also a QR code that the user can scan to set up a profile in their TOTP client application.</p>
<pre><code class="language-cfscript">prc.user.setTwoFactorAuthenticationKey( oneTimePasswordService.generateKey() );
userService.save( prc.user );
prc.qrCodeImage = oneTimePasswordService.getOTPQRCode(
name = "Recipe Box (#prc.user.getUsername()#)",
key = prc.user.getTwoFactorAuthenticationKey()
);
event.setView(view="settings/setup");
</code></pre>
<p>Here is the view rendered in the browser.</p>
<p><img src="https://static.jasonsteinshouer.com/images/2fa/2FA-setup.png" alt="2FA Setup: Enter Code" /></p>
<p>For this demo, I am using the Google Authenticator mobile application for generating TOTP codes on my mobile device. In the Google Authenticator app, I Add a new account and scan the barcode.</p>
<img src="https://static.jasonsteinshouer.com/images/2fa/2FA-authenticator-2.png" style="height: 450px" alt="Google Authenticator: Add Account" />
<img src="https://static.jasonsteinshouer.com/images/2fa/2FA-authenticator-3.png" style="height: 450px" alt="Google Authenticator: Scan barcode" />
<p>Then I see that my account was added and should see the TOTP code for my account.</p>
<img src="https://static.jasonsteinshouer.com/images/2fa/2FA-authenticator-4.png" style="height: 450px" alt="Google Authenticator: account added" />
<p>I then enter that code into the setup form and click Verify Code.</p>
<p><img src="https://static.jasonsteinshouer.com/images/2fa/2FA-setup-enter-code.png" alt="Verify code" /></p>
<p>This will call the <code>settings.enableTwoFactorAuthentication</code> handler action to verify the code entered is valid before enabling the 2FA service for the user.</p>
<pre><code class="language-cfscript">public void function enableTwoFactorAuthentication(event,rc,prc) {
prc.user = securityService.getLoggedInUser();
event.paramValue("passcode","");
if ( !prc.user.isTwoFactorAuthenticationEnabled() ) {
var key = prc.user.getTwoFactorAuthenticationKey();
if ( len( rc.passcode ) && oneTimePasswordService.verify( key, rc.passcode ) ) {
prc.user.setTwoFactorAuthenticationenabled( true );
userService.save( prc.user );
flash.put("message","2-Factor authentication setup successful.");
}
else {
prc.user.setTwoFactorAuthenticationEnabled( false );
prc.user.setTwoFactorAuthenticationKey( "" );
userService.save( prc.user );
flash.put("message","2-Factor authentication setup failed.");
}
}
setNextEvent("settings");
}
</code></pre>
<p>If the code was valid the user should see a message confirming that 2FA is enabled.</p>
<p><img src="https://static.jasonsteinshouer.com/images/2fa/2FA-setup-complete.png" alt="Confirmation" /></p>
<h3 id="authentication-process" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/05/26/totp-2fa-in-cfml.html#authentication-process">Authentication Process</a></h3>
<p>I have modified the authentication process to now check to see if a user has 2FA enabled. The authentication code is located in <code>handlers/Security.cfc</code>. When the user enters their username/password combination the handler validates them using the SecurityService component <code>checkUsernameAndPassword()</code> method. It checks to see if the user has 2FA enabled and sets the session variable <code>step2Required</code>, otherwise the user is authenticated.</p>
<p><strong>securityService.checkUsernameAndPassword()</strong></p>
<pre><code class="language-cfscript">if (
q.recordCount
&& passwordService.checkPassword(arguments.password,q.password)
) {
sessionStorage.setVar( "step1Valid",true );
var user = userService.get( q.p_user );
sessionStorage.setVar( "user", user );
if ( user.isTwoFactorAuthenticationEnabled() ) {
sessionStorage.setVar( "step2Required",true );
}
else {
sessionStorage.setVar( "isLoggedIn",true );
sessionRotate();
}
isValid = true;
}
</code></pre>
<p>If they are using 2FA they will see another prompt to enter their TOTP code. Then they will need to open the Google Authenticator to get the code.</p>
<img src="https://static.jasonsteinshouer.com/images/2fa/2FA-authenticator-5.png" style="height: 450px" alt="Google Authenticator: Get TOTP" />
<p>Then they enter it into the authentication form and click Verify Code.</p>
<p><img src="https://static.jasonsteinshouer.com/images/2fa/2FA-step2b.png" alt="Verify Code" /></p>
<p>This will call the <code>verifyCode</code> action on the Security handler. This handler then calls the <code>verifyOneTimePassword()</code> method on the SecurityService component and will authenticate the user, if the TOTP code is correct.</p>
<pre><code class="language-cfscript">public boolean function verifyOneTimePassword( required string password ) {
var user = getLoggedInUser();
var isValid = oneTimePasswordService.verify(
key = user.getTwoFactorAuthenticationKey(),
userToken = arguments.password
);
if ( isValid ) {
sessionStorage.setVar("isLoggedIn",true);
sessionRotate();
}
return isValid;
}
</code></pre>
<p>You can view the entire <a href="https://github.com/jsteinshouer/owasp-demo-cfml/blob/master/handlers/Security.cfc">Security handler here</a> and the <a href="https://github.com/jsteinshouer/owasp-demo-cfml/blob/master/models/security/SecurityService.cfc">SecurityService component here</a>.</p>
<p>One thing I did not cover was a way to recover an account in the event that the mobile device was lost or damaged. Even though I am not covering it in this example it should be implemented so user’s do not get locked out of their accounts.</p>
<p>I wanted to try and document this as part of my continued exloration and learning of application security. I have a demo application I have been using to implement some of the application security concepts that I am learning. If anyone is interested you can find the full project on <a href="https://github.com/jsteinshouer/owasp-demo-cfml">Github</a>.</p>
Using CFLint for Static Analysis with Jenkins2019-03-02T22:58:00Zhttps://jasonsteinshouer.com/2019/03/02/using-commandbox-cflint-in-jenkins.html<p>There are a lot of good options when it comes to Continuous Integration(CI) systems these days. <a href="https://jenkins.io/">Jenkins</a> is a time tested CI tool but has also evolved to adapt to modern development practices so I think it is still a great option when it comes to continuous integration. I am going to walk through how you can use <a href="https://github.com/cflint/CFLint">CFLint</a> and Jenkins to do static code analysis on your CFML code as part of a CI process.</p>
<h3 id="cflint" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/03/02/using-commandbox-cflint-in-jenkins.html#cflint">CFLint</a></h3>
<p>CFLint is a tool for doing static analysis on CFML code. It allows you to use rules to check code for possible bugs and also to ensure that best practices and standards are being met. Hopefully, you and your developers are using CFLint or another tool as you develop but it is also a good process to run as part of your continuous integration process.</p>
<p><a href="https://github.com/jsteinshouer/commandbox-cflint">CommandBox-CFLint</a> is a CommandBox module that packages the CFLint tool. In this tutorial, I use it with CommandBox to easily install and run CFLlint from within Jenkins.</p>
<h3 id="jenkins-setup" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/03/02/using-commandbox-cflint-in-jenkins.html#jenkins-setup">Jenkins Setup</a></h3>
<p>For this demo, I am running Jenkin in Docker. The command below I used is based on information in <a href="https://jenkins.io/doc/tutorials/build-a-node-js-and-react-app-with-npm/#run-jenkins-in-docker">this tutorial</a>. It should work pretty much the same if you have a physical server. The commands may be slightly different depending on if you are using Linux or Windows. Here is the command I used to run it on my Windows machine.</p>
<pre><code>docker run ^
--rm ^
-u root ^
-p 8080:8080 ^
-v jenkins-data:/var/jenkins_home ^
-v /var/run/docker.sock:/var/run/docker.sock ^
-v "%HOME%":/home ^
jenkinsci/blueocean
</code></pre>
<p>Your initial admin password can be found in this file.</p>
<pre><code>/var/jenkins_home/secrets/initialAdminPassword
</code></pre>
<p>The first time you sign in you will go through a setup process. Here you will want to install all the recommended plugins. You can optionally set up additional users.<br />
Once you are logged into the admin you will want to set up a Job. To do this follow <a href="https://jenkins.io/doc/tutorials/build-a-node-js-and-react-app-with-npm/#create-your-pipeline-project-in-jenkins">these steps</a> for your repository.</p>
<h3 id="pipeline-configuration" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/03/02/using-commandbox-cflint-in-jenkins.html#pipeline-configuration">Pipeline Configuration</a></h3>
<p>It is common in modern CI systems for the configuration to be stored in a text file that can be checked into the source control system. With the introduction of the Jenkins pipeline plugins, it is possible to define your build configuration in a Jenkinsfile. This is Groovy script with a domain specific language (DSL) for Jenkins.</p>
<p>Here is an example of a Jenkinsfile from the <a href="https://jenkins.io/doc/book/pipeline/jenkinsfile/">Jenkins documentation</a> that defines 3 stages of a pipeline.</p>
<pre><code>pipeline {
agent any
stages {
stage('Build') {
steps {
echo 'Building..'
}
}
stage('Test') {
steps {
echo 'Testing..'
}
}
stage('Deploy') {
steps {
echo 'Deploying....'
}
}
}
}
</code></pre>
<p>The <a href="https://jenkins.io/doc/book/pipeline/syntax/#agent">agent directive</a> is used to tell Jenkins how to execute the pipeline. This can be defined at the global level or at each individual stage. One option is to use Docker which is what I will use for this tutorial.</p>
<p>Here I tell Jenkins to execute the pipeline using the <a href="https://hub.docker.com/r/ortussolutions/commandbox/">Ortus Solutions CommandBox Docker image</a>.</p>
<pre><code>pipeline {
agent {
docker {
image 'ortussolutions/commandbox'
}
}
…
</code></pre>
<p>Then I define a single stage named Static Analysis with 2 steps.</p>
<pre><code>stages {
stage('Static Analysis') {
steps {
sh 'box install commandbox-cflint'
sh 'box cflint reportLevel=ERROR'
}
}
}
</code></pre>
<p>This first step tells CommandBox to install the commandbox-cflint module from <a href="https://www.forgebox.io/">ForgeBox</a>.</p>
<pre><code>sh 'box install commandbox-cflint'
</code></pre>
<p>The second will then run the CFLint command in the workspace directory. Keep in mind the Jenkins will check out the code into the workspace prior to executing these commands.</p>
<pre><code>sh 'box cflint reportLevel=ERROR'
</code></pre>
<p>The reportLevel parameter tells the command to only report items with the level of ERROR. If any errors are found by CFLint the pipeline run will be marked with failure.</p>
<p><img src="https://s3.us-west-2.amazonaws.com/jasonsteinshouer/images/2019-03-02_22h02_07.png" alt="Jenkins Failed" /></p>
<p>Then if I fix the errors and commit them to source control the job will run again and will then be successful.</p>
<p><img src="https://s3.us-west-2.amazonaws.com/jasonsteinshouer/images/2019-03-02_22h35_20.png" alt="Jenkins Success" /></p>
<h4 id="jenkinsfile" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2019/03/02/using-commandbox-cflint-in-jenkins.html#jenkinsfile">Jenkinsfile</a></h4>
<p>For reference here is the whole Jenkinsfile I used for this example.</p>
<pre><code>pipeline {
agent {
docker {
image 'ortussolutions/commandbox'
}
}
stages {
stage('Static Analysis') {
steps {
sh 'box install commandbox-cflint'
sh 'box cflint reportLevel=ERROR'
}
}
}
}
</code></pre>
Using Vue CLI 3 with a Coldbox Application2018-08-27T16:58:00Zhttps://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html<h3 id="tldr" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#tldr">TL;DR</a></h3>
<p>The Vue CLI is a great tool for developing Vue.js applications but I wanted to see how it could be integrated into an application with a CFML/Coldbox back-end. I think the key to doing this is to use the <a href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#webpack-development-server">Webpack Development Server</a> <code>proxy</code> setting to proxy requests to the CFML back-end application. I also adjusted the front-end and back-end routing to work well together. I created this <a href="https://github.com/jsteinshouer/guitar-tabs-vue">Github repository</a> that contains a simple Vue.js Single-Page Application (SPA) with a Coldbox back-end.</p>
<h3 id="introduction" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#introduction">Introduction</a></h3>
<p>I recently took this <a href="https://www.udemy.com/vuejs-2-the-complete-guide/">Udemy Course</a> on Vue.js. I learned a lot from the course and would recommend it to anyone interested in learning Vue.js. As the course progressed into more advanced exercises and projects the instructor had me use the Vue CLI to scaffold and run my Vue.js applications. I liked how easy it was to use. I could easily run a built-in webpack development server which does something called <a href="https://webpack.js.org/concepts/hot-module-replacement/">Hot Module Replacement</a>. Basically, this means that you can see your changes in real time without needing to reload the application. It is just as easy to build a minified version for production.</p>
<p>I have been wanting to try using the <a href="https://cli.vuejs.org/">Vue CLI</a> to develop a Vue.js Single-Page Application(SPA) with a Coldbox back-end. I thought I would document my steps here for myself and maybe it will help someone else too.</p>
<h3 id="coldbox-rest-api" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#coldbox-rest-api">Coldbox REST API</a></h3>
<p>To start I will use CommandBox create a Coldbox application using the REST template.</p>
<pre><code>coldbox create app name = "My App" skeleton=rest
</code></pre>
<p>I will start the CommandBox server and run it on port 3000.</p>
<pre><code>server start port=3000 rewritesEnable=true
</code></pre>
<h3 id="vue-cli-installation" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#vue-cli-installation">Vue CLI Installation</a></h3>
<p>The course I mention uses Vue CLI v2 but since Vue CLI v3 was recently released I decided to use that. Here is the command to install it globally.</p>
<pre><code>npm install @vue/cli -g
</code></pre>
<h3 id="generate-the-vue.js-application" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#generate-the-vue.js-application">Generate The Vue.js Application</a></h3>
<p>I put the Vue.js application in a directory called <code>client-app</code>. I kept all defaults when prompted to select options. The <code>--git false</code> flag will tell Vue CLI to not initiate a Git repository.</p>
<pre><code>vue create client-app --git false
</code></pre>
<p>This will generate the base Vue.js app. You can see it running by doing this.</p>
<pre><code>cd client-app
npm run serve
</code></pre>
<h4 id="vue.js-plugins" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#vue.js-plugins">Vue.js Plugins</a></h4>
<p>Since this is a SPA I will use the Vue.js router. I can add it using this command.</p>
<pre><code>vue add router
</code></pre>
<p>I plan to use Vuex for state management in this application so I add that as well.</p>
<pre><code>vue add vuex
</code></pre>
<p>I added Axios for communicating with the backend API.</p>
<pre><code>vue add axios
</code></pre>
<p>Finally, I add <a href="https://vuetifyjs.com/en/getting-started/quick-start">Vuetify</a> which is a Material Design component library for Vue.js. I just accept all the default options for the generator.</p>
<pre><code>vue add vuetify
</code></pre>
<h3 id="vue-cli-config" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#vue-cli-config">Vue CLI Config</a></h3>
<h4 id="asset-directory" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#asset-directory">Asset Directory</a></h4>
<p>You can create a file named <code>vue.config.js</code> in the client-app directory. This file will contain a Javascript object with the <a href="https://cli.vuejs.org/config/">configuration options</a> that will get used by the Vue CLI when running the development server or building your application for production.</p>
<p>The first configuration option that I set is <code>assetsDir</code>. This tells the Vue CLI where to put the <code>js</code>, <code>css</code>, <code>img</code>, and <code>fonts</code> files and folders. I set this to <code>assets</code> since that is where I will put them in the Coldbox application.</p>
<pre><code>module.exports = {
assetsDir: "assets"
}
</code></pre>
<h4 id="webpack-development-server" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#webpack-development-server">Webpack Development Server</a></h4>
<p>I will also set two settings for the webpack development server. They are <code>devServer.port</code> and <code>devServer.proxy</code>. The port setting tells which port to run the front-end development server on. I set this to 3001. The proxy setting is used to proxy requests from the front-end server to the back-end Coldbox application. Since I started my CommandBox server on port 3000. I set this setting to <code>http://localhost:3000</code>. My full config file looks like this.</p>
<pre><code>module.exports = {
assetsDir: "assets",
devServer: {
port: 3001,
proxy: 'http://localhost:3000'
}
}
</code></pre>
<h3 id="commandbox-scripts" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#commandbox-scripts">CommandBox Scripts</a></h3>
<p>I added a couple scripts to my <code>box.json</code> file that can be run using the CommandBox <code>run-script</code> command. The first is named <code>dev</code> which just starts the front-end and back-end servers for development. The second is <code>build</code> which runs a CommandBox task runner named build. This runs the client build process and copies the assets to the Coldbox asset directory. It also takes the <code>index.html</code> file that is generated copies it to <code>views/main/index.cfm</code> which is the Coldbox default view.</p>
<pre><code>"scripts":{
"dev" : "start --force && !npm run serve --prefix ./client-app",
"build" : "task run ./build/scripts/build"
}
</code></pre>
<p>You can view the full task runner here on <a href="https://github.com/jsteinshouer/guitar-tabs-vue/blob/master/build/scripts/Build.cfc">Github</a>.</p>
<h3 id="routing" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#routing">Routing</a></h3>
<h4 id="vue.js-router" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#vue.js-router">Vue.js Router</a></h4>
<p>Here I changed the Vue.js router mode from hash mode to history mode. This takes the hash out of the URL and achieves a cleaner navigation.</p>
<p><code>client-app/src/router.js</code></p>
<pre><code>export default new Router({
mode: 'history',
routes: [
...
]
})
</code></pre>
<h4 id="coldbox" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/08/27/vue-cli-3-with-coldbox.html#coldbox">Coldbox</a></h4>
<p>I set up a default wildcard Coldbox route that points to the view <code>views/main/index.cfm</code>. This allows any link other than API routes to load the client application.</p>
<p><code>config/Router.cfc</code></p>
<pre><code>route(pattern=".*",handler="Main",action="index").end();
</code></pre>
<p>If you would like to see more of the code <a href="https://github.com/jsteinshouer/guitar-tabs-vue">here is a github repository</a> of the application I have started working on.</p>
OWASP TOP 10 2017 A2-Broken Authentication: Password Guidance2018-07-13T16:58:00Zhttps://jasonsteinshouer.com/2018/07/13/owasp-top-10-broken-authentication-password-guidance.html<p>One of the recommendations I mention in <a href="https://jasonsteinshouer.com/2018/06/30/owasp-top-10-broken-authentication-password-requirements.html">OWASP TOP 10 2017 A2-Broken Authentication: Password Requirements</a> is to provide guidance to your users when creating passwords. Here I attempt to explore some ways of implementing feedback to users on the strength of their passwords.</p>
<p>I came across a password estimation library called <a href="https://github.com/dropbox/zxcvbn">zxcvbn</a> built by some folks at Dropbox. Previous ways of providing user’s feedback on password strength may have been inaccurate feedback because they meet certain length and complexity requirements.</p>
<p>The library uses pattern matching and data comprised of commonly used passwords, common names, dictionary words, and other patterns that may make a password easily guessable. Using its algorithms it seems to be able to provide better and more accurate feedback on the strength of a password.</p>
<p>I found this <a href="https://css-tricks.com/password-strength-meter/">example implementation</a> of a password meter built using this library. It inspired me to build this slightly different version with Vue.js and Bootstrap that you can see here.</p>
<p data-height="600" data-theme-id="0" data-slug-hash="YjPaKv" data-default-tab="result" data-user="jsteinshouer" data-embed-version="2" data-pen-title="Password Meter" class="codepen">See the Pen <a href="https://codepen.io/jsteinshouer/pen/YjPaKv/">Password Meter</a> by Jason Steinshouer (<a href="https://codepen.io/jsteinshouer">@jsteinshouer</a>) on <a href="https://codepen.io/">CodePen</a>.</p>
<script async="" src="https://static.codepen.io/assets/embed/ei.js"></script>
<p>I implemented it as a Vue.js component. Which takes two properties one is the <code>password</code> and the other is <code>target</code> which should be a selector for the password input. This allows it to focus the boostrap popover that on the input element. The popover is used to display the user suggestions and warnings.</p>
<p>There are two data properties. The first is <code>score</code> which is used to power the meter (0 - 4). The second is <code>info</code> which will contain any suggestions or warnings to the user. A bootstrap popover is used to display the contents of the info property.</p>
<pre><code class="language-javascript">Vue.component('password-meter', {
template: ' <div id="password-strength-meter" @click="showInfo">\
<meter max="4" :value="score"></meter> \
<span class="text-muted">Password Strength: \
<span class="glyphicon glyphicon-info-sign" v-if="info"></span> \
</span> \
</div>',
props: ["password","target"],
data: function() {
return {
score: 0,
info: ""
}
},
...
</code></pre>
<p>I am using a Vue.js computed property to convert the score to a word indicating the current password strength.</p>
<pre><code class="language-javascript">...
computed: {
strength: function() {
switch( this.score ) {
case 0: case 1:
return "Weak";
break;
case 2:
return "Medium";
break;
case 3:
return "Good";
break;
case 4:
return "Great";
break;
}
}
},
...
</code></pre>
<p>Here I am using <a href="https://underscorejs.org/">underscore.js</a> for providing debounce utility for running the estimation when the password changes. This may not be necessary but I figure it doesn’t hurt.</p>
<pre><code class="language-javascript">...
created: function() {
this.debouncedGetEstimate = _.debounce(this.getEstimate, 200);
},
watch: {
password: function() {
this.debouncedGetEstimate();
}
},
...
</code></pre>
<p>I have a <code>showInfo</code> method that is triggered when the user clicks the information icon. It triggers the popover with suggestions or warnings to display on the password input.</p>
<pre><code class="language-javascript">...
methods: {
showInfo: function() {
var vm = this;
if (vm.info) {
$( this.target ).popover({
content: function() {
return vm.info;
},
title: vm.strength,
trigger: "manual",
container: 'body'
});
$( this.target ).popover("toggle");
}
},
...
</code></pre>
<p>Finally, the <code>getEstimate</code> method calls the zxcvbn library to get an estimate of the password. The result contains the score and feedback. It contains other data that I am currently not using such as an estimated amount of time it would take to crack the password.</p>
<pre><code class="language-javascript">...
getEstimate: function() {
var estimate = zxcvbn( this.password );
this.score = estimate.score;
if (estimate.feedback.warning && estimate.feedback.warning.length && this.score < 3) {
this.info = estimate.feedback.warning + "\n";
this.info += "\n\n" + estimate.feedback.suggestions.join("\n");
}
else if (estimate.feedback.suggestions && estimate.feedback.suggestions.length && this.score < 3) {
this.info = estimate.feedback.suggestions.join("\n");
}
else {
this.info = "";
}
$( this.target ).popover("destroy");
}
}
});
</code></pre>
<p>Here is the html markup.</p>
<pre><code class="language-markup"><div class="input-group">
<input :type="passwordFieldType" name="password" id="password" v-model="password" class="form-control input-lg" placeholder="Password" required>
<span class="input-group-addon pointer" @click="showPassword = !showPassword">
<span class="glyphicon" :class="{'glyphicon-eye-open': !showPassword, 'glyphicon-eye-close': showPassword}" aria-hidden="true"></span>
</span>
</div>
<password-meter :password="password" target="#password"></password-meter>
</code></pre>
<p>Here is a <a href="http://jsfiddle.net/steinshouerj/r67e4fcj">jsfiddle</a> of it as well.</p>
<p>If you are interested in possibly using the library for password validation on the server. The project has several ports to other languages. <a href="https://github.com/GoSimpleLLC/nbvcxz">nbvcxz</a> is a Java port of the estimation tool that can be used in CFML to do the estimation on the server side.</p>
<p>This <a href="https://github.com/jsteinshouer/owasp-demo-cfml">GitHub Repo</a> contains an example of doing it on the server side using the Java library. It is a sample Coldbox application I created to demonstrate some ways for preventing some of the OWASP Top 10 vulnerabilities. If you are interested take a look at these files.</p>
<ul>
<li><a href="https://github.com/jsteinshouer/owasp-demo-cfml/blob/master/models/security/PasswordStrengthEstimator.cfc">models/security/PasswordStrengthEstimator.cfc</a></li>
<li><a href="https://github.com/jsteinshouer/owasp-demo-cfml/blob/master/models/security/PasswordStrengthEstimatorResult.cfc">models/security/PasswordStrengthEstimatorResult.cfc</a></li>
<li><a href="https://github.com/jsteinshouer/owasp-demo-cfml/blob/master/models/security/PasswordService.cfc">models/security/PasswordService.cfc</a></li>
</ul>
<h3 id="related-posts" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/07/13/owasp-top-10-broken-authentication-password-guidance.html#related-posts">Related Posts</a></h3>
<ul>
<li><a href="https://jasonsteinshouer.com/2018/06/30/owasp-top-10-broken-authentication-password-requirements.html">OWASP TOP 10 2017 A2-Broken Authentication: Password Requirements</a></li>
<li><a href="https://jasonsteinshouer.com/2018/06/30/implementing-a-password-blacklist.html">Implementing a Password Blacklist in CFML</a></li>
</ul>
Implementing a Weak Password Blocklist in CFML2018-06-30T10:17:00Zhttps://jasonsteinshouer.com/2018/06/30/implementing-a-weak-password-blocklist.html<p>As mentioned in <a href="https://jasonsteinshouer.com/2018/06/30/owasp-top-10-broken-authentication-password-requirements.html">OWASP TOP 10 2017 A2-Broken Authentication: Password Requirements</a> the new guidelines for password requirements say to drop all the complexity rules and to create a blocklist of weak, common, and compromised passwords that cannot be used when a user creates a new account. Here is a quick example of one way this could be implemented in CFML.</p>
<p>This <a href="https://github.com/danielmiessler/SecLists/tree/master/Passwords">Github Repository</a> contains many lists of weak, common, and leaked passwords. The <a href="https://www.owasp.org/index.php/Top_10-2017_A2-Broken_Authentication">OWASP Broken Authentication article</a> refers to it as a source for creating such a list. I chose the following list of 10,000 common passwords for this example.</p>
<p><a href="https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10k-most-common.txt">GitHub · danielmiessler/SecLists/Common-Credentials/10k-most-common.txt</a></p>
<p>I started off by creating a component named <code>PasswordService.cfc</code> that takes the file path to the file downloaded from Github. In this example, I am using Wirebox to inject a setting that contains the file path. The file is read and the contents are loaded into an array. You could also load these into a database table and load them in from there.</p>
<pre><code class="language-cfscript">component singleton {
property name="PASSWORD_BLOCKLIST";
property name="wirebox" inject="wirebox";
/**
* Constructor
*
* @passwordblocklistFile.inject coldbox:setting:PASSWORD_blocklist_FILE
*/
public PasswordService function init( required string passwordblocklistFile ) {
PASSWORD_BLOCKLIST = listToArray(
fileRead( arguments.passwordblocklistFile ),
chr(10) & chr(13)
);
return this;
}
</code></pre>
<p>I then created a method to check a password against the blocklist.</p>
<pre><code class="language-cfscript">public boolean function isAllowed( required string password ) {
return !PASSWORD_BLOCKLIST.find( lcase( arguments.password ) );
}
</code></pre>
<p>I can then use that in my <code>validatePassword</code> method.</p>
<pre><code class="language-cfscript">public models.util.ValidationResult function validatePassword( required string password ) {
var validationResult = wirebox.getInstance("util.ValidationResult");
/* Must be at least 8 characters long */
if ( arguments.password.len() < 8 ) {
validationResult.addError("The password must be at least 8 characters.");
}
/* Not on the blocklist */
if ( !isAllowed( arguments.password ) ) {
validationResult.addError("Your password is not allowed because it is too common!");
}
return validationResult;
}
</code></pre>
OWASP TOP 10 2017 A2-Broken Authentication: Password Requirements2018-06-30T09:30:00Zhttps://jasonsteinshouer.com/2018/06/30/owasp-top-10-broken-authentication-password-requirements.html<p>Continuing the series on the OWASP Top 10 now we look at the #2 OWASP vulnerability which is <a href="https://www.owasp.org/index.php/Top_10-2017_A2-Broken_Authentication">Broken Authentication</a>. One of the things that have been a problem with authentication is weak passwords. In this post, we will look at how best practices for user password creation have changed.</p>
<p>For a long time, it has been considered best practice for applications to implement password complexity rules that require a combination of upper case, lower case, numbers, and special character. Users would end up using a less secure password because these requirements were not user-friendly. These rules made passwords hard for people to remember and easy for computers to guess, as the following <a href="https://xkcd.com/">xkcd.com</a> cartoon illustrates.</p>
<p><img src="https://imgs.xkcd.com/comics/password_strength.png" alt="xkcd.com" /></p>
<h2 id="new-nist-password-guidelines" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/06/30/owasp-top-10-broken-authentication-password-requirements.html#new-nist-password-guidelines">New NIST Password Guidelines</a></h2>
<p>The industry has recognized that current industry practices have problems when it comes to security. Also, the best practices will continue to evolve and change with time. The National Institute for Standards and Technology (NIST) recently published (June 2017) new recommendations to try to address some of the issues as well as factor in new technology and best practices.</p>
<p><strong>Length > Complexity</strong></p>
<p>The old complexity requirements have made it very difficult for people to remember passwords. This encourages people to reuse the same password across all the services they use which is bad for security. The new guidelines say to drop the complexity requirements stick to length requirements. See</p>
<p><strong>Password Length</strong></p>
<p>The new guidelines say to require a minimum of 8 characters. The maximum allowed should be at least 64 characters. This is to support generated passwords from password managers.</p>
<p><strong>Allow all ASCII and UNICODE characters</strong></p>
<p>All ASCII and UNICODE characters should be allowed. This includes spaces, emoji, etc.</p>
<p><strong>Eliminate any other complexity requirements</strong></p>
<p>Any other complexity requirements should be removed.</p>
<p><strong>Password Guidance</strong></p>
<p>Offer guidance to the user on creating a strong passphrase. This includes some examples of what a passphase is and how to create one that is strong and easy for them to remember. There is also mention of using meters to show the user how strong their password is.</p>
<p><strong>Remove password change requirements</strong></p>
<p>The new guidelines say that enforcing periodic password changes is not beneficial to online security and should be removed as well.</p>
<p><strong>Allow copy and paste</strong></p>
<p>This recommendation is to support password managers as there are seen as a tool that generally makes online security better.</p>
<p><strong>Allow users to see passwords</strong></p>
<p>To make it more user-friendly allow the user to see the password as they are entering it in a registration form.</p>
<p><strong>Implement a password blacklist</strong></p>
<p>Dropping the password complexity requirements doesn’t mean you should allow weak passwords. The new guidelines prescribe implementing a blacklist of weak passwords that cannot be used. This may also contain passwords that have been leaked from other services that have been hacked.</p>
<h3 id="resources" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/06/30/owasp-top-10-broken-authentication-password-requirements.html#resources">Resources</a></h3>
<ul>
<li><a href="https://www.owasp.org/index.php/Top_10-2017_A2-Broken_Authentication">Top 10-2017 A2-Broken Authentication - OWASP</a></li>
<li><a href="https://pages.nist.gov/800-63-3/sp800-63b.html#memsecret">NIST Special Publication 800-63B</a></li>
<li><a href="https://xkcd.com/936/">xkcd: Password Strength</a></li>
</ul>
<h3 id="related-posts" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/06/30/owasp-top-10-broken-authentication-password-requirements.html#related-posts">Related Posts</a></h3>
<ul>
<li><a href="https://jasonsteinshouer.com/2018/06/30/implementing-a-password-blacklist.html">Implementing a Password Blacklist in CFML</a></li>
</ul>
Secure CFML: OWASP TOP 10 2017 A1-Injection2018-05-18T13:40:00Zhttps://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html<p>I am working on some training materials for my team on preventing the OWASP Top 10 vulnerabilities when using CFML to write applications. I thought it would be good to try and do some corresponding blog posts to go along with the training.</p>
<p>In this post, I will focus on the #1 OWASP vulnerability which is <a href="https://www.owasp.org/index.php/Top_10-2017_A1-Injection">Injection</a> and how to prevent it in CFML applications. Injection is when an attacker can use input parameters to inject malicious code to be executed by the application.</p>
<p>The most common and well-known type of injection vulnerability is SQL Injection but there are many others. If user input is not validated or sanitized it is a possible attack vector for injection.</p>
<h3 id="sql-injection" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#sql-injection">SQL Injection</a></h3>
<p>This is a common weakness of data-driven web applications. It occurs when malicious SQL code is injected as an input parameter and then executed on the database. Here is an example of a query in CFML that it vulnerable to SQL injection.</p>
<pre><code class="language-cfscript">userQuery = queryExecute("
select
p_user,
first_name,
last_name,
email,
phone,
from users
where p_user = #url.userID#
");
</code></pre>
<p>An attacker could use a query string like this<code>?userid=1 or 1=1</code> see all records in a table. Also, someone could delete or modify data. In this example all data from the table named clients could be deleted by passing the url query string <code>?userid=1;delete from clients</code></p>
<h4 id="prevention" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#prevention">Prevention</a></h4>
<p>Use query parameterization to prevent these types of attacks. In CFML you can use the <a href="http://cfdocs.org/cfqueryparam"><code><cfqueryparam></code></a> tag to do this. Below is an example of using a query parameter with the <a href="http://cfdocs.org/queryExecute"><code>queryExecute</code></a> function.</p>
<pre><code class="language-cfscript">userQuery = queryExecute("
select
p_user,
first_name,
last_name,
email,
phone,
from users
where p_user = :userID
",{ userID = url.userid });
</code></pre>
<p>This will throw an error if malicious parameters are passed in.</p>
<h3 id="orm-injection" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#orm-injection">ORM Injection</a></h3>
<p>ORM queries are also vulnerable as to injection. Be sure to use sql parameters when filtering and searching in ORM as well.</p>
<pre><code class="language-cfscript">ORMExecuteQuery("
FROM accounts
WHERE customerID = :customerID
",{
customerID = url.customerID
}, true);
</code></pre>
<h3 id="ldap-injection" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#ldap-injection">LDAP Injection</a></h3>
<p>LDAP queries using <a href="http://cfdocs.org/cfldap"><code>cfldap</code></a> are also vulnerable to SQL injection. Use the built-in ESAPI <a href="http://cfdocs.org/encodeForDN"><code>encodeForDN()</code></a> method to sanitize parameters used in a cfldap queries like the example below.</p>
<pre>
<cfldap
server="ServerName"
port=636
action="QUERY"
name="qLDAP"
secure="CFSSL_BASIC"
username="mydomain\#ldapUsername#"
password="#ldapPassword#"
start="dc=MYDOMAIN,dc=MYTLD"
attributes="cn,userPrincipalName,title,mail,thumbnailPhoto"
filter="(sAMAccountName=<b>#encodeForDN(username)#</b>)">
</pre>
<h3 id="xmlxpath-injection" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#xmlxpath-injection">XML/Xpath Injection</a></h3>
<p>If your application performs searches in XML you can use <a href="http://cfdocs.org/encodeForXPath"><code>encodeForXPath()</code></a> to sanitize user input.</p>
<pre><code class="language-cfscript">encodeForXPath("'or 1=1",false)
</code></pre>
<h3 id="operating-system-(os)-command-injection" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#operating-system-(os)-command-injection">Operating System (OS) Command Injection</a></h3>
<p>Executing native operating system commands with user input is also vulnerable to attack. See example below.</p>
<pre><code class="language-cfscript">cfexecute(
name="c:\windows\system32\cmd.exe",
arguments="/c ping #url.host#",
variable="output",
timeout="10"
);
</code></pre>
<p>In this example, an attacker could append as many other OS commands to be executed. For example:</p>
<p><code>?host=localhost %26 echo you have been pwned > badscript.bat %26 badscript.bat</code></p>
<p>This would create a script named badscript.bat then execute it.</p>
<h4 id="prevention-1" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#prevention-1">Prevention</a></h4>
<ul>
<li>Avoid using OS commands and use native Java libraries instead when available which should cover most cases.</li>
<li>Use ESAPI <a href="https://static.javadoc.io/org.owasp.esapi/esapi/2.0.1/org/owasp/esapi/Encoder.html#encodeForOS(org.owasp.esapi.codecs.Codec,%20java.lang.String)"><code>encodeForOS()</code></a> method to escape special characters</li>
<li>Use strict validation by only allowing certain words or characters.</li>
</ul>
<p>The ESAPI method is not available directly in Coldfusion at the time of this writing but you can access it with Java. See example below.</p>
<pre><code class="language-cfscript">ESAPIEncoder = createObject("java", "org.owasp.esapi.ESAPI").encoder();
WINDOWS_CODEC = createObject("java", "org.owasp.esapi.codecs.WindowsCodec").init();
cfexecute(
name="c:\windows\system32\cmd.exe",
arguments="/c ping #ESAPIEncoder.encodeForOS(WINDOWS_CODEC,url.host)#",
variable="output",
timeout="10"
);
</code></pre>
<h3 id="code-injection" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#code-injection">Code Injection</a></h3>
<p>This is when an attacker can inject language specific code that is then executed by the language interpreter. In CFML this is when <code>evaluate</code> or <code>iif</code> is used. <strong>Avoid using these operators on untrusted input.</strong></p>
<!--
EXAMPLE OF BAD THINGS
```markup
<cfset key_list = evaluate("key_list_" & url.key_list)>
```
```
?key_list_=xyz&key_list= eq 'abc' or setVariable("session.login_role", "Administrator") eq "Admi nistrator"
```
-->
<h3 id="summary" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#summary">Summary</a></h3>
<h4 id="perform-proper-input-validation" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#perform-proper-input-validation">Perform proper input validation</a></h4>
<ul>
<li>Whitelist validation</li>
<li>Use functions such as <code>isValid()</code> to validate user input</li>
</ul>
<h4 id="use-a-safe-api" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#use-a-safe-api">Use a safe API</a></h4>
<ul>
<li>SQL Parameters</li>
<li><a href="https://cfdocs.org/encodefor">ESAPI Functions (encodeFor*)</a></li>
</ul>
<h4 id="contextually-escape-user-data" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#contextually-escape-user-data">Contextually escape user data</a></h4>
<ul>
<li>If library is not available use <code>replace()</code> or <code>reReplace()</code> to remove unwanted characters.</li>
</ul>
<h3 id="resources" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/18/owasp-top-10-injection.html#resources">Resources</a></h3>
<ul>
<li><a href="https://www.owasp.org/index.php/Top_10-2017_A1-Injection">Top 10-2017 A1-Injection</a></li>
<li><a href="https://www.adobe.com/content/dam/acom/en/products/coldfusion/pdfs/cf11/cfml-developer-security-guide.pdf">CFML Developer Security Guide</a></li>
<li><a href="https://localhost.fm/2018/05/04/owasp-top-10.html">Localhost Podcast</a></li>
<li><a href="https://cfdocs.org/security">cfdocs.org</a></li>
<li><a href="https://www.petefreitag.com/">www.petefreitag.com</a></li>
</ul>
Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations2018-05-08T06:00:00Zhttps://jasonsteinshouer.com/2018/05/08/accelerate-book-notes.html<p>The book <a href="https://read.amazon.com/kp/kshare?asin=B07B9F83WM&id=7tIJy7ClSviSgwQwU2xJpQ&reshareId=4BX8T28SB0MYEH4TTASK&reshareChannel=system">Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations</a> by Nicole Forsgren Ph.D., Jez Humble, and Gene Kim warn that organizations should be learning continuous delivery so they do not fall behind in a world where IT is critical to remain competitive in any industry. This is based on research they have performed through years of creating the <a href="https://puppet.com/resources/whitepaper/state-of-devops-report">State of DevOps Report</a>. They tracked metrics such as the amount of time it takes to deliver software changes, how often they deploy to production, change fail rate, and time to restore service when a failure occurs. By analyzing all the data they found that these <a href="https://devops-research.com/assets/transformation_practices.pdf">24 capabilities</a> are where the high-performing organizations are practicing.</p>
<p>This is not a technical book but clearly explains what these practices are and how they are used to get better results. The book also does a nice job of describing exactly how the research was executed if you are interested in that. This was enjoyable to read and I recommend it to anyone involved in the delivering software products.</p>
<p>I am also including some of my notes on things I thought were particularly interesting.</p>
<h2 id="my-notes" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/08/accelerate-book-notes.html#my-notes">My Notes</a></h2>
<h3 id="software-development-practices" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/08/accelerate-book-notes.html#software-development-practices">Software Development Practices</a></h3>
<p><strong>Automate your deployment process</strong></p>
<ul>
<li>In order to do continuous delivery, you need to have an automated deployment process that does not require manual intervention.</li>
</ul>
<p><strong>Automated testing</strong></p>
<ul>
<li>Practice TDD to improve overall code design and readability. As a by-product, you get some automated regression testing.</li>
<li>Testers are still important., they are just focused on acceptance testing and exploratory testing</li>
</ul>
<p><strong>Test data management</strong></p>
<ul>
<li>The practice of management data sets needed for automated testing. <strong>SHOULD NOT USE PRODUCTION DATA</strong></li>
</ul>
<p><strong>Implement Continuous Integration</strong></p>
<ul>
<li>Teams should have a way to be confident their changes integrate with other developers changes. To have this confidence an automated build and testing process should be implemented.</li>
</ul>
<p><strong>Use trunk-based development methods</strong></p>
<ul>
<li>This one surprised me a little but once I thought about it it makes sense because it lines up with the practice of continuous integration. The sooner the developer finds out there is a conflict with a change they made the easier and faster it will be to resolve that conflict. If you do development isolated in your own branch for long periods (More than a day) a lot of time is spent trying to integrate it back into the trunk.</li>
<li><a href="http://www.davefarley.net/?p=247">Continuous Integration and Feature Branching</a></li>
</ul>
<p><strong>Shift left on security</strong></p>
<ul>
<li>Security should be considered in all parts of the software development lifecycle and not just an afterthought.</li>
</ul>
<p><strong>Use a loosely coupled architecture</strong></p>
<ul>
<li>Break applications down to smaller prices that are easier to manage and can be deployed independently from the rest of the application (Microservices)</li>
</ul>
<h3 id="lean-product-development" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/08/accelerate-book-notes.html#lean-product-development">Lean Product Development</a></h3>
<ul>
<li>Many of the concepts are based on concepts developed by Toyota using what became know as <a href="https://en.wikipedia.org/wiki/Lean_manufacturing">Lean Manufacturing</a></li>
</ul>
<p><strong>Work in small batches</strong></p>
<ul>
<li>Work should be broken into small pieces that can be completed in less than a week. This shortens the feedback loop and allows for more frequent deployment.</li>
</ul>
<blockquote>
<p>The key to working in small batches is to have work decomposed into features that allow for rapid development, instead of complex features developed on branches and released infrequently. This idea can be applied at both the feature and the product level. An MVP is a prototype of a product with just enough features to enable validated learning about the product and its business model. Working in small batches enables short lead times and faster feedback loops. In software organizations, the capability to work and deliver in small batches is especially important because it allows you to gather user feedback quickly using techniques such as A/B testing. It’s worth noting that an experimental approach to product development is highly correlated with the technical practices that contribute to continuous delivery.</p>
</blockquote>
<p><strong>Gather and implement customer feedback</strong></p>
<ul>
<li>Seek customer feedback and let it drive the design of the product.</li>
</ul>
<h3 id="leadership-management-practices" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/08/accelerate-book-notes.html#leadership-management-practices">Leadership / Management Practices</a></h3>
<p><strong>Have a lightweight change approval processes</strong></p>
<ul>
<li>Use peer reviews such as pair programming or intrateam code reviews instead of change approval boards or other gatekeepers</li>
</ul>
<p><strong>Architect for empowered teams</strong></p>
<ul>
<li>Let the team choose the tools they use. Do not dictate to them.</li>
</ul>
<p><strong>Foster and enable team experimentation</strong></p>
<ul>
<li>Allow developers to experiment and implement their ideas without the need for outside approval</li>
</ul>
<p><strong>Quality and Saftey should be a pre-requisite</strong></p>
<blockquote>
<p>Too often, quality is overshadowed by the pressure for speed. A courageous and supportive leader is crucial to help teams “slow down to speed up,” providing them with the permission and safety to put quality first (fit for use and purpose) which, in the long run, improves speed, consistency, and capacity while reducing cost, delays, and rework. Best of all, this improves customer satisfaction and trust</p>
</blockquote>
<ul>
<li>Should not need to have permission to do automated testing</li>
</ul>
<p><strong>Support a generative culture</strong></p>
<ul>
<li>Establish a dedicated training budget and make sure people know about it</li>
<li>Let people choose which training interests them</li>
<li>Dedicated time to explore side projects</li>
<li>Encourage staff to attend conferences</li>
<li>Set up internal hack days, where cross-functional teams can get together to work on a project</li>
<li>Lightning talks</li>
<li>Encourage teams to organize internal “yak days,” where teams get together to work on technical debt</li>
<li>Hold regular internal DevOps mini-conferences</li>
</ul>
<h3 id="general-notes" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2018/05/08/accelerate-book-notes.html#general-notes">General Notes</a></h3>
<ul>
<li><a href="https://www.mckinsey.com/industries/financial-services/our-insights/ings-agile-transformation">ING - Experimenting with different ways of working</a>
<ul>
<li>The last chapter talked about transformational leadership and how ING is implementing these practices</li>
<li><a href="https://www.mckinsey.com/~/media/McKinsey/Industries/Financial%20Services/Our%20Insights/INGs%20agile%20transformation/SVG%20QWeb_ING_ex1.ashx">Org Structure</a></li>
</ul>
</li>
<li><strong>Make it your own</strong> Organizations need to develop what works for them instead of adopting everything by the book. No one org will do it the same. Orgs should be evolving and adjusting as time goes on (Continuous Improvement).</li>
</ul>
<blockquote>
<p>Practice practice. You just have to try it: learn, succeed, fail, learn, adjust, repeat. Rhythm and routine, rhythm and routine, rhythm and routine . . .</p>
</blockquote>
<ul>
<li><strong>Work smarter not harder</strong></li>
</ul>
<blockquote>
<p>In 2017, we saw low performers lose some ground in stability. We suspect this is due to attempts to increase tempo (“work harder!”) which fail to address the underlying obstacles to improved overall performance (for example, rearchitecture, process improvement, and automation)</p>
</blockquote>
Linting Your CFML code with CFLint and Sublime Text Build Systems2018-03-31T06:35:00Zhttps://jasonsteinshouer.com/2018/03/31/running-cflint-with-sublime-text-build-systems.html<p>I know there is a Sublime Text 3 package for CFLint but I am still using ST 2. Here is an easy way to run <a href="https://github.com/cflint/CFLint">CFLint</a> on files from within ST2 or ST3 using <a href="http://sublimetext.info/docs/en/reference/build_systems.html">build systems</a>.</p>
<p>A Sublime build system is configured using JSON. Here I am using a <a href="https://www.forgebox.io/view/commandbox-cflint">CommandBox CFLint</a> module to run CFLint.</p>
<pre><code class="language-json">{
"cmd": ["box.exe", "cflint", "$file_name"],
"selector": "source.cfscript, text.html.cfm",
"working_dir": "$file_path",
"variants": []
}
</code></pre>
<p>Then save this to a file name <code>CFLint.sublime-build</code> in your Sublime Text user package directory. Mine is located here on Windows.</p>
<p><code>C:\Users\<username>\AppData\Roaming\Sublime Text 2\Packages\User</code></p>
<p>If you go to <code>Tools > Build System</code> you can select <code>Automatic</code> as your build system. Sublime Text will automatically detect when you are working in a <code>.cfm</code> or <code>.cfc</code> file and run the command when you press <code>Ctrl+B</code>. You can also select CFLint from the menu to explicitly say you want to use it as your build system.</p>
<p>When you press <code>Ctrl+B</code> to run it the console will open at the bottom, showing the output from the <code>cflint</code> CommandBox command. You can then press <code>Esc</code> to close it.</p>
<p>You can also call the CFLint jar directly without using the CommandBox module. Using this <code>sublime-build</code> configuration.</p>
<pre><code class="language-json">{
"cmd": ["java.exe", "-jar", "C:\\path\\to\\CFLint-1.3.0-all.jar", "-text", "-stdout", "-file", "$file_name"],
"selector": "source.cfscript, text.html.cfm",
"working_dir": "$file_path",
"variants": []
}
</code></pre>
<p>You can also setup variants for different varations of the command. For example here this configuration has a variant that will run CFLint on all the files in the directory with the file you are working on. To run a variant press <code>Ctrl+Shift+P</code> then type <code>build</code> and you should see the variants listed.</p>
<pre><code class="language-json">{
"cmd": ["java.exe", "-jar", "C:\\path\\to\\CFLint-1.3.0-all.jar", "-text", "-stdout", "-file", "$file_name"],
"selector": "source.cfscript, text.html.cfm",
"working_dir": "$file_path",
"variants": [
{
"name": "CFLint: Current Directory",
"cmd": [
"java.exe",
"-jar",
"C:\\path\\to\\CFLint-1.3.0-all.jar",
"-text",
"-stdout",
"-folder",
"$file_path"
]
}
]
}
</code></pre>
Example of Using a CommandBox Task Runner to Run CFLint2017-12-27T00:00:00Zhttps://jasonsteinshouer.com/2017/12/27/using-commandbox-task-runners-to-run-cflint.html<p>I have been looking into <a href="https://github.com/cflint/CFLint">CFLint</a> as a tool to help improve the quality of our teams CFML. Also, to aid in code reviews.</p>
<p>I created this <a href="https://gist.github.com/jsteinshouer/9e3556e5940f86388f9ecd91d129b78d">gist</a> that is a <a href="https://commandbox.ortusbooks.com/content/task-runners.html">CommandBox Task Runner</a> that takes a glob pattern and runs CFLint on any files that match.</p>
<pre><code class="language-bash">box task run taskFile=cflint pattern=**.cfc
</code></pre>
<p>By default, it will display the results in the console.</p>
<p><img src="https://www.dropbox.com/s/q9b10tbxe3ggrp0/cflint-console-output.PNG?dl=1" alt="Console Output" /></p>
<p>It can also generate an HTML report based on Bootstrap. See example below.</p>
<pre><code class="language-bash">box task run taskFile=cflint pattern=**.cfc --html
</code></pre>
<p><img src="https://www.dropbox.com/s/thq7ftu33ebaov6/cflint-html-results.PNG?dl=1" alt="HTML Report" /></p>
<p>You could also use it as part of an automatic build process. This will code will cause CommandBox to return an exit code of 1 if an error exists and thus cause build tools such as Jenkins to fail.</p>
<pre><code class="language-cfscript">if ( reportData.errorExists ) {
/* Flush any output to the console */
print.line().toConsole();
error("Please fix errors found by CFLint!");
}
</code></pre>
<p>CFLint can already output results to html and the console out of the box. The main reason I wrote this is that I wanted to only run CFLint on files that changed in an SVN development branch. Here is <a href="https://gist.github.com/jsteinshouer/8a21d1445a4f24be050946bb85c86136">a version</a> that uses SVN to get files for linting. It could be adapted for GIT as well which I may attempt at some point.</p>
Refactoring Legacy CFML with Approval Tests: Part II2017-10-02T20:35:00Zhttps://jasonsteinshouer.com/2017/10/02/refactoring-cfml-with-snapshot-testing-part-II.html<p>In <a href="https://jasonsteinshouer.com/2017/09/13/refactoring-cfml-with-snapshot-testing-part-I.html">part I</a> of this blog series we walked through using <a href="https://www.forgebox.io//view/testbox-snapshots">TestBox-Snapshots</a> to create some approval tests for refactoring a legacy CFML application. In this post, we will focus on setting our application up to use the <a href="https://www.ortussolutions.com/products/coldbox">ColdBox MVC framework</a>. Once it is setup we can start using the MVC pattern to separate concerns. We start by installing ColdBox.</p>
<h4 id="setup" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/10/02/refactoring-cfml-with-snapshot-testing-part-II.html#setup">Setup</a></h4>
<pre><code class="language-bash">box install coldbox
</code></pre>
<p>Now we will use the ColdBox scaffolding tools to get the ColdBox simple application template. I don’t want to overwrite my existing application so I install it in a temporary directory so that I can get the things I want from it.</p>
<pre><code class="language-bash">box mkdir .tmp
box coldbox create app directory=".tmp" name="ToDo" skeleton="simple" init=false installColdBox=false
</code></pre>
<h4 id="application.cfc" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/10/02/refactoring-cfml-with-snapshot-testing-part-II.html#application.cfc">Application.cfc</a></h4>
<p>First I will replace my existing <code>Application.cfc</code> file with the one from the template.</p>
<pre><code class="language-bash">box mv Application.cfc _Application.cfc
box cp .tmp/Application.cfc Application.cfc
</code></pre>
<p>We then add our datasource and table initialization query back into it. <a href="https://gist.github.com/jsteinshouer/784e8ab5a6ef8bd8dcc99816fa55057f">Here is a link</a> to the full modified <code>Application.cfc</code> file.</p>
<h4 id="layouts" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/10/02/refactoring-cfml-with-snapshot-testing-part-II.html#layouts">Layouts</a></h4>
<p>We will then create an empty default layout for our application.</p>
<pre><code class="language-bash">box mkdir layouts
box touch layouts/Main.cfm --open
</code></pre>
<p>Then add the following content to <code>layouts/Main.cfm</code>.</p>
<pre><code class="language-markup"><cfoutput>#renderView()#</cfoutput>
</code></pre>
<h4 id="views" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/10/02/refactoring-cfml-with-snapshot-testing-part-II.html#views">Views</a></h4>
<p>Now we create our views directory and move our <code>index.cfm</code> file into it. We do this because ColdBox has implicit views which do not require a handler. Eventually we will refactor this but for now, we will just move it. We also move the includes file into our views directory as well. In a real life legacy application, you should be able to move your entire app under views to get it up and running. See <a href="https://compknowhow.com/blog/legacy-app-meet-coldbox">this blog post</a> for more details.</p>
<pre><code class="language-bash">box mkdir views
box mv index.cfm views/index.cfm
box mkdir views/includes
box cp includes/form.cfm views/includes/form.cfm
</code></pre>
<p>Once we move our application files into views we can copy the empty <code>index.cfm</code> file into the site root.</p>
<pre><code class="language-bash">box cp .tmp/index.cfm index.cfm
</code></pre>
<h4 id="config" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/10/02/refactoring-cfml-with-snapshot-testing-part-II.html#config">Config</a></h4>
<p>We will copy the entire config directory from the ColdBox template.</p>
<pre><code class="language-bash">box cp .tmp/config config
</code></pre>
<h4 id="handlers" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/10/02/refactoring-cfml-with-snapshot-testing-part-II.html#handlers">Handlers</a></h4>
<p>To start we will not need handlers but the template comes with some application lifecycle handlers so we will copy that directory as well.</p>
<pre><code class="language-bash">box cp .tmp/handlers handlers
</code></pre>
<p>We should no longer need the ColdBox template that we downloaded to a temp directory so we can delete it.</p>
<pre><code class="language-bash">box rm .tmp --force --recurse
</code></pre>
<h4 id="routing" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/10/02/refactoring-cfml-with-snapshot-testing-part-II.html#routing">Routing</a></h4>
<p>In order to make our URLs behave the same, we need using URL rewrites and setup some application routes. So we will need to start the CommandBox server with rewrites enabled.</p>
<pre><code class="language-bash">box server start rewritesEnable=true
</code></pre>
<p>In <code>config/Routes.cfm</code> we add a route for the default page. According to <a href="https://compknowhow.com/blog/legacy-app-meet-coldbox">this blog post</a>, you will need a route for any page in your application’s root but anything in a sub-directory should work using the implicit views.</p>
<pre><code class="language-cfscript">// Your Application Routes
addRoute(pattern="/", view="index");
addRoute(pattern=":handler/:action?");
</code></pre>
<h4 id="approval-tests" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/10/02/refactoring-cfml-with-snapshot-testing-part-II.html#approval-tests">Approval Tests</a></h4>
<p>The application should now be running under ColdBox. You will notice however that if you run the approval tests now they fail. This is because our approval tests are not setup to use ColdBox. We could change our test to use <code>cfhttp</code> to get the page content or we can change our test so it is a ColdBox integration test.</p>
<p>To convert our test to a ColdBox integration test we need to do a few things. First, our test needs to inherit from the ColdBox <code>BaseTestCase</code> instead of the TestBox <code>BaseSpec</code>.</p>
<p>So this…</p>
<pre><code class="language-cfscript">component extends="testbox.system.BaseSpec"{
</code></pre>
<p>Should change to this.</p>
<pre><code class="language-cfscript">component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
</code></pre>
<p>We also need to call <code>super.beforeAll()</code> and <code>super.afterAll()</code> like so.</p>
<pre><code class="language-cfscript">// executes before all suites+specs in the run() method
function beforeAll(){
super.beforeAll();
addMatchers( "testbox-snapshots.SnapshotMatchers" );
}
// executes after all suites+specs in the run() method
function afterAll(){
super.afterAll();
}
</code></pre>
<p>Then at the start of our <code>beforeEach</code> method we need to call <code>setup()</code> to setup the ColdBox request for each test.</p>
<pre><code class="language-cfscript">beforeEach(function( currentSpec ){
setup();
...
</code></pre>
<p>In our tests, instead of using <code>cfinclude</code> we will use the <code>execute</code> method to execute the ColdBox event and get the rendered HTML from that.</p>
<pre><code class="language-cfscript">it( "should display a list of to-do items", function(){
var event = execute( event="index", renderResults=true );
content = event.getValue( name="cbox_rendered_content" );
expect(content).toMatchSnapshot();
});
</code></pre>
<p>After converted all the tests they should pass when ran. <a href="https://gist.github.com/jsteinshouer/25e1c988d9e5851c2191ee219593ca5e">Here</a> is the fully coverted test suite.</p>
<p>Now that our application is running with Coldbox we can start using the MVC design pattern to refactor our application and continue to use the approval tests as a guide.</p>
Refactoring Legacy CFML with Approval Tests: Part I2017-09-20T19:55:00Zhttps://jasonsteinshouer.com/2017/09/20/refactoring-cfml-with-snapshot-testing-part-I.html<p>In my <a href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html">previous post</a>, I discussed the importance of refactoring and a general workflow for using Approval Tests to be sure your output is not changing.</p>
<p>In this blog series, I hope to give some examples of how to use the <a href="https://www.forgebox.io//view/testbox-snapshots">TestBox-Snapshots</a> package for refactoring legacy CFML. The first post will walk you through installing the package and setting up a test suite to capture an initial snapshot.</p>
<p>I created a legacy CFML style To-Do application to use as an example of refactoring CFML using Approval Tests. Feel free to <a href="https://github.com/jsteinshouer/todo-legacy-cfml">clone the repo</a> and play along at home if you like.</p>
<pre><code class="language-bash">git clone https://github.com/jsteinshouer/todo-legacy-cfml.git
</code></pre>
<p>Then you run the application using CommandBox.</p>
<pre><code class="language-bash">box start
</code></pre>
<p>First, we need to install <a href="https://www.forgebox.io/view/testbox">TestBox</a> and the <a href="https://www.forgebox.io/view/testbox-snapshots">TestBox-Snaphots</a> package so we can take an initial snapshot.</p>
<pre><code class="language-bash">box install testbox
box install testbox-snapshots
</code></pre>
<p>Now we copy the TestBox test harness and create the specs folder for my tests.</p>
<pre><code class="language-bash">box cp ./testbox/test-harness ./tests
box mkdir ./tests/specs
</code></pre>
<p>I will create a folder in my specs directory specifically for my approval tests.</p>
<pre><code class="language-bash">box mkdir ./tests/specs/approval
</code></pre>
<p>Next, we will generate a test suite that will contain our approval tests. You can run the tests by calling the <code>/tests/runner.cfm</code> from the browser.</p>
<pre><code class="language-bash">box coldbox create bdd name="RefactorApproval" open=true directory="tests/specs/approval"
</code></pre>
<p>We then add the snapshot matchers to TestBox so we can use it for our Approval Tests. To do this we use the <code>addMatchers</code> method inside the <code>beforeAll</code> method;</p>
<pre><code class="language-cfscript">// executes before all suites+specs in the run() method
function beforeAll(){
addMatchers( "testbox-snapshots.SnapshotMatchers" );
}
</code></pre>
<p>Now we can start writing some approval tests to get our initial snapshots. First I will create a test suite named <code>RefactorApprovalTest</code> inside the <code>run()</code> method. I use the <code>beforeEach</code> method to execute a query that inserts some dummy data for testing. This will run prior to each test. It also resets the identity column on the todo table so the keys will match each time we run the test.</p>
<pre><code class="language-cfscript">describe( "RefactorApprovalTest", function(){
beforeEach(function( currentSpec ){
content = "";
/* Add some data for our tests */
queryExecute("
delete from todo;
--reset the identity so the keys match
ALTER TABLE todo ALTER COLUMN p_todo_id RESTART WITH 1;
insert into todo(p_todo_id,description,completed_date)
values
(1,'Do This',NULL),
(2,'And this',NULL),
(3,'This is done','2017-09-01 01:45:00')
");
});
});
</code></pre>
<p>For our first approval test, I use the <code>it()</code> method inside my test suite. I include the index page with no parameters and capture the output using <code>cfsavecontent</code>. Then use the snapshot matcher to check that the snapshot matches.</p>
<pre><code class="language-cfscript">it( "should display a list of to-do items", function(){
cfsavecontent(variable="content") {
include '/index.cfm';
}
expect(content).toMatchSnapshot();
});
</code></pre>
<p>With legacy code you may not always know what the code is supposed to do so you could also use a generic description such as <code>it("should match previous output with not parameters",...</code>.</p>
<p>When you run the test it will fail because it does not have a snapshot to compare to. Run the test and include <code>updateSnapshots=1</code> in the URL query string. Be sure to remove it when you start to refactor or it will create a new snapshot each time you run the tests.</p>
<p>I added more tests to cover the following three scenarios.</p>
<ol>
<li>Adding a new to-do</li>
</ol>
<pre><code class="language-cfscript">it( "should add a new to-do item", function(){
url.action = "add";
form.description = "another thing to do";
cfsavecontent(variable="content") {
include '/index.cfm';
}
expect(content).toMatchSnapshot();
});
</code></pre>
<ol start="2">
<li>Completing to-do items</li>
</ol>
<pre><code class="language-cfscript">it( "should complete a to-do item", function(){
url.action = "complete-todo";
form.id = 1;
cfsavecontent(variable="content") {
include '/index.cfm';
}
expect(content).toMatchSnapshot();
});
</code></pre>
<ol start="3">
<li>View completed to-do items</li>
</ol>
<pre><code class="language-cfscript">it( "should display a list of completed to-do items", function(){
url.action = "completed";
cfsavecontent(variable="content") {
include '/index.cfm';
}
expect(content).toMatchSnapshot();
});
</code></pre>
<p>We now have a suite of approval tests that will fail if the output changes. This will allow us to start refactoring the code with confidence. You can view the full test suite <a href="https://gist.github.com/jsteinshouer/2d42eda57b50af901b56bb5dc31555f1">here</a>. The next post we will start to refactor the code and use the test suite to verify that things have not changed.</p>
Refactoring Legacy Code with Approval Tests2017-09-09T10:26:01Zhttps://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html<p><a href="http://legacycode.rocks/">Legacycode.rocks</a> is a great resource for anyone working with legacy code. Aside from a ton of useful information, it is also motivational because they really try to take away the stigma of working with legacy code and frame it as something to really be proud of. If you work with legacy code, you know it can be frustrating and difficult however it can also be very satisfying to figure out how a piece of code works and be able to improve it.</p>
<p>One point they make, that is often overlooked, is that with a legacy application it is being maintained because it has proved to be valuable. The challenge of the maintainer is to improve it and keep it in a state where it continues to provide value.</p>
<p>I was listening to an <a href="https://www.stitcher.com/podcast/corgibytes-2/legacy-code-rocks/e/48729728">episode</a> of the <a href="http://legacycode.rocks/">legacycode.rocks</a> podcast with guest <a href="http://llewellynfalco.blogspot.com/">Llewellyn Falco</a>. In that episode they were talking about <a href="http://llewellynfalco.blogspot.com/2008/10/approval-tests.html">Approval Tests</a>.</p>
<p>This particular episode really got me thinking about how I am currently developing. Refactoring should be something that we do frequently as programmers. I realized that this I am not doing this a much as I should for a few reasons that I will get into.</p>
<h3 id="why-should-i-refactor" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html#why-should-i-refactor">Why Should I Refactor?</a></h3>
<p>A legacy application is generally defined as a system with little or no tests. It is usually something that was built by someone else that has slowly evolved over a long period of time. Chances are that it may have a considerable amount of technical debt associated with it. If we are not actively refactoring our code to make the code better then over time it will degrade and eventually get to a point where making changes to the application become very difficult and expensive.</p>
<h3 id="what-is-refactoring" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html#what-is-refactoring">What is Refactoring?</a></h3>
<p>I think that sometimes refactoring can get confused for rewriting. Refactoring is for cleaning up the code to make it easier to understand and maintain but it should not change its behavior.</p>
<p>There will be times that you need to change some code that was written by someone else or maybe yourself. You usually need to understand what the code is currently doing before modifying it. The harder the code is to understand the longer it will take to make those changes. An effective technique to try to understand the code may be to first refactor it or at least part of it.</p>
<blockquote>
<p>Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior. --Refactoring.com (Martin Fowler)</p>
</blockquote>
<p>I think that part of the reason for that fear is that I was looking at the amount of code in the application I work on. But refactoring is something that should be done in small increments. It does not seem as daunting of a task if done it small pieces over time.</p>
<h3 id="why-i-don't-refactor" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html#why-i-don't-refactor">Why I Don’t Refactor</a></h3>
<p>One reason may be that we feel like we don’t have enough time. I won’t get too deep into this but probably boils down to poor planning or organizational issues. For legacy systems, it is important to allocate time for this.</p>
<p>Another reason is that the code is complicated and we don’t understand it. I already touched on this. We can use refactoring as a technique to learn what the code is doing and to help our team and future self to better understand it.</p>
<p>The next reason is that the code is fragile and has no test coverage. So we fear that we may break it and not know if it is broken. This is a legitimate fear because if you break the system from refactoring then eventually you will be told to stop doing it.</p>
<h3 id="approval-tests-can-help" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html#approval-tests-can-help">Approval Tests Can Help</a></h3>
<p>The concept of approval tests seems simple and obvious but it had never occurred to me. It can help to give me confidence that the output has not changed when refactoring a legacy application.</p>
<p>The general concept is that you take a snapshot of the output of the code you are refactoring then verify that the output does not change.</p>
<h4 id="workflow-for-using-it" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html#workflow-for-using-it">Workflow For Using It</a></h4>
<h5 id="take-a-snapshot" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html#take-a-snapshot">Take a Snapshot</a></h5>
<p>This could be serialized data such as JSON, HTML, or even a screenshot.</p>
<h5 id="refactor" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html#refactor">Refactor</a></h5>
<p>Refactor in small increments so it is easy to rollback.</p>
<h5 id="compare-snapshots" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html#compare-snapshots">Compare Snapshots</a></h5>
<p>Take another snapshot and if the output is the same you can be fairly confident that the behavior has not changed. If they don’t match, rollback and figure out what went wrong.</p>
<h5 id="repeat" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html#repeat">Repeat</a></h5>
<p>Repeat the process in small increments.</p>
<p>I started looking into ways to do this in CFML and found that <a href="http://dev.elpete.com/">Eric Peterson</a> already built a <a href="https://www.forgebox.io//view/testbox-snapshots">TestBox matcher</a> that does this very same thing. Here is his <a href="http://dev.elpete.com/2017/05/09/snapshot-testing-in-testbox/">blog post</a> introducing it and how to use it. In an upcoming series, I will show examples of refactoring legacy CFML code using the TestBox-Snapshots matcher for approval tests.</p>
<p>Eric’s blog refers to Snapshot testing. I have also seen it referred to Golden Master but the concept seems to be the same to me. Here are some resources I used while looking into this subject. I also gave a talk to my team at work on this subject. <a href="https://www.dropbox.com/s/ob4bulgl64125aq/Refactoring%20with%20Approval%20Tests.pdf?dl=0">Here are the slides</a> if you are interested.</p>
<h3 id="resources" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/09/09/refactoring-legacy-code-with-approval-tests.html#resources">Resources</a></h3>
<ul>
<li><a href="http://dev.elpete.com/2017/05/09/snapshot-testing-in-testbox/">Snapshot Testing - dev.elpete.com</a></li>
<li><a href="http://approvaltests.com/">Approval Tests</a></li>
<li><a href="http://blog.thecodewhisperer.com/permalink/surviving-legacy-code-with-golden-master-and-sampling/">Surviving Legacy Code - blog.thecodewhisperer.com</a></li>
<li><a href="http://randycoulman.com/blog/2014/09/30/refactoring-legacy-code/">Refacoring Legacy Code - randycoulman.com</a></li>
</ul>
Playing Around with CFML and Docker: First Steps2017-05-06T13:56:00Zhttps://jasonsteinshouer.com/2017/05/06/playing-around-with-cfml-and-docker.html<p>Watching the keynote talks from this year’s Into the Box Conference gave me some inspiration to start learning more about running CFML applications in Docker. I currently run a Jekyll Docker image to develop this blog but I am still very much a newbie.</p>
<p>I know the there are some <a href="https://github.com/lucee/lucee-dockerfiles">images for Lucee</a> but haven’t had a chance to work with them. Also, The Ortus Solutions team recently released a <a href="https://www.ortussolutions.com/blog/commandbox-docker-image-360-released">new image</a> for CommandBox which allows you to run Coldfusion or Lucee inside a Docker container.</p>
<p>For my first experiment, I thought I would attempt to get the Coldbox skeleton application running using the CommandBox docker image provided by Ortus Solutions.</p>
<p>First I created a skeleton application using the Command Box scaffolding tools.</p>
<pre><code class="language-bash">mkdir docker-test
cd docker-test
box coldbox create app name="Docker Test" skeleton="AdvancedScript" installColdbox=true
</code></pre>
<p>I am somehwhat familiar with <a href="https://docs.docker.com/compose/gettingstarted/">Docker Compose</a> since that is what I use to run my blog development container. So I created the following docker-compose.yml file in the application root directory.</p>
<pre><code class="language-yml">web:
image: ortussolutions/commandbox:latest
ports:
- 8080:8080
volumes:
- .:/app
environment:
- PORT=8080
- CFENGINE=adobe@11
</code></pre>
<p>By default, it runs on port 8080 but you are free to change those values in the file if you want to use a different port. The example file is using Adobe CF 11 as the CFML engine but you can change that environment variable to be any CFML engine that Command Box supports.</p>
<p>Finally Run docker compose to create the image and run it.</p>
<pre><code class="language-bash">docker-compose up
</code></pre>
<p>Once it is running you should be able to browse <a href="http://localhost:8080/">http://localhost:8080</a> and see the Coldbox app.</p>
<h4 id="next-steps" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/05/06/playing-around-with-cfml-and-docker.html#next-steps">Next steps</a></h4>
<p>As a next step I will attempt to link it to a database service and also explore the cfconfig integration that the Ortus Solutions team describes in [their blog post] <a href="https://www.ortussolutions.com/blog/commandbox-docker-image-360-released">CommandBox Image Blog Post</a>.</p>
Mura 7 Development with CommandBox2017-03-17T07:56:00Zhttps://jasonsteinshouer.com/2017/03/17/mura-7-development-with-commandbox.html<p>This is a script I wrote to quickly setup a new <a href="http://www.getmura.com/">Mura 7</a> development server in <a href="https://www.ortussolutions.com/products/commandbox">CommandBox</a>. It runs on <a href="http://lucee.org/">Lucee</a> with an embedded H2 database. It uses the new <a href="https://www.forgebox.io/view/commandbox-cfconfig">cfconfig</a> module to setup the datasource.</p>
<script src="https://gist.github.com/jsteinshouer/7d2c4d7cbd0329a2ea92f9de103678f3.js"></script>
<p>Download and run the script in the directory you want the server setup.</p>
<pre><code>wget https://gist.githubusercontent.com/jsteinshouer/7d2c4d7cbd0329a2ea92f9de103678f3/raw/d9ef6a860e0fc6a8e51e2f23cf8cc6cac7bdac1a/Mura7_setup.boxr
</code></pre>
<pre><code>box recipe Mura7_setup.boxr
</code></pre>
<p>Once you run the script the browser will open and you will need to go through the initial Mura 7 setup form.</p>
<ol>
<li>Select MySQL as the Database type</li>
<li>Enter muradb as the datasource name</li>
<li>Click Next</li>
<li>You can change the Mura admin username and password here</li>
<li>Enter an admin email then click Next</li>
<li>You select the option to not have index.cfm in the navigation links if you like</li>
<li>Click Submit</li>
</ol>
<p>You can now start tinkering away on your new Mura 7 server. You can use these commands to start and stop the server.</p>
<pre><code>box server stop
</code></pre>
<pre><code>box server start
</code></pre>
Working with File Streams in CFML/Java2017-02-26T00:00:00Zhttps://jasonsteinshouer.com/2017/02/26/working-with-file-streams.html<p>In my previous <a href="https://jasonsteinshouer.com/tags/#streams">blog posts</a> I talked about working with streams in Node.js. I assumed I could do something similar working in CFML by using Java but had never attempted it. At my company we process many different data feeds in various formats. So we sometimes need to read and write some large data files. This can be a very memory intensive task with large data sets. So using streams is usually a more efficient solution.</p>
<h3 id="file-input-stream-example" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/02/26/working-with-file-streams.html#file-input-stream-example">File Input Stream Example</a></h3>
<p>Create a Java File object by passing in the file path of the file to read.</p>
<pre><code class="language-cfscript"> var inputFile = createObject("java", "java.io.File").init(myInputFilePath);
</code></pre>
<p>Then create a FileInputStream Java object and pass the File object.</p>
<pre><code class="language-cfscript"> var inputStream = createObject("java", "java.io.FileInputStream").init(inputFile);
</code></pre>
<p>I can then use the Java Scanner utility to loop over each line of the file as it becomes available.</p>
<pre><code class="language-cfscript">var sc = createObject("java", "java.util.Scanner").init(inputStream);
while(sc.hasNextLine()) {
var line = sc.nextLine();
/* process line here */
}
</code></pre>
<h3 id="file-output-stream-example" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/02/26/working-with-file-streams.html#file-output-stream-example">File Output Stream Example</a></h3>
<p>Here is a method that reads a file using an input stream and applies a closure to each line and writes the result to a file output stream.</p>
<pre><code class="language-cfscript">/**
*
* @source.hint file path to the input file
* @destination.hint file path for the output file
* @transform.hint function to process each line of the file
*
*/
function transformFile(
required string source,
required string destination,
required any transform
)
{
/* Create input stream */
var inputFile = createObject("java", "java.io.File").init(arguments.source);
var inputStream = createObject("java", "java.io.FileInputStream").init(inputFile);
/* Create input stream */
var sc = createObject("java", "java.util.Scanner").init(inputStream);
/* Create output stream */
var outputFile = CreateObject("java", "java.io.File").init(arguments.destination);
var outputStream = CreateObject("java", "java.io.FileOutputStream").init(outputFile);
/* Process each line of the input stream */
while(sc.hasNextLine()) {
var line = sc.nextLine();
/* Write results to output stream */
var newLine = transform(line);
outputStream.write(charsetDecode( newLine, "utf-8" ));
outputStream.flush();
}
/* Close files and cleanup */
outputStream.close();
inputStream.close();
sc.close();
outputStream = "";
inputStream = "";
inputFile = "";
outputFile = "";
sc = "";
}
</code></pre>
<p>I was able to scrub quotes out of a 300+ MB csv file quickly using this method. I ran out of memory when trying to do it using the cfml fileRead and fileWrite methods.</p>
<pre><code class="language-cfscript">transformFile(
source = sourceFilePath,
destination = destinationFilePath,
transform = function(line) {
return replace(arguments.line,"""","","all") & Chr(13) & Chr(10);
}
);
</code></pre>
Return Multiple Record Sets from a Query in Coldfusion2017-01-03T00:00:00Zhttps://jasonsteinshouer.com/2017/01/03/return-multiple-record-sets-in-coldfusion.html<p>In Adobe Coldfusion you can only return one record set from using <a href="http://cfdocs.org/cfquery">cfquery</a> or <a href="http://cfdocs.org/cfquery">queryExecute</a>. If you need to return multiple record sets then you can write a stored procedure and use <a href="http://cfdocs.org/cfstoredproc">cfstoredproc</a>.</p>
<h3 id="why-is-this-needed" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/01/03/return-multiple-record-sets-in-coldfusion.html#why-is-this-needed">Why is this needed?</a></h3>
<p>Recently my team has started using <a href="https://msdn.microsoft.com/en-us/library/bb675163(v=vs.110).aspx">Table-Valued Prameters</a> which is a pre-defined table type in SQL Server that you can pass as a parameter. Here is an example where we use dynamic sql to populate a table-valued prameter and pass it to the stored procedure.</p>
<pre><code class="language-cfscript">var sql = createObject("java","java.lang.StringBuffer").init("
DECLARE @employee_list udt_list_integer;
");
for (var employeeID in arguments.employees) {
sql.append("INSERT INTO @employee_list VALUES(#employeeID#);");
}
sql.append("
EXEC spu_my_stored_proc
@start_date = :startDate,
@end_date = :endDate,
@employee_list = @employee_list
");
var results = queryExecute(sql,{
startDate = {cfsqltype="cf_sql_timestamp",value=arguments.startDate},
endDate = {cfsqltype="cf_sql_timestamp",value=arguments.endDate}
}, datasource="MyDSN");
</code></pre>
<p>This works great if your stored procedures only return one record set. However recently we built one that needed to return mutiple result sets.</p>
<p>I do realize I could also do the dynamic sql inside the stored procedure but I perfer using CFML for this.</p>
<h3 id="solution" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/01/03/return-multiple-record-sets-in-coldfusion.html#solution">Solution</a></h3>
<p>After spending some time playing with the underlying Coldusion java libraries I was able to come up with a component that would allow me to execute a query and have it return mutiple result sets. I thought I would throw this out there in case this is useful for someone else.</p>
<p><strong>Disclaimer: This has only been testing using Adobe Coldfusion 11 and MS SQL Server 2008. Also, currently it does not support all the same features as queryExecute. For example, it doesn’t support lists at the moment but that could be added.</strong></p>
<h4 id="mutlirecordsetquery.cfc" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2017/01/03/return-multiple-record-sets-in-coldfusion.html#mutlirecordsetquery.cfc">MutliRecordSetQuery.cfc</a></h4>
<script src="https://gist.github.com/jsteinshouer/596397a5e8a527c72831dbd5f07118ff.js"></script>
<p>The previous example would now look like this.</p>
<pre><code class="language-cfscript">var multiRecordSetQuery = new MultiRecordSetQuery();
var sql = createObject("java","java.lang.StringBuffer").init("
DECLARE @employee_list udt_list_integer;
");
for (var employeeID in arguments.employees) {
sql.append("INSERT INTO @employee_list VALUES(#employeeID#);");
}
sql.append("
EXEC spu_my_stored_proc
@start_date = :startDate,
@end_date = :endDate,
@employee_list = @employee_list
");
var results = multiRecordSetQuery.execute(sql,{
startDate = {cfsqltype="cf_sql_timestamp",value=arguments.startDate},
endDate = {cfsqltype="cf_sql_timestamp",value=arguments.endDate}
}, "MyDSN");
</code></pre>
Executing Coldbox Events in Legacy Code2016-11-08T00:00:00Zhttps://jasonsteinshouer.com/2016/11/08/execute-cb-events-in-legacy-code.html<p>A quick tip for future Jason or anyone else who may work with Coldbox and legacy code (not MVC) running side-by-side. I found this well documented in the <a href="https://coldbox.ortusbooks.com/content/full/event_handlers/executing_events.html">Coldbox Documentation</a> but wanted to post it as a quick reference for myself.</p>
<h2 id="myhandler.cfc" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/11/08/execute-cb-events-in-legacy-code.html#myhandler.cfc">MyHandler.cfc</a></h2>
<p>Here the action is defined with a widget argument. When running it as a widget it will return the rendered content. Otherwise it is ran as a regular Coldbox request.</p>
<pre><code class="language-cfscript">component extends="coldbox.system.EventHandler" {
function myEvent(event,rc,prc,widget=false) {
prc.message = "Test from Coldbox event";
if (arguments.widget) {
return renderView("main/myView");
}
event.setView("main/myView");
}
}
</code></pre>
<h2 id="mainmyview.cfm" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/11/08/execute-cb-events-in-legacy-code.html#mainmyview.cfm">main/myView.cfm</a></h2>
<p>Simple view that just outputs the message.</p>
<pre><code class="language-markup"><cfoutput>#prc.message#</cfoutput>
</code></pre>
<h2 id="legacy.cfm" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/11/08/execute-cb-events-in-legacy-code.html#legacy.cfm">legacy.cfm</a></h2>
<p>Now I call the Coldbox event from my legacy code like so.</p>
<pre><code class="language-markup"><cfoutput>#application.cbController.runEvent(event="MyHandler.myEvent",eventArguments={widget=true})#</cfoutput>
</code></pre>
<h3 id="update:-11292016" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/11/08/execute-cb-events-in-legacy-code.html#update:-11292016">UPDATE: 11/29/2016</a></h3>
<p>I found out that since my legacy code was not running inside a Colbox request that I also needed to populate the Coldbox RequestContext so the event had access to any url or form scope parameters.</p>
<pre><code class="language-markup"><cfset application.cbController.getRequestService().requestCapture()>
<cfoutput>#application.cbController.runEvent(event="MyHandler.myEvent",eventArguments={widget=true})#</cfoutput>
</code></pre>
Notes on Migrating from Vue.js 1.x2016-11-03T00:00:00Zhttps://jasonsteinshouer.com/2016/11/03/vuejs-migration.html<p>I have been refactoring a Coldbox application as a Single Page Application using <a href="http://vuejs.org/">Vue.js</a> using <a href="https://www.ortussolutions.com/blog/introducing-coldbox-elixir-ui-asset-manager">ColdBox Elixir</a>. I was origionally looking at ColdBox Elixir for compiling Sass. After reading the <a href="https://coldbox-elixir.ortusbooks.com/installation.html">docs</a> I was curious and decided to look into Vue.js.</p>
<p>So far Vue.js and ColdBox Elixir have been a joy to work with. Vue.js <a href="https://medium.com/the-vue-point/vue-2-0-is-here-ef1f26acf4b8#.44y3tnyv4">recently released version 2.0</a> so i decided to try migrating what I had done to version 2.0.</p>
<p>I started by upgraded my vue and vue-router packages to the most recent versions.</p>
<pre><code class="language-bash">npm install vue@latest --save
npm install vue-router@latest --save
</code></pre>
<h2 id="vue-router-migration" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/11/03/vuejs-migration.html#vue-router-migration">Vue-Router Migration</a></h2>
<p>I ran gulp to compile my application and recieved this error in the browser.</p>
<pre><code>Uncaught TypeError: router.map is not a function(…)
</code></pre>
<p>So after reviewing the <a href="http://vuejs.org/guide/migration-vue-router.html">vue-router migration documentation</a> I changed my router configuration from this:</p>
<pre><code class="language-javascript">const router = new VueRouter({
history: true
});
router.map({
'/': {component: Home},
'/hello': {component: HelloWorld},
'/profile/:userID': {component: Bar}
});
router.start(App, '#app');
</code></pre>
<p>To this:</p>
<pre><code class="language-javascript">const routes = [
{ path: '/', component: Home},
{ path: '/hello', component: HelloWorld},
{ path: '/profile/:userID', component: Bar}
];
const router = new VueRouter({
mode: 'history',
routes: routes
});
const app = new Vue({
components: {App},
router: router
}).$mount('#app');
</code></pre>
<p>To resolve this warning I needed to change my anchor tags using v-link.</p>
<p><code>[Vue warn]: Failed to resolve directive: link</code></p>
<p>This code:</p>
<pre><code class="language-markup"><a href="#" v-link="/">Home</a>
</code></pre>
<p>Became this:</p>
<pre><code class="language-markup"><router-link to="/">Home</router-link>
</code></pre>
<h2 id="importing-from-npm" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/11/03/vuejs-migration.html#importing-from-npm">Importing From NPM</a></h2>
<p>After getting the vue-router setup changed I recieved this warning.</p>
<pre><code>[Vue warn]: Failed to mount component: template or render function not defined.
(found in root instance)
</code></pre>
<p>The warning is a little misleading. After some searching I discovered it is because using NPM the <a href="http://vuejs.org/guide/installation.html#Standalone-vs-Runtime-only-Build">runtime-only build</a> is imported.</p>
<pre><code class="language-javascript">import Vue from 'vue';
</code></pre>
<p>The <a href="http://vuejs.org/guide/installation.html#Standalone-vs-Runtime-only-Build">workaround</a> was to use Aliasify.</p>
<pre><code class="language-bash">npm install aliasify --save
</code></pre>
<p>I then added this to <code>package.json</code>.</p>
<pre><code class="language-bash">"aliasify": {
"aliases": {
"vue": "vue/dist/vue.js"
}
},
</code></pre>
<h2 id="vueify" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/11/03/vuejs-migration.html#vueify">Vueify</a></h2>
<p>My application uses Vue.js <a href="http://vuejs.org/guide/single-file-components.html">Single File Components</a> and Vueify is the tool that compiles them. Coldbox Elixir uses Vueify 8.x which only works with Vue 1.x. To resolve this I installed Vueify 9.</p>
<pre><code class="language-bash">npm install vueify@latest --save
</code></pre>
<h2 id="resolving-other-warnings" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/11/03/vuejs-migration.html#resolving-other-warnings">Resolving other warnings</a></h2>
<p>I recieved other warnings due to deprecated features or changes to Vue.js. Here are the warnings I encountered and changes to resolve them.</p>
<p>Warning:</p>
<p><code>[Vue warn]: Component template should contain exactly one root element:</code></p>
<p>Solution:</p>
<p>Added <code><div></div></code> around any component templates that had multiple root elements.</p>
<p>Before:</p>
<pre><code class="language-markup"><h1></h1>
<ul>
<li v-for="user in users"></li>
</ul>
</code></pre>
<p>After:</p>
<pre><code class="language-markup"><div>
<h1></h1>
<ul>
<li v-for="user in users"></li>
</ul>
</div>
</code></pre>
<p>Warning:</p>
<p><code>Failed to resolve directive: el </code></p>
<p>Solution:</p>
<p>The <a href="http://vuejs.org/guide/migration.html#v-el-and-v-ref-replaced">solution</a> for this was to replace the <code>v-el</code> attribute with the <code>ref</code> attribute. I Also had to changed my references from this.$elem to this.$refs.</p>
<p>One other change I made was to change the name of the <code>ready</code> event handler in my components to <code>mounted</code>.</p>
Performance Issues with SQL Server Query Plans2016-09-08T00:00:00Zhttps://jasonsteinshouer.com/2016/09/08/sql-server-query-plan-issues.html<p>I was recently tasked with building a complex report. I wrote a SQL Server stored procedure to return the data. It accepts 5 parameters and has complicated logic. It seemed to run fine at first but I started noticing performance issues when certain parameters were used. The issues seemed to go away if the sql service was restarted. Also, when I executed it in the SQL Server Management Studio it ran fine but would never finish when the same stored procedure was executed from the application.</p>
<p>After some googling I came across <a href="http://www.sommarskog.se/query-plan-mysteries.html">this article</a> that described the problem I was having. I knew that SQL Server cached the query execution plans but I did not consider that the plan maybe optimized for one set of parameters and not for another set.</p>
<p>My quick solution was to use <code>sp_recompile</code> to clear the cached query execution plan for the stored procedure before each execution.</p>
<pre><code class="language-sql">SP_RECOMPILE spu_my_complex_report;
EXEC spu_my_complex_report @param1, @param2,...;
</code></pre>
<p>This solves the problem but a possibly better solution may be to compare execution plans using the different parameters and write a seperate stored procedure for the combination that is causing the execution plan to differ from the others. This may cause duplication of some of the logic but would probably increase the efficiency of the report.</p>
<h2 id="update:" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/09/08/sql-server-query-plan-issues.html#update:">UPDATE:</a></h2>
<p>Using <code>sp_recompile</code> was throwing the folling error when more than one instance of the report was ran at the same time:</p>
<pre><code class="language-bash">Msg 2801, The definition of object 'spu_my_complex_report' has changed since it was compiled.
</code></pre>
<p>So I went ended up spliting the logic into two different stored procedures which was a better solution anyway. It seems to be working well.</p>
Using JSON Web Tokens for Authentication in CFML2016-08-18T21:24:00Zhttps://jasonsteinshouer.com/2016/08/18/authentication-in-cfml-with-jwt-tokens.html<p><a href="https://jwt.io/">JSON Web Tokens (JWT)</a> are commonly used in single-sign-on solutions. They can also be used to authenticate single-page front-end applications with a back-end API. The benefit is that they are lightweight and can be sent with every request so they are stateless. That means server side sessions are not necessary. This makes scaling an application easier as well.</p>
<p>Awhile back I created a CFML component named <a href="https://github.com/jsteinshouer/cf-jwt-simple">cf-jwt-simple</a> that creates and verify’s JSON Web Tokens. It was a port of a Node.js library to CFML. In this post I will give a example of using it to do authentication to a back-end api.</p>
<h2 id="setup-and-configuration" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/08/18/authentication-in-cfml-with-jwt-tokens.html#setup-and-configuration">Setup and Configuration</a></h2>
<p>First I use <a href="https://www.ortussolutions.com/products/commandbox">CommandBox</a> to scaffold a Coldbox application using the <a href="https://github.com/coldbox-templates/rest">Coldbox REST Template</a> by following the instructions from the <a href="https://www.ortussolutions.com/blog/rest2016-coldbox-rest-template">Ortus Solution’s blog post</a>.</p>
<pre><code class="language-bash">box
mkdir MyAPI --cd
coldbox create app skeleton=rest name=MyAPI --installColdBox --installTestBox
</code></pre>
<p>I then start the server with <a href="https://www.ortussolutions.com/products/commandbox">CommandBox</a>.</p>
<pre><code class="language-bash">server start --rewritesEnable
</code></pre>
<p>The browser should open and you should see a JSON response with “Welcome to my ColdBox RESTFul Service”.</p>
<p>Next use <a href="https://www.ortussolutions.com/products/commandbox">CommandBox</a> to install the <a href="https://github.com/jsteinshouer/cf-jwt-simple">cf-jwt-simple</a> package.</p>
<pre><code class="language-bash">install cf-jwt-simple
</code></pre>
<p><a href="https://github.com/jsteinshouer/cf-jwt-simple">cf-jwt-simple</a> requires a secret key that is used to signing and verification. I will add the key as a setting in <code>config/Coldbox.cfc</code>. Remember to change the secret key to some something more secure than this. Use something random and long as the key should be secure and unique to your application.</p>
<p><strong>config/Coldbox.cfc</strong></p>
<pre><code class="language-cfscript">// custom settings
settings = {
jwtSecretKey = "mys3cr3tkey"
};
</code></pre>
<p>I will also add a setting the token expiration time.</p>
<pre><code class="language-cfscript">// custom settings
settings = {
jwtSecretKey = "mys3cr3tkey",
/* An access token is valid this many minutes */
accessTokenExpiration = "30"
};
</code></pre>
<p>I add a wirebox mapping for the JWT in <code>config/Wirebox.cfc</code>. This will initialize the JWT library with the secret key that was setup in the previous step. See the <a href="http://wirebox.ortusbooks.com/content/mapping_dsl/">Wirebox Documentation</a> for more info in setting up bindings.</p>
<p><strong>config/Wirebox.cfc</strong></p>
<pre><code class="language-cfscript">// Map Bindings below
map("jwt").to("cf-jwt-simple.jwt").initWith(getProperty("jwtSecretKey")).asSingleton();
</code></pre>
<h2 id="model" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/08/18/authentication-in-cfml-with-jwt-tokens.html#model">Model</a></h2>
<p>Here I will use <a href="https://www.ortussolutions.com/products/commandbox">CommandBox</a> to create a new model component named AuthenticationService.cfc</p>
<pre><code class="language-bash">coldbox create model name="security/AuthenticationService" properties="jwt:any" accessors=true open=true tests=false
</code></pre>
<p><strong>models/AuthenticationService.cfc</strong></p>
<p>I then inject the JWT component into AuthenticationService using wirebox.</p>
<pre><code class="language-cfscript">property name="jwt" type="any" inject="jwt";
</code></pre>
<p>I also inject the expiration setting.</p>
<pre><code class="language-cfscript">property name="tokenExpiration" type="numeric" inject="coldbox:setting:accessTokenExpiration";
</code></pre>
<p>I then implement a method named validateUser that takes a username and password. It should lookup the username and use a password hashing algorithm to check that the hashed password matches the one stored in your database. Secure credential storage and password hashing is out of the scope of this tutorial. Here is a couple good reasources on doing this in CFML.</p>
<ul>
<li><a href="http://www.learncfinaweek.com/week1/Secure_Password_Storage/">Secure Password Storage</a></li>
<li><a href="http://blog.mxunit.org/2011/02/hashing-passwords-with-bcrypt-in.html?m=1">Hashing passwords with bcrypt in ColdFusion</a></li>
</ul>
<pre><code class="language-cfscript">public boolean function validateUser(required string username,required string password) {
var isValid = false;
/* Implement your user or client authentication here */
return isValid;
}
</code></pre>
<p>Next I create the grantToken method. It will return a fresh JWT with the userid as the subject. First I create a structure named payload that contains three reserved JWT claims. One of the goals of JWT is to be compact so they use three letter designation for the claims. “iss” stands for issuer. It is used to identify who issued the token. The “exp” claim stands for expiration. Here I use the <code>tokenExpiration</code> setting to set the expiration timestamp. The “sub” claim stands for subject. I am assigning the userid as the subject but it could be some other data as well. You can also add our own custom claims as well that can be accessed with every request. Be careful not to store any sensitive information unless you plan to use an encrypted JWT. <a href="https://github.com/jsteinshouer/cf-jwt-simple">cf-jwt-simple</a> does not handle encryption. If you need encryption it may be better to use one of the <a href="https://jwt.io/#libraries">Java libraries listed here</a>. Finally the token is encoded and signed using the HmacSHA512 algorithm.</p>
<pre><code class="language-cfscript">public string function grantToken(required string userID) {
var payload = {
"iss" = "https://myapi",
"exp" = dateAdd("n",tokenExpiration,now()),
"sub" = userID
};
/* Encode the data structure as a json web token */
return jwt.encode(payload,"HS512");
}
</code></pre>
<p>Then I create a method to validate our token when recieved from the client. First I try to decode the token. This will return the payload as a structure. If the signature is invalid it will throw an error which is why it is wrapped in a try-catch block. If the data structure exists we know the token signature was valid. Next I check the expiration to see if it is still within its validity period.</p>
<pre><code class="language-cfscript">public boolean function validateToken(required string accessToken) {
var validToken = false;
try {
var data = jwt.decode(accessToken);
validToken = true;
}
catch(any e) { }
if (structKeyExists(local, "data")) {
/* Check if token has expired. */
if (now() > data.exp) {
validToken = false;
}
}
return validToken;
}
</code></pre>
<p>I also add a method to decode the token and return the data encoded within the token</p>
<pre><code class="language-cfscript">public struct function decodeToken(accessToken) {
return jwt.decode(accessToken);
}
</code></pre>
<h2 id="handlers" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/08/18/authentication-in-cfml-with-jwt-tokens.html#handlers">Handlers</a></h2>
<p>Here I use <a href="https://www.ortussolutions.com/products/commandbox">CommandBox</a> to create a new handler component named <code>Authenticate.cfc</code>. It will be responsible for handling authentication requests.</p>
<pre><code class="language-bash">coldbox create handler actions="index" name="Authenticate" views=false integrationTests=false open=true
</code></pre>
<p>I then modify the handler to extend <code>BaseHandler</code>. I also restrict the request to the http POST method by setting the <code>this.allowedMethods</code> property. Finally I modify the index action to validate the username and password using the authService. It will return an access token if the authentication is successful. I inject the authService into the BaseHandler next so no need to do it here.</p>
<p><strong>handlers/Authenticate.cfc</strong></p>
<pre><code class="language-cfscript">component extends="BaseHandler" {
// OPTIONAL HANDLER PROPERTIES
this.prehandler_only = "";
this.prehandler_except = "";
this.posthandler_only = "";
this.posthandler_except = "";
this.aroundHandler_only = "";
this.aroundHandler_except = "";
// REST Allowed HTTP Methods Ex: this.allowedMethods =
this.allowedMethods = {index="POST"};
/**
* Get an api access token
*/
any function index( event, rc, prc ){
event.paramValue("username","");
event.paramValue("password","");
if (authService.validateUser(rc.username,rc.password)) {
prc.response.setData({"token" = authService.grantToken(rc.username)});
}
else {
prc.response
.setError( true )
.setErrorCode( 401 )
.addMessage( "User validation failed!" )
.setStatusCode( 401 )
.setStatusText( "Unauthorized" );
}
}
}
</code></pre>
<p><strong>handlers/BaseHandler.cfc</strong></p>
<p>Then I modify <code>handlers/BaseHandler.cfc</code> and inject the AuthenticationService.</p>
<pre><code class="language-cfscript">property name="authService" type="any" inject="security.AuthenticationService";
</code></pre>
<p>Before the action is executed in the <code>aroundHandler</code> method I add the following code snippet to check the authentication token for each request.</p>
<pre><code class="language-cfscript">/* Do not check authentication for the authenticate handler */
if (event.getCurrentEvent() != "authenticate.index") {
event.paramValue("token","");
/* Extract the token from the authorization header */
if (!len(rc.token) && structKeyExists(getHTTPRequestData().headers, "authorization")) {
rc.token = listLast(getHTTPRequestData().headers.authorization," ");
}
if (authService.validateToken(rc.token)) {
/* Validate token and store token data in prc scope */
prc.token = authService.decodeToken(rc.token);
}
else {
/* token invalid */
prc.response
.setError( true )
.setErrorCode( 401 )
.addMessage( "The access token is not valid!" )
.setStatusCode( 401 )
.setStatusText( "Unauthorized" );
}
}
// Execute action
if (!prc.response.getError()) {
arguments.targetAction( argumentCollection=args );
}
</code></pre>
<p>Before the authentication code I also add some code to enforce JSON content for POST and PUT methods. It will deserialize the JSON and merge it into the rc scope.</p>
<pre><code class="language-cfscript">/* Only accept application/json for content body on posts */
if (!prc.response.getError() && event.getHTTPMethod() == "POST" || event.getHTTPMethod() == "PUT") {
if (event.getHTTPHeader("Content-Type") != "application/json") {
prc.response
.setError( true )
.setErrorCode( 400 )
.addMessage( "Content-Type application/json is required!" )
.setStatusCode( 400 )
.setStatusText( "Bad Request" );
}
try {
structAppend(rc, event.getHTTPContent( json=true ));
}
catch(Any e) {
prc.response
.setError( true )
.setErrorCode( 400 )
.addMessage( "Invalid JSON Format!" )
.setStatusCode( 400 )
.setStatusText( "Bad Request" );
}
}
</code></pre>
<h2 id="tests" tabindex="-1"><a class="header-anchor" href="https://jasonsteinshouer.com/2016/08/18/authentication-in-cfml-with-jwt-tokens.html#tests">Tests</a></h2>
<p>Last I create some tests to verify that it is working. In <a href="https://www.ortussolutions.com/products/commandbox">CommandBox</a> run the following.</p>
<pre><code class="language-bash">coldbox create bdd name=integration/AuthenticationTests open=true
</code></pre>
<p>Here is the test suite I used verify that it is working.</p>
<p><strong>tests/specs/integration/AuthenticationTests.cfc</strong></p>
<pre><code class="language-cfscript">// all your suites go here.
describe("Authentication Test Suite", function() {
it( "Requests should return Unauthorized without an access token", function(){
cfhttp(url="http://#cgi.server_name#:#cgi.server_port#/");
expect( cfhttp.status_code ).toBe(401);
expect( cfhttp.status_text ).toBe("Unauthorized");
});
it( "Authenticate should grant an access token with valid credentials", function(){
var jwt = createObject("cf-jwt-simple.jwt").init("mys3cr3tkey");
var credentials = {
"username" = "fakeuser",
"password" = "mypassword"
};
cfhttp(
url="http://#cgi.server_name#:#cgi.server_port#/authenticate",
method="post",
charset="utf-8"
) {
cfhttpparam(type="header",name="Content-Type",value="application/json");
cfhttpparam(type="body",value="#serializeJSON(credentials)#")
};
expect( cfhttp.status_code ).toBe(200);
expect( cfhttp.status_text ).toBe("OK");
var result = deserializeJSON(cfhttp.fileContent);
expect( result.error ).toBeFalse();
expect( jwt.verify(result.data.token) ).toBe(true);
});
it( "Request should return OK with valid token", function(){
var jwt = createObject("cf-jwt-simple.jwt").init("mys3cr3tkey");
var payload = {
"iss" = "https://myapi",
"exp" = dateAdd("n",30,now()),
"sub" = "fakeuser"
};
/* Encode the data structure as a json web token */
var token = jwt.encode(payload,"HS512");
cfhttp(url="http://#cgi.server_name#:#cgi.server_port#/") {
cfhttpparam(type="header",name="Authorization",value="Bearer #token#");
};
var result = deserializeJSON(cfhttp.fileContent);
expect( cfhttp.status_code ).toBe(200);
expect( cfhttp.status_text ).toBe("OK");
expect( result.data ).toBe("Welcome to my Coldbox RESTFul SErvice");
});
});
</code></pre>
Parsing XML with Node.js Streams Part II2016-07-18T00:00:00Zhttps://jasonsteinshouer.com/2016/07/18/xml-parsing-with-node-streams-part-II.html<p>In my previous post <a href="https://jasonsteinshouer.com/javascript/2016/07/05/xml-to-csv-using-node-streams.html">Processing Large XML Data Feeds With Node.js</a> I gave an example of calling a SOAP webservice then writing the response XML to file. I then used the <a href="https://github.com/assistunion/xml-stream">xml-stream</a> module to parse through the xml and create a CSV/TSV file. At the time I originally tried streaming directly from the http request to <a href="https://github.com/assistunion/xml-stream">xml-stream</a> but was getting an error.</p>
<p>I have been going through the <a href="https://github.com/substack/stream-adventure">stream-adventure</a> workshop from <a href="http://nodeschool.io/">NodeSchool.io</a>. I recomend it as a great introduction into using streams in Node.js. I feel I now have a better understanding of streams in Node.js. I thought I would take another crack at piping the http response directly to <a href="https://github.com/assistunion/xml-stream">xml-stream</a>. As it turns out it was much simpler than I thought. Since the the request module itself returns a node stream, all I had to do was pass it into the <a href="https://github.com/assistunion/xml-stream">xml-stream</a> module. It worked great and was much faster than my previous program. See the full example below.</p>
<pre><code class="language-javascript">var request = require('request');
var XmlStream = require('xml-stream');
var fs = require("fs");
console.log("Sending Request...");
var fields = ['ID','FirstName','LastName','Address','City','State'];
var options = {
method: 'POST',
url: 'https://www.yourserver.com/webservice/endpoint,
headers: {
'Content-Type': 'text/xml',
'SOAPAction': 'https://www.yourserver.com/GetSomeData',
'Content-Length': Buffer.byteLength(soap),
'charset': 'utf-8'
},
body: 'Put your SOAP Request XML here'
};
var xml = new XmlStream(request(options));
var writeStream = fs.createWriteStream('./data.txt');
writeStream.write(fields.join("\t") + "\n");
xml.on('endElement: GetSomeDataResult', function(item) {
var line = [];
fields.forEach(function(field) {
line.push(item[field]);
});
writeStream.write(line.join("\t") + "\n");
});
xml.on("end", function() {
writeStream.end();
console.log("finished");
});
</code></pre>
Programatically Create an Apache Derby Database in Adobe Coldfusion2016-07-14T00:00:00Zhttps://jasonsteinshouer.com/2016/07/14/create-derby-db-in-coldfusion-programatically.html<p>With <a href="http://lucee.org/">Lucee</a> you can add a datasource for the <a href="http://www.h2database.com/html/main.html">H2</a> embedded database and if the database doesnt exist it will be created automatically. This can be handy if you have some automated setup routine for your application.</p>
<pre><code class="language-cfscript">this.datasources["myDsn"] = {
class: 'org.h2.Driver'
, connectionString: 'jdbc:h2:#getDirectoryFromPath( getCurrentTemplatePath() )#/db/myDatabase;MODE=MSSQLServer'
};
</code></pre>
<p>However, if you want to use an embedded <a href="https://db.apache.org/derby/">Apache Derby</a> database with Adobe Coldfusion the database must exist or it will throw an error. You can use the Java JDBC driver for Apache Derby to create the database if it does not exist.</p>
<pre><code class="language-cfscript">
//Create a derby database if it does not already exist
if (!fileExists("#getDirectoryFromPath( getCurrentTemplatePath() )#db/myDerbyDatabase/README_DO_NOT_TOUCH_FILES.txt")) {
//Get the apached derby JDBC class
var Class = createObject("java","java.lang.Class");
Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
// Use the DriverManager to connect
DriverManager = createObject("java", "java.sql.DriverManager");
//Path the the database
var dbLocation = replace(getDirectoryFromPath( getCurrentTemplatePath() ),"\","/","all") & "db/myDerbyDatabase";
//Connect and pass the create=true parameter
con = DriverManager.getConnection("jdbc:derby:#dbLocation#;create=true;");
con.close();
}
//setup CF datasource
this.datasources["myDsn"] = {
driver: 'Apache Derby Embedded'
, database: '#getDirectoryFromPath( getCurrentTemplatePath() )#db/myDerbyDatabase'
};
</code></pre>
<p>This opens a connection with the Apache Derby JDBC driver and passed <code>create=true</code> to create the database.</p>
Processing Large XML Data Feeds With Node.js2016-07-05T11:00:01Zhttps://jasonsteinshouer.com/2016/07/05/xml-to-csv-using-node-streams.html<p>At work we have a job that process a large data feed that involves consuming a SOAP webservice that returns 150MB+ of data. The job runs in Coldfusion which reads all the data into RAM and depending on how much RAM is available can cause Coldfusion to throw an out of memory error.</p>
<p>We discussed the concept of using streams to do this so that only a small chunk of data is processed at a time rather than loading everything into RAM at once. Rather than trying to use Java we decided it would be easier to use Node.js to handle it. For my first attempt at it I thought I would just try to convert the data into a CSV/TSV format which can easily be imported into our database for further processing.</p>
<p>For this project I installed the request and xml-stream packages from NPM. The <a href="https://github.com/request/request">request</a> module is used to make http requests easier to work with. The <a href="https://github.com/assistunion/xml-stream">xml-stream</a> module is used to parse through XML as a stream.</p>
<pre><code class="language-bash">npm install request xml-stream
</code></pre>
<p>First I import the modules I will be using.</p>
<pre><code class="language-javascript">var request = require('request');
var XmlStream = require('xml-stream');
var fs = require("fs");
</code></pre>
<p>Then I created an object for the request options which includes the url and options to send as a SOAP webservice request.</p>
<pre><code class="language-javascript">var options = {
method: 'POST',
url: 'https://www.yourserver.com/webservice/endpoint,
headers: {
'Content-Type': 'text/xml',
'SOAPAction': 'https://www.yourserver.com/GetSomeData',
'Content-Length': Buffer.byteLength(soap),
'charset': 'utf-8'
},
body: 'Put your SOAP Request XML here'
};
</code></pre>
<p>I initially tried to stream directly from the http request to xml-stream but has some issues and did not spend much time troubleshooting it. I decided to write the XML to file first then process it.</p>
<pre><code class="language-javascript">var reqStream = request(options).pipe(fs.createWriteStream("./everything.xml"));
</code></pre>
<p>Here I defined a handler to listen for the event that’s called when the create file stream is finished. This is the function that reads the file parses out the XML and writes the data to a tab delimited file.</p>
<pre><code class="language-javascript">reqStream.on("finish", function() {
var fields = ['ID','FirstName','LastName','Address','City','State'];
console.log("download done...");
console.log("creating csv...");
var readStream = fs.createReadStream("./everything.xml");
var xml = new XmlStream(readStream);
var writeStream = fs.createWriteStream('./everything.txt');
writeStream.write(fields.join("\t") + "\n");
xml.on('endElement: GetSomeDataResult', function(item) {
var line = [];
fields.forEach(function(field) {
line.push(item[field]);
});
writeStream.write(line.join("\t") + "\n");
});
xml.on("end", function() {
writeStream.end();
console.log("finished");
});
});
</code></pre>
<p>Here I define an array that contains a list of fields from the XML data that I will transform into a tab seperated txt file.</p>
<pre><code class="language-javascript">
var fields = ['ID','FirstName','LastName','Address','City','State'];
</code></pre>
<p>Then I create stream to read in the XML file and use it to create a new XmlStream. I also create a stream to write the new TXT file to.</p>
<pre><code class="language-javascript">var readStream = fs.createReadStream("./everything.xml");
var xml = new XmlStream(readStream);
var writeStream = fs.createWriteStream('./everything.txt');
</code></pre>
<p>This line writes the header row of the txt file.</p>
<pre><code class="language-javascript">writeStream.write(fields.join("\t") + "\n");
</code></pre>
<p>I then define a handler that is called everytime the XML stream parses an element named GetSomeDataResult. It grabs the data for each field and then writes a line to the txt file.</p>
<pre><code class="language-javascript">xml.on('endElement: GetSomeDataResult', function(item) {
var line = [];
fields.forEach(function(field) {
line.push(item[field]);
});
writeStream.write(line.join("\t") + "\n");
});
</code></pre>
<p>The last part is a handler that is called when the XML stream is finished parsing. It closes the txt file stream.</p>
<pre><code class="language-javascript">xml.on("end", function() {
writeStream.end();
console.log("finished");
});
</code></pre>
<p>Hopefully this approach will scale better than some other methods we have used to process data feeds.</p>
Hello World from Jekyll!2016-06-14T00:00:00Zhttps://jasonsteinshouer.com/2016/06/14/welcome-to-my-blog.html<p>I decided to try out using <a href="http://jekyllrb.com/">Jekyll</a> to build a personal blog. It is a static site generator and also the engine behind <a href="https://pages.github.com/">Github Pages</a>. This is appealing since I can host my site there for free. This first post focuses on setting up my development environment to get started with Jekyll.</p>
<p>First, I found a Jekyll theme that I liked. I used <a href="https://github.com/kronik3r/daktilo">Daktilo</a> and copied it to my project folder.</p>
<p>As of this writing Jekyll is not officially supported on Windows. Since I was working in Windows I decided to use <a href="https://www.vagrantup.com/">Vagrant</a> to setup my development environment as a virtual machine. Vagrant is a tool that helps automate the creation of development environments using virtual machines. With vagrant I can do my Jekyll development on a linux virtual machine. I already had <a href="https://www.vagrantup.com/">Vagrant</a> and <a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a> installed on my windows machine.</p>
<p>I found <a href="https://github.com/jsturtevant/jekyll-vagrant">this Github repository</a> with a Vagrant config and Linux shell script to install Jekyll and all of its <a href="https://jekyllrb.com/docs/installation/">dependencies</a>. I downloaded repo and copied the <code>Vagrantfile</code> and <code>boostrap.sh</code> files to my project directory.</p>
<p>I could then provision and start the Vagrant box with the following command.</p>
<pre><code class="language-bash">vagrant up
</code></pre>
<p>It takes a while the first time you run it because it must download the base Vagrant box then run the provisioning. Subsequent startups should be much faster. Once it is was up I could connect using SSH.</p>
<pre><code class="language-bash">vagrant ssh
</code></pre>
<p>Vagrant maps a the project directory to a directory named vagrant inside the virtual machine. I then changed to that directory.</p>
<pre><code class="language-bash">cd /vagrant
</code></pre>
<p>I use the following command to have Jekyll generate the site and serve it. It will also watch the directory and regenerate for any changes.</p>
<pre><code class="language-bash">jekyll serve --host 0.0.0.0 --watch --force_polling
</code></pre>
<p>I could browse the site from my host machine at <code>http://localhost:4000.</code> I was now ready to start developing the site. Check out the <a href="http://jekyllrb.com/">Jekyll docs</a> for more info on how to get starting using Jekyll.</p>