Basic Authentication with JSONP

| No Comments |

JSONP background

JSONP is the name for a technique to get around the same-origin policy. It works pretty well when you’re trying to make a cross-domain request- for example, to some service’s api, without using a server proxy. In my case, I was trying to build a tool that would work from a flat file.

The problem

JSONP doesn’t work with basic authentication. We can verify this by skipping the jquery shortcuts and trying a very simple jsonp request by hand:

 function makeRequest(username,password){
    var url = "https://" + username + ":" + password + "@api.github.com/user?callback=jsonpCallback";
    var scriptToAdd = $('<script type="text/javascript" src=""></script>');
    scriptToAdd.attr('src',url);
    $('body').append(scriptToAdd);
}
function jsonpCallback(response){
    console.log( "response=",response );
}

The above code attempts to make an authenticated request by appending a script tag with an src of “user:pass@host.com/path.” Test it out by adding that (and jquery) to a page running makeRequest( 'kkuchta', 'hunter2' );, substituting your username and password, of course. Inspect the resulting object, and you’ll see that the authentication failed.

The solution

Script tags may not like authentication in the url, but image tags don’t mind it. Furthermore, basic authentication will get cached. As such, all we need to do it add an image element whose src is our url with basic auth, then wait for it to load:

function makeRequest(username,password){
    var imgUrl = "https://" + username + ":" + password + "@api.github.com/user";
    var scriptUrl = "https://api.github.com/user?callback=jsonpCallback";

    // Attach image to cache auth
    var img = $('<img />').attr( "src", imgUrl );
    $('body').append(img);
    img.remove();

    // Let the image load, then do the jsonp request
    setTimeout(function(){
        var scriptToAdd = $('<script type="text/javascript"></script>');
        scriptToAdd.attr('src',scriptUrl);
        $('body').append(scriptToAdd);
    },1000);
}
function jsonpCallback(response){
    console.log( "response=",response );
}

Again, try makeRequest( 'someGithubUser', 'someGithubPass' ). This time, the resulting object should the correct, authenticated response. Jsonp with basic auth, woo! You should only have to do this once per session- all jsonp requests thereafter should be go right past the authentication. This also works fine with at least jQuery’s jsonp- presumably any other jsonp wrapper as well.

Mac shell script to toggle socks proxy

| 2 Comments |

What

A simple script that’ll toggle your System Preferences proxy settings on a Mac. It takes one argument- the name of the interface on which to toggle the proxy (eg, ‘Airport’ or ‘Ethernet’). You can easily wrap this with an automator script using the “Get specified text” action to provide the argument and the ‘Shell Script’ action to run the script.

Uses growl to notify you of what it’s done. That can be removed if you want- just delete the end of the script below the relavent comment.

How

Nothing too complex here- uses the mac command line ‘networksetup’ tool, which allows command-line modification of network setting, to get and set the proxy state. Then it uses the mac command line ‘osascript’ tool, which runs apple script, to notify growl. The applescript in question just registers itself, then sends the message.

Code

# Get current status
interface=$1
status=`networksetup -getsocksfirewallproxy $interface | grep "^Enabled: [a-zA-z]*$"`
echo "status=$status"
if [ "`echo $status | grep "No"`" ]; then
    newState="on"
else
    newState="off"
fi

# Set new state
networksetup -setsocksfirewallproxystate Airport $newState

# Print message (delete after here to remove growl notification)
if [ "$status" ]; then
    message="$interface socks proxy is now $newState"
else
    message="Error toggling socks proxy on interface:'$interface'"
fi
osascript -e 'tell application "GrowlHelperApp"' -e 'set the allNotificationsList to {"Toggled"}' -e 'set the enabledNotificationsList to {"Toggled"}' -e 'register as application "Proxy Toggler" all notifications allNotificationsList default notifications enabledNotificationsList' -e "notify with name \"Toggled\" title \"Toggled\" description \"$message\" application name \"Proxy Toggler\"" -e 'end tell'

Matching PHP and JS Encryption

| No Comments |

Problem

