STICKMAN VENTURES

Custom Software Development and Engineering

A fractal of kiosk design

Let me tell you a story. Once upon a time, there was a plan. It was a good plan. “Jacob, Walter,” Justin told us, “we’re opening the upstairs. Write me a kiosk.” And so we wrote a kiosk. This is its story.

If you haven’t read PHP: a fractal of bad design, you really should. (If you haven’t developed a deep hatred for PHP, you should do that, too. /s) But seriously. PHP is a flawed language. I believe there are far better alternatives. Nevertheless, we decided to write the kiosk in PHP. There were a few reasons.

  • universal support: Testing and deployment were very easy and blessedly free of long chains of installation steps.
  • simplicity where simplicity is due: The project was relatively simple and did not require a framework, so we didn’t use one. Raw PHP is easier to develop in than raw Python or Ruby or Clojure or your favorite language (unless it’s PHP :-)
  • team cohesion: PHP was pretty easy to learn for both Walter and I, and our skill levels were similar. I believe the project would have suffered if we used a language one of us knew well and the other was struggling to catch up in. Teamwork is good.

So there you have it. Now, I’d love to think that the kiosk is a shining example of how to write good PHP. That would most likely be a lie. However, I do know that we solved several interesting problems and ended up creating a lightweight, secure, and extensible kiosk app. Let’s look at each of those aspects through the classic lens of, you guessed it, code snippets!

Lightweight

Well, maybe the repository as a whole isn’t that lightweight. But as I mentioned above, we made a choice to use as few libraries and frameworks as possible near the beginning. We used jQuery for browser compatibility magic and easier DOM control, Bootstrap for nice looking pages, and CryptoJS because you should never roll your own crypto. Again, never roll your own crypto! On the PHP side, all we used was the stuff that comes bundled with an Apache install of PHP, namely the database APIs. The pages were also designed to be very modular. This reduces the “mental weight” of the repository.

The result of using only a few important libraries is that when it comes to refactoring, fat can be trimmed without compromising unrelated parts of the project. Using a larger framework tends to link those unrelated parts in that removing the entire framework requires a rewrite of a lot of code. jQuery is the only part of the kiosk which could cause a chain reaction like that. The PHP modules are all bundled. CryptoJS is modular by design. Bootstrap is purely for display, so removing it would make a rewrite necessary anyway.

Speaking of CryptoJS, the crypto portion of the kiosk is the one thing I would consider scrapping in favor of a more monolithic solution. Because you should never roll your own crypto! But enough about that. Let’s look at part of the library-light and modular design. This is the front page.


<?php
session_start ();
if (isset ( $_SESSION ['sess_user_id'] ) && isset ( $_SESSION ['sess_username'] )) {
 header ( "Location: /home" );
 exit ();
}
?>

... Begin HTML ...

<?php include $_SERVER['DOCUMENT_ROOT'] . '/../includes/templates/top_reqs.php'; ?>

... Begin toplevel div ...

<?php include $_SERVER['DOCUMENT_ROOT'] . '/../includes/templates/header.php'; ?>

... Construct the form ...
... End the toplevel div ...

<?php include $_SERVER['DOCUMENT_ROOT'] . '/../includes/templates/footer.php'; ?>

... End displayed content ...

<?php include $_SERVER['DOCUMENT_ROOT'] . '/../includes/templates/bottom_reqs.php'; ?>

<script type="text/javascript">$(document).ready(function() {KIOSK.base.initLogin();});</script>

... End HTML ...

A lot of the kiosk pages depend on header("Location:"); redirects. Once you know that, it’s trivial to understand that the front page just acts like a toplevel test of user credentials. The login page is a bit more complex, but continues the modular theme.

I’ve removed the markup with the exception of the initialization block at the bottom. If you really want to see how the login form is constructed, click over to GitHub and take a look. In any case, PHP starts to get uglier on this page. The strange way the div opens and closes in relation to the req includes was a problem we had to solve in an ugly way because of the way PHP and Javascript interact. Even though the order is strange, the modular nature of the project continues. You can go and visit any of the referenced pages and figure out what their function is.

Modules are good. Rabbit trails and spaghetti are bad.

Secure

Creating a good login system without SSL was the biggest challenge of the project. Fortunately, I had just come out of an online cryptography, and I made sure to be religious about good crypto principles while I let other holy wars go unresolved. Cryptographic primitives are built into PHP, and we decided to use the very capable SHA-256 module from CryptoJS. We built our own secure password store and login form on top of those tools.


