37 Signals Fizzy Kanban board

I walk through the source code of the latest Rails application open-sourced by 37 Signals

37 Signals released Fizzy today, which is their take on a Kanban board. What surprised everybody was that they also released the source code for it. There’s a lot to look at, which I cover in this video.

Let’s look at a few things I call out in the video that I need to elaborate on.

Nested resources

I particularly enjoyed the approach to nesting resources with controller modules. Here’s an example from their routes.rb file. We’ll be focusing on the not_now resource.

namespace :columns do
  resources :cards do
    scope module: :cards do
      namespace :drops do
        resource :not_now   # Maps to Columns::Cards::Drops::NotNowsController
        resource :stream
        resource :closure
        resource :column
      end
    end
  end
end

Here’s the controller it maps to.

class Columns::Cards::Drops::NotNowsController < ApplicationController
  include CardScoped

  def create
    @card.postpone
  end
end

Simple! I immediately know a POST /columns/cards/:card_id/drops/not_now will do something.

Restomatic

In my projects, I take the same approach, but I’ve cleaned up the routes a bit with a gem called Restomatic. Here’s how the gem would clean up the Fizzy routes file.

namespace :columns do
  nest :cards do
    namespace :drops do
      resource :not_now   # Maps to Columns::Cards::Drops::NotNowsController
      resource :stream
      resource :closure
      resource :column
    end
  end
end

I get the same end result in a slightly more readable format.

I was pleased to see 37 Signals’ use of magic links for logins. Usernames and passwords are a nightmare for customer support because people often sign up with the wrong email address, swear it’s right, then email support insisting they did it all right.

class SessionsController < ApplicationController
  disallow_account_scope
  require_unauthenticated_access except: :destroy
  rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." }

  layout "public"

  def new
  end

  def create
    if identity = Identity.find_by_email_address(email_address)
      magic_link = identity.send_magic_link
      serve_development_magic_link(magic_link)
    end

    redirect_to session_magic_link_path
  end

  def destroy
    terminate_session
    redirect_to_logout_url
  end

  private
    def email_address
      params.expect(:email_address)
    end
end

This actually creates a bit of a security risk because a customer might complain a mistyped email is theirs when it could be somebody else’s.

Magic link authentication eliminates that because a user can only access the application if they enter the correct email address.

Here’s what the Fizzy sessions controller looks like:

class SessionsController < ApplicationController
  disallow_account_scope
  require_unauthenticated_access except: :destroy
  rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." }

  layout "public"

  def new
  end

  def create
    if identity = Identity.find_by_email_address(email_address)
      magic_link = identity.send_magic_link
      serve_development_magic_link(magic_link)
    end

    redirect_to session_magic_link_path
  end

  def destroy
    terminate_session
    redirect_to_logout_url
  end

  private
    def email_address
      params.expect(:email_address)
    end
end

It gets an email address, sends off the magic link, and if the user clicks it within 15 minutes, they’re logged in.

I did notice a few potential issues with their approach, which surprised me since I don’t fancy myself as a security professional.

One problem with magic link logins is they can be intercepted since they’re sent over email. One way to combat this is to bind the magic link to the browser that initiates the magic link request by setting a secret in a cookie or a token in the session.

Why? If an adversary intercepts the magic link and they try to open it in their browser, they’d get an error because the secret does not exist in their session.

One downside to this approach is if a user initiates the magic link from their main browser, then their email client opens in an in-app browser, they won’t be authenticated. The fix? Show a message to the user, “Copy the link $LINK and paste it into the browser you used to log in”.

Code generators should be random

The code 37 Signals uses to generate random characters for its magic links is pseudorandom.

module MagicLink::Code
  CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".chars.freeze
  CODE_SUBSTITUTIONS = { "O" => "0", "I" => "1", "L" => "1" }.freeze

  class << self
    def generate(length)
      length.times.map { CODE_ALPHABET.sample }.join
    end

    def sanitize(code)
      return nil if code.blank?

      normalize_code(code)
        .then { apply_substitutions(_1) }
        .then { remove_invalid_characters(_1) }
    end

    private
      def normalize_code(code)
        code.to_s.upcase
      end

      def apply_substitutions(code)
        CODE_SUBSTITUTIONS.reduce(code) { |result, (from, to)| result.gsub(from, to) }
      end

      def remove_invalid_characters(code)
        code.gsub(/[^#{CODE_ALPHABET.join}]/, "")
      end
  end
end

So the offending line of code? It’s CODE_ALPHABET.sample. A better approach is to use the SecureRandom library to generate these characters, the easiest approach being SecureRandom.hex.

If you want something other than a string of hex characters to generate codes, check out the Anybase gem, which uses SecureRandom to generate a map of bytes that it maps to an array of characters.

Overall a great reference Rails project

It shouldn’t surprise anybody that when the creator of Rails builds a SaaS, the resulting source code is going to be a great example of writing clear and concise Rails code.

Overall, I’m pleased to see more SaaS companies open-sourcing their software, using magic links, and nesting controller resources instead of using Rails’ shallow routes.

Do you want to learn Phlex 💪 and enjoy these code examples?

Support Beautiful Ruby by pre-ordering the Phlex on Rails video course.

Order the Phlex on Rails video course for $379 $379