You’d think that encryption is in encryption- AES is AES, DES is DES, etc. Encrypt with scheme X, decrypt with scheme X, and it comes out clean. Turns out, though, that it’s not that easy. Or rather, it is, but the relevant libraries on different platforms like to do things different. Consider that to encrypt/decrypt something with AES you need:

  • To use the same mode (CBC, EBC, etc)
  • The same block type for block cipher modes
  • The same key size (128 bit, 256 bit, etc)
  • The same key (of course)
  • The same initialization vector
  • To know whether the library pads/truncates your key
  • To know whether/how the library hashes your key
  • To know whether the library includes necessary extra information (plaintext size and IV) in its output or whether you need to keep track of that yourself
  • To know whether your ciphertext output it hex, base64 encoded, UTF8, or whatever.

In further fun, the libraries you’re looking at may only expose half of these variables and just pick their own numbers for the other half.

So, when you need to encrypt something client-side and decrypt it in, say, PHP, it’s not as simple as you’d like.

Quick Aside

Why would you want to encrypt client-side! That’s insane! Well, the short version is that I’m not hiding data from bad guys- I’m hiding it from my own system. What’s happening is that the user is entering credential information for an external system. That includes a username + password, plus some variable other stuff. The variable other stuff (VOS for now) is what needs to get encrypted. The username and password are already treated with kid gloves in our system- they’re not saved, they’re blocked out if a dev tries to log them, etc. The VOS, though, is new, and we’re passing it back as part of a complex data object that isn’t treated carefully- it could end up in analytics, logs, dump files, emails, etc. As such, we’re encrypting it client-side with the user’s password and decrypting it just before we need to send it to the external system (a point at which we also have the user’s password). As such, if it accidentally gets logged or something, it’s not stored in the clear.

Solution time

Not that complex, just putting it here because there didn’t seem to be a good ready-made recommendation anywhere I google.

I ended up using SlowAES, which is fast enough for many purposes. It comes with parallel implementations in PHP, JS, Python and Ruby. They each take the same parameters and use the same utilities, so it’s easy to get them to line up. To save you some time, here’s a simple use example for PHP and JS (not thoroughly tested because we decided to go for another solution before this made it to that point, but anyway):

encrypt.php

<?php
require( 'slowaes/php/aes_fast.php');
require( 'slowaes/php/cryptoHelpers.php');

/*
 * Encrypts and decrypts plaintext with a given key.  Written to be compatible
 * with it's counterpart in js.  Uses AES.  Uses the slowAES encryption lib,
 * which is more than fast enough for our purposes; using it here because it
 * has several parallel versions in different languages (mainly php and js).
 *
 * Usage: Encrypt takes any string as the plaintext and any string as the key.
 *      Decrypt takes the output of encrypt and the same key used to encrypt.
 *
 * Details you might care about:
 *      The encryption output is really 3 space-seperated strings: 
 *      - The length of the original plaintext string as an integer
 *      - The Initialization Vector (iv).  This is just a random string that
 *        will be different each encryption, and can be sent in the clear
 *        with the ciphertext.  This is a hex string.
 *      - The ciphertext itself, as a hex string.
 *
 * Crypto details you won't care about unless you're setting up another set of
 * methods to match these ones:
 *      - AES (Rijndael, or very close, I think)
 *      - 256 bit key
 *      - 128 bit IV
 *      - CBC mode
 *
 **/
function encrypt( $plaintext, $key ){

    // Set up encryption parameters.
    $plaintext_utf8 = utf8_encode($plaintext);
    $inputData = cryptoHelpers::convertStringToByteArray($plaintext);
    $keyAsNumbers = cryptoHelpers::toNumbers(bin2hex($key));
    $keyLength = count($keyAsNumbers);
    $iv = cryptoHelpers::generateSharedKey(16);

    $encrypted = AES::encrypt(
        $inputData,
        AES::modeOfOperation_CBC,
        $keyAsNumbers,
        $keyLength,
        $iv
    );

    // Set up output format (space delimeted "plaintextsize iv cipher")
    $retVal = $encrypted['originalsize'] . " "
        . cryptoHelpers::toHex($iv) . " "
        . cryptoHelpers::toHex($encrypted['cipher']);

    return $retVal;
}