login : function() {
 var email = document.getElementById(“email”);
 if (email != null && email.value != “” && KIOSK.utils.validateEmail(email.value)) {
 if(KIOSK.login.loginType == 1) {
 var password = document.getElementById(“password”);
 if(password != null && password.value != “”) {
 $.ajax({
 url : “/ajax/request_salt.php”,
 data : {
 email : email.value
 },
 type : “post”,
 success : function(output) {
 var response = JSON.parse(output);
 if(response.response == 0) {
 var hash = CryptoJS.SHA256(KIOSK.utils.SHA256TenK(password.value + response.salt) + response.nonce).toString();
 $.ajax({
 url : “/ajax/login.php”,
 data : {
 type : 1,
 email : email.value,
 hash : hash
 },
 type : “post”,
 success : function(output) {
 var response = JSON.parse(output);
 if(response.response == 0) {
 window.location = response.redirect;
 } else if(response.response == 1) {
 KIOSK.login.bootstrapAlert(KIOSK.login.ALERT_DANGER, “Error!”, response.reason);
 } else if(response.response == 2) {
 KIOSK.login.bootstrapAlert(KIOSK.login.ALERT_INFO, “Oops!”, response.reason);
 }
 },
 error : function(jqXHR, textStatus, errorThrown) {
 window.location = “/”;
 }
 });
 } else if(response.response == 1) {
 KIOSK.login.bootstrapAlert(KIOSK.login.ALERT_INFO, “Oops!”, response.reason);
 }
 },
 error : function(jqXHR, textStatus, errorThrown) {
 window.location = “/”;
 }
 });
 } else {
 KIOSK.login.bootstrapAlert(KIOSK.login.ALERT_WARNING, “Oops!”, “Please enter your password.”);
 }
 } else if(KIOSK.login.loginType == 0) {
 $.ajax({
 url : “/ajax/login.php”,
 data : {
 type : 0,
 email : email.value,
 },
 type : “post”,
 success : function(output) {
 var response = JSON.parse(output);
 if(response.response == 0) {
 window.location = response.redirect;
 } else if(response.response == 1) {
 KIOSK.login.bootstrapAlert(KIOSK.login.ALERT_INFO, “Oops!”, response.reason);
 }
 },
 error : function(jqXHR, textStatus, errorThrown) {
 window.location = “/”;
 }
 });
 }
 } else {
 KIOSK.login.bootstrapAlert(KIOSK.login.ALERT_WARNING, “Oops!”, “The email you entered is not valid.”);
 }
}

Inside that very nesty function (yes, a refactor is in order! :-) is this important line.


var hash = CryptoJS.SHA256(KIOSK.utils.SHA256TenK(password.value + response.salt) + response.nonce).toString();

On the server side, the passwords database stores the hashed, salted password and the salt used to generate it. When a login request is issued, a nonce is generated the same way the salt is and sent along with the salt via AJAX. The client hashes all the information it has together and sends the hash back to the server, which can compare it to the nonce hashed with the salted password. Still with me? All that results in a secure login system, which is resistant to XSS and database compromise, but not man-in-the-middle. Unfortunately, a trusted authority is the only simple way to prevent that attack vector, and the kiosk can serve as its own trust in most cases.

Extensible

The way that PHP mixes logic in presentation can actually be a good thing. It’s certainly not the way I would do it (Lisp! Lisp!), but it works. And it does a whole lot for extensibility. Since the structure of the project is based on includes, there is no distinction between replacing an existing page, say, the one with the QR code on it, with a more dynamic one, like the task module we built for people with office “memberships”.

Here’s the QR code implementation.


<article>
 <h1 style="font-size: 300%;">Wifi Access:</h1>
 <h4>SSID: awesome-ssid</h4>
 <br />
 <img class="nearest" draggable="false"
 alt="awesome-ssid QR Code" src="/img/wifi.png" width="512px"
 height="512px">
 <p style="color: #999999; padding-top: 10px; max-width: 512px; font-size: 17px;">
 Or scan this code with a QR code with a compatible QR code reader.</p>
</article>

And the main task interface.


<?php
$domain = 'localhost';
$user = 'tasker';
$key = 'tasker';
$database = 'kiosk';

$mysqli = new mysqli ( $domain, $user, $key, $database ); $query = $mysqli->prepare ( "SELECT title, body FROM tasks WHERE foreign_key=?" ); $query->bind_param ( "i", $_SESSION ['sess_user_id'] ); $query->execute (); $query->bind_result ( $title, $body ); $index = 0;

while ( $query->fetch () ) { echo '<a id="item-'. $index .'" class="list-group-item"><button id="li' . $index . '" type="button" class="close">×</button><h2 class="list-group-item-heading">' . $title . '</h2><p class="list-group-item-text">' . $body . '</p></a>'; $index++; } ?>

Notice the lack of PHP in the first example. It is very easy to move from one to the other: As far as PHP is concerned, there is no difference whatsoever. Thus, the only limit to what you can do with the project is your knowledge of PHP.

On that note, get hacking! You can get the open source version of the kiosk here.

Ready to start?

Get in touch. We're ready to listen.