function decrypt( $input, $key ){

    // Split the input into its parts
    $cipherSplit = explode( " ", $input);
    $originalSize = intval($cipherSplit[0]);
    $iv = cryptoHelpers::toNumbers($cipherSplit[1]);
    $cipherText = $cipherSplit[2];

    // Set up encryption parameters
    $cipherIn = cryptoHelpers::toNumbers($cipherText);
    $keyAsNumbers = cryptoHelpers::toNumbers(bin2hex($key));
    $keyLength = count($keyAsNumbers);

    $decrypted = AES::decrypt(
        $cipherIn,
        $originalSize,
        AES::modeOfOperation_CBC,
        $keyAsNumbers,
        $keyLength,
        $iv
    );

    // Byte-array to text.
    $hexDecrypted = cryptoHelpers::toHex($decrypted);
    $retVal = pack("H*" , $hexDecrypted);

    return $retVal;
}

/**
 * Some simple testing code
 **/

$plaintext = "Testing the php encryption/decryption. Unicode LOD: ಠ_ಠ!";
$key = "multipass!";
$cipherText = encrypt($plaintext,$key);
$result = decrypt($cipherText,$key);
?>
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"></head><body>
    <h1>Encrypting</h1>
    <b>plaintext:</b> <?= $plaintext ?> <br>
    <b>key:</b> <?= $key ?> <br>
    <b>raw cipherText:</b> <?= $cipherText ?> <br>
    <h1>Decrypting</h1>
    <b>result:</b><?= $result ?>
</body></html>

encrypt.js.html

/**
 * An encryption setup to match our server-side one; see there for
 * documentation on it.
 **/
function decrypt(input, key){

    // Split the input into its compontents
    var inputSplit = input.split(" ");
    var originalSize = parseInt(inputSplit[0]);
    var iv = cryptoHelpers.toNumbers(inputSplit[1]);
    var cipherIn = cryptoHelpers.toNumbers(inputSplit[2]);

    // Set up encryption parameters
    var keyAsNumbers = cryptoHelpers.toNumbers( bin2hex( key ) );

    var decrypted = slowAES.decrypt(
        cipherIn,
        slowAES.modeOfOperation.CBC,
        keyAsNumbers,
        iv
    );

    // Byte-array to text
    var retVal = hex2bin(cryptoHelpers.toHex(decrypted));
    retVal = cryptoHelpers.decode_utf8(retVal);

    return retVal;
}

function encrypt( plaintext, key ){

    // Set up encryption parameters
    plaintext = cryptoHelpers.encode_utf8(plaintext);
    var inputData = cryptoHelpers.convertStringToByteArray(plaintext);
    var keyAsNumber = cryptoHelpers.toNumbers(bin2hex(key));
    var iv = cryptoHelpers.generateSharedKey(16);

    var encrypted = slowAES.encrypt(
        inputData,
        slowAES.modeOfOperation.CBC,
        keyAsNumber,
        iv
    );

    // Set up output format (space delimeted "plaintextsize iv cipher")
    var retVal = plaintext.length + " "
        + cryptoHelpers.toHex(iv) + " "
        + cryptoHelpers.toHex(encrypted);

    return retVal;
}

// Equivilent to PHP bin2hex
function bin2hex (s) {
    var i, f = 0,
        a = [];

    s += '';
    f = s.length;

    for (i = 0; i < f; i++) {
        a[i] = s.charCodeAt(i).toString(16).replace(/^([\da-f])$/, "0$1");
    }

    return a.join('');
}

// Equivilent to PHP hex2bin
function hex2bin(hex) {
    var str = '';
    for (var i = 0; i < hex.length; i += 2)
        str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
    return str;
}

/**
 * Some simple testing code
 **/
$(function(){
    var key = "multipass!";
    var plaintext = "Testing the php encryption/decryption. Unicode LOD: ಠ_ಠ!";
    var output = "";
    var cipherText = encrypt(plaintext,key);
    var newPlaintext = decrypt(cipherText,key);
    output += ("<br>plaintext=" + plaintext);
    output += ("<br>cipherText=" + cipherText);
    output += ("<br>newPlaintext=" + newPlaintext);

    $('#output').html(output);
});

Remember that this is probably neither efficient nor elegant. It hasn’t been tested extensively, so use at your own risk. Should do the job, though. The full code is zipped up here: JSPHPEncryption 2.zip

I got a pile of data with coordinates last night. The coordinates were in State Plane NY West format, which is a semi-state specific coordinate system that needs to be translated before you can use it like latitude/longitude. There are better references out there if you want a thorough treatise on the subject, but I thought I’d give the concrete solutions I came up with in case you, like me, just want to get lat/lon and could hardly care less about 80’s era cartography.

  1. Install proj4, the 80’s-era c library that everything relating to geospatial coordinate transformations seems to be built on. This is related to and possibly the basis for, but not actually the same thing as gdal. Gdal has some handy command-line tools in case you don’t need to do this programmatically. Further, it takes similar projection strings as proj4.

    Anyway, build it from source. I had no trouble on Mac OS 10.6.

  2. Install the ruby bindings. I had to build from source here (ruby 1.9.2) too- their build instructions were pretty handy. Just go through the configure/make/make install stuff, then rake as a gem, then gem install that local gem.

  3. Code it up:

    require 'proj4'
    proj = Proj4::Projection.new '+proj=tmerc +lat_0=40 +lon_0=-78.58333333333333 +k=0.9999375 +x_0=350000 +y_0=0 +ellps=GRS80 +units=ft +no_defs'
    geox = 1394144.401
    geoy = 1165107.387
    latlon = proj.inverse Proj4::Point.new(geox,geoy)
    converter = (180 / Math::PI)
    puts "lat = " + (latlon.lat * converter).to_s
    puts "lon = " + (latlon.lon * converter).to_s
    

Note that geox and geoy are the inputs. The string of parameters in the call to Projection.new is what defines the initial projection (that is, the state plane) that the input is on. You’ll want to replace that with the one for your state. To get that info:

  1. Go to http://home.comcast.net/~rickking04/gis/spc.htm and look up your FIPS zone. Mine is 3103, since my data is for Monroe county, NY.
  2. Go to http://spatialreference.org/ref/esri/ and enter your state. There should be a short list of results, one of which has your FIPS zone in it. Mine is “ESRI:102317: NAD 1983 HARN StatePlane New York West FIPS 3103”. Click on it.
  3. Click on the “Proj4” link, which should take you to the string you want. Mine is “+proj=tmerc +lat0=40 +lon0=-78.58333333333333 +k=0.9999375 +x0=350000 +y0=0 +ellps=GRS80 +units=m +no_defs”. Note that you may need to change the ‘units’ element in that string- mine was in feet, which is ‘ft’.

Facebook iFrame Authorization

| No Comments |

So, it turns out that any facebook page, when loaded in an iframe, will explode. That is, it’ll display the facebook logo and nothing else. It’d say it’s an anti-clickjacking measure, if I had to guess.

The problem is that this applies to /any/ iframe, including facebook’s own. This presents a problem when trying to load facebook’s authorization page in a facebook iframe-based app. This was a problem as of Jan 1, 2011, but facebook changes things so fast that it may be resolved by now. If not, here’s how to fix it.

How it should work:

  1. User loads apps.facebook.com/myapp, which loads myserver.com/fb_content/start in an iframe, which, upon detecting that the user has not authorized the app, will redirect to-
  2. Facebook’s authorization page (still in the iframe). If the user clicks “Allow”, it’ll redirect to whatever url you passed in the redirect, such as-
  3. myserver.com/fbcontent/coolauthorized/stuff (still in the iframe)

Of course, as stated, step 2 blows up.

How to make it work

  1. User loades apps.facebook.com/myapp, which loads myserver.com/fb_content/start in an iframe, which, upon detecting that the user has not authorized the app, will javascript redirect (busting out of the frame) to:
  2. Facebook’s authorization page (not in an iframe). If the user clicks “Allow”, it’ll redirect to the url passed, which gets you back to-
  3. myserver.com/fb_content/start (not in an iframe), since facebook authorization won’t redirect to a domain that’s not yours (eg, can’t do apps.facebook.com), so you need to first redirect to a page you control, detect that you’re not in an iframe, and so redirect to-
  4. apps.facebook.com/myapp, which loads myserver.com/fb_content/start in an iframe, which, upon detecting that the user has authorized your app, will redirect to-
  5. myserver.com/fbcontent/coolauthorized/stuff (still in the iframe)

Luckily, this all looks like “How it should work” to the user if the redirects are fast.

Rails code to do it

In app/controllers/oauth_controller:

def start
    require 'uri'
    escaped_callback_url = URI.escape(FB_CONFIG['callback_url'], Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
    signed_request_data = decode_signed_request(FB_CONFIG['app_secret'],params[:signed_request])

    if(signed_request_data)
        if(signed_request_data["user_id"] != nil)
            # User has already or just accepted the app
            session['auth_token'] = signed_request_data['oauth_token']
            redirect_to "wherever you want to go after authentication"
        else
            # User hasn't accepted our app
            @redirect_url = "https://graph.facebook.com/oauth/authorize?client_id=#{FB_CONFIG['app_id']}&redirect_uri=#{escaped_callback_url}&display=page&scope=publish_stream,user_photo_video_tags&type=user_agent&display=page"
        end
    else        
        #We're not in a facebook iframe
        redirect_to FB_CONFIG['app_url']
    end
end

private
    require 'base64'
    def base64_url_decode(str)
      str += '=' * (4 - str.length.modulo(4))
      Base64.decode64(str.gsub("-", "+").gsub("_", "/"))
    end

    require 'hmac-sha2'
    # used to validate signed requests from facebook http://developers.facebook.com/docs/authentication/canvas
    def decode_signed_request(facebook_secret, signed_request)
        if(signed_request==nil)
            return nil
        end
        if(facebook_secret==nil)
            raise "Facebook secret not set."
        end
      encoded_sig, encoded_data = signed_request.split(".")
      decoded_data = base64_url_decode(encoded_data)
      decoded_sig = base64_url_decode(encoded_sig)
        logger.info "Decoded data = " + decoded_data.inspect
        logger.info "Json parsed decoded data = " + ActiveSupport::JSON.decode(decoded_data).inspect
        expected_sig = HMAC::SHA256.digest(facebook_secret, encoded_data)
      return decoded_sig==expected_sig ? ActiveSupport::JSON.decode(decoded_data) : nil
    end

In app/views/oauth:

<% if @redirect_url %>
    <script type='text/javascript'>top.location.href = '<%= raw @redirect_url %>';</script>
<% end %>

In config/initializers/load_config:

FB_CONFIG = YAML.load_file("#{RAILS_ROOT}/config/facebook.yml")[RAILS_ENV]

In config/facebook.yml:

production:
    app_id: '1234your_app_id'
    api_key: '1234your_app_key'
    app_secret: '1234your_app_secret'
    callback_url: "myserver.com/fb_content/start"
    app_url: http://apps.facebook.com/myapp

In Gemfile:

gem 'json'
gem 'ruby-hmac'

So, it turns out that the code run in chrome content scripts run in their own context. As such, I can’t access global variables set by in-page code. However, chrome content scripts do use the same DOM. Further, when <script> elements are inserted into the DOM dynamically, they’re run using the page’s context, and can access those global variables/functions.

In this example, insertToDOM just adds the code as the content of a script element to the element (this code uses JQeury, but you could easily do it without.

function insertToDOM(code){
    $("head").append( $('<script language="javascript">' + code + '</script>'));
}

Now, to get all user-defined functions, we need to filter out all the normal javascript functions. In this case, we’re analyzing the function bodies- built-in functions will have bodies containing “[native code]”, so we can just get rid of those. Everything that reaches the console.log(window[f].toString()); line will be a user-defined function. Note that the toString method called on a function object will return its body.

insertToDOM(
    'for (var f in window){'+
        'bodyString = window[f]!=null ? window[f].toString() : "";'+
        'if(bodyString.indexOf != null && bodyString.indexOf("[native code]")==-1 && bodyString.indexOf("function ")!=-1 ){'+
            'console.log(window[f].toString());'+
        '}'+
    '}'
);

Irrelevant background

This code is part of an ongoing christmas javascript war I’m having with the webmaster of a forum I frequent. He annually covers his site with christmas themed decorations and obnoxious mouse-following reindeer. I wrote a chrome plugin to revert it. He wrote a script to detect my code and redirect to internet shock sites if it’s found. We’ve been going back and forth recently- he’ll fix his code, I’ll fix mine. This is my latest blow- a variant on this detects if the function body contains redirect code and kills the function if it does. I could just fix my code following his next move and then just stop publishing my code, but where’ the fun in that? :D

Code for the above plugin, if anyone’s interested, can be found at https://sse.se.rit.edu/hg-manage/kmk3817

Edit: For a later version of this I threw together an insertToDom function that takes a function object, chops out the body, and inserts that. So, passing in “function(){alert(“test”);}” will add “alert(“test”)” to the DOM in a script element.

function insertToDOM(func){
    //everything between the first and last brackets.
    funcString = func.toString();
    funcStringArray1 = funcString.split("}");
    funcStringArray1.pop();
    funcString = funcStringArray1.join("}");
    funcStringArray2 = funcString.split("{");
    funcStringArray2.shift();
    funcBody = funcStringArray2.join("{");
    $("head").append( $('<script language="javascript">' + funcBody + '</script>'));
}

Engineer'd

| No Comments |

I’m sick of Facebook’s liking system. I can see their idea, but it’s become complete crap. 99% of “likes” that show up in my feed are either thoroughly inane our outright malicious (seriously, I never thought I’d see this much clickjacking in the wild).

So, here’s a prototype of a script to remove them from the front page:

Edit: Newer version, now with 50% less fail (lots of false positives).

var likeRegex = /.*likes <a.*/;
var timerFct = function(){
    $(".storyContent").filter(function(){
        console.log(2);
        return likeRegex.test( $(this).html() );
    }).hide();
    setTimeout(timerFct,10000);
}
setTimeout(timerFct,3000);

It’s meant as part of a chrome plugin- once I package it as such (assuming there’s no pitfalls there- haven’t tried it yet), I’ll post it here. For you firefox users, throw it into Greasemonkey and you should be good.

To be clear, this is not guaranteed to work. The regex will hit plenty of false positives, and I’ll refine this if I get around to it later.

Edit: Chrome extension. Works for me- let me know if you have trouble: fbdeliker.crx

Wrttn.in Quine

| No Comments |

Because I got bored and my javascript is rusty, I put together what I’m calling a Wrttn.in quine.

Edit: Does not appear to be working in Chrome (my main browser). I’d only tested it in Firefox so far because A) firebug rocks and B) I’m too lazy to care about cross-browser compatibility in my free time. I’ll take a look at the problem sometime soon when it’s not after midnight on a workday.

Wrttn.in

Wrttn.in is just a notepad-type site. It’s similar to a.longreply.com. You enter some text, hit save, and it gives you two urls: a public one that shows that text (eg, wrttn.in/323f1) and a private one that allows you to administer it (eg. wrttn.in/admin/46feb701xda). Now, as it happens, the text you enter will not be sanitized for html or javascript… :D

Quine

A quine) is a programmer toy. It’s a program that outputs a copy of it’s source code. What I’ve written is a hunk of javascript that, when placed in a wrttn.in page, will put a button on the page. Pressing that button will ajax up a new wrttn.in page, populate it with the same code as the initial page, and redirect to it. Not a traditional quine, of course, but it’s the same spirit. :)

Explanations

A few things to explain in the following code:

  • It’s possible to pass in content on a wrttn.in create call, but that content will be stripped of it’s javascript. That’s why we have a subsequent edit call- edit does not strip it.
  • This may not work long since we’re not using an api or anything stable- the calls were just reverse engineered from the pages. It’d be pretty easy for the wrttn.in creator to shut down this code he decides to. Just has to sanitize his edit inputs.
  • Content type has to be markdown. Textile encodes the quote marks.
  • This is a quick hack- nothing more. Don’t expect great code.

Code

<div id="vcms_whole">
    <div id="vcms_content"><input type="button" id="btnCreateNew" value="Create New!"/></div>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.js" charset="utf-8">
    </script>
    <script type="text/javascript" charset="utf-8">
        function createNewPage(){
            var wcms_wholeElement = document.getElementById("vcms_whole");
            var outerDiv = document.createElement("div");
            outerDiv.appendChild(wcms_wholeElement);
            wholeHTML = outerDiv.innerHTML;
            var publicId = "none";
            $.ajax({
                async: false,
                url: 'http://wrttn.in/create',
                dataType: 'html',
                type: 'POST',
                data: {content:wholeHTML,parser:'markdown'},
                success: onCreateSuccess,
                cache: false
            });
            var fullEditUrl = "http://wrttn.in"+editUrl;

            //Send edit request because 'create' filters out the javascript, but edit does not.
            $.ajax({
                url: fullEditUrl,
                dataType: 'html',
                async: false,
                type: 'POST',
                data: {content:wholeHTML},
                success: onEditSuccess,
                cache: false
            });
        }

        function onCreateSuccess(data, status, request){
            var dataEl = $(data).filter("title");
            var titleText = dataEl.text()
            adminId = titleText.replace("wrttn admin:","");
            var idLink = document.createElement("a");
            var something = $(data);
            var editEl = $("#edit",something);
            var formEl = editEl.children("form")[0];
            editUrl = formEl.attributes.getNamedItem("action").value;
            var editUrlSections = editUrl.split("/");
            publicId=editUrlSections[editUrlSections.length-2];
        }

        function onEditSuccess(data, status, request){
            window.location='http://wrttn.in/admin/'+adminId;
        }

        function setup(){
            $("#btnCreateNew").bind('click',createNewPage);
        }

        setup();
    </script>
</div>

I have a slightly more interesting application for this technique, but I’ll post about that when/if I actually get to it.

Cheap Tricks

| No Comments |

I heard a fun statistic today: the average software development turnover rate is 20% annually. The Bureau of Labor Statistics says 33% for all departures in IT, and 15% for quits. Let’s say it’s 20%- that’s a whole new company every 5 years (not really, of course, but it’s still a significant chunk of your company gone in that period). How can any company achieve consistent results when more than half their company is swapped out every 3 years? Predictable budgets and schedules become hella-difficult, not even to mention the cost of training new employees and the lost production while that’s happening.

Of course, we all know that holding on to quality developers means keeping them challenged, paying them highly, throwing them cool technology, etc. That said, we’re not all made of money, so here’s a few cheap tricks that buy a lot of developer love for not much money.

Free food

Give your employees a free dinner once every month or two. Somewhere nice and a little highly-priced, but not someplace they have to dress up. High-quality barbecue joints or steakhouses are decent choices. A lot of the common-folk consider a meal around $50 a head to be too pricy to treat themselves, so it’s pretty great when it’s gifted from management. Remember, you paid them 100 times that amount this month alone (assuming 60k a year). It’s a gift you don’t even have to do on company time, and gets a lot of bang for a tiny cost.

Dual Monitors

A number of sites and studies espouse the productivity virtues of multiple monitors- they easily make up for a $100 investment on that alone. Further, though, developers enjoy using them. Again, you’re paying them $60,000 a year- you can afford to give them a tool they love that’s been shown to increase productivity up to 44%, lasts 3+ years, and costs 0.16% of a developer’s annual pay.

Free Coffee

It may seem like a small thing, but one nicety I’ve heard programmers and IT workers complain about endlessly is the lack of free coffee. It’s a little weird, considering how cheap home coffee-makers are (got mine for $12 at a thrift store), and how cheap making one’s self a cup is. That said, developers seem to love having it on hand in the office, and I can hardly fault them- they get up early, come in, and sit without much movement for the next 4 hours. Anyway, you can get a high-end office-oriented single-cup maker for $500, or $17 a head in a 30-person office. Hell, even a cheap home-oriented brewer (<$50) kept well supplied will do the job (though the wasted time for high-paid developers to set up and brew pots may not be worth the savings).

Decent Chairs

A rower notices the rowboat. A pilot notices his cockpit. When I tour a potential job, I notice the chair I’m going to spend 40 hours a week in. Yes, good chairs are stupidly expensive ($750 for the famous Aeron chair), but quality chairs stand out. There’s talk about improved back health, productivity, etc, but the biggest advantage is their attractiveness to new and old developers. They signal that the people in management care about the day-to-day physical needs of developers (even though we all know you just want more work out of us). At a dollar a week (assuming a 10 year lifespan), that Aeron chair is a solid investment- cheaper than (as Joel Spolsky points out) your average toilet paper expenditure.

There, that wasn’t so bad, was it? I didn’t suggest that everyone get private offices, litter the place with beanbags, or have catered sushi every day. These are simple things, and you can do them without really high-level approval. They’ll reduce turnover, increase productivity, and attract brighter minds- in short, they’ll pay for themselves.

FulStack, empty coffee cup

| No Comments |

I recently undertook participation in RIT48 with my good friends Chris ‘Cap’ Tosswill (a fellow software engineer) and Meghan Manders (a designer). RIT48 is a competition to create a web business in 48 hours, with mixed emphasis on business plan and implementation. Our team got a working prototype of our product (FulStack.com) up and running, and we won second place (+ $400)! More details in the project page.